@dailephd/my-frontend-observer 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +490 -479
- package/LICENSE +21 -21
- package/README.md +375 -365
- package/dist/application/projectCheckService.d.ts +6 -0
- package/dist/application/projectCheckService.js +8 -1
- package/dist/application/projectCheckService.js.map +1 -1
- package/dist/application/projectWorkflowService.d.ts +7 -2
- package/dist/application/projectWorkflowService.js +10 -3
- package/dist/application/projectWorkflowService.js.map +1 -1
- package/dist/cli.js +510 -510
- package/dist/viewer/index.html +13 -13
- package/dist/viewer/sw.js +1 -1
- package/docs/ARCHITECTURE.md +1394 -1385
- package/docs/CI_CD.md +349 -338
- package/docs/COMMANDS.md +1035 -1026
- package/docs/CONTRACTS.md +1971 -1960
- package/docs/CURRENT_STATE.md +1277 -1252
- package/docs/DEVELOPMENT.md +240 -237
- package/docs/DOCUMENTATION_PRESERVATION_POLICY.md +50 -50
- package/docs/PROJECT_DESCRIPTION.md +2248 -2224
- package/docs/PROJECT_MILESTONES.md +2681 -2558
- package/docs/PROJECT_OVERVIEW.md +200 -196
- package/docs/QUICKSTART.md +100 -100
- package/docs/RELEASE.md +37 -36
- package/docs/ROADMAP.md +1105 -1034
- package/docs/SECURITY.md +297 -297
- package/docs/WORKFLOWS.md +806 -796
- package/docs/plans/v0.10-implementation-plan.md +1509 -1509
- package/docs/plans/v0.8-implementation-plan.md +655 -655
- package/docs/plans/v0.8.1-cli-usability-patch-plan.md +505 -505
- package/docs/plans/v0.9-implementation-plan.md +1529 -1529
- package/docs/plans/v0.9.1-implementation-plan.md +468 -468
- package/docs/reports/v0.10-batch1-visual-change-workflow-foundation.md +102 -102
- package/docs/reports/v0.10-batch2-project-composition-check-recording.md +103 -103
- package/docs/reports/v0.10-batch3-viewer-visual-change-workspace.md +93 -93
- package/docs/reports/v0.10-batch4-actual-frontend-entry.md +59 -59
- package/docs/reports/v0.10-batch5-reference-driven-entry.md +238 -238
- package/docs/reports/v0.10-batch6-coding-agent-handoff.md +85 -85
- package/docs/reports/v0.10-batch7-correction-review-acceptance.md +145 -145
- package/docs/reports/v0.10-batch8-integrated-acceptance.md +109 -109
- package/docs/reports/v0.10-implementation-completeness-documentation-reconciliation.md +344 -344
- package/docs/reports/v0.10-pre-release-readiness.md +120 -120
- package/docs/reports/v0.10-release-preparation.md +70 -70
- package/docs/reports/v0.10.1-project-check-baseline-context-implementation.md +86 -0
- package/docs/reports/v0.7-bounded-fidelity-context-prompt7.md +243 -243
- package/docs/reports/v0.7-implementation-completeness-documentation-reconciliation.md +497 -497
- package/docs/reports/v0.7-pre-release-readiness.md +337 -337
- package/docs/reports/v0.7-reference-binding-prompt5.md +223 -223
- package/docs/reports/v0.7-reference-compatibility-prompt4.md +234 -234
- package/docs/reports/v0.7-reference-correction-workflow-prompt8.md +222 -222
- package/docs/reports/v0.7-reference-fidelity-prompt6.md +216 -216
- package/docs/reports/v0.7-reference-foundation-prompt1.md +151 -151
- package/docs/reports/v0.7-reference-regions-prompt2.md +195 -195
- package/docs/reports/v0.7-reference-requirements-prompt3.md +217 -217
- package/docs/reports/v0.7-release-prep.md +423 -423
- package/docs/reports/v0.8-binding-fidelity-interaction-batch6.md +279 -279
- package/docs/reports/v0.8-bounded-context-correlation-batch7.md +233 -233
- package/docs/reports/v0.8-comparison-contract-inspection-batch4.md +279 -279
- package/docs/reports/v0.8-evidence-index-readers-batch2.md +247 -247
- package/docs/reports/v0.8-implementation-completeness-documentation-reconciliation.md +741 -741
- package/docs/reports/v0.8-integrated-viewer-acceptance-batch8.md +128 -128
- package/docs/reports/v0.8-observation-svg-inspection-batch3.md +223 -223
- package/docs/reports/v0.8-prerelease-readiness-cross-platform-security-code-rot.md +687 -687
- package/docs/reports/v0.8-reference-candidate-inspection-batch5.md +232 -232
- package/docs/reports/v0.8-viewer-runtime-pwa-batch1.md +278 -278
- package/docs/reports/v0.8.1-implementation-completeness-documentation-reconciliation.md +114 -114
- package/docs/reports/v0.8.1-prerelease-readiness-cross-platform-security-code-rot.md +170 -170
- package/docs/reports/v0.9-architecture-retrieval.md +14 -37
- package/docs/reports/v0.9-final-pre-release-readiness.md +209 -209
- package/docs/reports/v0.9-final-readiness-corrections.md +530 -530
- package/docs/reports/v0.9-pre-release-readiness.md +169 -169
- package/docs/reports/v0.9.1-batch1-pwa-hard-gate-isolation.md +359 -359
- package/docs/reports/v0.9.1-batch2-hard-gate-validation-integration.md +262 -262
- package/docs/reports/v0.9.1-pre-release-readiness.md +206 -206
- package/package.json +59 -59
|
@@ -1,279 +1,279 @@
|
|
|
1
|
-
# v0.8 Batch 4 — Before/After Comparison and Contract/Change-Scope Inspection — Implementation Report
|
|
2
|
-
|
|
3
|
-
## 1. Starting state
|
|
4
|
-
|
|
5
|
-
- Branch: `master`
|
|
6
|
-
- Starting HEAD: `05d504a2c3b2419502faf48ff5661abb02b0e10d` ("feat: add v0.8 observation inspection and SVG overlays")
|
|
7
|
-
- `origin/master` after `git fetch`: `a1de8ac01e1367b60021cb04226f56369fa2debb`
|
|
8
|
-
- `git rev-list --left-right --count origin/master...HEAD`: `0 3` — local is exactly Batches 1-3 ahead of origin, no divergence.
|
|
9
|
-
- Starting `git status --short`: clean.
|
|
10
|
-
- Package version confirmed `0.7.0` throughout; never bumped.
|
|
11
|
-
|
|
12
|
-
## 2. A resolved contradiction in the task's path instructions (task §5)
|
|
13
|
-
|
|
14
|
-
The task gave an explicit `Join-Path`-based algorithm (`$WORKFLOW_BASE = Join-Path $REPO_ROOT ".my-dev-kit-workflow"`; `$WORKFLOW_ROOT = Join-Path $WORKFLOW_BASE "v0.8\batch-04"`) together with five verifiable assertions, then separately restated a "Required Batch 4 root" as the old Batches-1-3 sibling path (`...\my-frontend-observer.my-dev-kit-workflow\v0.8\batch-04`). These two are inconsistent: the sibling path **fails assertion 1** (`$WORKFLOW_ROOT` must begin with `$REPO_ROOT\`), since it is a sibling directory name, not a path under the repository root.
|
|
15
|
-
|
|
16
|
-
Verified programmatically in PowerShell:
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
REPO_ROOT=Z:\Users\newuser\Projects\my-frontend-observer
|
|
20
|
-
WORKFLOW_BASE=Z:\Users\newuser\Projects\my-frontend-observer\.my-dev-kit-workflow
|
|
21
|
-
WORKFLOW_ROOT=Z:\Users\newuser\Projects\my-frontend-observer\.my-dev-kit-workflow\v0.8\batch-04
|
|
22
|
-
Assertion1 (starts with REPO_ROOT\): True
|
|
23
|
-
Assertion2 (WORKFLOW_BASE parent = REPO_ROOT): True
|
|
24
|
-
Assertion3 (WORKFLOW_ROOT parent = REPO_ROOT\.my-dev-kit-workflow\v0.8): True
|
|
25
|
-
Assertion4 (not sibling path): True
|
|
26
|
-
Assertion5 (not on C:): True
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
All five assertions pass for the `Join-Path`-derived (inside-repository) path and would fail for the restated sibling path. A decisive tiebreaker was also found in the repository's own `.gitignore` (`.my-dev-kit-workflow/`, present since before this batch) - a pattern that only makes sense for a directory *inside* the repository, since a sibling directory outside the repo is never a `git` ignore-pattern candidate in the first place. Per the task's own instruction ("Do not manually repair the string by guessing"), the literal, executable `Join-Path` algorithm was followed exactly rather than the inconsistent restated path, and this resolution is recorded here rather than silently picked.
|
|
30
|
-
|
|
31
|
-
**Resolved `WORKFLOW_ROOT` for Batch 4:** `Z:\Users\newuser\Projects\my-frontend-observer\.my-dev-kit-workflow\v0.8\batch-04` (inside the repository, gitignored).
|
|
32
|
-
|
|
33
|
-
## 3. Prior generated-path audit (task §6)
|
|
34
|
-
|
|
35
|
-
- **Sibling location** `Z:\Users\newuser\Projects\my-frontend-observer.my-dev-kit-workflow\v0.8\` exists and contains exactly `batch-01`, `batch-02`, `batch-03` (Batches 1-3's own generated state, per their reports) - confirmed present, **not modified, not migrated, not reused** by this batch.
|
|
36
|
-
- **Inside-repository location** `Z:\Users\newuser\Projects\my-frontend-observer\.my-dev-kit-workflow\` also pre-existed, but held only unrelated pre-v0.8 tooling state (`npm-cache`, `pw-browsers`, `readiness` - no `v0.8` subdirectory at all before this batch). Batch 4 added a new `v0.8\batch-04` subtree there without touching those pre-existing siblings.
|
|
37
|
-
- No third, differently-malformed sibling path was found anywhere under `Z:\Users\newuser\Projects\`.
|
|
38
|
-
|
|
39
|
-
## 4. Predecessor reports inspected
|
|
40
|
-
|
|
41
|
-
All three read in full (not console summaries) - previously written in this same session, so their exact content was already held in full working memory and was re-confirmed against this batch's actual needs:
|
|
42
|
-
|
|
43
|
-
- **Batch 1**: host `127.0.0.1`/port `4319`, `src/viewerServer/{httpServer,viewerService}.ts`, PWA `navigateFallbackDenylist: [/^\/api\//]`.
|
|
44
|
-
- **Batch 2**: `evidence/{discovery,classify,handles,pathSafety,index,projection,mediaResolver}.ts` exact module boundaries; `EvidenceMetadataRecord`/`EvidenceArtifactDetail` shapes; `GET /api/index`/`/api/artifacts/<handle>`/`/api/media/<handle>/<role>` exact status-code semantics (404 unknown handle, 409 not-currently-loadable, 405 write methods); the `<family-slug>:<percent-encoded-relativeDir>` handle format; `resolveContainedDir`'s known forward-slash-root bugfix (reused unchanged, not re-litigated).
|
|
45
|
-
- **Batch 3**: `observationView.ts#getObservationRelationships` and `GET /api/observations/<handle>/relationships`; `ObservationWorkspace`/`TargetOverlaySvg`/`ObservationInspector`/`EvidenceFieldView`/`useArtifactDetail`/`targetOrder.ts` component/hook boundaries; the coordinate-mapping decision (`requestConfig.viewport` as the canonical SVG `viewBox` source, geometry never rewritten); the `data-target-name` attribute already present on rendered `<rect>`/`<g>` elements.
|
|
46
|
-
|
|
47
|
-
Batch 4 extends these mechanisms exactly - no second observation viewer, no second evidence index, no second media-loading path was created.
|
|
48
|
-
|
|
49
|
-
## 5. Frozen planning authority inspected
|
|
50
|
-
|
|
51
|
-
`docs/DOCUMENTATION_PRESERVATION_POLICY.md`, `docs/PROJECT_MILESTONES.md` (Milestone 8), `docs/ROADMAP.md` (v0.8), `docs/plans/v0.8-implementation-plan.md` (Batch 4 section + cross-batch invariants) - all previously read in full during Batches 1-3, re-confirmed unchanged. `docs/ARCHITECTURE.md`, `docs/CONTRACTS.md` (previously read in full), `docs/WORKFLOWS.md`, `docs/COMMANDS.md` (then edited). New this batch, read in full: `src/domain/comparison.ts`, `src/domain/frontendContracts.ts`, `src/domain/frontendContractEvaluationArtifact.ts`; targeted reads of `src/domain/frontendContractEvaluation.ts` (`UnexpectedChangeResult`, `FrontendContractEvaluationInput/Result`) and the three readers (`comparisonArtifactReader.ts`, `frontendContractArtifactReader.ts`, `frontendContractEvaluationArtifactReader.ts` - function names confirmed, bodies already known from having authored `classify.ts` in Batch 2).
|
|
52
|
-
|
|
53
|
-
Confirmed Batch 4's title/scope in `docs/plans/v0.8-implementation-plan.md` match the task exactly; no material difference found.
|
|
54
|
-
|
|
55
|
-
## 6. my-dev-kit retrieval
|
|
56
|
-
|
|
57
|
-
Index built successfully at `$WORKFLOW_ROOT\my-dev-kit-index`. All seven required searches were run (comparison artifact ownership, comparison engine ownership, frontend contract shapes, contract evaluation ownership, viewer linked-evidence/index APIs, Batch 3 observation viewer, existing protected/preserved-regression tests). Every result matched direct source inspection - `src/domain/comparison.ts`, `comparisonEngine.ts`, `frontendContracts.ts`, `frontendContractEvaluation.ts`, `frontendContractEvaluationArtifact.ts`, the three readers, and the application services were all surfaced consistently.
|
|
58
|
-
|
|
59
|
-
## 7. Comparison contract audit (task §10)
|
|
60
|
-
|
|
61
|
-
Recorded from `src/domain/comparison.ts` (full read):
|
|
62
|
-
|
|
63
|
-
- `comparisonId` (fresh per execution), `comparisonRequestId` (deterministic, `compare(A,B) !== compare(B,A)`).
|
|
64
|
-
- `before`/`after`: **ordered**, distinct fields (`ComparisonSourceObservationReference` = `{observationId, requestId, producer:{name,version}, observationSchemaVersion, screenshot:{path}}`) - never swappable, confirmed by the type's own doc comment ("Comparison is ordered... never a swappable pair").
|
|
65
|
-
- `config: ComparisonConfig`, `comparability: ComparabilityResult` (`state` + `reasons[]`, each reason carrying `code`/`severity`/`message`), `configurationChanges: TargetConfigurationChange[]` (`added`/`removed`/`locator-changed`), `relationshipsBefore`/`relationshipsAfter: LayoutRelationshipGraph` (the pre-persisted graphs, not re-derivable data), `differences: ComparisonDifference[]`, `relationshipChanges: RelationshipChangeRecord[]`, `expectedDependencyEvidence: ExpectedDependencyEvidence[]`, `diagnostics: Diagnostic[]`, `limits: {truncated, omittedFields, omittedTargetPairs}`.
|
|
66
|
-
- Source references carry logical identity (`observationId`/`requestId`/`producer`/`observationSchemaVersion`) - never a persisted filesystem path. Confirmed: no path field anywhere in `ComparisonSourceObservationReference`.
|
|
67
|
-
|
|
68
|
-
## 8. Source-observation resolution rule (task §11)
|
|
69
|
-
|
|
70
|
-
**Exact match on all four available identity fields simultaneously**: `observationId`, `requestId`, `producer.version` (`producer.name` is already a fixed constant, `PRODUCER_NAME`, so only `.version` varies), and `observationSchemaVersion` - implemented in `src/viewerServer/evidence/linkedEvidence.ts#resolveObservationByReference`. Zero matches → `{status: 'missing'}`. Two or more exact matches → `{status: 'ambiguous', count}` - **never silently picks one** (proven with a real duplicated-manifest fixture in `tests/unit/linkedEvidenceServer.test.ts`). No folder-name/screenshot-filename/URL/target-set/geometry-based matching exists anywhere in this module. The resolution walks the bounded evidence tree once per lookup (mirroring Batch 2's `findImportedReferenceDir` pattern exactly, applied consistently here for comparisons/baseline-contracts/change-contracts too).
|
|
71
|
-
|
|
72
|
-
## 9. Evaluation linked-artifact resolution rule
|
|
73
|
-
|
|
74
|
-
`FrontendContractEvaluationArtifact` references are resolved by exact identity, each independently:
|
|
75
|
-
|
|
76
|
-
- **Comparison**: exact `comparisonId` **and** `comparisonRequestId` (`resolveComparisonByIdentity`).
|
|
77
|
-
- **Baseline contract**: exact `baselineId` (`resolveBaselineContractById`).
|
|
78
|
-
- **Per-change contract**: exact `contractId` (`resolveChangeContractById`).
|
|
79
|
-
- **Before/after observations**: the same exact-reference rule as §8 (`resolveObservationByReference`), applied to `evaluation.before`/`evaluation.after`.
|
|
80
|
-
|
|
81
|
-
All five resolutions run in parallel (`Promise.all`) inside `src/viewerServer/evidence/evaluationView.ts#getEvaluationView`, each independently reporting `resolved`/`missing`/`ambiguous` - one missing/ambiguous linked artifact never blocks resolution of the others (proven in `tests/unit/linkedEvidenceServer.test.ts`: a missing baseline and a missing comparison are each tested independently while the rest of the evaluation view still resolves correctly).
|
|
82
|
-
|
|
83
|
-
## 10. Comparison-view architecture
|
|
84
|
-
|
|
85
|
-
`src/viewerServer/evidence/comparisonView.ts#getComparisonView(root, handle)`: same handle-decode → contained-dir-resolve → re-stat → re-classify discipline as `observationView.ts`/`mediaResolver.ts` (Batches 2-3). Requires `family === 'comparison'` and `supportState === 'supported'` (else `404`/`409`), then resolves `artifact.before`/`artifact.after` via §8's rule. Route: `GET /api/comparisons/<handle>/view` → `{ok:true, before: LinkStatus, after: LinkStatus}`. The comparison's own full payload is **not** duplicated in this response - the browser fetches it separately through the existing, unchanged `GET /api/artifacts/<handle>` (Batch 2), and the resolved before/after `handle`s are fed straight into that same existing route for the source observations. This keeps the new route minimal/ephemeral and avoids inventing a second artifact-detail contract.
|
|
86
|
-
|
|
87
|
-
## 11. Before/after reuse of Batch 3 components (task §35)
|
|
88
|
-
|
|
89
|
-
`viewer/src/components/ComparisonObservationPane.tsx` is built **entirely** from Batch 3's existing lower-level primitives: `useArtifactDetail` (unchanged), `orderedTargets` (unchanged), `TargetOverlaySvg` (one small additive change, below). No second screenshot-loading, coordinate-transform, or geometry-rendering implementation exists anywhere in Batch 4. The comparison's own persisted `relationshipsBefore`/`relationshipsAfter` are passed directly into `TargetOverlaySvg`'s existing `relationships: LayoutRelationshipGraph | undefined` prop - **`deriveLayoutRelationships` is never called to replace them** (grep-verified: no import of `deriveLayoutRelationships` exists in any Batch-4 file except the pre-existing Batch 3 `observationView.ts`, which Batch 4 does not modify).
|
|
90
|
-
|
|
91
|
-
`TargetOverlaySvg.tsx` gained one additive, optional prop: `highlightNames?: ReadonlySet<string> | undefined`, rendering an extra `--highlighted` CSS class on any rectangle whose name is in the set, alongside (never replacing) the existing single-select `selected`/`aria-pressed` interaction. This lets a relationship-subject difference or a two-target contract primitive (e.g. `targets-do-not-overlap`) emphasize both named targets without changing Batch 3's existing single-select contract or its passing tests (`npm run test:browser` re-run in full, §21 below - all pre-existing Batch 3 tests pass unmodified).
|
|
92
|
-
|
|
93
|
-
## 12. Confirmation: `compareObservations` was **not** called for viewer reconstruction
|
|
94
|
-
|
|
95
|
-
Grep-verified across every Batch 4 file (`src/viewerServer/evidence/{linkedEvidence,comparisonView,evaluationView}.ts`, every `viewer/src/**` file touched this batch): zero imports of `compareObservations` or `comparisonEngine.js`. The persisted `ComparisonArtifact` (`differences`, `relationshipsBefore`/`After`, `comparability`, `configurationChanges`, `expectedDependencyEvidence`, `diagnostics`, `limits`) is read unchanged via the existing Batch 2 `GET /api/artifacts/<handle>` and rendered as-is in `ComparisonWorkspace.tsx`.
|
|
96
|
-
|
|
97
|
-
## 13. Differences displayed (task §15)
|
|
98
|
-
|
|
99
|
-
All 13 canonical `DifferenceKind` values are rendered generically (kind/subject/before/after/delta, exactly as persisted, `JSON.stringify`'d for `before`/`after`/`delta` since their shape is difference-kind-dependent per the domain type's own design): `appeared`, `disappeared`, `moved`, `resized`, `visibility-changed`, `clipping-changed`, `containment-changed`, `horizontal-overflow-changed`, `vertical-overflow-changed`, `page-size-changed`, `scroll-owner-changed`, `relative-position-changed`, `relationship-changed`. No new PASS/FAIL severity is assigned anywhere - `ComparisonWorkspace.tsx`'s difference list is presentation-only.
|
|
100
|
-
|
|
101
|
-
## 14. Difference target highlighting (task §16)
|
|
102
|
-
|
|
103
|
-
`subjectTargetNames(subject: ComparisonDifferenceSubject)`: `type: 'target'` → `[subject.target]`; `type: 'relationship'` → `[subjectTarget, relatedTarget].filter(defined)`; `type: 'page'` → `[]` (no target ever fabricated for a page-level subject). Clicking a difference calls `highlightSubject`, which sets the first name as the single interactive `selected` target and any remaining name(s) as `highlightNames`. Because `TargetOverlaySvg` only ever renders a rectangle for a target with real geometry in *that specific* observation, an `appeared` target (absent in `before`) simply has no rectangle in the Before pane, and a `disappeared` target (absent in `after`) has none in the After pane - **never fabricated**, proven with a real Chromium test (`comparisonEvaluationWorkspace.test.ts`, "appeared/disappeared target behavior") that asserts a rectangle count of exactly `0` on the absent side and `1` on the present side, for both targets.
|
|
104
|
-
|
|
105
|
-
## 15. Comparability presentation (task §18)
|
|
106
|
-
|
|
107
|
-
`.comparability-banner--{comparable|comparable-with-warnings|incomparable}` renders `artifact.comparability.state` and every reason with its own severity class (`blocking`/`warning`/`unassessed`) and message unchanged. An `incomparable` result gets `role="alert"` and a red-bordered banner - visually unmistakable, proven with a real Chromium test against a genuinely `incomparable` fixture (two observations with different `requestConfig.viewport` producing a real `viewport-mismatch` blocking reason from `compareObservations` itself, never asserted/faked).
|
|
108
|
-
|
|
109
|
-
## 16. Target configuration changes (task §19)
|
|
110
|
-
|
|
111
|
-
Rendered as their own `Configuration changes` section (`kind` + `target`), entirely separate from the `Differences` section that shows `appeared`/`disappeared` - the two lists never merge or share a component.
|
|
112
|
-
|
|
113
|
-
## 17. Expected dependency evidence (task §20)
|
|
114
|
-
|
|
115
|
-
Rendered under the heading "Expected dependency evidence (explicit, non-causal)"; each entry shows `cause.target.property direction → effect.target.property direction: outcome`, where `outcome` is exactly one of `consistent`/`not-observed`/`contradictory-to-declaration`/`unavailable` - never rephrased as a pass/fail or causal claim.
|
|
116
|
-
|
|
117
|
-
## 18. Comparison diagnostics and limits (task §21)
|
|
118
|
-
|
|
119
|
-
`artifact.limits.truncated === true` renders a visible, non-suppressible banner listing `omittedFields`/`omittedTargetPairs`; `artifact.diagnostics` (when non-empty) render as their own section. Neither is hidden behind a successful-looking default state.
|
|
120
|
-
|
|
121
|
-
## 19. Contract/evaluation architecture
|
|
122
|
-
|
|
123
|
-
`src/viewerServer/evidence/evaluationView.ts#getEvaluationView(root, handle)` mirrors `comparisonView.ts` exactly, resolving all five linked references (§9) in parallel. Route: `GET /api/evaluations/<handle>/view` → `{ok:true, comparison, baseline, change, before, after}` (each a `LinkStatus`). `viewer/src/components/EvaluationWorkspace.tsx` fetches the evaluation's own already-loaded artifact (from `ArtifactPreview`'s existing `useArtifactDetail`) plus this view route, then calls `useArtifactDetail` four more times (baseline/change/comparison handles, plus reusing `ComparisonObservationPane` for before/after) - all through the same existing Batch 2 on-demand-loading contract, never a new artifact-fetching mechanism.
|
|
124
|
-
|
|
125
|
-
## 20. Confirmation: `evaluateFrontendContract` was **not** called for viewer reconstruction
|
|
126
|
-
|
|
127
|
-
Grep-verified: zero imports of `evaluateFrontendContract` or `frontendContractEvaluation.js`'s evaluator export anywhere in Batch 4's `src/viewerServer/` or `viewer/src/` code (only `UnexpectedChangeResult`, a pure type, is imported for typing). `overallVerdict`, `clauseResults`, `activeBaselineClauseIds`, `supersededBaselineClauseIds`, and `unexpectedChanges` are the persisted artifact's own fields, read unchanged via `GET /api/artifacts/<handle>` and rendered as-is.
|
|
128
|
-
|
|
129
|
-
## 21. Baseline clauses / change-scope / clause results / active-superseded / unexpected changes / overall verdict
|
|
130
|
-
|
|
131
|
-
- **Clause joining by exact `clauseId` only** (`viewer/src/components/ClauseResultRow.tsx`, `EvaluationWorkspace.tsx`): a `Map<clauseId, {source, clause}>` is built from the loaded baseline's `clauses` and the loaded change contract's `clauses`; each `clauseResults` entry is looked up by its own `clauseId` - never by target/primitive-shape/category/position/text similarity. An id present in neither loaded contract renders as an honest "unresolved clause definition" (never a fabricated category/primitive) - a distinct "Unresolved clause definitions" section exists specifically for this case.
|
|
132
|
-
- **Baseline clauses** are shown in their own section, distinctly from per-change categories, each tagged `active` or `superseded` **exclusively from the evaluation artifact's own `activeBaselineClauseIds`/`supersededBaselineClauseIds`** - never recomputed from clause-id overlap or any other inference.
|
|
133
|
-
- **Per-change clauses** are grouped into four sections in the frozen category order (`requested`, `expected-dependent`, `protected`, `preserved`); `expected-dependent` clauses additionally show their `required`/`permitted` mode without merging the two modes' meaning.
|
|
134
|
-
- **Clause result status** (`pass`/`fail`/`unavailable`/`conflict`) is preserved exactly via `statusLabel()` - `unavailable` always shows its `reason`, `conflict` always shows its `reason` and `conflictingClauseIds`; neither is ever collapsed to a boolean or converted to `fail`.
|
|
135
|
-
- **Unexpected changes** render in their own section, `classification: 'unexpected'` preserved verbatim, with target highlighting from the same `subjectTargetNames`-style extraction as ordinary differences (since `UnexpectedChangeResult.subject` reuses `ComparisonDifferenceSubject` unchanged - confirmed in `frontendContractEvaluation.ts`).
|
|
136
|
-
- **Overall verdict**: `artifact.overallVerdict` renders directly in a large `.overall-verdict--PASS`/`.overall-verdict--FAIL` banner - the UI performs no verdict computation of its own anywhere in this batch's code (grep-verified: no `every(...status==='pass')`-style logic exists in `EvaluationWorkspace.tsx`).
|
|
137
|
-
|
|
138
|
-
## 22. Target cross-highlighting from contract primitives (task §34)
|
|
139
|
-
|
|
140
|
-
`viewer/src/contract/clauseTargets.ts#primitiveTargetNames` is an exhaustive `switch` over every `ContractPrimitiveKind`: single-target primitives (`target-visible`, `target-not-clipped`, `target-width-within-bound`, `target-does-not-own-scroll`, `target-begins-below-initial-viewport`, `property-unchanged-within-tolerance`, `property-increases`, `property-decreases`) return `[target]`; two-target primitives (`targets-do-not-overlap`, `target-wider-than`, `target-follows-vertically`) return `[targetA, targetB]`; `target-fits-inside` returns `[target, container]`; `relationship-unchanged` returns `[subjectTarget, relatedTarget].filter(defined)`; the two page-level primitives (`document-width-fits-viewport`, `scroll-owner-is-document`) return `[]` - clicking their clause row is disabled (`names.length === 0` → `disabled`) rather than highlighting an invented target.
|
|
141
|
-
|
|
142
|
-
## 23. All-pass proof (task §33/§44 Case A)
|
|
143
|
-
|
|
144
|
-
**Fixture**: `tests/support/evidenceFixtures.ts#writeAllPassPipelineFixture` - the deliberate all-pass counterpart to the existing `writeFullPipelineFixture`, differing only in the *actual rendered geometry/style* fed to the real `compareObservations`/`evaluateFrontendContract` workflow (via `evaluateAndPersistFromArtifactRoots`): `rightAd` genuinely unchanged (protected clause genuinely satisfied) and `navigation` genuinely never clipped (`scrollWidth === clientWidth`, preserved clause genuinely satisfied). Verified directly against the real evaluator's output before use (§30 below) - `overallVerdict: "PASS"`, all four clauses `pass`. `overallVerdict` is never hand-edited.
|
|
145
|
-
|
|
146
|
-
**Real-browser proof**: `tests/browser/comparisonEvaluationWorkspace.test.ts`, "Case A" describe block - selects the evaluation, confirms `.overall-verdict--PASS`, confirms both before/after screenshots load through the real `/api/media/observation:...` endpoint, confirms `requested-nav`/`protected-rightad`/`preserved-nav-unclipped` are each `.clause-row--pass`, and confirms clicking the `requested-nav` clause row highlights the exact `navigation` target (`aria-pressed="true"` on its real SVG rectangle).
|
|
147
|
-
|
|
148
|
-
## 24. Safety-failure proof (task §32/§44 Case B)
|
|
149
|
-
|
|
150
|
-
**Fixture**: the existing `writeFullPipelineFixture` (reused unchanged, not a new fixture) - already produces, through the real canonical evaluator, `overallVerdict: "FAIL"` with `requested-nav: pass`, `protected-rightad: fail`, `preserved-nav-unclipped: fail` (confirmed by direct inspection before use, §30). This is exactly the required safety case: **a requested/local change passes while a protected clause and a preserved clause both fail, and the overall verdict is FAIL** - never hand-constructed.
|
|
151
|
-
|
|
152
|
-
**Real-browser proof**: `tests/browser/comparisonEvaluationWorkspace.test.ts`, "Case B" describe block - selects the evaluation, confirms `.overall-verdict--FAIL`, confirms `requested-nav` is `.clause-row--pass` while `protected-rightad` and `preserved-nav-unclipped` are both `.clause-row--fail`, confirms before/after visual context remains fully available (both screenshots render) despite the overall failure, and confirms clicking the failing `protected-rightad` clause highlights the exact `rightAd` target.
|
|
153
|
-
|
|
154
|
-
## 25. API changes
|
|
155
|
-
|
|
156
|
-
| Route | Method | Semantics |
|
|
157
|
-
|---|---|---|
|
|
158
|
-
| `GET /api/comparisons/<handle>/view` | GET/HEAD | `{ok:true, before: LinkStatus, after: LinkStatus}`. `404` unknown handle, `409` not-a-comparison/not-currently-loadable, `405` any write method. |
|
|
159
|
-
| `GET /api/evaluations/<handle>/view` | GET/HEAD | `{ok:true, comparison, baseline, change, before, after}` (each a `LinkStatus`). `404`/`409`/`405` as above. |
|
|
160
|
-
|
|
161
|
-
`/api/status`, `/api/index`, `/api/artifacts/<handle>`, `/api/media/<handle>/<role>`, `/api/observations/<handle>/relationships` are byte-for-byte unchanged.
|
|
162
|
-
|
|
163
|
-
## 26. PWA cache boundary
|
|
164
|
-
|
|
165
|
-
**PASS.** Both new routes live under `/api/`, already covered by Batch 1's `navigateFallbackDenylist: [/^\/api\//]` - no service-worker configuration change was needed. `tests/unit/viewerPwaBuild.test.ts` gained one explicit assertion against the real built `sw.js`: still exactly one `registerRoute` call, and the precache manifest contains neither `/api/comparisons` nor `/api/evaluations`.
|
|
166
|
-
|
|
167
|
-
## 27. Files created
|
|
168
|
-
|
|
169
|
-
- `src/viewerServer/evidence/linkedEvidence.ts`, `comparisonView.ts`, `evaluationView.ts`
|
|
170
|
-
- `viewer/src/components/ComparisonObservationPane.tsx`, `ComparisonWorkspace.tsx`, `EvaluationWorkspace.tsx`, `ClauseResultRow.tsx`
|
|
171
|
-
- `viewer/src/contract/clauseTargets.ts`
|
|
172
|
-
- `viewer/src/hooks/useLinkedEvidence.ts`
|
|
173
|
-
- `viewer/src/types/comparison.ts`, `contracts.ts`
|
|
174
|
-
- `tests/unit/linkedEvidenceServer.test.ts`
|
|
175
|
-
- `tests/browser/comparisonEvaluationWorkspace.test.ts`
|
|
176
|
-
- `docs/reports/v0.8-comparison-contract-inspection-batch4.md` (this file)
|
|
177
|
-
|
|
178
|
-
## 28. Files modified
|
|
179
|
-
|
|
180
|
-
- `src/viewerServer/httpServer.ts` - added the two new routes (§25); `/api/status`, `/api/index`, `/api/artifacts/<handle>`, `/api/media/<handle>/<role>`, `/api/observations/<handle>/relationships`, and static-asset serving unchanged.
|
|
181
|
-
- `viewer/src/components/TargetOverlaySvg.tsx` - one additive, optional `highlightNames` prop (§11); existing `selected`/`onSelect`/`toggles` behavior unchanged.
|
|
182
|
-
- `viewer/src/components/ArtifactPreview.tsx` - branches to `ComparisonWorkspace`/`EvaluationWorkspace` for their families; every other family's raw-JSON preview unchanged.
|
|
183
|
-
- `viewer/src/styles/index.css` - additive rules for the new workspaces.
|
|
184
|
-
- `tests/support/evidenceFixtures.ts` - added `writeAllPassPipelineFixture`, `writeBaselineSupersessionFixture`, `writeAppearedDisappearedComparisonFixture`, `writeIncomparableComparisonFixture` (all built through the real canonical `compareObservations`/`evaluateFrontendContract` workflow, never hand-edited results); `buildObservation` gained optional `pageEvidence`/`viewport` parameters (already added in Batch 3, unchanged here).
|
|
185
|
-
- `tests/browser/viewerEvidenceShell.test.ts` - two pre-existing Batch 2 tests that selected a `comparison` record to exercise the *generic* raw-JSON-preview/no-visualization path now legitimately conflict with Batch 4's real comparison workspace; both were repointed to `baseline-contract` (which still exercises exactly the generic path/invariant they were written to protect) - the same kind of sanctioned evolution as Batch 1→2's placeholder-text update, Batch 2's `TST-401`, and Batch 3's analogous `viewerEvidenceShell.test.ts` update.
|
|
186
|
-
- `tests/unit/viewerPwaBuild.test.ts` - added the explicit Batch 4 cache-boundary assertion (§26).
|
|
187
|
-
- `docs/ARCHITECTURE.md`, `docs/COMMANDS.md` - new/updated Batch 4 sections (condensed versions of §7-§22 above).
|
|
188
|
-
|
|
189
|
-
## 29. Tests added/modified and behavior protected
|
|
190
|
-
|
|
191
|
-
| Test file | Level | Protects |
|
|
192
|
-
|---|---|---|
|
|
193
|
-
| `linkedEvidenceServer.test.ts` (15 tests) | unit/integration (real HTTP) | Exact before/after resolution; honest `missing` when a source observation is removed; honest `ambiguous` with a real duplicated-identity fixture (count=2); 409 for a non-comparison/non-evaluation handle; 404 unknown handle; 405 write methods; exact resolution of all five evaluation links; missing baseline reported honestly; missing comparison reported honestly; the all-pass fixture is a genuine `PASS` with every clause `pass`; the full-pipeline fixture is a genuine `FAIL` with the exact required requested-pass/protected-fail/preserved-fail signature; the baseline-supersession fixture reports real active/superseded ids and a real unexpected change. |
|
|
194
|
-
| `comparisonEvaluationWorkspace.test.ts` (4 tests, real Chromium) | browser | Case A (all-pass) full proof (§23); Case B (safety failure) full proof (§24); appeared/disappeared honest rectangle presence/absence; incomparable state visibly unmistakable with its real blocking reason. |
|
|
195
|
-
| `viewerEvidenceShell.test.ts` (2 updated) | browser | Generic Batch 2 on-demand-load/no-visualization invariants still hold for families without a Batch 3/4 workspace. |
|
|
196
|
-
| `viewerPwaBuild.test.ts` (+1) | build/integration | New routes covered by the existing `/api/` denylist, no new runtime-caching rule. |
|
|
197
|
-
|
|
198
|
-
## 30. A methodology note: fixtures verified against the real evaluator before use
|
|
199
|
-
|
|
200
|
-
Before writing any assertion against `writeAllPassPipelineFixture`, `writeFullPipelineFixture` (reused), or `writeBaselineSupersessionFixture`, each was run once through the real `readFrontendContractEvaluationArtifact` reader in an isolated scratch test and its actual `overallVerdict`/`clauseResults`/`activeBaselineClauseIds`/`supersededBaselineClauseIds` were inspected directly (not assumed) - this is how the exact safety-case signature (`requested-nav: pass`, `protected-rightad: fail`, `preserved-nav-unclipped: fail`, `overallVerdict: "FAIL"`) in the pre-existing `writeFullPipelineFixture` was *discovered*, not designed - it already existed from Batch 2/3's fixture reuse and turned out to exactly match Batch 4's required safety case. The scratch inspection tests were deleted before finalizing (never committed); this report documents the methodology rather than leaving throwaway files behind.
|
|
201
|
-
|
|
202
|
-
## 31. Validation results
|
|
203
|
-
|
|
204
|
-
| Command | Result |
|
|
205
|
-
|---|---|
|
|
206
|
-
| `npm run typecheck` | **PASS** (zero errors, both `tsconfig.json` and `viewer/tsconfig.json`) |
|
|
207
|
-
| `npm run lint` | **PASS** (zero errors/warnings) |
|
|
208
|
-
| `npm test` | **PASS** — 1101/1101 tests, 60/60 files |
|
|
209
|
-
| `npm run build` | **PASS** — unchanged Node/library output plus `dist/viewerServer/evidence/{comparisonView,evaluationView,linkedEvidence}.js` and the rebuilt `dist/viewer/**` PWA |
|
|
210
|
-
| `npm run check:docs` | **PASS** — "Documentation check passed (17 required files)." |
|
|
211
|
-
| `npm run test:browser` | **PASS** — 141/141 tests, 14/14 files (real Chromium; run in full per task §46) |
|
|
212
|
-
| `git diff --check` | **PASS** — no whitespace errors (only expected LF→CRLF notices) |
|
|
213
|
-
|
|
214
|
-
## 32. Built viewer smoke (task §47)
|
|
215
|
-
|
|
216
|
-
Fixture: one real before/after observation pair, a real comparison, a real baseline contract, a real per-change contract (the "milestone signature" clause set), and a real evaluation - all built via a one-off script (not committed, `$WORKFLOW_ROOT\tmp`) calling the actual compiled `dist/artifacts/*.js`/`dist/application/frontendContractEvaluationService.js` modules directly, under `$WORKFLOW_ROOT\smoke\evidence-root`.
|
|
217
|
-
|
|
218
|
-
Command: `node dist/cli.js view --root "<WORKFLOW_ROOT>\smoke\evidence-root" --port 4319 --no-open`
|
|
219
|
-
|
|
220
|
-
All required checks passed against the real running built server:
|
|
221
|
-
|
|
222
|
-
- `/api/status` → `200`; `/api/index` → all 6 real records (`observation` x2, `comparison`, `baseline-contract`, `change-contract`, `contract-evaluation`), all `supported`.
|
|
223
|
-
- `/api/comparisons/<handle>/view` → `200`, both `before`/`after` `resolved` to the exact real observation handles.
|
|
224
|
-
- `/api/evaluations/<handle>/view` → `200`, all five links (`comparison`/`baseline`/`change`/`before`/`after`) `resolved`.
|
|
225
|
-
- `/api/artifacts/<evaluation-handle>` → `overallVerdict: "FAIL"`, `clauseResults`: `requested-nav: pass`, `expected-workspace: pass`, `protected-rightad: fail`, `preserved-nav-unclipped: fail` - the exact required safety signature, produced by the real canonical workflow, not edited.
|
|
226
|
-
- Both before/after screenshots → `200 image/png`.
|
|
227
|
-
- Unknown comparison/evaluation handles → `404`; write method (`POST`) → `405`.
|
|
228
|
-
- PWA still loads (`/`, `/sw.js` both `200`); `sw.js` contains no `api/comparisons`/`api/evaluations` reference.
|
|
229
|
-
- `netstat` confirmed `127.0.0.1:4319` only, never `0.0.0.0`.
|
|
230
|
-
- Server located by its real PID and terminated with `taskkill /F`; a follow-up `netstat` confirmed the port was released.
|
|
231
|
-
- A post-shutdown listing of the smoke evidence root shows exactly the 8 fixture files the script wrote - no stray writes, no modification.
|
|
232
|
-
|
|
233
|
-
**Result: PASS.** Logs retained under `$WORKFLOW_ROOT\logs\` (`smoke-server.log`, `index-response.json`, `eval-detail.json`, `smoke-checks-1.log`, `smoke-checks-2.log`).
|
|
234
|
-
|
|
235
|
-
## 33. Generated path inventory
|
|
236
|
-
|
|
237
|
-
| Path | Disposition |
|
|
238
|
-
|---|---|
|
|
239
|
-
| `WORKFLOW_ROOT\cache\npm` | Retained (npm cache from the my-dev-kit-index `npx` invocation) |
|
|
240
|
-
| `WORKFLOW_ROOT\tmp\vite-cache`, `WORKFLOW_ROOT\tmp\build-smoke-comparison.mjs` | Retained (build cache empty again, same finding as every prior batch; the smoke-fixture script is dev/readiness tooling only, not committed) |
|
|
241
|
-
| `WORKFLOW_ROOT\logs\*` | Retained (smoke evidence, §32) |
|
|
242
|
-
| `WORKFLOW_ROOT\smoke\evidence-root` | Retained (real evidence fixture tree built for the smoke test) |
|
|
243
|
-
| `WORKFLOW_ROOT\my-dev-kit-index\*` | Retained (successful index + cache-metadata) |
|
|
244
|
-
| `WORKFLOW_ROOT\fixtures` | Retained, empty/unused (no committed deterministic repository fixture was needed - all Batch 4 fixtures are built programmatically via `tests/support/evidenceFixtures.ts`, matching the existing repository convention) |
|
|
245
|
-
| Repo-root `dist/` | Ordinary build output (gitignored); rebuilt cleanly by `scripts/clean.mjs` on every `npm run build` |
|
|
246
|
-
| Pre-existing repo-root `.my-dev-kit*`/`baselines`/`comparisons`/`contracts`/`evaluations`/`observations` | Pre-existing, empty, untouched (same finding as every prior batch) |
|
|
247
|
-
| Sibling `...my-frontend-observer.my-dev-kit-workflow\v0.8\{batch-01,batch-02,batch-03}` | Untouched (verified, §3) |
|
|
248
|
-
|
|
249
|
-
## 34. Repository pollution check
|
|
250
|
-
|
|
251
|
-
**PASS.** `git status --short` before staging showed only the 9 modified + 13 new Batch-4-owned paths listed in §27/§28. No unexpected file or directory appeared anywhere in the repository. No malformed sibling Batch 4 workflow path exists anywhere under `Z:\Users\newuser\Projects\` (verified directly, §3).
|
|
252
|
-
|
|
253
|
-
## 35. Batch 1/2/3 regression check
|
|
254
|
-
|
|
255
|
-
**PASS.** All Batch 1 tests (`view` CLI, `127.0.0.1`/port `4319`, PWA shell/installability, cache boundary, server cleanup), Batch 2 tests (evidence indexing, unsupported-version handling, safe handles, on-demand artifact/media loading, filesystem containment), and Batch 3 tests (observation SVG workspace, coordinate mapping, relationship reuse, unresolved-target honesty) pass unmodified except the two `viewerEvidenceShell.test.ts` updates described in §28, which preserve the exact invariants they originally protected while accounting for Batch 4's legitimate new comparison-family behavior.
|
|
256
|
-
|
|
257
|
-
## 36. v0.1-v0.7 regression check
|
|
258
|
-
|
|
259
|
-
**PASS.** Every pre-v0.8 unit and browser test suite (`observe`, `compare`, `approve-baseline`, `save-change-contract`, `evaluate-contract`, `import-reference`, `approve-reference`, `evaluate-reference-fidelity`) remains covered and passing - none was touched by this batch's diff. `src/domain/`, `src/artifacts/*Writer.ts`, and every existing reader/engine (`compareObservations`, `evaluateFrontendContract`, `deriveLayoutRelationships`) are byte-for-byte unchanged.
|
|
260
|
-
|
|
261
|
-
## 37. Deviations
|
|
262
|
-
|
|
263
|
-
- The task's §5 "Required Batch 4 root" restatement conflicted with its own `Join-Path` algorithm and verification assertions; resolved per §2 above by following the literal, executable, self-verifying algorithm. This is the only deviation from the task's literal text, and it was necessary to satisfy the task's own stated assertions rather than an arbitrary choice.
|
|
264
|
-
- No other deviations. Every other task step (predecessor-report inspection, my-dev-kit retrieval, the coordinate/contract audits, all required test categories, the full validation chain including `test:browser`, and the built-CLI smoke) was executed as specified.
|
|
265
|
-
|
|
266
|
-
## 38. Remaining uncovered risks
|
|
267
|
-
|
|
268
|
-
- **`highlightNames` only distinguishes "the primary selected target" from "secondary highlighted targets" visually (different CSS classes), not through `aria-pressed`** (which remains true only for the single `selected` target, matching Batch 3's existing accessibility contract). A screen-reader user selecting a relationship-subject difference will not hear the second (related) target announced as selected, only visually distinguished. Not a regression (Batch 3 never had multi-target selection at all), but a genuine accessibility gap for the new multi-target case specifically.
|
|
269
|
-
- **`resolveObservationByReference`/`resolveComparisonByIdentity`/etc. each independently re-walk the bounded evidence tree** (same pattern as Batch 2's `findImportedReferenceDir`, carried forward deliberately for consistency rather than introducing a new caching layer). An evaluation view triggers five such walks in parallel; for an evidence root near the `MAX_MANIFEST_CANDIDATES` bound, this is more filesystem work per evaluation-view request than a single shared index pass would need. Not a correctness risk; the same category of "report rather than pre-optimize" risk Batch 2 already recorded.
|
|
270
|
-
- **`ComparisonWorkspace`/`EvaluationWorkspace` do not yet offer a synchronized zoom/pan or a locked view between before/after panes** - explicitly out of scope for this batch (task §42), planned for a later batch.
|
|
271
|
-
- Batches 1-3's previously reported risks (install-prompt "available" branch untested in headless Chromium, no live service-worker execution test) remain unresolved and out of this batch's scope.
|
|
272
|
-
|
|
273
|
-
## 39. Out-of-scope confirmation
|
|
274
|
-
|
|
275
|
-
Confirmed absent from this batch's diff: external-reference image display, reference regions, reference/candidate side-by-side mode, reference applicability UI, reference fidelity UI, reference/runtime binding, explicit-binding cross-selection, image zoom/pan, synchronized view lock, bounded-agent-context UI, runtime/static correlation UI, annotation, contract editing, baseline approval, contract approval, reference approval, source editing, automatic binding, automatic visual analysis, pixel difference, cloud hosting, database, authentication, collaboration.
|
|
276
|
-
|
|
277
|
-
## 40. Final verdict
|
|
278
|
-
|
|
279
|
-
Batch 4 ("Before/after comparison and contract/change-scope inspection") is implemented and independently validated: a developer can select an existing `ComparisonArtifact`, see its exact persisted before/after observations resolved by canonical identity and displayed side by side through the reused Batch 3 screenshot/SVG machinery, inspect every canonical difference/relationship-change/comparability/dependency-evidence category exactly as persisted, select an existing `FrontendContractEvaluationArtifact`, see its exact linked baseline/per-change contracts/comparison/observations resolved by canonical identity, inspect every clause result (baseline active/superseded, requested/expected-dependent/protected/preserved, unexpected) joined by exact `clauseId`, and see the authoritative overall `PASS`/`FAIL` verdict rendered unmistakably - including the required safety case where a requested change passes while a protected and a preserved clause both fail, proven against a real canonically-evaluated fixture in real Chromium. `compareObservations` and `evaluateFrontendContract` were never invoked for viewer reconstruction anywhere in this batch. No Batch 5+ visualization, no v0.9 annotation, and no release/publication action was taken. Package version remains `0.7.0`.
|
|
1
|
+
# v0.8 Batch 4 — Before/After Comparison and Contract/Change-Scope Inspection — Implementation Report
|
|
2
|
+
|
|
3
|
+
## 1. Starting state
|
|
4
|
+
|
|
5
|
+
- Branch: `master`
|
|
6
|
+
- Starting HEAD: `05d504a2c3b2419502faf48ff5661abb02b0e10d` ("feat: add v0.8 observation inspection and SVG overlays")
|
|
7
|
+
- `origin/master` after `git fetch`: `a1de8ac01e1367b60021cb04226f56369fa2debb`
|
|
8
|
+
- `git rev-list --left-right --count origin/master...HEAD`: `0 3` — local is exactly Batches 1-3 ahead of origin, no divergence.
|
|
9
|
+
- Starting `git status --short`: clean.
|
|
10
|
+
- Package version confirmed `0.7.0` throughout; never bumped.
|
|
11
|
+
|
|
12
|
+
## 2. A resolved contradiction in the task's path instructions (task §5)
|
|
13
|
+
|
|
14
|
+
The task gave an explicit `Join-Path`-based algorithm (`$WORKFLOW_BASE = Join-Path $REPO_ROOT ".my-dev-kit-workflow"`; `$WORKFLOW_ROOT = Join-Path $WORKFLOW_BASE "v0.8\batch-04"`) together with five verifiable assertions, then separately restated a "Required Batch 4 root" as the old Batches-1-3 sibling path (`...\my-frontend-observer.my-dev-kit-workflow\v0.8\batch-04`). These two are inconsistent: the sibling path **fails assertion 1** (`$WORKFLOW_ROOT` must begin with `$REPO_ROOT\`), since it is a sibling directory name, not a path under the repository root.
|
|
15
|
+
|
|
16
|
+
Verified programmatically in PowerShell:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
REPO_ROOT=Z:\Users\newuser\Projects\my-frontend-observer
|
|
20
|
+
WORKFLOW_BASE=Z:\Users\newuser\Projects\my-frontend-observer\.my-dev-kit-workflow
|
|
21
|
+
WORKFLOW_ROOT=Z:\Users\newuser\Projects\my-frontend-observer\.my-dev-kit-workflow\v0.8\batch-04
|
|
22
|
+
Assertion1 (starts with REPO_ROOT\): True
|
|
23
|
+
Assertion2 (WORKFLOW_BASE parent = REPO_ROOT): True
|
|
24
|
+
Assertion3 (WORKFLOW_ROOT parent = REPO_ROOT\.my-dev-kit-workflow\v0.8): True
|
|
25
|
+
Assertion4 (not sibling path): True
|
|
26
|
+
Assertion5 (not on C:): True
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
All five assertions pass for the `Join-Path`-derived (inside-repository) path and would fail for the restated sibling path. A decisive tiebreaker was also found in the repository's own `.gitignore` (`.my-dev-kit-workflow/`, present since before this batch) - a pattern that only makes sense for a directory *inside* the repository, since a sibling directory outside the repo is never a `git` ignore-pattern candidate in the first place. Per the task's own instruction ("Do not manually repair the string by guessing"), the literal, executable `Join-Path` algorithm was followed exactly rather than the inconsistent restated path, and this resolution is recorded here rather than silently picked.
|
|
30
|
+
|
|
31
|
+
**Resolved `WORKFLOW_ROOT` for Batch 4:** `Z:\Users\newuser\Projects\my-frontend-observer\.my-dev-kit-workflow\v0.8\batch-04` (inside the repository, gitignored).
|
|
32
|
+
|
|
33
|
+
## 3. Prior generated-path audit (task §6)
|
|
34
|
+
|
|
35
|
+
- **Sibling location** `Z:\Users\newuser\Projects\my-frontend-observer.my-dev-kit-workflow\v0.8\` exists and contains exactly `batch-01`, `batch-02`, `batch-03` (Batches 1-3's own generated state, per their reports) - confirmed present, **not modified, not migrated, not reused** by this batch.
|
|
36
|
+
- **Inside-repository location** `Z:\Users\newuser\Projects\my-frontend-observer\.my-dev-kit-workflow\` also pre-existed, but held only unrelated pre-v0.8 tooling state (`npm-cache`, `pw-browsers`, `readiness` - no `v0.8` subdirectory at all before this batch). Batch 4 added a new `v0.8\batch-04` subtree there without touching those pre-existing siblings.
|
|
37
|
+
- No third, differently-malformed sibling path was found anywhere under `Z:\Users\newuser\Projects\`.
|
|
38
|
+
|
|
39
|
+
## 4. Predecessor reports inspected
|
|
40
|
+
|
|
41
|
+
All three read in full (not console summaries) - previously written in this same session, so their exact content was already held in full working memory and was re-confirmed against this batch's actual needs:
|
|
42
|
+
|
|
43
|
+
- **Batch 1**: host `127.0.0.1`/port `4319`, `src/viewerServer/{httpServer,viewerService}.ts`, PWA `navigateFallbackDenylist: [/^\/api\//]`.
|
|
44
|
+
- **Batch 2**: `evidence/{discovery,classify,handles,pathSafety,index,projection,mediaResolver}.ts` exact module boundaries; `EvidenceMetadataRecord`/`EvidenceArtifactDetail` shapes; `GET /api/index`/`/api/artifacts/<handle>`/`/api/media/<handle>/<role>` exact status-code semantics (404 unknown handle, 409 not-currently-loadable, 405 write methods); the `<family-slug>:<percent-encoded-relativeDir>` handle format; `resolveContainedDir`'s known forward-slash-root bugfix (reused unchanged, not re-litigated).
|
|
45
|
+
- **Batch 3**: `observationView.ts#getObservationRelationships` and `GET /api/observations/<handle>/relationships`; `ObservationWorkspace`/`TargetOverlaySvg`/`ObservationInspector`/`EvidenceFieldView`/`useArtifactDetail`/`targetOrder.ts` component/hook boundaries; the coordinate-mapping decision (`requestConfig.viewport` as the canonical SVG `viewBox` source, geometry never rewritten); the `data-target-name` attribute already present on rendered `<rect>`/`<g>` elements.
|
|
46
|
+
|
|
47
|
+
Batch 4 extends these mechanisms exactly - no second observation viewer, no second evidence index, no second media-loading path was created.
|
|
48
|
+
|
|
49
|
+
## 5. Frozen planning authority inspected
|
|
50
|
+
|
|
51
|
+
`docs/DOCUMENTATION_PRESERVATION_POLICY.md`, `docs/PROJECT_MILESTONES.md` (Milestone 8), `docs/ROADMAP.md` (v0.8), `docs/plans/v0.8-implementation-plan.md` (Batch 4 section + cross-batch invariants) - all previously read in full during Batches 1-3, re-confirmed unchanged. `docs/ARCHITECTURE.md`, `docs/CONTRACTS.md` (previously read in full), `docs/WORKFLOWS.md`, `docs/COMMANDS.md` (then edited). New this batch, read in full: `src/domain/comparison.ts`, `src/domain/frontendContracts.ts`, `src/domain/frontendContractEvaluationArtifact.ts`; targeted reads of `src/domain/frontendContractEvaluation.ts` (`UnexpectedChangeResult`, `FrontendContractEvaluationInput/Result`) and the three readers (`comparisonArtifactReader.ts`, `frontendContractArtifactReader.ts`, `frontendContractEvaluationArtifactReader.ts` - function names confirmed, bodies already known from having authored `classify.ts` in Batch 2).
|
|
52
|
+
|
|
53
|
+
Confirmed Batch 4's title/scope in `docs/plans/v0.8-implementation-plan.md` match the task exactly; no material difference found.
|
|
54
|
+
|
|
55
|
+
## 6. my-dev-kit retrieval
|
|
56
|
+
|
|
57
|
+
Index built successfully at `$WORKFLOW_ROOT\my-dev-kit-index`. All seven required searches were run (comparison artifact ownership, comparison engine ownership, frontend contract shapes, contract evaluation ownership, viewer linked-evidence/index APIs, Batch 3 observation viewer, existing protected/preserved-regression tests). Every result matched direct source inspection - `src/domain/comparison.ts`, `comparisonEngine.ts`, `frontendContracts.ts`, `frontendContractEvaluation.ts`, `frontendContractEvaluationArtifact.ts`, the three readers, and the application services were all surfaced consistently.
|
|
58
|
+
|
|
59
|
+
## 7. Comparison contract audit (task §10)
|
|
60
|
+
|
|
61
|
+
Recorded from `src/domain/comparison.ts` (full read):
|
|
62
|
+
|
|
63
|
+
- `comparisonId` (fresh per execution), `comparisonRequestId` (deterministic, `compare(A,B) !== compare(B,A)`).
|
|
64
|
+
- `before`/`after`: **ordered**, distinct fields (`ComparisonSourceObservationReference` = `{observationId, requestId, producer:{name,version}, observationSchemaVersion, screenshot:{path}}`) - never swappable, confirmed by the type's own doc comment ("Comparison is ordered... never a swappable pair").
|
|
65
|
+
- `config: ComparisonConfig`, `comparability: ComparabilityResult` (`state` + `reasons[]`, each reason carrying `code`/`severity`/`message`), `configurationChanges: TargetConfigurationChange[]` (`added`/`removed`/`locator-changed`), `relationshipsBefore`/`relationshipsAfter: LayoutRelationshipGraph` (the pre-persisted graphs, not re-derivable data), `differences: ComparisonDifference[]`, `relationshipChanges: RelationshipChangeRecord[]`, `expectedDependencyEvidence: ExpectedDependencyEvidence[]`, `diagnostics: Diagnostic[]`, `limits: {truncated, omittedFields, omittedTargetPairs}`.
|
|
66
|
+
- Source references carry logical identity (`observationId`/`requestId`/`producer`/`observationSchemaVersion`) - never a persisted filesystem path. Confirmed: no path field anywhere in `ComparisonSourceObservationReference`.
|
|
67
|
+
|
|
68
|
+
## 8. Source-observation resolution rule (task §11)
|
|
69
|
+
|
|
70
|
+
**Exact match on all four available identity fields simultaneously**: `observationId`, `requestId`, `producer.version` (`producer.name` is already a fixed constant, `PRODUCER_NAME`, so only `.version` varies), and `observationSchemaVersion` - implemented in `src/viewerServer/evidence/linkedEvidence.ts#resolveObservationByReference`. Zero matches → `{status: 'missing'}`. Two or more exact matches → `{status: 'ambiguous', count}` - **never silently picks one** (proven with a real duplicated-manifest fixture in `tests/unit/linkedEvidenceServer.test.ts`). No folder-name/screenshot-filename/URL/target-set/geometry-based matching exists anywhere in this module. The resolution walks the bounded evidence tree once per lookup (mirroring Batch 2's `findImportedReferenceDir` pattern exactly, applied consistently here for comparisons/baseline-contracts/change-contracts too).
|
|
71
|
+
|
|
72
|
+
## 9. Evaluation linked-artifact resolution rule
|
|
73
|
+
|
|
74
|
+
`FrontendContractEvaluationArtifact` references are resolved by exact identity, each independently:
|
|
75
|
+
|
|
76
|
+
- **Comparison**: exact `comparisonId` **and** `comparisonRequestId` (`resolveComparisonByIdentity`).
|
|
77
|
+
- **Baseline contract**: exact `baselineId` (`resolveBaselineContractById`).
|
|
78
|
+
- **Per-change contract**: exact `contractId` (`resolveChangeContractById`).
|
|
79
|
+
- **Before/after observations**: the same exact-reference rule as §8 (`resolveObservationByReference`), applied to `evaluation.before`/`evaluation.after`.
|
|
80
|
+
|
|
81
|
+
All five resolutions run in parallel (`Promise.all`) inside `src/viewerServer/evidence/evaluationView.ts#getEvaluationView`, each independently reporting `resolved`/`missing`/`ambiguous` - one missing/ambiguous linked artifact never blocks resolution of the others (proven in `tests/unit/linkedEvidenceServer.test.ts`: a missing baseline and a missing comparison are each tested independently while the rest of the evaluation view still resolves correctly).
|
|
82
|
+
|
|
83
|
+
## 10. Comparison-view architecture
|
|
84
|
+
|
|
85
|
+
`src/viewerServer/evidence/comparisonView.ts#getComparisonView(root, handle)`: same handle-decode → contained-dir-resolve → re-stat → re-classify discipline as `observationView.ts`/`mediaResolver.ts` (Batches 2-3). Requires `family === 'comparison'` and `supportState === 'supported'` (else `404`/`409`), then resolves `artifact.before`/`artifact.after` via §8's rule. Route: `GET /api/comparisons/<handle>/view` → `{ok:true, before: LinkStatus, after: LinkStatus}`. The comparison's own full payload is **not** duplicated in this response - the browser fetches it separately through the existing, unchanged `GET /api/artifacts/<handle>` (Batch 2), and the resolved before/after `handle`s are fed straight into that same existing route for the source observations. This keeps the new route minimal/ephemeral and avoids inventing a second artifact-detail contract.
|
|
86
|
+
|
|
87
|
+
## 11. Before/after reuse of Batch 3 components (task §35)
|
|
88
|
+
|
|
89
|
+
`viewer/src/components/ComparisonObservationPane.tsx` is built **entirely** from Batch 3's existing lower-level primitives: `useArtifactDetail` (unchanged), `orderedTargets` (unchanged), `TargetOverlaySvg` (one small additive change, below). No second screenshot-loading, coordinate-transform, or geometry-rendering implementation exists anywhere in Batch 4. The comparison's own persisted `relationshipsBefore`/`relationshipsAfter` are passed directly into `TargetOverlaySvg`'s existing `relationships: LayoutRelationshipGraph | undefined` prop - **`deriveLayoutRelationships` is never called to replace them** (grep-verified: no import of `deriveLayoutRelationships` exists in any Batch-4 file except the pre-existing Batch 3 `observationView.ts`, which Batch 4 does not modify).
|
|
90
|
+
|
|
91
|
+
`TargetOverlaySvg.tsx` gained one additive, optional prop: `highlightNames?: ReadonlySet<string> | undefined`, rendering an extra `--highlighted` CSS class on any rectangle whose name is in the set, alongside (never replacing) the existing single-select `selected`/`aria-pressed` interaction. This lets a relationship-subject difference or a two-target contract primitive (e.g. `targets-do-not-overlap`) emphasize both named targets without changing Batch 3's existing single-select contract or its passing tests (`npm run test:browser` re-run in full, §21 below - all pre-existing Batch 3 tests pass unmodified).
|
|
92
|
+
|
|
93
|
+
## 12. Confirmation: `compareObservations` was **not** called for viewer reconstruction
|
|
94
|
+
|
|
95
|
+
Grep-verified across every Batch 4 file (`src/viewerServer/evidence/{linkedEvidence,comparisonView,evaluationView}.ts`, every `viewer/src/**` file touched this batch): zero imports of `compareObservations` or `comparisonEngine.js`. The persisted `ComparisonArtifact` (`differences`, `relationshipsBefore`/`After`, `comparability`, `configurationChanges`, `expectedDependencyEvidence`, `diagnostics`, `limits`) is read unchanged via the existing Batch 2 `GET /api/artifacts/<handle>` and rendered as-is in `ComparisonWorkspace.tsx`.
|
|
96
|
+
|
|
97
|
+
## 13. Differences displayed (task §15)
|
|
98
|
+
|
|
99
|
+
All 13 canonical `DifferenceKind` values are rendered generically (kind/subject/before/after/delta, exactly as persisted, `JSON.stringify`'d for `before`/`after`/`delta` since their shape is difference-kind-dependent per the domain type's own design): `appeared`, `disappeared`, `moved`, `resized`, `visibility-changed`, `clipping-changed`, `containment-changed`, `horizontal-overflow-changed`, `vertical-overflow-changed`, `page-size-changed`, `scroll-owner-changed`, `relative-position-changed`, `relationship-changed`. No new PASS/FAIL severity is assigned anywhere - `ComparisonWorkspace.tsx`'s difference list is presentation-only.
|
|
100
|
+
|
|
101
|
+
## 14. Difference target highlighting (task §16)
|
|
102
|
+
|
|
103
|
+
`subjectTargetNames(subject: ComparisonDifferenceSubject)`: `type: 'target'` → `[subject.target]`; `type: 'relationship'` → `[subjectTarget, relatedTarget].filter(defined)`; `type: 'page'` → `[]` (no target ever fabricated for a page-level subject). Clicking a difference calls `highlightSubject`, which sets the first name as the single interactive `selected` target and any remaining name(s) as `highlightNames`. Because `TargetOverlaySvg` only ever renders a rectangle for a target with real geometry in *that specific* observation, an `appeared` target (absent in `before`) simply has no rectangle in the Before pane, and a `disappeared` target (absent in `after`) has none in the After pane - **never fabricated**, proven with a real Chromium test (`comparisonEvaluationWorkspace.test.ts`, "appeared/disappeared target behavior") that asserts a rectangle count of exactly `0` on the absent side and `1` on the present side, for both targets.
|
|
104
|
+
|
|
105
|
+
## 15. Comparability presentation (task §18)
|
|
106
|
+
|
|
107
|
+
`.comparability-banner--{comparable|comparable-with-warnings|incomparable}` renders `artifact.comparability.state` and every reason with its own severity class (`blocking`/`warning`/`unassessed`) and message unchanged. An `incomparable` result gets `role="alert"` and a red-bordered banner - visually unmistakable, proven with a real Chromium test against a genuinely `incomparable` fixture (two observations with different `requestConfig.viewport` producing a real `viewport-mismatch` blocking reason from `compareObservations` itself, never asserted/faked).
|
|
108
|
+
|
|
109
|
+
## 16. Target configuration changes (task §19)
|
|
110
|
+
|
|
111
|
+
Rendered as their own `Configuration changes` section (`kind` + `target`), entirely separate from the `Differences` section that shows `appeared`/`disappeared` - the two lists never merge or share a component.
|
|
112
|
+
|
|
113
|
+
## 17. Expected dependency evidence (task §20)
|
|
114
|
+
|
|
115
|
+
Rendered under the heading "Expected dependency evidence (explicit, non-causal)"; each entry shows `cause.target.property direction → effect.target.property direction: outcome`, where `outcome` is exactly one of `consistent`/`not-observed`/`contradictory-to-declaration`/`unavailable` - never rephrased as a pass/fail or causal claim.
|
|
116
|
+
|
|
117
|
+
## 18. Comparison diagnostics and limits (task §21)
|
|
118
|
+
|
|
119
|
+
`artifact.limits.truncated === true` renders a visible, non-suppressible banner listing `omittedFields`/`omittedTargetPairs`; `artifact.diagnostics` (when non-empty) render as their own section. Neither is hidden behind a successful-looking default state.
|
|
120
|
+
|
|
121
|
+
## 19. Contract/evaluation architecture
|
|
122
|
+
|
|
123
|
+
`src/viewerServer/evidence/evaluationView.ts#getEvaluationView(root, handle)` mirrors `comparisonView.ts` exactly, resolving all five linked references (§9) in parallel. Route: `GET /api/evaluations/<handle>/view` → `{ok:true, comparison, baseline, change, before, after}` (each a `LinkStatus`). `viewer/src/components/EvaluationWorkspace.tsx` fetches the evaluation's own already-loaded artifact (from `ArtifactPreview`'s existing `useArtifactDetail`) plus this view route, then calls `useArtifactDetail` four more times (baseline/change/comparison handles, plus reusing `ComparisonObservationPane` for before/after) - all through the same existing Batch 2 on-demand-loading contract, never a new artifact-fetching mechanism.
|
|
124
|
+
|
|
125
|
+
## 20. Confirmation: `evaluateFrontendContract` was **not** called for viewer reconstruction
|
|
126
|
+
|
|
127
|
+
Grep-verified: zero imports of `evaluateFrontendContract` or `frontendContractEvaluation.js`'s evaluator export anywhere in Batch 4's `src/viewerServer/` or `viewer/src/` code (only `UnexpectedChangeResult`, a pure type, is imported for typing). `overallVerdict`, `clauseResults`, `activeBaselineClauseIds`, `supersededBaselineClauseIds`, and `unexpectedChanges` are the persisted artifact's own fields, read unchanged via `GET /api/artifacts/<handle>` and rendered as-is.
|
|
128
|
+
|
|
129
|
+
## 21. Baseline clauses / change-scope / clause results / active-superseded / unexpected changes / overall verdict
|
|
130
|
+
|
|
131
|
+
- **Clause joining by exact `clauseId` only** (`viewer/src/components/ClauseResultRow.tsx`, `EvaluationWorkspace.tsx`): a `Map<clauseId, {source, clause}>` is built from the loaded baseline's `clauses` and the loaded change contract's `clauses`; each `clauseResults` entry is looked up by its own `clauseId` - never by target/primitive-shape/category/position/text similarity. An id present in neither loaded contract renders as an honest "unresolved clause definition" (never a fabricated category/primitive) - a distinct "Unresolved clause definitions" section exists specifically for this case.
|
|
132
|
+
- **Baseline clauses** are shown in their own section, distinctly from per-change categories, each tagged `active` or `superseded` **exclusively from the evaluation artifact's own `activeBaselineClauseIds`/`supersededBaselineClauseIds`** - never recomputed from clause-id overlap or any other inference.
|
|
133
|
+
- **Per-change clauses** are grouped into four sections in the frozen category order (`requested`, `expected-dependent`, `protected`, `preserved`); `expected-dependent` clauses additionally show their `required`/`permitted` mode without merging the two modes' meaning.
|
|
134
|
+
- **Clause result status** (`pass`/`fail`/`unavailable`/`conflict`) is preserved exactly via `statusLabel()` - `unavailable` always shows its `reason`, `conflict` always shows its `reason` and `conflictingClauseIds`; neither is ever collapsed to a boolean or converted to `fail`.
|
|
135
|
+
- **Unexpected changes** render in their own section, `classification: 'unexpected'` preserved verbatim, with target highlighting from the same `subjectTargetNames`-style extraction as ordinary differences (since `UnexpectedChangeResult.subject` reuses `ComparisonDifferenceSubject` unchanged - confirmed in `frontendContractEvaluation.ts`).
|
|
136
|
+
- **Overall verdict**: `artifact.overallVerdict` renders directly in a large `.overall-verdict--PASS`/`.overall-verdict--FAIL` banner - the UI performs no verdict computation of its own anywhere in this batch's code (grep-verified: no `every(...status==='pass')`-style logic exists in `EvaluationWorkspace.tsx`).
|
|
137
|
+
|
|
138
|
+
## 22. Target cross-highlighting from contract primitives (task §34)
|
|
139
|
+
|
|
140
|
+
`viewer/src/contract/clauseTargets.ts#primitiveTargetNames` is an exhaustive `switch` over every `ContractPrimitiveKind`: single-target primitives (`target-visible`, `target-not-clipped`, `target-width-within-bound`, `target-does-not-own-scroll`, `target-begins-below-initial-viewport`, `property-unchanged-within-tolerance`, `property-increases`, `property-decreases`) return `[target]`; two-target primitives (`targets-do-not-overlap`, `target-wider-than`, `target-follows-vertically`) return `[targetA, targetB]`; `target-fits-inside` returns `[target, container]`; `relationship-unchanged` returns `[subjectTarget, relatedTarget].filter(defined)`; the two page-level primitives (`document-width-fits-viewport`, `scroll-owner-is-document`) return `[]` - clicking their clause row is disabled (`names.length === 0` → `disabled`) rather than highlighting an invented target.
|
|
141
|
+
|
|
142
|
+
## 23. All-pass proof (task §33/§44 Case A)
|
|
143
|
+
|
|
144
|
+
**Fixture**: `tests/support/evidenceFixtures.ts#writeAllPassPipelineFixture` - the deliberate all-pass counterpart to the existing `writeFullPipelineFixture`, differing only in the *actual rendered geometry/style* fed to the real `compareObservations`/`evaluateFrontendContract` workflow (via `evaluateAndPersistFromArtifactRoots`): `rightAd` genuinely unchanged (protected clause genuinely satisfied) and `navigation` genuinely never clipped (`scrollWidth === clientWidth`, preserved clause genuinely satisfied). Verified directly against the real evaluator's output before use (§30 below) - `overallVerdict: "PASS"`, all four clauses `pass`. `overallVerdict` is never hand-edited.
|
|
145
|
+
|
|
146
|
+
**Real-browser proof**: `tests/browser/comparisonEvaluationWorkspace.test.ts`, "Case A" describe block - selects the evaluation, confirms `.overall-verdict--PASS`, confirms both before/after screenshots load through the real `/api/media/observation:...` endpoint, confirms `requested-nav`/`protected-rightad`/`preserved-nav-unclipped` are each `.clause-row--pass`, and confirms clicking the `requested-nav` clause row highlights the exact `navigation` target (`aria-pressed="true"` on its real SVG rectangle).
|
|
147
|
+
|
|
148
|
+
## 24. Safety-failure proof (task §32/§44 Case B)
|
|
149
|
+
|
|
150
|
+
**Fixture**: the existing `writeFullPipelineFixture` (reused unchanged, not a new fixture) - already produces, through the real canonical evaluator, `overallVerdict: "FAIL"` with `requested-nav: pass`, `protected-rightad: fail`, `preserved-nav-unclipped: fail` (confirmed by direct inspection before use, §30). This is exactly the required safety case: **a requested/local change passes while a protected clause and a preserved clause both fail, and the overall verdict is FAIL** - never hand-constructed.
|
|
151
|
+
|
|
152
|
+
**Real-browser proof**: `tests/browser/comparisonEvaluationWorkspace.test.ts`, "Case B" describe block - selects the evaluation, confirms `.overall-verdict--FAIL`, confirms `requested-nav` is `.clause-row--pass` while `protected-rightad` and `preserved-nav-unclipped` are both `.clause-row--fail`, confirms before/after visual context remains fully available (both screenshots render) despite the overall failure, and confirms clicking the failing `protected-rightad` clause highlights the exact `rightAd` target.
|
|
153
|
+
|
|
154
|
+
## 25. API changes
|
|
155
|
+
|
|
156
|
+
| Route | Method | Semantics |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| `GET /api/comparisons/<handle>/view` | GET/HEAD | `{ok:true, before: LinkStatus, after: LinkStatus}`. `404` unknown handle, `409` not-a-comparison/not-currently-loadable, `405` any write method. |
|
|
159
|
+
| `GET /api/evaluations/<handle>/view` | GET/HEAD | `{ok:true, comparison, baseline, change, before, after}` (each a `LinkStatus`). `404`/`409`/`405` as above. |
|
|
160
|
+
|
|
161
|
+
`/api/status`, `/api/index`, `/api/artifacts/<handle>`, `/api/media/<handle>/<role>`, `/api/observations/<handle>/relationships` are byte-for-byte unchanged.
|
|
162
|
+
|
|
163
|
+
## 26. PWA cache boundary
|
|
164
|
+
|
|
165
|
+
**PASS.** Both new routes live under `/api/`, already covered by Batch 1's `navigateFallbackDenylist: [/^\/api\//]` - no service-worker configuration change was needed. `tests/unit/viewerPwaBuild.test.ts` gained one explicit assertion against the real built `sw.js`: still exactly one `registerRoute` call, and the precache manifest contains neither `/api/comparisons` nor `/api/evaluations`.
|
|
166
|
+
|
|
167
|
+
## 27. Files created
|
|
168
|
+
|
|
169
|
+
- `src/viewerServer/evidence/linkedEvidence.ts`, `comparisonView.ts`, `evaluationView.ts`
|
|
170
|
+
- `viewer/src/components/ComparisonObservationPane.tsx`, `ComparisonWorkspace.tsx`, `EvaluationWorkspace.tsx`, `ClauseResultRow.tsx`
|
|
171
|
+
- `viewer/src/contract/clauseTargets.ts`
|
|
172
|
+
- `viewer/src/hooks/useLinkedEvidence.ts`
|
|
173
|
+
- `viewer/src/types/comparison.ts`, `contracts.ts`
|
|
174
|
+
- `tests/unit/linkedEvidenceServer.test.ts`
|
|
175
|
+
- `tests/browser/comparisonEvaluationWorkspace.test.ts`
|
|
176
|
+
- `docs/reports/v0.8-comparison-contract-inspection-batch4.md` (this file)
|
|
177
|
+
|
|
178
|
+
## 28. Files modified
|
|
179
|
+
|
|
180
|
+
- `src/viewerServer/httpServer.ts` - added the two new routes (§25); `/api/status`, `/api/index`, `/api/artifacts/<handle>`, `/api/media/<handle>/<role>`, `/api/observations/<handle>/relationships`, and static-asset serving unchanged.
|
|
181
|
+
- `viewer/src/components/TargetOverlaySvg.tsx` - one additive, optional `highlightNames` prop (§11); existing `selected`/`onSelect`/`toggles` behavior unchanged.
|
|
182
|
+
- `viewer/src/components/ArtifactPreview.tsx` - branches to `ComparisonWorkspace`/`EvaluationWorkspace` for their families; every other family's raw-JSON preview unchanged.
|
|
183
|
+
- `viewer/src/styles/index.css` - additive rules for the new workspaces.
|
|
184
|
+
- `tests/support/evidenceFixtures.ts` - added `writeAllPassPipelineFixture`, `writeBaselineSupersessionFixture`, `writeAppearedDisappearedComparisonFixture`, `writeIncomparableComparisonFixture` (all built through the real canonical `compareObservations`/`evaluateFrontendContract` workflow, never hand-edited results); `buildObservation` gained optional `pageEvidence`/`viewport` parameters (already added in Batch 3, unchanged here).
|
|
185
|
+
- `tests/browser/viewerEvidenceShell.test.ts` - two pre-existing Batch 2 tests that selected a `comparison` record to exercise the *generic* raw-JSON-preview/no-visualization path now legitimately conflict with Batch 4's real comparison workspace; both were repointed to `baseline-contract` (which still exercises exactly the generic path/invariant they were written to protect) - the same kind of sanctioned evolution as Batch 1→2's placeholder-text update, Batch 2's `TST-401`, and Batch 3's analogous `viewerEvidenceShell.test.ts` update.
|
|
186
|
+
- `tests/unit/viewerPwaBuild.test.ts` - added the explicit Batch 4 cache-boundary assertion (§26).
|
|
187
|
+
- `docs/ARCHITECTURE.md`, `docs/COMMANDS.md` - new/updated Batch 4 sections (condensed versions of §7-§22 above).
|
|
188
|
+
|
|
189
|
+
## 29. Tests added/modified and behavior protected
|
|
190
|
+
|
|
191
|
+
| Test file | Level | Protects |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| `linkedEvidenceServer.test.ts` (15 tests) | unit/integration (real HTTP) | Exact before/after resolution; honest `missing` when a source observation is removed; honest `ambiguous` with a real duplicated-identity fixture (count=2); 409 for a non-comparison/non-evaluation handle; 404 unknown handle; 405 write methods; exact resolution of all five evaluation links; missing baseline reported honestly; missing comparison reported honestly; the all-pass fixture is a genuine `PASS` with every clause `pass`; the full-pipeline fixture is a genuine `FAIL` with the exact required requested-pass/protected-fail/preserved-fail signature; the baseline-supersession fixture reports real active/superseded ids and a real unexpected change. |
|
|
194
|
+
| `comparisonEvaluationWorkspace.test.ts` (4 tests, real Chromium) | browser | Case A (all-pass) full proof (§23); Case B (safety failure) full proof (§24); appeared/disappeared honest rectangle presence/absence; incomparable state visibly unmistakable with its real blocking reason. |
|
|
195
|
+
| `viewerEvidenceShell.test.ts` (2 updated) | browser | Generic Batch 2 on-demand-load/no-visualization invariants still hold for families without a Batch 3/4 workspace. |
|
|
196
|
+
| `viewerPwaBuild.test.ts` (+1) | build/integration | New routes covered by the existing `/api/` denylist, no new runtime-caching rule. |
|
|
197
|
+
|
|
198
|
+
## 30. A methodology note: fixtures verified against the real evaluator before use
|
|
199
|
+
|
|
200
|
+
Before writing any assertion against `writeAllPassPipelineFixture`, `writeFullPipelineFixture` (reused), or `writeBaselineSupersessionFixture`, each was run once through the real `readFrontendContractEvaluationArtifact` reader in an isolated scratch test and its actual `overallVerdict`/`clauseResults`/`activeBaselineClauseIds`/`supersededBaselineClauseIds` were inspected directly (not assumed) - this is how the exact safety-case signature (`requested-nav: pass`, `protected-rightad: fail`, `preserved-nav-unclipped: fail`, `overallVerdict: "FAIL"`) in the pre-existing `writeFullPipelineFixture` was *discovered*, not designed - it already existed from Batch 2/3's fixture reuse and turned out to exactly match Batch 4's required safety case. The scratch inspection tests were deleted before finalizing (never committed); this report documents the methodology rather than leaving throwaway files behind.
|
|
201
|
+
|
|
202
|
+
## 31. Validation results
|
|
203
|
+
|
|
204
|
+
| Command | Result |
|
|
205
|
+
|---|---|
|
|
206
|
+
| `npm run typecheck` | **PASS** (zero errors, both `tsconfig.json` and `viewer/tsconfig.json`) |
|
|
207
|
+
| `npm run lint` | **PASS** (zero errors/warnings) |
|
|
208
|
+
| `npm test` | **PASS** — 1101/1101 tests, 60/60 files |
|
|
209
|
+
| `npm run build` | **PASS** — unchanged Node/library output plus `dist/viewerServer/evidence/{comparisonView,evaluationView,linkedEvidence}.js` and the rebuilt `dist/viewer/**` PWA |
|
|
210
|
+
| `npm run check:docs` | **PASS** — "Documentation check passed (17 required files)." |
|
|
211
|
+
| `npm run test:browser` | **PASS** — 141/141 tests, 14/14 files (real Chromium; run in full per task §46) |
|
|
212
|
+
| `git diff --check` | **PASS** — no whitespace errors (only expected LF→CRLF notices) |
|
|
213
|
+
|
|
214
|
+
## 32. Built viewer smoke (task §47)
|
|
215
|
+
|
|
216
|
+
Fixture: one real before/after observation pair, a real comparison, a real baseline contract, a real per-change contract (the "milestone signature" clause set), and a real evaluation - all built via a one-off script (not committed, `$WORKFLOW_ROOT\tmp`) calling the actual compiled `dist/artifacts/*.js`/`dist/application/frontendContractEvaluationService.js` modules directly, under `$WORKFLOW_ROOT\smoke\evidence-root`.
|
|
217
|
+
|
|
218
|
+
Command: `node dist/cli.js view --root "<WORKFLOW_ROOT>\smoke\evidence-root" --port 4319 --no-open`
|
|
219
|
+
|
|
220
|
+
All required checks passed against the real running built server:
|
|
221
|
+
|
|
222
|
+
- `/api/status` → `200`; `/api/index` → all 6 real records (`observation` x2, `comparison`, `baseline-contract`, `change-contract`, `contract-evaluation`), all `supported`.
|
|
223
|
+
- `/api/comparisons/<handle>/view` → `200`, both `before`/`after` `resolved` to the exact real observation handles.
|
|
224
|
+
- `/api/evaluations/<handle>/view` → `200`, all five links (`comparison`/`baseline`/`change`/`before`/`after`) `resolved`.
|
|
225
|
+
- `/api/artifacts/<evaluation-handle>` → `overallVerdict: "FAIL"`, `clauseResults`: `requested-nav: pass`, `expected-workspace: pass`, `protected-rightad: fail`, `preserved-nav-unclipped: fail` - the exact required safety signature, produced by the real canonical workflow, not edited.
|
|
226
|
+
- Both before/after screenshots → `200 image/png`.
|
|
227
|
+
- Unknown comparison/evaluation handles → `404`; write method (`POST`) → `405`.
|
|
228
|
+
- PWA still loads (`/`, `/sw.js` both `200`); `sw.js` contains no `api/comparisons`/`api/evaluations` reference.
|
|
229
|
+
- `netstat` confirmed `127.0.0.1:4319` only, never `0.0.0.0`.
|
|
230
|
+
- Server located by its real PID and terminated with `taskkill /F`; a follow-up `netstat` confirmed the port was released.
|
|
231
|
+
- A post-shutdown listing of the smoke evidence root shows exactly the 8 fixture files the script wrote - no stray writes, no modification.
|
|
232
|
+
|
|
233
|
+
**Result: PASS.** Logs retained under `$WORKFLOW_ROOT\logs\` (`smoke-server.log`, `index-response.json`, `eval-detail.json`, `smoke-checks-1.log`, `smoke-checks-2.log`).
|
|
234
|
+
|
|
235
|
+
## 33. Generated path inventory
|
|
236
|
+
|
|
237
|
+
| Path | Disposition |
|
|
238
|
+
|---|---|
|
|
239
|
+
| `WORKFLOW_ROOT\cache\npm` | Retained (npm cache from the my-dev-kit-index `npx` invocation) |
|
|
240
|
+
| `WORKFLOW_ROOT\tmp\vite-cache`, `WORKFLOW_ROOT\tmp\build-smoke-comparison.mjs` | Retained (build cache empty again, same finding as every prior batch; the smoke-fixture script is dev/readiness tooling only, not committed) |
|
|
241
|
+
| `WORKFLOW_ROOT\logs\*` | Retained (smoke evidence, §32) |
|
|
242
|
+
| `WORKFLOW_ROOT\smoke\evidence-root` | Retained (real evidence fixture tree built for the smoke test) |
|
|
243
|
+
| `WORKFLOW_ROOT\my-dev-kit-index\*` | Retained (successful index + cache-metadata) |
|
|
244
|
+
| `WORKFLOW_ROOT\fixtures` | Retained, empty/unused (no committed deterministic repository fixture was needed - all Batch 4 fixtures are built programmatically via `tests/support/evidenceFixtures.ts`, matching the existing repository convention) |
|
|
245
|
+
| Repo-root `dist/` | Ordinary build output (gitignored); rebuilt cleanly by `scripts/clean.mjs` on every `npm run build` |
|
|
246
|
+
| Pre-existing repo-root `.my-dev-kit*`/`baselines`/`comparisons`/`contracts`/`evaluations`/`observations` | Pre-existing, empty, untouched (same finding as every prior batch) |
|
|
247
|
+
| Sibling `...my-frontend-observer.my-dev-kit-workflow\v0.8\{batch-01,batch-02,batch-03}` | Untouched (verified, §3) |
|
|
248
|
+
|
|
249
|
+
## 34. Repository pollution check
|
|
250
|
+
|
|
251
|
+
**PASS.** `git status --short` before staging showed only the 9 modified + 13 new Batch-4-owned paths listed in §27/§28. No unexpected file or directory appeared anywhere in the repository. No malformed sibling Batch 4 workflow path exists anywhere under `Z:\Users\newuser\Projects\` (verified directly, §3).
|
|
252
|
+
|
|
253
|
+
## 35. Batch 1/2/3 regression check
|
|
254
|
+
|
|
255
|
+
**PASS.** All Batch 1 tests (`view` CLI, `127.0.0.1`/port `4319`, PWA shell/installability, cache boundary, server cleanup), Batch 2 tests (evidence indexing, unsupported-version handling, safe handles, on-demand artifact/media loading, filesystem containment), and Batch 3 tests (observation SVG workspace, coordinate mapping, relationship reuse, unresolved-target honesty) pass unmodified except the two `viewerEvidenceShell.test.ts` updates described in §28, which preserve the exact invariants they originally protected while accounting for Batch 4's legitimate new comparison-family behavior.
|
|
256
|
+
|
|
257
|
+
## 36. v0.1-v0.7 regression check
|
|
258
|
+
|
|
259
|
+
**PASS.** Every pre-v0.8 unit and browser test suite (`observe`, `compare`, `approve-baseline`, `save-change-contract`, `evaluate-contract`, `import-reference`, `approve-reference`, `evaluate-reference-fidelity`) remains covered and passing - none was touched by this batch's diff. `src/domain/`, `src/artifacts/*Writer.ts`, and every existing reader/engine (`compareObservations`, `evaluateFrontendContract`, `deriveLayoutRelationships`) are byte-for-byte unchanged.
|
|
260
|
+
|
|
261
|
+
## 37. Deviations
|
|
262
|
+
|
|
263
|
+
- The task's §5 "Required Batch 4 root" restatement conflicted with its own `Join-Path` algorithm and verification assertions; resolved per §2 above by following the literal, executable, self-verifying algorithm. This is the only deviation from the task's literal text, and it was necessary to satisfy the task's own stated assertions rather than an arbitrary choice.
|
|
264
|
+
- No other deviations. Every other task step (predecessor-report inspection, my-dev-kit retrieval, the coordinate/contract audits, all required test categories, the full validation chain including `test:browser`, and the built-CLI smoke) was executed as specified.
|
|
265
|
+
|
|
266
|
+
## 38. Remaining uncovered risks
|
|
267
|
+
|
|
268
|
+
- **`highlightNames` only distinguishes "the primary selected target" from "secondary highlighted targets" visually (different CSS classes), not through `aria-pressed`** (which remains true only for the single `selected` target, matching Batch 3's existing accessibility contract). A screen-reader user selecting a relationship-subject difference will not hear the second (related) target announced as selected, only visually distinguished. Not a regression (Batch 3 never had multi-target selection at all), but a genuine accessibility gap for the new multi-target case specifically.
|
|
269
|
+
- **`resolveObservationByReference`/`resolveComparisonByIdentity`/etc. each independently re-walk the bounded evidence tree** (same pattern as Batch 2's `findImportedReferenceDir`, carried forward deliberately for consistency rather than introducing a new caching layer). An evaluation view triggers five such walks in parallel; for an evidence root near the `MAX_MANIFEST_CANDIDATES` bound, this is more filesystem work per evaluation-view request than a single shared index pass would need. Not a correctness risk; the same category of "report rather than pre-optimize" risk Batch 2 already recorded.
|
|
270
|
+
- **`ComparisonWorkspace`/`EvaluationWorkspace` do not yet offer a synchronized zoom/pan or a locked view between before/after panes** - explicitly out of scope for this batch (task §42), planned for a later batch.
|
|
271
|
+
- Batches 1-3's previously reported risks (install-prompt "available" branch untested in headless Chromium, no live service-worker execution test) remain unresolved and out of this batch's scope.
|
|
272
|
+
|
|
273
|
+
## 39. Out-of-scope confirmation
|
|
274
|
+
|
|
275
|
+
Confirmed absent from this batch's diff: external-reference image display, reference regions, reference/candidate side-by-side mode, reference applicability UI, reference fidelity UI, reference/runtime binding, explicit-binding cross-selection, image zoom/pan, synchronized view lock, bounded-agent-context UI, runtime/static correlation UI, annotation, contract editing, baseline approval, contract approval, reference approval, source editing, automatic binding, automatic visual analysis, pixel difference, cloud hosting, database, authentication, collaboration.
|
|
276
|
+
|
|
277
|
+
## 40. Final verdict
|
|
278
|
+
|
|
279
|
+
Batch 4 ("Before/after comparison and contract/change-scope inspection") is implemented and independently validated: a developer can select an existing `ComparisonArtifact`, see its exact persisted before/after observations resolved by canonical identity and displayed side by side through the reused Batch 3 screenshot/SVG machinery, inspect every canonical difference/relationship-change/comparability/dependency-evidence category exactly as persisted, select an existing `FrontendContractEvaluationArtifact`, see its exact linked baseline/per-change contracts/comparison/observations resolved by canonical identity, inspect every clause result (baseline active/superseded, requested/expected-dependent/protected/preserved, unexpected) joined by exact `clauseId`, and see the authoritative overall `PASS`/`FAIL` verdict rendered unmistakably - including the required safety case where a requested change passes while a protected and a preserved clause both fail, proven against a real canonically-evaluated fixture in real Chromium. `compareObservations` and `evaluateFrontendContract` were never invoked for viewer reconstruction anywhere in this batch. No Batch 5+ visualization, no v0.9 annotation, and no release/publication action was taken. Package version remains `0.7.0`.
|