Keyed state that picks a winner, keeps an edit, or grows into a tree.
Maps are where most collaborative apps keep their state, and where the choice of conflict model is most visible. watershed's maps span that choice.
SharedMap resolves JSON and encoded-handle values by server order, following the last-write-wins design used by Fluid Framework. LWWMap lets each string key's timestamp outrank a later server SN, and keeps removals as tombstones. OR-map keeps causal dots per entry so a concurrent write survives a delete. SharedDirectory makes SharedMap recursive, with hierarchical identity that survives concurrent creation and delete-then-recreate.
No. 01
SharedMap
DDS
map_kernel
A shared key/value map where the most recent write to a key wins.
watershed's flagship DDS follows Fluid Framework's SharedMap kernel design. Keys map to JSON values, including supported encoded handles. Each set is sequenced, and for a given key the write with the highest sequence number wins. Its inner set, delete, and clear payloads match the @fluidframework/map operation encoding.
Concurrent writes resolve deterministically by server order rather than by a merge function. A local write renders immediately; the ack promotes it, and if a higher-SN write to the same key arrives it replaces the value. Reference-generated corpus tests cover map state, events, and convergence. Attach and summary formats remain watershed's own, so this does not imply drop-in Fluid container interoperability.
Choose SharedMap for server-ordered JSON or handle state. Its summary keeps confirmed entries in insertion order, with no CRDT tombstones. LWWMap instead keeps strings, per-key timestamps, and tombstones, and reads keys in sorted order. Both can overwrite a concurrent losing value; neither preserves the disagreement for you.
Best for
Shared application state and settings objects edited by many clients
Learning and testing server-ordered last-write-wins collaboration
Key-value collaboration where a clear last-writer-wins rule is acceptable
Merge rule
for each key, the most recent write wins, decided by server order
Optimistic behavior
your writes show instantly, then lock in once the server confirms them
Summary shape
confirmed entries reload with their keys and insertion order intact
Model
Distributed data structure: converges by server order
No. 02
LWWMap
CRDT
lww_map_kernel
Shared string settings where a newer timestamp can beat a later server write.
Try racing two gate settings. A writes open with a newer timestamp, then B's closed gets the later server sequence number. LWWMap keeps open. SharedMap would keep closed: it follows server order, while LWWMap follows each key's timestamp through the sequenced runtime or the existing JavaScript CRDT mesh and relay paths.
At equal time, the greater writer ID wins: B's closed beats A's open even though the values point the other way alphabetically. A tombstone still beats a value at the same time. That's different from OR-map's observed-remove, add-wins rule. Removal isn't unconditional remove-wins: an older write can't resurrect a tombstone, but a newer write can restore the key.
The runtime supplies wall-clock time; the kernel advances it beyond the timestamp already observed or issued for that key, including pending edits and tombstones. Clock skew can still favor the writer whose clock is ahead. Last doesn't promise the most recent human action.
You get string keys and string values, with set and remove. An empty string is a value. Unlike SharedMap's JSON values and supported encoded handles, LWWMap has no nested JSON or handle traversal, no clear, and no tombstone-pruning API. SharedMap reloads insertion order; LWWMap retains timestamped removals and returns visible entries sorted by key.
Choose it when offline shared string settings need one deterministic winner and you can afford to lose another concurrent value. SharedMap also loses concurrent losing values. Neither is an MV register or a field-wise merge; use MvRegister when someone needs to read and resolve the disagreement.
Best for
Offline shared string settings with an acceptable deterministic single winner
Per-key labels and modes that reconcile across the JavaScript mesh or relay
String state where timestamps, rather than server sequence, should choose the winner
Merge rule
the greater per-key timestamp wins; at equal time, a tombstone beats a value and writer ID breaks a set/set tie
Optimistic behavior
your set or remove appears in magenta while confirmed entries stay in ink
Summary shape
values, timestamps, and retained tombstones reload together; visible entries read in sorted-key order
Model
Conflict-free replicated data type: converges by merge
No. 03
OrMap
CRDT
or_map_kernel
A map where editing a key and deleting it at once won't lose the edit.
An OR-map applies the OR-set's observed-remove semantics to keyed entries. Each entry records causal dots; removing a key only tombstones the dots it has observed, so a concurrent write to the same key survives a delete (add-wins).
Values can themselves be additive tallies, which turns the map into a keyed CRDT ledger. In the demo it appears as a stockpile ledger where striking a row hides it and re-opening submits a +0 delta to surface the retained tally.
Switch the demo to string sets and each document gets a checklist. A adds draft while B adds reviewed: both members survive. Put those edits in SharedMap as separate JSON arrays instead and, in the same server order, only B's later whole array remains. A channel has one fixed, homogeneous mode: tallies, LWW registers, MV registers, or sets of strings. Switching between tallies and sets starts fresh OR-map replicas.
In set mode, removing a member clears its observed add tags; removing a key also clears its observed members. An unseen concurrent addition survives. Removing the last member keeps a present empty set, while removing an absent member creates nothing. Add a removed key again and only its new members appear, even after an old delta is replayed. Causal metadata stays behind to enforce those removals; this is different from the tally mode's retained ledger.
The ledger / string sets and MV registers views run separate instances of this same structure. Switching to MV registers preserves the ledger or checklist you've been editing.
In MV-register mode, race two revisions under gate-mode and you'll see both answers. The OR-map keeps the key alive through remove/write concurrency; the MV register keeps alternatives inside that key. Resolve observed combines the selected client's alternatives in an ordinary write. It replaces only those observed revisions, so an unseen offline writer can still bring another answer.
Best for
Keyed ledgers edited offline or concurrently (stockpiles, inventories, per-key counters)
Per-document string labels and checklists where concurrent member edits must merge
Maps where deleting and concurrently updating a key must not lose the update
A CRDT-correct alternative to SharedMap when last-write-wins would drop data
Merge rule
delete a key while someone else edits it, and the edit survives (the write wins)
Optimistic behavior
edits and removals show locally at once, with confirmed set members alongside
Summary shape
entries remember their edit history, not just the current value
Model
Conflict-free replicated data type: converges by merge
No. 04
SharedDirectory
DDS
directory_kernel
SharedMap with folders: nested groups of keys, each keeping its own identity.
SharedDirectory is SharedMap made recursive, modeled after Fluid Framework's SharedDirectory design. Every folder node has its own last-write-wins key/value store plus a named set of child folders, addressed by absolute path (/surveys, /surveys/intake). Storage resolves exactly like SharedMap: each set is sequenced, highest sequence number wins per key.
The hard part is hierarchical identity, not storage. A folder can be created by two clients at the same instant, deleted, and recreated under the same path, and every replica must still agree on which folder is which. The kernel models that identity explicitly from creator ids, create-sequence data, and each op's reference sequence number. A stale op targeting an old instance of a path is ignored; concurrent same-name creates merge into a single folder. A flat map cannot express that.
Best for
Nested, collaboratively-edited state: document trees, project/site hierarchies, scene graphs
Studying hierarchical identity and server-ordered folder collaboration
Anywhere a flat map's keys want structure (folders of readings, grouped settings)
Merge rule
like SharedMap, but with folders; each folder keeps its identity even if it's deleted and remade
Optimistic behavior
folder and key edits show immediately until the server confirms them
Summary shape
the whole folder tree reloads intact
Model
Distributed data structure: converges by server order
Try an edit on any client, race concurrent writes, or switch on
jitter. Pending edits print in magenta;
once the sequencer stamps an SN, every replica lands the same
ink state.
1 · local edit prints in magenta
2 · the sequencer stamps it with an SN
3 · every client lands the same ink state
Merge rule: last write wins. Race two writes to the
same key and the op the sequencer stamps later overwrites the earlier
one, identically on every replica.
Compare LWWMap: its per-key timestamp can beat a later server SN.
Merge rule: timestamp wins per key. A later SN needn't
win: race A's newer “open” against B's later-sequenced “closed”.
At equal time, B's writer ID makes “closed” beat A's “open”; a tombstone
beats either. Unlike SharedMap's server order, arrival
order doesn't choose the value. Both maps discard losing concurrent values.
This is an in-page sequenced rig, not a live mesh.
The runtime supplies wall-clock time and advances it past the key's
observed timestamp. A fast clock can still win over a later human edit.
OR-map's concurrent add survives an observed remove; LWWMap removal wins
only at equal or greater time. LWWMap and LWWRegister both use author
identity to settle equal-time writes.
Reset reloads this survey and discards its queued edits. It's not a
clear or tombstone-pruning API.
Merge rule: increments commute. Race two increments
and both apply. The counter converges on the sum; nothing is
overwritten.
Merge rule: grow-only max per replica. Each client owns
one monotonically increasing tally. Race two inspections or deliver a
delta twice: replicas converge by keeping the maximum count seen for
each author.
Merge rule: deltas join a lattice. Every update
travels as a CRDT delta; merging is commutative and idempotent. Race
it, resend it, deliver it twice: it counts exactly once, and the
balance converges on fill − cut.
Merge rule: timestamp wins; author breaks a tie. Race two
equal-time revisions and every replica picks the same author, regardless
of which delta arrives first.
Merge rule: keep concurrent alternatives. Two revisions
can converge without agreeing on one answer. After both arrive, write
a combined revision to replace the alternatives you've seen.
Merge rule: first writer wins. A claim commits only
if the slot is still unclaimed when the sequencer stamps it (or its
reference SN matches the holder’s exactly). Nothing prints
optimistically: a filed claim is invisible, even to you, until it
round-trips as won or lost.
These are separate OR-map instances. Switching views keeps their edits.
Merge rule: add-wins, observed-remove. A tally strike
hides the stockpile; re-opening brings its ledger back. Race a strike
against a concurrent delivery and the row survives with every logged yard.
Merge the members, not a replacement array. A and B can
add "draft" and "reviewed" to one document and keep both. Write those as
separate JSON arrays in SharedMap, in the same stream order, and the later
whole value replaces the earlier one: ["reviewed"].
Remove a member to clear its observed tags; remove a key to clear the
observed members as well. An unseen addition survives either race.
Removing the last member leaves an empty set, not a missing key.
This is an in-page sequenced demonstration, not a live mesh.
A production channel keeps one homogeneous mode: tallies, LWW
registers, MV registers, or sets of strings. Switching here starts fresh OR-map
replicas only; it doesn't convert an existing channel.
Each scenario starts fresh OR-map replicas, then sends real edits
through the stream. Setup edits wait for all three clients before
the race. Try your own edits afterward, or cut B's link.
Ready to run.
Members print in sorted order. Quotes distinguish the empty string
from an empty set. Removed tags and key history stay in the causal
metadata, so re-delivering an old add can't revive what you removed.
Unlike the tally ledger, a removed set key starts with new members
when you add it again.
Keep the key. Read the disagreement. The OR-map decides
whether a key survives: remove it while another client writes, and that
unseen write keeps the key alive. The MV register inside the key keeps
concurrent alternatives. Neither server order nor a timestamp picks one
for you.
Race two gate revisions, then try Resolve observed.
It combines the selected key's alternatives in one ordinary write,
replacing only what that client has seen. Cut B's link and write there
first: A's resolution can't erase B's unseen revision.
Each OR-map has one homogeneous leaf mode: all tallies,
all LWW registers, all MV registers, or all string sets. This view and the stockpile ledger
are separate instances of the same structure, not mixed values in one map.
Reset reloads this instance's survey and discards its queued edits.
Merge rule: add-wins, observed-remove. Clearing a marker
removes only the add tags this replica had observed. Race a clear against
a concurrent re-mark and the marker remains present because the fresh tag
was unseen by the clear.
Merge rule: grow-only union. Marking a benchmark is a
permanent fact. Race two marks or deliver one twice: the registry only
grows, and duplicate deltas are absorbed.
Merge rule: tombstone wins. A retired marker can never
become active again. Race a re-place against a retire: both replicas keep
the tombstone, and duplicate retire deltas are absorbed.
Merge rule: atomic plus retained versions. A write that
knew the current atomic sequence wins the linearizable slot. Concurrent
losers still append a version, so LWW reads can intentionally diverge from
atomic reads.
Merge rule: FIFO by sequence. Adds enter the queue in
server order. Acquires remove the front item when their op sequences; a
racing acquire may come back empty because the earlier SN already took it.
Merge rule: one assignee, FIFO waiters. Volunteers join
a per-task queue in sequenced order. The first connected volunteer owns the
task; abandon promotes the next waiter, and complete clears the queue.
Merge rule: quorum acceptance. A set first prints as
pending with a frozen signoff list. It becomes accepted only after every
connected client signs off, or leaves drain the list.
Client A
0 pending
Shared map replica on Client A
mill-race
24
kettle-run
61
low-ford
42
sandbags-placed
earthwork-balance · yd³
fill Σ
74
cut Σ
30
cutfill
inspection-count
A Σ
0
B Σ
0
C Σ
0
Gate settings
Confirmed entries, sorted by key
Local view
Confirmed timestamps and tombstones (null)
An empty string is still a value. Removal retains a
timestamped tombstone; a newer ordinary edit can restore the key.
Field note
Confirmed value
Local view
Race uses one timestamp for both writers. The author ID—not arrival order—breaks the tie.
Revision slate
Confirmed alternatives
Local view
Resolve writes "raise crest + arm pump". Any new revision replaces only the history this client has seen.
Gate revisions
Confirmed entries, sorted by key and value
Local view
Sorted doesn't mean preferred. Equal text from two
concurrent writers still counts as two alternatives; an empty
string is a revision, not a removal.
Claims replica on Client A
north-levee
spillway-gate
pump-house
OR-map stockpile ledger replica on Client A
spoil-north
borrow-pit-7
wash-fill
Document checklist
inspection-brief
ConfirmedLocal view
spillway-plan
ConfirmedLocal view
pump-watch
ConfirmedLocal view
OR-set field marker roster replica on Client A
north-stake
sluice-tag
borrow-flag
G-set permanent benchmark registry replica on Client A
BM-17
BM-22
BM-31
2P-set retired marker ledger replica on Client A
stake-3
gate-pin
silt-flag
RegisterCollection replica on Client A
register
atomic
LWW
revise
north-bench
gate-setpoint
pump-mode
OrderedCollection replica on Client A
sheet
state
op
queue
held jobs
PactMap replica on Client A
pact
accepted
pending
op
datum-grid
gate-policy
inspection-window
TaskManager replica on Client A
task
assigned
waiters
op
sluice-inspection
pump-watch
crest-walk
Client B
0 pending
link cut — ops park locally until the link is restored
Shared map replica on Client B
mill-race
24
kettle-run
61
low-ford
42
sandbags-placed
earthwork-balance · yd³
fill Σ
74
cut Σ
30
cutfill
inspection-count
A Σ
0
B Σ
0
C Σ
0
Gate settings
Confirmed entries, sorted by key
Local view
Confirmed timestamps and tombstones (null)
An empty string is still a value. Removal retains a
timestamped tombstone; a newer ordinary edit can restore the key.
Field note
Confirmed value
Local view
Race uses one timestamp for both writers. The author ID—not arrival order—breaks the tie.
Revision slate
Confirmed alternatives
Local view
Resolve writes "raise crest + arm pump". Any new revision replaces only the history this client has seen.
Gate revisions
Confirmed entries, sorted by key and value
Local view
Sorted doesn't mean preferred. Equal text from two
concurrent writers still counts as two alternatives; an empty
string is a revision, not a removal.
Claims replica on Client B
north-levee
spillway-gate
pump-house
OR-map stockpile ledger replica on Client B
spoil-north
borrow-pit-7
wash-fill
Document checklist
inspection-brief
ConfirmedLocal view
spillway-plan
ConfirmedLocal view
pump-watch
ConfirmedLocal view
OR-set field marker roster replica on Client B
north-stake
sluice-tag
borrow-flag
G-set permanent benchmark registry replica on Client B
BM-17
BM-22
BM-31
2P-set retired marker ledger replica on Client B
stake-3
gate-pin
silt-flag
RegisterCollection replica on Client B
register
atomic
LWW
revise
north-bench
gate-setpoint
pump-mode
OrderedCollection replica on Client B
sheet
state
op
queue
held jobs
PactMap replica on Client B
pact
accepted
pending
op
datum-grid
gate-policy
inspection-window
TaskManager replica on Client B
task
assigned
waiters
op
sluice-inspection
pump-watch
crest-walk
Client C
0 pending
Shared map replica on Client C
mill-race
24
kettle-run
61
low-ford
42
sandbags-placed
earthwork-balance · yd³
fill Σ
74
cut Σ
30
cutfill
inspection-count
A Σ
0
B Σ
0
C Σ
0
Gate settings
Confirmed entries, sorted by key
Local view
Confirmed timestamps and tombstones (null)
An empty string is still a value. Removal retains a
timestamped tombstone; a newer ordinary edit can restore the key.
Field note
Confirmed value
Local view
Race uses one timestamp for both writers. The author ID—not arrival order—breaks the tie.
Revision slate
Confirmed alternatives
Local view
Resolve writes "raise crest + arm pump". Any new revision replaces only the history this client has seen.
Gate revisions
Confirmed entries, sorted by key and value
Local view
Sorted doesn't mean preferred. Equal text from two
concurrent writers still counts as two alternatives; an empty
string is a revision, not a removal.
Claims replica on Client C
north-levee
spillway-gate
pump-house
OR-map stockpile ledger replica on Client C
spoil-north
borrow-pit-7
wash-fill
Document checklist
inspection-brief
ConfirmedLocal view
spillway-plan
ConfirmedLocal view
pump-watch
ConfirmedLocal view
OR-set field marker roster replica on Client C
north-stake
sluice-tag
borrow-flag
G-set permanent benchmark registry replica on Client C
BM-17
BM-22
BM-31
2P-set retired marker ledger replica on Client C
stake-3
gate-pin
silt-flag
RegisterCollection replica on Client C
register
atomic
LWW
revise
north-bench
gate-setpoint
pump-mode
OrderedCollection replica on Client C
sheet
state
op
queue
held jobs
PactMap replica on Client C
pact
accepted
pending
op
datum-grid
gate-policy
inspection-window
TaskManager replica on Client C
task
assigned
waiters
op
sluice-inspection
pump-watch
crest-walk
Sequencer
Loading
booting watershed kernels…
Jitter can change simulated arrival order; animation speed changes
playback only. Each moving request shows its sampled hop latency. “Ops in
flight” counts every hop still travelling: one
client → sequencer leg, then one
sequencer → replica leg per client.
A246142
B246142
C246142
The live demo couldn't start: it runs watershed's runtime counter channel
and kernels as compiled JavaScript modules, and this browser didn't load
them. The rest of the page works fine without it. Reload the page to retry.
Magenta indicates revisions not yet field-checkedConvergence by server sequencing — assumptions on the models sheettylerbutler.com