@dailephd/my-frontend-observer 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +107 -7
  3. package/dist/application/projectWorkflowService.d.ts +18 -1
  4. package/dist/application/projectWorkflowService.js +40 -2
  5. package/dist/application/projectWorkflowService.js.map +1 -1
  6. package/dist/application/visualAnnotationContractPromotionService.d.ts +41 -0
  7. package/dist/application/visualAnnotationContractPromotionService.js +143 -0
  8. package/dist/application/visualAnnotationContractPromotionService.js.map +1 -0
  9. package/dist/application/visualAnnotationPersistenceService.d.ts +34 -0
  10. package/dist/application/visualAnnotationPersistenceService.js +68 -0
  11. package/dist/application/visualAnnotationPersistenceService.js.map +1 -0
  12. package/dist/application/visualAnnotationReferenceMaterializationService.d.ts +53 -0
  13. package/dist/application/visualAnnotationReferenceMaterializationService.js +194 -0
  14. package/dist/application/visualAnnotationReferenceMaterializationService.js.map +1 -0
  15. package/dist/artifacts/visualAnnotationArtifactReader.d.ts +19 -0
  16. package/dist/artifacts/visualAnnotationArtifactReader.js +66 -0
  17. package/dist/artifacts/visualAnnotationArtifactReader.js.map +1 -0
  18. package/dist/artifacts/visualAnnotationArtifactWriter.d.ts +42 -0
  19. package/dist/artifacts/visualAnnotationArtifactWriter.js +85 -0
  20. package/dist/artifacts/visualAnnotationArtifactWriter.js.map +1 -0
  21. package/dist/cli.js +464 -454
  22. package/dist/cli.js.map +1 -1
  23. package/dist/domain/visualAnnotation.d.ts +217 -0
  24. package/dist/domain/visualAnnotation.js +584 -0
  25. package/dist/domain/visualAnnotation.js.map +1 -0
  26. package/dist/domain/visualAnnotationIdentity.d.ts +17 -0
  27. package/dist/domain/visualAnnotationIdentity.js +47 -0
  28. package/dist/domain/visualAnnotationIdentity.js.map +1 -0
  29. package/dist/index.d.ts +11 -0
  30. package/dist/index.js +6 -0
  31. package/dist/index.js.map +1 -1
  32. package/dist/projectWorkflow/projectPaths.d.ts +9 -0
  33. package/dist/projectWorkflow/projectPaths.js +21 -0
  34. package/dist/projectWorkflow/projectPaths.js.map +1 -1
  35. package/dist/viewer/assets/{index-CN_yb9Uf.css → index-BN41MI7m.css} +1 -1
  36. package/dist/viewer/assets/index-CkKXnlrI.js +9 -0
  37. package/dist/viewer/index.html +2 -2
  38. package/dist/viewer/sw.js +1 -1
  39. package/dist/viewerServer/annotationAuthoring.d.ts +64 -0
  40. package/dist/viewerServer/annotationAuthoring.js +230 -0
  41. package/dist/viewerServer/annotationAuthoring.js.map +1 -0
  42. package/dist/viewerServer/annotationContractPromotion.d.ts +51 -0
  43. package/dist/viewerServer/annotationContractPromotion.js +105 -0
  44. package/dist/viewerServer/annotationContractPromotion.js.map +1 -0
  45. package/dist/viewerServer/annotationReferenceMaterialization.d.ts +40 -0
  46. package/dist/viewerServer/annotationReferenceMaterialization.js +91 -0
  47. package/dist/viewerServer/annotationReferenceMaterialization.js.map +1 -0
  48. package/dist/viewerServer/authoringSecurity.d.ts +59 -0
  49. package/dist/viewerServer/authoringSecurity.js +112 -0
  50. package/dist/viewerServer/authoringSecurity.js.map +1 -0
  51. package/dist/viewerServer/evidence/annotationView.d.ts +28 -0
  52. package/dist/viewerServer/evidence/annotationView.js +43 -0
  53. package/dist/viewerServer/evidence/annotationView.js.map +1 -0
  54. package/dist/viewerServer/evidence/classify.d.ts +3 -1
  55. package/dist/viewerServer/evidence/classify.js +12 -0
  56. package/dist/viewerServer/evidence/classify.js.map +1 -1
  57. package/dist/viewerServer/evidence/discovery.d.ts +2 -0
  58. package/dist/viewerServer/evidence/discovery.js +6 -0
  59. package/dist/viewerServer/evidence/discovery.js.map +1 -1
  60. package/dist/viewerServer/evidence/handles.js +1 -0
  61. package/dist/viewerServer/evidence/handles.js.map +1 -1
  62. package/dist/viewerServer/evidence/index.d.ts +29 -0
  63. package/dist/viewerServer/evidence/index.js +43 -1
  64. package/dist/viewerServer/evidence/index.js.map +1 -1
  65. package/dist/viewerServer/evidence/mediaResolver.d.ts +1 -1
  66. package/dist/viewerServer/evidence/mediaResolver.js +28 -2
  67. package/dist/viewerServer/evidence/mediaResolver.js.map +1 -1
  68. package/dist/viewerServer/evidence/projection.d.ts +5 -1
  69. package/dist/viewerServer/evidence/projection.js +19 -0
  70. package/dist/viewerServer/evidence/projection.js.map +1 -1
  71. package/dist/viewerServer/httpServer.d.ts +13 -2
  72. package/dist/viewerServer/httpServer.js +278 -4
  73. package/dist/viewerServer/httpServer.js.map +1 -1
  74. package/dist/viewerServer/viewerService.d.ts +8 -0
  75. package/dist/viewerServer/viewerService.js +38 -2
  76. package/dist/viewerServer/viewerService.js.map +1 -1
  77. package/docs/ARCHITECTURE.md +108 -21
  78. package/docs/CI_CD.md +57 -1
  79. package/docs/COMMANDS.md +44 -4
  80. package/docs/CONTRACTS.md +78 -8
  81. package/docs/CURRENT_STATE.md +157 -35
  82. package/docs/DEVELOPMENT.md +8 -3
  83. package/docs/PROJECT_DESCRIPTION.md +4 -1
  84. package/docs/PROJECT_MILESTONES.md +4 -0
  85. package/docs/PROJECT_OVERVIEW.md +41 -17
  86. package/docs/QUICKSTART.md +52 -39
  87. package/docs/RELEASE.md +16 -11
  88. package/docs/ROADMAP.md +406 -66
  89. package/docs/SECURITY.md +71 -14
  90. package/docs/WORKFLOWS.md +151 -23
  91. package/docs/plans/v0.9-implementation-plan.md +1529 -0
  92. package/docs/reports/v0.9-architecture-retrieval.md +567 -0
  93. package/docs/reports/v0.9-batch1-visual-annotation-foundation.md +351 -0
  94. package/docs/reports/v0.9-batch2-viewer-annotation-authoring-boundary.md +438 -0
  95. package/docs/reports/v0.9-batch3-runtime-screenshot-annotation-authoring.md +412 -0
  96. package/docs/reports/v0.9-batch4-external-reference-annotation-authoring.md +452 -0
  97. package/docs/reports/v0.9-batch5-runtime-intent-contract-promotion.md +535 -0
  98. package/docs/reports/v0.9-batch6-reference-materialization.md +514 -0
  99. package/docs/reports/v0.9-batch7-integrated-acceptance.md +644 -0
  100. package/docs/reports/v0.9-demo-foundation.md +589 -0
  101. package/docs/reports/v0.9-final-pre-release-readiness.md +209 -0
  102. package/docs/reports/v0.9-final-readiness-corrections.md +530 -0
  103. package/docs/reports/v0.9-pre-release-readiness.md +170 -0
  104. package/docs/reports/v0.9-tutorial-end-to-end-acceptance.md +980 -0
  105. package/docs/reports/v0.9-tutorial-integration.md +731 -0
  106. package/package.json +2 -2
  107. package/dist/viewer/assets/index-D98S1_2d.js +0 -9
