feat(chat): inline question cards in the stream, house-style options, answered Q→A history #60

Merged
dominik.polakovics merged 1 commit from afk/56 into main 2026-07-09 00:04:44 +02:00

Closes #56

Questions the agent asks now live in the conversation, not over it — pending as an interactive card in the one scrollable chat pane, answered as a readable Q→A summary instead of a raw JSON tool chip.

Provider contract (ADR-0028)

  • An answered dialog tool stays a dialog message: provider.Dialog gains a nullable outcome (dismissed / per-question results{question, chosen, other_text} / plan approved+feedback), derived in dialogoutcome.go from the same compat §5 toolUseResult/toolDenialKind ground truth the verification backstop reads — no new Claude Code coupling.
  • deriveState reports question only while a trailing dialog is unanswered; internal/chat's dormant-fallback lastDialog skips answered dialogs so AnswerDialog can never target a resolved picker.
  • planDialog no longer truncates the plan — plans render in full (pending spool, pending transcript, and history alike).
  • ADR-0020 decision 5 stands: pending dialogs stay out of the message stream server-side (pending_dialog side channel); placement is purely client-side.

Chat UI

  • Pending: interactive card inline in the stream (deduped by tool_id against a matching stream message — never rendered twice); composer collapses to a waiting note + Interrupt, textarea returns on resolve. No max-height scrollboxes — multi-question forms and full plan markdown flow naturally, approve/reject after the whole plan. A newly arriving card scrolls its top (the question text) into view only when the viewer is following the live tail; scrolled-up readers keep their place (jump pill unchanged).
  • Options: full-width rectangular cards on --radius-control — bold label, muted description now visible on all three picker paths (the flat multi-select path previously dropped it), accent border + check when selected, ≥44px targets, no pill radius/tooltips/truncation. Interaction semantics unchanged (single-select taps to answer, gated Submit on multi paths, busy disable, reset on tool_id change).
  • Answered history: compact inert Q→A pairs (chosen labels; typed Other text as a quote; a dismissed marker for denial/interrupt/timeout); plan history shows the full plan + approved/rejected(+feedback) marker.

Verification

  • go test ./... and go test -tags ui ./...: green (the internal/tmuxx integration tests fail in this sandbox on clean main too — environmental, no tmux sessions available). gofmt/go vet clean. New coverage: TestParseTranscript_answeredDialogOutcomes, TestParseTranscript_planPromptUntruncated, extended state edges, and TestCompat_LiveTranscripts_resolutionShapes re-pinned against the captured live 2.1.198 fixtures (answered single/multi-question, other-text, timeout, plan approve/reject-with-feedback).
  • Web (CI parity): npm run lint, format:check, npm test (34 files, 475 tests), npm run build — all green; go build -tags ui with the embedded dist OK. New tests cover the inline card (position, dedupe, resolve-returns-textarea), waiting-note composer, arrival scroll both ways, all answered-summary shapes, and the restyled toggles with visible descriptions.
  • golangci-lint isn't installable in this sandbox — the CI job runs it authoritatively.
  • No browser runs here: a visual pass on mobile (card arrival scroll under the sticky header, option card spacing, dark mode tint) is worth an eyeball on the deployed build.

🤖 Generated with Claude Code

https://claude.ai/code/session_01X9qAh94KTrYTSUoWYkUPpk

