feat(12.1-02): read-only preview screen with a status hint and record actions

- config_form.yaml preview block (optional headerPartial), reported in the form schema as preview
- fields with context: preview show only on the preview screen and are never written
- form messages preview and edit; recordActions without a preview block stops boot
- SPA route {id}/preview, PreviewView and PreviewField, record actions in the footer
- mapWinterUrl maps preview/:id; the update form returns to the preview
- summer-callout partial style classes for status hints
- README, docs, OpenAPI document, TS types and the embedded build updated
This commit is contained in:
Jakub Zych
2026-10-05 00:10:48 +02:00
parent 1c99de5013
commit a65c670574
50 changed files with 1441 additions and 66 deletions

View File

@@ -7,6 +7,7 @@ import { homePath } from '../state/useNavigation'
import LoginView from '../views/LoginView.vue'
import ListView from '../views/ListView.vue'
import FormView from '../views/FormView.vue'
import PreviewView from '../views/PreviewView.vue'
import SettingsFormView from '../views/SettingsFormView.vue'
import SettingsIndexView from '../views/SettingsIndexView.vue'
import NotFoundView from '../views/NotFoundView.vue'
@@ -34,7 +35,7 @@ export function safeRedirect(value: unknown): string | null {
return value
}
const CONTROLLER_ROUTES = new Set(['list', 'create', 'record'])
const CONTROLLER_ROUTES = new Set(['list', 'create', 'record', 'preview'])
/**
* The controller a route shows, or '' for every other screen (settings,
@@ -69,6 +70,13 @@ export function createAdminRouter(history: RouterHistory = createWebHistory(runt
{ path: '/:vendor/:plugin/:controller', name: 'list', component: ListView, meta: { shell: true } },
{ path: '/:vendor/:plugin/:controller/create', name: 'create', component: FormView, meta: { shell: true } },
{ path: '/:vendor/:plugin/:controller/:id(\\d+)', name: 'record', component: FormView, meta: { shell: true } },
// The read-only record screen of a form with a preview block (D-11).
{
path: '/:vendor/:plugin/:controller/:id(\\d+)/preview',
name: 'preview',
component: PreviewView,
meta: { shell: true },
},
{ path: '/:pathMatch(.*)*', name: 'not-found', component: NotFoundView, meta: { shell: true } },
],
})

View File

@@ -1,11 +1,12 @@
// Winter-shaped URLs (config_list recordUrl, config_form redirects) mapped
// onto the SPA's D-10 routes. The strings come from plugin YAML, so they are
// never used verbatim: only the current controller's list, create and record
// routes can come out (research Gap 8, T-10-20).
// never used verbatim: only the current controller's list, create, record
// and preview routes can come out (research Gap 8, T-10-20, T-12.1-16).
//
// <vendor>/<plugin>/<controller> -> list
// <vendor>/<plugin>/<controller>/create -> create
// <vendor>/<plugin>/<controller>/update/:id -> record (:id substituted)
// <vendor>/<plugin>/<controller>/preview/:id -> preview (:id substituted)
// anything else, or another controller -> list
import { controllerPath, parseControllerId } from './controllerRoutes'
@@ -35,5 +36,9 @@ export function mapWinterUrl(url: string | null | undefined, controllerId: strin
const target = rest[1] === ':id' ? String(id ?? '') : (rest[1] ?? '')
return DIGITS.test(target) ? `${base}/${target}` : base
}
if (rest.length === 2 && rest[0] === 'preview') {
const target = rest[1] === ':id' ? String(id ?? '') : (rest[1] ?? '')
return DIGITS.test(target) ? `${base}/${target}/preview` : base
}
return base
}