Skip to content

Commit 6ee4f04

Browse files
os-zhuangclaude
andauthored
feat(spec): complete FormView protocol with form-presentation props (conformance) (#2484)
The conformance check flagged 14 object-form props as frontend-only — the ObjectForm component accepts formType variants' config (drawerSide/drawerWidth/ modalSize/split*/tabPosition/layout/…) that FormViewSchema didn't declare. Add them (all optional, additive — view isn't in the property-liveness GOVERNED set; 6650 spec tests still green; api-surface unchanged). The react contract now sources them from the spec (with descriptions), and object-form conformance is 15 matched / 6 spec-only / 0 frontend-only (was 1 / 6 / 14). Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5a5bf61 commit 6ee4f04

5 files changed

Lines changed: 95 additions & 10 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
Complete the FormView protocol with the form-presentation options the ObjectForm
6+
component already accepts (conformance follow-up). FormViewSchema gains optional
7+
`layout`, `columns`, `title`, `description`, `defaultTab`, `tabPosition`,
8+
`allowSkip`, `showStepIndicator`, `splitDirection`, `splitSize`, `splitResizable`,
9+
`drawerSide`, `drawerWidth`, `modalSize` — the per-`type` (tabbed/wizard/split/
10+
drawer/modal) presentation config. The spec↔frontend conformance check went from
11+
14 frontend-only → 0 for object-form; the react-tier contract now sources these
12+
from the spec (with descriptions) instead of a hand-authored overlay.

packages/spec/src/ui/react-blocks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export const REACT_BLOCKS: ReactBlockDef[] = [
6969
schemaType: 'object-form',
7070
summary: "Server-connected create/edit/view form for one object. Config props come from the spec FormView schema; bind + wire it with the React props below.",
7171
schema: FormViewSchema,
72-
dataProps: ['sections', 'subforms', 'submitBehavior', 'defaultSort'],
72+
dataProps: ['layout', 'columns', 'tabPosition', 'drawerSide', 'drawerWidth', 'modalSize', 'splitDirection', 'sections', 'subforms', 'submitBehavior'],
7373
interactions: [
7474
OBJECT_NAME,
7575
{ name: 'mode', type: "'create' | 'edit' | 'view'", kind: 'controlled', description: 'Create a new record, or edit/view an existing one — drive from React state.' },

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,32 @@ export const FormViewSchema = lazySchema(() => z.object({
801801
'drawer', // Side panel
802802
'modal' // Dialog
803803
]).default('simple'),
804-
804+
805+
// --- Presentation options (per `type` variant). These mirror what the
806+
// ObjectForm component accepts so the protocol declares them; all optional. ---
807+
/** Field layout within the form body. */
808+
layout: z.enum(['vertical', 'horizontal', 'inline', 'grid']).optional().describe('Field layout direction'),
809+
/** Number of columns for the form body (grid/multi-column layouts). */
810+
columns: z.number().int().min(1).optional().describe('Number of columns for the form body'),
811+
/** Optional form title / description (for embedded or standalone forms). */
812+
title: z.string().optional().describe('Form title'),
813+
description: z.string().optional().describe('Form description'),
814+
/** Tabbed (`type: 'tabbed'`). */
815+
defaultTab: z.string().optional().describe('Initially active tab (tabbed forms)'),
816+
tabPosition: z.enum(['top', 'bottom', 'left', 'right']).optional().describe('Tab strip position (tabbed forms)'),
817+
/** Wizard (`type: 'wizard'`). */
818+
allowSkip: z.boolean().optional().describe('Allow skipping steps (wizard forms)'),
819+
showStepIndicator: z.boolean().optional().describe('Show the step indicator (wizard forms)'),
820+
/** Split (`type: 'split'`). */
821+
splitDirection: z.enum(['horizontal', 'vertical']).optional().describe('Split orientation (split forms)'),
822+
splitSize: z.number().optional().describe('Primary split panel size, % (split forms)'),
823+
splitResizable: z.boolean().optional().describe('Whether the split is resizable (split forms)'),
824+
/** Drawer (`type: 'drawer'`). */
825+
drawerSide: z.enum(['top', 'bottom', 'left', 'right']).optional().describe('Drawer side (drawer forms)'),
826+
drawerWidth: z.string().optional().describe('Drawer width, e.g. "480px" (drawer forms)'),
827+
/** Modal (`type: 'modal'`). */
828+
modalSize: z.enum(['sm', 'default', 'lg', 'xl', 'full']).optional().describe('Modal size (modal forms)'),
829+
805830
/** Data Source Configuration */
806831
data: ViewDataSchema.optional().describe('Data source configuration (defaults to "object" provider)'),
807832

skills/objectstack-ui/contracts/react-blocks.contract.json

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,55 @@
8080
"required": false,
8181
"description": "Custom persistence instead of the default create/update."
8282
},
83+
{
84+
"name": "layout",
85+
"type": "'vertical' | 'horizontal' | 'inline' | 'grid'",
86+
"kind": "data",
87+
"required": false,
88+
"description": "Field layout direction"
89+
},
90+
{
91+
"name": "columns",
92+
"type": "integer",
93+
"kind": "data",
94+
"required": false,
95+
"description": "Number of columns for the form body"
96+
},
97+
{
98+
"name": "tabPosition",
99+
"type": "'top' | 'bottom' | 'left' | 'right'",
100+
"kind": "data",
101+
"required": false,
102+
"description": "Tab strip position (tabbed forms)"
103+
},
104+
{
105+
"name": "drawerSide",
106+
"type": "'top' | 'bottom' | 'left' | 'right'",
107+
"kind": "data",
108+
"required": false,
109+
"description": "Drawer side (drawer forms)"
110+
},
111+
{
112+
"name": "drawerWidth",
113+
"type": "string",
114+
"kind": "data",
115+
"required": false,
116+
"description": "Drawer width, e.g. \"480px\" (drawer forms)"
117+
},
118+
{
119+
"name": "modalSize",
120+
"type": "'sm' | 'default' | 'lg' | 'xl' | 'full'",
121+
"kind": "data",
122+
"required": false,
123+
"description": "Modal size (modal forms)"
124+
},
125+
{
126+
"name": "splitDirection",
127+
"type": "'horizontal' | 'vertical'",
128+
"kind": "data",
129+
"required": false,
130+
"description": "Split orientation (split forms)"
131+
},
83132
{
84133
"name": "sections",
85134
"type": "object[]",
@@ -100,13 +149,6 @@
100149
"kind": "data",
101150
"required": false,
102151
"description": "Post-submit behavior"
103-
},
104-
{
105-
"name": "defaultSort",
106-
"type": "object[]",
107-
"kind": "data",
108-
"required": false,
109-
"description": "Default sort order for related list views within this form"
110152
}
111153
]
112154
},

skills/objectstack-ui/references/react-blocks.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,16 @@ Server-connected create/edit/view form for one object. Config props come from th
2727
| `onError` | `(error: Error) => void` | callback | | Called when the save fails. |
2828
| `onCancel` | `() => void` | callback | | Called when the user cancels. |
2929
| `submitHandler` | `(values) => any \| Promise<any>` | callback | | Custom persistence instead of the default create/update. |
30+
| `layout` | `'vertical' \| 'horizontal' \| 'inline' \| 'grid'` | data | | Field layout direction |
31+
| `columns` | `integer` | data | | Number of columns for the form body |
32+
| `tabPosition` | `'top' \| 'bottom' \| 'left' \| 'right'` | data | | Tab strip position (tabbed forms) |
33+
| `drawerSide` | `'top' \| 'bottom' \| 'left' \| 'right'` | data | | Drawer side (drawer forms) |
34+
| `drawerWidth` | `string` | data | | Drawer width, e.g. "480px" (drawer forms) |
35+
| `modalSize` | `'sm' \| 'default' \| 'lg' \| 'xl' \| 'full'` | data | | Modal size (modal forms) |
36+
| `splitDirection` | `'horizontal' \| 'vertical'` | data | | Split orientation (split forms) |
3037
| `sections` | `object[]` | data | | |
3138
| `subforms` | `object[]` | data | | Inline master-detail child collections |
3239
| `submitBehavior` | `object` | data | | Post-submit behavior |
33-
| `defaultSort` | `object[]` | data | | Default sort order for related list views within this form |
3440

3541
## `<ListView>``list-view`
3642

0 commit comments

Comments
 (0)