chore: handoff
This commit is contained in:
@@ -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 <name>] on <T>
|
||||||
|
(<cols>)`; auto-name `<Table>_<col…>_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 <name>` /
|
||||||
|
`drop index on <T> (<cols>)`). `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 <T> where <col> = <val>`
|
||||||
|
(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<String>, 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<String>` — flat index-name list (for
|
||||||
|
`drop index <name>` completion).
|
||||||
|
- `table_indexes: HashMap<String, Vec<String>>` — 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
|
||||||
|
<family>`); 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.
|
||||||
Reference in New Issue
Block a user