13 KiB
Session handoff — 2026-05-31 (53)
Fifty-third handover. A two-issue pair off handoff-52's open list. Closed #15 + #16 in a single commit — they share one piece of infrastructure (a curated SQL-function-name list), exactly as handoff-52 §5 predicted ("do together"). One ADR amendment authored: ADR-0022 Amendment 6, plus an ADR-0031 status note.
The session is worth reading for one process save: the
implementation /runda pass (DA hat) found a real duplicate-candidate
defect that the green test suite had not — a column named like a
function (count, date) was listed twice in the completion line. It
also caught a clippy regression I'd introduced and a whitespace/EOF
wart. None of these were visible in "tests pass." See §2.
§1. State at handoff
Branch: main. HEAD 6d8c9ee. Ahead of origin/main by
2 — handoff-52's 8311de4 (#9) was still unpushed at the start of
this session, and 6d8c9ee (this session) is on top. Tests: 2107
passing / 0 failing / 1 ignored (full cargo test; the lib-only
figure is 1538 — see the baseline note below). The 1 ignored is
the same long-standing ```ignore doctest in src/friendly/mod.rs
as every prior baseline. Clippy: clean (nursery group). Push is the
user's step.
Commit since handoff-52's 8311de4:
6d8c9ee feat: curated SQL function list — Tab completion (#15) + typing-time typo hint (#16)
Test-baseline bookkeeping (read once). requirements.md's "Test
baseline" had drifted: it still read 1131 (the ADR-0027-era lib
figure) because the intervening issue fixes #8/#13/#12/#7/#9 landed
tests without bumping it — the doc historically moves the number at ADR
milestones, not per issue. With the user's confirmation this session
refreshed it to 1538 lib / 2107 full, with an Amendment-6 entry
appended to the running list and a note explaining the prior drift.
Going forward the two figures are: cargo test --lib = 1538,
cargo test (all binaries) = 2107.
§2. The process save that paid off (read this)
Both issues went: read issue bodies + the ADRs they name → trace the
exact symbols (completion.rs, sql_expr.rs, the lockdown tests) →
escalate the genuine design choices to the user (function set,
colour, ADR-or-not) → test-first build → /runda on the
implementation → fix findings red-first → commit.
The implementation /runda (DA hat) is where the value was. Three
findings, none visible in a green suite:
- Duplicate-candidate defect (real). A column named exactly like a
function — a column called
countordate, both plausible — was listed twice in the completion line (count/Identifier+count/Function), distinguishable only by colour. The existing code deduped identifiers-vs-keywords but I had added no functions-vs-identifiers dedup. Fixed:functions.retain(|f| !identifiers.iter().any(|i| i.eq_ignore_ascii_case(f)))— the column wins (same spirit as the keyword-wins rule), case-insensitive so aCountcolumn also suppresses thecountfunction. Proven red-first: the new test fails without the retain, printing the exact[count/Identifier, count/Function]duplication. - #16 fix not yet proven load-bearing. Reverted the
invalid_identchange and confirmed the typo test FAILs without it — the fix is doing the work, not coinciding with another path. - Clippy regression + EOF whitespace I'd introduced (a
field_reassign_with_defaultin the new test, and stray trailing blank lines from a throwaway-probe removal). Both fixed; EOF normalised to be byte-identical to HEAD rather than sneaking in a cleanup of the pre-existing trailing-blank convention.
Lesson reinforced (matches handoff-52 §2.2): /runda on the
implementation, not just the design. A green suite is necessary, not
sufficient.
§3. Resolved issues
#15 + #16 — curated SQL function list (6d8c9ee, ADR-0022 Amendment 6)
Both hinge on the same gap: the advanced-mode SQL expression grammar
(ADR-0031 §1) admits a function-call shape at its sql_expr_ident
slot but, by design, does not know which names are real functions (the
slot is IdentSource::Columns, optimised for the column-reference
common case; the walker is a structural matcher, not an evaluator). So
both issues wanted the same thing — one source of truth for "what SQL
function names does this playground recognise."
New file — src/dsl/sql_functions.rs:
KNOWN_SQL_FUNCTIONS— 23 names, sorted + lowercase (a pinned invariant). The broader curated set (user choice): aggregates (count/sum/avg/min/max), common scalars (length/upper/lower/trim/substr/coalesce/abs/round), and a broader scalar tier (date/datetime/strftime/hex/ifnull/nullif/replace/instr/typeof/random).is_known_function_prefix(partial)— case-insensitive prefix test, empty prefix matches all.castdeliberately excluded — a faithfulness finding during build: SQLite'sCAST(expr AS type)is not a plainname(args)call the expression grammar parses (expr AS typeis not a valid argument expression), so offeringcastwould surface a candidate that cannot complete. Documented + test-locked (cast_is_excluded,cast_is_not_offered_as_a_function_candidate).
#15 — Tab completion (completion.rs, "Source 1.8"): at a
sql_expr_ident slot the curated functions are offered as candidates
(prefix-filtered like every other source), ordered after keywords/types
(a learner reads clause keywords first, then discovers callables). New
CandidateKind::Function + a ninth Theme colour tok_function (a
blue, distinct from keyword purple / identifier teal / type pink-magenta
in both dark() and light()) — parallel to how #8 gave types
tok_type. ui.rs maps the new kind.
#16 — typing-time typo hint restored (completion.rs,
invalid_ident_at_cursor): the issue-#6 fix had dropped the "no such
column" flag wholesale at sql_expr_ident slots (to stop the false
positive on names like sum). Now it bails only when the partial
prefix-matches a known function; otherwise it falls through to the
existing schema-column check. So select Agx (no FROM yet — schema
diagnostic silent) warns at typing time again, while select sum stays
quiet. The issue-#6 lockdown tests
(genuine_column_typo_in_complete_select_…,
advanced_select_partial_function_name_not_flagged_…) still pass, and
the submit-time unknown_column diagnostic path is untouched.
Dedup (the DA finding, §2): a column named like a function appears once; the column wins.
Coverage added (15 tests):
sql_functions::{list_is_sorted_and_lowercase, list_has_no_duplicates, cast_is_excluded, prefix_match_is_case_insensitive, empty_prefix_matches_all, unknown_prefix_does_not_match};
completion::{sql_expr_slot_offers_known_function_candidates, projection_slot_offers_known_function_candidates, sql_function_candidates_filter_by_prefix, sql_function_candidates_carry_function_kind, function_candidates_absent_at_non_expression_slots, function_candidate_deduped_against_a_like_named_column, cast_is_not_offered_as_a_function_candidate, invalid_ident_fires_for_genuine_typo_at_sql_expr_slot, invalid_ident_does_not_fire_for_function_prefix_at_sql_expr_slot, invalid_ident_does_not_fire_for_column_prefix_at_sql_expr_slot};
input_render::advanced_select_genuine_column_typo_before_from_warns_at_typing_time;
theme::function_colour_is_distinct_from_keyword_identifier_and_type.
Mode-gating confirmed sound (DA probe): DSL where-expressions use
role expr_column; only SQL uses sql_expr_ident. Functions therefore
never leak into simple mode. Slot coverage probed empirically —
functions surface at WHERE, comparison RHS, HAVING, projection list,
and post-AND.
§4. ADR / docs work
- ADR-0022 Amendment 6 — new (
6d8c9ee): the curated function list, theCandidateKind::Function+tok_functioncolour, the #16 invalid-ident narrowing, the dedup, and the no-validation-allowlist posture (the list drives completion + the typo hint only, never parse-time acceptance — an unknown function still parses and surfaces an engine-neutral execution error, preserving ADR-0031 §6/§7). Coverage list inline. - ADR-0031 status note (
6d8c9ee): records that Amendment 6 layers the known-function list on top of thesql_expr_identslot — grammar unchanged, no-allowlist posture intact; softens §5's "function names are not completed" line to "completed from a curated pedagogical list, not a validation allowlist." - README index (
6d8c9ee): ADR-0022 entry gains the Amendment-6 paragraph; ADR-0031 entry gains the status-note reference. - requirements.md (
6d8c9ee): I3 (tab-completion) + I4 (highlighting) refinement notes for #15; test baseline refreshed (see §1).
§5. What's open
Bug reports still open (filed handoff-50)
| # | Title | Label |
|---|---|---|
| 10 | [error] tag colour identical to [system] (ADR-0037 gap) |
enhancement |
| 11 | Copy output panel contents to the system clipboard | enhancement |
| 14 | --resume should restore the last-used input mode |
enhancement |
Closed this session: #15, #16 (commit 6d8c9ee — close them on
GitHub once pushed).
Categorisation / notes for pickup
- #10 (
[error]/[system]tag colour) is the most adjacent and was already flagged "most adjacent" in handoff-52: ADR-0040 kept the[error]tag on failure-reason lines, so the colour collision is still live. Needs an ADR-0037 amendment (it owns output tag styling). Smallest of the three; a clean next pick. - #14 ties to ADR-0015 Iter 6 (
--resume+ persistent input history); small, builds on the existinglast_projectmachinery. - #11 clipboard copy — self-contained but pulls in a new cross-platform clipboard crate (most surface area of the three).
Other tracks (from requirements.md)
- Track 2 project storage Iter 5/6 — export/import +
--resume+ persistent input history + migration framework. - C3a (modify relationship), C4 (m:n convenience).
- H1 friendly DB-error layer (partial); tutorial/lesson system (needs its own ADR); V4 session log + Markdown export; I1/I1b multi-line + readline; I3 tab-completion polish (this session advanced it for SQL function names); I4 highlighting beyond input echo.
§6. Process pins (carried forward + this session's reinforcement)
/rundaon the implementation, not just the design. This session's save (§2): the impl pass found a real duplicate-candidate defect, a clippy regression, and an EOF whitespace wart — none visible in a green suite. Held from handoff-52 §2.2.- Red-first proof for every fix. Each fix this session was proven by reverting it and watching the relevant test FAIL before restoring. (Global rule: reproduce bugs with a failing test first.)
- Escalate genuine design choices — do not decide for the user.
The function set, the candidate colour, and whether to write an ADR
were all surfaced via
AskUserQuestionbefore building; the stale test-baseline refresh was also confirmed, not assumed. - Faithfulness over convenience.
castlooks like an obvious member of "common scalars" but does not fit the call-shape grammar — excluded with a documented reason + a lock test, rather than shipped as a candidate that fails to complete. - Match HEAD, don't reformat unrelated things. The EOF fix restored the file's pre-existing trailing-blank convention byte-for-byte rather than "tidying" it — keeps the diff to the actual change (the user's append-only / no-gratuitous-reformat git philosophy).
- Reproduce-first (handoff-51 §2.1) and no easy-way-out on architectural discrepancies (handoff-51 §2.2): both still stand.
- Push is the user's step. Commits and pushes are decoupled;
unpushed commits are a normal working state. Two are unpushed now
(
8311de4,6d8c9ee).
§7. How to take over
- Read this note, then
CLAUDE.md, thendocs/requirements.md(note the refreshed baseline), thendocs/adr/README.md. - The codebase is on
mainat6d8c9ee, working tree clean, 2 commits ahead of origin (both unpushed — see §1). - If continuing the bug-list run: #10 is the smallest and most adjacent next pick (needs an ADR-0037 amendment for output tag styling); then #14, then #11.
- The tooling note from earlier this session: an intermittent empty-output bug in a newer Claude Code build was the cause of the "Bash/Read returning nothing" symptom — the user reverted to a working build mid-session. If output goes blank again, suspect the harness, not the repo.
- Honour the process pins in §6 —
/rundaon implementation + red-first proof are the two that earned their keep this session.