Skip to content

fix: connector_detached now skips fully dash-hidden connector shafts - #3912

Open
miga-heygen wants to merge 2 commits into
mainfrom
fix/connector-detached-dash-hidden
Open

miga-heygen wants to merge 2 commits into
mainfrom
fix/connector-detached-dash-hidden

Conversation

@miga-heygen

@miga-heygen miga-heygen commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Summary

connector_detached (the layout-audit finding for SVG connector paths whose rendered endpoints don't attach to any anchor) evaluated a path's geometry unconditionally — it had no gate for stroke-dashoffset/stroke-dasharray draw-on visibility. A connector fully hidden behind an un-advanced "draw the line in" dash animation still fired the finding as if it were rendered and visible. The sibling check, connector_orphan, already skipped fully dash-hidden shafts via shaftDashHidden(path).

Two things changed on top of simply adding that gate:

One owner for connector enumeration. connector_detached and connector_orphan each walked svg path with their own copy of the candidate/gate logic (visible svg, skip defs/marker/clipPath/mask/symbol/pattern, connector intent, endpoint resolution, dash gate) and the copies had drifted. A single connectorShafts(root) generator now owns that walk and yields { svg, path, user, rendered, chord, painted }; both findings consume it. connector_orphan still requires painted (display/visibility/opacity); connector_detached still does not gate on it — that is unchanged behaviour and documented at the owner. The path length is queried once per shaft (pathUserEndpoints now returns total) instead of twice, and the attach-threshold formula has one owner.

shaftDashHidden reads the whole pattern. It previously read only the first stroke-dasharray entry, so a visible dotted pattern like 0 4 — or any pattern with a long first dash — was treated as hidden and suppressed both findings. It now resolves the full computed list (comma/space separated, units, percentages against the SVG viewport's normalised diagonal, odd lists repeated; none, all-zero and negative lists render solid) and treats the shaft as hidden only when the visible window [dashoffset, dashoffset + length] sits inside a single gap, allowing up to 10% of the length to overlap a neighbouring dash (the tail of a nearly finished tween — same boundary the old >= 0.9 * length rule had). A zero-length dash paints only as a round/square line cap; under the default butt it renders nothing, which also covers the 0px, 999999px state a finished draw-off tween leaves behind.

Test plan

  • Regression test: a connector fully hidden via strokeDasharray/strokeDashoffset no longer fires connector_detached, on the same fixture as the adjacent positive control (shared foreignFrameDom/foreignFrameRects/foreignFrameStyles, mirroring this file's orphanDom convention).
  • Dash-pattern table (path length mocked to 100): 0 4 with butt cap → hidden; 0 4 round and 0, 4 square → painted; 0px, 999999px @ -99.999px → hidden; 0 400 round @ 0 → painted (dot inside the window), @ 1 → hidden; 4 0, 0, none → painted; 100px @ 100px and 100 @ -100 → hidden; 50 100 @ 50 and @ 40 (exactly 10% painted) → hidden, @ 30 → painted.
  • Existing connector_orphan tests (including its dash-hidden case) pass unchanged against the shared enumerator.
  • layout-audit.browser.test.ts does not collect under this sandbox's vitest (No such built-in module: node: — pre-existing, also on an unmodified checkout). Run instead under bun test with a happy-dom preload: 126 pass, 9 fail, where the 9 are contrast-audit cases that fail identically before this change. bunx tsc --noEmit -p packages/cli, oxlint, oxfmt --check clean; pre-commit lint/format/fallow/typecheck hooks pass.
  • Not verified in a real browser: % dash lengths (resolved against viewBox, else the SVG's layout box) and calc() values (parse to NaN → treated as solid, i.e. the safe direction).

miga-heygen and others added 2 commits September 12, 2026 23:03
connector_detached fired on SVG connector paths that were 100% hidden
via stroke-dashoffset/stroke-dasharray draw-on entrances, evaluating
rendered/user-space geometry unconditionally. The sibling
connector_orphan check already gates on shaftDashHidden(path); add the
same gate here, in the same position.

Co-Authored-By: Miguel Angel <miguel.sierra@heygen.com>
…wner for connector enumeration

`shaftDashHidden` read only the first `stroke-dasharray` entry, so any
pattern starting with 0 (`0 4` dotted) or a long first dash counted as
hidden and suppressed both connector findings on a visible stroke. It now
resolves the full computed list (comma/space separated, units,
percentages against the SVG viewport diagonal, odd lists repeated;
`none`, all-zero and negative lists render solid) and treats the shaft as
hidden only when the window `[dashoffset, dashoffset + length]` sits in a
single gap, with up to 10% of the length overlapping a neighbouring dash.
Zero-length dashes paint only under `stroke-linecap: round | square`;
under the default `butt` they render nothing, which also covers the
`0px, 999999px` state a finished draw-off tween leaves behind.

`connector_detached` and `connector_orphan` each walked SVG paths with
their own copy of the candidate/gate logic, which had already drifted.
A single `connectorShafts(root)` generator now owns enumeration, the
dash gate and endpoint resolution, and yields the `painted` verdict;
both findings consume it. The path length is queried once per shaft
(`pathUserEndpoints` returns it) instead of twice, and the attach
threshold formula has one owner.

Tests pin `0 4` (butt and round), `0, 4`, `4 0`, `0`, `none`, px units,
negative offsets, a draw-off end state, and both sides of the 10%
boundary.

Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
@miga-heygen

Copy link
Copy Markdown
Contributor Author

Addressed in 58aefbb.

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.

1 participant