Skip to content

feat(vite): whole-project analysis in the dev dashboard - #152

Merged
oekazuma merged 3 commits into
mainfrom
advisor/021-dev-dashboard-whole-project
Jul 8, 2026
Merged

oekazuma merged 3 commits into
mainfrom
advisor/021-dev-dashboard-whole-project

Conversation

@oekazuma

@oekazuma oekazuma commented Jul 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

Implements plan plans/021-dev-dashboard-whole-project.md (design doc included in this PR: docs/superpowers/specs/2026-07-08-dev-dashboard-whole-project-design.md).

The live dashboard (svelteVitals({ ui: true })) previously showed only routes you had physically visited, covered only SEO <head> rules, and computed Health over visited routes — the deliberate v1 boundaries of the original live-UI design. This PR integrates the CLI's whole-project static analysis into the dev server, so the dashboard now shows all routes and all five categories with a real project Health from the moment vite dev starts:

  • Startup analysis — configureServer kicks off analyzeProject asynchronously (never blocking dev-server startup); results land in a new static layer of the findings store and fan out over the existing SSE channel.
  • Auto re-analysis — server.watcher events under src/, static/, or on svelte.config.* / svelte-vitals.config.* (excluding node_modules/.svelte-kit/build/dist) trigger a debounced (500 ms) re-run; changes arriving mid-run coalesce into exactly one follow-up.
  • Live refinement — visiting a page still ingests rendered-page results, which now override the static results per route, scoped to the rule ids present in the live payload (a rendered page is closer to the truth, especially for dynamic values). Component-scoped, site-wide, and unvisited-route findings keep their static values.
  • Provenance badges — route headings show measured (live) vs static, via a new optional routeBadges argument on core's buildHtmlDocument. With the option omitted the output is byte-identical (pinned by test), so the CLI's --reporter html is untouched.

Architecture notes

  • @svelte-vitals/vite now depends on svelte-vitals (workspace) — the same precedent as @svelte-vitals/mcp, which already imports analyzeProject. The import is a dynamic import inside the dev-only ui plugin (grep for a static import is a done criterion), so build-mode usage loads none of the CLI package.
  • The findings store is now created by the ui plugin and shared between the analysis runner and the ingest middleware; merge logic is extracted as pure composeSnapshot/composeBadges functions.
  • Analysis failures are contained: console.warn and the dashboard falls back to today's live-only behavior. runner.stop() rides the existing http-server close cleanup.

Verification

  • pnpm typecheck, pnpm test (947 tests: core 382 / cli 437 / vite 115 / mcp 13), pnpm lint — all green
  • 31 new vite tests + 5 new core tests, including an integration test that runs the real analyzeProject (through the real dynamic import) against a new SvelteKit fixture: unvisited routes and multiple categories appear with zero page visits; a live ingest replaces exactly the matching rule ids on that route and flips its badge; unvisited/site findings are deep-equal untouched
  • Debounce/coalescing pinned with fake timers (N rapid changes → 1 run; mid-run change → exactly one follow-up)
  • Docs: dev-overlay guide rewritten (en/ja); changeset: minor for @svelte-vitals/vite and @svelte-vitals/core

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • The dev dashboard now shows an initial whole-project view as soon as vite dev starts, then updates in place as you work.
    • Route headings can now display status badges showing whether results are from the initial scan or live activity.
  • Bug Fixes

    • Dashboard updates now stay responsive during file changes and fall back safely to live-only behavior if a full scan fails.
  • Documentation

    • Updated the dev dashboard guides to describe the new startup, refresh, and badge behavior.

oekazuma added 2 commits July 9, 2026 00:26
buildHtmlDocument gains an optional third argument
(opts?: { routeBadges?: Record<string, 'measured' | 'static'> }) that renders a
provenance badge on route headings, for the dev dashboard's measured/static
distinction. Unknown badge values are dropped, and output is byte-identical to
before when the option is omitted (pinned by test). formatHtmlReport (the CLI
--reporter html path) is unchanged.
The live dashboard (svelteVitals({ ui: true })) now integrates the CLI's
whole-project static analysis (analyzeProject from svelte-vitals, loaded via
dynamic import inside the dev-only ui plugin so build mode never touches it):

- one async analysis at dev-server startup (never blocking), showing all
  routes and all categories with a real project Health before any page visit
