NEC-318: A live drawing never draws an element Excalidraw re-indexes on reconcile, and retries silently #310

Merged
nectenda-agent merged 18 commits from worktree-nec-318-a-live-drawing-never-draws-an-element-ex into main 2026-10-20 21:41:09 +01:00
Collaborator

Task: NEC-318 — https://projectron.nerchure.com/tasks/338

A live Excalidraw drawing never drew a remote shape that Excalidraw re-ordered as it reconciled it: reconcile repairs the shape's fractional index in place and bumps its version, so the binding took it for the view's own newer version, held it back and retried every pass. The view now draws it, and syncs the repaired place as an edit carried on the version it repaired with a nonce from the top of Excalidraw's range, so it loses every tie: an edit made anywhere at the same version stands over it, and the repair converges with nothing kept (index is not content). A repair the document has already moved past is not carried, and gives way in the view once the shape is not being edited. The third design: carrying the repair as Excalidraw made it lost a recolour (hardness seed 1523149228), and keeping it local left two vaults stacking shapes in opposite orders. Both are recorded in the spec and docs/sync-limitations.md.

Also fixed here, found on main while working this card: an edit made while a remote change to the same shape was held back could be removed silently (NEC-321; it is now carried as a rival judged against the version it was made on, removing nothing), and a version that does not stand was re-offered every capture until the runaway guard silently unbound the drawing (NEC-320, folded in at the user's call rather than filed as a ride-along; each such version is now offered once per binding). Ride-along NEC-319: spec-delta.mjs now counts a rule's nested clauses.

SAFE-A29 updated. Verified: plugin unit tests (2332), local gate (247 e2e, 2 skipped), every new rule mutation-checked both ways, and a 60-minute Excalidraw hardness run on the final code (A on 2.28.1, B on 2.27.3; seeds 1523149228, 1528500687, 1955423520), with repairs in every run and no bounce or long hold. Installed for the user's manual test before opening.

Also fixes NEC-320.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK

Spec: docs/changes/NEC-318-live-drawing-reindexed-element/spec.md

Also fixes NEC-319: spec-delta reads a rule only up to its first sub-bullet, so a reworded nested clause escapes the gate
Also fixes NEC-321: An edit made on a shape held back for a remote change is never carried in, and is drawn over

Changelog

A live Excalidraw drawing now shows a shape another vault adds or changes even when Excalidraw re-orders it on the way in, instead of leaving it out.

Task: NEC-318 — https://projectron.nerchure.com/tasks/338 A live Excalidraw drawing never drew a remote shape that Excalidraw re-ordered as it reconciled it: reconcile repairs the shape's fractional index in place and bumps its version, so the binding took it for the view's own newer version, held it back and retried every pass. The view now draws it, and syncs the repaired place as an edit carried on the version it repaired with a nonce from the top of Excalidraw's range, so it loses every tie: an edit made anywhere at the same version stands over it, and the repair converges with nothing kept (index is not content). A repair the document has already moved past is not carried, and gives way in the view once the shape is not being edited. The third design: carrying the repair as Excalidraw made it lost a recolour (hardness seed 1523149228), and keeping it local left two vaults stacking shapes in opposite orders. Both are recorded in the spec and docs/sync-limitations.md. Also fixed here, found on main while working this card: an edit made while a remote change to the same shape was held back could be removed silently (NEC-321; it is now carried as a rival judged against the version it was made on, removing nothing), and a version that does not stand was re-offered every capture until the runaway guard silently unbound the drawing (NEC-320, folded in at the user's call rather than filed as a ride-along; each such version is now offered once per binding). Ride-along NEC-319: spec-delta.mjs now counts a rule's nested clauses. SAFE-A29 updated. Verified: plugin unit tests (2332), local gate (247 e2e, 2 skipped), every new rule mutation-checked both ways, and a 60-minute Excalidraw hardness run on the final code (A on 2.28.1, B on 2.27.3; seeds 1523149228, 1528500687, 1955423520), with repairs in every run and no bounce or long hold. Installed for the user's manual test before opening. Also fixes NEC-320. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK Spec: `docs/changes/NEC-318-live-drawing-reindexed-element/spec.md` Also fixes NEC-319: spec-delta reads a rule only up to its first sub-bullet, so a reworded nested clause escapes the gate Also fixes NEC-321: An edit made on a shape held back for a remote change is never carried in, and is drawn over ## Changelog A live Excalidraw drawing now shows a shape another vault adds or changes even when Excalidraw re-orders it on the way in, instead of leaving it out.
A live drawing counted a remote shape as drawn only when Excalidraw's
reconcile handed it back at the document's own version. Reconcile also
repairs a shape whose place in the stacking order does not fit its
neighbours, in place, bumping its version. The binding took that copy
for an edit of the view's own, drew nothing, found nothing to carry in,
and retried silently for as long as the drawing stayed open.

Drawn is now decided by which element reconcile kept: the very copy
built from the document is drawn, and its new version carried in as an
edit made on the document's. The view's own element, kept, is held
whatever its version, since taken for drawn it would name the
document's version as its base and remove it with no copy kept. A
remote change held back draw after draw is logged once per hold.

Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Every third image insert, when an image has been deleted, inserts that
image's picture again: the same file, so the same file id, as in the
case NEC-318 was found by. Decided by the tag's hash, as the large
images are, so the random streams and recorded seeds are unchanged.

Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
The window in which a re-order and a user's edit can be rivals is the
capture throttle plus delivery, not the throttle alone, and a single
nudge or resize lost to it converges with nothing kept while content is
kept as a copy. The stale-view save is recorded as seen once by hand,
since no committed test can hold the old rule. SAFE-A29 says "not by
whether its version changed", which is what the code does. The hardness
run inserts a deleted image's picture again only when this vault has
that file, so its trace never names a reuse that did not happen.

Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
spec-delta ended a rule's text at the first line that looked like a
bullet, indented or not. A rule written as a list of clauses, SAFE-A29
for one, was read as its opening line alone, so a reworded clause
passed the gate as no rewording at all. It now ends only at a blank
line, a heading, or a top-level bullet.

Task: NEC-318
Also-fixes: NEC-319

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Each round reads both vaults' diagnostic logs for shapes the live
binding drew re-ordered by Excalidraw's reconcile (NEC-318). A shape
re-ordered more than three times in one vault is a re-order bouncing
between vaults, which the fix reasons cannot happen but no test had
shown. The trace also says how many were re-ordered, so a passing run
tells whether real Excalidraw reached that path.

Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
The first fix drew a shape Excalidraw re-ordered and carried its raised
version in as an edit made on the document's. The hardness run (seed
1523149228) found that an edit made at once in the other vault to a
shape it had just added was the re-order's rival on the same version,
and lost: kept as a copy, with a notice, and a nudge would have gone
with nothing kept.

