Skip to content

feat(core): share one HTML shell between --reporter html and the live dashboard - #231

Merged
oekazuma merged 2 commits into
mainfrom
feat/shared-html-report-shell
Jul 15, 2026
Merged

oekazuma merged 2 commits into
mainfrom
feat/shared-html-report-shell

Conversation

@oekazuma

@oekazuma oekazuma commented Jul 15, 2026 •

Copy link
Copy Markdown
Owner

Summary

svelte-vitals --reporter html had drifted from the live dashboard: the 2026-07-09 vite-ui redesign explicitly accepted the divergence ("Tradeoff, stated explicitly" in docs/superpowers/specs/2026-07-09-vite-ui-vitest-parity-design.md), and since then the dashboard gained the master/detail layout, search/sort, dark mode, the wordmark brand, and the per-finding AI Prompt while the static report kept the old single-page design.

This reinstates the original shared-renderer architecture — one renderer behind both surfaces — with the dashboard's UI as the shared one:

  • packages/core/src/reporter/app-shell.ts (new; assembled from the vite dashboard's style/script/renderer, not rewritten): renderAppShell(snapshot) emits the same document for both surfaces. snapshot.live gates the SSE wiring, the /data.json refetch, and the connection/analyzing indicators, and picks the <title> (svelte-vitals dashboard vs svelte-vitals report).
  • buildHtmlDocument/formatHtmlReport keep their public signatures and now emit the shared shell with live: false — plus a badge clamp and server-side docsUrl sanitizing, since the client script renders hrefs straight from the embedded snapshot and buildHtmlDocument is a public API fed loosely-typed reports.
  • packages/vite deletes dashboard.ts/dashboard-script.ts/dashboard-style.ts (−583 lines) and serves core's shell with live: true. buildSnapshot keeps its own docsUrl sanitizing because /data.json responses bypass renderAppShell.
  • The static report gains everything the dashboard has — searchable/sortable route sidebar, severity/category filters, dark mode, AI Prompt copy button — while staying fully self-contained and offline.

Test plan

  • core 396 / cli 672 / vite 153 tests — all pass across the three packages
  • Core html-report.test.ts rewritten against the embedded-snapshot contract, porting every hardening guarantee (script-breakout via </script>, javascript: docsUrl stripped server-side, attacker-controlled category keys, badge clamping) plus a new byte-level parity test: live: true vs live: false documents are identical except the title and the flag
  • New packages/vite/test/app-shell-static.test.ts boots the real static document in jsdom and pins the static contract: full layout renders, no EventSource is ever constructed, no connection/analyzing indicator, AI Prompt still present
  • Existing dashboard behavior tests (SSE staleness guard, AI Prompt interactions, shell/XSS tests) all pass against the shared shell — the dashboard is behaviorally unchanged
  • Real CLI run: --reporter html output starts with the new shell, "live":false embedded, dv-app containers present
  • .d.ts size kept in check (APP_STYLE/APP_SCRIPT annotated : string so the giant template literals don't become literal types)
  • Docs: reporters.md (en/ja) HTML section now describes the shared UI; changeset covers core/cli/vite (all minor)

Note

packages/action/dist turned out not to need a rebuild: the action never imports the HTML reporter, so esbuild tree-shakes the shell out of its bundle entirely (verified by rebuilding — byte-identical output), and the cli's shared chunk hash is unchanged because core is an external dependency of the cli bundle. CI's "Verify action dist is up to date" gate passes as-is.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • HTML reports now share the live dashboard’s full interface, including searchable and sortable routes, filters, dark mode, and copyable AI prompts.
    • Reports are fully self-contained and work offline without live-update indicators.
    • Added safeguards to prevent unsafe links and embedded content from affecting reports.
  • Documentation

    • Updated English and Japanese reporter guides to describe the enhanced HTML report experience.

… dashboard

The 2026-07-09 vite-ui redesign explicitly accepted that the dashboard
and the CLI's --reporter html would diverge ("Tradeoff, stated
explicitly"), and they did: the dashboard gained the master/detail
layout, search/sort, dark mode, the wordmark, and the AI Prompt while
the static report kept the old single-page design.

This reinstates the original shared-renderer architecture, but with the
dashboard's UI as the shared one. The shell (style + client script +
renderer + snapshot type) moves into core as reporter/app-shell.ts:

- renderAppShell(snapshot) emits the same document for both surfaces;
  snapshot.live gates the SSE wiring, /data.json refetch, and the
  connection/analyzing indicators, and picks the <title>.
- buildHtmlDocument/formatHtmlReport keep their public signatures and
  now emit the shared shell with live: false (plus a badge clamp and
  server-side docsUrl sanitizing, since the client renders hrefs from
  the embedded snapshot).
- packages/vite deletes dashboard{,-script,-style}.ts and serves
  core's shell with live: true; buildSnapshot keeps its own docsUrl
  sanitizing because /data.json responses bypass renderAppShell.
- Core html tests rewritten against the embedded-snapshot contract;
  a new vite jsdom test pins the static mode (no EventSource, no
  connection dot, AI Prompt still present).
@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b01df3a1-0cb1-4f16-869c-59ee8b2b423a

📥 Commits

Reviewing files that changed from the base of the PR and between b882c32 and 7929491.

📒 Files selected for processing (15)
  • .changeset/shared-html-report-shell.md
  • docs/src/content/docs/guides/reporters.md
  • docs/src/content/docs/ja/guides/reporters.md
  • packages/core/src/index.ts
  • packages/core/src/reporter/app-shell.ts
  • packages/core/src/reporter/html.ts
  • packages/core/test/html-report.test.ts
  • packages/vite/src/ui/dashboard-style.ts
  • packages/vite/src/ui/dashboard.ts
  • packages/vite/src/ui/middleware.ts
  • packages/vite/src/ui/snapshot.ts
  • packages/vite/test/app-shell-static.test.ts
  • packages/vite/test/dashboard-script-ai-prompt.test.ts
  • packages/vite/test/dashboard-script-staleness.test.ts
  • packages/vite/test/ui-dashboard.test.ts
💤 Files with no reviewable changes (2)
  • packages/vite/src/ui/dashboard-style.ts
  • packages/vite/src/ui/dashboard.ts

📝 Walkthrough

Walkthrough

The static HTML reporter and Vite live dashboard now use a shared @svelte-vitals/core app shell, including unified rendering, embedded snapshots, styles, scripts, sanitization, and live/static behavior.

Changes

Shared app shell

Layer / File(s) Summary
Snapshot contract and HTML renderer
packages/core/src/reporter/app-shell.ts, packages/core/src/reporter/html.ts, packages/core/src/index.ts
Core adds AppSnapshot, RouteBadge, renderAppShell, APP_SCRIPT, and APP_STYLE; existing HTML helpers delegate to the shared renderer with sanitized embedded data and static/live-specific behavior.
Vite dashboard integration
packages/vite/src/ui/snapshot.ts, packages/vite/src/ui/middleware.ts
Vite snapshots use the shared contract, sanitize docsUrl values, mark snapshots live, and render through renderAppShell.
Rendering and security validation
packages/core/test/html-report.test.ts, packages/vite/test/app-shell-static.test.ts
Tests cover static dashboard rendering, live/static parity, embedded metadata, SSE suppression, badge validation, URL sanitization, and script-injection handling.
Dashboard test migration
packages/vite/test/dashboard-script-*.test.ts, packages/vite/test/ui-dashboard.test.ts
Existing dashboard tests use the shared client script and live snapshot shape.
Documentation and release metadata
docs/src/content/docs/guides/reporters.md, docs/src/content/docs/ja/guides/reporters.md, .changeset/shared-html-report-shell.md
Documentation and release notes describe the shared dashboard UI, offline static output, and new core exports.

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

Sequence Diagram(s)

sequenceDiagram
  participant Reporter
  participant CoreAppShell
  participant ViteMiddleware
  participant Browser
  Reporter->>CoreAppShell: buildHtmlDocument(report, meta)
  CoreAppShell->>Browser: self-contained static HTML
  ViteMiddleware->>CoreAppShell: renderAppShell(live snapshot)
  CoreAppShell->>Browser: live dashboard HTML
  Browser->>ViteMiddleware: EventSource updates when snapshot.live is true
Loading

Possibly related PRs

🚥 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 accurately summarizes the main change: sharing one HTML shell between the static HTML reporter and the live dashboard.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR re-centralizes the HTML UI rendering into @svelte-vitals/core so the CLI’s static --reporter html output and the Vite live dashboard share the same app shell and can’t drift in features/behavior.

Changes:

  • Introduces a new shared core renderer (renderAppShell) with embedded snapshot + inline style/script; static mode gates SSE/refetch/connection UI.
  • Updates @svelte-vitals/vite UI middleware and tests to use the shared core shell, deleting the Vite-local dashboard shell/style implementation.
  • Updates HTML reporter tests and docs to reflect the shared-shell contract and static-vs-live parity expectations.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/vite/test/ui-dashboard.test.ts Switches dashboard shell rendering to core renderAppShell; updates snapshot fixture with live: true.
packages/vite/test/dashboard-script-staleness.test.ts Uses core APP_SCRIPT; updates embedded snapshot fixture to include live: true.
packages/vite/test/dashboard-script-ai-prompt.test.ts Uses core APP_SCRIPT; updates embedded snapshot fixture to include live: true.
packages/vite/test/app-shell-static.test.ts Adds jsdom test ensuring static HTML report boots the shared shell without creating EventSource.
packages/vite/src/ui/snapshot.ts Aligns dashboard snapshot type to core AppSnapshot and ensures live snapshots set live: true.
packages/vite/src/ui/middleware.ts Serves the core renderAppShell output instead of the Vite-local dashboard renderer.
packages/vite/src/ui/dashboard.ts Deletes the Vite-local dashboard HTML shell renderer.
packages/vite/src/ui/dashboard-style.ts Deletes the Vite-local dashboard CSS (now sourced from core).
packages/core/test/html-report.test.ts Rewrites HTML report tests around the embedded snapshot + shared-shell parity guarantees.
packages/core/src/reporter/html.ts Keeps HTML primitives (escapeHtml, safeHref, etc.) and re-exports report entry points from app-shell.
packages/core/src/reporter/app-shell.ts Adds the shared app shell renderer, snapshot type, and static html reporter implementation.
packages/core/src/index.ts Exposes new shared-shell exports (renderAppShell, AppSnapshot, APP_SCRIPT, APP_STYLE, etc.).
docs/src/content/docs/guides/reporters.md Updates English docs to describe the shared UI between report and dashboard.
docs/src/content/docs/ja/guides/reporters.md Updates Japanese docs to describe the shared UI between report and dashboard.
.changeset/shared-html-report-shell.md Adds changeset for core/cli/vite minor release reflecting the shared renderer change.
Comments suppressed due to low confidence (2)

packages/core/src/reporter/app-shell.ts:711

  • renderAppShell sanitizes the embedded report (docsUrl) but does not clamp snapshot.badges. Since renderAppShell is now a public API and the client script uses badge values to build class names (dv-badge-${badge}) and visible text, an unexpected string can leak into the DOM and styling. Consider filtering badges to the known set (measured | static) here too, not only in buildHtmlDocument.
    packages/core/src/reporter/app-shell.ts:10
  • app-shell.ts imports safeHref from ./html.js, but html.ts re-exports buildHtmlDocument/formatHtmlReport from ./app-shell.js, creating a circular dependency (app-shell → html → app-shell). ESM often tolerates this, but it’s fragile for tooling/bundlers and makes future refactors riskier. Consider moving safeHref (and any shared helpers) into a small third module (e.g. html-utils.ts) that both html.ts and app-shell.ts import, so the dependency graph stays acyclic.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/core/test/html-report.test.ts Outdated
siteIssues: []
};
const html = buildHtmlDocument(evil, { version: '0' });
// embedJson escapes `<` to <, so the raw tag never exists in the document.
Copilot review on PR #231: embedJson replaces `<` with the literal
escape sequence < (the parsed JSON still contains the original
string), not with an HTML entity — say so.
@oekazuma
oekazuma merged commit b291361 into main Jul 15, 2026
7 checks passed
@oekazuma
oekazuma deleted the feat/shared-html-report-shell branch July 15, 2026 02:31
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.

2 participants