@dailephd/my-frontend-observer 0.9.1 → 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 (150) hide show
  1. package/CHANGELOG.md +490 -471
  2. package/LICENSE +21 -21
  3. package/README.md +375 -357
  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/application/visualChangeAgentHandoffService.d.ts +28 -0
  11. package/dist/application/visualChangeAgentHandoffService.js +111 -0
  12. package/dist/application/visualChangeAgentHandoffService.js.map +1 -0
  13. package/dist/application/visualChangeProjectWorkflowService.d.ts +95 -0
  14. package/dist/application/visualChangeProjectWorkflowService.js +376 -0
  15. package/dist/application/visualChangeProjectWorkflowService.js.map +1 -0
  16. package/dist/application/visualChangeReviewService.d.ts +50 -0
  17. package/dist/application/visualChangeReviewService.js +69 -0
  18. package/dist/application/visualChangeReviewService.js.map +1 -0
  19. package/dist/application/visualChangeWorkflowPersistenceService.d.ts +26 -0
  20. package/dist/application/visualChangeWorkflowPersistenceService.js +15 -0
  21. package/dist/application/visualChangeWorkflowPersistenceService.js.map +1 -0
  22. package/dist/artifacts/visualChangeWorkflowArtifactReader.d.ts +9 -0
  23. package/dist/artifacts/visualChangeWorkflowArtifactReader.js +47 -0
  24. package/dist/artifacts/visualChangeWorkflowArtifactReader.js.map +1 -0
  25. package/dist/artifacts/visualChangeWorkflowArtifactWriter.d.ts +20 -0
  26. package/dist/artifacts/visualChangeWorkflowArtifactWriter.js +41 -0
  27. package/dist/artifacts/visualChangeWorkflowArtifactWriter.js.map +1 -0
  28. package/dist/cli.js +9 -7
  29. package/dist/cli.js.map +1 -1
  30. package/dist/domain/visualChangeAgentHandoff.d.ts +82 -0
  31. package/dist/domain/visualChangeAgentHandoff.js +80 -0
  32. package/dist/domain/visualChangeAgentHandoff.js.map +1 -0
  33. package/dist/domain/visualChangeAgentHandoffSerialization.d.ts +2 -0
  34. package/dist/domain/visualChangeAgentHandoffSerialization.js +11 -0
  35. package/dist/domain/visualChangeAgentHandoffSerialization.js.map +1 -0
  36. package/dist/domain/visualChangeCycle.d.ts +8 -0
  37. package/dist/domain/visualChangeCycle.js +7 -0
  38. package/dist/domain/visualChangeCycle.js.map +1 -0
  39. package/dist/domain/visualChangeWorkflow.d.ts +125 -0
  40. package/dist/domain/visualChangeWorkflow.js +109 -0
  41. package/dist/domain/visualChangeWorkflow.js.map +1 -0
  42. package/dist/domain/visualChangeWorkflowIdentity.d.ts +5 -0
  43. package/dist/domain/visualChangeWorkflowIdentity.js +24 -0
  44. package/dist/domain/visualChangeWorkflowIdentity.js.map +1 -0
  45. package/dist/index.d.ts +21 -1
  46. package/dist/index.js +12 -1
  47. package/dist/index.js.map +1 -1
  48. package/dist/projectWorkflow/projectPaths.d.ts +3 -0
  49. package/dist/projectWorkflow/projectPaths.js +7 -0
  50. package/dist/projectWorkflow/projectPaths.js.map +1 -1
  51. package/dist/viewer/assets/index-DglJ6f28.css +1 -0
  52. package/dist/viewer/assets/index-DsODREY5.js +9 -0
  53. package/dist/viewer/index.html +15 -15
  54. package/dist/viewer/sw.js +1 -1
  55. package/dist/viewerServer/evidence/classify.d.ts +3 -1
  56. package/dist/viewerServer/evidence/classify.js +10 -0
  57. package/dist/viewerServer/evidence/classify.js.map +1 -1
  58. package/dist/viewerServer/evidence/handles.js +1 -0
  59. package/dist/viewerServer/evidence/handles.js.map +1 -1
  60. package/dist/viewerServer/evidence/projection.d.ts +5 -0
  61. package/dist/viewerServer/evidence/projection.js +19 -0
  62. package/dist/viewerServer/evidence/projection.js.map +1 -1
  63. package/dist/viewerServer/evidence/visualChangeWorkflowView.d.ts +31 -0
  64. package/dist/viewerServer/evidence/visualChangeWorkflowView.js +36 -0
  65. package/dist/viewerServer/evidence/visualChangeWorkflowView.js.map +1 -0
  66. package/dist/viewerServer/httpServer.js +323 -1
  67. package/dist/viewerServer/httpServer.js.map +1 -1
  68. package/dist/viewerServer/referenceApproval.d.ts +22 -0
  69. package/dist/viewerServer/referenceApproval.js +42 -0
  70. package/dist/viewerServer/referenceApproval.js.map +1 -0
  71. package/dist/viewerServer/referenceVisualChangeAuthoring.d.ts +28 -0
  72. package/dist/viewerServer/referenceVisualChangeAuthoring.js +134 -0
  73. package/dist/viewerServer/referenceVisualChangeAuthoring.js.map +1 -0
  74. package/dist/viewerServer/runtimeVisualChangeAuthoring.d.ts +33 -0
  75. package/dist/viewerServer/runtimeVisualChangeAuthoring.js +81 -0
  76. package/dist/viewerServer/runtimeVisualChangeAuthoring.js.map +1 -0
  77. package/dist/viewerServer/visualChangeAuthoring.d.ts +46 -0
  78. package/dist/viewerServer/visualChangeAuthoring.js +63 -0
  79. package/dist/viewerServer/visualChangeAuthoring.js.map +1 -0
  80. package/dist/viewerServer/visualChangeHandoff.d.ts +23 -0
  81. package/dist/viewerServer/visualChangeHandoff.js +31 -0
  82. package/dist/viewerServer/visualChangeHandoff.js.map +1 -0
  83. package/dist/viewerServer/visualChangeReview.d.ts +30 -0
  84. package/dist/viewerServer/visualChangeReview.js +46 -0
  85. package/dist/viewerServer/visualChangeReview.js.map +1 -0
  86. package/docs/ARCHITECTURE.md +1394 -1373
  87. package/docs/CI_CD.md +349 -327
  88. package/docs/COMMANDS.md +1035 -1012
  89. package/docs/CONTRACTS.md +1971 -1926
  90. package/docs/CURRENT_STATE.md +1277 -1238
  91. package/docs/DEVELOPMENT.md +240 -237
  92. package/docs/DOCUMENTATION_PRESERVATION_POLICY.md +50 -50
  93. package/docs/PROJECT_DESCRIPTION.md +2248 -2224
  94. package/docs/PROJECT_MILESTONES.md +2681 -2558
  95. package/docs/PROJECT_OVERVIEW.md +200 -191
  96. package/docs/QUICKSTART.md +100 -96
  97. package/docs/RELEASE.md +37 -33
  98. package/docs/ROADMAP.md +1105 -1033
  99. package/docs/SECURITY.md +297 -275
  100. package/docs/WORKFLOWS.md +806 -770
  101. package/docs/plans/v0.10-implementation-plan.md +1509 -0
  102. package/docs/plans/v0.8-implementation-plan.md +655 -655
  103. package/docs/plans/v0.8.1-cli-usability-patch-plan.md +505 -505
  104. package/docs/plans/v0.9-implementation-plan.md +1529 -1529
  105. package/docs/plans/v0.9.1-implementation-plan.md +468 -468
  106. package/docs/reports/v0.10-batch1-visual-change-workflow-foundation.md +102 -0
  107. package/docs/reports/v0.10-batch2-project-composition-check-recording.md +103 -0
  108. package/docs/reports/v0.10-batch3-viewer-visual-change-workspace.md +93 -0
  109. package/docs/reports/v0.10-batch4-actual-frontend-entry.md +59 -0
  110. package/docs/reports/v0.10-batch5-reference-driven-entry.md +238 -0
  111. package/docs/reports/v0.10-batch6-coding-agent-handoff.md +85 -0
  112. package/docs/reports/v0.10-batch7-correction-review-acceptance.md +145 -0
  113. package/docs/reports/v0.10-batch8-integrated-acceptance.md +109 -0
  114. package/docs/reports/v0.10-implementation-completeness-documentation-reconciliation.md +344 -0
  115. package/docs/reports/v0.10-pre-release-readiness.md +120 -0
  116. package/docs/reports/v0.10-release-preparation.md +70 -0
  117. package/docs/reports/v0.10.1-project-check-baseline-context-implementation.md +86 -0
  118. package/docs/reports/v0.7-bounded-fidelity-context-prompt7.md +243 -243
  119. package/docs/reports/v0.7-implementation-completeness-documentation-reconciliation.md +497 -497
  120. package/docs/reports/v0.7-pre-release-readiness.md +337 -337
  121. package/docs/reports/v0.7-reference-binding-prompt5.md +223 -223
  122. package/docs/reports/v0.7-reference-compatibility-prompt4.md +234 -234
  123. package/docs/reports/v0.7-reference-correction-workflow-prompt8.md +222 -222
  124. package/docs/reports/v0.7-reference-fidelity-prompt6.md +216 -216
  125. package/docs/reports/v0.7-reference-foundation-prompt1.md +151 -151
  126. package/docs/reports/v0.7-reference-regions-prompt2.md +195 -195
  127. package/docs/reports/v0.7-reference-requirements-prompt3.md +217 -217
  128. package/docs/reports/v0.7-release-prep.md +423 -423
  129. package/docs/reports/v0.8-binding-fidelity-interaction-batch6.md +279 -279
  130. package/docs/reports/v0.8-bounded-context-correlation-batch7.md +233 -233
  131. package/docs/reports/v0.8-comparison-contract-inspection-batch4.md +279 -279
  132. package/docs/reports/v0.8-evidence-index-readers-batch2.md +247 -247
  133. package/docs/reports/v0.8-implementation-completeness-documentation-reconciliation.md +741 -741
  134. package/docs/reports/v0.8-integrated-viewer-acceptance-batch8.md +128 -128
  135. package/docs/reports/v0.8-observation-svg-inspection-batch3.md +223 -223
  136. package/docs/reports/v0.8-prerelease-readiness-cross-platform-security-code-rot.md +687 -687
  137. package/docs/reports/v0.8-reference-candidate-inspection-batch5.md +232 -232
  138. package/docs/reports/v0.8-viewer-runtime-pwa-batch1.md +278 -278
  139. package/docs/reports/v0.8.1-implementation-completeness-documentation-reconciliation.md +114 -114
  140. package/docs/reports/v0.8.1-prerelease-readiness-cross-platform-security-code-rot.md +170 -170
  141. package/docs/reports/v0.9-architecture-retrieval.md +14 -37
  142. package/docs/reports/v0.9-final-pre-release-readiness.md +209 -209
  143. package/docs/reports/v0.9-final-readiness-corrections.md +530 -530
  144. package/docs/reports/v0.9-pre-release-readiness.md +169 -169
  145. package/docs/reports/v0.9.1-batch1-pwa-hard-gate-isolation.md +359 -359
  146. package/docs/reports/v0.9.1-batch2-hard-gate-validation-integration.md +262 -262
  147. package/docs/reports/v0.9.1-pre-release-readiness.md +206 -206
  148. package/package.json +59 -59
  149. package/dist/viewer/assets/index-BN41MI7m.css +0 -1
  150. package/dist/viewer/assets/index-CkKXnlrI.js +0 -9
