From d05e8e4aa9fa288a3f8c8c65990b863be79fbee6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:24:39 -0400 Subject: [PATCH 1/2] =?UTF-8?q?fix(adapters):=20pass-2=20residual=20deltas?= =?UTF-8?q?=20=E2=80=94=20ct-key=20color-mix=20(AA),=20ct-test=20gap,=20bu?= =?UTF-8?q?bble=20radius,=20Model=20YAML=20label=20(#233)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - D4: .col-tooltip .ct-key -> color-mix(in oklab, var(--accent) 60%, white) per pass-2 base.css; latte gets a scoped 35% stand-in (the spec's own 60% mix lands 3.37:1 on latte's #4c4f69 tooltip fill — the #198/#206/#227 pattern). New 8-theme headless AA guard measures the key against the tooltip bubble's OWN fill (the effective backdrop), never --bg/--surface. - D5: .col-tooltip .ct-test gap 0.25rem 0.45rem -> 0.25rem 0.7rem - D6: badge-borne bubble border-radius --radius-pan -> --radius-sm - D7: drawer summary 'Authoring YAML(— diff)' -> 'Model YAML' (no diff suffix — the Diff/File toggle carries it); the external-fixture pointer copy follows the rename; --help text updated. - Goldens regenerated (jaffle-shop, playground, diff-showcase; explore unchanged); chrome snapshot rebaselined; every hunk maps to the four deltas. Closes #233 Co-Authored-By: Claude Fable 5 --- examples/diff-showcase-report.html | 28 +- examples/jaffle-shop-report.html | 28 +- examples/playground-report.html | 28 +- src/cli/args.rs | 5 +- templates/interaction.js | 10 +- templates/report.css | 18 +- tests/headless_toggle.rs | 275 +++++++++++++++++- ...egration__rendered_chrome_jaffle_shop.snap | 28 +- 8 files changed, 384 insertions(+), 36 deletions(-) diff --git a/examples/diff-showcase-report.html b/examples/diff-showcase-report.html index 8add3930..2d27f650 100644 --- a/examples/diff-showcase-report.html +++ b/examples/diff-showcase-report.html @@ -1198,7 +1198,7 @@ .mode-badge.has-mode-tip, .this-badge.has-mode-tip { position: relative; cursor: help; } .mode-badge.has-mode-tip:focus-visible, .this-badge.has-mode-tip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } .mode-badge.has-mode-tip .expect-tooltip-bubble, -.this-badge.has-mode-tip .expect-tooltip-bubble { left: 0; transform: none; z-index: 60; box-shadow: 0 6px 22px rgba(0,0,0,0.3); } +.this-badge.has-mode-tip .expect-tooltip-bubble { left: 0; transform: none; z-index: 60; box-shadow: 0 6px 22px rgba(0,0,0,0.3); border-radius: var(--radius-sm); /* cute-dbt#233 (audit D6) — pass-2 rounds the badge-borne bubble at --radius-sm; the shared .expect-tooltip-bubble keeps --radius-pan */ } .mode-badge.has-mode-tip:hover .expect-tooltip-bubble, .mode-badge.has-mode-tip:focus .expect-tooltip-bubble, .mode-badge.has-mode-tip:focus-visible .expect-tooltip-bubble, @@ -1526,8 +1526,20 @@ .col-tooltip .ct-desc { font-size: 1.12em; line-height: 1.5; opacity: 0.97; } .col-tooltip .ct-tests-label { margin-top: 0.6rem; font-size: 0.82em; text-transform: uppercase; letter-spacing: 0.07em; opacity: 0.6; } .col-tooltip .ct-tests { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.3rem; } -.col-tooltip .ct-test { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.45rem; } -.col-tooltip .ct-key { font-family: var(--font-mono); font-weight: 700; color: var(--accent); } +.col-tooltip .ct-test { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.7rem; } +/* cute-dbt#233 (audit D4) — the pass-2 readable-on-dark accent: raw + var(--accent) lands ≈3:1 against the always-dark tooltip fill on the + 4 light themes (AA fail); the oklab mix toward white clears 4.5:1 on + all 8 themes (headless-pinned). */ +.col-tooltip .ct-key { font-family: var(--font-mono); font-weight: 700; color: color-mix(in oklab, var(--accent) 60%, white); } +/* cute-dbt#233 — Latte ct-key AA: the spec's own 60% mix lands 3.37:1 on + latte's tooltip fill (#4c4f69 — the lightest of the 8; the spec tokens + carry the same value, so this is empirically the spec's gap, not ours). + The #198/#206/#227 pattern: a scoped stand-in only — the mix shifts + toward white for latte alone (≈5:1 measured); the verbatim latte theme + block and the other 7 themes' spec-literal mix stay pinned. Covers the + model tip too (it rides the .col-tooltip shell). */ +:root[data-theme="latte"] .col-tooltip .ct-key { color: color-mix(in oklab, var(--accent) 35%, white); } .col-tooltip .ct-vals { display: inline-flex; flex-wrap: wrap; gap: 0.25rem; } .col-tooltip .ct-val { background: rgba(255,255,255,0.14); border-radius: 4px; padding: 0.05rem 0.42rem; font-family: var(--font-mono); font-size: 0.9em; white-space: nowrap; } /* (cute-dbt#202 — the .ct-detail run is retired: detail strings now split @@ -6065,7 +6077,10 @@

Expected

// authored YAML exactly as cute-dbt#69 did. var hasDiff = t.yaml_diff && t.yaml_diff.lines && t.yaml_diff.lines.length; var $yamlDet = $("
").addClass("authoring-yaml").attr("open", "open"); - $yamlDet.append($("").text(hasDiff ? "Authoring YAML — diff" : "Authoring YAML")); + // cute-dbt#233 (audit D7) — pass-2 labels the drawer "Model YAML" + // with no diff-variant suffix; the Diff/File toggle in the code + // header carries the diff affordance. + $yamlDet.append($("").text("Model YAML")); var $yamlWrap = $("
").addClass("sql-block-wrap fixture-sql-block-wrap"); // cute-dbt#178 — GitHub-style code-card header: the file path sits left // of the Diff/File toggle. `defined_in` is the test's authoring-YAML @@ -8532,13 +8547,14 @@

Expected

} // The external-fixture affordance: never a silently-empty grid. Names the - // file and points the reader at the Authoring YAML drawer (the #96 view), + // file and points the reader at the Model YAML drawer (the #96 view), // which surfaces the authored block including the `fixture:` line. + // (Pointer copy follows the cute-dbt#233 D7 drawer rename.) function buildExternalFixtureAffordance(name) { var $box = $("
").addClass("given-empty external-fixture-note"); $box.append(document.createTextNode("data in external fixture file: ")); $box.append($("").addClass("td-defined-in").text(String(name))); - $box.append(document.createTextNode(" — see the Authoring YAML drawer above for this test's block.")); + $box.append(document.createTextNode(" — see the Model YAML drawer above for this test's block.")); return $box; } diff --git a/examples/jaffle-shop-report.html b/examples/jaffle-shop-report.html index f56dd90f..6ae32c1f 100644 --- a/examples/jaffle-shop-report.html +++ b/examples/jaffle-shop-report.html @@ -1198,7 +1198,7 @@ .mode-badge.has-mode-tip, .this-badge.has-mode-tip { position: relative; cursor: help; } .mode-badge.has-mode-tip:focus-visible, .this-badge.has-mode-tip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } .mode-badge.has-mode-tip .expect-tooltip-bubble, -.this-badge.has-mode-tip .expect-tooltip-bubble { left: 0; transform: none; z-index: 60; box-shadow: 0 6px 22px rgba(0,0,0,0.3); } +.this-badge.has-mode-tip .expect-tooltip-bubble { left: 0; transform: none; z-index: 60; box-shadow: 0 6px 22px rgba(0,0,0,0.3); border-radius: var(--radius-sm); /* cute-dbt#233 (audit D6) — pass-2 rounds the badge-borne bubble at --radius-sm; the shared .expect-tooltip-bubble keeps --radius-pan */ } .mode-badge.has-mode-tip:hover .expect-tooltip-bubble, .mode-badge.has-mode-tip:focus .expect-tooltip-bubble, .mode-badge.has-mode-tip:focus-visible .expect-tooltip-bubble, @@ -1526,8 +1526,20 @@ .col-tooltip .ct-desc { font-size: 1.12em; line-height: 1.5; opacity: 0.97; } .col-tooltip .ct-tests-label { margin-top: 0.6rem; font-size: 0.82em; text-transform: uppercase; letter-spacing: 0.07em; opacity: 0.6; } .col-tooltip .ct-tests { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.3rem; } -.col-tooltip .ct-test { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.45rem; } -.col-tooltip .ct-key { font-family: var(--font-mono); font-weight: 700; color: var(--accent); } +.col-tooltip .ct-test { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.7rem; } +/* cute-dbt#233 (audit D4) — the pass-2 readable-on-dark accent: raw + var(--accent) lands ≈3:1 against the always-dark tooltip fill on the + 4 light themes (AA fail); the oklab mix toward white clears 4.5:1 on + all 8 themes (headless-pinned). */ +.col-tooltip .ct-key { font-family: var(--font-mono); font-weight: 700; color: color-mix(in oklab, var(--accent) 60%, white); } +/* cute-dbt#233 — Latte ct-key AA: the spec's own 60% mix lands 3.37:1 on + latte's tooltip fill (#4c4f69 — the lightest of the 8; the spec tokens + carry the same value, so this is empirically the spec's gap, not ours). + The #198/#206/#227 pattern: a scoped stand-in only — the mix shifts + toward white for latte alone (≈5:1 measured); the verbatim latte theme + block and the other 7 themes' spec-literal mix stay pinned. Covers the + model tip too (it rides the .col-tooltip shell). */ +:root[data-theme="latte"] .col-tooltip .ct-key { color: color-mix(in oklab, var(--accent) 35%, white); } .col-tooltip .ct-vals { display: inline-flex; flex-wrap: wrap; gap: 0.25rem; } .col-tooltip .ct-val { background: rgba(255,255,255,0.14); border-radius: 4px; padding: 0.05rem 0.42rem; font-family: var(--font-mono); font-size: 0.9em; white-space: nowrap; } /* (cute-dbt#202 — the .ct-detail run is retired: detail strings now split @@ -6034,7 +6046,10 @@

Expected

// authored YAML exactly as cute-dbt#69 did. var hasDiff = t.yaml_diff && t.yaml_diff.lines && t.yaml_diff.lines.length; var $yamlDet = $("
").addClass("authoring-yaml").attr("open", "open"); - $yamlDet.append($("").text(hasDiff ? "Authoring YAML — diff" : "Authoring YAML")); + // cute-dbt#233 (audit D7) — pass-2 labels the drawer "Model YAML" + // with no diff-variant suffix; the Diff/File toggle in the code + // header carries the diff affordance. + $yamlDet.append($("").text("Model YAML")); var $yamlWrap = $("
").addClass("sql-block-wrap fixture-sql-block-wrap"); // cute-dbt#178 — GitHub-style code-card header: the file path sits left // of the Diff/File toggle. `defined_in` is the test's authoring-YAML @@ -8501,13 +8516,14 @@

Expected

} // The external-fixture affordance: never a silently-empty grid. Names the - // file and points the reader at the Authoring YAML drawer (the #96 view), + // file and points the reader at the Model YAML drawer (the #96 view), // which surfaces the authored block including the `fixture:` line. + // (Pointer copy follows the cute-dbt#233 D7 drawer rename.) function buildExternalFixtureAffordance(name) { var $box = $("
").addClass("given-empty external-fixture-note"); $box.append(document.createTextNode("data in external fixture file: ")); $box.append($("").addClass("td-defined-in").text(String(name))); - $box.append(document.createTextNode(" — see the Authoring YAML drawer above for this test's block.")); + $box.append(document.createTextNode(" — see the Model YAML drawer above for this test's block.")); return $box; } diff --git a/examples/playground-report.html b/examples/playground-report.html index b73b2b5d..c4f3ec63 100644 --- a/examples/playground-report.html +++ b/examples/playground-report.html @@ -1198,7 +1198,7 @@ .mode-badge.has-mode-tip, .this-badge.has-mode-tip { position: relative; cursor: help; } .mode-badge.has-mode-tip:focus-visible, .this-badge.has-mode-tip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } .mode-badge.has-mode-tip .expect-tooltip-bubble, -.this-badge.has-mode-tip .expect-tooltip-bubble { left: 0; transform: none; z-index: 60; box-shadow: 0 6px 22px rgba(0,0,0,0.3); } +.this-badge.has-mode-tip .expect-tooltip-bubble { left: 0; transform: none; z-index: 60; box-shadow: 0 6px 22px rgba(0,0,0,0.3); border-radius: var(--radius-sm); /* cute-dbt#233 (audit D6) — pass-2 rounds the badge-borne bubble at --radius-sm; the shared .expect-tooltip-bubble keeps --radius-pan */ } .mode-badge.has-mode-tip:hover .expect-tooltip-bubble, .mode-badge.has-mode-tip:focus .expect-tooltip-bubble, .mode-badge.has-mode-tip:focus-visible .expect-tooltip-bubble, @@ -1526,8 +1526,20 @@ .col-tooltip .ct-desc { font-size: 1.12em; line-height: 1.5; opacity: 0.97; } .col-tooltip .ct-tests-label { margin-top: 0.6rem; font-size: 0.82em; text-transform: uppercase; letter-spacing: 0.07em; opacity: 0.6; } .col-tooltip .ct-tests { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.3rem; } -.col-tooltip .ct-test { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.45rem; } -.col-tooltip .ct-key { font-family: var(--font-mono); font-weight: 700; color: var(--accent); } +.col-tooltip .ct-test { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.7rem; } +/* cute-dbt#233 (audit D4) — the pass-2 readable-on-dark accent: raw + var(--accent) lands ≈3:1 against the always-dark tooltip fill on the + 4 light themes (AA fail); the oklab mix toward white clears 4.5:1 on + all 8 themes (headless-pinned). */ +.col-tooltip .ct-key { font-family: var(--font-mono); font-weight: 700; color: color-mix(in oklab, var(--accent) 60%, white); } +/* cute-dbt#233 — Latte ct-key AA: the spec's own 60% mix lands 3.37:1 on + latte's tooltip fill (#4c4f69 — the lightest of the 8; the spec tokens + carry the same value, so this is empirically the spec's gap, not ours). + The #198/#206/#227 pattern: a scoped stand-in only — the mix shifts + toward white for latte alone (≈5:1 measured); the verbatim latte theme + block and the other 7 themes' spec-literal mix stay pinned. Covers the + model tip too (it rides the .col-tooltip shell). */ +:root[data-theme="latte"] .col-tooltip .ct-key { color: color-mix(in oklab, var(--accent) 35%, white); } .col-tooltip .ct-vals { display: inline-flex; flex-wrap: wrap; gap: 0.25rem; } .col-tooltip .ct-val { background: rgba(255,255,255,0.14); border-radius: 4px; padding: 0.05rem 0.42rem; font-family: var(--font-mono); font-size: 0.9em; white-space: nowrap; } /* (cute-dbt#202 — the .ct-detail run is retired: detail strings now split @@ -6034,7 +6046,10 @@

Expected

// authored YAML exactly as cute-dbt#69 did. var hasDiff = t.yaml_diff && t.yaml_diff.lines && t.yaml_diff.lines.length; var $yamlDet = $("
").addClass("authoring-yaml").attr("open", "open"); - $yamlDet.append($("").text(hasDiff ? "Authoring YAML — diff" : "Authoring YAML")); + // cute-dbt#233 (audit D7) — pass-2 labels the drawer "Model YAML" + // with no diff-variant suffix; the Diff/File toggle in the code + // header carries the diff affordance. + $yamlDet.append($("").text("Model YAML")); var $yamlWrap = $("
").addClass("sql-block-wrap fixture-sql-block-wrap"); // cute-dbt#178 — GitHub-style code-card header: the file path sits left // of the Diff/File toggle. `defined_in` is the test's authoring-YAML @@ -8501,13 +8516,14 @@

Expected

} // The external-fixture affordance: never a silently-empty grid. Names the - // file and points the reader at the Authoring YAML drawer (the #96 view), + // file and points the reader at the Model YAML drawer (the #96 view), // which surfaces the authored block including the `fixture:` line. + // (Pointer copy follows the cute-dbt#233 D7 drawer rename.) function buildExternalFixtureAffordance(name) { var $box = $("
").addClass("given-empty external-fixture-note"); $box.append(document.createTextNode("data in external fixture file: ")); $box.append($("").addClass("td-defined-in").text(String(name))); - $box.append(document.createTextNode(" — see the Authoring YAML drawer above for this test's block.")); + $box.append(document.createTextNode(" — see the Model YAML drawer above for this test's block.")); return $box; } diff --git a/src/cli/args.rs b/src/cli/args.rs index 2117e656..cb01683e 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -131,8 +131,9 @@ pub struct ReportArgs { /// `original_file_path` entries. /// /// When supplied, cute-dbt reads each in-scope `unit_test`'s source - /// YAML and surfaces an "Authoring YAML" drawer in the report - /// (cute-dbt#69). When absent, cute-dbt attempts to derive the + /// YAML and surfaces a "Model YAML" drawer in the report + /// (cute-dbt#69; label per the pass-2 spec, cute-dbt#233). When + /// absent, cute-dbt attempts to derive the /// project root from `--manifest` (by stripping a trailing /// `target/manifest.json`) before silently skipping the YAML /// extraction. diff --git a/templates/interaction.js b/templates/interaction.js index 317a5213..c678dd18 100644 --- a/templates/interaction.js +++ b/templates/interaction.js @@ -579,7 +579,10 @@ // authored YAML exactly as cute-dbt#69 did. var hasDiff = t.yaml_diff && t.yaml_diff.lines && t.yaml_diff.lines.length; var $yamlDet = $("
").addClass("authoring-yaml").attr("open", "open"); - $yamlDet.append($("").text(hasDiff ? "Authoring YAML — diff" : "Authoring YAML")); + // cute-dbt#233 (audit D7) — pass-2 labels the drawer "Model YAML" + // with no diff-variant suffix; the Diff/File toggle in the code + // header carries the diff affordance. + $yamlDet.append($("").text("Model YAML")); var $yamlWrap = $("
").addClass("sql-block-wrap fixture-sql-block-wrap"); // cute-dbt#178 — GitHub-style code-card header: the file path sits left // of the Diff/File toggle. `defined_in` is the test's authoring-YAML @@ -3046,13 +3049,14 @@ } // The external-fixture affordance: never a silently-empty grid. Names the - // file and points the reader at the Authoring YAML drawer (the #96 view), + // file and points the reader at the Model YAML drawer (the #96 view), // which surfaces the authored block including the `fixture:` line. + // (Pointer copy follows the cute-dbt#233 D7 drawer rename.) function buildExternalFixtureAffordance(name) { var $box = $("
").addClass("given-empty external-fixture-note"); $box.append(document.createTextNode("data in external fixture file: ")); $box.append($("").addClass("td-defined-in").text(String(name))); - $box.append(document.createTextNode(" — see the Authoring YAML drawer above for this test's block.")); + $box.append(document.createTextNode(" — see the Model YAML drawer above for this test's block.")); return $box; } diff --git a/templates/report.css b/templates/report.css index a83abc00..0a829877 100644 --- a/templates/report.css +++ b/templates/report.css @@ -958,7 +958,7 @@ g.dag-node:focus-visible { outline: 2px dashed var(--accent); outline-offset: 3p .mode-badge.has-mode-tip, .this-badge.has-mode-tip { position: relative; cursor: help; } .mode-badge.has-mode-tip:focus-visible, .this-badge.has-mode-tip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } .mode-badge.has-mode-tip .expect-tooltip-bubble, -.this-badge.has-mode-tip .expect-tooltip-bubble { left: 0; transform: none; z-index: 60; box-shadow: 0 6px 22px rgba(0,0,0,0.3); } +.this-badge.has-mode-tip .expect-tooltip-bubble { left: 0; transform: none; z-index: 60; box-shadow: 0 6px 22px rgba(0,0,0,0.3); border-radius: var(--radius-sm); /* cute-dbt#233 (audit D6) — pass-2 rounds the badge-borne bubble at --radius-sm; the shared .expect-tooltip-bubble keeps --radius-pan */ } .mode-badge.has-mode-tip:hover .expect-tooltip-bubble, .mode-badge.has-mode-tip:focus .expect-tooltip-bubble, .mode-badge.has-mode-tip:focus-visible .expect-tooltip-bubble, @@ -1286,8 +1286,20 @@ th.has-col-meta:focus-visible { outline: 2px solid var(--accent); outline-offset .col-tooltip .ct-desc { font-size: 1.12em; line-height: 1.5; opacity: 0.97; } .col-tooltip .ct-tests-label { margin-top: 0.6rem; font-size: 0.82em; text-transform: uppercase; letter-spacing: 0.07em; opacity: 0.6; } .col-tooltip .ct-tests { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.3rem; } -.col-tooltip .ct-test { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.45rem; } -.col-tooltip .ct-key { font-family: var(--font-mono); font-weight: 700; color: var(--accent); } +.col-tooltip .ct-test { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.7rem; } +/* cute-dbt#233 (audit D4) — the pass-2 readable-on-dark accent: raw + var(--accent) lands ≈3:1 against the always-dark tooltip fill on the + 4 light themes (AA fail); the oklab mix toward white clears 4.5:1 on + all 8 themes (headless-pinned). */ +.col-tooltip .ct-key { font-family: var(--font-mono); font-weight: 700; color: color-mix(in oklab, var(--accent) 60%, white); } +/* cute-dbt#233 — Latte ct-key AA: the spec's own 60% mix lands 3.37:1 on + latte's tooltip fill (#4c4f69 — the lightest of the 8; the spec tokens + carry the same value, so this is empirically the spec's gap, not ours). + The #198/#206/#227 pattern: a scoped stand-in only — the mix shifts + toward white for latte alone (≈5:1 measured); the verbatim latte theme + block and the other 7 themes' spec-literal mix stay pinned. Covers the + model tip too (it rides the .col-tooltip shell). */ +:root[data-theme="latte"] .col-tooltip .ct-key { color: color-mix(in oklab, var(--accent) 35%, white); } .col-tooltip .ct-vals { display: inline-flex; flex-wrap: wrap; gap: 0.25rem; } .col-tooltip .ct-val { background: rgba(255,255,255,0.14); border-radius: 4px; padding: 0.05rem 0.42rem; font-family: var(--font-mono); font-size: 0.9em; white-space: nowrap; } /* (cute-dbt#202 — the .ct-detail run is retired: detail strings now split diff --git a/tests/headless_toggle.rs b/tests/headless_toggle.rs index c4448112..4da562b6 100644 --- a/tests/headless_toggle.rs +++ b/tests/headless_toggle.rs @@ -774,15 +774,17 @@ fn yaml_diff_drawer_defaults_to_diff_and_toggles_to_authored() { tab.navigate_to(&url).expect("navigate"); tab.wait_until_navigated().expect("await navigation"); - // The edited test's drawer summary names the diff, and the Diff view is - // the default (Authored hidden). + // cute-dbt#233 (audit D7) — pass-2 labels the drawer "Model YAML" with + // NO diff-variant suffix (spec interaction.js:468): the Diff/File + // toggle in the code header carries the diff affordance. The Diff view + // stays the default (Authored hidden). assert_eq!( eval_string( &tab, "document.querySelector('.authoring-yaml > summary').textContent.trim()" ), - "Authoring YAML — diff", - "an edited test's drawer summary is 'Authoring YAML — diff'", + "Model YAML", + "the drawer summary is the pass-2 'Model YAML' label (cute-dbt#233 audit D7)", ); assert!( eval_bool(&tab, "document.querySelector('.yaml-diff-toggle') !== null"), @@ -7862,6 +7864,271 @@ fn badge_tip_text_matches_column_tooltip_description_size() { let _ = tab.close(true); } +// ===== cute-dbt#233 — pass-2 residual deltas (audit D4/D5/D6) =============== +// +// The 2026-06-11 design-conformance audit found the implementation kept +// stale pass-1 values that pass-2 revised. D4 is the load-bearing one: +// `.col-tooltip .ct-key` shipped raw `var(--accent)`, which resolves to +// ≈3:1 against the always-dark tooltip fill on the 4 light themes — an +// AA failure for the 12px bold mono test names. Pass-2 (engine/base.css, +// the `.ct-key` rule) brightens it to +// `color-mix(in oklab, var(--accent) 60%, white)`. +// +// The guard extends the #206/#227 AA family with the same methodology: +// contrast is computed from RESOLVED computed styles against the +// element's EFFECTIVE backdrop — for tooltip text that is the tooltip +// bubble's own opaque `--tooltip-bg` fill (every theme paints the tip +// dark), NEVER `--bg` and NEVER `--surface`. The sweep records the +// bubble's own resolved fill alongside the backdrop the ancestor walk +// found, so the methodology itself is pinned: if the walk ever escapes +// the bubble to a page surface, the test fails loudly. + +/// The `.col-tooltip .ct-key` WCAG sweep, evaluated in-page. Returns a +/// JSON array of `{theme, ratio, fg, bg, tipbg}` — one entry per theme; +/// `tipbg` is the bubble's own resolved background (the methodology pin). +const COL_TOOLTIP_CT_KEY_CONTRAST_SWEEP_JS: &str = r#"(function () { + var THEMES = ["light", "solarized", "latte", "rosepine", + "dark", "tokyo", "gruvbox", "dracula"]; + var DARK = { dark: true, tokyo: true, gruvbox: true, dracula: true }; + function parseRgb(s) { + var m = /rgba?\(([^)]+)\)/.exec(s || ""); + if (!m) return null; + var p = m[1].split(","); + return { r: parseFloat(p[0]), g: parseFloat(p[1]), b: parseFloat(p[2]), + a: p.length > 3 ? parseFloat(p[3]) : 1 }; + } + function chan(v) { + v = v / 255; + return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); + } + function lum(c) { + return 0.2126 * chan(c.r) + 0.7152 * chan(c.g) + 0.0722 * chan(c.b); + } + function ratio(f, b) { + var lf = lum(f), lb = lum(b); + var hi = Math.max(lf, lb), lo = Math.min(lf, lb); + return (hi + 0.05) / (lo + 0.05); + } + function backdropOf(el) { + for (var n = el; n; n = n.parentElement) { + var c = parseRgb(getComputedStyle(n).backgroundColor); + if (c && c.a === 1) return c; + } + return null; /* no opaque ancestor — surfaced as ratio -1 */ + } + /* normalize ANY css color serialization through a 1x1 canvas: the + color-mix(in oklab, ...) fg computes to a non-rgb() string in + Chrome (oklab()/color()), which the #206 regex parse can't read. + An unparseable string falls back to canvas-black, which lands a + ~1:1 ratio on the dark tooltip fill — a loud failure, never a + silent pass. */ + function cssToRgb(s) { + var cv = document.createElement("canvas"); + cv.width = cv.height = 1; + var cx = cv.getContext("2d"); + cx.fillStyle = s; + cx.fillRect(0, 0, 1, 1); + var d = cx.getImageData(0, 0, 1, 1).data; + return { r: d[0], g: d[1], b: d[2], a: d[3] / 255 }; + } + var tip = document.getElementById("col-tooltip"); + var key = tip.querySelector(".ct-key"); + var root = document.documentElement; + var out = []; + for (var i = 0; i < THEMES.length; i++) { + /* exactly theme.js applyTheme: set data-theme + sync html.dark */ + root.setAttribute("data-theme", THEMES[i]); + root.classList.toggle("dark", !!DARK[THEMES[i]]); + var cs = getComputedStyle(key); + var fg = cssToRgb(cs.color); + var own = parseRgb(cs.backgroundColor); + var bg = own && own.a === 1 ? own : backdropOf(key.parentElement); + out.push({ + theme: THEMES[i], + ratio: fg && bg ? ratio(fg, bg) : -1, + fg: cs.color, + bg: bg ? "rgb(" + bg.r + ", " + bg.g + ", " + bg.b + ")" : "none", + tipbg: getComputedStyle(tip).backgroundColor + }); + } + return JSON.stringify(out); +})()"#; + +#[test] +#[ignore = "requires Chrome; runs explicitly in the headless-zero-egress CI job via `-- --ignored`"] +fn col_tooltip_ct_key_meets_aa_contrast_on_every_theme() { + // A described + column-tested `id` column materializes the singleton + // #col-tooltip with a real `.ct-key` row through the shipped path + // (focusing the decorated expected-table header). + let mut col_desc = BTreeMap::new(); + col_desc.insert("id".to_owned(), "Primary key for dim_aa".to_owned()); + let ut = UnitTest::new( + "aa".to_owned(), + NodeId::new("dim_aa"), + Vec::new(), + UnitTestExpect::new( + serde_json::json!([{ "id": 1 }]), + Some("dict".to_owned()), + None, + ), + None, + DependsOn::default(), + None, + None, + None, + ); + let url = render_to_file( + "headless_233_ct_key_contrast.html", + vec![ + model_node("model.shop.dim_aa").with_column_descriptions(col_desc), + column_test_node( + "test.shop.unique_dim_aa_id", + "model.shop.dim_aa", + "id", + TestMetadata::new("unique", None, serde_json::Value::Null), + ), + ], + vec![("unit_test.shop.dim_aa.aa", ut)], + &["model.shop.dim_aa"], + &[], + ); + let browser = launch_browser(); + let tab = browser.new_tab().expect("new tab"); + tab.navigate_to(&url).expect("navigate"); + tab.wait_until_navigated().expect("await navigation"); + wait_for_document_ready(&tab); + select_model(&tab, "dim_aa"); + select_test(&tab, "unit_test.shop.dim_aa.aa"); + + let _ = eval( + &tab, + "document.querySelector('.expected-panel th.has-col-meta').focus()", + ); + assert!( + !eval_bool(&tab, "document.getElementById('col-tooltip').hidden"), + "precondition: focusing the decorated th reveals the column tooltip", + ); + + // cute-dbt#233 (audit D4) — the full 8-theme AA sweep. + let raw = eval_string(&tab, COL_TOOLTIP_CT_KEY_CONTRAST_SWEEP_JS); + let measured: Vec = + serde_json::from_str(&raw).expect("the contrast sweep returns valid JSON"); + assert_eq!(measured.len(), 8, "all 8 themes measured, got: {raw}",); + + let mut failures = Vec::new(); + for m in &measured { + let theme = m["theme"].as_str().expect("theme is a string"); + let ratio = m["ratio"].as_f64().expect("ratio is a number"); + let fg = m["fg"].as_str().unwrap_or("?"); + let bg = m["bg"].as_str().unwrap_or("?"); + let tipbg = m["tipbg"].as_str().unwrap_or("?"); + assert!( + ratio > 0.0, + "the {theme} .ct-key resolved no opaque backdrop — the backdrop \ + walk must end on a painted surface", + ); + // The methodology pin: the effective backdrop IS the tooltip + // bubble's own fill — never a page surface shining through. + assert_eq!( + bg, tipbg, + "the {theme} backdrop walk must land on the tooltip's own \ + --tooltip-bg fill (got {bg}, the bubble paints {tipbg})", + ); + eprintln!("col-tooltip .ct-key contrast {theme:>9} = {ratio:.2} ({fg} on {bg})"); + if ratio < 4.5 { + failures.push(format!("{theme} = {ratio:.2} ({fg} on {bg})")); + } + } + assert!( + failures.is_empty(), + ".col-tooltip .ct-key below the WCAG AA 4.5:1 floor (cute-dbt#233 \ + audit D4 — raw var(--accent) lands ≈3:1 on the 4 light themes): \ + {failures:#?}", + ); + + // cute-dbt#233 (audit D5) — the pass-2 chip-row gap is + // `0.25rem 0.7rem`: rowGap 2.5px / columnGap 7px at Sakura's 10px + // root (html{font-size:62.5%}); pass-1 shipped 0.45rem = 4.5px. + assert_eq!( + eval_string( + &tab, + "getComputedStyle(document.querySelector('#col-tooltip .ct-test')).rowGap" + ), + "2.5px", + "the .ct-test row gap stays the shared 0.25rem = 2.5px", + ); + assert_eq!( + eval_string( + &tab, + "getComputedStyle(document.querySelector('#col-tooltip .ct-test')).columnGap" + ), + "7px", + "the .ct-test column gap is the pass-2 0.7rem = 7px at the 10px \ + Sakura root (cute-dbt#233 audit D5 — pass-1 shipped 0.45rem = 4.5px)", + ); + + let _ = tab.close(true); +} + +#[test] +#[ignore = "requires Chrome; runs explicitly in the headless-zero-egress CI job via `-- --ignored`"] +fn badge_tip_bubble_rounds_at_small_radius() { + // cute-dbt#233 (audit D6) — pass-2 (engine/base.css:548-556) rounds + // the badge-borne bubble at `--radius-sm`; pass-1 let it inherit the + // shared `.expect-tooltip-bubble` `--radius-pan`. Asserted against + // in-page reference elements so no px values are hardcoded (the + // tokens vary per style pack); the sm≠pan precondition keeps the + // equality's teeth honest. + let url = render_to_file( + "headless_233_badge_radius.html", + vec![model_node_materialized("model.shop.dim_inc", "incremental")], + vec![( + "unit_test.shop.dim_inc.t", + incremental_dict_expect_test("t", "dim_inc"), + )], + &["model.shop.dim_inc"], + &[], + ); + let browser = launch_browser(); + let tab = browser.new_tab().expect("new tab"); + tab.navigate_to(&url).expect("navigate"); + tab.wait_until_navigated().expect("await navigation"); + wait_for_document_ready(&tab); + select_model(&tab, "dim_inc"); + select_test(&tab, "unit_test.shop.dim_inc.t"); + + let m = eval( + &tab, + "(function(){\ + var ref = document.createElement('div');\ + document.body.appendChild(ref);\ + ref.style.borderRadius = 'var(--radius-sm)';\ + var sm = getComputedStyle(ref).borderRadius;\ + ref.style.borderRadius = 'var(--radius-pan)';\ + var pan = getComputedStyle(ref).borderRadius;\ + ref.remove();\ + var got = getComputedStyle(document.querySelector(\ + '.expected-panel .mode-badge.has-mode-tip .expect-tooltip-bubble'\ + )).borderRadius;\ + return {got: got, sm: sm, pan: pan};})()", + ); + let got = m["got"].as_str().expect("bubble radius resolves"); + let sm = m["sm"].as_str().expect("--radius-sm resolves"); + let pan = m["pan"].as_str().expect("--radius-pan resolves"); + assert_ne!( + sm, pan, + "precondition: the default pack's --radius-sm and --radius-pan \ + differ, so the equality below has teeth", + ); + assert_eq!( + got, sm, + "the badge-borne bubble rounds at --radius-sm (cute-dbt#233 audit \ + D6 — the unfixed CSS inherited --radius-pan = {pan})", + ); + + let _ = tab.close(true); +} + #[test] #[ignore = "requires Chrome; runs explicitly in the headless-zero-egress CI job via `-- --ignored`"] fn expected_meta_row_reads_left_without_an_expect_diff() { diff --git a/tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap b/tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap index 4a4f9c6f..efa756df 100644 --- a/tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap +++ b/tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap @@ -975,7 +975,7 @@ g.dag-node:focus-visible { outline: 2px dashed var(--accent); outline-offset: 3p .mode-badge.has-mode-tip, .this-badge.has-mode-tip { position: relative; cursor: help; } .mode-badge.has-mode-tip:focus-visible, .this-badge.has-mode-tip:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } .mode-badge.has-mode-tip .expect-tooltip-bubble, -.this-badge.has-mode-tip .expect-tooltip-bubble { left: 0; transform: none; z-index: 60; box-shadow: 0 6px 22px rgba(0,0,0,0.3); } +.this-badge.has-mode-tip .expect-tooltip-bubble { left: 0; transform: none; z-index: 60; box-shadow: 0 6px 22px rgba(0,0,0,0.3); border-radius: var(--radius-sm); /* cute-dbt#233 (audit D6) — pass-2 rounds the badge-borne bubble at --radius-sm; the shared .expect-tooltip-bubble keeps --radius-pan */ } .mode-badge.has-mode-tip:hover .expect-tooltip-bubble, .mode-badge.has-mode-tip:focus .expect-tooltip-bubble, .mode-badge.has-mode-tip:focus-visible .expect-tooltip-bubble, @@ -1303,8 +1303,20 @@ th.has-col-meta:focus-visible { outline: 2px solid var(--accent); outline-offset .col-tooltip .ct-desc { font-size: 1.12em; line-height: 1.5; opacity: 0.97; } .col-tooltip .ct-tests-label { margin-top: 0.6rem; font-size: 0.82em; text-transform: uppercase; letter-spacing: 0.07em; opacity: 0.6; } .col-tooltip .ct-tests { display: flex; flex-direction: column; gap: 0.4rem; margin-top: 0.3rem; } -.col-tooltip .ct-test { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.45rem; } -.col-tooltip .ct-key { font-family: var(--font-mono); font-weight: 700; color: var(--accent); } +.col-tooltip .ct-test { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.7rem; } +/* cute-dbt#233 (audit D4) — the pass-2 readable-on-dark accent: raw + var(--accent) lands ≈3:1 against the always-dark tooltip fill on the + 4 light themes (AA fail); the oklab mix toward white clears 4.5:1 on + all 8 themes (headless-pinned). */ +.col-tooltip .ct-key { font-family: var(--font-mono); font-weight: 700; color: color-mix(in oklab, var(--accent) 60%, white); } +/* cute-dbt#233 — Latte ct-key AA: the spec's own 60% mix lands 3.37:1 on + latte's tooltip fill (#4c4f69 — the lightest of the 8; the spec tokens + carry the same value, so this is empirically the spec's gap, not ours). + The #198/#206/#227 pattern: a scoped stand-in only — the mix shifts + toward white for latte alone (≈5:1 measured); the verbatim latte theme + block and the other 7 themes' spec-literal mix stay pinned. Covers the + model tip too (it rides the .col-tooltip shell). */ +:root[data-theme="latte"] .col-tooltip .ct-key { color: color-mix(in oklab, var(--accent) 35%, white); } .col-tooltip .ct-vals { display: inline-flex; flex-wrap: wrap; gap: 0.25rem; } .col-tooltip .ct-val { background: rgba(255,255,255,0.14); border-radius: 4px; padding: 0.05rem 0.42rem; font-family: var(--font-mono); font-size: 0.9em; white-space: nowrap; } /* (cute-dbt#202 — the .ct-detail run is retired: detail strings now split @@ -2369,7 +2381,10 @@ html[data-style="swiss"] .suppress-chip { border-radius: 0; } // authored YAML exactly as cute-dbt#69 did. var hasDiff = t.yaml_diff && t.yaml_diff.lines && t.yaml_diff.lines.length; var $yamlDet = $("
").addClass("authoring-yaml").attr("open", "open"); - $yamlDet.append($("").text(hasDiff ? "Authoring YAML — diff" : "Authoring YAML")); + // cute-dbt#233 (audit D7) — pass-2 labels the drawer "Model YAML" + // with no diff-variant suffix; the Diff/File toggle in the code + // header carries the diff affordance. + $yamlDet.append($("").text("Model YAML")); var $yamlWrap = $("
").addClass("sql-block-wrap fixture-sql-block-wrap"); // cute-dbt#178 — GitHub-style code-card header: the file path sits left // of the Diff/File toggle. `defined_in` is the test's authoring-YAML @@ -4836,13 +4851,14 @@ html[data-style="swiss"] .suppress-chip { border-radius: 0; } } // The external-fixture affordance: never a silently-empty grid. Names the - // file and points the reader at the Authoring YAML drawer (the #96 view), + // file and points the reader at the Model YAML drawer (the #96 view), // which surfaces the authored block including the `fixture:` line. + // (Pointer copy follows the cute-dbt#233 D7 drawer rename.) function buildExternalFixtureAffordance(name) { var $box = $("
").addClass("given-empty external-fixture-note"); $box.append(document.createTextNode("data in external fixture file: ")); $box.append($("").addClass("td-defined-in").text(String(name))); - $box.append(document.createTextNode(" — see the Authoring YAML drawer above for this test's block.")); + $box.append(document.createTextNode(" — see the Model YAML drawer above for this test's block.")); return $box; } From ad78fc9f5985a29ce4fe9499e485f88ded292baf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:35:49 -0400 Subject: [PATCH 2/2] test: guard the badge-bubble lookup in the D6 radius pin (review) A missing '.expect-tooltip-bubble' now surfaces as got == "" with an explicit precondition assert naming the selector, instead of a cryptic in-page TypeError from getComputedStyle(null) (gemini review on #237). Co-Authored-By: Claude Fable 5 --- tests/headless_toggle.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/headless_toggle.rs b/tests/headless_toggle.rs index 4da562b6..e768b1de 100644 --- a/tests/headless_toggle.rs +++ b/tests/headless_toggle.rs @@ -8107,12 +8107,19 @@ fn badge_tip_bubble_rounds_at_small_radius() { ref.style.borderRadius = 'var(--radius-pan)';\ var pan = getComputedStyle(ref).borderRadius;\ ref.remove();\ - var got = getComputedStyle(document.querySelector(\ - '.expected-panel .mode-badge.has-mode-tip .expect-tooltip-bubble'\ - )).borderRadius;\ + var bubble = document.querySelector(\ + '.expected-panel .mode-badge.has-mode-tip .expect-tooltip-bubble');\ + var got = bubble ? getComputedStyle(bubble).borderRadius : '';\ return {got: got, sm: sm, pan: pan};})()", ); + // A missing bubble surfaces as got == "" so the assert below names the + // mismatch cleanly instead of a cryptic in-page TypeError. let got = m["got"].as_str().expect("bubble radius resolves"); + assert!( + !got.is_empty(), + "precondition: the badge-borne bubble renders \ + ('.expected-panel .mode-badge.has-mode-tip .expect-tooltip-bubble' not found)", + ); let sm = m["sm"].as_str().expect("--radius-sm resolves"); let pan = m["pan"].as_str().expect("--radius-pan resolves"); assert_ne!(