Spec M4: cockpit-style web UI design pass
State-driven accent HUD, stacked bands, CSS/JS split out of the inline HTML, polished presentational review recap. No behavior changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
|||||||
|
# M4 — "Look good" Design
|
||||||
|
|
||||||
|
**Goal:** A real design pass on the web UI: a cockpit-style, state-aware HUD that
|
||||||
|
reads at a glance, with CSS/JS split out of the inline HTML for maintainability,
|
||||||
|
and a polished review recap. No behavior changes.
|
||||||
|
|
||||||
|
**Status:** Design approved 2026-05-31. Supersedes the utilitarian inline UI
|
||||||
|
shipped through M3.5.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Direction & Visual System
|
||||||
|
|
||||||
|
The UI is an **instrument panel you glance at** — a cockpit, not a document.
|
||||||
|
Dark, near-black cool-neutral base. State is carried by a **single state-driven
|
||||||
|
accent**: a CSS custom property `--accent` switched by a `data-state` attribute
|
||||||
|
on the `<main>` element. The accent colors the status band's top border and the
|
||||||
|
state pill, so the frame itself communicates where you are without reading text.
|
||||||
|
|
||||||
|
### State → accent mapping
|
||||||
|
|
||||||
|
| State | `data-state` | Accent |
|
||||||
|
|--------------------|--------------|--------------------|
|
||||||
|
| locked | `locked` | dim gray |
|
||||||
|
| planning | `planning` | blue |
|
||||||
|
| active · on-task | `active` | calm green / cyan |
|
||||||
|
| active · nudge | `nudge` | amber |
|
||||||
|
| active · drifting | `drift` | red |
|
||||||
|
| review | `review` | violet-neutral |
|
||||||
|
|
||||||
|
`data-state` is derived in the client render from `runtime_state` plus, when
|
||||||
|
active, the drift sub-status (`drifting` → `drift`; a present `nudge` → `nudge`;
|
||||||
|
otherwise `active`). This mirrors the precedence already used by the status-file
|
||||||
|
renderer (drift outranks nudge).
|
||||||
|
|
||||||
|
### Design tokens (CSS custom properties)
|
||||||
|
|
||||||
|
- Surfaces / text: `--bg`, `--panel`, `--line`, `--ink`, `--ink-dim`
|
||||||
|
- State accent: `--accent` (the only variable that changes with `data-state`)
|
||||||
|
- Fixed semantic colors: `--ok`, `--warn`, `--danger`
|
||||||
|
|
||||||
|
### Typography
|
||||||
|
|
||||||
|
- Timer: heavy weight, `font-variant-numeric: tabular-nums`.
|
||||||
|
- Evidence times: `ui-monospace` so the bucket columns align.
|
||||||
|
- Band headers / pills: small, uppercase, letter-spaced (keeps the existing pill
|
||||||
|
idiom from the current UI).
|
||||||
|
- Prose: `system-ui`.
|
||||||
|
|
||||||
|
## 2. Layout — Stacked HUD Bands
|
||||||
|
|
||||||
|
Every state composes the same **band primitive**: a row with a top divider and
|
||||||
|
consistent horizontal/vertical padding. Stacking bands produces the layered HUD
|
||||||
|
look. The active session follows the approved sketch:
|
||||||
|
|
||||||
|
```
|
||||||
|
ACTIVE · on task · 7 switches ← status band (accent border-top + pill)
|
||||||
|
24:18 write the spec section ← timer band
|
||||||
|
done when: draft saved ← task band
|
||||||
|
now code·spec ● | code 18:02 … ← evidence band
|
||||||
|
[ Complete ] ← action band
|
||||||
|
```
|
||||||
|
|
||||||
|
Drift and nudge are **not** a separate floating box. When the session drifts or
|
||||||
|
is nudged, the **status band itself** changes copy and `data-state` flips, so the
|
||||||
|
whole frame goes amber/red. The same controls render inside that band:
|
||||||
|
|
||||||
|
- Drift: `Back to task` (`/refocus`), `This is on task` (`/ontask`),
|
||||||
|
`End session` (`/complete`).
|
||||||
|
- Nudge: `Dismiss` (client-only, current behavior).
|
||||||
|
- Pending: a quiet "checking focus…" line.
|
||||||
|
|
||||||
|
## 3. Per-State Treatment
|
||||||
|
|
||||||
|
- **Locked:** one dim band, large `Start planning` button (`/planning`).
|
||||||
|
- **Planning:** an intent + `Sharpen` band, then field bands — Next action,
|
||||||
|
Success condition, Minutes, Allowed apps — with the blue accent. All existing
|
||||||
|
input ids (`#intent`, `#na`, `#sc`, `#mins`, `#apps`, `#start`,
|
||||||
|
`#coachStatus`) and the coach pre-fill behavior are untouched.
|
||||||
|
- **Active:** the HUD described in §2.
|
||||||
|
- **Review (polished, presentational only):** summary bands built from data the
|
||||||
|
state already carries — `next_action`, `success_condition`, the context-switch
|
||||||
|
count, and the per-window bucket recap (reusing the existing `evidence`
|
||||||
|
fields). **No new backend data** is introduced; richer session reflection is
|
||||||
|
M7's job. The `End` button (`/end`) remains.
|
||||||
|
|
||||||
|
## 4. Structure
|
||||||
|
|
||||||
|
Split the single inline file into three files under `internal/web/static/`:
|
||||||
|
|
||||||
|
- `index.html` — markup shell only (`<head>` links the stylesheet and script).
|
||||||
|
- `app.css` — the full visual system (tokens, bands, per-state rules).
|
||||||
|
- `app.js` — the render logic, **moved verbatim**: same `render()` function,
|
||||||
|
same partial-update paths (`updateActiveDrift`, `updatePlanningCoach`), same
|
||||||
|
element ids, same `EventSource('/events')` and POST endpoints. The only
|
||||||
|
additions are the band markup in the template strings and setting
|
||||||
|
`main.dataset.state` per render.
|
||||||
|
|
||||||
|
`web.go` currently serves only `/` via `c.FileFromFS`. Add routes so the two new
|
||||||
|
assets are served from the embedded `staticFS`:
|
||||||
|
|
||||||
|
- `GET /app.css` → `static/app.css`
|
||||||
|
- `GET /app.js` → `static/app.js`
|
||||||
|
|
||||||
|
No new Go dependencies, no JavaScript build step, no framework. The embedded
|
||||||
|
static directory and the conciseness/token-efficiency ethos of the Go rewrite
|
||||||
|
are preserved.
|
||||||
|
|
||||||
|
## 5. Behavior & Data Flow — Unchanged
|
||||||
|
|
||||||
|
Same SSE stream, same partial-update `render()` logic, same element ids, same
|
||||||
|
POST endpoints, same server-authoritative expiry timer. The redesign is markup +
|
||||||
|
CSS + asset routing only. This is precisely what keeps the existing
|
||||||
|
`web_test.go` (endpoint and state-JSON assertions, markup-agnostic) green.
|
||||||
|
|
||||||
|
## 6. Testing
|
||||||
|
|
||||||
|
- **Existing `web_test.go` stays green.** It asserts on endpoint status codes and
|
||||||
|
the state JSON, not on HTML markup, so the visual rework does not touch it.
|
||||||
|
- **New Go test:** `GET /app.css` and `GET /app.js` each return `200` with the
|
||||||
|
correct `Content-Type` (`text/css`, `text/javascript` / `application/javascript`).
|
||||||
|
Asserted against the router via `httptest`, stdlib `testing` only.
|
||||||
|
- **Manual visual checklist** across the six `data-state` values: locked,
|
||||||
|
planning, active (on-task), active (nudge), active (drift), review. There is no
|
||||||
|
JavaScript test harness in this Go project; the rendering is presentational and
|
||||||
|
verified by eye, consistent with the existing approach.
|
||||||
|
|
||||||
|
## 7. Out of Scope
|
||||||
|
|
||||||
|
- Micro-interactions / motion (timer easing, accent transitions, panel slide-in)
|
||||||
|
— explicitly excluded for M4; can be a later pass.
|
||||||
|
- Any new backend data or fields on the state payload.
|
||||||
|
- M7 reflection content (real session summary, time-on-task analytics). The M4
|
||||||
|
review screen is presentational recap of already-available data only.
|
||||||
Reference in New Issue
Block a user