Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/formview-buttons-defaults-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
"@objectstack/spec": patch
---

chore(spec): mark FormView `buttons`/`defaults` live now the ObjectUI renderer folds them (#1894)

The structured `FormViewSchema.buttons` (per-button `submit`/`cancel`/`reset`
visibility + label) and `defaults` (create-mode initial values) shipped under
the ADR-0078 escape hatch — declared, but carrying an `[EXPERIMENTAL — NOT
ENFORCED]` marker because no consumer read them yet. The ObjectUI `ObjectForm`
renderer now folds both onto the flat props it reads
(`showSubmit`/`submitText`/`showCancel`/`cancelText`/`showReset`/
`initialValues`), so the escape-hatch marker is dropped and the two spec
liveness-ledger entries (`view.form.buttons`, `view.form.defaults`) flip
`experimental → live`.

No shape or parse-behavior change — both keys were already accepted. This
closes the `view` half of the inverse-drift cleanup (renderers reading
undeclared props), umbrella #1878.
1 change: 1 addition & 0 deletions docs/audits/2026-06-viewschema-property-liveness.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Variant routing (`tabbed/wizard/split/drawer/modal`) exists in `plugin-form/Obje
- `bulkActions` works **only** because `ListView.tsx:1318` remaps it to `batchActions` (ObjectGrid's real key) — a direct `object-grid` caller using `bulkActions` silently no-ops.
- `groups` vs `sections` — both alive but on different code paths, not aliased in one place.
- Inverse problem: `ObjectView.tsx:809-823`'s form adapter reads keys (`layout,showSubmit,submitText,customFields,title,initialValues,className`) that **don't exist in `FormViewSchema`** — renderer-invented surface with no spec backing.
- **✅ Resolved (#1894 / #2998).** `layout`/`title` are declared on `FormViewSchema`; the button/visibility keys (`showSubmit`/`submitText`/`showCancel`/`cancelText`/`showReset`) and `initialValues` got structured spec homes (`buttons` / `defaults`) that the objectui `ObjectForm` now folds onto its flat props — so the two ledger entries are `live`, and the flat keys survive only as deprecated back-compat. `customFields`/`className`/`fields` stay classified as renderer-only ObjectUI extensions (objectui#2545), not spec surface.

## Recommendation (for ADR)
1. **Migrate the chart renderers to `dataset`/`dimensions`/`values`** (ADR-0021) — pairs with the dashboard/report seed migration; currently the entire chart view variant is dead against the spec.
Expand Down
2 changes: 2 additions & 0 deletions docs/audits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ field enhanced-type configs (barcode/qr/slider/rating/color/location) + governan
### 6. Inverse drift — renderer depends on UNDECLARED props
dashboard `component`/`data`/`rowField`/`columnField`; view `ObjectView` form-adapter keys; app `accentColor`/`badgeVariant`/`separator`. These break a strict `Schema.parse()`.

**✅ Resolved (#1894).** All three reconciled so strict validation holds: **dashboard** — `component`/`data` + the pre-ADR-0021 inline-analytics keys (`rowField`/`columnField`/…) are quarantined and rejected by the strict `DashboardWidgetSchema` error map (`dashboard.zod.ts`), pointing authors at the `dataset`/`dimensions`/`values` shape (renderer-only extras → `options`). **app** — `accentColor`/`badgeVariant`/`separator` are now declared on `AppSchema` (`app.zod.ts`). **view** — the structured `buttons`/`defaults` were added to `FormViewSchema` (#2998 Track A) and are now consumed: objectui `ObjectForm` folds them onto its flat renderer props (framework#1894 / #2998), so the two liveness-ledger entries flip `experimental → live`; the remaining flat form-adapter keys (`customFields`/`className`/`fields`) are classified renderer-only ObjectUI extensions (objectui#2545).

### 7. Designer authoring gaps (live prop, no Studio editor)
dataset `filter`/`format`/`derived`/`dateGranularity`; skill `triggerConditions` (the activation-critical field); flow `notify` (absent from static palette).

Expand Down
2 changes: 1 addition & 1 deletion packages/spec/liveness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ EOF
| skill | 10 | – | – | – | fully live |
| dataset | 19 | – | 0 | – | `measures.certified` (declared-but-unenforced governance flag) REMOVED in 16.0 (#2377) |
| page | 16 | – | – | 1 | fully live + one planned |
| view | 68 | 2 | 5 | – | list/form drilled via `children` (#2998 Track B); dead = list.{responsive,performance} + form.{data,defaultSort,aria}, all but aria authorWarn'd; exp = form.{buttons,defaults} awaiting objectui#2545; audit-era DEAD lines superseded by re-verification (submitBehavior, sharing.lockedBy, list ViewData providers, and the ADR-0021 chart shape — all live now); level-2 dead residue (userActions.buttons, addRecord.mode/formView, tabs[].order) noted on parents — one drill level only |
| view | 70 | 0 | 5 | – | list/form drilled via `children` (#2998 Track B); dead = list.{responsive,performance} + form.{data,defaultSort,aria}, all but aria authorWarn'd; form.{buttons,defaults} now live — objectui ObjectForm folds them onto its flat props (framework#1894 / #2998); audit-era DEAD lines superseded by re-verification (submitBehavior, sharing.lockedBy, list ViewData providers, and the ADR-0021 chart shape — all live now); level-2 dead residue (userActions.buttons, addRecord.mode/formView, tabs[].order) noted on parents — one drill level only |

The `dead` set across types is the enforce-or-remove worklist (ADR-0049); every
misleading entry carries `authorWarn` so authors hear about it at compile time.
Expand Down
4 changes: 2 additions & 2 deletions packages/spec/liveness/view.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@
},
"sharing": { "status": "live", "evidence": "packages/rest/src/rest-server.ts:4384", "note": "Framework-side consumer: public (anonymous) form endpoints opt in via FormView.sharing — /forms/:slug resolves sharing.publicLink across form + formViews (rest-server.ts:1716, :4366-4405) and grants publicFormGrant. Renderer-side the config is not read (audit L20) — the public form is served, not re-parsed client-side." },
"submitBehavior": { "status": "live", "note": "objectui: WizardForm.tsx:315-316 + ObjectForm.tsx dispatch thank-you/redirect/continue/next-record; covered by ObjectForm.submitBehavior.test.tsx / WizardForm.successBehavior.test.tsx. Supersedes the 2026-06 audit's DEAD line (L20) — wiring landed with the objectui#2545/#2552 reconciliation." },
"buttons": { "status": "experimental", "note": "#2998 Track A — structured action-button config awaiting the ObjectForm read (objectui#2545); the renderer still reads the flat invented showSubmit/submitText keys (ObjectForm.tsx:147-157). Spec .describe() carries the [EXPERIMENTAL — NOT ENFORCED] marker; drop marker + flip to live when the wiring lands." },
"defaults": { "status": "experimental", "note": "#2998 Track A — create-mode initial values awaiting the ObjectForm read (objectui#2545); the renderer still reads the flat invented initialValues key (ObjectForm.tsx:364-437). Spec marker as above." },
"buttons": { "status": "live", "note": "objectui: ObjectForm.tsx foldFormButtons() reads schema.buttons and folds submit/cancel/reset {show,label} onto the flat showSubmit/submitText/showCancel/cancelText/showReset props (framework#1894 / #2998); ObjectView + RecordFormPage forward it. Flat keys kept as deprecated back-compat (explicit flat key wins)." },
"defaults": { "status": "live", "note": "objectui: ObjectForm.tsx foldFormButtons() reads schema.defaults and folds it into create-mode initialValues (framework#1894 / #2998); an explicit initialValues (URL prefill) still wins." },
"aria": {
"status": "dead",
"evidence": "no reader in either repo (objectui@fb35e48 grepped 2026-07-16: no schema.aria read in plugin-form/SchemaForm; only ListView applies aria)",
Expand Down
9 changes: 6 additions & 3 deletions packages/spec/src/ui/view.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1933,7 +1933,7 @@ describe('FormViewSchema - defaultSort', () => {
});

// ============================================================================
// FormView structured buttons + defaults (#2998, EXPERIMENTAL until objectui#2545)
// FormView structured buttons + defaults (#2998; live now the objectui ObjectForm folds them, framework#1894)
// ============================================================================

describe('FormViewSchema - buttons & defaults', () => {
Expand Down Expand Up @@ -1987,10 +1987,13 @@ describe('FormViewSchema - buttons & defaults', () => {
expect(result.defaults).toBeUndefined();
});

it('marks both keys experimental until the renderer wiring lands (ADR-0078)', () => {
it('marks both keys live now the ObjectForm renderer folds them (framework#1894 / #2998)', () => {
const shape = (FormViewSchema as unknown as z.ZodObject<z.ZodRawShape>).shape;
for (const key of ['buttons', 'defaults'] as const) {
expect(shape[key].description, `${key} .describe()`).toMatch(/EXPERIMENTAL — NOT ENFORCED/);
// The renderer wiring landed (objectui ObjectForm foldFormButtons), so the
// ADR-0078 escape-hatch marker must be gone — the spec liveness gate keys
// `experimental` off this exact substring.
expect(shape[key].description, `${key} .describe()`).not.toMatch(/EXPERIMENTAL — NOT ENFORCED/);
}
});
});
Expand Down
30 changes: 15 additions & 15 deletions packages/spec/src/ui/view.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,7 @@ export const FormSectionSchema = lazySchema(() => z.object({

/**
* A single form action button (submit / cancel / reset): visibility + label.
* Leaf of the [EXPERIMENTAL] {@link FormViewSchema} `buttons` block (#2998).
* Leaf of the {@link FormViewSchema} `buttons` block (#2998).
* `.strict()` per ADR-0089 D3a so a typo'd key errors instead of vanishing.
*/
export const FormButtonConfigSchema = lazySchema(() => z.object({
Expand Down Expand Up @@ -1023,30 +1023,30 @@ export const FormViewSchema = lazySchema(() => z.object({
]).optional().describe('Post-submit behavior'),

/**
* ⚠️ EXPERIMENTAL — NOT ENFORCED (#2998, ADR-0078). Structured action-button
* config: per-button visibility + label for `submit` / `cancel` / `reset`.
* This is the spec home for the flat renderer-invented keys ObjectUI's
* `ObjectForm` reads today (`showSubmit`/`submitText`/`showCancel`/`cancelText`/
* `showReset`, objectui#2545) — those never existed here and are silently
* stripped by this strip-mode container. Experimental until the ObjectUI
* renderer reads `buttons.*` (the ADR-0078 escape hatch: the contract ships
* ahead of its consumer, marked so authoring it is not a false promise).
* Structured action-button config: per-button visibility + label for
* `submit` / `cancel` / `reset`. The spec home for the flat renderer-invented
* keys ObjectUI's `ObjectForm` used to read (`showSubmit`/`submitText`/
* `showCancel`/`cancelText`/`showReset`) — now consumed: `ObjectForm` folds
* `buttons.*` down onto those flat props at render (framework#1894, #2998),
* with the flat keys kept only as deprecated back-compat. Authoring `buttons`
* takes effect; a stale flat key still wins where explicitly set.
*/
buttons: z.object({
submit: FormButtonConfigSchema.optional().describe('Submit button'),
cancel: FormButtonConfigSchema.optional().describe('Cancel button'),
reset: FormButtonConfigSchema.optional().describe('Reset button'),
}).strict().optional()
.describe('[EXPERIMENTAL — NOT ENFORCED, #2998] Form action-button visibility & labels. Renderer wiring pending in ObjectUI (objectui#2545).'),
.describe('Form action-button visibility & labels; folded onto the flat renderer props by ObjectUI ObjectForm (framework#1894 / #2998).'),

/**
* ⚠️ EXPERIMENTAL — NOT ENFORCED (#2998, ADR-0078). Initial field values for
* create-mode forms, keyed by field machine name. Spec home for ObjectUI's
* renderer-invented `initialValues` (objectui#2545). Experimental until the
* ObjectUI renderer reads it.
* Initial field values for create-mode forms, keyed by field machine name —
* the spec home for ObjectUI's renderer-invented `initialValues`. Consumed:
* `ObjectForm` folds `defaults` into its create-mode initial values at render
* (framework#1894, #2998); an explicit `initialValues` (e.g. URL prefill)
* still wins.
*/
defaults: z.record(z.string(), z.unknown()).optional()
.describe('[EXPERIMENTAL — NOT ENFORCED, #2998] Initial field values for create-mode forms (spec home for ObjectUI `initialValues`). Renderer wiring pending (objectui#2545).'),
.describe('Initial field values for create-mode forms (folded into ObjectUI ObjectForm initial values; framework#1894 / #2998).'),

/** ARIA accessibility attributes */
aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes for the form view'),
Expand Down
Loading