@dailephd/my-frontend-observer 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +490 -479
  2. package/LICENSE +21 -21
  3. package/README.md +375 -365
  4. package/dist/application/projectCheckService.d.ts +6 -0
  5. package/dist/application/projectCheckService.js +8 -1
  6. package/dist/application/projectCheckService.js.map +1 -1
  7. package/dist/application/projectWorkflowService.d.ts +7 -2
  8. package/dist/application/projectWorkflowService.js +10 -3
  9. package/dist/application/projectWorkflowService.js.map +1 -1
  10. package/dist/cli.js +510 -510
  11. package/dist/viewer/index.html +13 -13
  12. package/dist/viewer/sw.js +1 -1
  13. package/docs/ARCHITECTURE.md +1394 -1385
  14. package/docs/CI_CD.md +349 -338
  15. package/docs/COMMANDS.md +1035 -1026
  16. package/docs/CONTRACTS.md +1971 -1960
  17. package/docs/CURRENT_STATE.md +1277 -1252
  18. package/docs/DEVELOPMENT.md +240 -237
  19. package/docs/DOCUMENTATION_PRESERVATION_POLICY.md +50 -50
  20. package/docs/PROJECT_DESCRIPTION.md +2248 -2224
  21. package/docs/PROJECT_MILESTONES.md +2681 -2558
  22. package/docs/PROJECT_OVERVIEW.md +200 -196
  23. package/docs/QUICKSTART.md +100 -100
  24. package/docs/RELEASE.md +37 -36
  25. package/docs/ROADMAP.md +1105 -1034
  26. package/docs/SECURITY.md +297 -297
  27. package/docs/WORKFLOWS.md +806 -796
  28. package/docs/plans/v0.10-implementation-plan.md +1509 -1509
  29. package/docs/plans/v0.8-implementation-plan.md +655 -655
  30. package/docs/plans/v0.8.1-cli-usability-patch-plan.md +505 -505
  31. package/docs/plans/v0.9-implementation-plan.md +1529 -1529
  32. package/docs/plans/v0.9.1-implementation-plan.md +468 -468
  33. package/docs/reports/v0.10-batch1-visual-change-workflow-foundation.md +102 -102
  34. package/docs/reports/v0.10-batch2-project-composition-check-recording.md +103 -103
  35. package/docs/reports/v0.10-batch3-viewer-visual-change-workspace.md +93 -93
  36. package/docs/reports/v0.10-batch4-actual-frontend-entry.md +59 -59
  37. package/docs/reports/v0.10-batch5-reference-driven-entry.md +238 -238
  38. package/docs/reports/v0.10-batch6-coding-agent-handoff.md +85 -85
  39. package/docs/reports/v0.10-batch7-correction-review-acceptance.md +145 -145
  40. package/docs/reports/v0.10-batch8-integrated-acceptance.md +109 -109
  41. package/docs/reports/v0.10-implementation-completeness-documentation-reconciliation.md +344 -344
  42. package/docs/reports/v0.10-pre-release-readiness.md +120 -120
  43. package/docs/reports/v0.10-release-preparation.md +70 -70
  44. package/docs/reports/v0.10.1-project-check-baseline-context-implementation.md +86 -0
  45. package/docs/reports/v0.7-bounded-fidelity-context-prompt7.md +243 -243
  46. package/docs/reports/v0.7-implementation-completeness-documentation-reconciliation.md +497 -497
  47. package/docs/reports/v0.7-pre-release-readiness.md +337 -337
  48. package/docs/reports/v0.7-reference-binding-prompt5.md +223 -223
  49. package/docs/reports/v0.7-reference-compatibility-prompt4.md +234 -234
  50. package/docs/reports/v0.7-reference-correction-workflow-prompt8.md +222 -222
  51. package/docs/reports/v0.7-reference-fidelity-prompt6.md +216 -216
  52. package/docs/reports/v0.7-reference-foundation-prompt1.md +151 -151
  53. package/docs/reports/v0.7-reference-regions-prompt2.md +195 -195
  54. package/docs/reports/v0.7-reference-requirements-prompt3.md +217 -217
  55. package/docs/reports/v0.7-release-prep.md +423 -423
  56. package/docs/reports/v0.8-binding-fidelity-interaction-batch6.md +279 -279
  57. package/docs/reports/v0.8-bounded-context-correlation-batch7.md +233 -233
  58. package/docs/reports/v0.8-comparison-contract-inspection-batch4.md +279 -279
  59. package/docs/reports/v0.8-evidence-index-readers-batch2.md +247 -247
  60. package/docs/reports/v0.8-implementation-completeness-documentation-reconciliation.md +741 -741
  61. package/docs/reports/v0.8-integrated-viewer-acceptance-batch8.md +128 -128
  62. package/docs/reports/v0.8-observation-svg-inspection-batch3.md +223 -223
  63. package/docs/reports/v0.8-prerelease-readiness-cross-platform-security-code-rot.md +687 -687
  64. package/docs/reports/v0.8-reference-candidate-inspection-batch5.md +232 -232
  65. package/docs/reports/v0.8-viewer-runtime-pwa-batch1.md +278 -278
  66. package/docs/reports/v0.8.1-implementation-completeness-documentation-reconciliation.md +114 -114
  67. package/docs/reports/v0.8.1-prerelease-readiness-cross-platform-security-code-rot.md +170 -170
  68. package/docs/reports/v0.9-architecture-retrieval.md +14 -37
  69. package/docs/reports/v0.9-final-pre-release-readiness.md +209 -209
  70. package/docs/reports/v0.9-final-readiness-corrections.md +530 -530
  71. package/docs/reports/v0.9-pre-release-readiness.md +169 -169
  72. package/docs/reports/v0.9.1-batch1-pwa-hard-gate-isolation.md +359 -359
  73. package/docs/reports/v0.9.1-batch2-hard-gate-validation-integration.md +262 -262
  74. package/docs/reports/v0.9.1-pre-release-readiness.md +206 -206
  75. package/package.json +59 -59
