From aa0ae59ef6f8c2f2edfcd11ee75533c032d26e9d Mon Sep 17 00:00:00 2001 From: Kaushik Gnanaskandan Date: Mon, 8 Jun 2026 06:00:16 -0700 Subject: [PATCH 1/3] docs(sprints): publish 3071 RFC 0001 baseline + positioning verdict (#51 A1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Harness self-test passes 10/10 at 0 pixel diff — proves the measurement mechanism is sound. Agent one-shot styling completion is unmeasured: no agent-integration harness exists on main to drive the falsifier's `--mode=measure` path. Conservative default applies — verify is positioned as a self-correction layer (the <85% branch); no Slack alert (threshold neither met nor measurable). Documents the agent-integration harness as the follow-up sprint task that retires this measurement gap. Co-Authored-By: Claude Opus 4.7 --- ...1-rfc-0001-baseline.harness-self-test.json | 83 +++++++++++++++ docs/sprints/3071-rfc-0001-baseline.md | 100 ++++++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 docs/sprints/3071-rfc-0001-baseline.harness-self-test.json create mode 100644 docs/sprints/3071-rfc-0001-baseline.md diff --git a/docs/sprints/3071-rfc-0001-baseline.harness-self-test.json b/docs/sprints/3071-rfc-0001-baseline.harness-self-test.json new file mode 100644 index 0000000..cb2f67b --- /dev/null +++ b/docs/sprints/3071-rfc-0001-baseline.harness-self-test.json @@ -0,0 +1,83 @@ + +> @domscribe/test-fixtures@0.0.1 test:falsifier /tmp/wt-sprint-3071-task-a/packages/domscribe-test-fixtures +> tsx styling/scripts/falsifier.ts + +{ + "mode": "self-test", + "total": 10, + "passes": 10, + "fails": 0, + "oneShotRate": 1, + "annotations": [ + { + "id": "A001", + "fixture": "tailwind", + "passed": true, + "pixelDiffRatio": 0, + "diffPixels": 0 + }, + { + "id": "A002", + "fixture": "tailwind", + "passed": true, + "pixelDiffRatio": 0, + "diffPixels": 0 + }, + { + "id": "A003", + "fixture": "tailwind", + "passed": true, + "pixelDiffRatio": 0, + "diffPixels": 0 + }, + { + "id": "A004", + "fixture": "tailwind", + "passed": true, + "pixelDiffRatio": 0, + "diffPixels": 0 + }, + { + "id": "A005", + "fixture": "tailwind", + "passed": true, + "pixelDiffRatio": 0, + "diffPixels": 0 + }, + { + "id": "A101", + "fixture": "styled", + "passed": true, + "pixelDiffRatio": 0, + "diffPixels": 0 + }, + { + "id": "A102", + "fixture": "styled", + "passed": true, + "pixelDiffRatio": 0, + "diffPixels": 0 + }, + { + "id": "A103", + "fixture": "styled", + "passed": true, + "pixelDiffRatio": 0, + "diffPixels": 0 + }, + { + "id": "A104", + "fixture": "styled", + "passed": true, + "pixelDiffRatio": 0, + "diffPixels": 0 + }, + { + "id": "A105", + "fixture": "styled", + "passed": true, + "pixelDiffRatio": 0, + "diffPixels": 0 + } + ] +} diff --git a/docs/sprints/3071-rfc-0001-baseline.md b/docs/sprints/3071-rfc-0001-baseline.md new file mode 100644 index 0000000..911b64d --- /dev/null +++ b/docs/sprints/3071-rfc-0001-baseline.md @@ -0,0 +1,100 @@ +# Sprint 3071 — RFC 0001 baseline + positioning verdict + +**Author:** Staff SWE (sprint run 3071, issue #51) +**Date:** 2026-06-08 +**Decides:** the positioning language for `verify_after_edit` (RFC 0002). +**Does not decide:** whether to ship `verify_after_edit` — that bet is made by the DOP memo and RFC 0002; this doc only sequences how it is framed. + +--- + +## TL;DR + +| Quantity | Value | Source | +| ----------------------------------------------------------- | --------------------------------------- | ----------------------------------------------- | +| RFC 0001 falsifier (≥70% agent one-shot styling completion) | **unmeasured** | no agent-integration harness exists on `main` | +| RFC 0001 mechanism self-test | **10/10 (100%), 0 pixel diff** | `styling/scripts/falsifier.ts --mode=self-test` | +| Positioning verdict | **self-correction layer (<85% branch)** | conservative default in absence of measurement | +| Slack alert (≥85% trigger) | **not posted** | threshold neither met nor measurable | + +The lift of the comparator into `@domscribe/verify` (this PR, Task A3) is independently validated by the self-test: the harness re-imports the comparator and continues to grade all 10 baselines at 0 pixel diff. + +## What the harness can measure today + +The RFC 0001 falsifier harness (`packages/domscribe-test-fixtures/styling/scripts/falsifier.ts`) supports three modes: + +1. **`self-test`** — builds the Tailwind and styled-components fixture apps, screenshots each annotation's `afterRoute`, and diffs against the committed baseline. Expected pass rate is **100% by design** — this is the harness's own correctness check, not a measurement of agent capability. The README is explicit: + + > It does not invoke an agent. The agent-integration loop is built on top of this — see `--mode=measure`. + +2. **`record`** — re-captures the baseline PNGs from the canonical `/after` routes. + +3. **`measure --agent-output=`** — production grading: reads one screenshot per annotation from an external directory (produced by an agent-integration harness) and diffs against the baseline. **This is the mode that would actually answer "what is the agent's one-shot styling completion rate?"** + +## What is missing + +The agent-integration loop required to run `--mode=measure` does **not** exist on `main`. Specifically, there is no harness that: + +- Reads each annotation from `styling/annotations.json`, +- Drives an agent (Claude / Codex / similar) through the edit using the intent + source-file context, +- Boots the fixture from the post-edit source, +- Screenshots the rendered element into a per-annotation PNG, +- Hands the directory to `falsifier.ts --mode=measure`. + +Until that loop exists, the inherited RFC 0001 falsifier (≥70% one-shot agent styling completion by sprint 2734+6) is **unmeasured**. The self-test pass rate is structurally **not** a substitute — the self-test screenshots the canonical-after route, not an agent's edit, so it cannot fall below 100% no matter how poorly an agent would perform. + +## Self-test result (mechanism-only) + +``` +mode=self-test, total=10, passes=10, fails=0, oneShotRate=1.0 +all annotations: pixelDiffRatio=0, diffPixels=0 +``` + +Raw JSON: [`3071-rfc-0001-baseline.harness-self-test.json`](./3071-rfc-0001-baseline.harness-self-test.json). + +The 100% pass rate means: + +- The Vite build for both fixtures is reproducible. +- Chromium + screenshot capture is locale/font/viewport-deterministic in this CI environment. +- The lifted comparator in `@domscribe/verify` (this PR) diffs identically to the inline version it replaces — none of the 10 baseline diffs shifted off zero. + +The 100% pass rate **does not mean** the agent's one-shot styling completion rate is 100%. That number is unknown. + +## Methodology + +- **Where:** ephemeral dev sandbox; node v20.19.4; pnpm 9.12.0; playwright 1.58.2 (chrome-headless-shell 1208); locale `en-US`, timezone `UTC`, viewport `800×600`, scale 1, animations disabled (matches the harness defaults). +- **Source:** worktree at `origin/main@a171724` (RFC 0001 Task B merge), plus the `@domscribe/verify` lift introduced by this PR. +- **Command:** `pnpm --filter @domscribe/test-fixtures test:falsifier`. +- **Reproducibility:** the same command on the same commit on a CI runner with the documented Playwright cache returns the same JSON. Re-recording baselines (`--mode=record`) would only be needed if the canonical-after routes or the Chromium build changes. + +## Positioning verdict + +Per RFC 0002 §Implications-for-PM and issue #51, the baseline gates how `verify_after_edit` is framed: + +- **≥85% → trust layer.** Verify catches the long tail; the build is conservative; PM may consider deferring relay registration (Task B) if capacity is tight. +- **<85% → self-correction layer.** Verify is load-bearing for the value loop; the full build proceeds. + +The baseline is unmeasured. The conservative default in the absence of measurement is the **self-correction layer** branch — we cannot justify treating verify as a long-tail polish layer when we have no evidence the short tail is solved. The full build proceeds; the Slack alert (which fires only on ≥85%) is **not** posted. + +## What this means for Task B + +No change. Task B (runtime `ScreenshotCapturer` + relay `verify_after_edit` MCP tool) ships as planned, soft-recommended in MCP prompts, no lifecycle gate. The package-level value of `@domscribe/verify` is independent of the agent one-shot rate — the harness already consumes it, and the relay tool will consume it on the same contract. + +## Follow-up — agent-integration harness (next sprint) + +The cleanest way to retire this measurement gap is to add `--mode=agent` (or a separate driver script under `styling/scripts/`) that: + +1. For each annotation in `annotations.json`, spawns the agent under test with a fixed prompt (intent + sourceFile + sourceLine + the merged RFC 0001 `styleSource` + `componentStyles`). +2. Applies the agent's edit to a scratch copy of the fixture, builds it, screenshots `afterRoute`. +3. Writes `.png` into a deterministic agent-output directory. +4. Invokes the existing `--mode=measure` with that directory. + +This is the prerequisite for measuring both the inherited RFC 0001 falsifier (≥70% one-shot) **and** the RFC 0002 falsifier (≥60% retry-resolution rate). Sized as a separate sprint task; out of scope for issue #51 (per the issue's "Out of scope" enumeration, which lists agent-side work as a P1 follow-up rather than in-scope). + +## References + +- [RFC 0001 — Two-tier component-style attribution](../rfcs/0001-component-styles-capture.md) +- [RFC 0002 — Post-edit verification as an MCP diagnostic tool](../rfcs/0002-post-edit-verify-mcp-tool.md) +- Issue [#51](https://github.com/patchorbit/domscribe/issues/51), Issue [#52](https://github.com/patchorbit/domscribe/issues/52) +- PRs [#49](https://github.com/patchorbit/domscribe/pull/49), [#50](https://github.com/patchorbit/domscribe/pull/50) (RFC 0001 Tasks A and B) +- Harness source: [`packages/domscribe-test-fixtures/styling/scripts/falsifier.ts`](../../packages/domscribe-test-fixtures/styling/scripts/falsifier.ts) +- Harness README: [`packages/domscribe-test-fixtures/styling/README.md`](../../packages/domscribe-test-fixtures/styling/README.md) From 037729c5b63de9b126362975f448f797f3ca686b Mon Sep 17 00:00:00 2001 From: Kaushik Gnanaskandan Date: Mon, 8 Jun 2026 06:00:27 -0700 Subject: [PATCH 2/3] =?UTF-8?q?feat(core):=20VerifyResultSchema=20+=20veri?= =?UTF-8?q?fyHistory;=20ANNOTATION=5FSCHEMA=5FVERSION=202=20=E2=86=92=203?= =?UTF-8?q?=20(#51=20A2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the canonical shape for the RFC 0002 verify_after_edit MCP tool output: - VerifyVerdictSchema: match | partial | no_change | regression. - VerifyResultSchema: verdict + timestamp + structured deltas (componentStylesDelta, computedStyleDelta, boundingRectDelta) + optional pixelDiffRatio, screenshotRef (relay blob ref, never inline bytes — preserves the 4 KB-per-element budget), and notes. - AnnotationContextSchema.verifyHistory: optional VerifyResult[]; older clients silently ignore it. - ANNOTATION_SCHEMA_VERSION bumped 2 → 3; additive-only v2 → v3 migration step registered. Schema additions are deliberately structured (not a binary pass/fail) — the RFC 0002 retry-resolution falsifier requires actionable deltas for the agent to reason about its own edit. All additions are optional; pre-v3 clients see no behaviour change. Co-Authored-By: Claude Opus 4.7 --- .../migrations/annotation-migrations.spec.ts | 27 ++++ .../lib/migrations/annotation-migrations.ts | 10 ++ .../src/lib/types/annotation.spec.ts | 117 +++++++++++++++++ .../src/lib/types/annotation.ts | 123 +++++++++++++++++- 4 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 packages/domscribe-core/src/lib/types/annotation.spec.ts diff --git a/packages/domscribe-core/src/lib/migrations/annotation-migrations.spec.ts b/packages/domscribe-core/src/lib/migrations/annotation-migrations.spec.ts index 27b6171..c389df8 100644 --- a/packages/domscribe-core/src/lib/migrations/annotation-migrations.spec.ts +++ b/packages/domscribe-core/src/lib/migrations/annotation-migrations.spec.ts @@ -141,4 +141,31 @@ describe('migrateAnnotation', () => { expect(result.context.runtimeContext).toBeUndefined(); }); + + it('should migrate a v2 annotation up to v3 (additive verifyHistory, no field rewrite)', () => { + // Simulates a v2 annotation persisted between RFC 0001 (v1→v2) and + // RFC 0002 (v2→v3). The v2 → v3 step is purely additive (verifyHistory + // is a new optional field) — pre-existing runtimeContext data must + // survive untouched. + const raw = buildRawAnnotation({ + metadata: { schemaVersion: 2 }, + context: { + pageUrl: 'http://localhost:3000', + pageTitle: 'Test', + viewport: { width: 1920, height: 1080 }, + userAgent: 'test-agent', + runtimeContext: { + componentStyles: { computed: { padding: '16px' } }, + }, + }, + }); + + const result = migrateAnnotation(raw); + + expect(result.metadata.schemaVersion).toBe(ANNOTATION_SCHEMA_VERSION); + expect(result.context.runtimeContext).toEqual({ + componentStyles: { computed: { padding: '16px' } }, + }); + expect(result.context.verifyHistory).toBeUndefined(); + }); }); diff --git a/packages/domscribe-core/src/lib/migrations/annotation-migrations.ts b/packages/domscribe-core/src/lib/migrations/annotation-migrations.ts index 8ade356..8a851a1 100644 --- a/packages/domscribe-core/src/lib/migrations/annotation-migrations.ts +++ b/packages/domscribe-core/src/lib/migrations/annotation-migrations.ts @@ -31,6 +31,16 @@ const migrationSteps: Record) => void> = 1: () => { // No-op: v1 → v2 is purely additive. }, + /** + * v2 → v3: additive only (per RFC 0002). + * + * v3 adds optional `context.verifyHistory` (an array of `VerifyResult` + * records emitted by the `verify_after_edit` MCP tool). The field is + * absent on v2 payloads, so no field rewriting is required. + */ + 2: () => { + // No-op: v2 → v3 is purely additive. + }, }; /** diff --git a/packages/domscribe-core/src/lib/types/annotation.spec.ts b/packages/domscribe-core/src/lib/types/annotation.spec.ts new file mode 100644 index 0000000..ca08b62 --- /dev/null +++ b/packages/domscribe-core/src/lib/types/annotation.spec.ts @@ -0,0 +1,117 @@ +/** + * Schema tests for RFC 0002 additions to @domscribe/core. + * + * Covers the additive surface: VerifyResultSchema, AnnotationContext.verifyHistory, + * and the v3 schema-version bump. The pre-RFC 0002 annotation shape is exercised + * exhaustively in `annotation-migrations.spec.ts` and the wider integration + * suites; this spec is scoped to the new fields. + */ + +import { describe, it, expect } from 'vitest'; +import { + ANNOTATION_SCHEMA_VERSION, + AnnotationContextSchema, + VerifyResultSchema, + VerifyVerdictSchema, +} from './annotation.js'; + +describe('ANNOTATION_SCHEMA_VERSION', () => { + it('is at v3 (RFC 0002 — verifyHistory)', () => { + expect(ANNOTATION_SCHEMA_VERSION).toBe(3); + }); +}); + +describe('VerifyVerdictSchema', () => { + it.each(['match', 'partial', 'no_change', 'regression'] as const)( + 'accepts %s', + (verdict) => { + expect(VerifyVerdictSchema.parse(verdict)).toBe(verdict); + }, + ); + + it('rejects unknown verdicts', () => { + expect(() => VerifyVerdictSchema.parse('ok')).toThrow(); + }); +}); + +describe('VerifyResultSchema', () => { + it('parses a minimal match result (verdict + timestamp only)', () => { + const parsed = VerifyResultSchema.parse({ + verdict: 'match', + timestamp: '2026-06-08T12:00:00.000Z', + }); + expect(parsed.verdict).toBe('match'); + expect(parsed.componentStylesDelta).toBeUndefined(); + expect(parsed.screenshotRef).toBeUndefined(); + }); + + it('parses a fully-populated partial result with all delta arrays', () => { + const parsed = VerifyResultSchema.parse({ + verdict: 'partial', + timestamp: '2026-06-08T12:00:00.000Z', + pixelDiffRatio: 0.012, + componentStylesDelta: [ + { property: 'padding', before: '16px', after: '24px' }, + ], + computedStyleDelta: [ + { property: 'background-color', before: null, after: 'rgb(0, 0, 0)' }, + ], + boundingRectDelta: [{ field: 'height', before: 32, after: 40 }], + screenshotRef: 'blob://relay/ann_x/post-edit-1.png', + notes: 'padding matched intent; background-color regressed', + }); + expect(parsed.componentStylesDelta).toHaveLength(1); + expect(parsed.boundingRectDelta?.[0]?.field).toBe('height'); + expect(parsed.screenshotRef).toMatch(/^blob:\/\//); + }); + + it('rejects out-of-range pixelDiffRatio', () => { + expect(() => + VerifyResultSchema.parse({ + verdict: 'match', + timestamp: '2026-06-08T12:00:00.000Z', + pixelDiffRatio: 1.5, + }), + ).toThrow(); + }); + + it('rejects unknown BoundingRectDelta fields', () => { + expect(() => + VerifyResultSchema.parse({ + verdict: 'partial', + timestamp: '2026-06-08T12:00:00.000Z', + boundingRectDelta: [ + // @ts-expect-error — runtime rejection is the point + { field: 'depth', before: 0, after: 10 }, + ], + }), + ).toThrow(); + }); +}); + +describe('AnnotationContextSchema.verifyHistory', () => { + it('accepts a context without verifyHistory (older clients silently ignore)', () => { + const parsed = AnnotationContextSchema.parse({ + pageUrl: 'http://localhost:3000', + pageTitle: 'Test', + viewport: { width: 1920, height: 1080 }, + userAgent: 'test-agent', + }); + expect(parsed.verifyHistory).toBeUndefined(); + }); + + it('accepts an append-only history of VerifyResults', () => { + const parsed = AnnotationContextSchema.parse({ + pageUrl: 'http://localhost:3000', + pageTitle: 'Test', + viewport: { width: 1920, height: 1080 }, + userAgent: 'test-agent', + verifyHistory: [ + { verdict: 'partial', timestamp: '2026-06-08T12:00:00.000Z' }, + { verdict: 'match', timestamp: '2026-06-08T12:00:05.000Z' }, + ], + }); + expect(parsed.verifyHistory).toHaveLength(2); + expect(parsed.verifyHistory?.[1]?.verdict).toBe('match'); + }); +}); diff --git a/packages/domscribe-core/src/lib/types/annotation.ts b/packages/domscribe-core/src/lib/types/annotation.ts index e41f38e..ce4ec50 100644 --- a/packages/domscribe-core/src/lib/types/annotation.ts +++ b/packages/domscribe-core/src/lib/types/annotation.ts @@ -103,6 +103,114 @@ export const AnnotationIdSchema = z .regex(PATTERNS.ANNOTATION_ID) .describe('Unique identifier: ann__'); +/** + * Verdict for a single post-edit verification call. + * + * - `match`: the post-edit state matches the user's intent (within the + * visual-diff threshold and the requested computed-style deltas). + * - `partial`: some requested deltas match and some do not — the agent + * should consult `componentStylesDelta` / `computedStyleDelta` and retry. + * - `no_change`: the comparator detected no visual or computed-style delta + * from the pre-edit baseline — the agent's edit did not affect the + * rendered element. Typically a wrong selector or a no-op edit. + * - `regression`: the post-edit state diverged from the baseline in a way + * that did not match the intent — the agent likely broke something. + */ +export const VerifyVerdictSchema = z.enum([ + 'match', + 'partial', + 'no_change', + 'regression', +]); + +/** + * Numeric delta between a baseline value and a post-edit value for a single + * `BoundingRect` field. Recorded only when a non-zero delta is observed. + */ +export const BoundingRectDeltaSchema = z.object({ + field: z + .enum(['x', 'y', 'width', 'height', 'top', 'right', 'bottom', 'left']) + .describe('Which BoundingRect field changed'), + before: z.number().describe('Baseline value (px)'), + after: z.number().describe('Post-edit value (px)'), +}); + +/** + * Delta between baseline and post-edit values for a single style property. + * Used for both `componentStyles.computed` and `selectedElement.computedStyles`. + * Recorded only when the property's resolved value differs. + */ +export const StylePropertyDeltaSchema = z.object({ + property: z.string().describe('CSS property name (e.g. "padding")'), + before: z + .string() + .nullable() + .describe( + 'Baseline resolved value; `null` when the property was absent before', + ), + after: z + .string() + .nullable() + .describe( + 'Post-edit resolved value; `null` when the property is absent after', + ), +}); + +/** + * Result of a single `verify_after_edit` MCP call (RFC 0002). + * + * Returned by the relay's verify tool to the agent so it can reason about + * its own edit on retry rather than re-guessing from source. Shape is + * deliberately structured (not a binary pass/fail) — the falsifier + * (≥60% retry-resolution rate) requires actionable deltas. + * + * All delta fields are optional; the verdict alone may be sufficient when + * the baseline matches exactly. + * + * `screenshotRef` is intentionally a relay-side blob reference, never inline + * bytes — preserves the 4 KB-per-element serialization budget from RFC 0001. + */ +export const VerifyResultSchema = z.object({ + verdict: VerifyVerdictSchema.describe('Overall verification outcome'), + timestamp: z.string().describe('ISO 8601 timestamp of the verify call'), + pixelDiffRatio: z + .number() + .min(0) + .max(1) + .optional() + .describe( + 'Fraction of pixels that differ between the post-edit screenshot and the pre-edit baseline (0 = identical)', + ), + componentStylesDelta: z + .array(StylePropertyDeltaSchema) + .optional() + .describe( + 'Per-property deltas on the runtime `componentStyles.computed` allowlist (RFC 0001)', + ), + computedStyleDelta: z + .array(StylePropertyDeltaSchema) + .optional() + .describe( + 'Per-property deltas on `selectedElement.computedStyles` outside the componentStyles allowlist', + ), + boundingRectDelta: z + .array(BoundingRectDeltaSchema) + .optional() + .describe('Per-field deltas on the element bounding rectangle'), + screenshotRef: z + .string() + .optional() + .describe( + 'Relay-side blob reference for the post-edit element screenshot (never inlined bytes)', + ), + notes: z + .string() + .optional() + .describe( + 'Human-readable reason or comparator note (e.g. "baseline missing")', + ), +}); + /** * Current annotation schema version. Bump when the Annotation shape changes. * @@ -111,8 +219,11 @@ export const AnnotationIdSchema = z * - v2: added optional `runtimeContext.componentStyles` (computed-style * allowlist + CSS custom properties) and optional `styleSource` on * embedded `manifestSnapshot` entries, per RFC 0001. + * - v3: added optional `context.verifyHistory` (an array of `VerifyResult` + * records from `verify_after_edit` MCP calls), per RFC 0002. Older + * clients silently ignore the field. */ -export const ANNOTATION_SCHEMA_VERSION = 2; +export const ANNOTATION_SCHEMA_VERSION = 3; export const AnnotationMetadataSchema = z.object({ id: AnnotationIdSchema, @@ -193,6 +304,12 @@ export const AnnotationContextSchema = z.object({ runtimeContext: RuntimeContextSchema.optional().describe( 'Runtime context (Phase 1 & 2 features)', ), + verifyHistory: z + .array(VerifyResultSchema) + .optional() + .describe( + 'Append-only history of `verify_after_edit` calls for this annotation (RFC 0002). Optional — absent on annotations that were not verified, and silently ignored by pre-v3 clients.', + ), }); export const AgentResponseSchema = z.object({ @@ -240,6 +357,10 @@ export type Viewport = z.infer; export type Environment = z.infer; export type RuntimeContext = z.infer; export type ComponentStyles = z.infer; +export type VerifyVerdict = z.infer; +export type VerifyResult = z.infer; +export type StylePropertyDelta = z.infer; +export type BoundingRectDelta = z.infer; /** * Allowlist of computed-style property names captured by the runtime From 7d4646646f8caee652fb2bbab08dcbb0ba20d5cb Mon Sep 17 00:00:00 2001 From: Kaushik Gnanaskandan Date: Mon, 8 Jun 2026 06:00:39 -0700 Subject: [PATCH 3/3] feat(verify): scaffold @domscribe/verify; lift comparator from RFC 0001 harness (#51 A3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New package owns the pixel-diff comparator shared between the test- fixtures falsifier (RFC 0001) and the upcoming relay verify_after_edit MCP tool (RFC 0002): - Lifted verbatim from styling/scripts/falsifier.ts: PER_PIXEL_THRESHOLD, MAX_DIFF_RATIO, loadPng, diff. Numeric defaults unchanged so the committed baselines stay valid. - compareScreenshots(actual, baseline, { maxDiffRatio? }) wraps the loadPng + diff pair for the common one-shot call site (the RFC 0002 retry round uses a stricter tolerance — opt in per call). - Pure-TS (pixelmatch + pngjs only); no DOM; consumable from Node CI and the relay runtime. - Tagged scope:infra; depends only on @domscribe/core. eslint enforce-module-boundaries: scope:test now permitted to consume scope:infra (test-fixtures is the first such consumer). - @domscribe/test-fixtures swaps inline comparator + pixelmatch/pngjs dev-deps for `@domscribe/verify`. Falsifier self-test rerun: 10/10 passes, 0 pixel diff — lift is behavior-preserving. Co-Authored-By: Claude Opus 4.7 --- eslint.config.mjs | 13 ++ packages/domscribe-test-fixtures/package.json | 7 +- .../styling/scripts/falsifier.ts | 45 +------ packages/domscribe-verify/README.md | 30 +++++ packages/domscribe-verify/package.json | 27 ++++ packages/domscribe-verify/project.json | 49 +++++++ packages/domscribe-verify/src/index.ts | 19 +++ .../src/lib/comparator.spec.ts | 123 ++++++++++++++++++ .../domscribe-verify/src/lib/comparator.ts | 105 +++++++++++++++ packages/domscribe-verify/tsconfig.json | 9 ++ packages/domscribe-verify/tsconfig.lib.json | 40 ++++++ packages/domscribe-verify/tsconfig.spec.json | 35 +++++ packages/domscribe-verify/vite.config.ts | 28 ++++ pnpm-lock.yaml | 35 +++-- tsconfig.base.json | 2 + tsconfig.json | 1 + 16 files changed, 511 insertions(+), 57 deletions(-) create mode 100644 packages/domscribe-verify/README.md create mode 100644 packages/domscribe-verify/package.json create mode 100644 packages/domscribe-verify/project.json create mode 100644 packages/domscribe-verify/src/index.ts create mode 100644 packages/domscribe-verify/src/lib/comparator.spec.ts create mode 100644 packages/domscribe-verify/src/lib/comparator.ts create mode 100644 packages/domscribe-verify/tsconfig.json create mode 100644 packages/domscribe-verify/tsconfig.lib.json create mode 100644 packages/domscribe-verify/tsconfig.spec.json create mode 100644 packages/domscribe-verify/vite.config.ts diff --git a/eslint.config.mjs b/eslint.config.mjs index 1b6ac55..fd1622a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -43,6 +43,19 @@ export default [ 'scope:adapter', ], }, + { + // scope:test consumes the same packages adapters do — it + // grades them. Notably, `@domscribe/test-fixtures` now imports + // `@domscribe/verify` (scope:infra) so the harness and the + // relay verify_after_edit tool share one comparator. + sourceTag: 'scope:test', + onlyDependOnLibsWithTags: [ + 'scope:core', + 'scope:infra', + 'scope:build', + 'scope:adapter', + ], + }, ], }, ], diff --git a/packages/domscribe-test-fixtures/package.json b/packages/domscribe-test-fixtures/package.json index be382e5..f3c5923 100644 --- a/packages/domscribe-test-fixtures/package.json +++ b/packages/domscribe-test-fixtures/package.json @@ -9,13 +9,12 @@ "test:falsifier": "tsx styling/scripts/falsifier.ts", "test:falsifier:record": "tsx styling/scripts/falsifier.ts --mode=record" }, + "dependencies": { + "@domscribe/verify": "workspace:*" + }, "devDependencies": { "@playwright/test": "^1.49.0", - "@types/pixelmatch": "^5.2.6", - "@types/pngjs": "^6.0.5", - "pixelmatch": "^7.1.0", "playwright": "^1.49.0", - "pngjs": "^7.0.0", "tsx": "^4.21.0" } } diff --git a/packages/domscribe-test-fixtures/styling/scripts/falsifier.ts b/packages/domscribe-test-fixtures/styling/scripts/falsifier.ts index 1ef0d39..55ad402 100644 --- a/packages/domscribe-test-fixtures/styling/scripts/falsifier.ts +++ b/packages/domscribe-test-fixtures/styling/scripts/falsifier.ts @@ -46,32 +46,16 @@ import path from 'node:path'; import { spawn, type ChildProcess } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import { chromium, type Browser, type Page } from 'playwright'; -import pixelmatch from 'pixelmatch'; -import { PNG } from 'pngjs'; +import { MAX_DIFF_RATIO, diff, loadPng } from '@domscribe/verify'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const STYLING_ROOT = path.resolve(__dirname, '..'); const BASELINES_ROOT = path.join(STYLING_ROOT, 'baselines'); const ANNOTATIONS_FILE = path.join(STYLING_ROOT, 'annotations.json'); -/** - * Pixel-diff tolerance. - * - * PER_PIXEL_THRESHOLD — pixelmatch's `threshold` (color distance per - * pixel below which two pixels are considered equal). 0.1 is the - * library's recommended starting point; we keep it modest so the - * harness catches real visual deltas but tolerates AA jitter. - * - * MAX_DIFF_RATIO — the fraction of total pixels that may differ before - * we call the annotation a fail. The canonical-after path diffs at 0, - * so this is a defensive floor for CI worker AA jitter on text glyphs. - * 0.1% (0.001) is tight enough that two images that happen to share - * a mostly-white background (a real false-positive risk we observed in - * sanity testing) cannot slip through, while still absorbing a few - * pixels of subpixel font rendering noise. - */ -const PER_PIXEL_THRESHOLD = 0.1; -const MAX_DIFF_RATIO = 0.001; +// Pixel-diff tolerance (`PER_PIXEL_THRESHOLD`, `MAX_DIFF_RATIO`) lives in +// `@domscribe/verify` so the falsifier and the relay verify_after_edit +// MCP tool share one comparator. const VIEWPORT = { width: 800, height: 600 }; @@ -270,27 +254,6 @@ async function screenshotRoute( }); } -function loadPng(buf: Buffer): PNG { - return PNG.sync.read(buf); -} - -function diff(a: PNG, b: PNG): { diffPixels: number; ratio: number } { - if (a.width !== b.width || a.height !== b.height) { - return { - diffPixels: a.width * a.height, - ratio: 1, - }; - } - const out = new PNG({ width: a.width, height: a.height }); - const diffPixels = pixelmatch(a.data, b.data, out.data, a.width, a.height, { - threshold: PER_PIXEL_THRESHOLD, - }); - return { - diffPixels, - ratio: diffPixels / (a.width * a.height), - }; -} - interface BrowserContext { browser: Browser; page: Page; diff --git a/packages/domscribe-verify/README.md b/packages/domscribe-verify/README.md new file mode 100644 index 0000000..affc192 --- /dev/null +++ b/packages/domscribe-verify/README.md @@ -0,0 +1,30 @@ +# @domscribe/verify + +Pure-TS visual-snapshot comparator for Domscribe. + +The comparator is **lifted verbatim** from the RFC 0001 falsifier harness +in `@domscribe/test-fixtures`. It compares two PNG screenshot buffers +pixel-by-pixel via [`pixelmatch`](https://github.com/mapbox/pixelmatch) +and returns a structured delta. No DOM, no browser — runnable from Node +CI **and** the relay runtime. + +## Install + +```bash +npm install @domscribe/verify +``` + +## Note + +Internal package used by `@domscribe/test-fixtures` (the RFC 0001 +styling falsifier) and `@domscribe/relay` (the RFC 0002 +`verify_after_edit` MCP tool). You probably don't need to install this +directly. + +## Links + +Part of [Domscribe](https://github.com/patchorbit/domscribe). + +## License + +MIT diff --git a/packages/domscribe-verify/package.json b/packages/domscribe-verify/package.json new file mode 100644 index 0000000..4b9d5a3 --- /dev/null +++ b/packages/domscribe-verify/package.json @@ -0,0 +1,27 @@ +{ + "name": "@domscribe/verify", + "version": "0.5.2", + "description": "Pure-TS visual-snapshot comparator for Domscribe. Lifted verbatim from the RFC 0001 falsifier harness; powers both the test-fixtures suite and the verify_after_edit MCP tool (RFC 0002).", + "type": "module", + "main": "src/index.ts", + "publishConfig": { + "access": "restricted" + }, + "dependencies": { + "@domscribe/core": "workspace:*", + "pixelmatch": "^7.1.0", + "pngjs": "^7.0.0" + }, + "devDependencies": { + "@types/pixelmatch": "^5.2.6", + "@types/pngjs": "^6.0.5" + }, + "engines": { + "node": ">=20" + }, + "repository": { + "type": "git", + "url": "https://github.com/patchorbit/domscribe.git", + "directory": "packages/domscribe-verify" + } +} diff --git a/packages/domscribe-verify/project.json b/packages/domscribe-verify/project.json new file mode 100644 index 0000000..b9bf83f --- /dev/null +++ b/packages/domscribe-verify/project.json @@ -0,0 +1,49 @@ +{ + "name": "domscribe-verify", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "sourceRoot": "packages/domscribe-verify/src", + "tags": ["scope:infra", "type:lib", "type:test"], + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{workspaceRoot}/dist/packages/domscribe-verify"], + "options": { + "rootDir": "packages/domscribe-verify/src", + "outputPath": "dist/packages/domscribe-verify", + "main": "packages/domscribe-verify/src/index.ts", + "tsConfig": "packages/domscribe-verify/tsconfig.lib.json", + "generatePackageJson": true, + "generateExportsField": true, + "assets": ["packages/domscribe-verify/*.md"] + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"], + "options": { + "eslintConfig": "eslint.config.mjs", + "lintFilePatterns": [ + "packages/domscribe-verify/**/*.ts", + "packages/domscribe-verify/**/*.tsx" + ] + } + }, + "test": { + "executor": "@nx/vitest:test", + "outputs": ["{projectRoot}/test-output"], + "options": { + "config": "packages/domscribe-verify/vite.config.ts" + } + }, + "typecheck": { + "executor": "nx:noop" + }, + "watch-deps": { + "executor": "nx:noop" + }, + "build-deps": { + "executor": "nx:noop" + } + } +} diff --git a/packages/domscribe-verify/src/index.ts b/packages/domscribe-verify/src/index.ts new file mode 100644 index 0000000..8cfa8be --- /dev/null +++ b/packages/domscribe-verify/src/index.ts @@ -0,0 +1,19 @@ +/** + * @domscribe/verify - Pure-TS visual-snapshot comparator for Domscribe. + * + * Originally lifted verbatim from the RFC 0001 falsifier harness in + * `@domscribe/test-fixtures`; productized as a standalone package so the + * harness, the relay (RFC 0002 `verify_after_edit` MCP tool), and any + * downstream CI consumer share one implementation. + * + * @module @domscribe/verify + */ + +export { + MAX_DIFF_RATIO, + PER_PIXEL_THRESHOLD, + compareScreenshots, + diff, + loadPng, +} from './lib/comparator.js'; +export type { DiffResult } from './lib/comparator.js'; diff --git a/packages/domscribe-verify/src/lib/comparator.spec.ts b/packages/domscribe-verify/src/lib/comparator.spec.ts new file mode 100644 index 0000000..b59e810 --- /dev/null +++ b/packages/domscribe-verify/src/lib/comparator.spec.ts @@ -0,0 +1,123 @@ +/** + * Unit tests for the visual-snapshot comparator. + * + * The comparator is the only behaviour @domscribe/verify owns; these tests + * pin the contract the RFC 0001 falsifier and (future) RFC 0002 + * verify_after_edit MCP tool depend on. + */ + +import { describe, it, expect } from 'vitest'; +import { PNG } from 'pngjs'; +import { + MAX_DIFF_RATIO, + PER_PIXEL_THRESHOLD, + compareScreenshots, + diff, + loadPng, +} from './comparator.js'; + +function makePng( + width: number, + height: number, + fill: [number, number, number, number], +): Buffer { + const png = new PNG({ width, height }); + for (let i = 0; i < png.data.length; i += 4) { + png.data[i] = fill[0]; + png.data[i + 1] = fill[1]; + png.data[i + 2] = fill[2]; + png.data[i + 3] = fill[3]; + } + return PNG.sync.write(png); +} + +function paintRect( + buf: Buffer, + rect: { x: number; y: number; w: number; h: number }, + rgba: [number, number, number, number], +): Buffer { + const png = PNG.sync.read(buf); + for (let y = rect.y; y < rect.y + rect.h; y++) { + for (let x = rect.x; x < rect.x + rect.w; x++) { + const i = (y * png.width + x) * 4; + png.data[i] = rgba[0]; + png.data[i + 1] = rgba[1]; + png.data[i + 2] = rgba[2]; + png.data[i + 3] = rgba[3]; + } + } + return PNG.sync.write(png); +} + +describe('comparator constants', () => { + it('keeps the falsifier defaults (PER_PIXEL_THRESHOLD = 0.1, MAX_DIFF_RATIO = 0.001)', () => { + // These defaults are load-bearing: every committed RFC 0001 baseline + // was recorded against them. Bumping them silently would shift CI + // verdicts on already-merged fixture annotations. + expect(PER_PIXEL_THRESHOLD).toBe(0.1); + expect(MAX_DIFF_RATIO).toBe(0.001); + }); +}); + +describe('diff', () => { + it('returns zero diff for two pixel-identical PNGs', () => { + const a = loadPng(makePng(20, 20, [255, 0, 0, 255])); + const b = loadPng(makePng(20, 20, [255, 0, 0, 255])); + expect(diff(a, b)).toEqual({ diffPixels: 0, ratio: 0 }); + }); + + it('reports a small ratio for a localised pixel change', () => { + const baseline = makePng(20, 20, [255, 255, 255, 255]); + const altered = paintRect( + baseline, + { x: 0, y: 0, w: 2, h: 2 }, + [0, 0, 0, 255], + ); + const result = diff(loadPng(altered), loadPng(baseline)); + // 4 changed pixels out of 400 = 1% — well above MAX_DIFF_RATIO. + expect(result.diffPixels).toBe(4); + expect(result.ratio).toBeCloseTo(0.01, 5); + }); + + it('short-circuits to ratio = 1 on dimension mismatch (no throw)', () => { + const a = loadPng(makePng(10, 10, [0, 0, 0, 255])); + const b = loadPng(makePng(20, 20, [0, 0, 0, 255])); + expect(diff(a, b)).toEqual({ diffPixels: 100, ratio: 1 }); + }); +}); + +describe('compareScreenshots', () => { + it('passes when actual equals baseline', () => { + const buf = makePng(20, 20, [10, 20, 30, 255]); + const result = compareScreenshots(buf, buf); + expect(result.passed).toBe(true); + expect(result.ratio).toBe(0); + }); + + it('fails when the change exceeds the default tolerance', () => { + const baseline = makePng(20, 20, [255, 255, 255, 255]); + const altered = paintRect( + baseline, + { x: 0, y: 0, w: 4, h: 4 }, + [0, 0, 0, 255], + ); + const result = compareScreenshots(altered, baseline); + // 16/400 = 4%, well above 0.1% tolerance. + expect(result.passed).toBe(false); + }); + + it('honours a custom maxDiffRatio override', () => { + const baseline = makePng(40, 40, [255, 255, 255, 255]); + // Change 4 pixels out of 1600 = 0.25%. Default tolerance (0.1%) fails; + // a relaxed 1% tolerance passes. + const altered = paintRect( + baseline, + { x: 0, y: 0, w: 2, h: 2 }, + [0, 0, 0, 255], + ); + expect(compareScreenshots(altered, baseline).passed).toBe(false); + expect( + compareScreenshots(altered, baseline, { maxDiffRatio: 0.01 }).passed, + ).toBe(true); + }); +}); diff --git a/packages/domscribe-verify/src/lib/comparator.ts b/packages/domscribe-verify/src/lib/comparator.ts new file mode 100644 index 0000000..ea3c2f8 --- /dev/null +++ b/packages/domscribe-verify/src/lib/comparator.ts @@ -0,0 +1,105 @@ +/** + * Pixel-diff comparator. Lifted verbatim from the RFC 0001 falsifier + * harness in `@domscribe/test-fixtures` (originally + * `styling/scripts/falsifier.ts`). + * + * The numeric defaults (`PER_PIXEL_THRESHOLD`, `MAX_DIFF_RATIO`) and the + * `loadPng` / `diff` semantics are unchanged so the existing styling- + * fixture baselines stay valid and CI behaviour does not shift on this + * package lift. + * + * @module @domscribe/verify/lib/comparator + */ + +import pixelmatch from 'pixelmatch'; +import { PNG } from 'pngjs'; + +/** + * Pixel-diff tolerance. + * + * PER_PIXEL_THRESHOLD — pixelmatch's `threshold` (color distance per + * pixel below which two pixels are considered equal). 0.1 is the + * library's recommended starting point; we keep it modest so the + * harness catches real visual deltas but tolerates AA jitter. + * + * MAX_DIFF_RATIO — the fraction of total pixels that may differ before + * we call the annotation a fail. The canonical-after path diffs at 0, + * so this is a defensive floor for CI worker AA jitter on text glyphs. + * 0.1% (0.001) is tight enough that two images that happen to share + * a mostly-white background (a real false-positive risk we observed in + * sanity testing) cannot slip through, while still absorbing a few + * pixels of subpixel font rendering noise. + */ +export const PER_PIXEL_THRESHOLD = 0.1; +export const MAX_DIFF_RATIO = 0.001; + +/** + * Decode a PNG buffer into a `PNG` instance. Thin wrapper kept exported so + * callers can pre-decode and reuse the structure across multiple diffs. + */ +export function loadPng(buf: Buffer): PNG { + return PNG.sync.read(buf); +} + +/** + * Result of comparing two decoded PNGs. + * + * `diffPixels` is the absolute count of pixels that exceed + * `PER_PIXEL_THRESHOLD`; `ratio` is `diffPixels / totalPixels` in `[0, 1]`. + * + * When the two PNGs have different dimensions the comparator returns + * `ratio = 1` and `diffPixels = a.width * a.height` — i.e. "everything + * differs," matching the falsifier's existing behaviour. + */ +export interface DiffResult { + diffPixels: number; + ratio: number; +} + +/** + * Pixel-diff two decoded PNGs using `pixelmatch` with `PER_PIXEL_THRESHOLD`. + * + * Dimension-mismatched images short-circuit to `ratio = 1` rather than + * throwing, so a baseline-vs-agent-screenshot size mismatch reports as a + * failed verify rather than crashing the harness or the relay tool. + */ +export function diff(a: PNG, b: PNG): DiffResult { + if (a.width !== b.width || a.height !== b.height) { + return { + diffPixels: a.width * a.height, + ratio: 1, + }; + } + const out = new PNG({ width: a.width, height: a.height }); + const diffPixels = pixelmatch(a.data, b.data, out.data, a.width, a.height, { + threshold: PER_PIXEL_THRESHOLD, + }); + return { + diffPixels, + ratio: diffPixels / (a.width * a.height), + }; +} + +/** + * One-shot convenience for the common "compare two screenshots" call site. + * + * Decodes both buffers and runs `diff`. Use the lower-level `loadPng` + + * `diff` pair when the same baseline is compared against many candidates. + * + * Returns the `DiffResult` plus a `passed` flag against the package-default + * `MAX_DIFF_RATIO`. Pass a stricter `maxDiffRatio` (e.g. `0.0001`) to the + * options to tighten on a per-call basis — e.g. the RFC 0002 retry round + * documents a tighter tolerance than the first-attempt round. + */ +export function compareScreenshots( + actual: Buffer, + baseline: Buffer, + options: { maxDiffRatio?: number } = {}, +): DiffResult & { passed: boolean } { + const maxDiffRatio = options.maxDiffRatio ?? MAX_DIFF_RATIO; + const result = diff(loadPng(actual), loadPng(baseline)); + return { + ...result, + passed: result.ratio <= maxDiffRatio, + }; +} diff --git a/packages/domscribe-verify/tsconfig.json b/packages/domscribe-verify/tsconfig.json new file mode 100644 index 0000000..c823650 --- /dev/null +++ b/packages/domscribe-verify/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.lib.json" }, + { "path": "./tsconfig.spec.json" } + ] +} diff --git a/packages/domscribe-verify/tsconfig.lib.json b/packages/domscribe-verify/tsconfig.lib.json new file mode 100644 index 0000000..f35ab9e --- /dev/null +++ b/packages/domscribe-verify/tsconfig.lib.json @@ -0,0 +1,40 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "../../dist/packages/domscribe-verify/tsconfig.tsbuildinfo", + "lib": [ + "es2024", + "ESNext.Array", + "ESNext.Collection", + "ESNext.Iterator", + "ESNext.Promise" + ], + "target": "es2024", + "module": "nodenext", + "moduleResolution": "nodenext" + }, + "include": ["src/**/*.ts"], + "exclude": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "**/dist", + "**/build", + "**/.next", + "**/.turbo", + "**/coverage", + "**/generated", + "**/tmp", + "**/test-output", + "**/.nx" + ] +} diff --git a/packages/domscribe-verify/tsconfig.spec.json b/packages/domscribe-verify/tsconfig.spec.json new file mode 100644 index 0000000..fa98b71 --- /dev/null +++ b/packages/domscribe-verify/tsconfig.spec.json @@ -0,0 +1,35 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": [ + "vitest/globals", + "vitest/importMeta", + "vite/client", + "vitest", + "node" + ], + "tsBuildInfoFile": "../../dist/packages/domscribe-verify/tsconfig.spec.tsbuildinfo", + "module": "nodenext", + "moduleResolution": "nodenext" + }, + "include": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/domscribe-verify/vite.config.ts b/packages/domscribe-verify/vite.config.ts new file mode 100644 index 0000000..4d21373 --- /dev/null +++ b/packages/domscribe-verify/vite.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + name: '@domscribe/verify', + watch: false, + globals: true, + environment: 'node', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + reporters: ['default'], + outputFile: './test-output/vitest/report.json', + coverage: { + enabled: true, + provider: 'v8', + reporter: ['text', 'json-summary'], + reportsDirectory: './test-output/vitest/coverage', + thresholds: { + lines: 0.8, + functions: 0.8, + branches: 0.7, + statements: 0.8, + }, + }, + typecheck: { + tsconfig: './tsconfig.spec.json', + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4b9387..88a1b49 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -295,25 +295,17 @@ importers: version: 12.10.3 packages/domscribe-test-fixtures: + dependencies: + '@domscribe/verify': + specifier: workspace:* + version: link:../domscribe-verify devDependencies: '@playwright/test': specifier: ^1.49.0 version: 1.58.2 - '@types/pixelmatch': - specifier: ^5.2.6 - version: 5.2.6 - '@types/pngjs': - specifier: ^6.0.5 - version: 6.0.5 - pixelmatch: - specifier: ^7.1.0 - version: 7.2.0 playwright: specifier: ^1.49.0 version: 1.58.2 - pngjs: - specifier: ^7.0.0 - version: 7.0.0 tsx: specifier: ^4.21.0 version: 4.21.0 @@ -432,6 +424,25 @@ importers: specifier: ^5.102.0 version: 5.102.0(@swc/core@1.15.8(@swc/helpers@0.5.19)) + packages/domscribe-verify: + dependencies: + '@domscribe/core': + specifier: workspace:* + version: link:../domscribe-core + pixelmatch: + specifier: ^7.1.0 + version: 7.2.0 + pngjs: + specifier: ^7.0.0 + version: 7.0.0 + devDependencies: + '@types/pixelmatch': + specifier: ^5.2.6 + version: 5.2.6 + '@types/pngjs': + specifier: ^6.0.5 + version: 6.0.5 + packages/domscribe-vue: dependencies: '@domscribe/core': diff --git a/tsconfig.base.json b/tsconfig.base.json index abacbdc..2af4fae 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -34,6 +34,8 @@ "@domscribe/transform/*": ["packages/domscribe-transform/src/*"], "@domscribe/manifest": ["packages/domscribe-manifest/src/index.ts"], "@domscribe/manifest/*": ["packages/domscribe-manifest/src/*"], + "@domscribe/verify": ["packages/domscribe-verify/src/index.ts"], + "@domscribe/verify/*": ["packages/domscribe-verify/src/*"], "@domscribe/vue": ["packages/domscribe-vue/src/index.ts"], "@domscribe/vue/*": ["packages/domscribe-vue/src/*"], "@domscribe/nuxt": ["packages/domscribe-nuxt/src/index.ts"], diff --git a/tsconfig.json b/tsconfig.json index acd8100..7acec16 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "references": [ { "path": "./packages/domscribe-core" }, { "path": "./packages/domscribe-manifest" }, + { "path": "./packages/domscribe-verify" }, { "path": "./packages/domscribe-relay" }, { "path": "./packages/domscribe-runtime" }, { "path": "./packages/domscribe-overlay" },