Skip to content

#145 — surface incremental-model unit-test semantics - #146

Merged
cmbays merged 3 commits into
mainfrom
adapters-145-incremental
Jun 7, 2026
Merged

cmbays merged 3 commits into
mainfrom
adapters-145-incremental

Conversation

@cmbays

@cmbays cmbays commented Jun 7, 2026 •

Copy link
Copy Markdown
Contributor

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:

  1. The model is incremental — its tests behave differently from a table/view model's.
  2. dbt's documented gotcha — for an incremental-mode unit test, expect is the result of the materialization (the rows that will be merged/inserted), NOT the final table after the merge.

What ships

  • Model badge — an incremental pill next to the model selector.
  • Per-test branch badge — incremental branch / full-refresh branch, driven by overrides.macros.is_incremental.
  • Expect-semantics tooltip (the load-bearing deliverable) — the dbt gotcha wording on an ⓘ marker. LOCKED invariant: shown only on incremental-mode tests (is_incremental_mode === true) — never on the full-refresh branch (there expect is the final table) and never keyed off the this-given proxy.
  • prior model state badge on a given: - input: this.

How (TDD, layered — one PR)

  • domain: UnitTest.is_incremental_mode: Option<bool> (flat serde, skip-if-none) + 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 override channels as explicit JSON null (verified against a real manifest).
  • render: three skip-when-default payload fields — ModelPayload.is_incremental, TestPayload.is_incremental_mode, GivenPayload.is_this.
  • template: U1–U4 badges + tooltip; no payload denormalization (reads the enclosing currentModel(), the is_recursive precedent).
  • tests: BDD feature + self-contained step module (payload facts) + a headless DOM test (real Chromium) proving the rendered badges/labels/tooltip and the LOCKED placement (a full-refresh test carrying a this given shows no tooltip — proving the gate keys off the authoritative bool, not the proxy). Focused cargo mutants on the three new predicates: 10/10 viable mutants caught.
  • dogfood: a synthetic incremental model + unit test spliced into the committed playground-current.json example (badges/tooltip/prior model state now visible in examples/playground-report.html) + the dbt-project/ spike model for the live prdiff-preview.

Design notes (fusion-grounded)

given.input == "this" is fusion's own discriminator (resolve_unit_tests.rs); overrides is three sibling open maps (env_vars/macros/vars); the mode flag rides the specific macros.is_incremental key (overrides can appear on non-incremental tests too). feat → patch at v0.x (additive; no CLI/output break) — no BREAKING CHANGE footer.

Verification (all local gates green)

fmt · clippy --all-targets --locked -D warnings · nextest 697 passed · --test bdd 85 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.

Note: the Linux example-report-check byte-comparison is the one gate local-green cannot fully close (cross-platform determinism) — watching it on CI.

Follow-up (non-blocking)

Cross-engine confirmation of the "this" literal + overrides wire shape on dbt-core (only fusion is local here) — filed separately; does not block this PR.

Closes #145

🤖 Generated with Claude Code


Open in Stage

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for incremental model unit testing with proper test semantics
    • Added visual badges distinguishing incremental vs non-incremental models
    • Added mode indicators showing incremental branch vs full-refresh in tests
    • Added informational tooltips explaining expected results represent merged/inserted rows
  • Tests

    • Added comprehensive integration tests for incremental model scenarios
    • Added headless browser tests validating incremental UI behavior
  • Documentation

    • Added contributor guidelines for incremental model development

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>
@coderabbitai

coderabbitai Bot commented Jun 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cmbays, we couldn't start this review because you've reached your PR review rate limit.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6fd5c5b2-13a6-428c-a925-aa8bc0eeaac3

📥 Commits

Reviewing files that changed from the base of the PR and between db8ecc8 and 119b277.

⛔ Files ignored due to path filters (1)
  • tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap is excluded by !**/*.snap
📒 Files selected for processing (11)
  • .github/workflows/ci.yml
  • .github/workflows/report-preview.yml
  • AGENTS.md
  • dbt-project/models/marts/order_events_incremental.sql
  • examples/README.md
  • examples/diff-showcase-report.html
  • examples/jaffle-shop-report.html
  • examples/playground-report.html
  • src/domain/unit_test.rs
  • templates/report.html
  • tests/headless_toggle.rs
📝 Walkthrough

Walkthrough

This PR implements surface-level presentation of incremental-model unit-test semantics across the cute-dbt report. It adds domain modeling for incremental mode, ingests overrides.macros.is_incremental from dbt manifests via a tolerant wire adapter, serializes incremental flags and test modes to the JSON payload, updates templates with incremental badges and expect-materialization tooltips, and provides comprehensive BDD and headless test coverage.

Changes

Incremental-Model Unit-Test Semantics Surface

Layer / File(s) Summary
Domain Model — Incremental Mode & Materialized Config
src/domain/unit_test.rs, src/domain/manifest.rs
UnitTest gains optional is_incremental_mode field with builder/getter; NodeConfig exposes materialized() accessor to read model materialization type from config map.
Wire Deserialization — Overrides Ingestion
src/adapters/manifest.rs
Deserialize overrides.macros.is_incremental into WireUnitTestOverrides with tolerant boolean/string parsing; thread derived is_incremental_mode through wire-to-domain translation.
Payload Serialization — Model/Test/Given Fields
src/adapters/render.rs
Extend ModelPayload, GivenPayload, TestPayload to capture incremental model flag, "this" given marking, and per-test incremental mode; set during payload construction.
UI Presentation — Base Template & Rendering Logic
templates/report.html
Add incremental badge styling; toggle visibility based on model is_incremental; mark "this" givens with prior-model-state badge; conditionally show expect-semantics tooltip and mode indicator for incremental tests.
Example Reports — Incremental Semantics Rendering
examples/jaffle-shop-report.html, examples/playground-pr-diff-report.html, examples/playground-report.html
Apply incremental UI logic, CSS styling, and DOM rendering updates; demonstrate incremental badge, given marking, mode indicators, and tooltip behavior in output examples.
Feature Specs & BDD Step Definitions
features/incremental_models.feature, tests/steps/incremental_models.rs, tests/steps/world.rs, tests/steps/builders.rs, tests/steps/mod.rs
Define Gherkin scenarios for incremental badge visibility, per-test mode rendering, tooltip presence/absence, "this" given marking, and non-incremental fallback; implement Cucumber steps that build synthetic reports and validate embedded JSON payload.
Integration Test — Headless DOM Validation
tests/headless_toggle.rs
Add headless Chromium test validating incremental badge visibility, expect-materialization tooltip content/visibility, "prior model state" given badging, and idempotent UI clearing across model selection changes.
Test Fixtures & dbt Examples
dbt-project/models/marts/order_events_incremental.sql, dbt-project/models/marts/_incremental__models.yml, tests/fixtures/playground-source/models/marts/core/_core__models.yml, tests/fixtures/MANIFEST.toml
Add dbt incremental model example with high-water-mark filter; include unit test; update playground fixture with synthetic incremental model and test; update manifest inventory checksums and documentation.
CI Guards & Contributor Guidance
.github/workflows/ci.yml, lefthook.yml, AGENTS.md
Update feature count invariant from 11 to 12; add contributor guidance to pin dbt-fusion source and verify against real committed fixtures for incremental model work.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #147: Shares overlapping wire-level unit_test shape validation for overrides.macros.is_incremental and given.input as literal "this" deserialization across both dbt-core and dbt-fusion engines.

Possibly related PRs

  • breezy-bays-labs/cute-dbt#38: This PR extends the askama renderer payload structs introduced in #38 (adding ModelPayload.is_incremental, GivenPayload.is_this, TestPayload.is_incremental_mode) and updates the HTML templates to render those new incremental semantics.

Poem

🐰 A badge, a badge to mark the flow—
when models grow incremental, dear,
and expect means merge, not final show,
the tooltip whispers: see it clear. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely describes the main change: surfacing incremental-model unit-test semantics in reports with badges and tooltips.
Linked Issues check ✅ Passed All acceptance criteria from #145 are met: incremental badge [✓], expect-semantics tooltip [✓], prior model state badge for 'this' inputs [✓], per-test mode surface [✓], and zero-egress constraints [✓].
Out of Scope Changes check ✅ Passed All changes directly support incremental-model unit-test semantics (domain, adapters, rendering, UI, tests, features). No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch adapters-145-incremental

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented Jun 7, 2026 •

Copy link
Copy Markdown
Contributor

📄 Rendered report preview

All golden examples regenerated cleanly.

🟡 Golden examples

Committed to examples/ and byte-identity gated — the canonical reports contributors and consumers browse. Stable across PRs.

Report Download
playground-report.html Download
diff-showcase-report.html Download
jaffle-shop-report.html Download

🐶 Live dogfood preview

This PR's own dbt-project/ diff, freshly compiled by fusion into an ephemeral manifest (never committed) and rendered with --pr-diff. Regenerated every PR — the live self-dogfood, not a committed example.

Report Download
dbt-project-report.html Download

Click Download to fetch the rendered HTML. Each artifact
is a zip containing the single-file report — extract and open
in any browser. Reports are fully self-contained (zero
external resource requests).

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.html

Posted by report-preview.yml for 119b2775ffa6615c2b0d311236b829e4805d5032. Affordance only — never blocks merge.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread templates/report.html
…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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between b571967 and db8ecc8.

⛔ Files ignored due to path filters (2)
  • tests/snapshots/golden_report__rendered_report_skeleton.snap is excluded by !**/*.snap
  • tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap is excluded by !**/*.snap
📒 Files selected for processing (22)
  • .github/workflows/ci.yml
  • AGENTS.md
  • dbt-project/models/marts/_incremental__models.yml
  • dbt-project/models/marts/order_events_incremental.sql
  • examples/jaffle-shop-report.html
  • examples/playground-pr-diff-report.html
  • examples/playground-report.html
  • features/incremental_models.feature
  • lefthook.yml
  • src/adapters/manifest.rs
  • src/adapters/render.rs
  • src/domain/manifest.rs
  • src/domain/unit_test.rs
  • templates/report.html
  • tests/fixtures/MANIFEST.toml
  • tests/fixtures/playground-current.json
  • tests/fixtures/playground-source/models/marts/core/_core__models.yml
  • tests/headless_toggle.rs
  • tests/steps/builders.rs
  • tests/steps/incremental_models.rs
  • tests/steps/mod.rs
  • tests/steps/world.rs

Comment thread dbt-project/models/marts/order_events_incremental.sql
Comment thread examples/jaffle-shop-report.html
Comment thread examples/jaffle-shop-report.html Outdated
Comment thread examples/playground-report.html
Comment thread examples/playground-report.html Outdated
Comment thread src/domain/unit_test.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>
@cmbays

cmbays commented Jun 7, 2026

Copy link
Copy Markdown
Contributor Author

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 <span title> is hover-delayed and keyboard/touch-unreachable. The tooltip is now a focusable <button class="expect-tooltip" aria-label="…"> carrying a CSS bubble shown on :hover AND :focus/:focus-visible (bubble is aria-hidden so AT isn't double-announced via the aria-label). Pure CSS — no asset, no JS lib — so the zero-egress gate holds. New headless guards assert it's a <button>, the bubble carries the dbt wording, and focusing it reveals the bubble.

@cmbays
cmbays merged commit 50cb595 into main Jun 7, 2026
31 checks passed
@cmbays
cmbays deleted the adapters-145-incremental branch June 7, 2026 21:53
cmbays added a commit that referenced this pull request Jun 10, 2026
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>
cmbays added a commit that referenced this pull request Jun 10, 2026
…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>
cmbays pushed a commit that referenced this pull request Jun 11, 2026
…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>
cmbays added a commit that referenced this pull request Jun 11, 2026
…+ 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>
cmbays added a commit that referenced this pull request Jun 11, 2026
…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>
cmbays added a commit that referenced this pull request Jun 11, 2026
… 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>
cmbays added a commit that referenced this pull request Jun 11, 2026
#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>
cmbays added a commit that referenced this pull request Jun 11, 2026
…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>
cmbays added a commit that referenced this pull request Jun 11, 2026
…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>
cmbays added a commit that referenced this pull request Jun 11, 2026
…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>
cmbays added a commit that referenced this pull request Jun 12, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: surface incremental-model unit-test semantics (incremental badge + expect=merged-rows tooltip)

1 participant