diff --git a/.changeset/27354-skills-related-list-body-table.md b/.changeset/27354-skills-related-list-body-table.md new file mode 100644 index 00000000000..1a6e35fd6ef --- /dev/null +++ b/.changeset/27354-skills-related-list-body-table.md @@ -0,0 +1,11 @@ +--- +'@objectstack/skills': patch +--- + +docs(skills): objectstack-ui and objectstack-data teach a primary related list as a body table in the Details tab, and the relatedLists refusal with its package boundary + +Clause-②: no +Metadata-protocol: none + +- **Before.** Both skills said a `relatedList: 'primary'` child gets its own tab on the record page, `objectstack-ui` described one tab per primary list plus a shared Related tab and an app-level `relatedLayout` override, and `objectstack-data` said a `relatedLists` name matching no inbound FK is refused, with no boundary. +- **Now.** A primary list is a full-width body table in the details column after the first field section (inside the Details tab, no tab of its own); every other list is a side card in the shared Related tab; field changes join the Timeline tab under its Changes filter. A `relatedLists` child in the stack with no inbound FK is refused everywhere; one that exists nowhere is refused by `os validate` / `os build` and by a metadata write naming no package, while a write into a package stores it and the page places nothing. No ceiling moves; no schema, key or behaviour changes. diff --git a/skills/objectstack-data/rules/relationships.md b/skills/objectstack-data/rules/relationships.md index 302f1603668..aa0277878fa 100644 --- a/skills/objectstack-data/rules/relationships.md +++ b/skills/objectstack-data/rules/relationships.md @@ -229,8 +229,7 @@ Declare inline editing **on the relationship**, in the data model — not in a f view. Set `inlineEdit: true` on the child's `master_detail` (or `lookup`) field that points back to the parent. The parent's **standard** create/edit form then renders an editable grid for these children and saves parent + children in **one -atomic transaction** — with no form-view config and no bespoke page. The UI is -derived from metadata (relationship FK + child fields → grid columns). +atomic transaction** — with no form-view config and no bespoke page. ```typescript // On the CHILD object's FK field: @@ -271,10 +270,9 @@ inlineEdit: 'grid' // editable line-item grid (fast bulk entry; thin childre inlineEdit: 'form' // read-only list; "Add" / per-row edit opens the FULL form ``` -- **`'grid'`** — spreadsheet-like editable grid. Best for *thin* line items +- **`'grid'`** — best for *thin* line items (invoice/order lines): few columns, high volume, keyboard-fast. -- **`'form'`** — compact read-only list; Add / per-row edit opens the child's - complete form. Best for *fat* children (long text, attachments, many fields) +- **`'form'`** — best for *fat* children (long text, attachments, many fields) that don't fit a narrow grid cell. - **`true`** / omitted — **smart default**: picks `form` when the child has rich/form-only fields (textarea, file, image, json, location…) or more than ~8 @@ -320,11 +318,10 @@ InvoiceLine = { ### Detail-page related lists (the read-side mirror) -Where `inlineEdit` is the **write** side (child pulled into the parent's entry -form), the **related list** on the parent's record detail page is the **read** +Where `inlineEdit` is the **write** side, the **related list** on the parent's record detail page is the **read** side. You usually don't declare it: every child relationship -(`master_detail` and `lookup`) is shown as a related list on the parent's detail -page **by default** — owned (`master_detail`) children first. The relationship +(`master_detail` and `lookup`) is shown **by default** — owned (`master_detail`) +children first. The relationship flags exist to *refine* that: ```typescript @@ -333,7 +330,7 @@ project: { type: 'master_detail', reference: 'project', inlineEdit: true, // write side: edit inline in the Project form - relatedList: 'primary', // read side: a body table (default with inlineEdit) + relatedList: 'primary', // read side: a body table relatedListTitle: 'Tasks', // title of the detail-page list relatedListColumns: ['title', 'status', 'priority', 'due_date'], }, @@ -344,16 +341,21 @@ audit_ref: { type: 'master_detail', reference: 'invoice', relatedList: false }, `relatedList` is tri-state — placement (ADR-0085): -- `'primary'` — main content: a full-width table in the record's body (its own - tab on the tabbed page). Default for a `master_detail` FK with `inlineEdit`. +- `'primary'` — main content: a full-width table in the details column after + the first field section (in the Details tab, no tab of its own). Default for a + `master_detail` FK with `inlineEdit`. - `false` — hidden from the parent's page. - `true`, or omitted (except inline-edited `master_detail`) — a side card (the shared **Related** tab). - Order is the parent's: `relatedLists: ['task', 'event.related_to']` (child, or - `object.field`) lists those first; a name matching no inbound FK is refused. + `object.field`) lists those first. A child in the stack with no inbound FK is + refused everywhere; one that exists nowhere is refused by `os validate` / + `os build` (which leave a `sys_` / `cloud_` / `ai_` or sibling-artifact name + to the runtime) and by a metadata write naming no package — a write into a + package stores it, and the page places nothing. `relatedListTitle` / `relatedListColumns` override the derived title / columns; -both are optional — when omitted, columns auto-derive from the child object's +otherwise columns auto-derive from the child object's `highlightFields` (the same source the lookup picker uses). Audit FKs (`created_by`/`updated_by`/`owner_id`) never become related lists. A child that references the parent through several FKs (e.g. `primary_account` + diff --git a/skills/objectstack-ui/SKILL.md b/skills/objectstack-ui/SKILL.md index 152c439e21c..0aebe356f19 100644 --- a/skills/objectstack-ui/SKILL.md +++ b/skills/objectstack-ui/SKILL.md @@ -106,25 +106,24 @@ and the form's `taxRateField` overrides which parent field drives the totals. related list on the parent's record DETAIL page. You don't author it: every child relationship is shown as a related list by default (owned `master_detail` children first). Refine on the relationship: -- `relatedList: 'primary'` — main content: a body table, its **own tab** on the - tabbed page (see layout below); it places, the parent's `relatedLists` orders - (ADR-0085). +- `relatedList: 'primary'` — main content: a full-width body table (see layout + below); it places, the parent's `relatedLists` orders (ADR-0085). - `relatedList: false` — suppress a noisy child from the detail page. - `relatedListTitle` / `relatedListColumns` — override the derived title / columns (both optional; columns otherwise auto-derive from the child object's `highlightFields`). See objectstack-data → Relationships → Detail-page related lists. **Related-list layout.** On the synthesized record page, each -`relatedList: 'primary'` child (default: inline-edited `master_detail`) gets its -**own tab**; every other list stacks under one shared **Related** tab, both in -the object's `relatedLists` order. Promoting a child table to a tab is a -one-word change on the relationship. The object still declares no per-surface -*layout* hints: the old -`detail.relatedLayout` toggle and object-level `detail: {...}` block stay -**removed** (ADR-0085); `relatedLayout: 'tabs' | 'stack'` survives only as an -app-level default override, not an object key. For arrangements the -relationship layer can't express — filtered splits (e.g. Open vs Closed tabs), a -chart/report tab — assign the object a **custom record Page** and lay it out +`relatedList: 'primary'` child (default: inline-edited `master_detail`) is a +body table in the details column after the first field section (in the +Details tab, no tab of its own); every other list is a side card in one shared +**Related** tab, both in the object's `relatedLists` order. Field changes +(`trackHistory`) join the Timeline tab under its Changes filter; there is no +History tab. The object declares no per-surface *layout* hints: the +`detail.relatedLayout` toggle, object-level `detail: {...}` block and app-level +`relatedLayout` are **removed** (ADR-0085). For arrangements the relationship +layer can't express — filtered splits (e.g. Open vs Closed tabs), a chart/report +tab — assign the object a **custom record Page** and lay it out explicitly with `record:related_list` (or inline-editable `line_items`) blocks. ### Field Conditional Rules in Forms