@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.
Files changed (75) hide show
  1. package/CHANGELOG.md +490 -479
  2. package/LICENSE +21 -21
  3. package/README.md +375 -365
  4. package/dist/application/projectCheckService.d.ts +6 -0
  5. package/dist/application/projectCheckService.js +8 -1
  6. package/dist/application/projectCheckService.js.map +1 -1
  7. package/dist/application/projectWorkflowService.d.ts +7 -2
  8. package/dist/application/projectWorkflowService.js +10 -3
  9. package/dist/application/projectWorkflowService.js.map +1 -1
  10. package/dist/cli.js +510 -510
  11. package/dist/viewer/index.html +13 -13
  12. package/dist/viewer/sw.js +1 -1
  13. package/docs/ARCHITECTURE.md +1394 -1385
  14. package/docs/CI_CD.md +349 -338
  15. package/docs/COMMANDS.md +1035 -1026
  16. package/docs/CONTRACTS.md +1971 -1960
  17. package/docs/CURRENT_STATE.md +1277 -1252
  18. package/docs/DEVELOPMENT.md +240 -237
  19. package/docs/DOCUMENTATION_PRESERVATION_POLICY.md +50 -50
  20. package/docs/PROJECT_DESCRIPTION.md +2248 -2224
  21. package/docs/PROJECT_MILESTONES.md +2681 -2558
  22. package/docs/PROJECT_OVERVIEW.md +200 -196
  23. package/docs/QUICKSTART.md +100 -100
  24. package/docs/RELEASE.md +37 -36
  25. package/docs/ROADMAP.md +1105 -1034
  26. package/docs/SECURITY.md +297 -297
  27. package/docs/WORKFLOWS.md +806 -796
  28. package/docs/plans/v0.10-implementation-plan.md +1509 -1509
  29. package/docs/plans/v0.8-implementation-plan.md +655 -655
  30. package/docs/plans/v0.8.1-cli-usability-patch-plan.md +505 -505
  31. package/docs/plans/v0.9-implementation-plan.md +1529 -1529
  32. package/docs/plans/v0.9.1-implementation-plan.md +468 -468
  33. package/docs/reports/v0.10-batch1-visual-change-workflow-foundation.md +102 -102
  34. package/docs/reports/v0.10-batch2-project-composition-check-recording.md +103 -103
  35. package/docs/reports/v0.10-batch3-viewer-visual-change-workspace.md +93 -93
  36. package/docs/reports/v0.10-batch4-actual-frontend-entry.md +59 -59
  37. package/docs/reports/v0.10-batch5-reference-driven-entry.md +238 -238
  38. package/docs/reports/v0.10-batch6-coding-agent-handoff.md +85 -85
  39. package/docs/reports/v0.10-batch7-correction-review-acceptance.md +145 -145
  40. package/docs/reports/v0.10-batch8-integrated-acceptance.md +109 -109
  41. package/docs/reports/v0.10-implementation-completeness-documentation-reconciliation.md +344 -344
  42. package/docs/reports/v0.10-pre-release-readiness.md +120 -120
  43. package/docs/reports/v0.10-release-preparation.md +70 -70
  44. package/docs/reports/v0.10.1-project-check-baseline-context-implementation.md +86 -0
  45. package/docs/reports/v0.7-bounded-fidelity-context-prompt7.md +243 -243
  46. package/docs/reports/v0.7-implementation-completeness-documentation-reconciliation.md +497 -497
  47. package/docs/reports/v0.7-pre-release-readiness.md +337 -337
  48. package/docs/reports/v0.7-reference-binding-prompt5.md +223 -223
  49. package/docs/reports/v0.7-reference-compatibility-prompt4.md +234 -234
  50. package/docs/reports/v0.7-reference-correction-workflow-prompt8.md +222 -222
  51. package/docs/reports/v0.7-reference-fidelity-prompt6.md +216 -216
  52. package/docs/reports/v0.7-reference-foundation-prompt1.md +151 -151
  53. package/docs/reports/v0.7-reference-regions-prompt2.md +195 -195
  54. package/docs/reports/v0.7-reference-requirements-prompt3.md +217 -217
  55. package/docs/reports/v0.7-release-prep.md +423 -423
  56. package/docs/reports/v0.8-binding-fidelity-interaction-batch6.md +279 -279
  57. package/docs/reports/v0.8-bounded-context-correlation-batch7.md +233 -233
  58. package/docs/reports/v0.8-comparison-contract-inspection-batch4.md +279 -279
  59. package/docs/reports/v0.8-evidence-index-readers-batch2.md +247 -247
  60. package/docs/reports/v0.8-implementation-completeness-documentation-reconciliation.md +741 -741
  61. package/docs/reports/v0.8-integrated-viewer-acceptance-batch8.md +128 -128
  62. package/docs/reports/v0.8-observation-svg-inspection-batch3.md +223 -223
  63. package/docs/reports/v0.8-prerelease-readiness-cross-platform-security-code-rot.md +687 -687
  64. package/docs/reports/v0.8-reference-candidate-inspection-batch5.md +232 -232
  65. package/docs/reports/v0.8-viewer-runtime-pwa-batch1.md +278 -278
  66. package/docs/reports/v0.8.1-implementation-completeness-documentation-reconciliation.md +114 -114
  67. package/docs/reports/v0.8.1-prerelease-readiness-cross-platform-security-code-rot.md +170 -170
  68. package/docs/reports/v0.9-architecture-retrieval.md +14 -37
  69. package/docs/reports/v0.9-final-pre-release-readiness.md +209 -209
  70. package/docs/reports/v0.9-final-readiness-corrections.md +530 -530
  71. package/docs/reports/v0.9-pre-release-readiness.md +169 -169
  72. package/docs/reports/v0.9.1-batch1-pwa-hard-gate-isolation.md +359 -359
  73. package/docs/reports/v0.9.1-batch2-hard-gate-validation-integration.md +262 -262
  74. package/docs/reports/v0.9.1-pre-release-readiness.md +206 -206
  75. package/package.json +59 -59
package/docs/COMMANDS.md CHANGED
@@ -1,1026 +1,1035 @@
1
- # Commands
2
-
3
- ## v0.10 visual workflow operations
4
-
5
- v0.10 adds no CLI command. Project-aware `view` exposes the Visual changes
6
- workspace for actual/reference entry, explicit activation, bounded handoff,
7
- Run check, correction/review, acceptance, governance-result recording, and
8
- explicit restore. These are guarded local HTTP/UI actions backed by canonical
9
- application owners, not new command-line subcommands. Standalone `view --root`
10
- remains inspection-only.
11
-
12
- Every handoff names `check <baseline> --json` as the exact post-edit machine
13
- operation. It captures a fresh candidate and runs the existing canonical
14
- comparison, contract, and configured reference-fidelity owners.
15
-
16
- ## v0.8.1 common workflow
17
-
18
- `init --url <loopback-url> [--viewport WIDTHxHEIGHT] [--target id=selector ... | --targets-file file] [--default-baseline alias] [--replace]` creates schema-`1.1.0` project configuration; schema `1.0.0` remains readable and forbids `acceptance`. Schema `1.1.0` may add exactly:
19
-
20
- ```json
21
- {
22
- "acceptance": {
23
- "comparisonConfigFile": "config/comparison.json",
24
- "contract": {
25
- "baselineArtifact": ".frontend-observer/evidence/contracts/baseline/<id>",
26
- "changeArtifact": ".frontend-observer/evidence/contracts/change/<id>"
27
- },
28
- "reference": {
29
- "approvedArtifact": ".frontend-observer/evidence/references/approved/<id>",
30
- "bindingsFile": "config/reference-bindings.json"
31
- }
32
- }
33
- }
34
- ```
35
-
36
- Every acceptance path is portable and project-relative and is realpath-checked
37
- before use. Both contract paths are required together. The reference must be
38
- explicitly approved; bindings are explicit and default to an empty collection.
39
- `comparisonConfigFile` uses the existing `compare --config-file` format.
40
-
41
- `capture <alias> [--replace]` creates immutable canonical evidence. `current`
42
- is reserved for `check`. `check [<baseline>] [--json]` resolves the explicit
43
- alias or `defaultBaseline`, captures a new immutable `current`, compares it
44
- canonically, and evaluates configured contract/reference acceptance. Its exact
45
- exit codes are:
46
-
47
- ```text
48
- PASS 0
49
- FAIL 1
50
- REVIEW_REQUIRED 2
51
- BLOCKED 3
52
- ```
53
-
54
- Comparison alone is evidence and returns `REVIEW_REQUIRED`, even with zero
55
- differences. `--json` emits exactly one bounded schema-`1.0.0` document and
56
- never embeds screenshots or complete artifacts. `view [--root path]` uses
57
- project evidence and aliases when root is omitted and preserves standalone
58
- behavior when supplied. Advanced commands below remain supported.
59
-
60
- ## Product command surface
61
-
62
- `node dist/cli.js observe` (or `my-frontend-observer observe` once installed
63
- as a bin) captures one bounded, loopback-only browser observation and
64
- persists it as a portable artifact.
65
-
66
- ```text
67
- my-frontend-observer observe --url <loopback-url> [options]
68
- ```
69
-
70
- Required:
71
-
72
- - `--url <url>` — loopback target URL (`http`/`https`; `localhost`,
73
- `127.x.x.x`, or `::1` only - enforced by the existing request/safety
74
- contracts, not by CLI-local logic).
75
-
76
- Options:
77
-
78
- - `--viewport <WIDTHxHEIGHT>` — e.g. `1280x720`. Malformed syntax (missing
79
- `x`, non-numeric, empty side) is rejected before any browser launches;
80
- in-range bounds are enforced by the existing request validator.
81
- - `--target <id=css-selector>` — an explicit CSS-shorthand observation
82
- target. Repeatable; order is preserved. Parsed on the *first* `=` only, so
83
- a selector containing `=` survives intact, e.g.
84
- `--target action=button[data-state="active"]`. Cannot be combined with
85
- `--targets-file`.
86
- - `--targets-file <json-file>` — loads structured semantic observation
87
- targets from a local JSON file instead of `--target`. Cannot be combined
88
- with `--target`. See "Structured semantic targets" below.
89
- - `--scroll-scenario-file <json-file>` — loads one bounded runtime scroll
90
- scenario from a local JSON file. May be combined with either `--target` or
91
- `--targets-file` (it is independent of target configuration). See "Scroll
92
- scenario (`--scroll-scenario-file`)" below.
93
- - `--state-file <json-file>` — loads explicit, caller-declared frontend
94
- state identity (`theme`, `applicationState`, `authenticatedState`) from a
95
- local JSON file. Never inferred by the observer from screenshot pixels,
96
- CSS, DOM, or URLs - this is caller-declared metadata only, used solely for
97
- later comparability/compatibility evaluation (see "v0.7 Prompt 4 reference
98
- applicability and candidate-state compatibility" in `docs/CONTRACTS.md`).
99
- Independent of every other flag.
100
- - `--output <directory>` — portable, relative output location for the
101
- observation artifact (same contract as the request's `outputLocation`; no
102
- drive letter, no leading `/`, no `..` segments).
103
- - `--timeout <ms>` — overall request timeout in milliseconds.
104
- - `--help` — show `observe` usage.
105
-
106
- Also available: `--help` / `-h` (top-level usage) and `--version` (prints the
107
- actual package version).
108
-
109
- On success the command prints exactly:
110
-
111
- ```text
112
- Observation: <observation-id>
113
- State: <complete|partial|warning|fatal|invalid-request>
114
- Artifact: <artifact-root-path>
115
- Targets: <configured-target-count>
116
- Diagnostics: <diagnostic-count>
117
- ```
118
-
119
- and exits `0` for a validly persisted observation - including one whose
120
- `State` truthfully reports `partial` (e.g. a missing or ambiguous target)
121
- - or exits nonzero for invalid CLI syntax, a request the existing validator
122
- rejects, an unsafe/failed navigation with no persistable artifact, or a
123
- failed artifact write. No progress output is printed during a normal
124
- capture. CLI-syntax errors (e.g. a missing `--url`) print as `error:
125
- <message>` followed by `observe` usage; request/capture/persistence
126
- diagnostics print one per line as `[code] message`.
127
-
128
- ### Structured semantic targets (`--targets-file`)
129
-
130
- **Current status: shipped as part of the published `my-frontend-observer@0.3.0`
131
- package.** `--target` (CSS shorthand) remains fully supported alongside it.
132
-
133
- `--targets-file <json-file>` is the public entry point to the v0.2 canonical
134
- target/locator model established in `src/request/request.ts`. It supplies
135
- the same `targets` collection that `--target` supplies, just in structured
136
- form; both converge on the same `normalizeRequest()` validation and the same
137
- downstream browser resolver - there is no separate semantic observation path.
138
-
139
- File format (the exact, first frozen structure - the root object supports
140
- only the `targets` field; any other top-level field is rejected):
141
-
142
- ```json
143
- {
144
- "targets": [
145
- {
146
- "name": "primary-navigation",
147
- "locators": [
148
- { "kind": "role", "role": "navigation", "name": "Primary" },
149
- { "kind": "id", "value": "nav" }
150
- ]
151
- },
152
- {
153
- "name": "workspace",
154
- "locators": [
155
- { "kind": "data-attribute", "attribute": "data-region", "value": "workspace" }
156
- ]
157
- }
158
- ]
159
- }
160
- ```
161
-
162
- Each target has a stable `name` and an ordered `locators` array (1-5
163
- entries; order is the fallback order - the first locator that resolves
164
- uniquely wins, an ambiguous or unevaluable locator stops immediately without
165
- trying the next one). Each locator is one of the six frozen kinds:
166
-
167
- - `{ "kind": "role", "role": "<string>", "name"?: "<string>" }`
168
- - `{ "kind": "id", "value": "<string>" }`
169
- - `{ "kind": "data-attribute", "attribute": "data-*", "value": "<string>" }`
170
- - `{ "kind": "semantic-element", "tag": "<one of the frozen structural tags>" }`
171
- - `{ "kind": "css", "selector": "<string>" }`
172
- - `{ "kind": "text", "text": "<exact string>" }`
173
-
174
- `--targets-file` itself only validates that the file is readable, is valid
175
- JSON, and has an object root containing exactly a `targets` field - every
176
- target/locator-internal rule (bounds, per-kind required fields, supported
177
- values) is enforced by the same `normalizeRequest()` validator `--target`
178
- already goes through, so both input modes produce identical diagnostics for
179
- equivalent mistakes.
180
-
181
- The path may be relative (resolved from the current working directory) or
182
- absolute; it is operational input only - it never affects the observation's
183
- request identity and is never written into `manifest.json`.
184
-
185
- Example:
186
-
187
- ```powershell
188
- my-frontend-observer observe `
189
- --url http://localhost:3000/ `
190
- --viewport 1280x720 `
191
- --targets-file .\targets.json `
192
- --output observations
193
- ```
194
-
195
- ### Scroll scenario (`--scroll-scenario-file`)
196
-
197
- **Current status: shipped as part of the published `my-frontend-observer@0.3.0`
198
- package.** Observation schema is `1.2.0`.
199
-
200
- `--scroll-scenario-file <json-file>` is the public entry point to the v0.3
201
- runtime scroll-scenario contract established in `src/request/request.ts`
202
- (`ScrollScenario`/`ScrollAction`) and executed in `src/browser/`. It supplies
203
- exactly the value of the normalized request's `scrollScenario` field - the
204
- file root *is* the scenario object itself, with no wrapper field (unlike
205
- `--targets-file`'s `{ "targets": [...] }` root).
206
-
207
- A request supports **zero or one** scroll scenario. There are exactly two
208
- supported action kinds:
209
-
210
- Window scrolling:
211
-
212
- ```json
213
- {
214
- "action": {
215
- "kind": "window-scroll-by",
216
- "deltaX": 0,
217
- "deltaY": 600
218
- }
219
- }
220
- ```
221
-
222
- Target scrolling (the `target` value must be the stable `name` of one of the
223
- observation's own configured targets - never a CSS selector, DOM id, or
224
- source symbol):
225
-
226
- ```json
227
- {
228
- "action": {
229
- "kind": "target-scroll-by",
230
- "target": "tool-workspace",
231
- "deltaX": 0,
232
- "deltaY": 400
233
- }
234
- }
235
- ```
236
-
237
- `deltaX`/`deltaY` are signed integers bounded to `[-20000, 20000]`; at least
238
- one must be non-zero (both zero is rejected). Every scroll/action rule -
239
- supported action kind, required fields, delta types/bounds, the both-zero
240
- rule, and the stable-target-name reference for `target-scroll-by` - is
241
- enforced by the same `normalizeRequest()` validator used everywhere else, not
242
- duplicated in CLI code; `--scroll-scenario-file` itself only validates that
243
- the file is readable, is valid JSON, and has a non-array object root.
244
-
245
- The observer performs the requested scroll immediately (no smooth-scroll
246
- animation), waits exactly two `requestAnimationFrame` cycles, and captures a
247
- final runtime snapshot - the same final state that the observation's ordinary
248
- `pageEvidence`, `targetEvidence`, and `screenshot.png` describe. The actual
249
- resulting scroll position is browser-authoritative and may be clamped by
250
- document/element boundaries; a scenario that produces no movement (already at
251
- a boundary, or a non-scrollable target) is still a valid, successfully
252
- persisted observation, never a fabricated failure.
253
-
254
- Usable with either target input mode:
255
-
256
- ```powershell
257
- my-frontend-observer observe `
258
- --url http://localhost:3000/ `
259
- --target workspace=.workspace `
260
- --scroll-scenario-file .\scroll.json `
261
- --output observations
262
- ```
263
-
264
- ```powershell
265
- my-frontend-observer observe `
266
- --url http://localhost:3000/ `
267
- --targets-file .\targets.json `
268
- --scroll-scenario-file .\scroll.json `
269
- --output observations
270
- ```
271
-
272
- `--target` and `--targets-file` remain mutually exclusive with each other,
273
- exactly as before; `--scroll-scenario-file` is independent of both and is
274
- never itself a third mutually-exclusive target mode. `window-scroll-by`
275
- requires no configured target at all.
276
-
277
- The path may be relative (resolved from the current working directory) or
278
- absolute; it is operational input only - like `--targets-file`'s path, it
279
- never affects the observation's request identity and is never written into
280
- `manifest.json`. Two different scenario files with identical content produce
281
- the same `requestId`; only the requested scenario *configuration*
282
- participates in identity, never the runtime outcome (actual scroll
283
- distance, clamping, or scroll-owner result).
284
-
285
- If a `target-scroll-by` scenario's configured action target cannot be
286
- uniquely resolved at runtime (missing, ambiguous, or otherwise unavailable),
287
- the scroll is not performed, no movement is fabricated, and the observation
288
- persists honestly - typically as `partial` - carrying the same
289
- `target-missing`/`target-ambiguous`/`browser-evidence-unavailable` diagnostic
290
- that any other unresolved configured target would produce.
291
-
292
- ## `compare`
293
-
294
- **Current status: shipped as part of the published `my-frontend-observer@0.4.0`
295
- package.** Comparison schema is `1.0.0`, independent of and never reused for
296
- the observation schema (`1.2.0`).
297
-
298
- `my-frontend-observer compare` (or `node dist/cli.js compare` from a source
299
- checkout) reads two already-persisted observation artifacts and derives
300
- before/after evidence purely from their existing content:
301
-
302
- ```text
303
- my-frontend-observer compare --before <observation-artifact-root> --after <observation-artifact-root> --output <directory> [options]
304
- ```
305
-
306
- Required:
307
-
308
- - `--before <path>` — root directory of the "before" persisted observation
309
- artifact (the directory containing its `manifest.json`, as produced by
310
- `observe`).
311
- - `--after <path>` — root directory of the "after" persisted observation
312
- artifact.
313
- - `--output <directory>` — portable, relative output location for the
314
- comparison artifact (same contract as `observe --output`).
315
-
316
- Options:
317
-
318
- - `--config-file <json-file>` — loads a comparison configuration directly
319
- (no wrapper field): `{ "geometryTolerancePx": <0-10>,
320
- "expectedDependencies": [...] }`. Without it, `geometryTolerancePx`
321
- defaults to `0.5` CSS px with no declared dependencies. As with
322
- `--targets-file`/`--scroll-scenario-file`, `--config-file` only validates
323
- file readability, JSON validity, and a non-array object root; every
324
- semantic rule (tolerance bounds, dependency property/direction
325
- vocabulary, dependency source marker) is enforced by the same domain
326
- validator the comparison engine itself uses.
327
- - `--help` — show `compare` usage.
328
-
329
- **Comparison never launches a browser.** It reads two manifests through the
330
- existing observation-artifact reader, runs the pure comparison engine, and
331
- persists a portable `manifest.json` — no navigation, no target
332
- re-resolution, no Chromium process.
333
-
334
- On success the command prints exactly:
335
-
336
- ```text
337
- Comparison: <comparison-id>
338
- State: <comparable|comparable-with-warnings|incomparable>
339
- Artifact: <comparison-artifact-root>
340
- Differences: <count>
341
- Relationship changes: <count>
342
- Diagnostics: <count>
343
- ```
344
-
345
- and exits `0` — **including when `State` is `incomparable`**: comparison
346
- determining that two observations should not be treated as equivalent
347
- frontend states is itself a successful outcome, not a failure. The command
348
- exits nonzero only for invalid CLI syntax, an unreadable/malformed/
349
- structurally-invalid source artifact, invalid comparison configuration, or
350
- a failed artifact write.
351
-
352
- ### Comparability
353
-
354
- Before any rendered difference is calculated, the engine evaluates whether
355
- the two observations are comparable at all:
356
-
357
- - **Hard incompatibilities** (force `incomparable`): different logical page
358
- URL, different viewport, different browser engine, or a mismatched scroll
359
- scenario configuration (no scenario vs. a scenario, or two different
360
- scenario configurations).
361
- - **Warnings** (still `comparable-with-warnings`, comparison proceeds):
362
- different producer package version, different browser version, or a
363
- changed/added/removed configured target.
364
- - **Unassessed dimensions** the observer does not yet model (theme,
365
- authenticated state, application state) are always recorded, never
366
- silently claimed identical.
367
-
368
- An `incomparable` result still persists a structurally valid
369
- `ComparisonArtifact`: the comparability reasons are recorded, and ordinary
370
- rendered differences/relationship changes stay empty rather than fabricated.
371
-
372
- ### Difference and relationship evidence
373
-
374
- For a `comparable`/`comparable-with-warnings` result, the manifest's
375
- `differences` and `relationshipChanges` arrays carry structured before/
376
- after evidence: appeared/disappeared targets (only for a stable target name
377
- configured on both sides — a target added/removed from configuration is
378
- recorded separately as a `configurationChanges` entry, never fabricated as
379
- appeared/disappeared), moved/resized targets, visibility changes, clipping
380
- changes, actual dimensional overflow changes, DOM containment changes,
381
- page-size changes, scroll-owner changes, and layout-relationship
382
- transitions (e.g. `does-not-overlap` → `overlaps`, or
383
- `document-width-fits-viewport` → `document-width-exceeds-viewport`) reused
384
- verbatim from the same canonical relationship engine `observe` output feeds
385
- Batch 2's `deriveLayoutRelationships`.
386
-
387
- ### Explicit dependency evidence (non-causal)
388
-
389
- `--config-file`'s `expectedDependencies` lets you declare an expected
390
- layout relationship such as "`navigation.width` decreases →
391
- `workspace.width` increases" using only the frozen `x`/`y`/`width`/`height`
392
- property vocabulary and `increase`/`decrease`/`change`/`unchanged`
393
- direction vocabulary. Each declaration is evaluated independently against
394
- the two observations and persists exactly one outcome: `consistent`,
395
- `not-observed`, `contradictory-to-declaration`, or `unavailable`. **The
396
- observer never infers a dependency from co-change, and never emits a
397
- causal claim, a PASS/FAIL verdict, or a change-contract decision** — v0.4
398
- produces comparison evidence; whether that evidence satisfies some
399
- contract is v0.5+ scope.
400
-
401
- ### Path privacy
402
-
403
- `--before`, `--after`, `--config-file`, and `--output` are operational
404
- filesystem input only. None of them affect `comparisonRequestId`, and none
405
- of them are written into the persisted manifest — the manifest instead
406
- retains logical source references (`observationId`, `requestId`,
407
- `producer`, `observationSchemaVersion`, and the source `screenshot.path`).
408
- Two semantically identical observation/config pairs read from different
409
- filesystem locations produce the same `comparisonRequestId`; each execution
410
- still gets a fresh `comparisonId`.
411
-
412
- ### Source observations remain immutable
413
-
414
- Comparison is read-only with respect to its inputs: it never modifies
415
- either source observation's `manifest.json` or `screenshot.png`, and it
416
- never copies screenshot bytes into the comparison directory — the
417
- comparison artifact directory contains `manifest.json` only.
418
-
419
- Example:
420
-
421
- ```powershell
422
- node dist/cli.js compare `
423
- --before observations/<before-id> `
424
- --after observations/<after-id> `
425
- --output comparisons `
426
- --config-file .\comparison-config.json
427
- ```
428
-
429
- ## `approve-baseline`
430
-
431
- **Current status: shipped as part of the published `my-frontend-observer@0.5.0`
432
- package.** Frontend contract schema is `1.0.0`, independent of the
433
- observation (`1.2.0`) and comparison (`1.0.0`) schemas.
434
-
435
- `my-frontend-observer approve-baseline` is the *only* baseline-approval
436
- operation in the observer — approval is never inferred from a successful
437
- comparison or evaluation, and no command automatically supersedes or selects
438
- a baseline. It explicitly approves and persists one already-authored
439
- `PersistentBaselineContract` against the observation it claims to approve:
440
-
441
- ```text
442
- my-frontend-observer approve-baseline --observation <observation-artifact-root> --contract-file <json-file> --output <directory>
443
- ```
444
-
445
- Required:
446
-
447
- - `--observation <path>` — root directory of the persisted observation
448
- artifact this baseline claims to approve (read through the existing
449
- observation-artifact reader).
450
- - `--contract-file <json-file>` — local JSON file containing one raw
451
- `PersistentBaselineContract` (no wrapper field). As with `--config-file`,
452
- only file readability/JSON-validity/non-array-object-root is checked here;
453
- every structural rule (artifact kind, schema version, clause shape) is
454
- enforced by the existing frozen domain validator.
455
- - `--output <directory>` — portable, relative output location for the
456
- baseline artifact.
457
-
458
- Before persisting, the application layer verifies the contract's frozen
459
- `sourceObservation` reference (`observationId`, `requestId`, `producer`,
460
- `observationSchemaVersion`) actually matches the supplied observation
461
- artifact's stable identity — approving a baseline against an unrelated
462
- observation is rejected, even if both artifacts are individually valid. Any
463
- `supersedesBaselineId` already authored in the contract is preserved exactly
464
- as supplied; this command never discovers a prior baseline, infers
465
- supersession, or deletes anything.
466
-
467
- On success the command prints exactly:
468
-
469
- ```text
470
- Baseline: <baseline-id>
471
- State: approved
472
- Artifact: <baseline-artifact-root>
473
- Clauses: <count>
474
- Supersedes: <baseline-id|none>
475
- ```
476
-
477
- and exits `0`. It exits nonzero for invalid CLI syntax, an unreadable/
478
- malformed/structurally-invalid contract file, a `PerChangeContract` passed
479
- where a baseline is expected, a source-observation mismatch, an existing
480
- artifact collision (baseline identities are never overwritten), or a failed
481
- artifact write.
482
-
483
- ## `save-change-contract`
484
-
485
- **Current status: shipped as part of the published `my-frontend-observer@0.5.0`
486
- package.**
487
-
488
- `my-frontend-observer save-change-contract` validates and persists one
489
- already-authored `PerChangeContract` so it can later be evaluated — this is
490
- persistence only, never approval:
491
-
492
- ```text
493
- my-frontend-observer save-change-contract --contract-file <json-file> --output <directory>
494
- ```
495
-
496
- Required:
497
-
498
- - `--contract-file <json-file>` — local JSON file containing one raw
499
- `PerChangeContract` (no wrapper field).
500
- - `--output <directory>` — portable, relative output location for the
501
- change-contract artifact.
502
-
503
- Domain/application validation rejects a `PersistentBaselineContract` passed
504
- here, malformed clauses, an unsupported authored category, an authored
505
- `category: "unexpected"` (the derived-only fifth classification can never be
506
- authored as a permission), and invalid tolerance/mode fields — none of this
507
- is duplicated in CLI code. Any `supersedesBaselineClauseIds` already
508
- authored on a clause is preserved exactly; resolving those references
509
- against a particular baseline remains `evaluate-contract`'s responsibility.
510
-
511
- On success the command prints exactly:
512
-
513
- ```text
514
- Change contract: <contract-id>
515
- Artifact: <contract-artifact-root>
516
- Clauses: <count>
517
- Supersedes baseline clauses: <count>
518
- ```
519
-
520
- and exits `0`. It exits nonzero for invalid CLI syntax, an unreadable/
521
- malformed/structurally-invalid contract file, a persistent baseline contract
522
- passed here, an existing artifact collision, or a failed artifact write.
523
-
524
- ## `evaluate-contract`
525
-
526
- **Current status: shipped as part of the published `my-frontend-observer@0.5.0`
527
- package.** Frontend contract evaluation artifact schema is `1.0.0`, its own
528
- independent family.
529
-
530
- `my-frontend-observer evaluate-contract` executes the canonical Batch 2
531
- evaluator against already-persisted evidence/contracts and persists the
532
- result:
533
-
534
- ```text
535
- my-frontend-observer evaluate-contract --before <observation-artifact-root> --after <observation-artifact-root> --comparison <comparison-artifact-root> --baseline <baseline-contract-artifact-root> --change <per-change-contract-artifact-root> --output <directory> [--enforce]
536
- ```
537
-
538
- Required:
539
-
540
- - `--before <path>` / `--after <path>` — root directories of the persisted
541
- before/after observation artifacts.
542
- - `--comparison <path>` — root directory of the already-persisted comparison
543
- artifact for that before/after pair.
544
- - `--baseline <path>` — root directory of the already-approved baseline
545
- contract artifact.
546
- - `--change <path>` — root directory of the already-persisted per-change
547
- contract artifact.
548
- - `--output <directory>` — portable, relative output location for the
549
- evaluation artifact.
550
-
551
- Options:
552
-
553
- - `--enforce` — makes a `FAIL` verdict produce a nonzero process exit
554
- status. A `FAIL` evaluation is always persisted and printed identically
555
- with or without this flag; `--enforce` changes only the process exit code
556
- — never evaluation identity, contents, or persistence.
557
-
558
- **`evaluate-contract` never launches a browser, never re-resolves targets,
559
- and never recomputes comparison or relationship evidence** — it reads the
560
- already-persisted before/after observations and comparison exactly as given
561
- (through the existing observation-artifact reader and a new comparison
562
- reader) and calls the canonical `evaluateFrontendContract` exactly once.
563
-
564
- A `FAIL` verdict (a found regression or unsatisfied contract clause) is a
565
- successful, persisted evaluation outcome — not an execution error. On
566
- success (evaluation constructed and persisted, verdict `PASS`, or verdict
567
- `FAIL` without `--enforce`), the command prints exactly:
568
-
569
- ```text
570
- Evaluation: <evaluation-id>
571
- Verdict: <PASS|FAIL>
572
- Artifact: <evaluation-artifact-root>
573
- Clauses: <total-clause-result-count>
574
- Unexpected: <unexpected-change-count>
575
- Enforced: <yes|no>
576
- ```
577
-
578
- and exits `0`; with `--enforce` and verdict `FAIL`, it prints the same
579
- result and exits nonzero. It exits nonzero and persists nothing for invalid
580
- CLI syntax or an unreadable/malformed/incoherent source artifact (evaluation
581
- could not even be constructed) — distinct from a legitimate persisted `FAIL`.
582
-
583
- The evaluation artifact directory contains `manifest.json` only — no
584
- screenshot is copied. Operational `--before`/`--after`/`--comparison`/
585
- `--baseline`/`--change`/`--output` paths never enter the persisted
586
- `evaluationRequestId` or any other semantic field; two semantically
587
- identical evaluations invoked from different filesystem locations share the
588
- same `evaluationRequestId` even though each execution gets a fresh
589
- `evaluationId`.
590
-
591
- ## `evaluate-reference-fidelity`
592
-
593
- **Current status: shipped as part of the published `my-frontend-observer@0.7.0`
594
- package.** No new artifact
595
- family or schema version — this command persists nothing.
596
-
597
- `my-frontend-observer evaluate-reference-fidelity` evaluates whether an
598
- already-persisted candidate observation satisfies an external reference's
599
- selected design requirements, gated by reference adequacy (v0.7 Prompt 3),
600
- reference/candidate compatibility (v0.7 Prompt 4), and explicit
601
- region-to-target bindings (v0.7 Prompt 5):
602
-
603
- ```text
604
- my-frontend-observer evaluate-reference-fidelity --reference <external-reference-artifact-root> --candidate <observation-artifact-root> [--bindings-file <json-file>] [--enforce]
605
- ```
606
-
607
- Required:
608
-
609
- - `--reference <path>` — root directory of an already-imported or
610
- already-approved external-reference artifact.
611
- - `--candidate <path>` — root directory of the already-persisted candidate
612
- observation artifact to evaluate against it.
613
-
614
- Options:
615
-
616
- - `--bindings-file <json-file>` — local JSON file of the form
617
- `{ "bindings": [ { "referenceRegion": "...", "runtimeTarget": "..." } ] }`
618
- declaring which stable observer runtime target (a configured target name)
619
- explicitly corresponds to each reference region a selected requirement
620
- depends on. Never inferred from geometry, matching names, or source code.
621
- Optional — omitting it evaluates with no bindings at all, so every
622
- requirement whose subject depends on a reference region becomes
623
- `unavailable`.
624
- - `--enforce` — makes a `fail` fidelity state produce a nonzero process
625
- exit status. A `fail` result is always printed identically with or
626
- without this flag; `--enforce` changes only the process exit code, never
627
- the result's content, and has no effect on a `not-evaluated` result.
628
-
629
- **This command never launches a browser, never re-resolves targets, and
630
- never recomputes reference regions/requirements/adequacy, compatibility, or
631
- bindings** — it reads the already-persisted reference and candidate exactly
632
- as given and evaluates every selected requirement exactly once via
633
- `evaluateReferenceCandidateFidelity`.
634
-
635
- A `not-evaluated` result (reference adequacy inadequate, or reference/
636
- candidate incompatible) and a `fail` result (a found design mismatch) are
637
- both successful, structured evaluation outcomes — not execution errors. On
638
- success, the command prints exactly:
639
-
640
- ```text
641
- Reference: <referenceId>
642
- Candidate: <candidateObservationId>
643
- Adequacy: <adequate|partial|inadequate>
644
- Compatibility: <comparable|comparable-with-warnings|incomparable>
645
- State: <not-evaluated|pass|fail>
646
- Blocked by: <reference-inadequate|incompatible>
647
- Requirements: <count> (pass: <n>, fail: <n>, unavailable: <n>)
648
- Enforced: <yes|no>
649
- ```
650
-
651
- (`Compatibility`/`Blocked by` are printed only when computed/applicable)
652
- and exits `0`, unless `--enforce` is given and the state is `fail`, in
653
- which case it exits nonzero. It exits nonzero and persists nothing for
654
- invalid CLI syntax, an unreadable/malformed `--reference`/`--candidate`
655
- target, a malformed `--bindings-file`, or an invalid/out-of-bound binding
656
- declaration.
657
-
658
- ## `import-reference`
659
-
660
- **Current status: shipped as part of the published `my-frontend-observer@0.7.0`
661
- package.** Persists a new `ExternalReferenceArtifact` in the
662
- `imported` lifecycle state (external-reference schema `1.0.0`).
663
-
664
- ```text
665
- my-frontend-observer import-reference <image-file> --output <directory> [options]
666
- ```
667
-
668
- Required:
669
-
670
- - `<image-file>` — local path to a PNG, JPEG, or WebP external
671
- design-reference image.
672
- - `--output <directory>` — portable, relative output location for the
673
- external-reference artifact.
674
-
675
- Options:
676
-
677
- - `--label <text>` — optional human-readable label, stored as pure
678
- provenance — never part of the reference's logical identity.
679
- - `--supersedes <path>` — root directory of a prior external-reference
680
- artifact (imported or approved) that this import explicitly supersedes.
681
- The prior artifact is never modified.
682
- - `--regions-file <json-file>` — local JSON file of the form
683
- `{ "regions": [...] }` declaring explicit, meaningful reference-image
684
- regions (id plus a `{x, y, width, height}` rectangle in reference-image
685
- pixels, origin at the image's top-left corner). Optional — a reference
686
- imported without this flag behaves exactly as in v0.7 Prompt 1. Region
687
- content participates in the reference's logical identity; the file path
688
- itself never does.
689
- - `--requirements-file <json-file>` — local JSON file of the form
690
- `{ "requirements": [...] }` declaring explicit, user-selected design
691
- requirements over the regions above — what actually matters for later
692
- candidate evaluation, never inferred merely because a region
693
- property/relationship exists. Each requirement has a `category`
694
- (`requested` | `expected-dependent` | `protected` | `preserved` —
695
- `unexpected` is never authorable), a `subject` (a region property, a
696
- region-to-region relationship, or a derived two-region measurement), and
697
- — for property/measurement subjects — a `tolerance` (`exact` |
698
- `absolute-reference-px` | `percent`; relationship subjects must omit
699
- tolerance). Requires `--regions-file` (or an already-present region set)
700
- supplying every region a requirement refers to. Optional — a reference
701
- imported without this flag behaves exactly as in v0.7 Prompt 1/2.
702
- Requirement content participates in the reference's logical identity.
703
- - `--applicability-file <json-file>` — local JSON file declaring the
704
- runtime frontend state this reference is intended to represent:
705
- `{ "viewport": { "width", "height" }, "theme": "...", "applicationState":
706
- "...", "authenticatedState": "authenticated"|"unauthenticated" }` (each
707
- field independently optional; at least one required). `viewport` here is
708
- the CSS-pixel runtime viewport the design represents — distinct from the
709
- reference image's own pixel dimensions, which are never assumed equal.
710
- Never inferred from the image — caller-declared metadata only, used for
711
- later reference/candidate compatibility evaluation (see
712
- [CONTRACTS.md](CONTRACTS.md) "v0.7 Prompt 4"). Optional — a reference
713
- imported without this flag behaves exactly as in v0.7 Prompt 1/2/3.
714
- Applicability content participates in the reference's logical identity.
715
-
716
- Detects the image format from its header bytes only (never from the file
717
- extension), reads its pixel dimensions from the same bounded header bytes
718
- (never decoding pixel data), and persists a new external-reference artifact
719
- in the `imported` lifecycle state — importing never approves it. On
720
- success, prints a concise result (including the accepted region/requirement
721
- counts, the resulting reference-side requirement adequacy — `adequate`,
722
- `partial`, or `inadequate` — and whether applicability was declared) and
723
- exits `0`. On an unreadable file, an unsupported or undetectable format,
724
- invalid/out-of-bound dimensions, an over-limit file size, an unresolvable
725
- `--supersedes` target, an invalid region, an invalid requirement, or invalid
726
- applicability, prints structured diagnostics to stderr and exits nonzero.
727
-
728
- ## `approve-reference`
729
-
730
- **Current status: shipped as part of the published `my-frontend-observer@0.7.0`
731
- package.** Persists a new
732
- `ExternalReferenceArtifact` in the `approved` lifecycle state
733
- (external-reference schema `1.0.0`).
734
-
735
- ```text
736
- my-frontend-observer approve-reference --reference <external-reference-artifact-root> --output <directory> [options]
737
- ```
738
-
739
- Required:
740
-
741
- - `--reference <path>` — root directory of the already-imported
742
- external-reference artifact (the directory containing its
743
- `manifest.json`) to approve.
744
- - `--output <directory>` — portable, relative output location for the
745
- newly persisted approved artifact.
746
-
747
- Options:
748
-
749
- - `--supersedes <path>` — root directory of a prior external-reference
750
- artifact (imported or approved) that this approval explicitly
751
- supersedes. The prior artifact is never modified.
752
-
753
- This is the only explicit reference-approval act in the observer — approval
754
- is never inferred from a successful import or from any later fidelity
755
- evaluation. Approving persists a brand-new artifact instance (a fresh
756
- `referenceId` sharing the imported artifact's `referenceRequestId`) that
757
- carries a reference back to the imported artifact's image rather than a
758
- second copy of its bytes; the imported artifact's own manifest is never
759
- modified. Any regions, requirements, and applicability already declared on
760
- the imported artifact are carried forward unchanged (not re-validated
761
- against new input, not re-derived) — approval never adds, removes, or edits
762
- regions, requirements, or applicability. Only a reference currently in the
763
- `imported` lifecycle state can be approved. On success, prints a concise
764
- result (including the carried-forward region/requirement counts,
765
- reference-side requirement adequacy, and whether applicability was
766
- declared) and exits `0`. On an unreadable/malformed `--reference` target, a
767
- target that is not in the `imported` state, an unresolvable `--supersedes`
768
- target, or a persistence failure, prints structured diagnostics to stderr
769
- and exits nonzero.
770
-
771
- ## `view`
772
-
773
- **v0.9 visual annotation (released in `0.9.0`).**
774
- There is no separate annotation command. `view` is the annotation entry point:
775
-
776
- - `my-frontend-observer view` (inside an initialized project, without
777
- `--root`) is the normal project-aware viewer. It enables local annotation
778
- authoring for that project. In the browser you can draw on observations and
779
- external references, explicitly associate and confirm structured intent,
780
- save immutable annotations, promote selected confirmed runtime intent into a
781
- change contract, and materialize selected confirmed reference intent into a
782
- new imported reference revision. New artifacts are written only under the
783
- project's managed evidence root (`.frontend-observer/evidence`). Nothing is
784
- approved automatically.
785
- - `my-frontend-observer view --root <root>` is the advanced standalone form
786
- for arbitrary evidence roots. It is always read-only. Saved annotations can
787
- be inspected but not edited, and every authoring request is refused.
788
-
789
- In a project-aware session the viewer protocol is `1.3.0` and the API adds
790
- `GET /api/authoring/session`, `GET /api/annotations/<handle>/view`, the
791
- `annotation-overlay` media role, and exactly three authoring routes:
792
- `POST /api/annotations`, `POST /api/annotations/<handle>/promote-contract`,
793
- and `POST /api/annotations/<handle>/materialize-reference`. See
794
- `docs/SECURITY.md` for the local write boundary and `docs/WORKFLOWS.md` for
795
- the annotation workflow. The rest of this section describes the inspection
796
- surface, which is unchanged.
797
-
798
- **Current status: viewer behavior is released as package
799
- `@dailephd/my-frontend-observer@0.9.1`.** Starts one
800
- loopback-only Node viewer server and serves the same React + TypeScript +
801
- Vite application to a normal browser or an installed Progressive Web App.
802
- `--root` is used as a bounded, read-only evidence-discovery root: the server
803
- exposes a metadata-first `GET /api/index` of recognized Observer evidence
804
- beneath it, an on-demand `GET /api/artifacts/<handle>` for one selected
805
- supported artifact, an on-demand `GET /api/media/<handle>/<role>` for its
806
- owned/referenced media, an on-demand `GET /api/observations/<handle>/relationships`
807
- (existing canonical `deriveLayoutRelationships(...)`), `GET /api/comparisons/<handle>/view`
808
- and `GET /api/evaluations/<handle>/view` (exact-identity linked-evidence
809
- resolution), `GET /api/references/<handle>/view` (region-relationship graph
810
- and requirement adequacy, plus — new this batch — `coordinateMapping`, the
811
- exact result of the existing canonical `deriveCoordinateScale(reference)`,
812
- used only to gate view-lock eligibility), and
813
- `GET /api/references/<handle>/candidate/<handle>/view` (page/state-level
814
- compatibility plus optional matching-evaluation handles). New this batch:
815
- `GET /api/references/<handle>/candidate/<handle>/bindings` validates the
816
- session's explicit binding declarations against the selected reference and
817
- calls the existing canonical `evaluateReferenceRuntimeBindings` exactly
818
- once, and `GET /api/references/<handle>/candidate/<handle>/fidelity` is the
819
- explicit on-demand trigger that calls the existing canonical
820
- `evaluateReferenceCandidateFidelity` exactly once — never a second copy of a
821
- linked artifact's own payload, never a recomputed
822
- `compareObservations`/`evaluateFrontendContract`/
823
- `deriveReferenceRegionRelationships`/`deriveReferenceRequirementAdequacy`/
824
- `evaluateReferenceCandidateCompatibility` result, and both new routes are
825
- plain `GET` (deterministic, ephemeral, never persisted). New this batch:
826
- `GET /api/context` returns the viewer session's bounded-agent-context state
827
- established at startup by an optional `--context-file` (below) — `none` (no
828
- file supplied), `unsupported-version` (a recognized `artifactKind` with a
829
- `schemaVersion` this viewer does not currently support — shown honestly,
830
- never coerced), or the validated current context plus `sourceResolution`,
831
- the exact-identity resolution of its `sources` against the current evidence
832
- root (reusing/extending the Batch 4 `linkedEvidence.ts` resolver pattern) —
833
- see `docs/ARCHITECTURE.md` "v0.8 Batch 2" through "v0.8 Batch 7" for the
834
- exact discovery bounds, classification model, coordinate mapping, and
835
- handle/media/linked-evidence-resolution contracts.
836
-
837
- Selecting a supported `observation` record shows the Batch 3 screenshot/SVG
838
- workspace. Selecting a supported `comparison` record shows the Batch 4
839
- before/after side-by-side workspace. Selecting a supported
840
- `contract-evaluation` record shows the Batch 4 clause-result/overall-verdict
841
- workspace. Selecting a supported `external-reference-imported`/
842
- `external-reference-approved` record shows the reference image with region
843
- overlays in the reference image's own pixel coordinate domain, selected
844
- requirements/tolerances/adequacy/applicability/lifecycle/provenance/
845
- supersession, and, once a candidate observation is **explicitly** selected
846
- (never auto-selected), that candidate side by side using the reused Batch
847
- 3/4 runtime screenshot/SVG machinery plus the real canonical compatibility
848
- result. New this batch: both panes support independent, bounded (`1x`–`8x`)
849
- zoom and pointer-drag pan (Fit/Reset controls included) that never rewrites
850
- any evidence coordinate — only when explicit binding declarations were
851
- supplied (`--bindings-file`, below) and the selected reference/candidate
852
- resolve a real canonical `bound` result does selecting a reference region
853
- cross-highlight its exact declared runtime target (and selecting a runtime
854
- target cross-highlight every region that names it) — `ambiguous`/
855
- `unavailable` results and undeclared regions/targets never cross-select,
856
- even when their names happen to match. A "Lock view" control synchronizes
857
- both panes' zoom/pan in source-space (via the exact `coordinateMapping`
858
- scale factor) but is enabled only when a candidate is selected,
859
- compatibility is not `incomparable`, and `coordinateMapping.ok` is `true` —
860
- otherwise it stays disabled with an actionable reason, and any change to
861
- that eligibility (including switching reference/candidate) turns it off
862
- immediately. An explicit "Evaluate Fidelity" action calls the fidelity
863
- endpoint on demand (never automatically) and displays the canonical
864
- `not-evaluated`/`pass`/`fail` state, `blockedBy`, and every requirement
865
- result's status/numeric-or-relationship fields with correct unit labels
866
- (reference-image pixels vs. raw candidate CSS pixels) exactly as returned —
867
- alongside, never merged into, any selected existing contract-evaluation's
868
- own `overallVerdict`. A dedicated "Bounded context" mode (toggled from the
869
- viewer header, alongside the normal "Evidence" mode) shows: context
870
- identity/profile/adequacy/reason codes; every bounded runtime target's
871
- included fields (absent fields read "not included in this bounded context",
872
- never a fabricated falsy value); source references with their exact
873
- resolution status and, for each exactly-resolved source, one-click
874
- navigation back to its existing Batch 3/4/5 viewer surface plus a "View raw
875
- structured evidence" panel reusing the existing `GET /api/artifacts/<handle>`
876
- route unchanged; omissions/truncations with required loss visually
877
- distinguished from optional loss; runtime/static correlation — `correlated`
878
- (its one candidate, labeled "Correlated candidate", never "owner"),
879
- `ambiguous` (every supplied candidate, none visually promoted), or
880
- `unavailable` (zero fabricated candidates) — exactly as the artifact states,
881
- or "Static correlation not included in this context" when the `correlations`
882
- field itself is absent (never reported as `unavailable`); and the bounded
883
- reference-fidelity projection (`mismatches`/`protectedContext`/`blockedBy`)
884
- alongside — never merged into — a live, separately-triggered Batch 6
885
- on-demand fidelity evaluation for the same reference/candidate, when both
886
- are available. This mode never calls `projectBoundedAgentContext`,
887
- `deriveRuntimeStaticCorrelations`, or `attachRuntimeStaticCorrelations` —
888
- only the exact context the session was started with is ever displayed.
889
- Every other evidence family still shows the bounded metadata/raw-payload
890
- view established in Batch 2. Every route remains strictly read-only: no
891
- artifact is ever created, modified, or interpreted beyond its existing
892
- canonical reader/validator; no binding, fidelity, or bounded-context
893
- artifact is ever persisted; bounded agent context remains a programmatic-
894
- only Observer contract — this command adds no way to generate, save, or
895
- write one; and no batch in this lineage recomputes an "overall" verdict
896
- spanning contract and fidelity — they remain two independent,
897
- separately-displayed evidence dimensions.
898
-
899
- ```text
900
- my-frontend-observer view [--root <evidence-root>] [--bindings-file <json-file>] [--context-file <json-file>] [options]
901
- ```
902
-
903
- Without `--root`, `view` discovers the nearest initialized project and uses
904
- its managed evidence root plus ephemeral alias metadata. With `--root`, it
905
- uses standalone evidence-root behavior and does not require a project or
906
- catalog.
907
-
908
- Options:
909
-
910
- - `--root <path>` — local evidence-root directory the viewer session
911
- represents. Validated operationally (must exist and be a directory); this
912
- command never reads or interprets any Observer artifacts under it beyond
913
- the bounded discovery/classification the routes above describe.
914
-
915
- Options:
916
-
917
- - `--bindings-file <json-file>` — local JSON file of the form
918
- `{ "bindings": [ { "referenceRegion": "...", "runtimeTarget": "..." } ] }`
919
- — the exact same operational wrapper format, and the exact same shared
920
- parser, as `evaluate-reference-fidelity --bindings-file`. Read once at
921
- startup; unreadable/invalid-JSON/wrong-wrapper-shape fails startup
922
- clearly (no server is started). Reference-specific validity (region
923
- existence) is checked only once a reference is actually selected in the
924
- viewer, never at startup. The declarations become session-only viewer
925
- input: never persisted, never written into any Observer artifact, and the
926
- file's own path is never exposed to the browser. Omit to run with no
927
- binding declarations — the viewer remains fully usable; reference/runtime
928
- cross-selection simply stays disabled and fidelity may still be
929
- explicitly evaluated with an empty declaration collection.
930
- - `--context-file <json-file>` — local JSON file containing exactly one
931
- `BoundedAgentContextArtifact` value directly (no wrapper object). Read
932
- once at startup and validated through the existing canonical
933
- `isValidBoundedAgentContextArtifact` — never a second validator. Explicit,
934
- session-only viewer input: held only in server memory, never persisted,
935
- never written into any Observer artifact, and the file's own path is
936
- never exposed to the browser. Bounded agent context remains
937
- programmatic-only as an Observer-produced contract — this command does
938
- not add a way to generate, save, or write one; the viewer never rebuilds
939
- it (`projectBoundedAgentContext` is never called at runtime) and never
940
- derives or re-derives runtime/static correlation
941
- (`deriveRuntimeStaticCorrelations`/`attachRuntimeStaticCorrelations` are
942
- never called at runtime) — it only displays the exact context it was
943
- given. A recognized `artifactKind` with a `schemaVersion` other than the
944
- currently supported one (`1.0.0`) starts the viewer showing an honest
945
- "unsupported version" context state rather than failing. An unreadable
946
- file, invalid JSON, a non-object root, the wrong `artifactKind`, or a
947
- structurally invalid current-schema artifact fails startup clearly (no
948
- server is started). Omit to run with no bounded context supplied — the
949
- viewer remains fully usable; the "Bounded context" mode reports that none
950
- was supplied. May be freely combined with `--bindings-file`.
951
- - `--port <n>` — TCP port to bind, in `[0, 65535]`. Defaults to `4319`
952
- (chosen after checking that no fixture or test in this repository binds a
953
- fixed port — see `tests/fixtures/server.ts`, which always uses `0`/
954
- OS-assigned). An explicit alternate port is a different web origin than
955
- the default; an installed PWA is not portable across origins. If the
956
- requested port is already in use, the command fails with an actionable
957
- diagnostic — it never silently falls back to a different port.
958
- - `--no-open` — do not attempt to open the system default browser after the
959
- server starts. Auto-open is a best-effort convenience only: its failure is
960
- never fatal and never affects server startup success.
961
- - `--help` — show `view` usage.
962
-
963
- The server binds only to `127.0.0.1` (never `0.0.0.0`), serves only the
964
- built viewer application assets plus bounded `/api/*`
965
- endpoints described above, and never exposes the supplied evidence root as a
966
- generic static directory or arbitrary filesystem path. With `--root` it
967
- accepts no write methods and writes nothing. Without `--root`, the guarded
968
- project-aware authoring routes create only new immutable artifacts and workflow
969
- revisions through canonical owners; they never edit target source or rewrite
970
- existing evidence. On success,
971
- prints the viewer URL and keeps running (serving the viewer) until
972
- interrupted. On invalid syntax, a missing/non-directory `--root`, an
973
- invalid `--port`, an invalid `--bindings-file`, an invalid `--context-file`
974
- (other than a recognized-kind future `schemaVersion`, which starts
975
- normally), or a port already in use, prints structured diagnostics to
976
- stderr and exits nonzero without starting a server.
977
-
978
- ## Cross-tool compatibility handoffs
979
-
980
- The canonical command-by-command composition map is [my-dev-kit ecosystem workflow section 9.15](https://github.com/dailephd/my-dev-kit/blob/main/docs/ECOSYSTEM_DEVELOPMENT_WORKFLOWS.md#915-command-surface-compatibility-map).
981
-
982
- Current supported boundaries:
983
-
984
- - my-dev-kit file/symbol evidence can be mapped by a small **programmatic adapter** into the plain caller-supplied static-candidate records accepted by `deriveRuntimeStaticCorrelations(...)` / `attachRuntimeStaticCorrelations(...)`. Raw my-dev-kit search, lookup, slice, or context JSON is not a direct Observer CLI input.
985
- - A produced Observer `BoundedAgentContextArtifact` can be inspected directly with `view --context-file <file>`. That option accepts only the Observer bounded-context schema, not a my-dev-kit context capsule.
986
- - Orchestrator has a direct programmatic consumer for the released Observer bounded-agent-context wire contract. Orchestrator does not launch Observer.
987
- - A selected Lab tutorial screenshot PNG can be passed to `import-reference`, then explicitly approved and bound like any other external image reference. The Lab tutorial manifest and behavioral assertions are not imported.
988
- - Lab report/gallery commands do not generically consume Observer evidence roots, and Observer commands do not consume Lab security/audit/experiment reports.
989
- - `check [baseline] --json` is the preferred compact final-candidate runtime result for an external coding-agent or Orchestrator report, but the downstream consumer must preserve `PASS`, `FAIL`, `REVIEW_REQUIRED`, and `BLOCKED` rather than collapse them to process success/failure.
990
-
991
- ## Foundation commands
992
-
993
- - `npm install` — install dependencies (includes the `playwright` runtime
994
- dependency since Batch 2).
995
- - `npx playwright install chromium` — install the Chromium binary once per
996
- machine (see `docs/DEVELOPMENT.md`).
997
- - `npm run typecheck` — run TypeScript no-emit checking.
998
- - `npm run lint` — lint the repository and scripts.
999
- - `npm test` — run the fast unit suite (`tests/unit/`).
1000
- - `npm run test:browser` — run the real-Chromium integration suite
1001
- (`tests/browser/`), including a real `observe` end-to-end test against the
1002
- deterministic local fixture.
1003
- - `npm run test:security` — run only the safety-relevant subset of the suite
1004
- (`tests/unit/policy.test.ts` plus the real-Chromium enforcement cases in
1005
- `tests/browser/chromiumAdapter.test.ts`: unsafe initial target, prohibited
1006
- redirect, prohibited subresource request, and browser cleanup around
1007
- safety/navigation failure) — a discoverable entry point for security
1008
- review tooling; it is a subset of, not a replacement for, `npm test` and
1009
- `npm run test:browser`.
1010
- - `npm run build` — clean, then compile `src/` (including `src/cli.ts`) to
1011
- `dist/`, then build the viewer web app (`viewer/`) with Vite into
1012
- `dist/viewer/` (v0.8 Batch 1). Both outputs ship inside the existing
1013
- `dist` package allowlist — there is no second npm package.
1014
- - `npm run typecheck` also type-checks the browser-side viewer project
1015
- (`viewer/tsconfig.json`) in addition to `tsconfig.json`, since the viewer's
1016
- DOM/JSX-targeting TypeScript config is intentionally separate from the
1017
- Node-only `src/` compilation.
1018
- - `npm run check:docs` — validate canonical documents and roadmap structure.
1019
- - `npm pack --dry-run` — inspect the public package's tarball inventory
1020
- before publishing. The real tarball has been installed and exercised in a
1021
- clean temporary consumer directory (real Chromium install, real `observe`
1022
- run, real artifact) on Windows, Linux, and macOS as part of v0.1
1023
- validation, again for v0.2's packed semantic `--targets-file` behavior,
1024
- and again for v0.3's packed `--scroll-scenario-file` window/target scroll
1025
- behavior (`scripts/ci/runPackedObservationSmoke.mjs`); this is local
1026
- package validation, not a release/publication step.
1
+ # Commands
2
+
3
+ ## v0.10 visual workflow operations
4
+
5
+ v0.10 adds no CLI command. Project-aware `view` exposes the Visual changes
6
+ workspace for actual/reference entry, explicit activation, bounded handoff,
7
+ Run check, correction/review, acceptance, governance-result recording, and
8
+ explicit restore. These are guarded local HTTP/UI actions backed by canonical
9
+ application owners, not new command-line subcommands. Standalone `view --root`
10
+ remains inspection-only.
11
+
12
+ Every handoff names `check <baseline> --json` as the exact post-edit machine
13
+ operation. It captures a fresh candidate and runs the existing canonical
14
+ comparison, contract, and configured reference-fidelity owners.
15
+
16
+ Project-aware `check` validates the selected baseline first, then captures the
17
+ candidate from the current project URL, viewport, targets, and capture
18
+ defaults. It replays only that baseline artifact's optional `scrollScenario`
19
+ and declared `explicitState` so the candidate has the same observation
20
+ context. No new flags are required. `init` and ordinary `capture` remain
21
+ project-config-only; project config does not accept either low-level field.
22
+ Replaying `explicitState` preserves caller-declared identity metadata and does
23
+ not set up browser, application, or session state.
24
+
25
+ ## v0.8.1 common workflow
26
+
27
+ `init --url <loopback-url> [--viewport WIDTHxHEIGHT] [--target id=selector ... | --targets-file file] [--default-baseline alias] [--replace]` creates schema-`1.1.0` project configuration; schema `1.0.0` remains readable and forbids `acceptance`. Schema `1.1.0` may add exactly:
28
+
29
+ ```json
30
+ {
31
+ "acceptance": {
32
+ "comparisonConfigFile": "config/comparison.json",
33
+ "contract": {
34
+ "baselineArtifact": ".frontend-observer/evidence/contracts/baseline/<id>",
35
+ "changeArtifact": ".frontend-observer/evidence/contracts/change/<id>"
36
+ },
37
+ "reference": {
38
+ "approvedArtifact": ".frontend-observer/evidence/references/approved/<id>",
39
+ "bindingsFile": "config/reference-bindings.json"
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ Every acceptance path is portable and project-relative and is realpath-checked
46
+ before use. Both contract paths are required together. The reference must be
47
+ explicitly approved; bindings are explicit and default to an empty collection.
48
+ `comparisonConfigFile` uses the existing `compare --config-file` format.
49
+
50
+ `capture <alias> [--replace]` creates immutable canonical evidence. `current`
51
+ is reserved for `check`. `check [<baseline>] [--json]` resolves the explicit
52
+ alias or `defaultBaseline`, captures a new immutable `current`, compares it
53
+ canonically, and evaluates configured contract/reference acceptance. Its exact
54
+ exit codes are:
55
+
56
+ ```text
57
+ PASS 0
58
+ FAIL 1
59
+ REVIEW_REQUIRED 2
60
+ BLOCKED 3
61
+ ```
62
+
63
+ Comparison alone is evidence and returns `REVIEW_REQUIRED`, even with zero
64
+ differences. `--json` emits exactly one bounded schema-`1.0.0` document and
65
+ never embeds screenshots or complete artifacts. `view [--root path]` uses
66
+ project evidence and aliases when root is omitted and preserves standalone
67
+ behavior when supplied. Advanced commands below remain supported.
68
+
69
+ ## Product command surface
70
+
71
+ `node dist/cli.js observe` (or `my-frontend-observer observe` once installed
72
+ as a bin) captures one bounded, loopback-only browser observation and
73
+ persists it as a portable artifact.
74
+
75
+ ```text
76
+ my-frontend-observer observe --url <loopback-url> [options]
77
+ ```
78
+
79
+ Required:
80
+
81
+ - `--url <url>` — loopback target URL (`http`/`https`; `localhost`,
82
+ `127.x.x.x`, or `::1` only - enforced by the existing request/safety
83
+ contracts, not by CLI-local logic).
84
+
85
+ Options:
86
+
87
+ - `--viewport <WIDTHxHEIGHT>` — e.g. `1280x720`. Malformed syntax (missing
88
+ `x`, non-numeric, empty side) is rejected before any browser launches;
89
+ in-range bounds are enforced by the existing request validator.
90
+ - `--target <id=css-selector>` — an explicit CSS-shorthand observation
91
+ target. Repeatable; order is preserved. Parsed on the *first* `=` only, so
92
+ a selector containing `=` survives intact, e.g.
93
+ `--target action=button[data-state="active"]`. Cannot be combined with
94
+ `--targets-file`.
95
+ - `--targets-file <json-file>` — loads structured semantic observation
96
+ targets from a local JSON file instead of `--target`. Cannot be combined
97
+ with `--target`. See "Structured semantic targets" below.
98
+ - `--scroll-scenario-file <json-file>` — loads one bounded runtime scroll
99
+ scenario from a local JSON file. May be combined with either `--target` or
100
+ `--targets-file` (it is independent of target configuration). See "Scroll
101
+ scenario (`--scroll-scenario-file`)" below.
102
+ - `--state-file <json-file>` — loads explicit, caller-declared frontend
103
+ state identity (`theme`, `applicationState`, `authenticatedState`) from a
104
+ local JSON file. Never inferred by the observer from screenshot pixels,
105
+ CSS, DOM, or URLs - this is caller-declared metadata only, used solely for
106
+ later comparability/compatibility evaluation (see "v0.7 Prompt 4 reference
107
+ applicability and candidate-state compatibility" in `docs/CONTRACTS.md`).
108
+ Independent of every other flag.
109
+ - `--output <directory>` — portable, relative output location for the
110
+ observation artifact (same contract as the request's `outputLocation`; no
111
+ drive letter, no leading `/`, no `..` segments).
112
+ - `--timeout <ms>` — overall request timeout in milliseconds.
113
+ - `--help` — show `observe` usage.
114
+
115
+ Also available: `--help` / `-h` (top-level usage) and `--version` (prints the
116
+ actual package version).
117
+
118
+ On success the command prints exactly:
119
+
120
+ ```text
121
+ Observation: <observation-id>
122
+ State: <complete|partial|warning|fatal|invalid-request>
123
+ Artifact: <artifact-root-path>
124
+ Targets: <configured-target-count>
125
+ Diagnostics: <diagnostic-count>
126
+ ```
127
+
128
+ and exits `0` for a validly persisted observation - including one whose
129
+ `State` truthfully reports `partial` (e.g. a missing or ambiguous target)
130
+ - or exits nonzero for invalid CLI syntax, a request the existing validator
131
+ rejects, an unsafe/failed navigation with no persistable artifact, or a
132
+ failed artifact write. No progress output is printed during a normal
133
+ capture. CLI-syntax errors (e.g. a missing `--url`) print as `error:
134
+ <message>` followed by `observe` usage; request/capture/persistence
135
+ diagnostics print one per line as `[code] message`.
136
+
137
+ ### Structured semantic targets (`--targets-file`)
138
+
139
+ **Current status: shipped as part of the published `my-frontend-observer@0.3.0`
140
+ package.** `--target` (CSS shorthand) remains fully supported alongside it.
141
+
142
+ `--targets-file <json-file>` is the public entry point to the v0.2 canonical
143
+ target/locator model established in `src/request/request.ts`. It supplies
144
+ the same `targets` collection that `--target` supplies, just in structured
145
+ form; both converge on the same `normalizeRequest()` validation and the same
146
+ downstream browser resolver - there is no separate semantic observation path.
147
+
148
+ File format (the exact, first frozen structure - the root object supports
149
+ only the `targets` field; any other top-level field is rejected):
150
+
151
+ ```json
152
+ {
153
+ "targets": [
154
+ {
155
+ "name": "primary-navigation",
156
+ "locators": [
157
+ { "kind": "role", "role": "navigation", "name": "Primary" },
158
+ { "kind": "id", "value": "nav" }
159
+ ]
160
+ },
161
+ {
162
+ "name": "workspace",
163
+ "locators": [
164
+ { "kind": "data-attribute", "attribute": "data-region", "value": "workspace" }
165
+ ]
166
+ }
167
+ ]
168
+ }
169
+ ```
170
+
171
+ Each target has a stable `name` and an ordered `locators` array (1-5
172
+ entries; order is the fallback order - the first locator that resolves
173
+ uniquely wins, an ambiguous or unevaluable locator stops immediately without
174
+ trying the next one). Each locator is one of the six frozen kinds:
175
+
176
+ - `{ "kind": "role", "role": "<string>", "name"?: "<string>" }`
177
+ - `{ "kind": "id", "value": "<string>" }`
178
+ - `{ "kind": "data-attribute", "attribute": "data-*", "value": "<string>" }`
179
+ - `{ "kind": "semantic-element", "tag": "<one of the frozen structural tags>" }`
180
+ - `{ "kind": "css", "selector": "<string>" }`
181
+ - `{ "kind": "text", "text": "<exact string>" }`
182
+
183
+ `--targets-file` itself only validates that the file is readable, is valid
184
+ JSON, and has an object root containing exactly a `targets` field - every
185
+ target/locator-internal rule (bounds, per-kind required fields, supported
186
+ values) is enforced by the same `normalizeRequest()` validator `--target`
187
+ already goes through, so both input modes produce identical diagnostics for
188
+ equivalent mistakes.
189
+
190
+ The path may be relative (resolved from the current working directory) or
191
+ absolute; it is operational input only - it never affects the observation's
192
+ request identity and is never written into `manifest.json`.
193
+
194
+ Example:
195
+
196
+ ```powershell
197
+ my-frontend-observer observe `
198
+ --url http://localhost:3000/ `
199
+ --viewport 1280x720 `
200
+ --targets-file .\targets.json `
201
+ --output observations
202
+ ```
203
+
204
+ ### Scroll scenario (`--scroll-scenario-file`)
205
+
206
+ **Current status: shipped as part of the published `my-frontend-observer@0.3.0`
207
+ package.** Observation schema is `1.2.0`.
208
+
209
+ `--scroll-scenario-file <json-file>` is the public entry point to the v0.3
210
+ runtime scroll-scenario contract established in `src/request/request.ts`
211
+ (`ScrollScenario`/`ScrollAction`) and executed in `src/browser/`. It supplies
212
+ exactly the value of the normalized request's `scrollScenario` field - the
213
+ file root *is* the scenario object itself, with no wrapper field (unlike
214
+ `--targets-file`'s `{ "targets": [...] }` root).
215
+
216
+ A request supports **zero or one** scroll scenario. There are exactly two
217
+ supported action kinds:
218
+
219
+ Window scrolling:
220
+
221
+ ```json
222
+ {
223
+ "action": {
224
+ "kind": "window-scroll-by",
225
+ "deltaX": 0,
226
+ "deltaY": 600
227
+ }
228
+ }
229
+ ```
230
+
231
+ Target scrolling (the `target` value must be the stable `name` of one of the
232
+ observation's own configured targets - never a CSS selector, DOM id, or
233
+ source symbol):
234
+
235
+ ```json
236
+ {
237
+ "action": {
238
+ "kind": "target-scroll-by",
239
+ "target": "tool-workspace",
240
+ "deltaX": 0,
241
+ "deltaY": 400
242
+ }
243
+ }
244
+ ```
245
+
246
+ `deltaX`/`deltaY` are signed integers bounded to `[-20000, 20000]`; at least
247
+ one must be non-zero (both zero is rejected). Every scroll/action rule -
248
+ supported action kind, required fields, delta types/bounds, the both-zero
249
+ rule, and the stable-target-name reference for `target-scroll-by` - is
250
+ enforced by the same `normalizeRequest()` validator used everywhere else, not
251
+ duplicated in CLI code; `--scroll-scenario-file` itself only validates that
252
+ the file is readable, is valid JSON, and has a non-array object root.
253
+
254
+ The observer performs the requested scroll immediately (no smooth-scroll
255
+ animation), waits exactly two `requestAnimationFrame` cycles, and captures a
256
+ final runtime snapshot - the same final state that the observation's ordinary
257
+ `pageEvidence`, `targetEvidence`, and `screenshot.png` describe. The actual
258
+ resulting scroll position is browser-authoritative and may be clamped by
259
+ document/element boundaries; a scenario that produces no movement (already at
260
+ a boundary, or a non-scrollable target) is still a valid, successfully
261
+ persisted observation, never a fabricated failure.
262
+
263
+ Usable with either target input mode:
264
+
265
+ ```powershell
266
+ my-frontend-observer observe `
267
+ --url http://localhost:3000/ `
268
+ --target workspace=.workspace `
269
+ --scroll-scenario-file .\scroll.json `
270
+ --output observations
271
+ ```
272
+
273
+ ```powershell
274
+ my-frontend-observer observe `
275
+ --url http://localhost:3000/ `
276
+ --targets-file .\targets.json `
277
+ --scroll-scenario-file .\scroll.json `
278
+ --output observations
279
+ ```
280
+
281
+ `--target` and `--targets-file` remain mutually exclusive with each other,
282
+ exactly as before; `--scroll-scenario-file` is independent of both and is
283
+ never itself a third mutually-exclusive target mode. `window-scroll-by`
284
+ requires no configured target at all.
285
+
286
+ The path may be relative (resolved from the current working directory) or
287
+ absolute; it is operational input only - like `--targets-file`'s path, it
288
+ never affects the observation's request identity and is never written into
289
+ `manifest.json`. Two different scenario files with identical content produce
290
+ the same `requestId`; only the requested scenario *configuration*
291
+ participates in identity, never the runtime outcome (actual scroll
292
+ distance, clamping, or scroll-owner result).
293
+
294
+ If a `target-scroll-by` scenario's configured action target cannot be
295
+ uniquely resolved at runtime (missing, ambiguous, or otherwise unavailable),
296
+ the scroll is not performed, no movement is fabricated, and the observation
297
+ persists honestly - typically as `partial` - carrying the same
298
+ `target-missing`/`target-ambiguous`/`browser-evidence-unavailable` diagnostic
299
+ that any other unresolved configured target would produce.
300
+
301
+ ## `compare`
302
+
303
+ **Current status: shipped as part of the published `my-frontend-observer@0.4.0`
304
+ package.** Comparison schema is `1.0.0`, independent of and never reused for
305
+ the observation schema (`1.2.0`).
306
+
307
+ `my-frontend-observer compare` (or `node dist/cli.js compare` from a source
308
+ checkout) reads two already-persisted observation artifacts and derives
309
+ before/after evidence purely from their existing content:
310
+
311
+ ```text
312
+ my-frontend-observer compare --before <observation-artifact-root> --after <observation-artifact-root> --output <directory> [options]
313
+ ```
314
+
315
+ Required:
316
+
317
+ - `--before <path>` — root directory of the "before" persisted observation
318
+ artifact (the directory containing its `manifest.json`, as produced by
319
+ `observe`).
320
+ - `--after <path>` — root directory of the "after" persisted observation
321
+ artifact.
322
+ - `--output <directory>` — portable, relative output location for the
323
+ comparison artifact (same contract as `observe --output`).
324
+
325
+ Options:
326
+
327
+ - `--config-file <json-file>` — loads a comparison configuration directly
328
+ (no wrapper field): `{ "geometryTolerancePx": <0-10>,
329
+ "expectedDependencies": [...] }`. Without it, `geometryTolerancePx`
330
+ defaults to `0.5` CSS px with no declared dependencies. As with
331
+ `--targets-file`/`--scroll-scenario-file`, `--config-file` only validates
332
+ file readability, JSON validity, and a non-array object root; every
333
+ semantic rule (tolerance bounds, dependency property/direction
334
+ vocabulary, dependency source marker) is enforced by the same domain
335
+ validator the comparison engine itself uses.
336
+ - `--help` — show `compare` usage.
337
+
338
+ **Comparison never launches a browser.** It reads two manifests through the
339
+ existing observation-artifact reader, runs the pure comparison engine, and
340
+ persists a portable `manifest.json` — no navigation, no target
341
+ re-resolution, no Chromium process.
342
+
343
+ On success the command prints exactly:
344
+
345
+ ```text
346
+ Comparison: <comparison-id>
347
+ State: <comparable|comparable-with-warnings|incomparable>
348
+ Artifact: <comparison-artifact-root>
349
+ Differences: <count>
350
+ Relationship changes: <count>
351
+ Diagnostics: <count>
352
+ ```
353
+
354
+ and exits `0` — **including when `State` is `incomparable`**: comparison
355
+ determining that two observations should not be treated as equivalent
356
+ frontend states is itself a successful outcome, not a failure. The command
357
+ exits nonzero only for invalid CLI syntax, an unreadable/malformed/
358
+ structurally-invalid source artifact, invalid comparison configuration, or
359
+ a failed artifact write.
360
+
361
+ ### Comparability
362
+
363
+ Before any rendered difference is calculated, the engine evaluates whether
364
+ the two observations are comparable at all:
365
+
366
+ - **Hard incompatibilities** (force `incomparable`): different logical page
367
+ URL, different viewport, different browser engine, or a mismatched scroll
368
+ scenario configuration (no scenario vs. a scenario, or two different
369
+ scenario configurations).
370
+ - **Warnings** (still `comparable-with-warnings`, comparison proceeds):
371
+ different producer package version, different browser version, or a
372
+ changed/added/removed configured target.
373
+ - **Unassessed dimensions** the observer does not yet model (theme,
374
+ authenticated state, application state) are always recorded, never
375
+ silently claimed identical.
376
+
377
+ An `incomparable` result still persists a structurally valid
378
+ `ComparisonArtifact`: the comparability reasons are recorded, and ordinary
379
+ rendered differences/relationship changes stay empty rather than fabricated.
380
+
381
+ ### Difference and relationship evidence
382
+
383
+ For a `comparable`/`comparable-with-warnings` result, the manifest's
384
+ `differences` and `relationshipChanges` arrays carry structured before/
385
+ after evidence: appeared/disappeared targets (only for a stable target name
386
+ configured on both sides — a target added/removed from configuration is
387
+ recorded separately as a `configurationChanges` entry, never fabricated as
388
+ appeared/disappeared), moved/resized targets, visibility changes, clipping
389
+ changes, actual dimensional overflow changes, DOM containment changes,
390
+ page-size changes, scroll-owner changes, and layout-relationship
391
+ transitions (e.g. `does-not-overlap` → `overlaps`, or
392
+ `document-width-fits-viewport` → `document-width-exceeds-viewport`) reused
393
+ verbatim from the same canonical relationship engine `observe` output feeds
394
+ Batch 2's `deriveLayoutRelationships`.
395
+
396
+ ### Explicit dependency evidence (non-causal)
397
+
398
+ `--config-file`'s `expectedDependencies` lets you declare an expected
399
+ layout relationship such as "`navigation.width` decreases →
400
+ `workspace.width` increases" using only the frozen `x`/`y`/`width`/`height`
401
+ property vocabulary and `increase`/`decrease`/`change`/`unchanged`
402
+ direction vocabulary. Each declaration is evaluated independently against
403
+ the two observations and persists exactly one outcome: `consistent`,
404
+ `not-observed`, `contradictory-to-declaration`, or `unavailable`. **The
405
+ observer never infers a dependency from co-change, and never emits a
406
+ causal claim, a PASS/FAIL verdict, or a change-contract decision** — v0.4
407
+ produces comparison evidence; whether that evidence satisfies some
408
+ contract is v0.5+ scope.
409
+
410
+ ### Path privacy
411
+
412
+ `--before`, `--after`, `--config-file`, and `--output` are operational
413
+ filesystem input only. None of them affect `comparisonRequestId`, and none
414
+ of them are written into the persisted manifest — the manifest instead
415
+ retains logical source references (`observationId`, `requestId`,
416
+ `producer`, `observationSchemaVersion`, and the source `screenshot.path`).
417
+ Two semantically identical observation/config pairs read from different
418
+ filesystem locations produce the same `comparisonRequestId`; each execution
419
+ still gets a fresh `comparisonId`.
420
+
421
+ ### Source observations remain immutable
422
+
423
+ Comparison is read-only with respect to its inputs: it never modifies
424
+ either source observation's `manifest.json` or `screenshot.png`, and it
425
+ never copies screenshot bytes into the comparison directory — the
426
+ comparison artifact directory contains `manifest.json` only.
427
+
428
+ Example:
429
+
430
+ ```powershell
431
+ node dist/cli.js compare `
432
+ --before observations/<before-id> `
433
+ --after observations/<after-id> `
434
+ --output comparisons `
435
+ --config-file .\comparison-config.json
436
+ ```
437
+
438
+ ## `approve-baseline`
439
+
440
+ **Current status: shipped as part of the published `my-frontend-observer@0.5.0`
441
+ package.** Frontend contract schema is `1.0.0`, independent of the
442
+ observation (`1.2.0`) and comparison (`1.0.0`) schemas.
443
+
444
+ `my-frontend-observer approve-baseline` is the *only* baseline-approval
445
+ operation in the observer — approval is never inferred from a successful
446
+ comparison or evaluation, and no command automatically supersedes or selects
447
+ a baseline. It explicitly approves and persists one already-authored
448
+ `PersistentBaselineContract` against the observation it claims to approve:
449
+
450
+ ```text
451
+ my-frontend-observer approve-baseline --observation <observation-artifact-root> --contract-file <json-file> --output <directory>
452
+ ```
453
+
454
+ Required:
455
+
456
+ - `--observation <path>` — root directory of the persisted observation
457
+ artifact this baseline claims to approve (read through the existing
458
+ observation-artifact reader).
459
+ - `--contract-file <json-file>` — local JSON file containing one raw
460
+ `PersistentBaselineContract` (no wrapper field). As with `--config-file`,
461
+ only file readability/JSON-validity/non-array-object-root is checked here;
462
+ every structural rule (artifact kind, schema version, clause shape) is
463
+ enforced by the existing frozen domain validator.
464
+ - `--output <directory>` — portable, relative output location for the
465
+ baseline artifact.
466
+
467
+ Before persisting, the application layer verifies the contract's frozen
468
+ `sourceObservation` reference (`observationId`, `requestId`, `producer`,
469
+ `observationSchemaVersion`) actually matches the supplied observation
470
+ artifact's stable identity — approving a baseline against an unrelated
471
+ observation is rejected, even if both artifacts are individually valid. Any
472
+ `supersedesBaselineId` already authored in the contract is preserved exactly
473
+ as supplied; this command never discovers a prior baseline, infers
474
+ supersession, or deletes anything.
475
+
476
+ On success the command prints exactly:
477
+
478
+ ```text
479
+ Baseline: <baseline-id>
480
+ State: approved
481
+ Artifact: <baseline-artifact-root>
482
+ Clauses: <count>
483
+ Supersedes: <baseline-id|none>
484
+ ```
485
+
486
+ and exits `0`. It exits nonzero for invalid CLI syntax, an unreadable/
487
+ malformed/structurally-invalid contract file, a `PerChangeContract` passed
488
+ where a baseline is expected, a source-observation mismatch, an existing
489
+ artifact collision (baseline identities are never overwritten), or a failed
490
+ artifact write.
491
+
492
+ ## `save-change-contract`
493
+
494
+ **Current status: shipped as part of the published `my-frontend-observer@0.5.0`
495
+ package.**
496
+
497
+ `my-frontend-observer save-change-contract` validates and persists one
498
+ already-authored `PerChangeContract` so it can later be evaluated — this is
499
+ persistence only, never approval:
500
+
501
+ ```text
502
+ my-frontend-observer save-change-contract --contract-file <json-file> --output <directory>
503
+ ```
504
+
505
+ Required:
506
+
507
+ - `--contract-file <json-file>` — local JSON file containing one raw
508
+ `PerChangeContract` (no wrapper field).
509
+ - `--output <directory>` — portable, relative output location for the
510
+ change-contract artifact.
511
+
512
+ Domain/application validation rejects a `PersistentBaselineContract` passed
513
+ here, malformed clauses, an unsupported authored category, an authored
514
+ `category: "unexpected"` (the derived-only fifth classification can never be
515
+ authored as a permission), and invalid tolerance/mode fields — none of this
516
+ is duplicated in CLI code. Any `supersedesBaselineClauseIds` already
517
+ authored on a clause is preserved exactly; resolving those references
518
+ against a particular baseline remains `evaluate-contract`'s responsibility.
519
+
520
+ On success the command prints exactly:
521
+
522
+ ```text
523
+ Change contract: <contract-id>
524
+ Artifact: <contract-artifact-root>
525
+ Clauses: <count>
526
+ Supersedes baseline clauses: <count>
527
+ ```
528
+
529
+ and exits `0`. It exits nonzero for invalid CLI syntax, an unreadable/
530
+ malformed/structurally-invalid contract file, a persistent baseline contract
531
+ passed here, an existing artifact collision, or a failed artifact write.
532
+
533
+ ## `evaluate-contract`
534
+
535
+ **Current status: shipped as part of the published `my-frontend-observer@0.5.0`
536
+ package.** Frontend contract evaluation artifact schema is `1.0.0`, its own
537
+ independent family.
538
+
539
+ `my-frontend-observer evaluate-contract` executes the canonical Batch 2
540
+ evaluator against already-persisted evidence/contracts and persists the
541
+ result:
542
+
543
+ ```text
544
+ my-frontend-observer evaluate-contract --before <observation-artifact-root> --after <observation-artifact-root> --comparison <comparison-artifact-root> --baseline <baseline-contract-artifact-root> --change <per-change-contract-artifact-root> --output <directory> [--enforce]
545
+ ```
546
+
547
+ Required:
548
+
549
+ - `--before <path>` / `--after <path>` — root directories of the persisted
550
+ before/after observation artifacts.
551
+ - `--comparison <path>` — root directory of the already-persisted comparison
552
+ artifact for that before/after pair.
553
+ - `--baseline <path>` — root directory of the already-approved baseline
554
+ contract artifact.
555
+ - `--change <path>` — root directory of the already-persisted per-change
556
+ contract artifact.
557
+ - `--output <directory>` — portable, relative output location for the
558
+ evaluation artifact.
559
+
560
+ Options:
561
+
562
+ - `--enforce` — makes a `FAIL` verdict produce a nonzero process exit
563
+ status. A `FAIL` evaluation is always persisted and printed identically
564
+ with or without this flag; `--enforce` changes only the process exit code
565
+ — never evaluation identity, contents, or persistence.
566
+
567
+ **`evaluate-contract` never launches a browser, never re-resolves targets,
568
+ and never recomputes comparison or relationship evidence** — it reads the
569
+ already-persisted before/after observations and comparison exactly as given
570
+ (through the existing observation-artifact reader and a new comparison
571
+ reader) and calls the canonical `evaluateFrontendContract` exactly once.
572
+
573
+ A `FAIL` verdict (a found regression or unsatisfied contract clause) is a
574
+ successful, persisted evaluation outcome — not an execution error. On
575
+ success (evaluation constructed and persisted, verdict `PASS`, or verdict
576
+ `FAIL` without `--enforce`), the command prints exactly:
577
+
578
+ ```text
579
+ Evaluation: <evaluation-id>
580
+ Verdict: <PASS|FAIL>
581
+ Artifact: <evaluation-artifact-root>
582
+ Clauses: <total-clause-result-count>
583
+ Unexpected: <unexpected-change-count>
584
+ Enforced: <yes|no>
585
+ ```
586
+
587
+ and exits `0`; with `--enforce` and verdict `FAIL`, it prints the same
588
+ result and exits nonzero. It exits nonzero and persists nothing for invalid
589
+ CLI syntax or an unreadable/malformed/incoherent source artifact (evaluation
590
+ could not even be constructed) — distinct from a legitimate persisted `FAIL`.
591
+
592
+ The evaluation artifact directory contains `manifest.json` only — no
593
+ screenshot is copied. Operational `--before`/`--after`/`--comparison`/
594
+ `--baseline`/`--change`/`--output` paths never enter the persisted
595
+ `evaluationRequestId` or any other semantic field; two semantically
596
+ identical evaluations invoked from different filesystem locations share the
597
+ same `evaluationRequestId` even though each execution gets a fresh
598
+ `evaluationId`.
599
+
600
+ ## `evaluate-reference-fidelity`
601
+
602
+ **Current status: shipped as part of the published `my-frontend-observer@0.7.0`
603
+ package.** No new artifact
604
+ family or schema version — this command persists nothing.
605
+
606
+ `my-frontend-observer evaluate-reference-fidelity` evaluates whether an
607
+ already-persisted candidate observation satisfies an external reference's
608
+ selected design requirements, gated by reference adequacy (v0.7 Prompt 3),
609
+ reference/candidate compatibility (v0.7 Prompt 4), and explicit
610
+ region-to-target bindings (v0.7 Prompt 5):
611
+
612
+ ```text
613
+ my-frontend-observer evaluate-reference-fidelity --reference <external-reference-artifact-root> --candidate <observation-artifact-root> [--bindings-file <json-file>] [--enforce]
614
+ ```
615
+
616
+ Required:
617
+
618
+ - `--reference <path>` — root directory of an already-imported or
619
+ already-approved external-reference artifact.
620
+ - `--candidate <path>` — root directory of the already-persisted candidate
621
+ observation artifact to evaluate against it.
622
+
623
+ Options:
624
+
625
+ - `--bindings-file <json-file>` — local JSON file of the form
626
+ `{ "bindings": [ { "referenceRegion": "...", "runtimeTarget": "..." } ] }`
627
+ declaring which stable observer runtime target (a configured target name)
628
+ explicitly corresponds to each reference region a selected requirement
629
+ depends on. Never inferred from geometry, matching names, or source code.
630
+ Optional — omitting it evaluates with no bindings at all, so every
631
+ requirement whose subject depends on a reference region becomes
632
+ `unavailable`.
633
+ - `--enforce` — makes a `fail` fidelity state produce a nonzero process
634
+ exit status. A `fail` result is always printed identically with or
635
+ without this flag; `--enforce` changes only the process exit code, never
636
+ the result's content, and has no effect on a `not-evaluated` result.
637
+
638
+ **This command never launches a browser, never re-resolves targets, and
639
+ never recomputes reference regions/requirements/adequacy, compatibility, or
640
+ bindings** — it reads the already-persisted reference and candidate exactly
641
+ as given and evaluates every selected requirement exactly once via
642
+ `evaluateReferenceCandidateFidelity`.
643
+
644
+ A `not-evaluated` result (reference adequacy inadequate, or reference/
645
+ candidate incompatible) and a `fail` result (a found design mismatch) are
646
+ both successful, structured evaluation outcomes — not execution errors. On
647
+ success, the command prints exactly:
648
+
649
+ ```text
650
+ Reference: <referenceId>
651
+ Candidate: <candidateObservationId>
652
+ Adequacy: <adequate|partial|inadequate>
653
+ Compatibility: <comparable|comparable-with-warnings|incomparable>
654
+ State: <not-evaluated|pass|fail>
655
+ Blocked by: <reference-inadequate|incompatible>
656
+ Requirements: <count> (pass: <n>, fail: <n>, unavailable: <n>)
657
+ Enforced: <yes|no>
658
+ ```
659
+
660
+ (`Compatibility`/`Blocked by` are printed only when computed/applicable)
661
+ and exits `0`, unless `--enforce` is given and the state is `fail`, in
662
+ which case it exits nonzero. It exits nonzero and persists nothing for
663
+ invalid CLI syntax, an unreadable/malformed `--reference`/`--candidate`
664
+ target, a malformed `--bindings-file`, or an invalid/out-of-bound binding
665
+ declaration.
666
+
667
+ ## `import-reference`
668
+
669
+ **Current status: shipped as part of the published `my-frontend-observer@0.7.0`
670
+ package.** Persists a new `ExternalReferenceArtifact` in the
671
+ `imported` lifecycle state (external-reference schema `1.0.0`).
672
+
673
+ ```text
674
+ my-frontend-observer import-reference <image-file> --output <directory> [options]
675
+ ```
676
+
677
+ Required:
678
+
679
+ - `<image-file>` — local path to a PNG, JPEG, or WebP external
680
+ design-reference image.
681
+ - `--output <directory>` — portable, relative output location for the
682
+ external-reference artifact.
683
+
684
+ Options:
685
+
686
+ - `--label <text>` — optional human-readable label, stored as pure
687
+ provenance — never part of the reference's logical identity.
688
+ - `--supersedes <path>` — root directory of a prior external-reference
689
+ artifact (imported or approved) that this import explicitly supersedes.
690
+ The prior artifact is never modified.
691
+ - `--regions-file <json-file>` — local JSON file of the form
692
+ `{ "regions": [...] }` declaring explicit, meaningful reference-image
693
+ regions (id plus a `{x, y, width, height}` rectangle in reference-image
694
+ pixels, origin at the image's top-left corner). Optional — a reference
695
+ imported without this flag behaves exactly as in v0.7 Prompt 1. Region
696
+ content participates in the reference's logical identity; the file path
697
+ itself never does.
698
+ - `--requirements-file <json-file>` — local JSON file of the form
699
+ `{ "requirements": [...] }` declaring explicit, user-selected design
700
+ requirements over the regions above — what actually matters for later
701
+ candidate evaluation, never inferred merely because a region
702
+ property/relationship exists. Each requirement has a `category`
703
+ (`requested` | `expected-dependent` | `protected` | `preserved` —
704
+ `unexpected` is never authorable), a `subject` (a region property, a
705
+ region-to-region relationship, or a derived two-region measurement), and
706
+ — for property/measurement subjects — a `tolerance` (`exact` |
707
+ `absolute-reference-px` | `percent`; relationship subjects must omit
708
+ tolerance). Requires `--regions-file` (or an already-present region set)
709
+ supplying every region a requirement refers to. Optional — a reference
710
+ imported without this flag behaves exactly as in v0.7 Prompt 1/2.
711
+ Requirement content participates in the reference's logical identity.
712
+ - `--applicability-file <json-file>` — local JSON file declaring the
713
+ runtime frontend state this reference is intended to represent:
714
+ `{ "viewport": { "width", "height" }, "theme": "...", "applicationState":
715
+ "...", "authenticatedState": "authenticated"|"unauthenticated" }` (each
716
+ field independently optional; at least one required). `viewport` here is
717
+ the CSS-pixel runtime viewport the design represents — distinct from the
718
+ reference image's own pixel dimensions, which are never assumed equal.
719
+ Never inferred from the image — caller-declared metadata only, used for
720
+ later reference/candidate compatibility evaluation (see
721
+ [CONTRACTS.md](CONTRACTS.md) "v0.7 Prompt 4"). Optional — a reference
722
+ imported without this flag behaves exactly as in v0.7 Prompt 1/2/3.
723
+ Applicability content participates in the reference's logical identity.
724
+
725
+ Detects the image format from its header bytes only (never from the file
726
+ extension), reads its pixel dimensions from the same bounded header bytes
727
+ (never decoding pixel data), and persists a new external-reference artifact
728
+ in the `imported` lifecycle state — importing never approves it. On
729
+ success, prints a concise result (including the accepted region/requirement
730
+ counts, the resulting reference-side requirement adequacy — `adequate`,
731
+ `partial`, or `inadequate` — and whether applicability was declared) and
732
+ exits `0`. On an unreadable file, an unsupported or undetectable format,
733
+ invalid/out-of-bound dimensions, an over-limit file size, an unresolvable
734
+ `--supersedes` target, an invalid region, an invalid requirement, or invalid
735
+ applicability, prints structured diagnostics to stderr and exits nonzero.
736
+
737
+ ## `approve-reference`
738
+
739
+ **Current status: shipped as part of the published `my-frontend-observer@0.7.0`
740
+ package.** Persists a new
741
+ `ExternalReferenceArtifact` in the `approved` lifecycle state
742
+ (external-reference schema `1.0.0`).
743
+
744
+ ```text
745
+ my-frontend-observer approve-reference --reference <external-reference-artifact-root> --output <directory> [options]
746
+ ```
747
+
748
+ Required:
749
+
750
+ - `--reference <path>` — root directory of the already-imported
751
+ external-reference artifact (the directory containing its
752
+ `manifest.json`) to approve.
753
+ - `--output <directory>` — portable, relative output location for the
754
+ newly persisted approved artifact.
755
+
756
+ Options:
757
+
758
+ - `--supersedes <path>` — root directory of a prior external-reference
759
+ artifact (imported or approved) that this approval explicitly
760
+ supersedes. The prior artifact is never modified.
761
+
762
+ This is the only explicit reference-approval act in the observer — approval
763
+ is never inferred from a successful import or from any later fidelity
764
+ evaluation. Approving persists a brand-new artifact instance (a fresh
765
+ `referenceId` sharing the imported artifact's `referenceRequestId`) that
766
+ carries a reference back to the imported artifact's image rather than a
767
+ second copy of its bytes; the imported artifact's own manifest is never
768
+ modified. Any regions, requirements, and applicability already declared on
769
+ the imported artifact are carried forward unchanged (not re-validated
770
+ against new input, not re-derived) — approval never adds, removes, or edits
771
+ regions, requirements, or applicability. Only a reference currently in the
772
+ `imported` lifecycle state can be approved. On success, prints a concise
773
+ result (including the carried-forward region/requirement counts,
774
+ reference-side requirement adequacy, and whether applicability was
775
+ declared) and exits `0`. On an unreadable/malformed `--reference` target, a
776
+ target that is not in the `imported` state, an unresolvable `--supersedes`
777
+ target, or a persistence failure, prints structured diagnostics to stderr
778
+ and exits nonzero.
779
+
780
+ ## `view`
781
+
782
+ **v0.9 visual annotation (released in `0.9.0`).**
783
+ There is no separate annotation command. `view` is the annotation entry point:
784
+
785
+ - `my-frontend-observer view` (inside an initialized project, without
786
+ `--root`) is the normal project-aware viewer. It enables local annotation
787
+ authoring for that project. In the browser you can draw on observations and
788
+ external references, explicitly associate and confirm structured intent,
789
+ save immutable annotations, promote selected confirmed runtime intent into a
790
+ change contract, and materialize selected confirmed reference intent into a
791
+ new imported reference revision. New artifacts are written only under the
792
+ project's managed evidence root (`.frontend-observer/evidence`). Nothing is
793
+ approved automatically.
794
+ - `my-frontend-observer view --root <root>` is the advanced standalone form
795
+ for arbitrary evidence roots. It is always read-only. Saved annotations can
796
+ be inspected but not edited, and every authoring request is refused.
797
+
798
+ In a project-aware session the viewer protocol is `1.3.0` and the API adds
799
+ `GET /api/authoring/session`, `GET /api/annotations/<handle>/view`, the
800
+ `annotation-overlay` media role, and exactly three authoring routes:
801
+ `POST /api/annotations`, `POST /api/annotations/<handle>/promote-contract`,
802
+ and `POST /api/annotations/<handle>/materialize-reference`. See
803
+ `docs/SECURITY.md` for the local write boundary and `docs/WORKFLOWS.md` for
804
+ the annotation workflow. The rest of this section describes the inspection
805
+ surface, which is unchanged.
806
+
807
+ **Current status: viewer behavior is released as package
808
+ `@dailephd/my-frontend-observer@0.10.1`.** Starts one
809
+ loopback-only Node viewer server and serves the same React + TypeScript +
810
+ Vite application to a normal browser or an installed Progressive Web App.
811
+ `--root` is used as a bounded, read-only evidence-discovery root: the server
812
+ exposes a metadata-first `GET /api/index` of recognized Observer evidence
813
+ beneath it, an on-demand `GET /api/artifacts/<handle>` for one selected
814
+ supported artifact, an on-demand `GET /api/media/<handle>/<role>` for its
815
+ owned/referenced media, an on-demand `GET /api/observations/<handle>/relationships`
816
+ (existing canonical `deriveLayoutRelationships(...)`), `GET /api/comparisons/<handle>/view`
817
+ and `GET /api/evaluations/<handle>/view` (exact-identity linked-evidence
818
+ resolution), `GET /api/references/<handle>/view` (region-relationship graph
819
+ and requirement adequacy, plus — new this batch — `coordinateMapping`, the
820
+ exact result of the existing canonical `deriveCoordinateScale(reference)`,
821
+ used only to gate view-lock eligibility), and
822
+ `GET /api/references/<handle>/candidate/<handle>/view` (page/state-level
823
+ compatibility plus optional matching-evaluation handles). New this batch:
824
+ `GET /api/references/<handle>/candidate/<handle>/bindings` validates the
825
+ session's explicit binding declarations against the selected reference and
826
+ calls the existing canonical `evaluateReferenceRuntimeBindings` exactly
827
+ once, and `GET /api/references/<handle>/candidate/<handle>/fidelity` is the
828
+ explicit on-demand trigger that calls the existing canonical
829
+ `evaluateReferenceCandidateFidelity` exactly once — never a second copy of a
830
+ linked artifact's own payload, never a recomputed
831
+ `compareObservations`/`evaluateFrontendContract`/
832
+ `deriveReferenceRegionRelationships`/`deriveReferenceRequirementAdequacy`/
833
+ `evaluateReferenceCandidateCompatibility` result, and both new routes are
834
+ plain `GET` (deterministic, ephemeral, never persisted). New this batch:
835
+ `GET /api/context` returns the viewer session's bounded-agent-context state
836
+ established at startup by an optional `--context-file` (below) — `none` (no
837
+ file supplied), `unsupported-version` (a recognized `artifactKind` with a
838
+ `schemaVersion` this viewer does not currently support — shown honestly,
839
+ never coerced), or the validated current context plus `sourceResolution`,
840
+ the exact-identity resolution of its `sources` against the current evidence
841
+ root (reusing/extending the Batch 4 `linkedEvidence.ts` resolver pattern) —
842
+ see `docs/ARCHITECTURE.md` "v0.8 Batch 2" through "v0.8 Batch 7" for the
843
+ exact discovery bounds, classification model, coordinate mapping, and
844
+ handle/media/linked-evidence-resolution contracts.
845
+
846
+ Selecting a supported `observation` record shows the Batch 3 screenshot/SVG
847
+ workspace. Selecting a supported `comparison` record shows the Batch 4
848
+ before/after side-by-side workspace. Selecting a supported
849
+ `contract-evaluation` record shows the Batch 4 clause-result/overall-verdict
850
+ workspace. Selecting a supported `external-reference-imported`/
851
+ `external-reference-approved` record shows the reference image with region
852
+ overlays in the reference image's own pixel coordinate domain, selected
853
+ requirements/tolerances/adequacy/applicability/lifecycle/provenance/
854
+ supersession, and, once a candidate observation is **explicitly** selected
855
+ (never auto-selected), that candidate side by side using the reused Batch
856
+ 3/4 runtime screenshot/SVG machinery plus the real canonical compatibility
857
+ result. New this batch: both panes support independent, bounded (`1x`–`8x`)
858
+ zoom and pointer-drag pan (Fit/Reset controls included) that never rewrites
859
+ any evidence coordinate — only when explicit binding declarations were
860
+ supplied (`--bindings-file`, below) and the selected reference/candidate
861
+ resolve a real canonical `bound` result does selecting a reference region
862
+ cross-highlight its exact declared runtime target (and selecting a runtime
863
+ target cross-highlight every region that names it) — `ambiguous`/
864
+ `unavailable` results and undeclared regions/targets never cross-select,
865
+ even when their names happen to match. A "Lock view" control synchronizes
866
+ both panes' zoom/pan in source-space (via the exact `coordinateMapping`
867
+ scale factor) but is enabled only when a candidate is selected,
868
+ compatibility is not `incomparable`, and `coordinateMapping.ok` is `true` —
869
+ otherwise it stays disabled with an actionable reason, and any change to
870
+ that eligibility (including switching reference/candidate) turns it off
871
+ immediately. An explicit "Evaluate Fidelity" action calls the fidelity
872
+ endpoint on demand (never automatically) and displays the canonical
873
+ `not-evaluated`/`pass`/`fail` state, `blockedBy`, and every requirement
874
+ result's status/numeric-or-relationship fields with correct unit labels
875
+ (reference-image pixels vs. raw candidate CSS pixels) exactly as returned —
876
+ alongside, never merged into, any selected existing contract-evaluation's
877
+ own `overallVerdict`. A dedicated "Bounded context" mode (toggled from the
878
+ viewer header, alongside the normal "Evidence" mode) shows: context
879
+ identity/profile/adequacy/reason codes; every bounded runtime target's
880
+ included fields (absent fields read "not included in this bounded context",
881
+ never a fabricated falsy value); source references with their exact
882
+ resolution status and, for each exactly-resolved source, one-click
883
+ navigation back to its existing Batch 3/4/5 viewer surface plus a "View raw
884
+ structured evidence" panel reusing the existing `GET /api/artifacts/<handle>`
885
+ route unchanged; omissions/truncations with required loss visually
886
+ distinguished from optional loss; runtime/static correlation — `correlated`
887
+ (its one candidate, labeled "Correlated candidate", never "owner"),
888
+ `ambiguous` (every supplied candidate, none visually promoted), or
889
+ `unavailable` (zero fabricated candidates) — exactly as the artifact states,
890
+ or "Static correlation not included in this context" when the `correlations`
891
+ field itself is absent (never reported as `unavailable`); and the bounded
892
+ reference-fidelity projection (`mismatches`/`protectedContext`/`blockedBy`)
893
+ alongside — never merged into — a live, separately-triggered Batch 6
894
+ on-demand fidelity evaluation for the same reference/candidate, when both
895
+ are available. This mode never calls `projectBoundedAgentContext`,
896
+ `deriveRuntimeStaticCorrelations`, or `attachRuntimeStaticCorrelations` —
897
+ only the exact context the session was started with is ever displayed.
898
+ Every other evidence family still shows the bounded metadata/raw-payload
899
+ view established in Batch 2. Every route remains strictly read-only: no
900
+ artifact is ever created, modified, or interpreted beyond its existing
901
+ canonical reader/validator; no binding, fidelity, or bounded-context
902
+ artifact is ever persisted; bounded agent context remains a programmatic-
903
+ only Observer contract — this command adds no way to generate, save, or
904
+ write one; and no batch in this lineage recomputes an "overall" verdict
905
+ spanning contract and fidelity — they remain two independent,
906
+ separately-displayed evidence dimensions.
907
+
908
+ ```text
909
+ my-frontend-observer view [--root <evidence-root>] [--bindings-file <json-file>] [--context-file <json-file>] [options]
910
+ ```
911
+
912
+ Without `--root`, `view` discovers the nearest initialized project and uses
913
+ its managed evidence root plus ephemeral alias metadata. With `--root`, it
914
+ uses standalone evidence-root behavior and does not require a project or
915
+ catalog.
916
+
917
+ Options:
918
+
919
+ - `--root <path>` — local evidence-root directory the viewer session
920
+ represents. Validated operationally (must exist and be a directory); this
921
+ command never reads or interprets any Observer artifacts under it beyond
922
+ the bounded discovery/classification the routes above describe.
923
+
924
+ Options:
925
+
926
+ - `--bindings-file <json-file>` — local JSON file of the form
927
+ `{ "bindings": [ { "referenceRegion": "...", "runtimeTarget": "..." } ] }`
928
+ — the exact same operational wrapper format, and the exact same shared
929
+ parser, as `evaluate-reference-fidelity --bindings-file`. Read once at
930
+ startup; unreadable/invalid-JSON/wrong-wrapper-shape fails startup
931
+ clearly (no server is started). Reference-specific validity (region
932
+ existence) is checked only once a reference is actually selected in the
933
+ viewer, never at startup. The declarations become session-only viewer
934
+ input: never persisted, never written into any Observer artifact, and the
935
+ file's own path is never exposed to the browser. Omit to run with no
936
+ binding declarations — the viewer remains fully usable; reference/runtime
937
+ cross-selection simply stays disabled and fidelity may still be
938
+ explicitly evaluated with an empty declaration collection.
939
+ - `--context-file <json-file>` — local JSON file containing exactly one
940
+ `BoundedAgentContextArtifact` value directly (no wrapper object). Read
941
+ once at startup and validated through the existing canonical
942
+ `isValidBoundedAgentContextArtifact` — never a second validator. Explicit,
943
+ session-only viewer input: held only in server memory, never persisted,
944
+ never written into any Observer artifact, and the file's own path is
945
+ never exposed to the browser. Bounded agent context remains
946
+ programmatic-only as an Observer-produced contract — this command does
947
+ not add a way to generate, save, or write one; the viewer never rebuilds
948
+ it (`projectBoundedAgentContext` is never called at runtime) and never
949
+ derives or re-derives runtime/static correlation
950
+ (`deriveRuntimeStaticCorrelations`/`attachRuntimeStaticCorrelations` are
951
+ never called at runtime) — it only displays the exact context it was
952
+ given. A recognized `artifactKind` with a `schemaVersion` other than the
953
+ currently supported one (`1.0.0`) starts the viewer showing an honest
954
+ "unsupported version" context state rather than failing. An unreadable
955
+ file, invalid JSON, a non-object root, the wrong `artifactKind`, or a
956
+ structurally invalid current-schema artifact fails startup clearly (no
957
+ server is started). Omit to run with no bounded context supplied — the
958
+ viewer remains fully usable; the "Bounded context" mode reports that none
959
+ was supplied. May be freely combined with `--bindings-file`.
960
+ - `--port <n>` — TCP port to bind, in `[0, 65535]`. Defaults to `4319`
961
+ (chosen after checking that no fixture or test in this repository binds a
962
+ fixed port — see `tests/fixtures/server.ts`, which always uses `0`/
963
+ OS-assigned). An explicit alternate port is a different web origin than
964
+ the default; an installed PWA is not portable across origins. If the
965
+ requested port is already in use, the command fails with an actionable
966
+ diagnostic — it never silently falls back to a different port.
967
+ - `--no-open` — do not attempt to open the system default browser after the
968
+ server starts. Auto-open is a best-effort convenience only: its failure is
969
+ never fatal and never affects server startup success.
970
+ - `--help` — show `view` usage.
971
+
972
+ The server binds only to `127.0.0.1` (never `0.0.0.0`), serves only the
973
+ built viewer application assets plus bounded `/api/*`
974
+ endpoints described above, and never exposes the supplied evidence root as a
975
+ generic static directory or arbitrary filesystem path. With `--root` it
976
+ accepts no write methods and writes nothing. Without `--root`, the guarded
977
+ project-aware authoring routes create only new immutable artifacts and workflow
978
+ revisions through canonical owners; they never edit target source or rewrite
979
+ existing evidence. On success,
980
+ prints the viewer URL and keeps running (serving the viewer) until
981
+ interrupted. On invalid syntax, a missing/non-directory `--root`, an
982
+ invalid `--port`, an invalid `--bindings-file`, an invalid `--context-file`
983
+ (other than a recognized-kind future `schemaVersion`, which starts
984
+ normally), or a port already in use, prints structured diagnostics to
985
+ stderr and exits nonzero without starting a server.
986
+
987
+ ## Cross-tool compatibility handoffs
988
+
989
+ The canonical command-by-command composition map is [my-dev-kit ecosystem workflow section 9.15](https://github.com/dailephd/my-dev-kit/blob/main/docs/ECOSYSTEM_DEVELOPMENT_WORKFLOWS.md#915-command-surface-compatibility-map).
990
+
991
+ Current supported boundaries:
992
+
993
+ - my-dev-kit file/symbol evidence can be mapped by a small **programmatic adapter** into the plain caller-supplied static-candidate records accepted by `deriveRuntimeStaticCorrelations(...)` / `attachRuntimeStaticCorrelations(...)`. Raw my-dev-kit search, lookup, slice, or context JSON is not a direct Observer CLI input.
994
+ - A produced Observer `BoundedAgentContextArtifact` can be inspected directly with `view --context-file <file>`. That option accepts only the Observer bounded-context schema, not a my-dev-kit context capsule.
995
+ - Orchestrator has a direct programmatic consumer for the released Observer bounded-agent-context wire contract. Orchestrator does not launch Observer.
996
+ - A selected Lab tutorial screenshot PNG can be passed to `import-reference`, then explicitly approved and bound like any other external image reference. The Lab tutorial manifest and behavioral assertions are not imported.
997
+ - Lab report/gallery commands do not generically consume Observer evidence roots, and Observer commands do not consume Lab security/audit/experiment reports.
998
+ - `check [baseline] --json` is the preferred compact final-candidate runtime result for an external coding-agent or Orchestrator report, but the downstream consumer must preserve `PASS`, `FAIL`, `REVIEW_REQUIRED`, and `BLOCKED` rather than collapse them to process success/failure.
999
+
1000
+ ## Foundation commands
1001
+
1002
+ - `npm install` — install dependencies (includes the `playwright` runtime
1003
+ dependency since Batch 2).
1004
+ - `npx playwright install chromium` — install the Chromium binary once per
1005
+ machine (see `docs/DEVELOPMENT.md`).
1006
+ - `npm run typecheck` — run TypeScript no-emit checking.
1007
+ - `npm run lint` — lint the repository and scripts.
1008
+ - `npm test` — run the fast unit suite (`tests/unit/`).
1009
+ - `npm run test:browser` — run the real-Chromium integration suite
1010
+ (`tests/browser/`), including a real `observe` end-to-end test against the
1011
+ deterministic local fixture.
1012
+ - `npm run test:security` — run only the safety-relevant subset of the suite
1013
+ (`tests/unit/policy.test.ts` plus the real-Chromium enforcement cases in
1014
+ `tests/browser/chromiumAdapter.test.ts`: unsafe initial target, prohibited
1015
+ redirect, prohibited subresource request, and browser cleanup around
1016
+ safety/navigation failure) — a discoverable entry point for security
1017
+ review tooling; it is a subset of, not a replacement for, `npm test` and
1018
+ `npm run test:browser`.
1019
+ - `npm run build` — clean, then compile `src/` (including `src/cli.ts`) to
1020
+ `dist/`, then build the viewer web app (`viewer/`) with Vite into
1021
+ `dist/viewer/` (v0.8 Batch 1). Both outputs ship inside the existing
1022
+ `dist` package allowlist — there is no second npm package.
1023
+ - `npm run typecheck` also type-checks the browser-side viewer project
1024
+ (`viewer/tsconfig.json`) in addition to `tsconfig.json`, since the viewer's
1025
+ DOM/JSX-targeting TypeScript config is intentionally separate from the
1026
+ Node-only `src/` compilation.
1027
+ - `npm run check:docs` — validate canonical documents and roadmap structure.
1028
+ - `npm pack --dry-run` — inspect the public package's tarball inventory
1029
+ before publishing. The real tarball has been installed and exercised in a
1030
+ clean temporary consumer directory (real Chromium install, real `observe`
1031
+ run, real artifact) on Windows, Linux, and macOS as part of v0.1
1032
+ validation, again for v0.2's packed semantic `--targets-file` behavior,
1033
+ and again for v0.3's packed `--scroll-scenario-file` window/target scroll
1034
+ behavior (`scripts/ci/runPackedObservationSmoke.mjs`); this is local
1035
+ package validation, not a release/publication step.