my-frontend-observer 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/README.md +51 -13
- package/dist/application/comparisonService.d.ts +56 -0
- package/dist/application/comparisonService.js +77 -0
- package/dist/application/comparisonService.js.map +1 -0
- package/dist/application/observationPersistence.js +1 -0
- package/dist/application/observationPersistence.js.map +1 -1
- package/dist/artifacts/artifactReader.d.ts +19 -0
- package/dist/artifacts/artifactReader.js +36 -0
- package/dist/artifacts/artifactReader.js.map +1 -0
- package/dist/artifacts/comparisonArtifactWriter.d.ts +41 -0
- package/dist/artifacts/comparisonArtifactWriter.js +67 -0
- package/dist/artifacts/comparisonArtifactWriter.js.map +1 -0
- package/dist/browser/chromiumAdapter.js +58 -4
- package/dist/browser/chromiumAdapter.js.map +1 -1
- package/dist/browser/evidenceCapture.d.ts +29 -2
- package/dist/browser/evidenceCapture.js +43 -19
- package/dist/browser/evidenceCapture.js.map +1 -1
- package/dist/browser/scrollCapture.d.ts +32 -0
- package/dist/browser/scrollCapture.js +163 -0
- package/dist/browser/scrollCapture.js.map +1 -0
- package/dist/browser/types.d.ts +3 -1
- package/dist/cli.js +299 -2
- package/dist/cli.js.map +1 -1
- package/dist/domain/comparison.d.ts +198 -0
- package/dist/domain/comparison.js +324 -0
- package/dist/domain/comparison.js.map +1 -0
- package/dist/domain/comparisonEngine.d.ts +48 -0
- package/dist/domain/comparisonEngine.js +694 -0
- package/dist/domain/comparisonEngine.js.map +1 -0
- package/dist/domain/comparisonIdentity.d.ts +13 -0
- package/dist/domain/comparisonIdentity.js +46 -0
- package/dist/domain/comparisonIdentity.js.map +1 -0
- package/dist/domain/evidence.d.ts +2 -0
- package/dist/domain/evidence.js +4 -0
- package/dist/domain/evidence.js.map +1 -1
- package/dist/domain/identity.d.ts +8 -3
- package/dist/domain/identity.js +9 -3
- package/dist/domain/identity.js.map +1 -1
- package/dist/domain/relationships.d.ts +168 -0
- package/dist/domain/relationships.js +343 -0
- package/dist/domain/relationships.js.map +1 -0
- package/dist/domain/schema.d.ts +116 -2
- package/dist/domain/schema.js +186 -2
- package/dist/domain/schema.js.map +1 -1
- package/dist/domain/scrollEvidence.d.ts +51 -0
- package/dist/domain/scrollEvidence.js +134 -0
- package/dist/domain/scrollEvidence.js.map +1 -0
- package/dist/index.d.ts +17 -4
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/request/request.d.ts +29 -0
- package/dist/request/request.js +108 -0
- package/dist/request/request.js.map +1 -1
- package/docs/ARCHITECTURE.md +110 -4
- package/docs/CI_CD.md +62 -6
- package/docs/COMMANDS.md +247 -6
- package/docs/CONTRACTS.md +172 -7
- package/docs/CURRENT_STATE.md +148 -10
- package/docs/DEVELOPMENT.md +52 -12
- package/docs/PROJECT_OVERVIEW.md +18 -11
- package/docs/QUICKSTART.md +7 -1
- package/docs/RELEASE.md +14 -7
- package/docs/ROADMAP.md +9 -0
- package/docs/SECURITY.md +14 -1
- package/docs/WORKFLOWS.md +90 -22
- package/package.json +1 -1
package/docs/COMMANDS.md
CHANGED
|
@@ -29,6 +29,10 @@ Options:
|
|
|
29
29
|
- `--targets-file <json-file>` — loads structured semantic observation
|
|
30
30
|
targets from a local JSON file instead of `--target`. Cannot be combined
|
|
31
31
|
with `--target`. See "Structured semantic targets" below.
|
|
32
|
+
- `--scroll-scenario-file <json-file>` — loads one bounded runtime scroll
|
|
33
|
+
scenario from a local JSON file. May be combined with either `--target` or
|
|
34
|
+
`--targets-file` (it is independent of target configuration). See "Scroll
|
|
35
|
+
scenario (`--scroll-scenario-file`)" below.
|
|
32
36
|
- `--output <directory>` — portable, relative output location for the
|
|
33
37
|
observation artifact (same contract as the request's `outputLocation`; no
|
|
34
38
|
drive letter, no leading `/`, no `..` segments).
|
|
@@ -59,7 +63,7 @@ diagnostics print one per line as `[code] message`.
|
|
|
59
63
|
|
|
60
64
|
### Structured semantic targets (`--targets-file`)
|
|
61
65
|
|
|
62
|
-
**Current status: shipped as part of the published `my-frontend-observer@0.
|
|
66
|
+
**Current status: shipped as part of the published `my-frontend-observer@0.3.0`
|
|
63
67
|
package.** `--target` (CSS shorthand) remains fully supported alongside it.
|
|
64
68
|
|
|
65
69
|
`--targets-file <json-file>` is the public entry point to the v0.2 canonical
|
|
@@ -124,6 +128,240 @@ my-frontend-observer observe `
|
|
|
124
128
|
--output observations
|
|
125
129
|
```
|
|
126
130
|
|
|
131
|
+
### Scroll scenario (`--scroll-scenario-file`)
|
|
132
|
+
|
|
133
|
+
**Current status: shipped as part of the published `my-frontend-observer@0.3.0`
|
|
134
|
+
package.** Observation schema is `1.2.0`.
|
|
135
|
+
|
|
136
|
+
`--scroll-scenario-file <json-file>` is the public entry point to the v0.3
|
|
137
|
+
runtime scroll-scenario contract established in `src/request/request.ts`
|
|
138
|
+
(`ScrollScenario`/`ScrollAction`) and executed in `src/browser/`. It supplies
|
|
139
|
+
exactly the value of the normalized request's `scrollScenario` field - the
|
|
140
|
+
file root *is* the scenario object itself, with no wrapper field (unlike
|
|
141
|
+
`--targets-file`'s `{ "targets": [...] }` root).
|
|
142
|
+
|
|
143
|
+
A request supports **zero or one** scroll scenario. There are exactly two
|
|
144
|
+
supported action kinds:
|
|
145
|
+
|
|
146
|
+
Window scrolling:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"action": {
|
|
151
|
+
"kind": "window-scroll-by",
|
|
152
|
+
"deltaX": 0,
|
|
153
|
+
"deltaY": 600
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Target scrolling (the `target` value must be the stable `name` of one of the
|
|
159
|
+
observation's own configured targets - never a CSS selector, DOM id, or
|
|
160
|
+
source symbol):
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"action": {
|
|
165
|
+
"kind": "target-scroll-by",
|
|
166
|
+
"target": "tool-workspace",
|
|
167
|
+
"deltaX": 0,
|
|
168
|
+
"deltaY": 400
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`deltaX`/`deltaY` are signed integers bounded to `[-20000, 20000]`; at least
|
|
174
|
+
one must be non-zero (both zero is rejected). Every scroll/action rule -
|
|
175
|
+
supported action kind, required fields, delta types/bounds, the both-zero
|
|
176
|
+
rule, and the stable-target-name reference for `target-scroll-by` - is
|
|
177
|
+
enforced by the same `normalizeRequest()` validator used everywhere else, not
|
|
178
|
+
duplicated in CLI code; `--scroll-scenario-file` itself only validates that
|
|
179
|
+
the file is readable, is valid JSON, and has a non-array object root.
|
|
180
|
+
|
|
181
|
+
The observer performs the requested scroll immediately (no smooth-scroll
|
|
182
|
+
animation), waits exactly two `requestAnimationFrame` cycles, and captures a
|
|
183
|
+
final runtime snapshot - the same final state that the observation's ordinary
|
|
184
|
+
`pageEvidence`, `targetEvidence`, and `screenshot.png` describe. The actual
|
|
185
|
+
resulting scroll position is browser-authoritative and may be clamped by
|
|
186
|
+
document/element boundaries; a scenario that produces no movement (already at
|
|
187
|
+
a boundary, or a non-scrollable target) is still a valid, successfully
|
|
188
|
+
persisted observation, never a fabricated failure.
|
|
189
|
+
|
|
190
|
+
Usable with either target input mode:
|
|
191
|
+
|
|
192
|
+
```powershell
|
|
193
|
+
my-frontend-observer observe `
|
|
194
|
+
--url http://localhost:3000/ `
|
|
195
|
+
--target workspace=.workspace `
|
|
196
|
+
--scroll-scenario-file .\scroll.json `
|
|
197
|
+
--output observations
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```powershell
|
|
201
|
+
my-frontend-observer observe `
|
|
202
|
+
--url http://localhost:3000/ `
|
|
203
|
+
--targets-file .\targets.json `
|
|
204
|
+
--scroll-scenario-file .\scroll.json `
|
|
205
|
+
--output observations
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`--target` and `--targets-file` remain mutually exclusive with each other,
|
|
209
|
+
exactly as before; `--scroll-scenario-file` is independent of both and is
|
|
210
|
+
never itself a third mutually-exclusive target mode. `window-scroll-by`
|
|
211
|
+
requires no configured target at all.
|
|
212
|
+
|
|
213
|
+
The path may be relative (resolved from the current working directory) or
|
|
214
|
+
absolute; it is operational input only - like `--targets-file`'s path, it
|
|
215
|
+
never affects the observation's request identity and is never written into
|
|
216
|
+
`manifest.json`. Two different scenario files with identical content produce
|
|
217
|
+
the same `requestId`; only the requested scenario *configuration*
|
|
218
|
+
participates in identity, never the runtime outcome (actual scroll
|
|
219
|
+
distance, clamping, or scroll-owner result).
|
|
220
|
+
|
|
221
|
+
If a `target-scroll-by` scenario's configured action target cannot be
|
|
222
|
+
uniquely resolved at runtime (missing, ambiguous, or otherwise unavailable),
|
|
223
|
+
the scroll is not performed, no movement is fabricated, and the observation
|
|
224
|
+
persists honestly - typically as `partial` - carrying the same
|
|
225
|
+
`target-missing`/`target-ambiguous`/`browser-evidence-unavailable` diagnostic
|
|
226
|
+
that any other unresolved configured target would produce.
|
|
227
|
+
|
|
228
|
+
## `compare`
|
|
229
|
+
|
|
230
|
+
**Current status: shipped as part of the published `my-frontend-observer@0.4.0`
|
|
231
|
+
package.** Comparison schema is `1.0.0`, independent of and never reused for
|
|
232
|
+
the observation schema (`1.2.0`).
|
|
233
|
+
|
|
234
|
+
`my-frontend-observer compare` (or `node dist/cli.js compare` from a source
|
|
235
|
+
checkout) reads two already-persisted observation artifacts and derives
|
|
236
|
+
before/after evidence purely from their existing content:
|
|
237
|
+
|
|
238
|
+
```text
|
|
239
|
+
my-frontend-observer compare --before <observation-artifact-root> --after <observation-artifact-root> --output <directory> [options]
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Required:
|
|
243
|
+
|
|
244
|
+
- `--before <path>` — root directory of the "before" persisted observation
|
|
245
|
+
artifact (the directory containing its `manifest.json`, as produced by
|
|
246
|
+
`observe`).
|
|
247
|
+
- `--after <path>` — root directory of the "after" persisted observation
|
|
248
|
+
artifact.
|
|
249
|
+
- `--output <directory>` — portable, relative output location for the
|
|
250
|
+
comparison artifact (same contract as `observe --output`).
|
|
251
|
+
|
|
252
|
+
Options:
|
|
253
|
+
|
|
254
|
+
- `--config-file <json-file>` — loads a comparison configuration directly
|
|
255
|
+
(no wrapper field): `{ "geometryTolerancePx": <0-10>,
|
|
256
|
+
"expectedDependencies": [...] }`. Without it, `geometryTolerancePx`
|
|
257
|
+
defaults to `0.5` CSS px with no declared dependencies. As with
|
|
258
|
+
`--targets-file`/`--scroll-scenario-file`, `--config-file` only validates
|
|
259
|
+
file readability, JSON validity, and a non-array object root; every
|
|
260
|
+
semantic rule (tolerance bounds, dependency property/direction
|
|
261
|
+
vocabulary, dependency source marker) is enforced by the same domain
|
|
262
|
+
validator the comparison engine itself uses.
|
|
263
|
+
- `--help` — show `compare` usage.
|
|
264
|
+
|
|
265
|
+
**Comparison never launches a browser.** It reads two manifests through the
|
|
266
|
+
existing observation-artifact reader, runs the pure comparison engine, and
|
|
267
|
+
persists a portable `manifest.json` — no navigation, no target
|
|
268
|
+
re-resolution, no Chromium process.
|
|
269
|
+
|
|
270
|
+
On success the command prints exactly:
|
|
271
|
+
|
|
272
|
+
```text
|
|
273
|
+
Comparison: <comparison-id>
|
|
274
|
+
State: <comparable|comparable-with-warnings|incomparable>
|
|
275
|
+
Artifact: <comparison-artifact-root>
|
|
276
|
+
Differences: <count>
|
|
277
|
+
Relationship changes: <count>
|
|
278
|
+
Diagnostics: <count>
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
and exits `0` — **including when `State` is `incomparable`**: comparison
|
|
282
|
+
determining that two observations should not be treated as equivalent
|
|
283
|
+
frontend states is itself a successful outcome, not a failure. The command
|
|
284
|
+
exits nonzero only for invalid CLI syntax, an unreadable/malformed/
|
|
285
|
+
structurally-invalid source artifact, invalid comparison configuration, or
|
|
286
|
+
a failed artifact write.
|
|
287
|
+
|
|
288
|
+
### Comparability
|
|
289
|
+
|
|
290
|
+
Before any rendered difference is calculated, the engine evaluates whether
|
|
291
|
+
the two observations are comparable at all:
|
|
292
|
+
|
|
293
|
+
- **Hard incompatibilities** (force `incomparable`): different logical page
|
|
294
|
+
URL, different viewport, different browser engine, or a mismatched scroll
|
|
295
|
+
scenario configuration (no scenario vs. a scenario, or two different
|
|
296
|
+
scenario configurations).
|
|
297
|
+
- **Warnings** (still `comparable-with-warnings`, comparison proceeds):
|
|
298
|
+
different producer package version, different browser version, or a
|
|
299
|
+
changed/added/removed configured target.
|
|
300
|
+
- **Unassessed dimensions** the observer does not yet model (theme,
|
|
301
|
+
authenticated state, application state) are always recorded, never
|
|
302
|
+
silently claimed identical.
|
|
303
|
+
|
|
304
|
+
An `incomparable` result still persists a structurally valid
|
|
305
|
+
`ComparisonArtifact`: the comparability reasons are recorded, and ordinary
|
|
306
|
+
rendered differences/relationship changes stay empty rather than fabricated.
|
|
307
|
+
|
|
308
|
+
### Difference and relationship evidence
|
|
309
|
+
|
|
310
|
+
For a `comparable`/`comparable-with-warnings` result, the manifest's
|
|
311
|
+
`differences` and `relationshipChanges` arrays carry structured before/
|
|
312
|
+
after evidence: appeared/disappeared targets (only for a stable target name
|
|
313
|
+
configured on both sides — a target added/removed from configuration is
|
|
314
|
+
recorded separately as a `configurationChanges` entry, never fabricated as
|
|
315
|
+
appeared/disappeared), moved/resized targets, visibility changes, clipping
|
|
316
|
+
changes, actual dimensional overflow changes, DOM containment changes,
|
|
317
|
+
page-size changes, scroll-owner changes, and layout-relationship
|
|
318
|
+
transitions (e.g. `does-not-overlap` → `overlaps`, or
|
|
319
|
+
`document-width-fits-viewport` → `document-width-exceeds-viewport`) reused
|
|
320
|
+
verbatim from the same canonical relationship engine `observe` output feeds
|
|
321
|
+
Batch 2's `deriveLayoutRelationships`.
|
|
322
|
+
|
|
323
|
+
### Explicit dependency evidence (non-causal)
|
|
324
|
+
|
|
325
|
+
`--config-file`'s `expectedDependencies` lets you declare an expected
|
|
326
|
+
layout relationship such as "`navigation.width` decreases →
|
|
327
|
+
`workspace.width` increases" using only the frozen `x`/`y`/`width`/`height`
|
|
328
|
+
property vocabulary and `increase`/`decrease`/`change`/`unchanged`
|
|
329
|
+
direction vocabulary. Each declaration is evaluated independently against
|
|
330
|
+
the two observations and persists exactly one outcome: `consistent`,
|
|
331
|
+
`not-observed`, `contradictory-to-declaration`, or `unavailable`. **The
|
|
332
|
+
observer never infers a dependency from co-change, and never emits a
|
|
333
|
+
causal claim, a PASS/FAIL verdict, or a change-contract decision** — v0.4
|
|
334
|
+
produces comparison evidence; whether that evidence satisfies some
|
|
335
|
+
contract is v0.5+ scope.
|
|
336
|
+
|
|
337
|
+
### Path privacy
|
|
338
|
+
|
|
339
|
+
`--before`, `--after`, `--config-file`, and `--output` are operational
|
|
340
|
+
filesystem input only. None of them affect `comparisonRequestId`, and none
|
|
341
|
+
of them are written into the persisted manifest — the manifest instead
|
|
342
|
+
retains logical source references (`observationId`, `requestId`,
|
|
343
|
+
`producer`, `observationSchemaVersion`, and the source `screenshot.path`).
|
|
344
|
+
Two semantically identical observation/config pairs read from different
|
|
345
|
+
filesystem locations produce the same `comparisonRequestId`; each execution
|
|
346
|
+
still gets a fresh `comparisonId`.
|
|
347
|
+
|
|
348
|
+
### Source observations remain immutable
|
|
349
|
+
|
|
350
|
+
Comparison is read-only with respect to its inputs: it never modifies
|
|
351
|
+
either source observation's `manifest.json` or `screenshot.png`, and it
|
|
352
|
+
never copies screenshot bytes into the comparison directory — the
|
|
353
|
+
comparison artifact directory contains `manifest.json` only.
|
|
354
|
+
|
|
355
|
+
Example:
|
|
356
|
+
|
|
357
|
+
```powershell
|
|
358
|
+
node dist/cli.js compare `
|
|
359
|
+
--before observations/<before-id> `
|
|
360
|
+
--after observations/<after-id> `
|
|
361
|
+
--output comparisons `
|
|
362
|
+
--config-file .\comparison-config.json
|
|
363
|
+
```
|
|
364
|
+
|
|
127
365
|
## Foundation commands
|
|
128
366
|
|
|
129
367
|
- `npm install` — install dependencies (includes the `playwright` runtime
|
|
@@ -146,8 +384,11 @@ my-frontend-observer observe `
|
|
|
146
384
|
- `npm run build` — clean and compile `src/` (including `src/cli.ts`) to
|
|
147
385
|
`dist/`.
|
|
148
386
|
- `npm run check:docs` — validate canonical documents and roadmap structure.
|
|
149
|
-
- `npm pack --dry-run` — inspect the
|
|
150
|
-
publishing. The real tarball has been installed and exercised in a
|
|
151
|
-
temporary consumer directory (real Chromium install, real `observe`
|
|
152
|
-
real artifact) as part of v0.1
|
|
153
|
-
validation,
|
|
387
|
+
- `npm pack --dry-run` — inspect the public package's tarball inventory
|
|
388
|
+
before publishing. The real tarball has been installed and exercised in a
|
|
389
|
+
clean temporary consumer directory (real Chromium install, real `observe`
|
|
390
|
+
run, real artifact) on Windows, Linux, and macOS as part of v0.1
|
|
391
|
+
validation, again for v0.2's packed semantic `--targets-file` behavior,
|
|
392
|
+
and again for v0.3's packed `--scroll-scenario-file` window/target scroll
|
|
393
|
+
behavior (`scripts/ci/runPackedObservationSmoke.mjs`); this is local
|
|
394
|
+
package validation, not a release/publication step.
|
package/docs/CONTRACTS.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Current contracts
|
|
4
4
|
|
|
5
|
-
The observation artifact contract is published as `my-frontend-observer@0.
|
|
5
|
+
The observation artifact contract is published as `my-frontend-observer@0.4.0`
|
|
6
6
|
and proven both from the source checkout and from the packed npm tarball,
|
|
7
|
-
on Windows, Linux, and macOS. The observation schema is `1.
|
|
8
|
-
target contract"
|
|
7
|
+
on Windows, Linux, and macOS. The observation schema is `1.2.0` (see "v0.2
|
|
8
|
+
target contract" and "v0.3 scroll scenario contract" below):
|
|
9
9
|
|
|
10
|
-
- artifact kind `my-frontend-observer/observation`, schema version `1.
|
|
10
|
+
- artifact kind `my-frontend-observer/observation`, schema version `1.2.0`
|
|
11
11
|
(independent of the package version);
|
|
12
12
|
- one artifact root per observation, `<outputLocation>/<observationId>/`,
|
|
13
13
|
containing exactly `manifest.json` (the full `ObservationArtifact`, with
|
|
@@ -81,9 +81,174 @@ supplies the structured `{ "targets": [...] }` collection (see
|
|
|
81
81
|
existing `--target id=css-selector` shorthand - the two are mutually
|
|
82
82
|
exclusive per invocation, and both converge on the same
|
|
83
83
|
`normalizeRequest()`/browser-resolver/artifact path, so a semantic
|
|
84
|
-
observation produces exactly the same `manifest.json` shape
|
|
85
|
-
|
|
86
|
-
the
|
|
84
|
+
observation produces exactly the same `manifest.json` shape as a
|
|
85
|
+
CSS-shorthand one. Schema `1.1.0` was the v0.2 published artifact schema;
|
|
86
|
+
the current v0.4 package emits schema `1.2.0` for both target-input modes
|
|
87
|
+
(target semantics are unchanged from v0.2 - see the v0.3 scroll scenario
|
|
88
|
+
contract below for what schema `1.2.0` actually adds). `--targets-file`'s
|
|
89
|
+
local input path is never part of the persisted request identity or
|
|
90
|
+
artifact.
|
|
91
|
+
|
|
92
|
+
## v0.3 scroll scenario contract (shipped as part of this release)
|
|
93
|
+
|
|
94
|
+
v0.3 introduces one optional, additive request/evidence concern: a bounded
|
|
95
|
+
runtime scroll scenario, schema `1.2.0`.
|
|
96
|
+
|
|
97
|
+
A normalized request may carry `scrollScenario: { action }` with exactly one
|
|
98
|
+
of two frozen action kinds:
|
|
99
|
+
|
|
100
|
+
- `{ "kind": "window-scroll-by", "deltaX": <int>, "deltaY": <int> }`
|
|
101
|
+
- `{ "kind": "target-scroll-by", "target": "<stable target name>", "deltaX": <int>, "deltaY": <int> }`
|
|
102
|
+
|
|
103
|
+
`deltaX`/`deltaY` are signed integers bounded to `[-20000, 20000]`; at least
|
|
104
|
+
one must be non-zero. `target-scroll-by.target` refers only to an existing
|
|
105
|
+
stable configured target `name` (never a selector) and resolves through the
|
|
106
|
+
same canonical `resolveConfiguredTargets` algorithm every v0.2 locator kind
|
|
107
|
+
already uses - there is no second target-resolution path. A request with no
|
|
108
|
+
scenario normalizes and identifies exactly as it did before v0.3.
|
|
109
|
+
|
|
110
|
+
Execution (both action kinds share one code path): perform the immediate,
|
|
111
|
+
non-smooth scroll (`window.scrollBy`/`element.scrollBy`, `behavior:
|
|
112
|
+
'instant'`) on the already-navigated, already-ready page; wait exactly two
|
|
113
|
+
`requestAnimationFrame` cycles; capture a final runtime snapshot. No second
|
|
114
|
+
browser, page, or navigation is ever created. The resulting scroll position
|
|
115
|
+
is browser-authoritative and may be clamped by document/element boundaries;
|
|
116
|
+
a scenario producing no movement is still a valid, successfully persisted
|
|
117
|
+
observation.
|
|
118
|
+
|
|
119
|
+
The scenario evidence lives entirely inside the existing `manifest.json` as
|
|
120
|
+
one additional optional `scrollScenarioEvidence` field on `ObservationArtifact`
|
|
121
|
+
- there is no separate `scroll.json`/`scenario.json`. It contains:
|
|
122
|
+
|
|
123
|
+
- `initial`/`final`: bounded `ScrollRuntimeSnapshot`s (window `scrollX`/
|
|
124
|
+
`scrollY`; the browser's own scrolling-root/`documentElement`/`body`
|
|
125
|
+
metrics; per-configured-target `scrollTop`/`scrollLeft`/`scrollWidth`/
|
|
126
|
+
`scrollHeight`/`clientWidth`/`clientHeight`, actual overflow, bounding
|
|
127
|
+
rectangle, and viewport relation);
|
|
128
|
+
- `transition`: bounded before/after change evidence (window scroll deltas;
|
|
129
|
+
per-target `scrollTop`/`scrollLeft`/bounding-position/viewport-relation
|
|
130
|
+
changes; `enteredViewport`/`leftViewport`) - never a generic recursive
|
|
131
|
+
diff, and a target is simply omitted when either side's evidence isn't
|
|
132
|
+
itself usable (e.g. it never resolved);
|
|
133
|
+
- `scrollOwner`: one derived `EvidenceField<ScrollOwnerInterpretation>`
|
|
134
|
+
(`document` | `target:<stable-name>` | `none` | `indeterminate`), always
|
|
135
|
+
`source: "derived"` with non-empty `derivedFrom` naming the exact
|
|
136
|
+
contributing scroll-position measurements. Ownership is derived only from
|
|
137
|
+
observed `scrollTop`/`scrollLeft`/`window.scrollX`/`window.scrollY`
|
|
138
|
+
changes - never from bounding-rectangle movement (which moves for every
|
|
139
|
+
configured target whenever the document scrolls), computed overflow,
|
|
140
|
+
`position: fixed`/`sticky`, or DOM hierarchy.
|
|
141
|
+
|
|
142
|
+
Actual dimensional overflow (`scrollWidth > clientWidth` /
|
|
143
|
+
`scrollHeight > clientHeight`) is always reported separately from the
|
|
144
|
+
computed `overflow-x`/`overflow-y` CSS declaration; a declared
|
|
145
|
+
`overflow: auto` container with content that fits produces
|
|
146
|
+
`horizontalOverflow`/`verticalOverflow: false`. Viewport relation
|
|
147
|
+
(`above`/`intersecting`/`below`, `intersectsViewport`, `fullyWithinViewport`)
|
|
148
|
+
is derived only from bounding geometry plus viewport size, relative to the
|
|
149
|
+
browser viewport; a hidden/non-rendered target's viewport relation is
|
|
150
|
+
`not-applicable`, never a fabricated geometry claim - hidden and offscreen
|
|
151
|
+
remain distinct evidence concepts, and the existing `target-hidden`
|
|
152
|
+
diagnostic is unaffected.
|
|
153
|
+
|
|
154
|
+
The ordinary, already-existing `pageEvidence`/`targetEvidence`/
|
|
155
|
+
`screenshot.png` for a scenario observation always describe this same final
|
|
156
|
+
post-action state, never the pre-action state.
|
|
157
|
+
|
|
158
|
+
The scenario request participates in `requestId`; the runtime result
|
|
159
|
+
(actual scroll distance, clamping, or scroll-owner outcome) never does. The
|
|
160
|
+
public entry point is `my-frontend-observer observe --scroll-scenario-file
|
|
161
|
+
<json-file>` (see `docs/COMMANDS.md`); the file supplies the scenario value
|
|
162
|
+
directly, and its local path is operational input only, exactly like
|
|
163
|
+
`--targets-file`'s path - never persisted, never part of request identity.
|
|
164
|
+
|
|
165
|
+
## v0.4 comparison contract (shipped as part of this release)
|
|
166
|
+
|
|
167
|
+
**Current status: shipped as part of the published `my-frontend-observer@0.4.0`
|
|
168
|
+
package.** Observation schema remains `1.2.0`. Comparison is a distinct
|
|
169
|
+
artifact kind and schema, never a bump to the observation schema:
|
|
170
|
+
|
|
171
|
+
- artifact kind: `my-frontend-observer/comparison`;
|
|
172
|
+
- comparison schema: `1.0.0`.
|
|
173
|
+
|
|
174
|
+
**Geometry tolerance**: `ComparisonConfig.geometryTolerancePx`, default
|
|
175
|
+
`0.5` CSS px, bounded `[0, 10]`. Suppresses insignificant subpixel noise
|
|
176
|
+
only - never a design contract, never permission for a change.
|
|
177
|
+
|
|
178
|
+
**Layout relationship graph**: `deriveLayoutRelationships(observation,
|
|
179
|
+
options?)` derives, per observation, a bounded `LayoutRelationshipGraph`
|
|
180
|
+
among configured targets only (≤20 targets, ≤190 unordered pairs):
|
|
181
|
+
horizontal order (`left-of`/`right-of`/`horizontally-overlapping`),
|
|
182
|
+
vertical order (`above`/`below`/`vertically-overlapping`), area overlap
|
|
183
|
+
(`overlaps`/`does-not-overlap`), relative width (`wider-than`/
|
|
184
|
+
`narrower-than`/`equal-width-within-tolerance`), geometric fit
|
|
185
|
+
(`fits-inside`/`does-not-fit-inside` - geometry-only, deliberately distinct
|
|
186
|
+
from DOM containment), vertical sequencing (`follows-vertically`), and one
|
|
187
|
+
page-level relationship (`document-width-fits-viewport`/
|
|
188
|
+
`document-width-exceeds-viewport`). Every relationship carries explicit
|
|
189
|
+
evidence-path provenance back to the source observation. A configured
|
|
190
|
+
target lacking usable geometry is listed as honestly unresolved
|
|
191
|
+
(`not-found`/`ambiguous`/`unavailable`/`hidden`), never fabricated as a
|
|
192
|
+
zero-sized region.
|
|
193
|
+
|
|
194
|
+
**Comparability**: evaluated before any rendered difference, using exactly
|
|
195
|
+
three states (`comparable`/`comparable-with-warnings`/`incomparable`) with
|
|
196
|
+
structured reasons, never a bare boolean. Hard incompatibilities (page URL,
|
|
197
|
+
viewport, browser engine, scroll-scenario configuration mismatch) force
|
|
198
|
+
`incomparable`; producer-version, browser-version, and target-configuration
|
|
199
|
+
differences are warning-only; theme/authenticated-state/application-state
|
|
200
|
+
identity are recorded as `unassessed` dimensions the observer does not yet
|
|
201
|
+
model - never silently claimed identical. An `incomparable` result still
|
|
202
|
+
persists a structurally valid `ComparisonArtifact` with empty rendered
|
|
203
|
+
differences, not a fabricated comparison.
|
|
204
|
+
|
|
205
|
+
**Difference categories**: `appeared`/`disappeared` (only for a stable
|
|
206
|
+
target name configured on both sides, transitioning between a definite
|
|
207
|
+
`not-found` and `matched` resolution status - never for a target merely
|
|
208
|
+
added/removed from configuration, which is its own separate
|
|
209
|
+
`configurationChanges` entry), `moved`/`resized` (tolerance-aware, a target
|
|
210
|
+
may be both), `visibility-changed`, `clipping-changed` (reusing the
|
|
211
|
+
canonical `deriveTargetClipping` helper, never re-derived), `horizontal-
|
|
212
|
+
overflow-changed`/`vertical-overflow-changed` (actual dimensional overflow,
|
|
213
|
+
reusing the existing `deriveOverflowEvidence` helper - never inferred from
|
|
214
|
+
a CSS declaration alone), `containment-changed` (reusing existing v0.2
|
|
215
|
+
`TargetContainment` evidence), `page-size-changed`, `scroll-owner-changed`
|
|
216
|
+
(comparing `scrollScenarioEvidence.scrollOwner` only when scenario
|
|
217
|
+
*configuration* already matched), `relative-position-changed` (a relation
|
|
218
|
+
in the horizontal-order/vertical-order/area-overlap families changed - kept
|
|
219
|
+
distinct from plain absolute target movement) and `relationship-changed`
|
|
220
|
+
(every other relationship-family transition). Relationship changes are
|
|
221
|
+
matched by structural identity (family + subject/related target, or the
|
|
222
|
+
page-level key), never by array position.
|
|
223
|
+
|
|
224
|
+
**Explicit dependency evidence**: `ComparisonConfig.expectedDependencies`
|
|
225
|
+
lets a caller declare an expected relationship between two targets' numeric
|
|
226
|
+
properties (`x`/`y`/`width`/`height`) and directions (`increase`/
|
|
227
|
+
`decrease`/`change`/`unchanged`), always carrying `source:
|
|
228
|
+
"explicit-config"`. The observer never synthesizes a declaration from
|
|
229
|
+
observed co-change. Each declaration evaluates independently to exactly one
|
|
230
|
+
of `consistent`/`not-observed`/`contradictory-to-declaration`/
|
|
231
|
+
`unavailable` - never a causal claim (no `causedBy`/`causalConfidence`/
|
|
232
|
+
`causalScore`/`dependencyStrength`) and never a PASS/FAIL/approval verdict.
|
|
233
|
+
That distinction (evidence vs. contract verdict) is the boundary between
|
|
234
|
+
v0.4 and v0.5+.
|
|
235
|
+
|
|
236
|
+
**Comparison identity**: `comparisonRequestId` is a pure, deterministic
|
|
237
|
+
function of `{beforeObservationId, afterObservationId, normalized
|
|
238
|
+
ComparisonConfig}` - direction-sensitive (`compare(A, B) !==
|
|
239
|
+
compare(B, A)`), and never includes an operational filesystem path.
|
|
240
|
+
`comparisonId` is fresh per execution (same pattern as `observationId`).
|
|
241
|
+
|
|
242
|
+
**Source references**: the comparison artifact retains enough logical
|
|
243
|
+
identity to trace back to its authoritative source observations
|
|
244
|
+
(`observationId`, `requestId`, `producer`, `observationSchemaVersion`, and
|
|
245
|
+
the source `screenshot.path`) without embedding the full
|
|
246
|
+
`ObservationArtifact` or copying screenshot bytes. The persisted comparison
|
|
247
|
+
directory contains `manifest.json` only.
|
|
248
|
+
|
|
249
|
+
The public entry point is `my-frontend-observer compare --before <root>
|
|
250
|
+
--after <root> --output <directory> [--config-file <json-file>]` (see
|
|
251
|
+
`docs/COMMANDS.md`) - comparison itself never launches a browser.
|
|
87
252
|
|
|
88
253
|
## Approved v0.1 design inputs
|
|
89
254
|
|
package/docs/CURRENT_STATE.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Current State
|
|
2
2
|
|
|
3
|
-
The project is published at package version `0.
|
|
4
|
-
|
|
3
|
+
The project is published at package version `0.4.0` (roadmap v0.4, Layout
|
|
4
|
+
Relationships, Dependency Evidence, and Before/After Comparison; observation
|
|
5
|
+
schema `1.2.0`; comparison schema `1.0.0`).
|
|
5
6
|
|
|
6
7
|
## Greenfield foundation established
|
|
7
8
|
|
|
@@ -175,16 +176,153 @@ schema `1.1.0`.
|
|
|
175
176
|
`dist/cli.js` (not just the imported `runCli()` function) performs a real
|
|
176
177
|
semantic `--targets-file` observation end to end.
|
|
177
178
|
|
|
179
|
+
## v0.3 status (Runtime Scrolling, Overflow, and Visibility Behavior) - released as 0.3.0
|
|
180
|
+
|
|
181
|
+
v0.3 is implemented and released as package version `0.3.0`, observation
|
|
182
|
+
schema `1.2.0`. It was validated as a packed npm tarball in a clean
|
|
183
|
+
consumer environment on Windows, Linux, and macOS before release.
|
|
184
|
+
|
|
185
|
+
- **Batch 1** froze the `scrollScenario` request/identity/schema contract:
|
|
186
|
+
`ScrollScenario { action }` with exactly two action kinds
|
|
187
|
+
(`window-scroll-by`, `target-scroll-by`), signed-integer deltas bounded to
|
|
188
|
+
`[-20000, 20000]`, `target-scroll-by.target` referencing an existing stable
|
|
189
|
+
configured target name, scenario configuration participating in
|
|
190
|
+
`requestId` (runtime results never do), and the full bounded runtime
|
|
191
|
+
evidence model (`ScrollRuntimeSnapshot`, `ViewportRelationEvidence`,
|
|
192
|
+
`OverflowEvidence`, scenario transitions, `ScrollOwnerInterpretation`) in
|
|
193
|
+
schema `1.2.0` (up from `1.1.0`).
|
|
194
|
+
- **Batch 2** implemented real `window-scroll-by` execution
|
|
195
|
+
(`src/browser/scrollCapture.ts`, `src/domain/scrollEvidence.ts`): initial/
|
|
196
|
+
final runtime snapshots around an immediate `window.scrollBy({behavior:
|
|
197
|
+
'instant'})` and exactly two `requestAnimationFrame` cycles, real vertical/
|
|
198
|
+
horizontal document scrolling, actual-vs-computed overflow, real viewport
|
|
199
|
+
relation, `enteredViewport`/`leftViewport`, and `document`/`none`
|
|
200
|
+
scroll-owner evidence - with ordinary final `pageEvidence`/`targetEvidence`
|
|
201
|
+
and the screenshot always describing the same final post-action state.
|
|
202
|
+
- **Batch 3** implemented real `target-scroll-by` execution against the same
|
|
203
|
+
canonical `resolveConfiguredTargets` resolution already used by every v0.2
|
|
204
|
+
locator kind: real nested vertical/horizontal element scrolling, boundary
|
|
205
|
+
clamping, non-scrollable/no-movement targets, and the completed
|
|
206
|
+
`document`/`target:<name>`/`none`/`indeterminate` scroll-owner derivation
|
|
207
|
+
(`src/domain/scrollEvidence.ts#deriveScrollOwner`) - proven never to
|
|
208
|
+
attribute ownership from bounding-rectangle movement alone in either
|
|
209
|
+
direction. An unresolved/ambiguous/hidden action target is never scrolled
|
|
210
|
+
and never fabricated as moved; the existing target diagnostics explain it
|
|
211
|
+
honestly and the observation still persists.
|
|
212
|
+
- **Batch 4** exposed the existing contract through the real public CLI:
|
|
213
|
+
`my-frontend-observer observe --scroll-scenario-file <json-file>` (see
|
|
214
|
+
`docs/COMMANDS.md`). The file supplies the `scrollScenario` value directly
|
|
215
|
+
(no wrapper field); the CLI/input layer only validates file readability,
|
|
216
|
+
JSON validity, and a non-array object root - every scenario/action rule
|
|
217
|
+
stays owned by the existing `normalizeRequest()`. Usable with either
|
|
218
|
+
`--target` or `--targets-file` (independent of target configuration, never
|
|
219
|
+
a third mutually-exclusive mode); the scenario-file path is operational
|
|
220
|
+
input only, never persisted and never part of request identity, exactly
|
|
221
|
+
like `--targets-file`'s path. CLI output/exit-code semantics are
|
|
222
|
+
unchanged. Proven via real Chromium (`tests/browser/cliObserve.test.ts`)
|
|
223
|
+
and the built `dist/cli.js` (`scripts/dev/builtCliScrollScenarioSmoke.mjs`).
|
|
224
|
+
|
|
225
|
+
## v0.4 status (Layout Relationships, Dependency Evidence, and Before/After Comparison) - released as 0.4.0
|
|
226
|
+
|
|
227
|
+
v0.4 is implemented and released as package version `0.4.0`; observation
|
|
228
|
+
schema remains `1.2.0`; comparison schema is `1.0.0`. It was validated as a
|
|
229
|
+
packed npm tarball in a clean consumer environment on Windows, Linux, and
|
|
230
|
+
macOS - covering the legacy CSS-shorthand `--target` path, the structured
|
|
231
|
+
`--targets-file` path, both `--scroll-scenario-file` action kinds, and the
|
|
232
|
+
installed `compare` command (comparable and incomparable cases) - before
|
|
233
|
+
release.
|
|
234
|
+
|
|
235
|
+
- **Batch 1** froze the `my-frontend-observer/comparison` artifact contract
|
|
236
|
+
(schema `1.0.0`, independent of and never reused for the observation
|
|
237
|
+
schema): `ComparisonConfig` (geometry tolerance, default `0.5`px, bounded
|
|
238
|
+
`[0, 10]`px), the bounded layout-relationship vocabulary (horizontal/
|
|
239
|
+
vertical order, area overlap, relative width, geometric fit, vertical
|
|
240
|
+
sequencing, page-width fit, clipping), comparability states, the
|
|
241
|
+
before/after difference vocabulary, and the non-causal explicit
|
|
242
|
+
dependency-evidence contract, plus `comparisonRequestId`/`comparisonId`
|
|
243
|
+
identity (`src/domain/relationships.ts`, `src/domain/comparison.ts`,
|
|
244
|
+
`src/domain/comparisonIdentity.ts`). No derivation, comparison, or
|
|
245
|
+
persistence.
|
|
246
|
+
- **Batch 2** implemented the one canonical pure derivation engine,
|
|
247
|
+
`deriveLayoutRelationships(observation, options?)`
|
|
248
|
+
(`src/domain/relationships.ts`): consumes an existing `ObservationArtifact`
|
|
249
|
+
only (no Chromium, no re-resolution, no DOM access) and derives a bounded,
|
|
250
|
+
traceable `LayoutRelationshipGraph` among configured targets - stable
|
|
251
|
+
target identity, deterministic configured-target ordering, honest
|
|
252
|
+
unresolved-target handling (not-found/ambiguous/unavailable/hidden, never
|
|
253
|
+
a fabricated zero-sized region), and evidence-reference provenance for
|
|
254
|
+
every derived relationship. DOM containment is read directly from the
|
|
255
|
+
existing `TargetContainment` evidence rather than re-derived, and stays
|
|
256
|
+
distinct from geometric fit. A standalone `deriveTargetClipping(record)`
|
|
257
|
+
derives the frozen clipping concept per target from existing layout/style
|
|
258
|
+
evidence.
|
|
259
|
+
- **Batch 3** implemented the pure before/after comparison engine,
|
|
260
|
+
`compareObservations(before, after, config?)`
|
|
261
|
+
(`src/domain/comparisonEngine.ts`): validates both source observations,
|
|
262
|
+
evaluates comparability before any rendered difference is calculated
|
|
263
|
+
(hard page-URL/viewport/browser-engine/scroll-scenario mismatches;
|
|
264
|
+
producer/browser-version and target-configuration warnings), reuses
|
|
265
|
+
`deriveLayoutRelationships` unchanged for both sides, and derives target/
|
|
266
|
+
page differences (appeared/disappeared, moved, resized, visibility,
|
|
267
|
+
clipping, actual overflow, DOM containment, page size, scroll-owner) and
|
|
268
|
+
relationship changes (matched by family + subject/related target, never
|
|
269
|
+
array position) - all without launching Chromium, re-resolving targets, or
|
|
270
|
+
mutating either input observation. Explicit `ComparisonConfig.
|
|
271
|
+
expectedDependencies` are evaluated into non-causal
|
|
272
|
+
consistent/not-observed/contradictory-to-declaration/unavailable outcomes
|
|
273
|
+
only; the observer never infers a dependency from co-change. Comparison
|
|
274
|
+
identity reuses the existing Batch 1 `buildComparisonRequestIdentity`/
|
|
275
|
+
`buildComparisonIdentity` verbatim. Persistence
|
|
276
|
+
(`src/artifacts/comparisonArtifactWriter.ts#writeComparisonArtifact`,
|
|
277
|
+
atomic, `<outputLocation>/<comparisonId>/manifest.json` only, no copied
|
|
278
|
+
screenshots) and the application-level `compareAndPersist` use case
|
|
279
|
+
(`src/application/comparisonService.ts`) are implemented; a narrow
|
|
280
|
+
`readObservationArtifact` reader
|
|
281
|
+
(`src/artifacts/artifactReader.ts`) is established ahead of the Batch 4
|
|
282
|
+
CLI.
|
|
283
|
+
- **Batch 4** exposed the existing comparison workflow through the real
|
|
284
|
+
public CLI: `my-frontend-observer compare --before <observation-artifact-
|
|
285
|
+
root> --after <observation-artifact-root> --output <directory>
|
|
286
|
+
[--config-file <json-file>]` (see `docs/COMMANDS.md`). The CLI stays thin
|
|
287
|
+
- `src/cli.ts` parses arguments, optionally loads a config file (file
|
|
288
|
+
readability/JSON validity/object-root only, exactly like
|
|
289
|
+
`--targets-file`/`--scroll-scenario-file`), and delegates to one new
|
|
290
|
+
thin application-layer orchestration function,
|
|
291
|
+
`compareAndPersistFromArtifactRoots`
|
|
292
|
+
(`src/application/comparisonService.ts`), which reads both observation
|
|
293
|
+
roots through the existing `readObservationArtifact` reader and calls the
|
|
294
|
+
existing `compareAndPersist` exactly once - no comparability/geometry/
|
|
295
|
+
relationship/dependency logic lives in the CLI, and comparison itself
|
|
296
|
+
never launches Chromium (`src/cli.ts` still imports nothing from
|
|
297
|
+
`src/artifacts/` or `src/browser/`, matching the pre-existing observe-CLI
|
|
298
|
+
import-boundary test). `comparable`, `comparable-with-warnings`, and
|
|
299
|
+
`incomparable` all exit `0` - each is a successful comparison outcome;
|
|
300
|
+
only a genuine parse/read/domain/persistence failure exits nonzero.
|
|
301
|
+
Operational paths (`--before`/`--after`/`--config-file`/`--output`) never
|
|
302
|
+
affect `comparisonRequestId` and are never written into the persisted
|
|
303
|
+
manifest. Proven end-to-end via real Chromium
|
|
304
|
+
(`tests/browser/cliCompare.test.ts`) and the built `dist/cli.js`
|
|
305
|
+
(`scripts/dev/builtCliCompareSmoke.mjs`): unchanged/moved/resized/
|
|
306
|
+
appeared/disappeared/configuration-only-change/overlap/geometric-fit/
|
|
307
|
+
page-overflow/clipping/scroll-owner cases, plus an explicit
|
|
308
|
+
`--config-file` dependency-evidence case, all through the public command
|
|
309
|
+
surface.
|
|
310
|
+
|
|
311
|
+
v0.4's canonical relationship derivation, before/after comparison,
|
|
312
|
+
comparability, differences, relationship changes, explicit dependency
|
|
313
|
+
evidence, comparison persistence, and public `compare` CLI are all
|
|
314
|
+
implemented, exercised end-to-end, packed-validated cross-platform, and
|
|
315
|
+
released.
|
|
316
|
+
|
|
178
317
|
## Not implemented
|
|
179
318
|
|
|
180
|
-
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
unimplemented.
|
|
185
|
-
- No v0.3–v0.10 capability is implemented.
|
|
319
|
+
- v0.5+ frontend contracts/change scope (baseline approval, requested/
|
|
320
|
+
protected/preserved change scope, PASS/FAIL verdicts), source ownership,
|
|
321
|
+
my-dev-kit runtime/static integration, orchestrator/lab product
|
|
322
|
+
integration, viewer, and annotation all remain unimplemented.
|
|
186
323
|
|
|
187
324
|
## Next target
|
|
188
325
|
|
|
189
|
-
v0.
|
|
190
|
-
|
|
326
|
+
v0.1-v0.4 are implemented, validated, and released (`0.1.0`, `0.2.0`,
|
|
327
|
+
`0.3.0`, `0.4.0`). v0.5 (Executable Frontend Contracts and Explicit Change
|
|
328
|
+
Scope) is next.
|