Skip to content

refactor: arrow geometry as data (fix head rotation + binding drift) - #114

Open
Amark19 wants to merge 2 commits into
fix/elbow-smart-and-filtersfrom
refactor/arrow-data-model
Open

refactor: arrow geometry as data (fix head rotation + binding drift)#114
Amark19 wants to merge 2 commits into
fix/elbow-smart-and-filtersfrom
refactor/arrow-data-model

Conversation

@Amark19

@Amark19 Amark19 commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What changed

Arrows now store their two logical endpoints as data (arrowPoints, group-local); the rendered children (connector, head(s), label) are derived from that on every layout — the Excalidraw data-model idea, on the same fabric group.

  • arrowEndpoints.jsARROW_GEOM_FIELD is the source of truth; applyEndpointsLocal writes it before deriving; refitArrowBounds re-bases it across the frame change; localEndpoints reads it (one-time reconstruct-from-children migration for old boards/undo).
  • binding.js — persist arrowPoints so geometry survives reload/undo.
  • tests — lock the invariant.

Why

"Where does the arrow point?" used to be answered by reading the head's own left/top/angle back out. That coupled truth to render: a stale head → wrong endpoint → wronger head next re-route. That feedback loop was "the arrowhead stops rotating" and "bindings get messed up". Storing endpoints as data breaks the loop.

Test plan

  • 97 unit tests pass; lint + prettier clean.
  • New invariant tests: a corrupted head no longer moves the endpoint; geometry persists via toObject; old arrows migrate.
  • Verified live (real code via a temp bridge, since removed): straight + elbow heads rotate correctly (straight: 4 distinct angles; elbow: 0/90/−90/180 by facing side); endpoints stay glued to borders; 0px drift over 25 re-routes and across refit (no reselect jump).

Stacks on #113 (elbow routing) — base is fix/elbow-smart-and-filters.

🤖 Generated with Claude Code

…d-back loop)

Adopt the Excalidraw-style idea (on fabric, same group): an arrow's two logical
endpoints are now STORED DATA (arrowPoints, group-local), and the rendered
children (connector, head(s), label) are derived from that every layout.

Before, "where does the arrow point?" was answered by reading the head's own
left/top/angle back out (headTipOf). That coupled the truth to the render — a
stale head produced a wrong endpoint, which produced a wronger head next
re-route: a feedback loop that showed up as "the arrowhead stops rotating" and
"bindings drift/get messed up". Decoupling the truth from the render breaks it.

- arrowEndpoints.js: ARROW_GEOM_FIELD stores [tail, tip]; applyEndpointsLocal
  writes it before deriving; refitArrowBounds re-bases it across the frame
  change; localEndpoints reads it (with one-time reconstruct-from-children
  migration for old boards/undo snapshots).
- binding.js: persist arrowPoints so geometry survives reload/undo.
- tests: lock the invariant — a corrupted head no longer moves the endpoint;
  geometry persists; old arrows migrate.

Verified live: straight + elbow heads rotate correctly; endpoints stay glued;
0px drift over 25 re-routes and across refit (no reselect jump). 97 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 24, 2026

Copy link
Copy Markdown

Deploying whiteboard with  Cloudflare Pages  Cloudflare Pages

Latest commit: 46f3313
Status: ✅  Deploy successful!
Preview URL: https://53897cbf.whiteboard-988.pages.dev
Branch Preview URL: https://refactor-arrow-data-model.whiteboard-988.pages.dev

View logs

Comments should be short and only where the code can't explain itself; add the
convention to AGENTS.md.

Co-Authored-By: Claude Opus 4.8 <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.

1 participant