- debounced (500ms) re-analysis on relevant source changes via server.watcher,
  with mid-run changes coalescing into a single follow-up run
- the store gains a static layer; live (rendered) results replace static
  results per route by the payload's rule-id set, while site-wide findings and
  unvisited routes keep the static result
- route headings carry a measured/static provenance badge (core's routeBadges)
- analysis failures warn and fall back to the previous live-only behavior

Design doc: docs/superpowers/specs/2026-07-08-dev-dashboard-whole-project-design.md
(plan 021).
@coderabbitai

coderabbitai Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Failed to post review comments.

GitHub was unavailable or timed out while CodeRabbit was posting the review. Please request a new review later if the pull request still needs one. Use @coderabbitai full review to retry the review.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bb13bbbb-bbe8-41eb-8af2-08e0d355aa63

📥 Commits

Reviewing files that changed from the base of the PR and between f474ebf and 3ec049a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • .changeset/dev-dashboard-whole-project.md
  • docs/src/content/docs/guides/dev-overlay.md
  • docs/src/content/docs/ja/guides/dev-overlay.md
  • docs/superpowers/specs/2026-07-08-dev-dashboard-whole-project-design.md
  • packages/core/src/reporter/html.ts
  • packages/core/test/html-report.test.ts
  • packages/vite/package.json
  • packages/vite/src/plugin.ts
  • packages/vite/src/ui/analysis.ts
  • packages/vite/src/ui/middleware.ts
  • packages/vite/src/ui/serve.ts
  • packages/vite/src/ui/store.ts
  • packages/vite/test/fixtures/basic-project/package.json
  • packages/vite/test/fixtures/basic-project/src/app.html
  • packages/vite/test/fixtures/basic-project/src/routes/+page.svelte
  • packages/vite/test/fixtures/basic-project/src/routes/about/+page.svelte
  • packages/vite/test/ui-analysis.test.ts
  • packages/vite/test/ui-integration.test.ts
  • packages/vite/test/ui-middleware.test.ts
  • packages/vite/test/ui-plugin.test.ts
  • packages/vite/test/ui-store.test.ts
  • packages/vite/test/ui-watch-relevance.test.ts
⚠️ CI failures not shown inline (3)

GitHub Actions: CI / test (26): feat(vite): whole-project analysis in the dev dashboard

Conclusion: failure

View job details

##[group]Run if [ -n "$NODE_VERSION_OVERRIDE" ]; then
 �[36;1mif [ -n "$NODE_VERSION_OVERRIDE" ]; then�[0m
 �[36;1m  echo "node-version=$NODE_VERSION_OVERRIDE" >> "$GITHUB_OUTPUT"�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1mNODE_VERSION=$(jq -r '.devEngines.runtime.version' package.json)�[0m
 �[36;1mif [ -z "$NODE_VERSION" ] || [ "$NODE_VERSION" = "null" ]; then�[0m
 �[36;1m  echo "::error::Could not extract devEngines.runtime.version from package.json"�[0m

GitHub Actions: CI / docs: feat(vite): whole-project analysis in the dev dashboard

Conclusion: failure

View job details

##[group]Run if [ -n "$NODE_VERSION_OVERRIDE" ]; then
 �[36;1mif [ -n "$NODE_VERSION_OVERRIDE" ]; then�[0m
 �[36;1m  echo "node-version=$NODE_VERSION_OVERRIDE" >> "$GITHUB_OUTPUT"�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1mNODE_VERSION=$(jq -r '.devEngines.runtime.version' package.json)�[0m
 �[36;1mif [ -z "$NODE_VERSION" ] || [ "$NODE_VERSION" = "null" ]; then�[0m
 �[36;1m  echo "::error::Could not extract devEngines.runtime.version from package.json"�[0m

GitHub Actions: CI / 3_docs.txt: feat(vite): whole-project analysis in the dev dashboard

Conclusion: failure

View job details

##[group]Run if [ -n "$NODE_VERSION_OVERRIDE" ]; then
 �[36;1mif [ -n "$NODE_VERSION_OVERRIDE" ]; then�[0m
 �[36;1m  echo "node-version=$NODE_VERSION_OVERRIDE" >> "$GITHUB_OUTPUT"�[0m
 �[36;1m  exit 0�[0m
 �[36;1mfi�[0m
 �[36;1mNODE_VERSION=$(jq -r '.devEngines.runtime.version' package.json)�[0m
 �[36;1mif [ -z "$NODE_VERSION" ] || [ "$NODE_VERSION" = "null" ]; then�[0m
 �[36;1m  echo "::error::Could not extract devEngines.runtime.version from package.json"�[0m
🧰 Additional context used
📓 Path-based instructions (2)
packages/core/src/**

📄 CodeRabbit inference engine (packages/core/CLAUDE.md)

In @svelte-vitals/core, keep all code in src/ runtime-agnostic: do not use node: imports, I/O, or runtime-specific globals; inject all I/O through Runtime from src/runtime.ts.

Files:

  • packages/core/src/reporter/html.ts
packages/core/src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

In packages/core, keep the core runtime-agnostic: do not add node: imports, direct I/O calls, or runtime-specific globals; all I/O must go through the Runtime interface.

Files:

  • packages/core/src/reporter/html.ts
🧠 Learnings (4)
📚 Learning: 2026-06-23T06:24:01.179Z
Learnt from: oekazuma
Repo: oekazuma/svelte-vitals PR: 44
File: docs/src/content/docs/rules/seo008.md:18-30
Timestamp: 2026-06-23T06:24:01.179Z
Learning: In Svelte/SvelteKit, the contents of a `<script>` tag are emitted verbatim as raw text. For JSON-LD (`<script type="application/ld+json">`), do not use interpolations like `{JSON.stringify(...)}` (it will appear literally) and do not use `{html}` to try to serialize/insert JSON. Instead, write the JSON-LD as literal JSON directly inside the `<script>` tag so the output is valid JSON-LD.

Applied to files:

  • packages/vite/test/fixtures/basic-project/src/routes/+page.svelte
  • packages/vite/test/fixtures/basic-project/src/routes/about/+page.svelte
📚 Learning: 2026-06-26T01:49:08.908Z
Learnt from: oekazuma
Repo: oekazuma/svelte-vitals PR: 55
File: docs/superpowers/specs/2026-06-26-seo-jsonld-validation-design.md:0-0
Timestamp: 2026-06-26T01:49:08.908Z
Learning: When reviewing the repository’s SEO016 JSON-LD validation for Svelte components, treat `<script type="application/ld+json">...</script>` bodies as HTML/DOM raw text (not as evaluated template content). Do not skip/ignore script bodies just because they contain `{html ...}` or `{JSON.stringify(...)}`-style template expressions—those are emitted/captured verbatim and will cause JSON parsing to fail. Only skip the script body when it is empty or whitespace-only; otherwise run JSON validation against the literal text content.

Applied to files:

  • packages/vite/test/fixtures/basic-project/src/routes/+page.svelte
  • packages/vite/test/fixtures/basic-project/src/routes/about/+page.svelte
📚 Learning: 2026-06-25T06:18:06.885Z
Learnt from: oekazuma
Repo: oekazuma/svelte-vitals PR: 51
File: .changeset/perf-resource-hints.md:2-6
Timestamp: 2026-06-25T06:18:06.885Z
Learning: In the svelte-vitals monorepo, when `svelte-vitals/mcp` introduces newly added rule(s) and surfaces them via its `analyze` and `explain_rule` APIs, the corresponding changeset/version update for `svelte-vitals/mcp` should be a `minor` bump (to stay consistent with `svelte-vitals` and `svelte-vitals/vite`). Do not rely only on a cascade/patch bump to propagate the change.

Applied to files:

  • .changeset/dev-dashboard-whole-project.md
📚 Learning: 2026-06-23T06:23:57.565Z
Learnt from: oekazuma
Repo: oekazuma/svelte-vitals PR: 44
File: docs/package.json:1-20
Timestamp: 2026-06-23T06:23:57.565Z
Learning: In oekazuma/svelte-vitals, GitHub Actions steps in `.github/workflows/setup-node` and `deploy-docs.yml` extract `devEngines.runtime.version` via `jq -r '.devEngines.runtime.version' package.json` from the **repository root**. Because the `path: docs` for `withastro/action` only affects where the Astro site is built (not the Node extraction working directory), sub-workspace `package.json` files (e.g., `docs/package.json`, and other nested packages) should generally **not** duplicate `devEngines.runtime.version`; keep the source of truth in the root `package.json`.

Applied to files:

  • packages/vite/test/fixtures/basic-project/package.json
  • packages/vite/package.json
🪛 GitHub Actions: CI / 0_check.txt
packages/vite/test/fixtures/basic-project/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json (jq returned empty/null).

packages/vite/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json (jq returned empty/null).

🪛 GitHub Actions: CI / 1_test (26).txt
packages/vite/test/fixtures/basic-project/package.json

[error] 1-1: Failed to extract devEngines.runtime.version from package.json. Error: "Could not extract devEngines.runtime.version from package.json"

packages/vite/package.json

[error] 1-1: Failed to extract devEngines.runtime.version from package.json. Error: "Could not extract devEngines.runtime.version from package.json"

🪛 GitHub Actions: CI / 2_test (24.16.0).txt
packages/vite/test/fixtures/basic-project/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json

packages/vite/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json

🪛 GitHub Actions: CI / 3_docs.txt
packages/vite/test/fixtures/basic-project/package.json

[error] 1-1: CI script failed to extract devEngines.runtime.version from package.json. Error emitted: "Could not extract devEngines.runtime.version from package.json"

packages/vite/package.json

[error] 1-1: CI script failed to extract devEngines.runtime.version from package.json. Error emitted: "Could not extract devEngines.runtime.version from package.json"

🪛 GitHub Actions: CI / 4_lint.txt
packages/vite/test/fixtures/basic-project/package.json

[error] 1-1: CI failed to extract devEngines.runtime.version from package.json (jq -r '.devEngines.runtime.version' package.json returned empty or null).

packages/vite/package.json

[error] 1-1: CI failed to extract devEngines.runtime.version from package.json (jq -r '.devEngines.runtime.version' package.json returned empty or null).

🪛 GitHub Actions: CI / 5_test (22.13.0).txt
packages/vite/test/fixtures/basic-project/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json (jq -r '.devEngines.runtime.version' package.json)

packages/vite/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json (jq -r '.devEngines.runtime.version' package.json)

🪛 GitHub Actions: CI / check
packages/vite/test/fixtures/basic-project/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json

packages/vite/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json

🪛 GitHub Actions: CI / docs
packages/vite/test/fixtures/basic-project/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json.

packages/vite/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json.

🪛 GitHub Actions: CI / lint
packages/vite/test/fixtures/basic-project/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json (jq command failed to return a value). Command: jq -r '.devEngines.runtime.version' package.json

packages/vite/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json (jq command failed to return a value). Command: jq -r '.devEngines.runtime.version' package.json

🪛 GitHub Actions: CI / test (22.13.0)
packages/vite/test/fixtures/basic-project/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json (jq -r '.devEngines.runtime.version' package.json returned empty or null).

packages/vite/package.json

[error] 1-1: Could not extract devEngines.runtime.version from package.json (jq -r '.devEngines.runtime.version' package.json returned empty or null).

🪛 GitHub Actions: CI / test (24.16.0)
packages/vite/test/fixtures/basic-project/package.json

[error] 1-1: CI step failed to extract devEngines.runtime.version from package.json. Message: "Could not extract devEngines.runtime.version from package.json".

packages/vite/package.json

[error] 1-1: CI step failed to extract devEngines.runtime.version from package.json. Message: "Could not extract devEngines.runtime.version from package.json".

🪛 LanguageTool
docs/src/content/docs/guides/dev-overlay.md

[style] ~80-~80: ‘On top of that’ might be wordy. Consider a shorter alternative.
Context: ...s, and the dashboard refreshes itself. On top of that static baseline, browsing your app refi...

(EN_WORDINESS_PREMIUM_ON_TOP_OF_THAT)

🪛 markdownlint-cli2 (0.22.1)
docs/superpowers/specs/2026-07-08-dev-dashboard-whole-project-design.md

[warning] 84-84: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

📝 Walkthrough

Walkthrough

Adds whole-project static analysis triggered at vite dev startup, refined by live per-route results, with a debounced analysis runner, a two-layer findings store (static + live) with merge/badge helpers, core HTML reporter provenance badges (measured/static), plugin/middleware wiring, fixtures, tests, and documentation updates.

Changes

Dev dashboard whole-project analysis

Layer / File(s) Summary
Core HTML reporter badge support
packages/core/src/reporter/html.ts, packages/core/test/html-report.test.ts
buildHtmlDocument/renderRoutes gain optional routeBadges, render clamped/escaped badge markup and CSS, with tests confirming byte-identical output when omitted.
Layered findings store
packages/vite/src/ui/store.ts, packages/vite/test/ui-store.test.ts
Store now tracks static and live layers separately with setStatic, badges, composeSnapshot, and composeBadges merging live results over static per route.
Analysis runner
packages/vite/src/ui/analysis.ts, packages/vite/test/ui-analysis.test.ts
createAnalysisRunner lazily imports analyzeProject, debounces notifyChange, coalesces in-flight runs, and reports results/errors via callbacks.
Plugin/middleware wiring
packages/vite/src/plugin.ts, packages/vite/src/ui/middleware.ts, packages/vite/src/ui/serve.ts, packages/vite/package.json, packages/vite/test/ui-middleware.test.ts, packages/vite/test/ui-plugin.test.ts, packages/vite/test/ui-watch-relevance.test.ts
configureServer creates the store and runner, filters watcher events via isRelevant, stops runner on shutdown, and forwards the store/badges through installUiMiddleware/renderDashboard.
Fixture and integration tests
packages/vite/test/fixtures/basic-project/*, packages/vite/test/ui-integration.test.ts
Adds a SvelteKit fixture project and integration tests validating static analysis, live ingest override, and badge transitions end-to-end.
Docs and design spec
.changeset/dev-dashboard-whole-project.md, docs/src/content/docs/guides/dev-overlay.md, docs/src/content/docs/ja/guides/dev-overlay.md, docs/superpowers/specs/2026-07-08-dev-dashboard-whole-project-design.md
Updates changelog and dev-overlay guides (EN/JA) and adds a design spec describing the whole-project analysis architecture and merge rules.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Vite as ViteDevServer
  participant Plugin as configureServer
  participant Runner as AnalysisRunner
  participant Store as FindingsStore
  participant Middleware as installUiMiddleware/Dashboard

  Vite->>Plugin: configureServer(server)
  Plugin->>Store: createStore()
  Plugin->>Runner: createAnalysisRunner(...)
  Plugin->>Middleware: installUiMiddleware(server, config, store)
  Runner->>Store: setStatic(initial results)
  Vite->>Plugin: watcher 'all' event (relevant file)
  Plugin->>Runner: notifyChange(file)
  Runner->>Runner: debounce & re-run analyzeProject
  Runner->>Store: setStatic(updated results)
  Middleware->>Store: set(route, liveResults) on page visit
  Middleware->>Store: snapshot() / badges()
  Middleware-->>Vite: renderDashboard(mergedResults, badges)
Loading

Possibly related PRs

  • oekazuma/svelte-vitals#47: Introduces the buildHtmlDocument/renderRoutes core HTML reporter that this PR extends with routeBadges.
  • oekazuma/svelte-vitals#49: Builds the svelteVitals({ ui: true }) live dashboard (store/middleware/serve) that this PR extends with static analysis and provenance badges.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: whole-project analysis in the Vite dev dashboard.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Resolves conflicts with PR #150 (core version drift visibility): coreVersion
threading and the routeBadges/store integration are combined — renderDashboard
and buildHtmlDocument accept both, installUiMiddleware takes (server, config,
version, store, coreVersion?). Dropped the now-obsolete docs paragraph claiming
component-scoped rules never appear in the dev overlay (the whole-project
static layer now surfaces them); kept the new Version drift section (en/ja).
@oekazuma

oekazuma commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@oekazuma

oekazuma commented Jul 8, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@oekazuma
oekazuma merged commit 2a7af4d into main Jul 8, 2026
7 checks passed
@oekazuma
oekazuma deleted the advisor/021-dev-dashboard-whole-project branch July 8, 2026 16:16
oekazuma added a commit that referenced this pull request Jul 10, 2026
… parallel session

021 (dev dashboard whole-project, PR #152) and 022 (MCP categories input,
PR #149; renumbered from 020 to resolve the collision with the parallel
session's 020-reusable-github-action plan). Index rows updated with merge
outcomes including the #150 conflict resolution.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant