Chat: multi-select question answers never commit (batched picker keys dropped); no free-text Other in multi-select #64

Closed
opened 2026-07-09 01:40:56 +02:00 by dominik.polakovics · 1 comment

Symptoms (field report, 2026-07-09)

On any multi-select AskUserQuestion card — flat or inside a multi-question form, with or without Other involved:

  • selecting options and pressing Submit does nothing; the button re-enables after a moment and the card stays pending forever;
  • the synthesized "Other" row renders as a plain toggle with no text input, although Claude Code itself offers free text there.

Root cause 1 — the picker drops batched key bursts (the "submit does nothing")

multiSelectOps packed the walk onto the unnumbered Submit row into ONE tmux call: send-keys Down Down Down Down. Driven live on 2.1.198: a 4-key burst in one send-keys call moves the picker cursor zero rows (a 2-key burst landed once — the drop is burst-size/timing dependent). The recipe's keyDelay pacing only ever applied BETWEEN ops, so the walk never reached the Submit row and the closing Enters merely toggled the top option off/on — the dialog hung unanswered. Single-select recipes share the pattern but typical indices (0–2) stay under the drop threshold, which is why only multi-select visibly broke.

Live disproof and fix proof (real 2.1.198 TUI, tmux, transcript ground truth):

  • old recipe [Space][Down×4 batched][Enter][Enter] → cursor frozen, picker hung;
  • new recipe [Space][Down][Down][Down][Down][Enter][Enter] (one key per op, paced) → committed, recorded {"Which toppings?":"Olives"}.

Root cause 2 — Other-in-multi-select was rejected on a stale "uncaptured path" premise

normSelected 409'd any answer toggling the Other row ("uncaptured TUI path"), and the SPA rendered the row as a bare toggle with no input — so even the error path read as "nothing happens" (the 409 only surfaced in the top-of-page banner). Captured live 2026-07-09: bracketed-pasting text onto the "Type something" row fills it AND checks it in one move — recorded "Onions, no anchovies". (Two hazards pinned: Space on that row types a literal leading space into the field; a newline in the pasted text records as literal \r.)

Fix

  • every picker-navigation key is now its own op, so keyDelay paces every key (compat §7 universal rule, snapshots updated);
  • multi-select answers accept other_text end-to-end: adapter fills the free-text row via paste (its index never rides selected), intent backstop expects the text as one more ", " segment, web card (flat + form) renders the Other toggle + input with Submit gated on non-empty text; other_text alone is a valid answer;
  • other_text/plan-feedback validated single-line at the door (the \r corruption above).

Compat §7 re-pinned with the 2026-07-09 live session (five committed rounds).

## Symptoms (field report, 2026-07-09) On any multi-select AskUserQuestion card — flat or inside a multi-question form, with or without Other involved: - selecting options and pressing Submit does nothing; the button re-enables after a moment and the card stays pending forever; - the synthesized "Other" row renders as a plain toggle with no text input, although Claude Code itself offers free text there. ## Root cause 1 — the picker drops batched key bursts (the "submit does nothing") `multiSelectOps` packed the walk onto the unnumbered Submit row into ONE tmux call: `send-keys Down Down Down Down`. Driven live on 2.1.198: a 4-key burst in one send-keys call moves the picker cursor **zero rows** (a 2-key burst landed once — the drop is burst-size/timing dependent). The recipe's `keyDelay` pacing only ever applied BETWEEN ops, so the walk never reached the Submit row and the closing Enters merely toggled the top option off/on — the dialog hung unanswered. Single-select recipes share the pattern but typical indices (0–2) stay under the drop threshold, which is why only multi-select visibly broke. Live disproof and fix proof (real 2.1.198 TUI, tmux, transcript ground truth): - old recipe `[Space][Down×4 batched][Enter][Enter]` → cursor frozen, picker hung; - new recipe `[Space][Down][Down][Down][Down][Enter][Enter]` (one key per op, paced) → committed, recorded `{"Which toppings?":"Olives"}`. ## Root cause 2 — Other-in-multi-select was rejected on a stale "uncaptured path" premise `normSelected` 409'd any answer toggling the Other row ("uncaptured TUI path"), and the SPA rendered the row as a bare toggle with no input — so even the error path read as "nothing happens" (the 409 only surfaced in the top-of-page banner). Captured live 2026-07-09: **bracketed-pasting text onto the "Type something" row fills it AND checks it in one move** — recorded `"Onions, no anchovies"`. (Two hazards pinned: Space on that row types a literal leading space into the field; a newline in the pasted text records as literal `\r`.) ## Fix - every picker-navigation key is now its own op, so `keyDelay` paces every key (compat §7 universal rule, snapshots updated); - multi-select answers accept `other_text` end-to-end: adapter fills the free-text row via paste (its index never rides `selected`), intent backstop expects the text as one more `", "` segment, web card (flat + form) renders the Other toggle + input with Submit gated on non-empty text; `other_text` alone is a valid answer; - `other_text`/plan-feedback validated single-line at the door (the `\r` corruption above). Compat §7 re-pinned with the 2026-07-09 live session (five committed rounds).
Author
Owner

