Skip to content

T-CW4K-scaffold-dashboard-vite-vue

Status: closed/done · Impact: high · Complexity: medium

The dashboard UI is a ~180-line HTML/CSS/JS string (INDEX_HTML) in server.ts — past the limit of maintainability. D-0013-dashboard-app picks Vite + Vue 3 to replace it. This task lays the first stone: an apps/dashboard Vite + Vue 3 SPA shell plus the one-command dev loop that brings the Bun/Hono backend and the Vite dev server up together. It delivers D-0013’s first v1 success criterion — easy backend+frontend dev startup — and unblocks the build-into-plugin pipeline (T-JDEV), the generated client (T-JZL4), and the four-source port (T-UUMK).

LocationRole today
plugin/lib/services/dashboard/server.ts#makeAppHono app: GET / serves INDEX_HTML; GET /api/state and POST /api/refresh are the JSON API
plugin/lib/services/dashboard/server.ts#INDEX_HTMLThe entire UI as an embedded HTML string — fetches /api/state, renders tables, polls on an interval
plugin/lib/services/dashboard/server.ts#buildStateAssembles the /api/state payload (tasks, leases, milestones, summary)
plugin/lib/services/dashboard/dashboard-service.tsdefineService wiring sdlc dashboard {start,stop,list}; start detaches a backgrounded server on a chosen port
plugin/lib/services/dashboard/dashboard-service-main.tsDetached-child entry: parses args and runs the foreground server loop
justfileTask-runner recipes (fmt-markdown, check-markdown); no dev recipe yet
package.jsonSubstrate package; Hono is a dependency; no Vite/Vue toolchain

apps/ does not exist yet — T-RVMG creates the workspace and the apps//packages/ trees this app lands in.

A Vite + Vue 3 SPA shell lives at apps/dashboard/: vue-router for routing, a single root view that fetches GET /api/state through a relative /api base and renders a minimal proof-of-life (e.g. project root + ticket count from summary.task_total). The Vite dev server proxies /api to the running Bun/Hono backend, so the relative base works identically in dev and (later) when the built SPA is served same-origin by the existing server.