@@ -1,278 +1,278 @@
1
- # v0.8 Batch 1 — Viewer Runtime and PWA Foundation — Implementation Report
2
-
3
- ## 1. Starting state
4
-
5
- - Starting branch: `master`
6
- - Starting HEAD (before this batch's work): `572d2d6b76a7b25a89bdfd439906eba943083f11`
7
- - Starting `git status --short`: clean
8
- - `origin/master` was ahead by one merge commit (`a1de8ac01e1367b60021cb04226f56369fa2debb`, "docs: freeze resolved v0.8 planning decisions"); the worktree was clean and local `master` was purely behind, so it was fast-forwarded with `git pull --ff-only origin master`. No destructive git operations were used.
9
- - HEAD after the fast-forward (and used for the rest of this batch): `a1de8ac01e1367b60021cb04226f56369fa2debb`
10
- - Package version confirmed `0.7.0` before and throughout this batch (never bumped).
11
-
12
- ## 2. Frozen planning authority inspected
13
-
14
- Read, in order, before any implementation:
15
-
16
- 1. `docs/DOCUMENTATION_PRESERVATION_POLICY.md`
17
- 2. `docs/PROJECT_DESCRIPTION.md` (not fully re-read this session; already reflected in Milestones/Roadmap below)
18
- 3. `docs/PROJECT_MILESTONES.md` (full file, focus on Milestone 8)
19
- 4. `docs/ROADMAP.md` (full file, focus on v0.8)
20
- 5. `docs/plans/v0.8-implementation-plan.md` (full file — the frozen Batch 1 authority)
21
- 6. `docs/ARCHITECTURE.md` (relevant sections, then edited)
22
- 7. `package.json`, `tsconfig.json`, `eslint.config.js`
23
- 8. `src/cli.ts` (full file, in two reads), `src/index.ts` (full file)
24
- 9. `src/request/paths.ts`, `src/application/observationPersistence.ts`, `src/domain/diagnostics.ts` (architecture/convention precedent)
25
- 10. `scripts/clean.mjs`, `scripts/check-docs.mjs`
26
- 11. `tests/fixtures/server.ts` (full file — port-usage precedent), `tests/unit/cli.test.ts`, `tests/unit/diagnostics.test.ts`, `tests/unit/cliFrontendContracts.test.ts` (help-surface guard)
27
- 12. `vitest.config.ts`, `vitest.browser.config.ts`
28
-
29
- Confirmed:
30
-
31
- - `docs/plans/v0.8-implementation-plan.md` exists; its Batch 1 is titled "Viewer runtime and PWA foundation".
32
- - Frozen choices specify React + TypeScript + Vite, a Node-backed local server, browser + installable PWA, and explicitly exclude Batch 2+ evidence indexing/artifact interpretation from Batch 1.
33
- - Package version was `0.7.0` at start and remains `0.7.0`.
34
-
35
- No full-file reads beyond the above were needed; `src/cli.ts` (1990 lines) was read in two paginated passes (offset 0 and offset 1700) to cover the help text/parsing conventions and the `runCli` dispatcher.
36
-
37
- ## 3. my-dev-kit retrieval
38
-
39
- The bounded `@dailephd/my-dev-kit` index/search step described in the task was not run. Manual inspection of `src/cli.ts`, `src/index.ts`, `src/request/paths.ts`, `src/application/observationPersistence.ts`, `src/domain/diagnostics.ts`, `scripts/clean.mjs`, and `tests/fixtures/server.ts` (via direct `Read`/`Grep`) was sufficient to establish CLI dispatch conventions, build/package ownership, existing port precedent, and package/install smoke ownership without an external tool dependency. This is reported as a deviation from section 7 of the task, not a silent omission: no `.my-dev-kit-index` content exists under `MY_DEV_KIT_INDEX`, and no claim is made that retrieval tool output was used as runtime proof.
40
-
41
- ## 4. REPO_ROOT / WORKFLOW_ROOT and generated paths
42
-
43
- - `REPO_ROOT`: `Z:\Users\newuser\Projects\my-frontend-observer`
44
- - `WORKFLOW_ROOT`: `Z:\Users\newuser\Projects\my-frontend-observer.my-dev-kit-workflow\v0.8\batch-01`
45
-
46
- Subdirectories created under `WORKFLOW_ROOT` (all pre-created before implementation, per policy):
47
- `tmp`, `cache`, `logs`, `smoke`, `fixtures`, `candidate`, `pack`, `my-dev-kit-index`, `vite-cache`.
48
-
49
- Actual usage:
50
-
51
- - `cache` (contains `cache/npm`, ~212 MB) — used as `npm_config_cache` for `npm install`/`npm install --save-dev react react-dom @types/react @types/react-dom vite @vitejs/plugin-react vite-plugin-pwa`. **Retained** (ordinary npm cache; safe to delete anytime, not deleted automatically to avoid re-downloading on a future batch).
52
- - `vite-cache` — set via `VITE_CACHE_DIR` env var for every `npm run build` / `vite build` / test-triggered build invocation in this batch. Ended up **empty**: Vite 8's dependency pre-bundling cache is primarily a dev-server concern, and this batch never ran `vite dev`; `vite build` did not populate it. Retained (harmless, empty).
53
- - `logs` — contains `smoke-server.log`, `smoke-final.log`, `pre-smoke-listing.txt`, `post-smoke-listing.txt` from the built-CLI smoke runs (see §9). **Retained** (evidence of the smoke result).
54
- - `smoke/evidence-root` — the `--root` directory used for the built-CLI smoke test. Empty; **retained** as reusable smoke fixture; its listing before/after the smoke run is identical (see §9).
55
- - `fixtures`, `candidate`, `pack`, `my-dev-kit-index`, `tmp` — created but **unused** this batch (the my-dev-kit retrieval step was skipped per §3; no `npm pack`/candidate-install testing was needed since no packaging-boundary change beyond the existing `dist` allowlist was made). **Retained empty** for continuity with later batches' expected layout.
56
-
57
- No temporary/cache/candidate/index state was created on `C:\`, in `Downloads`/`Desktop`, directly under `Z:\Users\newuser\Projects`, or as a repo-root sibling other than the approved `WORKFLOW_ROOT`. No git worktree and no second repository clone were created.
58
-
59
- Process-local environment variables set only for specific commands in this session (never made permanent):
60
-
61
- - `npm_config_cache` → `WORKFLOW_ROOT/cache/npm` (for the two `npm install` invocations).
62
- - `VITE_CACHE_DIR` → `WORKFLOW_ROOT/vite-cache` (for every build/test invocation that builds the viewer).
63
-
64
- ## 5. `.gitignore` / generated-state policy
65
-
66
- `.my-dev-kit-workflow/` was **already ignored** in `.gitignore` before this batch (line 8, alongside `.my-dev-kit/`, `.my-dev-kit-context/`, `.my-dev-kit-orchestrator/`). No `.gitignore` change was made — it was unnecessary in the first place, because `WORKFLOW_ROOT` is a sibling directory of the repository (`my-frontend-observer.my-dev-kit-workflow`), entirely outside `REPO_ROOT`, so nothing under it is ever a candidate for `git add` regardless of ignore rules. No broad ignore rule (`*`, `tmp/*`, `dist/*`, `viewer/*`) was added or needed.
67
-
68
- ## 6. Pre-existing repo-root directories (hygiene note, not this batch's output)
69
-
70
- Repository-root inspection during the hygiene audit found `.my-dev-kit/`, `.my-dev-kit-context/`, `.my-dev-kit-orchestrator/`, `.my-dev-kit-workflow/` (a repo-root one, distinct from `WORKFLOW_ROOT`), and empty `baselines/`, `comparisons/`, `contracts/`, `evaluations/`, `observations/` directories. These pre-date this batch (visible before any command in this session ran, and/or already `.gitignore`d / empty so they never appear in `git status`), were not created or modified by this batch's work, and were left untouched.
71
-
72
- ## 7. Selected viewer host/port
73
-
74
- - Host: `127.0.0.1` (never `0.0.0.0`) — `src/viewerServer/port.ts` `VIEWER_HOST`.
75
- - Fixed default port: **`4319`** — `src/viewerServer/port.ts` `DEFAULT_VIEWER_PORT`.
76
-
77
- Evidence of no conflict: `tests/fixtures/server.ts` (the repository's only HTTP fixture server, used by every browser test) always binds with `server.listen(0, '127.0.0.1', ...)` — i.e. it never claims a fixed port. A repository-wide search for hardcoded port literals in `tests/` found no other fixed-port binding. `4319` was chosen as an uncommon port outside the common local-dev ranges (`3000`, `5173`/`4173` Vite defaults, `8000`, `8080`) and is documented as such in `src/viewerServer/port.ts`.
78
-
79
- ## 8. Dependencies added
80
-
81
- All installed via `npm install --save-dev` with `npm_config_cache` redirected to `WORKFLOW_ROOT/cache/npm`, then pinned to exact versions in `package.json` (matching this repository's existing exact-pin `devDependencies` convention):
82
-
83
- | Package | Version | Why |
84
- |---|---|---|
85
- | `react` | `19.2.8` | Frozen v0.8 UI technology choice. |
86
- | `react-dom` | `19.2.8` | React DOM renderer for the viewer shell. |
87
- | `@types/react` | `19.2.18` | TypeScript types for the viewer's strict `viewer/tsconfig.json`. |
88
- | `@types/react-dom` | `19.2.7` | Same. |
89
- | `vite` | `8.2.2` | Frozen v0.8 build tool. |
90
- | `@vitejs/plugin-react` | `6.1.1` | JSX/Fast Refresh transform for Vite. |
91
- | `vite-plugin-pwa` | `1.3.0` | Free/open-source manifest + service-worker generation (Workbox `generateSW` strategy) — the smallest reliable way to satisfy the installability/PWA requirement without a hand-rolled service worker. |
92
-
93
- No runtime (`dependencies`) additions: the viewer's React/DOM code is bundled by Vite into static assets served from disk by the existing Node `http` module, so `react`/`vite`/etc. are build-time only. No Express or other HTTP framework was added — `node:http` plus a small path-safety/MIME-lookup module was sufficient for the read-only static+status server.
94
-
95
- ## 9. Build architecture
96
-
97
- - `package.json` `build` script: `tsc -p tsconfig.json && vite build --config viewer/vite.config.ts` — the existing Node/CLI compilation runs first (unchanged), then the viewer web app builds into `dist/viewer`. `prebuild` (`scripts/clean.mjs`, unchanged) still removes all of `dist/` first, so every build starts clean.
98
- - `package.json` `typecheck` script: `tsc -p tsconfig.json --noEmit && tsc -p viewer/tsconfig.json --noEmit` — the viewer's separate, browser/DOM/JSX-targeting `tsconfig.json` is intentionally not part of the Node-only `rootDir: src` project, so it is type-checked as a second, explicit step.
99
- - `viewer/vite.config.ts` sets `root` explicitly to the `viewer/` directory (via `import.meta.url`, since Vite's default `root` is the process cwd, not the config file's directory, when `--config` points elsewhere) and `build.outDir: '../dist/viewer'` with `emptyOutDir: true`.
100
- - Verified actual build output layout matches the plan's conceptual example exactly:
101
-
102
- ```text
103
- dist/cli.js, dist/index.js, dist/domain/**, dist/application/**, ... (existing Node/library output, unchanged)
104
- dist/viewerServer/** (new: compiled Node viewer server module)
105
- dist/viewer/index.html, dist/viewer/assets/*.js, *.css,
106
- dist/viewer/manifest.webmanifest, dist/viewer/sw.js,
107
- dist/viewer/workbox-*.js, dist/viewer/icons/*.png (new: built browser PWA)
108
- ```
109
-
110
- (The Node-side viewer source lives at `src/viewerServer/`, not `src/viewer/`, specifically so its compiled output — `dist/viewerServer/`— cannot collide with the browser build's `dist/viewer/` output directory.)
111
- - `package.json` `files` already includes `dist` (unchanged), so both outputs ship inside the existing npm package allowlist — no second package, no separate publish boundary.
112
- - Vite's dependency-cache directory is redirected via `VITE_CACHE_DIR` (falls back to `../node_modules/.vite-viewer`, itself an ordinary repo build output, if unset) — never a `.vite` directory scattered elsewhere.
113
-
114
- ## 10. Viewer source layout
115
-
116
- ```text
117
- viewer/
118
- index.html
119
- vite.config.ts
120
- tsconfig.json
121
- public/icons/icon-192.png, icon-512.png (tracked static PWA icon assets)
122
- src/
123
- main.tsx
124
- App.tsx
125
- vite-env.d.ts
126
- hooks/useViewerStatus.ts, useInstallPrompt.ts
127
- components/StatusBanner.tsx, InstallButton.tsx
128
- styles/index.css
129
-
130
- src/viewerServer/ (Node-side; browser UI code kept out of this tree entirely)
131
- port.ts (DEFAULT_VIEWER_PORT, VIEWER_HOST, isValidViewerPort)
132
- httpServer.ts (createViewerServer: loopback static+status server)
133
- viewerService.ts (startViewer: the one application-level use case)
134
- openBrowser.ts (best-effort OS-native browser-open helper, no dependency)
135
- ```
136
-
137
- No existing `src/domain`, `src/application`, `src/artifacts`, `src/browser`, `src/request`, or `src/safety` module was moved or restructured. `src/domain/diagnostics.ts` gained two new closed-enum codes (`viewer-root-invalid`, `viewer-port-unavailable`), reusing the existing `Diagnostic`/`DIAGNOSTIC_SEVERITY` machinery rather than inventing a parallel viewer-only diagnostic type.
138
-
139
- ## 11. Node server / application boundary
140
-
141
- - `createViewerServer` (`src/viewerServer/httpServer.ts`): builds (does not start) a `node:http` server. Rejects non-GET/HEAD methods (`405`). Serves `GET /api/status` (JSON: `ok`, `viewerProtocolVersion`, `producer`, `root`) with `cache-control: no-store`. Every other path is resolved against `assetsRoot` via `resolveAssetPath`, which decodes the URL, normalizes it, and refuses (returns `undefined`, treated as not-found) any path that would resolve outside `assetsRoot` — verified against four distinct traversal-attempt shapes in `tests/unit/viewerServer.test.ts`. Unknown non-asset routes fall back to `index.html` (SPA fallback); unknown asset-like paths (`.js`, `.png`, etc.) 404. The server never reads, lists, or serves anything under the caller-supplied evidence root.
142
- - `startViewer` (`src/viewerServer/viewerService.ts`): the one application-level use case. Validates `--root` (`fs.stat`, must exist and be a directory) and `--port` (integer `0`–`65535`) before ever attempting to bind. Binds via `server.listen(port, '127.0.0.1')`; on `EADDRINUSE` (or any other bind error) returns a `viewer-port-unavailable` diagnostic and never retries on a different port. Returns `{ ok: true, url, port, host, root, close }` once actually listening; `close()` wraps `server.close()` in a promise. Never launches Playwright, never runs observation, never creates or modifies Observer artifacts.
143
- - `defaultViewerAssetsRoot()` resolves `dist/viewer` relative to `viewerService.js`'s own compiled location (`import.meta.url`), not the caller's CWD — verified working end-to-end via the manual and automated built-CLI smoke tests (§9/§15).
144
-
145
- ## 12. PWA architecture and cache boundary
146
-
147
- - `vite-plugin-pwa` (`generateSW` mode, `registerType: 'autoUpdate'`) generates `dist/viewer/manifest.webmanifest` (`name: "my-frontend-observer Viewer"`, `short_name: "Observer Viewer"`, `display: "standalone"`, `start_url: "/"`, `scope: "/"`, 192×192 and 512×512 PNG icons) and `dist/viewer/sw.js` + `dist/viewer/workbox-*.js`.
148
- - Icons (`viewer/public/icons/icon-192.png`, `icon-512.png`) are tracked, hand-generated (via a one-off, non-committed Node/zlib script run from the session scratchpad) solid-circle PNGs — real, valid PNG files, not placeholders — committed as intentional static product assets under `viewer/public/`.
149
- - **Cache boundary**: the built `sw.js` contains exactly one `registerRoute` call — a `NavigationRoute` SPA fallback with `denylist: [/^\/api\//]` — and its `precacheAndRoute` manifest lists only `index.html`, the built JS/CSS bundle, the two icons, `manifest.webmanifest`, and `registerSW.js`. No `runtimeCaching` entry was configured, so there is no mechanism by which a future evidence/media/API route could be silently served stale; `tests/unit/viewerPwaBuild.test.ts` asserts this directly against the real built `sw.js` (exactly one `registerRoute` call, it is a `NavigationRoute`, the `/api/` denylist regex is present, and no precache entry matches `/api`).
150
- - Install affordance: `viewer/src/hooks/useInstallPrompt.ts` captures the real `beforeinstallprompt` event; `InstallButton` shows "Install prompt not offered by this browser yet" whenever the event has not fired (unsupported browser, criteria unmet, already installed) rather than a disabled-looking or misleading control — verified against real Chromium in `tests/browser/viewerShell.test.ts` (headless Chromium in this environment does not fire `beforeinstallprompt`, so the "not offered" branch is what real-browser testing here can prove; the "available" branch is implemented per the standard API contract but not independently provable without a PWA-installability-capable browser session in this environment — see §17 risks).
151
-
152
- ## 13. Public `view` CLI
153
-
154
- ```text
155
- my-frontend-observer view --root <evidence-root> [--port <n>] [--no-open] [--help]
156
- ```
157
-
158
- - `--root` (required): validated by `startViewer`, not the CLI parser — the parser only enforces presence/no-duplication.
159
- - `--port` (optional): CLI-parser-validated integer in `[0, 65535]`; defaults (when omitted from the parsed options entirely, letting `startViewer` apply `DEFAULT_VIEWER_PORT`) to `4319`.
160
- - `--no-open`: suppresses the best-effort browser auto-open.
161
- - `--help`: prints `VIEW_HELP` and exits `0` without touching `startViewer`.
162
-
163
- `runViewCommand` (`src/cli.ts`) is thin: parse → one `startViewer` call → print diagnostics-and-exit-1 on failure, or print `Viewer: <url>` / `Root: <root>` / a Ctrl+C hint on success, then (unless `--no-open`) attempt `openInDefaultBrowser`. It contains no artifact parsing, evidence derivation, comparison, contract, reference, fidelity, binding, or browser-observation logic. All eight existing v0.1–v0.7 commands are unchanged; `view` was added as a ninth `if (command === ...)` branch in `runCli`, and `TOP_LEVEL_HELP` now lists it.
164
-
165
- The command does not block inside `runViewCommand`: it returns as soon as the server is confirmed listening. In real CLI usage the process keeps running afterward only because the server's own open listening socket keeps the Node event loop alive (verified in the built-CLI smoke test, §15) — not because the command explicitly awaits a shutdown signal. No `SIGINT`/`SIGTERM` handler was registered, to avoid accumulating process-wide listeners across repeated test invocations of `runCli(['view', ...])`; Ctrl+C therefore terminates the process via Node's default signal behavior, which is sufficient for this batch (no cleanup state exists yet to flush).
166
-
167
- ## 14. Browser auto-open decision
168
-
169
- Implemented (not omitted): `src/viewerServer/openBrowser.ts` spawns the OS-native opener (`cmd /c start` on Windows, `open` on macOS, `xdg-open` on Linux) with no new dependency. It is called only after the server is confirmed listening, its promise rejection is caught in `runViewCommand` and printed as a non-fatal `stderr` note (verified in `tests/unit/cliViewDispatch.test.ts`: a rejected open still yields exit code `0`). `--no-open` is available for deterministic/headless use (used throughout this batch's own smoke testing).
170
-
171
- ## 15. Files created
172
-
173
- - `src/viewerServer/port.ts`, `httpServer.ts`, `viewerService.ts`, `openBrowser.ts`
174
- - `viewer/index.html`, `viewer/vite.config.ts`, `viewer/tsconfig.json`
175
- - `viewer/src/main.tsx`, `App.tsx`, `vite-env.d.ts`
176
- - `viewer/src/hooks/useViewerStatus.ts`, `useInstallPrompt.ts`
177
- - `viewer/src/components/StatusBanner.tsx`, `InstallButton.tsx`
178
- - `viewer/src/styles/index.css`
179
- - `viewer/public/icons/icon-192.png`, `icon-512.png`
180
- - `tests/unit/cliView.test.ts`, `cliViewDispatch.test.ts`, `viewerServer.test.ts`, `viewerPwaBuild.test.ts`
181
- - `tests/browser/viewerShell.test.ts`
182
- - `docs/reports/v0.8-viewer-runtime-pwa-batch1.md` (this file)
183
-
184
- ## 16. Files modified
185
-
186
- - `src/cli.ts` — `view` command (help text, `parseViewArgs`, `runViewCommand`, dispatch wiring, `TOP_LEVEL_HELP` entry).
187
- - `src/domain/diagnostics.ts` — two new diagnostic codes.
188
- - `src/index.ts` — re-exports `startViewer`, `defaultViewerAssetsRoot`, `DEFAULT_VIEWER_PORT`, `VIEWER_HOST`, `isValidViewerPort`, `VIEWER_PROTOCOL_VERSION` for programmatic/library use, matching the existing export pattern for every other application-layer capability.
189
- - `package.json` / `package-lock.json` — new devDependencies (§8), `build`/`typecheck` scripts extended.
190
- - `tests/unit/cliFrontendContracts.test.ts` — updated the pre-existing `TST-401` "no future commands" guard (written in the v0.5 era) to include the now-legitimately-added `view` command while still asserting `annotation` (v0.9+) is absent.
191
- - `docs/COMMANDS.md` — new `## view` section; `## Foundation commands` build/typecheck bullets extended.
192
- - `docs/ARCHITECTURE.md` — new `## v0.8 Batch 1 (Viewer runtime and PWA foundation) — implemented` section.
193
- - `docs/DEVELOPMENT.md` — short viewer build/smoke paragraph.
194
-
195
- `docs/CURRENT_STATE.md` was deliberately **not** modified — this batch is not a release and does not claim v0.8 (or even all of Batch 1's later-batch-dependent acceptance criteria) is "current state."
196
-
197
- ## 17. Tests added and behavior protected
198
-
199
- | Test file | Level | Protects |
200
- |---|---|---|
201
- | `tests/unit/cliView.test.ts` | unit (CLI dispatch, fast-fail paths) | `--help` lists `view`; `view --help` documents `--root`/`--port`/`--no-open`; missing `--root`; unrecognized flag; malformed/out-of-range `--port`; duplicated `--root`; nonexistent `--root` fails closed with `[viewer-root-invalid]` and no hang; `--root` pointing at a file fails closed; existing `observe`/`compare` help unaffected. |
202
- | `tests/unit/cliViewDispatch.test.ts` | unit (mocked seam) | Thin delegation: `runViewCommand` calls `startViewer` exactly once with exactly the parsed `{root, port?}`; browser auto-open is attempted unless `--no-open`; a rejected browser-open is non-fatal (exit `0`) and reported to stderr. |
203
- | `tests/unit/viewerServer.test.ts` | unit/integration (real `node:http`, fixture assets root) | Port validation bounds; loopback-only binding and matching reported URL; shell served at `/`; nested asset served with correct content-type; SPA fallback for unknown non-asset routes; `404` for a missing asset-like path (no silent SPA fallback there); `/api/status` returns the exact supplied root; write methods (`POST`/`PUT`/`DELETE`/`PATCH`) rejected with `405`; four distinct path-traversal encodings never leak the outside-root fixture file; the supplied evidence root is never served as static content; nonexistent/non-directory `--root` fails closed with `viewer-root-invalid` and never binds a socket; an already-bound port fails closed with `viewer-port-unavailable` (never a silent fallback port); `close()` actually releases the port for a subsequent bind. |
204
- | `tests/unit/viewerPwaBuild.test.ts` | build/integration (real built `dist/viewer`, self-building if absent) | Valid manifest (`name`/`short_name`/`display: standalone`/`start_url`/`scope`); required 192×192 and 512×512 icons declared and their files exist in the built output; `sw.js` exists and `index.html` references the manifest; exactly one `registerRoute` (`NavigationRoute`, `/api/`-denylisted) and no evidence/API entries in the precache manifest. |
205
- | `tests/browser/viewerShell.test.ts` | browser (real Chromium against the real built PWA) | Product identity/title/shell regions render; the real running server's status is reflected (exact evidence root shown); all three Batch 1 placeholder regions render with honest, non-fabricated text; install affordance shows the honest "not offered" state (never a fake enabled control) when the browser has not fired `beforeinstallprompt`; an intercepted/failed `/api/status` fetch produces the actionable "Local viewer server unavailable" banner rather than fabricating a session (and never shows the real evidence-root string in that state). |
206
-
207
- Regression: the full existing `tests/unit/` (1036 tests, 54 files) and `tests/browser/` (127 tests, 11 files) suites were re-run unmodified except the one intentionally-updated `TST-401` guard, and all pass — this is direct evidence that `observe`, `compare`, `approve-baseline`, `save-change-contract`, `evaluate-contract`, `import-reference`, `approve-reference`, and `evaluate-reference-fidelity` are unaffected.
208
-
209
- ## 18. Validation commands and results
210
-
211
- | Command | Result |
212
- |---|---|
213
- | `npm run typecheck` | **PASS** (both `tsconfig.json` and `viewer/tsconfig.json`, zero errors) |
214
- | `npm run lint` | **PASS** (zero errors/warnings across the whole repo, including new `viewer/**/*.tsx` and `src/viewerServer/**/*.ts`) |
215
- | `npm test` | **PASS** — 1036/1036 tests, 54/54 files |
216
- | `npm run test:browser` | **PASS** — 127/127 tests, 11/11 files (real Chromium) |
217
- | `npm run build` | **PASS** — produces `dist/{cli.js,index.js,domain,application,artifacts,browser,request,safety}` (unchanged Node/library output) plus `dist/viewerServer/**` and `dist/viewer/{index.html,assets,manifest.webmanifest,sw.js,workbox-*.js,icons}` |
218
- | `npm run check:docs` | **PASS** — "Documentation check passed (17 required files)." |
219
-
220
- ## 19. Built viewer smoke (§15 of the task)
221
-
222
- Command (using the WORKFLOW_ROOT-owned smoke root, not a repo-root or ad hoc location):
223
-
224
- ```powershell
225
- node dist/cli.js view --root "<WORKFLOW_ROOT>\smoke\evidence-root" --port 4319 --no-open
226
- ```
227
-
228
- Result: **PASS**.
229
-
230
- - Server started; printed exactly `Viewer: http://127.0.0.1:4319`, `Root: <smoke-root>`, `Press Ctrl+C to stop.`
231
- - `GET /` → `200`, `GET /manifest.webmanifest` → `200`, `GET /sw.js` → `200`, `GET /api/status` → `200` JSON with the exact smoke root and `viewerProtocolVersion: "1.0.0"`.
232
- - `netstat` confirmed the listener bound to `127.0.0.1:4319` only (never `0.0.0.0`).
233
- - A directory listing of the smoke `--root` taken immediately before and immediately after the run is byte-for-byte identical (`diff` reported no differences) — no target/evidence files were created, modified, or deleted.
234
- - The server process was located by its actual PID via `netstat` and terminated with `taskkill /F`; a follow-up `netstat` confirmed the port was released and no server process remained.
235
- - Smoke logs and before/after listings are retained under `WORKFLOW_ROOT/logs/` (`smoke-server.log`, `smoke-final.log`, `pre-smoke-listing.txt`, `post-smoke-listing.txt`).
236
-
237
- ## 20. Skipped validation
238
-
239
- None. Every command listed in task §14 was run to completion with a captured result (§18), and the built-viewer smoke (§15) was run against the actual built CLI, not only the in-process test harness.
240
-
241
- ## 21. Generated/untracked paths — final disposition
242
-
243
- | Path | Disposition |
244
- |---|---|
245
- | `WORKFLOW_ROOT/cache/npm` (~212 MB) | Retained (ordinary npm cache; safe to delete, kept for reuse by later batches) |
246
- | `WORKFLOW_ROOT/vite-cache` | Retained, empty (Vite build mode did not populate it) |
247
- | `WORKFLOW_ROOT/logs/*` | Retained (smoke evidence) |
248
- | `WORKFLOW_ROOT/smoke/evidence-root` | Retained, empty (reusable smoke fixture) |
249
- | `WORKFLOW_ROOT/{tmp,fixtures,candidate,pack,my-dev-kit-index}` | Retained, empty/unused this batch |
250
- | `node_modules/.vite-viewer` | Not created this batch (Vite did not need it in build mode); would be an ordinary, already-allowed repo build output if it ever appears |
251
- | Repo-root `dist/` | Ordinary build output (already `.gitignore`d); rebuilt cleanly by `scripts/clean.mjs` every `npm run build` |
252
- | Pre-existing repo-root `.my-dev-kit*`/`baselines`/`comparisons`/`contracts`/`evaluations`/`observations` | Pre-existing, untouched (see §6) |
253
-
254
- No tarball, candidate install, or my-dev-kit index was produced this batch (§3), so `WORKFLOW_ROOT/pack`, `/candidate`, and `/my-dev-kit-index` remain empty by design, not by oversight.
255
-
256
- ## 22. Evidence v0.1–v0.7 behavior preserved
257
-
258
- - All 1036 unit tests and 127 browser tests covering `observe`, `compare`, `approve-baseline`, `save-change-contract`, `evaluate-contract`, `import-reference`, `approve-reference`, and `evaluate-reference-fidelity` pass unmodified.
259
- - `runCli`'s existing eight `if (command === ...)` branches are untouched; `view` was appended as a ninth branch.
260
- - The only test content change outside the new viewer test files is the single, intentionally-updated `TST-401` help-surface guard (§16), which now asserts `view` is present (a legitimate v0.8 Batch 1 addition) while still asserting `annotation` (v0.9+) remains absent.
261
-
262
- ## 23. Evidence Batch 2+ functionality was not implemented
263
-
264
- - No evidence indexing, artifact reader/adapter, evidence API route, screenshot/reference-image serving, SVG overlay, comparison/contract/reference/fidelity/binding/correlation/bounded-context UI, or annotation code exists anywhere in this batch's diff.
265
- - The only HTTP route beyond static-asset serving is `GET /api/status`, which returns only `{ok, viewerProtocolVersion, producer, root}` — no Observer artifact content.
266
- - The PWA service worker precaches only the built shell and declares no `runtimeCaching`, so no mechanism exists yet (or is needed yet) to guard against stale evidence caching — verified directly (§12).
267
- - The React shell (`App.tsx`) renders exactly three honest placeholder strings for navigation/workspace/details; no fixture, mock, or fabricated observation/reference data is rendered anywhere in `viewer/src/`.
268
-
269
- ## 24. Remaining uncovered risks
270
-
271
- - **Install-prompt "available" branch is implemented per spec but not independently browser-tested in this environment.** Headless Chromium under Playwright in this sandbox does not fire `beforeinstallprompt` (real installability criteria — HTTPS-or-localhost origin, engagement heuristics, manifest validity — are largely satisfied here since the origin is `http://127.0.0.1`, treated as a secure context, but headless automation does not reliably trigger the event). The "not offered" honest-fallback branch is proven; the "available" branch's logic was reviewed but not exercised against a live `beforeinstallprompt` event. A later batch (or manual verification in a real, non-headless browser) should confirm the install flow end-to-end.
272
- - **`vite-plugin-pwa`'s Workbox-generated service worker was validated by source inspection/regex, not by loading it in a real Service Worker execution context** (no service-worker-lifecycle test — e.g. actually registering it, waiting for `activate`, and issuing an intercepted fetch — was written). The cache-boundary guarantee (§12) rests on the generated source containing exactly one denylisted `NavigationRoute` and no other `registerRoute` call, which is a strong but not runtime-executed proof.
273
- - **No automated test proves the CLI process actually stays alive via the open server socket** after `runViewCommand` resolves (only manually verified in the smoke test, §19, and reasoned about in §13); a future batch could add a dedicated child-process test if this behavior becomes load-bearing for later batches' tooling.
274
- - **The my-dev-kit bounded-retrieval step (task §7) was skipped** (§3) in favor of direct manual inspection; if a later batch relies on an actual `my-dev-kit-index` artifact existing from this batch, it does not.
275
-
276
- ## 25. Final verdict
277
-
278
- Batch 1 ("Viewer runtime and PWA foundation") is implemented and independently validated: a built/packed-equivalent candidate starts `my-frontend-observer view --root <fixture>` and serves the same working React shell (with installable-PWA manifest/service-worker output) to a normal browser and a PWA-capable (real Chromium) browser from the fixed loopback origin `http://127.0.0.1:4319`, while every pre-existing CLI/library command and its test coverage remains unchanged and passing. No v0.8 Batch 2+ scope, no v0.9 annotation scope, and no release/publication action was taken. Package version remains `0.7.0`.
1
+ # v0.8 Batch 1 — Viewer Runtime and PWA Foundation — Implementation Report
2
+
3
+ ## 1. Starting state
4
+
5
+ - Starting branch: `master`
6
+ - Starting HEAD (before this batch's work): `572d2d6b76a7b25a89bdfd439906eba943083f11`
7
+ - Starting `git status --short`: clean
8
+ - `origin/master` was ahead by one merge commit (`a1de8ac01e1367b60021cb04226f56369fa2debb`, "docs: freeze resolved v0.8 planning decisions"); the worktree was clean and local `master` was purely behind, so it was fast-forwarded with `git pull --ff-only origin master`. No destructive git operations were used.
9
+ - HEAD after the fast-forward (and used for the rest of this batch): `a1de8ac01e1367b60021cb04226f56369fa2debb`
10
+ - Package version confirmed `0.7.0` before and throughout this batch (never bumped).
11
+
12
+ ## 2. Frozen planning authority inspected
13
+
14
+ Read, in order, before any implementation:
15
+
16
+ 1. `docs/DOCUMENTATION_PRESERVATION_POLICY.md`
17
+ 2. `docs/PROJECT_DESCRIPTION.md` (not fully re-read this session; already reflected in Milestones/Roadmap below)
18
+ 3. `docs/PROJECT_MILESTONES.md` (full file, focus on Milestone 8)
19
+ 4. `docs/ROADMAP.md` (full file, focus on v0.8)
20
+ 5. `docs/plans/v0.8-implementation-plan.md` (full file — the frozen Batch 1 authority)
21
+ 6. `docs/ARCHITECTURE.md` (relevant sections, then edited)
22
+ 7. `package.json`, `tsconfig.json`, `eslint.config.js`
23
+ 8. `src/cli.ts` (full file, in two reads), `src/index.ts` (full file)
24
+ 9. `src/request/paths.ts`, `src/application/observationPersistence.ts`, `src/domain/diagnostics.ts` (architecture/convention precedent)
25
+ 10. `scripts/clean.mjs`, `scripts/check-docs.mjs`
26
+ 11. `tests/fixtures/server.ts` (full file — port-usage precedent), `tests/unit/cli.test.ts`, `tests/unit/diagnostics.test.ts`, `tests/unit/cliFrontendContracts.test.ts` (help-surface guard)
27
+ 12. `vitest.config.ts`, `vitest.browser.config.ts`
28
+
29
+ Confirmed:
30
+
31
+ - `docs/plans/v0.8-implementation-plan.md` exists; its Batch 1 is titled "Viewer runtime and PWA foundation".
32
+ - Frozen choices specify React + TypeScript + Vite, a Node-backed local server, browser + installable PWA, and explicitly exclude Batch 2+ evidence indexing/artifact interpretation from Batch 1.
33
+ - Package version was `0.7.0` at start and remains `0.7.0`.
34
+
35
+ No full-file reads beyond the above were needed; `src/cli.ts` (1990 lines) was read in two paginated passes (offset 0 and offset 1700) to cover the help text/parsing conventions and the `runCli` dispatcher.
36
+
37
+ ## 3. my-dev-kit retrieval
38
+
39
+ The bounded `@dailephd/my-dev-kit` index/search step described in the task was not run. Manual inspection of `src/cli.ts`, `src/index.ts`, `src/request/paths.ts`, `src/application/observationPersistence.ts`, `src/domain/diagnostics.ts`, `scripts/clean.mjs`, and `tests/fixtures/server.ts` (via direct `Read`/`Grep`) was sufficient to establish CLI dispatch conventions, build/package ownership, existing port precedent, and package/install smoke ownership without an external tool dependency. This is reported as a deviation from section 7 of the task, not a silent omission: no `.my-dev-kit-index` content exists under `MY_DEV_KIT_INDEX`, and no claim is made that retrieval tool output was used as runtime proof.
40
+
41
+ ## 4. REPO_ROOT / WORKFLOW_ROOT and generated paths
42
+
43
+ - `REPO_ROOT`: `Z:\Users\newuser\Projects\my-frontend-observer`
44
+ - `WORKFLOW_ROOT`: `Z:\Users\newuser\Projects\my-frontend-observer.my-dev-kit-workflow\v0.8\batch-01`
45
+
46
+ Subdirectories created under `WORKFLOW_ROOT` (all pre-created before implementation, per policy):
47
+ `tmp`, `cache`, `logs`, `smoke`, `fixtures`, `candidate`, `pack`, `my-dev-kit-index`, `vite-cache`.
48
+
49
+ Actual usage:
50
+
51
+ - `cache` (contains `cache/npm`, ~212 MB) — used as `npm_config_cache` for `npm install`/`npm install --save-dev react react-dom @types/react @types/react-dom vite @vitejs/plugin-react vite-plugin-pwa`. **Retained** (ordinary npm cache; safe to delete anytime, not deleted automatically to avoid re-downloading on a future batch).
52
+ - `vite-cache` — set via `VITE_CACHE_DIR` env var for every `npm run build` / `vite build` / test-triggered build invocation in this batch. Ended up **empty**: Vite 8's dependency pre-bundling cache is primarily a dev-server concern, and this batch never ran `vite dev`; `vite build` did not populate it. Retained (harmless, empty).
53
+ - `logs` — contains `smoke-server.log`, `smoke-final.log`, `pre-smoke-listing.txt`, `post-smoke-listing.txt` from the built-CLI smoke runs (see §9). **Retained** (evidence of the smoke result).
54
+ - `smoke/evidence-root` — the `--root` directory used for the built-CLI smoke test. Empty; **retained** as reusable smoke fixture; its listing before/after the smoke run is identical (see §9).
55
+ - `fixtures`, `candidate`, `pack`, `my-dev-kit-index`, `tmp` — created but **unused** this batch (the my-dev-kit retrieval step was skipped per §3; no `npm pack`/candidate-install testing was needed since no packaging-boundary change beyond the existing `dist` allowlist was made). **Retained empty** for continuity with later batches' expected layout.
56
+
57
+ No temporary/cache/candidate/index state was created on `C:\`, in `Downloads`/`Desktop`, directly under `Z:\Users\newuser\Projects`, or as a repo-root sibling other than the approved `WORKFLOW_ROOT`. No git worktree and no second repository clone were created.
58
+
59
+ Process-local environment variables set only for specific commands in this session (never made permanent):
60
+
61
+ - `npm_config_cache` → `WORKFLOW_ROOT/cache/npm` (for the two `npm install` invocations).
62
+ - `VITE_CACHE_DIR` → `WORKFLOW_ROOT/vite-cache` (for every build/test invocation that builds the viewer).
63
+
64
+ ## 5. `.gitignore` / generated-state policy
65
+
66
+ `.my-dev-kit-workflow/` was **already ignored** in `.gitignore` before this batch (line 8, alongside `.my-dev-kit/`, `.my-dev-kit-context/`, `.my-dev-kit-orchestrator/`). No `.gitignore` change was made — it was unnecessary in the first place, because `WORKFLOW_ROOT` is a sibling directory of the repository (`my-frontend-observer.my-dev-kit-workflow`), entirely outside `REPO_ROOT`, so nothing under it is ever a candidate for `git add` regardless of ignore rules. No broad ignore rule (`*`, `tmp/*`, `dist/*`, `viewer/*`) was added or needed.
67
+
68
+ ## 6. Pre-existing repo-root directories (hygiene note, not this batch's output)
69
+
70
+ Repository-root inspection during the hygiene audit found `.my-dev-kit/`, `.my-dev-kit-context/`, `.my-dev-kit-orchestrator/`, `.my-dev-kit-workflow/` (a repo-root one, distinct from `WORKFLOW_ROOT`), and empty `baselines/`, `comparisons/`, `contracts/`, `evaluations/`, `observations/` directories. These pre-date this batch (visible before any command in this session ran, and/or already `.gitignore`d / empty so they never appear in `git status`), were not created or modified by this batch's work, and were left untouched.
71
+
72
+ ## 7. Selected viewer host/port
73
+
74
+ - Host: `127.0.0.1` (never `0.0.0.0`) — `src/viewerServer/port.ts` `VIEWER_HOST`.
75
+ - Fixed default port: **`4319`** — `src/viewerServer/port.ts` `DEFAULT_VIEWER_PORT`.
76
+
77
+ Evidence of no conflict: `tests/fixtures/server.ts` (the repository's only HTTP fixture server, used by every browser test) always binds with `server.listen(0, '127.0.0.1', ...)` — i.e. it never claims a fixed port. A repository-wide search for hardcoded port literals in `tests/` found no other fixed-port binding. `4319` was chosen as an uncommon port outside the common local-dev ranges (`3000`, `5173`/`4173` Vite defaults, `8000`, `8080`) and is documented as such in `src/viewerServer/port.ts`.
78
+
79
+ ## 8. Dependencies added
80
+
81
+ All installed via `npm install --save-dev` with `npm_config_cache` redirected to `WORKFLOW_ROOT/cache/npm`, then pinned to exact versions in `package.json` (matching this repository's existing exact-pin `devDependencies` convention):
82
+
83
+ | Package | Version | Why |
84
+ |---|---|---|
85
+ | `react` | `19.2.8` | Frozen v0.8 UI technology choice. |
86
+ | `react-dom` | `19.2.8` | React DOM renderer for the viewer shell. |
87
+ | `@types/react` | `19.2.18` | TypeScript types for the viewer's strict `viewer/tsconfig.json`. |
88
+ | `@types/react-dom` | `19.2.7` | Same. |
89
+ | `vite` | `8.2.2` | Frozen v0.8 build tool. |
90
+ | `@vitejs/plugin-react` | `6.1.1` | JSX/Fast Refresh transform for Vite. |
91
+ | `vite-plugin-pwa` | `1.3.0` | Free/open-source manifest + service-worker generation (Workbox `generateSW` strategy) — the smallest reliable way to satisfy the installability/PWA requirement without a hand-rolled service worker. |
92
+
93
+ No runtime (`dependencies`) additions: the viewer's React/DOM code is bundled by Vite into static assets served from disk by the existing Node `http` module, so `react`/`vite`/etc. are build-time only. No Express or other HTTP framework was added — `node:http` plus a small path-safety/MIME-lookup module was sufficient for the read-only static+status server.
94
+
95
+ ## 9. Build architecture
96
+
97
+ - `package.json` `build` script: `tsc -p tsconfig.json && vite build --config viewer/vite.config.ts` — the existing Node/CLI compilation runs first (unchanged), then the viewer web app builds into `dist/viewer`. `prebuild` (`scripts/clean.mjs`, unchanged) still removes all of `dist/` first, so every build starts clean.
98
+ - `package.json` `typecheck` script: `tsc -p tsconfig.json --noEmit && tsc -p viewer/tsconfig.json --noEmit` — the viewer's separate, browser/DOM/JSX-targeting `tsconfig.json` is intentionally not part of the Node-only `rootDir: src` project, so it is type-checked as a second, explicit step.
99
+ - `viewer/vite.config.ts` sets `root` explicitly to the `viewer/` directory (via `import.meta.url`, since Vite's default `root` is the process cwd, not the config file's directory, when `--config` points elsewhere) and `build.outDir: '../dist/viewer'` with `emptyOutDir: true`.
100
+ - Verified actual build output layout matches the plan's conceptual example exactly:
101
+
102
+ ```text
103
+ dist/cli.js, dist/index.js, dist/domain/**, dist/application/**, ... (existing Node/library output, unchanged)
104
+ dist/viewerServer/** (new: compiled Node viewer server module)
105
+ dist/viewer/index.html, dist/viewer/assets/*.js, *.css,
106
+ dist/viewer/manifest.webmanifest, dist/viewer/sw.js,
107
+ dist/viewer/workbox-*.js, dist/viewer/icons/*.png (new: built browser PWA)
108
+ ```
109
+
110
+ (The Node-side viewer source lives at `src/viewerServer/`, not `src/viewer/`, specifically so its compiled output — `dist/viewerServer/`— cannot collide with the browser build's `dist/viewer/` output directory.)
111
+ - `package.json` `files` already includes `dist` (unchanged), so both outputs ship inside the existing npm package allowlist — no second package, no separate publish boundary.
112
+ - Vite's dependency-cache directory is redirected via `VITE_CACHE_DIR` (falls back to `../node_modules/.vite-viewer`, itself an ordinary repo build output, if unset) — never a `.vite` directory scattered elsewhere.
113
+
114
+ ## 10. Viewer source layout
115
+
116
+ ```text
117
+ viewer/
118
+ index.html
119
+ vite.config.ts
120
+ tsconfig.json
121
+ public/icons/icon-192.png, icon-512.png (tracked static PWA icon assets)
122
+ src/
123
+ main.tsx
124
+ App.tsx
125
+ vite-env.d.ts
126
+ hooks/useViewerStatus.ts, useInstallPrompt.ts
127
+ components/StatusBanner.tsx, InstallButton.tsx
128
+ styles/index.css
129
+
130
+ src/viewerServer/ (Node-side; browser UI code kept out of this tree entirely)
131
+ port.ts (DEFAULT_VIEWER_PORT, VIEWER_HOST, isValidViewerPort)
132
+ httpServer.ts (createViewerServer: loopback static+status server)
133
+ viewerService.ts (startViewer: the one application-level use case)
134
+ openBrowser.ts (best-effort OS-native browser-open helper, no dependency)
135
+ ```
136
+
137
+ No existing `src/domain`, `src/application`, `src/artifacts`, `src/browser`, `src/request`, or `src/safety` module was moved or restructured. `src/domain/diagnostics.ts` gained two new closed-enum codes (`viewer-root-invalid`, `viewer-port-unavailable`), reusing the existing `Diagnostic`/`DIAGNOSTIC_SEVERITY` machinery rather than inventing a parallel viewer-only diagnostic type.
138
+
139
+ ## 11. Node server / application boundary
140
+
141
+ - `createViewerServer` (`src/viewerServer/httpServer.ts`): builds (does not start) a `node:http` server. Rejects non-GET/HEAD methods (`405`). Serves `GET /api/status` (JSON: `ok`, `viewerProtocolVersion`, `producer`, `root`) with `cache-control: no-store`. Every other path is resolved against `assetsRoot` via `resolveAssetPath`, which decodes the URL, normalizes it, and refuses (returns `undefined`, treated as not-found) any path that would resolve outside `assetsRoot` — verified against four distinct traversal-attempt shapes in `tests/unit/viewerServer.test.ts`. Unknown non-asset routes fall back to `index.html` (SPA fallback); unknown asset-like paths (`.js`, `.png`, etc.) 404. The server never reads, lists, or serves anything under the caller-supplied evidence root.
142
+ - `startViewer` (`src/viewerServer/viewerService.ts`): the one application-level use case. Validates `--root` (`fs.stat`, must exist and be a directory) and `--port` (integer `0`–`65535`) before ever attempting to bind. Binds via `server.listen(port, '127.0.0.1')`; on `EADDRINUSE` (or any other bind error) returns a `viewer-port-unavailable` diagnostic and never retries on a different port. Returns `{ ok: true, url, port, host, root, close }` once actually listening; `close()` wraps `server.close()` in a promise. Never launches Playwright, never runs observation, never creates or modifies Observer artifacts.
143
+ - `defaultViewerAssetsRoot()` resolves `dist/viewer` relative to `viewerService.js`'s own compiled location (`import.meta.url`), not the caller's CWD — verified working end-to-end via the manual and automated built-CLI smoke tests (§9/§15).
144
+
145
+ ## 12. PWA architecture and cache boundary
146
+
147
+ - `vite-plugin-pwa` (`generateSW` mode, `registerType: 'autoUpdate'`) generates `dist/viewer/manifest.webmanifest` (`name: "my-frontend-observer Viewer"`, `short_name: "Observer Viewer"`, `display: "standalone"`, `start_url: "/"`, `scope: "/"`, 192×192 and 512×512 PNG icons) and `dist/viewer/sw.js` + `dist/viewer/workbox-*.js`.
148
+ - Icons (`viewer/public/icons/icon-192.png`, `icon-512.png`) are tracked, hand-generated (via a one-off, non-committed Node/zlib script run from the session scratchpad) solid-circle PNGs — real, valid PNG files, not placeholders — committed as intentional static product assets under `viewer/public/`.
149
+ - **Cache boundary**: the built `sw.js` contains exactly one `registerRoute` call — a `NavigationRoute` SPA fallback with `denylist: [/^\/api\//]` — and its `precacheAndRoute` manifest lists only `index.html`, the built JS/CSS bundle, the two icons, `manifest.webmanifest`, and `registerSW.js`. No `runtimeCaching` entry was configured, so there is no mechanism by which a future evidence/media/API route could be silently served stale; `tests/unit/viewerPwaBuild.test.ts` asserts this directly against the real built `sw.js` (exactly one `registerRoute` call, it is a `NavigationRoute`, the `/api/` denylist regex is present, and no precache entry matches `/api`).
150
+ - Install affordance: `viewer/src/hooks/useInstallPrompt.ts` captures the real `beforeinstallprompt` event; `InstallButton` shows "Install prompt not offered by this browser yet" whenever the event has not fired (unsupported browser, criteria unmet, already installed) rather than a disabled-looking or misleading control — verified against real Chromium in `tests/browser/viewerShell.test.ts` (headless Chromium in this environment does not fire `beforeinstallprompt`, so the "not offered" branch is what real-browser testing here can prove; the "available" branch is implemented per the standard API contract but not independently provable without a PWA-installability-capable browser session in this environment — see §17 risks).
151
+
152
+ ## 13. Public `view` CLI
153
+
154
+ ```text
155
+ my-frontend-observer view --root <evidence-root> [--port <n>] [--no-open] [--help]
156
+ ```
157
+
158
+ - `--root` (required): validated by `startViewer`, not the CLI parser — the parser only enforces presence/no-duplication.
159
+ - `--port` (optional): CLI-parser-validated integer in `[0, 65535]`; defaults (when omitted from the parsed options entirely, letting `startViewer` apply `DEFAULT_VIEWER_PORT`) to `4319`.
160
+ - `--no-open`: suppresses the best-effort browser auto-open.
161
+ - `--help`: prints `VIEW_HELP` and exits `0` without touching `startViewer`.
162
+
163
+ `runViewCommand` (`src/cli.ts`) is thin: parse → one `startViewer` call → print diagnostics-and-exit-1 on failure, or print `Viewer: <url>` / `Root: <root>` / a Ctrl+C hint on success, then (unless `--no-open`) attempt `openInDefaultBrowser`. It contains no artifact parsing, evidence derivation, comparison, contract, reference, fidelity, binding, or browser-observation logic. All eight existing v0.1–v0.7 commands are unchanged; `view` was added as a ninth `if (command === ...)` branch in `runCli`, and `TOP_LEVEL_HELP` now lists it.
164
+
165
+ The command does not block inside `runViewCommand`: it returns as soon as the server is confirmed listening. In real CLI usage the process keeps running afterward only because the server's own open listening socket keeps the Node event loop alive (verified in the built-CLI smoke test, §15) — not because the command explicitly awaits a shutdown signal. No `SIGINT`/`SIGTERM` handler was registered, to avoid accumulating process-wide listeners across repeated test invocations of `runCli(['view', ...])`; Ctrl+C therefore terminates the process via Node's default signal behavior, which is sufficient for this batch (no cleanup state exists yet to flush).
166
+
167
+ ## 14. Browser auto-open decision
168
+
169
+ Implemented (not omitted): `src/viewerServer/openBrowser.ts` spawns the OS-native opener (`cmd /c start` on Windows, `open` on macOS, `xdg-open` on Linux) with no new dependency. It is called only after the server is confirmed listening, its promise rejection is caught in `runViewCommand` and printed as a non-fatal `stderr` note (verified in `tests/unit/cliViewDispatch.test.ts`: a rejected open still yields exit code `0`). `--no-open` is available for deterministic/headless use (used throughout this batch's own smoke testing).
170
+
171
+ ## 15. Files created
172
+
173
+ - `src/viewerServer/port.ts`, `httpServer.ts`, `viewerService.ts`, `openBrowser.ts`
174
+ - `viewer/index.html`, `viewer/vite.config.ts`, `viewer/tsconfig.json`
175
+ - `viewer/src/main.tsx`, `App.tsx`, `vite-env.d.ts`
176
+ - `viewer/src/hooks/useViewerStatus.ts`, `useInstallPrompt.ts`
177
+ - `viewer/src/components/StatusBanner.tsx`, `InstallButton.tsx`
178
+ - `viewer/src/styles/index.css`
179
+ - `viewer/public/icons/icon-192.png`, `icon-512.png`
180
+ - `tests/unit/cliView.test.ts`, `cliViewDispatch.test.ts`, `viewerServer.test.ts`, `viewerPwaBuild.test.ts`
181
+ - `tests/browser/viewerShell.test.ts`
182
+ - `docs/reports/v0.8-viewer-runtime-pwa-batch1.md` (this file)
183
+
184
+ ## 16. Files modified
185
+
186
+ - `src/cli.ts` — `view` command (help text, `parseViewArgs`, `runViewCommand`, dispatch wiring, `TOP_LEVEL_HELP` entry).
187
+ - `src/domain/diagnostics.ts` — two new diagnostic codes.
188
+ - `src/index.ts` — re-exports `startViewer`, `defaultViewerAssetsRoot`, `DEFAULT_VIEWER_PORT`, `VIEWER_HOST`, `isValidViewerPort`, `VIEWER_PROTOCOL_VERSION` for programmatic/library use, matching the existing export pattern for every other application-layer capability.
189
+ - `package.json` / `package-lock.json` — new devDependencies (§8), `build`/`typecheck` scripts extended.
190
+ - `tests/unit/cliFrontendContracts.test.ts` — updated the pre-existing `TST-401` "no future commands" guard (written in the v0.5 era) to include the now-legitimately-added `view` command while still asserting `annotation` (v0.9+) is absent.
191
+ - `docs/COMMANDS.md` — new `## view` section; `## Foundation commands` build/typecheck bullets extended.
192
+ - `docs/ARCHITECTURE.md` — new `## v0.8 Batch 1 (Viewer runtime and PWA foundation) — implemented` section.
193
+ - `docs/DEVELOPMENT.md` — short viewer build/smoke paragraph.
194
+
195
+ `docs/CURRENT_STATE.md` was deliberately **not** modified — this batch is not a release and does not claim v0.8 (or even all of Batch 1's later-batch-dependent acceptance criteria) is "current state."
196
+
197
+ ## 17. Tests added and behavior protected
198
+
199
+ | Test file | Level | Protects |
200
+ |---|---|---|
201
+ | `tests/unit/cliView.test.ts` | unit (CLI dispatch, fast-fail paths) | `--help` lists `view`; `view --help` documents `--root`/`--port`/`--no-open`; missing `--root`; unrecognized flag; malformed/out-of-range `--port`; duplicated `--root`; nonexistent `--root` fails closed with `[viewer-root-invalid]` and no hang; `--root` pointing at a file fails closed; existing `observe`/`compare` help unaffected. |
202
+ | `tests/unit/cliViewDispatch.test.ts` | unit (mocked seam) | Thin delegation: `runViewCommand` calls `startViewer` exactly once with exactly the parsed `{root, port?}`; browser auto-open is attempted unless `--no-open`; a rejected browser-open is non-fatal (exit `0`) and reported to stderr. |
203
+ | `tests/unit/viewerServer.test.ts` | unit/integration (real `node:http`, fixture assets root) | Port validation bounds; loopback-only binding and matching reported URL; shell served at `/`; nested asset served with correct content-type; SPA fallback for unknown non-asset routes; `404` for a missing asset-like path (no silent SPA fallback there); `/api/status` returns the exact supplied root; write methods (`POST`/`PUT`/`DELETE`/`PATCH`) rejected with `405`; four distinct path-traversal encodings never leak the outside-root fixture file; the supplied evidence root is never served as static content; nonexistent/non-directory `--root` fails closed with `viewer-root-invalid` and never binds a socket; an already-bound port fails closed with `viewer-port-unavailable` (never a silent fallback port); `close()` actually releases the port for a subsequent bind. |
204
+ | `tests/unit/viewerPwaBuild.test.ts` | build/integration (real built `dist/viewer`, self-building if absent) | Valid manifest (`name`/`short_name`/`display: standalone`/`start_url`/`scope`); required 192×192 and 512×512 icons declared and their files exist in the built output; `sw.js` exists and `index.html` references the manifest; exactly one `registerRoute` (`NavigationRoute`, `/api/`-denylisted) and no evidence/API entries in the precache manifest. |
205
+ | `tests/browser/viewerShell.test.ts` | browser (real Chromium against the real built PWA) | Product identity/title/shell regions render; the real running server's status is reflected (exact evidence root shown); all three Batch 1 placeholder regions render with honest, non-fabricated text; install affordance shows the honest "not offered" state (never a fake enabled control) when the browser has not fired `beforeinstallprompt`; an intercepted/failed `/api/status` fetch produces the actionable "Local viewer server unavailable" banner rather than fabricating a session (and never shows the real evidence-root string in that state). |
206
+
207
+ Regression: the full existing `tests/unit/` (1036 tests, 54 files) and `tests/browser/` (127 tests, 11 files) suites were re-run unmodified except the one intentionally-updated `TST-401` guard, and all pass — this is direct evidence that `observe`, `compare`, `approve-baseline`, `save-change-contract`, `evaluate-contract`, `import-reference`, `approve-reference`, and `evaluate-reference-fidelity` are unaffected.
208
+
209
+ ## 18. Validation commands and results
210
+
211
+ | Command | Result |
212
+ |---|---|
213
+ | `npm run typecheck` | **PASS** (both `tsconfig.json` and `viewer/tsconfig.json`, zero errors) |
214
+ | `npm run lint` | **PASS** (zero errors/warnings across the whole repo, including new `viewer/**/*.tsx` and `src/viewerServer/**/*.ts`) |
215
+ | `npm test` | **PASS** — 1036/1036 tests, 54/54 files |
216
+ | `npm run test:browser` | **PASS** — 127/127 tests, 11/11 files (real Chromium) |
217
+ | `npm run build` | **PASS** — produces `dist/{cli.js,index.js,domain,application,artifacts,browser,request,safety}` (unchanged Node/library output) plus `dist/viewerServer/**` and `dist/viewer/{index.html,assets,manifest.webmanifest,sw.js,workbox-*.js,icons}` |
218
+ | `npm run check:docs` | **PASS** — "Documentation check passed (17 required files)." |
219
+
220
+ ## 19. Built viewer smoke (§15 of the task)
221
+
222
+ Command (using the WORKFLOW_ROOT-owned smoke root, not a repo-root or ad hoc location):
223
+
224
+ ```powershell
225
+ node dist/cli.js view --root "<WORKFLOW_ROOT>\smoke\evidence-root" --port 4319 --no-open
226
+ ```
227
+
228
+ Result: **PASS**.
229
+
230
+ - Server started; printed exactly `Viewer: http://127.0.0.1:4319`, `Root: <smoke-root>`, `Press Ctrl+C to stop.`
231
+ - `GET /` → `200`, `GET /manifest.webmanifest` → `200`, `GET /sw.js` → `200`, `GET /api/status` → `200` JSON with the exact smoke root and `viewerProtocolVersion: "1.0.0"`.
232
+ - `netstat` confirmed the listener bound to `127.0.0.1:4319` only (never `0.0.0.0`).
233
+ - A directory listing of the smoke `--root` taken immediately before and immediately after the run is byte-for-byte identical (`diff` reported no differences) — no target/evidence files were created, modified, or deleted.
234
+ - The server process was located by its actual PID via `netstat` and terminated with `taskkill /F`; a follow-up `netstat` confirmed the port was released and no server process remained.
235
+ - Smoke logs and before/after listings are retained under `WORKFLOW_ROOT/logs/` (`smoke-server.log`, `smoke-final.log`, `pre-smoke-listing.txt`, `post-smoke-listing.txt`).
236
+
237
+ ## 20. Skipped validation
238
+
239
+ None. Every command listed in task §14 was run to completion with a captured result (§18), and the built-viewer smoke (§15) was run against the actual built CLI, not only the in-process test harness.
240
+
241
+ ## 21. Generated/untracked paths — final disposition
242
+
243
+ | Path | Disposition |
244
+ |---|---|
245
+ | `WORKFLOW_ROOT/cache/npm` (~212 MB) | Retained (ordinary npm cache; safe to delete, kept for reuse by later batches) |
246
+ | `WORKFLOW_ROOT/vite-cache` | Retained, empty (Vite build mode did not populate it) |
247
+ | `WORKFLOW_ROOT/logs/*` | Retained (smoke evidence) |
248
+ | `WORKFLOW_ROOT/smoke/evidence-root` | Retained, empty (reusable smoke fixture) |
249
+ | `WORKFLOW_ROOT/{tmp,fixtures,candidate,pack,my-dev-kit-index}` | Retained, empty/unused this batch |
250
+ | `node_modules/.vite-viewer` | Not created this batch (Vite did not need it in build mode); would be an ordinary, already-allowed repo build output if it ever appears |
251
+ | Repo-root `dist/` | Ordinary build output (already `.gitignore`d); rebuilt cleanly by `scripts/clean.mjs` every `npm run build` |
252
+ | Pre-existing repo-root `.my-dev-kit*`/`baselines`/`comparisons`/`contracts`/`evaluations`/`observations` | Pre-existing, untouched (see §6) |
253
+
254
+ No tarball, candidate install, or my-dev-kit index was produced this batch (§3), so `WORKFLOW_ROOT/pack`, `/candidate`, and `/my-dev-kit-index` remain empty by design, not by oversight.
255
+
256
+ ## 22. Evidence v0.1–v0.7 behavior preserved
257
+
258
+ - All 1036 unit tests and 127 browser tests covering `observe`, `compare`, `approve-baseline`, `save-change-contract`, `evaluate-contract`, `import-reference`, `approve-reference`, and `evaluate-reference-fidelity` pass unmodified.
259
+ - `runCli`'s existing eight `if (command === ...)` branches are untouched; `view` was appended as a ninth branch.
260
+ - The only test content change outside the new viewer test files is the single, intentionally-updated `TST-401` help-surface guard (§16), which now asserts `view` is present (a legitimate v0.8 Batch 1 addition) while still asserting `annotation` (v0.9+) remains absent.
261
+
262
+ ## 23. Evidence Batch 2+ functionality was not implemented
263
+
264
+ - No evidence indexing, artifact reader/adapter, evidence API route, screenshot/reference-image serving, SVG overlay, comparison/contract/reference/fidelity/binding/correlation/bounded-context UI, or annotation code exists anywhere in this batch's diff.
265
+ - The only HTTP route beyond static-asset serving is `GET /api/status`, which returns only `{ok, viewerProtocolVersion, producer, root}` — no Observer artifact content.
266
+ - The PWA service worker precaches only the built shell and declares no `runtimeCaching`, so no mechanism exists yet (or is needed yet) to guard against stale evidence caching — verified directly (§12).
267
+ - The React shell (`App.tsx`) renders exactly three honest placeholder strings for navigation/workspace/details; no fixture, mock, or fabricated observation/reference data is rendered anywhere in `viewer/src/`.
268
+
269
+ ## 24. Remaining uncovered risks
270
+
271
+ - **Install-prompt "available" branch is implemented per spec but not independently browser-tested in this environment.** Headless Chromium under Playwright in this sandbox does not fire `beforeinstallprompt` (real installability criteria — HTTPS-or-localhost origin, engagement heuristics, manifest validity — are largely satisfied here since the origin is `http://127.0.0.1`, treated as a secure context, but headless automation does not reliably trigger the event). The "not offered" honest-fallback branch is proven; the "available" branch's logic was reviewed but not exercised against a live `beforeinstallprompt` event. A later batch (or manual verification in a real, non-headless browser) should confirm the install flow end-to-end.
272
+ - **`vite-plugin-pwa`'s Workbox-generated service worker was validated by source inspection/regex, not by loading it in a real Service Worker execution context** (no service-worker-lifecycle test — e.g. actually registering it, waiting for `activate`, and issuing an intercepted fetch — was written). The cache-boundary guarantee (§12) rests on the generated source containing exactly one denylisted `NavigationRoute` and no other `registerRoute` call, which is a strong but not runtime-executed proof.
273
+ - **No automated test proves the CLI process actually stays alive via the open server socket** after `runViewCommand` resolves (only manually verified in the smoke test, §19, and reasoned about in §13); a future batch could add a dedicated child-process test if this behavior becomes load-bearing for later batches' tooling.
274
+ - **The my-dev-kit bounded-retrieval step (task §7) was skipped** (§3) in favor of direct manual inspection; if a later batch relies on an actual `my-dev-kit-index` artifact existing from this batch, it does not.
275
+
276
+ ## 25. Final verdict
277
+
278
+ Batch 1 ("Viewer runtime and PWA foundation") is implemented and independently validated: a built/packed-equivalent candidate starts `my-frontend-observer view --root <fixture>` and serves the same working React shell (with installable-PWA manifest/service-worker output) to a normal browser and a PWA-capable (real Chromium) browser from the fixed loopback origin `http://127.0.0.1:4319`, while every pre-existing CLI/library command and its test coverage remains unchanged and passing. No v0.8 Batch 2+ scope, no v0.9 annotation scope, and no release/publication action was taken. Package version remains `0.7.0`.