refbrowser: cg-reftest skill + Playwright producer + suite manifests#680
refbrowser: cg-reftest skill + Playwright producer + suite manifests#680softmarshmallow merged 6 commits intomainfrom
Conversation
New HTML/CSS reftest harness. Pairs the existing @grida/reftest diff
tool with a Playwright-Chromium producer for expecteds and the cg
`golden_htmlcss` example for actuals, driven by suite JSONs under
`fixtures/test-html/suites/`.
What landed:
- `.agents/skills/cg-reftest/scripts/refbrowser_render.ts` — new
Playwright producer. Reads a `--suite <path>` JSON, iterates
fixtures, renders each under a fixed viewport with optional
extra-CSS injection.
- `crates/grida-canvas/src/htmlcss/mod.rs` — new public helper
`htmlcss::with_extra_stylesheets(html, &[css])` that injects a
`<style>` block before `</head>` (case-insensitive). Agnostic —
any caller with HTML + extra rules can use it.
- `crates/grida-canvas/examples/golden_htmlcss.rs` — now accepts
`--suite <path>`, resolves extra_css relative to the suite file,
calls `with_extra_stylesheets` so the cascade is symmetric with
Chromium. FontRepository and CSS reads are hoisted/cached once
per run.
- `fixtures/test-html/_reftest/hide-text.css` — shared stylesheet
that zeros glyph coverage (`color: transparent`) and pins
line-box height (`line-height: 1`) so Blink-vs-Skia text
divergence doesn't dominate diffs on non-text fixtures.
- `fixtures/test-html/suites/{L0.exact,L0.coverage}.json` — two
suites. `exact` is the byte-exact gate (floor 1.0); `coverage`
tracks aspirational scope. Promotion rule: fixture reaches
100.00% → move entry from coverage to exact.
- `fixtures/test-html/README.md` — viewport preset list and the
paint-vs-layout authoring rule (paint fixtures force preset size
via `min-height`; layout fixtures measure natural cull).
- `.agents/skills/cg-reftest/SKILL.md` — refbrowser pipeline,
"Reading the score" warning (content-area-relative scoring),
known divergence surfaces (alpha-comp rounding, layout math,
text, AA on curves, gradients, filters/shadows, `%` radius),
WPT see-also, and two "future work" technique sections:
subtree bisection (diff attribution) and viewport sweep
(width-matrix for layout fixtures).
- `.agents/skills/fixtures/SKILL.md` — one-line cross-ref to
`fixtures/test-html/README.md` for the paint-vs-layout rule.
- `packages/grida-reftest/package.json` — devDeps bump for
`@playwright/test` and `tsx`.
Verified end-to-end. L0.exact gates at 100.00% on box-dimensions.
L0.coverage averages 98.49% strict (`--threshold 0 --aa off`) and
surfaces three concrete cg backlog items via sub-100 scores:
`%`-radius not honored (paint-border-radius), alpha-compositing
rounds differently (paint-background-solid, paint-opacity), and
inner-bar width-resolution under asymmetric padding (box-padding).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a Playwright Chromium–based refbrowser reftest pipeline: new TypeScript CLI renderer, suite-driven rendering support in Rust example, HTML/CSS extra-stylesheet injection helper, fixture suite manifests and fixture sizing updates, reftest helper CSS, dev deps, and expanded documentation and prompts for workflow and authoring. Changes
Sequence Diagram(s)sequenceDiagram
participant CLI as CLI
participant Renderer as Refbrowser_Render.ts
participant FS as Filesystem
participant Playwright as Playwright/Chromium
participant Reporter as report.json
CLI->>Renderer: invoke (--suite or --fixture, --out-dir)
Renderer->>FS: read suite JSON or fixture HTML
Renderer->>FS: read extra_css files (cache)
Renderer->>Playwright: launch Chromium
loop per fixture
Renderer->>Playwright: create context/page (deterministic settings)
Renderer->>FS: navigate to file://<fixture.html>
Playwright-->>Renderer: network/fonts ready events
Renderer->>Playwright: inject <style> (extra_css)
Playwright-->>FS: screenshot -> save <out-dir>/<stem>.png
Renderer->>Reporter: record per-fixture status
end
Playwright-->>Renderer: close browser
Renderer-->>CLI: exit (process.exitCode set if failures)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: efd779f173
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| htmlcss::with_extra_stylesheets(&html, &extras) | ||
| }; | ||
|
|
||
| render_to_png(&html, 600.0, &name, out_dir, fonts); |
There was a problem hiding this comment.
Honor suite viewport width when rendering cg actuals
Suite manifests now carry per-fixture viewport.width, but the cg producer still renders every fixture at a hard-coded 600.0 width. Any suite entry using a non-600 preset (for example the documented mobile/tablet presets) will generate mismatched actuals even when rendering logic is correct, producing false regressions and making those presets unusable in refbrowser comparisons.
Useful? React with 👍 / 👎.
| const cssCache = new Map<string, string>(); | ||
| try { | ||
| browser = await chromium.launch(); | ||
| ctx = await browser.newContext({ |
There was a problem hiding this comment.
Isolate fixtures by creating context per render
The browser context is created once and reused for the whole suite, so cookies/localStorage/service worker state can leak between fixtures. That makes screenshots order-dependent when fixtures include any script-driven state, which can cause flaky diffs and mask real renderer changes. Creating a fresh incognito context per fixture avoids cross-test contamination.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
.agents/skills/cg-reftest/scripts/refbrowser_render.ts (1)
218-240: Pair-wisei += 2arg parser is brittle.The loop assumes every arg comes in
--key valuepairs. A stray positional, a boolean-like flag, or a swapped order silently produces garbage inargs(e.g.--out-dir foo --suite barparses, butfoo --out-dir bar --suite bazdoes not). Low risk today because only three flags exist and all take values, but worth hardening — iterate one token at a time and match known flags, likegolden_htmlcss.rs::parse_argsdoes on the Rust side.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.agents/skills/cg-reftest/scripts/refbrowser_render.ts around lines 218 - 240, The parseArgs function's for-loop (using i += 2) is brittle; change it to iterate tokens one-by-one over argv, inspect each token (argv[i]) and match known flags "--out-dir", "--suite", and "--fixture", consume the next token as the value only for those flags (error if value missing), ignore or error on unknown tokens/flags, and set args["out-dir"], args["suite"], args["fixture"] accordingly; keep the existing validation and return structure but use path.resolve on args["out-dir"] as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.agents/skills/cg-reftest/scripts/refbrowser_render.ts:
- Around line 184-186: The file URL is built incorrectly for Windows by using
`file://${path.resolve(htmlPath)}` which yields invalid backslashes and missing
extra slash and encoding; replace this manual construction by converting the
resolved path to a proper file URL using `pathToFileURL` from `node:url` (the
same module already importing `fileURLToPath`), assign that result to `fileUrl`,
and pass it to `page.goto(fileUrl, { waitUntil: "load" })` so paths, slashes and
percent-encoding are handled correctly.
- Around line 191-193: The current check in the block handling config.wait_for
"fonts" calls page.evaluate(() => document.fonts.ready) which returns an in-page
Promise that Playwright cannot serialize; update the call in the
refbrowser_render logic so the promise is awaited inside the browser context
(e.g., use page.evaluate with an async function or .then to resolve
document.fonts.ready) so the evaluate returns a serializable value and actually
waits before proceeding.
In @.agents/skills/cg-reftest/SKILL.md:
- Around line 391-393: The shell snippet uses the non-portable concatenation
`"$TMPDIR"grida-htmlcss-goldens`; change it to use an explicit separator and a
fallback so it works when TMPDIR is empty (Linux/CI). Replace occurrences of
`"$TMPDIR"grida-htmlcss-goldens` with `"${TMPDIR:-/tmp}/grida-htmlcss-goldens"`
(update both the shown mkdir/cp lines and the repeated occurrence at the other
location) so the path is absolute and portable across platforms.
- Around line 1008-1037: The example invocation for refbrowser_render.ts uses a
non-existent flag (--fixture-dir) and omits the required --out-dir; update the
example to call the script with the supported flags (either --suite or --fixture
as appropriate) and include --out-dir, matching the earlier correct suite-based
invocation shown around lines 366-379; specifically, replace `--fixture-dir
fixtures/test-html/L0` with the supported flag (e.g., `--suite L0` or `--fixture
<name>`) and add `--out-dir target/refbrowser/expected` so the call uses only
the implemented options of refbrowser_render.ts.
In `@crates/grida-canvas/examples/golden_htmlcss.rs`:
- Around line 187-209: In parse_args, unknown long options (a.starts_with("--"))
currently advance i by only 1 which causes the following token (often a flag
value) to be treated as a positional; update the logic in parse_args to either
(A) maintain an explicit known flag set (e.g., include "--suite") and only treat
those as flags, or (B) when encountering a token starting with "--", peek
argv.get(i+1) and if it exists and does not start with '-' treat it as that
flag's value and advance i by 2 (otherwise advance by 1); adjust handling of
variables a, i, argv, suite, and positional accordingly to ensure flag values
are not leaked into positional.
---
Nitpick comments:
In @.agents/skills/cg-reftest/scripts/refbrowser_render.ts:
- Around line 218-240: The parseArgs function's for-loop (using i += 2) is
brittle; change it to iterate tokens one-by-one over argv, inspect each token
(argv[i]) and match known flags "--out-dir", "--suite", and "--fixture", consume
the next token as the value only for those flags (error if value missing),
ignore or error on unknown tokens/flags, and set args["out-dir"], args["suite"],
args["fixture"] accordingly; keep the existing validation and return structure
but use path.resolve on args["out-dir"] as before.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f0df2a5a-a319-4723-ad6a-227ec3d57620
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (13)
.agents/skills/cg-reftest/SKILL.md.agents/skills/cg-reftest/scripts/refbrowser_render.ts.agents/skills/fixtures/SKILL.mdcrates/grida-canvas/examples/golden_htmlcss.rscrates/grida-canvas/src/htmlcss/mod.rsfixtures/test-html/L0/paint-background-solid.htmlfixtures/test-html/L0/paint-border-radius.htmlfixtures/test-html/L0/paint-opacity.htmlfixtures/test-html/README.mdfixtures/test-html/_reftest/hide-text.cssfixtures/test-html/suites/L0.coverage.jsonfixtures/test-html/suites/L0.exact.jsonpackages/grida-reftest/package.json
Addresses PR #680 review: - [P1] golden_htmlcss.rs now parses `viewport.{width,height}` from suite defaults and per-fixture entries, passes them through to `htmlcss::render`. Previously the cg side rendered every fixture at a hard-coded 600×600 regardless of what the suite specified, so any non-600 preset (mobile/tablet/desktop) would produce mismatched actuals and false regressions. Widths now inherit defaults → per-fixture override → DEFAULT_WIDTH, same as extras. - [P2] refbrowser_render.ts creates a fresh incognito `browser.newContext()` per fixture instead of reusing one across the whole suite. Previously any cookie / localStorage / service-worker state set by fixture N could leak into N+1 and make diffs order-dependent. The doc comment already claimed per-fixture isolation; the code now matches it.
Addresses PR #680 CodeRabbit review: - [Major] refbrowser_render.ts: build page URL via `pathToFileURL` instead of `file://${path}`. The concatenation produces invalid URLs on Windows (backslashes) and mishandles spaces / non-ASCII paths. - [Major] refbrowser_render.ts: await `document.fonts.ready` inside the evaluate callback rather than returning the Promise out. The returned `FontFaceSet` can't serialize across Playwright's boundary; awaiting it in-page makes the wait explicit. - [Minor] golden_htmlcss.rs: when `parse_args` encounters an unknown `--flag`, peek the next token and consume it as the flag's value if it doesn't start with `-`. Previously an unknown `--foo bar` would leak `bar` into the positional stream and be treated as a file path. - [Minor] SKILL.md: replace `"$TMPDIR"grida-htmlcss-goldens` with `"${TMPDIR:-/tmp}/grida-htmlcss-goldens"` — portable across macOS (TMPDIR ends with `/`, got lucky) and Linux/CI (TMPDIR unset). - [Major] SKILL.md example 1008-1037: drop the nonexistent `--fixture-dir` flag and bring the invocation in line with the suite-based example earlier in the doc.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/grida-canvas/examples/golden_htmlcss.rs`:
- Around line 146-176: render_with_extras currently panics if reading html_path
fails; change that to skip the failing fixture with a warning (same style as
ensure_css_cached) instead of aborting the whole suite: replace the
std::fs::read_to_string(...).unwrap_or_else(...) call in render_with_extras with
a match or if let that on Err logs a warning including html_path and the error
and then returns early (so it doesn't call htmlcss::with_extra_stylesheets or
render_to_png); keep success path unchanged so html, name, extras handling and
render_to_png remain the same.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 01923602-83e8-4c8e-825f-3bb97ed57930
📒 Files selected for processing (3)
.agents/skills/cg-reftest/SKILL.md.agents/skills/cg-reftest/scripts/refbrowser_render.tscrates/grida-canvas/examples/golden_htmlcss.rs
✅ Files skipped from review due to trivial changes (1)
- .agents/skills/cg-reftest/scripts/refbrowser_render.ts
…uite Addresses PR #680 CodeRabbit review: - A missing / misnamed entry in a suite (common for the aspirational L0.coverage.json) previously panicked in render_with_extras and killed every subsequent fixture. Match the style of ensure_css_cached: print a warning with the path + error and continue with the next fixture.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
crates/grida-canvas/examples/golden_htmlcss.rs (2)
244-271: Consider--suite=PATHform and a friendlier missing-value message.Two small usability gaps in the current parser:
--suite=fixtures/.../L0.exact.json(the GNU long-option form) isn’t accepted — only the space-separated form works. A quicksplit_once('=')handles both without adding dependencies.--suitewith no following token panics viaunwrap_or_else(|| panic!(...)). A plaineprintln! + std::process::exit(2)gives a cleaner CLI UX (no Rust backtrace noise onRUST_BACKTRACE=1).Neither is blocking —
--suiteis the only flag and the current behavior is documented — filing as optional.♻️ Proposed tweak
while i < argv.len() { let a = &argv[i]; - if a == "--suite" { + if let Some(v) = a.strip_prefix("--suite=") { + suite = Some(v.to_string()); + i += 1; + } else if a == "--suite" { let v = argv .get(i + 1) - .unwrap_or_else(|| panic!("--suite requires a path argument")); + .unwrap_or_else(|| { + eprintln!("error: --suite requires a path argument"); + std::process::exit(2); + }); suite = Some(v.clone()); i += 2; } else if a.starts_with("--") {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/grida-canvas/examples/golden_htmlcss.rs` around lines 244 - 271, The parse_args function should accept both `--suite PATH` and `--suite=PATH` forms and emit a friendly error instead of panicking when no value is provided: in parse_args, detect if a token starts_with("--suite") and if so try split_once('=') to extract the path when present, otherwise look at argv.get(i+1); if no value is found print a concise message with eprintln!("--suite requires a path argument") and call std::process::exit(2) instead of unwrap_or_else panic; ensure you set suite = Some(value.clone()) and advance i appropriately in each branch so the positional stream remains correct.
84-102: Minor: viewport inheritance is correct but theunwrap_or(defaults.viewport)step is dead code.When
entry.viewportisNone,vpbecomesdefaults.viewport, sovp.width.or(defaults.viewport.width)is a no-op (same value on both sides). Whenentry.viewportisSome(v), the per-field.or(defaults.viewport.*)chain does the real work. The net effect is correct — entries get per-field inheritance — but Line 92 can be dropped without changing semantics:♻️ Simplification
- let vp = entry.viewport.unwrap_or(defaults.viewport); - let width = vp - .width - .or(defaults.viewport.width) - .unwrap_or(DEFAULT_WIDTH); - let height = vp - .height - .or(defaults.viewport.height) - .unwrap_or(DEFAULT_HEIGHT); + let vp = entry.viewport.unwrap_or_default(); + let width = vp.width.or(defaults.viewport.width).unwrap_or(DEFAULT_WIDTH); + let height = vp.height.or(defaults.viewport.height).unwrap_or(DEFAULT_HEIGHT);Also worth noting for future doc updates:
extra_cssinheritance is all-or-nothing (an explicit"extra_css": []on a fixture overrides defaults rather than merging), which differs from viewport’s per-field merge. Probably intentional for list-typed fields, but a one-line note in the file-level doc comment (Lines 30-32) would prevent surprises.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/grida-canvas/examples/golden_htmlcss.rs` around lines 84 - 102, The code in resolve_entry binds vp by value with entry.viewport.unwrap_or(defaults.viewport), which makes the subsequent per-field .or(defaults.viewport.*) a no-op; change vp to a reference so per-field inheritance works as intended by using entry.viewport.as_ref().unwrap_or(&defaults.viewport) (reference the resolve_entry function and the vp, width, height variables), leaving the width/height lines unchanged so they can call vp.width.or(defaults.viewport.width). Also consider adding a short doc-note about extra_css being an all-or-nothing override elsewhere in the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@crates/grida-canvas/examples/golden_htmlcss.rs`:
- Around line 244-271: The parse_args function should accept both `--suite PATH`
and `--suite=PATH` forms and emit a friendly error instead of panicking when no
value is provided: in parse_args, detect if a token starts_with("--suite") and
if so try split_once('=') to extract the path when present, otherwise look at
argv.get(i+1); if no value is found print a concise message with
eprintln!("--suite requires a path argument") and call std::process::exit(2)
instead of unwrap_or_else panic; ensure you set suite = Some(value.clone()) and
advance i appropriately in each branch so the positional stream remains correct.
- Around line 84-102: The code in resolve_entry binds vp by value with
entry.viewport.unwrap_or(defaults.viewport), which makes the subsequent
per-field .or(defaults.viewport.*) a no-op; change vp to a reference so
per-field inheritance works as intended by using
entry.viewport.as_ref().unwrap_or(&defaults.viewport) (reference the
resolve_entry function and the vp, width, height variables), leaving the
width/height lines unchanged so they can call
vp.width.or(defaults.viewport.width). Also consider adding a short doc-note
about extra_css being an all-or-nothing override elsewhere in the file.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 53653858-ba9c-48f1-8c16-0c28697e6f3c
📒 Files selected for processing (1)
crates/grida-canvas/examples/golden_htmlcss.rs
Pastable template for driving a single CSS feature through audit → ground → fixture → implement → verify, with reftest-backed gate policy so every landing has objective Chromium-parity proof. Shipped as a prompt (not a skill) because it's an orchestrator over existing /research, /fixtures, /cg-reftest skills — opt-in is the right invocation model for a heavy 5-phase loop.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.agents/prompts/cg-htmlcss-feature.md:
- Around line 24-30: The unlabeled fenced block containing the ASCII phase
diagram (the multi-line box diagram starting with "┌──────────┐ ┌──────────┐
┌──────────┐") should be given an explicit fence language to satisfy
markdownlint MD040; update the opening triple-backtick to include a language
token (e.g., "text") so the block becomes a labeled fence and leave the closing
triple-backticks unchanged.
- Around line 292-316: The unlabeled fenced code block that starts with "Drive
the htmlcss feature loop for: <property or behavior>." must be labeled to
satisfy MD040; update that fenced block’s opening fence to include a language
tag (use "text") so it becomes ```text and leave the closing fence as-is; locate
the block by searching for the exact string "Drive the htmlcss feature loop
for:" in .agents/prompts/cg-htmlcss-feature.md and change the fence label
accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 164ef97c-ff58-4c47-91d6-156c932c41ef
📒 Files selected for processing (1)
.agents/prompts/cg-htmlcss-feature.md
Summary
New HTML/CSS reftest harness: refbrowser. Renders fixtures in Playwright Chromium and cg's htmlcss pipeline in parallel, diffs via
@grida/reftest. Suite JSONs gate the comparison;L0.exactmust pass 100.00% byte-exact.Pipeline:
What's included
Producers (both read the same suite JSON):
.agents/skills/cg-reftest/scripts/refbrowser_render.ts— new Playwright producer.crates/grida-canvas/examples/golden_htmlcss.rs— accepts--suite <path>; appliesextra_cssviahtmlcss::with_extra_stylesheetsso the cascade is symmetric with Chromium.cg API addition:
htmlcss::with_extra_stylesheets(html, &[css])(crates/grida-canvas/src/htmlcss/mod.rs). Agnostic — any caller with HTML + extra author rules can use it, not test-specific.Fixture scaffolding:
fixtures/test-html/suites/L0.exact.json— byte-exact gate (1 fixture today).fixtures/test-html/suites/L0.coverage.json— aspirational scope (5 fixtures).fixtures/test-html/_reftest/hide-text.css— shared helper; zeros glyph coverage + pinsline-height: 1so Blink-vs-Skia text divergence doesn't dominate non-text diffs.fixtures/test-html/README.md— viewport presets (canvas-md600×800 default, plussm/mobile/tablet/desktop), paint-vs-layout authoring rule, promotion workflow.min-heightpreset pattern; layout fixtures kept natural-cull.Docs:
.agents/skills/cg-reftest/SKILL.md— full refbrowser pipeline section, WPT see-also, "Reading the score" warning (content-area-relative scoring pitfall), known divergence surfaces as backlog items, and two "future work" heuristic sections:.agents/skills/fixtures/SKILL.md— cross-ref to the test-html README for the paint-vs-layout rule.Deps:
@playwright/test+tsxadded as devDeps of@grida/reftest.Scores
Verified end-to-end with strict diff (
--threshold 0, floor 1.0):L0.exactL0.coverageThe sub-100 scores in
L0.coveragesurface three concrete cg backlog items:%border-radius not honored (paint-border-radius 98.94%) —border-radius: 50%andH / Vforms render as square; fixed-length radii work.rgba(255,0,0,0.7)over white to 77, Chromium to 76. 1-unit channel delta but real policy divergence.Each is documented in
SKILL.mdunder "Known divergence surfaces." Not tolerance-excused; the score carries the truth.Why strict (
--threshold 0)We're building a renderer from scratch. Every pixel delta from Chromium is a decision cg made differently — worth surfacing, not absorbing. The skill walks through the tradeoff: tuning threshold up hides real bugs (see the rgba rounding discovery). Floor is 100.00% byte-exact for
L0.exact;L0.coverageis informational.Not in scope (follow-ups)
@grida/reftest --suite <path>flag to pull gate config from the suite JSON and auto-enforcefloor. Today CI/dev assertstests[].similarity_score ≥ gate.floorby readingreport.jsonthemselves.Test plan
cargo check -p cg --example golden_htmlcsscleanrefbrowser_render.ts --suite L0.exact.jsonrenders expectedscargo run -p cg --example golden_htmlcss -- --suite L0.exact.jsonrenders actuals@grida/reftestdiff on L0.exact returns 100.00% for box-dimensionsSummary by CodeRabbit
New Features
Documentation
Fixtures
Examples