Repository navigation
Commit 6e3e546
Part of #20301
Clause-②: no (narrowing)
Stage 1 of a staged card: the list view's own `tabs` key only. #20301
remains open for its second key, the view container's body `name`, which
waits until cloud's writer stops sending it (triage note 2). This PR
does not touch the container's `name`.
## What
`ListViewSchema.tabs` (and the same key on `ObjectListViewSchema`, a
view item record's list `config`, and the flattened list overlay at `PUT
/api/v1/meta/view`) is retired under ADR-0049 enforce-or-remove. The
triage verdict was RETIRE under the maintainer's family criterion:
named-view switching is mainstream and is already delivered here by
`listViews`, rendered by the saved-view switcher (`ViewTabBar`).
- **Tombstone.** `tabs` becomes a `retiredKey()` tombstone inside
`ListViewShapeSchema` only, beside the `pageName` tombstone on the same
strict shape. Authoring it is a `tsc` error (input type `never`) and a
parse error that carries the prescription: delete the key, and move each
tab to a named `listViews` entry (tab `name` becomes the entry key,
`label` the entry `label`, its `filter` rules join the view's own
`filter`, copy `columns`).
- **ADR-0087.** D2 conversion `view-list-tabs-removed` (protocol 18,
retired from the load path) strips the key from every list payload in
`stack.views[]`, in all three persisted spellings. **D2 chosen**: the
delete is lossless in pixels because nothing ever read or drew the key,
so a stored `view` row replays without it at every rehydration seam and
re-saves through the strict write door instead of being refused there
over a key that never had an effect. One D3 entry,
`list-view-tabs-retired`, per ruling B on #17152, with no tracker number
in any author-shown field. `RETIRED_KEYS_BY_MAJOR[18]` gains
`ui/ListView:tabs` and `ui/ObjectListView:tabs`. Declared boundary (same
as the three sibling list-view conversions): an object's own `listViews`
is reached by no conversion, so such an object is refused at its own
door.
- **Ledger.** The row `view.json` `/props/list/children/tabs` stays
`dead` (tombstone discipline), re-verified 2026-09-27, with a REMOVED
note. The stale `view/list.tabs` row leaves
`undrilled-containers.baseline.json` (the tombstone is no longer a
container, which `check:liveness` reported).
- **Kit.** The metadata form's `tabs` repeater leaves with the key; the
extracted form-label bundles are regenerated (pure deletion, 4 files).
Reference docs, `authorable-surface/ui.json` (two rows become
`[RETIRED]`) and the migration registry are regenerated by their
generators. The published `objectstack-ui` skill's list-view rules stop
teaching the key. `layout-dsl.mdx` stops pointing `ViewTab` at list
views. Changeset: `@objectstack/spec` minor, BREAKING, FROM to TO table.
- **Tests that pinned the retired acceptance** were flipped or
re-pointed: `view.test.ts` drops "should accept list view with tabs" and
the key from the full-parity fixture; two wire-door tests move their tab
carrier to the surviving `userFilters.tabs`.
`ViewTabSchema` is **not** retired: the page-only `userFilters.tabs`
preset bar reuses it and renders. Pinned as a boundary.
## Measured first (premise holds)
- What is true, stated first: **a list view's own `tabs` has no
reader**, and objectui's `TabBar`, the one component that would draw it,
has zero production mounts. `userFilters.tabs` is a different key with
the same element type (`z.array(ViewTabSchema)`): objectui's
`UserFilters` passes it to `TabFilters`, which renders it in production
as a page list's preset bar, and it stays.
- objectui at the pin `f8a9d0fb` (`.objectui-sha`): `git grep -E` for a
`TabBar` JSX mount = 10 hits in 2 files, both test files
(`components/__tests__/TabBar.i18nLabel.test.tsx`,
`__tests__/icon-seam-fallbacks-5935.test.tsx`); every `TabBar` import is
a test's. **Lit control**, same instrument and corpus: a `ViewTabBar`
JSX mount in production source = 1 file,
`packages/app-shell/src/views/ObjectView.tsx` (`views={viewTabItems}`,
built from `objectDef.listViews`). No production `.tabs` read of a list
view anywhere in `packages/*/src` at the pin. objectui's type mirror
(`types/src/objectql.ts`, `tabs?: ListViewSchema['tabs']`) and its zod
twin import the spec field by reference, so the tombstone flows in on
the next pin bump, the `pageName` precedent; no export is removed here,
so the Console Pin Gate build is unaffected.
- This repo at base `4e0f72e8d`: zero list-view `tabs` authorings in
`examples/` or in platform sources (the 2 `tabs:` hits in `examples/`
are a page `userFilters` preset bar and a record-page slot);
**control**: `listViews:` authored in 9 example files. The one published
skill example that taught the key
(`skills/objectstack-ui/rules/list-views.md`) is corrected here. No
platform writer sends it.
- Readers in this repo: two author-time reference walks read the key off
RAW input
(`packages/lint/src/validate-list-view-field-refs.ts#checkTabs`,
`computeViewReferenceDiagnostics` in
`packages/metadata-protocol/src/metadata-diagnostics.ts`). Neither draws
a tab bar; the ledger already graded them as not delivering the key's
effect. Left in place, see Acceptance notes.
`packages/lint/src/validate-capability-references.ts:186` (a generic
`rec.tabs` walk) does NOT reach a list view: its walk starts at
`stack.apps[*]` and descends only an app node's `navigation` / `areas` /
`tabs` / `children` / `items`, so it never visits a view. A third
reader, found by CI and not by the pre-change census: the derived
reference-site index behind `findReferencesToMeta` (the Studio "Used by"
panel) walks each type's schema, and `listViews.*.tabs[].view` (a
`ViewTab` naming a list view) was the ONLY property under `object`
spelled as a `view` reference. With the key retired the walk finds none,
so a view's "Used by" scan no longer reads `object` rows. Measured by an
ablation of the tombstone over the `object` input schema: 1 view-spelled
path now (`listViews.*.navigation.view`, itself retired), 2 with the
tombstone removed (plus `listViews.*.tabs[].view`). No such reference is
authorable any more; a stored object row still carrying one is refused
at its own door (no conversion reaches an object's `listViews`).
- hotcrm's inert-key test (#16094) is inherited, NOT MEASURED here (out
of this seat's repo scope). Cloud: NOT MEASURED here; the card's census
at cloud `96eb092` names cloud writers for `view.name` only.
## Ablation (the tombstone is what shuts every door)
Run on committed HEAD `74f8ab9727` with `node
scripts/ablation-replace.mjs` (wrap mode, restore armed on
EXIT/INT/TERM) against `packages/spec/src/ui/view.zod.ts`:
- mutation landed on disk: anchor ` tabs: retiredKey(` x1 to x0,
replacement (`tabs` back to a plain `ViewTab` array, the tombstone
parked on a dummy key) x0 to x1, blob `b64e9996f3fc` to `90f0a141e429`;
- `vitest run src/ui/view-list-tabs-retirement.test.ts`: **11 failed, 18
passed (29)**. Red: the refusal at all seven doors, the empty-array pin,
the prescription pin, the parse-channel half of the tsc pin, and the
object-door boundary. Still green, as expected: the D2 conversion,
registration, form, `userFilters.tabs` boundary and absence pins, which
do not depend on the tombstone. Direction observed: turned red;
- restore proven by bytes, not exit code: blob after restore
`b64e9996f3fc` equals the HEAD blob, `git diff HEAD` empty, tombstone
anchor count 1, mutant count 0.
The tsc door is proven by the positive run: `check:test-typecheck` is
green with an `@ts-expect-error` on the `tabs` line of a `defineView`
call, and an unneeded directive is itself a TS2578 in that same program.
It was not separately ablated under tsc.
## Verification
**Rework round 1, head `606046e96a`.** The at-tier review failed on the
truth of two sentences; the retirement itself stood. (1) "The one
component that reads a `ViewTab[]` has zero production mounts" was
false, because `userFilters.tabs` is a `ViewTab[]` that `TabFilters`
renders. It now reads: a list view's own `tabs` has no reader, and
`TabBar`, the one component that would draw it, has zero production
mounts; `userFilters.tabs` is read, rendered, and stays. Corrected in
the changeset, the tombstone docblock in `view.zod.ts`, both retired-key
entries, the conversion docblock, the step-18 rationale, the ledger
note, the pin's header, and this body. (2) "examples, skills or platform
sources" now reads "examples or platform sources", plus the note that
the one published skill example that taught it is corrected here. `main`
merged at `d0003a10c7` with `scripts/pm/os-regen-merge.sh`: the
hand-written registry tails conflicted with #20251 and both sides were
kept, main's first. That also repaired a join from the previous merge
that had dropped a period and space before this PR's rationale sentence.
The object reference page was regenerated at `8845cb564e`, and the prose
corrections landed at `c9f81fa132`. `main` moved again (the RLS policy
`tags` retirement) and was merged at `606046e96a`, again keeping both
registry tails, main's first; the migration registry regenerated
byte-identically. Checks at `c9f81fa132`: spec build + `check:generated`
all 15 up to date; `check:liveness`, `check:adr-0087-registration`,
`check-changeset-no-major`, `check-empty-changeset`,
`check:doc-authoring`, `check:nul-bytes`, `check:issue-citations`, spec
docblock anchors, comment-mask adoption and
`check:cross-package-test-inputs` all exit 0; the changeset's `Clause-②:
no (narrowing)` line reads back as declared. Targeted spec tests at
`c9f81fa132`: 5 files / 863 tests passed (`view-list-tabs-retirement`,
`conversions`, `migrations`, `view`, `retired-key-migrate-sentence`). At
`606046e96a`: the source-reading spec gates (`check:migration-registry`,
`check:spec-changes`, `check:upgrade-guide`, `check:liveness`,
`check:authorable-surface`, `check:docs`), `check-comment-mask-corpus`
and `check:query-options-erasure` all exit 0. The shared verify lock was
not acquired for a local post-merge spec rebuild. CI on `606046e96a`: 35
check runs, 33 success and 2 skipped, including TypeScript Type Check
(the generated-artifact gates), Test Core 6/6 and Lint & Repo Gates.
Derived gates at `606046e96a`: 122, 114 run green, 8 NOT MEASURED. Five
of those lost their build prerequisites when a local turbo rebuild was
OOM-killed on the shared box: `check:doc-formula-expressions`,
`check:doc-security-posture`, `check:skill-examples`,
`check:docs-transcript-drift` and `check:lean-entry-closure`, all green
at `a3f8054dcc` and covered by CI. The other three are `check:i18n`,
`check:dual-build-cjs-loads` and `check:type-check-debt`.
**CI repair round, head `a3f8054dcc`** (main merged again at
`bfa765ca08`, reference pages regenerated at `c13e610ab5`). Four
consumer pins of the retired key, in packages the first round did not
run, moved to the post-retirement truth, none weakened:
`repeater-row-properties.test.ts` now asserts `view:tabs` ABSENT beside
the two surviving view repeaters;
`object-lifecycle-panel-echo-decisions.test.ts` positive control 608 to
598 (the repeater's label plus its nine row labels), with the reason in
the pin's comment; `protocol.graft-normalized-operators.test.ts` keeps
its view-filter leg and moves the nested `ViewTab.filter` leg to the
surviving `userFilters.tabs` carrier;
`protocol.read-seam-empty-accumulator.test.ts` reads the consulted
source types from the derived index and pins `object` absent (see
Measured first). Full suites at `a3f8054dcc`:
`@objectstack/platform-objects` 55 files / 911 tests passed;
`@objectstack/metadata-protocol` 189 files passed, 3 skipped
(pre-existing) / 2736 tests passed; `@objectstack/lint` 112 files / 4640
tests passed; `@objectstack/objectql` `metadata-diagnostics.test.ts` 10
passed. Derived gates: 122, 119 run green, 3 NOT MEASURED (`check:i18n`:
its CLI prerequisite build never acquired the shared lock, green at
`baa383008b` on the same bundles; `check:dual-build-cjs-loads`;
`check:type-check-debt`).
First round, at merged HEAD `baa383008b` unless stated.
- `pnpm --filter @objectstack/spec build` then `check:generated`: **All
15 generated artifacts are up to date** (migration registry,
spec-changes, upgrade guide, authorable surface, api-surface, docs,
liveness counts, test-typecheck and the rest).
- `pnpm --filter @objectstack/spec typecheck`: exit 0 (tsc, scripts
typecheck, and test typecheck: 53 files / 255 errors / 142 pinned
signatures held, unchanged).
- `pnpm --filter @objectstack/spec test` (local project): **554 files
passed, 16426 tests passed**, 1 todo.
- Repo project: full `test:repo` at `74f8ab9727`: **34 files, 633 tests
passed**. After merging `main` again (the flattened-overlay `owner` /
`hidden` retirement landed and conflicted only in the two hand-written
registry tails; both sides kept), targeted rerun of the two repo-project
retirement pins at `baa383008b`: 2 files, 47 tests passed. Declared
narrowing: the other 32 repo-project files were not rerun after that
merge; CI runs them.
- i18n: `node scripts/check-i18n-bundles.mjs --write` regenerated the
four `metadata-forms` bundles; `pnpm check:i18n` exit 0.
- Derived gate families (`node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack`, reconciled with `--ran`): **121 derived,
119 run, 2 NOT MEASURED, 0 unrun**. Named in the dispatch and green:
`check:liveness`, `check:migration-registry`, `check:generated`,
`check:spec-changes`.
- NOT MEASURED: `check:dual-build-cjs-loads` (exit 3, needs a full
workspace build), `check:type-check-debt` (repo-wide tsc re-measure of
every package exceeded the foreground cap on a shared box).
- `check:dts-closure` exit 1 on tree state, not on this diff: five
packages this PR does not touch (cloud-connection, organizations,
plugin-approvals, plugin-dev, verify) hold JS-only `dist/` in this
worktree after a shared turbo cache replay during an unrelated closure
build; the same gate was exit 0 at `74f8ab9727` before that build. CI
builds fresh.
- Lint, narrowed and proven: eslint `--no-inline-config --format json`
over the 15 changed JS/TS files: 15 files, 0 errors, 0 warnings, none
ignored. Population is `pnpm lint` (`eslint . --no-inline-config`,
`eslint.config.mjs`); invariance: that config enables no type-aware
linting (no `parserOptions.project`, stated in `eslint.config.mjs`), so
this diff cannot move any untouched file's verdict.
## Governed surface: Tier H
`skills/objectstack-ui/rules/list-views.md` is under `skills/**`, so
this PR is **Tier H**: it lands only by the maintainer's hand or an
authorized approval. No generated file under `skills/**` changed.
Readings for the `skills/**` edit (lines): the edited file 309 to 302
(net -7, pure deletion: the list-level `tabs` example and a rule that
described a tab bar that never rendered, replaced by a 3-line
`listViews` pointer). Whole pack, all 10 `skills/**/SKILL.md`: 4402 to
4402 (no SKILL.md edited).
## Acceptance notes
Out of this card's file surface, noted and not filed (dead code class;
carrier named):
1. The two author-time walks above still read a list view's `tabs` off
raw input, and their test fixtures still author it
(`packages/lint/src/validate-list-view-field-refs.test.ts`,
`packages/objectql/src/metadata-diagnostics.test.ts`, plus the CLI's
negative i18n pin `packages/cli/test/i18n-tab-coverage.test.ts`). After
this PR the parse refuses the key first, so those branches are
unreachable for valid input. The tree-scoped absence pin declares these
three files as self-expiring residue: each is asserted to still hold an
offender, so the day a follow-up deletes the walks, the pin forces the
entry out. Carrier: none named.
2. `packages/cli/src/utils/i18n-extract.ts` and
`packages/lint/src/validate-translation-references.ts` carry prose that
says the list view's own `tabs` "has no reader"; still true, now for a
stronger reason. Carrier: none named.
3. objectui at its next pin bump: `types/src/objectql.ts` keeps `tabs?:
ListViewSchema['tabs']` (becomes `undefined`-typed) and its parity tests
may want the row updated. Carrier: the objectui pin-bump PR.
## 维护者速读(草稿)
**改了什么**
列表视图上的 `tabs` 字段被正式退役。以后在列表视图里写 `tabs`,类型检查和保存都会直接报错,错误信息会告诉作者改用
`listViews`(每个命名视图就是切换条上的一个页签)。存量数据里残留的这个字段,升级时会被自动删掉。
**为什么改**
这个字段一直能写、能存,但没有任何代码读它,界面上也从来没有画出过任何东西:本来要画它的那个页签组件在正式代码里一次都没被用上;真正显示在记录列表上方的页签条,是按
`listViews` 逐个列出的已存视图。页面筛选栏里的 `userFilters.tabs`
是另一个字段,照常显示,不受影响。同一个能力两种写法,其中一种完全无效,AI 和作者都容易写错。按“主流平台已有、本平台已交付 ⇒
只保留一种写法”的判据退役。
**风险与代价(含回滚)**
属于破坏性变更:外部仓库里若有人写了这个字段,升级后会被拒绝(报错里带修改方法)。本仓示例与平台代码实测零使用,唯一教过这种写法的已发布技能示例已在本
PR 中改正;外部使用面未测量。存量数据自动清理,不影响任何已有画面。回滚:revert 本 PR 即可恢复该字段。
**席位意见**
**你要做的**
本 PR 触及已发布技能目录(`skills/**`),属于 Tier H,需要你本人批准后才能合并。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0fcb101 commit 6e3e546
29 files changed
Lines changed: 1039 additions & 243 deletions
File tree
- .changeset
- content/docs
- protocol/objectui
- references
- api
- data
- ui
- packages
- metadata-protocol/src
- platform-objects/src/apps/translations
- spec
- authorable-surface
- liveness
- scripts/liveness
- src
- conversions
- migrations
- entries
- retired-keys
- semantic
- ui
- skills/objectstack-ui/rules
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
563 | 563 | | |
564 | 564 | | |
565 | 565 | | |
566 | | - | |
567 | | - | |
568 | | - | |
569 | | - | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
570 | 570 | | |
571 | 571 | | |
572 | 572 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1699 | 1699 | | |
1700 | 1700 | | |
1701 | 1701 | | |
1702 | | - | |
| 1702 | + | |
1703 | 1703 | | |
1704 | 1704 | | |
1705 | 1705 | | |
| |||
1784 | 1784 | | |
1785 | 1785 | | |
1786 | 1786 | | |
1787 | | - | |
| 1787 | + | |
1788 | 1788 | | |
1789 | 1789 | | |
1790 | 1790 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
398 | 398 | | |
399 | 399 | | |
400 | 400 | | |
401 | | - | |
| 401 | + | |
402 | 402 | | |
403 | 403 | | |
404 | 404 | | |
| |||
0 commit comments