Skip to content

feat(spec)!: retire ListViewSchema.navigation.view, the detail-view binding nothing ever resolved - #18619

Merged
os-bill merged 3 commits into
mainfrom
claude/issue-16885-retire-navigation-view
Sep 17, 2026
Merged

os-bill merged 3 commits into
mainfrom
claude/issue-16885-retire-navigation-view

Conversation

@os-bill

@os-bill os-bill commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16885

Clause-②: no

Retires ListViewSchema.navigation.view under ADR-0049 enforce-or-remove, executing the
maintainer ruling of 2026-09-13 (director decision batch #126 item 4, comment 5651023571,
verbatim 「同意」, option B), with its item 5 superseded by comment 5657440531: the
changeset level is minor, not major.

What the key did

navigation.view was an unconstrained string whose describe promised "the form view to use
for 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 slot
that 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 neither
branch. Declared, consumed, and wrong.

Acceptance criteria — executable, with both controls

PROBEnavigation: { view: 'summary_view' } is now refused, at the key the author wrote,
with the prescription naming page assignment as the route:

PROBE  navigation:{view:"summary_view"} -> success = false
       path = navigation.view
       message = `view.list.navigation.view` was removed in @objectstack/spec 17.5.0
                 (ADR-0049 enforce-or-remove) — ... Delete the key; to choose what opens for a
                 record, assign a `record` page to the object and let `isDefault` pick the one
                 that opens — page assignment is the machinery that resolves a detail layout ...
LIT    navigation:{mode:"page"}         -> success = true
LIT    all five surviving keys          -> success = true

LIT CONTROL — the live siblings still parse. { mode: 'page' } is accepted, and so are all
five survivors together (mode, preventNavigation, openNewTab, size, width). A tombstone
that broke its siblings would satisfy the refusal assertion while being a larger bug, and
navigation is one closed shape, so that blast radius is the whole block. Pinned at all three
doors — ListViewSchema, ObjectListViewSchema, and the flattened PUT /api/v1/meta/view
overlay.

DARK CONTROL — the reading used is the regenerated packages/spec/authorable-surface/ui.json
ui/NavigationConfig:* row set. It returned: 6 rows before, 6 rows after; exactly one line
changed, ui/NavigationConfig:view -> ui/NavigationConfig:view [RETIRED]; the retired key
never 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.

gate red green
check:generated (check:docs leg) ✗ 1 of 15 artifact(s) stale: content/docs/references/** ✓ All 15 generated artifacts are up to date. after --fix regenerated exactly that one
check:generated (check:api-surface leg) ✗ ... dist/**/*.d.ts describe DIFFERENT sources than the ones on disk (stale-dist trap, after the test file moved) after a real rebuild
check-adr-0087-registration exit 1declares a breaking change (BREAKING) but no adr-0087: disposition marker — proven by committing the changeset with the marker line dropped exit 01 declared-breaking changeset(s), each carrying an ADR-0087 disposition ... registered list-view-navigation-view-retired
the three refusal pins 3 failed / 8 passed under an ablation that restored the pre-retirement live string 11 passed on the real tree

