Apps
Todo
# todo — a normal app with an app-owned persisted model
A small TUI app that demonstrates the recommended app-state persistence
pattern: define your own schema directory, load the last snapshot at
startup, and `[$model persist]` after each change.
Launch:
```sh
retro app deft/todo
retra app deft/todo
````
## Layout
```text
packages/todo/
├── package.dft
├── src/store.dft # headless load/save + pure state edits
├── src/todo.dft # Deftlet panel (draw + keys)
├── test/test_todo.dft
└── todo.scm/ # app-owned schema, generated by Schema Builder
├── types.dft
├── design.dft
├── persist.dft
├── model.dft
└── schema-meta.dft
````
`todo.scm/` defines two tables:
- `Todo` — `id`, `title`, `done`, `created`.
- `AppState` — a singleton (`id "app"`) holding the last filter, selected
todo id, and save time.
## State
State lives in `.deft/todo.db` (override with `TODO_DB`). On startup the app
calls `[$model load]`; a missing `_persist_meta_` is a normal first run.
The interface is composed with `deft/ui/compose` (`[div]`, `|> [children …]`,
`|> [props …]`) from the standard UI components: a `ui/multiline-text`
editor for new todos, a `ui/checkbox` per todo, and `ui/button` /
`ui/label` / `ui/row` / `ui/column` for actions and layout. There are no
dialogs in the input path.
After every mutation it rebuilds a fresh model snapshot and calls
`[$model persist]`. This is a snapshot replace, not an append: deleting a
todo in the UI removes its row on the next save.
## Keys
| Key | Action |
|-----|--------|
| type + `enter` | Add the current editor text as a todo |
| `Add` button | Add the current editor text as a todo |
| click checkbox | Toggle that todo done |
| `space` / `enter` | Toggle the selected todo (editor unfocused) |
| click `✕` / `delete` | Delete that / the selected todo |
| `All` / `Active` / `Done` | Filter buttons |
| `f` | Cycle filter: all / active / done |
| `↑` / `↓` | Move selection while the editor is unfocused |
| `/` / `tab` | Focus the multiline editor |
| `esc` | Leave the editor so list shortcuts work |
| `r` | Reload from disk |
| `m` | Migrate the DB to `todo.scm` after a schema change |
The filter and selected todo are persisted, so reopening the app restores
the same view.
## Schema changes
Edit/regenerate `todo.scm/` with the Schema Builder. The generated schema
version is checked by `[$model load]`; after a schema change press `m` to run
the generated `migrate`, or run it directly through `deft/persist`.
The schema descriptor is stored in `_persist_meta_`; it identifies the exact
version and layout of the persisted data. See the manual page
Persisting App State for the full walkthrough.
## Tests
```sh
deft test packages/todo/test/test_todo.dft
````