watershed Collaborative data structures for Gleam

← watershed / Patterns

Implementation patterns
from the examples.

Each entry connects a problem to the example code that addresses it. Browse by problem here, or follow the build guide in implementation order.

6 notes

Architecture & composition

Decide what the shell owns, what a component may touch, and which work must stay outside the app.

  1. Treat the server as an optional decorator

    A peer-to-peer app may use a relay for durability, but it should not need one to start.

    Clap counter · src/clap_counter_lustre.gleam · Step 01

  2. One shared core, two runtimes

    Keep the shared core portable by connecting to the same document from the BEAM and the browser.

    Dice CLI · src/dice_cli.gleam · Step 01

  3. Keep latency-critical loops out of the update path

    Let a real-time loop read a plain snapshot. Do not make it wait on the application.

    Drum machine · src/drum_machine_lustre/audio_ffi.mjs · Step 05

  4. Hand a rendering surface to FFI, and bootstrap on Connected

    Let an FFI module own the canvas pixels, and create shared channels only after the connection opens.

    Pixel canvas · src/pixel_canvas_lustre.gleam · Step 01

  5. Stamp the schema; refuse bad reads

    Fill and stamp a typed map before attaching it, so an incompatible reader gets an error.

    Scoreboard CLI · src/scoreboard_cli.gleam · Step 02

  6. Panels take a TypedMap, never a root

    Give a reusable component a typed map, whether that map is a root or a child. Keep document-wide effects in the shell.

    Nested app showcase · src/showcase_lustre.gleam · Step 01

6 notes

Conflicts & consistency

Two clients change the same state at once. Keep the UI honest while the shared structure settles the result.

  1. Propose on release, render the pending signoff

    Send one consensus proposal per gesture, then show whose approval is still missing.

    Drum machine · src/drum_machine_lustre.gleam · Step 04

  2. Fallible edits render; never assert on a mutation

    Handle every index-based edit as fallible. A peer may change the list between render and click.

    Collaborative playlist · src/playlist_lustre/component.gleam · Step 02

  3. When a move is not atomic, crown one channel authoritative

    A move across channels is not atomic. Choose one source of truth and reconcile the rest while rendering.

    Retro board (full) · src/retro_board_lustre/board.gleam · Step 02

  4. Seed idempotently with Claims

    Let every client seed the same initial values through first-writer-wins claims.

    Collaborative Sudoku · src/sudoku_lustre/component.gleam · Step 01

  5. Anchors, not offsets

    Store an anchor instead of a text offset, then resolve its current position after each edit.

    Shared text editor · src/text_lustre/component.gleam · Step 02

  6. Show writes that have not settled

    When writes are not optimistic, show them as pending until the confirming event arrives.

    Tournament bracket · src/tournament_bracket_lustre.gleam · Step 04

2 notes

Presence & coordination

Some facts belong to the session, not the document: who's here, who drives, and what they need to tell each other.

  1. The minimal presence idiom

    Declare one presence effect, use one typed payload, and remove the local session before the roster enters your model.

    Retro board (tutorial) · src/retro_tutorial_lustre.gleam · Step 05

  2. Ride an application protocol on ripples

    Send short-lived coordination over ripples, not through a document channel.

    Grocery triptych · src/grocery_triptych_lustre/scenario_protocol.gleam · Step 05

3 notes

Testing & diagnostics

Reproduce the failures that matter, and read the runtime's own diagnostics before you guess at a sync bug.

  1. Sample diagnostics on every event

    Put the runtime's diagnostics on screen before you debug synchronization.

    Collaborative dice · src/dice_lustre.gleam · Step 01

  2. Extract pure modules; test without a server

    Move decisions into pure modules so most tests need no document, sluice, or server.

    Grocery triptych · src/grocery_triptych_lustre/refresh_guard.gleam · Step 06

  3. Test client death deterministically

    Test a client dying mid-job with an in-process disconnect that produces the same leave event as the server.

    Work queue · test/queue_semantics_test.gleam · Step 06