diff --git a/CLAUDE.md b/CLAUDE.md
index 24da2f9..7309784 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -32,8 +32,17 @@ Current decisions at a glance (each backed by an ADR):
(SQL + app-level commands) on toggle; `:` one-shot escape from
simple to advanced (ADR-0003). No other sigils.
- **Project format:** `project.yaml` + `data/
.csv` +
- `history.log`; `playground.db` is a derived artifact (ADR-0004).
- *(Format defined; track 2 implementation pending.)*
+ `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.)*
+- **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).
- **Types:** `text`, `int`, `real`, `decimal`, `bool`, `date`,
`datetime`, `blob`, `serial`, `shortid`. Compound primary keys
supported. No real UUIDs (ADR-0005). FK column type
@@ -146,10 +155,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): file-
- backed projects, save/load/new/export/import, persistent
- history. Format is fully designed in ADR-0004; the
- metadata-table round-trip lands here.
+- **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.
- **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/adr/0004-project-file-format.md b/docs/adr/0004-project-file-format.md
index 923e804..dabfab6 100644
--- a/docs/adr/0004-project-file-format.md
+++ b/docs/adr/0004-project-file-format.md
@@ -2,7 +2,10 @@
## Status
-Accepted
+Accepted. Amended by [ADR-0015](0015-project-storage-runtime.md) —
+see the "Amendments" section at the end of this file for the
+specifics; the rest of this ADR remains the canonical reference
+for the project file format.
## Context
@@ -62,3 +65,38 @@ A project is a directory containing:
- The `version` field opens the door to format migrations as the
app evolves; old projects load by running registered migrators
in sequence.
+
+## Amendments
+
+### Amendment 1 — runtime data flow ([ADR-0015](0015-project-storage-runtime.md))
+
+The phrase "`playground.db` is a derived artifact" describes a
+*recovery* property: the database can always be reconstructed
+from `project.yaml` + `data/`. It does not describe runtime
+data flow.
+
+At write time, all persistence targets (the SQLite database,
+`project.yaml`, the relevant `data/.csv` files, and
+`history.log`) share a single source — the user's command — and
+are written alongside one another in a defined order (see
+ADR-0015 §6). None of the text files is "downstream" of the
+database at write time.
+
+### Amendment 2 — `.db` rebuild trigger ([ADR-0015](0015-project-storage-runtime.md))
+
+The "rebuild with confirmation when `.db` exists" semantics in
+the original Decision section are replaced by a simpler model:
+
+- On load, if `playground.db` exists, it is opened as-is.
+- On load, if `playground.db` is missing, it is rebuilt
+ silently from `project.yaml` + `data/`.
+- A new app-level command, `rebuild`, explicitly discards the
+ current `playground.db` and reconstructs it from the text
+ sources, with a confirmation prompt and a summary of what
+ will be reconstructed.
+
+The application does not attempt to detect drift between the
+database and the text sources automatically. `rebuild` is the
+explicit user-driven path for cases where drift exists (git
+pull over an existing `.db`, hand edits to YAML/CSV, recovery
+after a rare failure described in ADR-0015 §6).
diff --git a/docs/adr/0007-sharing-and-export.md b/docs/adr/0007-sharing-and-export.md
index b6bd8a8..445aaf1 100644
--- a/docs/adr/0007-sharing-and-export.md
+++ b/docs/adr/0007-sharing-and-export.md
@@ -2,7 +2,10 @@
## Status
-Accepted
+Accepted. Amended by [ADR-0015](0015-project-storage-runtime.md) —
+see the "Amendments" section at the end of this file for the
+specifics; the rest of this ADR remains the canonical reference
+for sharing and export.
## Context
@@ -23,9 +26,11 @@ mechanisms:
1. **`export` command.**
- Available as an app-level command in both input modes
(ADR-0003).
- - Produces a single zip file containing `project.yaml`,
- `data/`, and `history.log`, **excluding** `playground.db`
- (the recipient rebuilds it on open per ADR-0004).
+ - Produces a single zip file containing `project.yaml` and
+ `data/`, **excluding** `playground.db` (the recipient
+ rebuilds it on open per ADR-0004) and **excluding**
+ `history.log` (the user's working log is private; see
+ Amendment 1 below).
- Default output path is the parent directory of the project.
- Default filename:
`YYYYMMDD--export-.zip`, where
@@ -46,6 +51,33 @@ mechanisms:
If real-world usage later reveals friction these mechanisms cannot
solve, a publish feature can be revisited as a separate ADR.
+## Amendments
+
+### Amendment 1 — `history.log` excluded from export ([ADR-0015](0015-project-storage-runtime.md))
+
+The export zip's contents are now `project.yaml` + `data/`
+only. Both `playground.db` (always derived) and `history.log`
+(the user's private working log) are excluded.
+
+Rationale: the history captures every successful command the
+user has run in the project, including exploratory or
+embarrassing detours. Sharing it by default is wrong — users
+will share more than they intend. A user who *does* want to
+share their session log can attach `history.log` separately
+or re-export with a future flag if real demand emerges.
+
+The `.gitignore` template (Decision item 2) is **not**
+updated to exclude `history.log`. The template's purpose is
+to give a sensible default for committing a project to git,
+which is a different question from "what gets shared in an
+export zip." Some users will want their working log in
+version control (lessons, audits, reproducible
+problem-reports); others won't. We don't make that call for
+them — the user decides whether `history.log` belongs in
+their git history. The export zip remains the
+auto-curated-for-strangers artifact; the gitignore remains
+neutral on `history.log`.
+
## Consequences
- Zero server-side surface area, zero accounts, zero hosting
diff --git a/docs/adr/0015-project-storage-runtime.md b/docs/adr/0015-project-storage-runtime.md
new file mode 100644
index 0000000..f61347b
--- /dev/null
+++ b/docs/adr/0015-project-storage-runtime.md
@@ -0,0 +1,540 @@
+# ADR-0015: Project storage runtime
+
+## Status
+
+Accepted. Amends ADR-0004 (project file format) and ADR-0007
+(sharing and export); see the "Relationship to earlier ADRs"
+section at the end for the exact deltas.
+
+## Context
+
+ADR-0004 defined the on-disk shape of a project — `project.yaml`
++ `data/.csv` + `history.log`, with `playground.db` as a
+derived artifact. It deliberately did not specify runtime
+semantics: when a project comes into existence, where it lives,
+how the on-disk files are kept consistent with the running
+SQLite database, what happens on load, on failure, on
+concurrent open, and how the canonical app-level commands
+(`save`, `load`, `new`, `export`, `import`) are scoped.
+
+Track 1 of the application built everything against an
+in-memory SQLite database. Every quit lost all work. This is
+the largest single UX gap left in the project, and the next
+useful feature (replay/undo, ADR-0006) depends on the
+`history.log` written here.
+
+This ADR fills the runtime gap. It commits to a single
+persistence model — *every successful command writes through
+to all targets immediately, and validation gates everything* —
+and works the resulting design through to file naming, the
+load picker, the failure model, and concurrent-open behaviour.
+
+## Decision
+
+### 1. Lifecycle and locations
+
+There is no in-memory database in normal operation. Every
+session is backed by a project on disk.
+
+- **Startup with no CLI argument:** the application creates a
+ new temporary project under the OS data directory (see
+ below), opens it, and runs against it.
+- **Startup with a CLI argument** (`rdbms-playground `,
+ requirement L1): the application opens the project at that
+ path. If the path does not exist or does not look like a
+ project (no `project.yaml`), it refuses with a friendly
+ error.
+- **`save` / `save as`** elevate or copy a project to a chosen
+ location.
+- **`load`** opens a different project (see section 7).
+- **`new`** creates a fresh temp project from inside the
+ running application, after closing the current one.
+
+The OS data root is platform-standard:
+
+- Linux: `$XDG_DATA_HOME/rdbms-playground` (defaulting to
+ `~/.local/share/rdbms-playground` when `XDG_DATA_HOME` is
+ unset).
+- macOS: `~/Library/Application Support/rdbms-playground`.
+- Windows: `%APPDATA%\rdbms-playground`.
+
+Inside the data root: `projects/` holds projects — both
+auto-generated temp ones and ones the user has saved with a
+name of their choosing. There is no requirement that named
+projects move *out* of the data root, and no encouragement to
+do so: keeping a saved project right alongside the temp ones
+is the easiest workflow and is fully supported. Users who
+prefer a different home (a course directory, a shared drive,
+a git working tree) save there instead. The application
+prescribes nothing.
+
+The data root also carries a small state file
+`last_project` (a single line containing the absolute path of
+the most recently opened project). It exists to support
+`--resume` (section 7).
+
+A `--data-dir` CLI flag fully replaces the OS-standard data
+root for the duration of that run; both project creation and
+the load picker's listing use the supplied directory and only
+that directory. The `last_project` state file is read and
+written under the active data root, so a user with multiple
+data roots gets independent resume histories per root, which
+is the intuitive behaviour.
+
+### 2. Project naming and display name
+
+Temp project directory names follow the pattern
+`---`, where the words are drawn
+from a small built-in wordlist compiled into the binary (no
+external file or network call). Example:
+`20260507-water-buffalo-skating`. The leading date keeps the
+file listing chronologically sortable; the words give learners
+something nameable to refer to.
+
+Named projects use whatever directory name the user chose at
+`save` time.
+
+**Collision handling.**
+
+- For auto-generated temp names: before creating the
+ directory, the application checks for an existing entry of
+ the same name in the data root and regenerates the
+ three-word slug if one is found. The wordlist is large
+ enough (multiple categories, dozens of words each) that
+ collisions are essentially never observed in practice; the
+ check is cheap and removes the failure mode entirely.
+- For user-supplied names at `save` / `save as` / `import`:
+ if the target directory already exists (whether it
+ contains a project or anything else), the operation is
+ refused with a friendly error. The user picks a different
+ name or moves/removes the existing directory first. We
+ deliberately do not auto-suffix or merge — silently
+ changing the name the user typed, or writing into someone
+ else's directory, is worse than asking them to pick again.
+
+The application carries a *display name* derived from the
+project directory name by a small prettifier:
+
+- Strip a leading `YYYYMMDD-` if present (temp projects).
+- Split on `-` (kebab-case), `_` (snake_case), or case
+ boundaries (camelCase / PascalCase).
+- Title-case each word.
+
+So `20260507-water-buffalo-skating` displays as
+"Water Buffalo Skating"; `MyOrders` displays as "My Orders";
+`customer_demo` displays as "Customer Demo".
+
+The display name is shown in the bottom status bar at all
+times, prefixed with `Project:` so it's unambiguous. This is
+how the user knows which project they are editing.
+
+### 3. `project.yaml` shape
+
+Flat ordered lists. Tables and columns preserve declaration
+order; relationships preserve creation order.
+
+```yaml
+version: 1
+project:
+ created_at: 2026-05-07T14:30:12Z
+tables:
+ - name: Customers
+ primary_key: [id]
+ columns:
+ - { name: id, type: serial }
+ - { name: Name, type: text }
+relationships:
+ - name: Customers_id_to_Orders_CustId
+ parent: { table: Customers, column: id }
+ child: { table: Orders, column: CustId }
+ on_delete: cascade
+ on_update: no_action
+```
+
+The `version: 1` field is required. Migrators (section 9)
+upgrade older versions on load. The project's name is
+**not** stored in `project.yaml`; the directory name on disk
+is the canonical name. Recording it twice would create an
+opportunity for the two to drift if the user renamed the
+directory by hand; with one source of truth, that question
+doesn't arise.
+
+### 4. CSV encoding
+
+One file per table, `data/.csv`, UTF-8, RFC 4180
+quoting, header row carrying column names in declaration
+order.
+
+Per-type encoding:
+
+| Type | CSV form |
+|------------|---------------------------------------|
+| `text` | RFC 4180 string |
+| `int` | decimal integer |
+| `real` | shortest-round-trip decimal |
+| `decimal` | string form already validated by `value.rs` |
+| `bool` | `true` / `false` |
+| `date` | `YYYY-MM-DD` |
+| `datetime` | ISO 8601 with `T` and a `Z` or offset |
+| `blob` | base64 (standard alphabet, padded) |
+| `serial` | integer |
+| `shortid` | base58 string |
+
+NULL is the empty unquoted field; the empty quoted field
+(`""`) is an empty string. The distinction is preserved
+because SQL preserves it and the playground is meant to teach
+SQL.
+
+### 5. `history.log` format
+
+Append-only, one record per line, three pipe-separated fields:
+
+```
+2026-05-07T14:30:12Z|ok|create table Customers with pk id:serial
+2026-05-07T14:30:30Z|ok|insert into Customers ('Alice')
+```
+
+- **Timestamp** in ISO 8601 with `Z`.
+- **Status** is always `ok` in v1, because failed commands
+ are not recorded — this matches ADR-0006's "successfully
+ executed command" wording and keeps the log directly
+ replayable. The status field is kept in the line format
+ anyway so future use cases (audit logs that record
+ attempts, validation diagnostics, distinguishing
+ user-issued from imported commands) can carry additional
+ values without a format break.
+- **Command** is the user's input as typed. Newlines (when
+ multi-line input arrives, requirement I1) are escaped as
+ literal `\n`.
+
+`history.log` is **not** included in `export` (see section 11
+and the ADR-0007 amendment). It is private to the user's
+working copy.
+
+### 6. Persistence ordering
+
+A successful user command produces effects in four targets:
+the SQLite database, `project.yaml`, the relevant
+`data/.csv` file(s), and `history.log`. INV-2 from the
+Phase-1 record requires that the **combined db persistence
+logic** — validation, metadata-table handling, the SQLite
+mutations — gate everything else.
+
+The implementation order inside a command is:
+
+1. **Validate and stage in the database.** Open a SQLite
+ transaction. Perform validation, schema/metadata
+ mutations, data mutations. Do not commit yet.
+2. **Stage text targets.** Write `project.yaml` (if schema or
+ relationships changed) and affected `data/.csv`
+ files (if rows changed) to temp files inside the project
+ directory. Append the new line for `history.log` to a
+ temp copy. `fsync` each.
+3. **Rename text targets.** Atomic rename each temp file to
+ its final path (POSIX `rename(2)`; on Windows
+ `MoveFileEx(REPLACE_EXISTING)`).
+4. **Commit the SQLite transaction.**
+
+Failure handling:
+
+- Failure in step 1 or 2 → roll back the SQLite transaction;
+ no rename happens; on-disk state is unchanged. Surface the
+ failure (see section 8) and quit.
+- Failure in step 3 (rename fails after `fsync`) → roll back
+ the SQLite transaction; orphan temp files remain in the
+ project directory and are cleaned up on next open. On-disk
+ semantic state is unchanged. Surface and quit.
+- Failure in step 4 (commit fails after rename succeeded) →
+ rare; on next launch the on-disk text is ahead of the
+ `playground.db`. The user sees stale data and runs
+ `rebuild` (section 7) to recover. Documented edge case;
+ acceptable for v1.
+
+This ordering is "commit db last so a fatal failure leaves
+disk state recoverable via `rebuild`."
+
+### 7. Load and rebuild
+
+**Load on startup or via the `load` command.** If
+`playground.db` exists in the project directory, it is opened
+as-is. If it does not exist, it is rebuilt silently from
+`project.yaml` + `data/.csv`. There is no automatic
+detection of drift between the database and the text sources
+on load; that's what `rebuild` is for.
+
+**`--resume` CLI option.** Equivalent to passing the path
+recorded in the `/last_project` state file as the
+positional CLI argument. If `last_project` is missing or
+points at a path that no longer exists, `--resume` exits
+with an error pointing the user at the absent project; it
+does **not** silently fall back to creating a new temp
+project, because the user's intent ("resume what I had") is
+clear and silent fallback would mask the problem. `--resume`
+and an explicit positional path are mutually exclusive; the
+combination errors out.
+
+The `last_project` file is rewritten on every successful
+project open (startup, `load`, `new`, `save as`, `import`).
+A clean exit doesn't clear it — that's the whole point of
+`--resume` after a quit.
+
+**CSV row-load failure during rebuild.** When rebuilding
+`playground.db` from `project.yaml` + `data/.csv`,
+each row insert can fail (malformed CSV, type-validation
+failure, FK violation, NOT NULL violation, etc.). The
+behaviour mirrors the persistence failure model (section 8):
+the rebuild stops at the first failing row and surfaces a
+fatal error of the form
+
+> Unable to load row *N* from `data/.csv` into table
+> ``: *<diagnosis from the value/FK/constraint
+> validator>*
+
+The application then quits. There is no realistic case
+where a CSV produced by a previous well-behaved session
+contains an unloadable row; if one does, something has gone
+wrong (hand edit, partial git merge, file corruption) and
+the user should fix the file or restore an earlier copy.
+Continuing past the bad row would either lose data
+silently (skip it) or load partial state (stop but keep
+what loaded), both of which leave the user in a worse
+position than a clear error message.
+
+**`rebuild` app-level command.** Discards the current
+`playground.db` and reconstructs it from `project.yaml` +
+`data/`. Always shows a confirmation prompt with a summary
+("12 tables, 47 rows will be reconstructed; existing
+`playground.db` will be replaced") before doing the work.
+Useful when:
+
+- The user pulled new YAML/CSV from git over an old `.db`.
+- A prior persistence failure left the `.db` behind the text
+ (section 6, step-4 failure mode).
+- The user hand-edited the YAML or CSV outside the app.
+
+**Load picker UX.** The `load` command opens an in-TUI modal
+listing temp projects from the data dir, sorted newest
+first, with the prettified display name and creation
+timestamp. Arrow keys select; Enter loads; Esc cancels;
+pressing `b` (for "browse") switches the modal to a
+path-entry prompt for projects outside the data dir. This
+covers both common (pick a recent temp) and uncommon (open a
+named project at a custom path) cases without forcing the
+user into a fully manual path entry up front.
+
+### 8. Failure model
+
+Persistence failures are fatal. The application surfaces a
+banner with the operation, the path, and the OS error
+message, then quits cleanly so the banner remains visible
+above the shell prompt. The user investigates (disk full,
+permission denied, network filesystem hiccup) and restarts.
+
+This is the right model because the realistic failure modes
+for a local data directory do not heal transiently. Showing a
+warning and continuing risks silent loss when the user later
+quits the app while the failure window is still open.
+
+The persistence ordering in section 6 ensures that "fatal
+failure → quit" never leaves the disk in a state that cannot
+be recovered: it is either unchanged (the common case) or
+recoverable via `rebuild` (the rare step-4 failure).
+
+The "quit on failure" mode is also not anticipated to be
+particularly disruptive in practice. Even if a transient
+issue (a network drive timing out, an antivirus scanner
+holding a file briefly) does cause a fatal failure, the
+user's path back into the session is just
+`rdbms-playground --resume`. With section 6's ordering
+guaranteeing recoverable disk state and `--resume`
+guaranteeing one-command return, the cost of erring on the
+side of "stop and let the user investigate" is small enough
+that the safety benefit dominates.
+
+### 9. Migration framework (F3)
+
+`project.yaml` carries `version: 1` from the outset. Future
+format changes bump the version and add a registered
+migrator function:
+
+```rust
+fn migrate_v1_to_v2(raw: &mut RawProject) -> Result<(), MigrateError> { ... }
+```
+
+Migrators are stored in an ordered list keyed by source
+version. On load, the application:
+
+1. Reads the file's `version`.
+2. If `version < latest_known`, copies the original file to
+ `project.yaml.v.bak` (where `` is the original
+ version).
+3. Runs each migrator in sequence from `version + 1` to
+ `latest_known`.
+4. Writes the upgraded YAML back at the new version.
+5. If any migrator fails, restores the `.bak` and surfaces
+ the failure as a fatal load error.
+
+The framework is built in v1 even though no migrator exists
+yet. The first real migrator (when v2 lands) exercises it.
+
+### 10. Concurrency
+
+A lock file `/.rdbms-playground.lock` is written
+when a project is opened, containing the PID and hostname of
+the owning process. On open:
+
+- If no lock file exists: take the lock and proceed.
+- If a lock file exists with a live PID on this host: refuse
+ with a friendly error pointing the user at the running
+ instance.
+- If a lock file exists but the PID is dead (or it lists a
+ different hostname): take the lock (clean handover from a
+ crashed prior instance).
+
+The lock is removed on clean exit. Crashes leave it behind;
+the next open reclaims it.
+
+The lock blocks only other rdbms-playground TUI instances.
+External read-only tooling (`sqlite3 playground.db -readonly`,
+text editors looking at `project.yaml`, etc.) is not
+prevented. The user is on their own if they fiddle with the
+project files concurrently with the running app — that's a
+power-user workflow we don't get in the way of.
+
+### 11. App-level commands
+
+The track 2 command set, all available in both modes per
+ADR-0003:
+
+- **`save`** — for a temp project, prompts for a target
+ directory and elevates to a named project (effectively
+ identical to `save as`). For a named project, reports
+ "auto-saved; use `save as` to copy to a new location."
+- **`save as`** — prompts for a target directory; copies
+ the entire project there and switches to operating on the
+ copy.
+- **`load`** — opens the load picker (section 7).
+- **`new`** — creates a fresh temp project; closes the
+ current one cleanly first (auto-save guarantees the
+ current state is on disk).
+- **`rebuild`** — section 7.
+- **`export`** — produces a zip per ADR-0007, *excluding*
+ both `playground.db` and `history.log` (see ADR-0007
+ amendment below). Default filename pattern unchanged.
+- **`import`** — accepts an exported zip, unpacks it into a
+ named project at a chosen location, runs `rebuild` on
+ open. The exported zip has no `playground.db` and no
+ `history.log`, so a fresh `playground.db` is created from
+ YAML+CSV, and `history.log` starts empty. The chosen
+ target directory must not already exist (per the §2
+ collision rule); the user picks a different name or
+ removes the existing directory first.
+
+The `.gitignore` template (F2) is created in every new
+project directory and excludes:
+
+```
+/playground.db
+/.rdbms-playground.lock
+/project.yaml.v*.bak
+```
+
+`playground.db` is rebuildable; the lock file is
+per-process; migration backup files are local recovery aids
+that don't belong in shared history. The `data/` directory
+and `project.yaml` itself are *not* ignored — they are the
+shared source of truth.
+
+`history.log` is **not** ignored by default. Whether to
+commit one's working log is a per-user, per-project taste
+question — some learners will treat the log as part of the
+audit trail and want it in git; others will prefer to keep
+it private. The export zip handles the "share with
+strangers" case (ADR-0007 amendment 1); committing to git
+is a different decision and we leave it to the user.
+
+### 12. Persistent input history (I2-persist)
+
+The in-memory navigable input history (Up/Down arrows,
+draft preservation, consecutive-duplicate dedup) gains a
+loader: on project open, the history navigation seed is
+populated from the project's `history.log` (latest N entries,
+where N is the same in-memory cap as today). New successful
+commands append to `history.log` and are pushed onto the
+in-memory stack as they are now.
+
+Project-scoped only. A separate global rolling history is
+deferred to a future ADR (OOS-6).
+
+### 13. Out of scope
+
+The following are tracked but not part of this ADR:
+
+- **OOS-1.** Snapshot ring buffer and `undo` (U1, U2,
+ ADR-0006).
+- **OOS-2.** `replay` command (U4). The `history.log`
+ format is replay-compatible; the command itself ships
+ later.
+- **OOS-3.** Multi-tab output / V4 session log work.
+- **OOS-4.** Tab completion or syntax highlighting for the
+ new commands' arguments.
+- **OOS-5.** L2 (submitting a command alongside project
+ load).
+- **OOS-6.** Global rolling input history.
+
+## Relationship to earlier ADRs
+
+This ADR amends two earlier ADRs in place rather than
+superseding them outright; the earlier ADRs remain the
+canonical reference for everything outside the amended
+clauses.
+
+- **ADR-0004 — Project file format.** The "playground.db is a
+ derived artifact" framing remains correct for *recovery*
+ (the database can be reconstructed from text sources at any
+ time). It does not describe runtime data flow: at write
+ time, all four targets (db, yaml, csv, history.log) share a
+ single source — the user's command — and are written
+ alongside one another per section 6 here. The "rebuild
+ with confirmation when `.db` exists" semantics are
+ reframed: there is no automatic drift detection on load;
+ the rebuild path is the explicit `rebuild` command, which
+ prompts for confirmation when invoked.
+- **ADR-0007 — Sharing and export.** The export contents are
+ now `project.yaml` + `data/`, *excluding* both
+ `playground.db` (as before) and `history.log` (new).
+ Rationale: the history is the user's working log and may
+ contain commands they don't want to share. Export remains
+ zip-based; default filename pattern is unchanged.
+
+The amendments are made in place in those ADR files, with a
+note pointing to this ADR.
+
+## Consequences
+
+- The biggest UX gap closes: quitting no longer loses work.
+- A failed command leaves the disk unchanged. A succeeded
+ command is durable on disk before the application
+ acknowledges it, with one documented edge case that the
+ `rebuild` command exists to fix.
+- The persistence path runs four file writes per command in
+ the common case. At teaching scale this is invisible; at
+ bulk-insert scale (thousands of rows in tight loops) it
+ could matter, and a future "batch" command will be the
+ remedy. Premature debouncing is rejected (it would create
+ a real inconsistency window for negligible gain at this
+ scale).
+- The "commit db last" ordering is the load-bearing
+ invariant for failure recovery. Future contributors
+ changing the persistence flow must preserve it.
+- The display-name prettifier is small and lives close to
+ the project loader; future filename conventions
+ (instructor-supplied lesson kits, perhaps) plug into it.
+- The lock file is a small piece of state that survives
+ crashes; the "live PID on this host" check is the
+ load-bearing piece of its correctness. Cross-host network
+ filesystems will give us false positives there; we accept
+ that and document it if real users hit it.
+- `history.log` becomes the persistent history surface.
+ Once `replay` (OOS-2) and `undo` (OOS-1) land, they read
+ from the same file with no schema changes.
diff --git a/docs/adr/README.md b/docs/adr/README.md
index 8b0b8fb..dd60602 100644
--- a/docs/adr/README.md
+++ b/docs/adr/README.md
@@ -20,3 +20,4 @@ This directory contains the project's ADRs, recorded per
- [ADR-0012 — Internal metadata for user-facing column types](0012-internal-metadata-for-user-facing-types.md)
- [ADR-0013 — Relationships, naming, and the rebuild-table strategy](0013-relationships-and-rebuild-table.md)
- [ADR-0014 — Data operations, value literals, and the auto-show pattern](0014-data-operations-and-value-model.md)
+- [ADR-0015 — Project storage runtime](0015-project-storage-runtime.md)
diff --git a/docs/requirements.md b/docs/requirements.md
index a292fcf..0aea38e 100644
--- a/docs/requirements.md
+++ b/docs/requirements.md
@@ -100,11 +100,12 @@ against it.
- [ ] **A1** All canonical app-level commands implemented and
available in both modes: `save`, `save as`, `load`, `new`,
- `export`, `import`, `seed`, `replay`, `undo`, `redo`, `mode`,
- `help`, `hint`, `quit`.
+ `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`
- and friends in track 2, `seed` in the seeding iteration, etc.)*
+ 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.)*
## DSL data commands
@@ -242,8 +243,23 @@ against it.
- [ ] **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 with confirmation and a change
- summary when present (per ADR-0004).
+ 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).
## Project file format (per ADR-0004)
@@ -318,6 +334,11 @@ against it.
## CLI
- [ ] **L1** Load a project via a positional CLI argument.
+- [ ] **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).
- [~] **L2** Submit a command alongside project load — deferred,
not v1.
@@ -408,6 +429,10 @@ necessarily qualitative, the criterion is named and the bar is
- [-] **N3** Cross-emulator visual regression coverage — per
ADR-0008. Crossterm abstracts terminals adequately; we revisit
only if a real regression surfaces.
+- [~] **N4** Global rolling input history (cross-session,
+ cross-project). Mentioned in I2's wording; deferred per
+ ADR-0015 §12 — project-scoped history (via `history.log`) is
+ the v1 surface. Revisit if real demand emerges.
---