@dailephd/my-frontend-observer 0.9.0 → 0.10.0

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 (110) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +17 -7
  3. package/dist/application/visualChangeAgentHandoffService.d.ts +28 -0
  4. package/dist/application/visualChangeAgentHandoffService.js +111 -0
  5. package/dist/application/visualChangeAgentHandoffService.js.map +1 -0
  6. package/dist/application/visualChangeProjectWorkflowService.d.ts +95 -0
  7. package/dist/application/visualChangeProjectWorkflowService.js +376 -0
  8. package/dist/application/visualChangeProjectWorkflowService.js.map +1 -0
  9. package/dist/application/visualChangeReviewService.d.ts +50 -0
  10. package/dist/application/visualChangeReviewService.js +69 -0
  11. package/dist/application/visualChangeReviewService.js.map +1 -0
  12. package/dist/application/visualChangeWorkflowPersistenceService.d.ts +26 -0
  13. package/dist/application/visualChangeWorkflowPersistenceService.js +15 -0
  14. package/dist/application/visualChangeWorkflowPersistenceService.js.map +1 -0
  15. package/dist/artifacts/visualChangeWorkflowArtifactReader.d.ts +9 -0
  16. package/dist/artifacts/visualChangeWorkflowArtifactReader.js +47 -0
  17. package/dist/artifacts/visualChangeWorkflowArtifactReader.js.map +1 -0
  18. package/dist/artifacts/visualChangeWorkflowArtifactWriter.d.ts +20 -0
  19. package/dist/artifacts/visualChangeWorkflowArtifactWriter.js +41 -0
  20. package/dist/artifacts/visualChangeWorkflowArtifactWriter.js.map +1 -0
  21. package/dist/cli.js +510 -508
  22. package/dist/cli.js.map +1 -1
  23. package/dist/domain/visualChangeAgentHandoff.d.ts +82 -0
  24. package/dist/domain/visualChangeAgentHandoff.js +80 -0
  25. package/dist/domain/visualChangeAgentHandoff.js.map +1 -0
  26. package/dist/domain/visualChangeAgentHandoffSerialization.d.ts +2 -0
  27. package/dist/domain/visualChangeAgentHandoffSerialization.js +11 -0
  28. package/dist/domain/visualChangeAgentHandoffSerialization.js.map +1 -0
  29. package/dist/domain/visualChangeCycle.d.ts +8 -0
  30. package/dist/domain/visualChangeCycle.js +7 -0
  31. package/dist/domain/visualChangeCycle.js.map +1 -0
  32. package/dist/domain/visualChangeWorkflow.d.ts +125 -0
  33. package/dist/domain/visualChangeWorkflow.js +109 -0
  34. package/dist/domain/visualChangeWorkflow.js.map +1 -0
  35. package/dist/domain/visualChangeWorkflowIdentity.d.ts +5 -0
  36. package/dist/domain/visualChangeWorkflowIdentity.js +24 -0
  37. package/dist/domain/visualChangeWorkflowIdentity.js.map +1 -0
  38. package/dist/index.d.ts +21 -1
  39. package/dist/index.js +12 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/projectWorkflow/projectPaths.d.ts +3 -0
  42. package/dist/projectWorkflow/projectPaths.js +7 -0
  43. package/dist/projectWorkflow/projectPaths.js.map +1 -1
  44. package/dist/viewer/assets/index-DglJ6f28.css +1 -0
  45. package/dist/viewer/assets/index-DsODREY5.js +9 -0
  46. package/dist/viewer/index.html +2 -2
  47. package/dist/viewer/sw.js +1 -1
  48. package/dist/viewerServer/evidence/classify.d.ts +3 -1
  49. package/dist/viewerServer/evidence/classify.js +10 -0
  50. package/dist/viewerServer/evidence/classify.js.map +1 -1
  51. package/dist/viewerServer/evidence/handles.js +1 -0
  52. package/dist/viewerServer/evidence/handles.js.map +1 -1
  53. package/dist/viewerServer/evidence/projection.d.ts +5 -0
  54. package/dist/viewerServer/evidence/projection.js +19 -0
  55. package/dist/viewerServer/evidence/projection.js.map +1 -1
  56. package/dist/viewerServer/evidence/visualChangeWorkflowView.d.ts +31 -0
  57. package/dist/viewerServer/evidence/visualChangeWorkflowView.js +36 -0
  58. package/dist/viewerServer/evidence/visualChangeWorkflowView.js.map +1 -0
  59. package/dist/viewerServer/httpServer.js +323 -1
  60. package/dist/viewerServer/httpServer.js.map +1 -1
  61. package/dist/viewerServer/referenceApproval.d.ts +22 -0
  62. package/dist/viewerServer/referenceApproval.js +42 -0
  63. package/dist/viewerServer/referenceApproval.js.map +1 -0
  64. package/dist/viewerServer/referenceVisualChangeAuthoring.d.ts +28 -0
  65. package/dist/viewerServer/referenceVisualChangeAuthoring.js +134 -0
  66. package/dist/viewerServer/referenceVisualChangeAuthoring.js.map +1 -0
  67. package/dist/viewerServer/runtimeVisualChangeAuthoring.d.ts +33 -0
  68. package/dist/viewerServer/runtimeVisualChangeAuthoring.js +81 -0
  69. package/dist/viewerServer/runtimeVisualChangeAuthoring.js.map +1 -0
  70. package/dist/viewerServer/visualChangeAuthoring.d.ts +46 -0
  71. package/dist/viewerServer/visualChangeAuthoring.js +63 -0
  72. package/dist/viewerServer/visualChangeAuthoring.js.map +1 -0
  73. package/dist/viewerServer/visualChangeHandoff.d.ts +23 -0
  74. package/dist/viewerServer/visualChangeHandoff.js +31 -0
  75. package/dist/viewerServer/visualChangeHandoff.js.map +1 -0
  76. package/dist/viewerServer/visualChangeReview.d.ts +30 -0
  77. package/dist/viewerServer/visualChangeReview.js +46 -0
  78. package/dist/viewerServer/visualChangeReview.js.map +1 -0
  79. package/docs/ARCHITECTURE.md +17 -5
  80. package/docs/CI_CD.md +33 -1
  81. package/docs/COMMANDS.md +19 -5
  82. package/docs/CONTRACTS.md +38 -4
  83. package/docs/CURRENT_STATE.md +92 -11
  84. package/docs/DEVELOPMENT.md +32 -2
  85. package/docs/PROJECT_MILESTONES.md +28 -0
  86. package/docs/PROJECT_OVERVIEW.md +36 -14
  87. package/docs/QUICKSTART.md +7 -3
  88. package/docs/RELEASE.md +15 -11
  89. package/docs/ROADMAP.md +55 -2
  90. package/docs/SECURITY.md +25 -3
  91. package/docs/WORKFLOWS.md +29 -3
  92. package/docs/plans/v0.10-implementation-plan.md +1509 -0
  93. package/docs/plans/v0.9.1-implementation-plan.md +468 -0
  94. package/docs/reports/v0.10-batch1-visual-change-workflow-foundation.md +102 -0
  95. package/docs/reports/v0.10-batch2-project-composition-check-recording.md +103 -0
  96. package/docs/reports/v0.10-batch3-viewer-visual-change-workspace.md +93 -0
  97. package/docs/reports/v0.10-batch4-actual-frontend-entry.md +59 -0
  98. package/docs/reports/v0.10-batch5-reference-driven-entry.md +238 -0
  99. package/docs/reports/v0.10-batch6-coding-agent-handoff.md +85 -0
  100. package/docs/reports/v0.10-batch7-correction-review-acceptance.md +145 -0
  101. package/docs/reports/v0.10-batch8-integrated-acceptance.md +109 -0
  102. package/docs/reports/v0.10-implementation-completeness-documentation-reconciliation.md +344 -0
  103. package/docs/reports/v0.10-pre-release-readiness.md +120 -0
  104. package/docs/reports/v0.10-release-preparation.md +70 -0
  105. package/docs/reports/v0.9.1-batch1-pwa-hard-gate-isolation.md +359 -0
  106. package/docs/reports/v0.9.1-batch2-hard-gate-validation-integration.md +262 -0
  107. package/docs/reports/v0.9.1-pre-release-readiness.md +206 -0
  108. package/package.json +3 -2
  109. package/dist/viewer/assets/index-BN41MI7m.css +0 -1
  110. package/dist/viewer/assets/index-CkKXnlrI.js +0 -9
