Skip to content

fix(lint): a dashboard header modal action's target resolves against declared pages, only (#9013) - #9077

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-9013-modal-target-page-only
Aug 16, 2026
Merged

fix(lint): a dashboard header modal action's target resolves against declared pages, only (#9013)#9077
os-project-manager merged 1 commit into
mainfrom
claude/issue-9013-modal-target-page-only

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#9013

What broke, and which way

validateDashboardActionRefs's resolveActionTarget accepted, for actionType: 'modal', a defined action name, a bare object name, and the verb_object prefix form (create_/new_/add_/edit_/update_ + a defined object) — on its docblock's claim that it mirrored the objectui runtime dispatch. That mirror was deleted: maintainer ruling objectstack#6739-A (2026-08-09) decided a type: 'modal' string target names a PAGE, only; objectui#4764 retired the object fallback in the shared useActionModal, and objectui#4782 (merged 2026-08-16T06:28:14Z) deleted DashboardView's own second copy of the prefix convention after enumerating both repos' corpora and finding zero producers.

The inversion ran both ways: os validate blessed buttons that now dispatch to a named refusal at runtime, and it ERRORED on a page-named modal target — the one shape the runtime serves (the old resolver consulted actions and objects, never stack.pages).

The changes (the card's three items, plus two named additions)

  1. packages/lint/src/validate-dashboard-action-refs.ts — for actionType: 'modal', a string target resolves only against declared pages (stack.pages). MODAL_VERB_RE is deleted; the docblock's mirror claim is rewritten to cite the post-metadata/loaders/database-loader.test.ts 的 mock driver supports 写满 off-spec 键(aggregations/nestedObjects/geoQueries/changeStreams…)—— 从未与 DriverCapabilities 对上 #4782 dispatch; the modal refusal message/hint now name the page contract and point at actionType: 'form' with an object.view form-view target (mirroring objectui's shared modalTargetRefusalMessage wording). script resolution is unchanged.
  2. packages/lint/src/validate-dashboard-action-refs.test.ts — the pinning tests flip: the three retired shapes (defined action name, verb_object against a real object, bare object name) are now pinned as refusals, and a new test pins that a declared-page target resolves. The Flag dead action/route references in dashboard header actions (ADR-0049 for references) #3367 repro keeps its 2-errors-1-warning shape with its comment corrected.
  3. content/docs/deployment/validating-metadata.mdx — the example no longer marks create_opportunity (verb-prefix form) as valid; it now shows it as an error with the prescription, plus a valid declared-page target.
  4. packages/lint/src/authoring-rules.ts (same file surface, same defect class) — the rule's registration comment still described "a header or widget action ... resolves to no defined action"; corrected to the script/action, modal/page split.
  5. ADR-0087 semantic entry dashboard-header-modal-target-page-only (packages/spec/src/migrations/entries/semantic/18.… + regenerated registry.ts). This is the changeset machinery's own requirement, not scope creep: the changeset below is declared BREAKING and carries a FROM → TO prescription, so the only honest check:adr-0087-registration disposition is registered. spec-changes.json and the upgrade guide are byte-unchanged by design — step-18 entries project into those artifacts at the major cut (same as analytics-authorable-unknown-keys-refused).

Changeset — real, minor, declared BREAKING

@objectstack/lint is published and this narrows the accept set of a gating os validate rule, so no skip-changeset. Bump is minor per the post-v17-cut lockstep convention (check-changeset-no-major.mjs pushes breaking changes to minor outside the launch window; precedent: analytics-authorable-unknown-keys-refused), with a **BREAKING** declaration, the FROM → TO, and adr-0087: registered dashboard-header-modal-target-page-only. @objectstack/spec is bumped too because the ledger entry lands in its shipped registry.

Proof the narrowing refuses (reverse verification, direction decided in advance)

Predicted: against the origin/main resolver, exactly 4 tests red — 3 because the old code accepts the retired shapes, 1 because the old code refuses a page-named target; the #3367 repro green in both regimes (its stack declares neither page nor object, dead either way). Measured exactly that (fix committed first; old source stood up via git restore --source=origin/main, then restored from HEAD):

× passes a modal action that names a declared page
× ERRORS on a modal action that names a defined action — an action is not a page
× ERRORS on the retired verb_object convention, even against a real object
× ERRORS on a bare object name — the create-form fallback is retired
Test Files 1 failed | 72 passed (73) · Tests 4 failed | 2057 passed

Restored at HEAD: 73 passed (73) · 2061 passed | 4 skipped.

Verification at 25bd737b5 (the union ran after the final commit, clean tree)

  • pnpm --filter @objectstack/lint test — 73 files / 2061 passed; typecheck clean.
  • pnpm --filter @objectstack/spec test — 406 files / 10743 passed; typecheck clean; check:generated — all 13 artifacts current (check:migration-registry, check:spec-changes, check:upgrade-guide included).
  • Gate union derived via node scripts/pm/dispatch-gates.mjs over the real changed paths, all green at 25bd737b5: check:changeset-gate-self-tests, check:cross-package-test-inputs, check:doc-formula-expressions (lint), check:docs-audit-scope, check:merge-driver, check:objectui-changeset, check:role-word, check:spec-parsed-alias, check:type-source-resolution, check-adr-0087-registration, check-changeset-no-major, check-dev-prereqs (after the full packages-closure build, as lint.yml runs it), check-empty-changeset, check:nul-bytes, plus the test-file convention gates check:query-options-erasure, check:type-check-coverage, check:type-check-debt (re-measure, no drift), check:engine-double-contract, check:where-matcher.
  • Consumption-radius sweep: no other fixture or rule spells the retired shapes — the only remaining verb_object mentions are immutable CHANGELOG history; other create_opportunity hits are unrelated (a flow name, a convert param, a translation fixture).

Corpus note: no dashboard in this repo's examples/apps authors header.actions[] at all (matching objectui#4782's enumeration), so no fixture re-spelling was needed outside the rule's own tests.


Generated by Claude Code

…pages, only (#9013)
validateDashboardActionRefs mirrored DashboardView's deleted modal dispatch:
it accepted a defined action name, a bare object name, and the verb_object
prefix form for actionType:'modal', and errored on page-named targets. Under
maintainer ruling objectstack#6739-A (retired at runtime by objectui#4764 +
objectui#4782), a modal string target names a PAGE, only — the rule now
resolves modal targets against stack.pages, the pinning tests pin both
directions, the validating-metadata docs example demonstrates the ruled
shape, and the change registers ADR-0087 semantic entry
dashboard-header-modal-target-page-only.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011RB4waLuNbdruCo6X9oobm
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec.

106 hand-written doc(s) reference the affected code — list omitted above 15 rows. Re-derive: node scripts/docs-audit/affected-docs.mjs --json origin/main.

7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@os-project-manager@claude