From 0c366400c93ccd55102478be0bf87d0e3f497494 Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Fri, 3 Jul 2026 10:39:12 +0800 Subject: [PATCH] docs: retire legacy UI-hint keys (ADR-0085 semantic roles) Sweep hand-written docs and skills for teachings of the object UI-hint keys ADR-0085 removed/renamed, and rewrite them to the current semantic-role protocol (nameField / highlightFields / stageField / fieldGroups + Field.group, per-page customization via an assigned Page): - content/docs/guides/data-modeling.mdx: fieldGroups example + property table taught the removed ObjectFieldGroup.visibleOn and the deprecated defaultExpanded flag; now teach the collapse enum ('none'|'expanded'|'collapsed') and drop group-level visibleOn. - content/docs/guides/public-forms.mdx: section 10 taught the removed (never-implemented) ObjectSchema.defaultDetailForm binding and the retired views.formViews/views.form resolution chain; rewritten to explain record detail is derived from the semantic roles, with an assigned Page as the per-page customization path. Frontmatter description updated accordingly. - skills/objectstack-data/SKILL.md: fieldGroups example modernized (collapse instead of defaultExpanded; group visibleOn removed) and the group-property bullet now teaches the collapse enum. - skills/objectstack-ui/SKILL.md: detail.relatedLayout ('stack'|'tabs') teaching removed with the whole detail:{...} block; now teaches the default single Related tab + custom record Page (record:related_list) for first-class child-table navigation. - skills/objectstack-formula/SKILL.md: dropped the ObjectFieldGroup.visibleOn row from the CEL-surface inventory (the key was removed per ADR-0085 / ADR-0049 enforce-or-remove). ADRs, changelogs/changesets, audits, and auto-generated references (content/docs/references/**, skills/objectstack-ui/references/ react-blocks.md) are deliberately untouched. Verified: check:doc-authoring clean; docs:build green. Co-Authored-By: Claude Fable 5 --- content/docs/guides/data-modeling.mdx | 12 ++++--- content/docs/guides/public-forms.mdx | 45 +++++++++++++-------------- skills/objectstack-data/SKILL.md | 10 ++++-- skills/objectstack-formula/SKILL.md | 1 - skills/objectstack-ui/SKILL.md | 27 ++++++---------- 5 files changed, 46 insertions(+), 49 deletions(-) diff --git a/content/docs/guides/data-modeling.mdx b/content/docs/guides/data-modeling.mdx index 531432d985..1f3be71e17 100644 --- a/content/docs/guides/data-modeling.mdx +++ b/content/docs/guides/data-modeling.mdx @@ -549,8 +549,8 @@ export const Account = ObjectSchema.create({ fieldGroups: [ { key: 'contact_info', label: 'Contact Information', icon: 'user' }, - { key: 'billing', label: 'Billing', defaultExpanded: false }, - { key: 'system', label: 'System', visibleOn: '$user.isAdmin' }, + { key: 'billing', label: 'Billing', collapse: 'collapsed' }, + { key: 'system', label: 'System' }, ], fields: { @@ -573,8 +573,7 @@ export const Account = ObjectSchema.create({ | `label` | `string` | Human-readable group header | | `icon` | `string?` | Lucide/Material icon name for the group header | | `description` | `string?` | Optional description under the header | -| `defaultExpanded` | `boolean` (default `true`) | Whether the group is expanded initially | -| `visibleOn` | `string?` | Expression controlling group-level visibility | +| `collapse` | `'none' \| 'expanded' \| 'collapsed'` (default `'none'`) | Collapse behaviour on every surface: `'none'` = always open (no toggle), `'expanded'` = collapsible and starts open, `'collapsed'` = collapsible and starts closed (ADR-0085; replaces the deprecated `defaultExpanded` flag, which is still accepted as an alias) | ### Supported Migrations (MVP) @@ -582,7 +581,10 @@ export const Account = ObjectSchema.create({ ✅ Assign an existing field to a group — set `Field.group`. ⏳ Deferred (future iterations): explicit per-field in-group ordering, nested -groups, permission-scoped group visibility beyond `visibleOn`. +groups, group-level visibility predicates. Groups render identically on forms, +modals, and detail pages; when a single page needs a bespoke layout beyond +groups, assign it a custom Page schema instead of adding per-surface hints +here (ADR-0085). --- diff --git a/content/docs/guides/public-forms.mdx b/content/docs/guides/public-forms.mdx index 897a5bc327..99841477f9 100644 --- a/content/docs/guides/public-forms.mdx +++ b/content/docs/guides/public-forms.mdx @@ -1,6 +1,6 @@ --- title: Forms (Public + Internal) -description: Render any FormView either publicly (anonymous, /f/:slug) or internally (authed operators, /forms/:name). The same metadata drives both, with URL prefill, configurable post-submit behavior, defaultDetailForm record binding, and declarative open-form actions. +description: Render any FormView either publicly (anonymous, /f/:slug) or internally (authed operators, /forms/:name). The same metadata drives both, with URL prefill, configurable post-submit behavior, and declarative open-form actions. --- # Forms @@ -359,29 +359,26 @@ export default Action.create({ Compared to `{ type: 'url', target: '/console/forms/quick_create' }`, the `'form'` variant is portable across runtimes — Studio/console/native shells can route it through their own form renderer instead of a raw navigation. -## 10. `defaultDetailForm` — bind a FormView to record detail - -Set `defaultDetailForm` on an object schema to pin the FormView used by the record-detail / edit screen. This is the Airtable Interface-form binding pattern. - -```ts -// hotcrm/src/objects/lead.object.ts -import { ObjectSchema } from '@objectstack/spec/data'; - -export default ObjectSchema.create({ - name: 'lead', - label: 'Lead', - defaultDetailForm: 'quick_create', // → views.formViews.quick_create - fields: { /* … */ }, -}); -``` - -Resolution order at runtime: - -1. `views.formViews[defaultDetailForm]` if set -2. `views.form` (the unnamed default form view) -3. Auto-generated grid layout from object fields - -The same FormView can therefore drive **three** experiences — public collection, internal quick-create, and record-detail editing — without duplicating layout metadata. +## 10. Record detail is driven by semantic roles, not a form binding + +There is **no** object-level key that pins a FormView to the record-detail / +edit screen (the once-documented `defaultDetailForm` was never implemented and +has been removed from `ObjectSchema`). Record-detail rendering is derived from +the object's **cross-surface semantic roles** (ADR-0085): + +- `nameField` — which field is the record's display name. +- `highlightFields` — the most important fields (detail highlight strip, + default list columns, cards). +- `stageField` — the linear-lifecycle field behind the detail progress + stepper (`false` suppresses detection). +- `fieldGroups` + `Field.group` — semantic grouping, rendered identically on + forms, modals, and detail pages. + +When a record page needs a bespoke layout beyond what the roles derive, assign +the object a **custom Page schema** — that is the supported per-page +customization path. FormViews remain the metadata for the two form +experiences this guide covers: public collection (`/f/:slug`) and internal +entry (`/forms/:name`). ## 11. Security checklist (public mode) diff --git a/skills/objectstack-data/SKILL.md b/skills/objectstack-data/SKILL.md index 36bfc42d23..fe354ff8de 100644 --- a/skills/objectstack-data/SKILL.md +++ b/skills/objectstack-data/SKILL.md @@ -111,6 +111,12 @@ Organize fields into logical groups (e.g., "Contact Information", "Billing", `ObjectFieldGroup.key`. In-group display order equals the traversal order of `fields`. - Group keys must be `snake_case`; group labels are human-readable. +- Optional per-group: `icon`, `description`, and `collapse` + (`'none'` always open · `'expanded'` collapsible, starts open · + `'collapsed'` collapsible, starts closed — replaces the deprecated + `defaultExpanded` flag, ADR-0085). Groups render identically on forms, + modals, and detail pages; for a bespoke single-page layout assign a + custom Page instead. ```typescript import { ObjectSchema } from '@objectstack/spec'; @@ -121,8 +127,8 @@ export default ObjectSchema.create({ fieldGroups: [ { key: 'contact_info', label: 'Contact Information', icon: 'user' }, - { key: 'billing', label: 'Billing', defaultExpanded: false }, - { key: 'system', label: 'System', visibleOn: P`os.user.isAdmin == true` }, + { key: 'billing', label: 'Billing', collapse: 'collapsed' }, + { key: 'system', label: 'System' }, ], fields: { diff --git a/skills/objectstack-formula/SKILL.md b/skills/objectstack-formula/SKILL.md index 57d6092c9b..820904d839 100644 --- a/skills/objectstack-formula/SKILL.md +++ b/skills/objectstack-formula/SKILL.md @@ -304,7 +304,6 @@ to the envelope. | `Field` | `visibleOn` | cel | | `Field` | `defaultValue` (M9.9b) | cel | | `ConditionalValidation` | `when` | cel | -| `ObjectFieldGroup` | `visibleOn` | cel | | `View` | `visibleOn` | cel | | `View.criteria` | filter expression | cel | | `Action` | `disabled` | cel (or boolean) | diff --git a/skills/objectstack-ui/SKILL.md b/skills/objectstack-ui/SKILL.md index 7e4314afaa..532340da52 100644 --- a/skills/objectstack-ui/SKILL.md +++ b/skills/objectstack-ui/SKILL.md @@ -151,23 +151,16 @@ columns (see objectstack-data → Relationships → Detail-page related lists). Authored record pages can still place an explicit `record:related_list` (or inline-editable `record:line_items`) when they need bespoke placement. -**Related-list layout — `detail.relatedLayout` (`'stack'` | `'tabs'`).** By -default every related list stacks under a single **Related** tab on the record -detail page (`relatedLayout: 'stack'`). To promote each related table to its own -**peer tab** — sitting alongside *Details* / *Activity* instead of nested under -*Related* — set `relatedLayout: 'tabs'` in the object's `detail` block: - -```typescript -detail: { relatedLayout: 'tabs' } // Details · Tasks · Risks · Milestones … -``` - -Pure config — no custom record page needed; the synthesized detail page emits -one peer tab per related entry (label falls back to the object name, the -relationship's icon carries through), with Activity/History ordered after them. -Omit it (or `'stack'`) for the default single-Related-tab behavior — zero -regression. Use `tabs` when a record has several substantial child tables that -each deserve first-class navigation; keep `stack` when related lists are -secondary to the main record body. +**Related-list layout.** Every related list stacks under a single **Related** +tab on the synthesized record detail page — that is the only built-in layout. +The old `detail.relatedLayout` (`'stack'` | `'tabs'`) toggle — like the whole +object-level `detail: {...}` block — was **removed** (ADR-0085): an object +declares no per-surface layout hints. When a record's child tables deserve +first-class navigation (their own tabs, bespoke placement), assign the object a +**custom record Page** and lay it out explicitly with `record:related_list` +(or inline-editable `record:line_items`) blocks. For lighter tweaks stay at the +relationship layer: `relatedList: false` to suppress a noisy child, +`relatedListTitle` / `relatedListColumns` to override title / columns. ### Field Conditional Rules in Forms