Skip to content

feat(collaborator): add appendContent RPC for merge (non-clobbering) writes#10959

Open
jasonestewart wants to merge 1 commit into
hcengineering:developfrom
jasonestewart:mrkt-185-append-content
Open

feat(collaborator): add appendContent RPC for merge (non-clobbering) writes#10959
jasonestewart wants to merge 1 commit into
hcengineering:developfrom
jasonestewart:mrkt-185-append-content

Conversation

@jasonestewart

@jasonestewart jasonestewart commented Jul 6, 2026

Copy link
Copy Markdown

Summary

The collaborator service currently exposes only full-replace writes (getContent / createContent / updateContent). updateContent clears the document's Y.XmlFragment (fragment.delete(0, fragment.length)) before applying the incoming update, so a server-side read-modify-write silently clobbers concurrent edits — e.g. an automation appending to a Document body while a user is editing it in the browser.

This PR adds an appendContent RPC that is identical to updateContent except it omits the fragment.delete(...) step. Applying the incoming update onto the untouched fragment lets the Y.js CRDT merge the new content in, giving append semantics. Because the mutation runs inside the same hocuspocus direct connection updateContent uses, it is race-free against concurrent editors via the CRDT layer.

Use case

Automated agents and human editors collaborating on the same Document body — an automation can append (log lines, generated sections, status updates) without wiping out whatever a person is concurrently typing.

Changes

  • server/collaborator — new appendContent method (a copy of updateContent minus the fragment clear) and its registration in the RPC dispatch table.
  • collaborator-clientAppendContentRequest / AppendContentResponse types and an appendMarkup(document, markup) client method wrapping the RPC (mirrors updateMarkup).
  • TestsappendContent semantics: append into an empty doc, append preserving prior content, replace-vs-append contrast, and two independent appends both landing (CRDT merge, no clobber).

Notes / open questions

  • server/collaborator has no existing RPC/hocuspocus integration harness, so the added tests validate the underlying Y.js merge behaviour the RPC relies on (the single semantic difference from updateContent) rather than spinning up a full hocuspocus server. Happy to add an end-to-end integration test against a live websocket session if you have a preferred harness for it.
  • The new method deliberately mirrors updateContent line-for-line so it tracks any future changes to that path.

🤖 Generated with Claude Code

…writes

The collaborator service only exposes full-replace writes (createContent /
updateContent). updateContent clears the document's Y.XmlFragment before
applying the incoming update, so a read-modify-write from an automation
silently clobbers any concurrent edits (e.g. a human editing the same
Document in the browser).

Add an appendContent RPC that is identical to updateContent but omits the
`fragment.delete(0, fragment.length)` step. Applying the incoming update
onto the untouched fragment lets the Y.js CRDT merge the new content in,
giving append semantics that are race-free against concurrent editors
(the mutation runs inside the same hocuspocus direct connection).

- server/collaborator: new appendContent method + dispatch registration
- collaborator-client: AppendContentRequest/Response types + appendMarkup()
- tests: append-to-empty, append-preserves-prior, replace-vs-append,
  and two-independent-appends-both-land (CRDT merge, no clobber)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@huly-github-staging

Copy link
Copy Markdown

Connected to Huly®: UBERF-16634

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