fix(spec): the strict blueprint nav item's label describe states that null inherits the target's current label - #21309
Conversation
…null-inherits rule StrictNavItem.label read only "Nav entry label, or null", so the design model that generates against this mirror was never told that null is the choice that inherits the target's CURRENT label and follows renames. It now states the lenient twin's rule in the strict spelling, and a pin beside the viewName lockstep pin holds the two describes to one rule. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…real file Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 138 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 31cd87cc57285fa055a94535065385890e8d7b09 && git checkout 31cd87cc57285fa055a94535065385890e8d7b09
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 393ae878d3d52fe843c56b4621c004b934dcf853 f7fee5be0ec20b5124a5261fb1260d154a3e65c6 && git checkout -B drift-repro 393ae878d3d52fe843c56b4621c004b934dcf853 && git merge --no-ff f7fee5be0ec20b5124a5261fb1260d154a3e65c6
node scripts/docs-audit/affected-docs.mjs --json 393ae878d3d52fe843c56b4621c004b934dcf853 |
Contract reviewServed-tier: Isolated reviewer for card #21248 / PR #21309. Inputs: the card body and its three comments (triage 5941498298, claim 5944357787, dev report 5945274529), the PR body and file list, the net diff against ① Derived judgmentsAccept set and public surface: unchanged. Judged right.
The describe is true at the renderer. Judged right.
The two describes say one rule, and the pin holds sameness. Judged right.
Steering. Judged right.
No applier-side text matching, no new gate. Verified on the diff: no applier lives in this repo and the diff touches no script or workflow; the pin is a unit test in the package's own suite, which the triage direction itself asked for. ② Semver level
③ Boundary flagsDev deviations, each answered:
Independence: the diff was produced by the dev subagent on the branch below; this record is rendered by an isolated reviewer and adopted by the dispatching session named below. Implemented-by: VERDICT: PASS Check-runs on this head at the final read (2026-10-02T04:13Z): 35 check-runs, all concluded; 32 success, 3 skipped by path (Build Docs, Console Pin Gate, Packed-tarball smoke), none failed, none running. The two families ③.2 left NOT MEASURED locally concluded success in CI: Lint & Repo Gates (the full |
Fixes #21248
Clause-②: no
What changed
StrictNavItem.labelinpackages/spec/src/ai/solution-blueprint.zod.tsis the describe the AI design step reads.propose_blueprint's structured output is generated againstSolutionBlueprintStrictSchema, and strict mode makeslabela required decision on every nav entry. Until now it read'Nav entry label, or null'. It now reads:absentthere,nullhere) and the written arm (presentthere,a stringhere) say the same thing on both sides.BlueprintNavItemSchema.labelis unchanged. The strict side adds the closing steer the triage direction names.z.string().nullable(), so the schema accepts and refuses the same blueprints.viewName.packages/spec/src/ai/solution-blueprint.test.ts, placed in thestrict mirror ↔ lenient schema — key parityblock right after theviewNamepin (states ONE label rule on both sides …). It reads both navlabeldescribes the same way theviewNamepin reads both nav shapes. It extracts what each side's empty spelling and written spelling mean, and asserts the two sides say the same thing. It pins sameness, not wording, so both sides can be reworded together..changeset/21248-strict-nav-label-describe.md:@objectstack/specpatch,Clause-②: no.⛔ No applier-side text matching. ⛔ No new gate.
Premise checks (on
origin/main4e6dc2338a):372read'Nav entry label, or null'; the lenient twin is at:189..objectui-shapin31971ff1e28f. Inpackages/layout/src/NavigationRenderer.tsx,resolveNavItemLabelreturnsinheritedNavItemLabel(item, targetLabel)whenitem.label === undefined. The fallback order is: the view's label (when the entry names a labelled view), then the object's or dashboard's label, then the machine name. The label is asked of the host's metadata on every render, so "a renamed target shows its new name on the next render". A present string renders verbatim. The new describe states only that. The strictnullreaches the renderer as an absent label through the null strip that this file's own strict-mirror header documents ("the blueprint tools strip those nulls").viewNamelockstep pin is a KEY-parity pin, not a describe pin. It consists ofcarries viewName …andthe NAV ITEM schemas carry exactly the same keys. No describe-text pin existed before. The new pin follows its style and sits beside it.Generated artefacts
After
pnpm --filter @objectstack/spec build,pnpm --filter @objectstack/spec check:generatedreported all 15 artefacts up to date. No tracked artefact carries this describe. The reference page rendersSolutionBlueprintStrict.apponly one level deep (navshows asobject[]), so the strict nav item's describe was never oncontent/docs/references/ai/solution-blueprint.mdx, before or after this change. The only copy is the gitignoredpackages/spec/json-schema/ai/SolutionBlueprintStrict.json, which carries the new text after the build.Verification (final head
f7fee5be0e)pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ai/solution-blueprint.test.ts: 44 passed (43 before, plus the new pin).pnpm --filter @objectstack/spec test: exit 0. Test Files 597 passed (597); Tests 17475 passed, 1 todo.pnpm --filter @objectstack/spec typecheck: exit 0.check:test-typecheck: OK, 52 test files compiled.scripts/ablation-replace.mjsin wrap mode, which verifies the write on disk and restores against HEAD. The test imports./solution-blueprint.zodby relative path, so nodist/sits on the resolution path and no rebuild was needed.'Nav entry label, or null'. The pin went red withexpected undefined to be defined(1 failed, 43 passed). The file was restored: blob9fad23cd1314equals HEAD andgit diff HEADis empty.absent ⇒ the entry inherits the CURRENT labeltoabsent ⇒ the entry copies the target label. The pin went red withexpected { …(2) } to deeply equal { …(2) }. The file was restored the same way.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths) derived 83 commands atf7fee5be0e. All 83 ran and exited 0.--ranreconciliation: 83 derived, 83 run, 0 NOT-MEASURED, 0 UNRUN.check:doc-formula-expressions,check:lean-entry-closureandcheck:dual-build-cjs-loads. Each went green once its prerequisites were built (formula and lint; the objectql closure; the workspace build). The final pass ran with those builds in place.origin/mainmoved 15 commits after the branch point. Of the files it changed that the derivation reads,ci.ymlonly addsenv:to the Build Core build step.scripts/pm/issue-transfer.mjsandscripts/docs-audit/handwritten-docs.jsontouch none of this diff's paths.pnpm exec eslint --no-inline-config --format jsonover the two touched.tsfiles: 2 files, 0 errors, 0 warnings. ESLint's ownisPathIgnoredreturns false for both.calculateConfigForFileshows neitherparserOptions.projectnorprojectService, so type-aware linting is off and this diff cannot change any verdict on an untouched file. The fullpnpm lintis CI's.test:repo(the specrepovitest project), narrowed. I ran the 4 of its 48 files that read the blueprint source or describes.scripts/solution-blueprint-header-row.test.ts: 4 passed.scripts/escape-mdx.test.ts,scripts/query-pointer-row.test.tsandsrc/api/rest-api-config-dead-keys-retirement.test.ts: 39 passed.scripts/build-schemas-check-mode.test.tsand the fulltest:repoproject. Reason: that file spawnsbuild-schemas.tsrepeatedly, and neither run finished inside a 480 s bound on the shared box. Left to CI.Acceptance notes
StrictNavItem.viewName's describe, which this PR leaves unchanged, illustrates its rule with entries named by their labels ("a 「工单列表」 entry with viewName null plus a 「工单看板」 entry …"). This is a labelled example on the same nav item the model fills, and the source report counts labelled examples among the inputs that steer the model toward writing labels. No wrong answer has been measured from it. carrier: the cloud seat's golden-journey measurement, after cloud's pin carries this change.Generated artefacts touched: none tracked (see above). Diff: 3 files, describe text, one test and one changeset.
Generated by Claude Code