From f4eedf3dd29c138f67da3fe25c9c0bbe6c8cbaf7 Mon Sep 17 00:00:00 2001 From: "claude@clouddev1" Date: Sun, 17 May 2026 09:22:49 +0000 Subject: [PATCH] chore: handoff --- docs/handoff/20260517-handoff-15.md | 266 ++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 docs/handoff/20260517-handoff-15.md diff --git a/docs/handoff/20260517-handoff-15.md b/docs/handoff/20260517-handoff-15.md new file mode 100644 index 0000000..41e897c --- /dev/null +++ b/docs/handoff/20260517-handoff-15.md @@ -0,0 +1,266 @@ +# Session handoff — 2026-05-17 (15) + +Fifteenth handover. This session implemented **ADR-0025 +(Indexes)** end to end — a fresh feature off the +`docs/requirements.md` roadmap — and fixed an input-history +navigation bug found while testing it. + +**Headline: indexes are a real DSL feature now.** `add index` +/ `drop index`, rebuild-safe, persisted in `project.yaml`, +shown in the structure view and a new nested items panel. +ADR-0025 is Accepted and fully implemented. The natural next +job is **QA1 — `EXPLAIN QUERY PLAN`** — now unblocked because +there is finally an index for a plan to use; see §4. + +## State at handoff + +**Branch:** `main`. Working tree clean. `origin/main` is at +`0dc159f` (the ADR-0025 commit — the user pushed it); local +HEAD is **1 commit ahead** (`4e4dbbf`, the history fix — +unpushed; the user pushes asynchronously). + +Commits since handoff-14: + +``` +4e4dbbf Input history: reset the nav cursor on every submit +0dc159f Indexes: add index / drop index, persistence, display (ADR-0025) +``` + +**Tests:** **1039 passing, 0 failing, 1 ignored** (`cargo +test` — up from 1006/1007). The ignored test is the +long-standing `` ```ignore `` doc-test in +`src/friendly/mod.rs`. The typing-surface matrix is now **152 +cells** (was 144). + +**Clippy:** clean with `nursery` lints + `-D warnings`. + +## §1. What shipped + +### ADR-0025 — indexes (`0dc159f`) + +A new ADR (`docs/adr/0025-indexes.md`, Accepted) and its full +implementation. The four design forks were settled by the user +before drafting: + +- **Naming:** optional, `add index [as ] on + ()`; auto-name `__idx` when omitted. + Mirrors the ADR-0013 `as`-name convention. +- **UNIQUE:** out of scope — plain non-unique indexes only; + `UNIQUE` stays a separate `C3` sub-item. +- **Items list (S2):** the left panel is now a nested tree — + each table with its index names indented beneath it. +- **`drop column`:** refuses an indexed column by default; a + new `--cascade` flag opts into dropping the covering indexes. + +What landed: + +- **Grammar** (`src/dsl/grammar/ddl.rs`): `add index` is a + third branch of `add`; `drop index` a fourth branch of + `drop`; both drop forms (`drop index ` / + `drop index on ()`). `drop column` gained an + optional `--cascade` flag. +- **AST** (`src/dsl/command.rs`): `Command::AddIndex` / + `Command::DropIndex`, an `IndexSelector` enum (`Named` / + `Columns`, mirroring `RelationshipSelector`), and a + `cascade: bool` field on `Command::DropColumn`. +- **db layer** (`src/db.rs`): `do_add_index` / `do_drop_index`; + index reads via the engine's native catalog + (`pragma_index_list` / `pragma_index_info`, origin `c` + only) — **no metadata table**, unlike relationships; + `IndexInfo` added to `TableDescription`; friendly errors for + duplicate name, redundant column set, missing table/column, + unknown / ambiguous index. +- **Rebuild safety:** `rebuild_table_with_copy` now captures a + table's indexes before the `DROP TABLE` and recreates them + after the rename — so `change column` and the relationship + operations no longer silently drop indexes. +- **`drop column --cascade`:** `do_drop_column` detects + covering indexes, refuses without the flag, drops them with + it. Returns a new `DropColumnResult { description, + dropped_indexes }`; the runtime reports each dropped index. +- **Persistence:** an additive `indexes:` list in + `project.yaml` (`version:` unchanged — older files default + it empty). `read_schema_snapshot` writes it, + `do_rebuild_from_text` recreates indexes on rebuild; + `export` / `import` carry them. +- **Display:** an `Indexes:` section in `render_structure`; + the nested items panel via a new `SchemaCache::table_indexes` + map. +- **Tests:** 30 new across tiers — 6 parser, 13 db + integration (real in-memory SQLite), 2 `output_render`, 1 + full project rebuild round-trip (`tests/iteration3_rebuild.rs`), + 8 typing-surface matrix cells (`tests/typing_surface/index_ops.rs`). + +### Input-history cursor fix (`4e4dbbf`) + +See §2. + +## §2. Bug found and fixed this session + +**Input-history cursor stranded on re-submit.** +`App::push_history` did its `history_cursor` / `history_draft` +reset *after* the consecutive-duplicate early-return. So +recalling a command with Up and re-submitting it unchanged (a +duplicate → not appended → reset skipped) left the cursor +pinned at that entry; the next Up then stepped *backwards* +from there (`Some(i) => i-1`) instead of restarting at the +newest entry — the next Up appeared to "skip" an entry. + +Fixed by moving the reset ahead of the early-return guards — +submitting a command always ends history navigation. Tier-1 +regression test +`app::tests::resubmitting_a_recalled_command_does_not_strand_the_cursor` +drives the recall/resubmit keystroke sequence. + +## §3. Notes for the next agent + +**Two ADR-0025 deviations are recorded** in that ADR's +`## Implementation notes` section — read them, the decision +text and the code agree there but the sketch above it does not: + +- `rename column` / `drop column` use native `ALTER TABLE`, + not the rebuild primitive. SQLite's `RENAME COLUMN` already + rewrites index definitions, so rename needs no index code; + `drop column` handles covering indexes directly. Only + `change column` (and the relationship ops) go through the + rebuild primitive, where the column set is unchanged, so + indexes are recreated verbatim — no rename-map needed. +- The S2 panel carries per-table index names in + `SchemaCache::table_indexes` rather than restructuring + `app.tables` and the `TablesRefreshed` event. + +**One defensively-unreachable branch.** `do_drop_index`'s +"more than one index matches these columns" arm cannot be +reached via the app's own commands — `do_add_index` refuses a +redundant index over an identical column set. It is reachable +only via a hand-edited `project.yaml`, which the project does +defend against elsewhere. Left in, untested. + +## §4. What's next — QA1 is unblocked + +`docs/requirements.md` was reconciled this session: the index +portion of `C3` is done, **`S2` moved to `[x]`**, the `QA1` +note updated, test baseline refreshed to 1039. + +**The natural next pick is `QA1` — `EXPLAIN QUERY PLAN`.** +Until this session there was no index for a plan to be +interesting about; now `show data where = ` +(the C5 filtered form, already implemented) is a query whose +plan visibly flips between a full scan and an index search. +But QA1 has an unmet dependency of its own: **`QA2`** (plan +rendering — tree layout, annotation taxonomy, colour scheme) +is `[~]` and **needs an ADR before any implementation**. So +QA1 is "write the QA2 ADR, then build" — not a self-contained +drop-in. **Prioritisation is a user product decision — ask, +don't assume.** + +Other open clusters in `requirements.md` (unchanged from +handoff-14 §4, minus indexes): + +- **Complex WHERE expressions** (`C5a`, `[~]`) — AND/OR / + comparison / LIKE in UPDATE/DELETE/show-data filters. Needs + an ADR. The bridge from DSL toward real SQL. +- **SQL in advanced mode** (`Q1`/`Q4`, `[~]`) — `sqlparser-rs` + + a defined subset. Needs an ADR. +- **Snapshot / undo** (`U1`/`U2`) — designed in ADR-0006, not + built. +- **m:n convenience** (`C4`), **modify relationship** (`C3a`, + `[~]`), **table rename** (`C1`). +- **Friendly error layer** (`H1`) — partial. **Syntax-help in + parse errors** (`H1a`) — piecemeal. +- **Session log + Markdown export** (`V4`, `[~]`), + **multi-line input** (`I1`), **readline shortcuts** + (`I1b`), **seeding** (`SD1`), **CI** (`TT5`), + **tutorial system** (`TU1`, `[~]`). + +## §5. Architectural delta (vs. handoff-14) + +### New `Command` variants / fields + +- `Command::AddIndex { name: Option, table, columns }`. +- `Command::DropIndex { selector: IndexSelector }`. +- `IndexSelector` enum — `Named` / `Columns`. +- `Command::DropColumn` gained `cascade: bool`. + +### New grammar surface + +- `IdentSource::Indexes` — completion source for existing + index names. +- `add` / `drop` `Choice`s gained index branches; + `drop column` gained the `--cascade` flag node. + +### New db surface + +- `IndexInfo` (public) — on `TableDescription::indexes`. +- `DropColumnResult { description, dropped_indexes }` — the + new return type of `Database::drop_column` / + `Request::DropColumn` / `do_drop_column`. +- `Request::AddIndex` / `DropIndex`; `Database::add_index` / + `drop_index`. +- `read_table_indexes` / `read_index_columns` helpers; + `do_add_index` / `do_drop_index`. +- `rebuild_table_with_copy` is now index-preserving. + +### New event / outcome / friendly surface + +- `AppEvent::DslDropColumnSucceeded { command, result }`; + `App::handle_dsl_drop_column_success`. +- `CommandOutcome::DropColumn(DropColumnResult)`. +- `friendly::Operation::AddIndex` / `DropIndex`. + +### `SchemaCache` + +- `indexes: Vec` — flat index-name list (for + `drop index ` completion). +- `table_indexes: HashMap>` — per-table + index names (for the S2 panel). + +### Persistence + +- `IndexSchema { name, table, columns }`; + `SchemaSnapshot.indexes`; a `indexes:` block in the + `project.yaml` writer + an optional `#[serde(default)]` + field in the reader. + +### Catalog + +- Added: `parse.usage.add_index`, `parse.usage.drop_index`, + `ok.index_dropped_with_column`. Modified: `help.ddl.add`, + `help.ddl.drop`. New keys registered in + `src/friendly/keys.rs`. + +### Tests + +- New matrix file `tests/typing_surface/index_ops.rs` (8 + cells) → 152 matrix cells total. + +## §6. How to take over + +1. **Read this file, then handoff-14, then 13** for the chain. +2. **Read `CLAUDE.md`** — the working-style rules. This + session escalated all four index design forks to the user + before drafting the ADR. +3. **Read `docs/requirements.md`** — reconciled this session; + the authoritative "what's open" tracker. +4. **Read `docs/adr/0025-indexes.md`** — including its + `## Implementation notes` (§3 above). +5. **Run `cargo test`** — 1039 passing, 0 failing, 1 ignored. +6. **Run `cargo clippy --all-targets -- -D warnings`** — clean. +7. **`4e4dbbf` is unpushed** — the user pushes; don't treat it + as blocking. +8. **Pick the next work from §4** — `QA1` is the natural pick + but it needs the `QA2` rendering ADR first, and + prioritisation is a user product decision. Ask, don't + assume. + +### Note on the typing-surface matrix + +`tests/typing_surface/` (now 152 cells) is the regression net +for everything walker/hint/completion. After any grammar or +walker change: a failing cell with *correct* new behaviour → +update its snapshot +(`INSTA_UPDATE=always cargo test --test typing_surface_matrix +`); a failing cell with *wrong* behaviour → the cell +earned its keep. Adding `add index` shifted one +`add_relationship` cell's snapshot this session (the `add ` +candidate set gained `index`) — that was a correct update.