speckit-eye Home

TASKS · tasks.md

Tasks: Dashboard Redesign — Sidebar Shell, Task Map, Feature Pages, Document Reader and Themes

Raw markdown
---

description: "Task list for the speckit-eye dashboard redesign — sidebar shell, task map, feature pages, document reader, themes and search"
---

# Tasks: Dashboard Redesign — Sidebar Shell, Task Map, Feature Pages, Document Reader and Themes

**Input**: Design documents from `specs/002-dashboard-redesign/`

**Prerequisites**: [plan.md](./plan.md), [spec.md](./spec.md), [research.md](./research.md), [data-model.md](./data-model.md), [contracts/](./contracts/) ([routes.md](./contracts/routes.md), [spec-md-structure.md](./contracts/spec-md-structure.md), [search-index.md](./contracts/search-index.md)), [quickstart.md](./quickstart.md)

**Tests**: REQUIRED. Constitution §IV (unit tests for all code) and §V (E2E coverage of every user story and major requirement) are non-negotiable. Each logic task names its unit test file and ships the module and its tests together. Each story phase has its own Playwright suite. E2E test names MUST start with the story and requirement ids they cover, for example `US2 FR-024 shows the tooltip 500 ms after the pointer enters` (DEVELOPMENT.md rule, §V traceability).

**Organization**: Tasks are grouped by user story so each story can be implemented and tested as its own increment. Story order follows the spec priorities: US1 (P1) → US2, US3, US4 (P2) → US5, US6 (P3).

## Format: `[ID] [P?] [Story] Description`

- **[P]**: Can run in parallel (different files, no dependency on an unfinished task)
- **[Story]**: The user story the task belongs to (US1–US6)
- All paths are relative to the repository root

## Conventions that apply to every task

- Plain JavaScript ES modules with JSDoc, Node.js ≥ 22, no TypeScript, no UI framework and no bundler (research D1). The runtime dependency list stays `markdown-it` only.
- All server HTML is produced with the `html` tagged template from `src/render/html.js`; only trusted fragments go through `raw()` (001 R4). Rendered documents and task text never run scripts: `markdown-it` with raw HTML off (001 FR-024).
- **CSP is unchanged** (`default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:`): no element may carry a `style="…"` attribute, no inline `<script>`, no `eval`/`new Function`. Data-driven widths use the `w-pct-<0..100>` classes (research D6); SVG presentation attributes (`stroke-dasharray`, `stroke`) are allowed; positions computed in the browser are set through the CSSOM (`el.style.left = …`).
- **Without JavaScript** every page stays readable and navigable, and the tree, phases, task rows, clarification sessions, stories and the mobile menu open and close (`<details>`). Controls that only work with scripts (theme switch, order, depth, filters, search entry, map mode, expand all, detail panel) are rendered with the `hidden` attribute and un-hidden by their module's `init` (FR-053).
- **Browser modules** live in `src/client/`, are served as `assets/<name>.js`, and follow one contract (research D2, contracts/routes.md "Browser modules"): `export function init(root, deps)` — idempotent, called on load and after every live swap — and, when the module holds page-local state, `export function save(root) → object`, read back via `init(root, { state, … })`. `deps` injects `document`, `window`, `storage`, `setTimeout`/`clearTimeout`, `fetch`, `navigator` so the module is unit-testable. Every `localStorage` access goes through `src/client/prefs.js`.
- **Unit tests** use `node:test` + `node:assert/strict`, live in `tests/unit/`, and never touch the real file system, network or a browser (§IV). Client modules are tested against `happy-dom` (`new Window()` per test, research D11) in files named `tests/unit/client-<module>.test.js`; layout-dependent code is tested with stubbed `getBoundingClientRect` values. Existing hand-written fakes (`tests/unit/fake-fs.js`, `tests/unit/fake-reader.js`) stay.
- Colors exist once, as `light-dark()` tokens on `:root` in `src/styles/input.css` (research D3); values come from `mockups/001-first-redesign/DESIGN_HANDOFF.md`'s token table plus the rose blocked tokens of research D15. Utilities reference tokens (`bg-(--surface)`); no Tailwind `dark:` variant.
- Icons come only from `src/render/icons.js` (research D5). Fonts come only from `dist/fonts/` (research D4).
- Ordering rules exist only in `src/model/ranks.js`; proportional widths only via `shares()` in `src/model/shares.js`; task states keep 001's rules in `src/model/task-state.js` (§III).
- The target project is never written (001 FR-006). Only `src/project/reader.js` reads input files; only `src/build/build.js` writes output files.
- Every user-visible change adds a line under `## [Unreleased]` in `CHANGELOG.md` (§VI, research D18). The version in `package.json` stays `1.0.0`.
- Fixture counts quoted in tasks come from `tests/fixtures/projects/README.md` and quickstart.md (`mixed`: 4 features, 65 tasks, 40 done; `002-beta` T018 depends on the open T011).

---

## Phase 1: Setup (Shared Infrastructure)

**Purpose**: Dependencies, asset build, browser test matrix and CI

- [X] T001 Update `package.json`: add devDependencies `@fontsource-variable/geist`, `@fontsource-variable/geist-mono`, `@fontsource/instrument-serif` and `happy-dom` (latest majors); (the `tailwindcss` and `@tailwindcss/cli` `^4.1` requirement, needed for `@source inline`, is already in place since commit `441f041`); replace script `build:css` with `build:assets` = `tailwindcss -i src/styles/input.css -o dist/styles.css --minify && node scripts/copy-assets.js`; set `prepack` = `pnpm run build:assets` and `test:e2e` = `pnpm run build:assets && playwright test`; add `"dist/fonts/"` to `"files"` next to `"dist/styles.css"` (`scripts/` stays out of `files`: build-time only); run `pnpm install` to update `pnpm-lock.yaml`
- [X] T002 Create `scripts/copy-assets.js` (research D4): export a pure `selectFontFiles(entries)` that, given `{package, files: string[]}` listings of the three `@fontsource*` packages, returns `{from, to}` pairs for only the Latin and Latin-extended `woff2` files (`*-latin-wght-normal.woff2`, `*-latin-ext-wght-normal.woff2` for the variable packages; `*-latin-400-normal.woff2`, `*-latin-400-italic.woff2`, `*-latin-ext-400-*.woff2` for Instrument Serif) plus each package's `LICENSE` as `OFL-<family>.txt` (`OFL-geist.txt`, `OFL-geist-mono.txt`, `OFL-instrument-serif.txt`), all targeted at `dist/fonts/`; and a thin `main()` (not unit-tested, one line per I/O call) that lists the package folders via `fs.readdirSync`, calls `selectFontFiles`, creates `dist/fonts/` and copies the files; run only when executed directly (`import.meta.url` check). Unit tests for `selectFontFiles` (keeps Latin/Latin-ext only, drops Cyrillic/other subsets and `.woff`, maps licence names, stable order) in `tests/unit/copy-assets.test.js`
- [X] T003 [P] Update `playwright.config.js` (research D16): projects `chromium` (Desktop Chrome), `firefox` (Desktop Firefox), `webkit` (Desktop Safari), `chromium-nojs` (Desktop Chrome, `javaScriptEnabled: false`, `testMatch: /nojs\.spec\.js/`) and `chromium-mobile` (`viewport: {width: 375, height: 812}`, `hasTouch: true`, `isMobile: true`, Chromium, `testMatch: /mobile\.spec\.js/`); the three desktop projects `testIgnore: [/nojs\.spec\.js/, /mobile\.spec\.js/]`; default viewport 1440 × 900 for desktop projects (SC-009); keep `workers: 1`, `retries`, `reporter`, `timeout`; update the header comment (three engines per FR-055)
- [X] T004 [P] Update `.github/workflows/ci.yml`: in the `e2e` job replace `playwright install --with-deps chromium` with `playwright install --with-deps chromium firefox webkit`; keep the OS matrix. The `wsl` job keeps installing Chromium only and runs its live-update suite with `--project chromium` (it tests the file watcher under WSL, not browser engines); its test path is switched to `tests/e2e/live-state.spec.js` in T073
- [X] T005 [P] Update `DEVELOPMENT.md`: `build:assets` instead of `build:css` (stylesheet and fonts), `happy-dom` in unit tests of `src/client/` modules, the browser-module `init`/`save` convention, the five Playwright projects and that the cloud container has Chromium only (research D16), the new `scripts/` folder in the project structure, and in the release checklist that package contents now include `dist/fonts/`

**Checkpoint**: `pnpm install`, `pnpm run build:assets` writes `dist/styles.css` and `dist/fonts/*.woff2` + `OFL-*.txt`; `pnpm test` passes.

---

## Phase 2: Foundational (Blocking Prerequisites)

**Purpose**: Tokens, fonts, icons, binary site entries, the new app shell (sidebar, icon rail, mobile menu), the browser-module bootstrap, live-update save/reinit, and the derived model fields every story reads

**⚠️ CRITICAL**: No user story work can begin until this phase is complete

### Styles, fonts, icons

- [X] T006 Rewrite the base of `src/styles/input.css` (research D3, D4, D6, D15): keep `@import "tailwindcss"`, `@plugin "@tailwindcss/typography"`, `@source "../render"`, `@source "../client"`; add `@font-face` rules for Geist (variable, `font-weight: 100 900`), Geist Mono (variable) and Instrument Serif (400 normal and italic) with `src: url(fonts/<file>.woff2) format("woff2")` **relative to the stylesheet** and `font-display: swap`; define every color token of the handoff's token table **once** on `:root` with `light-dark(<light>, <dark>)` (e.g. `--bg: light-dark(#F4F3EF, #0E1013)`), plus `--rose: light-dark(#BE123C, #FB7185)`, `--rose-bg: light-dark(#FFE4E6, #3F1522)`, `--rose-text: light-dark(#9F1239, #FDA4AF)` and a single `--next` orange `#C2410C` that is not a `light-dark()` pair (FR-047); `:root { color-scheme: light dark }`, `:root[data-theme="light"] { color-scheme: light }`, `:root[data-theme="dark"] { color-scheme: dark }`; a `.always-dark { color-scheme: dark }` utility for sidebar, rail, Up next bar and tooltip; `@theme` font families `--font-sans: "Geist Variable"`, `--font-mono: "Geist Mono Variable"`, `--font-serif: "Instrument Serif"`; `@utility w-pct-* { width: calc(--value(integer) * 1%); }` with `@source inline("w-pct-{0..100}")`; keep the global reduced-motion rule (001 FR-036); focus ring utility (visible outline on `:focus-visible`, FR-050); confirm by hand that `pnpm run build:assets` output contains `.w-pct-0` … `.w-pct-100` and the `@font-face` rules with relative `fonts/` URLs (guarded automatically by the E2E test added in T076)
- [X] T007 [P] Create `src/render/icons.js` (research D5): ISC licence notice of Lucide in the file header; an `ICONS` map of SVG inner markup for `eye`, `search`, `grid`, `shield`, `layers`, `sun`, `moon`, `monitor`, `chevron-right`, `chevron-up-down`, `arrow-right`, `alert-triangle`, `check`, `check-circle`, `copy`, `x`, `sort`, `menu`, `file-text`; export `icon(name, { size = 16, label = null } = {})` returning a trusted `<svg viewBox="0 0 24 24" width height fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">` (or `role="img"` + `<title>` when `label` is given); throws on unknown names. Unit tests (every name renders, no `style` attribute, unknown name throws, label → accessible) in `tests/unit/icons.test.js`

### Binary site entries and new assets

- [X] T008 Change `src/render/site.js` (research D12): `SiteEntry.body` becomes `string | Uint8Array`; add exported types `FONT_TYPE = "font/woff2"`, `JSON_TYPE = "application/json; charset=utf-8"`, `TEXT_TYPE = "text/plain; charset=utf-8"`; `renderSite(project, { base, mode, version, generatedAt, assets })` now takes `assets = { styles, modules: {name: source}, fonts: {file: Uint8Array | string} }` and publishes `assets/styles.css`, `assets/theme.js` (body from `themeScript()` of T017, not read from disk), `assets/<module>.js` for every module (`app.js`, `prefs.js` and later modules; `live.js` only in serve mode), and `assets/fonts/<file>` with `FONT_TYPE` for `.woff2` and `TEXT_TYPE` for `.txt`; update `tests/unit/site.test.js` (binary bodies kept as `Uint8Array`, content types, `live.js` absent in static mode, font paths)
- [X] T009 [P] Change `src/serve/handler.js` to send `Uint8Array` bodies unchanged (no string encoding, correct `Content-Length`) while keeping every 001 header including the CSP; update `tests/unit/handler.test.js` (binary body byte-exact, CSP header unchanged)
- [X] T010 [P] Change `src/build/build.js` (`writeSite`) to write `Uint8Array` bodies with no encoding and string bodies as UTF-8; update `tests/unit/build.test.js`
- [X] T011 Change `src/cli/main.js` asset loading: replace the fixed `overview.js`/`live.js` map with a list of browser modules read from `src/client/` (`app.js`, `prefs.js`, `live.js`, and the modules added by later tasks — keep the list in one exported constant `CLIENT_MODULES` in `src/cli/main.js`), `dist/styles.css`, and every file of `dist/fonts/` read as bytes (`readFile` without encoding); pass them to `renderSite` in both serve and build mode (`live.js` only in serve); update `tests/unit/main.test.js` with the injected fake file system (fonts read as bytes, module list published, clear error when `dist/fonts` is missing that tells the developer to run `pnpm run build:assets`)

### Model additions shared by all stories

- [X] T012 [P] Create `src/model/shares.js` (data-model "Percent shares"): export `shares(values: number[]) → integer[]` — floor each `value * 100 / sum`, give the remaining points to the largest fractional parts (ties: earlier entry first), always sums to 100, all zeros for an empty array or zero sum; export `sharesNoZero(values)` that, after `shares`, raises any non-zero value that rounded to 0 to 1 by taking 1 from the largest part (FeatureSegment `parts` rule). Unit tests (sum = 100, ties, zero sum, `[123, 32, 72, 15]`, tiny non-zero part kept at 1) in `tests/unit/shares.test.js`
- [X] T013 [P] Extend `src/model/task-state.js` (data-model Task `display`, `waitingOn`): keep 001's rules and precedence; add `display` = `done | next | blocked | open` mapped from 001 `completed → done`, `current → next`, `blocked → blocked`, `future → open`; add `waitingOn: string[]` = the IDs in `dependsOn` whose tasks are still open, in written order, only for `blocked` tasks (empty otherwise; missing IDs ignored); export `DISPLAY_LABEL = {done: "Done", next: "Next", blocked: "Blocked", open: "Open"}`; update `tests/unit/task-state.test.js` (T020 depends on T012 + T013 with only T012 open → `waitingOn: ["T012"]`; next beats blocked; dependency on missing ID ignored)
- [X] T014 Extend `src/model/build-model.js` (data-model Feature): add `number` = leading digits of `dir` (`001-x` → `"001"`) or the timestamp prefix, `null` without a numeric prefix; `status` = `done | in-progress | not-started | no-tasks` from the 001 Stage per the FeatureStatus table (`complete → done`, `in-progress → in-progress`, `ready → not-started`, `empty | specified | planned → no-tasks`); `statusLabel` per the same table ("Complete", "N open", "Ready" or "N open" when it is the active feature, the stage label "Specified"/"Planned"/"Empty"); Task `anchor` = `"task-" + key` with `/` and `@` replaced by `-`; update `tests/unit/build-model.test.js`
- [X] T015 Add feature documents to `src/model/build-model.js` (data-model DocumentGroups): `documents = { groups: [{name: "Define"|"Design"|"Contracts"|"Build"|"Other", items: Artifact[]}] (empty groups left out), tabs: [{label, count, items: Artifact[]}] }` with the artifact kind → group/tab table (spec → Define/Specification; checklist → Define/"Quality checklist" when it is the only checklist and its file is `checklists/requirements.md`, otherwise "Checklists" with count; plan, research, data-model, quickstart → Design; contract → Contracts/"Contracts" with count; tasks → Build/no tab; other → Other/"More" with count), tab order Specification, Plan, Research, Data model, Quickstart, Contracts, checklists, More; add `project.documents` for the constitution ("Project") and one group per assessment; update `tests/unit/build-model.test.js` (feature without `research.md` has no Research tab; four contracts → "Contracts" count 4)

### App shell and browser bootstrap

