# 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: 1. **Duplicate-candidate defect (real).** A column named exactly like a function — a column called `count` or `date`, 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 a `Count` column also suppresses the `count` function. Proven **red-first**: the new test fails without the retain, printing the exact `[count/Identifier, count/Function]` duplication. 2. **#16 fix not yet proven load-bearing.** Reverted the `invalid_ident` change and confirmed the typo test **FAILs** without it — the fix is doing the work, not coinciding with another path. 3. **Clippy regression + EOF whitespace** I'd introduced (a `field_reassign_with_default` in 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. - **`cast` deliberately excluded** — a faithfulness finding during build: SQLite's `CAST(expr AS type)` is **not** a plain `name(args)` call the expression grammar parses (`expr AS type` is not a valid argument expression), so offering `cast` would 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, the `CandidateKind::Function` + `tok_function` colour, 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* the `sql_expr_ident` slot — 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](https://github.com/oliversturm/rdbms-playground/issues/10) | `[error]` tag colour identical to `[system]` (ADR-0037 gap) | enhancement | | [11](https://github.com/oliversturm/rdbms-playground/issues/11) | Copy output panel contents to the system clipboard | enhancement | | [14](https://github.com/oliversturm/rdbms-playground/issues/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 existing `last_project` machinery. - **#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) - **`/runda` on 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 `AskUserQuestion` before building; the stale test-baseline refresh was also confirmed, not assumed. - **Faithfulness over convenience.** `cast` looks 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 1. Read this note, then `CLAUDE.md`, then `docs/requirements.md` (note the refreshed baseline), then `docs/adr/README.md`. 2. The codebase is on `main` at `6d8c9ee`, working tree clean, **2 commits ahead of origin** (both unpushed — see §1). 3. 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**. 4. 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. 5. Honour the process pins in §6 — `/runda` on implementation + red-first proof are the two that earned their keep this session.