From ca71184678577fbbe5048e4aac4e13eea17f7b36 Mon Sep 17 00:00:00 2001 From: "claude@clouddev1" Date: Fri, 8 May 2026 07:07:38 +0000 Subject: [PATCH] Handoff doc + CLAUDE.md and requirements.md refresh Adds docs/handoff/20260508-handoff-2.md describing the state at the end of this session: ADR-0015 designed, Iterations 1-4 of track 2 shipped (file-backed projects with auto-named [temp] dirs, per-command write-through, rebuild from text on missing .db, save/save as/new/load/rebuild commands with modal dialogs and project switching), plus the cleanup pass (--help, in-app help, post-rebuild message, unmodified-temp cleanup) and the safety hardening of safely_delete_temp_project. Lists the next-up moves (Iteration 5: export/import, Iteration 6: --resume + persistent input history + migration scaffold) and an end-to-end smoke test. requirements.md: marks P1-P5, P-NAME-1/2/3, F1, F2, U3, L1 as [x] with iteration references; adds P-CLEAN-1 for the safe cleanup; updates A1, I2, H3, L1a progress notes. CLAUDE.md: updates the project-storage decisions and deferred-items entry to reflect what's now live vs. still pending. --- CLAUDE.md | 28 +- docs/handoff/20260508-handoff-2.md | 456 +++++++++++++++++++++++++++++ docs/requirements.md | 111 +++---- 3 files changed, 537 insertions(+), 58 deletions(-) create mode 100644 docs/handoff/20260508-handoff-2.md 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.