# Episode 06 --- Demo Assets Supporting `build-a-domain.md`. The domain built live in this episode is **todo** --- the simplest possible real domain plugin, implemented at `muse/plugins/todo/plugin.py` in the `muse` repo (started from `muse/plugins/scaffold`). ## Production note: this already existed before Episode 04 was recorded If you're reading this after watching episodes out of order and wondering why `muse/plugins/todo` (and even `muse/plugins/chess`, Episode 22's finale plugin) already existed in the `muse` repo before Episode 04 was recorded --- that's expected, not a mistake. Scripts for the whole season get written up front, and demo code/plugins get built and tested well ahead of the recording schedule, in whatever order is convenient --- not necessarily episode order. Recording is the thing that happens sequentially; script-writing and demo-building don't. Check this directory's presence and the top-level `README.md` status table before assuming something is missing or was added by mistake. ## The todo domain --- one file, one task per line, content-addressed Model: a single tracked file, `todo.txt`. Each task is addressed by its own content, not by line position: ```python def _task_address(task: str) -> str: digest = hashlib.sha256(task.encode("utf-8")).hexdigest()[:12] return f"{_TASK_FILE}::{digest}" ``` That one design choice --- **identity by content, not position** --- is what makes every other method fall out cleanly, and it's the idea every later, more complex domain (MIDI, code symbols, chess) builds on. ## How the six-method protocol maps onto it | Method | What it does for `todo` | |---|---| | **`snapshot`** | Reads `todo.txt` off disk, hashes its raw bytes as one blob. No per-task parsing here --- just "what does the file contain right now." | | **`diff`** | Loads both snapshots' task sets (splitting `todo.txt` into lines) and computes `target - base` (added) / `base - target` (removed) as **set difference**. Each added/removed task becomes its own `AddressedInsertOp` / `AddressedDeleteOp`, nested inside one outer `PatchOp` keyed on `todo.txt` (that outer key is what `checkout` actually restores from disk). | | **`merge`** | Three-way, but does no line-based patching: `merged = (base ∪ left ∪ right) − removed`, where `removed` is anything either side deleted relative to base. Because tasks are content-addressed set elements, not line positions, **two branches adding two different tasks can never conflict** --- there's no "line 5 vs line 5" collision to even detect. | | **`drift`** | `muse status` calls this: re-snapshot live state, diff it against the last commit, report whether anything changed. | | **`apply`** | A no-op --- the core engine already restores `todo.txt`'s bytes from the object store during `checkout`; there's no domain-specific reconstruction needed since the file is opaque bytes, not a structured format. | | **`schema`** | Declares `SetSchema(kind="set", element_type="task", identity="by_content")`, `merge_mode="three_way"`, zero sub-dimensions --- the minimum viable domain: no address-keyed merge extension, no CRDT, just the required core. | ## The contrast with Git this episode is built around Git would treat `todo.txt` as text and merge by line --- two people adding a task on the same line number is a conflict even if the tasks are unrelated. `todo` instead treats the file as an **unordered set of content-addressed elements**, so "did the same *task* change" is the only thing that can conflict --- and since there's no edit operation (only add/remove of whole tasks), that basically never happens. ## `make-todo-episode06-demo.sh` --- fully scripted, run it directly ```bash ./make-todo-episode06-demo.sh ``` Builds a disposable `todo-episode06/` repo: one task committed on `main`, two branches each adding a *different* task, both merged back with zero conflicts. Uses `muse-dev` rather than plain `muse` --- the todo domain ships on `muse`'s `dev` branch but hadn't gone out in a released tarball as of this recording. Swap `MUSE_BIN` to `muse` once a release includes it.