Step 5: Make it a list
A created-at timestamp gives every peer the same order, for free — and a delete button, to match.
Right now new notes land in whatever order the map happens to iterate — not wrong, exactly,
but not a list either. The fix is data, not coordination: store a creation time on every
note, and let each peer compute the same order from it. This step touches both halves of
the pane — the // src/codec.ts and // src/tool.tsx comment lines mark where each file
begins; on disk they're two separate files. (The merged import block at the top splits the
same way: defineStateCodec and field belong to src/codec.ts; everything else —
including module-level helpers like newId — belongs to src/tool.tsx.)
First, in src/codec.ts, give Note a creation time:
export type Note = {
text: string
/** Creation time — a stable sort key every peer agrees on. */
at: number
}Then, in src/tool.tsx, sort the entries by it inside Board, and stamp it when a note
is created:
const notes = useMemo(
() => Object.entries(data.notes).sort((a, b) => a[1].at - b[1].at),
[data.notes],
)
const addNote = () =>
actions.map("notes").set(newId(), { text: "", at: Date.now() })The useMemo isn't ceremony: sorting builds a brand-new array, and doing it inline would
hand React a fresh array on every render. Memoized on data.notes, it only recomputes when
a note actually changes.
Finally — still in src/tool.tsx — add a Delete button next to each textarea, inside the
notes.map(…) loop, wired to actions.map("notes").delete(id). That's a new method on the
hand-written actions type too: delete(key: string): void joins set in what
map(name: "notes") returns — the answer shows the full type.
Edit and the example re-runs. Tab indents; press Escape to leave the editor.
You should see: add three or four notes, delete one from the middle — the rest keep their
order. Every peer computes that order from the same at, so nobody has to agree on it over
the network; it's just a read.
Why not field.list? A list merges by position — great when the sequence itself is the
meaning (a timeline, a set of steps), but two people inserting at the same index at the same
moment land in a defined-but-arbitrary order. A field.map keyed by id has no positions to
disagree about; sorting by at gets you order back as a plain read. The Hello World
starter kit has the full case for it.