Skip to content

Commit fde7456

Browse files
feat(spec)!: addRecord says what renders — enabled required, position top, mode/formView retired (#24970)
Closes #24692 Clause-②: yes (narrowing: `mode` / `formView` retire through ADR-0087 drop conversions; `enabled` becomes required) A list's `addRecord` block now says what the renderer draws. `enabled` is required with no default, `position` defaults to `top`, and `mode` / `formView` are retired through the `spec-property-retirement` kit. It ships in 18.x under the standing ruling for breaking narrowings after GA (#6015, comment 6292005854, 「甲」), because ADR-0087 conversions cover every body that parsed under 18.0. ## The ruling this implements (comment 6281834942, letter B′, items 1–5 verbatim) > 1. `AddRecordConfigSchema` (`packages/spec/src/ui/view.zod.ts`, the same schema on the page `list-view` node) keeps two keys: `enabled`, REQUIRED with no default, so `addRecord: {}` is refused at authoring and at publish instead of drawing nothing; and `position`, default `top`, values `top` / `bottom` / `both` as the renderer draws them. > > 2. `mode` and `formView` retire through the `spec-property-retirement` kit: ADR-0087 conversion entries that drop the two keys from authored and stored bodies with a notice, `retiredKey()` tombstones on the strict object, the two form composites (`view.form.ts:178`, `page.form.ts:165`), regenerated artifacts, the liveness ledger. `Clause-②: yes (narrowing)`, breaking, with its ADR-0087 disposition and the FROM → TO in the changeset. > > 3. The producers follow in the same PR: showcase `active-projects.page.ts:36`, `contact.view.ts:60` and the lint fixture `showcase-shape.fixtures.ts:414` lose `mode` / `formView`; `content/docs/ui/create-vs-edit-form.mdx:88` states the convention of item 4 and names no binding — the docs line is part of the retirement kit's surface and is declared in the claim as a cross-domain file. > > 4. objectui: the create entry selects `formViews.create` when the object declares it, else today's `form` / `formViews.default` (`app-shell/src/utils/recordFormNavigation.ts:218`, one selection line, pinned by a test). objectstack-ai/objectui#13099 is that card; its parity row (`p1-spec-alignment.test.ts:189`, the `mode` default) changes when objectui's pin takes the spec release. > > 5. ADR-0050 (Proposed) Decision 2 gains a dated note that the list add-record open mode is no longer declared on `addRecord`; how the create form opens stays the renderer's, and a per-list open mode, if ever wanted, is the list's existing `navigation.mode`, never a second enum. Tier H; it may ride the spec PR (the PR is then Tier H whole) or go as its own one-line PR — the spec seat chooses. ## What changed **Schema (`packages/spec/src/ui/view.zod.ts`, `AddRecordConfigSchema`).** One def serves every door: `ListViewSchema`, `ObjectListViewSchema` (an object's or a container's `listViews`), `InterfacePageConfigSchema` (a list page's `interfaceConfig`) and the page `list-view` node's props row. - `enabled` is `z.boolean()` with no default. If the key is omitted, the parse refuses with a prescription that names the key and says how to fix it. - `position` defaults to `top`. The values `top` / `bottom` / `both` are unchanged. I checked the renderer at objectui origin/main and at the pinned sha `616df6598b`: `ListView.tsx#resolveAddRecordPlacement` maps an absent position to top, and `showAddRecord` needs `enabled === true`. - `mode` and `formView` are `retiredKey()` tombstones. Authoring either key is a `tsc` error and also a parse error that carries the prescription. **ADR-0087.** There are two D2 conversions, both retired from the load path with `retiredAfter: '18.0.0'`. - They reach `stack.views[]` in all three persisted spellings, `objects[].listViews.*`, `pages[].interfaceConfig` and every page `list-view` node. - `add-record-mode-form-view-removed` strips both keys and emits one notice per key. - `add-record-enabled-defaulted` writes `enabled: true` onto a block that omits `enabled`. An explicit value is never touched. - `RETIRED_KEYS_BY_MAJOR[18]` gains `ui/AddRecordConfig:mode` and `ui/AddRecordConfig:formView`. - The D3 entries are `add-record-mode-form-view-retired` and `add-record-enabled-required`. Each has its own step-18 rationale fragment. - The two default moves are declared in `DEFAULT_CHANGES_BY_MAJOR[18]`. **Ruling item 3 under 「甲」: the `enabled` half ships with a conversion, not as a warning.** I measured whether any producer writes `addRecord` without `enabled`: - this repository at `71696121a6`: the showcase's two blocks and the lint fixture all write `enabled: true`; - objectui's Studio design surface at the pin: it writes `addRecord: { enabled: true }`; - production databases: not measured. The measured population is zero, so the conversion is sound. It is lossless against the contract, because it writes the value the 18.0 parse already produced. It is not lossless in pixels, and the changeset says so: a stored block that omitted `enabled` drew no button, and once converted it draws the one it declared. No 19.0 refusal card is needed. **Forms.** In `view.form.ts` and `page.form.ts`, the `addRecord` composites now list `enabled` and `position` explicitly. The new nested labels are translated in the three locales. **Liveness ledger.** The `liveness/view.json` `addRecord` row records the removal. The sub-keys are one drill level below a recorded undrilled container, so no row is added or removed. The README residue line is updated. **Producers and docs.** - showcase `contact.view.ts` and `active-projects.page.ts`, and the lint fixture `showcase-shape.fixtures.ts`, drop `mode` / `formView`. - `content/docs/ui/create-vs-edit-form.mdx` states the convention (`formViews.create` when declared, else `form` / `formViews.default`) and names no binding. - `content/docs/ui/views.mdx` drops `addRecord.formView` from where a named form is served and states the `create` convention. - `@objectstack/lint`: `os explain list-view-field-unknown` and one docblock no longer name `addRecord.formView`. `lintViewRefs` never read it. - The reference pages under `content/docs/references/` are regenerated. **Tests.** - New `packages/spec/src/ui/add-record-config-retirement.test.ts`, in the repo project. It covers every door, the prescriptions, the `tsc` channel, the conversions over stored view, page and object rows, idempotence, load-path retirement, the ledger rows, the forms, and a tree-scoped absence pin over the five declared roots. - `retired-key-removal-version.pin.test.ts` joins both new conversions. - The old `view.test.ts` / `page.test.ts` pins on `mode` and the `bottom` default are rewritten. ## Not in this PR - **ADR-0050 Decision 2 note:** a Tier H file, so it goes in its own one-line PR (ruling item 5). The exact text is in the dev report. - **objectui** (ruling item 4) is objectstack-ai/objectui#13099, which is already closed. Its `p1-spec-alignment.test.ts:189` parity row (`mode` default `inline`) will go red when objectui's pin takes this spec release, and ruling item 4 says that row changes then. No objectui source writes `mode` or `formView` at the pin (`git grep` at `616df6598b`). - `content/docs/releases/` is untouched. ## Acceptance notes - The showcase's `active-projects.page.ts` sets `userActions.addRecordForm: false` next to `addRecord: { enabled: true }`. objectui's `ListView` withholds the Add button on `addRecordForm === false`, so that page probably shows no Add button, even though its comment says it shows one. I only read this from the code and did not reproduce it in a browser, so it is not filed. Carrier: none. - The 18.0 parse materialized `position: 'bottom'` into built artifacts that carried an `addRecord` block. Those artifacts keep `bottom` explicitly, so nothing moves on screen for them. ## Verification (final head `a05f141642`, after merging origin/main) - `pnpm --filter @objectstack/spec build` and `check:generated`: `✓ All 14 generated artifacts are up to date`. - `pnpm --filter @objectstack/spec test`: `Test Files 703 passed (703)`, `Tests 21369 passed | 1 todo`. - `pnpm --filter @objectstack/spec test:repo`: `72 passed (72)`, `1236 passed`. - `pnpm --filter @objectstack/spec typecheck`: exit 0. - `pnpm --filter @objectstack/lint typecheck`: exit 0. - `pnpm --filter @objectstack/lint test`: `151 passed (151)`, `7013 passed`. - These six ran as one `&&` chain under the verify lock, which reported `VERDICT command-exit 0`. - showcase: `objectstack validate` printed `✓ Validation passed`, and `tsc --noEmit` exited 0. - Gates: - `check-adr-0087-registration`: registered, 4 ids new here. - `check:i18n`: in sync. - `check:i18n-coverage`: none new. - `check:cross-package-test-inputs`, `check:nul-bytes` and `check:doc-authoring`: green. - All 120 families `dispatch-gates` derives were run on the final head `a05f141642`, and every one exited 0. `--ran` reconciles: `120 run, 0 NOT-MEASURED`. - Ablation of the absence pin: putting `formView: 'create'` back into `contact.view.ts` (`ablation-replace`: mutation landed, blob changed) turned the pin red, `1 failed | 34 passed`, naming `examples/app-showcase/src/ui/views/contact.view.ts`. The restore was verified: the blob equals HEAD and `git diff HEAD` is empty. - `pnpm lint` (repo-wide eslint) is NOT MEASURED locally and left to CI. --- _Generated by [Claude Code](https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7532ea3 commit fde7456

36 files changed

Lines changed: 1049 additions & 92 deletions
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/lint': patch
4+
---
5+
6+
feat(spec)!: a list's `addRecord` block says what renders — `enabled` is required, `position` defaults to `top`, and `mode` / `formView` are retired
7+
8+
Clause-②: yes (narrowing)
9+
10+
<!-- adr-0087: registered add-record-mode-form-view-removed, add-record-enabled-defaulted, add-record-mode-form-view-retired, add-record-enabled-required -->
11+
12+
**BREAKING** (an authoring narrowing), shipped as `minor` under the launch-window convention for breaking changes, and in 18.x under the standing rule for breaking narrowings: an ADR-0087 conversion carries every body that parsed under 18.0, so no stored row or built artifact is refused.
13+
14+
`AddRecordConfigSchema` — the `addRecord` block on a list view, an object's or a view container's `listViews`, a list page's `interfaceConfig` and a page `list-view` node — declared defaults the renderer never applied (`enabled` `true`, `position` `bottom`, `mode` `inline`) and two keys no renderer read. The list's Add button is drawn only for an explicit `enabled: true`, an absent `position` renders at the top, and every Add opens the object's create form. The block now says exactly that:
15+
16+
- **`enabled` is REQUIRED, with no default.** `addRecord: {}` is refused at parse (`os validate`, `os build`, the save doors) with a prescription naming the key, instead of drawing nothing.
17+
- **`position` defaults to `top`**, where the renderer has always placed an absent position. Values stay `top` / `bottom` / `both`. No screen moves.
18+
- **`mode` and `formView` are `retiredKey()` tombstones** (ADR-0049 enforce-or-remove): authoring either is a `tsc` error and a parse error carrying the prescription. The create form is chosen by convention instead: the create entry opens `formViews.create` when the object's view container declares it, else `form` (or `formViews.default`). How that form opens stays the renderer's.
19+
20+
### FROM → TO
21+
22+
| before | after |
23+
| --- | --- |
24+
| `addRecord: {}` (parsed as `enabled: true`, drew nothing) | refused: write `addRecord: { enabled: true }` |
25+
| `addRecord: { position: 'bottom' }` | refused: write `addRecord: { enabled: true, position: 'bottom' }` |
26+
| `addRecord: { enabled: true }` (parsed `position: 'bottom'`) | parses as `position: 'top'`, where it always rendered |
27+
| `addRecord: { enabled: true, mode: 'form' }` (any `mode`) | `addRecord: { enabled: true }` |
28+
| `addRecord: { enabled: true, formView: 'create' }` | `addRecord: { enabled: true }`, with the form declared as `formViews.create` on the object's view container |
29+
30+
**The one-line fix:** delete `mode` and `formView`, and write `enabled: true` (or `enabled: false`) on every `addRecord` block. `os migrate meta --from 17` lists the edits for existing metadata sources; `--write` applies the ones it can prove, and you apply the rest by hand.
31+
32+
### The ADR-0087 disposition
33+
34+
- D2 `add-record-mode-form-view-removed` strips `mode` and `formView` from every `addRecord` block it reaches (`stack.views[]` in all three persisted spellings, `objects[].listViews.*`, `pages[].interfaceConfig` and every page `list-view` node), with a notice per key. Lossless in pixels: no renderer read either key.
35+
- D2 `add-record-enabled-defaulted` writes `enabled: true`, the default the 18.0 spec declared, onto a block that omits `enabled`, with a notice. Measured before choosing it: no producer in this repository (the showcase's two blocks, the lint fixture) or in objectui's Studio design surface writes `addRecord` without `enabled`, so the conversion carries the narrowing with a zero measured population. **One visible consequence, stated:** a stored block that omitted `enabled` drew no Add button, because the renderer read the raw body; converted, it draws the button its declaration always promised. To keep a list without one, write `enabled: false` or drop the block. An explicit `enabled` value is never touched.
36+
- Both are retired from the load path: a live author is refused at parse and taught; stored rows, built artifacts inside their declared-floor window and `os migrate meta --from 17` replay them.
37+
- `RETIRED_KEYS_BY_MAJOR[18]` gains `ui/AddRecordConfig:mode` and `ui/AddRecordConfig:formView`; the D3 entries are `add-record-mode-form-view-retired` and `add-record-enabled-required`; the default moves (`enabled` `true` → none, `position` `bottom` → `top`) are declared in `DEFAULT_CHANGES_BY_MAJOR[18]`.
38+
39+
### Also in this change
40+
41+
- The Studio view form and page form offer exactly `enabled` and `position` for `addRecord`.
42+
- `@objectstack/lint`: `os explain list-view-field-unknown` no longer names `addRecord.formView` as a position another rule checks.
43+
- The showcase's two `addRecord` blocks and the docs (`ui/create-vs-edit-form`, `ui/views`) drop `mode` / `formView` and state the create-form convention.

‎content/docs/references/api/protocol.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,7 +1863,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
18631863
| **userActions** | `{ sort?: boolean; search?: boolean; filter?: boolean; refresh?: boolean; … }` | optional | User action toggles for the view toolbar |
18641864
| **appearance** | `{ showDescription?: boolean; allowedVisualizations?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>[] }` | optional | Appearance and visualization configuration |
18651865
| **tabs** | `never` | optional | [REMOVED] `view.list.tabs` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — no renderer ever mounted a tab bar for it, so authoring it drew nothing: the tab strip above an object's records is the saved-view switcher (ViewTabBar), which renders one tab per named list view and never read this key. Delete the key, and move each tab you want to a named list view under the object's `listViews` instead: the tab's `name` becomes the entry's key, its `label` the entry's `label`, and its `filter` rules join the view's own `filter` on that entry (copy the view's `columns` too); a tab whose `view` already named a list view needs nothing more. Every `listViews` entry renders as a tab in the switcher. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
1866-
| **addRecord** | `{ enabled?: boolean; position?: Enum<'top' \| 'bottom' \| 'both'>; mode?: Enum<'inline' \| 'form' \| 'modal'>; formView?: string }` | optional | Add record entry point configuration |
1866+
| **addRecord** | `{ enabled: boolean; position?: Enum<'top' \| 'bottom' \| 'both'> }` | optional | Add record entry point configuration |
18671867
| **showRecordCount** | `boolean` | optional | Show record count at the bottom of the list |
18681868
| **allowPrinting** | `boolean` | optional | Allow users to print the view |
18691869
| **emptyState** | `{ title?: string \| Record<string, string>; message?: string \| Record<string, string>; icon?: string; link?: object }` | optional | Empty state configuration when no records found |
@@ -1949,7 +1949,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
19491949
| **userActions** | `{ sort?: boolean; search?: boolean; filter?: boolean; refresh?: boolean; … }` | optional | User action toggles for the view toolbar |
19501950
| **appearance** | `{ showDescription?: boolean; allowedVisualizations?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>[] }` | optional | Appearance and visualization configuration |
19511951
| **tabs** | `never` | optional | [REMOVED] `view.list.tabs` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — no renderer ever mounted a tab bar for it, so authoring it drew nothing: the tab strip above an object's records is the saved-view switcher (ViewTabBar), which renders one tab per named list view and never read this key. Delete the key, and move each tab you want to a named list view under the object's `listViews` instead: the tab's `name` becomes the entry's key, its `label` the entry's `label`, and its `filter` rules join the view's own `filter` on that entry (copy the view's `columns` too); a tab whose `view` already named a list view needs nothing more. Every `listViews` entry renders as a tab in the switcher. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
1952-
| **addRecord** | `{ enabled?: boolean; position?: Enum<'top' \| 'bottom' \| 'both'>; mode?: Enum<'inline' \| 'form' \| 'modal'>; formView?: string }` | optional | Add record entry point configuration |
1952+
| **addRecord** | `{ enabled: boolean; position?: Enum<'top' \| 'bottom' \| 'both'> }` | optional | Add record entry point configuration |
19531953
| **showRecordCount** | `boolean` | optional | Show record count at the bottom of the list |
19541954
| **allowPrinting** | `boolean` | optional | Allow users to print the view |
19551955
| **emptyState** | `{ title?: string \| Record<string, string>; message?: string \| Record<string, string>; icon?: string; link?: object }` | optional | Empty state configuration when no records found |

‎content/docs/references/data/object.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ const result = ApiMethod.parse(data);
409409
| **userActions** | `{ sort?: boolean; search?: boolean; filter?: boolean; refresh?: boolean; … }` | optional | User action toggles for the view toolbar |
410410
| **appearance** | `{ showDescription?: boolean; allowedVisualizations?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>[] }` | optional | Appearance and visualization configuration |
411411
| **tabs** | `never` | optional | [REMOVED] `view.list.tabs` was removed in @objectstack/spec 17.5.0 (ADR-0049 enforce-or-remove) — no renderer ever mounted a tab bar for it, so authoring it drew nothing: the tab strip above an object's records is the saved-view switcher (ViewTabBar), which renders one tab per named list view and never read this key. Delete the key, and move each tab you want to a named list view under the object's `listViews` instead: the tab's `name` becomes the entry's key, its `label` the entry's `label`, and its `filter` rules join the view's own `filter` on that entry (copy the view's `columns` too); a tab whose `view` already named a list view needs nothing more. Every `listViews` entry renders as a tab in the switcher. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
412-
| **addRecord** | `{ enabled?: boolean; position?: Enum<'top' \| 'bottom' \| 'both'>; mode?: Enum<'inline' \| 'form' \| 'modal'>; formView?: string }` | optional | Add record entry point configuration |
412+
| **addRecord** | `{ enabled: boolean; position?: Enum<'top' \| 'bottom' \| 'both'> }` | optional | Add record entry point configuration |
413413
| **showRecordCount** | `boolean` | optional | Show record count at the bottom of the list |
414414
| **allowPrinting** | `boolean` | optional | Allow users to print the view |
415415
| **emptyState** | `{ title?: string \| Record<string, string>; message?: string \| Record<string, string>; icon?: string; link?: object }` | optional | Empty state configuration when no records found |

‎content/docs/references/ui/page.mdx‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ Interface-level page configuration (Airtable parity)
9595
| **appearance** | `{ showDescription?: boolean; allowedVisualizations?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>[] }` | optional | Appearance and visualization configuration |
9696
| **userFilters** | `{ element?: Enum<'dropdown' \| 'tabs' \| 'toggle'>; fields?: object[]; tabs?: object[]; showAllRecords?: boolean; … }` | optional | End-user quick-filter bar for this page (overrides the source view's userFilters) |
9797
| **userActions** | `{ sort?: boolean; search?: boolean; filter?: boolean; refresh?: boolean; … }` | optional | User action toggles |
98-
| **addRecord** | `{ enabled?: boolean; position?: Enum<'top' \| 'bottom' \| 'both'>; mode?: Enum<'inline' \| 'form' \| 'modal'>; formView?: string }` | optional | Add record entry point configuration |
98+
| **addRecord** | `{ enabled: boolean; position?: Enum<'top' \| 'bottom' \| 'both'> }` | optional | Add record entry point configuration |
9999
| **buttons** | `string[]` | optional | Toolbar buttons — names of the source object's actions to surface in the page toolbar |
100100
| **recordAction** | `Enum<'drawer' \| 'page' \| 'modal' \| 'none'>` | optional | How clicking a record opens its detail (drawer \| page \| modal \| none). Default: drawer |
101101
| **showRecordCount** | `boolean` | optional | Show record count at page bottom |
@@ -177,10 +177,10 @@ View filter rule
177177

178178
| Property | Type | Required | Description |
179179
| :--- | :--- | :--- | :--- |
180-
| **enabled** | `boolean` | optional (default: `true`) | Show the add record entry point |
181-
| **position** | `Enum<'top' \| 'bottom' \| 'both'>` | optional (default: `"bottom"`) | Position of the add record button |
182-
| **mode** | `Enum<'inline' \| 'form' \| 'modal'>` | optional (default: `"inline"`) | How to add a new record |
183-
| **formView** | `string` | optional | Named form view to use when mode is "form" or "modal" |
180+
| **enabled** | `boolean` | ✅ | Show the add record entry point — REQUIRED: `true` draws the Add button, `false` declares it off |
181+
| **position** | `Enum<'top' \| 'bottom' \| 'both'>` | optional (default: `"top"`) | Position of the add record button: `top` (the toolbar, the default), `bottom` (under the list) or `both` |
182+
| **mode** | `never` | optional | [REMOVED] `addRecord.mode` was removed in @objectstack/spec 18.1.0 (ADR-0049 enforce-or-remove) — no renderer ever read it: the Add button always opens the create form, never an inline row or a mode of its own choosing. Delete the key; the create form opens the way the object list's New opens it. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
183+
| **formView** | `never` | optional | [REMOVED] `addRecord.formView` was removed in @objectstack/spec 18.1.0 (ADR-0049 enforce-or-remove) — no renderer ever read it: the Add button opens the create form chosen by convention, never a named form. Delete the key, and to give the Add button its own form declare it as `formViews.create` on the object's view container (without one, the default form is used). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand. |
184184

185185
### Nested Shape: `InterfacePageConfig.emptyState`
186186

@@ -266,7 +266,7 @@ View filter rule
266266
| **appearance** | `{ showDescription?: boolean; allowedVisualizations?: Enum<'grid' \| 'kanban' \| 'gallery' \| 'calendar' \| 'timeline' \| 'gantt' \| 'map' \| 'chart' \| 'tree'>[] }` | optional | Appearance and visualization configuration |
267267
| **userFilters** | `{ element?: Enum<'dropdown' \| 'tabs' \| 'toggle'>; fields?: object[]; tabs?: object[]; showAllRecords?: boolean; … }` | optional | End-user quick-filter bar for this page (overrides the source view's userFilters) |
268268
| **userActions** | `{ sort?: boolean; search?: boolean; filter?: boolean; refresh?: boolean; … }` | optional | User action toggles |
269-
| **addRecord** | `{ enabled?: boolean; position?: Enum<'top' \| 'bottom' \| 'both'>; mode?: Enum<'inline' \| 'form' \| 'modal'>; formView?: string }` | optional | Add record entry point configuration |
269+
| **addRecord** | `{ enabled: boolean; position?: Enum<'top' \| 'bottom' \| 'both'> }` | optional | Add record entry point configuration |
270270
| **buttons** | `string[]` | optional | Toolbar buttons — names of the source object's actions to surface in the page toolbar |
271271
| **recordAction** | `Enum<'drawer' \| 'page' \| 'modal' \| 'none'>` | optional | How clicking a record opens its detail (drawer \| page \| modal \| none). Default: drawer |
272272
| **showRecordCount** | `boolean` | optional | Show record count at page bottom |

0 commit comments

Comments
 (0)