@@ -1,1373 +1,1394 @@
1
- # Architecture
2
-
3
- ## v0.8.1 project workflow
4
-
5
- Versioned project configuration (`1.0.0` compatibility plus current `1.1.0`
6
- acceptance input), upward discovery, centralized managed paths, and the atomic
7
- alias catalog live under `src/projectWorkflow`. Aliases select exact canonical
8
- artifact directories; they never replace artifact identities.
9
-
10
- `src/application/projectCheckService.ts` composes the existing observation,
11
- comparison, contract-evaluation, reference-reader, explicit-binding,
12
- compatibility, and fidelity owners. `src/projectWorkflow/checkAcceptance.ts`
13
- owns contained acceptance-input resolution and shared file-wrapper parsing.
14
- `checkResult.ts` owns the bounded ephemeral projection, not a persisted check
15
- artifact or evaluation engine. Status precedence is `BLOCKED`, then `FAIL`,
16
- then `PASS` when all configured executable dimensions pass, then
17
- `REVIEW_REQUIRED` when comparison is the only evidence. Canonical observations,
18
- comparisons, and contract evaluations remain persisted; reference fidelity and
19
- the workflow result remain in memory/presentation.
20
-
21
- ## Current package architecture
22
-
23
- The current repository is one published TypeScript ESM package
24
- (`@dailephd/my-frontend-observer@0.9.0`). The CLI remains
25
- `my-frontend-observer`; the npm scope does not rename the product or artifact
26
- identities.
27
-
28
- - `src/cli.ts` is the real, thin public CLI parsing/dispatch/presentation
29
- boundary for the current command surface (`observe`, `compare`,
30
- `approve-baseline`, `save-change-contract`, `evaluate-contract`,
31
- `import-reference`, `approve-reference`, `evaluate-reference-fidelity`,
32
- `view`, `init`, `capture`, `check`); argument parsing and output formatting
33
- only, per command - domain semantics remain owned by application/domain
34
- services rather than the CLI. v0.6 added no new CLI command; v0.7 added the
35
- three external-reference commands; v0.8 added `view`; v0.8.1 added `init`,
36
- `capture`, and `check` and made `view` project-aware while preserving its
37
- standalone `--root` behavior.
38
- - `src/index.ts` is the library entry point re-exporting the observer-owned
39
- contracts/functions from every layer below, including the v0.6 bounded-agent-
40
- context projection and runtime/static correlation surface, and the v0.7
41
- external-reference/region/requirement/applicability/compatibility/binding/
42
- fidelity/correction-workflow surface (`prepareReferenceCorrection`/
43
- `reviewReferenceCorrectionAttempt` remain programmatic-only, with no CLI
44
- command).
45
- - `scripts/clean.mjs` safely removes only the project `dist/` directory.
46
- - `scripts/check-docs.mjs` validates the canonical documentation foundation,
47
- roadmap version presence, and the no-batches rule.
48
- - TypeScript, ESLint, Vitest, and package configuration provide foundation
49
- validation, now exercised by real product tests (`tests/unit/`,
50
- `tests/browser/`).
51
-
52
- Batch 1 added the observation domain/schema and safety-policy layer
53
- (`src/domain/`, `src/request/`, `src/safety/`). Batch 2 added a real
54
- Playwright Chromium browser adapter (`src/browser/`), a minimal application
55
- seam invoking it (`src/application/`), and a deterministic browser
56
- fixture/test boundary (`tests/fixtures/`, `tests/browser/`, run via
57
- `npm run test:browser`). Batch 3 extended that single browser adapter with an
58
- internal page/target measurement module (`src/browser/evidenceCapture.ts`)
59
- that reads page and explicit-CSS-target evidence from the same live,
60
- already-ready page used for the screenshot - no second browser/page is ever
61
- opened, and Playwright objects still never leave `src/browser/`. Batch 4
62
- added the artifact ownership boundary itself: `src/artifacts/artifactWriter.ts`
63
- is the one canonical place that writes an observation to disk (temp
64
- directory, then one atomic rename into `<outputLocation>/<observationId>/`),
65
- and `src/application/observationPersistence.ts` assembles the frozen
66
- `ObservationArtifact` from a browser-capture result before handing it to the
67
- writer. The artifact layer has no Playwright dependency and is testable
68
- without launching Chromium. Batch 5 completed the boundary chain: `src/cli.ts`
69
- parses `observe` arguments (CLI-syntax errors only - e.g. malformed
70
- `WIDTHxHEIGHT`), constructs a raw request, and hands it to the existing
71
- Batch 1 `normalizeRequest`; on success it calls one new application-level use
72
- case, `observe()` in `src/application/observationPersistence.ts`, which runs
73
- the existing `runBrowserCapture` exactly once and, only on success, the
74
- existing artifact writer exactly once, then returns a small observer-owned
75
- `ApplicationObservationResult` (observation id, completion state, artifact
76
- path, target/diagnostic counts) for the CLI to print. The CLI never imports
77
- Playwright or the filesystem-write path directly. Batch 6 closed the
78
- remaining real-Chromium coverage gap (a genuine navigation failure, distinct
79
- from a readiness timeout or a pre-launch safety rejection) and validated the
80
- packed npm tarball end to end in a clean consumer environment, independent
81
- of the source checkout. At the end of the v0.1 implementation there was no
82
- controlled-scroll or comparison behavior.
83
-
84
- ## Current v0.2 architecture (released/current architecture)
85
-
86
- v0.2 extends the same architecture rather than adding a parallel one.
87
- `src/request/request.ts` now owns a canonical `{name, locators}` target
88
- model (`TargetLocator`, six frozen kinds) in place of the old CSS-only
89
- shape; the legacy `{name, selector}` input still normalizes into it. The one
90
- existing browser-side target resolver/measurement module,
91
- `src/browser/evidenceCapture.ts`, was extended - not replaced - to resolve
92
- all six locator kinds against the live page through a single Playwright
93
- `Locator` per attempt, honor the frozen ordered-fallback/ambiguity/
94
- unavailable-no-fallback contract, and converge every kind on the same
95
- measurement path (`captureResolvedTargetRecord`); it additionally computes
96
- bounded semantic state, derived landmark identity, and configured-target-
97
- only DOM containment from the same already-resolved elements in the same
98
- capture pass - no second browser/page, no second resolution algorithm.
99
- `src/domain/schema.ts` extends `TargetEvidenceRecord`/`TargetResolution`
100
- additively for schema `1.1.0`, with matching structural validation in
101
- `isValidObservationArtifact`. `src/cli.ts` gained one CLI/input-boundary-
102
- only addition, `--targets-file`: it reads and validates only the JSON root
103
- wrapper (via the already-imported `node:fs`, never `node:fs/promises`) and
104
- hands the parsed `targets` value into the existing `RawObservationRequest`/
105
- `normalizeRequest()` path unchanged - there is no second application
106
- observation use case, and Playwright objects still never leave
107
- `src/browser/`. The artifact writer, application observation use case, and
108
- overall boundary chain (`CLI → normalizeRequest → observe() →
109
- runBrowserCapture → artifact writer`) are unchanged from v0.1.
110
-
111
- ## Current v0.3 architecture (released/current architecture)
112
-
113
- v0.3 extends the same single-observation architecture again; it does not add
114
- a second browser lifecycle, target resolver, or artifact path.
115
- `src/request/request.ts` adds one optional `scrollScenario` field to
116
- `NormalizedObservationRequest` (`ScrollScenario { action }`, exactly
117
- `window-scroll-by` or `target-scroll-by`); `src/domain/schema.ts` adds the
118
- matching bounded runtime evidence types (`ScrollRuntimeSnapshot`,
119
- `ViewportRelationEvidence`, `OverflowEvidence`, `ScrollScenarioTransition`,
120
- `ScrollOwnerInterpretation`) and structural validation for schema `1.2.0`
121
- (additive over `1.1.0`). `src/domain/scrollEvidence.ts` holds the pure,
122
- browser-independent derivations (viewport relation, actual overflow,
123
- transitions, and `deriveScrollOwner`) so they are unit-testable without
124
- Chromium. `src/browser/scrollCapture.ts` holds the one browser-side scenario
125
- capture module: it reuses `evidenceCapture.ts#resolveConfiguredTargets` (now
126
- exported) to resolve configured targets exactly once, captures an initial
127
- `ScrollRuntimeSnapshot`, performs the one immediate scroll
128
- (`window.scrollBy`/`element.scrollBy`, both `behavior: 'instant'`), waits
129
- exactly two `requestAnimationFrame` cycles, and captures a final snapshot -
130
- all inside `chromiumAdapter.ts#captureViewportInternal`'s existing single
131
- navigate → ready → capture flow, strictly before the unchanged
132
- screenshot/`capturePageEvidence`/`captureTargetEvidence` calls, so every
133
- downstream capture (including a no-scenario request, which skips this block
134
- entirely) describes only the final state. `src/cli.ts` gained one CLI/input-
135
- boundary-only addition, `--scroll-scenario-file`: mirroring
136
- `--targets-file`, it reads and validates only the file readability/JSON-
137
- validity/non-array-object-root shape and hands the parsed value straight
138
- into `RawObservationRequest.scrollScenario` - every scenario/action rule
139
- (kind, deltas, target reference) stays owned by `normalizeRequest()`. There
140
- is still one canonical `observe()` application use case and one artifact
141
- writer; `scrollScenarioEvidence` is simply one more optional field on the
142
- same `ObservationArtifact`.
143
-
144
- ## Current v0.4 architecture (released/current architecture)
145
-
146
- v0.4 adds one new downstream pipeline that consumes `ObservationArtifact`
147
- values rather than producing them - it never adds a second browser lifecycle,
148
- target resolver, or observation engine:
149
-
150
- ```text
151
- ObservationArtifact before ObservationArtifact after
152
- \ /
153
- `--------. .-------'
154
- \ /
155
- artifact reader (src/artifacts/artifactReader.ts)
156
- ↓
157
- comparability evaluation (src/domain/comparisonEngine.ts)
158
- ↓
159
- canonical relationship derivation, called for each side independently
160
- (src/domain/relationships.ts#deriveLayoutRelationships)
161
- ↓
162
- canonical comparison derivation
163
- (src/domain/comparisonEngine.ts#compareObservations)
164
- ↓
165
- ComparisonArtifact
166
- ↓
167
- atomic comparison writer (src/artifacts/comparisonArtifactWriter.ts)
168
-
169
- CLI `compare`
170
- ↓
171
- application service only (src/application/comparisonService.ts)
172
- ↓
173
- [reader → domain comparison → writer, as above]
174
- ```
175
-
176
- `src/domain/relationships.ts` froze the layout-relationship contract and
177
- implements the one canonical pure derivation,
178
- `deriveLayoutRelationships(observation, options?)`: horizontal/vertical
179
- order, area overlap, relative width, geometric fit, vertical sequencing,
180
- page-width fit/exceeds, and a standalone `deriveTargetClipping(record)` -
181
- all computed only from an already-captured `ObservationArtifact`'s own
182
- `targetEvidence`/`pageEvidence`, never from a second browser query. DOM
183
- containment is read directly from the existing v0.2 `TargetContainment`
184
- evidence rather than re-derived, and stays a distinct concept from
185
- geometric fit.
186
-
187
- `src/domain/comparisonEngine.ts` implements the one canonical pure
188
- before/after engine, `compareObservations(before, after, config?)`:
189
- validates both source artifacts, evaluates comparability *before* any
190
- rendered difference is calculated, calls `deriveLayoutRelationships` once
191
- per side with the same tolerance, and derives target/page differences and
192
- relationship changes. `src/artifacts/comparisonArtifactWriter.ts` persists
193
- the result atomically (sibling temp directory, then one rename) as
194
- `<outputLocation>/<comparisonId>/manifest.json` only - no screenshot is
195
- copied; the manifest's `before`/`after` references point back to the
196
- source observations' own `screenshot.path`. `src/application/
197
- comparisonService.ts` is the one application-layer seam: `compareAndPersist`
198
- takes two in-memory `ObservationArtifact`s and does exactly one comparison
199
- plus exactly one persist; `compareAndPersistFromArtifactRoots` is a thin
200
- wrapper that additionally reads both sides from disk via the existing
201
- `src/artifacts/artifactReader.ts#readObservationArtifact` reader (itself
202
- just a `manifest.json` parse plus the same `isValidObservationArtifact`
203
- structural gate the writer uses).
204
-
205
- `src/cli.ts` gained one new top-level command, `compare`
206
- (`--before`/`--after`/`--output`/`--config-file`), implemented with the same
207
- thin-CLI-boundary discipline as `observe`: `parseCompareArgs` handles only
208
- argument shape/duplication, an optional `loadComparisonConfigFile` reads and
209
- validates only file readability/JSON-validity/non-array-object-root (exactly
210
- like `--targets-file`/`--scroll-scenario-file`), and the command body calls
211
- `compareAndPersistFromArtifactRoots` exactly once. **The CLI's comparison
212
- path never launches Chromium** - `src/cli.ts` imports nothing from
213
- `src/browser/` or `src/artifacts/` (it only reaches persistence and artifact
214
- reading indirectly, through the application-layer seam above), matching the
215
- same import-boundary discipline already enforced for `observe`.
216
-
217
- ## Current v0.5 architecture (released/current architecture)
218
-
219
- v0.5 adds one new downstream layer that consumes `ComparisonArtifact` values
220
- (plus the source `ObservationArtifact` pair) rather than producing them - no
221
- new browser lifecycle, target resolver, or comparison engine is added:
222
-
223
- ```text
224
- ObservationArtifact before + after
225
- ↓
226
- existing v0.4 comparison/relationship pipeline (unchanged)
227
- ↓
228
- ComparisonArtifact
229
- ↓ PersistentBaselineContract
230
- | +
231
- `------------------------→ PerChangeContract
232
- ↓
233
- canonical contract evaluation
234
- (src/domain/frontendContractEvaluation.ts#evaluateFrontendContract)
235
- ↓
236
- clause results + unexpected changes + overall PASS/FAIL
237
- ```
238
-
239
- `src/domain/frontendContracts.ts` froze the contract/change-scope type,
240
- constant, and structural-validator vocabulary (Batch 1); `src/domain/
241
- frontendContractIdentity.ts` froze deterministic contract/baseline/clause
242
- identity in the same canonicalize+sha256(+opaque-nonce) style as `src/domain/
243
- comparisonIdentity.ts`. `src/domain/frontendContractEvaluation.ts#evaluateFrontendContract`
244
- (Batch 2) is the one canonical pure evaluation entry point: it validates its
245
- five inputs (before/after `ObservationArtifact`, `ComparisonArtifact`,
246
- `PersistentBaselineContract`, `PerChangeContract`) are structurally coherent
247
- and mutually consistent, calculates the active baseline clause set after
248
- explicit supersession, detects bounded structural conflicts, evaluates every
249
- active clause via the frozen 15-primitive vocabulary against the existing
250
- `ComparisonArtifact`/`ObservationArtifact` evidence (never re-deriving
251
- clipping/relationship/scroll-owner facts), classifies unaccounted-for
252
- `ComparisonArtifact.differences` entries as `unexpected`, and derives one
253
- overall `PASS`/`FAIL` verdict. It performs no I/O, launches no browser, and
254
- persists nothing - `src/domain/frontendContractEvaluation.ts` itself remains
255
- untouched by the persistence layer below (Batch 3).
256
-
257
- Batch 3 adds the persistence/application boundary around this frozen domain,
258
- without redefining it:
259
-
260
- ```text
261
- ComparisonArtifact (read via new src/artifacts/comparisonArtifactReader.ts)
262
- +
263
- PersistentBaselineContract / PerChangeContract
264
- (read/written via src/artifacts/frontendContractArtifactReader.ts / ...Writer.ts)
265
- ↓
266
- src/application/frontendContractEvaluationService.ts#evaluateAndPersist
267
- ↓
268
- evaluateFrontendContract() [called exactly once, unmodified]
269
- ↓
270
- src/domain/frontendContractEvaluationArtifact.ts
271
- (minimal additive persisted envelope around the frozen result)
272
- ↓
273
- src/artifacts/frontendContractEvaluationArtifactWriter.ts
274
- (atomic write, exactly once on a structurally constructible result)
275
- ```
276
-
277
- `evaluateAndPersistFromArtifactRoots` is the CLI-facing wrapper, reading
278
- before/after observations through the existing `readObservationArtifact` (no
279
- second observation reader), the comparison and both contract classes
280
- through the new readers, then delegating to `evaluateAndPersist` exactly
281
- once - mirroring `application/comparisonService.ts#compareAndPersistFromArtifactRoots`'s
282
- own thin-wrapper shape.
283
-
284
- Batch 4 exposes this through the same thin-CLI boundary already established
285
- by `observe`/`compare`:
286
-
287
- ```text
288
- src/cli.ts (argument parsing, JSON-file-shape checks, help/output formatting,
289
- exit-code selection only)
290
- ↓
291
- src/application/frontendContractPersistenceService.ts#approveAndPersistBaseline
292
- src/application/frontendContractPersistenceService.ts#persistPerChangeContract
293
- src/application/frontendContractEvaluationService.ts#evaluateAndPersistFromArtifactRoots
294
- ↓
295
- domain validators (isValidPersistentBaselineContract / isValidPerChangeContract)
296
- + artifact readers/writers (Batch 3)
297
- + evaluateFrontendContract() (Batch 2, unmodified)
298
- ```
299
-
300
- Three new top-level commands - `approve-baseline`, `save-change-contract`,
301
- `evaluate-contract` - each parse only CLI-syntax concerns (duplicate/missing
302
- flags, JSON-file readability/parseability/object-root shape) and delegate to
303
- exactly one application-layer call; `src/cli.ts` imports no artifact writer/
304
- reader module and no browser code, matching the existing `observe`/`compare`
305
- import-boundary discipline exactly. `approveAndPersistBaseline` adds the one
306
- new coherence check Batch 3 did not need: verifying a baseline contract's
307
- frozen `sourceObservation` reference actually matches the supplied
308
- observation artifact before persisting - explicit approval only, never
309
- inferred from a `compare` or `evaluate-contract` result. `--enforce` on
310
- `evaluate-contract` is applied only after evaluation and persistence have
311
- already completed; it selects the process exit status for an already-final
312
- `FAIL` result and is never part of any identity or persisted field.
313
-
314
- ## Current v0.6 architecture (released as `0.6.0`)
315
-
316
- v0.6 adds one new downstream, read-only layer that consumes existing v0.1-v0.5
317
- evidence (`ObservationArtifact`, `ComparisonArtifact`,
318
- `PersistentBaselineContract`/`PerChangeContract`, and evaluation results)
319
- plus caller-supplied bounded static candidate evidence - it adds no new
320
- browser lifecycle, target resolver, observation/comparison/contract engine,
321
- or persisted artifact family:
322
-
323
- ```text
324
- ObservationArtifact(s) + ComparisonArtifact + contract/evaluation evidence
325
- ↓
326
- src/domain/boundedAgentContextProjection.ts#projectBoundedAgentContext
327
- ↓
328
- BoundedRuntimeTargetProjection
329
- (page/viewport identity, stable targets, geometry, runtime behavior,
330
- relationships, before/after differences, contract results,
331
- requested/expected-dependent/protected/preserved scope reused verbatim
332
- from src/domain/frontendContracts.ts, diagnostics, artifact/screenshot
333
- references, provenance, adequacy, omission, truncation)
334
- ↓
335
- src/domain/boundedAgentContextCorrelation.ts
336
- #deriveRuntimeStaticCorrelations / #attachRuntimeStaticCorrelations
337
- ↓
338
- RuntimeStaticCorrelation[] (correlated / ambiguous / unavailable,
339
- competing candidates preserved verbatim - never collapsed to one owner)
340
- ↓
341
- src/index.ts (public export/correlation boundary only)
342
- ```
343
-
344
- `src/domain/boundedAgentContext.ts` freezes the bounded-projection and
345
- correlation type/constant vocabulary (`Adequacy`, `ADEQUACY_REASON_CODES`,
346
- `OmissionRecord`, `TruncationRecord`, `BOUNDED_AGENT_CONTEXT_ARTIFACT_KIND`,
347
- schema `1.0.0`). `src/domain/boundedAgentContextIdentity.ts` derives a
348
- deterministic logical identity distinct from a fresh per-execution instance
349
- identity, in the same canonicalize+hash style as
350
- `comparisonIdentity.ts`/`frontendContractIdentity.ts`.
351
- `boundedAgentContextProjection.ts` performs no browser I/O and re-derives
352
- nothing already owned upstream - it reads already-captured artifacts and
353
- reuses the existing v0.4 relationship/comparison evidence and v0.5
354
- change-scope clause types directly. `boundedAgentContextCorrelation.ts`
355
- accepts only plain, caller-supplied candidate static-evidence records; it has
356
- **no** dependency on `@dailephd/my-dev-kit`, since the audit that preceded
357
- implementation found no generic static-side retrieval capability actually
358
- missing (see `docs/ROADMAP.md` v0.6 "Dependency direction" - the "determine
359
- whether my-dev-kit requires a static-side change" step concluded no).
360
- Runtime target identity is carried through this module verbatim; the module
361
- never adds a `sourceOwner`/`causedBy`-shaped field, preserving the
362
- architectural rule that runtime identity never silently becomes source
363
- ownership.
364
-
365
- This layer is a programmatic export/correlation boundary only: `src/index.ts`
366
- re-exports its full type/function surface, but there is no new CLI command,
367
- no `src/artifacts/boundedAgentContext*` writer/reader, and no orchestrator or
368
- lab code in this repository - those remain separate sibling-repository
369
- responsibilities per the Milestone 6 ownership split in
370
- `docs/PROJECT_MILESTONES.md`.
371
-
372
- ## v0.7 (released as `0.7.0`), v0.8 (released as `0.8.0`), and planned v0.9–v0.10 reference-evidence architecture constraints
373
-
374
- The external visual-reference capability (v0.7) is released as package
375
- version `0.7.0` - see "v0.7 Prompt 1" through "v0.7 Prompt 8" below for the
376
- actual architecture, and `docs/CURRENT_STATE.md` for release state. It
377
- extends the existing v0.1-v0.6 evidence architecture rather than becoming a
378
- UI-only feature or a parallel visual-comparison stack. v0.8 (interactive
379
- viewer) is released as package version `0.8.0`. v0.9 (structured visual
380
- annotation) is released as package version `0.9.0` - see "v0.9 visual
381
- annotation architecture" below. v0.10 (full graphical human-LLM
382
- workflow) remains future and unimplemented. The constraints below applied to
383
- v0.9 and still apply to v0.10.
384
-
385
- The evidence domains remain distinct:
386
-
387
- ```text
388
- runtime observation A ↔ runtime observation B
389
- → existing before/after comparison
390
-
391
- approved baseline/per-change contract ↔ candidate runtime evidence
392
- → existing canonical contract evaluation
393
-
394
- external visual reference ↔ candidate runtime evidence
395
- → reference applicability + structured fidelity evaluation (v0.7,
396
- released as `0.7.0` - see "v0.7 Prompt 4" and "v0.7 Prompt 6" below)
397
- ```
398
-
399
- An external reference is not an `ObservationArtifact`, and a reference region
400
- is not a runtime target. The released v0.7 implementation preserves explicit
401
- identity and provenance for the reference image/version, reference regions,
402
- applicable viewport/theme/application state, authored requirements, tolerances,
403
- approval/supersession state, and reference-region/runtime-target bindings.
404
- Bindings are explicit and evaluate to `bound`, `ambiguous`, or `unavailable`;
405
- they never silently become source ownership.
406
-
407
- The non-UI reference model and structured reference-vs-candidate evaluation
408
- were established in v0.7 before v0.8. v0.8 may render side-by-side images,
409
- overlays, measurements, bindings, provenance, and fidelity results, but it must
410
- consume those existing engines and must not invent a second reference model or
411
- evaluation engine. v0.9 may author annotations against either runtime
412
- screenshots or external references, but both coordinate/identity domains remain
413
- explicit and feed the same canonical contract/change-scope semantics. v0.10
414
- combines both visual entry modes with the existing correction loop.
415
-
416
- Where a reference requirement is executable, it uses the existing v0.5
417
- requested/expected-dependent/protected/preserved semantics. Informational or
418
- unassessed reference evidence remains non-executable until explicitly selected.
419
- There is no reference-only PASS/FAIL taxonomy.
420
-
421
- The released reference evaluation reuses existing relationship/value
422
- conventions where they mean the same thing and adds distinct reference-owned
423
- units only where the image evidence requires them. v0.7 does not use pixel or
424
- image-region similarity as a success mechanism; a later bounded similarity
425
- feature may supplement structured evidence if separately designed, but it must
426
- not replace browser-authoritative runtime geometry, canonical contract
427
- evaluation, or explicit relationship evidence.
428
-
429
- Candidate rendering still uses the one existing Chromium observation engine.
430
- The observer remains non-mutating. `my-dev-kit` remains the static/source
431
- evidence owner, and the v0.6 correlation/bounded-context boundary remains the
432
- route for attaching relevant source evidence to reference-driven correction
433
- packets. Heavy reference image bytes are referenced rather than copied into
434
- every downstream context/evaluation record.
435
-
436
- Theme, application-state, viewport, and authenticated-state applicability are
437
- checked before reference fidelity is interpreted through the released v0.7
438
- compatibility path, which reuses v0.4 comparability conventions. If reference
439
- and candidate do not represent compatible intended states, the result is
440
- explicitly incompatible/incomparable rather than a fabricated visual difference
441
- set. v0.8, released as package version `0.8.0`, displays this result exactly
442
- as required rather than redefining the state model - see "v0.8 Batch 5"
443
- below.
444
-
445
- The constraints above were carried out by the actual v0.7 implementation
446
- described in "v0.7 Prompt 1" through "v0.7 Prompt 8" below: explicit
447
- identity/provenance, applicability/compatibility, region-to-target bindings,
448
- requested/expected-dependent/protected/preserved reuse, and the non-mutating
449
- Chromium/correlation boundaries all remain as constrained here. v0.8 (see
450
- "v0.8 Batch 1" through "v0.8 Batch 8" below) applied them unchanged, and so
451
- does the implemented v0.9 annotation layer. They continue to apply unchanged
452
- to the still-future v0.10 work.
453
-
454
- The exact public artifact names, schema versions, persistence layout, supported
455
- image formats, coordinate model, requirement/tolerance primitives, and fidelity
456
- behavior were frozen by the actual v0.7 implementation below, not by earlier
457
- planning language. Style/asset-similarity mechanisms remain future unless
458
- separately implemented.
459
-
460
- ## v0.8 Batch 1 (Viewer runtime and PWA foundation) — implemented
461
-
462
- Batch 1 of the frozen `docs/plans/v0.8-implementation-plan.md` establishes
463
- only the viewer runtime/build shell — no evidence indexing, artifact reading,
464
- or evidence UI. It does not implement any of the v0.7-derived reference/
465
- fidelity/binding display constraints above; those remain future work for
466
- later v0.8 batches, which must consume this runtime boundary rather than
467
- redefine it.
468
-
469
- ```text
470
- my-frontend-observer view [--root <evidence-root>]
471
- |
472
- v
473
- thin CLI dispatch (src/cli.ts: parseViewArgs/runViewCommand)
474
- |
475
- v
476
- viewer application seam (src/viewerServer/viewerService.ts: startViewer)
477
- |
478
- v
479
- Node local server, loopback-only (src/viewerServer/httpServer.ts)
480
- |
481
- +---------------------+----------------------+
482
- | |
483
- v v
484
- built viewer assets (dist/viewer) GET /api/status
485
- (React + TypeScript + Vite PWA) (session/root identity only)
486
- ```
487
-
488
- - **Node server boundary** (`src/viewerServer/`): binds only to `127.0.0.1`
489
- on one fixed default port (`4319`, `src/viewerServer/port.ts`); serves only
490
- the built viewer assets plus the one read-only status endpoint; resolves
491
- every requested path against the built assets root and fails closed on any
492
- path that would resolve outside it; accepts no write HTTP methods; performs
493
- no artifact reading, browser observation, or mutation. `--root` is
494
- validated operationally (exists, is a directory) and exposed only as an
495
- opaque status string — it is never interpreted as Observer evidence in this
496
- batch.
497
- - **Browser application** (`viewer/`): a React + TypeScript + Vite app, built
498
- independently of `src/` via `viewer/tsconfig.json` and `viewer/vite.config.ts`,
499
- output to `dist/viewer` inside the existing package `dist` allowlist (no
500
- second npm package). Renders an honest foundation shell only — product
501
- identity, live session status via `/api/status`, and placeholder
502
- navigation/workspace/details regions — never fabricated evidence.
503
- - **PWA**: `vite-plugin-pwa` generates a web app manifest (`standalone`
504
- display, stable `start_url`/`scope`, installability icons) and a service
505
- worker that precaches only the built application shell. It declares no
506
- `runtimeCaching` rules, so future evidence/media/API routes remain
507
- network/server-backed rather than silently served as stale cached truth
508
- when the local server is unavailable (enforced by
509
- `tests/unit/viewerPwaBuild.test.ts`, which asserts on the actual built
510
- `sw.js`, not a hand-written approximation). An install affordance appears
511
- only when the browser actually fires `beforeinstallprompt`; its absence is
512
- shown honestly, never as a disabled-looking fake control.
513
- - **CLI**: `view [--root <evidence-root>] [--port <n>] [--no-open]` remains a
514
- thin dispatcher — it parses syntax, delegates once to `startViewer`, prints
515
- the URL/root, and optionally best-effort opens the system browser (failure
516
- there is never fatal to server startup). All v0.1-v0.7 commands are
517
- unchanged.
518
-
519
- This batch introduces no second observer, relationship engine, comparison
520
- engine, contract engine, reference model, or bounded-context builder — there
521
- is nothing yet for the viewer to consume beyond its own runtime identity.
522
-
523
- ## v0.8 Batch 2 (Evidence indexing, canonical readers, and lazy data boundary) — implemented
524
-
525
- Batch 2 adds the safe, read-only data boundary between existing on-disk
526
- Observer evidence and the Batch 1 viewer runtime, entirely under
527
- `src/viewerServer/evidence/`. It introduces no new persisted artifact family,
528
- no schema migration, and no second validator — every recognized candidate is
529
- decided exclusively by the existing canonical reader/validator for its
530
- family (`src/artifacts/*Reader.ts`).
531
-
532
- ```text
533
- GET /api/index bounded discovery + classification -> metadata only
534
- GET /api/artifacts/<handle> one canonical-reader read, on demand -> full projection
535
- GET /api/media/<handle>/<role> one resolved, contained media file, streamed on demand
536
- ```
537
-
538
- - **Discovery** (`evidence/discovery.ts`): a bounded, deterministic walk
539
- beneath `--root` that opens only files literally named `manifest.json` (the
540
- one filename every current persisted family uses) — no other file is ever
541
- read or classified, so arbitrary files can never become evidence merely by
542
- existing under the root. Directory entries that are symlinks/junctions are
543
- never followed. Bounds (`evidence/limits.ts`): traversal depth 6,
544
- directories visited 2000, candidate manifests 1000, index records 500,
545
- manifest read size 2,000,000 bytes — chosen after inspecting that every
546
- current writer produces a shallow `<outputLocation>/<id>/manifest.json`
547
- shape (see `docs/CONTRACTS.md`), not a deep tree.
548
- - **Classification is not validation** (`evidence/classify.ts`): peeks only
549
- `artifactKind`/`schemaVersion` (plus, where the shared kind is ambiguous,
550
- tries each existing reader/validator in turn — e.g. baseline vs. per-change
551
- contract) to decide *which* existing canonical reader to call; the reader's
552
- own structural validator remains the sole authority. Six honest, mutually
553
- exclusive states: `supported`, `unsupported-version`, `invalid-structure`,
554
- `unrecognized-kind`, `malformed-json`, `unreadable` — never collapsed into
555
- one boolean, and never conflated with an artifact's own `completion`
556
- state (passed through separately, only for the families that carry one:
557
- observation and external-reference). A manifest declaring the
558
- `bounded-agent-context` kind is classified `unrecognized-kind`: v0.6/v0.7
559
- never added a disk writer/reader for that family (confirmed via direct
560
- source inspection and `@dailephd/my-dev-kit` search), so Batch 2 does not
561
- invent persistence-shaped handling for it.
562
- - **Viewer handles** (`evidence/handles.ts`, `evidence/pathSafety.ts`): a
563
- handle is a family-prefixed, percent-encoded, root-relative directory path
564
- — never a raw filesystem path accepted from the browser. Every route that
565
- accepts a handle re-decodes and re-resolves it against the evidence root,
566
- re-checks containment, and re-classifies that one candidate before serving
567
- anything; a handle whose backing directory or manifest no longer matches
568
- what was indexed fails closed as unknown, never stale.
569
- - **Ephemeral projection** (`evidence/projection.ts`): `EvidenceMetadataRecord`
570
- (bounded, `/api/index`-shaped: handle, family, support state, logical id,
571
- schema version, completion where applicable, media availability summary,
572
- a handful of related ids) and `EvidenceArtifactDetail` (the already-
573
- validated domain object, wrapped with `handle`/`family` — no new evidence
574
- schema, no recomputation, no persistence).
575
- - **Media resolution** (`evidence/mediaResolver.ts`): `screenshot` (observation),
576
- `image` (imported external reference), and `source-image` (approved
577
- external reference) are the only three recognized roles. An approved
578
- reference's image is never assumed to live in the approved artifact's own
579
- directory — its `sourceReference.referenceId` is looked up against the
580
- current index to find the actual owning imported artifact
581
- (`evidence/index.ts#findImportedReferenceDir`), exactly matching the v0.7
582
- reference-ownership contract in `docs/CONTRACTS.md`. A genuinely missing
583
- screenshot/image/source artifact is reported as 404, never fabricated.
584
- - **PWA cache boundary preserved, not re-verified from scratch**: every new
585
- route lives under `/api/`, already covered by Batch 1's
586
- `denylist:[/^\/api\//]` navigation-fallback rule — no new `runtimeCaching`
587
- entry was needed or added (`tests/unit/viewerPwaBuild.test.ts` asserts this
588
- against the real built `sw.js`).
589
- - **Minimal UI** (`viewer/src/hooks/useEvidenceIndex.ts`,
590
- `useArtifactDetail.ts`, `components/EvidenceList.tsx`,
591
- `ArtifactPreview.tsx`): a bounded evidence list (metadata-first) plus
592
- on-demand full-artifact loading on selection, with every support state
593
- shown honestly. No screenshot rendering, SVG overlay, or comparison/
594
- contract/reference visualization exists yet — that begins in Batch 3.
595
-
596
- ## v0.8 Batch 3 (Runtime observation inspection and SVG overlays) — implemented
597
-
598
- Batch 3 makes one already-supported `ObservationArtifact` (Batch 2's data
599
- boundary, unchanged) genuinely understandable: a real screenshot, SVG target
600
- overlays in the observation's own canonical coordinate domain, target
601
- selection/inspection, and canonical layout-relationship display. No second
602
- relationship engine, no client-side evidence derivation, no new persisted
603
- artifact.
604
-
605
- **Coordinate audit (the load-bearing decision for this batch)**: target
606
- geometry (`TargetGeometry.x/y/width/height`) is captured via
607
- `el.getBoundingClientRect()` (`src/browser/evidenceCapture.ts`) - CSS pixels,
608
- relative to the current viewport's top-left, at the same live page state the
609
- screenshot is taken from. The screenshot itself is `page.screenshot({type:
610
- 'png'})` (`src/browser/chromiumAdapter.ts`), Playwright's default
611
- (non-fullPage) mode, against a browser context created with no
612
- `deviceScaleFactor` override (`browser.newContext({viewport})`) - so it
613
- defaults to `1`, meaning every observation this repository can currently
614
- produce has a screenshot whose raw PNG pixel dimensions equal
615
- `requestConfig.viewport.width × requestConfig.viewport.height` exactly (1
616
- CSS pixel = 1 PNG pixel). `requestConfig.viewport` (a required, strongly-typed
617
- field on every valid `ObservationArtifact`, distinct from the loosely-typed
618
- `pageEvidence` bag) is therefore the canonical, always-present source for the
619
- SVG display frame.
620
-
621
- **SVG coordinate model** (`viewer/src/components/TargetOverlaySvg.tsx`): the
622
- `<svg>` root's `viewBox` is `0 0 {requestConfig.viewport.width}
623
- {requestConfig.viewport.height}` - the exact frame `getBoundingClientRect()`
624
- already used. The screenshot loads into a `<image>` element filling that same
625
- viewBox (`preserveAspectRatio="none"`, since the two frames are already
626
- pixel-identical). Target `<rect>` elements use `geometry.x/y/width/height`
627
- completely unchanged - no rounding, no `devicePixelRatio` multiplication, no
628
- clamping; geometry lying partly outside the viewBox is drawn at its real
629
- coordinates and clipped only by the SVG root's default `overflow: hidden`
630
- (a display-only effect, verified never to touch the underlying evidence
631
- value - `tests/unit/observationCoordinateMapping.test.ts`). This is robust
632
- even if a future capture path used a different `deviceScaleFactor`: the
633
- `<image>`/viewBox scaling is presentation-only browser behavior, never a
634
- manual pixel calculation in this codebase. `devicePixelRatio` (captured as
635
- `pageEvidence.devicePixelRatio`) is shown as informational observation-level
636
- evidence only and is never consulted for any geometry calculation.
637
-
638
- **Server additions** (`src/viewerServer/evidence/observationView.ts`, one new
639
- route `GET /api/observations/<handle>/relationships`): the only new
640
- server-side computation this batch adds is one thin, defense-in-depth-wrapped
641
- call to the existing canonical, pure `deriveLayoutRelationships` (`src/domain/
642
- relationships.ts`) - never a second relationship predicate implementation.
643
- Mirrors the exact handle-decode → contained-dir-resolve → re-classify
644
- discipline `loadArtifactByHandle`/`resolveMedia` already established in
645
- Batch 2; a handle for a non-`observation` family or a non-`supported`
646
- candidate is rejected (`409`) before derivation is even attempted. The
647
- existing `GET /api/artifacts/<handle>` (full `ObservationArtifact`) and
648
- `GET /api/media/<handle>/screenshot` (Batch 2, unchanged) remain the only
649
- other data sources the observation workspace uses - no new artifact
650
- projection endpoint was needed, since the full validated domain object
651
- already contains everything the target/observation inspector displays.
652
-
653
- **Client-side presentation only** (`viewer/src/observation/targetOrder.ts`,
654
- `viewer/src/components/{ObservationWorkspace,TargetList,TargetOverlaySvg,
655
- ObservationInspector,EvidenceFieldView}.tsx`): React selects, orders
656
- (by the observation's own authored `requestConfig.targets` order, not
657
- incidental object-key order), and formats already-fetched canonical fields.
658
- It never resolves targets, computes relationships, or derives
659
- visibility/overflow/scroll-owner semantics - `deriveLayoutRelationships`
660
- runs exclusively on the server (above). An unresolved target (`not-found`/
661
- `ambiguous`/`unavailable`) is selectable from the target list and shown
662
- honestly in the inspector, but never receives a fabricated `<rect>` -
663
- `orderedTargets()`'s `hasGeometry` flag is `true` only when
664
- `geometry.state` is `'available'` or `'partial'`.
665
-
666
- **Selection**: viewer presentation state only (React `useState`, reset on
667
- observation change), never persisted, synchronized in both directions
668
- between the target list, the SVG `<rect>` (`role="button"`, keyboard-
669
- operable), and the inspector via the target's existing stable `name`.
670
-
671
- **Overlay toggles**: geometry, labels (disabled when geometry is off), and
672
- relationships - each independently toggleable and purely presentational
673
- (hiding/showing already-rendered elements), never altering the underlying
674
- evidence or the fetched artifact/graph.
675
-
676
- **PWA cache boundary preserved**: the new `/api/observations/*` route lives
677
- under the same `/api/` prefix Batch 1's `navigateFallbackDenylist` already
678
- denylists - no service-worker configuration change was needed
679
- (`tests/unit/viewerPwaBuild.test.ts` asserts this against the real built
680
- `sw.js`).
681
-
682
- ## v0.8 Batch 4 (Before/after comparison and contract/change-scope inspection) — implemented
683
-
684
- Batch 4 exposes the existing v0.4 `ComparisonArtifact` and v0.5
685
- `FrontendContractEvaluationArtifact` through the viewer, entirely under
686
- `src/viewerServer/evidence/{linkedEvidence,comparisonView,evaluationView}.ts`
687
- and `viewer/src/components/{ComparisonWorkspace,EvaluationWorkspace,
688
- ComparisonObservationPane,ClauseResultRow}.tsx`. **`compareObservations` and
689
- `evaluateFrontendContract` are never called anywhere in this batch** - every
690
- displayed comparison/evaluation field is read unchanged from its persisted
691
- artifact via the existing Batch 2 `GET /api/artifacts/<handle>`.
692
-
693
- - **Exact linked-evidence resolution** (`evidence/linkedEvidence.ts`): given
694
- a `ComparisonSourceObservationReference`/`FrontendContractObservationReference`,
695
- a `comparisonId`+`comparisonRequestId` pair, or a `baselineId`/`contractId`,
696
- resolves the matching indexed artifact by **exact identity only**
697
- (`observationId`+`requestId`+`producer.version`+`observationSchemaVersion`
698
- for observations; the id fields themselves for comparisons/contracts) -
699
- never by folder name, screenshot filename, URL, target-set, or geometry
700
- similarity. Zero matches → `missing`; two or more exact matches →
701
- `ambiguous` (never silently picks one). Mirrors the exact bounded-walk
702
- pattern Batch 2's `findImportedReferenceDir` already established.
703
- - **Two additive, read-only routes**: `GET /api/comparisons/<handle>/view`
704
- (resolves the comparison's `before`/`after`) and
705
- `GET /api/evaluations/<handle>/view` (resolves `comparison`, `baseline`,
706
- `change`, `before`, `after`) - both under `/api/`, both GET/HEAD-only, both
707
- returning only resolved-handle-or-missing-or-ambiguous status, never a
708
- duplicated copy of the linked artifact's own payload (the browser fetches
709
- that separately through the existing `GET /api/artifacts/<handle>`, reusing
710
- Batch 2's on-demand-loading contract exactly).
711
- - **Before/after visual reuse, not reimplementation**: `ComparisonObservationPane.tsx`
712
- is built entirely from Batch 3's existing lower-level primitives
713
- (`useArtifactDetail`, `orderedTargets`, `TargetOverlaySvg`) - no second
714
- screenshot-loading, coordinate-transform, or geometry-rendering code
715
- exists. The comparison's own persisted `relationshipsBefore`/
716
- `relationshipsAfter` are passed directly into `TargetOverlaySvg`'s existing
717
- `relationships` prop - never recomputed via `deriveLayoutRelationships`.
718
- `TargetOverlaySvg` gained one small additive, optional `highlightNames`
719
- prop (alongside the existing single-select `selected`) so a
720
- relationship-subject difference or a two-target contract primitive
721
- (`targets-do-not-overlap`, `target-fits-inside`, etc.) can emphasize both
722
- named targets at once without changing Batch 3's existing single-select
723
- interaction contract.
724
- - **Difference/relationship-change/clause presentation is evidence display,
725
- not re-derivation**: `ComparisonWorkspace.tsx` renders `differences`,
726
- `relationshipChanges`, `configurationChanges` (kept visually distinct from
727
- appeared/disappeared runtime differences), and `expectedDependencyEvidence`
728
- exactly as persisted, labeling dependency outcomes as explicit non-causal
729
- evidence. `comparability` (comparable/comparable-with-warnings/incomparable
730
- plus blocking/warning/unassessed reasons) is shown honestly; an
731
- `incomparable` result is visually unmistakable
732
- (`.comparability-banner--incomparable`).
733
- - **Clause joining by exact `clauseId` only** (`EvaluationWorkspace.tsx`):
734
- baseline clauses (from the linked `PersistentBaselineContract`) and
735
- per-change clauses (from the linked `PerChangeContract`) are joined to the
736
- evaluation's `clauseResults` by exact id - never by target/primitive-shape/
737
- category/position. A `clauseId` absent from both loaded contracts is shown
738
- as an honest "unresolved clause definition", never fabricated. Baseline
739
- clause active/superseded status comes exclusively from the evaluation
740
- artifact's own `activeBaselineClauseIds`/`supersededBaselineClauseIds` -
741
- never recomputed from clause overlap. `pass`/`fail`/`unavailable`/
742
- `conflict` are preserved exactly (never collapsed to a boolean);
743
- `unavailable` shows its reason, `conflict` shows its reason and
744
- `conflictingClauseIds`.
745
- - **Overall verdict is authoritative and unmistakable**: `overallVerdict`
746
- (`PASS`/`FAIL`) is rendered directly from the artifact, in a large
747
- `.overall-verdict--PASS`/`.overall-verdict--FAIL` banner - the UI never
748
- computes it from visible rows. The required safety case (a `requested`
749
- clause `pass` alongside a `protected`/`preserved` clause `fail` still
750
- producing overall `FAIL`) and the all-pass case are both proven against
751
- real, canonically-evaluated fixtures (`tests/support/evidenceFixtures.ts#writeFullPipelineFixture`/
752
- `writeAllPassPipelineFixture`) in real Chromium
753
- (`tests/browser/comparisonEvaluationWorkspace.test.ts`) - `overallVerdict`
754
- is never hand-edited to construct either demonstration.
755
- - **Target/relationship cross-highlighting uses only explicit canonical
756
- identity**: `primitiveTargetNames()` (`viewer/src/contract/clauseTargets.ts`)
757
- extracts a contract primitive's named target field(s) (`target`, `targetA`/
758
- `targetB`, `target`+`container`, `subjectTarget`/`relatedTarget`) by an
759
- exhaustive switch over `ContractPrimitiveKind` - page-level primitives
760
- (`document-width-fits-viewport`, `scroll-owner-is-document`) return no
761
- names, so clicking them never fabricates a target highlight.
762
- - **PWA cache boundary preserved**: both new routes live under `/api/`,
763
- already covered by Batch 1's `navigateFallbackDenylist`; verified against
764
- the real built `sw.js`.
765
-
766
- ## v0.8 Batch 5 (External reference and reference/candidate inspection) — implemented
767
-
768
- - **Reference indexing/media already existed (Batch 2), unchanged**: the
769
- `external-reference-imported`/`external-reference-approved` families,
770
- `GET /api/media/<handle>/image` (imported), and
771
- `GET /api/media/<handle>/source-image` (approved, resolved through
772
- `findImportedReferenceDir`'s exact `referenceId` walk) were already built
773
- in Batch 2 and required no change here - Batch 5 only adds the visual
774
- workspace consuming them.
775
- - **Two new additive, read-only routes**
776
- (`src/viewerServer/evidence/referenceView.ts`):
777
- `GET /api/references/<handle>/view` derives the selected reference's own
778
- region-relationship graph (`deriveReferenceRegionRelationships`) and
779
- requirement adequacy (`deriveReferenceRequirementAdequacy`) - both pure
780
- functions over the artifact's own persisted `regions`/`requirements`,
781
- never persisted, never a second derivation engine (mirrors Batch 3's
782
- `getObservationRelationships` server-side-derivation pattern).
783
- `GET /api/references/<handle>/candidate/<handle>/view` evaluates
784
- reference/candidate compatibility through the existing canonical
785
- `evaluateReferenceCandidateCompatibility` (never a second, viewer-owned
786
- compatibility model) and separately lists every existing
787
- `FrontendContractEvaluationArtifact` whose own persisted `after` reference
788
- exactly identifies the candidate, for explicit, never-auto-selected
789
- optional display.
790
- - **Reference-image SVG coordinate model is a genuinely distinct domain from
791
- the candidate's runtime SVG** (`ReferenceRegionOverlaySvg.tsx`): `viewBox`
792
- is the reference image's own pixel dimensions (never the candidate's CSS
793
- viewport, never devicePixelRatio-multiplied); each region's canonical
794
- `{x, y, width, height}` is rendered unchanged. Because this is a different
795
- coordinate domain and data source from `TargetOverlaySvg` (runtime CSS
796
- pixels, `TargetGeometry`), it is a separate, sibling component rather than
797
- a parameterization of the existing one - reuse would have silently
798
- conflated the two domains. The candidate side, in contrast, reuses Batch
799
- 3/4's exact `ComparisonObservationPane`/`TargetOverlaySvg` machinery
800
- unchanged (a synthetic `{status:'resolved', handle}` `LinkStatus` is
801
- constructed once a candidate is explicitly chosen).
802
- - **Reference region selection and runtime target selection are two
803
- independent, never-synchronized selection domains**
804
- (`ReferenceWorkspace.tsx`): selecting a reference region never selects or
805
- highlights a runtime target, even when both happen to share the same
806
- string name (proven with a real Chromium fixture deliberately naming both
807
- `"header"` - `tests/browser/referenceCandidateWorkspace.test.ts`, Case E).
808
- No binding connector/highlight-across-panes exists in this batch - that is
809
- Batch 6's explicit-binding-interaction scope.
810
- - **Compatibility vs. reference adequacy vs. candidate fidelity are kept
811
- strictly distinct, never conflated**: compatibility
812
- (`comparable`/`comparable-with-warnings`/`incomparable` plus
813
- blocking/warning/unassessed reasons) comes only from
814
- `evaluateReferenceCandidateCompatibility`; reference-side requirement
815
- adequacy (`adequate`/`partial`/`inadequate`) comes only from
816
- `deriveReferenceRequirementAdequacy`; candidate fidelity is never computed
817
- in this batch at all - the UI always shows an explicit "not evaluated in
818
- this batch" note rather than ever implying a fidelity PASS from a
819
- compatibility PASS or an adequate reference (task §32/§33 boundary,
820
- `evaluateReferenceCandidateFidelity` is never imported/called anywhere in
821
- Batch 5).
822
- - **Optional contract/evaluation context is opt-in, never inferred**
823
- (`ReferenceWorkspace.tsx`): when the reference/candidate view lists more
824
- than one exactly-matching evaluation artifact, the developer must
825
- explicitly pick one from a `<select>` - the newest/first match is never
826
- silently chosen, and with zero selected the candidate's contract status
827
- reads "not selected/not available", never a fabricated PASS.
828
- - **Imported vs. approved image ownership preserved exactly as Batch 2 built
829
- it**: an imported reference's image is fetched from its own directory; an
830
- approved reference's image is fetched from its exact imported source via
831
- `sourceReference.referenceId`, never assumed co-located, never
832
- duplicated - proven with a real Chromium fixture asserting the two
833
- `<image href>` values resolve to the `image`/`source-image` roles
834
- respectively (Cases A/B).
835
- - **PWA cache boundary preserved**: both new routes live under `/api/`,
836
- already covered by Batch 1's `navigateFallbackDenylist`; verified against
837
- the real built `sw.js` (no new `registerRoute`, no `/api/references`
838
- precache entry).
839
-
840
- ## v0.8 Batch 6 (Explicit-binding interaction, zoom/pan, conditional lock, and on-demand reference fidelity) — implemented
841
-
842
- - **`view --bindings-file <json-file>`**: reuses the exact same operational
843
- binding-file wrapper parser (`loadBindingsFile` in `src/cli.ts`) that
844
- `evaluate-reference-fidelity --bindings-file` already used - one shared
845
- parser, never a second divergent one. The file is read once at startup;
846
- its declarations become `ViewerServerState.bindingDeclarations` (an opaque
847
- `unknown[]` until validated against a specific reference); the file path
848
- itself is never persisted, returned, or exposed to the browser. Reference-
849
- specific declaration validity (region existence, shape) is deferred to the
850
- moment a reference is actually selected server-side, via the existing
851
- canonical `isValidReferenceRuntimeBindingDeclarations` - never checked at
852
- startup without a reference.
853
- - **Two new additive, read-only routes**
854
- (`src/viewerServer/evidence/referenceView.ts`):
855
- `GET /api/references/<handle>/candidate/<handle>/bindings` validates the
856
- session's declarations against the selected reference and calls the
857
- existing canonical `evaluateReferenceRuntimeBindings` exactly once.
858
- `GET /api/references/<handle>/candidate/<handle>/fidelity` is the explicit
859
- on-demand fidelity trigger - calls the existing canonical
860
- `evaluateReferenceCandidateFidelity` exactly once, using the exact same
861
- session declarations, so its embedded `bindings` field and the `/bindings`
862
- route's own result always structurally agree for identical inputs (same
863
- pure function, same arguments). Neither route persists anything; both are
864
- `GET` (idempotent, deterministic, ephemeral over already-selected explicit
865
- input) - no mutation route was added.
866
- - **`deriveCoordinateScale` exported additively** from
867
- `externalReferenceFidelity.ts` (previously module-private) - Batch 6's
868
- view-lock eligibility reuses this exact function unchanged (same formula,
869
- same `ASPECT_RATIO_MAPPING_TOLERANCE`, same no-applicable-viewport
870
- failure) rather than a second aspect-ratio/scale implementation. The
871
- existing `GET /api/references/<handle>/candidate/<handle>/view` route now
872
- additionally returns `coordinateMapping: DeriveCoordinateScaleResult` -
873
- purely a function of the reference, independent of the candidate.
874
- - **Explicit-binding cross-selection uses only canonical
875
- `ReferenceRuntimeBindingResult.referenceRegion`/`.runtimeTarget` fields**
876
- (`ReferenceWorkspace.tsx`): selecting a `bound` reference region
877
- highlights (via `TargetOverlaySvg`'s existing Batch 4 `highlightNames`
878
- prop - never the primary `selected`/`aria-pressed` target) its exact
879
- declared runtime target; selecting a runtime target highlights every
880
- region whose `bound` result names it (many-to-one, via a new additive
881
- `highlightRegionIds` prop on `ReferenceRegionOverlaySvg`, matching
882
- `TargetOverlaySvg`'s established highlight pattern). `ambiguous`/
883
- `unavailable` results never cross-select. Proven with a real equal-name
884
- ("header" region + "header" target) Chromium fixture: no cross-selection
885
- without an explicit declaration, real cross-selection with one.
886
- - **Zoom/pan is a repository-owned, presentation-only hook**
887
- (`viewer/src/hooks/useZoomPan.ts`): bounded `[1x, 8x]` scale, `×1.25`/
888
- `÷1.25` step, expressed as one `{scale, focalX, focalY}` triple in the
889
- pane's own source-coordinate frame (reference-image pixels or candidate
890
- CSS pixels - never rewritten). Renders via an SVG `viewBox` override
891
- (additive `viewBoxOverride`/`svgRef`/pointer-handler props on
892
- `TargetOverlaySvg`/`ReferenceRegionOverlaySvg`, defaulting to Batch 3/5's
893
- exact prior behavior when omitted) - image and overlay stay one
894
- transformed unit automatically since both live inside the same `<svg>`
895
- root, and native SVG hit-testing means selection keeps working correctly
896
- under zoom/pan with no extra coordinate math. Panning uses the SVG
897
- element's own `getScreenCTM()` to convert screen-space pointer deltas into
898
- source-space deltas - reuses the browser's native transform rather than a
899
- custom aspect-ratio-aware pixel calculation - and only engages (calling
900
- `setPointerCapture`) once the pointer has moved past a small threshold, so
901
- an ordinary click on a region/target rect is never hijacked into a
902
- phantom drag. Fit and Reset are the same fitted-1x/centered default (task
903
- §26 - no second presentation-only default was introduced).
904
- - **View lock reuses one single shared state, never two independently
905
- synchronized states**: `ReferenceWorkspace.tsx` owns `refZoom`/`candZoom`
906
- directly (always-controlled `useZoomPan` calls) and each pane's zoom/pan
907
- action handler updates both states in one synchronous call when locked -
908
- no reactive effect watches one pane's state to update the other, so no
909
- feedback-loop risk exists. Lock is available only when a candidate is
910
- selected, compatibility is not `incomparable`, and `coordinateMapping.ok`
911
- is `true`; any change to that eligibility (including selecting a
912
- different reference/candidate) immediately disables lock and shows an
913
- actionable reason. Synchronization converts a candidate CSS-pixel focal
914
- point/scale into reference-image-pixel space (and back) using only
915
- `coordinateMapping.scale.scaleX`/`scaleY` - the exact same canonical
916
- factor `deriveCoordinateScale` already produces, applied as a straight
917
- multiply/divide (its own algebraic inverse), never a second mapping rule.
918
- - **Contract/fidelity independence is never collapsed into one status**:
919
- the existing Batch 5 "Optional contract/evaluation context" section
920
- (unchanged) and the new on-demand `ReferenceFidelityPanel` are two
921
- separate sections rendering two separate canonical results
922
- (`FrontendContractEvaluationArtifact.overallVerdict` and
923
- `ReferenceCandidateFidelityEvaluation.state`) side by side; when both are
924
- present, an explicit note states that fidelity does not override an
925
- active contract failure. No new coordinator/aggregate verdict is computed
926
- anywhere in this batch.
927
- - **PWA cache boundary preserved**: both new routes live under `/api/`,
928
- already covered by Batch 1's `navigateFallbackDenylist`.
929
-
930
- ## v0.8 Batch 7 (Bounded agent context, correlation, provenance, and raw evidence navigation) — implemented
931
-
932
- - **Bounded agent context remains programmatic-only**: no filesystem writer
933
- was added for `BoundedAgentContextArtifact` (it is still not an
934
- Observer-evidence-root artifact family - `src/viewerServer/evidence/classify.ts`'s
935
- "known but unreadered kind" comment is unchanged). `view --context-file
936
- <json-file>` reads exactly one already-serialized
937
- `BoundedAgentContextArtifact` value directly (no wrapper object) as
938
- explicit, session-only viewer input - read once at startup
939
- (`src/cli.ts#loadContextFile`, mirroring `loadBindingsFile`'s exact
940
- read/size-bound/parse shape), classified by
941
- `src/viewerServer/context.ts#classifyContextFileContent` (reuses the
942
- existing canonical `isValidBoundedAgentContextArtifact` - never a second
943
- validator), and held only in `ViewerServerState.context` for the life of
944
- the process. The file's size is bounded by the existing Batch 2
945
- `MAX_MANIFEST_CANDIDATE_BYTES` (2,000,000 bytes) rather than a second
946
- bound, since a context artifact's own frozen numeric caps already make it
947
- far smaller in any realistic case.
948
- - **Three honest session states, never coerced into one another**: `'none'`
949
- (no `--context-file`; every Batch 1-6 feature stays fully available),
950
- `'unsupported-version'` (recognized `artifactKind`, a `schemaVersion`
951
- other than the current one - the viewer still starts, showing this
952
- state explicitly rather than either failing or misinterpreting the
953
- fields), and `'valid'` (structurally validated current-schema context).
954
- Every other problem (unreadable file, wrong `artifactKind`, a
955
- structurally invalid *current*-schema artifact) fails viewer startup
956
- clearly - an explicitly supplied file is never silently ignored.
957
- - **`GET /api/context`** (`httpServer.ts`) returns the session's exact
958
- classified state; for `'valid'`, it additionally returns
959
- `sourceResolution` - the result of resolving
960
- `artifact.sources` against the current evidence root by **exact
961
- canonical identity only**
962
- (`src/viewerServer/evidence/contextSourceView.ts`, reusing/extending
963
- Batch 4's `linkedEvidence.ts` resolver pattern with three additive
964
- functions: `resolveObservationById` (bare `observationId`, the only
965
- identity a context source reference actually carries),
966
- `resolveEvaluationByIdentity`, and `resolveReferenceByIdentity`). Zero
967
- matches → `missing`; two or more → `ambiguous` (never silently picks
968
- one) - the same discipline every other Batch 4/5 resolver already
969
- established.
970
- - **Raw structured evidence reuses the existing Batch 2 artifact-detail
971
- route unchanged**: `RawEvidenceViewer.tsx` calls the existing
972
- `useArtifactDetail`/`GET /api/artifacts/<handle>` for any exactly-resolved
973
- source - no second full-artifact retrieval mechanism, no local filesystem
974
- read, no arbitrary path accepted from the browser.
975
- `EvidenceReference.path` values are always displayed as plain provenance
976
- text, never passed to `fs.readFile`/`path.resolve`/a static file server.
977
- - **Bounded runtime targets, adequacy, omissions, truncations, and
978
- correlation are rendered exactly as the validated artifact states them** -
979
- never recomputed, never boolean-collapsed
980
- (`ContextWorkspace.tsx`): `Adequacy.state`
981
- (`adequate`/`partial`/`inadequate`) and reasons are shown verbatim;
982
- absent bounded-target fields render "not included in this bounded
983
- context", never a fabricated falsy/zero value; `required: true`
984
- omissions/truncations render in a visually distinct
985
- `.context-required-loss` block, separate from optional ones;
986
- `correlations` absent renders "Static correlation not included in this
987
- context" - never "unavailable" (that status is reserved for a real
988
- per-target `RuntimeStaticCorrelationRecord` with zero candidates).
989
- `correlated`/`ambiguous`/`unavailable` are preserved exactly; a
990
- `correlated` record's one candidate is labeled "Correlated candidate", an
991
- `ambiguous` record shows **every** supplied candidate with none visually
992
- promoted, and `unavailable` fabricates zero candidates - matching the
993
- frozen `CORRELATION_STATUSES` invariants
994
- (`domain/boundedAgentContext.ts`) the validator itself already enforces.
995
- All new UI text was audited against ownership/edit-authorization language
996
- (no "owner"/"source owner"/"owned by") - correlation is presented as
997
- evidence, never as edit authorization.
998
- - **Context-target ↔ runtime-target interaction never infers source
999
- ownership**: selecting a bounded target or a correlation record uses only
1000
- exact `targetId`/`runtimeTargetId` string matching; for each *exactly
1001
- resolved* source observation, `SourceObservationTargetCheck` checks
1002
- membership in that observation's own already-fetched `targetEvidence`
1003
- (a plain lookup over already-loaded JSON, never a new derivation) and, if
1004
- more than one resolved source observation contains the same target id,
1005
- lists all of them rather than picking one.
1006
- - **Bounded reference-fidelity projection is never recomputed, and is kept
1007
- visibly distinct from a live on-demand evaluation**: absent `fidelity`
1008
- renders "Reference fidelity not included in this bounded context" - never
1009
- implied as passing. When present, `mismatches` and `protectedContext` are
1010
- rendered in separate sections from the artifact's own fields exactly as
1011
- supplied; a `state: 'not-evaluated'` blocked projection always shows
1012
- `blockedBy` prominently and never renders an empty mismatch list as "no
1013
- problems". When the context's `referenceId`/`candidateObservationId`
1014
- exactly resolve within the current evidence root, `ContextWorkspace.tsx`
1015
- embeds the existing, unchanged Batch 6 `ReferenceFidelityPanel` (the same
1016
- on-demand `evaluateReferenceCandidateFidelity` trigger) directly beneath
1017
- the bounded projection, labeled "Bounded context fidelity projection"
1018
- above and "Current on-demand fidelity evaluation" below - two separate,
1019
- clearly labeled evidence instances, never silently merged or replaced.
1020
- - **No runtime rebuild of context or correlation, and no my-dev-kit
1021
- execution from the shipped viewer**: grep-verified - `projectBoundedAgentContext(`,
1022
- `deriveRuntimeStaticCorrelations(`, and `attachRuntimeStaticCorrelations(`
1023
- appear nowhere under `src/viewerServer/` or `viewer/src/` (only in test/
1024
- fixture-generation code, per the frozen plan's explicit test-fixture
1025
- exception); no `child_process`/`npx @dailephd/my-dev-kit` invocation
1026
- exists in the viewer server or browser bundle.
1027
- - **PWA cache boundary preserved**: `GET /api/context` lives under `/api/`,
1028
- already covered by Batch 1's `navigateFallbackDenylist`.
1029
-
1030
- ## v0.8 Batch 8 (Integrated viewer acceptance, PWA hardening, and packaged proof) — implemented
1031
-
1032
- Batch 8 is the final v0.8 implementation batch. It is integration/hardening,
1033
- not a new architecture layer: no new API route, no new CLI flag, and no new
1034
- canonical-engine call site were added. See
1035
- `docs/reports/v0.8-integrated-viewer-acceptance-batch8.md` for the full
1036
- record.
1037
-
1038
- - **Closed three named real-browser coverage gaps**, each proved against the
1039
- actual built viewer through the actual loopback server, never a hand-edited
1040
- fixture verdict: many reference regions bound to one runtime target all
1041
- cross-highlight together (the pre-existing target→regions loop in
1042
- `ReferenceWorkspace.tsx` already iterated every matching binding - the gap
1043
- was in real-browser proof, not in the derivation); reference-fidelity
1044
- `fail` alongside a genuine frontend-contract `PASS` for the same candidate
1045
- display independently (the pre-existing independence note in
1046
- `ReferenceWorkspace.tsx` was already verdict-agnostic); a bounded context
1047
- whose sources include two observations sharing a stable target id lists
1048
- every matching source observation (the pre-existing
1049
- `SourceObservationTargetCheck` in `ContextWorkspace.tsx` already checked
1050
- membership per source independently, never picking one).
1051
- - **One real accessibility defect found and fixed**: a cross-highlighted,
1052
- non-selected region/target `<rect>` (`TargetOverlaySvg.tsx`,
1053
- `ReferenceRegionOverlaySvg.tsx`) exposed no accessible state distinguishing
1054
- it from a plain unselected rect - `aria-pressed` correctly stayed `false`
1055
- (it is not the primary single-selection), but nothing else communicated
1056
- the highlight to assistive technology. Fixed by adding
1057
- `data-highlighted="true"` and an `aria-label` suffix
1058
- (`" (highlighted: related to current selection)"`) when highlighted and
1059
- not selected, leaving `aria-pressed` semantics untouched.
1060
- - **First live-browser PWA proof suite** (`tests/browser/pwaHardening.test.ts`):
1061
- real service-worker registration and activation against the built shell;
1062
- the manifest fetched and confirmed `display: "standalone"`; zero Cache
1063
- Storage entries under any `/api/` pathname after normal use, confirming
1064
- the `navigateFallbackDenylist` boundary holds live, not just in the built
1065
- `sw.js` regex; and the hard server-down gate - after the server is closed
1066
- and the same page reloaded, the app shell still renders from the precache,
1067
- but the evidence-dependent surface shows the explicit
1068
- `.evidence-list__error` "Evidence index unavailable" state, with the
1069
- previously-visible evidence asserted absent. Install-control is proven
1070
- only via synthetic `beforeinstallprompt` dispatch (a genuine browser
1071
- install prompt was not observed under automation). Standalone-mode CDP
1072
- display-mode emulation was attempted but not observed to take effect -
1073
- recorded honestly, never overstated as actual OS-level installation proof.
1074
- - **Packaged-candidate proof**: `npm pack` → clean consumer install (outside
1075
- the repository) → the actually-installed CLI executable (not repo
1076
- `dist/cli.js`) → the installed `view` server → real Chromium against the
1077
- packaged/installed server, not a source-checkout dev server. Read-only
1078
- evidence-hash proof (SHA-256 of every file in the exercised evidence root,
1079
- taken before and after the packaged-browser session) confirmed no
1080
- mutation and no new viewer-created artifact anywhere in the evidence root.
1081
- - **Re-confirmed the no-second-engine invariant** across all eight batches by
1082
- re-running the exact `grep -rn` audit from earlier batches - unchanged
1083
- findings, no duplicate evidence engine exists.
1084
-
1085
- ## v0.9 visual annotation architecture (released in 0.9.0)
1086
-
1087
- v0.9 is released as package version `0.9.0`. It adds one new
1088
- evidence family and a narrow local authoring path to the existing viewer. It
1089
- adds no new evaluator, no second contract or reference model, and no new
1090
- PASS/FAIL semantics.
1091
-
1092
- **Evidence ownership**:
1093
-
1094
- - `src/domain/visualAnnotation.ts` owns `VisualAnnotationArtifact` (artifact
1095
- kind `my-frontend-observer/visual-annotation`, schema `1.0.0`): the source
1096
- union, structured marks, explicit associations, candidate/confirmed
1097
- interpretation, validation, and the pure overlay SVG renderer.
1098
- - `src/domain/visualAnnotationIdentity.ts` owns deterministic request identity
1099
- and fresh instance identity.
1100
- - `src/artifacts/visualAnnotationArtifactWriter.ts` and
1101
- `visualAnnotationArtifactReader.ts` own atomic persistence (temporary
1102
- `.tmp-<id>` directory, then rename) and canonical reading, including the
1103
- derived `annotation-overlay.svg` and its digest.
1104
- - `src/application/visualAnnotationPersistenceService.ts` owns saving one
1105
- annotation or one superseding revision.
1106
-
1107
- **Coordinate domains**: runtime annotations use the observation's runtime CSS
1108
- pixel space. Reference annotations use the reference image's own pixel space
1109
- (for an approved reference, the owning imported image). The two domains are
1110
- never mixed. Zoomed or panned drawing is mapped back through the SVG's own
1111
- transform (`viewer/src/svg/sourceCoordinates.ts`).
1112
-
1113
- **Viewer discovery and media** (`src/viewerServer/evidence/`): the index
1114
- classifies `visual-annotation` evidence and skips writer `.tmp-*`
1115
- directories. `annotationView.ts` resolves an annotation's exact canonical
1116
- source and reports `unavailable` instead of guessing a replacement. The media
1117
- resolver serves the `annotation-overlay` role only after re-rendering the SVG
1118
- from the artifact and verifying it, with a script-blocking sandbox policy.
1119
-
1120
- **Project-aware authoring security**: `src/viewerServer/authoringSecurity.ts`
1121
- owns the in-memory 32-byte session capability and the Host, Origin, token,
1122
- content-type, and content-encoding checks. `httpServer.ts` owns the shared
1123
- bounded JSON gate and routes exactly three `POST` responsibilities.
1124
- `view --root` never creates an authoring session, so the standalone
1125
- arbitrary-root viewer stays read-only.
1126
-
1127
- 1. `POST /api/annotations` (`src/viewerServer/annotationAuthoring.ts`) saves
1128
- a new annotation or a revision through the canonical persistence service,
1129
- with stale-parent conflict detection. This module also owns the single
1130
- per-session write queue used by all three routes.
1131
- 2. `POST /api/annotations/:handle/promote-contract`
1132
- (`src/viewerServer/annotationContractPromotion.ts`) calls
1133
- `src/application/visualAnnotationContractPromotionService.ts`. Selected
1134
- confirmed runtime intent becomes one canonical `PerChangeContract` through
1135
- the existing contract persistence service. Optional activation goes only
1136
- through `activateProjectChangeContract` in
1137
- `src/application/projectWorkflowService.ts`.
1138
- 3. `POST /api/annotations/:handle/materialize-reference`
1139
- (`src/viewerServer/annotationReferenceMaterialization.ts`) calls
1140
- `src/application/visualAnnotationReferenceMaterializationService.ts`.
1141
- Selected confirmed reference intent becomes a new imported
1142
- `ExternalReferenceArtifact` through the existing `importExternalReference`,
1143
- superseding the source. The image bytes come only from the safe media
1144
- resolver.
1145
-
1146
- Output locations come from `src/projectWorkflow/projectPaths.ts`
1147
- (`annotations`, `contracts`, and `references` under
1148
- `.frontend-observer/evidence`). The browser never supplies a path.
1149
-
1150
- **Viewer UI** (`viewer/src/`): `annotation/` holds pure presentation models
1151
- (`annotationGeometry.ts`, `runtimeIntent.ts`, `referenceIntent.ts`,
1152
- `confirmation.ts`). `components/AnnotationLayer.tsx`,
1153
- `AnnotationToolbar.tsx`, `RuntimeAnnotationPanel.tsx`,
1154
- `ReferenceAnnotationPanel.tsx`, `CandidateRegionPreviewLayer.tsx`, and
1155
- `AnnotationPanelSections.tsx` render marks, tools, intent, promotion, and
1156
- materialization. Hooks under `hooks/` hold draft, saved-list, session,
1157
- pointer, promotion, and materialization state. The authoring token lives only
1158
- in React memory.
1159
-
1160
- **Existing evaluators remain authoritative**: the canonical contract
1161
- evaluator, reference relationship derivation, requirement adequacy, reference
1162
- fidelity, and project `check` are unchanged. Annotation evidence feeds them
1163
- only through promoted contracts and materialized references.
1164
-
1165
- ## Retained v0.1 architecture constraints
1166
-
1167
- v0.1 planning preserved these approved boundaries without treating module
1168
- names from the historical run as mandatory:
1169
-
1170
- ```text
1171
- thin command-line boundary
1172
- ↓
1173
- reusable observation engine/application layer
1174
- ↓
1175
- browser automation boundary
1176
- ↓
1177
- observer-owned runtime evidence
1178
-
1179
- observer-owned domain/schema
1180
- ↓
1181
- artifact ownership boundary
1182
-
1183
- deterministic fixture/test boundary
1184
- ↓
1185
- browser-level validation
1186
- ```
1187
-
1188
- Use one browser engine implementation, keep browser logic out of presentation,
1189
- avoid speculative plugin/multi-browser abstractions, and keep observed
1190
- applications external. Versions before v0.6 did not add runtime coupling to
1191
- sibling ecosystem projects. v0.6 adds only explicit bounded context and
1192
- correlation/export contracts within this repository, preserving independent
1193
- ownership; orchestrator-consumption and lab-compatibility work are separate
1194
- sibling-repository deliverables, not part of this repository's architecture.
1195
-
1196
- The text/config-driven coding-agent workflow and the non-UI external-reference
1197
- evidence foundation are operational as of v0.7. The viewer and annotation
1198
- layers must consume the same canonical observation, relationship, comparison,
1199
- contract, change-scope, reference, correlation, and context boundaries rather
1200
- than creating parallel engines. The concrete implementation plan and module
1201
- layout for each future version must be designed only after that version's
1202
- planning workflow inspects the current repositories.
1203
-
1204
- v0.7 Prompt 1 implements only the bottom of that external-reference stack: a
1205
- new, standalone `ExternalReferenceArtifact` evidence root
1206
- (`src/domain/externalReference.ts`, `externalReferenceImage.ts`,
1207
- `externalReferenceIdentity.ts`, `src/artifacts/externalReferenceArtifact{Writer,Reader}.ts`,
1208
- `src/application/externalReferencePersistenceService.ts`) with its own
1209
- identity, provenance, bounded image metadata, and a two-state
1210
- (`imported`/`approved`) lifecycle - see `docs/CONTRACTS.md` "v0.7 Prompt 1
1211
- external-reference artifact contract" for the exact shape. It follows the
1212
- same identity/persistence/diagnostics/export conventions as every existing
1213
- artifact family (deterministic canonicalize-then-sha256 request identity,
1214
- nonce-based fresh instance identity, atomic temp-dir-then-rename persistence,
1215
- the shared `DIAGNOSTIC_CODES` vocabulary) without reusing or duplicating the
1216
- observation, comparison, or contract engines themselves - an external
1217
- reference is desired-design evidence, never an `ObservationArtifact`, an
1218
- approved baseline, or a runtime target.
1219
-
1220
- v0.7 Prompt 2 adds explicit reference regions and reusable reference-region
1221
- relationships on top of that foundation (`domain/externalReferenceRegions.ts`,
1222
- `externalReferenceRegionRelationships.ts`) - see `docs/CONTRACTS.md` "v0.7
1223
- Prompt 2 explicit reference regions and relationships" for the exact shape.
1224
- The relationship-derivation predicates are reused verbatim (now exported
1225
- additively) from `domain/relationships.ts` rather than reimplemented, so
1226
- reference-region geometry and runtime-target geometry can never diverge on
1227
- the same underlying formula; only the geometry-only relationship families
1228
- apply, since a static image exposes no DOM, scroll, or viewport evidence.
1229
- v0.7 Prompt 3 adds selected design requirements, tolerance semantics, and
1230
- reference-evidence adequacy on top of that region model
1231
- (`domain/externalReferenceRequirements.ts`,
1232
- `externalReferenceRequirementIdentity.ts`) - see `docs/CONTRACTS.md` "v0.7
1233
- Prompt 3 selected design requirements, tolerance semantics, and
1234
- reference-evidence adequacy" for the exact shape. Requirement categories are
1235
- the exact v0.5 `AuthoredChangeScopeCategory` vocabulary, imported directly
1236
- rather than reinvented, since that type carries no runtime-only coupling of
1237
- its own; tolerance is a genuinely new, reference-owned type (never a reuse
1238
- of `frontendContracts.ts`'s runtime/CSS-pixel-implicit `ContractTolerance`);
1239
- and reference-evidence adequacy is a small, independently-owned vocabulary
1240
- distinct from `boundedAgentContext.ts`'s runtime/static-correlation
1241
- `Adequacy`. A region property or derived relationship is never promoted to
1242
- an executable requirement automatically - only explicit user/configuration
1243
- selection does that.
1244
-
1245
- v0.7 Prompt 4 adds explicit reference applicability
1246
- (`domain/externalReferenceApplicability.ts`) and observation-side explicit
1247
- state identity (`domain/explicitState.ts`, shared by both artifact
1248
- families), plus one new pure domain module,
1249
- `domain/externalReferenceCompatibility.ts`, that evaluates whether a
1250
- candidate `ObservationArtifact` describes the same frontend state as a
1251
- given `ExternalReferenceArtifact` - see `docs/CONTRACTS.md` "v0.7 Prompt 4
1252
- reference applicability and candidate-state compatibility" for the exact
1253
- shape. This is page/state-level only, never geometry or fidelity, and
1254
- remains a wholly separate concern from Prompt 3's reference-evidence
1255
- adequacy - the two can independently disagree (an adequate reference can be
1256
- incomparable against a given candidate, and vice versa). Rather than
1257
- inventing a second comparability engine, Prompt 4 extracts one new exported
1258
- pure helper from v0.4's own `domain/comparisonEngine.ts`
1259
- (`assessOptionalComparabilityDimension`) and reuses it from both v0.4's
1260
- `evaluateComparability` (Observation-vs-Observation) and the new
1261
- `evaluateReferenceCandidateCompatibility` (Reference-vs-Observation) - the
1262
- one additive behavior change to v0.4 itself is that `evaluateComparability`
1263
- now assesses (rather than always reporting unassessed) theme/authenticated-
1264
- state/application-state whenever both observations declare
1265
- `requestConfig.explicitState`, while every historical/legacy observation
1266
- pair retains the exact prior unassessed-only behavior. No new persisted
1267
- artifact kind is introduced for the compatibility result; it is a pure,
1268
- on-demand function of two already-persisted artifacts.
1269
-
1270
- v0.7 Prompt 5 adds explicit reference-region <-> runtime-target binding
1271
- (`domain/externalReferenceRuntimeBinding.ts`) - see `docs/CONTRACTS.md`
1272
- "v0.7 Prompt 5 explicit reference-region <-> runtime-target binding" for
1273
- the exact shape. It answers only "which stable v0.2 runtime target does
1274
- this candidate observation resolve for each explicitly declared reference
1275
- region", strictly downstream of Prompt 4's compatibility gate (reused
1276
- verbatim, never duplicated) and strictly upstream of v0.6's own
1277
- runtime/static correlation - the two identity domains (a Prompt 2
1278
- `ReferenceRegion.id` and a v0.2 `NamedTarget.name`) never collapse into
1279
- each other, and this stage stops at the runtime target, never reaching
1280
- source ownership. Following v0.6's uncertainty discipline
1281
- (`domain/boundedAgentContextCorrelation.ts`), binding never guesses through
1282
- ambiguity - an ambiguously or unavailably resolved v0.2 target is reported
1283
- as such, never silently treated as bound - though the actual per-status
1284
- mapping (`bound`/`ambiguous`/`unavailable`) is binding's own, independently
1285
- owned vocabulary, not a reuse of v0.6's `correlated`/`ambiguous`/
1286
- `unavailable` correlation-status semantics (a different evidence boundary:
1287
- correlation ranks *static candidates* for one runtime target, whereas
1288
- binding resolves *one runtime target's own existence* for one declared
1289
- correspondence). No second target resolver, no browser execution, and no
1290
- new persisted artifact family were introduced; neither
1291
- `ExternalReferenceArtifact` nor `ObservationArtifact` is mutated to carry a
1292
- binding result, since a reference may later be evaluated against several
1293
- candidates and an observation against several references.
1294
-
1295
- v0.7 Prompt 6 adds structured reference-vs-candidate fidelity evaluation
1296
- (`domain/externalReferenceFidelity.ts`, `application/referenceFidelityEvaluationService.ts`,
1297
- and the `evaluate-reference-fidelity` CLI command) - the first point in this
1298
- stack where a reference's authored expectation is compared against live
1299
- candidate evidence. See `docs/CONTRACTS.md` "v0.7 Prompt 6 structured
1300
- reference-vs-candidate fidelity evaluation" for the exact shape. It is
1301
- downstream of every prior v0.7 prompt and reuses each verbatim: Prompt 3's
1302
- `deriveReferenceRequirementAdequacy`/`deriveReferenceRequirementExpectation`/
1303
- `deriveReferenceRequirementMeasurement` (never redefined), Prompt 4's
1304
- `evaluateReferenceCandidateCompatibility` (a hard gate, never duplicated),
1305
- and Prompt 5's `evaluateReferenceRuntimeBindings` (the sole source of
1306
- runtime-target identity - no automatic binding, no second target resolver).
1307
- It also reuses v0.4's `deriveLayoutRelationships` for runtime relationship
1308
- evidence, scoped to the exact requested relationship family (the same
1309
- Prompt 3 bug-fix precedent). The one genuinely new problem this prompt
1310
- solves is the reference-image-pixel <-> CSS-pixel coordinate mapping: a
1311
- single explicit, deterministic full-frame scale derived from
1312
- `reference.applicability.viewport` and the reference image's own
1313
- dimensions, with a tiny independent aspect-ratio-coherence check (never a
1314
- design tolerance) gating whether that mapping exists at all. No new
1315
- persisted artifact family, no browser execution, and no source-ownership
1316
- attribution - this is reference fidelity only, a separate concern from any
1317
- later v0.5 baseline/per-change contract result or v0.7 overall verdict.
1318
-
1319
- v0.7 Prompt 7 adds bounded reference-fidelity projection into the existing
1320
- v0.6 bounded-agent-context architecture (`domain/referenceFidelityProjection.ts`,
1321
- plus additive extensions to `domain/boundedAgentContext.ts`,
1322
- `domain/boundedAgentContextIdentity.ts`, and
1323
- `domain/boundedAgentContextProjection.ts`) - see `docs/CONTRACTS.md` "v0.7
1324
- Prompt 7 bounded reference-fidelity projection and v0.6 bounded-agent-
1325
- context integration" for the exact shape. `projectBoundedAgentContext`
1326
- itself, not a new parallel context system, gains one new optional input (an
1327
- already-computed Prompt 6 fidelity evaluation): fidelity-relevant runtime
1328
- targets fold into the exact same required/permitted-target-allocation,
1329
- evidence-tiering, omission/truncation, and adequacy machinery v0.5 contract
1330
- clauses already compete in, and a new `fidelity?:
1331
- BoundedReferenceFidelityProjection` field on `BoundedAgentContextArtifact`
1332
- (mirroring `correlations?`'s own additive, non-version-bumping precedent
1333
- from v0.6 Batch 3) carries a bounded, priority-ordered selection of Prompt
1334
- 6's non-passing requirement results plus passing protected/preserved
1335
- context. No second bounded-context architecture, no recomputation of Prompt
1336
- 2-6/v0.4/v0.5 logic, and no change to v0.6's own runtime/static correlation
1337
- (`deriveRuntimeStaticCorrelations`/`attachRuntimeStaticCorrelations` are
1338
- untouched and reused exactly as before) - a caller joins fidelity, target,
1339
- and correlation evidence by the one stable v0.2 runtime target id all three
1340
- already share. Every new field is optional and additive; a pre-Prompt-7
1341
- caller supplying no fidelity evidence receives byte-identical output,
1342
- including logical identity.
1343
-
1344
- v0.7 Prompt 8 adds the first complete, controlled end-to-end external-
1345
- reference correction workflow (`domain/referenceCorrectionWorkflow.ts`,
1346
- `domain/referenceCorrectionIdentity.ts`) - see `docs/CONTRACTS.md` "v0.7
1347
- Prompt 8 controlled end-to-end external-reference coding-agent correction
1348
- workflow" for the exact shape. This is a narrowly-scoped coordinator, not a
1349
- second workflow engine: it exposes exactly two pure operations -
1350
- `prepareReferenceCorrection` (pre-change evidence -> a bounded coding-agent
1351
- handoff, built from Prompt 1/3/4/5/6/7's existing engines) and
1352
- `reviewReferenceCorrectionAttempt` (a fresh post-edit candidate -> one
1353
- composed overall result, built from v0.4's `compareObservations`, v0.7
1354
- Prompt 6's `evaluateReferenceCandidateFidelity`, and v0.5's
1355
- `evaluateFrontendContract`) - with an explicit, un-automatable seam between
1356
- them where an external implementation actor edits target source. Overall
1357
- `'pass'` requires both reference fidelity `'pass'` and v0.5 contract
1358
- evaluation `'PASS'` - matching the selected design reference is necessary
1359
- but never sufficient, so a candidate that visually satisfies the reference
1360
- while regressing an active protected or preserved contract clause still
1361
- resolves to overall `'fail'`. Review identity is a deterministic hash of
1362
- `{referenceRequestId, baselineObservationId, baselineContractId,
1363
- baselineContractClauses, changeContractId, changeContractClauses,
1364
- bindingDeclarations}`; attempt identity is a deterministic hash of
1365
- `{reviewRequestId, candidateObservationId}`. `reviewReferenceCorrectionAttempt`
1366
- rejects any call whose supplied `reviewRequestId` does not match what its own
1367
- baseline/contract/reference/binding inputs recompute - the mechanism that
1368
- makes "every attempt evaluates against the same approved baseline" an
1369
- enforced invariant, not just a documented one. No new persisted artifact
1370
- family, no CLI surface, and - most importantly - no code path anywhere in
1371
- this module (or anything it calls) that opens, parses, or writes a target
1372
- source file: real candidate capture remains the caller's own responsibility
1373
- through the existing, unmodified real-Chromium observation pipeline.
1
+ # Architecture
2
+
3
+ ## v0.10 visual workflow boundaries
4
+
5
+ `VisualChangeWorkflowArtifact` (`1.0.0`) is the only new persisted v0.10
6
+ family. It owns frozen scope, explicit activation/restoration, immutable
7
+ attempt and review revisions, and optional references to governance results.
8
+ The Viewer is the human workflow entry; `checkProject` remains the canonical
9
+ evaluator. `VisualChangeAgentHandoff` (`1.0.0`) is a generated, non-persisted
10
+ transfer contract. An external human or coding agent edits source. Optional
11
+ orchestrator metadata is traceability only and Observer never imports or runs
12
+ the orchestrator. Human acceptance is separate from baseline/reference
13
+ governance and never rewrites prior workflow evidence.
14
+
15
+ ## v0.8.1 project workflow
16
+
17
+ Versioned project configuration (`1.0.0` compatibility plus current `1.1.0`
18
+ acceptance input), upward discovery, centralized managed paths, and the atomic
19
+ alias catalog live under `src/projectWorkflow`. Aliases select exact canonical
20
+ artifact directories; they never replace artifact identities.
21
+
22
+ Project `check` validates the selected baseline artifact before candidate
23
+ capture. The capture still takes URL, viewport, targets, and operational
24
+ defaults from current project configuration. Only the validated baseline's
25
+ optional `scrollScenario` and `explicitState` are replayed through the
26
+ existing normalized request and observation path. The project-config schema
27
+ does not own these fields. Explicit state remains declarative identity
28
+ metadata; replay does not establish application or session state.
29
+
30
+ `src/application/projectCheckService.ts` composes the existing observation,
31
+ comparison, contract-evaluation, reference-reader, explicit-binding,
32
+ compatibility, and fidelity owners. `src/projectWorkflow/checkAcceptance.ts`
33
+ owns contained acceptance-input resolution and shared file-wrapper parsing.
34
+ `checkResult.ts` owns the bounded ephemeral projection, not a persisted check
35
+ artifact or evaluation engine. Status precedence is `BLOCKED`, then `FAIL`,
36
+ then `PASS` when all configured executable dimensions pass, then
37
+ `REVIEW_REQUIRED` when comparison is the only evidence. Canonical observations,
38
+ comparisons, and contract evaluations remain persisted; reference fidelity and
39
+ the workflow result remain in memory/presentation.
40
+
41
+ ## Current package architecture
42
+
43
+ The current repository is one published TypeScript ESM package
44
+ (`@dailephd/my-frontend-observer@0.10.1`). The CLI remains
45
+ `my-frontend-observer`; the npm scope does not rename the product or artifact
46
+ identities.
47
+
48
+ - `src/cli.ts` is the real, thin public CLI parsing/dispatch/presentation
49
+ boundary for the current command surface (`observe`, `compare`,
50
+ `approve-baseline`, `save-change-contract`, `evaluate-contract`,
51
+ `import-reference`, `approve-reference`, `evaluate-reference-fidelity`,
52
+ `view`, `init`, `capture`, `check`); argument parsing and output formatting
53
+ only, per command - domain semantics remain owned by application/domain
54
+ services rather than the CLI. v0.6 added no new CLI command; v0.7 added the
55
+ three external-reference commands; v0.8 added `view`; v0.8.1 added `init`,
56
+ `capture`, and `check` and made `view` project-aware while preserving its
57
+ standalone `--root` behavior.
58
+ - `src/index.ts` is the library entry point re-exporting the observer-owned
59
+ contracts/functions from every layer below, including the v0.6 bounded-agent-
60
+ context projection and runtime/static correlation surface, and the v0.7
61
+ external-reference/region/requirement/applicability/compatibility/binding/
62
+ fidelity/correction-workflow surface (`prepareReferenceCorrection`/
63
+ `reviewReferenceCorrectionAttempt` remain programmatic-only, with no CLI
64
+ command).
65
+ - `scripts/clean.mjs` safely removes only the project `dist/` directory.
66
+ - `scripts/check-docs.mjs` validates the canonical documentation foundation,
67
+ roadmap version presence, and the no-batches rule.
68
+ - TypeScript, ESLint, Vitest, and package configuration provide foundation
69
+ validation, now exercised by real product tests (`tests/unit/`,
70
+ `tests/browser/`).
71
+
72
+ Batch 1 added the observation domain/schema and safety-policy layer
73
+ (`src/domain/`, `src/request/`, `src/safety/`). Batch 2 added a real
74
+ Playwright Chromium browser adapter (`src/browser/`), a minimal application
75
+ seam invoking it (`src/application/`), and a deterministic browser
76
+ fixture/test boundary (`tests/fixtures/`, `tests/browser/`, run via
77
+ `npm run test:browser`). Batch 3 extended that single browser adapter with an
78
+ internal page/target measurement module (`src/browser/evidenceCapture.ts`)
79
+ that reads page and explicit-CSS-target evidence from the same live,
80
+ already-ready page used for the screenshot - no second browser/page is ever
81
+ opened, and Playwright objects still never leave `src/browser/`. Batch 4
82
+ added the artifact ownership boundary itself: `src/artifacts/artifactWriter.ts`
83
+ is the one canonical place that writes an observation to disk (temp
84
+ directory, then one atomic rename into `<outputLocation>/<observationId>/`),
85
+ and `src/application/observationPersistence.ts` assembles the frozen
86
+ `ObservationArtifact` from a browser-capture result before handing it to the
87
+ writer. The artifact layer has no Playwright dependency and is testable
88
+ without launching Chromium. Batch 5 completed the boundary chain: `src/cli.ts`
89
+ parses `observe` arguments (CLI-syntax errors only - e.g. malformed
90
+ `WIDTHxHEIGHT`), constructs a raw request, and hands it to the existing
91
+ Batch 1 `normalizeRequest`; on success it calls one new application-level use
92
+ case, `observe()` in `src/application/observationPersistence.ts`, which runs
93
+ the existing `runBrowserCapture` exactly once and, only on success, the
94
+ existing artifact writer exactly once, then returns a small observer-owned
95
+ `ApplicationObservationResult` (observation id, completion state, artifact
96
+ path, target/diagnostic counts) for the CLI to print. The CLI never imports
97
+ Playwright or the filesystem-write path directly. Batch 6 closed the
98
+ remaining real-Chromium coverage gap (a genuine navigation failure, distinct
99
+ from a readiness timeout or a pre-launch safety rejection) and validated the
100
+ packed npm tarball end to end in a clean consumer environment, independent
101
+ of the source checkout. At the end of the v0.1 implementation there was no
102
+ controlled-scroll or comparison behavior.
103
+
104
+ ## Current v0.2 architecture (released/current architecture)
105
+
106
+ v0.2 extends the same architecture rather than adding a parallel one.
107
+ `src/request/request.ts` now owns a canonical `{name, locators}` target
108
+ model (`TargetLocator`, six frozen kinds) in place of the old CSS-only
109
+ shape; the legacy `{name, selector}` input still normalizes into it. The one
110
+ existing browser-side target resolver/measurement module,
111
+ `src/browser/evidenceCapture.ts`, was extended - not replaced - to resolve
112
+ all six locator kinds against the live page through a single Playwright
113
+ `Locator` per attempt, honor the frozen ordered-fallback/ambiguity/
114
+ unavailable-no-fallback contract, and converge every kind on the same
115
+ measurement path (`captureResolvedTargetRecord`); it additionally computes
116
+ bounded semantic state, derived landmark identity, and configured-target-
117
+ only DOM containment from the same already-resolved elements in the same
118
+ capture pass - no second browser/page, no second resolution algorithm.
119
+ `src/domain/schema.ts` extends `TargetEvidenceRecord`/`TargetResolution`
120
+ additively for schema `1.1.0`, with matching structural validation in
121
+ `isValidObservationArtifact`. `src/cli.ts` gained one CLI/input-boundary-
122
+ only addition, `--targets-file`: it reads and validates only the JSON root
123
+ wrapper (via the already-imported `node:fs`, never `node:fs/promises`) and
124
+ hands the parsed `targets` value into the existing `RawObservationRequest`/
125
+ `normalizeRequest()` path unchanged - there is no second application
126
+ observation use case, and Playwright objects still never leave
127
+ `src/browser/`. The artifact writer, application observation use case, and
128
+ overall boundary chain (`CLI → normalizeRequest → observe() →
129
+ runBrowserCapture → artifact writer`) are unchanged from v0.1.
130
+
131
+ ## Current v0.3 architecture (released/current architecture)
132
+
133
+ v0.3 extends the same single-observation architecture again; it does not add
134
+ a second browser lifecycle, target resolver, or artifact path.
135
+ `src/request/request.ts` adds one optional `scrollScenario` field to
136
+ `NormalizedObservationRequest` (`ScrollScenario { action }`, exactly
137
+ `window-scroll-by` or `target-scroll-by`); `src/domain/schema.ts` adds the
138
+ matching bounded runtime evidence types (`ScrollRuntimeSnapshot`,
139
+ `ViewportRelationEvidence`, `OverflowEvidence`, `ScrollScenarioTransition`,
140
+ `ScrollOwnerInterpretation`) and structural validation for schema `1.2.0`
141
+ (additive over `1.1.0`). `src/domain/scrollEvidence.ts` holds the pure,
142
+ browser-independent derivations (viewport relation, actual overflow,
143
+ transitions, and `deriveScrollOwner`) so they are unit-testable without
144
+ Chromium. `src/browser/scrollCapture.ts` holds the one browser-side scenario
145
+ capture module: it reuses `evidenceCapture.ts#resolveConfiguredTargets` (now
146
+ exported) to resolve configured targets exactly once, captures an initial
147
+ `ScrollRuntimeSnapshot`, performs the one immediate scroll
148
+ (`window.scrollBy`/`element.scrollBy`, both `behavior: 'instant'`), waits
149
+ exactly two `requestAnimationFrame` cycles, and captures a final snapshot -
150
+ all inside `chromiumAdapter.ts#captureViewportInternal`'s existing single
151
+ navigate → ready → capture flow, strictly before the unchanged
152
+ screenshot/`capturePageEvidence`/`captureTargetEvidence` calls, so every
153
+ downstream capture (including a no-scenario request, which skips this block
154
+ entirely) describes only the final state. `src/cli.ts` gained one CLI/input-
155
+ boundary-only addition, `--scroll-scenario-file`: mirroring
156
+ `--targets-file`, it reads and validates only the file readability/JSON-
157
+ validity/non-array-object-root shape and hands the parsed value straight
158
+ into `RawObservationRequest.scrollScenario` - every scenario/action rule
159
+ (kind, deltas, target reference) stays owned by `normalizeRequest()`. There
160
+ is still one canonical `observe()` application use case and one artifact
161
+ writer; `scrollScenarioEvidence` is simply one more optional field on the
162
+ same `ObservationArtifact`.
163
+
164
+ ## Current v0.4 architecture (released/current architecture)
165
+
166
+ v0.4 adds one new downstream pipeline that consumes `ObservationArtifact`
167
+ values rather than producing them - it never adds a second browser lifecycle,
168
+ target resolver, or observation engine:
169
+
170
+ ```text
171
+ ObservationArtifact before ObservationArtifact after
172
+ \ /
173
+ `--------. .-------'
174
+ \ /
175
+ artifact reader (src/artifacts/artifactReader.ts)
176
+ ↓
177
+ comparability evaluation (src/domain/comparisonEngine.ts)
178
+ ↓
179
+ canonical relationship derivation, called for each side independently
180
+ (src/domain/relationships.ts#deriveLayoutRelationships)
181
+ ↓
182
+ canonical comparison derivation
183
+ (src/domain/comparisonEngine.ts#compareObservations)
184
+ ↓
185
+ ComparisonArtifact
186
+ ↓
187
+ atomic comparison writer (src/artifacts/comparisonArtifactWriter.ts)
188
+
189
+ CLI `compare`
190
+ ↓
191
+ application service only (src/application/comparisonService.ts)
192
+ ↓
193
+ [reader → domain comparison → writer, as above]
194
+ ```
195
+
196
+ `src/domain/relationships.ts` froze the layout-relationship contract and
197
+ implements the one canonical pure derivation,
198
+ `deriveLayoutRelationships(observation, options?)`: horizontal/vertical
199
+ order, area overlap, relative width, geometric fit, vertical sequencing,
200
+ page-width fit/exceeds, and a standalone `deriveTargetClipping(record)` -
201
+ all computed only from an already-captured `ObservationArtifact`'s own
202
+ `targetEvidence`/`pageEvidence`, never from a second browser query. DOM
203
+ containment is read directly from the existing v0.2 `TargetContainment`
204
+ evidence rather than re-derived, and stays a distinct concept from
205
+ geometric fit.
206
+
207
+ `src/domain/comparisonEngine.ts` implements the one canonical pure
208
+ before/after engine, `compareObservations(before, after, config?)`:
209
+ validates both source artifacts, evaluates comparability *before* any
210
+ rendered difference is calculated, calls `deriveLayoutRelationships` once
211
+ per side with the same tolerance, and derives target/page differences and
212
+ relationship changes. `src/artifacts/comparisonArtifactWriter.ts` persists
213
+ the result atomically (sibling temp directory, then one rename) as
214
+ `<outputLocation>/<comparisonId>/manifest.json` only - no screenshot is
215
+ copied; the manifest's `before`/`after` references point back to the
216
+ source observations' own `screenshot.path`. `src/application/
217
+ comparisonService.ts` is the one application-layer seam: `compareAndPersist`
218
+ takes two in-memory `ObservationArtifact`s and does exactly one comparison
219
+ plus exactly one persist; `compareAndPersistFromArtifactRoots` is a thin
220
+ wrapper that additionally reads both sides from disk via the existing
221
+ `src/artifacts/artifactReader.ts#readObservationArtifact` reader (itself
222
+ just a `manifest.json` parse plus the same `isValidObservationArtifact`
223
+ structural gate the writer uses).
224
+
225
+ `src/cli.ts` gained one new top-level command, `compare`
226
+ (`--before`/`--after`/`--output`/`--config-file`), implemented with the same
227
+ thin-CLI-boundary discipline as `observe`: `parseCompareArgs` handles only
228
+ argument shape/duplication, an optional `loadComparisonConfigFile` reads and
229
+ validates only file readability/JSON-validity/non-array-object-root (exactly
230
+ like `--targets-file`/`--scroll-scenario-file`), and the command body calls
231
+ `compareAndPersistFromArtifactRoots` exactly once. **The CLI's comparison
232
+ path never launches Chromium** - `src/cli.ts` imports nothing from
233
+ `src/browser/` or `src/artifacts/` (it only reaches persistence and artifact
234
+ reading indirectly, through the application-layer seam above), matching the
235
+ same import-boundary discipline already enforced for `observe`.
236
+
237
+ ## Current v0.5 architecture (released/current architecture)
238
+
239
+ v0.5 adds one new downstream layer that consumes `ComparisonArtifact` values
240
+ (plus the source `ObservationArtifact` pair) rather than producing them - no
241
+ new browser lifecycle, target resolver, or comparison engine is added:
242
+
243
+ ```text
244
+ ObservationArtifact before + after
245
+ ↓
246
+ existing v0.4 comparison/relationship pipeline (unchanged)
247
+ ↓
248
+ ComparisonArtifact
249
+ ↓ PersistentBaselineContract
250
+ | +
251
+ `------------------------→ PerChangeContract
252
+ ↓
253
+ canonical contract evaluation
254
+ (src/domain/frontendContractEvaluation.ts#evaluateFrontendContract)
255
+ ↓
256
+ clause results + unexpected changes + overall PASS/FAIL
257
+ ```
258
+
259
+ `src/domain/frontendContracts.ts` froze the contract/change-scope type,
260
+ constant, and structural-validator vocabulary (Batch 1); `src/domain/
261
+ frontendContractIdentity.ts` froze deterministic contract/baseline/clause
262
+ identity in the same canonicalize+sha256(+opaque-nonce) style as `src/domain/
263
+ comparisonIdentity.ts`. `src/domain/frontendContractEvaluation.ts#evaluateFrontendContract`
264
+ (Batch 2) is the one canonical pure evaluation entry point: it validates its
265
+ five inputs (before/after `ObservationArtifact`, `ComparisonArtifact`,
266
+ `PersistentBaselineContract`, `PerChangeContract`) are structurally coherent
267
+ and mutually consistent, calculates the active baseline clause set after
268
+ explicit supersession, detects bounded structural conflicts, evaluates every
269
+ active clause via the frozen 15-primitive vocabulary against the existing
270
+ `ComparisonArtifact`/`ObservationArtifact` evidence (never re-deriving
271
+ clipping/relationship/scroll-owner facts), classifies unaccounted-for
272
+ `ComparisonArtifact.differences` entries as `unexpected`, and derives one
273
+ overall `PASS`/`FAIL` verdict. It performs no I/O, launches no browser, and
274
+ persists nothing - `src/domain/frontendContractEvaluation.ts` itself remains
275
+ untouched by the persistence layer below (Batch 3).
276
+
277
+ Batch 3 adds the persistence/application boundary around this frozen domain,
278
+ without redefining it:
279
+
280
+ ```text
281
+ ComparisonArtifact (read via new src/artifacts/comparisonArtifactReader.ts)
282
+ +
283
+ PersistentBaselineContract / PerChangeContract
284
+ (read/written via src/artifacts/frontendContractArtifactReader.ts / ...Writer.ts)
285
+ ↓
286
+ src/application/frontendContractEvaluationService.ts#evaluateAndPersist
287
+ ↓
288
+ evaluateFrontendContract() [called exactly once, unmodified]
289
+ ↓
290
+ src/domain/frontendContractEvaluationArtifact.ts
291
+ (minimal additive persisted envelope around the frozen result)
292
+ ↓
293
+ src/artifacts/frontendContractEvaluationArtifactWriter.ts
294
+ (atomic write, exactly once on a structurally constructible result)
295
+ ```
296
+
297
+ `evaluateAndPersistFromArtifactRoots` is the CLI-facing wrapper, reading
298
+ before/after observations through the existing `readObservationArtifact` (no
299
+ second observation reader), the comparison and both contract classes
300
+ through the new readers, then delegating to `evaluateAndPersist` exactly
301
+ once - mirroring `application/comparisonService.ts#compareAndPersistFromArtifactRoots`'s
302
+ own thin-wrapper shape.
303
+
304
+ Batch 4 exposes this through the same thin-CLI boundary already established
305
+ by `observe`/`compare`:
306
+
307
+ ```text
308
+ src/cli.ts (argument parsing, JSON-file-shape checks, help/output formatting,
309
+ exit-code selection only)
310
+ ↓
311
+ src/application/frontendContractPersistenceService.ts#approveAndPersistBaseline
312
+ src/application/frontendContractPersistenceService.ts#persistPerChangeContract
313
+ src/application/frontendContractEvaluationService.ts#evaluateAndPersistFromArtifactRoots
314
+ ↓
315
+ domain validators (isValidPersistentBaselineContract / isValidPerChangeContract)
316
+ + artifact readers/writers (Batch 3)
317
+ + evaluateFrontendContract() (Batch 2, unmodified)
318
+ ```
319
+
320
+ Three new top-level commands - `approve-baseline`, `save-change-contract`,
321
+ `evaluate-contract` - each parse only CLI-syntax concerns (duplicate/missing
322
+ flags, JSON-file readability/parseability/object-root shape) and delegate to
323
+ exactly one application-layer call; `src/cli.ts` imports no artifact writer/
324
+ reader module and no browser code, matching the existing `observe`/`compare`
325
+ import-boundary discipline exactly. `approveAndPersistBaseline` adds the one
326
+ new coherence check Batch 3 did not need: verifying a baseline contract's
327
+ frozen `sourceObservation` reference actually matches the supplied
328
+ observation artifact before persisting - explicit approval only, never
329
+ inferred from a `compare` or `evaluate-contract` result. `--enforce` on
330
+ `evaluate-contract` is applied only after evaluation and persistence have
331
+ already completed; it selects the process exit status for an already-final
332
+ `FAIL` result and is never part of any identity or persisted field.
333
+
334
+ ## Current v0.6 architecture (released as `0.6.0`)
335
+
336
+ v0.6 adds one new downstream, read-only layer that consumes existing v0.1-v0.5
337
+ evidence (`ObservationArtifact`, `ComparisonArtifact`,
338
+ `PersistentBaselineContract`/`PerChangeContract`, and evaluation results)
339
+ plus caller-supplied bounded static candidate evidence - it adds no new
340
+ browser lifecycle, target resolver, observation/comparison/contract engine,
341
+ or persisted artifact family:
342
+
343
+ ```text
344
+ ObservationArtifact(s) + ComparisonArtifact + contract/evaluation evidence
345
+ ↓
346
+ src/domain/boundedAgentContextProjection.ts#projectBoundedAgentContext
347
+ ↓
348
+ BoundedRuntimeTargetProjection
349
+ (page/viewport identity, stable targets, geometry, runtime behavior,
350
+ relationships, before/after differences, contract results,
351
+ requested/expected-dependent/protected/preserved scope reused verbatim
352
+ from src/domain/frontendContracts.ts, diagnostics, artifact/screenshot
353
+ references, provenance, adequacy, omission, truncation)
354
+ ↓
355
+ src/domain/boundedAgentContextCorrelation.ts
356
+ #deriveRuntimeStaticCorrelations / #attachRuntimeStaticCorrelations
357
+ ↓
358
+ RuntimeStaticCorrelation[] (correlated / ambiguous / unavailable,
359
+ competing candidates preserved verbatim - never collapsed to one owner)
360
+ ↓
361
+ src/index.ts (public export/correlation boundary only)
362
+ ```
363
+
364
+ `src/domain/boundedAgentContext.ts` freezes the bounded-projection and
365
+ correlation type/constant vocabulary (`Adequacy`, `ADEQUACY_REASON_CODES`,
366
+ `OmissionRecord`, `TruncationRecord`, `BOUNDED_AGENT_CONTEXT_ARTIFACT_KIND`,
367
+ schema `1.0.0`). `src/domain/boundedAgentContextIdentity.ts` derives a
368
+ deterministic logical identity distinct from a fresh per-execution instance
369
+ identity, in the same canonicalize+hash style as
370
+ `comparisonIdentity.ts`/`frontendContractIdentity.ts`.
371
+ `boundedAgentContextProjection.ts` performs no browser I/O and re-derives
372
+ nothing already owned upstream - it reads already-captured artifacts and
373
+ reuses the existing v0.4 relationship/comparison evidence and v0.5
374
+ change-scope clause types directly. `boundedAgentContextCorrelation.ts`
375
+ accepts only plain, caller-supplied candidate static-evidence records; it has
376
+ **no** dependency on `@dailephd/my-dev-kit`, since the audit that preceded
377
+ implementation found no generic static-side retrieval capability actually
378
+ missing (see `docs/ROADMAP.md` v0.6 "Dependency direction" - the "determine
379
+ whether my-dev-kit requires a static-side change" step concluded no).
380
+ Runtime target identity is carried through this module verbatim; the module
381
+ never adds a `sourceOwner`/`causedBy`-shaped field, preserving the
382
+ architectural rule that runtime identity never silently becomes source
383
+ ownership.
384
+
385
+ This layer is a programmatic export/correlation boundary only: `src/index.ts`
386
+ re-exports its full type/function surface, but there is no new CLI command,
387
+ no `src/artifacts/boundedAgentContext*` writer/reader, and no orchestrator or
388
+ lab code in this repository - those remain separate sibling-repository
389
+ responsibilities per the Milestone 6 ownership split in
390
+ `docs/PROJECT_MILESTONES.md`.
391
+
392
+ ## v0.7-v0.10 reference-evidence architecture constraints
393
+
394
+ The external visual-reference capability (v0.7) is released as package
395
+ version `0.7.0` - see "v0.7 Prompt 1" through "v0.7 Prompt 8" below for the
396
+ actual architecture, and `docs/CURRENT_STATE.md` for release state. It
397
+ extends the existing v0.1-v0.6 evidence architecture rather than becoming a
398
+ UI-only feature or a parallel visual-comparison stack. v0.8 (interactive
399
+ viewer) is released as package version `0.8.0`. v0.9 (structured visual
400
+ annotation) is released as package version `0.9.0` - see "v0.9 visual
401
+ annotation architecture" below. v0.10 (full graphical human-LLM workflow) is
402
+ released as `0.10.0`; v0.10.1 adds narrow project-check baseline context
403
+ replay without changing these evidence domains. The constraints below apply
404
+ to v0.9 and v0.10.
405
+
406
+ The evidence domains remain distinct:
407
+
408
+ ```text
409
+ runtime observation A ↔ runtime observation B
410
+ → existing before/after comparison
411
+
412
+ approved baseline/per-change contract ↔ candidate runtime evidence
413
+ → existing canonical contract evaluation
414
+
415
+ external visual reference ↔ candidate runtime evidence
416
+ → reference applicability + structured fidelity evaluation (v0.7,
417
+ released as `0.7.0` - see "v0.7 Prompt 4" and "v0.7 Prompt 6" below)
418
+ ```
419
+
420
+ An external reference is not an `ObservationArtifact`, and a reference region
421
+ is not a runtime target. The released v0.7 implementation preserves explicit
422
+ identity and provenance for the reference image/version, reference regions,
423
+ applicable viewport/theme/application state, authored requirements, tolerances,
424
+ approval/supersession state, and reference-region/runtime-target bindings.
425
+ Bindings are explicit and evaluate to `bound`, `ambiguous`, or `unavailable`;
426
+ they never silently become source ownership.
427
+
428
+ The non-UI reference model and structured reference-vs-candidate evaluation
429
+ were established in v0.7 before v0.8. v0.8 may render side-by-side images,
430
+ overlays, measurements, bindings, provenance, and fidelity results, but it must
431
+ consume those existing engines and must not invent a second reference model or
432
+ evaluation engine. v0.9 may author annotations against either runtime
433
+ screenshots or external references, but both coordinate/identity domains remain
434
+ explicit and feed the same canonical contract/change-scope semantics. v0.10
435
+ combines both visual entry modes with the existing correction loop.
436
+
437
+ Where a reference requirement is executable, it uses the existing v0.5
438
+ requested/expected-dependent/protected/preserved semantics. Informational or
439
+ unassessed reference evidence remains non-executable until explicitly selected.
440
+ There is no reference-only PASS/FAIL taxonomy.
441
+
442
+ The released reference evaluation reuses existing relationship/value
443
+ conventions where they mean the same thing and adds distinct reference-owned
444
+ units only where the image evidence requires them. v0.7 does not use pixel or
445
+ image-region similarity as a success mechanism; a later bounded similarity
446
+ feature may supplement structured evidence if separately designed, but it must
447
+ not replace browser-authoritative runtime geometry, canonical contract
448
+ evaluation, or explicit relationship evidence.
449
+
450
+ Candidate rendering still uses the one existing Chromium observation engine.
451
+ The observer remains non-mutating. `my-dev-kit` remains the static/source
452
+ evidence owner, and the v0.6 correlation/bounded-context boundary remains the
453
+ route for attaching relevant source evidence to reference-driven correction
454
+ packets. Heavy reference image bytes are referenced rather than copied into
455
+ every downstream context/evaluation record.
456
+
457
+ Theme, application-state, viewport, and authenticated-state applicability are
458
+ checked before reference fidelity is interpreted through the released v0.7
459
+ compatibility path, which reuses v0.4 comparability conventions. If reference
460
+ and candidate do not represent compatible intended states, the result is
461
+ explicitly incompatible/incomparable rather than a fabricated visual difference
462
+ set. v0.8, released as package version `0.8.0`, displays this result exactly
463
+ as required rather than redefining the state model - see "v0.8 Batch 5"
464
+ below.
465
+
466
+ The constraints above were carried out by the actual v0.7 implementation
467
+ described in "v0.7 Prompt 1" through "v0.7 Prompt 8" below: explicit
468
+ identity/provenance, applicability/compatibility, region-to-target bindings,
469
+ requested/expected-dependent/protected/preserved reuse, and the non-mutating
470
+ Chromium/correlation boundaries all remain as constrained here. v0.8 (see
471
+ "v0.8 Batch 1" through "v0.8 Batch 8" below) applied them unchanged, and so
472
+ does the implemented v0.9 annotation layer. They continue to apply unchanged
473
+ to the still-future v0.10 work.
474
+
475
+ The exact public artifact names, schema versions, persistence layout, supported
476
+ image formats, coordinate model, requirement/tolerance primitives, and fidelity
477
+ behavior were frozen by the actual v0.7 implementation below, not by earlier
478
+ planning language. Style/asset-similarity mechanisms remain future unless
479
+ separately implemented.
480
+
481
+ ## v0.8 Batch 1 (Viewer runtime and PWA foundation) — implemented
482
+
483
+ Batch 1 of the frozen `docs/plans/v0.8-implementation-plan.md` establishes
484
+ only the viewer runtime/build shell — no evidence indexing, artifact reading,
485
+ or evidence UI. It does not implement any of the v0.7-derived reference/
486
+ fidelity/binding display constraints above; those remain future work for
487
+ later v0.8 batches, which must consume this runtime boundary rather than
488
+ redefine it.
489
+
490
+ ```text
491
+ my-frontend-observer view [--root <evidence-root>]
492
+ |
493
+ v
494
+ thin CLI dispatch (src/cli.ts: parseViewArgs/runViewCommand)
495
+ |
496
+ v
497
+ viewer application seam (src/viewerServer/viewerService.ts: startViewer)
498
+ |
499
+ v
500
+ Node local server, loopback-only (src/viewerServer/httpServer.ts)
501
+ |
502
+ +---------------------+----------------------+
503
+ | |
504
+ v v
505
+ built viewer assets (dist/viewer) GET /api/status
506
+ (React + TypeScript + Vite PWA) (session/root identity only)
507
+ ```
508
+
509
+ - **Node server boundary** (`src/viewerServer/`): binds only to `127.0.0.1`
510
+ on one fixed default port (`4319`, `src/viewerServer/port.ts`); serves only
511
+ the built viewer assets plus the one read-only status endpoint; resolves
512
+ every requested path against the built assets root and fails closed on any
513
+ path that would resolve outside it; accepts no write HTTP methods; performs
514
+ no artifact reading, browser observation, or mutation. `--root` is
515
+ validated operationally (exists, is a directory) and exposed only as an
516
+ opaque status string — it is never interpreted as Observer evidence in this
517
+ batch.
518
+ - **Browser application** (`viewer/`): a React + TypeScript + Vite app, built
519
+ independently of `src/` via `viewer/tsconfig.json` and `viewer/vite.config.ts`,
520
+ output to `dist/viewer` inside the existing package `dist` allowlist (no
521
+ second npm package). Renders an honest foundation shell only — product
522
+ identity, live session status via `/api/status`, and placeholder
523
+ navigation/workspace/details regions — never fabricated evidence.
524
+ - **PWA**: `vite-plugin-pwa` generates a web app manifest (`standalone`
525
+ display, stable `start_url`/`scope`, installability icons) and a service
526
+ worker that precaches only the built application shell. It declares no
527
+ `runtimeCaching` rules, so future evidence/media/API routes remain
528
+ network/server-backed rather than silently served as stale cached truth
529
+ when the local server is unavailable (enforced by
530
+ `tests/unit/viewerPwaBuild.test.ts`, which asserts on the actual built
531
+ `sw.js`, not a hand-written approximation). An install affordance appears
532
+ only when the browser actually fires `beforeinstallprompt`; its absence is
533
+ shown honestly, never as a disabled-looking fake control.
534
+ - **CLI**: `view [--root <evidence-root>] [--port <n>] [--no-open]` remains a
535
+ thin dispatcher — it parses syntax, delegates once to `startViewer`, prints
536
+ the URL/root, and optionally best-effort opens the system browser (failure
537
+ there is never fatal to server startup). All v0.1-v0.7 commands are
538
+ unchanged.
539
+
540
+ This batch introduces no second observer, relationship engine, comparison
541
+ engine, contract engine, reference model, or bounded-context builder — there
542
+ is nothing yet for the viewer to consume beyond its own runtime identity.
543
+
544
+ ## v0.8 Batch 2 (Evidence indexing, canonical readers, and lazy data boundary) — implemented
545
+
546
+ Batch 2 adds the safe, read-only data boundary between existing on-disk
547
+ Observer evidence and the Batch 1 viewer runtime, entirely under
548
+ `src/viewerServer/evidence/`. It introduces no new persisted artifact family,
549
+ no schema migration, and no second validator — every recognized candidate is
550
+ decided exclusively by the existing canonical reader/validator for its
551
+ family (`src/artifacts/*Reader.ts`).
552
+
553
+ ```text
554
+ GET /api/index bounded discovery + classification -> metadata only
555
+ GET /api/artifacts/<handle> one canonical-reader read, on demand -> full projection
556
+ GET /api/media/<handle>/<role> one resolved, contained media file, streamed on demand
557
+ ```
558
+
559
+ - **Discovery** (`evidence/discovery.ts`): a bounded, deterministic walk
560
+ beneath `--root` that opens only files literally named `manifest.json` (the
561
+ one filename every current persisted family uses) — no other file is ever
562
+ read or classified, so arbitrary files can never become evidence merely by
563
+ existing under the root. Directory entries that are symlinks/junctions are
564
+ never followed. Bounds (`evidence/limits.ts`): traversal depth 6,
565
+ directories visited 2000, candidate manifests 1000, index records 500,
566
+ manifest read size 2,000,000 bytes — chosen after inspecting that every
567
+ current writer produces a shallow `<outputLocation>/<id>/manifest.json`
568
+ shape (see `docs/CONTRACTS.md`), not a deep tree.
569
+ - **Classification is not validation** (`evidence/classify.ts`): peeks only
570
+ `artifactKind`/`schemaVersion` (plus, where the shared kind is ambiguous,
571
+ tries each existing reader/validator in turn — e.g. baseline vs. per-change
572
+ contract) to decide *which* existing canonical reader to call; the reader's
573
+ own structural validator remains the sole authority. Six honest, mutually
574
+ exclusive states: `supported`, `unsupported-version`, `invalid-structure`,
575
+ `unrecognized-kind`, `malformed-json`, `unreadable` — never collapsed into
576
+ one boolean, and never conflated with an artifact's own `completion`
577
+ state (passed through separately, only for the families that carry one:
578
+ observation and external-reference). A manifest declaring the
579
+ `bounded-agent-context` kind is classified `unrecognized-kind`: v0.6/v0.7
580
+ never added a disk writer/reader for that family (confirmed via direct
581
+ source inspection and `@dailephd/my-dev-kit` search), so Batch 2 does not
582
+ invent persistence-shaped handling for it.
583
+ - **Viewer handles** (`evidence/handles.ts`, `evidence/pathSafety.ts`): a
584
+ handle is a family-prefixed, percent-encoded, root-relative directory path
585
+ — never a raw filesystem path accepted from the browser. Every route that
586
+ accepts a handle re-decodes and re-resolves it against the evidence root,
587
+ re-checks containment, and re-classifies that one candidate before serving
588
+ anything; a handle whose backing directory or manifest no longer matches
589
+ what was indexed fails closed as unknown, never stale.
590
+ - **Ephemeral projection** (`evidence/projection.ts`): `EvidenceMetadataRecord`
591
+ (bounded, `/api/index`-shaped: handle, family, support state, logical id,
592
+ schema version, completion where applicable, media availability summary,
593
+ a handful of related ids) and `EvidenceArtifactDetail` (the already-
594
+ validated domain object, wrapped with `handle`/`family` — no new evidence
595
+ schema, no recomputation, no persistence).
596
+ - **Media resolution** (`evidence/mediaResolver.ts`): `screenshot` (observation),
597
+ `image` (imported external reference), and `source-image` (approved
598
+ external reference) are the only three recognized roles. An approved
599
+ reference's image is never assumed to live in the approved artifact's own
600
+ directory — its `sourceReference.referenceId` is looked up against the
601
+ current index to find the actual owning imported artifact
602
+ (`evidence/index.ts#findImportedReferenceDir`), exactly matching the v0.7
603
+ reference-ownership contract in `docs/CONTRACTS.md`. A genuinely missing
604
+ screenshot/image/source artifact is reported as 404, never fabricated.
605
+ - **PWA cache boundary preserved, not re-verified from scratch**: every new
606
+ route lives under `/api/`, already covered by Batch 1's
607
+ `denylist:[/^\/api\//]` navigation-fallback rule — no new `runtimeCaching`
608
+ entry was needed or added (`tests/unit/viewerPwaBuild.test.ts` asserts this
609
+ against the real built `sw.js`).
610
+ - **Minimal UI** (`viewer/src/hooks/useEvidenceIndex.ts`,
611
+ `useArtifactDetail.ts`, `components/EvidenceList.tsx`,
612
+ `ArtifactPreview.tsx`): a bounded evidence list (metadata-first) plus
613
+ on-demand full-artifact loading on selection, with every support state
614
+ shown honestly. No screenshot rendering, SVG overlay, or comparison/
615
+ contract/reference visualization exists yet — that begins in Batch 3.
616
+
617
+ ## v0.8 Batch 3 (Runtime observation inspection and SVG overlays) — implemented
618
+
619
+ Batch 3 makes one already-supported `ObservationArtifact` (Batch 2's data
620
+ boundary, unchanged) genuinely understandable: a real screenshot, SVG target
621
+ overlays in the observation's own canonical coordinate domain, target
622
+ selection/inspection, and canonical layout-relationship display. No second
623
+ relationship engine, no client-side evidence derivation, no new persisted
624
+ artifact.
625
+
626
+ **Coordinate audit (the load-bearing decision for this batch)**: target
627
+ geometry (`TargetGeometry.x/y/width/height`) is captured via
628
+ `el.getBoundingClientRect()` (`src/browser/evidenceCapture.ts`) - CSS pixels,
629
+ relative to the current viewport's top-left, at the same live page state the
630
+ screenshot is taken from. The screenshot itself is `page.screenshot({type:
631
+ 'png'})` (`src/browser/chromiumAdapter.ts`), Playwright's default
632
+ (non-fullPage) mode, against a browser context created with no
633
+ `deviceScaleFactor` override (`browser.newContext({viewport})`) - so it
634
+ defaults to `1`, meaning every observation this repository can currently
635
+ produce has a screenshot whose raw PNG pixel dimensions equal
636
+ `requestConfig.viewport.width × requestConfig.viewport.height` exactly (1
637
+ CSS pixel = 1 PNG pixel). `requestConfig.viewport` (a required, strongly-typed
638
+ field on every valid `ObservationArtifact`, distinct from the loosely-typed
639
+ `pageEvidence` bag) is therefore the canonical, always-present source for the
640
+ SVG display frame.
641
+
642
+ **SVG coordinate model** (`viewer/src/components/TargetOverlaySvg.tsx`): the
643
+ `<svg>` root's `viewBox` is `0 0 {requestConfig.viewport.width}
644
+ {requestConfig.viewport.height}` - the exact frame `getBoundingClientRect()`
645
+ already used. The screenshot loads into a `<image>` element filling that same
646
+ viewBox (`preserveAspectRatio="none"`, since the two frames are already
647
+ pixel-identical). Target `<rect>` elements use `geometry.x/y/width/height`
648
+ completely unchanged - no rounding, no `devicePixelRatio` multiplication, no
649
+ clamping; geometry lying partly outside the viewBox is drawn at its real
650
+ coordinates and clipped only by the SVG root's default `overflow: hidden`
651
+ (a display-only effect, verified never to touch the underlying evidence
652
+ value - `tests/unit/observationCoordinateMapping.test.ts`). This is robust
653
+ even if a future capture path used a different `deviceScaleFactor`: the
654
+ `<image>`/viewBox scaling is presentation-only browser behavior, never a
655
+ manual pixel calculation in this codebase. `devicePixelRatio` (captured as
656
+ `pageEvidence.devicePixelRatio`) is shown as informational observation-level
657
+ evidence only and is never consulted for any geometry calculation.
658
+
659
+ **Server additions** (`src/viewerServer/evidence/observationView.ts`, one new
660
+ route `GET /api/observations/<handle>/relationships`): the only new
661
+ server-side computation this batch adds is one thin, defense-in-depth-wrapped
662
+ call to the existing canonical, pure `deriveLayoutRelationships` (`src/domain/
663
+ relationships.ts`) - never a second relationship predicate implementation.
664
+ Mirrors the exact handle-decode → contained-dir-resolve → re-classify
665
+ discipline `loadArtifactByHandle`/`resolveMedia` already established in
666
+ Batch 2; a handle for a non-`observation` family or a non-`supported`
667
+ candidate is rejected (`409`) before derivation is even attempted. The
668
+ existing `GET /api/artifacts/<handle>` (full `ObservationArtifact`) and
669
+ `GET /api/media/<handle>/screenshot` (Batch 2, unchanged) remain the only
670
+ other data sources the observation workspace uses - no new artifact
671
+ projection endpoint was needed, since the full validated domain object
672
+ already contains everything the target/observation inspector displays.
673
+
674
+ **Client-side presentation only** (`viewer/src/observation/targetOrder.ts`,
675
+ `viewer/src/components/{ObservationWorkspace,TargetList,TargetOverlaySvg,
676
+ ObservationInspector,EvidenceFieldView}.tsx`): React selects, orders
677
+ (by the observation's own authored `requestConfig.targets` order, not
678
+ incidental object-key order), and formats already-fetched canonical fields.
679
+ It never resolves targets, computes relationships, or derives
680
+ visibility/overflow/scroll-owner semantics - `deriveLayoutRelationships`
681
+ runs exclusively on the server (above). An unresolved target (`not-found`/
682
+ `ambiguous`/`unavailable`) is selectable from the target list and shown
683
+ honestly in the inspector, but never receives a fabricated `<rect>` -
684
+ `orderedTargets()`'s `hasGeometry` flag is `true` only when
685
+ `geometry.state` is `'available'` or `'partial'`.
686
+
687
+ **Selection**: viewer presentation state only (React `useState`, reset on
688
+ observation change), never persisted, synchronized in both directions
689
+ between the target list, the SVG `<rect>` (`role="button"`, keyboard-
690
+ operable), and the inspector via the target's existing stable `name`.
691
+
692
+ **Overlay toggles**: geometry, labels (disabled when geometry is off), and
693
+ relationships - each independently toggleable and purely presentational
694
+ (hiding/showing already-rendered elements), never altering the underlying
695
+ evidence or the fetched artifact/graph.
696
+
697
+ **PWA cache boundary preserved**: the new `/api/observations/*` route lives
698
+ under the same `/api/` prefix Batch 1's `navigateFallbackDenylist` already
699
+ denylists - no service-worker configuration change was needed
700
+ (`tests/unit/viewerPwaBuild.test.ts` asserts this against the real built
701
+ `sw.js`).
702
+
703
+ ## v0.8 Batch 4 (Before/after comparison and contract/change-scope inspection) — implemented
704
+
705
+ Batch 4 exposes the existing v0.4 `ComparisonArtifact` and v0.5
706
+ `FrontendContractEvaluationArtifact` through the viewer, entirely under
707
+ `src/viewerServer/evidence/{linkedEvidence,comparisonView,evaluationView}.ts`
708
+ and `viewer/src/components/{ComparisonWorkspace,EvaluationWorkspace,
709
+ ComparisonObservationPane,ClauseResultRow}.tsx`. **`compareObservations` and
710
+ `evaluateFrontendContract` are never called anywhere in this batch** - every
711
+ displayed comparison/evaluation field is read unchanged from its persisted
712
+ artifact via the existing Batch 2 `GET /api/artifacts/<handle>`.
713
+
714
+ - **Exact linked-evidence resolution** (`evidence/linkedEvidence.ts`): given
715
+ a `ComparisonSourceObservationReference`/`FrontendContractObservationReference`,
716
+ a `comparisonId`+`comparisonRequestId` pair, or a `baselineId`/`contractId`,
717
+ resolves the matching indexed artifact by **exact identity only**
718
+ (`observationId`+`requestId`+`producer.version`+`observationSchemaVersion`
719
+ for observations; the id fields themselves for comparisons/contracts) -
720
+ never by folder name, screenshot filename, URL, target-set, or geometry
721
+ similarity. Zero matches → `missing`; two or more exact matches →
722
+ `ambiguous` (never silently picks one). Mirrors the exact bounded-walk
723
+ pattern Batch 2's `findImportedReferenceDir` already established.
724
+ - **Two additive, read-only routes**: `GET /api/comparisons/<handle>/view`
725
+ (resolves the comparison's `before`/`after`) and
726
+ `GET /api/evaluations/<handle>/view` (resolves `comparison`, `baseline`,
727
+ `change`, `before`, `after`) - both under `/api/`, both GET/HEAD-only, both
728
+ returning only resolved-handle-or-missing-or-ambiguous status, never a
729
+ duplicated copy of the linked artifact's own payload (the browser fetches
730
+ that separately through the existing `GET /api/artifacts/<handle>`, reusing
731
+ Batch 2's on-demand-loading contract exactly).
732
+ - **Before/after visual reuse, not reimplementation**: `ComparisonObservationPane.tsx`
733
+ is built entirely from Batch 3's existing lower-level primitives
734
+ (`useArtifactDetail`, `orderedTargets`, `TargetOverlaySvg`) - no second
735
+ screenshot-loading, coordinate-transform, or geometry-rendering code
736
+ exists. The comparison's own persisted `relationshipsBefore`/
737
+ `relationshipsAfter` are passed directly into `TargetOverlaySvg`'s existing
738
+ `relationships` prop - never recomputed via `deriveLayoutRelationships`.
739
+ `TargetOverlaySvg` gained one small additive, optional `highlightNames`
740
+ prop (alongside the existing single-select `selected`) so a
741
+ relationship-subject difference or a two-target contract primitive
742
+ (`targets-do-not-overlap`, `target-fits-inside`, etc.) can emphasize both
743
+ named targets at once without changing Batch 3's existing single-select
744
+ interaction contract.
745
+ - **Difference/relationship-change/clause presentation is evidence display,
746
+ not re-derivation**: `ComparisonWorkspace.tsx` renders `differences`,
747
+ `relationshipChanges`, `configurationChanges` (kept visually distinct from
748
+ appeared/disappeared runtime differences), and `expectedDependencyEvidence`
749
+ exactly as persisted, labeling dependency outcomes as explicit non-causal
750
+ evidence. `comparability` (comparable/comparable-with-warnings/incomparable
751
+ plus blocking/warning/unassessed reasons) is shown honestly; an
752
+ `incomparable` result is visually unmistakable
753
+ (`.comparability-banner--incomparable`).
754
+ - **Clause joining by exact `clauseId` only** (`EvaluationWorkspace.tsx`):
755
+ baseline clauses (from the linked `PersistentBaselineContract`) and
756
+ per-change clauses (from the linked `PerChangeContract`) are joined to the
757
+ evaluation's `clauseResults` by exact id - never by target/primitive-shape/
758
+ category/position. A `clauseId` absent from both loaded contracts is shown
759
+ as an honest "unresolved clause definition", never fabricated. Baseline
760
+ clause active/superseded status comes exclusively from the evaluation
761
+ artifact's own `activeBaselineClauseIds`/`supersededBaselineClauseIds` -
762
+ never recomputed from clause overlap. `pass`/`fail`/`unavailable`/
763
+ `conflict` are preserved exactly (never collapsed to a boolean);
764
+ `unavailable` shows its reason, `conflict` shows its reason and
765
+ `conflictingClauseIds`.
766
+ - **Overall verdict is authoritative and unmistakable**: `overallVerdict`
767
+ (`PASS`/`FAIL`) is rendered directly from the artifact, in a large
768
+ `.overall-verdict--PASS`/`.overall-verdict--FAIL` banner - the UI never
769
+ computes it from visible rows. The required safety case (a `requested`
770
+ clause `pass` alongside a `protected`/`preserved` clause `fail` still
771
+ producing overall `FAIL`) and the all-pass case are both proven against
772
+ real, canonically-evaluated fixtures (`tests/support/evidenceFixtures.ts#writeFullPipelineFixture`/
773
+ `writeAllPassPipelineFixture`) in real Chromium
774
+ (`tests/browser/comparisonEvaluationWorkspace.test.ts`) - `overallVerdict`
775
+ is never hand-edited to construct either demonstration.
776
+ - **Target/relationship cross-highlighting uses only explicit canonical
777
+ identity**: `primitiveTargetNames()` (`viewer/src/contract/clauseTargets.ts`)
778
+ extracts a contract primitive's named target field(s) (`target`, `targetA`/
779
+ `targetB`, `target`+`container`, `subjectTarget`/`relatedTarget`) by an
780
+ exhaustive switch over `ContractPrimitiveKind` - page-level primitives
781
+ (`document-width-fits-viewport`, `scroll-owner-is-document`) return no
782
+ names, so clicking them never fabricates a target highlight.
783
+ - **PWA cache boundary preserved**: both new routes live under `/api/`,
784
+ already covered by Batch 1's `navigateFallbackDenylist`; verified against
785
+ the real built `sw.js`.
786
+
787
+ ## v0.8 Batch 5 (External reference and reference/candidate inspection) — implemented
788
+
789
+ - **Reference indexing/media already existed (Batch 2), unchanged**: the
790
+ `external-reference-imported`/`external-reference-approved` families,
791
+ `GET /api/media/<handle>/image` (imported), and
792
+ `GET /api/media/<handle>/source-image` (approved, resolved through
793
+ `findImportedReferenceDir`'s exact `referenceId` walk) were already built
794
+ in Batch 2 and required no change here - Batch 5 only adds the visual
795
+ workspace consuming them.
796
+ - **Two new additive, read-only routes**
797
+ (`src/viewerServer/evidence/referenceView.ts`):
798
+ `GET /api/references/<handle>/view` derives the selected reference's own
799
+ region-relationship graph (`deriveReferenceRegionRelationships`) and
800
+ requirement adequacy (`deriveReferenceRequirementAdequacy`) - both pure
801
+ functions over the artifact's own persisted `regions`/`requirements`,
802
+ never persisted, never a second derivation engine (mirrors Batch 3's
803
+ `getObservationRelationships` server-side-derivation pattern).
804
+ `GET /api/references/<handle>/candidate/<handle>/view` evaluates
805
+ reference/candidate compatibility through the existing canonical
806
+ `evaluateReferenceCandidateCompatibility` (never a second, viewer-owned
807
+ compatibility model) and separately lists every existing
808
+ `FrontendContractEvaluationArtifact` whose own persisted `after` reference
809
+ exactly identifies the candidate, for explicit, never-auto-selected
810
+ optional display.
811
+ - **Reference-image SVG coordinate model is a genuinely distinct domain from
812
+ the candidate's runtime SVG** (`ReferenceRegionOverlaySvg.tsx`): `viewBox`
813
+ is the reference image's own pixel dimensions (never the candidate's CSS
814
+ viewport, never devicePixelRatio-multiplied); each region's canonical
815
+ `{x, y, width, height}` is rendered unchanged. Because this is a different
816
+ coordinate domain and data source from `TargetOverlaySvg` (runtime CSS
817
+ pixels, `TargetGeometry`), it is a separate, sibling component rather than
818
+ a parameterization of the existing one - reuse would have silently
819
+ conflated the two domains. The candidate side, in contrast, reuses Batch
820
+ 3/4's exact `ComparisonObservationPane`/`TargetOverlaySvg` machinery
821
+ unchanged (a synthetic `{status:'resolved', handle}` `LinkStatus` is
822
+ constructed once a candidate is explicitly chosen).
823
+ - **Reference region selection and runtime target selection are two
824
+ independent, never-synchronized selection domains**
825
+ (`ReferenceWorkspace.tsx`): selecting a reference region never selects or
826
+ highlights a runtime target, even when both happen to share the same
827
+ string name (proven with a real Chromium fixture deliberately naming both
828
+ `"header"` - `tests/browser/referenceCandidateWorkspace.test.ts`, Case E).
829
+ No binding connector/highlight-across-panes exists in this batch - that is
830
+ Batch 6's explicit-binding-interaction scope.
831
+ - **Compatibility vs. reference adequacy vs. candidate fidelity are kept
832
+ strictly distinct, never conflated**: compatibility
833
+ (`comparable`/`comparable-with-warnings`/`incomparable` plus
834
+ blocking/warning/unassessed reasons) comes only from
835
+ `evaluateReferenceCandidateCompatibility`; reference-side requirement
836
+ adequacy (`adequate`/`partial`/`inadequate`) comes only from
837
+ `deriveReferenceRequirementAdequacy`; candidate fidelity is never computed
838
+ in this batch at all - the UI always shows an explicit "not evaluated in
839
+ this batch" note rather than ever implying a fidelity PASS from a
840
+ compatibility PASS or an adequate reference (task §32/§33 boundary,
841
+ `evaluateReferenceCandidateFidelity` is never imported/called anywhere in
842
+ Batch 5).
843
+ - **Optional contract/evaluation context is opt-in, never inferred**
844
+ (`ReferenceWorkspace.tsx`): when the reference/candidate view lists more
845
+ than one exactly-matching evaluation artifact, the developer must
846
+ explicitly pick one from a `<select>` - the newest/first match is never
847
+ silently chosen, and with zero selected the candidate's contract status
848
+ reads "not selected/not available", never a fabricated PASS.
849
+ - **Imported vs. approved image ownership preserved exactly as Batch 2 built
850
+ it**: an imported reference's image is fetched from its own directory; an
851
+ approved reference's image is fetched from its exact imported source via
852
+ `sourceReference.referenceId`, never assumed co-located, never
853
+ duplicated - proven with a real Chromium fixture asserting the two
854
+ `<image href>` values resolve to the `image`/`source-image` roles
855
+ respectively (Cases A/B).
856
+ - **PWA cache boundary preserved**: both new routes live under `/api/`,
857
+ already covered by Batch 1's `navigateFallbackDenylist`; verified against
858
+ the real built `sw.js` (no new `registerRoute`, no `/api/references`
859
+ precache entry).
860
+
861
+ ## v0.8 Batch 6 (Explicit-binding interaction, zoom/pan, conditional lock, and on-demand reference fidelity) — implemented
862
+
863
+ - **`view --bindings-file <json-file>`**: reuses the exact same operational
864
+ binding-file wrapper parser (`loadBindingsFile` in `src/cli.ts`) that
865
+ `evaluate-reference-fidelity --bindings-file` already used - one shared
866
+ parser, never a second divergent one. The file is read once at startup;
867
+ its declarations become `ViewerServerState.bindingDeclarations` (an opaque
868
+ `unknown[]` until validated against a specific reference); the file path
869
+ itself is never persisted, returned, or exposed to the browser. Reference-
870
+ specific declaration validity (region existence, shape) is deferred to the
871
+ moment a reference is actually selected server-side, via the existing
872
+ canonical `isValidReferenceRuntimeBindingDeclarations` - never checked at
873
+ startup without a reference.
874
+ - **Two new additive, read-only routes**
875
+ (`src/viewerServer/evidence/referenceView.ts`):
876
+ `GET /api/references/<handle>/candidate/<handle>/bindings` validates the
877
+ session's declarations against the selected reference and calls the
878
+ existing canonical `evaluateReferenceRuntimeBindings` exactly once.
879
+ `GET /api/references/<handle>/candidate/<handle>/fidelity` is the explicit
880
+ on-demand fidelity trigger - calls the existing canonical
881
+ `evaluateReferenceCandidateFidelity` exactly once, using the exact same
882
+ session declarations, so its embedded `bindings` field and the `/bindings`
883
+ route's own result always structurally agree for identical inputs (same
884
+ pure function, same arguments). Neither route persists anything; both are
885
+ `GET` (idempotent, deterministic, ephemeral over already-selected explicit
886
+ input) - no mutation route was added.
887
+ - **`deriveCoordinateScale` exported additively** from
888
+ `externalReferenceFidelity.ts` (previously module-private) - Batch 6's
889
+ view-lock eligibility reuses this exact function unchanged (same formula,
890
+ same `ASPECT_RATIO_MAPPING_TOLERANCE`, same no-applicable-viewport
891
+ failure) rather than a second aspect-ratio/scale implementation. The
892
+ existing `GET /api/references/<handle>/candidate/<handle>/view` route now
893
+ additionally returns `coordinateMapping: DeriveCoordinateScaleResult` -
894
+ purely a function of the reference, independent of the candidate.
895
+ - **Explicit-binding cross-selection uses only canonical
896
+ `ReferenceRuntimeBindingResult.referenceRegion`/`.runtimeTarget` fields**
897
+ (`ReferenceWorkspace.tsx`): selecting a `bound` reference region
898
+ highlights (via `TargetOverlaySvg`'s existing Batch 4 `highlightNames`
899
+ prop - never the primary `selected`/`aria-pressed` target) its exact
900
+ declared runtime target; selecting a runtime target highlights every
901
+ region whose `bound` result names it (many-to-one, via a new additive
902
+ `highlightRegionIds` prop on `ReferenceRegionOverlaySvg`, matching
903
+ `TargetOverlaySvg`'s established highlight pattern). `ambiguous`/
904
+ `unavailable` results never cross-select. Proven with a real equal-name
905
+ ("header" region + "header" target) Chromium fixture: no cross-selection
906
+ without an explicit declaration, real cross-selection with one.
907
+ - **Zoom/pan is a repository-owned, presentation-only hook**
908
+ (`viewer/src/hooks/useZoomPan.ts`): bounded `[1x, 8x]` scale, `×1.25`/
909
+ `÷1.25` step, expressed as one `{scale, focalX, focalY}` triple in the
910
+ pane's own source-coordinate frame (reference-image pixels or candidate
911
+ CSS pixels - never rewritten). Renders via an SVG `viewBox` override
912
+ (additive `viewBoxOverride`/`svgRef`/pointer-handler props on
913
+ `TargetOverlaySvg`/`ReferenceRegionOverlaySvg`, defaulting to Batch 3/5's
914
+ exact prior behavior when omitted) - image and overlay stay one
915
+ transformed unit automatically since both live inside the same `<svg>`
916
+ root, and native SVG hit-testing means selection keeps working correctly
917
+ under zoom/pan with no extra coordinate math. Panning uses the SVG
918
+ element's own `getScreenCTM()` to convert screen-space pointer deltas into
919
+ source-space deltas - reuses the browser's native transform rather than a
920
+ custom aspect-ratio-aware pixel calculation - and only engages (calling
921
+ `setPointerCapture`) once the pointer has moved past a small threshold, so
922
+ an ordinary click on a region/target rect is never hijacked into a
923
+ phantom drag. Fit and Reset are the same fitted-1x/centered default (task
924
+ §26 - no second presentation-only default was introduced).
925
+ - **View lock reuses one single shared state, never two independently
926
+ synchronized states**: `ReferenceWorkspace.tsx` owns `refZoom`/`candZoom`
927
+ directly (always-controlled `useZoomPan` calls) and each pane's zoom/pan
928
+ action handler updates both states in one synchronous call when locked -
929
+ no reactive effect watches one pane's state to update the other, so no
930
+ feedback-loop risk exists. Lock is available only when a candidate is
931
+ selected, compatibility is not `incomparable`, and `coordinateMapping.ok`
932
+ is `true`; any change to that eligibility (including selecting a
933
+ different reference/candidate) immediately disables lock and shows an
934
+ actionable reason. Synchronization converts a candidate CSS-pixel focal
935
+ point/scale into reference-image-pixel space (and back) using only
936
+ `coordinateMapping.scale.scaleX`/`scaleY` - the exact same canonical
937
+ factor `deriveCoordinateScale` already produces, applied as a straight
938
+ multiply/divide (its own algebraic inverse), never a second mapping rule.
939
+ - **Contract/fidelity independence is never collapsed into one status**:
940
+ the existing Batch 5 "Optional contract/evaluation context" section
941
+ (unchanged) and the new on-demand `ReferenceFidelityPanel` are two
942
+ separate sections rendering two separate canonical results
943
+ (`FrontendContractEvaluationArtifact.overallVerdict` and
944
+ `ReferenceCandidateFidelityEvaluation.state`) side by side; when both are
945
+ present, an explicit note states that fidelity does not override an
946
+ active contract failure. No new coordinator/aggregate verdict is computed
947
+ anywhere in this batch.
948
+ - **PWA cache boundary preserved**: both new routes live under `/api/`,
949
+ already covered by Batch 1's `navigateFallbackDenylist`.
950
+
951
+ ## v0.8 Batch 7 (Bounded agent context, correlation, provenance, and raw evidence navigation) — implemented
952
+
953
+ - **Bounded agent context remains programmatic-only**: no filesystem writer
954
+ was added for `BoundedAgentContextArtifact` (it is still not an
955
+ Observer-evidence-root artifact family - `src/viewerServer/evidence/classify.ts`'s
956
+ "known but unreadered kind" comment is unchanged). `view --context-file
957
+ <json-file>` reads exactly one already-serialized
958
+ `BoundedAgentContextArtifact` value directly (no wrapper object) as
959
+ explicit, session-only viewer input - read once at startup
960
+ (`src/cli.ts#loadContextFile`, mirroring `loadBindingsFile`'s exact
961
+ read/size-bound/parse shape), classified by
962
+ `src/viewerServer/context.ts#classifyContextFileContent` (reuses the
963
+ existing canonical `isValidBoundedAgentContextArtifact` - never a second
964
+ validator), and held only in `ViewerServerState.context` for the life of
965
+ the process. The file's size is bounded by the existing Batch 2
966
+ `MAX_MANIFEST_CANDIDATE_BYTES` (2,000,000 bytes) rather than a second
967
+ bound, since a context artifact's own frozen numeric caps already make it
968
+ far smaller in any realistic case.
969
+ - **Three honest session states, never coerced into one another**: `'none'`
970
+ (no `--context-file`; every Batch 1-6 feature stays fully available),
971
+ `'unsupported-version'` (recognized `artifactKind`, a `schemaVersion`
972
+ other than the current one - the viewer still starts, showing this
973
+ state explicitly rather than either failing or misinterpreting the
974
+ fields), and `'valid'` (structurally validated current-schema context).
975
+ Every other problem (unreadable file, wrong `artifactKind`, a
976
+ structurally invalid *current*-schema artifact) fails viewer startup
977
+ clearly - an explicitly supplied file is never silently ignored.
978
+ - **`GET /api/context`** (`httpServer.ts`) returns the session's exact
979
+ classified state; for `'valid'`, it additionally returns
980
+ `sourceResolution` - the result of resolving
981
+ `artifact.sources` against the current evidence root by **exact
982
+ canonical identity only**
983
+ (`src/viewerServer/evidence/contextSourceView.ts`, reusing/extending
984
+ Batch 4's `linkedEvidence.ts` resolver pattern with three additive
985
+ functions: `resolveObservationById` (bare `observationId`, the only
986
+ identity a context source reference actually carries),
987
+ `resolveEvaluationByIdentity`, and `resolveReferenceByIdentity`). Zero
988
+ matches → `missing`; two or more → `ambiguous` (never silently picks
989
+ one) - the same discipline every other Batch 4/5 resolver already
990
+ established.
991
+ - **Raw structured evidence reuses the existing Batch 2 artifact-detail
992
+ route unchanged**: `RawEvidenceViewer.tsx` calls the existing
993
+ `useArtifactDetail`/`GET /api/artifacts/<handle>` for any exactly-resolved
994
+ source - no second full-artifact retrieval mechanism, no local filesystem
995
+ read, no arbitrary path accepted from the browser.
996
+ `EvidenceReference.path` values are always displayed as plain provenance
997
+ text, never passed to `fs.readFile`/`path.resolve`/a static file server.
998
+ - **Bounded runtime targets, adequacy, omissions, truncations, and
999
+ correlation are rendered exactly as the validated artifact states them** -
1000
+ never recomputed, never boolean-collapsed
1001
+ (`ContextWorkspace.tsx`): `Adequacy.state`
1002
+ (`adequate`/`partial`/`inadequate`) and reasons are shown verbatim;
1003
+ absent bounded-target fields render "not included in this bounded
1004
+ context", never a fabricated falsy/zero value; `required: true`
1005
+ omissions/truncations render in a visually distinct
1006
+ `.context-required-loss` block, separate from optional ones;
1007
+ `correlations` absent renders "Static correlation not included in this
1008
+ context" - never "unavailable" (that status is reserved for a real
1009
+ per-target `RuntimeStaticCorrelationRecord` with zero candidates).
1010
+ `correlated`/`ambiguous`/`unavailable` are preserved exactly; a
1011
+ `correlated` record's one candidate is labeled "Correlated candidate", an
1012
+ `ambiguous` record shows **every** supplied candidate with none visually
1013
+ promoted, and `unavailable` fabricates zero candidates - matching the
1014
+ frozen `CORRELATION_STATUSES` invariants
1015
+ (`domain/boundedAgentContext.ts`) the validator itself already enforces.
1016
+ All new UI text was audited against ownership/edit-authorization language
1017
+ (no "owner"/"source owner"/"owned by") - correlation is presented as
1018
+ evidence, never as edit authorization.
1019
+ - **Context-target ↔ runtime-target interaction never infers source
1020
+ ownership**: selecting a bounded target or a correlation record uses only
1021
+ exact `targetId`/`runtimeTargetId` string matching; for each *exactly
1022
+ resolved* source observation, `SourceObservationTargetCheck` checks
1023
+ membership in that observation's own already-fetched `targetEvidence`
1024
+ (a plain lookup over already-loaded JSON, never a new derivation) and, if
1025
+ more than one resolved source observation contains the same target id,
1026
+ lists all of them rather than picking one.
1027
+ - **Bounded reference-fidelity projection is never recomputed, and is kept
1028
+ visibly distinct from a live on-demand evaluation**: absent `fidelity`
1029
+ renders "Reference fidelity not included in this bounded context" - never
1030
+ implied as passing. When present, `mismatches` and `protectedContext` are
1031
+ rendered in separate sections from the artifact's own fields exactly as
1032
+ supplied; a `state: 'not-evaluated'` blocked projection always shows
1033
+ `blockedBy` prominently and never renders an empty mismatch list as "no
1034
+ problems". When the context's `referenceId`/`candidateObservationId`
1035
+ exactly resolve within the current evidence root, `ContextWorkspace.tsx`
1036
+ embeds the existing, unchanged Batch 6 `ReferenceFidelityPanel` (the same
1037
+ on-demand `evaluateReferenceCandidateFidelity` trigger) directly beneath
1038
+ the bounded projection, labeled "Bounded context fidelity projection"
1039
+ above and "Current on-demand fidelity evaluation" below - two separate,
1040
+ clearly labeled evidence instances, never silently merged or replaced.
1041
+ - **No runtime rebuild of context or correlation, and no my-dev-kit
1042
+ execution from the shipped viewer**: grep-verified - `projectBoundedAgentContext(`,
1043
+ `deriveRuntimeStaticCorrelations(`, and `attachRuntimeStaticCorrelations(`
1044
+ appear nowhere under `src/viewerServer/` or `viewer/src/` (only in test/
1045
+ fixture-generation code, per the frozen plan's explicit test-fixture
1046
+ exception); no `child_process`/`npx @dailephd/my-dev-kit` invocation
1047
+ exists in the viewer server or browser bundle.
1048
+ - **PWA cache boundary preserved**: `GET /api/context` lives under `/api/`,
1049
+ already covered by Batch 1's `navigateFallbackDenylist`.
1050
+
1051
+ ## v0.8 Batch 8 (Integrated viewer acceptance, PWA hardening, and packaged proof) — implemented
1052
+
1053
+ Batch 8 is the final v0.8 implementation batch. It is integration/hardening,
1054
+ not a new architecture layer: no new API route, no new CLI flag, and no new
1055
+ canonical-engine call site were added. See
1056
+ `docs/reports/v0.8-integrated-viewer-acceptance-batch8.md` for the full
1057
+ record.
1058
+
1059
+ - **Closed three named real-browser coverage gaps**, each proved against the
1060
+ actual built viewer through the actual loopback server, never a hand-edited
1061
+ fixture verdict: many reference regions bound to one runtime target all
1062
+ cross-highlight together (the pre-existing target→regions loop in
1063
+ `ReferenceWorkspace.tsx` already iterated every matching binding - the gap
1064
+ was in real-browser proof, not in the derivation); reference-fidelity
1065
+ `fail` alongside a genuine frontend-contract `PASS` for the same candidate
1066
+ display independently (the pre-existing independence note in
1067
+ `ReferenceWorkspace.tsx` was already verdict-agnostic); a bounded context
1068
+ whose sources include two observations sharing a stable target id lists
1069
+ every matching source observation (the pre-existing
1070
+ `SourceObservationTargetCheck` in `ContextWorkspace.tsx` already checked
1071
+ membership per source independently, never picking one).
1072
+ - **One real accessibility defect found and fixed**: a cross-highlighted,
1073
+ non-selected region/target `<rect>` (`TargetOverlaySvg.tsx`,
1074
+ `ReferenceRegionOverlaySvg.tsx`) exposed no accessible state distinguishing
1075
+ it from a plain unselected rect - `aria-pressed` correctly stayed `false`
1076
+ (it is not the primary single-selection), but nothing else communicated
1077
+ the highlight to assistive technology. Fixed by adding
1078
+ `data-highlighted="true"` and an `aria-label` suffix
1079
+ (`" (highlighted: related to current selection)"`) when highlighted and
1080
+ not selected, leaving `aria-pressed` semantics untouched.
1081
+ - **First live-browser PWA proof suite** (`tests/browser/pwaHardening.test.ts`):
1082
+ real service-worker registration and activation against the built shell;
1083
+ the manifest fetched and confirmed `display: "standalone"`; zero Cache
1084
+ Storage entries under any `/api/` pathname after normal use, confirming
1085
+ the `navigateFallbackDenylist` boundary holds live, not just in the built
1086
+ `sw.js` regex; and the hard server-down gate - after the server is closed
1087
+ and the same page reloaded, the app shell still renders from the precache,
1088
+ but the evidence-dependent surface shows the explicit
1089
+ `.evidence-list__error` "Evidence index unavailable" state, with the
1090
+ previously-visible evidence asserted absent. Install-control is proven
1091
+ only via synthetic `beforeinstallprompt` dispatch (a genuine browser
1092
+ install prompt was not observed under automation). Standalone-mode CDP
1093
+ display-mode emulation was attempted but not observed to take effect -
1094
+ recorded honestly, never overstated as actual OS-level installation proof.
1095
+ - **Packaged-candidate proof**: `npm pack` → clean consumer install (outside
1096
+ the repository) → the actually-installed CLI executable (not repo
1097
+ `dist/cli.js`) → the installed `view` server → real Chromium against the
1098
+ packaged/installed server, not a source-checkout dev server. Read-only
1099
+ evidence-hash proof (SHA-256 of every file in the exercised evidence root,
1100
+ taken before and after the packaged-browser session) confirmed no
1101
+ mutation and no new viewer-created artifact anywhere in the evidence root.
1102
+ - **Re-confirmed the no-second-engine invariant** across all eight batches by
1103
+ re-running the exact `grep -rn` audit from earlier batches - unchanged
1104
+ findings, no duplicate evidence engine exists.
1105
+
1106
+ ## v0.9 visual annotation architecture (released in 0.9.0)
1107
+
1108
+ v0.9 is released as package version `0.9.0`. It adds one new
1109
+ evidence family and a narrow local authoring path to the existing viewer. It
1110
+ adds no new evaluator, no second contract or reference model, and no new
1111
+ PASS/FAIL semantics.
1112
+
1113
+ **Evidence ownership**:
1114
+
1115
+ - `src/domain/visualAnnotation.ts` owns `VisualAnnotationArtifact` (artifact
1116
+ kind `my-frontend-observer/visual-annotation`, schema `1.0.0`): the source
1117
+ union, structured marks, explicit associations, candidate/confirmed
1118
+ interpretation, validation, and the pure overlay SVG renderer.
1119
+ - `src/domain/visualAnnotationIdentity.ts` owns deterministic request identity
1120
+ and fresh instance identity.
1121
+ - `src/artifacts/visualAnnotationArtifactWriter.ts` and
1122
+ `visualAnnotationArtifactReader.ts` own atomic persistence (temporary
1123
+ `.tmp-<id>` directory, then rename) and canonical reading, including the
1124
+ derived `annotation-overlay.svg` and its digest.
1125
+ - `src/application/visualAnnotationPersistenceService.ts` owns saving one
1126
+ annotation or one superseding revision.
1127
+
1128
+ **Coordinate domains**: runtime annotations use the observation's runtime CSS
1129
+ pixel space. Reference annotations use the reference image's own pixel space
1130
+ (for an approved reference, the owning imported image). The two domains are
1131
+ never mixed. Zoomed or panned drawing is mapped back through the SVG's own
1132
+ transform (`viewer/src/svg/sourceCoordinates.ts`).
1133
+
1134
+ **Viewer discovery and media** (`src/viewerServer/evidence/`): the index
1135
+ classifies `visual-annotation` evidence and skips writer `.tmp-*`
1136
+ directories. `annotationView.ts` resolves an annotation's exact canonical
1137
+ source and reports `unavailable` instead of guessing a replacement. The media
1138
+ resolver serves the `annotation-overlay` role only after re-rendering the SVG
1139
+ from the artifact and verifying it, with a script-blocking sandbox policy.
1140
+
1141
+ **Project-aware authoring security**: `src/viewerServer/authoringSecurity.ts`
1142
+ owns the in-memory 32-byte session capability and the Host, Origin, token,
1143
+ content-type, and content-encoding checks. `httpServer.ts` owns the shared
1144
+ bounded JSON gate and routes exactly three `POST` responsibilities.
1145
+ `view --root` never creates an authoring session, so the standalone
1146
+ arbitrary-root viewer stays read-only.
1147
+
1148
+ 1. `POST /api/annotations` (`src/viewerServer/annotationAuthoring.ts`) saves
1149
+ a new annotation or a revision through the canonical persistence service,
1150
+ with stale-parent conflict detection. This module also owns the single
1151
+ per-session write queue used by all three routes.
1152
+ 2. `POST /api/annotations/:handle/promote-contract`
1153
+ (`src/viewerServer/annotationContractPromotion.ts`) calls
1154
+ `src/application/visualAnnotationContractPromotionService.ts`. Selected
1155
+ confirmed runtime intent becomes one canonical `PerChangeContract` through
1156
+ the existing contract persistence service. Optional activation goes only
1157
+ through `activateProjectChangeContract` in
1158
+ `src/application/projectWorkflowService.ts`.
1159
+ 3. `POST /api/annotations/:handle/materialize-reference`
1160
+ (`src/viewerServer/annotationReferenceMaterialization.ts`) calls
1161
+ `src/application/visualAnnotationReferenceMaterializationService.ts`.
1162
+ Selected confirmed reference intent becomes a new imported
1163
+ `ExternalReferenceArtifact` through the existing `importExternalReference`,
1164
+ superseding the source. The image bytes come only from the safe media
1165
+ resolver.
1166
+
1167
+ Output locations come from `src/projectWorkflow/projectPaths.ts`
1168
+ (`annotations`, `contracts`, and `references` under
1169
+ `.frontend-observer/evidence`). The browser never supplies a path.
1170
+
1171
+ **Viewer UI** (`viewer/src/`): `annotation/` holds pure presentation models
1172
+ (`annotationGeometry.ts`, `runtimeIntent.ts`, `referenceIntent.ts`,
1173
+ `confirmation.ts`). `components/AnnotationLayer.tsx`,
1174
+ `AnnotationToolbar.tsx`, `RuntimeAnnotationPanel.tsx`,
1175
+ `ReferenceAnnotationPanel.tsx`, `CandidateRegionPreviewLayer.tsx`, and
1176
+ `AnnotationPanelSections.tsx` render marks, tools, intent, promotion, and
1177
+ materialization. Hooks under `hooks/` hold draft, saved-list, session,
1178
+ pointer, promotion, and materialization state. The authoring token lives only
1179
+ in React memory.
1180
+
1181
+ **Existing evaluators remain authoritative**: the canonical contract
1182
+ evaluator, reference relationship derivation, requirement adequacy, reference
1183
+ fidelity, and project `check` are unchanged. Annotation evidence feeds them
1184
+ only through promoted contracts and materialized references.
1185
+
1186
+ ## Retained v0.1 architecture constraints
1187
+
1188
+ v0.1 planning preserved these approved boundaries without treating module
1189
+ names from the historical run as mandatory:
1190
+
1191
+ ```text
1192
+ thin command-line boundary
1193
+ ↓
1194
+ reusable observation engine/application layer
1195
+ ↓
1196
+ browser automation boundary
1197
+ ↓
1198
+ observer-owned runtime evidence
1199
+
1200
+ observer-owned domain/schema
1201
+ ↓
1202
+ artifact ownership boundary
1203
+
1204
+ deterministic fixture/test boundary
1205
+ ↓
1206
+ browser-level validation
1207
+ ```
1208
+
1209
+ Use one browser engine implementation, keep browser logic out of presentation,
1210
+ avoid speculative plugin/multi-browser abstractions, and keep observed
1211
+ applications external. Versions before v0.6 did not add runtime coupling to
1212
+ sibling ecosystem projects. v0.6 adds only explicit bounded context and
1213
+ correlation/export contracts within this repository, preserving independent
1214
+ ownership; orchestrator-consumption and lab-compatibility work are separate
1215
+ sibling-repository deliverables, not part of this repository's architecture.
1216
+
1217
+ The text/config-driven coding-agent workflow and the non-UI external-reference
1218
+ evidence foundation are operational as of v0.7. The viewer and annotation
1219
+ layers must consume the same canonical observation, relationship, comparison,
1220
+ contract, change-scope, reference, correlation, and context boundaries rather
1221
+ than creating parallel engines. The concrete implementation plan and module
1222
+ layout for each future version must be designed only after that version's
1223
+ planning workflow inspects the current repositories.
1224
+
1225
+ v0.7 Prompt 1 implements only the bottom of that external-reference stack: a
1226
+ new, standalone `ExternalReferenceArtifact` evidence root
1227
+ (`src/domain/externalReference.ts`, `externalReferenceImage.ts`,
1228
+ `externalReferenceIdentity.ts`, `src/artifacts/externalReferenceArtifact{Writer,Reader}.ts`,
1229
+ `src/application/externalReferencePersistenceService.ts`) with its own
1230
+ identity, provenance, bounded image metadata, and a two-state
1231
+ (`imported`/`approved`) lifecycle - see `docs/CONTRACTS.md` "v0.7 Prompt 1
1232
+ external-reference artifact contract" for the exact shape. It follows the
1233
+ same identity/persistence/diagnostics/export conventions as every existing
1234
+ artifact family (deterministic canonicalize-then-sha256 request identity,
1235
+ nonce-based fresh instance identity, atomic temp-dir-then-rename persistence,
1236
+ the shared `DIAGNOSTIC_CODES` vocabulary) without reusing or duplicating the
1237
+ observation, comparison, or contract engines themselves - an external
1238
+ reference is desired-design evidence, never an `ObservationArtifact`, an
1239
+ approved baseline, or a runtime target.
1240
+
1241
+ v0.7 Prompt 2 adds explicit reference regions and reusable reference-region
1242
+ relationships on top of that foundation (`domain/externalReferenceRegions.ts`,
1243
+ `externalReferenceRegionRelationships.ts`) - see `docs/CONTRACTS.md` "v0.7
1244
+ Prompt 2 explicit reference regions and relationships" for the exact shape.
1245
+ The relationship-derivation predicates are reused verbatim (now exported
1246
+ additively) from `domain/relationships.ts` rather than reimplemented, so
1247
+ reference-region geometry and runtime-target geometry can never diverge on
1248
+ the same underlying formula; only the geometry-only relationship families
1249
+ apply, since a static image exposes no DOM, scroll, or viewport evidence.
1250
+ v0.7 Prompt 3 adds selected design requirements, tolerance semantics, and
1251
+ reference-evidence adequacy on top of that region model
1252
+ (`domain/externalReferenceRequirements.ts`,
1253
+ `externalReferenceRequirementIdentity.ts`) - see `docs/CONTRACTS.md` "v0.7
1254
+ Prompt 3 selected design requirements, tolerance semantics, and
1255
+ reference-evidence adequacy" for the exact shape. Requirement categories are
1256
+ the exact v0.5 `AuthoredChangeScopeCategory` vocabulary, imported directly
1257
+ rather than reinvented, since that type carries no runtime-only coupling of
1258
+ its own; tolerance is a genuinely new, reference-owned type (never a reuse
1259
+ of `frontendContracts.ts`'s runtime/CSS-pixel-implicit `ContractTolerance`);
1260
+ and reference-evidence adequacy is a small, independently-owned vocabulary
1261
+ distinct from `boundedAgentContext.ts`'s runtime/static-correlation
1262
+ `Adequacy`. A region property or derived relationship is never promoted to
1263
+ an executable requirement automatically - only explicit user/configuration
1264
+ selection does that.
1265
+
1266
+ v0.7 Prompt 4 adds explicit reference applicability
1267
+ (`domain/externalReferenceApplicability.ts`) and observation-side explicit
1268
+ state identity (`domain/explicitState.ts`, shared by both artifact
1269
+ families), plus one new pure domain module,
1270
+ `domain/externalReferenceCompatibility.ts`, that evaluates whether a
1271
+ candidate `ObservationArtifact` describes the same frontend state as a
1272
+ given `ExternalReferenceArtifact` - see `docs/CONTRACTS.md` "v0.7 Prompt 4
1273
+ reference applicability and candidate-state compatibility" for the exact
1274
+ shape. This is page/state-level only, never geometry or fidelity, and
1275
+ remains a wholly separate concern from Prompt 3's reference-evidence
1276
+ adequacy - the two can independently disagree (an adequate reference can be
1277
+ incomparable against a given candidate, and vice versa). Rather than
1278
+ inventing a second comparability engine, Prompt 4 extracts one new exported
1279
+ pure helper from v0.4's own `domain/comparisonEngine.ts`
1280
+ (`assessOptionalComparabilityDimension`) and reuses it from both v0.4's
1281
+ `evaluateComparability` (Observation-vs-Observation) and the new
1282
+ `evaluateReferenceCandidateCompatibility` (Reference-vs-Observation) - the
1283
+ one additive behavior change to v0.4 itself is that `evaluateComparability`
1284
+ now assesses (rather than always reporting unassessed) theme/authenticated-
1285
+ state/application-state whenever both observations declare
1286
+ `requestConfig.explicitState`, while every historical/legacy observation
1287
+ pair retains the exact prior unassessed-only behavior. No new persisted
1288
+ artifact kind is introduced for the compatibility result; it is a pure,
1289
+ on-demand function of two already-persisted artifacts.
1290
+
1291
+ v0.7 Prompt 5 adds explicit reference-region <-> runtime-target binding
1292
+ (`domain/externalReferenceRuntimeBinding.ts`) - see `docs/CONTRACTS.md`
1293
+ "v0.7 Prompt 5 explicit reference-region <-> runtime-target binding" for
1294
+ the exact shape. It answers only "which stable v0.2 runtime target does
1295
+ this candidate observation resolve for each explicitly declared reference
1296
+ region", strictly downstream of Prompt 4's compatibility gate (reused
1297
+ verbatim, never duplicated) and strictly upstream of v0.6's own
1298
+ runtime/static correlation - the two identity domains (a Prompt 2
1299
+ `ReferenceRegion.id` and a v0.2 `NamedTarget.name`) never collapse into
1300
+ each other, and this stage stops at the runtime target, never reaching
1301
+ source ownership. Following v0.6's uncertainty discipline
1302
+ (`domain/boundedAgentContextCorrelation.ts`), binding never guesses through
1303
+ ambiguity - an ambiguously or unavailably resolved v0.2 target is reported
1304
+ as such, never silently treated as bound - though the actual per-status
1305
+ mapping (`bound`/`ambiguous`/`unavailable`) is binding's own, independently
1306
+ owned vocabulary, not a reuse of v0.6's `correlated`/`ambiguous`/
1307
+ `unavailable` correlation-status semantics (a different evidence boundary:
1308
+ correlation ranks *static candidates* for one runtime target, whereas
1309
+ binding resolves *one runtime target's own existence* for one declared
1310
+ correspondence). No second target resolver, no browser execution, and no
1311
+ new persisted artifact family were introduced; neither
1312
+ `ExternalReferenceArtifact` nor `ObservationArtifact` is mutated to carry a
1313
+ binding result, since a reference may later be evaluated against several
1314
+ candidates and an observation against several references.
1315
+
1316
+ v0.7 Prompt 6 adds structured reference-vs-candidate fidelity evaluation
1317
+ (`domain/externalReferenceFidelity.ts`, `application/referenceFidelityEvaluationService.ts`,
1318
+ and the `evaluate-reference-fidelity` CLI command) - the first point in this
1319
+ stack where a reference's authored expectation is compared against live
1320
+ candidate evidence. See `docs/CONTRACTS.md` "v0.7 Prompt 6 structured
1321
+ reference-vs-candidate fidelity evaluation" for the exact shape. It is
1322
+ downstream of every prior v0.7 prompt and reuses each verbatim: Prompt 3's
1323
+ `deriveReferenceRequirementAdequacy`/`deriveReferenceRequirementExpectation`/
1324
+ `deriveReferenceRequirementMeasurement` (never redefined), Prompt 4's
1325
+ `evaluateReferenceCandidateCompatibility` (a hard gate, never duplicated),
1326
+ and Prompt 5's `evaluateReferenceRuntimeBindings` (the sole source of
1327
+ runtime-target identity - no automatic binding, no second target resolver).
1328
+ It also reuses v0.4's `deriveLayoutRelationships` for runtime relationship
1329
+ evidence, scoped to the exact requested relationship family (the same
1330
+ Prompt 3 bug-fix precedent). The one genuinely new problem this prompt
1331
+ solves is the reference-image-pixel <-> CSS-pixel coordinate mapping: a
1332
+ single explicit, deterministic full-frame scale derived from
1333
+ `reference.applicability.viewport` and the reference image's own
1334
+ dimensions, with a tiny independent aspect-ratio-coherence check (never a
1335
+ design tolerance) gating whether that mapping exists at all. No new
1336
+ persisted artifact family, no browser execution, and no source-ownership
1337
+ attribution - this is reference fidelity only, a separate concern from any
1338
+ later v0.5 baseline/per-change contract result or v0.7 overall verdict.
1339
+
1340
+ v0.7 Prompt 7 adds bounded reference-fidelity projection into the existing
1341
+ v0.6 bounded-agent-context architecture (`domain/referenceFidelityProjection.ts`,
1342
+ plus additive extensions to `domain/boundedAgentContext.ts`,
1343
+ `domain/boundedAgentContextIdentity.ts`, and
1344
+ `domain/boundedAgentContextProjection.ts`) - see `docs/CONTRACTS.md` "v0.7
1345
+ Prompt 7 bounded reference-fidelity projection and v0.6 bounded-agent-
1346
+ context integration" for the exact shape. `projectBoundedAgentContext`
1347
+ itself, not a new parallel context system, gains one new optional input (an
1348
+ already-computed Prompt 6 fidelity evaluation): fidelity-relevant runtime
1349
+ targets fold into the exact same required/permitted-target-allocation,
1350
+ evidence-tiering, omission/truncation, and adequacy machinery v0.5 contract
1351
+ clauses already compete in, and a new `fidelity?:
1352
+ BoundedReferenceFidelityProjection` field on `BoundedAgentContextArtifact`
1353
+ (mirroring `correlations?`'s own additive, non-version-bumping precedent
1354
+ from v0.6 Batch 3) carries a bounded, priority-ordered selection of Prompt
1355
+ 6's non-passing requirement results plus passing protected/preserved
1356
+ context. No second bounded-context architecture, no recomputation of Prompt
1357
+ 2-6/v0.4/v0.5 logic, and no change to v0.6's own runtime/static correlation
1358
+ (`deriveRuntimeStaticCorrelations`/`attachRuntimeStaticCorrelations` are
1359
+ untouched and reused exactly as before) - a caller joins fidelity, target,
1360
+ and correlation evidence by the one stable v0.2 runtime target id all three
1361
+ already share. Every new field is optional and additive; a pre-Prompt-7
1362
+ caller supplying no fidelity evidence receives byte-identical output,
1363
+ including logical identity.
1364
+
1365
+ v0.7 Prompt 8 adds the first complete, controlled end-to-end external-
1366
+ reference correction workflow (`domain/referenceCorrectionWorkflow.ts`,
1367
+ `domain/referenceCorrectionIdentity.ts`) - see `docs/CONTRACTS.md` "v0.7
1368
+ Prompt 8 controlled end-to-end external-reference coding-agent correction
1369
+ workflow" for the exact shape. This is a narrowly-scoped coordinator, not a
1370
+ second workflow engine: it exposes exactly two pure operations -
1371
+ `prepareReferenceCorrection` (pre-change evidence -> a bounded coding-agent
1372
+ handoff, built from Prompt 1/3/4/5/6/7's existing engines) and
1373
+ `reviewReferenceCorrectionAttempt` (a fresh post-edit candidate -> one
1374
+ composed overall result, built from v0.4's `compareObservations`, v0.7
1375
+ Prompt 6's `evaluateReferenceCandidateFidelity`, and v0.5's
1376
+ `evaluateFrontendContract`) - with an explicit, un-automatable seam between
1377
+ them where an external implementation actor edits target source. Overall
1378
+ `'pass'` requires both reference fidelity `'pass'` and v0.5 contract
1379
+ evaluation `'PASS'` - matching the selected design reference is necessary
1380
+ but never sufficient, so a candidate that visually satisfies the reference
1381
+ while regressing an active protected or preserved contract clause still
1382
+ resolves to overall `'fail'`. Review identity is a deterministic hash of
1383
+ `{referenceRequestId, baselineObservationId, baselineContractId,
1384
+ baselineContractClauses, changeContractId, changeContractClauses,
1385
+ bindingDeclarations}`; attempt identity is a deterministic hash of
1386
+ `{reviewRequestId, candidateObservationId}`. `reviewReferenceCorrectionAttempt`
1387
+ rejects any call whose supplied `reviewRequestId` does not match what its own
1388
+ baseline/contract/reference/binding inputs recompute - the mechanism that
1389
+ makes "every attempt evaluates against the same approved baseline" an
1390
+ enforced invariant, not just a documented one. No new persisted artifact
1391
+ family, no CLI surface, and - most importantly - no code path anywhere in
1392
+ this module (or anything it calls) that opens, parses, or writes a target
1393
+ source file: real candidate capture remains the caller's own responsibility
1394
+ through the existing, unmodified real-Chromium observation pipeline.