A place repaired is not an edit. Whatever reconcile repairs, a copy of
the document's or the view's own, now keeps the version and nonce it
was handed; only its place changes, in that view, until a real edit
there carries it. So it is drawn like any other remote change, nothing
is carried in for it, and it is never a rival. Drawn is the key rule
again, which now holds the view's own element back whatever reconcile
did to its place.

The hardness run traces the re-orders kept and the long holds logged
rather than counting bounces, since a re-order is no longer synced.
docs/sync-limitations.md also records NEC-320, found on the way: a
view still editing a shape whose own edit lost re-offers it until the
runaway guard unbinds the drawing.

Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Kept in one view alone, a repair of a shape's place could meet a place
the other vault later gave a shape it moved, and the two stacked the
shapes in opposite orders (found in review). Carried in as Excalidraw
made it, it was the rival of any edit made on the same version, and
won as often as not (the hardness run, seed 1523149228).

So a repair is carried in, on the version it repaired, with a nonce
from the top of Excalidraw's range. A person's edit at the same version
beats it everywhere, and a repair that loses converges with nothing
kept, since it changes nothing but the place and the merge already
converges such a loser against its recorded base. A repair the
document has moved past is not carried at all: it would be a rival made
on nothing the document knows, and the unit test lost the other vault's
edit without that rule. Drawn is decided by identity again, which holds
back the view's own element whatever reconcile did to it.

The hardness run fails again on a shape one vault repairs more than
three times, now that a repair fixes the place in the document.

Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Review of the synced-repair design found two faults, each reproduced
with the unit tests' fake reconcile and failing a new test.

Excalidraw raising the view's own edit is that edit, raised. Given the
losing nonce and held back as a repair, an edit made while a remote
change was held was drawn over, in no document and no copy. Only a copy
of the document's version, or the view's element exactly as it was last
known to hold the document's, is now a repair; anything else is left as
Excalidraw raised it and carried in as an edit, as before.

A repair whose base then lost a tie stood a version above what stands,
so reconcile kept it, and nothing was drawn, every 100 ms, for good.
Such a repair now gives way in the view to what stands, unless the
shape is being edited. One carried in and standing itself is kept: left
out too, the shape vanished from the view.

Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
An edit made in a drawing while a remote change to the same shape is
held back for it is made on the version the view holds. Once the
document had moved further on, its version was lower than what stood:
it was passed over as older, never carried in, and drawn over when the
hold ended, in no document and no copy. It is now carried in as a rival
made on the version the view held, and settle keeps it as a copy if it
lost writing.

Carried so, it would be offered again on every capture while the view
keeps it, as a losing edit already was: settled into a copy every 100
ms until the runaway guard stopped the binding with nothing said. A
version that does not stand is now carried in once per binding.

Task: NEC-318
Also-fixes: NEC-321
Also-fixes: NEC-320

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Carried as made on the version the view last held, an edit made during
a hold removed that version from the document whenever its number was
higher. With three vaults that version can be another vault's edit that
lost, still waiting for its author to keep it: removed here, it was in
no document and no copy (found in review, reproduced with a third
vault). The base is now passed unverified, so the edit is judged
against it and a raise that changed nothing still converges, but
nothing is removed.

Task: NEC-318
Also-fixes: NEC-321

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Without its base the edit is still kept, but a move that lost becomes
a spurious copy; the three-vault test now counts copies, and fails so.

Task: NEC-318
Also-fixes: NEC-321

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Record the review, gate and hardness hour on NEC-318's final design
Some checks failed
Release note / release-note (pull_request) Successful in 19s
CI / promote (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / e2e (pull_request) Has been cancelled
51edc256cb
Task: NEC-318

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Release notes for NEC-318 and the two fixes found alongside it
All checks were successful
Release note / release-note (pull_request) Successful in 17s
CI / build (pull_request) Successful in 5m37s
CI / e2e (pull_request) Successful in 12m22s
CI / promote (pull_request) Has been skipped
CI / build (push) Successful in 5m55s
CI / e2e (push) Successful in 12m17s
CI / promote (push) Successful in 31s
9a503dcf14
Task: NEC-318
Also-fixes: NEC-321
Also-fixes: NEC-320

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AFBXMDJhig8T4bgUzjLYhK
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Nectenda/nectenda!310
No description provided.