@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,731 @@
1
+ # v0.9 Tutorial Integration
2
+
3
+ ## 1. VERDICT
4
+
5
+ `PASS_V0_9_TUTORIAL_INTEGRATION`
6
+
7
+ ```text
8
+ LAB_V0_4_8_POINTER_DEFECT_CLOSED true
9
+ POSITIONAL_CLICK_SUPPORTED true
10
+ POSITIONAL_DRAG_SUPPORTED true
11
+ POINT_TUTORIAL_GESTURE PASS
12
+ RECTANGLE_TUTORIAL_GESTURE PASS
13
+ LINE_TUTORIAL_GESTURE PASS
14
+ ARROW_TUTORIAL_GESTURE PASS
15
+ NOTE_TUTORIAL_GESTURE PASS
16
+ ```
17
+
18
+ The defect that blocked the previous attempt is closed. All four Observer
19
+ tutorials validate against the released `@dailephd/my-dev-kit-lab@0.4.8`, run
20
+ end to end in real Chromium, and produce the full artifact set. Every outcome
21
+ was then verified by reading the canonical Observer evidence the runs actually
22
+ wrote, not by watching the video.
23
+
24
+ Observer implements no tutorial runtime, gained no dependency, and its product
25
+ semantics are unchanged. The package stays at `0.8.1` and v0.9 stays
26
+ unreleased.
27
+
28
+ ## 2. Repository identity
29
+
30
+ - Repository: `C:\Users\daile\Projects\my-frontend-observer`
31
+ - Branch: `master`
32
+ - Starting HEAD: `59ae009d4bfdd38b91aa7a4e8ce4e1ca69970948`
33
+ - Package: `@dailephd/my-frontend-observer`
34
+ - Package version: `0.8.1` (unchanged)
35
+ - v0.9: unreleased
36
+
37
+ ## 3. Prompt 1 entry gate
38
+
39
+ Preflight matched every required value: branch `master`, HEAD
40
+ `59ae009d4bfdd38b91aa7a4e8ce4e1ca69970948`, clean tracked worktree, package
41
+ `0.8.1`, and `docs/reports/v0.9-architecture-retrieval.md` still carrying its
42
+ intentional `skip-worktree` flag (`S`). No pull, rebase, reset, stash or clean
43
+ was run.
44
+
45
+ The only worktree entry at preflight was the untracked blocker report from the
46
+ previous attempt, which this report replaces.
47
+
48
+ The Prompt 1 demo is unchanged in design. The canonical viewport is still
49
+ 1440x900, the nine states are intact, and the `move-footer` correction still
50
+ uses relative positioning so real Chromium observes a genuine 64px footer
51
+ shift. A digest of every tracked file under `examples/v09-demo/` was taken
52
+ before the final tutorial runs and compared afterwards; see section 23.
53
+
54
+ ## 4. my-dev-kit-lab v0.4.8 released contract
55
+
56
+ Read from the released package rather than assumed:
57
+
58
+ ```text
59
+ TutorialScenarioV1 schemaVersion 1.0.0
60
+ TutorialTargetContractV1 schemaVersion 1.0.0
61
+ TutorialRunResultV1 schemaVersion 1.0.0
62
+ TutorialManifestV1 schemaVersion 1.0.0
63
+ ```
64
+
65
+ All four remain `1.0.0`. The v0.4.8 changelog states this explicitly, and the
66
+ committed scenarios and the generated target contracts use those exact values.
67
+
68
+ Locator kinds, assertions, the `{{targetRoot}}` placeholder and the
69
+ `contract-root`/`target-root` process working directories are unchanged from
70
+ v0.4.7. The action vocabulary gained two members and lost none:
71
+
72
+ ```text
73
+ goto, click, fill, press, hover, drag, wait-for, pointer-click, pointer-drag
74
+ ```
75
+
76
+ Existing element-to-element `drag` is preserved unchanged, so v0.4.7 scenarios
77
+ remain valid.
78
+
79
+ `LAB_CLI_SOURCE: npm`. `npx --yes @dailephd/my-dev-kit-lab@0.4.8 --version`
80
+ reported `0.4.8`. The sibling checkout at `C:\Users\daile\Projects\my-dev-kit-lab`
81
+ was inspected read-only for corroboration and reports version `0.4.8`; it was
82
+ not modified, built or written to.
83
+
84
+ ## 5. v0.4.8 positional pointer-action contract
85
+
86
+ The released shapes, taken from the validator and the executor:
87
+
88
+ ```json
89
+ {
90
+ "type": "pointer-click",
91
+ "locator": { "kind": "test-id", "testId": "runtime-annotation-surface" },
92
+ "position": { "x": 0.33, "y": 0.48 },
93
+ "coordinateSpace": "fraction",
94
+ "timeoutMs": 5000
95
+ }
96
+ ```
97
+
98
+ ```json
99
+ {
100
+ "type": "pointer-drag",
101
+ "locator": { "kind": "test-id", "testId": "runtime-annotation-surface" },
102
+ "from": { "x": 0.24, "y": 0.18 },
103
+ "to": { "x": 0.83, "y": 0.36 },
104
+ "coordinateSpace": "fraction",
105
+ "timeoutMs": 5000
106
+ }
107
+ ```
108
+
109
+ Contract facts that matter for authoring:
110
+
111
+ - `coordinateSpace` is required and its only accepted value is `"fraction"`.
112
+ - Coordinates are fractions of the located element's own bounding box, with
113
+ inclusive bounds `[0, 1]`. Anything outside that fails validation.
114
+ - A zero-length `pointer-drag` is rejected by the validator, so a drag always
115
+ expresses a real gesture.
116
+ - Execution is real Playwright mouse input: `mouse.move`, `mouse.down`, for a
117
+ drag a `mouse.move` to the end point in a fixed 8 steps
118
+ (`POINTER_DRAG_MOVE_STEPS = 8`), then `mouse.up`. The `mouse.up` runs even
119
+ when the movement throws, so a failed step cannot leave a button held down.
120
+ - Both actions wait for the located element to be visible and then read its
121
+ bounding box. They anchor to one element rather than to the page, so a
122
+ scenario still cannot address arbitrary screen coordinates.
123
+ - The cursor, click feedback, highlight and callout overlays are the lab's own
124
+ and are unchanged; Observer contributes none of them.
125
+
126
+ Both names match what the defect diagnosis suggested, but they were taken from
127
+ the released package, not assumed.
128
+
129
+ ## 6. Pointer defect reproduction closure
130
+
131
+ The v0.4.7 blocker was that `drag` resolved two locators and dragged between
132
+ their element centres, while Observer drawing needs two freely chosen points
133
+ inside one surface. In every drawing mode the viewer deliberately makes target
134
+ rectangles pointer-inert, so the only pointer-receiving element covering the
135
+ drawing surface was the single screenshot `<image>`, and two locators could not
136
+ yield two distinct points of one element.
137
+
138
+ Before writing any scenario, that exact interaction was reproduced against the
139
+ real demo, the real project-aware viewer and the real released v0.4.8 CLI. A
140
+ disposable gate scenario drove all five drawing primitives and asserted the
141
+ draft item count after each one:
142
+
143
+ ```text
144
+ step action result draft item count
145
+ rectangle-draw pointer-drag passed 0 -> 1
146
+ line-draw pointer-drag passed 1 -> 2
147
+ arrow-draw pointer-drag passed 2 -> 3
148
+ point-place pointer-click passed 3 -> 4
149
+ note-place pointer-click passed 4 -> 5
150
+ ```
151
+
152
+ The run finished `status: passed` with `cleanupErrors: []`, and the captured
153
+ screenshot shows five real marks on the observation with the panel reporting
154
+ "5 draft items". The marks were created through the viewer's ordinary pointer
155
+ event path: `useAnnotationPointerInteraction`'s pointerdown, pointermove and
156
+ pointerup handlers on the source-frame SVG.
157
+
158
+ No Observer workaround was involved. No hidden control, no tutorial-only
159
+ endpoint, no direct annotation POST, no `page.evaluate`, no React state
160
+ mutation and no synthetic fixture. The gate passed with zero Observer source
161
+ changes: the two viewer selectors described in section 14 were added
162
+ afterwards, for authoring quality, not to make the gate pass.
163
+
164
+ One measured detail that shaped every scenario: the drawing SVG's bounding box
165
+ maps one-to-one onto the source frame, so a fraction of the element is a
166
+ fraction of the captured viewport. That is why the committed coordinates read
167
+ as sensible positions on the page.
168
+
169
+ ## 7. Point / Rectangle / Line / Arrow / Note gesture proof
170
+
171
+ ```text
172
+ primitive lab action surface locator position specification real Observer draft
173
+ point pointer-click test-id runtime-annotation-surface position (0.33, 0.48) point mark added
174
+ rectangle pointer-drag test-id runtime-annotation-surface (0.24,0.18) -> (0.83,0.36) rectangle mark added
175
+ line pointer-drag test-id runtime-annotation-surface (0.24,0.64) -> (0.80,0.64) line mark added
176
+ arrow pointer-drag test-id runtime-annotation-surface (0.30,0.55) -> (0.62,0.55) arrow mark added
177
+ note pointer-click test-id runtime-annotation-surface position (0.35, 0.77) note mark added with its text
178
+ ```
179
+
180
+ The same two actions drive the reference surface in scenarios 3 and 4, anchored
181
+ to `test-id reference-annotation-surface`.
182
+
183
+ Every one of the five is exercised again in the committed scenarios, so the
184
+ gate is not a one-off: scenario 1 alone creates all five mark kinds and its
185
+ saved artifact contains `point, rectangle, line, arrow, note`.
186
+
187
+ ## 8. Disposable tutorial target architecture
188
+
189
+ Each run gets a lab-owned run root. The lab passes `<runRoot>/target` to the
190
+ prepare command as `{{targetRoot}}`, and prepare builds a complete throwaway
191
+ Observer project inside it:
192
+
193
+ ```text
194
+ <targetRoot>/demo/ materialized deterministic demo
195
+ <targetRoot>/demo/server.mjs the demo server the lab then manages
196
+ <targetRoot>/frontend-observer.json canonical project configuration
197
+ <targetRoot>/.frontend-observer/ canonical managed evidence state
198
+ ```
199
+
200
+ These are the current canonical Observer paths (`PROJECT_CONFIG_FILENAME` and
201
+ `PROJECT_STATE_DIRECTORY`), not a parallel layout. No second project
202
+ configuration was invented.
203
+
204
+ Nothing is ever written under `examples/v09-demo/`. The demo the lab serves is
205
+ the materialized copy inside the target root, not the tracked template.
206
+
207
+ ## 9. Dynamic target-contract generation
208
+
209
+ `examples/v09-demo/scripts/generate-tutorial-target.mjs` writes one contract to
210
+ an explicit `--out` path for an explicit `--scenario`.
211
+
212
+ Ports are discovered with Node built-ins only: a temporary
213
+ `net.Server` bound to `127.0.0.1:0`, the assigned port read back, the listener
214
+ closed. Two ports are taken and checked to be distinct. No dependency was
215
+ added.
216
+
217
+ Closing the probe is unavoidable, and it means a port is only known-free at the
218
+ instant it is chosen; nothing can reserve a TCP port without holding it open.
219
+ The contract is therefore generated immediately before each run, and that
220
+ constraint is documented in the demo README.
221
+
222
+ `--demo-port` and `--viewer-port` pin the ports for deterministic debugging and
223
+ for the generator's own tests. Generated contracts carry absolute paths and
224
+ live ports, so none are committed; only the generator is.
225
+
226
+ The contract declares a `demo-server` process with an HTTP readiness probe on
227
+ `/health`, an `observer-viewer` process with a readiness probe on
228
+ `/api/status`, and an `applicationUrl` pointing at the viewer, which is the
229
+ application the tutorials drive. Both processes use `cwd: "target-root"`, and
230
+ the viewer is started without `--root` so the project-aware session discovers
231
+ the prepared project itself.
232
+
233
+ ## 10. Prepare/bootstrap lifecycle
234
+
235
+ `examples/v09-demo/scripts/prepare-tutorial-target.mjs` is the contract's
236
+ trusted prepare executable. It runs once and terminates; it never becomes a
237
+ server.
238
+
239
+ In order:
240
+
241
+ 1. validate `--target-root`, `--scenario` and `--demo-port`;
242
+ 2. materialize the demo by calling Prompt 1's own `prepareDemoTarget`, so no
243
+ second demo-copy implementation exists;
244
+ 3. start Prompt 1's `startDemoServer` in process on the same `demoPort` the lab
245
+ will later use;
246
+ 4. `node dist/cli.js init` with the demo URL, the 1440x900 viewport and a
247
+ targets file;
248
+ 5. `node dist/cli.js capture baseline`;
249
+ 6. scenario-specific evidence;
250
+ 7. stop the temporary server in a `finally`.
251
+
252
+ The temporary server exists because Observer's canonical `init` and `capture`
253
+ need a live loopback target. Stopping it in a `finally` means a failing prepare
254
+ still releases the port, which a test verifies by rebinding that exact port
255
+ after prepare returns.
256
+
257
+ Every artifact comes from a canonical owner. Prepare authors contract JSON only
258
+ as *input* to `approve-baseline` and `save-change-contract`, which are what
259
+ validate and persist the artifacts; it never writes an artifact manifest
260
+ itself.
261
+
262
+ ## 11. Baseline observation setup
263
+
264
+ `capture baseline` is run through the built CLI, and the result is verified
265
+ rather than assumed: the alias `baseline` resolves in the project catalog, the
266
+ observation manifest's `observationId` matches the catalog entry, the
267
+ `screenshot.png` is non-empty, and all ten configured runtime targets report
268
+ `selectionStatus: matched`.
269
+
270
+ The configured targets bind to Prompt 1's stable vocabulary:
271
+
272
+ ```text
273
+ header, navigation, hero, sidebar, content, card1, card2, cta, footer, asset
274
+ ```
275
+
276
+ Observer target names must match `^[A-Za-z0-9_-]{1,64}$`, which has no place
277
+ for a hyphenated `card-1`, so the two card targets are named `card1` and
278
+ `card2` while their selectors remain `[data-demo-target="card-1"]` and
279
+ `[data-demo-target="card-2"]`. That is the only naming adjustment, and it is
280
+ deterministic.
281
+
282
+ ## 12. Contract activation setup
283
+
284
+ For `observer-v09-runtime-contract`, prepare additionally approves a baseline
285
+ contract and saves an initial change contract through the canonical commands,
286
+ then writes project acceptance.
287
+
288
+ The acceptance block is written through the canonical
289
+ `validateProjectConfig` owner: the configuration is read, extended,
290
+ revalidated and only then written back, so an invalid acceptance block can
291
+ never reach disk.
292
+
293
+ One correction was needed during implementation. `--output` names a *parent*
294
+ directory and the writer creates the artifact directory beneath it, so
295
+ acceptance must point at the artifact directory, not the output argument.
296
+ Pointing at the parent produced a genuine runtime refusal from the promotion
297
+ service ("the configured baseline contract is missing or invalid"). The real
298
+ persisted paths are now read back from the CLI's own `Artifact:` output and
299
+ converted to portable project-relative paths.
300
+
301
+ After the tutorial promotes and activates, the canonical check in section 21
302
+ confirms the baseline entry is untouched and only the change entry moved.
303
+
304
+ ## 13. External-reference setup
305
+
306
+ For both reference scenarios, prepare imports the fixed Prompt 1 asset
307
+ `examples/v09-demo/references/v09-reference.png` through `import-reference`
308
+ with a `--label` of `v09-tutorial-reference`, then approves it through
309
+ `approve-reference`. No external-reference manifest is handwritten.
310
+
311
+ Regions come from a new tracked fixture,
312
+ `examples/v09-demo/references/v09-reference-regions.json`, holding seven
313
+ regions: `header, hero, sidebar, content, cta, asset, footer`.
314
+
315
+ The geometry was not guessed. The committed PNG's dimensions were read from its
316
+ own IHDR chunk and confirmed to be 1440x900, and the rectangles were then
317
+ measured from the demo's `reference` state in real Chromium at that exact
318
+ viewport. Because the PNG is a 1:1 viewport capture, reference-image pixels
319
+ equal that state's rendered geometry. Every rectangle is integral and inside
320
+ the image bounds. A test re-derives the digest of the committed PNG and
321
+ asserts the imported artifact carries exactly those bytes and those region ids.
322
+
323
+ Applicability was left undeclared. The importer does not require it here, and
324
+ inventing a theme or application state merely for completeness would have been
325
+ fabricated metadata.
326
+
327
+ ## 14. Viewer selector stabilization
328
+
329
+ Two selectors were added, one per annotation drawing surface:
330
+
331
+ ```text
332
+ data-testid="runtime-annotation-surface" the observation drawing surface
333
+ data-testid="reference-annotation-surface" the reference drawing surface
334
+ ```
335
+
336
+ The entire production diff under `viewer/src/` is 18 added lines across four
337
+ files and zero deleted lines: an optional `surfaceTestId` prop on
338
+ `TargetOverlaySvg` and `ReferenceRegionOverlaySvg`, rendered as `data-testid`
339
+ only when supplied, plus the two call sites that supply it.
340
+
341
+ The prop is optional on purpose. `TargetOverlaySvg` is also rendered twice
342
+ side by side in the comparison workspace, where one shared test id would be
343
+ ambiguous, so only the annotation-capable workspace supplies one. A test
344
+ asserts exactly one of each id is ever present.
345
+
346
+ Everything else in the scenarios uses existing accessibility, in the priority
347
+ order the task sets: `role` plus accessible name wherever possible
348
+ (`Rectangle mode`, `Associate target hero`, `Select reference region sidebar`,
349
+ `observation baseline`), existing stable data attributes for status
350
+ (`data-draft-item-count`, `data-interpretation-state`, `data-activation-state`,
351
+ `data-materialized-lifecycle`), and deliberate stable classes for a few status
352
+ paragraphs. No selector binds to a generated identifier, and a test enforces
353
+ that by rejecting any 32-character hex run, any `annotation-item-` prefix and
354
+ any `:nth-child` anywhere in the four scenarios.
355
+
356
+ No behavior branch, no tutorial-only control, no hidden button and no alternate
357
+ code path was added. A test drives a real drawing gesture through the newly
358
+ identified surface to prove the attribute changed nothing.
359
+
360
+ ## 15. Annotation basics scenario
361
+
362
+ `observer-v09-annotation-basics`, 27 steps.
363
+
364
+ Covers the project-aware viewer, selecting the baseline observation by its
365
+ human alias, Select and Pan modes, all five mark kinds, explicit target
366
+ association, save, reload, and reopening the saved annotation as a revision.
367
+
368
+ The narration teaches that a mark is evidence rather than a requirement; that
369
+ drawing over a target does not associate it, because overlap genuinely is
370
+ ambiguous; that Select and Pan are separate so one drag is never ambiguous;
371
+ that saving produces immutable evidence; and that editing produces a revision
372
+ rather than a rewrite.
373
+
374
+ The arrow step already plants the idea the next tutorial proves: a drawn
375
+ direction is a fact about the drawing, not an instruction about the layout.
376
+
377
+ One real authoring defect was found and fixed here rather than worked around.
378
+ The arrow originally crossed the rectangle's clickable centre, so selecting the
379
+ rectangle failed with a genuine pointer interception. The arrow was moved to
380
+ the card row.
381
+
382
+ ## 16. Runtime intent and contract scenario
383
+
384
+ `observer-v09-runtime-contract`, 106 steps.
385
+
386
+ Demonstrates all five operations, `inspect`, `move`, `resize`, `preserve` and
387
+ `remove`, and all four authorable categories, `requested`,
388
+ `expected-dependent` with mode `required`, `protected` and `preserved`. The
389
+ narration states plainly that `unexpected` is not authorable, because it is
390
+ something Observer concludes when comparing runs, never something a person
391
+ declares in advance.
392
+
393
+ The required mappings are all shown as real structured output:
394
+
395
+ ```text
396
+ Move Right -> property-increases hero.x
397
+ Resize Wider -> property-increases sidebar.width
398
+ Preserve property -> property-unchanged-within-tolerance footer.y
399
+ Preserve relationship -> relationship-unchanged
400
+ ```
401
+
402
+ Arrow non-inference is demonstrated, not merely narrated: the arrow is drawn
403
+ pointing left, the move direction `right` is chosen explicitly, and the
404
+ scenario asserts the resulting primitive is `property-increases` on
405
+ `"property": "x"` for `"target": "hero"`.
406
+
407
+ Confirmation invalidation is shown by retargeting a confirmed intent from
408
+ `hero` to `content`: the primitive follows the new association and the
409
+ interpretation drops back to `candidate`, which the scenario asserts. The
410
+ subject is then restored and reconfirmed.
411
+
412
+ `Remove` is confirmed and then shown to be unpromotable, with the product's own
413
+ words asserted: the vocabulary has "no target-absent primitive". `Inspect` is
414
+ confirmed and shown to be informational for good.
415
+
416
+ Selective promotion picks two of four confirmed intents by their deterministic
417
+ human-readable labels, and activation is a separate explicit checkbox. The
418
+ promotion result is asserted to report `2 clauses` and
419
+ `Activation: activated`.
420
+
421
+ Two real defects were found and fixed while making this run: the evidence-list
422
+ locator `baseline` became ambiguous once a contract path also contained the
423
+ word, and a perfectly horizontal line has a zero-height bounding box that
424
+ Playwright treats as invisible. The locator is now `observation baseline` and
425
+ the line is drawn diagonally.
426
+
427
+ ## 17. Reference authoring scenario
428
+
429
+ `observer-v09-reference-authoring`, 64 steps.
430
+
431
+ Covers reference-image coordinates, explicit region association, candidate
432
+ region create and refine, all three requirement subject kinds, informational
433
+ intent, asset-sensitive intent, confirmation and save.
434
+
435
+ The coordinate distinction is asserted, not just narrated: the surface's
436
+ accessible name is checked to be exactly `Reference image, 1440 by 900 pixels`,
437
+ and the narration explains these are image pixels and not the runtime CSS
438
+ pixels of a rendered page.
439
+
440
+ The three requirements use real current vocabulary:
441
+
442
+ ```text
443
+ region-property hero.width, protected, absolute-reference-px 4
444
+ region-relationship chosen from relationships Observer derived from this
445
+ reference's own geometry, protected
446
+ region-measurement vertical-gap hero to cta, preserved,
447
+ absolute-reference-px 6
448
+ ```
449
+
450
+ The relationship is picked from the canonical derived list rather than typed,
451
+ so the scenario cannot require a relationship the image does not contain.
452
+
453
+ Asset-sensitive intent is shown as informational, with the narration
454
+ explaining that an image mattering to a human eye is not the same as a
455
+ checkable geometric claim.
456
+
457
+ The requirement form keeps its previous values when another mark is selected,
458
+ unlike the runtime form which resets. That is existing product behavior and was
459
+ not changed; the scenario steps from the retained value instead, and the demo
460
+ README documents it for anyone editing these files.
461
+
462
+ ## 18. Reference materialization scenario
463
+
464
+ `observer-v09-reference-materialization`, 51 steps.
465
+
466
+ From a clean run: open the approved source reference, create a candidate
467
+ region, refine an existing region, author two requirements, confirm all four,
468
+ save, select three of the four, and materialize.
469
+
470
+ Leaving the fourth unselected is the point. It is confirmed and correct and
471
+ simply not part of this revision, which is what makes selection meaningful and
472
+ is verified afterwards by the new revision carrying exactly one requirement.
473
+
474
+ The scenario asserts the product's own bounded evidence before acting, that
475
+ materialization is blocked until the annotation is saved and that the warning
476
+ states the current reference remains unchanged and the result is "not approved
477
+ automatically"; and afterwards that the result reports `Lifecycle: imported`,
478
+ `Approval required: yes`, a `Supersedes:` line and `Requirements: 1`.
479
+
480
+ The final step notes that the viewer did not switch to the new revision, and
481
+ explains why quietly adopting an unapproved reference would make approval
482
+ meaningless. No behavior was changed to make navigation easier.
483
+
484
+ ## 19. Lab scenario validation
485
+
486
+ Every scenario was validated by the real released CLI against a freshly
487
+ generated target contract:
488
+
489
+ ```text
490
+ observer-v09-annotation-basics valid targetIdMatches true 27 steps
491
+ observer-v09-runtime-contract valid targetIdMatches true 106 steps
492
+ observer-v09-reference-authoring valid targetIdMatches true 64 steps
493
+ observer-v09-reference-materialization valid targetIdMatches true 51 steps
494
+ ```
495
+
496
+ ## 20. Real tutorial execution
497
+
498
+ All four executed through `my-dev-kit-lab tutorial run` with real Chromium,
499
+ each into its own run root outside the repository, under
500
+ `%TEMP%\my-frontend-observer-v09-tutorial-smoke\final\`. `Z:` was not available
501
+ on this machine, so the documented fallback was used.
502
+
503
+ ```text
504
+ scenario status cleanupErrors failed steps
505
+ observer-v09-annotation-basics passed [] none
506
+ observer-v09-runtime-contract passed [] none
507
+ observer-v09-reference-authoring passed [] none
508
+ observer-v09-reference-materialization passed [] none
509
+ ```
510
+
511
+ Every run reported `TutorialRunResultV1` schema `1.0.0`. Each scenario received
512
+ a fresh target root and built its own starting state; no run consumed another
513
+ run's output.
514
+
515
+ ## 21. Canonical Observer post-run verification
516
+
517
+ The video is not correctness authority. Each run's disposable target root was
518
+ read directly. All 35 checks passed.
519
+
520
+ Scenario 1: one saved `my-frontend-observer/visual-annotation` artifact,
521
+ schema `1.0.0`, source `runtime-observation` whose `observationId` equals the
522
+ prepared baseline; all five mark kinds present; exactly one explicit
523
+ `runtime-target` association, to `hero`; the other four marks still
524
+ unassociated.
525
+
526
+ Scenario 2: project acceptance still names
527
+ `tutorial-baseline/v09-tutorial-baseline` as `baselineArtifact`, unchanged;
528
+ `changeArtifact` now points at a promoted change contract that is not the
529
+ initial one; that contract has exactly two clauses:
530
+
531
+ ```text
532
+ requested:property-increases:hero.x
533
+ expected-dependent:property-increases:sidebar.width
534
+ ```
535
+
536
+ Remove did not become a clause, Inspect did not become a clause, and the saved
537
+ annotation still records all six items with all six confirmed, including the
538
+ unpromotable remove and inspect intents.
539
+
540
+ Scenario 3: annotation source is `external-reference`; seven items, all
541
+ confirmed; region `create` and `refine` both present; all three requirement
542
+ subject kinds present; informational and asset-sensitive intents confirmed and
543
+ not materialized; and no new reference revision was created by authoring
544
+ alone, which is the distinction the next tutorial builds on.
545
+
546
+ Scenario 4: exactly one approved reference still exists and no approved
547
+ successor was created; a new imported revision exists whose
548
+ `supersedesReferenceId` equals the approved source's `referenceId`; it carries
549
+ 8 regions including `promo-band`, and the refined `sidebar` rectangle really
550
+ differs from the source's; it carries exactly one requirement, the selected
551
+ `hero.width` one, and the unselected footer requirement is absent; the source
552
+ approved reference is unchanged at 7 regions and 0 requirements; and the image
553
+ digest is byte-identical to the committed PNG
554
+ (`4bdb94db1096532d...`), proving the source image is reused and annotation
555
+ marks are not baked into it.
556
+
557
+ ## 22. Tutorial artifact inventory
558
+
559
+ Every required artifact was written as a non-empty regular file, and every
560
+ requested screenshot was produced.
561
+
562
+ ```text
563
+ scenario video srt vtt markdown manifest screenshots
564
+ observer-v09-annotation-basics 1.8 MB 3.0 kB 2.9 kB 3.2 kB 24.6 kB 9 of 9
565
+ observer-v09-runtime-contract 5.5 MB 11.3 kB 11.0 kB 12.0 kB 66.3 kB 13 of 13
566
+ observer-v09-reference-authoring 3.8 MB 7.5 kB 7.3 kB 8.0 kB 42.9 kB 8 of 8
567
+ observer-v09-reference-materialization 2.8 MB 4.5 kB 4.4 kB 4.7 kB 32.8 kB 6 of 6
568
+ ```
569
+
570
+ All four manifests declare `TutorialManifestV1` schema `1.0.0`.
571
+
572
+ No generated artifact is tracked. Videos, screenshots, subtitles, Markdown,
573
+ manifests, generated target contracts, target roots and logs all live outside
574
+ the repository or under the git-ignored workflow root. Prompt 3 owns the human
575
+ review of these artifacts and any decision to retain some of them.
576
+
577
+ ## 23. Source/demo immutability
578
+
579
+ A SHA-256 digest of all 19 tracked files under `examples/v09-demo/` was taken
580
+ after implementation was complete and immediately before the four final runs,
581
+ and recomputed after all four finished.
582
+
583
+ ```text
584
+ demoSourceImmutable: true
585
+ ```
586
+
587
+ Tutorial execution mutated nothing in the tracked template. The bootstrap test
588
+ independently fingerprints the template around a real prepare and asserts the
589
+ same thing, and the Prompt 1 materialization test continues to prove the
590
+ template survives a destructive rematerialization.
591
+
592
+ ## 24. Full Observer regression
593
+
594
+ ```text
595
+ npm run typecheck PASS
596
+ npm run lint PASS
597
+ npm test PASS (89 files, 1451 tests)
598
+ npm run test:browser PASS (29 files, 275 tests)
599
+ npm run test:security PASS (16 files / 167 tests, then 3 files / 77 tests)
600
+ npm run build PASS
601
+ npm run check:docs PASS (17 required files)
602
+ npm pack --dry-run PASS (0.8.1, 364 files, 0 examples/ entries)
603
+ ```
604
+
605
+ New tests added by this stage:
606
+
607
+ - `tests/unit/v09TutorialTargetContract.test.ts` (16 tests): released schema
608
+ version, exact target id shared by all four scenarios, only keys the
609
+ released validator accepts, no shell-like field, `{{targetRoot}}` as the only
610
+ placeholder, demo readiness on `/health`, viewer readiness on `/api/status`,
611
+ `applicationUrl` pointing at the viewer, loopback-only urls, rejection of an
612
+ unknown scenario and of invalid or identical ports, determinism for explicit
613
+ ports, and two distinct free ports when none are given.
614
+ - `tests/unit/v09TutorialScenarios.test.ts` (10 tests): exactly four files,
615
+ released schema version, unique ids in order, one shared target id,
616
+ 1440x900 viewport, non-empty narration on every step, only released
617
+ actions/locators/assertions, in-bounds non-degenerate pointer fractions,
618
+ never using element-to-element `drag` for drawing, no Observer-only field, no
619
+ absolute path, and no selector bound to a generated identifier. It
620
+ deliberately does not reimplement the lab validator, which is run separately.
621
+ - `tests/browser/v09TutorialBootstrap.test.ts` (17 tests): real prepare runs
622
+ with real Chromium capture, proving materialization, canonical project
623
+ initialization, target binding to the demo vocabulary, baseline capture with
624
+ all ten targets matched, the temporary server releasing its port, contract
625
+ acceptance with valid baseline and change contracts tied to the captured
626
+ observation, canonical reference import and approval with a digest match
627
+ against the committed PNG, and template immutability.
628
+ - `tests/browser/v09ViewerTutorialSelectors.test.ts` (6 tests): each new test
629
+ id present exactly once in the state a tutorial uses it in, on the real SVG
630
+ surface, with its pre-existing role, accessible name and class untouched,
631
+ present across every interaction mode, still drawing correctly, and never
632
+ duplicated.
633
+
634
+ One existing test was updated rather than weakened:
635
+ `tests/unit/v09DemoMaterialization.test.ts` pinned the demo template's
636
+ top-level entries, which legitimately gained `tutorials/`. No test was skipped,
637
+ deleted or made permissive, and no `.skip`, `.only`, `continue-on-error` or
638
+ `|| true` was introduced.
639
+
640
+ ## 25. Scope audit
641
+
642
+ All of the following are `false`:
643
+
644
+ ```text
645
+ Observer tutorial runtime implemented false
646
+ Observer Playwright loader added false
647
+ Observer browser recorder added false
648
+ Observer subtitle writer added false
649
+ Observer tutorial-manifest schema added false
650
+ Observer cursor implementation added false
651
+ Observer callout renderer added false
652
+ Observer arbitrary tutorial JavaScript added false
653
+ Observer product semantics changed false
654
+ annotation schema changed false
655
+ contract schema changed false
656
+ reference schema changed false
657
+ package version changed false
658
+ dependency added false
659
+ my-dev-kit-lab modified false
660
+ v0.9 released false
661
+ ```
662
+
663
+ `package.json` and `package-lock.json` are untouched. The dependency set is
664
+ still exactly `playwright`; `@dailephd/my-dev-kit-lab` appears in neither
665
+ `dependencies` nor `devDependencies`, and is invoked only as an external tool
666
+ through `npx`.
667
+
668
+ `src/` is unchanged. The only production change is the 18-line additive viewer
669
+ selector diff in section 14.
670
+
671
+ `examples/v09-demo` was deliberately not added to `package.json#files`; a pack
672
+ dry run confirms zero `examples/` entries. Prompt 3 owns that decision.
673
+
674
+ Documentation scope was respected: only `examples/v09-demo/README.md` and this
675
+ report were written.
676
+
677
+ ## 26. Changed files
678
+
679
+ Added:
680
+
681
+ ```text
682
+ examples/v09-demo/scripts/generate-tutorial-target.mjs
683
+ examples/v09-demo/scripts/prepare-tutorial-target.mjs
684
+ examples/v09-demo/references/v09-reference-regions.json
685
+ examples/v09-demo/tutorials/01-annotation-basics.json
686
+ examples/v09-demo/tutorials/02-runtime-intent-contract.json
687
+ examples/v09-demo/tutorials/03-reference-authoring.json
688
+ examples/v09-demo/tutorials/04-reference-materialization.json
689
+ tests/unit/v09TutorialTargetContract.test.ts
690
+ tests/unit/v09TutorialScenarios.test.ts
691
+ tests/browser/v09TutorialBootstrap.test.ts
692
+ tests/browser/v09ViewerTutorialSelectors.test.ts
693
+ docs/reports/v0.9-tutorial-integration.md
694
+ ```
695
+
696
+ Modified:
697
+
698
+ ```text
699
+ examples/v09-demo/README.md developer tutorial section
700
+ viewer/src/components/TargetOverlaySvg.tsx optional surfaceTestId prop
701
+ viewer/src/components/ReferenceRegionOverlaySvg.tsx optional surfaceTestId prop
702
+ viewer/src/components/ObservationWorkspace.tsx supplies the runtime id
703
+ viewer/src/components/ReferenceWorkspace.tsx supplies the reference id
704
+ tests/unit/v09DemoMaterialization.test.ts template inventory now includes tutorials/
705
+ ```
706
+
707
+ Generated paths, all git-ignored or outside the repository:
708
+
709
+ ```text
710
+ .my-dev-kit-workflow/v0.9/tutorial-integration/ generated contracts, probes, smoke results
711
+ %TEMP%/my-frontend-observer-v09-tutorial-smoke/ tutorial run roots and artifacts
712
+ ```
713
+
714
+ `git diff --check` reports no whitespace problems, and `git status --short`
715
+ shows no generated tutorial artifact in tracked source.
716
+
717
+ ## 27. Remaining Prompt 3 work
718
+
719
+ Prompt 3 owns end-to-end tutorial acceptance, human review of the four videos,
720
+ subtitles and Markdown, documentation reconciliation across the release-facing
721
+ documents, the decision on whether `examples/v09-demo` ships inside `0.9.0`,
722
+ and the final release regression.
723
+
724
+ Two things are worth carrying forward. The scenarios drive native `<select>`
725
+ controls with `ArrowDown` presses because the lab has no select action, so the
726
+ press counts follow the order of Observer's frozen intent vocabularies and will
727
+ need updating if one of those lists gains a member; this is documented in the
728
+ demo README. And free-port discovery cannot hold a port, so a target contract
729
+ should always be generated immediately before its run.
730
+
731
+ No blocker remains.