feat(render): solid arrowhead style + start-head render coverage#64
Merged
Conversation
Closes the gap where hasStartHead was only ever true via 'both' (#61): curve start inset, start-head socket anchoring, and elbow startTrim.
arrowHeadPath(closed=true) appends Z so the head can be filled (#60).
'solid' closes the head triangle and fills it with the stroke color via rough.js fillStyle 'solid', keeping the hand-drawn look (#60). Default 'line' output is unchanged. Drops the redundant fill='none' clobber in appendDrawable (rough.js already sets it on stroke paths) and tags each segment with its drawable's reveal group for the fade-in that follows.
rough.js emits a head's fill path before its outline strokes, so raw segment order can't drive sequential reveal. segmentFractions gives every segment its revealed fraction: fill paths carry no length in the draw budget and mirror their own head group's stroke fraction, fading in while the outline dash-draws (#60). dashOffsets delegates to it, so stroke timing is unchanged.
…ions Simplify pass: shared anchors()/paths() test helpers (pathDs derives from paths); dashOffsets deleted from src (applyProgress uses segmentFractions directly; its numeric suite now guards segmentFractions via a local offsets helper); segments field typed via DrawSegment; segmentFractions skips group bookkeeping when no fill segment exists (per-frame hot path).
Add integration test proving each solid head fades independently for head:'both' via appendDrawable's real group-id wiring (review finding #1).
New section between obstacle avoidance and the tree reveal: head 'both' with headStyle 'solid' so both filled heads fade in with their outlines. Hardcoded section indexes for the tree and reduced-motion cards shift by one.
This was referenced Jul 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Arrowheads can now render as solid filled triangles.
headStyle: 'solid'closes the head path and fills it with the stroke color through rough.js's solid fill, so the hand-drawn look carries into the fill. The default open-V style is untouched — omittingheadStyle(or passing'line') renders byte-identical output, guarded by seed-deterministic tests.A filled shape can't reveal through
stroke-dashoffset, so solid heads fade in instead: each head's fill opacity rides its own outline's draw fraction, meaning the fill materializes while the outline sketches once the line reaches that head. Reduced-motion and static renders show it fully.This branch also closes the render-level coverage gap where a start-only arrowhead was never exercised — the start-inset, socket-anchoring, and elbow leg-trim paths were previously only reached via
head: 'both'.Design notes
segmentFractionsinsrc/draw.ts).dashOffsetsretired from production.applyProgressconsumes per-segment fractions directly (dash offset for strokes, opacity for fills). The old numeric suite still guards the offset math via a test-local helper.segmentFractionsshort-circuits to the previous two-pass, zero-allocation shape when no fill segment exists, so default line-style arrows pay nothing new per animation frame.fill="none"on stroke paths, soappendDrawableno longer overwrites fills — which is also what lets a solid head's fill path survive.Validation
Full suite green: 186 vitest tests across 15 files, including byte-identity guards for the default style (rendered
dstrings and fill attributes withheadStyleomitted vs'line'), animation-offset equivalence for stroke-only segments, per-head independent fade forhead: 'both', and start-head anchoring at true socket coordinates. Typecheck, ESLint, Prettier, and tsup build all clean. Browser spot-check of the demo page was not run in this session (no browser tooling available); the solid style is visually verifiable there.Related