Ana
syncing… favorites| ★ | Name |
|---|
Ben
syncing… favorites| ★ | Name |
|---|
What you are looking at
Ana and Ben are two complete, independent clients on one page. Each panel is its own data-query with its own store, its own optimistic state, and its own in-flight writes. They share one canonical table, and every change is shared: star a row, or click a name to rename it (Enter saves, Esc cancels), and the other user gets it.
Click a star on Ana's side. It flips instantly, her panel shows syncing… while the write is in flight, and a beat after the server confirms, Ben's panel hears about the change and refetches. Start a rename and tick the same row's star while the rename is still syncing; the two writes own their own fields and settle independently.
The whole declaration
wildflower.query('employees', {
from: () => server.list(),
key: 'id',
persist: true, // the last confirmed rows survive a reload
to: item => server.save(item)
});
// the star's click handler
wildflower.getQuery('employees').write({ id, favorite: true });
The write lands on screen immediately and field-merges only the field it names. The server's answer confirms it, or rolls that one field back if it says no. A refetch arriving while the write is still in flight never overwrites the written cell, because in-flight writes own their fields until they settle.
The persist line is why a reload paints both tables already filled. The last confirmed rows are kept in your browser's storage, shown at once, and revalidated against the server behind the paint. Only confirmed server truth is ever stored; an optimistic value that has not settled never touches disk.
Undo is another write
Each panel's My actions list is that user's own path through the shared table, newest first, with a pointer marking where they stand. Undo does not rewind state; it issues the inverse intent through the same write(), so Ana's undo propagates to Ben like any other change. Because writes are field-level, undoing your star never disturbs a rename someone else made to the same row.
When another user has already changed a field you are about to undo, that entry is tagged and the pointer steps past it to your next action. Undo something, then watch the dimmed entry above the pointer: that is your redo, and making any new change clears it.
Where the server is
In this demo the canonical table lives in your browser's storage and a channel message stands in for whatever tells clients about changes: a poll, a server-sent event, a webhook-driven push. In production, from and to point at your API instead, and nothing else changes. Open this page in another window and it joins the same table as a third user.
Why two queries do not share a cache
Ana and Ben deliberately hold independent copies. A write through Ana's client never touches Ben's copy until his own sync delivers it, so one view can never corrupt another mid-write. When two views must agree immediately, the pattern is one line. wildflower.invalidateQueries() takes the sibling names and refetches them together once the write settles; that is what the channel handler here does, and it is the same call you would use against a real API.