speckit-eye Home

1 open 002-dashboard-redesign

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

85 / 86 tasks · 8 of 9 phases

99 %

Width = task count · Selected: Phase 9: Polish & Cross-Cutting Concerns

Phase 1Setup (Shared Infrastructure)5/5Complete
T001Update 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

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

T002Create 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.jsJavaScript2 files

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

T003Update 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)

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)

  • Parallel
  • SC-009
  • FR-055
T004Update .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 T073YAML2 files

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

  • Parallel
T005Update 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/

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/

  • Parallel
Phase 2Foundational (Blocking Prerequisites)18/18Complete
T006Rewrite 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)CSSinput.css

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)

  • FR-047
  • FR-036
  • FR-050
T007Create 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.jsJavaScript2 files

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

  • Parallel
T008Change 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)JavaScript4 files

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)

T009Change 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)JavaScript2 files

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)

  • Parallel
T010Change src/build/build.js (writeSite) to write Uint8Array bodies with no encoding and string bodies as UTF-8; update tests/unit/build.test.jsJavaScript2 files

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

  • Parallel
T011Change 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)JavaScript3 files

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)

T012Create 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.jsJavaScript2 files

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

  • Parallel
T013Extend 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)JavaScript2 files

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)

  • Parallel
  • depends on T012
T014Extend 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.jsJavaScript2 files

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

T015Add 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)JavaScript3 files

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)

T016Create 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.jsJavaScript2 files

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

  • Parallel
T017Theme 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.jsJavaScript6 files

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

  • SC-005
T018Rewrite 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")JavaScript5 files

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")

  • FR-005
  • FR-009
  • FR-008
T019Add 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)CSSinput.css

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)

  • FR-009
  • FR-050
T020Create 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.jsJavaScript3 files

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

  • FR-045
T021Change 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)JavaScript2 files

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)

  • FR-051
T022Add 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)JavaScript6 files

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)

T023Update 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)JavaScript test6 files

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)

  • FR-004
Phase 3User Story 1 - See where the project stands in the new overview (Priority: P1) 🎯 MVPP111/11Complete
T024Create 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.jsJavaScript3 files

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

  • US1
  • Parallel
T025Create 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.jsJavaScript3 files

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

  • US1
  • Parallel
  • FR-015
T026Add 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.jsJavaScript2 files

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

  • US1
  • FR-011
T027Rewrite 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)JavaScript2 files

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)

  • US1
  • FR-010
  • FR-011
  • FR-013
T028Rewrite 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)JavaScript2 files

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)

  • US1
  • FR-012
  • FR-014
  • FR-018
  • FR-016
T029Switch 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.jsJavaScript2 files

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

  • US1
T030Add 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)CSSinput.css

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)

  • US1
  • Parallel
  • FR-010
  • FR-018
  • FR-019
  • FR-022
T031Create 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.jsJavaScript2 files

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

  • US1
  • FR-012
  • FR-019
  • FR-020
T032Register 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.jsJavaScript4 files

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

  • US1
T033Rewrite 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)JavaScript testus1-overview.spec.js

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)

  • US1
  • FR-005
  • FR-006
  • FR-011
  • FR-013
  • FR-012
  • FR-015
  • FR-019
  • FR-020
  • SC-001
  • SC-002
  • FR-018
  • SC-009
T034Add 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 fixtureMarkdown testREADME.md

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

  • US1
Phase 4User Story 2 - Explore every task on the task map (Priority: P2)P28/8Complete
T035Create 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=)JavaScript3 files

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=)

  • US2
  • FR-021
  • FR-023
  • FR-027
  • FR-028
T036Replace the 001 grid in src/render/overview.js with renderTaskMap and remove the moved grid code and its tests from tests/unit/overview.test.jsJavaScript2 files

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

  • US2
T037Add 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 motionCSSinput.css

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

  • US2
  • Parallel
  • FR-024
  • FR-025
T038Create 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.jsJavaScript2 files

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

  • US2
  • FR-024
  • FR-026
  • FR-028
T039Register 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.jsJavaScript6 files

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

  • US2
T040Create 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)JavaScript testus2-taskmap.spec.js

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)

  • US2
  • FR-021
  • FR-023
  • FR-024
  • FR-050
  • FR-025
  • FR-026
  • FR-022
  • FR-028
T041Update 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 visibleJavaScript testscale.spec.js

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

  • US2
  • FR-027
  • SC-006
  • FR-011
T042Add CHANGELOG.md lines for the task map (modes, blocked color, tooltip, reveal in tree)

Add CHANGELOG.md lines for the task map (modes, blocked color, tooltip, reveal in tree)

  • US2
Phase 5User Story 3 - Work through a feature on its feature page (Priority: P2)P211/11Complete
T043Create 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.jsJavaScript5 files

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

  • US3
  • Parallel
  • FR-035
  • FR-037
T044Add 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.jsJavaScript2 files

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

  • US3
  • FR-032
T045Extend 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)JavaScript3 files

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)

  • US3
  • Parallel
  • FR-044
T046Create 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.jsJavaScripttask-text.js

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

  • US3
  • FR-035
  • FR-037
