Repository navigation
feat(spec): a blueprint nav entry can name the VIEW it opens (cloud#2150) - #16841
Conversation
…150) `BlueprintNavItemSchema` gets an optional `viewName`, and the OpenAI-strict mirror's `StrictNavItem` gets it in lockstep (required-but-nullable, per the strict convention). Until now the shape could only say which OBJECT a menu entry opens. A model that had just designed a kanban and wanted it in the app menu therefore had exactly one move left: emit a SECOND entry at the same `target` and carry the intent in `label`/`icon` alone. Both entries then opened the object's default view, and the cloud applier derives a nav id from the target, so the two collided — the user clicked 「工单看板」 and got the list. Nothing anywhere complained: the target object really does exist, so the dangling-target lint had nothing to say. The runtime nav item could always express this (`ObjectNavItemSchema.viewName`, "Default list view to open"), so the gap was the blueprint's alone and the duplicate entry was the reasonable move under the expressiveness the model was given. Both halves, deliberately. A key on the lenient side only is a fix on no path the product runs: `propose_blueprint` validates its structured output against the mirror, so the design step could never author a board entry while `apply_blueprint` accepted a key nothing emits — the `seedData` divergence (cloud#2118) with a different key name. `viewName` is deliberately un-regexed on BOTH sides, unlike `target`: a view answers to a bare key (`views[].name`) and to the qualified `<object>.<key>` a staged view record carries, and the applier normalizes between them. Constraining the leaf would make one spelling legal to generate and illegal to apply — the cloud#1967 shape, where an already-approved blueprint was refused wholesale. The strict↔lenient key-parity pin is widened one level further out (fields → objects → NAV ITEMS); ablating the mirror half fails four tests. Generated authorable-surface and the blueprint reference are regenerated. Part of objectstack-ai/cloud#2150. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 131 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 bef7a30604c2beb4432db92bdcc5d9031e735170 && git checkout bef7a30604c2beb4432db92bdcc5d9031e735170
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c5ea982d971cb2550d35a845694a6cbc2575ea52 597696d1bb1ec6913c532e762a733bdaa9952c64 && git checkout -B drift-repro c5ea982d971cb2550d35a845694a6cbc2575ea52 && git merge --no-ff 597696d1bb1ec6913c532e762a733bdaa9952c64
node scripts/docs-audit/affected-docs.mjs --json c5ea982d971cb2550d35a845694a6cbc2575ea52
|
…ope` (cloud#2150)
The empty-state gate refuses a permissive empty state nobody signed off on, and
the strict mirror's describe states one out loud ('or null for the object's
default list'). It is presentation breadth, not authorization: the entry's
`target` already fixes WHICH OBJECT is reachable, record- and field-level
security are enforced downstream, and the empty state is the behaviour every
existing app already has.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
What
BlueprintNavItemSchemagets an optionalviewName, and the OpenAI-strict mirror'sStrictNavItemgets it in lockstep (required-but-nullable, per the strict convention).Why
Until now the blueprint nav shape could only say which object an entry opens. A model that had just designed a kanban and wanted it in the app menu therefore had exactly one move left: emit a second entry at the same
target, with the intent carried bylabel/iconalone.Measured downstream (objectstack-ai/cloud#2150, a real tenant DB):
Two identical ids, both opening the object's default view. The kanban view (
frez_ticket.ticket_status_board) really was built — the menu just could not reach it. And nothing complained: the target object exists, so the dangling-target lint had nothing to say.The runtime could always express this —
ObjectNavItemSchema.viewNameis'Default list view to open. Defaults to "all". …'— so the gap was the blueprint's alone, and the duplicate entry was the reasonable move under the expressiveness the model was given.Both halves, deliberately
A key on the lenient side only is a fix on no path the product runs:
propose_blueprintvalidates its structured output against the mirror, so the design step could never author a board entry whileapply_blueprintaccepted a key nothing emits. That is theseedDatadivergence (cloud#2118) with a different key name.Ablation — reverting only the
StrictNavItemhalf and re-runningpackages/spec/src/ai/solution-blueprint.test.ts:Restored:
43 passed (43).Why no
SNAKE_CASEregexviewNameis deliberately un-regexed on both sides, unliketarget. A view answers to two interchangeable spellings — the bare key a blueprint'sviews[].namecarries, and the qualified<object>.<key>a staged view record'snamecarries — and the applier normalizes between them. Constraining the leaf would make one spelling legal to GENERATE and illegal to APPLY: the cloud#1967 shape, where an already-approved blueprint was refused wholesale at apply time. VALUE parity holds because neither side constrains it.Parity pin widened
Third time the same gap was found one level further out: fields → objects → nav items. The nav-level key-parity test now fails on the next divergence in either direction.
Verification
packages/specfull suite: 13328 passed | 23 skipped (13351)tsc --noEmit -p packages/spec: cleancheck:authorable-surface,check:docs: in sync (both regenerated in this commit —authorable-surface/ai.jsongains one key, the blueprint reference gains one row)Companion
The cloud half — passing
viewNamethrough to the runtime nav entry, and the nav-id derivation change that lets one object legitimately carry two entries — is objectstack-ai/cloud#2156 (draft, and it pins this branch head until this one merges). Part of objectstack-ai/cloud#2150.🤖 Generated with Claude Code