feat(spec)!: retire ListViewSchema.navigation.view, the detail-view binding nothing ever resolved - #18619
Conversation
WIP: tombstone + ADR-0087 registration. Generated artifacts follow. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
Tombstone, ADR-0087 registration, regenerated artifacts and the pins. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a693aa4e1bac6118d3280c0d95fc8fa996c4593c && git checkout a693aa4e1bac6118d3280c0d95fc8fa996c4593c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 21b7c12b4f779b86409db2a7bb9235fe80ef8a54 650c7938a962900518041f8aac7279e056166367 && git checkout -B drift-repro 21b7c12b4f779b86409db2a7bb9235fe80ef8a54 && git merge --no-ff 650c7938a962900518041f8aac7279e056166367
node scripts/docs-audit/affected-docs.mjs --json 21b7c12b4f779b86409db2a7bb9235fe80ef8a54
|
Fixes #16885
Clause-②: no
Retires
ListViewSchema.navigation.viewunder ADR-0049 enforce-or-remove, executing themaintainer ruling of 2026-09-13 (director decision batch #126 item 4, comment
5651023571,verbatim 「同意」, option B), with its item 5 superseded by comment
5657440531: thechangeset level is
minor, notmajor.What the key did
navigation.viewwas an unconstrained string whose describe promised "the form view to usefor details". No layer from spec to console ever resolved a view by that name. Its one read in
the shipped console passed the value into the second argument of
onNavigate— the slotthat otherwise carries the navigation-MODE token — so an authored name did not select a view,
it substituted for the mode. A consumer in the same bundle reads that argument against a
closed two-value vocabulary (
edit/view), so any other authored value matched neitherbranch. Declared, consumed, and wrong.
Acceptance criteria — executable, with both controls
PROBE —
navigation: { view: 'summary_view' }is now refused, at the key the author wrote,with the prescription naming page assignment as the route:
LIT CONTROL — the live siblings still parse.
{ mode: 'page' }is accepted, and so are allfive survivors together (
mode,preventNavigation,openNewTab,size,width). A tombstonethat broke its siblings would satisfy the refusal assertion while being a larger bug, and
navigationis one closed shape, so that blast radius is the whole block. Pinned at all threedoors —
ListViewSchema,ObjectListViewSchema, and the flattenedPUT /api/v1/meta/viewoverlay.
DARK CONTROL — the reading used is the regenerated
packages/spec/authorable-surface/ui.jsonui/NavigationConfig:*row set. It returned: 6 rows before, 6 rows after; exactly one linechanged,
ui/NavigationConfig:view->ui/NavigationConfig:view [RETIRED]; the retired keynever appears plain, and no other
ui/NavigationConfig:*row disappeared."ui/NavigationConfig:openNewTab", "ui/NavigationConfig:preventNavigation", "ui/NavigationConfig:size", - "ui/NavigationConfig:view", + "ui/NavigationConfig:view [RETIRED]", "ui/NavigationConfig:width",Gates observed RED before GREEN
A gate never observed failing for this change is not known to be a gate for it.
check:generated(check:docsleg)✗ 1 of 15 artifact(s) stale: content/docs/references/**✓ All 15 generated artifacts are up to date.after--fixregenerated exactly that onecheck:generated(check:api-surfaceleg)✗ ... dist/**/*.d.ts describe DIFFERENT sources than the ones on disk(stale-dist trap, after the test file moved)✓after a real rebuildcheck-adr-0087-registrationdeclares a breaking change (BREAKING) but no adr-0087: disposition marker— proven by committing the changeset with the marker line dropped1 declared-breaking changeset(s), each carrying an ADR-0087 disposition ... registered list-view-navigation-view-retiredThe ablation mutated
packages/spec/src/ui/view.zod.tson disk (tombstone occurrences 1 -> 0,ablation marker 0 -> 1, blob hash moved), ran the pins, then restored under a
trap— restoredblob hash byte-identical to
HEADandgit diff HEADempty. Note the direction: only the threerefusal pins went red; the eight sibling-acceptance assertions stayed green, which is the correct
shape for reverting a tombstone.
Gate results
pnpm --filter @objectstack/spec check:generated— all 15 green, including the four named onthe card:
check:authorable-surface,check:api-surface,check:docs,check:liveness. Workingtree clean afterwards (no regeneration drift).
pnpm --filter @objectstack/spec typecheck— green.pnpm --filter @objectstack/spec test—484 files, 13830 tests, all passed.
pnpm lint(repo-wide,eslint . --no-inline-config) —green, so no narrowing was taken and none is declared.
All 14 source audits
check:generateddeliberately does not run — green.check:skill-examplesfirst reported PREREQUISITE NOT MET (packages/client-react/distunbuiltin a fresh worktree); after building that closure it reads
✅ 258 prose examples type-check across 3 surface(s).node scripts/check-changeset-no-major.mjs— green, nomajorbump.check-widening-tellsdid not recur.node scripts/pm/check-widening-tells.mjs --declaration no --diff <this diff>exits 0:10 changed file(s) — 4 judged against a declared surface (no widening tell), 6 NOT MEASURED. The T1/T2 firing onretiredKey()lines recorded on #17955 and#17300 did not reproduce here, so there is nothing to report as a regression and the
declaration is unchanged, as ruled.
The ADR-0087 disposition is SEMANTIC, deliberately
The ruling asked for a semantic migration entry, and the shape earns it: a mechanical D2 strip
would delete the key without recording which list view lost it, and an author who wrote it
wanted a named detail layout — a want page assignment serves and a stripped key does not record.
So the entry is a D3
SemanticMigration,list-view-navigation-view-retired, and the tombstoneprescription therefore carries no
os migrate metasentence: that sentence is owed only wherea conversion covers the surface (
shared/retired-key.tsmodule docblock; the class pinretired-key-migrate-sentence.test.tsdeliberately judges nothing when the marker is absent).The precedent for registering a retired key with no D2 conversion is
data/AggregationNode:distinctin this same table.Registered as
ui/NavigationConfig:viewinRETIRED_KEYS_BY_MAJOR[18], which also starts itsaging clock. Note for the reviewer: no major-18 semantic entry reaches
spec-changes.jsonordocs/protocol-upgrade-guide.mdyet — measured, not assumed: four sampled major-18 ids(
change-management-family-retired,training-family-retired,scim-provider-object-retired,epoch-instant-keys-renamed) return 0 in both files, while major-17 ids return non-zero and theguide stops at "Protocol 17". This entry behaving the same way is the steady state for the open
window, not a gap.
Liveness ledger — nothing to update, and why
view/list/navigationis oneliverow with no classified children, becausecheck-livenessdrills one level. That is #17424's subject; it is cited here and not fixed, exactly as ruling
item 4 directs. Since the walk never reaches
navigation's children, no ledger row exists forviewto markdead, and none is owed —check:livenessis green with the tombstone in place,with no UNCLASSIFIED row.
packages/spec/liveness/**is untouched (held by #18582).The objectui contract twin is in the SIBLING repo — reported, not touched
Ruling item 3 asked where the contract twins live. They are in
objectui, not here. Thereading:
objectui/packages/types/src/__tests__/view-navigation-config-spec-parity.test.tsat the pinned.objectui-sha(53ded82bf7a494f54e344e19099dbf00854b8694) is the twin. It assertsEqual<ViewNavigationConfig, NavigationConfig>—ViewNavigationConfigis this spec type,re-exported by reference since objectui#4588.
const navigation: ViewNavigationConfig = { view: 'summary_view' };. Once objectui resolves aspec carrying this tombstone, those lines become
tscerrors under that package'stype-check.ViewNavigationConfigoccursonly in
packages/console/CHANGELOG.md(a release-owned file), and the trackedsdui.manifest.jsondeclaresnavigationas a flatobjectinput with no drill intoview,so
check:react-declaration-parityis structurally blind to it. Nothing here to update.The Console Pin Gate is not affected and does not run on this PR. Measured: that job is gated
on the
consolepaths filter (.objectui-shaplus four scripts), none of which this diff touches;and it runs
scripts/build-console.sh— a build, which does not type-check the sibling'stsconfig.test.json. objectui's src at the pinned SHA contains no value write ofnavigation.viewoutside that test file (only docblocks), and reads of.viewstill compileagainst
never | undefined. So this PR does not turnmainred.What is still owed, in the other repository: the twin gains the tombstone pin before objectui
picks up a spec carrying this retirement. Its dependency is
@objectstack/spec: ^17.0.0, so apublished 17.5.0 is in range.
File surface
Every path touched, including the pre-declared OPEN set.
packages/spec/src/ui/view.zod.tsretiredKey()tombstone + its prescription constantpackages/spec/src/migrations/entries/retired-keys/18.ui__NavigationConfig__view.tsRETIRED_KEYS_BY_MAJOR[18]registration (build-schemas gate (b))packages/spec/src/migrations/entries/semantic/18.list-view-navigation-view-retired.tspackages/spec/src/ui/view.test.tspackages/spec/src/migrations/registry.tsgen:migration-registry, never hand-edited.changeset/16885-retire-navigation-view.mdminor,**BREAKING**banner, FROM -> TO, ADR-0087 dispositionpackages/spec/authorable-surface/ui.jsonbuildcontent/docs/references/ui/view.mdxgen:docscontent/docs/references/api/protocol.mdxgen:docs(the inline navigation summary dropsview?: string)content/docs/references/data/object.mdxHeld by other in-flight work and not touched:
packages/spec/scripts/build-schemas.ts(#17969),packages/spec/liveness/**andpackages/spec/scripts/liveness/check-liveness.mts(#18582),packages/spec/src/api/protocol.zod.ts(#18597). Ruling item 2 was already discharged — PR #17796reads
closed, not merged — so the retirement is written fresh.Acceptance notes
Noted, not filed: nothing in the touched files.
One finding to file separately, unrelated to this diff and not fixed here:
pnpm check:cross-package-test-inputsreaches opposite verdicts on an identical source treedepending only on whether
packages/spec/dist/happens to be built — exit 1 with it present(115 entries), exit 0 with the same directory moved aside, same commit, restored afterwards.
The flagged radius is
packages/spec/dist/descended frompackages/cli/test/init-created-files-summary.e2e.test.ts, which this diff does not touch. Nopnpm buildstep precedespnpm check:cross-package-test-inputsinlint.yml'slintjob, soCI always runs it against an unbuilt tree and it passes vacuously — the declaration hole it exists
to catch is structurally invisible on every PR. That is the "a verifier that silently degrades is
worse than no verifier" shape from AGENTS.md Route & surface ownership rule 3.
维护者速读(草稿)
改了什么 ——
ListViewSchema.navigation.view退役:键仍在 shape 里,但变成 ADR-0049 墓碑,写它是
tsc报错、传值进来是带处方的 parse 拒收。处方点名替代路线:给对象发布record页面,用
isDefault决定打开哪一个。同批落地 ADR-0087 的 D3 语义迁移条目、changeset(minor+**BREAKING**)、以及三道门上的拒收 pin。为什么改 —— 这个键承诺"用哪个表单视图打开详情",而从 spec 到 console 没有任何一层按名字解析
视图。它唯一的读取把值塞进了
onNavigate的第二个参数 —— 那个槽位装的是导航模式令牌。所以作者写的视图名不是在选视图,而是在顶替模式;而同一个 bundle 里有消费者拿这个参数去对
edit/view两值词表,任何别的值两个分支都不匹配。仓库内零编写实例,唯一的外部作者已自行删除。维护者 2026-09-13 裁决 B:零拉力即移除。
风险与代价(含回滚) —— 风险低。行为面没有回归可言:这个键从来没选中过任何东西,决定详情怎么
呈现的是
mode和size,两者一字未动,并且有 pin 钉着五个存活键仍然接受。代价是发布面:这是breaking,升级时仍写着该键的元数据会在三道门(含
PUT /api/v1/meta/view)被拒。回滚就是revert 本 PR —— 墓碑、注册条目、changeset 与生成产物都在同一个提交序列里,没有跨仓副作用。
一个待办留在姊妹仓:objectui 的契约孪生 pin 把
{ view: 'summary_view' }写成合法值,需要在objectui 取到带墓碑的 spec 之前补上墓碑 pin。本 PR 不触发 Console Pin Gate,不会让
main变红。席位意见 ——
你要做的 ——
Generated by Claude Code