T047Complete 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")JavaScript2 files

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")

  • US3
  • FR-030
  • FR-031
  • FR-008
T048Add 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)JavaScript2 files

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)

  • US3
  • FR-032
  • FR-035
T049Add 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 highlightCSSinput.css

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

  • US3
  • Parallel
T050Create 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.jsJavaScript2 files

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

  • US3
  • FR-032
  • FR-036
T051Register 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.jsJavaScript4 files

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

  • US3
T052Add 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 listGo5 files

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

  • US3
  • FR-030
  • FR-031
  • FR-008
  • FR-032
  • FR-034
  • FR-035
  • FR-036
  • FR-044
  • FR-033
  • SC-009
T053Add CHANGELOG.md lines for feature pages (tabs, phase rail, filters, kind/file chips, detail panel, Copy ID, View source line, addressable tasks)

Add CHANGELOG.md lines for feature pages (tabs, phase rail, filters, kind/file chips, detail panel, Copy ID, View source line, addressable tasks)

  • US3
Phase 6User Story 4 - Choose a light, dark or system theme (Priority: P2)P24/4Complete
T054Verify 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)CSS5 files

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)

  • US4
  • FR-047
T055Make 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)JavaScript2 files

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)

  • US4
  • FR-045
T056Create 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)JavaScript testus4-theme.spec.js

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)

  • US4
  • FR-045
  • FR-046
  • SC-005
  • FR-047
  • SC-010
T057Add CHANGELOG.md lines for the theme switch

Add CHANGELOG.md lines for the theme switch

  • US4
Phase 7User Story 5 - Read specs and other documents in the document reader (Priority: P3)P39/9Complete
T058Create 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)JavaScript test3 files

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)

  • US5
  • SC-012
T059Add 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.jsJavaScript2 files

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

  • US5
T060Create 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.jsJavaScript test3 files

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

  • US5
  • FR-042
  • FR-043
T061Create 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)JavaScript5 files

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)

  • US5
  • FR-038
  • FR-041
  • FR-044
T062Add 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> controlCSSinput.css

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

  • US5
  • Parallel
T063Create 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.jsJavaScript2 files

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

  • US5
  • FR-040
  • FR-042
T064Register 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.jsJavaScript3 files

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

  • US5
T065Create 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.jsJavaScript test2 files

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

  • US5
  • FR-038
  • FR-039
  • FR-042
  • FR-041
  • FR-040
  • FR-043
  • SC-012
  • FR-007
T066Add CHANGELOG.md lines for the document reader and the structured spec.md view

Add CHANGELOG.md lines for the document reader and the structured spec.md view

  • US5
Phase 8User Story 6 - Find a task, feature or document with search (Priority: P3)P36/6Complete
T067Create 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.jsJavaScript2 files

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

  • US6
T068Publish assets/search-index.json from src/render/site.js with JSON_TYPE in both modes; update tests/unit/site.test.jsJSON3 files

Publish assets/search-index.json from src/render/site.js with JSON_TYPE in both modes; update tests/unit/site.test.js

  • US6
T069Create 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.jsJavaScript3 files

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

  • US6
  • FR-049
  • FR-049c
T070Register 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.jsJavaScript4 files

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

  • US6
T071Create 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 reloadJavaScript testus6-search.spec.js

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

  • US6
  • FR-049
  • FR-049a
  • FR-049b
  • FR-049c
T072Add 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 searchJavaScript testscale.spec.js

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

  • US6
  • SC-014
Phase 9Polish & Cross-Cutting Concerns13/14
T073Create 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 chromiumJavaScript test3 files

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

  • Parallel
  • FR-020
  • FR-051
  • FR-028
  • FR-001
  • SC-008
T074Create 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 shownJavaScript testnojs.spec.js

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

  • Parallel
  • FR-053
  • SC-011
  • FR-009
  • FR-048
T075Create 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)JavaScript testmobile.spec.js

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)

  • Parallel
  • FR-009
  • SC-009
  • FR-010
  • FR-024
  • FR-026
  • FR-031
  • FR-032
  • FR-039
  • FR-050
T076Update 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 timeJavaScript testus4-build.spec.js

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

  • Parallel
  • FR-052
  • FR-004
  • FR-005
T077Update 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 panelJavaScript testsecurity.spec.js

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

  • Parallel
  • FR-004
  • SC-013
  • FR-001
T078Update 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.mdJavaScript test2 files

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

  • Parallel
  • SC-004
T079Update 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 sJavaScript testscale.spec.js

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

  • SC-007
T080Run 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)JavaScriptfeature.js

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)

  • SC-016
T081Update 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)Markdownusage.md

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)

  • Parallel
  • FR-054
  • FR-055
T082Update 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)Markdown6 files

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)

  • Parallel
T083Update 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 onlyPNGscreenshot.png

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

  • Parallel
  • FR-054
T084Review 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)

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)

T085Remove 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 suiteJavaScriptoverview.js

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

T086Run all quickstart.md scenarios 1–8 by hand against mixed, nonstandard, this repository and a static build under /eye/, and fix any discrepancy

Run all quickstart.md scenarios 1–8 by hand against mixed, nonstandard, this repository and a static build under /eye/, and fix any discrepancy

tasks.md