@@ -0,0 +1,120 @@
1
+ # v0.10 Pre-release Readiness
2
+
3
+ ## Provisional verdict
4
+
5
+ RUN_A passed on the corrected validation candidate. This report is intentionally
6
+ provisional: it is shipped by the package `files` allowlist, so committing it
7
+ changes candidate bytes. A complete RUN_B against the report-containing HEAD is
8
+ required before the final readiness verdict may be declared.
9
+
10
+ ## Repository and release state
11
+
12
+ - Repository: `my-frontend-observer`
13
+ - Validation branch: `validation/v0.10-pre-release`
14
+ - Validation entry HEAD: `ce8bf7c7b0060b35cfb2fdd850c7a80ff0b340ad`
15
+ - RUN_A HEAD: `b1c273d853dd9f00394b355522d06c344252e928`
16
+ - Package: `@dailephd/my-frontend-observer@0.9.1`
17
+ - Latest npm-published version: `0.9.1`
18
+ - v0.10 release/tag/publication: not performed
19
+ - Implementation-completeness reconciliation: `ce8bf7c7b0060b35cfb2fdd850c7a80ff0b340ad`
20
+
21
+ ## Local validation before RUN_A
22
+
23
+ The canonical local chain passed on the corrected HEAD: `npm ci`, Chromium
24
+ installation, typecheck, lint, unit tests (99 files / 1,524 tests), browser
25
+ tests (32 files / 301 tests), security tests, isolated PWA hard gate, build,
26
+ documentation check, `git diff --check`, and `npm pack --dry-run`.
27
+
28
+ The local security result was PASS. The isolated PWA hard gate passed independently
29
+ and also through the security command, preserving active-worker, controlled-page,
30
+ shell-precache, zero-API-cache, server-down, offline-shell, unavailable-evidence,
31
+ and stale-identity assertions.
32
+
33
+ ## RUN_A
34
+
35
+ - Workflow: `Pre-release readiness`
36
+ - Run ID: `35785226212`
37
+ - URL: https://github.com/dailephd/my-frontend-observer/actions/runs/35785226212
38
+ - Event: push
39
+ - Branch: `validation/v0.10-pre-release`
40
+ - Exact HEAD: `b1c273d853dd9f00394b355522d06c344252e928`
41
+ - Conclusion: PASS
42
+
43
+ All seven required jobs passed:
44
+
45
+ | Job | Result |
46
+ | --- | --- |
47
+ | Package exact candidate (Linux, Node 24) | PASS |
48
+ | ubuntu-latest exact-candidate smoke | PASS |
49
+ | windows-latest exact-candidate smoke | PASS |
50
+ | macos-latest exact-candidate smoke | PASS |
51
+ | ubuntu-latest Observer tutorial readiness | PASS |
52
+ | windows-latest Observer tutorial readiness | PASS |
53
+ | macos-latest Observer tutorial readiness | PASS |
54
+
55
+ ## Exact candidate and matrix proof
56
+
57
+ - Candidate filename: `dailephd-my-frontend-observer-0.9.1.tgz`
58
+ - Candidate package version: `0.9.1`
59
+ - Candidate SHA-256: `bda75f4f5ca441b3f58f9fe6ffa535f41904608a783961db8bcef4171f353ef5`
60
+ - Independently downloaded/recomputed SHA-256: identical
61
+ - Windows, Linux, and macOS each verified the candidate SHA and used the same tarball.
62
+ - Each OS uploaded `smoke-summary.json`, `viewer-smoke-summary.json`,
63
+ `v09-annotation-smoke-summary.json`, and `v010-workflow-smoke-summary.json`.
64
+
65
+ The four packed smokes passed on every OS: observation, Viewer/project workflow,
66
+ v0.9 annotation, and v0.10 visual workflow. The v0.10 summaries prove installed
67
+ package workflow creation/activation, two-attempt immutable correction history,
68
+ non-PASS rejection and PASS acceptance, reference bindings, Viewer discovery,
69
+ standalone read-only behavior, and absence of authoring token, note text, absolute
70
+ project/temp paths, and orchestrator paths.
71
+
72
+ The three tutorial-readiness jobs also passed, preserving the v0.9 tutorial
73
+ regression on Windows, Linux, and macOS.
74
+
75
+ ## Security and package audit
76
+
77
+ RUN_A exercised the existing formal security suite and PWA hard gate in the
78
+ candidate job. The added v0.10 attack surface remains bounded by loopback,
79
+ Host/Origin/token, JSON/body/compression, path-containment, immutable-parent,
80
+ handoff-privacy, standalone-read-only, and orchestrator-non-authority controls.
81
+
82
+ The candidate retained package version `0.9.1`, Viewer protocol `1.3.0`, all
83
+ governed schema versions, and no dependency/schema/version changes. Package
84
+ contents were audited locally for compiled v0.10 exports and Viewer assets, with
85
+ tests, generated state, browser profiles, summaries, and nested tarballs excluded.
86
+
87
+ ## Failed-run history and correction
88
+
89
+ Earlier same-HEAD runs are retained as historical diagnosis:
90
+
91
+ 1. Run `35780908327` exposed hosted browser flakes in an existing screenshot
92
+ comparison and materialization toast assertion; it was cancelled after the
93
+ candidate failed and its matrix was not promoted.
94
+ 2. A same-HEAD rerun reproduced the materialization toast synchronization issue.
95
+ The test was corrected to rely on the already asserted HTTP 201 response and
96
+ exact canonical artifact reselection.
97
+ 3. Run `35783718376` then exposed a separate Chromium adapter flake and a
98
+ macOS-only packed activation failure.
99
+ 4. The packed smoke was corrected to canonicalize its disposable temp root before
100
+ deriving relative artifact paths. Local validation passed, and RUN_A above
101
+ passed all seven jobs on the corrected candidate.
102
+
103
+ No product semantics, governed schema, dependency, package version, or readiness
104
+ workflow architecture changed.
105
+
106
+ ## RUN_B requirement
107
+
108
+ This tracked report changes the npm candidate bytes because `docs/` is included
109
+ in the package. Commit it with:
110
+
111
+ `docs: record v0.10 pre-release readiness`
112
+
113
+ Push the resulting `REPORT_HEAD` to this validation branch and require a new
114
+ complete `Pre-release readiness` run whose exact HEAD equals `REPORT_HEAD`.
115
+ RUN_B must independently pass the candidate job, all three exact-candidate smoke
116
+ lanes, and all three tutorial-readiness lanes. Only then may the overall verdict
117
+ become `PASS_V0_10_PRERELEASE_READY_FOR_RELEASE_PREP`.
118
+
119
+ Release operations performed: none. Version bump, tag, publication, merge, and
120
+ GitHub Release remain unauthorized and incomplete.
@@ -0,0 +1,70 @@
1
+ # v0.10.0 Release Preparation
2
+
3
+ ## 1. Verdict and identity
4
+
5
+ Verdict: `PASS_READY_TO_PUBLISH_AFTER_USER_APPROVAL`
6
+
7
+ Repository: `dailephd/my-frontend-observer` (`C:\Users\daile\Projects\my-frontend-observer`)
8
+ Workflow: `RELEASE_PREPARATION`
9
+ Source validation branch: `validation/v0.10-pre-release`
10
+ Starting RUN_B-validated SHA: `4ae5b9e0328364dd27e0761b8f243093bdcb3f45`
11
+ Inherited RUN_B: `35789033295`, workflow `Pre-release readiness`, conclusion success. Its seven required jobs all succeeded. Readiness was inherited; RUN_B was not rerun.
12
+ Release branch: `release/v0.10.0`, created directly from the exact validated SHA without merge or rebase.
13
+ Package: `@dailephd/my-frontend-observer`
14
+ Old package version: `0.9.1`
15
+ Target package version: `0.10.0`
16
+
17
+ ## 2. Release-state preflight
18
+
19
+ - npm current version before preparation: `0.9.1`.
20
+ - npm `0.10.0`: absent (npm returned E404 for the exact version).
21
+ - Local `v0.10.0` tag: absent.
22
+ - Remote `origin` `v0.10.0` tag: absent.
23
+ - GitHub Release `v0.10.0`: absent.
24
+ - Local and remote `release/v0.10.0` branches: absent before creation.
25
+ - GitHub identity: `dailephd/my-frontend-observer`, default branch `master`; `gh auth status` succeeded.
26
+ - `package.json` identity: `@dailephd/my-frontend-observer`.
27
+
28
+ ## 3. Version and documentation state
29
+
30
+ `package.json` version, `package-lock.json` version, and `package-lock.json` `packages[""]` version are all `0.10.0`. The version was changed with `npm version 0.10.0 --no-git-tag-version`; no version commit or tag was created by npm.
31
+
32
+ The changelog keeps `## [Unreleased]` for future work and adds `## 0.10.0 - 2026-09-23` describing the shipped Visual Change workflow, actual/reference entry, artifact/handoff versions, explicit activation, bounded coding-agent handoff, immutable correction history, PASS-only acceptance, separate governance, installed-package/Viewer support, and security boundaries.
33
+
34
+ README identifies v0.10.0 as current and accurately describes the normal user-facing Visual Change workflow. `docs/CURRENT_STATE.md` records the current release, complete shipped workflow, formal Windows/Linux/macOS readiness, package version, independently versioned schemas, and Viewer protocol. `docs/ROADMAP.md` changes only the high-level v0.10 release status and preserves future-version planning. `docs/PROJECT_OVERVIEW.md`, `docs/RELEASE.md`, `docs/CI_CD.md`, `docs/SECURITY.md`, and `docs/QUICKSTART.md` state the final v0.10 release and readiness facts while retaining relevant security limitations. Commands, workflows, contracts, and architecture semantics were not changed.
35
+
36
+ `docs/reports/v0.10-pre-release-readiness.md` was preserved unchanged. The existing `scripts/check-docs.mjs` was narrowly updated to enforce final release consistency: a dated 0.10.0 changelog entry plus an Unreleased section, current package/release/readiness/protocol facts, roadmap release status, and canonical contract markers. It was not disabled and no second checker was added. `npm run check:docs` passed after the change.
37
+
38
+ ## 4. Source, dependencies, schemas, and package
39
+
40
+ Production source audit: `git diff -- src viewer/src` is empty; no executable product changes were made. Dependency audit: `dependencies` and `devDependencies` are identical to the starting commit (`DEPENDENCY_CHANGED = false`).
41
+
42
+ Schema/protocol inventory is unchanged: observation `1.2.0`; comparison `1.0.0`; frontend contract `1.0.0`; evaluation `1.0.0`; bounded-agent-context `1.0.0`; external-reference `1.0.0`; visual annotation `1.0.0`; visual-change-workflow `1.0.0`; handoff `1.0.0`; Viewer protocol `1.3.0`.
43
+
44
+ Package hygiene: `.gitignore` excludes `node_modules/`, `dist/`, and the `.my-dev-kit-*` generated workflow/context roots. No `.npmignore` is present. `package.json#files` allowlists `dist`, README, CHANGELOG, docs, and LICENSE. `npm pack --dry-run` reports package `@dailephd/my-frontend-observer@0.10.0`, 437 files in the final dry run (436 before adding this report), and includes compiled visual-change workflow owners, Viewer/PWA assets, README, changelog, and release docs. The JSON dry-run content audit found no tests, private workflow/context roots, caches, browser profiles, smoke summaries, nested tarballs, or `node_modules`. No tarball was created by the dry run.
45
+
46
+ ## 5. Local validation
47
+
48
+ All requested commands passed after release-preparation edits:
49
+
50
+ - `npm run typecheck`: PASS.
51
+ - `npm run lint`: PASS.
52
+ - `npm test`: PASS, 99 files and 1,524 tests.
53
+ - `npm run test:browser`: PASS, 32 files and 301 tests.
54
+ - `npm run test:security`: PASS, including 16 security unit files (168 tests), 3 browser files (77 tests), and the PWA hard gate.
55
+ - `npm run test:pwa-hard-gate`: PASS, one hard-gate test; six unrelated tests filtered by the task's script.
56
+ - `npm run build`: PASS.
57
+ - `npm run check:docs`: PASS (17 required documentation files).
58
+ - `git diff --check`: PASS.
59
+ - `npm pack --dry-run`: PASS, package identity/version and content audit as above.
60
+ - `node dist/cli.js --version`: `0.10.0`.
61
+ - `node dist/cli.js --help`: PASS; expected command surface displayed.
62
+
63
+ ## 6. Git, publication boundary, and handoff
64
+
65
+ Release-preparation commit message: `chore: prepare v0.10.0 release`.
66
+ Push target: `origin release/v0.10.0`. The commit is pushed and the post-push check confirms the remote branch matches the local release-preparation commit. The tracked worktree is clean after push. The final commit SHA and branch tip are recorded in the handoff summary.
67
+
68
+ No product-semantic changes, dependency changes, tag, GitHub Release, npm publication, dist-tag change, PR, or merge were made. The historical pre-release readiness report was not edited. Generated build output remains ignored; no local/private generated files are staged or packaged.
69
+
70
+ Exact next action: await explicit user approval. After approval, run the standardized GitHub release + final npm publication workflow from `release/v0.10.0`. The publication workflow must create/validate the release PR, merge it, validate master, create/push `v0.10.0`, create/verify the GitHub Release, verify release-channel parity, and run `npm publish --access public` as the final state-changing command.
@@ -0,0 +1,359 @@
1
+ # v0.9.1 Batch 1 report: PWA hard-gate isolation
2
+
3
+ ## 1. VERDICT
4
+
5
+ `PASS_V0_9_1_BATCH1_PWA_HARD_GATE_ISOLATED`
6
+
7
+ The PWA server-down hard gate is now a self-contained experiment. It passes when
8
+ selected alone and when the whole PWA file runs. No production file changed. No
9
+ product defect was found.
10
+
11
+ This report covers Batch 1 only. v0.9.1 is not released. Full repository
12
+ regression and cross-platform release readiness are not complete. Batch 2 owns
13
+ them.
14
+
15
+ ## 2. Repository identity
16
+
17
+ 1. Repository: `C:\Users\daile\Projects\my-frontend-observer`
18
+ 2. Package: `@dailephd/my-frontend-observer`, version `0.9.0` (unchanged)
19
+ 3. Branch: `feature/v0.9.1-pwa-hard-gate-isolation`, created from `master`
20
+ 4. Starting HEAD: `985d0ce47a070fd9f84eae478f4098df00d57bd8` (merge of PR #15)
21
+ 5. Released baseline: v0.9.0 at `eacd543f64d45d8b721e766173af87e6a878152c`
22
+ 6. Runtime: Node `v24.20.0`, Playwright `1.62.1`, Vitest `4.1.11`, Windows 11
23
+
24
+ ## 3. Planning authority
25
+
26
+ 1. PR #15 (`docs: plan v0.9.1 PWA hard-gate isolation`) was merged into `master`
27
+ at 2026-09-21T15:11:04Z as `985d0ce`.
28
+ 2. `docs/plans/v0.9.1-implementation-plan.md` is present on `master` and was
29
+ read in full. It is the frozen authority for this batch.
30
+ 3. The planning doc changes in `ROADMAP.md`, `CURRENT_STATE.md`,
31
+ `DEVELOPMENT.md`, `CI_CD.md`, and `PROJECT_MILESTONES.md`, plus
32
+ `DOCUMENTATION_PRESERVATION_POLICY.md`, were read.
33
+ 4. A first attempt at this prompt stopped with
34
+ `BLOCKED_V0_9_1_PLANNING_NOT_MERGED` because PR #15 was still open. No changes
35
+ were made in that attempt.
36
+
37
+ ## 4. Original isolated failure reproduction
38
+
39
+ After `npm run build` (exit 0), this command ran against the unchanged test:
40
+
41
+ `npx vitest run --config vitest.browser.config.ts tests/browser/pwaHardening.test.ts -t "HARD GATE"`
42
+
43
+ 1. Exit code: 1
44
+ 2. Result: 1 failed, 6 skipped
45
+ 3. Failure step: `await page.reload()` right after `server.close()`
46
+ 4. Failure message: `page.reload: net::ERR_CONNECTION_REFUSED`
47
+ 5. Shell reload failed: yes. The page was not served by a service worker.
48
+ 6. Evidence-unavailable assertion reached: no. The test failed before it.
49
+
50
+ A diagnostic outside the repository (scratchpad only, not committed) repeated
51
+ the original steps and recorded service-worker and cache state at the moment the
52
+ original test shut the server down. The result was the same with the historical
53
+ fixed profile and with a fresh temporary profile:
54
+
55
+ 1. Registration present: true
56
+ 2. Worker state: `installing` = true, `active` = null
57
+ 3. `navigator.serviceWorker.controller`: null
58
+ 4. Cache Storage: one `workbox-precache-v2-<origin>/` cache with 5 entries, so
59
+ the precache was still being filled
60
+ 5. Reload after close: `net::ERR_CONNECTION_REFUSED`
61
+
62
+ So service-worker control and a complete shell cache were both absent at the
63
+ shutdown boundary.
64
+
65
+ ## 5. Root cause
66
+
67
+ Two faults combined.
68
+
69
+ 1. The wait condition was wrong. The old test waited for
70
+ `(await navigator.serviceWorker.getRegistration())?.active !== undefined`.
71
+ `registration.active` is `null`, not `undefined`, while a worker is still
72
+ installing. The condition was therefore true as soon as any registration
73
+ existed. It never proved activation, control, or a filled precache.
74
+ 2. The test relied on shared state. In normal file order, earlier tests in the
75
+ same describe had already used the same server origin and the same
76
+ persistent context. By the time the hard gate ran, the worker was active and
77
+ controlling that origin, so the weak wait did not matter.
78
+
79
+ When selected alone, the viewer server starts on a new random port (`port: 0`).
80
+ That is a new origin with no worker. The fixed profile's older registrations
81
+ belong to other origins and do not help. The page was still uncontrolled when
82
+ the server closed, so the reload went to the network and failed.
83
+
84
+ Classification stays `TEST_DEFECT`. `PRODUCT_CHANGE_REQUIRED = false`.
85
+
86
+ ## 6. Previous shared-state topology
87
+
88
+ In the first describe block, `beforeAll` created and shared:
89
+
90
+ 1. one evidence root from `mkdtemp`
91
+ 2. one viewer server
92
+ 3. one persistent Chromium context launched from the fixed profile
93
+ `.my-dev-kit-workflow/v0.8/batch-08/pwa-profile`
94
+
95
+ The hard gate was the fourth `it()` in that block. The three earlier tests
96
+ registered the worker, fetched the manifest, and warmed the cache on the same
97
+ origin. The fixed profile also kept service-worker and cache data between local
98
+ runs. That block's `afterAll` never closed its server. Only the hard gate's
99
+ mid-test `server.close()` shut it down.
100
+
101
+ ## 7. New hard-gate resource ownership
102
+
103
+ The hard gate now lives in its own describe block:
104
+ `PWA server-down safety - self-contained hard-gate experiment`. It has no
105
+ `beforeAll` and uses no describe-level variables. Inside the test it creates and
106
+ owns:
107
+
108
+ 1. an evidence root:
109
+ `mkdtemp(path.join(tmpdir(), 'my-frontend-observer-pwa-hard-gate-evidence-'))`
110
+ 2. the fixture, through the existing helpers `writeManyRegionsOneTargetFixture`
111
+ and `persistAnnotationUnder`. These are now wrapped by a local
112
+ `writePwaFixture` helper that both describe blocks use. The real annotation
113
+ is kept so the annotation view and media routes are part of the cache
114
+ boundary.
115
+ 3. a dedicated viewer server from `startViewer` on port 0
116
+ 4. a persistent profile:
117
+ `mkdtemp(path.join(tmpdir(), 'my-frontend-observer-pwa-hard-gate-profile-'))`
118
+ 5. a `BrowserContext` from
119
+ `chromium.launchPersistentContext(profileRoot, { headless: true })`
120
+ 6. a fresh page
121
+
122
+ The test name is unchanged, so `-t "HARD GATE"` still selects it.
123
+
124
+ ## 8. Fresh-profile design
125
+
126
+ 1. The hard gate uses its own temporary profile and context. It never shares
127
+ one with any other test.
128
+ 2. The first describe block, which keeps the focused registration, manifest,
129
+ and `/api/` cache tests, now creates its own temporary profile
130
+ (`my-frontend-observer-pwa-profile-*`) in `beforeAll` and deletes it in
131
+ `afterAll`.
132
+ 3. That `afterAll` now also closes its viewer server, since the hard gate no
133
+ longer closes it.
134
+ 4. The constant for `.my-dev-kit-workflow/v0.8/batch-08/pwa-profile` was
135
+ removed. No test uses that path any more. The historical directory itself was
136
+ left on disk untouched.
137
+ 5. Profile and evidence removal uses
138
+ `rm(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })`,
139
+ because Chromium can briefly hold profile files after close on Windows.
140
+
141
+ ## 9. Service-worker registration proof
142
+
143
+ After the first navigation, the test evaluates `navigator.serviceWorker.ready`
144
+ in the real page and requires:
145
+
146
+ 1. `registration.active !== null` is true
147
+ 2. `registration.scope` equals `<viewer origin>/`
148
+
149
+ `ready` only resolves when a worker is active, so there is no fixed sleep.
150
+
151
+ Observed: `serviceWorkerActive = true`.
152
+
153
+ ## 10. Current-client control proof
154
+
155
+ Control is checked on its own, apart from activation:
156
+
157
+ 1. The test reads `navigator.serviceWorker.controller !== null`.
158
+ 2. If it is null, the page reloads while the server is still live.
159
+ 3. The test then waits (bounded at 10 seconds) for
160
+ `navigator.serviceWorker.controller !== null`.
161
+ 4. It requires `controller.scriptURL` to equal `<viewer origin>/sw.js`.
162
+
163
+ Observed: `controlledOnFirstLoad = true` and `clientControlled = true`. The
164
+ generated worker calls `self.skipWaiting()` and `clientsClaim()`, so once
165
+ activation finishes it takes control of the first page. The reload branch
166
+ stays in place for any platform where control arrives later.
167
+
168
+ ## 11. App-shell cache proof
169
+
170
+ The test reads every Cache Storage entry from the page before shutdown. It
171
+ requires:
172
+
173
+ 1. at least one cache whose name starts with `workbox-precache`
174
+ 2. cached pathnames that include `/index.html`, `/registerSW.js`, and
175
+ `/manifest.webmanifest`
176
+ 3. a cached `/index.html` (matched with `ignoreSearch`, because Workbox adds
177
+ `__WB_REVISION__`) whose body contains `<div id="root"></div>`
178
+ 4. that the module entry script named inside that cached `index.html` matches
179
+ `/assets/*.js` and is itself in the cache
180
+
181
+ Why this is stable: the generated `sw.js` precaches `index.html`,
182
+ `registerSW.js`, and `manifest.webmanifest` under fixed names, and serves
183
+ navigations with `createHandlerBoundToURL("index.html")`. Only the Vite asset
184
+ names are hashed. The test never hardcodes a hash. It reads the entry script
185
+ name from the cached shell, so the check stays valid across rebuilds.
186
+
187
+ Observed: 7 cached entries (index, registerSW, manifest, two icons, one JS
188
+ bundle, one CSS bundle).
189
+
190
+ ## 12. API cache-exclusion proof
191
+
192
+ From the controlled page, the hard gate first fetches `/api/index`, the
193
+ annotation view route, and the annotation overlay media route. It requires each
194
+ to return `200` with `cache-control: no-store`. It then counts cached requests
195
+ whose pathname starts with `/api/` across every cache.
196
+
197
+ Observed: `apiCacheEntryCount = 0`.
198
+
199
+ The separate focused test `never caches /api/ responses at the service-worker
200
+ cache-storage layer` was kept. The hard gate does not depend on it.
201
+
202
+ ## 13. Live evidence proof
203
+
204
+ Before shutdown the test:
205
+
206
+ 1. waits for the first `.evidence-list__item`
207
+ 2. requires it to be visible
208
+ 3. requires the `.evidence-list` text to contain the fixture candidate id
209
+ `many-regions-candidate` (read from the fixture helper's return value, not
210
+ hardcoded)
211
+ 4. makes a direct Node-side `fetch` to `/api/index` (5 second timeout) and
212
+ requires HTTP 200 with a body that contains the same candidate id
213
+
214
+ ## 14. Server-down proof
215
+
216
+ After `await server.close()` the test makes a direct Node-side `fetch` to the
217
+ same `/api/index` URL with a 5 second timeout. A Node request cannot be
218
+ intercepted by the page's service worker, so this tests the origin server alone.
219
+ The test requires the request to fail. If it gets any HTTP response, the test
220
+ fails with that status in the message.
221
+
222
+ The page was not navigated away. The controlled page and worker stayed in place.
223
+
224
+ ## 15. Offline reload proof
225
+
226
+ Only after the checks in sections 9 to 14 pass does the test call
227
+ `page.reload()`. It then requires:
228
+
229
+ 1. `h1, header` to render within 10 seconds, so the shell was served from the
230
+ precache
231
+ 2. `navigator.serviceWorker.controller !== null` after the reload
232
+
233
+ ## 16. Stale-evidence safety proof
234
+
235
+ After the offline reload the test requires:
236
+
237
+ 1. `.evidence-list__error` to appear
238
+ 2. body text to contain `Evidence index unavailable`
239
+ 3. body text not to contain the previously visible candidate id
240
+ 4. zero `.evidence-list__item` elements
241
+
242
+ These assertions are unchanged from the original or stricter. None were
243
+ weakened.
244
+
245
+ ## 17. Cleanup proof
246
+
247
+ Cleanup runs in `finally` in a fixed order: close the context, close the server
248
+ unless it was already closed on purpose, remove the evidence root, remove the
249
+ profile. Each step runs even if an earlier step fails. Errors are collected.
250
+
251
+ 1. If the experiment failed, cleanup errors are logged with `console.error` and
252
+ the original assertion error is what the test reports.
253
+ 2. If the experiment passed but cleanup failed, the test fails with every
254
+ cleanup error listed.
255
+ 3. After cleanup, the test requires that neither the evidence root nor the
256
+ profile directory exists.
257
+
258
+ A leak audit counted `my-frontend-observer-pwa-*` and
259
+ `my-frontend-observer-b8-pwa*` directories under the OS temp directory before
260
+ and after each run. All counts were 0. That covers the hard gate's profile and
261
+ evidence root and the first describe block's temporary profile and evidence
262
+ root.
263
+
264
+ Not executed: the failure-path cleanup (steps 1 and 2 above) was not triggered
265
+ on purpose. It is supported by code inspection only.
266
+
267
+ ## 18. Isolated run #1
268
+
269
+ `npx vitest run --config vitest.browser.config.ts tests/browser/pwaHardening.test.ts -t "HARD GATE"`
270
+
271
+ PASS. 1 passed, 6 skipped. Exit 0. The run came right after a fresh
272
+ `npm run build`.
273
+
274
+ ## 19. Isolated run #2
275
+
276
+ The same command was run again right away. PASS. 1 passed, 6 skipped. Exit 0.
277
+
278
+ The isolated command was run five times in total over the edits. The final two
279
+ runs, on the committed test content, both passed. One of them used
280
+ `--reporter=verbose` and printed the boundary line:
281
+
282
+ `HARD GATE pre-shutdown boundary {"serviceWorkerActive":true,"controlledOnFirstLoad":true,"clientControlled":true,"shellEntryScriptCached":true,"precacheEntryCount":7,"apiCacheEntryCount":0,"liveEvidenceVisible":true}`
283
+
284
+ That line was produced by the test itself after its assertions passed.
285
+
286
+ ## 20. Full PWA file result
287
+
288
+ `npx vitest run --config vitest.browser.config.ts tests/browser/pwaHardening.test.ts`
289
+
290
+ PASS. 7 passed. Exit 0.
291
+
292
+ ## 21. Typecheck/lint
293
+
294
+ 1. `npm run typecheck`: PASS (exit 0).
295
+ 2. `npm run lint`: PASS (exit 0). The first lint run failed with
296
+ `no-unsafe-finally` because the cleanup error was thrown inside `finally`.
297
+ The throw was moved after the `try/finally` block, then isolated runs 1 and 2,
298
+ the full file, typecheck, and lint were all run again and passed.
299
+
300
+ ## 22. Production-code audit
301
+
302
+ `git diff --name-only` lists only `tests/browser/pwaHardening.test.ts` plus this
303
+ report. No file under `src/` or `viewer/` changed. `viewer/vite.config.ts`,
304
+ the service-worker caching policy, `package.json`, `package-lock.json`,
305
+ `.github/workflows/`, and `CHANGELOG.md` were not touched. `git diff --check`
306
+ passed.
307
+
308
+ ## 23. Product-defect decision
309
+
310
+ 1. `PRODUCT_DEFECT_DISCOVERED = false`
311
+ 2. `PRODUCT_CHANGE_REQUIRED = false`
312
+
313
+ With every precondition proven, the released v0.9.0 viewer shell loads offline
314
+ from the precache. The evidence surface shows its explicit unavailable state.
315
+ Previously fetched evidence is not shown as current. The original failure came
316
+ only from the test's weak wait and its reliance on shared state.
317
+
318
+ ## 24. Changed files
319
+
320
+ 1. `tests/browser/pwaHardening.test.ts`
321
+ 2. `docs/reports/v0.9.1-batch1-pwa-hard-gate-isolation.md`
322
+
323
+ Generated paths (all removed by the tests or kept outside the repository):
324
+
325
+ 1. `%TEMP%\my-frontend-observer-pwa-hard-gate-evidence-*` (removed)
326
+ 2. `%TEMP%\my-frontend-observer-pwa-hard-gate-profile-*` (removed)
327
+ 3. `%TEMP%\my-frontend-observer-pwa-profile-*` and
328
+ `%TEMP%\my-frontend-observer-b8-pwa-*` (removed)
329
+ 4. The diagnostic script and config in the Claude Code session scratchpad
330
+ (outside the repository, not committed)
331
+ 5. `dist/` from `npm run build` (ignored build output)
332
+
333
+ These small temporary directories sit under the OS temp directory on `C:\`, as
334
+ the task's `mkdtemp(tmpdir())` design specifies. They are deleted at the end of
335
+ every passing run.
336
+
337
+ ## 25. Batch 2 handoff
338
+
339
+ Batch 2 still owns:
340
+
341
+ 1. the permanent `test:pwa-hard-gate` package script
342
+ 2. wiring that command into the security or pre-release readiness path
343
+ 3. moving `CURRENT_STATE.md`, `ROADMAP.md`, `DEVELOPMENT.md`, `CI_CD.md`, and
344
+ `CHANGELOG.md` from planned to implemented wording
345
+ 4. the full unit, browser, security, build, docs, and pack validation
346
+
347
+ Notes for Batch 2:
348
+
349
+ 1. The focused test `never caches /api/ responses` still uses the same weak
350
+ `?.active !== undefined` wait that the old hard gate used. It stays correct
351
+ in file order, because it runs after the registration test on the same
352
+ origin. If it is ever meant to stand alone, it should wait on
353
+ `navigator.serviceWorker.ready` instead. It was not changed here to keep
354
+ Batch 1 narrow.
355
+ 2. The standalone display-mode test still writes a log to
356
+ `.my-dev-kit-workflow/v0.8/batch-08/logs`. That is a log file, not a browser
357
+ profile, and was not changed.
358
+ 3. The historical directory `.my-dev-kit-workflow/v0.8/batch-08/pwa-profile`
359
+ still exists on disk. No test needs it now. It was not deleted.