The ablation mutated packages/spec/src/ui/view.zod.ts on disk (tombstone occurrences 1 -> 0,
ablation marker 0 -> 1, blob hash moved), ran the pins, then restored under a trap — restored
blob hash byte-identical to HEAD and git diff HEAD empty. Note the direction: only the three
refusal 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:generatedall 15 green, including the four named on
the card: check:authorable-surface, check:api-surface, check:docs, check:liveness. Working
tree 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:generated deliberately does not run — green.
check:skill-examples first reported PREREQUISITE NOT MET (packages/client-react/dist unbuilt
in 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, no major bump.

check-widening-tells did 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 on retiredKey() 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 tombstone
prescription therefore carries no os migrate meta sentence: that sentence is owed only where
a conversion covers the surface (shared/retired-key.ts module docblock; the class pin
retired-key-migrate-sentence.test.ts deliberately judges nothing when the marker is absent).
The precedent for registering a retired key with no D2 conversion is
data/AggregationNode:distinct in this same table.

Registered as ui/NavigationConfig:view in RETIRED_KEYS_BY_MAJOR[18], which also starts its
aging clock. Note for the reviewer: no major-18 semantic entry reaches spec-changes.json or
docs/protocol-upgrade-guide.md yet
— 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 the
guide 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/navigation is one live row with no classified children, because check-liveness
drills 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 for
view to mark dead, and none is owed — check:liveness is 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. The
reading:

  • objectui/packages/types/src/__tests__/view-navigation-config-spec-parity.test.ts at the pinned
    .objectui-sha (53ded82bf7a494f54e344e19099dbf00854b8694) is the twin. It asserts
    Equal<ViewNavigationConfig, NavigationConfig>ViewNavigationConfig is this spec type,
    re-exported by reference since objectui#4588.
  • That file authors the retired key as a legal value in four places, e.g.
    const navigation: ViewNavigationConfig = { view: 'summary_view' };. Once objectui resolves a
    spec carrying this tombstone, those lines become tsc errors under that package's type-check.
  • In this repo there is no twin pin covering the key: repo-wide, ViewNavigationConfig occurs
    only in packages/console/CHANGELOG.md (a release-owned file), and the tracked
    sdui.manifest.json declares navigation as a flat object input with no drill into view,
    so check:react-declaration-parity is 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 console paths filter (.objectui-sha plus four scripts), none of which this diff touches;
and it runs scripts/build-console.sh — a build, which does not type-check the sibling's
tsconfig.test.json. objectui's src at the pinned SHA contains no value write of
navigation.view outside that test file (only docblocks), and reads of .view still compile
against never | undefined. So this PR does not turn main red.

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 a
published 17.5.0 is in range.

File surface

Every path touched, including the pre-declared OPEN set.

path why
packages/spec/src/ui/view.zod.ts the retiredKey() tombstone + its prescription constant
packages/spec/src/migrations/entries/retired-keys/18.ui__NavigationConfig__view.ts new — RETIRED_KEYS_BY_MAJOR[18] registration (build-schemas gate (b))
packages/spec/src/migrations/entries/semantic/18.list-view-navigation-view-retired.ts new — the ADR-0087 D3 semantic entry
packages/spec/src/ui/view.test.ts the pins: refusal at three doors, plus the lit controls
packages/spec/src/migrations/registry.ts OPEN set — generated by gen:migration-registry, never hand-edited
.changeset/16885-retire-navigation-view.md OPEN set — minor, **BREAKING** banner, FROM -> TO, ADR-0087 disposition
packages/spec/authorable-surface/ui.json OPEN set — regenerated by build
content/docs/references/ui/view.mdx OPEN set — regenerated by gen:docs
content/docs/references/api/protocol.mdx OPEN set — regenerated by gen:docs (the inline navigation summary drops view?: string)
content/docs/references/data/object.mdx OPEN set — same, one row

Held by other in-flight work and not touched: packages/spec/scripts/build-schemas.ts (#17969),
packages/spec/liveness/** and packages/spec/scripts/liveness/check-liveness.mts (#18582),
packages/spec/src/api/protocol.zod.ts (#18597). Ruling item 2 was already discharged — PR #17796
reads 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-inputs reaches opposite verdicts on an identical source tree
depending 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 from
packages/cli/test/init-created-files-summary.e2e.test.ts, which this diff does not touch. No
pnpm build step precedes pnpm check:cross-package-test-inputs in lint.yml's lint job, so
CI 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:零拉力即移除。

风险与代价(含回滚) —— 风险低。行为面没有回归可言:这个键从来没选中过任何东西,决定详情怎么
呈现的是 modesize,两者一字未动,并且有 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

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>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:ui tests tooling labels Sep 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 8 documentable anchor(s). ⚠️ 2 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/src/migrations/entries/retired-keys/18.ui__NavigationConfig__view.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/plugin-endpoints.mdx (via /api/v1/meta/view (route, a path literal in acceptanceCriteria; a path literal in semantic))
  • content/docs/concepts/metadata-lifecycle.mdx (via /api/v1/meta/view (route, a path literal in acceptanceCriteria; a path literal in semantic))
  • content/docs/kernel/services-checklist.mdx (via /api/v1/meta/view (route, a path literal in acceptanceCriteria; a path literal in semantic))
  • content/docs/protocol/objectui/index.mdx (via /api/v1/meta/view (route, a path literal in acceptanceCriteria; a path literal in semantic))
  • content/docs/protocol/objectui/layout-dsl.mdx (via summary_view (literal, a string literal in NavigationConfigSchema))
  • content/docs/ui/forms.mdx (via /api/v1/meta/view (route, a path literal in acceptanceCriteria; a path literal in semantic))
What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/src/migrations/entries/retired-keys/18.ui__NavigationConfig__view.ts) — pages documenting those are invisible to this run
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 21b7c12b4f779b86409db2a7bb9235fe80ef8a54packageMentionDocs.

Which tree this was computed on

This run read content/docs from a693aa4e1bac6118d3280c0d95fc8fa996c4593c — the merge of head 650c7938a962900518041f8aac7279e056166367 into base 21b7c12b4f779b86409db2a7bb9235fe80ef8a54, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 21b7c12b4f779b86409db2a7bb9235fe80ef8a54 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-bill os-bill added domain:spec priority:p2 Medium: important, M3 labels Sep 17, 2026 — with Claude
@os-bill
os-bill marked this pull request as ready for review September 17, 2026 09:38
@os-bill
os-bill added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 3d8779d Sep 17, 2026
45 checks passed
@os-bill
os-bill deleted the claude/issue-16885-retire-navigation-view branch September 17, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation domain:spec priority:p2 Medium: important, M3 protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

List view navigation.view is declared in spec but resolves no form view — its only read lands it in the onNavigate navigation-MODE argument

2 participants