11 KiB
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…>_idxwhen omitted. Mirrors the ADR-0013as-name convention. - UNIQUE: out of scope — plain non-unique indexes only;
UNIQUEstays a separateC3sub-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--cascadeflag opts into dropping the covering indexes.
What landed:
- Grammar (
src/dsl/grammar/ddl.rs):add indexis a third branch ofadd;drop indexa fourth branch ofdrop; both drop forms (drop index <name>/drop index on <T> (<cols>)).drop columngained an optional--cascadeflag. - AST (
src/dsl/command.rs):Command::AddIndex/Command::DropIndex, anIndexSelectorenum (Named/Columns, mirroringRelationshipSelector), and acascade: boolfield onCommand::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, originconly) — no metadata table, unlike relationships;IndexInfoadded toTableDescription; friendly errors for duplicate name, redundant column set, missing table/column, unknown / ambiguous index. - Rebuild safety:
rebuild_table_with_copynow captures a table's indexes before theDROP TABLEand recreates them after the rename — sochange columnand the relationship operations no longer silently drop indexes. drop column --cascade:do_drop_columndetects covering indexes, refuses without the flag, drops them with it. Returns a newDropColumnResult { description, dropped_indexes }; the runtime reports each dropped index.- Persistence: an additive
indexes:list inproject.yaml(version:unchanged — older files default it empty).read_schema_snapshotwrites it,do_rebuild_from_textrecreates indexes on rebuild;export/importcarry them. - Display: an
Indexes:section inrender_structure; the nested items panel via a newSchemaCache::table_indexesmap. - 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 columnuse nativeALTER TABLE, not the rebuild primitive. SQLite'sRENAME COLUMNalready rewrites index definitions, so rename needs no index code;drop columnhandles covering indexes directly. Onlychange 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_indexesrather than restructuringapp.tablesand theTablesRefreshedevent.
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 }.IndexSelectorenum —Named/Columns.Command::DropColumngainedcascade: bool.
New grammar surface
IdentSource::Indexes— completion source for existing index names.add/dropChoices gained index branches;drop columngained the--cascadeflag node.
New db surface
IndexInfo(public) — onTableDescription::indexes.DropColumnResult { description, dropped_indexes }— the new return type ofDatabase::drop_column/Request::DropColumn/do_drop_column.Request::AddIndex/DropIndex;Database::add_index/drop_index.read_table_indexes/read_index_columnshelpers;do_add_index/do_drop_index.rebuild_table_with_copyis 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 (fordrop index <name>completion).table_indexes: HashMap<String, Vec<String>>— per-table index names (for the S2 panel).
Persistence
IndexSchema { name, table, columns };SchemaSnapshot.indexes; aindexes:block in theproject.yamlwriter + 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 insrc/friendly/keys.rs.
Tests
- New matrix file
tests/typing_surface/index_ops.rs(8 cells) → 152 matrix cells total.
§6. How to take over
- Read this file, then handoff-14, then 13 for the chain.
- Read
CLAUDE.md— the working-style rules. This session escalated all four index design forks to the user before drafting the ADR. - Read
docs/requirements.md— reconciled this session; the authoritative "what's open" tracker. - Read
docs/adr/0025-indexes.md— including its## Implementation notes(§3 above). - Run
cargo test— 1039 passing, 0 failing, 1 ignored. - Run
cargo clippy --all-targets -- -D warnings— clean. 4e4dbbfis unpushed — the user pushes; don't treat it as blocking.- Pick the next work from §4 —
QA1is the natural pick but it needs theQA2rendering 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.