feat(cli): coordinate-frame layout findings in check#2354
Conversation
Four production compositions shipped with 100-600px layout drift, each a different coordinate-frame confusion the check graded info or missed entirely: viewport pixels written as container left/top, gsap x/y treated as absolute position, a -350px margin fighting flex centering, and stage-relative path coords drawn into a nested SVG. Three new layout findings close the class: - positioned_out_of_parent: an absolute/fixed element rendering mostly outside its positioning ancestor (warning) — the parent needs no overflow clipping, which is what let container_overflow miss it. - box_out_of_canvas: a painted panel breaching the canvas (warning) — text is canvas_overflow's, media is frame_out_of_frame's, painted boxes were nobody's. - connector_detached: a connector path whose endpoints land far from every anchorable element (warning) — measured coordinates drawn into an SVG with a different origin. canvas_overflow additionally promotes from info to warning when held across samples AND the breach exceeds 5% of the canvas. All three are persistence-tiered and respect data-layout-allow-overflow. Verified against the four incident compositions: every one now surfaces its drift as held warnings (previously: info or silence).
…ives Reworks all three findings after two-lens review (adversarial FP hunt in real Chrome + maintainer pass): - escaped_container (was positioned_out_of_parent): uses offsetParent (transform-aware, skips fixed-as-canvas), exempts fully-detached callouts within an attachment allowance while still flagging touching-but-mostly-outside drift. - panel_out_of_canvas (was box_out_of_canvas): paint alone qualifies (flat solid panels were a false negative), fully off-canvas rects are parked entrances and stay silent, pointer-events:none marks decorative layers, hero-sized breaches warn while small bleeds stay info. - connector_detached: endpoints via getPointAtLength + getScreenCTM (viewBox, preserveAspectRatio, group transforms, every command type), defs/marker/clipPath subtrees skipped, word-boundary connector naming, containment tier limited to opaque non-ancestor targets (a text-bearing wrapper contains its own diagram's endpoints). - canvas_overflow promotion requires partial visibility — a fully off-canvas rect is a parked entrance, not drift. Verified: the four incident compositions still surface their drift as held warnings; the review's false-positive repros (fixed HUD, callout, parked entrance, corner bleed, marker arrowheads, g-transform and viewBox-scaled connectors) are clean at warning level. Docs and the CLI skill reference now describe the coordinate-frame findings.
|
v2 pushed (c16cc17) after a two-lens review pass (adversarial false-positive hunt in real Chrome + maintainer review): Renames: Heuristic rework (every item below was a confirmed FP/FN repro):
Validation: four production incident compositions still surface their drift as held ⚠ (escaped_container ×2 on the radial clock; connector_detached 537px/615px on the loop and network scenes; canvas_overflow→warning + panel_out_of_canvas on the off-canvas painting). The review's FP repro compositions (fixed HUD, tooltip callout, parked entrance, corner bleed, marker arrowheads, g-transformed and viewBox-scaled connectors) are clean at warning level. cli suite: 1657 passed, 1 pre-existing host-env-sensitive telemetry failure reproduces on main. Known cosmetic limit: an ambient container rotation (e.g. a 2° sway) fragments the connector dedup key across samples, so those report per-sample info instead of one held warning — the primary finding for that composition ( |
miguel-heygen
left a comment
There was a problem hiding this comment.
Found one blocking false-negative in the new panel contract, plus a test-contract gap.
-
Blocker — painted panels with direct text are excluded before their geometry/paint is checked (
packages/cli/src/commands/layout-audit.browser.js:918).hasOwnTextCandidate()is true for a panel whose text is a direct text node, so the detector skips the entire element. The existingcanvas_overflowpath measures the text range, not the painted box; therefore a common card/hero such as<div style="background:…">Title</div>can have its background cross the canvas while its title remains in-bounds and produce neitherpanel_out_of_canvasnorcanvas_overflow. I reproduced this in the existing happy-dom harness with a 1920×1080 canvas, an 800×600 painted hero at x=1400, and an in-bounds 200×50 text rect: expected onepanel_out_of_canvas, received[]. The new test atlayout-audit.browser.test.ts:557only uses empty panels, so it misses this branch. Please make ownership text-vs-panel specific (skip only a finding already covered by the same geometry, rather than every own-text element) and keep this repro as a regression test. -
Important — positively pin the new LLM-facing diagnostic contract. The three new findings define exact
message/fixHintinstructions, andformatLayoutIssue()sends them directly to CLIcheckoutput (packages/cli/src/utils/layoutAudit.ts:151-152,packages/cli/src/commands/check.ts:337-339). The new browser tests assert code/severity/selector but none asserts the intended message/fix text. Please add at least one positive assertion per new diagnostic so wording cannot silently disappear or drift while the type-only tests remain green.
Focused verification: the submitted 70 layout-audit tests pass. The added direct-text repro fails exactly as described. Current head is c16cc17a; required CI has no failures, with regression shards still running at review time.
Verdict: REQUEST CHANGES
Reasoning: The new panel_out_of_canvas contract misses a normal painted-panel shape whenever the panel owns direct text, and the LLM-facing diagnostics are not positively pinned.
— Deepwork
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Nice piece of work — the coordinate-frame framing (each of the four incident compositions is a different frame confusion) is exactly the right lens, and the three new findings + the canvas_overflow info→warning escalation land the four production drifts as held warnings the way the PR promises. Persistence-tier integration is symmetric (new codes go in PERSISTENCE_TIERED_CODES, single-sample transients demote to info, and the framePositionKey extension keeps distinct connector_detached paths from collapsing into one). Tests cover the positive cases, the allow-overflow opt-out, the fixed-element skip, the hero vs. bleed severity split, and the parked-entrance-not-drift canvas-overflow-escalation guard. LGTM with one concern and two nits.
Concerns
- PR-body naming drifts from the code. The body table and "Fix" section describe the new findings as
positioned_out_of_parentandbox_out_of_canvas, but the emitted codes areescaped_containerandpanel_out_of_canvas(layoutAudit.ts:22-25,layout-audit.browser.js:875/:931). Same concept, two labels — but downstream anything grepping the PR body (dashboards, follow-up PR authors, docs) will look for names that don't exist. Same paragraph also promises the finding fires on "anabsolute/fixedelement rendering mostly outside its positioning ancestor"; the code checks onlyposition: absoluteatlayout-audit.browser.js:860, and the test atlayout-audit.browser.test.ts:530(respects the allow-overflow opt-out and skips fixed elements) actually assertsfixedelements are not flagged. Behavior is sensible (position: fixedhas no meaningfuloffsetParent), so the fix is to update the body — but the mismatch is worth resolving before either name shows up in a dashboard tile or an incident postmortem. Default per[[code-vs-stated-contract]]is make the code match the stated intent, but here the stated intent is the drifted one; updating the body is cheaper and more accurate.
Nits
isCanvasBreachHeldLargeusesMath.min(containerRect.width, containerRect.height) * 0.05— the short edge, not the canvas as such. PR body says "5% of the canvas" — the short-edge choice is the more conservative bar (higher threshold on a landscape composition), and it matches the connector-gap floor's short-edge convention, but the body wording is slightly imprecise vs. the code. Same wording nit in the "Notes for review" thresholds line.connectorAnchorRectsexcludes every element inside any<svg>viaelement.closest("svg")atlayout-audit.browser.js:1001. That's a deliberate scope choice, but it means a diagram whose anchors live inside an SVG (<image>raster,<foreignObject>HTML, or a sibling icon-svg) won't count as attachable. For the four incident compositions the anchors are DOM elements, so this doesn't matter here — but it's worth stating as a known blind spot so a future incident that puts anchors in an SVG parent doesn't silently fail to fireconnector_detached.
Questions
- The escalation shifts
canvas_overflowfrom always-info to potentially-warning under the "held + ≥5% short edge + partially visible" gate. Any known composition onmainwhose currently-infocanvas_overflowwill newly promote to warning after this ships? The four incident cases are the intended targets, but if any non-incident production comp currently rides on info-level canvas_overflow as a design choice, it will needdata-layout-allow-overflowat once. Rolling the escalation out on its own smaller PR (as offered in your Notes) would make that migration atomic — up to you.
What I didn't verify
- Full run of the four incident compositions myself — trusting the numbers in your Verification section.
- Whether the escalated
canvas_overflowinteracts as expected with any downstream--jsonconsumer that filters on severity strictly. If any dashboard is groupingcanvas_overflowas info by identity, that grouping breaks.
…lent false negative A painted panel whose direct text stays in-bounds while its box breaches the canvas produced neither finding: canvas_overflow measures the text range and panel_out_of_canvas skipped every own-text element. Skip the panel finding only when the element's own text ALSO breaches (that geometry belongs to canvas_overflow); pin the message/fixHint wording of all three findings with positive assertions; document the SVG-internal anchor blind spot.
|
@miguel-heygen both items addressed in
@james-russo-rames-d-jusso all four points taken, PR body rewritten in place:
|
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
pointer-events:none exempted the framed-painting incident's gold frame layers — hero content that happens to disable hit-testing. Decoration is now gradient-only paint (spotlights, textures, vignettes); url() images, solid fills and borders are content regardless of pointer-events.
|
One more self-caught regression while re-validating the full incident set on the latest head: the |
miguel-heygen
left a comment
There was a problem hiding this comment.
Re-review at 6dc5504c: both prior findings are addressed. packages/cli/src/commands/layout-audit.browser.js:928-933 now distinguishes an out-of-canvas panel from its own text geometry, the exact direct-text repro is pinned at layout-audit.browser.test.ts:610-631, and all three new LLM-facing diagnostics have positive message/fixHint assertions. The PR body also now matches the emitted codes and fixed-element contract.
Blocker — the follow-up paint classifier makes every gradient-only content panel invisible. packages/cli/src/commands/layout-audit.browser.js:892-907 now returns true only for url(), a non-transparent solid color, or a border. A normal hero/card whose only paint is linear-gradient(...) is therefore classified as decoration regardless of size, pointer behavior, or content intent. I reproduced this in the submitted harness with the same 1920×1080 canvas and 800×600 hero at x=1400: changing its background to linear-gradient(90deg, #101828, #344054) yields zero panel_out_of_canvas findings. That reopens the core “painted box was nobody’s” false-negative for a common panel style. The spotlight exception needs a narrower decoration signal than paint kind alone (for example, combine gradient paint with an explicit decorative convention) and a gradient-content regression test.
Important — shallow text breaches still double-report despite the new ownership contract. packages/cli/src/commands/layout-audit.browser.js:930-932 checks the text rect using the panel floor (27px on 1920×1080), while canvas_overflow uses the audit tolerance at layout-audit.browser.js:483-498. With a hero breaching 280px and its direct text breaching only 20px, I get both canvas_overflow and panel_out_of_canvas; the new counter-test only covers a 230px text breach. Use the same geometric threshold as the owning text diagnostic and pin the below-panel-floor case so one element does not produce two competing instructions.
Submitted focused tests pass 72/72. Both added adversarial repros fail exactly as above. Current required checks have no failures; eight regression shards are still running.
— Deepwork
Verdict: REQUEST CHANGES
Reasoning: The original direct-text false-negative and diagnostic pins are fixed, but the new blanket gradient exemption creates another common painted-panel false-negative; the ownership threshold also still permits duplicate findings.
james-russo-rames-d-jusso
left a comment
There was a problem hiding this comment.
Reviewed at 6dc5504c (R2 delta from c16cc17a).
R2 addresses both Miguel's blocker and the diagnostic-pinning gap cleanly, plus tightens isPaintedPanel with a nicer decoration heuristic.
What's clean
- Miguel's panel-with-direct-text FN fix (
layout-audit.browser.js:929-932) — the skip is now geometric: if the panel owns direct text AND that text also breaches,canvas_overflowowns it; otherwise the panel finding fires. Fallback whentextRectFor()returns null is safe (falls through to the panel check). Two new tests pin both branches: painted hero + in-bounds text →panel_out_of_canvas(test at line ~605), painted hero + breaching text →canvas_overflowonly (test at line ~626). Exactly the ownership contract Miguel asked for. isPaintedPanelrefactor (browser.js:892-910) — swaps thepointer-events:none-convention filter for a positive paint-intent check:url(...)images / non-transparent solid color with alpha > 0.05 / non-zero borders count as content; gradient-only paint is decoration.spotlight(radial gradient) test asserts NOT flagged,goldframe(border-only) test asserts IS flagged. Better signal — no longer depends on authors remembering to setpointer-events:noneon decoration.- Diagnostic wording pinned per Miguel's #2 — escape/panel/connector tests now positively assert
message+fixHintfragments (escaped_container: "computed in a different frame" + "offset parent's frame";panel_out_of_canvas: "Painted panel extends outside the composition canvas." + "data-layout-allow-overflow";connector_detached: "drawn into an SVG with a different origin" + "Subtract the SVG's own rect"). LLM-facing contracts can't now silently drift while type-only asserts stay green. - SVG-anchor blind spot documented (
browser.js:1016) — my R1 nit gets a code comment. Reasonable — behavior fix would be significant scope; making the constraint visible in the source is the right call for a follow-up.
One small note
panel_out_of_canvas uses PANEL_BREACH_FLOOR_PX = 24 (or ≥2.5% short edge) for the text-owns-overflow check, while canvas_overflow runs at tolerance = 2. A panel breaching by 200px whose own text breaches by only 10px would clear canvas_overflow's 2px threshold but NOT the panel's 24px floor, so both findings could fire on the same element (text→canvas_overflow info, panel→panel_out_of_canvas warning). That's arguably the right output for a real geometric double-event, but Miguel's ownership contract read as "one or the other, not both" — worth a two-line comment on the intent, or a follow-up tightening the check to match canvas_overflow's tolerance if the intent was strict-mutex.
Non-blocking either way.
What I didn't verify
- Didn't re-run the local vitest suite against R2 — trusting the CI signal on
6dc5504c(unit tests already green; regression shards still churning at review time). - Didn't verify the R2
spotlight/goldframerenders through the full happy-dom harness end-to-end — pattern matches R1's existing pinned tests, which cover the same shape.
…hes canvas_overflow's tolerance A gradient with any solid stop (alpha >= 0.6) is content — heroes and cards painted with linear-gradient were invisible under the blanket gradient exemption; all-translucent stops (spotlights, vignettes) stay decoration. The text-ownership check now uses the audit tolerance that canvas_overflow itself fires at, making the contract strict-mutex: any text breach past that tolerance cedes the element, so a shallow 20px text breach no longer double-reports.
|
@miguel-heygen both R2 findings addressed in
Full validation matrix re-run on this head (all six compositions, no sampling): four incidents still fire as held ⚠ (clock @james-russo-rames-d-jusso your R2 note is the same threshold mismatch miguel escalated — resolved as strict-mutex (same tolerance), with the intent stated in the code comment. |
Problem
Four production compositions (HeyGen Video Agent / Zephyr, 07-07/08) shipped with 100–600px layout drift. Each was a different coordinate-frame confusion, and
checkgraded every one ℹ info — or saw nothing at all:left/topx/y(translate) treated as absolute position-350pxmargin fighting flex centeringcanvas_overflowℹ info → Check passedFix — three new layout findings + one promotion
escaped_container(warning): aposition:absoluteelement rendering mostly outside itsoffsetParent(transform-aware containing block).position:fixedis deliberately skipped — fixed-as-canvas-coords is correct HF authoring. Fully-detached callouts within an attachment allowance are exempt; touching-but-mostly-outside drift still flags. The offset parent needs no overflow clipping — which is exactly what letcontainer_overflowmiss the escaped nodes.panel_out_of_canvas(warning for hero-sized ≥10% of canvas, info for small bleeds): a painted panel breaching the canvas. Division of labor: a breaching text range belongs tocanvas_overflow, media toframe_out_of_frame— painted boxes were nobody's. Ownership is geometric: a panel whose box breaches while its own text stays in-bounds still reports (review-caught FN). Fully off-canvas rects are parked entrances and stay silent;pointer-events:nonemarks decorative layers.connector_detached(warning): a connector path (marker attrs or word-boundaryconn|connector|arrow|edge|link|flow|wirenaming) whose both endpoints land far from every anchorable element. Endpoints viagetPointAtLength+getScreenCTM(viewBox, preserveAspectRatio,<g transform>, all command types);defs/marker/clipPath/masksubtrees skipped. Known blind spot: anchors living inside an SVG (<image>,foreignObject) are not counted as attachable.canvas_overflowescalation: info → warning when held across samples, the breach exceeds 5% of the shorter canvas edge, AND the rect is partially visible — a fully off-canvas rect is a parked entrance, not drift.All three are persistence-tiered (single-sample → info) and respect
data-layout-allow-overflow.Verification
main). New coverage: escape/badge/callout/fixed, painted-panel severity split, direct-text panel FN regression, text-also-breaching ownership counter-case, detached/anchored/marker-tip connectors, word-boundary naming, opt-outs, tier promotion + parked-entrance and short-breach floors, message/fixHint wording pinned per finding.Rollout notes
check's exit code — everything here is warning/info, so no pipeline goes red from this PR.canvas_overflowas a design choice will reclassify to warning only if held + ≥5% short edge + partially visible;data-layout-allow-overflowopts out. Any--jsonconsumer groupingcanvas_overflowas info by identity will see the reclassification. Happy to split the escalation into its own PR if preferred.escaped_container.🤖 Generated with Claude Code