@@ -0,0 +1,452 @@
1
+ # v0.9 Batch 4 Report: External-Reference Annotation Authoring
2
+
3
+ ## 1. VERDICT
4
+
5
+ PASS_V0_9_BATCH4_EXTERNAL_REFERENCE_ANNOTATION_AUTHORING
6
+
7
+ ## 2. Repository identity
8
+
9
+ 1. Repository: `C:\Users\daile\Projects\my-frontend-observer`
10
+ 2. Branch: `master`
11
+ 3. Starting HEAD: `9d8169b653b087a581ef38a04f97bfab5a252aae`
12
+ 4. Ending HEAD before the final commit: `9d8169b653b087a581ef38a04f97bfab5a252aae`
13
+ 5. Package version: `0.8.1` (unchanged in `package.json` and `package-lock.json`)
14
+
15
+ ## 3. Prompt 3 entry gate
16
+
17
+ 1. Prompt 3 verdict: `PASS_V0_9_BATCH3_RUNTIME_SCREENSHOT_ANNOTATION_AUTHORING`
18
+ 2. Prompt 3 commit: `9d8169b653b087a581ef38a04f97bfab5a252aae`
19
+ 3. Runtime annotation UI: implemented
20
+ 4. Prompt 2 save API: unchanged
21
+ 5. Viewer protocol: `1.1.0`
22
+ 6. Preflight found a clean tracked worktree on `master` at the required HEAD.
23
+ 7. `git ls-files -v docs/reports/v0.9-architecture-retrieval.md` printed `S`
24
+ at entry and before commit. The `skip-worktree` state was preserved and the
25
+ file was not restored, overwritten, or committed. No pull, rebase, reset,
26
+ or clean was run.
27
+ 8. No Prompt 1 to 3 API conflict and no domain contract defect were found.
28
+ No file under `src/` changed.
29
+
30
+ ## 4. Imported/approved reference source handling
31
+
32
+ 1. The source frame is `image.width`/`image.height` for an imported
33
+ reference and `sourceReference.image.width`/`height` for an approved one.
34
+ These are the same values the reference SVG already uses for its viewBox.
35
+ 2. The browser sends only `{ sourceHandle, parentAnnotationHandle?, items }`.
36
+ The Prompt 2 server builds `referenceId`, `referenceRequestId`,
37
+ `lifecycle`, `imageOwnerReferenceId`, and `imageSha256`.
38
+ 3. The annotation is tied to whichever reference the user opened.
39
+ Browser-verified:
40
+ 1. An imported annotation has `lifecycle: imported`, the imported
41
+ `referenceId`, and the image owner equal to itself.
42
+ 2. An approved annotation has `lifecycle: approved`, the approved
43
+ `referenceId`, and `imageOwnerReferenceId` equal to the imported
44
+ `sourceReference.referenceId`.
45
+ 3. Neither is redirected to the other.
46
+
47
+ ## 5. Reference annotation discovery
48
+
49
+ 1. The Prompt 3 discovery hook was generalized into `useSourceAnnotations`
50
+ (in `useRuntimeAnnotations.ts`). `useRuntimeAnnotations` is now a thin
51
+ wrapper with unchanged behavior.
52
+ 2. New `viewer/src/hooks/useReferenceAnnotations.ts` finds candidates through
53
+ `/api/index` by `relatedIds.sourceReferenceId`. It keeps a candidate only
54
+ when `/api/annotations/:handle/view` reports `source.status === available`,
55
+ `source.family` equal to the current reference family, and `source.handle`
56
+ equal to the current reference handle. That check is
57
+ `annotationViewBelongsToSource`.
58
+ 3. Labels, image dimensions, `referenceRequestId` alone, region ids, and image
59
+ hashes are never used to attach an annotation.
60
+
61
+ ## 6. Shared annotation infrastructure reuse
62
+
63
+ Reused unchanged:
64
+
65
+ 1. `AnnotationLayer`
66
+ 2. `useAnnotationPointerInteraction`
67
+ 3. `sourceCoordinates.screenPointToSvgSource`
68
+ 4. `annotationGeometry`
69
+ 5. `useAuthoringSession`
70
+ 6. `useZoomPan`, in the reference pane's existing controlled mode
71
+
72
+ Small generic refactors, with runtime behavior preserved and all Prompt 3
73
+ tests still passing:
74
+
75
+ 1. `useRuntimeAnnotationDraft.ts` now exports `useAnnotationDraft(sourceHandle)`.
76
+ `useRuntimeAnnotationDraft` is an alias of it.
77
+ 2. The 404 save message says "source evidence" instead of "source
78
+ observation". The existing tests check "no longer exists", which is
79
+ unchanged.
80
+ 3. `AnnotationToolbar` makes its zoom controls optional through `zoom?` and
81
+ `zoomLabel`, because the reference pane already renders its own
82
+ `ZoomControls`.
83
+ 4. New `AnnotationPanelSections.tsx` holds the session notice, saved list, and
84
+ draft lifecycle. It was extracted from `RuntimeAnnotationPanel` with
85
+ identical markup and used by both panels.
86
+
87
+ No reference-specific layer, coordinate helper, pointer interaction, or
88
+ toolbar was created.
89
+
90
+ ## 7. Reference-image coordinate behavior
91
+
92
+ 1. All drawing and moving uses the shared CTM transform in the reference SVG's
93
+ own viewBox, which is reference-image pixels.
94
+ 2. The candidate viewport, CSS pixels, devicePixelRatio, the reference/candidate
95
+ coordinate scale, and bindings are never used for persisted geometry.
96
+ 3. Browser-verified at exact reference-image coordinates:
97
+ 1. A rectangle drawn in reverse from 250,200 to 50,80 persists as
98
+ 50, 80, 200, 120.
99
+ 2. Zoomed and panned drawings persist their intended coordinates.
100
+ 3. A drawing made while the view is locked persists reference pixels, not
101
+ values mapped through the 0.25 lock scale.
102
+
103
+ ## 8. Interaction modes
104
+
105
+ 1. The same closed set is used: `select`, `pan`, `point`, `rectangle`,
106
+ `line`, `arrow`, `note`. No region, requirement, or asset mode exists.
107
+ Those are interpretations of a selected mark.
108
+ 2. `ReferenceRegionOverlaySvg` gains three optional props: `annotationLayer`,
109
+ `regionsInteractive` (default `true`), and `interactionClassName`. Callers
110
+ that pass none of them are unchanged.
111
+ 3. Per mode:
112
+ 1. Select: region selection plus mark selection and move.
113
+ 2. Pan: `useZoomPan` handlers only.
114
+ 3. Drawing modes: drawing only. Region pointer selection is off, but region
115
+ keyboard selection stays available.
116
+ 4. Behavior change, recorded deliberately: the reference pane now pans only
117
+ in Pan mode. Before this batch a zoomed reference pane also panned on a
118
+ drag in its default state. This matches the Prompt 3 rule that a select
119
+ drag never pans. The candidate pane is unchanged.
120
+ 5. Layout fix found by the browser tests. The reference workspace is a
121
+ fixed-height scrolling flex column, so its panes row could shrink, and the
122
+ details section, now taller with the annotation panel, was painted over the
123
+ lower part of the reference image. The fix is
124
+ `.reference-workspace > * { flex-shrink: 0; }`.
125
+
126
+ ## 9. Reference region association
127
+
128
+ 1. Drawing over a region never associates it.
129
+ 2. With a draft item selected and a region selected (pointer in Select mode,
130
+ or keyboard), "Associate region <id>" sets
131
+ `{ kind: 'reference-region', regionId }`. "Clear association" removes it.
132
+ 3. Selecting a region never associates automatically.
133
+
134
+ ## 10. Reference relationship association
135
+
136
+ 1. Options come only from `referenceView.regionRelationships.pairwiseRelationships`,
137
+ the canonical server derivation.
138
+ 2. "Associate relationship" sets
139
+ `{ kind: 'reference-relationship', subjectRegion, relatedRegion, relationship }`.
140
+ 3. Lines and arrows never imply a relationship. Nothing is derived in React.
141
+
142
+ ## 11. Region create candidate
143
+
144
+ 1. Available only for a selected rectangle, with a "Proposed region ID" input
145
+ (`maxLength=64`) and a hint about the allowed characters.
146
+ 2. The id is never rewritten, lowercased, or replaced.
147
+ 3. Blocked when:
148
+ 1. the id is empty or does not match `^[A-Za-z0-9_-]{1,64}$`;
149
+ 2. it duplicates a source region (case-insensitive);
150
+ 3. it duplicates another draft create (case-insensitive);
151
+ 4. source regions plus other distinct draft creates already reach 20
152
+ (`MAX_REFERENCE_REGIONS`, mirrored).
153
+ 4. The candidate is
154
+ `{ kind: 'reference-region', mode: 'create', region: { id, rectangle } }`,
155
+ using the exact mark geometry.
156
+ 5. The preview renders a dashed candidate rectangle labeled
157
+ "candidate new <id> (<state>)".
158
+
159
+ ## 12. Region refine candidate
160
+
161
+ 1. Requires a selected rectangle and a selected existing region. The user
162
+ clicks "Refine selected region <id>".
163
+ 2. The candidate is `mode: 'refine'` with the same region id and the mark
164
+ geometry. The item is also explicitly associated with that region.
165
+ 3. The preview shows the original source rectangle and the candidate
166
+ replacement, each with its own distinct class. The source region keeps
167
+ rendering with its original geometry.
168
+ 4. A summary line reads "Existing source regions: N · Candidate creates: N ·
169
+ Candidate refinements: N" and is marked as not the final region set.
170
+
171
+ ## 13. Informational and asset-sensitive intent
172
+
173
+ 1. "Mark informational" gives `candidate { kind: 'inspect' }`.
174
+ 2. "Mark asset-sensitive" gives `{ kind: 'asset-sensitive' }`.
175
+ 3. "Mark asset-sensitive for region <id>" requires an explicitly selected
176
+ region and gives `{ kind: 'asset-sensitive', regionId }`.
177
+ 4. None of these create a requirement, a PASS/FAIL result, or a contract
178
+ clause.
179
+
180
+ ## 14. Candidate reference requirements
181
+
182
+ 1. Categories are exactly `requested`, `expected-dependent`, `protected`, and
183
+ `preserved`. `expected-dependent` requires `required` or `permitted`. For
184
+ other categories the mode is absent, even if one was chosen earlier.
185
+ 2. Subjects:
186
+ 1. `region-property` takes an existing or draft-proposed region and one of
187
+ the 8 canonical properties. A tolerance is required.
188
+ 2. `region-relationship` takes only a canonical server relationship,
189
+ selected by index. No tolerance is allowed. Candidate-created regions
190
+ never appear, because they have no derived relationships.
191
+ 3. `region-measurement` takes two distinct known regions and one of the 6
192
+ canonical measurements. A tolerance is required. No measured value is
193
+ ever computed or stored.
194
+ 3. Tolerance kinds are `exact`, `absolute-reference-px` (labeled
195
+ "reference-image pixels"), and `percent`. Amounts must be finite and
196
+ between 0 and 100.
197
+ 4. "Set candidate requirement" is disabled with a visible reason until the
198
+ form is complete. It sets
199
+ `candidate { kind: 'reference-requirement', requirement }` with the
200
+ canonical `RawReferenceRequirement` shape.
201
+ 5. The panel lists "Candidate annotation region proposals" and "Candidate
202
+ annotation requirements" separately from the source regions and
203
+ requirements shown by the unchanged reference inspector.
204
+
205
+ ## 15. Confirmation workflow
206
+
207
+ 1. "Confirm reference intent" is enabled only for a candidate. It sets
208
+ `{ state: 'confirmed', intent, confirmedAt: new Date().toISOString() }`.
209
+ 2. Saving never confirms. Browser-verified: a candidate stays a candidate
210
+ after save.
211
+ 3. The selected item always shows its interpretation state as text and the
212
+ exact intent structure as JSON before and after confirmation.
213
+ 4. Confirmation was proven in the browser for all four families:
214
+ `reference-region`, `reference-requirement`, `inspect`, and
215
+ `asset-sensitive`.
216
+
217
+ ## 16. Confirmation invalidation
218
+
219
+ 1. Every reference draft edit passes through `applyReferenceItemEdit`. A
220
+ rectangle's region intent is first synchronized to the rectangle geometry.
221
+ 2. If the item was confirmed and its meaning changed, it becomes a candidate
222
+ without `confirmedAt`. Meaning covers the mark, the association, and the
223
+ intent.
224
+ 3. Setting a new intent (region id or mode, requirement fields, tolerance,
225
+ asset region) always produces a candidate.
226
+ 4. Browser-verified:
227
+ 1. Moving a confirmed candidate region synchronizes its rectangle, drops it
228
+ to candidate, and a later reconfirmation gets a new `confirmedAt`.
229
+ 2. Changing a confirmed requirement's tolerance requires reconfirmation.
230
+ 3. Changing a confirmed item's association requires reconfirmation.
231
+ 5. Unit-verified: note text edits and asset-region changes also invalidate,
232
+ and a no-op edit keeps confirmation.
233
+
234
+ ## 17. Save/reload/revision
235
+
236
+ 1. Saving uses the unchanged Prompt 2 `POST /api/annotations`. On 201 the
237
+ canonical view is reloaded, the draft is replaced, the parent is set,
238
+ dirty is cleared, and the saved list is refreshed.
239
+ 2. Failures use the shared bounded messages, keep the draft, and are never
240
+ retried.
241
+ 3. Browser-verified reload: an approved-reference annotation containing a
242
+ plain mark, a region candidate, a confirmed requirement, and an
243
+ asset-sensitive candidate comes back with the same annotation id, item ids,
244
+ marks, and interpretations. The view equals the persisted artifact, and
245
+ the source handle is exactly the approved reference with media role
246
+ `source-image`.
247
+ 4. Browser-verified revision: B supersedes A, item ids are kept, A's manifest
248
+ bytes are unchanged, and B has a fresh id.
249
+ 5. After a reload the token is re-acquired and no draft is recovered.
250
+
251
+ ## 18. Source immutability
252
+
253
+ 1. Browser tests snapshot every file in the imported and approved reference
254
+ directories, meaning the manifests and the image.
255
+ 2. The snapshots are compared after drawing, candidate create, candidate
256
+ refine, requirement authoring, confirmation, save, and revision. They were
257
+ byte-identical in each test.
258
+ 3. Reference-family artifact count is unchanged, no manifest contains
259
+ `supersedesReferenceId`, and only visual-annotation artifacts are added.
260
+ 4. No import, approval, materialization, or reference-write call exists in
261
+ the viewer.
262
+
263
+ ## 19. Binding/view-lock regression
264
+
265
+ 1. View lock (browser):
266
+ 1. With a compatible candidate locked, zooming the reference pane still
267
+ moves the candidate view.
268
+ 2. Drawing on the reference then leaves the candidate viewBox unchanged and
269
+ puts no annotation marks in the candidate pane.
270
+ 3. The bound `header → header` binding row is unchanged.
271
+ 4. The persisted rectangle is in reference pixels with no association.
272
+ 2. Equal names (browser): the reference region `header` and the runtime
273
+ target `header`, with no declaration, never cross-highlight. The explicit
274
+ association is `reference-region header`, and no `runtime-target` appears
275
+ anywhere in the artifact.
276
+ 3. Explicit binding cross-highlight (browser): reference `header` highlights
277
+ the candidate `header`, and candidate `sidebar` highlights reference
278
+ `sidebar`. Associating an annotation with `header` leaves both highlights
279
+ unchanged, and the association persists separately.
280
+ 4. The existing `referenceCandidateWorkspace`, `referenceBindingFidelityWorkspace`,
281
+ and `referenceCorrectionWorkflow` browser suites pass unchanged.
282
+
283
+ ## 20. Browser tests
284
+
285
+ New `tests/browser/referenceAnnotationAuthoring.test.ts` has 19 real-Chromium
286
+ tests, using the existing reference fixtures and the Prompt 2/3
287
+ `TestResources`/`readManifest` helpers.
288
+
289
+ 1. Availability: the project-aware toolbar and Save are present. The
290
+ standalone viewer is read-only, region selection still works, and no
291
+ non-GET request is sent.
292
+ 2. Imported reference: rectangle plus note, exact coordinates, imported source
293
+ identity, and dimensions.
294
+ 3. Approved reference: all five mark kinds, approved identity, and the
295
+ imported image owner.
296
+ 4. Zoom and pan coordinates, with pan and draw exclusive.
297
+ 5. View lock regression.
298
+ 6. No equal-name binding, plus explicit region association (keyboard region
299
+ selection).
300
+ 7. Explicit relationship association.
301
+ 8. Binding cross-highlight separate from association.
302
+ 9. Region create candidate: duplicate id refused, preview, confirm, move
303
+ invalidates and syncs geometry, reconfirm, source byte-identical.
304
+ 10. Region refine candidate: original and candidate preview, source region
305
+ unchanged, association set, source byte-identical.
306
+ 11. Informational and asset-sensitive intent (with and without a region), plus
307
+ confirmation.
308
+ 12. Region-property requirement (protected, `header` width,
309
+ absolute-reference-px 4), confirmed, source requirements unchanged.
310
+ 13. Expected-dependent mode required, and omitted for other categories.
311
+ 14. Region-relationship requirement from a canonical relationship, no
312
+ tolerance, and no candidate-region relationship offered.
313
+ 15. Region-measurement requirement (vertical-gap, percent), with an
314
+ out-of-range amount refused, and invalidation on tolerance and
315
+ association change.
316
+ 16. No auto requirement from drawn geometry, and save never confirms.
317
+ 17. Save and reload.
318
+ 18. Revision, no materialization, source byte-identical.
319
+ 19. Keyboard selection of marks and regions, and keyboard-operated intent and
320
+ confirm controls.
321
+
322
+ Test-harness notes:
323
+
324
+ 1. The reference SVG is inside scrolling containers. Gestures first scroll the
325
+ SVG into view and then compute both client points from the SVG CTM. This
326
+ is how the flex-shrink overlap was found.
327
+ 2. No test performs an out-of-band canonical write while the page refreshes
328
+ evidence.
329
+ 3. The file passed on repeated runs.
330
+
331
+ ## 21. Unit tests
332
+
333
+ New `tests/unit/viewerReferenceAnnotationModel.test.ts` (18 tests) covers:
334
+
335
+ 1. Create and refine construction, including rejection for non-rectangles.
336
+ 2. Case-insensitive and pattern id blocking, the region maximum, and an item
337
+ re-creating its own id.
338
+ 3. Geometry synchronization.
339
+ 4. Region summary and requirement region choices.
340
+ 5. Confirmation.
341
+ 6. Invalidation by move, association, note text, and asset region, and no
342
+ invalidation on a no-op edit.
343
+ 7. Informational and asset-sensitive shapes.
344
+ 8. Region-property, relationship, and measurement requirement construction.
345
+ 9. Expected-dependent mode rules and tolerance bounds.
346
+ 10. Exact-family membership and association labels.
347
+
348
+ Several built items are also checked against the canonical
349
+ `isValidVisualAnnotationContent`, which proves the viewer's structures are
350
+ accepted by the domain validator.
351
+
352
+ ## 22. Regression validation
353
+
354
+ Every command below was run on Windows in the repository root.
355
+
356
+ 1. `npm run typecheck`: PASS
357
+ 2. `npm run lint`: PASS
358
+ 3. `npm test`: PASS (78 files, 1316 tests)
359
+ 4. `npm run build`: PASS
360
+ 5. `npm run test:browser`: PASS (23 files, 221 tests, real Chromium). This
361
+ includes `runtimeAnnotationAuthoring`, `referenceCandidateWorkspace`,
362
+ `referenceBindingFidelityWorkspace`, `referenceCorrectionWorkflow`,
363
+ `pwaHardening`, and `observationSvgWorkspace`.
364
+ 6. `npm run test:security`: PASS (unit: 13 files, 143 tests. Browser: 3 files,
365
+ 77 tests)
366
+ 7. `npm run check:docs`: PASS
367
+ 8. `npm pack --dry-run`: PASS (345 files, 900.7 kB)
368
+
369
+ ## 23. Security regression
370
+
371
+ 1. No server, domain, artifact, or application file changed.
372
+ 2. Host, Origin, token, body-size, path, symlink, method, and PWA no-cache
373
+ tests pass.
374
+ 3. The reference UI keeps the token in React memory only.
375
+ 4. A source search of the new reference viewer files found no storage API,
376
+ `console` call, `dangerouslySetInnerHTML`, or reference import, approval,
377
+ or materialization call.
378
+
379
+ ## 24. Known deferred writer/discovery race
380
+
381
+ This risk is carried forward unchanged from Prompt 3. It is not fixed and
382
+ Prompt 4 does not own it.
383
+
384
+ 1. On Windows, the canonical artifact writers' atomic directory rename can
385
+ fail with `EPERM` if viewer evidence discovery is reading inside the
386
+ writer's `.tmp-<id>` directory at that moment.
387
+ 2. It needs a concurrent evidence refresh during a save.
388
+ 3. `src/artifacts/*` and `src/viewerServer/evidence/discovery.ts` were not
389
+ modified.
390
+ 4. Prompt 4 tests perform no out-of-band canonical write while the page
391
+ refreshes evidence.
392
+
393
+ ## 25. Scope audit
394
+
395
+ 1. server authoring API changed: false
396
+ 2. annotation artifact schema changed: false
397
+ 3. external-reference artifact schema changed: false
398
+ 4. source reference mutated: false
399
+ 5. reference materialization implemented: false
400
+ 6. reference approval implemented: false
401
+ 7. runtime contract promotion implemented: false
402
+ 8. runtime intent confirmation implemented: false
403
+ 9. ContractPrimitive vocabulary changed: false
404
+ 10. project config schema changed: false
405
+ 11. alias catalog schema changed: false
406
+ 12. package version changed: false
407
+ 13. dependency changed: false
408
+
409
+ Runtime annotation semantics are unchanged: new runtime items stay
410
+ `uninterpreted`, and the runtime panel has no intent or confirmation
411
+ controls.
412
+
413
+ ## 26. Changed files
414
+
415
+ ### 26.1 Added
416
+
417
+ 1. `viewer/src/annotation/referenceIntent.ts`
418
+ 2. `viewer/src/components/ReferenceAnnotationPanel.tsx`
419
+ 3. `viewer/src/components/CandidateRegionPreviewLayer.tsx` (display-only
420
+ candidate region preview inside the reference SVG)
421
+ 4. `viewer/src/components/AnnotationPanelSections.tsx` (shared panel
422
+ sections extracted from the runtime panel)
423
+ 5. `viewer/src/hooks/useReferenceAnnotations.ts`
424
+ 6. `viewer/src/hooks/useReferenceAnnotationDraft.ts`
425
+ 7. `tests/browser/referenceAnnotationAuthoring.test.ts`
426
+ 8. `tests/unit/viewerReferenceAnnotationModel.test.ts`
427
+ 9. `docs/reports/v0.9-batch4-external-reference-annotation-authoring.md`
428
+
429
+ ### 26.2 Modified
430
+
431
+ 1. `viewer/src/components/ReferenceWorkspace.tsx`
432
+ 2. `viewer/src/components/ReferenceRegionOverlaySvg.tsx`
433
+ 3. `viewer/src/components/AnnotationToolbar.tsx` (optional zoom controls)
434
+ 4. `viewer/src/components/RuntimeAnnotationPanel.tsx` (uses the shared
435
+ sections, with identical markup)
436
+ 5. `viewer/src/hooks/useRuntimeAnnotationDraft.ts` (generic
437
+ `useAnnotationDraft`)
438
+ 6. `viewer/src/hooks/useRuntimeAnnotations.ts` (generic
439
+ `useSourceAnnotations`)
440
+ 7. `viewer/src/styles/index.css`
441
+ 8. `viewer/src/types/reference.ts` (type-only `RawReferenceRequirement`
442
+ re-export)
443
+
444
+ ### 26.3 Generated paths
445
+
446
+ 1. `dist/` was rebuilt. It is ignored.
447
+ 2. Tests create and remove temporary directories under the OS temp directory.
448
+ 3. Validation logs went to the session scratchpad.
449
+
450
+ ## 27. Remaining next step
451
+
452
+ v0.9 Prompt 5 — Intent interpretation, confirmation, and canonical change-contract promotion