M9: collaboration: per-user undo, rejections, reconnect (#105)

* M9: collaboration: per-user undo, rejections, reconnect

Step 3 of docs/collab-protocol.md, the parts that don't need the transport:

- Per-user undo and redo (figcraft_collab::Client::{undo, redo}): each undoable batch is a step
  (values before/after by target, nodes created/deleted). Others' batches ordered after the step
  take its targets for good; creates invert to deletes (not when others built inside), deletes
  re-create the subtree. An undo or redo waits for every earlier batch and is remade from the
  confirmed state when it goes out; derived data follows as a follow-up batch.
- Rejections: refused batches leave the history (with an undo waiting on them) and queue a
  Notice; the engine shows it through Session::message (the UI's toast).
- Offline queue and reconnect: Hello.resume {session, client_id, seq}; the server keeps a
  bounded log and answers with the missed batches or a snapshot, plus what it did with the
  client's batches (processed {last_batch, refused}); queued batches then go out.
  Server::state / restore persist the session (client-id counter, log, session id).
- Presence carries editing_text {node, caret, selection}.
- Engine: documents in a live session (LiveDoc, Session::collab_*): commands, gestures and
  groups sync as one step each; edit.undo / edit.redo route to the per-user history; remote
  edits show under a gesture in progress. Outside a session snapshot undo is unchanged.
- Sim: dropped connections, undo/redo, and a checker that no undo reverts a change by someone
  else its author had seen. New tests in undo_tests, reconnect_tests, collab_live_tests,
  collab_undo_tests.
- CLAUDE.md / AGENTS.md: collab in the L2 layer list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M9: collaboration wording in the progress docs after per-user undo and reconnect

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M9: protocol doc: what the engine's live-session tests cover

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
BFlat
2026-10-11 03:19:24 +07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 4d7a9187ff
commit 94ff3d4df6
38 changed files with 2890 additions and 273 deletions
+9 -2
View File
@@ -482,7 +482,9 @@ and a file keeps the newest 20 autosaves (user versions are never dropped for th
`collab.diff {against? (undo: the last undoable action, the default | saved: since the last
save)}` returns `{changes, count, bytes}`: the change batch a live session would send for that edit
(per-property `create` / `set` / `delete` and document-level changes, see
`docs/collab-protocol.md`) and its binary size. It changes nothing.
`docs/collab-protocol.md`) and its binary size. It changes nothing. In a live session, where undo
is per user (`edit.undo` / `edit.redo` take back only your own steps, see
`docs/collab-protocol.md`), only `saved` applies.
### Live sessions
@@ -502,11 +504,16 @@ what happened since the last one and sends your edits (a joined file opens as a
- `collab.peers {}` → `{available, status (off|connecting|live|reconnecting|failed), reason?,
attempt?, role (host|guest), me, name, color, fileName, pages, link, url, address, network,
follow, syncing (the document arrived), document (the index of the synced open document), peers: [{id, name, color, presence: {cursor: [page, x, y], selection: [ids], viewport:
[page, x, y, w, h], editing_text}}]}`.
[page, x, y, w, h], editing_text: {node, caret, selection?}}}]}`.
- `collab.cursor {x, y}` puts your cursor at a page position (omit both to hide it): an agent's
pointer. Your selection, page and view go out by themselves when they change.
- `collab.follow {client}` follows that person's viewport; `collab.follow {}` stops (so does any
pan or zoom of your own, or Escape).
- On the session's document every command (or `batch`, or gesture) is one step of your own undo
history: `edit.undo` / `edit.redo` take back only your steps and leave what others changed since.
An edit the host refuses (someone deleted the layer) is rolled back and the reason is the
session's message. While the connection is reconnecting, editing goes on; the edits go out when
it is back (the same client, catching up with what it missed).
### Plugins