You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Found while working #6739, via a probe against defineStack. Filed separately — #6739 is one showcase corpus line; this is the validator's reach.
Blocked-by: #6739 — the content of the widened rule depends on #6739's contract answer (may a modal target name an object, or only a page?). The hole below is real under either answer.
What
packages/spec/src/stack.zod.ts:972-1006 validates action cross-references by iterating config.actions — the registered action list — only. Actions authored inline on a page element (element:button → properties.action, an InlineActionSchema) are never walked, so none of their targets are checked.
Measured
Probe: five minimal stacks, one defineStack call each, same object (probe_task) and page (probe_home) in all five.
A registered modal -> object : REJECTED -> Action 'probe_new_task' references page 'probe_task' (via modal target) which is not defined in pages.
B registered modal -> page : ACCEPTED
C registered modal -> nothing : REJECTED -> Action 'probe_new_task' references page 'probe_nowhere' (via modal target) which is not defined in pages.
D inline modal -> object : ACCEPTED
E inline modal -> nothing : ACCEPTED
Row E is the defect on its own terms: target: 'probe_nowhere' names neither a page nor an object nor anything else in the stack. Authored on a registered action it is a build error (row C). Authored inline on a button it builds clean, ships, and fails only when a user clicks — objectui's useActionModalmodalHandler answers
Modal target "probe_nowhere" matches no page or object — a modal action's `target` names the page to open.
…i.e. a silent-until-clicked dead button, which is exactly the class row C exists to prevent.
The D/A split is the second half, and is #6739's question rather than this one's: the same type + the same target string gets opposite verdicts depending only on where the action was declared.
Reproduction
Self-contained, no repo fixtures — defineStack from @objectstack/spec with a manifest, one object, one page, and the action authored each way. The inline shape is the one PR #6737 shipped for the showcase home CTA (examples/app-showcase/src/ui/pages/index.ts:95).
Why it matters
Inline actions are the shape the AI-authoring path emits most readily — a button with its behaviour written right there, no separate registry entry. That is precisely the surface that most needs authoring-time rejection, and it is the one surface the cross-reference walk does not visit. "Declared = enforced" currently holds for registered actions and not for inline ones.
flow targets have the same hole (the same loop checks action.type === 'flow'), so an inline flow action naming a nonexistent flow is also unvalidated.
Direction
Walk page/region/component trees for inline actions and subject them to the same cross-reference checks as registered ones — same rule, same message, one more traversal. What the modal branch should accept comes from #6739.
Classification: concrete validator gap with a five-stack measured probe (row E: a dangling inline type: 'modal' target builds clean and dies on click). Queueable defect.
Routing: the fix widens defineStack cross-reference validation in packages/spec/src/stack.zod.ts:972-1006 (verified on origin/main @ 20526f5: the walk iterates config.actions only). Widening makes previously-accepted stacks rejectable — an acceptance-face change, which is the seat directive's red line ⇒ domain:spec, regardless of diff size.
CLAIM + UNBLOCK — spec-lane PM seat (#6017), session session_01PiRUoQkTSBBmpyXBY3cVn2. Branch: claude/issue-6889-inline-action-crossref. Dispatching a local dev agent (model: Opus — the card's own Direction section specifies the shape: same rule, same message, one more traversal).
pm:blocked dropped: the recorded blocker #6739 closed completed 2026-08-10T04:16:36Z and its fix is merged on main (the showcase action is now type: 'form' + target: 'showcase_task.edit', per the maintainer-ruled resolution recorded there).
Direction per the card: walk page/region/component trees for inline (element:button → properties.action) actions and subject them to the same cross-reference checks as registered ones in defineStack (packages/spec/src/stack.zod.ts — re-anchor the walk by content, line numbers will have drifted). Row E (dangling target matching nothing) is the defect and is unambiguous under any contract answer. The flow-target hole rides the same traversal.
⚠️ Contract guard for the modal branch: what a modal target may name comes from #6739's ruling — read that card's comments and anchor the answer before coding. If its resolution did NOT settle the D/A split (registered modal→object is build-rejected today while objectui resolves page-first-then-object at runtime), mirror the registered-action rule exactly and STOP-and-report the residual question rather than inventing the contract. Acceptance-face narrowing ⇒ census the shipped corpus for inline actions that would newly refuse (list them in the PR; expected 0 after #6739's fix), changeset level judged from precedent, four-step regen if generated surfaces move.
Activity
claude commentedon Aug 9, 2026
Triage:
pm:queue+pm:blocked+domain:spec.type: 'modal'target builds clean and dies on click). Queueable defect.defineStackcross-reference validation inpackages/spec/src/stack.zod.ts:972-1006(verified onorigin/main@20526f5: the walk iteratesconfig.actionsonly). Widening makes previously-accepted stacks rejectable — an acceptance-face change, which is the seat directive's red line ⇒domain:spec, regardless of diff size.Blocked-by: #6739(verified present); showcase's registeredshowcase_new_taskaction targets a PAGE, so the global_nav "New Task" command opens the home page in a dialog instead of creating a task #6739 is openneeds-user-decision, and the widened rule's content (may a modal target name an object?) depends on that answer. The row-E hole is real under either answer, so the card queues now and unblocks on the ruling.showcase_new_taskaction targets a PAGE, so the global_nav "New Task" command opens the home page in a dialog instead of creating a task #6739 (target semantics — the D/A split half), fix(examples): showcase home page "Create Task" button now carries element:button's inline action #6737 (shipped the inline shape), Object-formparamson atype:'url'inline action is a THIRD meaning of the key (interpolation scope +newTab) with no authorized spelling (observation) #6828 (url-arm params — different key) — distinct; no duplicate.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 10, 2026
CLAIM + UNBLOCK — spec-lane PM seat (#6017), session
session_01PiRUoQkTSBBmpyXBY3cVn2. Branch:claude/issue-6889-inline-action-crossref. Dispatching a local dev agent (model: Opus — the card's own Direction section specifies the shape: same rule, same message, one more traversal).pm:blockeddropped: the recorded blocker #6739 closed completed 2026-08-10T04:16:36Z and its fix is merged onmain(the showcase action is nowtype: 'form'+target: 'showcase_task.edit', per the maintainer-ruled resolution recorded there).Direction per the card: walk page/region/component trees for inline (
element:button→properties.action) actions and subject them to the same cross-reference checks as registered ones indefineStack(packages/spec/src/stack.zod.ts— re-anchor the walk by content, line numbers will have drifted). Row E (dangling target matching nothing) is the defect and is unambiguous under any contract answer. Theflow-target hole rides the same traversal.targetmay name comes from #6739's ruling — read that card's comments and anchor the answer before coding. If its resolution did NOT settle the D/A split (registered modal→object is build-rejected today while objectui resolves page-first-then-object at runtime), mirror the registered-action rule exactly and STOP-and-report the residual question rather than inventing the contract. Acceptance-face narrowing ⇒ census the shipped corpus for inline actions that would newly refuse (list them in the PR; expected 0 after #6739's fix), changeset level judged from precedent, four-step regen if generated surfaces move.Generated by Claude Code
fix(spec): defineStack cross-reference validation reaches inline page…
fix(showcase): a `type: 'modal'` target names a page — `showcase_new_…
fix(spec): defineStack cross-reference validation reaches inline page…
fix(spec): defineStack cross-reference validation reaches object-embe…
test(spec): the second root-level test file group's titles state each…