Skip to content

Commit c9f81fa

Browse files
committed
docs(spec): say what is true about the readers of a list view's tabs
A list view's own `tabs` has no reader, and objectui's TabBar, the one component that would draw it, has zero production mounts. The earlier wording said the one component that reads a ViewTab[] has none, which is false: `userFilters.tabs` is a ViewTab[] too, and TabFilters reads and renders it. The population sentence now says examples or platform sources, and that the one published skill example that taught the key is corrected in this change. Corrected in the changeset, the tombstone docblock, both retired-key entries, the conversion docblock, the step-18 rationale, the ledger note and the pin's header; the migration registry regenerated. Claude-Session: https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8845cb5 commit c9f81fa

8 files changed

Lines changed: 85 additions & 64 deletions

File tree

‎.changeset/list-view-tabs-retired.md‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ enforce-or-remove; triage verdict RETIRE, on the rule that a capability the
1212
mainstream has and this platform already delivers keeps ONE spelling.
1313

1414
The key parsed at every list-view door and was stored, and no renderer ever
15-
drew it. Measured before removal, each reading beside a lit control: the one
16-
component that reads a `ViewTab[]` (objectui's `TabBar`) has zero production
17-
mounts at the objectui commit this repo pins — every occurrence is in its own
18-
two test files — while the saved-view switcher (`ViewTabBar`) mounts in the
19-
object view and is fed from the object's `listViews`. That switcher IS the tab
20-
strip above an object's records: one tab per named list view. Zero list views
21-
in this repo's examples, skills or platform sources authored the key.
15+
drew it. Measured before removal, each reading beside a lit control: a list
16+
view's own `tabs` has no reader, and objectui's `TabBar` — the one component
17+
that would draw it — has zero production mounts at the objectui commit this
18+
repo pins (every occurrence is in its own two test files), while the saved-view
19+
switcher (`ViewTabBar`) mounts in the object view and is fed from the object's
20+
`listViews`. That switcher IS the tab strip above an object's records: one tab
21+
per named list view. `userFilters.tabs` is a different key with the same
22+
element type: it is read and rendered as a page list's preset bar, and it
23+
stays. Zero list views in this repo's examples or platform sources authored the
24+
key; the one published skill example that taught it is corrected here.
2225

2326
### FROM → TO
2427

‎packages/spec/liveness/view.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@
211211
"status": "dead",
212212
"verifiedAt": "2026-09-27",
213213
"evidenceScope": "cross-repo",
214-
"note": "REMOVED 2026-09-27 (#20301, ADR-0049 enforce-or-remove; triage verdict RETIRE under the maintainer's #18900 criterion — mainstream named-view switching is already delivered here, by `listViews` rendered in ViewTabBar) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error at every list-view door built from the shape: ListViewSchema, ObjectListViewSchema and the flattened overlay arm) and stripped from stored rows and sources by the protocol-18 view-list-tabs-removed conversion. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); to give end users one tab per preset, declare each tab as a named entry under the object's `listViews` — every entry renders as a tab in the saved-view switcher. RE-MEASURED 2026-09-27 before removal, with lit controls: objectui at the pinned sha f8a9d0fb — every `<TabBar` occurrence is in one of its two test files (components/__tests__/TabBar.i18nLabel.test.tsx, __tests__/icon-seam-fallbacks-5935.test.tsx) and every TabBar import is a test's, zero production mounts; CONTROL on the same instrument: `<ViewTabBar` mounts at app-shell/src/views/ObjectView.tsx, fed `views={viewTabItems}` built from `objectDef.listViews`. objectui's own type mirror (types/src/objectql.ts, `tabs?: ListViewSchema['tabs']`, and the zod twin's by-reference import) carries the tombstone in on the pin bump, the pageName precedent. In this repo: zero authorings in examples/, skills/ (one published rules example taught the key and is corrected in the same change) or platform sources — CONTROL: `listViews:` authored in 9 example files. The two author-time reference walks (packages/lint/src/validate-list-view-field-refs.ts#checkTabs, packages/metadata-protocol/src/metadata-diagnostics.ts) read raw input, deliver no tab bar, and are left for their own follow-up. The prior note (RE-DERIVED 2026-09-07, #16362) flipped this row live to dead on the same measurement at objectui @a472b071 and recorded that TabBar does sort on `tabs[].order` — both moot now that the key is gone and nothing mounts TabBar."
214+
"note": "REMOVED 2026-09-27 (#20301, ADR-0049 enforce-or-remove; triage verdict RETIRE under the maintainer's #18900 criterion — mainstream named-view switching is already delivered here, by `listViews` rendered in ViewTabBar) — tombstoned at the schema (retiredKey carries the prescription; authoring it is a tsc error and a parse error at every list-view door built from the shape: ListViewSchema, ObjectListViewSchema and the flattened overlay arm) and stripped from stored rows and sources by the protocol-18 view-list-tabs-removed conversion. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); to give end users one tab per preset, declare each tab as a named entry under the object's `listViews` — every entry renders as a tab in the saved-view switcher. RE-MEASURED 2026-09-27 before removal, with lit controls: objectui at the pinned sha f8a9d0fb — every `<TabBar` occurrence is in one of its two test files (components/__tests__/TabBar.i18nLabel.test.tsx, __tests__/icon-seam-fallbacks-5935.test.tsx) and every TabBar import is a test's, zero production mounts (TabBar is the one component that would draw a list view's own `tabs`; `userFilters.tabs`, a different key of the same element type, is read and rendered by UserFilters/TabFilters and stays); CONTROL on the same instrument: `<ViewTabBar` mounts at app-shell/src/views/ObjectView.tsx, fed `views={viewTabItems}` built from `objectDef.listViews`. objectui's own type mirror (types/src/objectql.ts, `tabs?: ListViewSchema['tabs']`, and the zod twin's by-reference import) carries the tombstone in on the pin bump, the pageName precedent. In this repo: zero authorings in examples/ or platform sources — CONTROL: `listViews:` authored in 9 example files; the one published skill example that taught it (skills/objectstack-ui/rules/list-views.md) is corrected in the same change. The two author-time reference walks (packages/lint/src/validate-list-view-field-refs.ts#checkTabs, packages/metadata-protocol/src/metadata-diagnostics.ts) read raw input, deliver no tab bar, and are left for their own follow-up. The prior note (RE-DERIVED 2026-09-07, #16362) flipped this row live to dead on the same measurement at objectui @a472b071 and recorded that TabBar does sort on `tabs[].order` — both moot now that the key is gone and nothing mounts TabBar."
215215
},
216216
"addRecord": {
217217
"status": "live",

‎packages/spec/src/conversions/registry.ts‎

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10572,12 +10572,14 @@ const viewOverlayOwnerHiddenRemoved: MetadataConversion = {
1057210572
* maintainer's #18900 criterion: mainstream named-view switching is already
1057310573
* delivered here, by `listViews`).
1057410574
*
10575-
* `ListViewSchema.tabs` parsed, was stored, and drew nothing. The one
10576-
* component that reads a `ViewTab[]` — objectui's `TabBar` — has zero
10577-
* production mounts at the pinned objectui sha, and the tab strip above an
10578-
* object's records is the saved-view switcher (`ViewTabBar`), which renders
10579-
* one tab per NAMED LIST VIEW and reads no `tabs` key. The measurement, with
10580-
* lit controls, is on the ledger row (`liveness/view.json`,
10575+
* `ListViewSchema.tabs` parsed, was stored, and drew nothing. A list view's
10576+
* own `tabs` has no reader, and objectui's `TabBar` — the one component that
10577+
* would draw it — has zero production mounts at the pinned objectui sha. The
10578+
* tab strip above an object's records is the saved-view switcher
10579+
* (`ViewTabBar`), which renders one tab per NAMED LIST VIEW and reads no `tabs`
10580+
* key. `userFilters.tabs`, a different key of the same element type, is read
10581+
* and rendered (the page preset bar) and stays. The measurement, with lit
10582+
* controls, is on the ledger row (`liveness/view.json`,
1058110583
* `/props/list/children/tabs`).
1058210584
*
1058310585
* **Retired from the load path** — the key is a `retiredKey()` tombstone on the
@@ -10587,9 +10589,9 @@ const viewOverlayOwnerHiddenRemoved: MetadataConversion = {
1058710589
* stored `view` row CAN carry the key: every list-view door accepted and
1058810590
* persisted it until this release, and `applyConversionsToStoredItem` replays
1058910591
* the chain over stored `view` rows as `{ views: [row] }`. Without it such a
10590-
* row would fail its rehydration parse over a key that never had an effect. It
10591-
* also lets `os migrate meta --from 17` list the mechanical edits for existing
10592-
* sources.
10592+
* row would carry the retired key back to the strict write door, which refuses
10593+
* it on the next save, over a key that never had an effect. It also lets
10594+
* `os migrate meta --from 17` list the mechanical edits for existing sources.
1059310595
*
1059410596
* The delete is lossless in pixels: no renderer ever drew the tabs, so
1059510597
* removing them changes no screen. `stripKeys` deletion is idempotent by
@@ -10601,9 +10603,10 @@ const viewOverlayOwnerHiddenRemoved: MetadataConversion = {
1060110603
* and `view-export-options-pdf-removed` have. `objects[].listViews.*` is NOT
1060210604
* reached by any conversion in this registry, so an object body carrying the
1060310605
* key is refused at its own door rather than converted. Measured population
10604-
* for both: zero authored list-view `tabs` in this tree's examples, skills and
10605-
* platform sources (the in-tree hits are test fixtures of two author-time
10606-
* reference walks, which read raw input and never parse it).
10606+
* for both: zero authored list-view `tabs` in this tree's examples or platform
10607+
* sources (the in-tree hits are test fixtures of two author-time reference
10608+
* walks, which read raw input and never parse it); the one published skill
10609+
* example that taught it is corrected in the same change.
1060710610
*/
1060810611
const viewListTabsRemoved: MetadataConversion = {
1060910612
id: 'view-list-tabs-removed',

‎packages/spec/src/migrations/entries/retired-keys/18.ui__ListView__tabs.ts‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
// #20301 (ADR-0049 enforce-or-remove; triage verdict RETIRE under the
44
// maintainer's #18900 criterion). `ListView.tabs` declared tab definitions for
5-
// a multi-tab view interface, and no renderer ever drew them: the one
6-
// component that reads a `ViewTab[]` (objectui's `TabBar`) has no production
7-
// mount, and the tab strip above an object's records is the saved-view
8-
// switcher (`ViewTabBar`), which renders one tab per `listViews` entry and
9-
// reads no `tabs` key. Tombstoned with `retiredKey()` beside the `pageName`
10-
// tombstone already on this shape; `ViewTabSchema` stays, reused by the
11-
// page-only `userFilters.tabs` preset bar. D2: `view-list-tabs-removed`.
5+
// a multi-tab view interface, and no renderer ever drew them: a list view's own
6+
// `tabs` has no reader, and objectui's `TabBar`, the one component that would
7+
// draw it, has no production mount. The tab strip above an object's records is
8+
// the saved-view switcher (`ViewTabBar`), which renders one tab per `listViews`
9+
// entry and reads no `tabs` key. `userFilters.tabs`, a different key of the
10+
// same element type, is read and rendered, and stays. Tombstoned with
11+
// `retiredKey()` beside the `pageName` tombstone already on this shape;
12+
// `ViewTabSchema` stays, reused by the page-only `userFilters.tabs` preset bar.
13+
// D2: `view-list-tabs-removed`.
1214
export const entry = 'ui/ListView:tabs';
Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
22

33
// #20301 (ADR-0049 enforce-or-remove; triage verdict RETIRE under the
4-
// maintainer's #18900 criterion). `ObjectListView.tabs` declared tab definitions for
5-
// a multi-tab view interface, and no renderer ever drew them: the one
6-
// component that reads a `ViewTab[]` (objectui's `TabBar`) has no production
7-
// mount, and the tab strip above an object's records is the saved-view
8-
// switcher (`ViewTabBar`), which renders one tab per `listViews` entry and
9-
// reads no `tabs` key. Tombstoned with `retiredKey()` beside the `pageName`
10-
// tombstone already on this shape; `ViewTabSchema` stays, reused by the
11-
// page-only `userFilters.tabs` preset bar. D2: `view-list-tabs-removed`.
4+
// maintainer's #18900 criterion). `ObjectListView.tabs` declared tab
5+
// definitions for a multi-tab view interface, and no renderer ever drew them: a
6+
// list view's own `tabs` has no reader, and objectui's `TabBar`, the one
7+
// component that would draw it, has no production mount. The tab strip above an
8+
// object's records is the saved-view switcher (`ViewTabBar`), which renders one
9+
// tab per `listViews` entry and reads no `tabs` key. `userFilters.tabs`, a
10+
// different key of the same element type, is read and rendered, and stays.
11+
// Tombstoned with `retiredKey()` beside the `pageName` tombstone already on
12+
// this shape; `ViewTabSchema` stays, reused by the page-only `userFilters.tabs`
13+
// preset bar. D2: `view-list-tabs-removed`.
1214
export const entry = 'ui/ObjectListView:tabs';

‎packages/spec/src/migrations/registry.ts‎

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5406,10 +5406,12 @@ const step18: MigrationStep = {
54065406
+ 'built artifacts carry it without anyone having written it. Retired from the load path; '
54075407
+ 'an authored key is refused with the prescription. '
54085408
+ 'It also retires the list view\'s own `tabs` (ADR-0049 enforce-or-remove). The '
5409-
+ 'key parsed and was stored at every list-view door and drew nothing: the one component '
5410-
+ 'that reads a `ViewTab[]` has no production mount, and the tab strip above an object\'s '
5411-
+ 'records is the saved-view switcher, which renders one tab per `listViews` entry and '
5412-
+ 'reads no `tabs` key. The key is a `retiredKey()` tombstone on the list-view shape (its '
5409+
+ 'key parsed and was stored at every list-view door and drew nothing: a list view\'s own '
5410+
+ '`tabs` has no reader, the one component that would draw it has no production mount, '
5411+
+ 'and the tab strip above an object\'s records is the saved-view switcher, which renders '
5412+
+ 'one tab per `listViews` entry and reads no `tabs` key (`userFilters.tabs`, a different '
5413+
+ 'key of the same element type, is read and rendered, and stays). The key is a '
5414+
+ '`retiredKey()` tombstone on the list-view shape (its '
54135415
+ 'prescription says how to move each tab to a named `listViews` entry); `ViewTabSchema` '
54145416
+ 'itself stays, because the page-only `userFilters.tabs` preset bar reuses it and renders. '
54155417
+ 'The D2 conversion `view-list-tabs-removed` strips the key from every list payload in '
@@ -20274,13 +20276,15 @@ export const RETIRED_KEYS_BY_MAJOR: Readonly<Record<number, readonly string[]>>
2027420276
'ui/ListView:pageName',
2027520277
// #20301 (ADR-0049 enforce-or-remove; triage verdict RETIRE under the
2027620278
// maintainer's #18900 criterion). `ListView.tabs` declared tab definitions for
20277-
// a multi-tab view interface, and no renderer ever drew them: the one
20278-
// component that reads a `ViewTab[]` (objectui's `TabBar`) has no production
20279-
// mount, and the tab strip above an object's records is the saved-view
20280-
// switcher (`ViewTabBar`), which renders one tab per `listViews` entry and
20281-
// reads no `tabs` key. Tombstoned with `retiredKey()` beside the `pageName`
20282-
// tombstone already on this shape; `ViewTabSchema` stays, reused by the
20283-
// page-only `userFilters.tabs` preset bar. D2: `view-list-tabs-removed`.
20279+
// a multi-tab view interface, and no renderer ever drew them: a list view's own
20280+
// `tabs` has no reader, and objectui's `TabBar`, the one component that would
20281+
// draw it, has no production mount. The tab strip above an object's records is
20282+
// the saved-view switcher (`ViewTabBar`), which renders one tab per `listViews`
20283+
// entry and reads no `tabs` key. `userFilters.tabs`, a different key of the
20284+
// same element type, is read and rendered, and stays. Tombstoned with
20285+
// `retiredKey()` beside the `pageName` tombstone already on this shape;
20286+
// `ViewTabSchema` stays, reused by the page-only `userFilters.tabs` preset bar.
20287+
// D2: `view-list-tabs-removed`.
2028420288
'ui/ListView:tabs',
2028520289
// #16885 — the list view's `navigation.view` binding, retired under ADR-0049
2028620290
// enforce-or-remove by maintainer ruling 2026-09-13 (director decision batch
@@ -20368,14 +20372,16 @@ export const RETIRED_KEYS_BY_MAJOR: Readonly<Record<number, readonly string[]>>
2036820372
// `view-page-mount-removed`.
2036920373
'ui/ObjectListView:pageName',
2037020374
// #20301 (ADR-0049 enforce-or-remove; triage verdict RETIRE under the
20371-
// maintainer's #18900 criterion). `ObjectListView.tabs` declared tab definitions for
20372-
// a multi-tab view interface, and no renderer ever drew them: the one
20373-
// component that reads a `ViewTab[]` (objectui's `TabBar`) has no production
20374-
// mount, and the tab strip above an object's records is the saved-view
20375-
// switcher (`ViewTabBar`), which renders one tab per `listViews` entry and
20376-
// reads no `tabs` key. Tombstoned with `retiredKey()` beside the `pageName`
20377-
// tombstone already on this shape; `ViewTabSchema` stays, reused by the
20378-
// page-only `userFilters.tabs` preset bar. D2: `view-list-tabs-removed`.
20375+
// maintainer's #18900 criterion). `ObjectListView.tabs` declared tab
20376+
// definitions for a multi-tab view interface, and no renderer ever drew them: a
20377+
// list view's own `tabs` has no reader, and objectui's `TabBar`, the one
20378+
// component that would draw it, has no production mount. The tab strip above an
20379+
// object's records is the saved-view switcher (`ViewTabBar`), which renders one
20380+
// tab per `listViews` entry and reads no `tabs` key. `userFilters.tabs`, a
20381+
// different key of the same element type, is read and rendered, and stays.
20382+
// Tombstoned with `retiredKey()` beside the `pageName` tombstone already on
20383+
// this shape; `ViewTabSchema` stays, reused by the page-only `userFilters.tabs`
20384+
// preset bar. D2: `view-list-tabs-removed`.
2037920385
'ui/ObjectListView:tabs',
2038020386
// ADR-0090 D2 (no Profile concept) + ADR-0049 enforce-or-remove; maintainer
2038120387
// ruling 2026-09-12, decision batch #121 item 2, verbatim 「同意」.

‎packages/spec/src/ui/view-list-tabs-retirement.test.ts‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77
*
88
* `ListViewSchema.tabs` parsed at every list-view door, was stored, and drew
99
* nothing. Measured before removal, with lit controls, and recorded on the
10-
* ledger row (`liveness/view.json`, `/props/list/children/tabs`): objectui's
11-
* `TabBar` — the one component that reads a `ViewTab[]` — has zero production
12-
* mounts at the pinned sha, while `ViewTabBar`, the saved-view switcher,
13-
* mounts in the object view and is fed from `listViews`.
10+
* ledger row (`liveness/view.json`, `/props/list/children/tabs`): a list
11+
* view's own `tabs` has no reader, and objectui's `TabBar` — the one component
12+
* that would draw it — has zero production mounts at the pinned sha, while
13+
* `ViewTabBar`, the saved-view switcher, mounts in the object view and is fed
14+
* from `listViews`. `userFilters.tabs`, a different key of the same element
15+
* type, is read and rendered (the page preset bar) and stays — the BOUNDARY
16+
* pinned below.
1417
*
1518
* Bookkeeping shapes, pinned below:
1619
* 1. A `retiredKey()` tombstone on the SHAPE every list-view door is built

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2773,12 +2773,14 @@ const ListViewShapeSchema = lazySchema(() => strictObject({
27732773
* [#20301] REMOVED — ADR-0049 enforce-or-remove, triage verdict RETIRE by
27742774
* the maintainer's #18900 criterion (mainstream named-view switching is
27752775
* already delivered here, by `listViews`). The key parsed, was stored, and
2776-
* drew nothing: objectui's `TabBar` — the one component that reads a
2777-
* `ViewTab[]` — has zero production mounts, and the tab strip above an
2778-
* object's records is the saved-view switcher (`ViewTabBar`), which renders
2779-
* one tab per named list view and reads no `tabs` key. Measured with lit
2780-
* controls and recorded on the ledger row (`liveness/view.json`,
2781-
* `/props/list/children/tabs`).
2776+
* drew nothing: a list view's own `tabs` has no reader, and objectui's
2777+
* `TabBar` — the one component that would draw it — has zero production
2778+
* mounts. The tab strip above an object's records is the saved-view
2779+
* switcher (`ViewTabBar`), which renders one tab per named list view and
2780+
* reads no `tabs` key. (`userFilters.tabs`, a different key of the same
2781+
* element type, IS read and rendered — the page preset bar — and stays.)
2782+
* Measured with lit controls and recorded on the ledger row
2783+
* (`liveness/view.json`, `/props/list/children/tabs`).
27822784
*
27832785
* Tombstoned rather than deleted so the removal is audible in both channels
27842786
* an upgrading author hits — `tsc` (the input type is `never`) and the parse

0 commit comments

Comments
 (0)