reflection-check 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/adapters/route-manifest.d.ts +3 -0
- package/dist/adapters/route-manifest.js +98 -0
- package/dist/adapters/route-manifest.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +93 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/doctor.d.ts +4 -0
- package/dist/commands/doctor.js +5 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/gc.d.ts +8 -0
- package/dist/commands/gc.js +45 -0
- package/dist/commands/gc.js.map +1 -0
- package/dist/commands/review.d.ts +7 -0
- package/dist/commands/review.js +149 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/run.d.ts +10 -0
- package/dist/commands/run.js +168 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/update.d.ts +11 -0
- package/dist/commands/update.js +183 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/contracts/browser/assertions.d.ts +34 -0
- package/dist/contracts/browser/assertions.js +87 -0
- package/dist/contracts/browser/assertions.js.map +1 -0
- package/dist/contracts/browser/browser-contract.d.ts +13 -0
- package/dist/contracts/browser/browser-contract.js +35 -0
- package/dist/contracts/browser/browser-contract.js.map +1 -0
- package/dist/contracts/browser/console-observer.d.ts +6 -0
- package/dist/contracts/browser/console-observer.js +14 -0
- package/dist/contracts/browser/console-observer.js.map +1 -0
- package/dist/contracts/browser/overflow-check.d.ts +6 -0
- package/dist/contracts/browser/overflow-check.js +15 -0
- package/dist/contracts/browser/overflow-check.js.map +1 -0
- package/dist/contracts/browser/route-runner.d.ts +21 -0
- package/dist/contracts/browser/route-runner.js +98 -0
- package/dist/contracts/browser/route-runner.js.map +1 -0
- package/dist/contracts/component/component-visual-contract.d.ts +30 -0
- package/dist/contracts/component/component-visual-contract.js +147 -0
- package/dist/contracts/component/component-visual-contract.js.map +1 -0
- package/dist/contracts/design/command-adapter.d.ts +17 -0
- package/dist/contracts/design/command-adapter.js +60 -0
- package/dist/contracts/design/command-adapter.js.map +1 -0
- package/dist/contracts/design/design-contract.d.ts +8 -0
- package/dist/contracts/design/design-contract.js +149 -0
- package/dist/contracts/design/design-contract.js.map +1 -0
- package/dist/contracts/visual/baseline-compare.d.ts +19 -0
- package/dist/contracts/visual/baseline-compare.js +94 -0
- package/dist/contracts/visual/baseline-compare.js.map +1 -0
- package/dist/contracts/visual/image-diff.d.ts +27 -0
- package/dist/contracts/visual/image-diff.js +58 -0
- package/dist/contracts/visual/image-diff.js.map +1 -0
- package/dist/contracts/visual/thresholds.d.ts +15 -0
- package/dist/contracts/visual/thresholds.js +11 -0
- package/dist/contracts/visual/thresholds.js.map +1 -0
- package/dist/contracts/visual/visual-contract.d.ts +11 -0
- package/dist/contracts/visual/visual-contract.js +32 -0
- package/dist/contracts/visual/visual-contract.js.map +1 -0
- package/dist/core/artifact-store.d.ts +18 -0
- package/dist/core/artifact-store.js +105 -0
- package/dist/core/artifact-store.js.map +1 -0
- package/dist/core/baseline-store.d.ts +18 -0
- package/dist/core/baseline-store.js +56 -0
- package/dist/core/baseline-store.js.map +1 -0
- package/dist/core/config.d.ts +129 -0
- package/dist/core/config.js +159 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/define-reflection.d.ts +2 -0
- package/dist/core/define-reflection.js +4 -0
- package/dist/core/define-reflection.js.map +1 -0
- package/dist/core/exit-codes.d.ts +7 -0
- package/dist/core/exit-codes.js +9 -0
- package/dist/core/exit-codes.js.map +1 -0
- package/dist/core/failure-classifier.d.ts +3 -0
- package/dist/core/failure-classifier.js +19 -0
- package/dist/core/failure-classifier.js.map +1 -0
- package/dist/core/gc.d.ts +19 -0
- package/dist/core/gc.js +161 -0
- package/dist/core/gc.js.map +1 -0
- package/dist/core/manifest.d.ts +23 -0
- package/dist/core/manifest.js +21 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/core/redaction.d.ts +3 -0
- package/dist/core/redaction.js +63 -0
- package/dist/core/redaction.js.map +1 -0
- package/dist/core/report-schema.d.ts +262 -0
- package/dist/core/report-schema.js +112 -0
- package/dist/core/report-schema.js.map +1 -0
- package/dist/core/report-writer.d.ts +4 -0
- package/dist/core/report-writer.js +77 -0
- package/dist/core/report-writer.js.map +1 -0
- package/dist/core/server-manager.d.ts +23 -0
- package/dist/core/server-manager.js +64 -0
- package/dist/core/server-manager.js.map +1 -0
- package/dist/core/target-ir.d.ts +64 -0
- package/dist/core/target-ir.js +85 -0
- package/dist/core/target-ir.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/playwright/browser-manager.d.ts +2 -0
- package/dist/integrations/playwright/browser-manager.js +5 -0
- package/dist/integrations/playwright/browser-manager.js.map +1 -0
- package/dist/integrations/playwright/context-factory.d.ts +7 -0
- package/dist/integrations/playwright/context-factory.js +19 -0
- package/dist/integrations/playwright/context-factory.js.map +1 -0
- package/dist/integrations/playwright/trace-policy.d.ts +5 -0
- package/dist/integrations/playwright/trace-policy.js +7 -0
- package/dist/integrations/playwright/trace-policy.js.map +1 -0
- package/dist/integrations/storybook/index-json.d.ts +21 -0
- package/dist/integrations/storybook/index-json.js +44 -0
- package/dist/integrations/storybook/index-json.js.map +1 -0
- package/dist/integrations/storybook/server.d.ts +8 -0
- package/dist/integrations/storybook/server.js +23 -0
- package/dist/integrations/storybook/server.js.map +1 -0
- package/dist/integrations/storybook/story-url.d.ts +2 -0
- package/dist/integrations/storybook/story-url.js +13 -0
- package/dist/integrations/storybook/story-url.js.map +1 -0
- package/dist/utils/process.d.ts +9 -0
- package/dist/utils/process.js +69 -0
- package/dist/utils/process.js.map +1 -0
- package/docs/agent-workflows.md +146 -0
- package/docs/artifacts-and-gc.md +125 -0
- package/docs/browser-contract.md +98 -0
- package/docs/ci.md +44 -0
- package/docs/configuration.md +210 -0
- package/docs/getting-started.md +166 -0
- package/docs/plans/reflection-implementation-plan.md +898 -0
- package/docs/target-ir-and-adapters.md +111 -0
- package/docs/validation-process.md +172 -0
- package/docs/visual-contract.md +174 -0
- package/package.json +62 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Target IR and adapters
|
|
2
|
+
|
|
3
|
+
Reflection keeps external input formats separate from the contract runners by compiling them into a small Target IR: a normalized inventory of things Reflection can validate.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
Reflection config optional adapters
|
|
7
|
+
│ │
|
|
8
|
+
└──────────┬─────────┘
|
|
9
|
+
▼
|
|
10
|
+
Target IR
|
|
11
|
+
▼
|
|
12
|
+
contract runners / reports
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Why Target IR exists
|
|
16
|
+
|
|
17
|
+
Target IR is the seam between _where validation targets come from_ and _how Reflection validates them_. The current Reflection config is one source. Future integrations can compile their own manifests into the same shape without making browser, visual, component, or design runners depend on that integration.
|
|
18
|
+
|
|
19
|
+
This gives us three useful constraints:
|
|
20
|
+
|
|
21
|
+
- **Neutral core:** runner code should work from Reflection concepts, not external product names.
|
|
22
|
+
- **Optional adapters:** adapters can be present or absent without changing normal `reflection run` behavior.
|
|
23
|
+
- **Agent-readable inventory:** agents can inspect a single list of targets and understand the validation surface.
|
|
24
|
+
|
|
25
|
+
## Current target families
|
|
26
|
+
|
|
27
|
+
The IR currently supports four families:
|
|
28
|
+
|
|
29
|
+
| Family | Source contract | Typical run modes | Purpose |
|
|
30
|
+
| --- | --- | --- | --- |
|
|
31
|
+
| `browser-route` | browser routes | `smoke`, `full` | Render a route and evaluate DOM/layout/console expectations. |
|
|
32
|
+
| `route-visual` | browser visual smoke cases | `smoke`, `full` | Compare route screenshots against read-only baselines. |
|
|
33
|
+
| `component-visual` | Storybook component cases | `visual`, `full` | Compare component story screenshots against read-only baselines. |
|
|
34
|
+
| `design-command` | design command checks | `design`, `full` | Run project-owned design contract commands. |
|
|
35
|
+
|
|
36
|
+
All targets include:
|
|
37
|
+
|
|
38
|
+
- `id` — stable target id.
|
|
39
|
+
- `family` — one of the target families above.
|
|
40
|
+
- `source` — where the target came from, for example `reflection-config` or `adapter`.
|
|
41
|
+
- `runModes` — modes where the target is relevant.
|
|
42
|
+
- `blocking` — whether a failing target should block the run.
|
|
43
|
+
|
|
44
|
+
## Reflection config compiler
|
|
45
|
+
|
|
46
|
+
`compileReflectionTargets(config)` compiles the typed Reflection config into Target IR.
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
const ir = compileReflectionTargets(config);
|
|
52
|
+
|
|
53
|
+
console.log(ir.targets.map((target) => `${target.family}:${target.id}`));
|
|
54
|
+
// [
|
|
55
|
+
// 'browser-route:login',
|
|
56
|
+
// 'route-visual:login-mobile',
|
|
57
|
+
// 'component-visual:button-primary',
|
|
58
|
+
// 'design-command:tokens'
|
|
59
|
+
// ]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The compiler preserves visual metadata that matters to later review, including zero-valued thresholds. Do not use truthiness checks when copying optional numeric metadata; use explicit `!== undefined` checks so values like `0` survive.
|
|
63
|
+
|
|
64
|
+
## Adapter contract
|
|
65
|
+
|
|
66
|
+
Adapters should compile their input into the same target shape and mark targets with `source: 'adapter'`.
|
|
67
|
+
|
|
68
|
+
Adapters must not make core runners aware of the source format. The runner should receive normalized route/story/visual/command information and should not branch on adapter names.
|
|
69
|
+
|
|
70
|
+
A good adapter is:
|
|
71
|
+
|
|
72
|
+
- **optional** — normal Reflection config works without it;
|
|
73
|
+
- **validated** — malformed adapter input fails before runner execution;
|
|
74
|
+
- **neutral** — no external product names leak into core commands or reports unless the user explicitly names their own target ids;
|
|
75
|
+
- **lossless enough for review** — route paths, viewports, expectations, baselines, thresholds, and blocking semantics are preserved in IR.
|
|
76
|
+
|
|
77
|
+
## Route manifest adapter proof
|
|
78
|
+
|
|
79
|
+
Phase 6.2 adds a fixture JSON route-manifest adapter in `src/adapters/route-manifest.ts`. Its job is deliberately narrow: prove that an external manifest can become browser-route Target IR without changing the browser runner.
|
|
80
|
+
|
|
81
|
+
The manifest shape is:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"project": "example-app",
|
|
86
|
+
"baseUrl": "http://127.0.0.1:5173",
|
|
87
|
+
"routes": [
|
|
88
|
+
{
|
|
89
|
+
"id": "login",
|
|
90
|
+
"path": "/login",
|
|
91
|
+
"viewports": ["desktop", "mobile"],
|
|
92
|
+
"expects": [{ "role": "heading", "name": "Welcome" }],
|
|
93
|
+
"blocking": true
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
That adapter emits `browser-route` targets using the generic Target IR. It does not introduce a new runner or a new user-facing external product concept.
|
|
100
|
+
|
|
101
|
+
Use the pure parser for already-loaded JSON:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const ir = parseRouteManifestTargets(routeManifestJson);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Use the loader when the manifest lives on disk:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const ir = await loadRouteManifestTargets('routes.json');
|
|
111
|
+
```
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Reflection validation process
|
|
2
|
+
|
|
3
|
+
Reflection is meant to be a validation protocol that humans, agents, and CI can run without guessing what changed or whether visual evidence is safe to accept.
|
|
4
|
+
|
|
5
|
+
The command split is intentional:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
reflection doctor # lightweight CLI/setup check; project contracts run through --config
|
|
9
|
+
reflection run # produce evidence and report.json; pass --config for project contracts
|
|
10
|
+
reflection review # summarize the latest evidence for humans/agents
|
|
11
|
+
reflection update # accept intentional visual changes; never automatic
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Local validation loop
|
|
15
|
+
|
|
16
|
+
Use this loop before claiming frontend work is complete:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
reflection doctor
|
|
20
|
+
reflection run --config reflection.config.ts --mode smoke
|
|
21
|
+
reflection review --json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
If Reflection is being run from this repository during development, build first and use the local CLI:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm build
|
|
28
|
+
node dist/cli.js doctor
|
|
29
|
+
node dist/cli.js run --config examples/basic-react/reflection.config.ts --mode smoke
|
|
30
|
+
node dist/cli.js review --json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Interpret the review result as the contract:
|
|
34
|
+
|
|
35
|
+
- `status: "pass"` means the validation evidence passed.
|
|
36
|
+
- `status: "pass-with-review"` means blocking checks passed, but review items need a human-readable summary and artifact links.
|
|
37
|
+
- `status: "fail"` means blocking checks failed; fix those before calling the work complete.
|
|
38
|
+
|
|
39
|
+
Always include the `reportPath`, blocking failures, review items, and artifact paths when reporting results to a human.
|
|
40
|
+
|
|
41
|
+
## Agent instructions
|
|
42
|
+
|
|
43
|
+
Agents should treat Reflection as an evidence gate, not as a self-healing tool.
|
|
44
|
+
|
|
45
|
+
Required agent behavior:
|
|
46
|
+
|
|
47
|
+
1. Run `reflection doctor` before the validation flow when setup may be uncertain. It is currently a lightweight setup check; the configured project contract is exercised by `reflection run --config ...`.
|
|
48
|
+
2. Run `reflection run --config reflection.config.ts --mode smoke` to generate current evidence.
|
|
49
|
+
3. Run `reflection review --json` to get the machine-readable summary.
|
|
50
|
+
4. Fix blocking failures before finishing the task.
|
|
51
|
+
5. Summarize review items with artifact paths instead of hiding them.
|
|
52
|
+
6. Never run non-dry `reflection update` without explicit human approval.
|
|
53
|
+
7. Never run `reflection update` in CI.
|
|
54
|
+
|
|
55
|
+
Agents may propose intentional baseline changes with a dry run:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
reflection update --route <routeId> --from-run latest --dry-run
|
|
59
|
+
reflection update --case <routeVisualCaseId> --from-run latest --dry-run
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`reflection update` currently promotes route-level `visualSmoke` baselines. Component visual baselines still require manual review/copy until component baseline promotion is implemented.
|
|
63
|
+
|
|
64
|
+
Only after explicit human approval may an agent run the matching non-dry update:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
reflection update --route <routeId> --from-run latest
|
|
68
|
+
# or
|
|
69
|
+
reflection update --case <routeVisualCaseId> --from-run latest
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
After any non-dry update, inspect the resulting git diff and report exactly which baseline files changed.
|
|
73
|
+
|
|
74
|
+
## CI validation loop
|
|
75
|
+
|
|
76
|
+
CI should generate and publish evidence, but must never update baselines.
|
|
77
|
+
|
|
78
|
+
Recommended CI command shape:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
reflection doctor
|
|
82
|
+
reflection run --ci --config reflection.config.ts --mode smoke
|
|
83
|
+
reflection review --report-dir artifacts/reflection --json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`reflection run --ci` writes to `artifacts/reflection` by default. `reflection review` defaults to `.reflection`, so CI review commands must pass the CI report root explicitly.
|
|
87
|
+
|
|
88
|
+
For this repository's built CLI:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pnpm install --frozen-lockfile
|
|
92
|
+
pnpm build
|
|
93
|
+
node dist/cli.js doctor
|
|
94
|
+
node dist/cli.js run --ci --config examples/basic-react/reflection.config.ts --mode smoke
|
|
95
|
+
node dist/cli.js review --report-dir artifacts/reflection --json
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Upload Reflection run artifacts even on failure so humans and agents can inspect what happened:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
artifacts/reflection/**
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
For local non-CI runs, the default report root remains `.reflection`. If a project config or command uses `--report-dir`, upload and review that explicit report root instead.
|
|
105
|
+
|
|
106
|
+
## Baseline update policy
|
|
107
|
+
|
|
108
|
+
Reflection does not silently heal visual diffs.
|
|
109
|
+
|
|
110
|
+
- Normal `reflection run` must not create or update baselines.
|
|
111
|
+
- `reflection update --dry-run` is safe for agents to use when proposing a change.
|
|
112
|
+
- Non-dry `reflection update` is a human-approved mutation step.
|
|
113
|
+
- CI must treat any `reflection update` attempt as invalid.
|
|
114
|
+
- Prefer targeted updates (`--route` or `--case`) over `--all`.
|
|
115
|
+
- `--all` must be explicit and should be reserved for deliberate broad rebaselines.
|
|
116
|
+
|
|
117
|
+
## Adding Reflection to repository agent files
|
|
118
|
+
|
|
119
|
+
Reflection keeps its canonical operating instructions in this file. Repository-level agent files should carry only a short pointer section so agents discover the validation process without duplicating the whole protocol.
|
|
120
|
+
|
|
121
|
+
Use this file-selection logic when adding Reflection to a repository:
|
|
122
|
+
|
|
123
|
+
1. Look for existing agent instruction files:
|
|
124
|
+
- `AGENTS.md`
|
|
125
|
+
- `CLAUDE.md`
|
|
126
|
+
- `.github/copilot-instructions.md`
|
|
127
|
+
- `copilot-instructions.md`
|
|
128
|
+
2. If one or more of those files already exist, add the Reflection validation section to the existing file or files and do not create another agent instruction file.
|
|
129
|
+
3. If none of those files exist, create `AGENTS.md` and add the Reflection validation section there.
|
|
130
|
+
4. Keep the canonical process in `docs/validation-process.md`; the agent instruction file should point here instead of copying this full guide.
|
|
131
|
+
|
|
132
|
+
Suggested section:
|
|
133
|
+
|
|
134
|
+
````markdown
|
|
135
|
+
## Reflection validation
|
|
136
|
+
|
|
137
|
+
Use Reflection as the UI evidence gate before claiming frontend work is complete.
|
|
138
|
+
|
|
139
|
+
Run:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
reflection doctor
|
|
143
|
+
reflection run --config reflection.config.ts --mode smoke
|
|
144
|
+
reflection review --json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Rules:
|
|
148
|
+
|
|
149
|
+
- Treat blocking failures as task blockers.
|
|
150
|
+
- Summarize review items with artifact paths for the human.
|
|
151
|
+
- Use `reflection update --route <routeId> --from-run latest --dry-run` only to propose intentional visual changes.
|
|
152
|
+
- Do not run non-dry `reflection update` unless the human explicitly approves it.
|
|
153
|
+
- Never run `reflection update` in CI.
|
|
154
|
+
|
|
155
|
+
Full protocol: `docs/validation-process.md`.
|
|
156
|
+
````
|
|
157
|
+
|
|
158
|
+
Do not duplicate the full protocol into repository-level agent files. Keep the detailed process here and add only the pointer section above to the selected existing agent file, or to a new `AGENTS.md` when no supported agent instruction file exists.
|
|
159
|
+
|
|
160
|
+
## Minimum completion evidence
|
|
161
|
+
|
|
162
|
+
When reporting a completed Reflection validation run, include:
|
|
163
|
+
|
|
164
|
+
- command(s) run
|
|
165
|
+
- pass/fail status
|
|
166
|
+
- report path
|
|
167
|
+
- blocking failures, if any
|
|
168
|
+
- review items, if any
|
|
169
|
+
- artifact paths relevant to changed or failing UI
|
|
170
|
+
- whether any baseline update was dry-run only or human-approved non-dry
|
|
171
|
+
|
|
172
|
+
A good final agent message should make the evidence easy to inspect without requiring the human to re-run commands immediately.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Visual contract
|
|
2
|
+
|
|
3
|
+
The visual contract compares current screenshots against approved baseline images. It is deliberately review-first: visual diffs are review-only by default, and baseline updates are explicit human-approved mutations.
|
|
4
|
+
|
|
5
|
+
Reflection currently supports two visual surfaces:
|
|
6
|
+
|
|
7
|
+
- route-level visual smoke cases from browser route screenshots;
|
|
8
|
+
- Storybook component visual cases.
|
|
9
|
+
|
|
10
|
+
## Evidence screenshots vs baselines
|
|
11
|
+
|
|
12
|
+
Keep these concepts separate:
|
|
13
|
+
|
|
14
|
+
| Concept | Location | Created by | Meaning |
|
|
15
|
+
| --- | --- | --- | --- |
|
|
16
|
+
| Evidence screenshot | `.reflection/runs/<run-id>/**` or configured report root | `reflection run` | What the current run rendered. Safe to regenerate. |
|
|
17
|
+
| Baseline image | Configured `baselineRoot` + `baseline` | `reflection update` after review, or manual fixture setup | Approved reference image. Should be source-controlled when intentional. |
|
|
18
|
+
| Diff image | `.reflection/runs/<run-id>/visual/**/diff.png` | `reflection run` during comparison | Highlight of pixels that differ between evidence and baseline. |
|
|
19
|
+
|
|
20
|
+
Normal runs never mutate baselines.
|
|
21
|
+
|
|
22
|
+
## Review-only default
|
|
23
|
+
|
|
24
|
+
A visual mismatch or missing baseline is review-only unless the case opts into strict/blocking behavior.
|
|
25
|
+
|
|
26
|
+
Use review-only defaults while adopting Reflection:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
visualSmoke: [
|
|
30
|
+
{
|
|
31
|
+
id: 'home-mobile',
|
|
32
|
+
route: 'home',
|
|
33
|
+
viewport: 'mobile',
|
|
34
|
+
baselineRoot: 'tests/fixtures/baselines',
|
|
35
|
+
baseline: 'browser/home/mobile.chromium-linux.light.png'
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Promote a visual case to blocking only when the baseline is stable and the team wants CI/task completion to fail on visual drift:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
{
|
|
44
|
+
id: 'home-mobile',
|
|
45
|
+
route: 'home',
|
|
46
|
+
viewport: 'mobile',
|
|
47
|
+
baseline: 'browser/home/mobile.chromium-linux.light.png',
|
|
48
|
+
strict: true
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`strict: true` or `blocking: true` makes a failing visual comparison blocking. Otherwise it produces `pass-with-review` so the artifact can be inspected without hiding the change.
|
|
53
|
+
|
|
54
|
+
## Route-level visual smoke
|
|
55
|
+
|
|
56
|
+
Route visual smoke cases reuse browser route screenshots. A case must point at a configured route id and viewport:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
browser: {
|
|
60
|
+
routes: [
|
|
61
|
+
{
|
|
62
|
+
id: 'login',
|
|
63
|
+
path: '/login',
|
|
64
|
+
viewports: ['mobile'],
|
|
65
|
+
expects: [{ screenshot: 'final' }]
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
visualSmoke: [
|
|
69
|
+
{
|
|
70
|
+
id: 'login-mobile',
|
|
71
|
+
route: 'login',
|
|
72
|
+
viewport: 'mobile',
|
|
73
|
+
baselineRoot: 'tests/fixtures/baselines',
|
|
74
|
+
baseline: 'browser/login/mobile.chromium-linux.light.png',
|
|
75
|
+
threshold: { maxDiffPixelRatio: 0.01 }
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
If the matching browser route result is missing, the visual check becomes a review item by default or a blocking failure when strict/blocking is enabled.
|
|
82
|
+
|
|
83
|
+
## Component visual baselines
|
|
84
|
+
|
|
85
|
+
Component visual cases use Storybook. Reflection resolves the configured `storyId` through Storybook `/index.json`, opens the iframe URL, captures a screenshot, and compares it with the configured baseline.
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
component: {
|
|
89
|
+
storybook: {
|
|
90
|
+
command: 'pnpm storybook --host 127.0.0.1 --port 6006',
|
|
91
|
+
readyUrl: 'http://127.0.0.1:6006',
|
|
92
|
+
reuseExisting: true,
|
|
93
|
+
timeoutMs: 60_000
|
|
94
|
+
},
|
|
95
|
+
cases: [
|
|
96
|
+
{
|
|
97
|
+
id: 'button-primary',
|
|
98
|
+
storyId: 'atoms-button--primary',
|
|
99
|
+
viewport: 'component',
|
|
100
|
+
baselineRoot: 'tests/fixtures/baselines',
|
|
101
|
+
baseline: 'components/button/primary.chromium-linux.light.png',
|
|
102
|
+
threshold: { maxDiffPixelRatio: 0.005 }
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Pseudo states
|
|
109
|
+
|
|
110
|
+
Prefer story-controlled states. A story named `Button/Hover` or `Button/Focused` is usually more deterministic than moving the mouse in the browser.
|
|
111
|
+
|
|
112
|
+
When the browser must force a state, Reflection requires effective animation stabilization:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
{
|
|
116
|
+
id: 'button-hover',
|
|
117
|
+
storyId: 'atoms-button--primary',
|
|
118
|
+
baseline: 'components/button/hover.chromium-linux.light.png',
|
|
119
|
+
stateNote: 'Browser-forced hover until a story-controlled hover variant exists.',
|
|
120
|
+
browserState: {
|
|
121
|
+
kind: 'hover',
|
|
122
|
+
selector: 'button',
|
|
123
|
+
animationStabilization: {
|
|
124
|
+
disableAnimations: true
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Reports include `statePolicy` metadata:
|
|
131
|
+
|
|
132
|
+
- `story-controlled` when no `browserState` is configured;
|
|
133
|
+
- `browser-forced-with-stabilization` when Reflection applies hover/focus in the browser.
|
|
134
|
+
|
|
135
|
+
## Thresholds
|
|
136
|
+
|
|
137
|
+
Visual thresholds support:
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
threshold: {
|
|
141
|
+
maxDiffPixels: 25,
|
|
142
|
+
maxDiffPixelRatio: 0.01
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`maxDiffPixelRatio: 0` is valid and means exact pixel matching. Reflection preserves zero-valued thresholds intentionally.
|
|
147
|
+
|
|
148
|
+
## Updating baselines
|
|
149
|
+
|
|
150
|
+
Always inspect artifacts first:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
reflection review --json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Then dry-run a targeted update:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
reflection update --route login --from-run latest --dry-run
|
|
160
|
+
reflection update --case login-mobile --from-run latest --dry-run
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Only after explicit human approval:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
reflection update --route login --from-run latest
|
|
167
|
+
reflection update --case login-mobile --from-run latest
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`reflection update` currently promotes route-level `visualSmoke` baselines. For component visual cases, inspect the run artifacts and copy an approved `actual.png` to the configured component baseline path manually until targeted component baseline promotion is implemented.
|
|
171
|
+
|
|
172
|
+
After a non-dry update, inspect the git diff and report exactly which baseline files changed.
|
|
173
|
+
|
|
174
|
+
CI must never update baselines. `reflection update --ci` is refused.
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reflection-check",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "CLI for evidence-backed rendered UI validation.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"bin": {
|
|
16
|
+
"reflection": "dist/cli.js",
|
|
17
|
+
"reflection-check": "dist/cli.js"
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"docs",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"keywords": [
|
|
29
|
+
"ui-validation",
|
|
30
|
+
"playwright",
|
|
31
|
+
"visual-regression",
|
|
32
|
+
"design-systems",
|
|
33
|
+
"developer-tools"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.json",
|
|
37
|
+
"check": "pnpm typecheck && pnpm test && pnpm build",
|
|
38
|
+
"prepack": "pnpm build",
|
|
39
|
+
"smoke:package": "node scripts/smoke-package-install.mjs",
|
|
40
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"reflection": "pnpm build && node dist/cli.js"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"commander": "^14.0.3",
|
|
46
|
+
"jiti": "^2.7.0",
|
|
47
|
+
"pixelmatch": "^7.2.0",
|
|
48
|
+
"pngjs": "^7.0.0",
|
|
49
|
+
"playwright": "^1.60.0",
|
|
50
|
+
"zod": "^4.4.3"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/node": "^25.7.0",
|
|
54
|
+
"@types/pngjs": "^6.0.5",
|
|
55
|
+
"typescript": "^5.6.3",
|
|
56
|
+
"vitest": "^2.1.9"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=22"
|
|
60
|
+
},
|
|
61
|
+
"packageManager": "pnpm@10.12.1"
|
|
62
|
+
}
|