diff --git a/examples/diff-showcase-report.html b/examples/diff-showcase-report.html
index ffc0e9d9..22d0ad00 100644
--- a/examples/diff-showcase-report.html
+++ b/examples/diff-showcase-report.html
@@ -1730,7 +1730,19 @@
surface (NOT the dark .col-tooltip shell — it reuses the code-card
chrome so the reconstruction reads like the drawers). */
.fmt-tooltip {
- position: fixed; z-index: 90; max-width: min(34rem, 92vw); max-height: 22rem; overflow: auto;
+ position: fixed; z-index: 90; max-width: min(34rem, 92vw);
+ /* cute-dbt#246 — never a reachability fold: the bubble is
+ pointer-events:none and hides on mouseleave, so anything laid past
+ the old fixed 22rem max-height could never be scrolled to or read.
+ The JS side now bounds the content by REAL MEASUREMENT (trim lines
+ until the laid-out content fits + a truthful elision row), and the
+ height cap is viewport-relative — the #240 defect-D width-cap
+ pattern — so positionTipNear's post-flip top clamp always fully
+ contains the box on the height axis, at any viewport height.
+ overflow:auto stays as the safety net (PR #286 review): if some
+ exotic rendering still defeats the measured trim, the mismatch
+ degrades to a visible scrollbar — a signal — never a silent clip. */
+ max-height: calc(100vh - 16px); overflow: auto;
border: var(--rule-w) solid var(--border-strong); border-radius: var(--radius-pan);
box-shadow: 0 8px 26px rgba(0,0,0,0.34); background: var(--code-bg);
pointer-events: none;
@@ -1738,6 +1750,11 @@
.fmt-tooltip .code-header { font-size: 0.74rem; }
.fmt-tooltip .sql-block { margin: 0; padding: 0.4rem 0; }
.fmt-tooltip .code-line { padding-right: 1rem; }
+/* cute-dbt#246 — the truthful elision row under the trimmed block. Plain
+ var(--text) on the bubble's var(--code-bg) (the code-line pairing the
+ 8-theme AA matrix already covers); fixed px so the Sakura 62.5% root
+ (1rem = 10px) cannot shrink it unreadable. */
+.fmt-tooltip .fmt-more { padding: 0.15rem 0.8rem 0.4rem; font-size: 11px; color: var(--text); }
/* ============================================================================
6. ENGINE-PR ADDITIONS (cute-dbt#178)
@@ -7593,16 +7610,59 @@
Expected
}
// Format tip: the fixture reconstructed in its authored format, in a
// framed mini code-surface (sql/yaml ride the #132 highlighters).
+ //
+ // cute-dbt#246 — the bubble is pointer-events:none and hides on
+ // mouseleave/blur, so anything laid below a height fold could never be
+ // scrolled to or read (the old fixed 22rem + overflow:auto pairing left
+ // below-fold rows silently unreachable for EVERY input modality). Bound
+ // the content instead of folding it: show at most FMT_TIP_MAX_LINES
+ // lines — the tip is a glance surface; the grid below the badge
+ // carries the full data — then trim by REAL MEASUREMENT (PR #286
+ // review): remove rendered lines until the laid-out content fits the
+ // bubble's viewport-capped box. Measuring the live layout (never a
+ // px-per-line estimate) keeps the bound correct under browser zoom, a
+ // forced minimum font size (a11y), wrapped long lines, and any future
+ // rem-based sizing — and the truthful elision row counts exactly what
+ // was trimmed, however it was trimmed.
+ var FMT_TIP_MAX_LINES = 20;
+ function fmtTipMoreText(more) {
+ return "… +" + more + " more line" + (more === 1 ? "" : "s");
+ }
function showFmtTip(trigger) {
var fmt = trigger.getAttribute("data-fmt"), lang = trigger.getAttribute("data-fmt-lang");
var block = trigger.getAttribute("data-fmt-block") || "";
- var code = lang === "sql" ? highlightLinesSql(block)
- : lang === "yaml" ? highlightLinesYaml(block)
- : highlightLinesPlain(block);
+ var lines = String(block).replace(/\n+$/, "").split("\n");
+ var shown = Math.min(lines.length, FMT_TIP_MAX_LINES);
+ var more = lines.length - shown;
+ var code = lang === "sql" ? highlightLinesSql(lines.slice(0, shown).join("\n"))
+ : lang === "yaml" ? highlightLinesYaml(lines.slice(0, shown).join("\n"))
+ : highlightLinesPlain(lines.slice(0, shown).join("\n"));
var el = ensureTip("fmt-tooltip", "fmt-tooltip");
el.innerHTML = '' + code + "
";
+ + '' + code + "
"
+ + (more > 0 ? '' + fmtTipMoreText(more) + "
" : "");
el.hidden = false;
+ // Measure the live layout at full width (reset a stale edge-clamped
+ // left BEFORE measuring — the #232 D18 lesson: a squeezed box wraps
+ // differently and mis-measures).
+ el.style.left = "0px";
+ // The elision row participates in the measured height, so it must
+ // exist before trimming starts (created here if the ceiling alone
+ // didn't already call for one).
+ var moreEl = el.querySelector(".fmt-more");
+ if (!moreEl && el.scrollHeight > el.clientHeight + 1) {
+ moreEl = document.createElement("div");
+ moreEl.className = "fmt-more";
+ el.appendChild(moreEl);
+ }
+ var rows = el.querySelectorAll(".code-line");
+ var keep = rows.length;
+ while (keep > 1 && el.scrollHeight > el.clientHeight + 1) {
+ keep -= 1;
+ rows[keep].parentNode.removeChild(rows[keep]);
+ more += 1;
+ }
+ if (moreEl) moreEl.textContent = fmtTipMoreText(more);
positionTipNear(el, trigger);
}
// Overrides tip: key = value rows grouped by macros / vars / env_vars.
diff --git a/examples/jaffle-shop-report.html b/examples/jaffle-shop-report.html
index 0758a390..28e53830 100644
--- a/examples/jaffle-shop-report.html
+++ b/examples/jaffle-shop-report.html
@@ -1730,7 +1730,19 @@
surface (NOT the dark .col-tooltip shell — it reuses the code-card
chrome so the reconstruction reads like the drawers). */
.fmt-tooltip {
- position: fixed; z-index: 90; max-width: min(34rem, 92vw); max-height: 22rem; overflow: auto;
+ position: fixed; z-index: 90; max-width: min(34rem, 92vw);
+ /* cute-dbt#246 — never a reachability fold: the bubble is
+ pointer-events:none and hides on mouseleave, so anything laid past
+ the old fixed 22rem max-height could never be scrolled to or read.
+ The JS side now bounds the content by REAL MEASUREMENT (trim lines
+ until the laid-out content fits + a truthful elision row), and the
+ height cap is viewport-relative — the #240 defect-D width-cap
+ pattern — so positionTipNear's post-flip top clamp always fully
+ contains the box on the height axis, at any viewport height.
+ overflow:auto stays as the safety net (PR #286 review): if some
+ exotic rendering still defeats the measured trim, the mismatch
+ degrades to a visible scrollbar — a signal — never a silent clip. */
+ max-height: calc(100vh - 16px); overflow: auto;
border: var(--rule-w) solid var(--border-strong); border-radius: var(--radius-pan);
box-shadow: 0 8px 26px rgba(0,0,0,0.34); background: var(--code-bg);
pointer-events: none;
@@ -1738,6 +1750,11 @@
.fmt-tooltip .code-header { font-size: 0.74rem; }
.fmt-tooltip .sql-block { margin: 0; padding: 0.4rem 0; }
.fmt-tooltip .code-line { padding-right: 1rem; }
+/* cute-dbt#246 — the truthful elision row under the trimmed block. Plain
+ var(--text) on the bubble's var(--code-bg) (the code-line pairing the
+ 8-theme AA matrix already covers); fixed px so the Sakura 62.5% root
+ (1rem = 10px) cannot shrink it unreadable. */
+.fmt-tooltip .fmt-more { padding: 0.15rem 0.8rem 0.4rem; font-size: 11px; color: var(--text); }
/* ============================================================================
6. ENGINE-PR ADDITIONS (cute-dbt#178)
@@ -7528,16 +7545,59 @@ Expected
}
// Format tip: the fixture reconstructed in its authored format, in a
// framed mini code-surface (sql/yaml ride the #132 highlighters).
+ //
+ // cute-dbt#246 — the bubble is pointer-events:none and hides on
+ // mouseleave/blur, so anything laid below a height fold could never be
+ // scrolled to or read (the old fixed 22rem + overflow:auto pairing left
+ // below-fold rows silently unreachable for EVERY input modality). Bound
+ // the content instead of folding it: show at most FMT_TIP_MAX_LINES
+ // lines — the tip is a glance surface; the grid below the badge
+ // carries the full data — then trim by REAL MEASUREMENT (PR #286
+ // review): remove rendered lines until the laid-out content fits the
+ // bubble's viewport-capped box. Measuring the live layout (never a
+ // px-per-line estimate) keeps the bound correct under browser zoom, a
+ // forced minimum font size (a11y), wrapped long lines, and any future
+ // rem-based sizing — and the truthful elision row counts exactly what
+ // was trimmed, however it was trimmed.
+ var FMT_TIP_MAX_LINES = 20;
+ function fmtTipMoreText(more) {
+ return "… +" + more + " more line" + (more === 1 ? "" : "s");
+ }
function showFmtTip(trigger) {
var fmt = trigger.getAttribute("data-fmt"), lang = trigger.getAttribute("data-fmt-lang");
var block = trigger.getAttribute("data-fmt-block") || "";
- var code = lang === "sql" ? highlightLinesSql(block)
- : lang === "yaml" ? highlightLinesYaml(block)
- : highlightLinesPlain(block);
+ var lines = String(block).replace(/\n+$/, "").split("\n");
+ var shown = Math.min(lines.length, FMT_TIP_MAX_LINES);
+ var more = lines.length - shown;
+ var code = lang === "sql" ? highlightLinesSql(lines.slice(0, shown).join("\n"))
+ : lang === "yaml" ? highlightLinesYaml(lines.slice(0, shown).join("\n"))
+ : highlightLinesPlain(lines.slice(0, shown).join("\n"));
var el = ensureTip("fmt-tooltip", "fmt-tooltip");
el.innerHTML = '' + code + "
";
+ + '' + code + "
"
+ + (more > 0 ? '' + fmtTipMoreText(more) + "
" : "");
el.hidden = false;
+ // Measure the live layout at full width (reset a stale edge-clamped
+ // left BEFORE measuring — the #232 D18 lesson: a squeezed box wraps
+ // differently and mis-measures).
+ el.style.left = "0px";
+ // The elision row participates in the measured height, so it must
+ // exist before trimming starts (created here if the ceiling alone
+ // didn't already call for one).
+ var moreEl = el.querySelector(".fmt-more");
+ if (!moreEl && el.scrollHeight > el.clientHeight + 1) {
+ moreEl = document.createElement("div");
+ moreEl.className = "fmt-more";
+ el.appendChild(moreEl);
+ }
+ var rows = el.querySelectorAll(".code-line");
+ var keep = rows.length;
+ while (keep > 1 && el.scrollHeight > el.clientHeight + 1) {
+ keep -= 1;
+ rows[keep].parentNode.removeChild(rows[keep]);
+ more += 1;
+ }
+ if (moreEl) moreEl.textContent = fmtTipMoreText(more);
positionTipNear(el, trigger);
}
// Overrides tip: key = value rows grouped by macros / vars / env_vars.
diff --git a/examples/playground-report.html b/examples/playground-report.html
index 585db232..14507717 100644
--- a/examples/playground-report.html
+++ b/examples/playground-report.html
@@ -1730,7 +1730,19 @@
surface (NOT the dark .col-tooltip shell — it reuses the code-card
chrome so the reconstruction reads like the drawers). */
.fmt-tooltip {
- position: fixed; z-index: 90; max-width: min(34rem, 92vw); max-height: 22rem; overflow: auto;
+ position: fixed; z-index: 90; max-width: min(34rem, 92vw);
+ /* cute-dbt#246 — never a reachability fold: the bubble is
+ pointer-events:none and hides on mouseleave, so anything laid past
+ the old fixed 22rem max-height could never be scrolled to or read.
+ The JS side now bounds the content by REAL MEASUREMENT (trim lines
+ until the laid-out content fits + a truthful elision row), and the
+ height cap is viewport-relative — the #240 defect-D width-cap
+ pattern — so positionTipNear's post-flip top clamp always fully
+ contains the box on the height axis, at any viewport height.
+ overflow:auto stays as the safety net (PR #286 review): if some
+ exotic rendering still defeats the measured trim, the mismatch
+ degrades to a visible scrollbar — a signal — never a silent clip. */
+ max-height: calc(100vh - 16px); overflow: auto;
border: var(--rule-w) solid var(--border-strong); border-radius: var(--radius-pan);
box-shadow: 0 8px 26px rgba(0,0,0,0.34); background: var(--code-bg);
pointer-events: none;
@@ -1738,6 +1750,11 @@
.fmt-tooltip .code-header { font-size: 0.74rem; }
.fmt-tooltip .sql-block { margin: 0; padding: 0.4rem 0; }
.fmt-tooltip .code-line { padding-right: 1rem; }
+/* cute-dbt#246 — the truthful elision row under the trimmed block. Plain
+ var(--text) on the bubble's var(--code-bg) (the code-line pairing the
+ 8-theme AA matrix already covers); fixed px so the Sakura 62.5% root
+ (1rem = 10px) cannot shrink it unreadable. */
+.fmt-tooltip .fmt-more { padding: 0.15rem 0.8rem 0.4rem; font-size: 11px; color: var(--text); }
/* ============================================================================
6. ENGINE-PR ADDITIONS (cute-dbt#178)
@@ -7528,16 +7545,59 @@ Expected
}
// Format tip: the fixture reconstructed in its authored format, in a
// framed mini code-surface (sql/yaml ride the #132 highlighters).
+ //
+ // cute-dbt#246 — the bubble is pointer-events:none and hides on
+ // mouseleave/blur, so anything laid below a height fold could never be
+ // scrolled to or read (the old fixed 22rem + overflow:auto pairing left
+ // below-fold rows silently unreachable for EVERY input modality). Bound
+ // the content instead of folding it: show at most FMT_TIP_MAX_LINES
+ // lines — the tip is a glance surface; the grid below the badge
+ // carries the full data — then trim by REAL MEASUREMENT (PR #286
+ // review): remove rendered lines until the laid-out content fits the
+ // bubble's viewport-capped box. Measuring the live layout (never a
+ // px-per-line estimate) keeps the bound correct under browser zoom, a
+ // forced minimum font size (a11y), wrapped long lines, and any future
+ // rem-based sizing — and the truthful elision row counts exactly what
+ // was trimmed, however it was trimmed.
+ var FMT_TIP_MAX_LINES = 20;
+ function fmtTipMoreText(more) {
+ return "… +" + more + " more line" + (more === 1 ? "" : "s");
+ }
function showFmtTip(trigger) {
var fmt = trigger.getAttribute("data-fmt"), lang = trigger.getAttribute("data-fmt-lang");
var block = trigger.getAttribute("data-fmt-block") || "";
- var code = lang === "sql" ? highlightLinesSql(block)
- : lang === "yaml" ? highlightLinesYaml(block)
- : highlightLinesPlain(block);
+ var lines = String(block).replace(/\n+$/, "").split("\n");
+ var shown = Math.min(lines.length, FMT_TIP_MAX_LINES);
+ var more = lines.length - shown;
+ var code = lang === "sql" ? highlightLinesSql(lines.slice(0, shown).join("\n"))
+ : lang === "yaml" ? highlightLinesYaml(lines.slice(0, shown).join("\n"))
+ : highlightLinesPlain(lines.slice(0, shown).join("\n"));
var el = ensureTip("fmt-tooltip", "fmt-tooltip");
el.innerHTML = '' + code + "
";
+ + '' + code + "
"
+ + (more > 0 ? '' + fmtTipMoreText(more) + "
" : "");
el.hidden = false;
+ // Measure the live layout at full width (reset a stale edge-clamped
+ // left BEFORE measuring — the #232 D18 lesson: a squeezed box wraps
+ // differently and mis-measures).
+ el.style.left = "0px";
+ // The elision row participates in the measured height, so it must
+ // exist before trimming starts (created here if the ceiling alone
+ // didn't already call for one).
+ var moreEl = el.querySelector(".fmt-more");
+ if (!moreEl && el.scrollHeight > el.clientHeight + 1) {
+ moreEl = document.createElement("div");
+ moreEl.className = "fmt-more";
+ el.appendChild(moreEl);
+ }
+ var rows = el.querySelectorAll(".code-line");
+ var keep = rows.length;
+ while (keep > 1 && el.scrollHeight > el.clientHeight + 1) {
+ keep -= 1;
+ rows[keep].parentNode.removeChild(rows[keep]);
+ more += 1;
+ }
+ if (moreEl) moreEl.textContent = fmtTipMoreText(more);
positionTipNear(el, trigger);
}
// Overrides tip: key = value rows grouped by macros / vars / env_vars.
diff --git a/templates/interaction.js b/templates/interaction.js
index 3bde39c7..eb59e202 100644
--- a/templates/interaction.js
+++ b/templates/interaction.js
@@ -1761,16 +1761,59 @@
}
// Format tip: the fixture reconstructed in its authored format, in a
// framed mini code-surface (sql/yaml ride the #132 highlighters).
+ //
+ // cute-dbt#246 — the bubble is pointer-events:none and hides on
+ // mouseleave/blur, so anything laid below a height fold could never be
+ // scrolled to or read (the old fixed 22rem + overflow:auto pairing left
+ // below-fold rows silently unreachable for EVERY input modality). Bound
+ // the content instead of folding it: show at most FMT_TIP_MAX_LINES
+ // lines — the tip is a glance surface; the grid below the badge
+ // carries the full data — then trim by REAL MEASUREMENT (PR #286
+ // review): remove rendered lines until the laid-out content fits the
+ // bubble's viewport-capped box. Measuring the live layout (never a
+ // px-per-line estimate) keeps the bound correct under browser zoom, a
+ // forced minimum font size (a11y), wrapped long lines, and any future
+ // rem-based sizing — and the truthful elision row counts exactly what
+ // was trimmed, however it was trimmed.
+ var FMT_TIP_MAX_LINES = 20;
+ function fmtTipMoreText(more) {
+ return "… +" + more + " more line" + (more === 1 ? "" : "s");
+ }
function showFmtTip(trigger) {
var fmt = trigger.getAttribute("data-fmt"), lang = trigger.getAttribute("data-fmt-lang");
var block = trigger.getAttribute("data-fmt-block") || "";
- var code = lang === "sql" ? highlightLinesSql(block)
- : lang === "yaml" ? highlightLinesYaml(block)
- : highlightLinesPlain(block);
+ var lines = String(block).replace(/\n+$/, "").split("\n");
+ var shown = Math.min(lines.length, FMT_TIP_MAX_LINES);
+ var more = lines.length - shown;
+ var code = lang === "sql" ? highlightLinesSql(lines.slice(0, shown).join("\n"))
+ : lang === "yaml" ? highlightLinesYaml(lines.slice(0, shown).join("\n"))
+ : highlightLinesPlain(lines.slice(0, shown).join("\n"));
var el = ensureTip("fmt-tooltip", "fmt-tooltip");
el.innerHTML = '' + code + "
";
+ + '' + code + "
"
+ + (more > 0 ? '' + fmtTipMoreText(more) + "
" : "");
el.hidden = false;
+ // Measure the live layout at full width (reset a stale edge-clamped
+ // left BEFORE measuring — the #232 D18 lesson: a squeezed box wraps
+ // differently and mis-measures).
+ el.style.left = "0px";
+ // The elision row participates in the measured height, so it must
+ // exist before trimming starts (created here if the ceiling alone
+ // didn't already call for one).
+ var moreEl = el.querySelector(".fmt-more");
+ if (!moreEl && el.scrollHeight > el.clientHeight + 1) {
+ moreEl = document.createElement("div");
+ moreEl.className = "fmt-more";
+ el.appendChild(moreEl);
+ }
+ var rows = el.querySelectorAll(".code-line");
+ var keep = rows.length;
+ while (keep > 1 && el.scrollHeight > el.clientHeight + 1) {
+ keep -= 1;
+ rows[keep].parentNode.removeChild(rows[keep]);
+ more += 1;
+ }
+ if (moreEl) moreEl.textContent = fmtTipMoreText(more);
positionTipNear(el, trigger);
}
// Overrides tip: key = value rows grouped by macros / vars / env_vars.
diff --git a/templates/report.css b/templates/report.css
index 64b022c0..deee9cc9 100644
--- a/templates/report.css
+++ b/templates/report.css
@@ -1044,7 +1044,19 @@ th.has-col-meta:focus-visible { outline: 2px solid var(--accent); outline-offset
surface (NOT the dark .col-tooltip shell — it reuses the code-card
chrome so the reconstruction reads like the drawers). */
.fmt-tooltip {
- position: fixed; z-index: 90; max-width: min(34rem, 92vw); max-height: 22rem; overflow: auto;
+ position: fixed; z-index: 90; max-width: min(34rem, 92vw);
+ /* cute-dbt#246 — never a reachability fold: the bubble is
+ pointer-events:none and hides on mouseleave, so anything laid past
+ the old fixed 22rem max-height could never be scrolled to or read.
+ The JS side now bounds the content by REAL MEASUREMENT (trim lines
+ until the laid-out content fits + a truthful elision row), and the
+ height cap is viewport-relative — the #240 defect-D width-cap
+ pattern — so positionTipNear's post-flip top clamp always fully
+ contains the box on the height axis, at any viewport height.
+ overflow:auto stays as the safety net (PR #286 review): if some
+ exotic rendering still defeats the measured trim, the mismatch
+ degrades to a visible scrollbar — a signal — never a silent clip. */
+ max-height: calc(100vh - 16px); overflow: auto;
border: var(--rule-w) solid var(--border-strong); border-radius: var(--radius-pan);
box-shadow: 0 8px 26px rgba(0,0,0,0.34); background: var(--code-bg);
pointer-events: none;
@@ -1052,6 +1064,11 @@ th.has-col-meta:focus-visible { outline: 2px solid var(--accent); outline-offset
.fmt-tooltip .code-header { font-size: 0.74rem; }
.fmt-tooltip .sql-block { margin: 0; padding: 0.4rem 0; }
.fmt-tooltip .code-line { padding-right: 1rem; }
+/* cute-dbt#246 — the truthful elision row under the trimmed block. Plain
+ var(--text) on the bubble's var(--code-bg) (the code-line pairing the
+ 8-theme AA matrix already covers); fixed px so the Sakura 62.5% root
+ (1rem = 10px) cannot shrink it unreadable. */
+.fmt-tooltip .fmt-more { padding: 0.15rem 0.8rem 0.4rem; font-size: 11px; color: var(--text); }
/* ============================================================================
6. ENGINE-PR ADDITIONS (cute-dbt#178)
diff --git a/tests/headless_toggle.rs b/tests/headless_toggle.rs
index 10cd81f7..cbd94524 100644
--- a/tests/headless_toggle.rs
+++ b/tests/headless_toggle.rs
@@ -9877,16 +9877,20 @@ fn ov_tip_long_value_contained_in_bubble_and_viewport_at_right_edge() {
"the overrides tip CONTAINS its content (the long value wraps \
inside the painted background instead of painting past it)",
);
- // Pin 2 — viewport containment at the right-edge trigger. The top
- // bound also pins the PR #244-review post-flip top clamp (the full
- // pathological-viewport-height pin rides cute-dbt#246).
+ // Pin 2 — viewport containment at the right-edge trigger, on BOTH
+ // axes (cute-dbt#246 extended the guard to the height axis; the top
+ // bound also pins the PR #244-review post-flip top clamp, and the
+ // full pathological-viewport-height pin lives in
+ // `fmt_tip_contained_at_pathological_viewport_height`).
assert!(
eval_bool(
&tab,
&format!(
"(function(){{var r={TIP}.getBoundingClientRect();\
var vw=document.documentElement.clientWidth;\
- return r.top >= 0 && r.left >= 0 && r.right <= vw + 1;}})()"
+ var vh=document.documentElement.clientHeight;\
+ return r.top >= 0 && r.bottom <= vh + 1 \
+ && r.left >= 0 && r.right <= vw + 1;}})()"
),
),
"the overrides bubble's box lies fully within the visible viewport \
@@ -9911,6 +9915,297 @@ fn ov_tip_long_value_contained_in_bubble_and_viewport_at_right_edge() {
let _ = tab.close(true);
}
+/// A long unit-test fixture for the cute-dbt#246 fmt-tip reachability
+/// pins: 30 dict rows × 2 columns reconstruct as 60 YAML lines — far
+/// past any realistic bubble fold.
+fn long_fixture_unit_test() -> UnitTest {
+ let rows: Vec = (1..=30)
+ .map(|i| serde_json::json!({ "id": i, "amount": i * 10 }))
+ .collect();
+ UnitTest::new(
+ "longfix".to_owned(),
+ NodeId::new("dim_x"),
+ vec![UnitTestGiven::new(
+ "ref('src')".to_owned(),
+ serde_json::Value::Array(rows),
+ Some("dict".to_owned()),
+ None,
+ )],
+ UnitTestExpect::new(
+ serde_json::json!([{ "id": 1 }]),
+ Some("dict".to_owned()),
+ None,
+ ),
+ None,
+ DependsOn::default(),
+ None,
+ None,
+ None,
+ )
+}
+
+#[test]
+#[ignore = "requires Chrome; runs explicitly in the headless-zero-egress CI job via `-- --ignored`"]
+fn fmt_tip_long_fixture_is_bounded_not_folded() {
+ // cute-dbt#246 (bullet 1) — #fmt-tooltip is pointer-events:none and
+ // hides on mouseleave/blur, so any content laid below its max-height
+ // fold (the old fixed 22rem + overflow:auto pairing) could never be
+ // scrolled to or read — silently unreachable for EVERY input
+ // modality. RED pre-fix with this 60-line reconstruction (content
+ // ~3× the fold); the fix bounds the content (measurement-driven line
+ // trim + a truthful elision row) so the fold cannot occur.
+ let url = render_to_file(
+ "headless_246_fmt_tip_long.html",
+ vec![model_node("model.shop.dim_x"), model_node("model.shop.src")],
+ vec![("unit_test.shop.dim_x.longfix", long_fixture_unit_test())],
+ &["model.shop.dim_x"],
+ &[],
+ );
+ let browser = launch_browser();
+ let tab = browser.new_tab().expect("new tab");
+ tab.navigate_to(&url).expect("navigate");
+ tab.wait_until_navigated().expect("await navigation");
+ select_model(&tab, "dim_x");
+
+ const BADGE: &str = "document.querySelector('.given-section .format-badge.has-fmt-tip')";
+ const TIP: &str = "document.getElementById('fmt-tooltip')";
+ let _ = eval(&tab, &format!("{BADGE}.focus()"));
+ assert!(
+ !eval_bool(&tab, &format!("{TIP}.hidden")),
+ "focusing the format badge reveals the fmt tip",
+ );
+ // The reachability pin (RED pre-fix): no fold — everything the bubble
+ // holds paints inside its own painted box (1px tolerance, the repo
+ // convention). A fold here is unreachable by definition: the bubble
+ // is pointer-events:none and hides the moment the trigger is left.
+ assert!(
+ eval_bool(
+ &tab,
+ &format!("{TIP}.scrollHeight <= {TIP}.clientHeight + 1")
+ ),
+ "the fmt tip CONTAINS its content — no below-fold rows (a fold is \
+ unreachable for every input modality: pointer-events:none + \
+ hide-on-mouseleave)",
+ );
+ // The bound is truthful, not silent: a visible elision row counts
+ // exactly what was trimmed (shown + counted == the 60 authored
+ // lines). Relational pins, not exact counts — the budget is derived
+ // from the live viewport height, and the headless default window
+ // maps to different viewport heights per platform (macOS reserves
+ // window chrome; Linux does not).
+ assert!(
+ eval_bool(
+ &tab,
+ &format!(
+ "(function(){{var shown={TIP}.querySelectorAll('.diff-code').length;\
+ var m={TIP}.querySelector('.fmt-more');\
+ if (!m) return false;\
+ var n=parseInt(m.textContent.replace(/[^0-9]/g, ''), 10);\
+ return shown > 0 && shown <= 20 && shown + n === 60;}})()"
+ ),
+ ),
+ "the elision row truthfully accounts for every trimmed line \
+ (shown + counted === 60, within the 20-line ceiling)",
+ );
+ assert!(
+ eval_string(
+ &tab,
+ &format!("{TIP}.querySelector('.fmt-more').textContent")
+ )
+ .starts_with('\u{2026}'),
+ "the elision row reads as an ellipsis line",
+ );
+ // The trim takes the HEAD of the reconstruction (sorted dict keys:
+ // amount precedes id), never a mid-block slice.
+ assert_eq!(
+ eval_string(
+ &tab,
+ &format!("{TIP}.querySelector('.diff-code').textContent")
+ ),
+ "- amount: 10",
+ "the first shown line is the head of the authored reconstruction",
+ );
+ // And the box itself lies fully inside the visible viewport.
+ assert!(
+ eval_bool(
+ &tab,
+ &format!(
+ "(function(){{var r={TIP}.getBoundingClientRect();\
+ var vw=document.documentElement.clientWidth;\
+ var vh=document.documentElement.clientHeight;\
+ return r.top >= 0 && r.bottom <= vh + 1 \
+ && r.left >= 0 && r.right <= vw + 1;}})()"
+ ),
+ ),
+ "the fmt tip's box lies fully within the visible viewport",
+ );
+
+ // ===== font-scale phase (PR #286 review) =====
+ // A forced minimum font size (Chrome a11y) inflates the px-sized
+ // .sql-block lines past any per-line estimate; the bound must come
+ // from measuring the live layout, never constants. Simulate the
+ // inflation (12px → 24px doubles every line box), re-show the tip,
+ // and re-assert the same truths: no fold, truthful elision.
+ let shown_normal = eval(
+ &tab,
+ &format!("{TIP}.querySelectorAll('.diff-code').length"),
+ )
+ .as_i64()
+ .expect("shown line count is a number");
+ let _ = eval(&tab, &format!("{BADGE}.blur()"));
+ let _ = eval(
+ &tab,
+ "(function(){var s=document.createElement('style');\
+ s.textContent='.fmt-tooltip .sql-block { font-size: 24px; }';\
+ document.head.appendChild(s);})()",
+ );
+ let _ = eval(&tab, &format!("{BADGE}.focus()"));
+ assert!(
+ !eval_bool(&tab, &format!("{TIP}.hidden")),
+ "the fmt tip re-reveals under the inflated font",
+ );
+ assert!(
+ eval_bool(
+ &tab,
+ &format!("{TIP}.scrollHeight <= {TIP}.clientHeight + 1")
+ ),
+ "no fold under a forced larger font — the measured trim adapts \
+ (an estimate-based budget would overshoot and clip)",
+ );
+ assert!(
+ eval_bool(
+ &tab,
+ &format!(
+ "(function(){{var shown={TIP}.querySelectorAll('.diff-code').length;\
+ var m={TIP}.querySelector('.fmt-more');\
+ if (!m) return false;\
+ var n=parseInt(m.textContent.replace(/[^0-9]/g, ''), 10);\
+ return shown > 0 && shown <= 20 && shown + n === 60;}})()"
+ ),
+ ),
+ "the elision row stays truthful under the inflated font \
+ (shown + counted === 60)",
+ );
+ // Non-vacuous: doubled line boxes must fit FEWER lines — proof the
+ // trim measured the new layout rather than reusing a constant.
+ let shown_scaled = eval(
+ &tab,
+ &format!("{TIP}.querySelectorAll('.diff-code').length"),
+ )
+ .as_i64()
+ .expect("shown line count is a number");
+ assert!(
+ shown_scaled < shown_normal,
+ "the measured trim adapts to the inflated font \
+ ({shown_scaled} lines shown vs {shown_normal} at normal size)",
+ );
+
+ let _ = tab.close(true);
+}
+
+#[test]
+#[ignore = "requires Chrome; runs explicitly in the headless-zero-egress CI job via `-- --ignored`"]
+fn fmt_tip_contained_at_pathological_viewport_height() {
+ // cute-dbt#246 (bullet 2's remaining pin) — the full pathological-
+ // viewport-height containment pin promised by the PR #244 guard
+ // comment: at pathological viewport heights the flip-above branch
+ // used to park bubbles at negative top (clamped since #244), and the
+ // fixed 22rem max-height kept the box taller than the screen (bottom
+ // far past the viewport — RED pre-fix). The viewport-relative height
+ // cap + the post-flip top clamp must fully contain the box on the
+ // height axis even here; contained beats clipped.
+ //
+ // Window-size → viewport mapping is PLATFORM-DEPENDENT (CDP
+ // setDeviceMetricsOverride is a documented no-op in headless_chrome
+ // 1.0.21, see the #157 viewport test, so the launch window is the
+ // only lever): macOS floors the outer window at ~375px and reserves
+ // ~143px of chrome (a ~111px request measured a 232px viewport),
+ // while the Linux CI runner HONORS tiny requests (the same ~111px
+ // request measured a 24px viewport — too short to even hold the
+ // 1-line budget floor plus the bubble chrome). A ~300px request
+ // lands BOTH platforms in a comparable pathological-but-sane band
+ // (macOS ≈232px via its floor; Linux ≈213-300px depending on chrome
+ // height). Every pin below therefore measures against the LIVE
+ // clientHeight, and the premise assert keeps the resulting viewport
+ // inside that band — well under the ~440px a full 20-line budget
+ // needs, so the height-axis pins are exercised for real (RED
+ // pre-fix here via the no-fold pin: the old fixed 222px box — 22rem
+ // at the Sakura 10px root + borders — folded 1178px of content).
+ let url = render_to_file(
+ "headless_246_fmt_tip_pathological.html",
+ vec![model_node("model.shop.dim_x"), model_node("model.shop.src")],
+ vec![("unit_test.shop.dim_x.longfix", long_fixture_unit_test())],
+ &["model.shop.dim_x"],
+ &[],
+ );
+ let browser = launch_browser_sized(Some((900, 300)));
+ let tab = browser.new_tab().expect("new tab");
+ tab.navigate_to(&url).expect("navigate");
+ tab.wait_until_navigated().expect("await navigation");
+ select_model(&tab, "dim_x");
+
+ const BADGE: &str = "document.querySelector('.given-section .format-badge.has-fmt-tip')";
+ const TIP: &str = "document.getElementById('fmt-tooltip')";
+ // Premise: the launch produced a pathologically short viewport on
+ // this platform's window mapping, so the pins below cannot pass as
+ // a tall-viewport no-op.
+ let vh = eval(&tab, "document.documentElement.clientHeight")
+ .as_i64()
+ .expect("clientHeight is a number");
+ assert!(
+ (100..=352).contains(&vh),
+ "premise: the pathological launch yields a short viewport \
+ (got {vh}px; expected the ~300px request to land in the \
+ 100..=352 band on every platform's window-chrome mapping)",
+ );
+ // focus() scrolls the trigger into the (tiny) viewport, so the
+ // singleton positions off a real on-screen rect — the keyboard path.
+ let _ = eval(&tab, &format!("{BADGE}.focus()"));
+ assert!(
+ !eval_bool(&tab, &format!("{TIP}.hidden")),
+ "focusing the format badge reveals the fmt tip at the pathological height",
+ );
+ assert!(
+ eval_bool(
+ &tab,
+ &format!(
+ "(function(){{var r={TIP}.getBoundingClientRect();\
+ var vh=document.documentElement.clientHeight;\
+ return r.top >= 0 && r.bottom <= vh + 1;}})()"
+ ),
+ ),
+ "the fmt tip is contained on the height axis at a pathological \
+ viewport height (post-flip top clamp + viewport-relative height cap)",
+ );
+ // No fold even here: the viewport-derived line budget keeps the
+ // content inside the capped box.
+ assert!(
+ eval_bool(
+ &tab,
+ &format!("{TIP}.scrollHeight <= {TIP}.clientHeight + 1")
+ ),
+ "the fmt tip contains its content with no fold at the pathological height",
+ );
+ // The elision stays truthful at whatever budget the platform's floor
+ // produced: shown lines + the counted trim == the 60 authored lines.
+ assert!(
+ eval_bool(
+ &tab,
+ &format!(
+ "(function(){{var shown={TIP}.querySelectorAll('.diff-code').length;\
+ var m={TIP}.querySelector('.fmt-more');\
+ if (!m) return false;\
+ var n=parseInt(m.textContent.replace(/[^0-9]/g, ''), 10);\
+ return shown < 60 && shown + n === 60;}})()"
+ ),
+ ),
+ "the elision row truthfully accounts for every trimmed line \
+ (shown + counted === 60)",
+ );
+
+ let _ = tab.close(true);
+}
+
/// cute-dbt#240 (defect C) — `.finding-covered-by code` AA sweep across
/// all 8 themes. Sakura's `code {{ background:#f1f1f1 }}` paired with the
/// near-white dark-theme `--text` at ≈1.2:1 (the founder's "near-white
diff --git a/tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap b/tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap
index 58fff440..c7d5553a 100644
--- a/tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap
+++ b/tests/snapshots/render_integration__rendered_chrome_jaffle_shop.snap
@@ -1508,7 +1508,19 @@ th.has-col-meta:focus-visible { outline: 2px solid var(--accent); outline-offset
surface (NOT the dark .col-tooltip shell — it reuses the code-card
chrome so the reconstruction reads like the drawers). */
.fmt-tooltip {
- position: fixed; z-index: 90; max-width: min(34rem, 92vw); max-height: 22rem; overflow: auto;
+ position: fixed; z-index: 90; max-width: min(34rem, 92vw);
+ /* cute-dbt#246 — never a reachability fold: the bubble is
+ pointer-events:none and hides on mouseleave, so anything laid past
+ the old fixed 22rem max-height could never be scrolled to or read.
+ The JS side now bounds the content by REAL MEASUREMENT (trim lines
+ until the laid-out content fits + a truthful elision row), and the
+ height cap is viewport-relative — the #240 defect-D width-cap
+ pattern — so positionTipNear's post-flip top clamp always fully
+ contains the box on the height axis, at any viewport height.
+ overflow:auto stays as the safety net (PR #286 review): if some
+ exotic rendering still defeats the measured trim, the mismatch
+ degrades to a visible scrollbar — a signal — never a silent clip. */
+ max-height: calc(100vh - 16px); overflow: auto;
border: var(--rule-w) solid var(--border-strong); border-radius: var(--radius-pan);
box-shadow: 0 8px 26px rgba(0,0,0,0.34); background: var(--code-bg);
pointer-events: none;
@@ -1516,6 +1528,11 @@ th.has-col-meta:focus-visible { outline: 2px solid var(--accent); outline-offset
.fmt-tooltip .code-header { font-size: 0.74rem; }
.fmt-tooltip .sql-block { margin: 0; padding: 0.4rem 0; }
.fmt-tooltip .code-line { padding-right: 1rem; }
+/* cute-dbt#246 — the truthful elision row under the trimmed block. Plain
+ var(--text) on the bubble's var(--code-bg) (the code-line pairing the
+ 8-theme AA matrix already covers); fixed px so the Sakura 62.5% root
+ (1rem = 10px) cannot shrink it unreadable. */
+.fmt-tooltip .fmt-more { padding: 0.15rem 0.8rem 0.4rem; font-size: 11px; color: var(--text); }
/* ============================================================================
6. ENGINE-PR ADDITIONS (cute-dbt#178)
@@ -3864,16 +3881,59 @@ html[data-style="swiss"] .degraded-chip { border-radius: 0; }
}
// Format tip: the fixture reconstructed in its authored format, in a
// framed mini code-surface (sql/yaml ride the #132 highlighters).
+ //
+ // cute-dbt#246 — the bubble is pointer-events:none and hides on
+ // mouseleave/blur, so anything laid below a height fold could never be
+ // scrolled to or read (the old fixed 22rem + overflow:auto pairing left
+ // below-fold rows silently unreachable for EVERY input modality). Bound
+ // the content instead of folding it: show at most FMT_TIP_MAX_LINES
+ // lines — the tip is a glance surface; the grid below the badge
+ // carries the full data — then trim by REAL MEASUREMENT (PR #286
+ // review): remove rendered lines until the laid-out content fits the
+ // bubble's viewport-capped box. Measuring the live layout (never a
+ // px-per-line estimate) keeps the bound correct under browser zoom, a
+ // forced minimum font size (a11y), wrapped long lines, and any future
+ // rem-based sizing — and the truthful elision row counts exactly what
+ // was trimmed, however it was trimmed.
+ var FMT_TIP_MAX_LINES = 20;
+ function fmtTipMoreText(more) {
+ return "… +" + more + " more line" + (more === 1 ? "" : "s");
+ }
function showFmtTip(trigger) {
var fmt = trigger.getAttribute("data-fmt"), lang = trigger.getAttribute("data-fmt-lang");
var block = trigger.getAttribute("data-fmt-block") || "";
- var code = lang === "sql" ? highlightLinesSql(block)
- : lang === "yaml" ? highlightLinesYaml(block)
- : highlightLinesPlain(block);
+ var lines = String(block).replace(/\n+$/, "").split("\n");
+ var shown = Math.min(lines.length, FMT_TIP_MAX_LINES);
+ var more = lines.length - shown;
+ var code = lang === "sql" ? highlightLinesSql(lines.slice(0, shown).join("\n"))
+ : lang === "yaml" ? highlightLinesYaml(lines.slice(0, shown).join("\n"))
+ : highlightLinesPlain(lines.slice(0, shown).join("\n"));
var el = ensureTip("fmt-tooltip", "fmt-tooltip");
el.innerHTML = '' + code + "
";
+ + '' + code + "
"
+ + (more > 0 ? '' + fmtTipMoreText(more) + "
" : "");
el.hidden = false;
+ // Measure the live layout at full width (reset a stale edge-clamped
+ // left BEFORE measuring — the #232 D18 lesson: a squeezed box wraps
+ // differently and mis-measures).
+ el.style.left = "0px";
+ // The elision row participates in the measured height, so it must
+ // exist before trimming starts (created here if the ceiling alone
+ // didn't already call for one).
+ var moreEl = el.querySelector(".fmt-more");
+ if (!moreEl && el.scrollHeight > el.clientHeight + 1) {
+ moreEl = document.createElement("div");
+ moreEl.className = "fmt-more";
+ el.appendChild(moreEl);
+ }
+ var rows = el.querySelectorAll(".code-line");
+ var keep = rows.length;
+ while (keep > 1 && el.scrollHeight > el.clientHeight + 1) {
+ keep -= 1;
+ rows[keep].parentNode.removeChild(rows[keep]);
+ more += 1;
+ }
+ if (moreEl) moreEl.textContent = fmtTipMoreText(more);
positionTipNear(el, trigger);
}
// Overrides tip: key = value rows grouped by macros / vars / env_vars.