diff --git a/CLAUDE.md b/CLAUDE.md
index 7309784..ebfc2ac 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -33,16 +33,26 @@ Current decisions at a glance (each backed by an ADR):
simple to advanced (ADR-0003). No other sigils.
- **Project format:** `project.yaml` + `data/
.csv` +
`history.log`; `playground.db` is a derived artifact (ADR-0004,
- amended by ADR-0015).
- *(Format defined; runtime semantics defined in ADR-0015; track 2
- implementation pending.)*
+ amended by ADR-0015). Implemented through Iteration 4 +
+ cleanup; export/import (Iter 5) and migration framework /
+ --resume / persistent input history (Iter 6) pending.
- **Project storage runtime:** every command persists through to
db + yaml + csv + history.log in one execution context, gated
by the combined db persistence logic; commit-db-last ordering
for crash-recoverable state; existence-only load + explicit
`rebuild` command; in-TUI list-with-browse load picker; lock
file for single-instance enforcement; persistence failures
- are fatal (banner + quit) (ADR-0015).
+ are fatal (banner + quit) (ADR-0015). Empty tables produce no
+ CSV. CSV reader hand-rolled to preserve NULL-vs-empty
+ distinction. Temp projects are marked by a literal `[temp]`
+ segment in their directory name (validate_user_name rejects
+ brackets, so user-named projects can never collide).
+- **Temp project cleanup:** unmodified empty temps
+ (kind=Temp, empty schema, empty data dir) are auto-deleted
+ on switch and on quit by `safely_delete_temp_project`,
+ which stacks containment / symlink-rejection /
+ `[temp]`-marker / contents-allowlist guards. Anything
+ unexpected → refuse, never delete the wrong thing.
- **Types:** `text`, `int`, `real`, `decimal`, `bool`, `date`,
`datetime`, `blob`, `serial`, `shortid`. Compound primary keys
supported. No real UUIDs (ADR-0005). FK column type
@@ -155,11 +165,11 @@ Key invariants in the code:
These are explicitly tracked (mostly in `requirements.md`) but
not yet implemented:
-- **Project storage** (track 2 / P-series, F-series, P-NAME-*):
- file-backed projects, save/load/new/rebuild/export/import,
- persistent history, project-name display + prettifier. Format
- is fully designed in ADR-0004 (with amendments) and runtime
- semantics in ADR-0015; implementation is the next iteration.
+- **Project storage** (track 2): largely implemented through
+ Iteration 4 + cleanup pass + safety hardening (Iterations
+ 1–4 of ADR-0015). Pending pieces: `export` / `import` (Iter
+ 5), `--resume` + persistent input history hydration +
+ migration framework scaffold (Iter 6).
- **Complex WHERE expressions** (C5a): AND/OR/comparison/LIKE
in UPDATE/DELETE/show-data filters. The bridge from DSL
fluency to real SQL.
diff --git a/docs/handoff/20260508-handoff-2.md b/docs/handoff/20260508-handoff-2.md
new file mode 100644
index 0000000..5ce42e3
--- /dev/null
+++ b/docs/handoff/20260508-handoff-2.md
@@ -0,0 +1,456 @@
+# Session handoff — 2026-05-08 (2)
+
+This is the second handover. The first session designed and
+shipped track 2's project storage end-to-end: the design ADR
+(0015), file-backed projects, per-command persistence to YAML
++ CSV + history.log, rebuild-from-text on missing `.db`,
+explicit `rebuild` / `save` / `save as` / `new` / `load`
+commands, `--help` / in-app `help`, and a hardened cleanup of
+unmodified temp projects. Iterations 5 and 6 of track 2 are
+pending; the next session can pick up from this file +
+`CLAUDE.md` + the linked ADRs.
+
+## State at handoff
+
+**Branch:** `main`. Working tree clean. The track-2 commits
+since the previous handoff:
+
+```
+58a964d Harden temp-project cleanup with stacked safety guards
+b7addd6 Cleanup pass: --help, in-app help, post-rebuild message,
+ unmodified-temp cleanup
+f219827 Iteration 4b: save / save as / new / load with project
+ switching
+ba93d3c Iteration 4a: rebuild command with confirmation modal
+f0fc063 Iteration 3: existence-only load + rebuild from text on
+ missing .db
+5410075 Persistence: empty table -> no CSV (per Iteration 2
+ follow-up)
+5c076f6 Iteration 2: per-command write-through to project.yaml,
+ CSVs, history.log
+601d3b6 Iteration 1: file-backed projects with auto-named temps,
+ lock file, and L1 CLI
+4fca862 Project storage runtime: ADR-0015 + ADR-0004/0007
+ amendments
+```
+
+**Tests:** 345 passing (272 lib + 73 across six integration
+files), 0 failing, 0 skipped.
+**Clippy:** clean with `nursery` lints enabled.
+**Release build:** ~6.3MB single binary (up from 5MB at the
+previous handoff; the increase is `serde_yml` +
+`serde` derive + `sysinfo` + `directories` + `csv` +
+`base64` + `gethostname`).
+
+The user's terminal is a real TTY; the TUI runs cleanly there
+but cannot be exercised from a non-TTY environment. `cargo
+test` covers everything that doesn't require a real terminal.
+
+## What's implemented (delta vs. previous handoff)
+
+The previous handoff covered: TUI shell, in-memory SQLite,
+DSL grammar, type system, INSERT/UPDATE/DELETE, auto-show.
+All of that is still in place.
+
+**On-disk projects (Iterations 1–3):**
+- Auto-named temp project on startup under
+ `/projects/`. OS-standard data root resolved via
+ the `directories` crate (Linux / macOS / Windows); overridden
+ by `--data-dir`.
+- Naming pattern: `-[temp]---`
+ with a built-in 161-word list. The literal `[temp]` segment
+ is what marks a directory as a temp project — brackets are
+ rejected by `validate_user_name` so user-named projects can
+ never collide.
+- Per-command write-through to `project.yaml`,
+ `data/
.csv`, and `history.log`. Atomic per-file
+ write-tmp + fsync + rename. Commit-db-last ordering inside
+ the SQLite transaction so a text-write failure rolls back
+ the db and leaves disk state recoverable.
+- `is_temp` is dir-name-based via the `[temp]` segment — fast
+ enough for the load picker without a YAML parse per
+ project.
+- Empty tables produce no CSV (a header-only file would
+ carry no information YAML doesn't already record). The
+ rule is enforced by `Persistence::write_table_data`,
+ which delegates to `delete_table_data` on an empty
+ snapshot.
+- Existence-only load on startup. If `playground.db` is
+ missing, the runtime rebuilds from `project.yaml` +
+ `data/` before the event loop starts; the result is
+ surfaced as a system message in the output panel rather
+ than a silent reconstruction.
+- CSV reader is hand-rolled to preserve the NULL-vs-empty
+ distinction (the `csv` crate doesn't expose whether a
+ field was syntactically quoted).
+
+**Lifecycle commands (Iteration 4):**
+- `rebuild` — confirmation modal with a summary ("3 tables
+ and 47 rows will be reconstructed; the existing
+ playground.db will be replaced"). Y/N/Esc. The worker's
+ `do_rebuild_from_text` wipes existing schema + metadata
+ before reloading from text, so it works on populated as
+ well as empty databases.
+- `save` — for a temp project, opens a path-entry modal
+ (acts as save as). For a named project, prints
+ "already auto-saved; use `save as` to copy to a different
+ location".
+- `save as` — always prompts for a target. Relative names
+ resolve under `/projects/`; absolute paths used
+ as-is. `copy_project` is a recursive copy that excludes
+ the per-process lock file (a fresh one is acquired on
+ open) and preserves everything else including
+ `playground.db`.
+- `new` — closes current project, creates a fresh
+ auto-named temp, switches.
+- `load` — opens an in-TUI picker. List mode shows projects
+ in the active data root sorted newest-first by
+ `project.yaml` mtime, with `[TEMP]` prefixes for temp
+ projects. Arrow keys navigate; Enter loads; `b` switches
+ to a path-entry sub-mode for projects elsewhere on disk;
+ Esc cancels. Empty data root jumps straight to path
+ entry.
+- Modal infrastructure: `App.modal: Option` +
+ per-modal key routing; renderer draws a centred overlay.
+
+**Project switching at runtime:**
+- The runtime owns a `Session` with `Option` +
+ `Option` + `data_root`. `perform_switch` handles
+ Load / SaveAs / NewTemp uniformly. The `take()` pattern
+ drops the old project (releasing its lock) before opening
+ the new one — required for the "load my own current
+ project" case.
+
+**CLI / app polish:**
+- `--help` / `-h` prints a usage banner (options +
+ app-level commands + DSL grammar reference) and exits.
+ Parse errors also print the banner.
+- `help` in-app command notes the same listing to the
+ output panel — a stand-in for the H3 help system that
+ stays in sync with what's actually wired up.
+- `[TEMP] ` prefix in the bottom status bar when the
+ current project is temp.
+
+**Unmodified temp cleanup (with stacked safety guards):**
+- On project switch and on quit, if the current project is
+ an unmodified temp (kind=Temp, `project.yaml` parses with
+ empty `tables` and `relationships`, AND `data/` is empty),
+ it is deleted.
+- Deletion is gated by `safely_delete_temp_project` which
+ refuses unless ALL of: path is not a symlink; path is a
+ directory; canonical path is under
+ `/projects/`; basename contains the
+ literal `[temp]` segment; direct children are exclusively
+ well-known project artefacts (project.yaml, data/,
+ history.log, playground.db, .gitignore, lock file) plus
+ migration `.bak` files and atomic-write `.tmp` files.
+ Anything unexpected → refuse; never delete the wrong
+ thing.
+- 7 dedicated tests cover each guard, including a `#[cfg(unix)]`
+ symlink-rejection test.
+
+**Persistence module (`src/persistence/`):**
+- `mod.rs` — `Persistence` handle (project path), atomic
+ write primitive, public types
+ (`SchemaSnapshot` / `TableSnapshot` / `CellValue` etc.).
+- `yaml.rs` — hand-rolled writer, `serde_yml`-backed
+ reader. Emits `project.created_at` (writer) and parses
+ it (reader). Action keywords (no_action, restrict,
+ set_null, cascade — note: `set_default` is still
+ unsupported per ADR-0014).
+- `csv_io.rs` — hand-rolled writer + reader. Per-type
+ encoding from ADR-0015 §4. Hand-rolled because the `csv`
+ crate strips the was-quoted bit we need for NULL-vs-empty
+ distinction. Bool stored as `INTEGER` in SQLite, written
+ as `true`/`false` in CSV.
+- `history.rs` — append-only `history.log` writer. Format:
+ `|ok|` with `\n` and `\r`
+ escaped inside the source.
+
+## ADR index (read these before touching the related areas)
+
+```
+0000 Record architecture decisions (process)
+0001 Language and TUI framework (Rust + Ratatui)
+0002 Database engine (SQLite STRICT)
+0003 Input modes and command dispatch
+0004 Project file format
+ — amended by 0015 (derived-artifact framing,
+ rebuild-with-confirmation semantics)
+0005 Column type vocabulary (ten types)
+0006 Undo snapshots and replay log (deferred)
+0007 Sharing and export
+ — amended by 0015 (history.log not in export zip,
+ but ALSO not in .gitignore — user decides)
+0008 Testing approach (four tiers)
+0009 DSL command syntax conventions
+0010 Database access via worker thread
+0011 FK column type compatibility
+0012 Internal metadata for user-facing column types
+0013 Relationships, naming, and rebuild-table strategy
+0014 Data operations, value literals, and auto-show
+0015 Project storage runtime (track 2 — implemented through
+ Iteration 4 + cleanup; Iterations 5 and 6 pending)
+```
+
+## What's pending — proposed next moves (in order)
+
+### 1. Iteration 5 — `export` / `import` / `.gitignore` template
+
+Per ADR-0015 §11 and ADR-0007:
+
+- `export` — produces a zip containing `project.yaml` +
+ `data/`, **excluding** `playground.db` and (per ADR-0007
+ amendment 1) `history.log`. Default filename
+ `YYYYMMDD--export-NN.zip` with a non-clobbering
+ two-digit sequence. The user picks the output directory
+ (modal path entry).
+- `import` — accepts an exported zip, unpacks it into a named
+ project at a chosen location, runs `rebuild` on open. The
+ zip lacks `.db` and `history.log`, so a fresh `playground.db`
+ is created from YAML+CSV and `history.log` starts empty. If
+ the chosen location already exists (per the §2 collision
+ rule from naming), `import` refuses with a friendly error.
+- `.gitignore` template — currently the only items inside it
+ are `/playground.db`, `/.rdbms-playground.lock`,
+ `/project.yaml.v*.bak`. The amendment to ADR-0007 left
+ `history.log` out of the gitignore (user choice). That part
+ is already implemented (`Project::initialize_skeleton`); the
+ Iteration 5 work is just adding `export` / `import`
+ themselves.
+
+Will need a zip dep — `zip` crate is the standard choice. Add
+two new modal types (`ExportPathEntry`, `ImportPathEntry`) or
+reuse `Modal::PathEntry` with new `PathEntryPurpose` variants.
+
+Estimated scope: moderate — ~400–600 lines + tests.
+
+### 2. Iteration 6 — `--resume` + persistent input history + migration scaffold
+
+The remaining ADR-0015 §1 / §7 / §9 / §12 items:
+
+- `--resume` CLI flag opens the most recently used project
+ (path tracked in `/last_project`). Errors cleanly
+ if missing; mutually exclusive with a positional path.
+- Persistent input history (I2): on project open, hydrate the
+ in-memory navigable history from `history.log`'s most recent
+ entries. New successful commands keep appending as today.
+- Migration framework (F3): scaffold only — register no
+ migrators yet, but the load path checks `version`, copies
+ `project.yaml` to `project.yaml.v.bak`, runs the
+ registered migrators in order, writes back at the latest
+ version. Exercised the moment a v2 ever lands.
+
+Estimated scope: small/moderate — ~300–500 lines + tests.
+
+### 3. Other deferred items (still untouched, in order of likely priority)
+
+- **Complex WHERE expressions (C5a)** — AND/OR/comparison/LIKE
+ in UPDATE/DELETE/show-data filters. Bridge from DSL to real
+ SQL.
+- **Indexes (C3 partial) + EXPLAIN QUERY PLAN (QA1)** — strong
+ teaching demo.
+- **B2 column drops/renames/type changes** — the
+ `rebuild_table` primitive already exists.
+- **Friendly error layer (H1)** — translate SQLite messages to
+ learner-friendly ones.
+- **Session log + Markdown export (V4)** — bigger UX project.
+- **m:n convenience (C4)** — auto-generates a junction table.
+- **CI (TT5)** — the test infrastructure exists; the workflow
+ file does not.
+
+## Sharp edges and subtleties (delta vs. previous handoff)
+
+The previous handoff's sharp edges (sync `update`, worker
+thread, metadata transactions, rebuild-table primitive,
+wrap-aware scroll math) all still apply. New ones:
+
+- **`Action::ExecuteDsl` is a struct variant.** Tests can no
+ longer pattern-match it via `vec![Action::ExecuteDsl(...)]`.
+ The walking-skeleton tests use `assert_one_execute_dsl` which
+ compares only the parsed `Command`, ignoring the source
+ string.
+- **All public `Database` methods take `source: Option`
+ as a final argument.** `None` skips the `history.log` append
+ (used for tests and internal calls); `Some(text)` records the
+ user-typed line. The Python-script bulk-update from
+ Iteration 2 added `, None` to every existing test call site.
+- **`Persistence` is wired ONLY when calling
+ `Database::open_with_persistence`.** Tests that use
+ `Database::open(":memory:")` get no YAML/CSV/history.log
+ writes — that's intentional and lets the SQLite layer be
+ exercised in isolation.
+- **`do_rebuild_from_text` wipes existing user tables and
+ metadata before reloading.** Works for the silent on-load
+ case (empty db: wipe is a no-op) and the explicit `rebuild`
+ case (replaces whatever was there).
+- **The runtime's `Session` holds `Option` +
+ `Option`.** Project switches `take()` the old
+ values (releasing the lock and stopping the worker) BEFORE
+ opening the new ones — required for the
+ load-my-own-current-project case where the new open would
+ otherwise see a self-held lock.
+- **`safely_delete_temp_project` is the ONLY way the runtime
+ ever removes a project directory.** Don't reach for
+ `std::fs::remove_dir_all` directly — use the helper, which
+ stacks containment / symlink-rejection / `[temp]`-marker /
+ contents-allowlist guards. Refusal is non-fatal; the
+ directory just stays put.
+- **Modal state lives in `App.modal: Option` and
+ routes ALL keys when active.** New modals plug into
+ `handle_modal_key` and the `render_modal` dispatcher in
+ `ui.rs`. A modal's submission yields one or more `Action`s
+ for the runtime to enact.
+- **`AppEvent::RebuildSucceeded` is reused for both the
+ explicit `rebuild` command and the silent on-load
+ rebuild.** The handler is kind enough to be a no-op on
+ `modal = None`, so the dual use works without a special
+ case.
+- **`is_unmodified_temp` requires BOTH empty schema (from
+ YAML) AND empty `data/` directory.** The combined check is
+ the authoritative signal; the `safely_delete` helper adds
+ defence-in-depth on the path side.
+
+## Repository layout (delta vs. previous handoff)
+
+```
+src/
+ action.rs — Action enum (Quit / ExecuteDsl /
+ PrepareRebuild / Rebuild /
+ OpenLoadPicker / LoadProject /
+ SaveAs / NewProject)
+ app.rs — App state + Modal infrastructure
+ + per-modal key handlers
+ cli.rs — Args + HELP_TEXT
+ db.rs — worker, do_rebuild_from_text,
+ finalize_persistence (+ all the
+ previous DDL/DML)
+ dsl/ — unchanged
+ event.rs — AppEvent (incl. RebuildPrepared,
+ RebuildSucceeded, RebuildFailed,
+ PersistenceFatal, LoadPickerReady,
+ ProjectSwitched, ProjectSwitchFailed)
+ lib.rs — re-exports incl. persistence + project
+ logging.rs — unchanged
+ main.rs — handles --help before booting
+ mode.rs — unchanged
+ persistence/ — new module (Iteration 2+)
+ mod.rs — Persistence handle, atomic write,
+ public types
+ yaml.rs — writer (hand-rolled) + reader
+ (serde_yml)
+ csv_io.rs — writer + reader (both hand-rolled)
+ history.rs — history.log appender
+ project/ — unchanged location, expanded:
+ mod.rs — Project, ProjectKind, lifecycle,
+ list_projects, copy_project,
+ safely_delete_temp_project,
+ is_unmodified_temp
+ naming.rs — slug generator with [temp] marker,
+ is_temp_dirname helper
+ prettifier.rs — strips date prefix AND [temp]-
+ lock.rs — unchanged from Iteration 1
+ wordlist.txt — 161 words
+ runtime.rs — Session, perform_switch,
+ spawn_prepare_rebuild,
+ spawn_rebuild
+ snapshots/ — insta snapshots (incl. new
+ rebuild_confirm_modal_dark)
+ theme.rs — unchanged
+ ui.rs — modal renderers
+ (render_rebuild_confirm,
+ render_path_entry,
+ render_load_picker)
+ + status-bar [TEMP] prefix
+tests/
+ walking_skeleton.rs — Tier-3 (existing)
+ project_lifecycle.rs — Iteration 1
+ iteration2_persistence.rs — Iteration 2 + the empty-CSV rule
+ iteration3_rebuild.rs — Iteration 3
+ iteration4a_rebuild_command.rs — Iteration 4a
+ iteration4b_lifecycle_commands.rs — Iteration 4b + safety
+ guards + cleanup tests
+```
+
+## How to take over
+
+1. Read this file.
+2. Read `CLAUDE.md` for the working-style rules and current
+ layout.
+3. Read `docs/requirements.md` for granular progress.
+4. Skim `docs/adr/README.md`; read ADR-0015 in full if you'll
+ touch the project storage runtime.
+5. Run `cargo test` to confirm the 345-test green baseline.
+6. `cargo run --release` to see the app — try the smoke test
+ below.
+
+### End-to-end smoke test
+
+Verifies temp-project lifecycle, persistence, switching, and
+the [TEMP] / `[TEMP]` cleanup convention:
+
+```
+# Launch — should land in a temp project named like
+# 20260508-[temp]---; status bar shows
+# "Project: [TEMP] ".
+$ rdbms-playground
+
+# Inside the app:
+help -- prints command list
+create table Customers with pk id:serial
+add column Customers: Name (text)
+insert into Customers ('Alice')
+insert into Customers ('Bob')
+show data Customers -- two rows
+save -- prompts for a name
+ -- type "MyOrders" + Enter
+ -- status bar updates;
+ -- [TEMP] disappears
+
+# Confirm on disk: the OLD temp dir is deleted (was unmodified
+# at the point of save... no wait, save HAS modifications;
+# it stays). Test the unmodified-temp cleanup separately:
+new -- creates fresh temp
+ -- previous (named) project
+ -- stays put
+load -- picker shows MyOrders +
+ -- the new fresh temp
+ -- arrow-down to MyOrders,
+ -- Enter
+ -- the fresh temp gets
+ -- auto-deleted (was
+ -- unmodified)
+
+# Now rebuild from text:
+rebuild -- modal: Y
+ -- "[ok] rebuild — 1 table,
+ -- 2 rows reconstructed"
+delete from Customers --all-rows
+show data Customers -- empty
+quit
+```
+
+If anything in that sequence fails, something is wrong. The
+sequence exercises auto-temp creation, schema + DML
+persistence (yaml + csv + history.log), `save` (temp →
+named), `new` (close + create new temp), `load` picker (with
+unmodified-temp cleanup of the just-created fresh temp), and
+explicit `rebuild`.
+
+### Manual spot-checks worth running
+
+- `--help` and `-h` both print the usage banner.
+- A project with a table and data: delete `playground.db`,
+ reopen — system message "[ok] rebuild — 1 table, 2 rows
+ reconstructed" appears in the output panel, data is
+ intact.
+- Two `rdbms-playground` instances on the same project — the
+ second refuses with a clear lock-held error.
+- `save` then `save as` from a named project — `save` says
+ "already auto-saved", `save as` prompts.
+- `load` with `b` to switch to path-entry submode.
+- After `quit` from a fresh-launch (untouched) temp, the
+ temp directory is gone from `/projects/`.
+- Add a `notes.md` to a temp project's directory; the
+ cleanup refuses (warn in tracing log) and the directory
+ stays.
diff --git a/docs/requirements.md b/docs/requirements.md
index 0aea38e..20cebc2 100644
--- a/docs/requirements.md
+++ b/docs/requirements.md
@@ -74,10 +74,11 @@ against it.
end), Ctrl-U (delete to start). Pending.
- [ ] **I2** Persistent navigable input history (project-scoped,
with a global rolling history also available).
- *(Progress: in-memory navigable history (Up/Down arrows, draft
- preservation, dedup of consecutive duplicates) is implemented;
- persistence across sessions arrives with track 2's project
- storage.)*
+ *(Progress: in-memory navigable history is implemented; the
+ on-disk record is `history.log` (Iteration 2). What's still
+ missing for I2 is hydrating the navigable history from
+ `history.log` on project open — Iteration 6. Global rolling
+ history deferred per OOS-6 / N4.)*
- [ ] **I3** Tab completion for app commands, DSL keywords, table
names, column names, and SQL keywords.
- [ ] **I4** Syntax highlighting for both the DSL and SQL.
@@ -102,10 +103,11 @@ against it.
available in both modes: `save`, `save as`, `load`, `new`,
`rebuild`, `export`, `import`, `seed`, `replay`, `undo`,
`redo`, `mode`, `help`, `hint`, `quit`.
- *(Progress: `quit`/`q` and `mode simple|advanced` implemented;
- the rest land alongside the features they belong to — `save`,
- `load`, `new`, `rebuild`, `export`, `import` in track 2
- (ADR-0015), `seed` in the seeding iteration, etc.)*
+ *(Progress: `quit`/`q`, `mode simple|advanced`, `help` (basic
+ listing), `save`, `save as`, `load`, `new`, `rebuild` all
+ implemented (Iteration 4). `export` / `import` land in track
+ 2's Iteration 5; `seed` in the seeding iteration; `replay` /
+ `undo` / `redo` in the U-series; `hint` with H2.)*
## DSL data commands
@@ -232,46 +234,51 @@ against it.
## Project lifecycle (per ADR-0004)
-- [ ] **P1** An auto-named temporary project is created on
- startup unless a project is specified, and stored in a
- platform-standard path
- (e.g. `~/.rdbms-playground/projects/temp-`).
-- [ ] **P2** `save` elevates a temp project to a named project at
- a chosen location.
-- [ ] **P3** Project is always saved as changes occur — there is
- no manual dirty state.
-- [ ] **P4** `load` opens a picker listing temp projects with
- timestamps, with the option to browse to an arbitrary location.
-- [ ] **P5** `playground.db` is a derived artifact: rebuilt
- silently when missing; rebuilt explicitly via the new
- `rebuild` app-level command, which prompts with a change
- summary before reconstructing from `project.yaml` + `data/`
- (per ADR-0004 amendment 2 and ADR-0015 §7).
-- [ ] **P-NAME-1** Temp project directory naming pattern:
- `---` from a built-in wordlist
- (ADR-0015 §2). Date-sortable; collisions checked against
- existing folders and the slug is regenerated on the rare
- collision. User-supplied save names that already exist as
- folders are refused with a friendly error.
-- [ ] **P-NAME-2** Display-name prettifier converts a project
- directory name to a human-readable display name: strip a
- leading `YYYYMMDD-` for temp projects; split kebab / snake /
- camel; title-case each word (ADR-0015 §2).
-- [ ] **P-NAME-3** The current project's display name is shown
- in the UI status bar at all times, prefixed with `Project:`
- (ADR-0015 §2).
+- [x] **P1** Auto-named temp project on startup under
+ `/projects/`. OS-standard data root via
+ `directories` crate; `--data-dir` overrides (Iteration 1).
+- [x] **P2** `save` / `save as` elevate / copy + switch
+ (Iteration 4b). `save` on a named project reports
+ "already auto-saved".
+- [x] **P3** Auto-save: per-command write-through to YAML +
+ CSV + `history.log` inside the SQLite tx with
+ commit-db-last ordering (Iteration 2). No dirty state.
+- [x] **P4** `load` opens an in-TUI picker, sorted newest
+ first, with `[TEMP]` markers and a `b`-to-browse path-entry
+ sub-mode (Iteration 4b).
+- [x] **P5** Existence-only load + explicit `rebuild`
+ command with confirmation modal (Iterations 3 + 4a).
+- [x] **P-NAME-1** Temp project directory naming pattern:
+ `-[temp]---` from a 161-word
+ built-in list (Iterations 1 + 4b). Bracketed `[temp]`
+ marker is unambiguous against user-named projects because
+ `validate_user_name` rejects brackets.
+- [x] **P-NAME-2** Display-name prettifier strips
+ `YYYYMMDD-` AND `[temp]-`; splits kebab / snake / camel;
+ title-cases each word.
+- [x] **P-NAME-3** Status bar shows
+ `Project: [TEMP] ` for temp projects,
+ `Project: ` for named.
+- [x] **P-CLEAN-1** Unmodified empty temp projects are
+ auto-deleted on switch and quit, gated by
+ `safely_delete_temp_project`'s stacked guards
+ (containment, symlink rejection, `[temp]` marker, contents
+ allowlist).
## Project file format (per ADR-0004)
-- [ ] **F1** `project.yaml` with `version` field carries schema,
- relationships, and project metadata; `data/
.csv` carries
- table data (UTF-8, header row, RFC 4180).
-- [ ] **F2** A `.gitignore` template (excluding `playground.db`)
- is created in each new project.
-- [ ] **F3** Project file format includes a registered-migrator
- mechanism so older `version` values load cleanly as the format
- evolves. (Exercised once `version` increments past 1; the
- mechanism itself is built in v1.)
+- [x] **F1** `project.yaml` with `version: 1` field carries
+ schema (ordered tables + columns), relationships, and
+ `created_at`. `data/
.csv` carries table data (UTF-8,
+ header row, RFC 4180; NULL distinct from empty string)
+ (Iteration 2). Empty tables produce no CSV.
+- [x] **F2** `.gitignore` template (`/playground.db`,
+ `/.rdbms-playground.lock`, `/project.yaml.v*.bak`) created
+ in each new project (Iteration 1). Per ADR-0007 amendment
+ 1, `history.log` is NOT in the template — user decides
+ whether to commit it.
+- [ ] **F3** Migration framework — pending Iteration 6.
+ Scaffold (no migrators yet) is the v1 deliverable.
## Undo and replay (per ADR-0006)
@@ -280,8 +287,9 @@ against it.
- [ ] **U2** `undo` restores the most recent snapshot; `redo`
re-applies; both prompt for confirmation showing the snapshot
timestamp and a summary of the changes that will be discarded.
-- [ ] **U3** `history.log` records every successfully executed
- command in append-only form.
+- [x] **U3** `history.log` records every successfully executed
+ command in append-only form (Iteration 2). Format:
+ `|ok|` per ADR-0015 §5.
- [ ] **U4** `replay` runs commands from a `history.log` or
`.commands` file.
@@ -330,15 +338,20 @@ against it.
input or the most recent error.
- [ ] **H3** `help` provides general reference and per-command
help.
+ *(Progress: `help` app-level command lists currently-supported
+ commands + DSL grammar reference + types, kept in sync with
+ what's wired up. Per-command detail is the missing piece.)*
## CLI
-- [ ] **L1** Load a project via a positional CLI argument.
+- [x] **L1** Load a project via a positional CLI argument
+ (Iteration 1). Plus `--data-dir` to override the data root
+ and `--help` / `-h` for the usage banner.
- [ ] **L1a** `--resume` CLI flag opens the most recently used
project (path tracked in `/last_project`). Errors
cleanly if no previous project exists or the recorded path is
gone; mutually exclusive with a positional path argument
- (ADR-0015 §7).
+ (ADR-0015 §7). Pending Iteration 6.
- [~] **L2** Submit a command alongside project load — deferred,
not v1.