Skip to content
Merged
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
11 changes: 11 additions & 0 deletions .changeset/27354-skills-related-list-body-table.md
Original file line number Diff line number Diff line change
@@ -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.
30 changes: 16 additions & 14 deletions skills/objectstack-data/rules/relationships.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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'],
},
Expand All @@ -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` +
Expand Down
25 changes: 12 additions & 13 deletions skills/objectstack-ui/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading