Repository navigation
#145 — surface incremental-model unit-test semantics - #146
Conversation
Make incremental-model unit-test semantics legible in the report. A reviewer can now see that a model is incremental and that, for an incremental-mode test, Expected is the result of the materialization (the rows merged/inserted) -- NOT the final table after the merge. - domain: UnitTest.is_incremental_mode (flat Option<bool>) + a with_incremental_mode builder/accessor (new() stays 9-param); NodeConfig::materialized() POD accessor. - adapters: fusion-faithful open-map overrides ingestion (overrides.macros.is_incremental), null-tolerant (fusion emits unset channels as JSON null). - render: ModelPayload.is_incremental, TestPayload.is_incremental_mode, GivenPayload.is_this payload fields (skip-when-default). - template: U1 model "incremental" badge, U2/U3 per-test incremental/full-refresh branch badge + expect-semantics tooltip, U4 "prior model state" badge on a `given: - input: this`. The tooltip gates on the authoritative is_incremental_mode === true -- never the this-given proxy, never the full-refresh branch. - tests: BDD feature + self-contained step module (feature-count 11->12 in both ci.yml and lefthook.yml) + a headless DOM/text test proving the LOCKED tooltip placement; focused cargo-mutants on the three new predicates (10/10 viable caught). - dogfood: a synthetic incremental model + unit test spliced into the committed playground example (badges/tooltip/this-badge now visible) plus the dbt-project/ spike model for the live prdiff preview. Closes #145 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Warning Review limit reached
More reviews will be available in 58 minutes and 55 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (11)
📝 WalkthroughWalkthroughThis PR implements surface-level presentation of incremental-model unit-test semantics across the cute-dbt report. It adds domain modeling for incremental mode, ingests ChangesIncremental-Model Unit-Test Semantics Surface
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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 |
📄 Rendered report previewAll golden examples regenerated cleanly. 🟡 Golden examplesCommitted to
🐶 Live dogfood previewThis PR's own
Click Download to fetch the rendered HTML. Each artifact Alternative: GitHub CLI# gh CLI >= 2.63 extracts into ./report-preview-playground/.
gh run download 27105629506 -R breezy-bays-labs/cute-dbt -n report-preview-playground
open report-preview-playground/playground-report.htmlPosted by |
There was a problem hiding this comment.
Code Review
This pull request introduces support for incremental-model unit-test semantics (cute-dbt#145), including model-level incremental badges, per-test mode badges, expect-semantics tooltips, and "prior model state" badges for this given inputs. The changes span the manifest parser, domain models, payload rendering, HTML templates, and integration tests. A review comment identifies a potential UI leak where .mode-badge and .expect-tooltip elements are not cleared when switching models or when no unit test is selected, suggesting they be reset at the start of renderForSelectedModel.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
…el select renderExpectedPanel (which clears + re-appends the per-test mode badge and expect-semantics tooltip) runs ONLY when a unit test is selected. Selecting a modified-but-untested model leaves currentTest() null, so a prior incremental-mode test's badge + tooltip would leak on the persistent .panel-header. Clear them unconditionally in renderForSelectedModel (targeted -- never touches .expected-rowcount). Caught by gemini-code-assist on PR #146; covered by a new headless assertion (incremental tooltip present -> select an untested model -> badge + tooltip gone, with a precondition assert so it cannot pass as a no-op) and the regenerated example + render_integration snapshots. Refs #145 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@dbt-project/models/marts/order_events_incremental.sql`:
- Around line 16-19: The incremental WHERE clause uses (select max(order_date)
from {{ this }}) which can be NULL for an existing-but-empty target table; wrap
that subquery with a NULL-guard (e.g., COALESCE/NVL) to provide a safe minimum
date so the filter still returns rows on first incremental run — update the
conditional block in order_events_incremental.sql (the is_incremental() branch)
to compare order_date against COALESCE((select max(order_date) from {{ this }}),
<safe-min-date>) ensuring the fallback literal is the same type as order_date.
In `@examples/jaffle-shop-report.html`:
- Around line 4996-5000: Replace the non-focusable span created in the
$exHdr.append call with a focusable control (e.g., a <button type="button">)
that preserves the existing attributes (title and aria-label) and any
click/hover handlers so keyboard and touch users can access the tooltip; update
the DOM creation in the code that builds the `.expect-tooltip` element to create
a button instead of a span and ensure it receives the same text "ⓘ". Also add a
minimal CSS reset for the .expect-tooltip button (border: none; background:
transparent; padding: 0; margin: 0; font: inherit; cursor: help) so the visual
appearance remains identical while making it keyboard-focusable and accessible.
- Around line 4988-4991: The current code treats any non-true value as a
full-refresh which collapses omitted/null into "full-refresh branch"; update the
logic around t.is_incremental_mode and incrementalMode so you first check that
t.is_incremental_mode is explicitly a boolean (e.g., typeof
t.is_incremental_mode === "boolean") and only call $exHdr.append(...) when it is
boolean, using mode-incremental/mode-full-refresh and corresponding text for
true/false; if you prefer to show an unknown state instead, add a third branch
that renders a distinct badge (e.g., mode-unknown / "unknown mode") when
t.is_incremental_mode is null/undefined rather than treating it as full-refresh.
In `@examples/playground-report.html`:
- Around line 4996-5000: Replace the non-focusable span appended by $exHdr with
a real focusable control: create a <button> (or jQuery $("<button>")) with class
"expect-tooltip" instead of $("<span>"), remove the reliance on the title
attribute, and expose the explanatory text stored in the tip variable via an
accessible description — e.g. render a visually-hidden element containing tip
and set the button's aria-describedby to that element's id (or set a clear
aria-label if you prefer a short label), ensure the button has the same visible
text "ⓘ" and retains any existing event handlers so keyboard users can focus and
open the tooltip.
- Around line 4987-4991: The badge rendering treats any non-true/missing
t.is_incremental_mode as a "full-refresh" and should only render when the mode
is explicitly boolean; update the guard around the block that uses em,
em.is_incremental and t.is_incremental_mode so you first check typeof
t.is_incremental_mode === "boolean" (or explicit presence) before computing
incrementalMode and appending to $exHdr, and only compute incrementalMode =
t.is_incremental_mode === true and addClass/.text when that boolean check
passes.
In `@src/domain/unit_test.rs`:
- Around line 553-580: Extend the is_incremental_mode_flat_serde_roundtrip test
to also exercise Some(false): create a UnitTest via
none.with_incremental_mode(Some(false)), serialize to JSON and assert the
serialized string contains "\"is_incremental_mode\":false", then deserialize
back with serde_json::from_str::<UnitTest>() and assert
back.is_incremental_mode() == Some(false) and back == the original instance;
update assertions around is_incremental_mode(), with_incremental_mode, and the
final equality check to include this case so the flat serde round-trip covers
None, Some(true), and Some(false).
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7e28057c-6ff3-48e7-a21e-24e041b9e3d2
⛔ Files ignored due to path filters (2)
tests/snapshots/golden_report__rendered_report_skeleton.snapis excluded by!**/*.snaptests/snapshots/render_integration__rendered_chrome_jaffle_shop.snapis excluded by!**/*.snap
📒 Files selected for processing (22)
.github/workflows/ci.ymlAGENTS.mddbt-project/models/marts/_incremental__models.ymldbt-project/models/marts/order_events_incremental.sqlexamples/jaffle-shop-report.htmlexamples/playground-pr-diff-report.htmlexamples/playground-report.htmlfeatures/incremental_models.featurelefthook.ymlsrc/adapters/manifest.rssrc/adapters/render.rssrc/domain/manifest.rssrc/domain/unit_test.rstemplates/report.htmltests/fixtures/MANIFEST.tomltests/fixtures/playground-current.jsontests/fixtures/playground-source/models/marts/core/_core__models.ymltests/headless_toggle.rstests/steps/builders.rstests/steps/incremental_models.rstests/steps/mod.rstests/steps/world.rs
… artifact naming Address PR #146 review (Christopher + CodeRabbit + gemini). Tooltip (Christopher's report: hovering the ⓘ showed nothing; CodeRabbit finding A): the expect-semantics tooltip is the load-bearing deliverable but was a native `<span title>` — hover-delayed, keyboard-unreachable, and often never painted. Reworked into a focusable `<button>` carrying a CSS bubble shown on :hover AND :focus (aria-label for assistive tech; bubble aria-hidden so it is not announced twice). Pure CSS — no asset, no JS tooltip lib — so the zero-egress gate holds. New headless guards: the tooltip is a <button>, the bubble carries the dbt wording, and FOCUS reveals the bubble (the keyboard path a native title never had; :hover shares the rule). Artifact naming (Christopher: the two --pr-diff artifacts need clearer names): rename the committed showcase example `playground-pr-diff-report.html` -> `diff-showcase-report.html` (matrix name `pr-diff-showcase` -> `diff-showcase`) and group the sticky-comment preview into two labelled sections — GOLDEN examples (committed, byte-identity gated: jaffle-shop, playground, diff-showcase) vs the LIVE dogfood preview (the PR's own ephemeral dbt-project render). Updated ci.yml, report-preview.yml, AGENTS.md, examples/README.md (also fixed the playground regen command to include the --project-root the byte-gate requires). CodeRabbit C: guard the spike model's incremental watermark against a NULL max() over an existing-but-empty target (coalesce to a safe min date). CodeRabbit D: cover the explicit Some(false) full-refresh wire state in the flat serde round-trip test. CodeRabbit B (None -> "full-refresh branch") is intentionally NOT changed — per dbt's documented default, a unit test on an incremental model runs in full-refresh mode unless overrides.macros.is_incremental is true, so an absent flag is a determinate full-refresh, not "unknown" (advisor-locked D6). Refs #145 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Re: the two expect-tooltip accessibility comments (now outdated — the fix regenerated those exact lines): fixed in 119b277. You were right, and it matched a real report from a human reviewer (hovering the ⓘ showed nothing) — a native |
The expect-semantics tooltip (templates/report.html) said Expected is "the rows that will be merged or inserted ... (the final table after the merge)". That wording is merge/append-centric: it is mechanically wrong for `insert_overwrite` (replaces whole partitions — nothing merges) and `microbatch` (per-window batches), and loose for `delete+insert`. Generalize to the strategy-invariant truth (true for all five strategies — append/merge/delete+insert/insert_overwrite/microbatch): "Expected is the output of the model's compiled SELECT on the incremental branch — the rows the configured incremental strategy will apply to the table — not the table's final state after the run." A dbt unit test compares the compiled SELECT for the chosen is_incremental branch and never exercises the materialization strategy (dbt-core#8664), so this is a copy-precision correctness fix, not a per-strategy code path. The single `tip` string drives both the visible CSS bubble and the aria-label (the #146 focusable-button pattern), so one edit keeps them in sync. - headless: extend incremental_badges_modes_tooltip_and_this_given to assert the new strategy-invariant phrase renders in BOTH the bubble and the aria-label, and that the old "merged or inserted" wording is absent (RED before the template edit, GREEN after). - BDD: update incremental_models.feature prose + matching step regexes to strategy-generic phrasing (the step bodies assert the is_incremental_mode payload bool — behaviour unchanged). - byte-identity regen: the 3 golden examples (jaffle-shop/playground/diff-showcase) + the chrome HTML insta snapshot — intended-only (the 3-line tip flip, nothing else). - dogfood: add explicit incremental_strategy='merge' to the live dbt-project order_events_incremental model (config-only; `dbt parse` validated) so the --pr-diff self-dogfood exercises the tooltip on a fresh CI-compiled manifest. Closes #159 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ata tests (#166) * feat(domain): column descriptions + column-test attribution on Node (#165) Adds the TestMetadata POD (generic-test name / namespace / untyped kwargs — fusion dbt-schemas nodes.rs shape) and four tolerant Node fields: column_descriptions (non-empty authored prose only, a separate map from the .contract column-set so a description edit can never flag state:modified.contract), plus column_name / attached_node / test_metadata (the column-scoped test attribution). with_* builders keep the many existing Node::new call sites unchanged (the with_incremental_mode precedent). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(adapters): column-header tooltips — ingest, payload, th affordance (#165) Manifest adapter: WireColumn gains description (fusion serializes an unset one as "" — dropped at translation); WireNode gains column_name / attached_node / test_metadata (domain TestMetadata deserializes the wire verbatim, the Checksum/DependsOn reuse precedent). Render: ColumnMetaPayload {description, tests} computed in Rust — column_meta_for_model resolves authored descriptions + COLUMN-SCOPED generic tests (column_name set AND attached_node = the owning model; model-level tests with column args are out of v1 scope), summarized as package-qualified names plus key args for accepted_values (values) and relationships (to/field). Each given resolves the model that OWNS its columns (ref(...) input model; this = the target model), the expect table resolves the target model; maps are filtered to the rendered FixtureTable's columns and omitted when empty (no empty bubbles). Template: a focusable th <button> + CSS bubble on :hover AND :focus with aria-label (the #146 expect-tooltip contract, never a native title). One divergence: the bubble is position:fixed with JS-set coordinates because the th lives inside the .table-fit overflow-x scroller that would clip an absolute bubble; visibility stays pure CSS. Click is swallowed so tapping the info button never re-sorts the DataTable. The Diff grid's unified column axis shares the same map. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: column-tooltip headless guard, BDD payload facts, real-fixture pin (#165) Headless (Chrome): focus reveals the bubble (the keyboard path a native title never had); exactly one tooltip button per table — the described+tested column gets it, a bare column gets NONE (the no-empty-bubbles negative path); a given table resolves its own input model's metadata. BDD (extends report_generation.feature — no new .feature file, the feature-count gate stays 12): the embedded payload lists unique/not_null for the fixture's tested key column with the empty authored description dropped, and carries no entry for an undescribed, untested column. Real-fixture pin: the fusion-compiled jaffle-shop manifest's column descriptions, empty-description drop, unique/not_null attribution, and accepted_values kwargs all survive ingestion. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: dogfood column metadata + regenerate the three golden examples (#165) Live dogfood (dbt-project, YAML only): unique/not_null on order_events_incremental.order_id (the merge key) and authored descriptions on the staging columns the marts unit tests mock — the PR-diff preview report now surfaces the tooltips on given/this/expect tables. Synthetic showcase (playground): the fct_encounters_incremental model gains columns with descriptions + unique/not_null on encounter_id in the source YAML and the manifest (hand-edited, byte-stable JSON round-trip — synthetic-only, no regen, no root_path). The two new test nodes are mirrored into the BASELINE so the arm-2 "modified node with zero unit tests" scoping path cannot surface them as junk model cards (they pre-exist the synthetic PR). MANIFEST.toml shas updated. Goldens regenerated with the exact CI invocations; all three are byte-identical to the renderer output and now show the affordance: descriptions, unique/not_null, accepted_values (values: ...), relationships (to/field), dbt_expectations.* namespacing, and the this-given resolution. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ecklist + tiered findings + suppressed-count The render lane of the coverage-intelligence epic (#168): the per-model Coverage checks panel rendering the #186/#191 engine verdicts in-context. - Payload (render.rs): FindingPayload wraps the domain Finding (flattened, wire keys unchanged) with render-resolved pin_node (DAG anchor: bracketed constructs pin the named CTE node, model-level constructs the terminal) and sketches (the union check's 'suggested given' evidence lifted into copyable blocks). ReportPayload gains check_specs — the offline spec catalog (name/tier/instrument/conditions/exclusions/rationale + the click-only book_href), keyed by fired check only, serde-skipped when empty so findings-free payloads stay byte-stable. - Surface (report.html/interaction.js/report.css): a self-contained .model-findings section per model — three-valued checklist (mark + word, never colour-only), labeled tier chips (TOTAL solid / HIGH outlined / advisory muted — never blended, no percentages), covered-by attribution, evidence rows, evidence pinning (select + smooth-scroll + flash), the recommendation with #188-pattern copyable YAML sketches, the inline 'What is this check?' rationale drawer (fully offline; the book link is a plain click-only anchor), and the visible-but-quiet suppressed reveal (collapsed count -> rows with reason + source). Token-native on the #187 chassis; tier-chip tooltips use the #146/#188 bubble contract. - Dogfood: playground fixture gains unique_key on mart_dq_summary (grain UNCOVERED), an inline ignore pragma on dim_payers (suppressed-with-reason), and the synthetic-body-mod idiom on fct_clinical_events (union UNCOVERED with three given-row sketches) — all three goldens now show the panel, jaffle-shop deliberately renders the quiet empty state. - Tests: 7 render.rs payload units, 2 BDD scenarios (check_specs catalog facts), 2 headless guards (panel/checklist/tier distinctness/tooltip/ sketch+copy/rationale/book-link/pin/both-toggle-views/empty-state; suppressed collapsed-count + reason reveal); insta snapshots + goldens regenerated intended-only. Closes #170 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…+ tiered findings + suppressed-count (#194) * feat(adapters): #170 — report findings surface: per-model coverage checklist + tiered findings + suppressed-count The render lane of the coverage-intelligence epic (#168): the per-model Coverage checks panel rendering the #186/#191 engine verdicts in-context. - Payload (render.rs): FindingPayload wraps the domain Finding (flattened, wire keys unchanged) with render-resolved pin_node (DAG anchor: bracketed constructs pin the named CTE node, model-level constructs the terminal) and sketches (the union check's 'suggested given' evidence lifted into copyable blocks). ReportPayload gains check_specs — the offline spec catalog (name/tier/instrument/conditions/exclusions/rationale + the click-only book_href), keyed by fired check only, serde-skipped when empty so findings-free payloads stay byte-stable. - Surface (report.html/interaction.js/report.css): a self-contained .model-findings section per model — three-valued checklist (mark + word, never colour-only), labeled tier chips (TOTAL solid / HIGH outlined / advisory muted — never blended, no percentages), covered-by attribution, evidence rows, evidence pinning (select + smooth-scroll + flash), the recommendation with #188-pattern copyable YAML sketches, the inline 'What is this check?' rationale drawer (fully offline; the book link is a plain click-only anchor), and the visible-but-quiet suppressed reveal (collapsed count -> rows with reason + source). Token-native on the #187 chassis; tier-chip tooltips use the #146/#188 bubble contract. - Dogfood: playground fixture gains unique_key on mart_dq_summary (grain UNCOVERED), an inline ignore pragma on dim_payers (suppressed-with-reason), and the synthetic-body-mod idiom on fct_clinical_events (union UNCOVERED with three given-row sketches) — all three goldens now show the panel, jaffle-shop deliberately renders the quiet empty state. - Tests: 7 render.rs payload units, 2 BDD scenarios (check_specs catalog facts), 2 headless guards (panel/checklist/tier distinctness/tooltip/ sketch+copy/rationale/book-link/pin/both-toggle-views/empty-state; suppressed collapsed-count + reason reveal); insta snapshots + goldens regenerated intended-only. Closes #170 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(adapters): #170 review dispositions — in-place Cytoscape pin, flash-timer reset, own-property tally guard - finding-pin under the Cytoscape engine now emits a tap on the live cy node (the exact bound click path: in-place lineage classes + __cuteSelectNode) instead of renderDag() — honoring the #180 no-rebuild-per-click contract (CodeRabbit); headless guard added (same cy instance + .sel class after a pin). - rapid re-pins clear the pending flash timer so an old timeout can't cut the new animation short (gemini). - verdict tally uses an own-property guard (gemini). - goldens + chrome snapshot regenerated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(adapters): #170 post-rebase hardening — tag-like payload escaping + qualified-construct pins Rebased onto main with the #173 join check pair (#195), whose spec prose ('WHERE <right>.<key> IS NULL') now rides the payload through the #170 check_specs catalog: - payload_json_for_html_script escapes EVERY tag-opening '<' shape ('</', '<!', '<?', '<letter') to \u003c — inert in browsers either way, but raw '<right>' read as markup to non-HTML5 tag scanners (the tl-based BDD payload extractors choked); bare '< ' / '<digit' (compiled-SQL comparisons) stay raw, preserving the documented contract. - resolve_pin_node understands the #173 qualified construct form (left_join[<consumer>:<right>]) and pins the consumer CTE; unit test added. - the #195 suggested-given BDD steps read the sketch from the finding's 'sketches' array (the #170 lift moved it out of evidence — their sketches get the copy-button affordance for free); the step now also pins that the entry is never duplicated back into evidence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…217) * feat(adapters): design-2 layout restructure + DAG node-detail shelf (#201) Restructure the report body around the model -> test split (the pass-2 design canon, merged INTO shipped code - never a file replacement): - .dag-stage wraps .dag-canvas-col (Mermaid + Cytoscape hosts + legend) beside the new aside.dag-shelf (head: title + close; body). Clicking a DAG node opens the shelf: model-detail card from the #200 manifest_nodes lookup (tags -> name+materialized -> description -> model tests -> columns with click-to-expand test-count chips) + compiled SQL with the role badge riding the summary row. Given fixtures are deliberately NOT rendered in the shelf - they live in the Given panel (the #131 ordinal binding rides renderAllInputs' loop index). - Node->model mapping: final-select -> this model; import -> the ref() target of its bound given (the #131 bound_to_node seam); unknown -> card omitted, compiled SQL still shows. - window.__cuteSelectNode now opens the shelf - the #192 Cytoscape tap and the #194 findings pin both keep routing through this one seam (no renderDag per click; Mermaid's activate keeps its re-render). Model/test re-selection closes the shelf and re-centers the DAG. - section.test-section/.test-card below the DAG: the unit-test selector IS the card title (sizeSelectToWidest: static width, max-width clamped), the #91 scope toggle to its right (data-testid retained), the .test-badges row (tb-tag / untagged tb-muted / tb-meta - the overrides badge arrives with #202), the larger-font always-open description + details body. Founder decision on epic #197: always-open card, NOT a drawer; only the authoring-YAML drawer stays collapsible. - .model-findings (#194) preserved INTACT, placed ABOVE the model SQL + DAG near the model selection (founder decision on epic #197, superseding the audit's between-DAG-and-test-section proposal). - test-description-section and the left .panel-toggle (Inspect / All-inputs) retired: the left panel header reads Given and always renders all inputs; the #74 no-empty-landmark intent carries over via the hidden attribute on the in-card description. - The DAG hint subtitle becomes the #146-contract focusable button + CSS bubble (hover AND keyboard focus, aria-label parity). - Kept shipped: theme-aware cyto-dag.js palette, ModelPayload.path in renderModelSql (no <name>.sql regression), the authoring-YAML drawer labels and Diff/File toggles. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: port-forward headless guards to the #201 shelf layout + new shelf guard Every re-target is deliberate, never collateral (#201): - findings_panel_renders_checklist_tiers_sketch_rationale_and_pin + cytoscape_hover_card_appears_and_tap_highlights_lineage_in_place: .left-panel-body .node-detail -> .dag-shelf-body .node-detail (the __cuteSelectNode seam survives; the pin still selects + scrolls + pin-flashes). - same_ref_givens / changed_cell_null-vs-string / cell_diff_toggle / fusion-csv / sql-literal / external-fixture / column-tooltip / incremental guards drop the show_all_inputs panel-toggle click (the Given panel always renders every input now); the helper is retired. - incremental_badges_modes_tooltip_and_this_given: BUBBLE_VIS scoped to the expected panel - the DAG hint is a second .expect-tooltip on the page since #201, so a bare first-match read the wrong bubble. - stacked_panel_does_not_blow_out_viewport_at_375px re-verified on the new layout (the .dag-stage flex row wraps below on narrow - the #157/#158 contract holds). - source_given fixture-card guard re-homed: the card renders in the Given panel; clicking the bound import CTE opens the shelf with compiled SQL and NO fixture card (the no-fixtures rule). - NEW dag_node_click_opens_shelf_with_model_card_and_close_clears_selection: shelf opens with compiled SQL + role badge on the summary; the final-select node renders the manifest_nodes model card (description + tags); an unmapped node omits the card but keeps compiled SQL; the close button clears body + baked Mermaid selection; a fresh test selection closes the shelf; the DAG-hint info button honors the #146 focus-reveals-bubble contract. - BDD: the #74 ordering scenario re-homed - the test section (hosting the description) renders between the cte-dag section and the panel-row. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: regenerate golden examples + re-accept insta DOM snapshots for #201 examples/{jaffle-shop,playground,diff-showcase}-report.html regenerated with the exact ci.yml example-report-check recipe; every changed byte traces to the #201 templates (report.html DOM, interaction.js, report.css) - audited via git diff --text -U0. No root_path/username bytes. Insta skeleton + chrome snapshots re-accepted for the intended restructure only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… reconstruction, overrides badge, info tips (#220) Three body-appended singleton tips (#model-tooltip / #fmt-tooltip / #ov-tooltip) on mouseenter AND focusin behind focusable triggers: the Given header renders ref( + .gt-model pill + ), the Expected panel gains its model pill (header fallback or fixture-view bar, bar order [model pill][mode badge][row count] with the Diff/File toggle far right), the format badge shows for ALL formats incl. dict and reconstructs the fixture client-side (sql/csv/yaml), and the test badge row gains the 'overrides . N' badge with grouped key = value rows. Founder decision (epic #197) applied: the incremental-branch mode badge and the prior-model-state this-badge keep the #146 focusable-trigger CSS-bubble mechanism with the pass-2 badge-borne styling - the separate expected-panel i button is retired, the pass-2's JS info-tip twin for those badges is not ported (exactly one mechanism), and the LOCKED D4 invariant holds (tooltip rides is_incremental_mode === true, never the this-given proxy, never full-refresh). Column-header tooltips: detail strings now render as ct-vals/ct-val chips (ct-detail retired); per-table column_meta sourcing unchanged. The #161 idempotent clear-list extends to .expected-model-badge. Every interpolated tip value passes through escapeHtml (payload strings are attacker-adjacent YAML). Goldens regenerated (template-only deltas, payload byte-stable, double-render byte-identical); explore pages untouched. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
#222) A Coverage intelligence switch in the report settings panel (default ON, keyboard-operable checkbox per the #146 a11y discipline). OFF sets html[data-coverage=off] and ONE report.css rule hides the .model-findings panel — the only check-engine-derived surface in the report (checklist, tier chips, suppressed-count reveal all live inside it). Purely a DISPLAY toggle: the payload and rendered DOM stay untouched, so ON restores in place with zero re-render. Wired + persisted by theme.js as the coverage field of cute-dbt.appearance.v1 ("on"/"off", absent = "on") — the same mechanism as theme/density/the engine picker, and the cross-page contract the explorer arm (#103/#104) adopts when check-derived content lands there. Distinct from the producer-side [checks] selection/suppression (#171): checks stay computed; an individual viewer hides the surface. Headless guard: default-ON boot, focus + REAL Space keypress toggles OFF (findings hidden, checklist rows stay in the DOM), click restores ON, persistence across reload. Goldens regenerated (delta = settings markup + theme.js + css only; explore pages untouched); insta snapshots accepted intended-only. Part of #219 (explorer arm deferred — no check-derived content renders on the explore pages yet, and #221 owns their templates this cycle). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ted badges left, 13.44px tip text (#234) * fix(adapters): pass-2 conformance — edge-aware tooltip bubbles, expected badges left, 13.44px tip text (#232) Executes the three fix recipes from the 2026-06-11 design-conformance audit (epic #197 pass-2 spec): - R2 (audit D2): renderExpectedPanel always emits the .fixture-view-bar meta row for table renders — [model pill][mode badge][N rows] reading left under the Expected title, mirroring Given; the right-aligned, order-reversed .panel-header fallback is retired (sql-format / external-fixture early-return paths keep the header placement, spec-consistent). The #178 persistent-rowcount rescue is untouched. - R1 (audit D1): edge-aware CSS bubbles — belt-and-braces right-anchor for header-placed badges (the spec's base.css precedent), a geometry- only data-tip-edge tagger on the delegated mouseenter/focusin path (visibility stays pure CSS — the #146/#161 contract intact), and the shared bubble max-width capped at min(70vw, calc(100vw - 16px)). Opportunistic D18: positionTipNear resets left before measuring offsetWidth (stale-width clamp overshoot). - R3 (audit D3): bubble text 0.78rem (7.8px at Sakura's 62.5% root) → 13.44px, matching the column-tooltip description (12px shell base × 1.12em). Headless: three new RED-first pins (375px-class viewport containment, computed-size equality vs #col-tooltip .ct-desc, no-diff meta-row structure) + existing placement pins updated to the spec-true bar location; the #146 focus-reveals-bubble guard stays green. Goldens regenerated with the CI commands; every byte delta traces to the R1/R2/R3/D18 blocks. Closes #232 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: 1px tolerance on #232 viewport-edge assertions (review suggestion) getBoundingClientRect returns fractional px in headless Chrome; the exact bounds (right <= vw, left >= 0.0) were a flake vector. 1px tolerance mirrors the #157 sub-pixel precedent — the guarded regression is 23-47px of clipping, so the teeth are intact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…l-tip ct anatomy (#236) Concern 1 — given column-header tooltips: the JS binder never regressed; the given-side column_meta payload (#165/#166) only ever resolved ref()-to-MODEL and 'this' inputs. A seed-ref given (the committed jaffle-shop ref('raw_customers') wire shape) and a source('a','b') given carried NO column metadata, so their headers offered no tooltip while the expected table's did — the symptom found reviewing PR #230. Fix: - domain: SourceNode ingests authored per-column descriptions (with_column_descriptions builder, the Node precedent; fusion ManifestSource.columns via serialize_dbt_columns, dbt-schemas manifest_nodes.rs @ 9977b6cb, verified on the committed playground fixture's synthea_raw.patients.Id) - adapter: WireSource parses the columns map (empty-string unset descriptions dropped, the #165 rule) - render: resolve_given_ref_node resolves givens over dbt's refable set (model | seed | snapshot); column_meta_for_source merges source column descriptions + column-scoped tests attached to the source node; shared attached_column_tests keeps both arms deterministic - honest degrade preserved: a metadata-less given column renders NO trigger — never an empty bubble Concern 2 — model-badge tooltip anatomy: the model-ref tip's test rows now ride the COLUMN tooltip's own .ct-test/.ct-key/.ct-vals anatomy via the shared ctTestHtml builder (never a parallel chip system); test names keep the readable-on-dark color-mix accent treatment; the .mt-mtests/.dt-* dress is retired inside the tip (the shelf card keeps its light-surface .dt-* chips). The .col-tooltip .ct-key color stays #233's lane — untouched. Guards: 3 new headless tests (seed+source given tooltips on hover AND focus per the #146/#161 contract, honest-degrade no-empty-bubble, model tip chip anatomy + computed accent color), 3 new render payload tests, domain/adapter ingestion tests; the #202 model-tip pin updated to the new anatomy. Goldens regenerated (the playground source given now ships a real Id tooltip); insta chrome snapshot rebaselined. Closes #235 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ps, covered-by contrast, overrides-badge clip, baseline banner (#244) * fix(adapters): #240 — review-2 render defects: tooltip overflow, given-header tooltips, covered-by contrast, overrides-badge clip, baseline banner Five founder-reported defects from the PR #239 review (issue #240): A — long mono tokens (dbt_expectations.expect_table_row_count_to_be_between) painted 55px past the model tip's painted background: overflow-wrap on the .col-tooltip shell + wrapping .ct-val chips. B — given column headers with no manifest metadata were silently hover-dead (third report): EVERY fixture header is a trigger now; metadata-less ones reveal a truthful fallback naming the owning node (never an empty bubble — the honesty invariant strengthens). C — .finding-covered-by code rode Sakura's light #f1f1f1 code bg at ~1.2:1 on dark themes: themed var(--bg-alt)/var(--text) re-skin (5.9–14.8:1 measured across all 8 themes). D — .ov-tooltip nowrap rows painted long override values past the bubble and the viewport edge: rows flex-wrap + overflow-wrap, singleton width capped at calc(100vw - 16px), positionTipNear clamps against documentElement.clientWidth (scrollbar-safe). E — code.diff-scope-baseline same root cause as C, same fix. Goldens regenerated (jaffle-shop, playground, diff-showcase) — every hunk is the embedded template change; snapshot updated likewise. Closes #240 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: #240 — universal hover-parity guard + containment + AA pins (the guard-methodology fix) Why #166/#236 validations passed while the founder's experience stayed broken: they asserted tooltip reveal on columns KNOWN to carry manifest metadata (existential), while the founder hovered undeclared columns — hover-dead by the old design, outside every assertion universe. The new guards quantify universally and pin geometry: - fixture_header_tooltips_universal_after_view_toggle_and_sort: EVERY visible given/expected header must reveal a non-empty bubble on a real mouseover — initial render, after a real Diff→File toggle, after a real column sort, and after the round-trip (the #145/#146 wipe class). - model_tip_long_test_name_wraps_inside_bubble: content containment (scrollWidth ≤ clientWidth + 1; no descendant past the bubble edge). - ov_tip_long_value_contained_in_bubble_and_viewport_at_right_edge: long-override containment + viewport containment at a true right-edge trigger geometry. - covered_by_test_ids_meet_aa_contrast_on_every_theme + baseline_banner_path_meets_aa_contrast_on_every_theme: the #206/#227/ #231/#233 effective-backdrop AA family extended to defects C/E, all 8 themes (RED at 1.2:1 pre-fix). Three #235-era pins are deliberately rewritten to the new contract (metadata-less headers are fallback triggers, never dead): the no-EMPTY- bubble honesty invariant is preserved and strengthened. All five new guards captured RED against the unfixed templates (stash- verified) and GREEN after the fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: dbt-project README touch — render the live dbt-project preview row for #240 validation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(adapters): #240 — givenOwnerLabel accepts double-quoted ref()/source() inputs (PR #244 review) dbt accepts both quote styles in a given's input:, and dbt-fusion ships the authored string VERBATIM — a ref("stg_payments") given compiles onto the manifest wire double-quoted, unnormalized (verified against a real fusion 2.0.0-preview.177 compile of the dogfood project). The owner-label regexes behind the metadata-less fallback bubble were single-quote-only, degrading double-quoted inputs to the raw-string fallback. Quote-class + backreference now accepts both styles (mixed quotes stay unparsed -> truthful raw-input fallback). New headless guard given_owner_label_accepts_double_quoted_ref_and_source (RED without the regex fix, stash-verified). Goldens + chrome snapshot regenerated; every hunk is the embedded regex/comment change. The repo's Rust-side parsers (parse_ref_name / parse_source_ref) share the single-quote-only gap pre-dating this PR; tracked separately. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(adapters): #240 — latte 'Covered by' label AA (4.37 -> 5.53) + sweep transition hazard (PR #244 verification residual) The covered-by CODE pills pass AA on all 8 themes, but the 'Covered by ' prefix label (.finding-covered-by .f-label, 11.88px weight-400 so the 4.5 floor applies) rode the verbatim latte --text-muted: #6c6f85 on the finding row's #eff1f5 fill = 4.37:1. The established #227/#231 deepened stand-in extends to this label via the narrowest token override (:root[data-theme=latte] .finding-covered-by .f-label { --text-muted: #5c5f77 }) — re-measured empirically on the actual finding-row backdrop: 5.53:1. rosepine (4.56) stays verbatim; the latte theme block stays pinned. Guard changes (the existential-vs-universal lesson applied to AA sweeps): - the covered-by sweep now measures EVERY text run in the quoted line (code pills AND the f-label prefix), tags entries by element kind, and asserts the label run is present — stash-verified RED at latte 4.37 without the override, GREEN at 5.53 with it; - both #240 sweeps (covered-by + baseline path) inject '* { transition: none !important }' for the sweep's duration: body transitions background/color over 120ms, so an instant setAttribute + getComputedStyle read can land mid-transition (verifier-observed false readings). Goldens + chrome snapshot regenerated; every hunk is the embedded CSS override/comment. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(adapters): #240 — trim whitespace-only column descriptions + post-flip top clamp (PR #244 CodeRabbit threads) Thread 1 (applied): a whitespace-only authored description (' ') passed the hasMeta gate untrimmed, suppressing the cute-dbt#240 truthful fallback and opening an effectively-empty bubble on a description-only column — the exact state the never-empty-bubble contract forbids. decorateColHeader (the single writer of data-col-desc) now trims once; aria-label, the fallback arm, and the bubble all read the trimmed value. New headless guard whitespace_only_description_degrades_to_truthful_fallback (stash-verified RED untrimmed, GREEN trimmed). Thread 2 (applied): positionTipNear gains the post-flip top clamp (top < 8 -> 8) — at pathological viewport heights the flipped position (trigger top - bubble height) goes negative, pushing the bubble above the screen. Mirrors the horizontal clamp; resolves the clamp half of cute-dbt#246 (the fmt-tooltip overflow half stays there). The existing right-edge containment guard now also pins top >= 0. Goldens + chrome snapshot regenerated; every hunk is the embedded trim/clamp change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ution honesty (#283) * feat(domain): #259 — degraded backing, exists-but-disabled, singular honest-UNKNOWN in the grain check The grain.unique-key-unbacked verdict stops overclaiming on the #258 test-config wire (the cute-dbt#259 truthfulness pack): - DegradedBacking POD + Finding.degraded (serde-skipped when empty — payload byte-stability): a covering test that is warn-severity, where-filtered, or limit-capped still attributes, with every cause enumerated per test in domain-composed copy. Never a fourth verdict, never a percentage — the three-valued covered/uncovered/unknown vocabulary stays the trust contract; the cue rides in-row beside the attribution (#262 copy principles). An unrecognized severity surfaces its raw value, never guessed at. - exists-but-disabled evidence: a disabled uniqueness test on the declared grain (config.enabled: false in nodes, or a generic-test entry in the Manifest.disabled map — the shared uniqueness_columns recognizer now serves both linkage shapes) never counts as coverage but surfaces as a distinct fact from absent. - singular-test linkage: with no enabled generic uniqueness backing, an enabled singular (SQL-file) test referencing the model via depends_on (the only wire linkage singular tests carry, #258) degrades the verdict to honest UNKNOWN — evidence states what WAS checked and enumerates the singular tests — never a false Uncovered nag on singular-test shops. Disabled-map singular entries carry no linkage (both engines empty depends_on on disabled nodes) and are declared out in the spec exclusions. Spec conditions/exclusions mirror the new predicate; registry.toml + the book check page are regenerated from SPECS (byte-gated). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(adapters): #259 — degraded-backing chip + per-test cause list on the findings panel FindingPayload flattens the domain Finding, so the new degraded array rides the wire unchanged (pinned by a build_payload test). The findings panel renders it pure-presentationally: a summary 'degraded backing' chip ONLY when every attributing test is weakened (no full-strength backing at all — a partially degraded attribution keeps the summary quiet, no false alarm), under the #146/#188 tooltip contract (focusable trigger, aria-label, CSS-positioned bubble on hover AND focus, never a native title); plus the per-test enumerated causes beside the Covered-by attribution either way (in-row honesty). The exists-but-disabled and singular-test cues render through the existing generic evidence list — no new affordance needed. Chip styling pairs always-AA --text with the non-text amber edge token, so no per-theme contrast stand-in is needed; the latte .f-label deepening extends to the new label. Chrome snapshot re-accepted (CSS/JS bundle only). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: #259 — truthfulness fixture splice + BDD/headless/real-fixture pins + golden regen playground-current.json (current side only, leak-checked node-level splice — every other byte verified identical): unique_dim_payers_payer_key gains severity warn, unique_fct_provider_metrics_provider_metrics_key gains where ('year_actual >= 2024') + limit 100 (each with the matching authored unrendered_config twin), int_patients__never_admitted gains unique_key = 'patient_id' plus a hand-authored synthetic SINGULAR test (assert_never_admitted_patients_distinct, cloned field-for-field from the engine-emitted assert_patient_dates_valid shape, depends_on-linked only), and the disabled map gains a fourth entry — a disabled unique test on mart_dq_summary.entity_type cloned from the engine-emitted disabled-generic specimen. MANIFEST.toml sha256 + provenance updated. Pins: four new check_engine real-fixture tests (warn-degraded covered, where+limit causes, exists-but-disabled on the UNCOVERED row, singular-only honest UNKNOWN); the #258 ingestion pin extended to the fourth disabled entry (none weakened); four new BDD scenarios over the subprocess wire (degraded marks ⊆ by, disabled-MAP placement, singular unknown) with the builders growing a disabled-map injection arm; one new headless test driving the chip/tooltip/causes/quiet-partial DOM. Goldens regenerated per the ci.yml recipes; payload-level diff audit confirms intended-only deltas: dim_payers covered+degraded(warn) and int_patients__never_admitted unknown(singular) and mart_dq_summary uncovered+exists-but-disabled in playground-report, fct_provider_metrics covered+degraded(where,limit) + mart_dq_summary in diff-showcase, check_specs prose everywhere, explore tests.html payload only; jaffle diff is the CSS/JS bundle alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore(domain): #259 — borrow instead of allocate in coverage scans Review follow-up (Gemini, PR #283): the grain detector's covering scan collects (&str, &Node) and sorts on the borrowed key — ids become owned Strings only at the POD ownership boundary (verdict.by / DegradedBacking.by), dropping the intermediate owned collection; singular_tests_on returns Vec<&str> borrowed from the manifest — its sole consumer (grain_fallback_verdict) only formats the ids into evidence copy, so owning them allocated just to discard. Pure refactor: no payload change, goldens byte-identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Summary
Makes incremental-model unit-test semantics legible in the report. A reviewer reading a unit test on an incremental model can now see two load-bearing facts that were previously invisible:
expectis the result of the materialization (the rows that will be merged/inserted), NOT the final table after the merge.What ships
incrementalpill next to the model selector.incremental branch/full-refresh branch, driven byoverrides.macros.is_incremental.is_incremental_mode === true) — never on the full-refresh branch (thereexpectis the final table) and never keyed off thethis-given proxy.prior model statebadge on agiven: - input: this.How (TDD, layered — one PR)
UnitTest.is_incremental_mode: Option<bool>(flat serde, skip-if-none) + awith_incremental_modebuilder/accessor (new()stays 9-param);NodeConfig::materialized()POD accessor.overrides.macros.is_incremental), null-tolerant — fusion emits unset override channels as explicit JSONnull(verified against a real manifest).ModelPayload.is_incremental,TestPayload.is_incremental_mode,GivenPayload.is_this.currentModel(), theis_recursiveprecedent).thisgiven shows no tooltip — proving the gate keys off the authoritative bool, not the proxy). Focusedcargo mutantson the three new predicates: 10/10 viable mutants caught.playground-current.jsonexample (badges/tooltip/prior model statenow visible inexamples/playground-report.html) + thedbt-project/spike model for the liveprdiff-preview.Design notes (fusion-grounded)
given.input == "this"is fusion's own discriminator (resolve_unit_tests.rs);overridesis three sibling open maps (env_vars/macros/vars); the mode flag rides the specificmacros.is_incrementalkey (overrides can appear on non-incremental tests too).feat→ patch at v0.x (additive; no CLI/output break) — noBREAKING CHANGEfooter.Verification (all local gates green)
fmt · clippy
--all-targets --locked -D warnings· nextest 697 passed ·--test bdd85 scenarios / 543 steps (12 features) · render_integration · headless_toggle (21, incl. the new incremental DOM test) · headless_zero_egress (zero external requests against the regenerated examples) · rustdoc-D warnings· llvm-cov 98.85% (gate 85) · deny · mdbook · all 3 examples byte-identical locally · feature-count==12 mirror (ci.yml + lefthook.yml) · non-mirror-guard.Follow-up (non-blocking)
Cross-engine confirmation of the
"this"literal +overrideswire shape on dbt-core (only fusion is local here) — filed separately; does not block this PR.Closes #145
🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Tests
Documentation