Closes #56 Questions the agent asks now live **in the conversation**, not over it — pending as an interactive card in the one scrollable chat pane, answered as a readable Q→A summary instead of a raw JSON tool chip. ## Provider contract (ADR-0028) - An answered dialog tool stays a **dialog message**: `provider.Dialog` gains a nullable `outcome` (`dismissed` / per-question `results{question, chosen, other_text}` / plan `approved`+`feedback`), derived in `dialogoutcome.go` from the same compat §5 `toolUseResult`/`toolDenialKind` ground truth the verification backstop reads — no new Claude Code coupling. - `deriveState` reports `question` only while a trailing dialog is **unanswered**; `internal/chat`'s dormant-fallback `lastDialog` skips answered dialogs so `AnswerDialog` can never target a resolved picker. - `planDialog` no longer truncates the plan — plans render in full (pending spool, pending transcript, and history alike). - ADR-0020 decision 5 stands: pending dialogs stay out of the message stream server-side (`pending_dialog` side channel); placement is purely client-side. ## Chat UI - **Pending**: interactive card inline in the stream (deduped by `tool_id` against a matching stream message — never rendered twice); composer collapses to a waiting note + Interrupt, textarea returns on resolve. No `max-height` scrollboxes — multi-question forms and full plan markdown flow naturally, approve/reject after the whole plan. A newly arriving card scrolls its **top** (the question text) into view only when the viewer is following the live tail; scrolled-up readers keep their place (jump pill unchanged). - **Options**: full-width rectangular cards on `--radius-control` — bold label, muted description now visible on **all three** picker paths (the flat multi-select path previously dropped it), accent border + check when selected, ≥44px targets, no pill radius/tooltips/truncation. Interaction semantics unchanged (single-select taps to answer, gated Submit on multi paths, busy disable, reset on `tool_id` change). - **Answered history**: compact inert Q→A pairs (chosen labels; typed Other text as a quote; a dismissed marker for denial/interrupt/timeout); plan history shows the full plan + approved/rejected(+feedback) marker. ## Verification - `go test ./...` and `go test -tags ui ./...`: green (the `internal/tmuxx` integration tests fail in this sandbox on clean main too — environmental, no tmux sessions available). `gofmt`/`go vet` clean. New coverage: `TestParseTranscript_answeredDialogOutcomes`, `TestParseTranscript_planPromptUntruncated`, extended state edges, and `TestCompat_LiveTranscripts_resolutionShapes` re-pinned against the captured live 2.1.198 fixtures (answered single/multi-question, other-text, timeout, plan approve/reject-with-feedback). - Web (CI parity): `npm run lint`, `format:check`, `npm test` (34 files, 475 tests), `npm run build` — all green; `go build -tags ui` with the embedded dist OK. New tests cover the inline card (position, dedupe, resolve-returns-textarea), waiting-note composer, arrival scroll both ways, all answered-summary shapes, and the restyled toggles with visible descriptions. - golangci-lint isn't installable in this sandbox — the CI job runs it authoritatively. - No browser runs here: a visual pass on mobile (card arrival scroll under the sticky header, option card spacing, dark mode tint) is worth an eyeball on the deployed build. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01X9qAh94KTrYTSUoWYkUPpk
feat(chat): inline question cards, house-style options, answered Q→A history (#56)
All checks were successful
ci / native (pull_request) Successful in 4m32s
de2ab20744
Backend (provider chat contract, ADR-0028): an answered dialog tool
(AskUserQuestion / ExitPlanMode) stays a dialog message instead of
demoting to a raw tool chip. provider.Dialog gains a nullable Outcome
(DialogOutcome{dismissed, results, approved, feedback} with
QuestionResult{question, chosen, other_text}), derived at parse time in
dialogoutcome.go from the compat §5 toolUseResult/toolDenialKind ground
truth — a second reader of an already-pinned coupling. deriveState
reports question only for an unanswered trailing dialog; internal/chat's
dormant-fallback lastDialog skips answered dialogs; planDialog no longer
truncates the plan markdown.

Web: the pending dialog renders as an interactive card inline in the
chat stream (at its transcript message's position when one matches by
tool_id, else appended — never twice), with the composer collapsed to a
waiting note + Interrupt; no nested scrollboxes — the chat pane is the
only scroll container, plans render in full with the actions at the end;
a newly arriving card scrolls its top into view only when following the
live tail. Options move off the seg pill onto full-width card buttons
(control radius, bold label, always-visible muted description on all
three picker paths, accent border + check when selected, 44px targets).
Answered dialogs render as compact inert Q→A summaries (per-question
pairs, quoted other-text, dismissed marker); interaction semantics are
unchanged (tap-to-answer single-select, gated Submit, busy disable,
reset on tool_id change).

Closes #56

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X9qAh94KTrYTSUoWYkUPpk
Sign in to join this conversation.
No reviewers
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!60
No description provided.