Build image / build-and-push (push) Successful in 17s
The prop names were backwards. `canEdit` meant "may delete" and `canWrite` meant "may rewrite the text" — which is the opposite of how either word reads, on a component where the distinction is the entire permission model. They are now canDelete and canRewrite. An open edit draft never re-synced with its entry, so a refetch after someone else's edit left a stale draft and a Cancel that restored the wrong text. It now resyncs when the entry changes underneath, but only while not editing, so a background refetch can't clobber what's being typed — the version guard is what catches a genuine collision. A 409 from an edit had no handling, so the component kept its stale version and every retry conflicted again: a button that just doesn't work. A conflict now invalidates, so the fresh version arrives. Saving an empty edit returned silently, which also reads as a broken button. It says why nothing happened, and points at Delete if that's what was meant. A failed delete's error now clears on retry rather than making a second attempt look like a second failure. formatObservedAt let out-of-range parts roll over — 2026-13-45 rendering as February 2027, confidently wrong. It shows the raw string instead. Also: the scope select guards against a non-numeric value becoming a NaN objectId and a cryptic 400; the journal total is memoized rather than spread-and-reduced every render; EditorRail's doc comment no longer calls the journal "later" now that it's here; and a static class list stopped being split across two cn() arguments for no reason. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