feat(lint): flag dead action/route references in dashboard header actions (#3367)#3383
Merged
Merged
Conversation
…ions Dashboard header `actions[]` and widget `actionUrl` can name a script/modal action or a url route that does not exist. Nothing in `os validate` / `os build` flags them, so they ship as buttons that render and silently do nothing when clicked — a false affordance. This applies ADR-0049's "declared ≠ enforced" gate to references rather than properties (#3367). New `validateDashboardActionRefs` rule in @objectstack/lint, wired into `os validate` and `os build`: - actionType 'script' | 'modal' → ERROR unless `actionUrl` resolves to a defined action (stack.actions or an object's actions). 'modal' also resolves via the runtime `<verb>_<object>` convention (create_/new_/add_/edit_/update_ + a real object) and bare object names, mirroring the objectui modalHandler. - actionType 'url' → WARNING when a relative in-app path names an objects/reports/dashboards/pages/views route absent from the stack. External, interpolated (${…}), and opaque routes are skipped to keep false positives near zero — the same conservative posture as lint-view-refs / capability-references. Covers dashboard `header.actions[]` and widget `actionUrl`. Fixture coverage for defined vs dangling script/modal targets, the modal convention, and resolved vs unresolved url routes, plus the #3367 repro. Docs + changeset included. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018KSiJuXDAi78CQD3Lv2TNJ
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 2 package(s): 19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
The `os build` sample output in cli.mdx enumerates each compile step; the new ADR-0049 dashboard action-reference check landed between the widget-binding and SDUI-styling steps, so list it there too. Addresses the docs-drift advisory on #3383. Docs-only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018KSiJuXDAi78CQD3Lv2TNJ
os-zhuang
marked this pull request as ready for review
July 21, 2026 14:33
…-header-refs-7zo1ut # Conflicts: # content/docs/getting-started/validating-metadata.mdx # packages/cli/src/commands/validate.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Closes #3367. Dashboard header
actions[]and widgetactionUrlcan reference ascript/modalaction or aurlroute that exists nowhere. Nothing inos validate/os buildflags them, so they ship as buttons that render and silently do nothing when clicked — a false affordance. This is ADR-0049's "declared ≠ enforced" gate applied to references rather than to properties.Nothing in the protocol schema can express this:
actionUrlis a free string, so{ actionType: 'script', actionUrl: 'export_dashboard_pdf' }parses and ships even when no such action is defined.Changes
@objectstack/lint— new purevalidateDashboardActionRefs(stack) => Finding[]rule (validate-dashboard-action-refs.ts, exported fromindex.ts). Walks every dashboardheader.actions[]and widgetactionUrl.@objectstack/cli— wired into bothos validateandos build(compile), errors gate / warnings advisory, mirroring the existingvalidateWidgetBindingsstep.validating-metadata.mdx: new "Dead action/route references" section, gate-table row, and step-output line.minorfor the fixed group.Resolution (mirrors the objectui runtime dispatch so lint flags exactly what would fail to resolve)
scriptactionUrlnames a defined action (stack.actionsor an object'sactions)modal<verb>_<object>convention (create_/new_/add_/edit_/update_+ a real object), or a bare object nameurlobjects/reports/dashboards/pages/viewsroute exists in the stackflow/apiExternal URLs (
http(s)://,//), interpolated targets (${…}), and opaque routes are skipped to keep false positives near zero — the same conservative posture as the siblinglint-view-refsandvalidate-capability-referencesrules.Severity rationale follows the issue's acceptance criteria: a dead
script/modaltarget fails validation (it fails open at runtime as a dead button); an unresolvedurlroute is at least warned (route resolution is app-context-dependent and a path may be served by another installed package or a host/console route).Acceptance criteria
script/modalaction fails validation, naming the dashboard, button label, and unresolved target.urlaction pointing at a non-existent in-app route is warned.<verb>_<object>against a real object) pass.Tests
@objectstack/lint— 15 new tests (all green); full package suite254 passed.@objectstack/cli— full suite602 passed; fulltscbuild + eslint clean.diston the Flag dead action/route references in dashboard header actions (ADR-0049 for references) #3367 repro:export_dashboard_pdf→ error,create_opportunity→ resolves via the modal convention (anopportunityobject exists),/reports/forecast→ warning.🤖 Generated with Claude Code
https://claude.ai/code/session_018KSiJuXDAi78CQD3Lv2TNJ
Generated by Claude Code