diff --git a/examples/app-showcase/src/pages/task-workbench.page.ts b/examples/app-showcase/src/pages/task-workbench.page.ts index 44abf28a99..177aade487 100644 --- a/examples/app-showcase/src/pages/task-workbench.page.ts +++ b/examples/app-showcase/src/pages/task-workbench.page.ts @@ -61,6 +61,10 @@ export const TaskWorkbenchPage: Page = { addRecordForm: false, }, + // Toolbar buttons ARE object actions (ADR-0047) — referenced by name from + // the showcase_task object's ActionSchema, not free text. + buttons: ['showcase_bulk_reassign', 'showcase_mark_done'], + showRecordCount: true, }, }; diff --git a/packages/spec/src/ui/page.form.ts b/packages/spec/src/ui/page.form.ts index 1af9477354..94820688ef 100644 --- a/packages/spec/src/ui/page.form.ts +++ b/packages/spec/src/ui/page.form.ts @@ -95,7 +95,7 @@ export const pageForm = defineForm({ { field: 'filterBy', type: 'repeater', helpText: 'Always-on base filter for the page' }, { field: 'levels', helpText: 'Hierarchy levels to display (tree-like sources)' }, // ── Appearance ── - { field: 'appearance', type: 'composite', helpText: 'Allowed visualizations (Grid / Kanban / Calendar / …) and description visibility' }, + { field: 'appearance', type: 'composite', disclosure: 'popover', helpText: 'Allowed visualizations (Grid / Kanban / Calendar / …) and description visibility' }, // ── User filters ── { field: 'userFilters', @@ -103,8 +103,10 @@ export const pageForm = defineForm({ helpText: 'End-user filter bar: None (no bar) / Tabs (named presets) / Dropdown (per-field). None removes the config.', }, // ── User actions ── - { field: 'userActions', type: 'composite', helpText: 'Toolbar toggles (search, sort, filter, row height)' }, - { field: 'addRecord', type: 'composite', helpText: 'Add-record entry point' }, + { field: 'userActions', type: 'composite', disclosure: 'popover', helpText: 'Toolbar toggles (search, sort, filter, row height)' }, + { field: 'addRecord', type: 'composite', disclosure: 'popover', helpText: 'Add-record entry point' }, + // Buttons ARE object actions — pick from the source object's actions. + { field: 'buttons', widget: 'action-multi', dependsOn: 'source', helpText: "Toolbar buttons — pick from this object's actions" }, { field: 'showRecordCount', helpText: 'Show the record count bar' }, { field: 'allowPrinting', helpText: 'Allow users to print this page' }, ], diff --git a/packages/spec/src/ui/page.zod.ts b/packages/spec/src/ui/page.zod.ts index c6a17ca3eb..20c237d1fb 100644 --- a/packages/spec/src/ui/page.zod.ts +++ b/packages/spec/src/ui/page.zod.ts @@ -257,6 +257,10 @@ export const InterfacePageConfigSchema = lazySchema(() => z.object({ /** Add record */ addRecord: AddRecordConfigSchema.optional().describe('Add record entry point configuration'), + /** Toolbar buttons — references to the source object's actions (ActionSchema). + * Buttons ARE object actions (not free text): correct-by-construction. */ + buttons: z.array(z.string()).optional().describe("Toolbar buttons — names of the source object's actions to surface in the page toolbar"), + /** Record count */ showRecordCount: z.boolean().optional().describe('Show record count at page bottom'), diff --git a/packages/spec/src/ui/view.zod.ts b/packages/spec/src/ui/view.zod.ts index 8daffac9b6..f769f86898 100644 --- a/packages/spec/src/ui/view.zod.ts +++ b/packages/spec/src/ui/view.zod.ts @@ -715,6 +715,7 @@ export const FormFieldSchema: z.ZodType = lazySchema(() => z.object({ dependsOn: z.string().optional().describe('Parent field name for cascading'), visibleOn: ExpressionInputSchema.optional().describe('Visibility predicate (CEL).'), + disclosure: z.enum(['inline', 'popover']).optional().describe('Composite rendering: inline bordered box (default) or a summary line + gear popover (progressive disclosure).'), })); /**