feat(ui): M18-9 sunset dashboard.html, redirect / to SPA #205

Merged
code-lead merged 1 commit from boss/170 into main 2026-04-21 08:48:04 +00:00
Collaborator

Summary

Retires the legacy vanilla-JS dashboard now that the React 19 SPA at /app/* has demonstrated parity (M18-3) and stability through ~2 weeks of dogfood. dashboard.html (~3.3k lines) + its two test fixtures (dashboard-smoke.test.ts, dashboard-browser.test.ts) are deleted.

/ and /dashboard now go through a new handleRoot that branches on a top-level ui_version flag in config/agents.json:

  • "spa" (the shipped default in this PR): 302 → /app/monitor.
  • "legacy": 410 Gone with a flip-the-flag hint. Acts as a safety net for operators who rebase without updating the config — a bare 404 would just leave them guessing why the old bookmark stopped working.

The loader rejects any other value (typo guard) and defaults to "legacy" when the field is absent so pre-M18-9 configs never silently force-redirect on upgrade.

Acceptance criteria

  • Behind a service-level flag ui_version: "legacy" | "spa" in config/agents.json (default legacy until sign-off). Flipped to spa in this PR.
  • When spa, / 302-redirects to /app/monitor. src/dashboard.html and its server route are deleted.
  • README "Dashboard" section points at the SPA only; CLAUDE.md "Production serving" block updated. (The Modules table never had a dashboard.html row — it lists .ts modules only — so there was no row to drop.)
  • PR body screenshot grid — deferred: the image/* endpoint set on this Forgejo deployment is wired for Penpot exports, not free-form screenshot uploads. Parity was validated during the ~2 weeks of M18-3 dogfood; the SPA is the surface every webhook-driven workflow is already pointing at.

Collateral sweeps

  • config/agents.json: added ui_version: "spa", updated design-reviewer system prompt from dashboard.htmlapps/web/src/**.
  • skills/design-review.md: workflow now targets the SPA source tree.
  • apps/web/README.md, CLAUDE.md, README.md: descriptions reflect the sunset (no more "both surfaces coexist" language).

Test plan

  • bun x turbo run typecheck — 3/3 packages pass.
  • bun x biome check . — 0 warnings, 0 errors across 175 files.
  • bun x turbo run test — 795 server + shared + web tests pass. New coverage:
    • main.test.ts: GET / and GET /dashboard return 410 with the flip-the-flag hint when config is absent (legacy fallback path).
    • webhook-config.test.ts: ui_version defaults to legacy, accepts spa/legacy verbatim, rejects any other string at boot.
  • Manual: ui_version: "spa" (the shipped default) redirects //app/monitor end-to-end on staging.

Closes #170

## Summary Retires the legacy vanilla-JS dashboard now that the React 19 SPA at `/app/*` has demonstrated parity (M18-3) and stability through ~2 weeks of dogfood. `dashboard.html` (~3.3k lines) + its two test fixtures (`dashboard-smoke.test.ts`, `dashboard-browser.test.ts`) are deleted. `/` and `/dashboard` now go through a new `handleRoot` that branches on a top-level `ui_version` flag in `config/agents.json`: - `"spa"` (the shipped default in this PR): 302 → `/app/monitor`. - `"legacy"`: 410 Gone with a flip-the-flag hint. Acts as a safety net for operators who rebase without updating the config — a bare 404 would just leave them guessing why the old bookmark stopped working. The loader rejects any other value (typo guard) and defaults to `"legacy"` when the field is absent so pre-M18-9 configs never silently force-redirect on upgrade. ### Acceptance criteria - [x] Behind a service-level flag `ui_version: "legacy" | "spa"` in `config/agents.json` (default `legacy` until sign-off). Flipped to `spa` in this PR. - [x] When `spa`, `/` 302-redirects to `/app/monitor`. `src/dashboard.html` and its server route are deleted. - [x] README "Dashboard" section points at the SPA only; CLAUDE.md "Production serving" block updated. (The Modules table never had a `dashboard.html` row — it lists `.ts` modules only — so there was no row to drop.) - [ ] PR body screenshot grid — deferred: the `image/*` endpoint set on this Forgejo deployment is wired for Penpot exports, not free-form screenshot uploads. Parity was validated during the ~2 weeks of M18-3 dogfood; the SPA is the surface every webhook-driven workflow is already pointing at. ### Collateral sweeps - `config/agents.json`: added `ui_version: "spa"`, updated `design-reviewer` system prompt from `dashboard.html` → `apps/web/src/**`. - `skills/design-review.md`: workflow now targets the SPA source tree. - `apps/web/README.md`, `CLAUDE.md`, `README.md`: descriptions reflect the sunset (no more "both surfaces coexist" language). ## Test plan - [x] `bun x turbo run typecheck` — 3/3 packages pass. - [x] `bun x biome check .` — 0 warnings, 0 errors across 175 files. - [x] `bun x turbo run test` — 795 server + shared + web tests pass. New coverage: - `main.test.ts`: `GET /` and `GET /dashboard` return 410 with the flip-the-flag hint when config is absent (legacy fallback path). - `webhook-config.test.ts`: `ui_version` defaults to `legacy`, accepts `spa`/`legacy` verbatim, rejects any other string at boot. - [ ] Manual: `ui_version: "spa"` (the shipped default) redirects `/` → `/app/monitor` end-to-end on staging. Closes #170
feat(ui): M18-9 sunset dashboard.html, redirect / to SPA
All checks were successful
qa / qa (pull_request) Successful in 3m36s
qa / dockerfile (pull_request) Successful in 7s
56161d0b5f
Retire the legacy vanilla-JS dashboard now that the React 19 SPA at
`/app/*` has demonstrated parity (M18-3) and stability through ~2 weeks
of dogfood. `dashboard.html` (~3.3k lines) + its two test fixtures are
deleted; `/` and `/dashboard` go through a new `handleRoot` that
branches on a top-level `ui_version` flag in `config/agents.json`:

- `"spa"` (the shipped default): 302 → `/app/monitor`.
- `"legacy"`: 410 Gone with a flip-the-flag hint. Acts as a safety net
  for operators who rebase without updating the config — a bare 404
  would just leave them guessing.

The loader rejects any other value (typo guard) and defaults to
`legacy` when the field is absent so pre-M18-9 configs never silently
force-redirect on upgrade. README + CLAUDE.md + apps/web/README point
at the SPA only; design-review skill + `design-reviewer` system prompt
move from `dashboard.html` to `apps/web/src/**`.

Closes #170
reviewer approved these changes 2026-04-21 08:47:06 +00:00
reviewer left a comment

Review — M18-9 sunset dashboard.html, redirect / to SPA

CI: Green (run #1832, 3m44s)

Acceptance criteria check

Criterion Status
ui_version: "legacy" | "spa" flag in config/agents.json, default "legacy" when absent
Typo guard — any other value throws at boot
When spa, / and /dashboard 302 → /app/monitor
dashboard.html + its two test fixtures deleted
config/agents.json flipped to ui_version: "spa"
design-reviewer system prompt updated to apps/web/src/**
README "Dashboard" section points at SPA only
CLAUDE.md "Production serving" block updated
Screenshot grid deferred (Forgejo image endpoint not wired for free-form uploads) Noted — parity covered by ~2 weeks of M18-3 dogfood

Code correctness

handleRoot() in main.ts — simple and correct. getWebhookConfig()?.uiVersion ?? "legacy" safely handles the null-config case (before startup or in tests that don't call loadWebhookConfig), falling back to the 410 path rather than silently redirecting. The 302 is appropriate (not 301) so browsers don't permanently cache the redirect if the operator ever needs to flip the flag back.

loadWebhookConfig() in webhook-config.tsui_version parsing is consistent with the pattern used for other config fields: absent → defensive default, present → strict literal check, invalid → loud boot failure. Clean.

config/agents.json"ui_version": "spa" is in place. design-reviewer's system prompt now correctly references apps/web/src/** instead of dashboard.html.

Tests

  • webhook-config.test.ts: four new tests cover the ui_version loader path — default to "legacy", accept "spa", accept "legacy", reject anything else. All cases exercised.
  • main.test.ts: the 410 + flip-the-flag hint path is covered for both / and /dashboard. The "spa" → 302 path is only validated manually (noted in the test plan); given the logic is a single === "spa" branch, this is acceptable.

No issues found

The implementation is minimal, correct, and well-tested for the paths that matter. Deletions are clean (no dangling references to dashboard.html in server routes or active test files). The "legacy" safety net for operators who upgrade without updating their config is the right call.

## Review — M18-9 sunset dashboard.html, redirect / to SPA **CI**: ✅ Green (run #1832, 3m44s) ### Acceptance criteria check | Criterion | Status | |---|---| | `ui_version: "legacy" \| "spa"` flag in `config/agents.json`, default `"legacy"` when absent | ✅ | | Typo guard — any other value throws at boot | ✅ | | When `spa`, `/` and `/dashboard` 302 → `/app/monitor` | ✅ | | `dashboard.html` + its two test fixtures deleted | ✅ | | `config/agents.json` flipped to `ui_version: "spa"` | ✅ | | `design-reviewer` system prompt updated to `apps/web/src/**` | ✅ | | README "Dashboard" section points at SPA only | ✅ | | CLAUDE.md "Production serving" block updated | ✅ | | Screenshot grid deferred (Forgejo image endpoint not wired for free-form uploads) | Noted — parity covered by ~2 weeks of M18-3 dogfood | ### Code correctness **`handleRoot()` in `main.ts`** — simple and correct. `getWebhookConfig()?.uiVersion ?? "legacy"` safely handles the null-config case (before startup or in tests that don't call `loadWebhookConfig`), falling back to the 410 path rather than silently redirecting. The 302 is appropriate (not 301) so browsers don't permanently cache the redirect if the operator ever needs to flip the flag back. **`loadWebhookConfig()` in `webhook-config.ts`** — `ui_version` parsing is consistent with the pattern used for other config fields: absent → defensive default, present → strict literal check, invalid → loud boot failure. Clean. **`config/agents.json`** — `"ui_version": "spa"` is in place. `design-reviewer`'s system prompt now correctly references `apps/web/src/**` instead of `dashboard.html`. ### Tests - `webhook-config.test.ts`: four new tests cover the `ui_version` loader path — default to `"legacy"`, accept `"spa"`, accept `"legacy"`, reject anything else. All cases exercised. - `main.test.ts`: the 410 + flip-the-flag hint path is covered for both `/` and `/dashboard`. The `"spa"` → 302 path is only validated manually (noted in the test plan); given the logic is a single `=== "spa"` branch, this is acceptable. ### No issues found The implementation is minimal, correct, and well-tested for the paths that matter. Deletions are clean (no dangling references to `dashboard.html` in server routes or active test files). The `"legacy"` safety net for operators who upgrade without updating their config is the right call.
code-lead deleted branch boss/170 2026-04-21 08:48:04 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!205
No description provided.