One command starts both processes together: a dev task (a moon task in apps/dashboard/moon.yml and a just dev recipe that invokes it) runs sdlc dashboard start (backend) and vite (dev server) concurrently. vite build produces a static apps/dashboard/dist/. The existing INDEX_HTML and GET / route are untouched — retiring them is T-UUMK/T-JDEV work.

  1. Confirm the apps/ tree and moon are present (from T-RVMG). Create apps/dashboard/.
  2. Scaffold the Vite + Vue 3 SPA. Add apps/dashboard/package.json (devDeps: vite, @vitejs/plugin-vue, vue, vue-router, typescript; scripts: dev, build, preview), apps/dashboard/index.html (Vite entry mounting #app), apps/dashboard/src/main.ts (creates the Vue app + router, mounts to #app), and apps/dashboard/src/App.vue (root component). Optionally add unplugin-auto-import per D-0013 — record the decision inline if adopted; the shell does not require it.
  3. Configure the dev proxy. In apps/dashboard/vite.config.ts register @vitejs/plugin-vue and a server.proxy entry mapping /api to the backend origin. The backend binds an ephemeral port (server.ts pickFreePort), so the proxy target must be resolvable — pin a fixed dev port by passing --port to sdlc dashboard start in the dev task and point the proxy at http://127.0.0.1:<that-port>.
  4. Wire the proof-of-life fetch. In App.vue, on mount fetch('/api/state') (relative base) and render project_root plus summary.task_total. No four-source view — that is T-UUMK.
  5. Add the one-command dev loop. Create apps/dashboard/moon.yml with a dev task that runs the backend (sdlc dashboard start --port <fixed>) and vite concurrently. Add a just dev recipe to the root justfile that invokes the moon dev task (moon run dashboard:dev), so both just dev and the moon task are valid single-command entry points.
  6. Verify the build. Confirm vite build (via moon run dashboard:build) emits apps/dashboard/dist/index.html and assets.
LocationKindChange
apps/dashboard/package.jsonnewSPA package manifest: Vite/Vue/vue-router devDeps; dev/build/preview scripts
apps/dashboard/vite.config.tsnewVite config: @vitejs/plugin-vue + server.proxy mapping /api to the dev backend port
apps/dashboard/index.htmlnewVite HTML entry mounting #app, loading src/main.ts
apps/dashboard/src/main.tsnewCreates the Vue app + vue-router, mounts to #app
apps/dashboard/src/App.vuenewRoot component: fetches /api/state and renders proof-of-life
apps/dashboard/moon.ymlnewmoon project: dev task (backend + vite concurrently), build task (vite build)
justfilemodifyAdd a dev recipe invoking moon run dashboard:dev
  • AC-1: One command (just dev, which runs moon run dashboard:dev) starts BOTH the Bun/Hono backend (sdlc dashboard start) and the Vite dev server together.
  • AC-2: Loading the Vite dev server URL renders the SPA, which fetches GET /api/state through the relative /api base and displays at least the project root and ticket count from the live payload.
  • AC-3: The relative /api base resolves in dev via the Vite server.proxy entry — no hardcoded backend host/port in App.vue/main.ts.
  • AC-4: moon run dashboard:build (i.e. vite build) produces apps/dashboard/dist/index.html plus bundled assets.
  • Building the SPA into the plugin and the web-dist/ drift gate → T-JDEV.
  • The generated typed API client → T-JZL4; this shell uses a plain relative fetch.
  • Porting the full four-source dashboard view (working state, GitHub PRs) → T-UUMK; only a minimal proof-of-life fetch of /api/state here.
  • Retiring INDEX_HTML and repointing GET / in server.ts — left intact; later milestone work.
  • T-RVMG — needs the apps/ tree and the moon workspace runner in place before apps/dashboard/ and its moon.yml can be added.

Captured by /sdlc:task-work on 2026-06-19. PR: pending.

  • AC-1: agent-manual — ran moon run dashboard:dev (the target just dev wraps); the dev log showed BOTH up: backend on 127.0.0.1:6174 and Vite ready on :5173.
  • AC-2: agent-manual (data path) / deferred-user (visual render) — curl through the Vite proxy returned the live /api/state payload (project_root + summary.task_total=318); App.vue compiles and fetches on mount. Eyeballing the two rendered values in a browser at http://localhost:5173/ is the remaining human step.
  • AC-3: agent-manual — curl http://localhost:5173/api/state (relative base via Vite server.proxy) returned the backend payload; App.vue/main.ts carry no host/port literals.
  • AC-4: agent-manual — moon run dashboard:build produced apps/dashboard/dist/index.html plus dist/assets/index-*.{js,css}.
  • The moon apps/* discovery glob auto-registered apps/dashboard as project dashboard — no .moon/workspace.yml edit needed.
  • sdlc dashboard start detaching and returning made the one-command dev loop a simple sequence (start backend, then vite in the foreground) — no concurrency helper required.
  • The baseline-diff gate correctly classified all 9 pre-existing rumdl MD013 findings in an untouched file as pre-existing.
  • moon version mismatch blocked every moon task — the proto shim resolved a global moon 2.0.0-rc.0, which fails the workspace versionConstraint: '>=1.41.0' (a -rc prerelease doesn’t satisfy a stable range). Fixed by adding a root .prototools pinning moon = "1.41.8". The pin arguably belongs in T-RVMG / D-0012-monorepo-tooling workspace setup rather than in this app task. → T-RVMG-adopt-moon-workspace-runner
  • moon system toolchain doesn’t add node_modules/.bin to PATH — a bare vite task command fails with exit 127; had to invoke bunx vite. A note in the moon-workspace conventions (or a toolchain that injects .bin) would prevent rediscovery. → T-RVMG-adopt-moon-workspace-runner
  • sdlc dashboard start --help omits the --port / --host / --interval / --no-network flags that parseStartArgs accepts — --port was confirmed working only by reading the parser. The generated help is a docs gap. → dashboard-start-help-lists-flags
  • The baseline-diff quality gate treats rumdl’s summary line (Found N issues in X/Y files) as a finding; when the branch base moves under parallel WIP the issue-count changes (11→9) and surfaces a spurious new-drift=1 even though the branch touches no docs//plugin/ files. normalizeFinding masks timing/port/PID tokens but not the issue-count token in a summary line. → T-BCNP-quality-gate-ignores-summary-and-corpus-lines
  • The lint-state-origin pre-commit hook false-positives when origin/main lags local main: parallel sessions’ state-only verify/start commits that sit on local main (ahead of origin/main) appear as task-branch violations, because _lint_state_origin_core.ts compares each task/* branch against origin/main only — not against local main too, contrary to its own docstring. Required pushing main mid-flow to clear it before any commit-on-main could land. → T-6R73-pre-commit-drift-hooks-gate-unconditionally-forcing-no

T-RVMG


← Back to Tasks