Skip to content

#165 — column-header tooltips: authored descriptions + column-level data tests - #166

Merged
cmbays merged 4 commits into
mainfrom
feature-165-column-tooltips
Jun 10, 2026
Merged

cmbays merged 4 commits into
mainfrom
feature-165-column-tooltips

Conversation

@cmbays

@cmbays cmbays commented Jun 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Column headers on the unit-test given/expected DataTables now carry a focusable ⓘ tooltip showing the column's authored description plus its column-level data tests (unique, not_null, accepted_values with its values, relationships with to/field, package-qualified tests like dbt_expectations.*) — so you can see a column is expected-unique while reading fixture rows.

Closes #165

What changed

  • Domain — TestMetadata POD (generic-test name / namespace / untyped kwargs) + four tolerant Node fields: column_descriptions (non-empty prose only, kept SEPARATE from the .contract column-set so a description edit never flags state:modified.contract), column_name, attached_node, test_metadata. Additive with_* builders (the with_incremental_mode precedent).
  • Manifest adapter — ingests columns[*].description (fusion serializes an unset description as "" — dropped at translation) and the test-node attribution fields; singular tests (no test_metadata) and model-level tests (column_name: null) tolerated per ADR-5.
  • Render — ColumnMetaPayload {description, tests} computed fully in Rust (JS only renders): per-table maps resolved against the model that owns the table's columns — ref(...) given → the input model, this given → the target model, expect → the target model — filtered to the rendered table's columns, key omitted when empty (no empty bubbles).
  • Template — the #145 — surface incremental-model unit-test semantics #146 expect-tooltip pattern mirrored exactly: focusable <button> + CSS bubble on :hover AND :focus/:focus-visible, aria-label for AT, bubble aria-hidden, never a native title. One deliberate 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 absolutely-positioned bubble (#145 — surface incremental-model unit-test semantics #146's bubble hangs off the un-clipped panel header; this one cannot). Visibility itself stays pure CSS. A click on the button is swallowed so it never re-sorts the DataTable. The Diff grid's unified column axis shares the same map.

Scope decisions (settled at issue triage — not re-litigated here)

  • v1 resolves ONLY true column-scoped data tests: manifest test nodes with column_name set and attached (attached_node) to the owning model. Model-level tests with column args are deferred.
  • Description source of truth = the manifest model node's columns map (works without --project-root). No raw-YAML path.
  • Affordance only on columns with a description AND/OR ≥1 column test — no empty bubbles.

Additional v1 boundaries surfaced while building: source(...) givens resolve to nothing (manifest sources are not ingested in v0.x), seed-backed ref(...) givens likewise (resolve_target_model is model-only), and column-name matching is exact-case.

dbt-fusion verification (research-first)

Pinned at dbt-labs/dbt-fusion@9977b6cbb1b761065536300037560d8e3c037011:

  • DbtColumn { name, data_type, description } — crates/dbt-schemas/src/schemas/dbt_column.rs; serialize_dbt_column_desc + #[serialize_always] means an unset description hits the wire as "", never an absent key — the adapter drops empties.
  • ManifestDataTest { column_name, attached_node, test_metadata } — crates/dbt-schemas/src/schemas/manifest/manifest_nodes.rs (#[skip_serializing_none] ⇒ singular tests OMIT test_metadata).
  • TestMetadata { name, kwargs (defaulted), namespace } — crates/dbt-schemas/src/schemas/nodes.rs.

Cross-checked against the REAL committed fixtures (not just synthetic JSON): jaffle-shop carries unique/not_null/accepted_values/relationships with namespace: null and an EMPTY description on stg_customers.customer_id; playground carries 10 singular tests without test_metadata, 33 model-level tests with null column_name, and dbt_expectations-namespaced tests — all pinned by a new real-fixture ingestion test.

Tests

  • Unit: domain POD round-trips; adapter tolerance (empty descriptions, explicit nulls, singular/model-level tests); summarize_column_test arg rendering; per-owner given/this/expect resolution; empty-map wire omission.
  • Headless (Chrome): focus reveals the bubble; bare column has NO button; given table resolves its own input model's metadata; aria parity.
  • BDD: extends features/report_generation.feature (no new .feature file — the feature-count gate stays 12) with payload-fact scenarios over the real subprocess + committed fixture.

Dogfood + goldens

  • Live (dbt-project, YAML only): unique/not_null on order_events_incremental.order_id + staging descriptions on the columns the marts unit tests mock.
  • Synthetic showcase: playground's fct_encounters_incremental gains columns/descriptions + unique/not_null on encounter_id in BOTH the source YAML and the manifest (hand-edited via a verified byte-stable JSON round-trip; synthetic-only, no recompile, no root_path). The playground fixture already carried rich column metadata on dim_payers/mart_dq_summary, so all three goldens now show descriptions, unique/not_null, accepted_values (values: …), dbt_expectations.*, and the this-given resolution.
  • All three goldens regenerated with the exact CI invocations and verified byte-identical to renderer output; per-golden payload diffs verified to be column_meta-only (Python structural diff, not just eyeballs).

Finding (out of lane, not fixed here)

StateComparator::models_in_scope arm 2 inserts EVERY modified node with zero unit tests — without filtering resource_type == "model". A baseline-mode PR that adds a generic test node surfaces it as a junk "model" card named by its hash suffix (observed live when the new synthetic test nodes briefly rendered as a165c01d01/b165c01d02 cards). Worked around in the fixture by mirroring the test nodes into the baseline; worth its own issue.

Gates (all run directly — lefthook skips in fresh worktrees)

  • cargo fmt --all --check ✓
  • cargo clippy --all-targets --locked -- -D warnings — exit 0 ✓ (one justified #[allow(clippy::struct_field_names)]: attached_node mirrors the dbt wire key)
  • cargo nextest run — 746 passed ✓
  • cargo test --test bdd — 86 scenarios / 550 steps ✓
  • cargo test --test headless_zero_egress --locked -- --ignored ✓; cargo test --test headless_toggle --locked -- --ignored — 29 passed ✓
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --document-private-items --locked ✓
  • cargo deny check ✓
  • Example byte-identity simulated locally for all three goldens ✓

Zero-egress invariants hold: no new resource refs, pure-CSS visibility + text-node DOM building, no JS parsing engines — all metadata computed in Rust.

🤖 Generated with Claude Code


Open in Stage

github-actions Bot and others added 4 commits June 9, 2026 22:13
…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>
…ce (#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>
…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>
…#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>
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

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 29 minutes and 14 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: c4830a0e-69f7-4a01-ada5-de14c79a18e0

📥 Commits

Reviewing files that changed from the base of the PR and between f2d8632 and 43b45ca.

⛔ Files ignored due to path filters (1)
  • tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap is excluded by !**/*.snap
📒 Files selected for processing (18)
  • dbt-project/models/marts/_incremental__models.yml
  • dbt-project/models/staging/schema.yml
  • examples/diff-showcase-report.html
  • examples/jaffle-shop-report.html
  • examples/playground-report.html
  • features/report_generation.feature
  • src/adapters/manifest.rs
  • src/adapters/render.rs
  • src/domain/manifest.rs
  • src/domain/mod.rs
  • templates/report.html
  • tests/fixtures/MANIFEST.toml
  • tests/fixtures/playground-baseline.json
  • tests/fixtures/playground-current.json
  • tests/fixtures/playground-source/models/marts/core/_core__models.yml
  • tests/headless_toggle.rs
  • tests/manifest_ingestion.rs
  • tests/steps/report_generation.rs
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature-165-column-tooltips

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

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 View Download
playground-report.html ▶ Open ↗ ⬇ Download
jaffle-shop-report.html ▶ Open ↗ ⬇ Download
diff-showcase-report.html ▶ Open ↗ ⬇ 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 View Download
dbt-project-report.html ▶ Open ↗ ⬇ Download

▶ Open ↗ opens the report in your browser in one click —
published to this repo's GitHub Pages under /pr-166/.
⬇ Download fetches the same self-contained HTML as a workflow
artifact (auth-gated; works fully offline). Either way the report
makes zero external resource requests.

The Pages preview may take ~1 min to update after this comment
posts. On PRs from forks the Open link is unavailable (read-only
token) — use Download.

Alternative: GitHub CLI
# gh CLI >= 2.63 extracts into ./report-preview-playground/.
gh run download 27248614343 -R breezy-bays-labs/cute-dbt -n report-preview-playground
open report-preview-playground/playground-report.html

Posted by report-preview.yml for 43b45ca24936c437f9c11fcc86749e8af5fc40d7. 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 implements column-header tooltips (cute-dbt#165) to display authored column descriptions and column-level data tests in the generated reports. It updates manifest ingestion to parse test attributions and column descriptions, builds the metadata payload in the rendering adapter, and adds interactive tooltips in the HTML template. Feedback on these changes highlights a potential performance bottleneck in column_meta_for_model due to redundant full-manifest scans, suggesting caching or pre-grouping. Additionally, a UI issue was identified where the position: fixed tooltips can become detached from column headers during scrolling, and it is recommended to reposition or hide them on scroll events.

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 src/adapters/render.rs
Comment thread templates/report.html
@cmbays
cmbays merged commit cba790f into main Jun 10, 2026
31 checks passed
@cmbays
cmbays deleted the feature-165-column-tooltips branch June 10, 2026 02:38
github-actions Bot added a commit that referenced this pull request Jun 10, 2026
cmbays added a commit that referenced this pull request Jun 10, 2026
… (#175)

The baseline arm's models_in_scope arm 2 (the self-modified,
zero-unit-tests explorer signal) iterated the full modified set without
a resource_type filter. The modified set spans every nodes resource
type — fusion's state:modified matcher (crates/dbt-scheduler/src/
node_selector.rs match_state at dbt-labs/dbt-fusion@9977b6cbb1b7610655
36300037560d8e3c037011) is resource-agnostic, with resource-type
narrowing a separate orthogonal selector method — so a newly added
generic test node (or any modified non-model node) leaked into the
scoped model set and rendered as a junk model card in baseline mode
(observed live on PR #166).

Arm 2 now admits only resource_type == "model", the same projection
arm 1 already applies via resolve_target_model and the PrDiff arm
applies in select_in_scope_pr_diff. Regression coverage: a table test
across resource types (test/seed/snapshot/analysis/operation excluded;
model control included), the checksum-modified test-node case, the
mixed modified set, an end-to-end baseline-arm select_in_scope test,
and a pin test on the PrDiff arm's existing filter so the two arms
cannot drift.

PR #166's baseline mirroring of its synthetic test nodes is no longer
load-bearing for card scoping (left in place; documented in the PR).
All three golden examples verified byte-identical.

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 10, 2026
Land Claude Design Phase-1's chassis CSS — the semantic token layer, the
five AA-pinned [data-theme] blocks (Light/Dark/Tokyo/Solarized/Gruvbox),
the four html[data-style] direction packs, the density layer and the
tokenized component rules — replacing the template's custom <style> block
body. <html> gains data-style="soft" data-difflayout="auto". Pure
visual; zero payload/JS change.

Placement: templates/report.css (first-party, beside the template it
styles) embedded via include_str! as asset_embed::REPORT_CSS and
interpolated {{ report_css|safe }}. Deliberately OUTSIDE assets/ — the
provenance gate walks every file under assets/ and would demand an
upstream pin this authored file doesn't have. Banner-pin test (head
banner + end-of-file sentinel) fails CI on a truncated copy; a
comment-balance test pins the handoff's documented porting bug (a star
followed by slash inside a comment body once ate the [hidden] rule).

The merged file carries the handoff's per-theme AA contrast table as the
"AA pins — do not re-brighten" comment block; no token color altered.
Two reconciliations with shipped behavior the handoff forked before:
the cute-dbt#157 minmax(0,1fr) stacked-track fix (a 375px headless
regression test pins it) and the cute-dbt#165/#166 column-tooltip
trigger contract (the handoff's body-appended .col-tooltip bubble waits
for the engine DOM in PR-2/PR-3). A clearly-marked PR-1 bridge layer
keeps the pre-engine diff-line DOM block-laid (whitespace: pre) and the
copy-button clearance until cute-dbt#178 lands the engine.

Regenerated the 3 golden examples + 2 insta snapshots; the deltas are
the html attr line + the style-block body only (zero payload changes).
All 29 headless toggle tests + zero-egress + 787 nextest + 86 BDD
scenarios green.

Closes #177

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 10, 2026
…r, accent keys, value chips) + gemini fixes

Christopher's review override on PR #188: the #166-tooltip deferral is
reversed — the column-header tooltips must match the handoff spec in
this PR, adapted to OUR shipped per-table column_meta payload (NOT
ModelPayload.column_meta, which stays PR-3).

Spec match (handoff engine/interaction.js + base.css, README section 2.2):
- The WHOLE header cell is the hover/focus trigger — the info icon
  (buildColTooltip's focusable button + per-th bubble) is gone.
  decorateColHeader marks a metadata-bearing th with .has-col-meta +
  data-col-name/desc/tests; showColTip fills + positions the single
  body-appended #col-tooltip bubble with the handoff's positioning math
  verbatim (position:fixed, horizontal clamp, flip-above on bottom
  overflow — never clipped by the .table-fit scroller).
- Bubble content per spec: .ct-desc (description, larger) ->
  "Data tests" label -> per-test .ct-test rows with .ct-key (accent
  color), .ct-vals > .ct-val chips for accepted_values args, .ct-detail
  muted mono for relationships/range detail. CSS ported from the
  handoff base.css (comment bodies kept free of close-sequences per the
  README porting-bug warning).
- Render-layer POD (adapter, not domain): ColumnMetaPayload.tests is
  now Vec<ColumnTestPayload {name, values, detail}> built by
  column_test_payload, the section-2.2 display mapping: unique /
  not_null -> bare prose names; accepted_values -> "accepted values" +
  values chips; relationships -> detail "model.field" (ref()/source()
  unwrapped to the last quoted arg); accepted_range (any namespace) ->
  detail "min-max" / ">= min" / "<= max"; any other test keeps its
  package-qualified raw identifier with no values/detail.
- A11y (the #166 contract carried onto the th): tabindex="0" so
  keyboard users reach the tip via focusin; aria-label on the trigger
  summarizes description + tests; the singleton bubble is aria-hidden;
  hover AND focus both reveal; decorated headers SHED the native title
  (undecorated headers keep theirs); :focus-visible outline added.

Guards updated:
- headless: column_header_tooltips_th_trigger_hover_focus_and_skip_
  bare_columns — th-trigger semantics (no icon/button in any th),
  tabindex/aria/title assertions, focus AND hover reveal + hide,
  .ct-key/.ct-val/.ct-detail content over an accepted_values +
  relationships fixture, given-table input-model resolution.
- BDD: the report_generation scenario matches the structured entries'
  display `name` ("not_null" -> "not null"); payload contract otherwise
  unchanged.
- render.rs unit tests pin the full section-2.2 mapping table.

Gemini review threads (PR #188, both applied): theme.js gains an
ES5-safe qsaForEach helper replacing NodeList.prototype.forEach at
every control-wiring site, and reflowTables binds jQuery locally.

Goldens + the chrome insta snapshot regenerated; byte-identity
re-verified CI-style; visual check against the handoff reference.html
confirms the tooltip treatment matches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cmbays added a commit that referenced this pull request Jun 10, 2026
…olumn tooltips (#188)

* feat(adapters): design integration PR-2 - engine merge (settings panel, unified/split diffs) + port-forward

Replace the template's ~2100-line inline engine with two first-party
include_str! assets: templates/interaction.js (the merged redesign
interaction engine) and templates/theme.js (the appearance engine), wired
through asset_embed::{INTERACTION_JS,THEME_JS} with banner-pin +
end-of-file-sentinel tests (the REPORT_CSS pattern).

Engine merge (handoff -> shipped, our behaviors win every conflict):
- Settings panel: static Appearance markup (style/theme/accent/density/
  diff-style/diff-layout) in the template, wired by theme.js; persists
  under cute-dbt.appearance.v1; DataTables dark syncs via html.dark. The
  cog + panel are now universal (themes apply to baseline reports too);
  the #139 context-lines/normalize rows stay PR-diff-only.
- Diff renderers: GitHub-grade two-column line-number gutters, shared
  diffBody/diffNumbers helpers, hunk folds keep the #132/#136 contract,
  and a NEW split (side-by-side) renderer - diffViewsHtml emits BOTH
  layouts over the same BlockDiff lines verbatim (kind/text/emphasis; no
  Rust diff-engine change); CSS shows one per html[data-difflayout] with
  a responsive fallback.
- Code cards: file-path headers (defined_in on the YAML drawer,
  <model>.sql on the Model-SQL card), single-gutter line numbers on
  plain blocks (highlightLines*), incremental badge relocated into the
  Model-SQL card header (static span removed per handoff README section 3).
- DAG: theme-aware edge palette (JOIN_COLORS_LIGHT/_DARK + dagEdges),
  __cuteRerenderDag re-tint hook; Cytoscape surface deliberately
  excluded (Bucket 2).

Port-forward audit (the fork predates these; shipped behavior kept):
- #156 stable DAG node identity: already present in the handoff fork's
  JS; the Rust side never forked. Verified, no regression.
- #158 minmax(0,1fr) stacked-panel fix: PR-1 chassis kept it; the 375px
  headless regression stays green.
- #161 strategy-correct expect-semantics tooltip: kept OUR wording (the
  handoff carried the pre-#159 merge/insert copy - rejected).
- #166 column tooltips: kept OUR payload shape + focusable-button/CSS-
  bubble renderer (the handoff's body-appended th.has-col-meta renderer
  targets the not-yet-existing ModelPayload.column_meta - rejected;
  belongs to handoff PR-3).

Also: report.css drops its PR-1 bridge layer per its own contract and
gains the .settings-row-stack chrome; the edge-vocab-completeness gate +
BDD legend steps re-point at templates/interaction.js and now check both
palettes; headless guards added for the appearance toggles, the split
renderer, the gutters and the relocated badge; goldens + insta
snapshots regenerated (byte-identity re-verified CI-style).

Closes #178

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(adapters): column-header tooltips to the handoff spec (th-trigger, accent keys, value chips) + gemini fixes

Christopher's review override on PR #188: the #166-tooltip deferral is
reversed — the column-header tooltips must match the handoff spec in
this PR, adapted to OUR shipped per-table column_meta payload (NOT
ModelPayload.column_meta, which stays PR-3).

Spec match (handoff engine/interaction.js + base.css, README section 2.2):
- The WHOLE header cell is the hover/focus trigger — the info icon
  (buildColTooltip's focusable button + per-th bubble) is gone.
  decorateColHeader marks a metadata-bearing th with .has-col-meta +
  data-col-name/desc/tests; showColTip fills + positions the single
  body-appended #col-tooltip bubble with the handoff's positioning math
  verbatim (position:fixed, horizontal clamp, flip-above on bottom
  overflow — never clipped by the .table-fit scroller).
- Bubble content per spec: .ct-desc (description, larger) ->
  "Data tests" label -> per-test .ct-test rows with .ct-key (accent
  color), .ct-vals > .ct-val chips for accepted_values args, .ct-detail
  muted mono for relationships/range detail. CSS ported from the
  handoff base.css (comment bodies kept free of close-sequences per the
  README porting-bug warning).
- Render-layer POD (adapter, not domain): ColumnMetaPayload.tests is
  now Vec<ColumnTestPayload {name, values, detail}> built by
  column_test_payload, the section-2.2 display mapping: unique /
  not_null -> bare prose names; accepted_values -> "accepted values" +
  values chips; relationships -> detail "model.field" (ref()/source()
  unwrapped to the last quoted arg); accepted_range (any namespace) ->
  detail "min-max" / ">= min" / "<= max"; any other test keeps its
  package-qualified raw identifier with no values/detail.
- A11y (the #166 contract carried onto the th): tabindex="0" so
  keyboard users reach the tip via focusin; aria-label on the trigger
  summarizes description + tests; the singleton bubble is aria-hidden;
  hover AND focus both reveal; decorated headers SHED the native title
  (undecorated headers keep theirs); :focus-visible outline added.

Guards updated:
- headless: column_header_tooltips_th_trigger_hover_focus_and_skip_
  bare_columns — th-trigger semantics (no icon/button in any th),
  tabindex/aria/title assertions, focus AND hover reveal + hide,
  .ct-key/.ct-val/.ct-detail content over an accepted_values +
  relationships fixture, given-table input-model resolution.
- BDD: the report_generation scenario matches the structured entries'
  display `name` ("not_null" -> "not null"); payload contract otherwise
  unchanged.
- render.rs unit tests pin the full section-2.2 mapping table.

Gemini review threads (PR #188, both applied): theme.js gains an
ES5-safe qsaForEach helper replacing NodeList.prototype.forEach at
every control-wiring site, and reflowTables binds jQuery locally.

Goldens + the chrome insta snapshot regenerated; byte-identity
re-verified CI-style; visual check against the handoff reference.html
confirms the tooltip treatment matches.

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

feature: column-header tooltips on unit-test tables — authored descriptions + column-level data tests

1 participant