Skip to content

Commit 6febbdf

Browse files
committed
chore(spec): flip FormView buttons/defaults to live; close view inverse-drift (#1894)
The structured `FormViewSchema.buttons`/`defaults` shipped under the ADR-0078 escape hatch (declared, but marked `[EXPERIMENTAL — NOT ENFORCED]`) pending an ObjectUI consumer. The objectui `ObjectForm` renderer now folds both onto the flat props it reads (`showSubmit`/`submitText`/`showCancel`/`cancelText`/ `showReset`/`initialValues`), so: - Drop the experimental markers from the two `.describe()` strings + the `FormButtonConfigSchema` leaf doc (the spec liveness gate keys `experimental` off that exact substring). - Flip `view.form.buttons` / `view.form.defaults` in `liveness/view.json` `experimental → live`, citing the objectui consumer; update the `view` row in `liveness/README.md` (70 live / 0 exp / 5 dead). - Update the `view.test.ts` marker assertion to lock in the live state. - Annotate audit cluster #6 + the viewschema audit's inverse-drift line as resolved. Closes the `view` item of #1894 (inverse drift — renderers read undeclared props); the dashboard + app items were already reconciled. Co-lands with the objectui ObjectForm wiring. No shape/parse-behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMaDBhnZEUu1fcw8Rvo6Yq
1 parent d99aeb3 commit 6febbdf

7 files changed

Lines changed: 46 additions & 21 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
chore(spec): mark FormView `buttons`/`defaults` live now the ObjectUI renderer folds them (#1894)
6+
7+
The structured `FormViewSchema.buttons` (per-button `submit`/`cancel`/`reset`
8+
visibility + label) and `defaults` (create-mode initial values) shipped under
9+
the ADR-0078 escape hatch — declared, but carrying an `[EXPERIMENTAL — NOT
10+
ENFORCED]` marker because no consumer read them yet. The ObjectUI `ObjectForm`
11+
renderer now folds both onto the flat props it reads
12+
(`showSubmit`/`submitText`/`showCancel`/`cancelText`/`showReset`/
13+
`initialValues`), so the escape-hatch marker is dropped and the two spec
14+
liveness-ledger entries (`view.form.buttons`, `view.form.defaults`) flip
15+
`experimental → live`.
16+
17+
No shape or parse-behavior change — both keys were already accepted. This
18+
closes the `view` half of the inverse-drift cleanup (renderers reading
19+
undeclared props), umbrella #1878.

docs/audits/2026-06-viewschema-property-liveness.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Variant routing (`tabbed/wizard/split/drawer/modal`) exists in `plugin-form/Obje
2323
- `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.
2424
- `groups` vs `sections` — both alive but on different code paths, not aliased in one place.
2525
- 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.
26+
- **✅ 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.
2627

2728
## Recommendation (for ADR)
2829
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.

docs/audits/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ field enhanced-type configs (barcode/qr/slider/rating/color/location) + governan
5252
### 6. Inverse drift — renderer depends on UNDECLARED props
5353
dashboard `component`/`data`/`rowField`/`columnField`; view `ObjectView` form-adapter keys; app `accentColor`/`badgeVariant`/`separator`. These break a strict `Schema.parse()`.
5454

55+
**✅ 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).
56+
5557
### 7. Designer authoring gaps (live prop, no Studio editor)
5658
dataset `filter`/`format`/`derived`/`dateGranularity`; skill `triggerConditions` (the activation-critical field); flow `notify` (absent from static palette).
5759

packages/spec/liveness/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ EOF
201201
| skill | 10 |||| fully live |
202202
| dataset | 19 || 0 || `measures.certified` (declared-but-unenforced governance flag) REMOVED in 16.0 (#2377) |
203203
| page | 16 ||| 1 | fully live + one planned |
204-
| 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 |
204+
| 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 |
205205

206206
The `dead` set across types is the enforce-or-remove worklist (ADR-0049); every
207207
misleading entry carries `authorWarn` so authors hear about it at compile time.

packages/spec/liveness/view.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@
9999
},
100100
"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." },
101101
"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." },
102-
"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." },
103-
"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." },
102+
"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)." },
103+
"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." },
104104
"aria": {
105105
"status": "dead",
106106
"evidence": "no reader in either repo (objectui@fb35e48 grepped 2026-07-16: no schema.aria read in plugin-form/SchemaForm; only ListView applies aria)",

packages/spec/src/ui/view.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,7 +1933,7 @@ describe('FormViewSchema - defaultSort', () => {
19331933
});
19341934

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

19391939
describe('FormViewSchema - buttons & defaults', () => {
@@ -1987,10 +1987,13 @@ describe('FormViewSchema - buttons & defaults', () => {
19871987
expect(result.defaults).toBeUndefined();
19881988
});
19891989

1990-
it('marks both keys experimental until the renderer wiring lands (ADR-0078)', () => {
1990+
it('marks both keys live now the ObjectForm renderer folds them (framework#1894 / #2998)', () => {
19911991
const shape = (FormViewSchema as unknown as z.ZodObject<z.ZodRawShape>).shape;
19921992
for (const key of ['buttons', 'defaults'] as const) {
1993-
expect(shape[key].description, `${key} .describe()`).toMatch(/EXPERIMENTAL NOT ENFORCED/);
1993+
// The renderer wiring landed (objectui ObjectForm foldFormButtons), so the
1994+
// ADR-0078 escape-hatch marker must be gone — the spec liveness gate keys
1995+
// `experimental` off this exact substring.
1996+
expect(shape[key].description, `${key} .describe()`).not.toMatch(/EXPERIMENTAL NOT ENFORCED/);
19941997
}
19951998
});
19961999
});

packages/spec/src/ui/view.zod.ts

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -898,7 +898,7 @@ export const FormSectionSchema = lazySchema(() => z.object({
898898

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

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

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

10511051
/** ARIA accessibility attributes */
10521052
aria: AriaPropsSchema.optional().describe('ARIA accessibility attributes for the form view'),

0 commit comments

Comments
 (0)