This was generated by AI while landing a PR.

Land-PR audit for PR #65 (fix(chat): multi-select answers commit again — per-key picker walk + free-text Other in multi-select)

Verdict: PASS (after one mechanical fix pushed to the head branch).

What was checked:

  • CI: the original head 60df8f3 failed the required ci / native check in 53s — root cause was prettier --check on web/src/routes/RunChat.tsx (a single line-wrap of the multiAnswerReady arrow). Fixed with prettier --write, pushed as 5025421 (style(web): prettier-format the multiAnswerReady arrow). CI on the new head: success — that green vouches for eslint, prettier, vitest, vite build, go build/go test -tags ui, and golangci-lint.
  • Local reproduction (before the push): eslint clean, vitest 496/496, vite build clean, go build -tags ui OK, go test -tags ui ./... exit 0 across all 30 packages (tmuxx integration tests included).
  • Diff review: matches issue #64's two root causes end-to-end. One named key per op (downOps) across all picker recipes; other_text accepted for multi-select (flat + per-question) with the Other row's index barred from selected; other_text-alone valid; single-line validation at the door (validateOtherText) used consistently by both the keystroke and intent paths (question Other, plan feedback); sameAnswer multiset compare degrades identically on both sides for text containing ", ". Web card + form gate Submit on non-empty Other text and strip the Other index from the payload. Compat snapshots pin the new recipes with the 2026-07-09 live evidence.
  • Conventions: Conventional-Commits title, working Closes #64, merge into main is conflict-free (verified via git merge-tree against 2b2e935).

No blockers remain. Merge awaits the human's free-text go-ahead.

> *This was generated by AI while landing a PR.* **Land-PR audit for PR #65** (`fix(chat): multi-select answers commit again — per-key picker walk + free-text Other in multi-select`) **Verdict: PASS** (after one mechanical fix pushed to the head branch). What was checked: - **CI**: the original head `60df8f3` failed the required `ci / native` check in 53s — root cause was `prettier --check` on `web/src/routes/RunChat.tsx` (a single line-wrap of the `multiAnswerReady` arrow). Fixed with `prettier --write`, pushed as `5025421` (`style(web): prettier-format the multiAnswerReady arrow`). CI on the new head: **success** — that green vouches for eslint, prettier, vitest, vite build, `go build`/`go test -tags ui`, and golangci-lint. - **Local reproduction** (before the push): eslint clean, vitest 496/496, vite build clean, `go build -tags ui` OK, `go test -tags ui ./...` exit 0 across all 30 packages (tmuxx integration tests included). - **Diff review**: matches issue #64's two root causes end-to-end. One named key per op (`downOps`) across all picker recipes; `other_text` accepted for multi-select (flat + per-question) with the Other row's index barred from `selected`; `other_text`-alone valid; single-line validation at the door (`validateOtherText`) used consistently by both the keystroke and intent paths (question Other, plan feedback); `sameAnswer` multiset compare degrades identically on both sides for text containing `", "`. Web card + form gate Submit on non-empty Other text and strip the Other index from the payload. Compat snapshots pin the new recipes with the 2026-07-09 live evidence. - **Conventions**: Conventional-Commits title, working `Closes #64`, merge into `main` is conflict-free (verified via `git merge-tree` against `2b2e935`). No blockers remain. Merge awaits the human's free-text go-ahead.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab#64
No description provided.