- [X] T016 [P] Create `src/client/prefs.js` (research D9): export `KEYS = {theme: "sk-theme", order: "sk-order", filter: "sk-filter", map: "sk-map"}`, `DEFAULTS = {theme: "system", order: "progress", filter: "all", map: null}` and `ALLOWED` value lists (`light|dark|system`, `progress|number|least|name`, `all|open`, `stacked|grouped`); `createPrefs(storage)` returning `get(name)` (unknown or invalid stored value → default) and `set(name, value)` (ignores invalid values); every storage access wrapped in `try/catch`, so a throwing or `null` storage falls back to defaults and never throws. Unit tests (defaults, round trip, invalid values, throwing storage on get and set, null storage) in `tests/unit/client-prefs.test.js`
- [X] T017 Theme before first paint (after T016, same file `src/client/prefs.js`) without file-reading tests (research D3, SC-005, constitution §IV): add to `src/client/prefs.js` an exported, **self-contained** function `applyStoredTheme(document, storage)` (no references outside its own body, because its source text is reused) that reads `"sk-theme"` inside `try/catch` and sets `document.documentElement.dataset.theme` to `light` or `dark`, doing nothing for `system`, a missing or invalid value, or a throwing/`null` storage; create `src/render/theme-script.js` exporting `themeScript()` that returns the classic (non-module) script text `(<applyStoredTheme source>)(document, (function () { try { return window.localStorage; } catch (e) { return null; } })());` built from `applyStoredTheme.toString()`, so the published `assets/theme.js` (T008) is generated in memory and there is no separate `src/client/theme.js` file and no entry in `CLIENT_MODULES`. Unit tests: `applyStoredTheme` against a happy-dom window with fake storages (`light`, `dark`, `system`, missing, invalid, throwing, `null`) in `tests/unit/client-prefs.test.js`; `themeScript()` evaluated with `node:vm` in a context holding a happy-dom `document`/`window` (script text only, no file access) in `tests/unit/theme-script.test.js`
- [X] T018 Rewrite `src/render/layout.js` (contracts/routes.md "Page shell", FR-005–FR-009): export `renderPage({ title, base, mode, project, page: "overview"|"feature"|"document", current, main, version, generatedAt })`; `<head>` loads `{base}assets/theme.js` (blocking, first script), `{base}assets/styles.css`, `<script type="module" src="{base}assets/app.js">`, and `{base}assets/live.js` in serve mode only; `<body data-mode data-version data-page data-base>`; for `overview` and `feature` pages an `<aside data-region="sidebar" class="always-dark">` with brand (eye icon, "speckit-eye"), project name, `<button data-part="search" hidden>` "Search tasks, specs…" with a ⌘K hint, `<nav aria-label="Main">` (Overview, Constitution when present, "Assessment: <slug>" per assessment), `<nav aria-label="Features">` headed "Features N / M done" listing each feature in `ranks.progress` order (until T024 lands, folder order) as `<a data-key="side:<dir>" data-sig data-status href="{base}features/<dir>/index.html">` with status dot (done filled green, in progress blue ring, not started/no tasks grey), the truncated name with `title` = full name, and the open-task count or a check icon, `aria-current="page"` on `current`; `<div role="group" aria-label="Theme" data-part="theme" hidden>` with three `<button aria-pressed data-theme-choice="light|dark|system">` (sun, moon, monitor); `<footer>` "speckit-eye <version>" plus " · generated <time>" in static mode; for `document` pages a `<nav data-region="rail" class="always-dark">` with icon links to the same destinations, the search button (hidden) and the theme group; on every page a `<details data-region="mobile-menu">` whose `<summary>` (menu icon) shows only on narrow screens and contains the same navigation (works without JS, FR-009); `<main>`; `<div data-region="live-status" hidden>` in serve mode; an empty `<dialog data-region="search">`; `<div data-region="tooltip" role="tooltip" hidden>` on the overview only; no `style` attribute anywhere; remove the old header, header links and Menu (FR-008). Rewrite `tests/unit/layout.test.js` (regions per page type, script order, no `style=`, `aria-current`, static footer time, sidebar count "Features 1 / 4 done")
- [X] T019 Add the shell's CSS to `src/styles/input.css`: sidebar (fixed width per handoff, own scroll for the Features list), icon rail, main column, mobile layout below the handoff's breakpoint (sidebar hidden, `mobile-menu` summary shown, no horizontal page scroll, FR-009), status dots, pills, chips, buttons with ≥ 36 px hit targets (FR-050)
- [X] T020 Create `src/client/app.js` (contracts/routes.md "Browser modules"): imports `prefs.js` and the page modules; `start({ document, window, storage })` reads `document.body.dataset.page`, calls the shared `initShell(root, deps)` (un-hides and wires the theme switch: `aria-pressed` reflects `prefs.get("theme")`, a click stores the choice via prefs and sets or removes `data-theme` on `<html>` immediately, so it works even when storage fails, FR-045, US4 AC5) and then `init(root, deps)` of each module registered for that page in one `MODULES = { overview: [...], feature: [...], document: [...], all: [...] }` table; exports `save(root) → {<module>: state}` and `reinit(root, state)` for `live.js`; the file ends with a guarded bootstrap `if (typeof document !== "undefined") start(...)`; until US1/US2 replace it, the overview entry still runs the 001 `attachHover`/`attachClick` from `src/client/overview.js`. Unit tests with happy-dom (right modules per page, `reinit` passes each module its saved state, theme switch updates `aria-pressed` and `data-theme`, throwing storage still switches) in `tests/unit/client-app.test.js`
- [X] T021 Change `src/client/live.js` (research D2, FR-051): before replacing `<main>`, call `app.save(root)` and record `window.scrollY` and `scrollTop` of every `[data-keep-scroll]` by name; after the swap keep 001 `applyToggles` and the change flash (`collectSigs`/`changedKeys`), then call `app.reinit(root, state)` and restore the scroll positions; also replace the sidebar Features list and footer (they carry `data-sig`) so sidebar counts update; emit a `sk:change` event on `document` after each swap (used by search, T069); keep the 001 SSE protocol and reconnect; update `tests/unit/client-live.test.js` (save called before swap, reinit after, scroll restored for `tree`, `tasks`, `docs`, sidebar entry flashed on change, `sk:change` dispatched once after each swap)
- [X] T022 Add the feature page route to `src/render/site.js`: publish `features/<dir>/index.html` for every feature (contracts/routes.md) rendered by a new `renderFeaturePage(feature, project, { base })` exported from a new `src/render/feature.js`, which for now renders only `<header data-region="feature-head">` (breadcrumb "Overview / Features", status pill, `<code>dir</code>`, `<h1>` title, "done / total tasks · completed of total phases") and a plain list of the feature's documents, inside `renderPage({ page: "feature", current: dir })`; document pages (`src/render/artifact.js`) now render with `page: "document"` (icon rail) until US5 replaces them; unit tests in `tests/unit/feature.test.js` and `tests/unit/site.test.js` (one feature page per feature, sidebar marks it current)
- [X] T023 Update the existing E2E suites to the new shell so the suite stays green: `tests/e2e/helpers.js` (selectors for `[data-region="sidebar"]`, `[data-region="rail"]`, feature page paths), `tests/e2e/security.spec.js`, `tests/e2e/us3-artifacts.spec.js`, `tests/e2e/us4-build.spec.js`, `tests/e2e/self-counts.spec.js`, `tests/e2e/scale.spec.js` (replace header/Menu navigation with sidebar or rail links; keep every assertion's intent); add to `tests/e2e/security.spec.js` a test `FR-004 serves fonts from its own origin with the unchanged CSP` (fonts load with `font/woff2`, no request leaves the origin, CSP header string unchanged)

**Checkpoint**: Every page renders inside the new shell (sidebar or icon rail), fonts and icons load from the tool itself, the theme switch works on the current page, live updates keep scroll positions, and unit + E2E suites pass in Chromium.

---

## Phase 3: User Story 1 - See where the project stands in the new overview (Priority: P1) 🎯 MVP

**Goal**: The redesigned overview: page head with view filter, stats card with per-feature segmented bar, Up next bar, orderable feature tree with depth control and amber warning rows, and a sidebar Features list in "In progress first" order.

**Independent Test**: Serve `tests/fixtures/projects/mixed` (and `nonstandard` for warnings); confirm the sidebar list, stats card numbers (62 %, "40 of 65 tasks", Features "1 / 4" with "1 in progress", Phases "5 / 9" with "4 remaining", Open tasks "25" "across 2 features"), segments 46 / 31 / 23 %, Up next bar, tree order, depth control, filter and warning rows against the fixture (quickstart Scenario 1).

### Model for User Story 1

- [X] T024 [P] [US1] Create `src/model/ranks.js` (data-model FeatureRanks, research D10): export `computeRanks(features) → Map<dir, {progress, number, least, name}>` with 0-based positions: `progress` = group 1 features with open tasks (`in-progress` before `not-started`), group 2 `no-tasks`, group 3 `done`, inside each (sub-)group `dir` descending; `number` = `dir` ascending; `least` = `counts.percent` ascending, then `counts.open` descending, then `dir` ascending, features without tasks last by `dir`; `name` = `title` with `localeCompare(…, "en", {sensitivity: "base"})`, then `dir`; wire it into `src/model/build-model.js` as `feature.ranks`. Unit tests (each order on a four-feature example, ties, features without tasks) in `tests/unit/ranks.test.js`
- [X] T025 [P] [US1] Create `src/model/warnings.js` (data-model WarningGroup, FR-015): export `mergeLines(numbers) → {from, to}[]` (sorted, consecutive merged: 23, 254–258 → `[{23,23},{254,258}]`), `formatRange({from, to})` → `"L23"` / `"L254–258"` (en dash), and `groupWarnings(warnings, contentByFile) → WarningGroup[]` with `code`, `file`, `count`, `title` (plain sentence with the count, e.g. "6 checkboxes without a task ID in tasks.md"; one title template and `note` per 001 code `W1`–`W12` in one table, e.g. W1 note "counted, not linkable"), `lines`, `sourceLines` (`{line, text}[]` of the raw lines from the already-read content), ordered by first line, warnings without a line in their own group per code; wire into `src/model/build-model.js` as `feature.warningGroups`. Unit tests (merging, en dash, singular/plural titles, ordering, no-line warnings, every 001 code has a title) in `tests/unit/warnings.test.js`
- [X] T026 [US1] Add `project.overview` (data-model OverviewStats, FeatureSegment, FR-011) to `src/model/build-model.js`: `percent`, `done`, `total` from `totals.tasks` (001 rounding, capped at 99 % while any task is open); `features {completed, total, inProgress}`; `phases {completed, total, remaining}`; `openTasks {count, features}`; `segments` for features with `counts.total > 0` in `dir` order with `dir`, `number`, `title`, `share` (via `shares()` over all segments' totals), `parts {done, open, next}` via `sharesNoZero` (blocked counted as open), `counts`, `showLabel = share >= 4`; `legend {done, open, blocked, next}` over all tasks (`open` excludes blocked and next). Unit tests with the spec's example (123/123, 24/32, 72/72, 14/15 → "96 %", 233 of 242, Features 2 / 4 with 2 in progress, Open 9 across 2 features, segments proportional to 123, 32, 72, 15) and the `mixed` fixture numbers in `tests/unit/build-model.test.js`

### Rendering for User Story 1

- [X] T027 [US1] Rewrite `src/render/overview.js` page head, stats card and Up next bar (contracts/routes.md "Overview `<main>`", FR-010, FR-011, FR-013): `<header data-region="page-head">` with project name, `<h1>Project overview</h1>` and the hidden `role="group" aria-label="Show"` filter with `data-filter="all|open"` buttons; `<section data-region="stats">` with the percentage, "done of total tasks", three counters (`data-stat="features|phases|open"`, each with `data-key="stat:<name>"` and `data-sig`), the segments `<a data-key="seg:<dir>" href="#<feature anchor>" class="w-pct-N min-w-[3px]" title="<number> · <title> — d done, o open, n next">` each containing `<span data-part="done|open|next" class="w-pct-N">`, labels (feature number when `showLabel`), and a legend Done / Open / Next up; empty project → 0 % with a short explanation and an empty state instead of the bar; `<section data-region="up-next" class="always-dark">` with ID chip, task text exactly as written on one line with `title` = full text, feature › phase, "Open quickstart" (only when the active feature has a quickstart artifact) and "View task" → `features/<dir>/index.html#<anchor>`, or `data-empty="complete|no-tasks"` with "Every task is complete" / "No tasks yet"; unit tests in `tests/unit/overview.test.js` (numbers, widths as classes, no `style=`, empty and complete states, quickstart button presence)
- [X] T028 [US1] Rewrite the tree part of `src/render/overview.js` (FR-012, FR-014–FR-018): `<section data-region="features">` header with hidden `<button data-part="order" data-order="progress">` (sort icon + label of the current order) and hidden `<div role="group" data-part="depth">` Features | Phases | Tasks; `<div data-region="tree" data-keep-scroll="tree" data-filter="all">` rendering features in `ranks.progress` order as `<li data-feature data-rank-progress data-rank-number data-rank-least data-rank-name [data-complete]>` → `<details data-key data-sig data-status [data-active] [open]>` whose `<summary>` has chevron, status dot, number chip (omitted when `number` is null), truncated title with `title`, warnings badge "N warnings" when `warningGroups` is non-empty, status pill (`statusLabel`), mini progress bar (`w-pct-<percent>`), "done/total" (no bar for `no-tasks`), then `<a data-part="open-feature">` to the feature page; an expanded feature with warnings starts with one amber `<a data-part="warning" data-code href="features/<dir>/index.html#warnings">` row per group with title, note, line chips (`formatRange`) and "Details"; phases as `<details data-key data-status [data-complete]>` with "Phase N", title, priority badge (P1 filled dark, P2 grey, P3 outlined) when the phase belongs to one story, "done/total" plus check when complete or "—" and no chevron when empty; 001 story sub-level kept; task rows `<li data-key id="<anchor>" data-state="done|next|blocked|open" data-sig>` with mark, `<a href="features/<dir>/index.html#<anchor>">ID</a>` ("No ID" as plain text for tasks without an ID), raw text on one line with `title`, and a "NEXT" badge + tint on the next task; only the active chain open on load (001 FR-016); keep the 001 grid in the right column until T039 replaces it. Unit tests in `tests/unit/overview.test.js` (rank attributes, warning row "6 checkboxes without a task ID in tasks.md" with "L23" and "L254–258", badge "6 warnings", empty phase "—", NEXT badge, blocked `data-state`)
- [X] T029 [US1] Switch the sidebar Features list in `src/render/layout.js` to `ranks.progress` order and mark the entry of the current feature page; update `tests/unit/layout.test.js`
- [X] T030 [P] [US1] Add overview CSS to `src/styles/input.css`: page head, stats card, segmented bar and legend, Up next bar (always dark, one-line truncation), two-column grid (tree left, map column right, card tops aligned; stacked with the tree first on narrow screens, FR-010), tree card with its own scroll above about 800 px (FR-018, handoff), feature/phase/task rows, amber warning rows and L-chips, pills and priority badges, mini bars, `[data-filter="open"] [data-complete], [data-filter="open"] li[data-state="done"] { display: none }` (FR-019), `[data-selected]` outline, and `[data-state]` marks including the rose blocked mark (FR-022)

### Browser behavior for User Story 1

- [X] T031 [US1] Create `src/client/tree.js` (FR-012, FR-019, FR-020, research D10): `init(root, { document, storage, state })` un-hides the order button, depth group and view filter; **order**: applies `prefs.get("order")` by sorting the feature `<li>` elements on `data-rank-<order>` (moves nodes, never re-renders), each click cycles progress → number → least → name → progress, stores it and sets the button label ("In progress first", "Number", "Least complete", "Name A–Z"); **depth**: "Features" closes every feature and phase `<details>`, "Phases" opens features and closes phases, "Tasks" opens features and every phase with tasks (and story levels); the chosen depth shows `aria-pressed`, and a manual toggle of any row clears it; **filter**: sets `data-filter` on the tree from `prefs.get("filter")`, buttons store it; exports `reveal(root, anchor)` (opens the task's feature, phase and story `<details>`, scrolls the tree card — not the window — so the row is visible, sets `data-selected` on the row and clears it from any other row; a click elsewhere clears it) for `taskmap.js`; `save(root)` returns `{depth}`. Unit tests with happy-dom (sort by each rank, cycle and label, depth open/close sets, manual toggle clears depth, filter attribute, prefs round trip, reveal opens ancestors and selects, save/init restores depth) in `tests/unit/client-tree.test.js`
- [X] T032 [US1] Register `tree.js` for the `overview` page in `src/client/app.js`, add `tree.js` to `CLIENT_MODULES` in `src/cli/main.js`, and update `tests/unit/client-app.test.js` and `tests/unit/main.test.js`

### E2E for User Story 1

- [X] T033 [US1] Rewrite `tests/e2e/us1-overview.spec.js` against `mixed` and `nonstandard`: `US1 FR-005 FR-006 sidebar lists features in progress-first order with dots and counts`; `US1 FR-011 stats card shows 62 % and 40 of 65 tasks` (plus features, phases, open counters); `US1 FR-011 segmented bar has one segment per feature with tasks, widths 46/31/23 %` (compare element box widths within 1 %); `US1 FR-013 Up next bar names the next task with full text on hover and View task opens the feature page`; `US1 FR-012 order cycles through four orders and the label names the current one`; `US1 FR-012 depth Tasks expands every phase with tasks and Features collapses all`; `US1 FR-015 warning row shows count, line chips and Details link` (nonstandard); `US1 FR-019 Open tasks only hides complete features and done tasks`; `US1 FR-020 order and filter are remembered after reload`; `US1 FR-006 sidebar entry opens the feature page and marks it current`; `US1 SC-001 percentage, in-progress feature and next task visible without scrolling at 1440 × 900`; `US1 SC-002 View task shows the next task's full text and files in one click, and any task is two clicks from the map (square, then its ID)`; `US1 FR-018 tree scrolls inside its card above about 800 px`; `US1 SC-009 sidebar left of content, tree left of map` (bounding boxes)
- [X] T034 [US1] Add `CHANGELOG.md` lines under `## [Unreleased]` for the new sidebar shell, stats card, Up next bar and feature tree (order, depth, open-only filter, warning rows), and update `tests/fixtures/projects/README.md` with the expected stats, segment shares and ranks of each fixture

**Checkpoint**: The overview and sidebar are fully functional; US1 E2E suite passes; the 001 grid is still shown on the right until US2.

---

## Phase 4: User Story 2 - Explore every task on the task map (Priority: P2)

**Goal**: The task map with stacked / by-feature modes, four state colors including rose for blocked, legend, hover growth, delayed tooltip, tree highlight, and click-to-reveal in the tree.

**Independent Test**: On `mixed`, the map shows 65 squares with legend "Done 40 · Open 23 · Blocked 1 · Next 1 · 1 dot = 1 task"; hover, tooltip timing and placement near both edges, "By feature" / "Stack all", and clicking a square in collapsed `003-gamma` reveal the task (quickstart Scenario 2).

### Rendering for User Story 2

- [X] T035 [US2] Create `src/render/taskmap.js` (FR-021–FR-023, FR-027, FR-028, contracts/routes.md): move `GRID_ROWS_THRESHOLD`, `GRID_BARS_THRESHOLD` and `gridLayout` from `src/render/overview.js` and rename the layouts to `stacked` (≤ 1,000 tasks), `grouped` (1,001–5,000) and `bars` (> 5,000); export `renderTaskMap(project, { base })` returning `<section data-region="taskmap" data-layout>` with a header (title "Task map" and hidden `<button data-part="map-mode" aria-pressed>` reading "By feature" / "Stack all", absent for `bars`) above the card; the grid as `<a data-key data-sig data-state data-parents="<dir> <phaseKey> [groupKey]" href="#<anchor>" title="<ID or No ID> · <state label> — <text> — <feature>" aria-label=…>` per task including checkboxes without an ID, in folder and task order (never the tree order); grouped mode as `<div data-part="group">` with feature name, done/total and its squares; bars mode as one progress bar per feature (`w-pct`); `<ul data-part="legend">` "Done N · Open N · Blocked N · Next N · 1 dot = 1 task" from `project.overview.legend` (what one mark stands for in bars mode); an empty state for a project without tasks. Unit tests in `tests/unit/taskmap.test.js` (one square per task incl. no-ID, order independent of ranks, thresholds 1,000/1,001/5,000/5,001, legend sums to total, blocked `data-state`, no `style=`)
- [X] T036 [US2] Replace the 001 grid in `src/render/overview.js` with `renderTaskMap` and remove the moved grid code and its tests from `tests/unit/overview.test.js`
- [X] T037 [P] [US2] Add task map CSS to `src/styles/input.css`: square sizes for stacked and grouped modes per handoff, colors done (filled green), open (blue outline), blocked (`--rose` 1.5 px outline + `--rose-bg` tint), next (filled `--next`); hover/`:focus-visible` growth `transform: scale(1.6)` with a two-ring outline within 120 ms ease-out affecting no other square (FR-024); tooltip (always dark, status pill, `line-clamp: 2` with `-webkit-line-clamp` fallback, fade); tree tint classes `[data-hl="ancestor"]` (light) and `[data-hl="deepest"]` (stronger + outline) (FR-025); growth and fade off under reduced motion

### Browser behavior for User Story 2

- [X] T038 [US2] Create `src/client/taskmap.js` (research D8, FR-024–FR-026, FR-028): `init(root, { document, window, storage, setTimeout, clearTimeout, state })`: **mode**: un-hide the toggle, apply `prefs.get("map")` (falling back to the server's `data-layout`), toggle between stacked and grouped by setting `data-layout` and the button text/`aria-pressed`, store it; **tooltip**: one shared `[data-region="tooltip"]`; delegated `pointerenter` on squares starts a 500 ms timer (`TOOLTIP_DELAY_MS = 500`) unless `pointerType === "touch"`; keyboard `focus` shows at once; `pointerleave`/`blur` clears the timer and hides at once; content = ID (or "Checkbox without a task ID"), status pill (`DISPLAY_LABEL`), text, feature name — all set via `textContent`; position above the square, aligned left, centre or right by available room and clamped to the card and the viewport via CSSOM `style.left/top` (exported pure `placeTooltip(squareRect, tipRect, boundsRect) → {left, top, align}`); **highlight**: while a square is hovered or focused, set `data-hl="ancestor"` on its visible feature/phase/story rows from `data-parents` and `data-hl="deepest"` on the deepest visible one (task row if visible), cleared on leave; **click / Enter**: `preventDefault` and call `tree.reveal(root, anchor)`; touch tap does the same with no tooltip; `save(root)` returns nothing extra (mode is a preference). Unit tests with happy-dom and fake timers (no tooltip before 500 ms, shown at 500 ms, hidden on leave, focus shows at once, touch never shows, `placeTooltip` left/right/centre and clamping, no-ID text, highlight deepest visible, click calls reveal, mode persisted) in `tests/unit/client-taskmap.test.js`
- [X] T039 [US2] Register `taskmap.js` for the `overview` page in `src/client/app.js`, remove the 001 `attachHover`/`attachClick` usage, delete `src/client/overview.js` and `tests/unit/client-overview.test.js` (§II), add `taskmap.js` to `CLIENT_MODULES` in `src/cli/main.js`; update `tests/unit/client-app.test.js` and `tests/unit/main.test.js`

### E2E for User Story 2

- [X] T040 [US2] Create `tests/e2e/us2-taskmap.spec.js` (moving the 001 grid tests from `us1-overview.spec.js`): `US2 FR-021 FR-023 map shows 65 squares and legend Done 40 · Open 23 · Blocked 1 · Next 1`; `US2 FR-021 By feature regroups into labelled blocks and Stack all restores`; `US2 FR-024 square grows on hover and tooltip appears after 500 ms, not before` (measure with `page.clock` or timestamps: 400–600 ms); `US2 FR-024 tooltip hides at once on leave`; `US2 FR-024 FR-050 keyboard focus on a square shows the tooltip at once and Enter reveals the task in the tree`; `US2 FR-024 tooltips of first- and last-column squares are fully inside card and viewport`; `US2 FR-025 hovering a square in a collapsed feature tints its feature row`; `US2 FR-026 clicking a square in collapsed 003-gamma opens feature and phase, scrolls the tree card and outlines the row; its ID opens the feature page`; `US2 FR-022 T018 is blocked in map and tree and its tooltip pill reads Blocked`; `US2 FR-022 after checking T011 in tasks.md T018 becomes open` (live update on a fixture copy); `US2 FR-028 map mode is remembered after reload`; `US2 FR-024 checkbox without an ID shows "Checkbox without a task ID"` (nonstandard)
- [X] T041 [US2] Update `tests/e2e/scale.spec.js` for the 50 × 2,000 fixture: `US2 FR-027 map opens By feature above 1,000 tasks`, `US2 SC-006 hover growth within 150 ms and tooltip at 500 ± 100 ms with 2,000 squares`, `US1 FR-011 with 50 features, segments too narrow for a label show none but stay visible`
- [X] T042 [US2] Add `CHANGELOG.md` lines for the task map (modes, blocked color, tooltip, reveal in tree)

**Checkpoint**: US1 and US2 work together on the overview; both E2E suites pass.

---

## Phase 5: User Story 3 - Work through a feature on its feature page (Priority: P2)

**Goal**: The full feature page: header with ring, document tabs, phase rail accordion, warnings banner, filterable task list with kind/file chips, in-place expansion, detail panel with Copy ID and View source line, addressable selected task.

**Independent Test**: Open `002-beta` on `mixed` and the `nonstandard` feature; check header counts, tabs, rail widths and accordion, each filter, row expansion, the detail panel for T018 ("Waiting on T011"), Copy ID, View source line to `tasks.html#L31`, and the warnings banner (quickstart Scenario 3).

### Model and text rendering for User Story 3

- [X] T043 [P] [US3] Create `src/model/task-files.js` (data-model Task `files`, `kind`, `refs`, TaskKind; FR-035, FR-037): export `taskFiles(description)` = backticked spans matching `/^[^\s`]*\/[^\s`]*[A-Za-z0-9_-]\.[A-Za-z0-9]+$/` or ending in `Makefile`/`Dockerfile`, in order, duplicates removed; `taskKind(file) → {label, test} | null` with test when the file name matches `/(^|[._-])(test|spec)s?([._-]|$)/i` or a folder is `test`, `tests`, `__tests__` or `e2e`, and label from the extension table (`.go` Go, `.vue` Vue, `.js`/`.mjs`/`.cjs` JavaScript, `.jsx` JSX, `.ts`/`.mts`/`.cts` TypeScript, `.tsx` TSX, `.py` Python, `.rb` Ruby, `.rs` Rust, `.java` Java, `.kt` Kotlin, `.swift` Swift, `.cs` C#, `.php` PHP, `.css` CSS, `.scss` SCSS, `.html` HTML, `.svelte` Svelte, `.md` Markdown, `.json` JSON, `.yml`/`.yaml` YAML, `.toml` TOML, `.sh` Shell, `.sql` SQL; other → extension upper-cased without the dot; `Makefile`/`Dockerfile` → the name); `kindChip(kind)` = `label` or `label + " test"`; `taskRefs(description)` = `FR-\d+[a-z]?` and `SC-\d+` in order, deduplicated; wire into `src/model/build-model.js` (`task.files`, `task.kind`, `task.refs`) and add `feature.kindChips` (distinct chip texts without " test", first appearance order). Unit tests (`internal/input/simulator_test.go` → "Go test"; two files; no file; `.proto` → PROTO; `tests/e2e/x.spec.js` → "JavaScript test"; URL-like spans without `/` ignored; refs) in `tests/unit/task-files.test.js`
- [X] T044 [US3] Add `feature.phaseShares` to `src/model/build-model.js`: `shares()` over each phase's task total (phases without tasks get 0; rendered at the minimum width, FR-032); unit tests in `tests/unit/build-model.test.js`
- [X] T045 [P] [US3] Extend `src/render/markdown.js`: export `renderInline(md, text)` using the shared `markdown-it` instance's `renderInline` (raw HTML off, 001 link rules); make `tasks.md` documents wrap every task line in `<span id="L<line>">` (FR-044; line numbers from the source, fences and comments excluded as in 001 `visibleLines`) with `:target` highlight CSS in `src/styles/input.css`; update `tests/unit/markdown.test.js` (inline HTML stays text, task lines addressable)
- [X] T046 [US3] Create `src/render/task-text.js` (contracts/routes.md "Inline task text", FR-035, FR-037): export `renderTaskText(md, task)` → inline HTML of `description` via `renderInline` with `` `code` `` as mono chips and `FR-…`/`SC-…` references wrapped as `<span class="chip" data-ref>` chips (only in text nodes, never inside code or link text); `renderKindChip(task)` (fixed-width chip, empty placeholder of the same width when `kind` is null); `renderFileChip(task)` (file name of the single file with `title` = full path, "N files" when several, empty placeholder when none); `renderMarkers(task)` (USn, "Parallel", FR/SC refs, "depends on …"). Unit tests (text never altered, `<script>` in text stays text, chips aligned by fixed classes, "2 files") in `tests/unit/task-text.test.js`

### Rendering for User Story 3

- [X] T047 [US3] Complete `src/render/feature.js` header and tabs (FR-030, FR-031): header with breadcrumb (Overview / Features), status pill, `<code>dir</code>`, `<h1>`, "done / total tasks · completed of total phases" and an SVG progress ring (`stroke-dasharray` from `counts.percent`, check icon when complete); `<nav data-region="tabs" aria-label="Documents">` with "Tasks <n>" (`aria-current="page"`) then one tab per `feature.documents.tabs` entry in order, a tab with one document links to its page, a tab with several renders `<details data-part="tab-menu"><summary>Contracts <n></summary>` listing them (FR-008); unit tests in `tests/unit/feature.test.js` (full tab set, no Research tab without `research.md`, "Contracts 4", "Quality checklist" vs "Checklists 2", "More")
- [X] T048 [US3] Add the Tasks tab body to `src/render/feature.js` (FR-032–FR-035, contracts/routes.md "Feature page `<main>`"): `<nav data-part="rail">` with one `<a href="#phase-<n>" class="w-pct-N" data-status>` per phase (`phaseShares`, label-sized minimum width, short name "P4" and task count, narrow non-openable block for empty phases) and `<p data-part="caption">` "Width = task count · Selected: <phase or None>"; `<section id="warnings" data-part="warnings">` amber banner when `warningGroups` is non-empty with titles, line chips and `<details><summary>Show lines</summary><ol>` of `sourceLines`; hidden `<div data-part="filters">` with chips All (count), Open (count, blocked included), Tests, one chip per `feature.kindChips`, `<input type="search">` and "Expand all" / "Collapse all"; `<div data-part="list" data-keep-scroll="tasks">` with `<details name="phases" data-key id="phase-<n>" data-status [open]>` per phase (active phase open, none when no open tasks) whose summary has "Phase N", title, priority badge and done/total; task rows `<details data-key id="<anchor>" data-state data-kind data-test? data-files="<names>" data-text="<lower-cased id + text>">` with `<summary>` mark, ID ("No ID" without address for ID-less tasks), `renderTaskText`, kind chip, file chip; body: full wrapped text and `renderMarkers`; hidden `<aside data-region="detail">`; a feature without `tasks.md` shows an empty Tasks tab explaining its stage. Unit tests in `tests/unit/feature.test.js` (rail classes, active phase open, banner lines, filter chip set, row attributes, empty stage)
- [X] T049 [P] [US3] Add feature page CSS to `src/styles/input.css`: header and ring, tabs (scroll sideways on narrow screens), rail (scroll sideways on narrow screens) and caption, amber banner, filter chips and search input, task list columns with fixed-width kind and file chips so they line up, one-line truncation in summaries and wrapped text when open, detail panel beside the list (stacked below it on narrow screens), selected-row outline, `:target` highlight

### Browser behavior for User Story 3

- [X] T050 [US3] Create `src/client/feature.js` (FR-032–FR-036): `init(root, { document, window, navigator, setTimeout, state })`: **rail ↔ list**: choosing a rail block or a phase summary opens that phase (native `<details name="phases">` closes the others; for browsers without exclusive groups, close the others in JS), choosing the open one closes it, `aria-current` on the rail block and the caption follow the open phase; **filters**: un-hide the filter bar; chips (All/Open/Tests/kind) and the text filter combine (text matches `data-text` and `data-files`); while any filter is active every phase with matches is open and phases without matches are hidden; each chip shows its match count; no match → "No tasks match" with a "Clear filters" button; **expand/collapse all**; **selection**: opening a task row selects it (second click collapses), fills and un-hides the detail panel (ID, status via `DISPLAY_LABEL`, full text, phase, markers, file paths, "Waiting on T012, T013" from `data-waiting-on` for blocked tasks, "Copy ID", "View source line" → `tasks.html#L<line>`, close button) and sets the address fragment with `history.replaceState`; **address**: on init a `#task-…` fragment opens its phase, expands and selects the task and scrolls it into view (FR-036, US3 AC10); **copy**: `navigator.clipboard.writeText` with a text-selection fallback and a short "Copied" confirmation; `save(root)` returns `{chips, text, selected}`; the renderer (T048) adds `data-waiting-on`, `data-line` and `data-phase` to task rows for this module. Unit tests with happy-dom (accordion incl. choose-again-to-close, filter combinations and counts, empty-result message and clear, selection fills panel incl. "Waiting on T012", fragment on init, copy success and fallback, save/init restores) in `tests/unit/client-feature.test.js`
- [X] T051 [US3] Register `feature.js` for the `feature` page in `src/client/app.js` and add it to `CLIENT_MODULES` in `src/cli/main.js`; make the Up next "View task", tree task IDs and search results link to `features/<dir>/index.html#<anchor>` (already rendered; verify with a unit test in `tests/unit/overview.test.js`); update `tests/unit/client-app.test.js`

### E2E for User Story 3

- [X] T052 [US3] Add backticked file paths to a few `002-beta` task texts in `tests/fixtures/projects/mixed` (for example `src/list.go`, `src/list_test.go`, `web/List.vue`; checkbox states and counts unchanged) so the kind and Tests chips have data, and list them in `tests/fixtures/projects/README.md`; then create `tests/e2e/us3-feature.spec.js` on `mixed`, `complete` and `nonstandard`: `US3 FR-030 header shows pill, folder, title, counts and ring` (and "Complete" + check for a complete feature); `US3 FR-031 tabs list only existing documents in order`; `US3 FR-008 Contracts tab lists its documents without a navigation`; `US3 FR-032 rail block widths proportional to task counts`; `US3 FR-032 choosing a phase opens it and closes the other; choosing it again closes it`; `US3 FR-034 Open filter lists 10 open tasks and hides phases without matches`; `US3 FR-034 text filter narrows and a nonsense word shows No tasks match with Clear filters`; `US3 FR-034 Tests chip and each kind chip filter the list and show their counts`; `US3 duplicate task IDs can each be selected on their own` (nonstandard); `US3 FR-035 kind and file chips line up in columns` (bounding boxes); `US3 FR-036 clicking T018 expands it and the detail panel says Waiting on T011`; `US3 FR-036 Copy ID puts T018 on the clipboard` (grant clipboard permissions in Chromium; Firefox/WebKit check the confirmation only); `US3 FR-044 View source line opens tasks.html#L31 highlighted`; `US3 FR-036 opening an address with #task-… shows the task open, selected and in view`; `US3 FR-033 amber banner and Show lines reveal the source lines` (nonstandard); `US3 SC-009 detail panel right of the task list`
- [X] T053 [US3] Add `CHANGELOG.md` lines for feature pages (tabs, phase rail, filters, kind/file chips, detail panel, Copy ID, View source line, addressable tasks)

**Checkpoint**: Feature pages are fully usable; US1–US3 suites pass.

---

## Phase 6: User Story 4 - Choose a light, dark or system theme (Priority: P2)

**Goal**: A Light / Dark / System switch on every page, applied before first paint, remembered per browser, following the OS in System mode, with always-dark regions and an identical next orange.

**Independent Test**: With the OS in light mode choose Dark, open a feature page and a document, reload, trigger a live update: dark from first paint everywhere; then System follows OS changes (quickstart Scenario 4).

- [X] T054 [US4] Verify and complete the always-dark regions and token coverage in `src/styles/input.css` (FR-047): `.always-dark` on sidebar, icon rail, Up next bar and map tooltip in `src/render/layout.js`, `src/render/overview.js` and `src/render/taskmap.js`; `--next` identical in both themes; done/open/blocked/next/warning keep their color family; add a unit test in `tests/unit/layout.test.js` that the rendered sidebar, icon rail, Up next bar and tooltip carry `always-dark` (rendered HTML only, no file access, §IV); the identical next orange in both themes is checked end to end in T056 (`US4 FR-047`, computed colors)
- [X] T055 [US4] Make the theme switch follow OS changes in System mode in `src/client/app.js` (FR-045): nothing to do in JS when `data-theme` is absent (CSS `color-scheme: light dark` follows the OS), but keep the switch's `aria-pressed` correct after live updates (`reinit`) and across pages; ensure `live.js` never touches `<html data-theme>`; add unit tests in `tests/unit/client-app.test.js` (reinit keeps the pressed button, System removes `data-theme`)
- [X] T056 [US4] Create `tests/e2e/us4-theme.spec.js`: `US4 FR-045 first visit shows System and follows the OS scheme` (`page.emulateMedia({colorScheme})`); `US4 FR-045 System follows an OS switch without reload`; `US4 FR-046 SC-005 Dark applies from first paint on another page and after reload` (assert `data-theme="dark"` and the computed `--bg` before `DOMContentLoaded` handlers run, e.g. via `page.addInitScript` observer recording the first paint's background); `US4 FR-046 theme survives a live update`; `US4 FR-047 sidebar, Up next bar and tooltip are dark and next orange is identical in both themes`; `US4 FR-046 storage refused: the switch changes the current page and the next page is System` (`addInitScript` making `localStorage` throw); `US4 SC-010 body text contrast ≥ 4.5:1 in both themes on overview, feature and document pages` (compute from computed colors, including the rose pill)
- [X] T057 [US4] Add `CHANGELOG.md` lines for the theme switch

**Checkpoint**: Theme works everywhere; US1–US4 suites pass.

---

## Phase 7: User Story 5 - Read specs and other documents in the document reader (Priority: P3)

**Goal**: The reader layout for every document (icon rail, grouped document list, reading column, "On this page" panel with progress), numbered sections, "Expand all", "Raw markdown", and the structured `spec.md` view — with no source line lost.

**Independent Test**: Serve this repository; open `001-speckit-eye-dashboard` → Specification and a fixture's `plan.md` and contract; check the document list, contents panel, every structured section, Expand all, Raw markdown, and that every non-blank line appears (quickstart Scenario 5).

### Parsing for User Story 5

- [X] T058 [US5] Create `src/parse/spec-structure.js` (contracts/spec-md-structure.md, research D13): export `parseSpecStructure(text) → {metadata, request, blocks}`; blocks in file order with 1-based inclusive `from`/`to`, **disjoint and covering every line**; never throws, never rewrites text; lines inside fences and HTML comments (001 `visibleLines`) never matched; recognize `title` (first `# ` heading), metadata (`**Feature Branch**:`, `**Created**:`, `**Status**:` before the first `## `, code spans unwrapped in branch, missing fields omitted, `null` when none), request (`**Input**:` with a leading `User description:` and one pair of surrounding `"…"`/`“…”` removed for display only), `section` for every `## ` heading (`number` two digits, `heading`, `anchor` with 001 `slugify`), `clarifications` (`## Clarifications` → sessions from `### Session <date>`, items `- Q: … → A: …` or `->` with indented continuation lines, badge `yes`/`no`/`neutral` from the answer's first word after trimming punctuation), `story` (`### User Story <n> - <title> (Priority: P<k>)` up to the next `### `, `## ` or `---`; `description`, `**Why this priority**:` → `why`, `**Independent Test**:` → `test`, `**Acceptance Scenarios**:` numbered items → `scenarios` with `given`/`when`/`then` only when exactly one of each appears in that order, trailing `,`/`;` trimmed, else `raw`), `requirements` (`### Functional Requirements` → areas from bold-only paragraphs, items `**FR-<id>**:` with continuation lines, unnamed area for items before the first area, other lines as `plain`), `entities` (`### Key Entities` → `- **<name>**: <description>` items, others `plain`), everything else `plain` with its Markdown source. Unit tests for every rule and each example row of the contract, plus the **coverage invariant** asserted over in-memory sample texts only (constitution §IV: no file access in unit tests): a new `tests/unit/spec-samples.js` exporting template strings — a full template-conformant spec, one with every deviation of the contract (non-template sections, clarifications not in `Q: … → A:` form, scenarios without a single Given/When/Then, fences and HTML comments containing look-alike lines, empty file, file without `## ` headings) and an excerpt reproducing the structure of this repository's 001 spec — in `tests/unit/spec-structure.test.js`. The same invariant over the real files of `tests/fixtures/projects/**` and this repository's `specs/**` is checked end to end in T065 (SC-012)
- [X] T059 [US5] Add `feature.spec` to `src/model/build-model.js` (`parseSpecStructure` of the already-read `spec.md`, `null` without it) and resolve each story's **phase links** (phases whose `mergedStory` or groups carry the story's label, with their counts); unit tests in `tests/unit/build-model.test.js`

### Rendering for User Story 5

- [X] T060 [US5] Create `src/render/spec-view.js` (FR-042, FR-043, contracts/spec-md-structure.md "Rendering notes"): export `renderSpecBlocks(spec, { md, feature })` → HTML: metadata card (branch, created, status); request as a pull quote; numbered `section` headings with anchors; clarifications "N answered · M sessions" in the heading, sessions as `<details data-key>` (first open) with question count, items as question, fixed-width badge ("Yes", "No", "A") and answer text in one aligned column, the first three visible and the rest in `<details data-part="more"><summary>Show N more answers</summary>`; stories as `<details data-key>` cards (first open, others collapsed rows with ID, priority badge, title, scenario count) with description, "Why this priority", "Independent test" boxes, a numbered Given / When / Then table (`raw` scenarios as one full-width row) and the linked phase with done/total; requirements with hidden area chips (`data-part="areas"`, "All" default), area headings, each requirement with its ID chip and `MUST NOT`, `MUST`, `SHOULD NOT`, `SHOULD`, `MAY` as whole upper-case words wrapped in `<mark data-kw>` (longest first, text nodes only); entities as a two-column card grid; `plain` blocks via the 001 Markdown renderer; all text through the shared `markdown-it` instance. Unit tests (each structure, badges, "Show 10 more answers", keyword highlight not inside code, `<script>` stays text, **every non-blank source line's text appears in the output** for every sample of `tests/unit/spec-samples.js`) in `tests/unit/spec-view.test.js`
- [X] T061 [US5] Create `src/render/reader.js` replacing `src/render/artifact.js` (FR-038–FR-041, FR-044, contracts/routes.md "Document page `<main>`"): export `renderDocument(artifact, { base, project, feature, assessment, md })` rendering, inside `renderPage({ page: "document" })`, `<nav data-region="doc-list" data-keep-scroll="docs">` (back link to the feature page, feature title and status, groups Define/Design/Contracts/Build/Other from `feature.documents.groups` with contracts by title, empty groups left out, current marked `aria-current`; project documents get the project document list), `<article data-region="doc" data-key="<source path>">` with header (eyebrow "<KIND> · <file>", `<h1>`, hidden `<button data-part="expand-all">`, `<details data-part="raw"><summary>Raw markdown</summary><pre>` exact source`</pre></details>`), then `renderSpecBlocks` for `spec.md` or the 001 Markdown rendering with top-level sections numbered 01, 02, … (tasks.md keeps the `L<line>` anchors, T045), and `<nav data-region="toc" aria-label="On this page">` listing sections (and, for a specification, its stories under the user-scenarios section as `US1 · <title>`) with a hidden `<div data-part="progress">`; the 001 deleted-document notice stays; update `src/render/site.js` to use it; delete `src/render/artifact.js` and move/replace `tests/unit/artifact.test.js` with `tests/unit/reader.test.js` (group placement, current mark, numbering, raw source exact, TOC entries, project list)
- [X] T062 [P] [US5] Add reader CSS to `src/styles/input.css`: three-column reader (doc list, reading column with comfortable measure in Instrument Serif headings / Geist body per handoff, contents panel), metadata card, pull quote, clarification rows with the badge column aligned, story cards and Given/When/Then table, requirement chips and `mark[data-kw]`, entity grid; on narrow screens hide the contents panel and put the document list behind a `<details>` control

### Browser behavior for User Story 5

- [X] T063 [US5] Create `src/client/reader.js` (FR-040–FR-042): `init(root, { document, window, IntersectionObserver, state })`: **contents**: observe section headings, mark the section in view (`aria-current="location"`) and set the progress indicator (`w-pct-N` class from scroll position; un-hide it), links jump to sections; **expand all**: un-hide the button, open every `<details>` inside the article (and toggle to "Collapse all"); **raw view**: with JS the `Raw markdown` control swaps the formatted view for the source view and back; **show more** answers; **requirement area chips**: un-hide, "All" default, picking an area hides the other areas' requirements; `save(root)` returns `{raw, expanded, area, more}`. Unit tests with happy-dom and a fake `IntersectionObserver` (current section, progress class, expand all, raw toggle, area filter, save/init restore) in `tests/unit/client-reader.test.js`
- [X] T064 [US5] Register `reader.js` for the `document` page in `src/client/app.js`, add it to `CLIENT_MODULES` in `src/cli/main.js`; update `tests/unit/client-app.test.js`

### E2E for User Story 5

- [X] T065 [US5] Create `tests/e2e/us5-reader.spec.js` (replacing `tests/e2e/us3-artifacts.spec.js`, whose still-valid 001 checks — every artifact reachable, raw HTML shown as text, deleted-document notice — move here), serving this repository and the `artifacts` fixture: `US5 FR-038 FR-039 document list groups Define, Design, Contracts, Build, Other with the current document marked`; `US5 FR-042 metadata card and pull quote`; `US5 FR-042 clarifications show N answered · M sessions, first session open, Show N more answers and aligned badges` (badge column x-positions equal); `US5 FR-042 user story card has why, independent test, Given/When/Then table and phase progress`; `US5 FR-042 area chip narrows requirements and MUST is highlighted`; `US5 FR-041 contents panel marks the section in view and progress advances on scroll`; `US5 FR-040 Raw markdown shows the exact source and toggles back`; `US5 FR-040 Expand all opens every collapsed part`; `US5 FR-043 SC-012 every non-blank source line appears in the formatted view` (for every `spec.md` of this repository's `specs/*/` and of `tests/fixtures/projects/**`, read by the test runner, which is allowed in E2E tests); `US5 FR-043 a non-template section renders as ordinary text`; `US5 FR-007 document pages show the icon rail instead of the sidebar`; delete `tests/e2e/us3-artifacts.spec.js`
- [X] T066 [US5] Add `CHANGELOG.md` lines for the document reader and the structured `spec.md` view

**Checkpoint**: All documents use the reader; US1–US5 suites pass.

---

## Phase 8: User Story 6 - Find a task, feature or document with search (Priority: P3)

**Goal**: ⌘K / Ctrl+K search over tasks, features, document titles and section headings, grouped results, keyboard navigation, same behavior in serve and static builds, fresh after live updates.

**Independent Test**: In serve mode and in a static build under `/eye/`, open search by shortcut and by the sidebar entry; search a task ID, a word from a task text, a feature name and a heading; open one result of each kind (quickstart Scenario 7).

- [X] T067 [US6] Create `src/render/search-index.js` (contracts/search-index.md, research D14): export `buildSearchIndex(project) → {version: 1, entries}` with entries in page order (features in folder order, each followed by its tasks in file order and its documents in 001 artifact order with every `##`/`###` heading, then project documents): `task` (`label` ID or "No ID", `detail` text as written, `context` "<number> · <title>", `state` display state, `url` `features/<dir>/index.html#<anchor>` or `#phase-<n>` for ID-less tasks), `feature` (`label` "<number> · <title>", `detail` "<statusLabel> · d/t", `url`), `document` (title, file name, context "Project" or "Assessment: <slug>" for project documents), `heading` (heading text, document title, `url` with the 001 slug anchor); `terms` = lower-cased `label + " " + detail` for tasks and documents, `label` only for features and headings (decisions.md), with whitespace collapsed; no document body text. Unit tests (order, every field, headings from `##`/`###` only, body words absent, sub-path-free URLs) in `tests/unit/search-index.test.js`
- [X] T068 [US6] Publish `assets/search-index.json` from `src/render/site.js` with `JSON_TYPE` in both modes; update `tests/unit/site.test.js`
- [X] T069 [US6] Create `src/client/search.js` (FR-049–FR-049c, contracts/search-index.md "Matching"): export pure `search(entries, query) → {groups: [{name: "Tasks"|"Features"|"Documents", items, more}], empty}` (lower-case, split on whitespace, empty query → nothing, every word must occur in `terms`, ranking: exact full task ID first, then `label` starting with the first word, then the rest, ties in page order; documents and headings in one group; first 8 per group plus "N more"); `init(root, { document, window, fetch, navigator })` un-hides every `[data-part="search"]` entry, opens `<dialog data-region="search">` with `showModal()` on ⌘K (macOS, from `navigator.platform`/`userAgentData`) or Ctrl+K and on entry click, focuses the input, fetches `{base}assets/search-index.json` on first use (relative to `document.body.dataset.base`) and again on the next use after a `sk:change` event, renders results (tasks with state mark, ID, text and feature; features with number, name, status; documents/headings with title and feature) via `textContent`, arrow keys move the active option (`aria-activedescendant`), Enter or click navigates to `base + url`, "N more" expands the group, Escape closes and focus returns to the opener, "No results for "<query>"" when nothing matches. Unit tests (matching and ranking rules, 8 + more, empty query, no match; happy-dom: shortcut opens, Escape restores focus, arrow/Enter navigation, fetch once then again after `sk:change`, base prefix) in `tests/unit/client-search.test.js`
- [X] T070 [US6] Register `search.js` for all pages in `src/client/app.js` and add it to `CLIENT_MODULES` in `src/cli/main.js`; add dialog and result CSS to `src/styles/input.css`; update `tests/unit/client-app.test.js`
- [X] T071 [US6] Create `tests/e2e/us6-search.spec.js`: `US6 FR-049 Ctrl/⌘K and the sidebar entry open search with the cursor in the box; Escape closes and returns focus`; `US6 FR-049a T018 is the first result with state mark, text and feature`; `US6 FR-049a every typed word must match across groups`; `US6 FR-049b Enter on a task opens its feature page with the task selected and in view`; `US6 FR-049b a heading result opens the reader at that heading`; `US6 FR-049a no match shows the message`; `US6 FR-049a a word only in a document body finds nothing`; `US6 FR-049c static build under /eye/ searches the same without other hosts` (build with `--base /eye/`, serve under the sub-path, record requests); `US6 FR-049c a task added to tasks.md is found after the live update without reload`
- [X] T072 [US6] Add to `tests/e2e/scale.spec.js`: `US6 SC-014 results update within 200 ms per keystroke with 2,000 tasks and a full ID is first`; add `CHANGELOG.md` lines for search

**Checkpoint**: All six stories are independently functional.

---

## Phase 9: Polish & Cross-Cutting Concerns

**Purpose**: Live-state, no-JS, mobile, build, scale and security coverage in three engines; docs, README and CHANGELOG

- [X] T073 [P] Create `tests/e2e/live-state.spec.js` (replacing `tests/e2e/us2-live.spec.js`, whose 001 checks move here), following quickstart Scenario 6: `US1 FR-020 FR-051 order, filter, depth, expanded rows and tree scroll survive a live update`; `US2 FR-028 FR-051 map mode survives a live update`; `US3 FR-051 feature filters, text filter, open phase, selected task and list scroll survive a live update`; `US5 FR-051 raw view, expansions and area chip survive a live update`; `FR-051 changed counts are briefly highlighted (stats, segments, sidebar)`; `FR-001 SC-008 changes appear within 2 s`; `US3 FR-051 deleting the selected task clears the selection`; delete `tests/e2e/us2-live.spec.js` and, in the same change, point the `wsl` job of `.github/workflows/ci.yml` at `pnpm run test:e2e tests/e2e/live-state.spec.js --project chromium`
- [X] T074 [P] Create `tests/e2e/nojs.spec.js` (runs in `chromium-nojs`): `FR-053 SC-011 overview tree, phases and task rows open and close without scripts`; `FR-053 map squares jump to their tree rows`; `FR-053 feature page tabs, phases, task rows and Show lines work`; `FR-053 reader clarification sessions, stories, Show more answers and Raw markdown open and close`; `FR-053 FR-009 mobile menu opens without scripts`; `FR-048 page follows the OS theme`; `FR-053 search entry, theme switch, order, depth and filters are not shown`
- [X] T075 [P] Create `tests/e2e/mobile.spec.js` (runs in `chromium-mobile`): `FR-009 SC-009 every page type has no horizontal page scroll at 375 px`; `FR-009 sidebar is behind the menu control`; `FR-010 tree comes before the map`; `FR-024 FR-026 tapping a square reveals its task without a tooltip`; `FR-031 FR-032 tabs and phase rail scroll sideways inside their own area`; `FR-039 reader hides the contents panel and puts the document list behind a control`; `FR-050 buttons and links have hit targets of at least 36 px` (squares excepted)
- [X] T076 [P] Update `tests/e2e/us4-build.spec.js`: `FR-052 static build under a sub-path has the same pages as serve mode, including feature pages, fonts and search index`; `FR-004 D6 data-driven widths and bundled fonts work under the sub-path` (a segment and a phase-rail block have the expected non-zero widths from their `w-pct-N` classes, `document.fonts.check` confirms Geist and Instrument Serif loaded from `assets/fonts/`); `FR-052 theme, map mode, filters, reader and search work in the static build`; `FR-005 static footer shows the generation time`
- [X] T077 [P] Update `tests/e2e/security.spec.js`: `FR-004 SC-013 no page requests another origin` (overview, feature, document; with search opened and fonts loaded); `FR-004 no element carries a style attribute and no inline script exists` on every page type; `FR-001 document raw HTML and scripts stay text in the structured spec view, tooltips and detail panel`
- [X] T078 [P] Update `tests/e2e/self-counts.spec.js` (SC-004): counts on stats card, segments, sidebar, tree, map legend and feature pages match the checkbox counts of this repository's `specs/*/tasks.md`
- [X] T079 Update `tests/e2e/scale.spec.js` (SC-007): overview and each sampled feature page load within 2 s with 50 features / 2,000 tasks, static build within 30 s
- [ ] T080 Run the full E2E suite in `chromium`, `chromium-nojs` and `chromium-mobile` locally and confirm CI passes in `firefox` and `webkit` (SC-016); fix engine-specific issues (for example the exclusive `<details name>` fallback in `src/client/feature.js`, `line-clamp` fallback, clipboard permission differences in tests)
- [X] T081 [P] Update `docs/usage.md` (FR-054): new layout (sidebar, icon rail, mobile menu), overview (stats card, Up next, tree order/depth/filter, warning rows), task map (modes, colors including blocked rose, tooltip, reveal, large-project modes), feature pages (tabs, rail, filters, chips, detail panel, addresses), reader (document list, contents, structured `spec.md`, Raw markdown), theme switch, search (shortcut, what is and is not searched), no-JS behavior, supported browsers (FR-055)
- [X] T082 [P] Update `docs/architecture.md`: new modules (`model/shares.js`, `ranks.js`, `warnings.js`, `task-files.js`; `parse/spec-structure.js`; `render/icons.js`, `taskmap.js`, `task-text.js`, `feature.js`, `reader.js`, `spec-view.js`, `search-index.js`), the browser module `init`/`save` convention and `app.js` bootstrap, site map entries (feature pages, fonts, search index, binary bodies), theme tokens with `light-dark()`, `w-pct` utilities and the CSP rationale, bundled fonts and `scripts/copy-assets.js`; check `docs/hosting.md` still matches (sub-path, fonts relative to the stylesheet)
- [X] T083 [P] Update `README.md` (FR-054, §VIII): new `docs/screenshot.png` of the redesigned overview (taken from the `mixed` fixture at 1440 × 900 with Playwright and committed) and a feature list naming the task map, feature pages, document reader, theme switch and search; user-facing content only
- [X] T084 Review `CHANGELOG.md` `## [Unreleased]` for a complete, user-facing description of the redesign (layout, overview, task map, feature pages, reader, theme, search, removed header menu, changed page addresses) (§VI, research D18)
- [X] T085 Remove dead code and leftovers (§II): unused exports from `src/render/overview.js` (`featureStatus` if superseded by `feature.status`, old grid helpers), unused CSS of 001, the `build:css` references in docs, and any 001 test that tests removed behavior; run `pnpm test`, `pnpm run test:coverage` (every new module covered) and the E2E suite
- [X] T086 Run all quickstart.md scenarios 1–8 by hand against `mixed`, `nonstandard`, this repository and a static build under `/eye/`, and fix any discrepancy

---

## Dependencies & Execution Order

### Phase Dependencies

- **Setup (Phase 1)**: No dependencies — start immediately.
- **Foundational (Phase 2)**: Depends on Setup (fonts, `happy-dom`, `build:assets`). **Blocks all user stories.**
- **US1 (Phase 3)**: Depends on Foundational. MVP.
- **US2 (Phase 4)**: Depends on Foundational and on US1's tree (`tree.reveal`, `data-hl` targets, overview layout slot).
- **US3 (Phase 5)**: Depends on Foundational (feature page route T022, documents T015). Independent of US1/US2 except that tree task IDs and Up next "View task" link to its addresses (already rendered by US1).
- **US4 (Phase 6)**: Depends on Foundational (generated `assets/theme.js`, switch, tokens). Its always-dark checks touch regions from US1/US2; run after them or skip those assertions until they exist.
- **US5 (Phase 7)**: Depends on Foundational (rail, documents). Story phase links read phases from the existing model.
- **US6 (Phase 8)**: Depends on Foundational; task results link to US3's feature page addresses and heading results to US5's anchors (both already exist as 001 slugs / T014 anchors, so search can be built in parallel).
- **Polish (Phase 9)**: Depends on all stories.

### User Story Dependencies

```text
Setup → Foundational ─┬─ US1 (P1) ── US2 (P2)
                      ├─ US3 (P2)
                      ├─ US4 (P2)   (assertions on US1/US2 regions)
                      ├─ US5 (P3)
                      └─ US6 (P3)
                                   └────────── Polish
```

### Within Each User Story

- Model tasks before rendering; rendering before the browser module; module before its registration in `app.js`/`CLIENT_MODULES`; E2E suite after the story's code; CHANGELOG last.
- Every module task ships with its unit tests (§IV).
- Tasks touching `src/styles/input.css`, `src/client/app.js`, `src/cli/main.js` or `src/model/build-model.js` are sequential across stories (shared files).

### Parallel Opportunities

- Phase 1: T003, T004, T005 in parallel after T001; T002 after T001.
- Phase 2: T007, T009, T010, T012, T013, T016 in parallel; T017 after T016; T008 before T009/T010/T011; T018 → T019/T020 → T021 → T022 → T023.
- US1: T024 and T025 in parallel; T030 (CSS) in parallel with T027/T028.
- US2: T037 (CSS) in parallel with T035.
- US3: T043 and T045 in parallel; T049 (CSS) in parallel with T047/T048.
- US5: T062 (CSS) in parallel with T060/T061.
- After Foundational, US3, US4, US5 and US6 can be developed in parallel with US1 → US2 by different people (watch the shared files above).
- Polish: T073–T078 and T081–T083 in parallel.

---

## Parallel Example: User Story 1

```bash
# Model modules together (different files):
Task: "T024 Create src/model/ranks.js with tests/unit/ranks.test.js"
Task: "T025 Create src/model/warnings.js with tests/unit/warnings.test.js"

# Styles while rendering is written:
Task: "T030 Add overview CSS to src/styles/input.css"
Task: "T027 Rewrite page head, stats card and Up next in src/render/overview.js"
```

## Parallel Example: User Story 3

```bash
Task: "T043 Create src/model/task-files.js with tests/unit/task-files.test.js"
Task: "T045 Add renderInline and L<line> anchors in src/render/markdown.js"
Task: "T049 Add feature page CSS to src/styles/input.css"
```

## Parallel Example: Polish

```bash
Task: "T073 tests/e2e/live-state.spec.js"
Task: "T074 tests/e2e/nojs.spec.js"
Task: "T075 tests/e2e/mobile.spec.js"
Task: "T081 docs/usage.md"
Task: "T082 docs/architecture.md"
```

---

## Implementation Strategy

### MVP First (User Story 1 Only)

1. Complete Phase 1: Setup.
2. Complete Phase 2: Foundational (shell, tokens, fonts, bootstrap, live save/reinit).
3. Complete Phase 3: US1 — the redesigned overview.
4. **STOP and VALIDATE**: `tests/e2e/us1-overview.spec.js` and quickstart Scenario 1.
5. The tool is usable end to end (001 grid still on the right, documents in the old body inside the rail layout).

### Incremental Delivery

1. Setup + Foundational → new shell everywhere.
2. US1 → overview (MVP).
3. US2 → task map replaces the 001 grid.
4. US3 → feature pages.
5. US4 → theme switch verified across all pages.
6. US5 → document reader.
7. US6 → search.
8. Polish → no-JS, mobile, three engines, docs, README, CHANGELOG.

Each step keeps the unit and E2E suites green, so any checkpoint is a working tool.

---

## Notes

- [P] tasks = different files, no dependency on an unfinished task.
- [Story] label maps each task to its user story for traceability (§V); E2E test names carry `USn FR-xxx`.
- Never add a `style` attribute, inline script or third-party URL; the security suite checks all three.
- Task text and document text are never rewritten, only visually truncated.
- Commit after each task or logical group; stop at any checkpoint to validate a story on its own.

Input: Design documents from specs/002-dashboard-redesign/

Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/ (routes.md, spec-md-structure.md, search-index.md), quickstart.md

Tests: REQUIRED. Constitution §IV (unit tests for all code) and §V (E2E coverage of every user story and major requirement) are non-negotiable. Each logic task names its unit test file and ships the module and its tests together. Each story phase has its own Playwright suite. E2E test names MUST start with the story and requirement ids they cover, for example US2 FR-024 shows the tooltip 500 ms after the pointer enters (DEVELOPMENT.md rule, §V traceability).

Organization: Tasks are grouped by user story so each story can be implemented and tested as its own increment. Story order follows the spec priorities: US1 (P1) → US2, US3, US4 (P2) → US5, US6 (P3).

02 Format: [ID] [P?] [Story] Description

  • [P]: Can run in parallel (different files, no dependency on an unfinished task)
  • [Story]: The user story the task belongs to (US1–US6)
  • All paths are relative to the repository root

03 Conventions that apply to every task

  • Plain JavaScript ES modules with JSDoc, Node.js ≥ 22, no TypeScript, no UI framework and no bundler (research D1). The runtime dependency list stays markdown-it only.
  • All server HTML is produced with the html tagged template from src/render/html.js; only trusted fragments go through raw() (001 R4). Rendered documents and task text never run scripts: markdown-it with raw HTML off (001 FR-024).
  • CSP is unchanged (default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:): no element may carry a style="…" attribute, no inline <script>, no eval/new Function. Data-driven widths use the w-pct-<0..100> classes (research D6); SVG presentation attributes (stroke-dasharray, stroke) are allowed; positions computed in the browser are set through the CSSOM (el.style.left = …).
  • Without JavaScript every page stays readable and navigable, and the tree, phases, task rows, clarification sessions, stories and the mobile menu open and close (<details>). Controls that only work with scripts (theme switch, order, depth, filters, search entry, map mode, expand all, detail panel) are rendered with the hidden attribute and un-hidden by their module's init (FR-053).
  • Browser modules live in src/client/, are served as assets/<name>.js, and follow one contract (research D2, contracts/routes.md "Browser modules"): export function init(root, deps) — idempotent, called on load and after every live swap — and, when the module holds page-local state, export function save(root) → object, read back via init(root, { state, … }). deps injects document, window, storage, setTimeout/clearTimeout, fetch, navigator so the module is unit-testable. Every localStorage access goes through src/client/prefs.js.
  • Unit tests use node:test + node:assert/strict, live in tests/unit/, and never touch the real file system, network or a browser (§IV). Client modules are tested against happy-dom (new Window() per test, research D11) in files named tests/unit/client-<module>.test.js; layout-dependent code is tested with stubbed getBoundingClientRect values. Existing hand-written fakes (tests/unit/fake-fs.js, tests/unit/fake-reader.js) stay.
  • Colors exist once, as light-dark() tokens on :root in src/styles/input.css (research D3); values come from mockups/001-first-redesign/DESIGN_HANDOFF.md's token table plus the rose blocked tokens of research D15. Utilities reference tokens (bg-(--surface)); no Tailwind dark: variant.
  • Icons come only from src/render/icons.js (research D5). Fonts come only from dist/fonts/ (research D4).
  • Ordering rules exist only in src/model/ranks.js; proportional widths only via shares() in src/model/shares.js; task states keep 001's rules in src/model/task-state.js (§III).
  • The target project is never written (001 FR-006). Only src/project/reader.js reads input files; only src/build/build.js writes output files.
  • Every user-visible change adds a line under ## [Unreleased] in CHANGELOG.md (§VI, research D18). The version in package.json stays 1.0.0.
  • Fixture counts quoted in tasks come from tests/fixtures/projects/README.md and quickstart.md (mixed: 4 features, 65 tasks, 40 done; 002-beta T018 depends on the open T011).

04 Phase 1: Setup (Shared Infrastructure)

Purpose: Dependencies, asset build, browser test matrix and CI

  • T001 Update package.json: add devDependencies @fontsource-variable/geist, @fontsource-variable/geist-mono, @fontsource/instrument-serif and happy-dom (latest majors); (the tailwindcss and @tailwindcss/cli ^4.1 requirement, needed for @source inline, is already in place since commit 441f041); replace script build:css with build:assets = tailwindcss -i src/styles/input.css -o dist/styles.css --minify && node scripts/copy-assets.js; set prepack = pnpm run build:assets and test:e2e = pnpm run build:assets && playwright test; add "dist/fonts/" to "files" next to "dist/styles.css" (scripts/ stays out of files: build-time only); run pnpm install to update pnpm-lock.yaml
  • T002 Create scripts/copy-assets.js (research D4): export a pure selectFontFiles(entries) that, given {package, files: string[]} listings of the three @fontsource* packages, returns {from, to} pairs for only the Latin and Latin-extended woff2 files (*-latin-wght-normal.woff2, *-latin-ext-wght-normal.woff2 for the variable packages; *-latin-400-normal.woff2, *-latin-400-italic.woff2, *-latin-ext-400-*.woff2 for Instrument Serif) plus each package's LICENSE as OFL-<family>.txt (OFL-geist.txt, OFL-geist-mono.txt, OFL-instrument-serif.txt), all targeted at dist/fonts/; and a thin main() (not unit-tested, one line per I/O call) that lists the package folders via fs.readdirSync, calls selectFontFiles, creates dist/fonts/ and copies the files; run only when executed directly (import.meta.url check). Unit tests for selectFontFiles (keeps Latin/Latin-ext only, drops Cyrillic/other subsets and .woff, maps licence names, stable order) in tests/unit/copy-assets.test.js
  • T003 [P] Update playwright.config.js (research D16): projects chromium (Desktop Chrome), firefox (Desktop Firefox), webkit (Desktop Safari), chromium-nojs (Desktop Chrome, javaScriptEnabled: false, testMatch: /nojs\.spec\.js/) and chromium-mobile (viewport: {width: 375, height: 812}, hasTouch: true, isMobile: true, Chromium, testMatch: /mobile\.spec\.js/); the three desktop projects testIgnore: [/nojs\.spec\.js/, /mobile\.spec\.js/]; default viewport 1440 × 900 for desktop projects (SC-009); keep workers: 1, retries, reporter, timeout; update the header comment (three engines per FR-055)
  • T004 [P] Update .github/workflows/ci.yml: in the e2e job replace playwright install --with-deps chromium with playwright install --with-deps chromium firefox webkit; keep the OS matrix. The wsl job keeps installing Chromium only and runs its live-update suite with --project chromium (it tests the file watcher under WSL, not browser engines); its test path is switched to tests/e2e/live-state.spec.js in T073
  • T005 [P] Update DEVELOPMENT.md: build:assets instead of build:css (stylesheet and fonts), happy-dom in unit tests of src/client/ modules, the browser-module init/save convention, the five Playwright projects and that the cloud container has Chromium only (research D16), the new scripts/ folder in the project structure, and in the release checklist that package contents now include dist/fonts/

Checkpoint: pnpm install, pnpm run build:assets writes dist/styles.css and dist/fonts/*.woff2 + OFL-*.txt; pnpm test passes.


05 Phase 2: Foundational (Blocking Prerequisites)

Purpose: Tokens, fonts, icons, binary site entries, the new app shell (sidebar, icon rail, mobile menu), the browser-module bootstrap, live-update save/reinit, and the derived model fields every story reads

⚠️ CRITICAL: No user story work can begin until this phase is complete

Styles, fonts, icons

  • T006 Rewrite the base of src/styles/input.css (research D3, D4, D6, D15): keep @import "tailwindcss", @plugin "@tailwindcss/typography", @source "../render", @source "../client"; add @font-face rules for Geist (variable, font-weight: 100 900), Geist Mono (variable) and Instrument Serif (400 normal and italic) with src: url(fonts/<file>.woff2) format("woff2") relative to the stylesheet and font-display: swap; define every color token of the handoff's token table once on :root with light-dark(<light>, <dark>) (e.g. --bg: light-dark(#F4F3EF, #0E1013)), plus --rose: light-dark(#BE123C, #FB7185), --rose-bg: light-dark(#FFE4E6, #3F1522), --rose-text: light-dark(#9F1239, #FDA4AF) and a single --next orange #C2410C that is not a light-dark() pair (FR-047); :root { color-scheme: light dark }, :root[data-theme="light"] { color-scheme: light }, :root[data-theme="dark"] { color-scheme: dark }; a .always-dark { color-scheme: dark } utility for sidebar, rail, Up next bar and tooltip; @theme font families --font-sans: "Geist Variable", --font-mono: "Geist Mono Variable", --font-serif: "Instrument Serif"; @utility w-pct-* { width: calc(--value(integer) * 1%); } with @source inline("w-pct-{0..100}"); keep the global reduced-motion rule (001 FR-036); focus ring utility (visible outline on :focus-visible, FR-050); confirm by hand that pnpm run build:assets output contains .w-pct-0 … .w-pct-100 and the @font-face rules with relative fonts/ URLs (guarded automatically by the E2E test added in T076)
  • T007 [P] Create src/render/icons.js (research D5): ISC licence notice of Lucide in the file header; an ICONS map of SVG inner markup for eye, search, grid, shield, layers, sun, moon, monitor, chevron-right, chevron-up-down, arrow-right, alert-triangle, check, check-circle, copy, x, sort, menu, file-text; export icon(name, { size = 16, label = null } = {}) returning a trusted <svg viewBox="0 0 24 24" width height fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> (or role="img" + <title> when label is given); throws on unknown names. Unit tests (every name renders, no style attribute, unknown name throws, label → accessible) in tests/unit/icons.test.js

Binary site entries and new assets

  • T008 Change src/render/site.js (research D12): SiteEntry.body becomes string | Uint8Array; add exported types FONT_TYPE = "font/woff2", JSON_TYPE = "application/json; charset=utf-8", TEXT_TYPE = "text/plain; charset=utf-8"; renderSite(project, { base, mode, version, generatedAt, assets }) now takes assets = { styles, modules: {name: source}, fonts: {file: Uint8Array | string} } and publishes assets/styles.css, assets/theme.js (body from themeScript() of T017, not read from disk), assets/<module>.js for every module (app.js, prefs.js and later modules; live.js only in serve mode), and assets/fonts/<file> with FONT_TYPE for .woff2 and TEXT_TYPE for .txt; update tests/unit/site.test.js (binary bodies kept as Uint8Array, content types, live.js absent in static mode, font paths)
  • T009 [P] Change src/serve/handler.js to send Uint8Array bodies unchanged (no string encoding, correct Content-Length) while keeping every 001 header including the CSP; update tests/unit/handler.test.js (binary body byte-exact, CSP header unchanged)
  • T010 [P] Change src/build/build.js (writeSite) to write Uint8Array bodies with no encoding and string bodies as UTF-8; update tests/unit/build.test.js
  • T011 Change src/cli/main.js asset loading: replace the fixed overview.js/live.js map with a list of browser modules read from src/client/ (app.js, prefs.js, live.js, and the modules added by later tasks — keep the list in one exported constant CLIENT_MODULES in src/cli/main.js), dist/styles.css, and every file of dist/fonts/ read as bytes (readFile without encoding); pass them to renderSite in both serve and build mode (live.js only in serve); update tests/unit/main.test.js with the injected fake file system (fonts read as bytes, module list published, clear error when dist/fonts is missing that tells the developer to run pnpm run build:assets)

Model additions shared by all stories

  • T012 [P] Create src/model/shares.js (data-model "Percent shares"): export shares(values: number[]) → integer[] — floor each value * 100 / sum, give the remaining points to the largest fractional parts (ties: earlier entry first), always sums to 100, all zeros for an empty array or zero sum; export sharesNoZero(values) that, after shares, raises any non-zero value that rounded to 0 to 1 by taking 1 from the largest part (FeatureSegment parts rule). Unit tests (sum = 100, ties, zero sum, [123, 32, 72, 15], tiny non-zero part kept at 1) in tests/unit/shares.test.js
  • T013 [P] Extend src/model/task-state.js (data-model Task display, waitingOn): keep 001's rules and precedence; add display = done | next | blocked | open mapped from 001 completed → done, current → next, blocked → blocked, future → open; add waitingOn: string[] = the IDs in dependsOn whose tasks are still open, in written order, only for blocked tasks (empty otherwise; missing IDs ignored); export DISPLAY_LABEL = {done: "Done", next: "Next", blocked: "Blocked", open: "Open"}; update tests/unit/task-state.test.js (T020 depends on T012 + T013 with only T012 open → waitingOn: ["T012"]; next beats blocked; dependency on missing ID ignored)
  • T014 Extend src/model/build-model.js (data-model Feature): add number = leading digits of dir (001-x → "001") or the timestamp prefix, null without a numeric prefix; status = done | in-progress | not-started | no-tasks from the 001 Stage per the FeatureStatus table (complete → done, in-progress → in-progress, ready → not-started, empty | specified | planned → no-tasks); statusLabel per the same table ("Complete", "N open", "Ready" or "N open" when it is the active feature, the stage label "Specified"/"Planned"/"Empty"); Task anchor = "task-" + key with / and @ replaced by -; update tests/unit/build-model.test.js
  • T015 Add feature documents to src/model/build-model.js (data-model DocumentGroups): documents = { groups: [{name: "Define"|"Design"|"Contracts"|"Build"|"Other", items: Artifact[]}] (empty groups left out), tabs: [{label, count, items: Artifact[]}] } with the artifact kind → group/tab table (spec → Define/Specification; checklist → Define/"Quality checklist" when it is the only checklist and its file is checklists/requirements.md, otherwise "Checklists" with count; plan, research, data-model, quickstart → Design; contract → Contracts/"Contracts" with count; tasks → Build/no tab; other → Other/"More" with count), tab order Specification, Plan, Research, Data model, Quickstart, Contracts, checklists, More; add project.documents for the constitution ("Project") and one group per assessment; update tests/unit/build-model.test.js (feature without research.md has no Research tab; four contracts → "Contracts" count 4)

App shell and browser bootstrap

  • T016 [P] Create src/client/prefs.js (research D9): export KEYS = {theme: "sk-theme", order: "sk-order", filter: "sk-filter", map: "sk-map"}, DEFAULTS = {theme: "system", order: "progress", filter: "all", map: null} and ALLOWED value lists (light|dark|system, progress|number|least|name, all|open, stacked|grouped); createPrefs(storage) returning get(name) (unknown or invalid stored value → default) and set(name, value) (ignores invalid values); every storage access wrapped in try/catch, so a throwing or null storage falls back to defaults and never throws. Unit tests (defaults, round trip, invalid values, throwing storage on get and set, null storage) in tests/unit/client-prefs.test.js
  • T017 Theme before first paint (after T016, same file src/client/prefs.js) without file-reading tests (research D3, SC-005, constitution §IV): add to src/client/prefs.js an exported, self-contained function applyStoredTheme(document, storage) (no references outside its own body, because its source text is reused) that reads "sk-theme" inside try/catch and sets document.documentElement.dataset.theme to light or dark, doing nothing for system, a missing or invalid value, or a throwing/null storage; create src/render/theme-script.js exporting themeScript() that returns the classic (non-module) script text (<applyStoredTheme source>)(document, (function () { try { return window.localStorage; } catch (e) { return null; } })()); built from applyStoredTheme.toString(), so the published assets/theme.js (T008) is generated in memory and there is no separate src/client/theme.js file and no entry in CLIENT_MODULES. Unit tests: applyStoredTheme against a happy-dom window with fake storages (light, dark, system, missing, invalid, throwing, null) in tests/unit/client-prefs.test.js; themeScript() evaluated with node:vm in a context holding a happy-dom document/window (script text only, no file access) in tests/unit/theme-script.test.js
  • T018 Rewrite src/render/layout.js (contracts/routes.md "Page shell", FR-005–FR-009): export renderPage({ title, base, mode, project, page: "overview"|"feature"|"document", current, main, version, generatedAt }); <head> loads {base}assets/theme.js (blocking, first script), {base}assets/styles.css, <script type="module" src="{base}assets/app.js">, and {base}assets/live.js in serve mode only; <body data-mode data-version data-page data-base>; for overview and feature pages an <aside data-region="sidebar" class="always-dark"> with brand (eye icon, "speckit-eye"), project name, <button data-part="search" hidden> "Search tasks, specs…" with a ⌘K hint, <nav aria-label="Main"> (Overview, Constitution when present, "Assessment: <slug>" per assessment), <nav aria-label="Features"> headed "Features N / M done" listing each feature in ranks.progress order (until T024 lands, folder order) as <a data-key="side:<dir>" data-sig data-status href="{base}features/<dir>/index.html"> with status dot (done filled green, in progress blue ring, not started/no tasks grey), the truncated name with title = full name, and the open-task count or a check icon, aria-current="page" on current; <div role="group" aria-label="Theme" data-part="theme" hidden> with three <button aria-pressed data-theme-choice="light|dark|system"> (sun, moon, monitor); <footer> "speckit-eye <version>" plus " · generated <time>" in static mode; for document pages a <nav data-region="rail" class="always-dark"> with icon links to the same destinations, the search button (hidden) and the theme group; on every page a <details data-region="mobile-menu"> whose <summary> (menu icon) shows only on narrow screens and contains the same navigation (works without JS, FR-009); <main>; <div data-region="live-status" hidden> in serve mode; an empty <dialog data-region="search">; <div data-region="tooltip" role="tooltip" hidden> on the overview only; no style attribute anywhere; remove the old header, header links and Menu (FR-008). Rewrite tests/unit/layout.test.js (regions per page type, script order, no style=, aria-current, static footer time, sidebar count "Features 1 / 4 done")
  • T019 Add the shell's CSS to src/styles/input.css: sidebar (fixed width per handoff, own scroll for the Features list), icon rail, main column, mobile layout below the handoff's breakpoint (sidebar hidden, mobile-menu summary shown, no horizontal page scroll, FR-009), status dots, pills, chips, buttons with ≥ 36 px hit targets (FR-050)
  • T020 Create src/client/app.js (contracts/routes.md "Browser modules"): imports prefs.js and the page modules; start({ document, window, storage }) reads document.body.dataset.page, calls the shared initShell(root, deps) (un-hides and wires the theme switch: aria-pressed reflects prefs.get("theme"), a click stores the choice via prefs and sets or removes data-theme on <html> immediately, so it works even when storage fails, FR-045, US4 AC5) and then init(root, deps) of each module registered for that page in one MODULES = { overview: [...], feature: [...], document: [...], all: [...] } table; exports save(root) → {<module>: state} and reinit(root, state) for live.js; the file ends with a guarded bootstrap if (typeof document !== "undefined") start(...); until US1/US2 replace it, the overview entry still runs the 001 attachHover/attachClick from src/client/overview.js. Unit tests with happy-dom (right modules per page, reinit passes each module its saved state, theme switch updates aria-pressed and data-theme, throwing storage still switches) in tests/unit/client-app.test.js
  • T021 Change src/client/live.js (research D2, FR-051): before replacing <main>, call app.save(root) and record window.scrollY and scrollTop of every [data-keep-scroll] by name; after the swap keep 001 applyToggles and the change flash (collectSigs/changedKeys), then call app.reinit(root, state) and restore the scroll positions; also replace the sidebar Features list and footer (they carry data-sig) so sidebar counts update; emit a sk:change event on document after each swap (used by search, T069); keep the 001 SSE protocol and reconnect; update tests/unit/client-live.test.js (save called before swap, reinit after, scroll restored for tree, tasks, docs, sidebar entry flashed on change, sk:change dispatched once after each swap)
  • T022 Add the feature page route to src/render/site.js: publish features/<dir>/index.html for every feature (contracts/routes.md) rendered by a new renderFeaturePage(feature, project, { base }) exported from a new src/render/feature.js, which for now renders only <header data-region="feature-head"> (breadcrumb "Overview / Features", status pill, <code>dir</code>, <h1> title, "done / total tasks · completed of total phases") and a plain list of the feature's documents, inside renderPage({ page: "feature", current: dir }); document pages (src/render/artifact.js) now render with page: "document" (icon rail) until US5 replaces them; unit tests in tests/unit/feature.test.js and tests/unit/site.test.js (one feature page per feature, sidebar marks it current)
  • T023 Update the existing E2E suites to the new shell so the suite stays green: tests/e2e/helpers.js (selectors for [data-region="sidebar"], [data-region="rail"], feature page paths), tests/e2e/security.spec.js, tests/e2e/us3-artifacts.spec.js, tests/e2e/us4-build.spec.js, tests/e2e/self-counts.spec.js, tests/e2e/scale.spec.js (replace header/Menu navigation with sidebar or rail links; keep every assertion's intent); add to tests/e2e/security.spec.js a test FR-004 serves fonts from its own origin with the unchanged CSP (fonts load with font/woff2, no request leaves the origin, CSP header string unchanged)

Checkpoint: Every page renders inside the new shell (sidebar or icon rail), fonts and icons load from the tool itself, the theme switch works on the current page, live updates keep scroll positions, and unit + E2E suites pass in Chromium.


06 Phase 3: User Story 1 - See where the project stands in the new overview (Priority: P1) 🎯 MVP

Goal: The redesigned overview: page head with view filter, stats card with per-feature segmented bar, Up next bar, orderable feature tree with depth control and amber warning rows, and a sidebar Features list in "In progress first" order.

Independent Test: Serve tests/fixtures/projects/mixed (and nonstandard for warnings); confirm the sidebar list, stats card numbers (62 %, "40 of 65 tasks", Features "1 / 4" with "1 in progress", Phases "5 / 9" with "4 remaining", Open tasks "25" "across 2 features"), segments 46 / 31 / 23 %, Up next bar, tree order, depth control, filter and warning rows against the fixture (quickstart Scenario 1).

Model for User Story 1

  • T024 [P] [US1] Create src/model/ranks.js (data-model FeatureRanks, research D10): export computeRanks(features) → Map<dir, {progress, number, least, name}> with 0-based positions: progress = group 1 features with open tasks (in-progress before not-started), group 2 no-tasks, group 3 done, inside each (sub-)group dir descending; number = dir ascending; least = counts.percent ascending, then counts.open descending, then dir ascending, features without tasks last by dir; name = title with localeCompare(…, "en", {sensitivity: "base"}), then dir; wire it into src/model/build-model.js as feature.ranks. Unit tests (each order on a four-feature example, ties, features without tasks) in tests/unit/ranks.test.js
  • T025 [P] [US1] Create src/model/warnings.js (data-model WarningGroup, FR-015): export mergeLines(numbers) → {from, to}[] (sorted, consecutive merged: 23, 254–258 → [{23,23},{254,258}]), formatRange({from, to}) → "L23" / "L254–258" (en dash), and groupWarnings(warnings, contentByFile) → WarningGroup[] with code, file, count, title (plain sentence with the count, e.g. "6 checkboxes without a task ID in tasks.md"; one title template and note per 001 code W1–W12 in one table, e.g. W1 note "counted, not linkable"), lines, sourceLines ({line, text}[] of the raw lines from the already-read content), ordered by first line, warnings without a line in their own group per code; wire into src/model/build-model.js as feature.warningGroups. Unit tests (merging, en dash, singular/plural titles, ordering, no-line warnings, every 001 code has a title) in tests/unit/warnings.test.js
  • T026 [US1] Add project.overview (data-model OverviewStats, FeatureSegment, FR-011) to src/model/build-model.js: percent, done, total from totals.tasks (001 rounding, capped at 99 % while any task is open); features {completed, total, inProgress}; phases {completed, total, remaining}; openTasks {count, features}; segments for features with counts.total > 0 in dir order with dir, number, title, share (via shares() over all segments' totals), parts {done, open, next} via sharesNoZero (blocked counted as open), counts, showLabel = share >= 4; legend {done, open, blocked, next} over all tasks (open excludes blocked and next). Unit tests with the spec's example (123/123, 24/32, 72/72, 14/15 → "96 %", 233 of 242, Features 2 / 4 with 2 in progress, Open 9 across 2 features, segments proportional to 123, 32, 72, 15) and the mixed fixture numbers in tests/unit/build-model.test.js

Rendering for User Story 1

  • T027 [US1] Rewrite src/render/overview.js page head, stats card and Up next bar (contracts/routes.md "Overview <main>", FR-010, FR-011, FR-013): <header data-region="page-head"> with project name, <h1>Project overview</h1> and the hidden role="group" aria-label="Show" filter with data-filter="all|open" buttons; <section data-region="stats"> with the percentage, "done of total tasks", three counters (data-stat="features|phases|open", each with data-key="stat:<name>" and data-sig), the segments <a data-key="seg:<dir>" href="#<feature anchor>" class="w-pct-N min-w-[3px]" title="<number> · <title> — d done, o open, n next"> each containing <span data-part="done|open|next" class="w-pct-N">, labels (feature number when showLabel), and a legend Done / Open / Next up; empty project → 0 % with a short explanation and an empty state instead of the bar; <section data-region="up-next" class="always-dark"> with ID chip, task text exactly as written on one line with title = full text, feature › phase, "Open quickstart" (only when the active feature has a quickstart artifact) and "View task" → features/<dir>/index.html#<anchor>, or data-empty="complete|no-tasks" with "Every task is complete" / "No tasks yet"; unit tests in tests/unit/overview.test.js (numbers, widths as classes, no style=, empty and complete states, quickstart button presence)
  • T028 [US1] Rewrite the tree part of src/render/overview.js (FR-012, FR-014–FR-018): <section data-region="features"> header with hidden <button data-part="order" data-order="progress"> (sort icon + label of the current order) and hidden <div role="group" data-part="depth"> Features | Phases | Tasks; <div data-region="tree" data-keep-scroll="tree" data-filter="all"> rendering features in ranks.progress order as <li data-feature data-rank-progress data-rank-number data-rank-least data-rank-name [data-complete]> → <details data-key data-sig data-status [data-active] [open]> whose <summary> has chevron, status dot, number chip (omitted when number is null), truncated title with title, warnings badge "N warnings" when warningGroups is non-empty, status pill (statusLabel), mini progress bar (w-pct-<percent>), "done/total" (no bar for no-tasks), then <a data-part="open-feature"> to the feature page; an expanded feature with warnings starts with one amber <a data-part="warning" data-code href="features/<dir>/index.html#warnings"> row per group with title, note, line chips (formatRange) and "Details"; phases as <details data-key data-status [data-complete]> with "Phase N", title, priority badge (P1 filled dark, P2 grey, P3 outlined) when the phase belongs to one story, "done/total" plus check when complete or "—" and no chevron when empty; 001 story sub-level kept; task rows <li data-key id="<anchor>" data-state="done|next|blocked|open" data-sig> with mark, <a href="features/<dir>/index.html#<anchor>">ID</a> ("No ID" as plain text for tasks without an ID), raw text on one line with title, and a "NEXT" badge + tint on the next task; only the active chain open on load (001 FR-016); keep the 001 grid in the right column until T039 replaces it. Unit tests in tests/unit/overview.test.js (rank attributes, warning row "6 checkboxes without a task ID in tasks.md" with "L23" and "L254–258", badge "6 warnings", empty phase "—", NEXT badge, blocked data-state)
  • T029 [US1] Switch the sidebar Features list in src/render/layout.js to ranks.progress order and mark the entry of the current feature page; update tests/unit/layout.test.js
  • T030 [P] [US1] Add overview CSS to src/styles/input.css: page head, stats card, segmented bar and legend, Up next bar (always dark, one-line truncation), two-column grid (tree left, map column right, card tops aligned; stacked with the tree first on narrow screens, FR-010), tree card with its own scroll above about 800 px (FR-018, handoff), feature/phase/task rows, amber warning rows and L-chips, pills and priority badges, mini bars, [data-filter="open"] [data-complete], [data-filter="open"] li[data-state="done"] { display: none } (FR-019), [data-selected] outline, and [data-state] marks including the rose blocked mark (FR-022)

Browser behavior for User Story 1

  • T031 [US1] Create src/client/tree.js (FR-012, FR-019, FR-020, research D10): init(root, { document, storage, state }) un-hides the order button, depth group and view filter; order: applies prefs.get("order") by sorting the feature <li> elements on data-rank-<order> (moves nodes, never re-renders), each click cycles progress → number → least → name → progress, stores it and sets the button label ("In progress first", "Number", "Least complete", "Name A–Z"); depth: "Features" closes every feature and phase <details>, "Phases" opens features and closes phases, "Tasks" opens features and every phase with tasks (and story levels); the chosen depth shows aria-pressed, and a manual toggle of any row clears it; filter: sets data-filter on the tree from prefs.get("filter"), buttons store it; exports reveal(root, anchor) (opens the task's feature, phase and story <details>, scrolls the tree card — not the window — so the row is visible, sets data-selected on the row and clears it from any other row; a click elsewhere clears it) for taskmap.js; save(root) returns {depth}. Unit tests with happy-dom (sort by each rank, cycle and label, depth open/close sets, manual toggle clears depth, filter attribute, prefs round trip, reveal opens ancestors and selects, save/init restores depth) in tests/unit/client-tree.test.js
  • T032 [US1] Register tree.js for the overview page in src/client/app.js, add tree.js to CLIENT_MODULES in src/cli/main.js, and update tests/unit/client-app.test.js and tests/unit/main.test.js

E2E for User Story 1

  • T033 [US1] Rewrite tests/e2e/us1-overview.spec.js against mixed and nonstandard: US1 FR-005 FR-006 sidebar lists features in progress-first order with dots and counts; US1 FR-011 stats card shows 62 % and 40 of 65 tasks (plus features, phases, open counters); US1 FR-011 segmented bar has one segment per feature with tasks, widths 46/31/23 % (compare element box widths within 1 %); US1 FR-013 Up next bar names the next task with full text on hover and View task opens the feature page; US1 FR-012 order cycles through four orders and the label names the current one; US1 FR-012 depth Tasks expands every phase with tasks and Features collapses all; US1 FR-015 warning row shows count, line chips and Details link (nonstandard); US1 FR-019 Open tasks only hides complete features and done tasks; US1 FR-020 order and filter are remembered after reload; US1 FR-006 sidebar entry opens the feature page and marks it current; US1 SC-001 percentage, in-progress feature and next task visible without scrolling at 1440 × 900; US1 SC-002 View task shows the next task's full text and files in one click, and any task is two clicks from the map (square, then its ID); US1 FR-018 tree scrolls inside its card above about 800 px; US1 SC-009 sidebar left of content, tree left of map (bounding boxes)
  • T034 [US1] Add CHANGELOG.md lines under ## [Unreleased] for the new sidebar shell, stats card, Up next bar and feature tree (order, depth, open-only filter, warning rows), and update tests/fixtures/projects/README.md with the expected stats, segment shares and ranks of each fixture

Checkpoint: The overview and sidebar are fully functional; US1 E2E suite passes; the 001 grid is still shown on the right until US2.


07 Phase 4: User Story 2 - Explore every task on the task map (Priority: P2)

Goal: The task map with stacked / by-feature modes, four state colors including rose for blocked, legend, hover growth, delayed tooltip, tree highlight, and click-to-reveal in the tree.

Independent Test: On mixed, the map shows 65 squares with legend "Done 40 · Open 23 · Blocked 1 · Next 1 · 1 dot = 1 task"; hover, tooltip timing and placement near both edges, "By feature" / "Stack all", and clicking a square in collapsed 003-gamma reveal the task (quickstart Scenario 2).

Rendering for User Story 2

  • T035 [US2] Create src/render/taskmap.js (FR-021–FR-023, FR-027, FR-028, contracts/routes.md): move GRID_ROWS_THRESHOLD, GRID_BARS_THRESHOLD and gridLayout from src/render/overview.js and rename the layouts to stacked (≤ 1,000 tasks), grouped (1,001–5,000) and bars (> 5,000); export renderTaskMap(project, { base }) returning <section data-region="taskmap" data-layout> with a header (title "Task map" and hidden <button data-part="map-mode" aria-pressed> reading "By feature" / "Stack all", absent for bars) above the card; the grid as <a data-key data-sig data-state data-parents="<dir> <phaseKey> [groupKey]" href="#<anchor>" title="<ID or No ID> · <state label> — <text> — <feature>" aria-label=…> per task including checkboxes without an ID, in folder and task order (never the tree order); grouped mode as <div data-part="group"> with feature name, done/total and its squares; bars mode as one progress bar per feature (w-pct); <ul data-part="legend"> "Done N · Open N · Blocked N · Next N · 1 dot = 1 task" from project.overview.legend (what one mark stands for in bars mode); an empty state for a project without tasks. Unit tests in tests/unit/taskmap.test.js (one square per task incl. no-ID, order independent of ranks, thresholds 1,000/1,001/5,000/5,001, legend sums to total, blocked data-state, no style=)
  • T036 [US2] Replace the 001 grid in src/render/overview.js with renderTaskMap and remove the moved grid code and its tests from tests/unit/overview.test.js
  • T037 [P] [US2] Add task map CSS to src/styles/input.css: square sizes for stacked and grouped modes per handoff, colors done (filled green), open (blue outline), blocked (--rose 1.5 px outline + --rose-bg tint), next (filled --next); hover/:focus-visible growth transform: scale(1.6) with a two-ring outline within 120 ms ease-out affecting no other square (FR-024); tooltip (always dark, status pill, line-clamp: 2 with -webkit-line-clamp fallback, fade); tree tint classes [data-hl="ancestor"] (light) and [data-hl="deepest"] (stronger + outline) (FR-025); growth and fade off under reduced motion

Browser behavior for User Story 2

  • T038 [US2] Create src/client/taskmap.js (research D8, FR-024–FR-026, FR-028): init(root, { document, window, storage, setTimeout, clearTimeout, state }): mode: un-hide the toggle, apply prefs.get("map") (falling back to the server's data-layout), toggle between stacked and grouped by setting data-layout and the button text/aria-pressed, store it; tooltip: one shared [data-region="tooltip"]; delegated pointerenter on squares starts a 500 ms timer (TOOLTIP_DELAY_MS = 500) unless pointerType === "touch"; keyboard focus shows at once; pointerleave/blur clears the timer and hides at once; content = ID (or "Checkbox without a task ID"), status pill (DISPLAY_LABEL), text, feature name — all set via textContent; position above the square, aligned left, centre or right by available room and clamped to the card and the viewport via CSSOM style.left/top (exported pure placeTooltip(squareRect, tipRect, boundsRect) → {left, top, align}); highlight: while a square is hovered or focused, set data-hl="ancestor" on its visible feature/phase/story rows from data-parents and data-hl="deepest" on the deepest visible one (task row if visible), cleared on leave; click / Enter: preventDefault and call tree.reveal(root, anchor); touch tap does the same with no tooltip; save(root) returns nothing extra (mode is a preference). Unit tests with happy-dom and fake timers (no tooltip before 500 ms, shown at 500 ms, hidden on leave, focus shows at once, touch never shows, placeTooltip left/right/centre and clamping, no-ID text, highlight deepest visible, click calls reveal, mode persisted) in tests/unit/client-taskmap.test.js
  • T039 [US2] Register taskmap.js for the overview page in src/client/app.js, remove the 001 attachHover/attachClick usage, delete src/client/overview.js and tests/unit/client-overview.test.js (§II), add taskmap.js to CLIENT_MODULES in src/cli/main.js; update tests/unit/client-app.test.js and tests/unit/main.test.js

E2E for User Story 2

  • T040 [US2] Create tests/e2e/us2-taskmap.spec.js (moving the 001 grid tests from us1-overview.spec.js): US2 FR-021 FR-023 map shows 65 squares and legend Done 40 · Open 23 · Blocked 1 · Next 1; US2 FR-021 By feature regroups into labelled blocks and Stack all restores; US2 FR-024 square grows on hover and tooltip appears after 500 ms, not before (measure with page.clock or timestamps: 400–600 ms); US2 FR-024 tooltip hides at once on leave; US2 FR-024 FR-050 keyboard focus on a square shows the tooltip at once and Enter reveals the task in the tree; US2 FR-024 tooltips of first- and last-column squares are fully inside card and viewport; US2 FR-025 hovering a square in a collapsed feature tints its feature row; US2 FR-026 clicking a square in collapsed 003-gamma opens feature and phase, scrolls the tree card and outlines the row; its ID opens the feature page; US2 FR-022 T018 is blocked in map and tree and its tooltip pill reads Blocked; US2 FR-022 after checking T011 in tasks.md T018 becomes open (live update on a fixture copy); US2 FR-028 map mode is remembered after reload; US2 FR-024 checkbox without an ID shows "Checkbox without a task ID" (nonstandard)
  • T041 [US2] Update tests/e2e/scale.spec.js for the 50 × 2,000 fixture: US2 FR-027 map opens By feature above 1,000 tasks, US2 SC-006 hover growth within 150 ms and tooltip at 500 ± 100 ms with 2,000 squares, US1 FR-011 with 50 features, segments too narrow for a label show none but stay visible
  • T042 [US2] Add CHANGELOG.md lines for the task map (modes, blocked color, tooltip, reveal in tree)

Checkpoint: US1 and US2 work together on the overview; both E2E suites pass.


08 Phase 5: User Story 3 - Work through a feature on its feature page (Priority: P2)

Goal: The full feature page: header with ring, document tabs, phase rail accordion, warnings banner, filterable task list with kind/file chips, in-place expansion, detail panel with Copy ID and View source line, addressable selected task.

Independent Test: Open 002-beta on mixed and the nonstandard feature; check header counts, tabs, rail widths and accordion, each filter, row expansion, the detail panel for T018 ("Waiting on T011"), Copy ID, View source line to tasks.html#L31, and the warnings banner (quickstart Scenario 3).

Model and text rendering for User Story 3

  • T043 [P] [US3] Create src/model/task-files.js (data-model Task files, kind, refs, TaskKind; FR-035, FR-037): export taskFiles(description) = backticked spans matching /^[^\s]*/[^\s]*[A-Za-z0-9_-]\.[A-Za-z0-9]+$/ or ending in Makefile/Dockerfile, in order, duplicates removed; taskKind(file) → {label, test} | null with test when the file name matches /(^|[._-])(test|spec)s?([._-]|$)/i or a folder is test, tests, __tests__ or e2e, and label from the extension table (.go Go, .vue Vue, .js/.mjs/.cjs JavaScript, .jsx JSX, .ts/.mts/.cts TypeScript, .tsx TSX, .py Python, .rb Ruby, .rs Rust, .java Java, .kt Kotlin, .swift Swift, .cs C#, .php PHP, .css CSS, .scss SCSS, .html HTML, .svelte Svelte, .md Markdown, .json JSON, .yml/.yaml YAML, .toml TOML, .sh Shell, .sql SQL; other → extension upper-cased without the dot; Makefile/Dockerfile → the name); kindChip(kind) = label or label + " test"; taskRefs(description) = FR-\d+[a-z]? and SC-\d+ in order, deduplicated; wire into src/model/build-model.js (task.files, task.kind, task.refs) and add feature.kindChips (distinct chip texts without " test", first appearance order). Unit tests (internal/input/simulator_test.go → "Go test"; two files; no file; .proto → PROTO; tests/e2e/x.spec.js → "JavaScript test"; URL-like spans without / ignored; refs) in tests/unit/task-files.test.js
  • T044 [US3] Add feature.phaseShares to src/model/build-model.js: shares() over each phase's task total (phases without tasks get 0; rendered at the minimum width, FR-032); unit tests in tests/unit/build-model.test.js
  • T045 [P] [US3] Extend src/render/markdown.js: export renderInline(md, text) using the shared markdown-it instance's renderInline (raw HTML off, 001 link rules); make tasks.md documents wrap every task line in <span id="L<line>"> (FR-044; line numbers from the source, fences and comments excluded as in 001 visibleLines) with :target highlight CSS in src/styles/input.css; update tests/unit/markdown.test.js (inline HTML stays text, task lines addressable)
  • T046 [US3] Create src/render/task-text.js (contracts/routes.md "Inline task text", FR-035, FR-037): export renderTaskText(md, task) → inline HTML of description via renderInline with `code` as mono chips and FR-…/SC-… references wrapped as <span class="chip" data-ref> chips (only in text nodes, never inside code or link text); renderKindChip(task) (fixed-width chip, empty placeholder of the same width when kind is null); renderFileChip(task) (file name of the single file with title = full path, "N files" when several, empty placeholder when none); renderMarkers(task) (USn, "Parallel", FR/SC refs, "depends on …"). Unit tests (text never altered, <script> in text stays text, chips aligned by fixed classes, "2 files") in tests/unit/task-text.test.js

Rendering for User Story 3

  • T047 [US3] Complete src/render/feature.js header and tabs (FR-030, FR-031): header with breadcrumb (Overview / Features), status pill, <code>dir</code>, <h1>, "done / total tasks · completed of total phases" and an SVG progress ring (stroke-dasharray from counts.percent, check icon when complete); <nav data-region="tabs" aria-label="Documents"> with "Tasks <n>" (aria-current="page") then one tab per feature.documents.tabs entry in order, a tab with one document links to its page, a tab with several renders <details data-part="tab-menu"><summary>Contracts <n></summary> listing them (FR-008); unit tests in tests/unit/feature.test.js (full tab set, no Research tab without research.md, "Contracts 4", "Quality checklist" vs "Checklists 2", "More")
  • T048 [US3] Add the Tasks tab body to src/render/feature.js (FR-032–FR-035, contracts/routes.md "Feature page <main>"): <nav data-part="rail"> with one <a href="#phase-<n>" class="w-pct-N" data-status> per phase (phaseShares, label-sized minimum width, short name "P4" and task count, narrow non-openable block for empty phases) and <p data-part="caption"> "Width = task count · Selected: <phase or None>"; <section id="warnings" data-part="warnings"> amber banner when warningGroups is non-empty with titles, line chips and <details><summary>Show lines</summary><ol> of sourceLines; hidden <div data-part="filters"> with chips All (count), Open (count, blocked included), Tests, one chip per feature.kindChips, <input type="search"> and "Expand all" / "Collapse all"; <div data-part="list" data-keep-scroll="tasks"> with <details name="phases" data-key id="phase-<n>" data-status [open]> per phase (active phase open, none when no open tasks) whose summary has "Phase N", title, priority badge and done/total; task rows <details data-key id="<anchor>" data-state data-kind data-test? data-files="<names>" data-text="<lower-cased id + text>"> with <summary> mark, ID ("No ID" without address for ID-less tasks), renderTaskText, kind chip, file chip; body: full wrapped text and renderMarkers; hidden <aside data-region="detail">; a feature without tasks.md shows an empty Tasks tab explaining its stage. Unit tests in tests/unit/feature.test.js (rail classes, active phase open, banner lines, filter chip set, row attributes, empty stage)
  • T049 [P] [US3] Add feature page CSS to src/styles/input.css: header and ring, tabs (scroll sideways on narrow screens), rail (scroll sideways on narrow screens) and caption, amber banner, filter chips and search input, task list columns with fixed-width kind and file chips so they line up, one-line truncation in summaries and wrapped text when open, detail panel beside the list (stacked below it on narrow screens), selected-row outline, :target highlight

Browser behavior for User Story 3

  • T050 [US3] Create src/client/feature.js (FR-032–FR-036): init(root, { document, window, navigator, setTimeout, state }): rail ↔ list: choosing a rail block or a phase summary opens that phase (native <details name="phases"> closes the others; for browsers without exclusive groups, close the others in JS), choosing the open one closes it, aria-current on the rail block and the caption follow the open phase; filters: un-hide the filter bar; chips (All/Open/Tests/kind) and the text filter combine (text matches data-text and data-files); while any filter is active every phase with matches is open and phases without matches are hidden; each chip shows its match count; no match → "No tasks match" with a "Clear filters" button; expand/collapse all; selection: opening a task row selects it (second click collapses), fills and un-hides the detail panel (ID, status via DISPLAY_LABEL, full text, phase, markers, file paths, "Waiting on T012, T013" from data-waiting-on for blocked tasks, "Copy ID", "View source line" → tasks.html#L<line>, close button) and sets the address fragment with history.replaceState; address: on init a #task-… fragment opens its phase, expands and selects the task and scrolls it into view (FR-036, US3 AC10); copy: navigator.clipboard.writeText with a text-selection fallback and a short "Copied" confirmation; save(root) returns {chips, text, selected}; the renderer (T048) adds data-waiting-on, data-line and data-phase to task rows for this module. Unit tests with happy-dom (accordion incl. choose-again-to-close, filter combinations and counts, empty-result message and clear, selection fills panel incl. "Waiting on T012", fragment on init, copy success and fallback, save/init restores) in tests/unit/client-feature.test.js
  • T051 [US3] Register feature.js for the feature page in src/client/app.js and add it to CLIENT_MODULES in src/cli/main.js; make the Up next "View task", tree task IDs and search results link to features/<dir>/index.html#<anchor> (already rendered; verify with a unit test in tests/unit/overview.test.js); update tests/unit/client-app.test.js

E2E for User Story 3

  • T052 [US3] Add backticked file paths to a few 002-beta task texts in tests/fixtures/projects/mixed (for example src/list.go, src/list_test.go, web/List.vue; checkbox states and counts unchanged) so the kind and Tests chips have data, and list them in tests/fixtures/projects/README.md; then create tests/e2e/us3-feature.spec.js on mixed, complete and nonstandard: US3 FR-030 header shows pill, folder, title, counts and ring (and "Complete" + check for a complete feature); US3 FR-031 tabs list only existing documents in order; US3 FR-008 Contracts tab lists its documents without a navigation; US3 FR-032 rail block widths proportional to task counts; US3 FR-032 choosing a phase opens it and closes the other; choosing it again closes it; US3 FR-034 Open filter lists 10 open tasks and hides phases without matches; US3 FR-034 text filter narrows and a nonsense word shows No tasks match with Clear filters; US3 FR-034 Tests chip and each kind chip filter the list and show their counts; US3 duplicate task IDs can each be selected on their own (nonstandard); US3 FR-035 kind and file chips line up in columns (bounding boxes); US3 FR-036 clicking T018 expands it and the detail panel says Waiting on T011; US3 FR-036 Copy ID puts T018 on the clipboard (grant clipboard permissions in Chromium; Firefox/WebKit check the confirmation only); US3 FR-044 View source line opens tasks.html#L31 highlighted; US3 FR-036 opening an address with #task-… shows the task open, selected and in view; US3 FR-033 amber banner and Show lines reveal the source lines (nonstandard); US3 SC-009 detail panel right of the task list
  • T053 [US3] Add CHANGELOG.md lines for feature pages (tabs, phase rail, filters, kind/file chips, detail panel, Copy ID, View source line, addressable tasks)

Checkpoint: Feature pages are fully usable; US1–US3 suites pass.


09 Phase 6: User Story 4 - Choose a light, dark or system theme (Priority: P2)

Goal: A Light / Dark / System switch on every page, applied before first paint, remembered per browser, following the OS in System mode, with always-dark regions and an identical next orange.

Independent Test: With the OS in light mode choose Dark, open a feature page and a document, reload, trigger a live update: dark from first paint everywhere; then System follows OS changes (quickstart Scenario 4).

  • T054 [US4] Verify and complete the always-dark regions and token coverage in src/styles/input.css (FR-047): .always-dark on sidebar, icon rail, Up next bar and map tooltip in src/render/layout.js, src/render/overview.js and src/render/taskmap.js; --next identical in both themes; done/open/blocked/next/warning keep their color family; add a unit test in tests/unit/layout.test.js that the rendered sidebar, icon rail, Up next bar and tooltip carry always-dark (rendered HTML only, no file access, §IV); the identical next orange in both themes is checked end to end in T056 (US4 FR-047, computed colors)
  • T055 [US4] Make the theme switch follow OS changes in System mode in src/client/app.js (FR-045): nothing to do in JS when data-theme is absent (CSS color-scheme: light dark follows the OS), but keep the switch's aria-pressed correct after live updates (reinit) and across pages; ensure live.js never touches <html data-theme>; add unit tests in tests/unit/client-app.test.js (reinit keeps the pressed button, System removes data-theme)
  • T056 [US4] Create tests/e2e/us4-theme.spec.js: US4 FR-045 first visit shows System and follows the OS scheme (page.emulateMedia({colorScheme})); US4 FR-045 System follows an OS switch without reload; US4 FR-046 SC-005 Dark applies from first paint on another page and after reload (assert data-theme="dark" and the computed --bg before DOMContentLoaded handlers run, e.g. via page.addInitScript observer recording the first paint's background); US4 FR-046 theme survives a live update; US4 FR-047 sidebar, Up next bar and tooltip are dark and next orange is identical in both themes; US4 FR-046 storage refused: the switch changes the current page and the next page is System (addInitScript making localStorage throw); US4 SC-010 body text contrast ≥ 4.5:1 in both themes on overview, feature and document pages (compute from computed colors, including the rose pill)
  • T057 [US4] Add CHANGELOG.md lines for the theme switch

Checkpoint: Theme works everywhere; US1–US4 suites pass.


10 Phase 7: User Story 5 - Read specs and other documents in the document reader (Priority: P3)

Goal: The reader layout for every document (icon rail, grouped document list, reading column, "On this page" panel with progress), numbered sections, "Expand all", "Raw markdown", and the structured spec.md view — with no source line lost.

Independent Test: Serve this repository; open 001-speckit-eye-dashboard → Specification and a fixture's plan.md and contract; check the document list, contents panel, every structured section, Expand all, Raw markdown, and that every non-blank line appears (quickstart Scenario 5).

Parsing for User Story 5

  • T058 [US5] Create src/parse/spec-structure.js (contracts/spec-md-structure.md, research D13): export parseSpecStructure(text) → {metadata, request, blocks}; blocks in file order with 1-based inclusive from/to, disjoint and covering every line; never throws, never rewrites text; lines inside fences and HTML comments (001 visibleLines) never matched; recognize title (first # heading), metadata (**Feature Branch**:, **Created**:, **Status**: before the first ## , code spans unwrapped in branch, missing fields omitted, null when none), request (**Input**: with a leading User description: and one pair of surrounding "…"/“…” removed for display only), section for every ## heading (number two digits, heading, anchor with 001 slugify), clarifications (## Clarifications → sessions from ### Session <date>, items - Q: … → A: … or -> with indented continuation lines, badge yes/no/neutral from the answer's first word after trimming punctuation), story (### User Story <n> - <title> (Priority: P<k>) up to the next ### , ## or ---; description, **Why this priority**: → why, **Independent Test**: → test, **Acceptance Scenarios**: numbered items → scenarios with given/when/then only when exactly one of each appears in that order, trailing ,/; trimmed, else raw), requirements (### Functional Requirements → areas from bold-only paragraphs, items **FR-<id>**: with continuation lines, unnamed area for items before the first area, other lines as plain), entities (### Key Entities → - **<name>**: <description> items, others plain), everything else plain with its Markdown source. Unit tests for every rule and each example row of the contract, plus the coverage invariant asserted over in-memory sample texts only (constitution §IV: no file access in unit tests): a new tests/unit/spec-samples.js exporting template strings — a full template-conformant spec, one with every deviation of the contract (non-template sections, clarifications not in Q: … → A: form, scenarios without a single Given/When/Then, fences and HTML comments containing look-alike lines, empty file, file without ## headings) and an excerpt reproducing the structure of this repository's 001 spec — in tests/unit/spec-structure.test.js. The same invariant over the real files of tests/fixtures/projects/** and this repository's specs/** is checked end to end in T065 (SC-012)
  • T059 [US5] Add feature.spec to src/model/build-model.js (parseSpecStructure of the already-read spec.md, null without it) and resolve each story's phase links (phases whose mergedStory or groups carry the story's label, with their counts); unit tests in tests/unit/build-model.test.js

Rendering for User Story 5

  • T060 [US5] Create src/render/spec-view.js (FR-042, FR-043, contracts/spec-md-structure.md "Rendering notes"): export renderSpecBlocks(spec, { md, feature }) → HTML: metadata card (branch, created, status); request as a pull quote; numbered section headings with anchors; clarifications "N answered · M sessions" in the heading, sessions as <details data-key> (first open) with question count, items as question, fixed-width badge ("Yes", "No", "A") and answer text in one aligned column, the first three visible and the rest in <details data-part="more"><summary>Show N more answers</summary>; stories as <details data-key> cards (first open, others collapsed rows with ID, priority badge, title, scenario count) with description, "Why this priority", "Independent test" boxes, a numbered Given / When / Then table (raw scenarios as one full-width row) and the linked phase with done/total; requirements with hidden area chips (data-part="areas", "All" default), area headings, each requirement with its ID chip and MUST NOT, MUST, SHOULD NOT, SHOULD, MAY as whole upper-case words wrapped in <mark data-kw> (longest first, text nodes only); entities as a two-column card grid; plain blocks via the 001 Markdown renderer; all text through the shared markdown-it instance. Unit tests (each structure, badges, "Show 10 more answers", keyword highlight not inside code, <script> stays text, every non-blank source line's text appears in the output for every sample of tests/unit/spec-samples.js) in tests/unit/spec-view.test.js
  • T061 [US5] Create src/render/reader.js replacing src/render/artifact.js (FR-038–FR-041, FR-044, contracts/routes.md "Document page <main>"): export renderDocument(artifact, { base, project, feature, assessment, md }) rendering, inside renderPage({ page: "document" }), <nav data-region="doc-list" data-keep-scroll="docs"> (back link to the feature page, feature title and status, groups Define/Design/Contracts/Build/Other from feature.documents.groups with contracts by title, empty groups left out, current marked aria-current; project documents get the project document list), <article data-region="doc" data-key="<source path>"> with header (eyebrow "<KIND> · <file>", <h1>, hidden <button data-part="expand-all">, <details data-part="raw"><summary>Raw markdown</summary><pre> exact source</pre></details>), then renderSpecBlocks for spec.md or the 001 Markdown rendering with top-level sections numbered 01, 02, … (tasks.md keeps the L<line> anchors, T045), and <nav data-region="toc" aria-label="On this page"> listing sections (and, for a specification, its stories under the user-scenarios section as US1 · <title>) with a hidden <div data-part="progress">; the 001 deleted-document notice stays; update src/render/site.js to use it; delete src/render/artifact.js and move/replace tests/unit/artifact.test.js with tests/unit/reader.test.js (group placement, current mark, numbering, raw source exact, TOC entries, project list)
  • T062 [P] [US5] Add reader CSS to src/styles/input.css: three-column reader (doc list, reading column with comfortable measure in Instrument Serif headings / Geist body per handoff, contents panel), metadata card, pull quote, clarification rows with the badge column aligned, story cards and Given/When/Then table, requirement chips and mark[data-kw], entity grid; on narrow screens hide the contents panel and put the document list behind a <details> control

Browser behavior for User Story 5

  • T063 [US5] Create src/client/reader.js (FR-040–FR-042): init(root, { document, window, IntersectionObserver, state }): contents: observe section headings, mark the section in view (aria-current="location") and set the progress indicator (w-pct-N class from scroll position; un-hide it), links jump to sections; expand all: un-hide the button, open every <details> inside the article (and toggle to "Collapse all"); raw view: with JS the Raw markdown control swaps the formatted view for the source view and back; show more answers; requirement area chips: un-hide, "All" default, picking an area hides the other areas' requirements; save(root) returns {raw, expanded, area, more}. Unit tests with happy-dom and a fake IntersectionObserver (current section, progress class, expand all, raw toggle, area filter, save/init restore) in tests/unit/client-reader.test.js
  • T064 [US5] Register reader.js for the document page in src/client/app.js, add it to CLIENT_MODULES in src/cli/main.js; update tests/unit/client-app.test.js

E2E for User Story 5

  • T065 [US5] Create tests/e2e/us5-reader.spec.js (replacing tests/e2e/us3-artifacts.spec.js, whose still-valid 001 checks — every artifact reachable, raw HTML shown as text, deleted-document notice — move here), serving this repository and the artifacts fixture: US5 FR-038 FR-039 document list groups Define, Design, Contracts, Build, Other with the current document marked; US5 FR-042 metadata card and pull quote; US5 FR-042 clarifications show N answered · M sessions, first session open, Show N more answers and aligned badges (badge column x-positions equal); US5 FR-042 user story card has why, independent test, Given/When/Then table and phase progress; US5 FR-042 area chip narrows requirements and MUST is highlighted; US5 FR-041 contents panel marks the section in view and progress advances on scroll; US5 FR-040 Raw markdown shows the exact source and toggles back; US5 FR-040 Expand all opens every collapsed part; US5 FR-043 SC-012 every non-blank source line appears in the formatted view (for every spec.md of this repository's specs/*/ and of tests/fixtures/projects/**, read by the test runner, which is allowed in E2E tests); US5 FR-043 a non-template section renders as ordinary text; US5 FR-007 document pages show the icon rail instead of the sidebar; delete tests/e2e/us3-artifacts.spec.js
  • T066 [US5] Add CHANGELOG.md lines for the document reader and the structured spec.md view

Checkpoint: All documents use the reader; US1–US5 suites pass.


11 Phase 8: User Story 6 - Find a task, feature or document with search (Priority: P3)

Goal: ⌘K / Ctrl+K search over tasks, features, document titles and section headings, grouped results, keyboard navigation, same behavior in serve and static builds, fresh after live updates.

Independent Test: In serve mode and in a static build under /eye/, open search by shortcut and by the sidebar entry; search a task ID, a word from a task text, a feature name and a heading; open one result of each kind (quickstart Scenario 7).

  • T067 [US6] Create src/render/search-index.js (contracts/search-index.md, research D14): export buildSearchIndex(project) → {version: 1, entries} with entries in page order (features in folder order, each followed by its tasks in file order and its documents in 001 artifact order with every ##/### heading, then project documents): task (label ID or "No ID", detail text as written, context "<number> · <title>", state display state, url features/<dir>/index.html#<anchor> or #phase-<n> for ID-less tasks), feature (label "<number> · <title>", detail "<statusLabel> · d/t", url), document (title, file name, context "Project" or "Assessment: <slug>" for project documents), heading (heading text, document title, url with the 001 slug anchor); terms = lower-cased label + " " + detail for tasks and documents, label only for features and headings (decisions.md), with whitespace collapsed; no document body text. Unit tests (order, every field, headings from ##/### only, body words absent, sub-path-free URLs) in tests/unit/search-index.test.js
  • T068 [US6] Publish assets/search-index.json from src/render/site.js with JSON_TYPE in both modes; update tests/unit/site.test.js
  • T069 [US6] Create src/client/search.js (FR-049–FR-049c, contracts/search-index.md "Matching"): export pure search(entries, query) → {groups: [{name: "Tasks"|"Features"|"Documents", items, more}], empty} (lower-case, split on whitespace, empty query → nothing, every word must occur in terms, ranking: exact full task ID first, then label starting with the first word, then the rest, ties in page order; documents and headings in one group; first 8 per group plus "N more"); init(root, { document, window, fetch, navigator }) un-hides every [data-part="search"] entry, opens <dialog data-region="search"> with showModal() on ⌘K (macOS, from navigator.platform/userAgentData) or Ctrl+K and on entry click, focuses the input, fetches {base}assets/search-index.json on first use (relative to document.body.dataset.base) and again on the next use after a sk:change event, renders results (tasks with state mark, ID, text and feature; features with number, name, status; documents/headings with title and feature) via textContent, arrow keys move the active option (aria-activedescendant), Enter or click navigates to base + url, "N more" expands the group, Escape closes and focus returns to the opener, "No results for "<query>"" when nothing matches. Unit tests (matching and ranking rules, 8 + more, empty query, no match; happy-dom: shortcut opens, Escape restores focus, arrow/Enter navigation, fetch once then again after sk:change, base prefix) in tests/unit/client-search.test.js
  • T070 [US6] Register search.js for all pages in src/client/app.js and add it to CLIENT_MODULES in src/cli/main.js; add dialog and result CSS to src/styles/input.css; update tests/unit/client-app.test.js
  • T071 [US6] Create tests/e2e/us6-search.spec.js: US6 FR-049 Ctrl/⌘K and the sidebar entry open search with the cursor in the box; Escape closes and returns focus; US6 FR-049a T018 is the first result with state mark, text and feature; US6 FR-049a every typed word must match across groups; US6 FR-049b Enter on a task opens its feature page with the task selected and in view; US6 FR-049b a heading result opens the reader at that heading; US6 FR-049a no match shows the message; US6 FR-049a a word only in a document body finds nothing; US6 FR-049c static build under /eye/ searches the same without other hosts (build with --base /eye/, serve under the sub-path, record requests); US6 FR-049c a task added to tasks.md is found after the live update without reload
  • T072 [US6] Add to tests/e2e/scale.spec.js: US6 SC-014 results update within 200 ms per keystroke with 2,000 tasks and a full ID is first; add CHANGELOG.md lines for search

Checkpoint: All six stories are independently functional.


12 Phase 9: Polish & Cross-Cutting Concerns

Purpose: Live-state, no-JS, mobile, build, scale and security coverage in three engines; docs, README and CHANGELOG

  • T073 [P] Create tests/e2e/live-state.spec.js (replacing tests/e2e/us2-live.spec.js, whose 001 checks move here), following quickstart Scenario 6: US1 FR-020 FR-051 order, filter, depth, expanded rows and tree scroll survive a live update; US2 FR-028 FR-051 map mode survives a live update; US3 FR-051 feature filters, text filter, open phase, selected task and list scroll survive a live update; US5 FR-051 raw view, expansions and area chip survive a live update; FR-051 changed counts are briefly highlighted (stats, segments, sidebar); FR-001 SC-008 changes appear within 2 s; US3 FR-051 deleting the selected task clears the selection; delete tests/e2e/us2-live.spec.js and, in the same change, point the wsl job of .github/workflows/ci.yml at pnpm run test:e2e tests/e2e/live-state.spec.js --project chromium
  • T074 [P] Create tests/e2e/nojs.spec.js (runs in chromium-nojs): FR-053 SC-011 overview tree, phases and task rows open and close without scripts; FR-053 map squares jump to their tree rows; FR-053 feature page tabs, phases, task rows and Show lines work; FR-053 reader clarification sessions, stories, Show more answers and Raw markdown open and close; FR-053 FR-009 mobile menu opens without scripts; FR-048 page follows the OS theme; FR-053 search entry, theme switch, order, depth and filters are not shown
  • T075 [P] Create tests/e2e/mobile.spec.js (runs in chromium-mobile): FR-009 SC-009 every page type has no horizontal page scroll at 375 px; FR-009 sidebar is behind the menu control; FR-010 tree comes before the map; FR-024 FR-026 tapping a square reveals its task without a tooltip; FR-031 FR-032 tabs and phase rail scroll sideways inside their own area; FR-039 reader hides the contents panel and puts the document list behind a control; FR-050 buttons and links have hit targets of at least 36 px (squares excepted)
  • T076 [P] Update tests/e2e/us4-build.spec.js: FR-052 static build under a sub-path has the same pages as serve mode, including feature pages, fonts and search index; FR-004 D6 data-driven widths and bundled fonts work under the sub-path (a segment and a phase-rail block have the expected non-zero widths from their w-pct-N classes, document.fonts.check confirms Geist and Instrument Serif loaded from assets/fonts/); FR-052 theme, map mode, filters, reader and search work in the static build; FR-005 static footer shows the generation time
  • T077 [P] Update tests/e2e/security.spec.js: FR-004 SC-013 no page requests another origin (overview, feature, document; with search opened and fonts loaded); FR-004 no element carries a style attribute and no inline script exists on every page type; FR-001 document raw HTML and scripts stay text in the structured spec view, tooltips and detail panel
  • T078 [P] Update tests/e2e/self-counts.spec.js (SC-004): counts on stats card, segments, sidebar, tree, map legend and feature pages match the checkbox counts of this repository's specs/*/tasks.md
  • T079 Update tests/e2e/scale.spec.js (SC-007): overview and each sampled feature page load within 2 s with 50 features / 2,000 tasks, static build within 30 s
  • T080 Run the full E2E suite in chromium, chromium-nojs and chromium-mobile locally and confirm CI passes in firefox and webkit (SC-016); fix engine-specific issues (for example the exclusive <details name> fallback in src/client/feature.js, line-clamp fallback, clipboard permission differences in tests)
  • T081 [P] Update docs/usage.md (FR-054): new layout (sidebar, icon rail, mobile menu), overview (stats card, Up next, tree order/depth/filter, warning rows), task map (modes, colors including blocked rose, tooltip, reveal, large-project modes), feature pages (tabs, rail, filters, chips, detail panel, addresses), reader (document list, contents, structured spec.md, Raw markdown), theme switch, search (shortcut, what is and is not searched), no-JS behavior, supported browsers (FR-055)
  • T082 [P] Update docs/architecture.md: new modules (model/shares.js, ranks.js, warnings.js, task-files.js; parse/spec-structure.js; render/icons.js, taskmap.js, task-text.js, feature.js, reader.js, spec-view.js, search-index.js), the browser module init/save convention and app.js bootstrap, site map entries (feature pages, fonts, search index, binary bodies), theme tokens with light-dark(), w-pct utilities and the CSP rationale, bundled fonts and scripts/copy-assets.js; check docs/hosting.md still matches (sub-path, fonts relative to the stylesheet)
  • T083 [P] Update README.md (FR-054, §VIII): new docs/screenshot.png of the redesigned overview (taken from the mixed fixture at 1440 × 900 with Playwright and committed) and a feature list naming the task map, feature pages, document reader, theme switch and search; user-facing content only
  • T084 Review CHANGELOG.md ## [Unreleased] for a complete, user-facing description of the redesign (layout, overview, task map, feature pages, reader, theme, search, removed header menu, changed page addresses) (§VI, research D18)
  • T085 Remove dead code and leftovers (§II): unused exports from src/render/overview.js (featureStatus if superseded by feature.status, old grid helpers), unused CSS of 001, the build:css references in docs, and any 001 test that tests removed behavior; run pnpm test, pnpm run test:coverage (every new module covered) and the E2E suite
  • T086 Run all quickstart.md scenarios 1–8 by hand against mixed, nonstandard, this repository and a static build under /eye/, and fix any discrepancy

13 Dependencies & Execution Order

Phase Dependencies

  • Setup (Phase 1): No dependencies — start immediately.
  • Foundational (Phase 2): Depends on Setup (fonts, happy-dom, build:assets). Blocks all user stories.
  • US1 (Phase 3): Depends on Foundational. MVP.
  • US2 (Phase 4): Depends on Foundational and on US1's tree (tree.reveal, data-hl targets, overview layout slot).
  • US3 (Phase 5): Depends on Foundational (feature page route T022, documents T015). Independent of US1/US2 except that tree task IDs and Up next "View task" link to its addresses (already rendered by US1).
  • US4 (Phase 6): Depends on Foundational (generated assets/theme.js, switch, tokens). Its always-dark checks touch regions from US1/US2; run after them or skip those assertions until they exist.
  • US5 (Phase 7): Depends on Foundational (rail, documents). Story phase links read phases from the existing model.
  • US6 (Phase 8): Depends on Foundational; task results link to US3's feature page addresses and heading results to US5's anchors (both already exist as 001 slugs / T014 anchors, so search can be built in parallel).
  • Polish (Phase 9): Depends on all stories.

User Story Dependencies

Setup → Foundational ─┬─ US1 (P1) ── US2 (P2)
                      ├─ US3 (P2)
                      ├─ US4 (P2)   (assertions on US1/US2 regions)
                      ├─ US5 (P3)
                      └─ US6 (P3)
                                   └────────── Polish

Within Each User Story

  • Model tasks before rendering; rendering before the browser module; module before its registration in app.js/CLIENT_MODULES; E2E suite after the story's code; CHANGELOG last.
  • Every module task ships with its unit tests (§IV).
  • Tasks touching src/styles/input.css, src/client/app.js, src/cli/main.js or src/model/build-model.js are sequential across stories (shared files).

Parallel Opportunities

  • Phase 1: T003, T004, T005 in parallel after T001; T002 after T001.
  • Phase 2: T007, T009, T010, T012, T013, T016 in parallel; T017 after T016; T008 before T009/T010/T011; T018 → T019/T020 → T021 → T022 → T023.
  • US1: T024 and T025 in parallel; T030 (CSS) in parallel with T027/T028.
  • US2: T037 (CSS) in parallel with T035.
  • US3: T043 and T045 in parallel; T049 (CSS) in parallel with T047/T048.
  • US5: T062 (CSS) in parallel with T060/T061.
  • After Foundational, US3, US4, US5 and US6 can be developed in parallel with US1 → US2 by different people (watch the shared files above).
  • Polish: T073–T078 and T081–T083 in parallel.

14 Parallel Example: User Story 1

# Model modules together (different files):
Task: "T024 Create src/model/ranks.js with tests/unit/ranks.test.js"
Task: "T025 Create src/model/warnings.js with tests/unit/warnings.test.js"

# Styles while rendering is written:
Task: "T030 Add overview CSS to src/styles/input.css"
Task: "T027 Rewrite page head, stats card and Up next in src/render/overview.js"

15 Parallel Example: User Story 3

Task: "T043 Create src/model/task-files.js with tests/unit/task-files.test.js"
Task: "T045 Add renderInline and L<line> anchors in src/render/markdown.js"
Task: "T049 Add feature page CSS to src/styles/input.css"

16 Parallel Example: Polish

Task: "T073 tests/e2e/live-state.spec.js"
Task: "T074 tests/e2e/nojs.spec.js"
Task: "T075 tests/e2e/mobile.spec.js"
Task: "T081 docs/usage.md"
Task: "T082 docs/architecture.md"

17 Implementation Strategy

MVP First (User Story 1 Only)

  1. Complete Phase 1: Setup.
  2. Complete Phase 2: Foundational (shell, tokens, fonts, bootstrap, live save/reinit).
  3. Complete Phase 3: US1 — the redesigned overview.
  4. STOP and VALIDATE: tests/e2e/us1-overview.spec.js and quickstart Scenario 1.
  5. The tool is usable end to end (001 grid still on the right, documents in the old body inside the rail layout).

Incremental Delivery

  1. Setup + Foundational → new shell everywhere.
  2. US1 → overview (MVP).
  3. US2 → task map replaces the 001 grid.
  4. US3 → feature pages.
  5. US4 → theme switch verified across all pages.
  6. US5 → document reader.
  7. US6 → search.
  8. Polish → no-JS, mobile, three engines, docs, README, CHANGELOG.

Each step keeps the unit and E2E suites green, so any checkpoint is a working tool.


18 Notes

  • [P] tasks = different files, no dependency on an unfinished task.
  • [Story] label maps each task to its user story for traceability (§V); E2E test names carry USn FR-xxx.
  • Never add a style attribute, inline script or third-party URL; the security suite checks all three.
  • Task text and document text are never rewritten, only visually truncated.
  • Commit after each task or logical group; stop at any checkpoint to validate a story on its own.