Skip to content

Hazel in Patchwork - #1959

Draft
disconcision wants to merge 145 commits into
devfrom
patchwork
Draft

Hazel in Patchwork#1959
disconcision wants to merge 145 commits into
devfrom
patchwork

Conversation

@disconcision

@disconcision disconcision commented Sep 16, 2025

Copy link
Copy Markdown
Member

Try it out:

  1. Go to patchwork.inkandswitch.com.
  2. Go to 'My tools' in the Patchwork sidebar, and select 'Register a Module'. Enter the following module URL: automerge:27ShjXmwBQPAovhh9Sgd5L5ue1a1. This registers a HazelDoc Patchwork datatype and tool, which will enable you to create and view new HazelDocs.
  3. Go to 'Create new' in the Patchwork sidebar, and select 'New HazelDoc'.
  4. This may be set to try and load Hazel locally by default; click the 'Remote' button on the lowest top bar to set the Hazel URL to the patchwork branch on the hazel build server.
  5. At this point, you should see a hazel scratchpad, possibly in Patchwork-enforced Dark Mode, depending on your system settings
  6. You should be able to share this doc with others by selecting 'Copy share URL' in the ... menu at the top right.

Relevant external URLs:


Hazel in Patchwork

This adds supports for collaborative multiplayer hazel programs via patchwork+automerge.

In particular:

  • Hazel programs can be created within Patchwork by adding the HazelDoc tool via its automerge URL.
  • These can be shared by generating a Patchwork share link.
  • Caret positions are broadcast across all clients, and decorated with patchwork usernames and user color (if logged in).
  • This version of Hazel behaves different when loaded in an iframe, for example by our patchwork tool wrapper. It starts in Zen Mode (hiding the top, bottom, and side bars); press Cmd/Ctrl-K to toggle the rest of the UI. All other editors modes other than a single scratch slide are disabled. Localstorage persistence is disabled in favor of automerge.
  • Many kinds of concurrent edits are possible without clobbering other user's edits, however there are rough edges to this and inconsistent states across clients are possible.

Minor limitations:

  • Your caret position will be changed if another user makes an edit to the token/delimiter that you're on, or to a selection that contains your caret. Fallback repositioning logic handles many cases well but isn't perfect.
  • Similarly, your range selection may be disrupted by other user's edits to pieces around its extremities. In such cases we drop the selection rather than possibly make errors around the edges. Edits contained entirely within a selection usually don't disrupt it though.
  • Remote carets are not proactively cleaned up on disconnect and remain for up to 3 minutes if a user drops

Major limitation:

  • Hazel's engagement with the automerge CRDT framework is partial. In particular, certain concurrent edits may clobber another user's edits if edits are made within the same 'tile footprint'. This is a slightly subtle notion. One way to mentally approximate is is that concurrent edits can be made without issue inside different top definitions, but concurrently adding top-level definitions, or otherwise concurrently modifying the top level structure, may result in clobbering. Below I'll describe in detail the convex hull of situations where clobbering may occur. However, I have instituted logic to make many such edits compatible with each other, at the cost of making the class of problematic conflicting edits even harder to describe, and likely introducing subtle bugs. To a first approximation though, the only situations which are currently known to be problematic (in the sense of possibly causing inconsistency, or else certainly causing clobbering) are when two users simultaneously try to edit delimiters of a multi-delimiter form (say for example given let x = 1 i where one user appends and n at the same time as another backspaces the t. The only other situation which definitely causes clobbering are when two users edit the same token at the same time. There's no intrinsic complexity here; we'd just need to switch the token type to the automerge text CRDT; it's just not totally clear to me if that's desirable behavior for all token classes. It might be reasonable for a comment, but when editing a variable name perhaps clobbering is better? Unsure.

Appendix: Restructuring operations versus concurrent edits.

Note: The below is now outdated, after implementing the plan specified in automerge-granular-sync.md. However it is retained as an intuition pump for the impedance mismatch which has now been partially addressed. In particular, both of the examples at the end now work fine concurrently

  • We currently use the automerge CRDT at the level of the piece map; a map from UUIDs to flat syntactic forms, which reference other UUIDs as children. However, these flattened forms are flatter than one might suspect. Instead of containing a list of child UUIDs, they contain a list of lists of UUIDs. Each of the top level lists represents a child, and each of the child lists represents the tiles constituting that child.

For example, consider the following program:

let x = 1 + (2 * 3) in
let y = 4 in
x + y

This will have the following (simplified) flattened rep:

{
  X: Root([[A, B, C, D, E]]),
  A: Let([[F], [G, H, I]]),
  F: Atom("x"),
  G: Atom("1"),
  H: Atom("+"),
  I: Parens([[J, K, L]]),
  J: Atom("2"),
  K: Atom("*"),
  L: Atom("3"),
  B: Let([[M], [N]]),
  M: Atom("y"),
  N: Atom("4"),
  C: Atom("x"),
  D: Atom("+"),
  E: Atom("y"),
}

Our basic setup handles concurrent edits to individual entries in the above list, but concurrent edits to the same entry will result in clobbering. So, for example, any two atomic tokens can be concurrently edited. But, any addition to the root segment, for example adding a new let definition (say, for a new variable z) between the two existing one while another user adds a + z to the end of the program, will result in clobbering. Additionally, if a user tries to edit the pattern for the definition of x, say changing it to x, w, while another user edits the definition similarly, say changing 1 + (2 * 3) to 1 + (2 * 3), 4, these will get clobbered because, even though they are not edits to the same segment, they are nonetheless both edits to the list of lists for node A.

disconcision and others added 23 commits February 4, 2026 20:56
# Conflicts:
#	src/web/app/History.re
#	src/web/app/editors/code/CodeEditable.re
The module system merge from dev added new Sort.t variants (Mod, Sig, MPat).
Update FlatDoc.mli and PatchworkComm.re to handle them in JS conversion.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…at sorts

The previous commit manually added Sort variants to FlatDoc.mli, but the
source of truth is flatdoc.d.ts. Updated the .d.ts and regenerated via
ts2ocaml, then fixed all cascading tag renames in PatchworkComm.re.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move caret save/restore machinery into a general-purpose module with
a clean `transform(z, seg => seg)` API, decoupled from patchwork sync.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	src/web/app/editors/code/CodeEditable.re
- Add Structural(_) case to SyncReplace.should_send_state (new Action variant from dev)
- Update PatchworkUndoSync field access chain for Scratchpad record type change (was tuple)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
#	src/web/app/editors/Editors.re
…in tests

The IDB stub sets window=globalThis for Node.js, but without also setting
parent, PatchworkComm.is_in_iframe() returns true (parent undefined !== window).
This caused documentation_slides to be empty, making List.tl([]) crash in
Test_ReparseDocSlides.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts:
#	src/haz3lcore/zipper/action/Perform.re
#	src/web/app/Page.re
#	src/web/app/editors/code/CodeEditable.re
#	src/web/www/style.css
@disconcision
disconcision marked this pull request as draft April 20, 2026 09:01
disconcision and others added 6 commits April 27, 2026 16:54
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.

3 participants