T-JDEV-dashboard-build-into-plugin
Status: closed/superseded · Impact: high · Complexity: large
Ship the built dashboard SPA inside the plugin as a committed, generated, drift-gated artifact at
plugin/lib/services/dashboard/web-dist/, so consumers get a working dashboard with no build step
(D-0013-dashboard-app §Decision.2; preserves the D-0006-typescript-substrate run-from-source
model). This task wires the build-and-ship pipeline — a moon build task, a
sdlc dashboard build --check drift gate mirroring sdlc docs generate site --check
(D-0010-deterministic-site-assembly), and repointing GET / at the committed bundle — retiring
the ~180-line INDEX_HTML string that has reached its maintainability limit.
| Location | Role today |
|---|---|
plugin/lib/services/dashboard/server.ts#INDEX_HTML | ~180-line HTML/CSS/JS string (lines 805–982); the entire UI, parameterized via __INTERVAL__. |
plugin/lib/services/dashboard/server.ts#makeApp | Hono app factory (lines 403–452); GET / returns INDEX_HTML (410–415), GET /api/state + POST /api/refresh serve JSON, app.all("*") 404s. |
plugin/lib/services/dashboard/ | Bun/Hono service: server.ts (body), dashboard-service.ts (defineService ["dashboard"] → {start,stop,list}), no ops/ dir, no web-dist/. |
plugin/lib/services/docs/ops/generate.ts#handler | The --check drift-gate model: checkArtifacts re-renders without writing, throws NAMESPACE_CONFLICT (exit 7) naming drifted targets; clean tree exits 0. |
plugin/lib/services/docs/generate.ts#checkArtifacts | Compares fresh-rendered bytes against the committed file; emits a DriftFinding per changed target. The byte-equality pattern the build gate mirrors. |
lefthook.yml | Pre-commit hooks; sibling PR chore/docs-drift-gate adds project-check-docs-drift running bun plugin/cli/sdlc.ts docs generate --check. No web-dist gate. |
sdlc.yaml#quality_checks | CI gate list; sibling PR adds bun run plugin/cli/sdlc.ts docs generate --check. No web-dist gate. |
No repo-root .gitattributes exists yet (no linguist-generated convention).
apps/dashboard/ is scaffolded by T-CW4K (dependency); its vite build
output (apps/dashboard/dist) is this task’s input.
Proposed
Section titled “Proposed”plugin/lib/services/dashboard/web-dist/exists and is committed: the staticvite buildoutput ofapps/dashboard(index.html+ hashedassets/), copied verbatim into the plugin tree.- A moon build task on
apps/dashboardrunsvite buildthen copiesdist/→ the pluginweb-dist/(clean-replace, deterministic). sdlc dashboard buildregeneratesweb-dist/from a fresh build;sdlc dashboard build --checkperforms a fresh build into a temp dir, compares it byte-for-byte against the committedweb-dist/, and exits non-zero (naming drifted files) when they differ — mirroringsdlc docs generate site --check. A clean tree exits 0.lefthook.ymlruns the--checkgate at pre-commit (aproject-check-dashboard-drifthook, paralleling the siblingproject-check-docs-drift);sdlc.yaml#quality_checksruns it in CI.web-dist/**is markedlinguist-generatedin a repo-root.gitattributes.INDEX_HTMLand the__INTERVAL__substitution are gone fromserver.ts.GET /servesweb-dist/index.html; asset requests serve hashed files fromweb-dist/; any other unmatched non-/apiGET falls back toweb-dist/index.html(client-side routing). The JSON API (GET /api/state,POST /api/refresh) and theapp.all("*")404 for non-GET //apimisses are unchanged.- A consumer who installs by symlink and never builds gets a working dashboard from the committed
web-dist/.
Approach
Section titled “Approach”- Add a
dashboard buildop module atplugin/lib/services/dashboard/ops/build.ts(path["dashboard","build"],defineOp). The handler resolvesapps/dashboardand the pluginweb-dist/underctx.projectRoot. Default behavior: runvite build(via the moon task, see step 2), then clean-replaceweb-dist/with the freshdist/(clear stale files, copy new). Add a--checkboolean input. - Add the moon build task to
apps/dashboard/moon.yml(scaffolded byT-CW4K):vite build→dist, then copydist/intoplugin/lib/services/dashboard/web-dist/. The op shells out to the moon task so the build command lives in one place (the deterministic-tail pattern, like thefmt-markdownjustfile recipe). Decision deferred to implementation: whether the copy-into-plugin step is a moon task action or lives in the op — pick whichever keeps the build command single-sourced. - Implement
--checkmirroringdocs/ops/generate.ts#handlerandcheckArtifacts: build into a temp dir, walk the produced file set and the committedweb-dist/file set, compare bytes per file, collect a drift finding per created/updated/deleted file. On non-empty drift, throwOpError("NAMESPACE_CONFLICT", …)(exit 7) naming the drifted files and the fix (run sdlc dashboard build and commit); clean exits 0. Write nothing under--check. - Retire
INDEX_HTML: delete theexport const INDEX_HTMLblock (server.ts 805–982) and the embedded__INTERVAL__token. RepointmakeApp’sGET /(server.ts 410–415) to read and serveweb-dist/index.html. Add an asset route serving files underweb-dist/(correct content types), and an SPA fallback: any unmatched GET whose path is not under/apiservesweb-dist/index.htmlwith 200. KeepGET /api/state,POST /api/refresh, and theapp.all("*")404 (now reached only by non-GET requests and/apimisses). Theintervalparam no longer threads through the served HTML — the SPA owns its own polling cadence (T-UUMK); drop it from the/path only. - Run the moon build task once to populate
web-dist/; commit the bundle. Add the repo-root.gitattributesentryplugin/lib/services/dashboard/web-dist/** linguist-generated. - Wire the gate: add
project-check-dashboard-drifttolefthook.ymlpre-commit runningbun plugin/cli/sdlc.ts dashboard build --check, and append the same tosdlc.yaml#quality_checks(paralleling the siblingchore/docs-drift-gatewiring).
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/services/dashboard/ops/build.ts | new | dashboard build op + --check drift gate (mirrors docs generate). |
plugin/lib/services/dashboard/web-dist/ | new | Committed vite build output (index.html + assets/). |
.gitattributes | new | plugin/lib/services/dashboard/web-dist/** linguist-generated. |
plugin/lib/services/dashboard/server.ts#INDEX_HTML | delete | Remove the ~180-line UI string and __INTERVAL__ token. |
plugin/lib/services/dashboard/server.ts#makeApp | modify | GET / + asset routes serve web-dist/; SPA fallback for unmatched non-/api GETs; JSON API + 404 unchanged. |
lefthook.yml | modify | Add project-check-dashboard-drift running dashboard build --check. |
sdlc.yaml | modify | Append bun run plugin/cli/sdlc.ts dashboard build --check to quality_checks. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
GET /on a running dashboard serves the built SPA fromplugin/lib/services/dashboard/web-dist/index.html(not the oldINDEX_HTML). - AC-2: A deep-link GET (e.g.
/tasks/T-JDEV, no matching asset, not under/api) returns 200 withweb-dist/index.html(SPA fallback); a hashed asset GET serves the matchingweb-dist/assets/*file. - AC-3:
grep -n INDEX_HTML plugin/lib/services/dashboard/server.tsreturns no match (the string and__INTERVAL__token are gone). - AC-4:
GET /api/stateandPOST /api/refreshbehave exactly as before (JSON bodies, status codes,--no-network409 on refresh). - AC-5:
sdlc dashboard build --checkexits non-zero (exit 7) and names the drifted files when committedweb-dist/differs from a fresh build; exits 0 when they match. - AC-6:
git check-attr linguist-generated plugin/lib/services/dashboard/web-dist/index.htmlreportsset(the bundle is marked generated). - AC-7: From a clean checkout with no build run, starting the dashboard serves a working UI from
the committed
web-dist/(no consumer build step required). - AC-8:
lefthook.ymlandsdlc.yaml#quality_checksboth invokedashboard build --check.
Out of scope
Section titled “Out of scope”- The SPA’s features and the four-source view — Vite/Vue scaffold is
T-CW4K; porting the dashboard view (active leases, tickets, milestones, local working state, GitHub PRs) to the SPA isT-UUMK. - Runtime bundle-vs-API version drift (a
web-dist/built against an older API than the server serving it) — the commit-time--checkgate cannot catch it; deferred per D-0013-dashboard-app §Open questions. - Localhost-bind hardening and a
POST /api/refreshguard — D-0013-dashboard-app §Open questions. - Net-new JSON API sources (local working state, GitHub PR status) — added with the view in
T-UUMK.
Dependencies
Section titled “Dependencies”T-CW4K— scaffoldsapps/dashboard(Vite + Vue 3). This task’s build task andweb-dist/have nothing to build until that app exists; hard dependency, recorded in frontmatterdepends_on:.
Depends on
Section titled “Depends on”T-CW4K