Repository navigation
Redesign Ask Trueup as a professional chat interface - #23
Merged
Merged
Conversation
The chat feature worked correctly (a real pass a day earlier fixed the tool-trace bug, added auto-scroll, markdown rendering, and the streaming composer) but was never given a real visual pass -- that commit touched zero layout CSS. Investigation found the "very basic" feel had one root structural cause plus several real, specific gaps, not vague polish: - The flex height chain was broken: .tu-app-layout__main had no min-height: 0, so the composer never pinned to the bottom and the transcript never scrolled internally -- the auto-scroll logic that already existed was a silent no-op. Fixed properly: making .tu-app-layout__main and every chat-owned ancestor a real flex container using flex: 1 at each level, not height: 100% -- a flex item's flex-resolved size doesn't count as a "definite height" for a percentage-height child to resolve against (a real, easy-to-miss CSS gotcha, caught by inspecting live computed styles in the browser, not assumed from the diff -- the first height:100% attempt looked plausible and was still wrong two levels down). - Bubble differentiation was too weak (~4 hex points apart). Redesigned: assistant replies render as flowing ink-colored text, not a bubble -- grounded in design-system.md's own "one authoritative voice" framing of the ink token, not a generic chat-bubble default -- marked by a small circular monogram reusing the wordmark's serif letterform. User messages stay a bounded, right-aligned bubble. - Two chat-specific error codes (daily_chat_query_cap_exceeded, chat_turn_in_progress) were unmapped, so a customer hitting the daily cap saw "Something went wrong." Mapped both. - The stream error state had no retry -- ErrorState only renders "Try again" when onRetry is passed, and it wasn't. Wired up, resending the same text without re-appending a duplicate user bubble. - The composer disabled while streaming, so you couldn't compose your next question. Decoupled: the field stays typeable throughout, only the send/stop control changes; the stop icon is now a real stop glyph, not x's cancel/dismiss shape. - Raw tool names (execute_read_only_sql) leaked into "How this was answered" -- not appropriate on a financial platform's customer-facing surface. Humanized both known tool names. - listSessions/getMessages existed but were unused -- every page visit destroyed the conversation. useChatSession now resumes the most recent session on mount instead of always creating a fresh one, and a new SessionHistory sidebar (basic, per the approved scope: session metadata only, no preview text -- the backend doesn't return one) lets a customer switch sessions or start a new one explicitly. Switching resets useChatStream's state so a stale error/streaming state from the previous session can't bleed into the newly-selected transcript. - The markdown renderer had no table support, but the system prompt has no formatting guidance and gpt-4o-mini commonly emits tables unprompted for multi-row data. Added GFM pipe-table parsing, rendered through the app's real Table compound component, and added one line to the system prompt asking for tables on multi-row results. Verified against the real running app with genuine end-to-end OpenAI streaming (a real API key is configured locally, confirmed -- not the FakeAgentAdapter test double): the empty state, real streaming with the transcript actually auto-scrolling now, the table-rendering path (verified via the parsed React element tree -- paragraph/Table/ paragraph -- since this test account has no multi-row account data for the model to report on honestly), the humanized tool trace, typing a follow-up while a reply streams (confirmed the text survives streaming intact), starting a new chat, switching between sessions with the correct one restoring its real messages, light and dark themes (dark mode's avatar colors confirmed via computed styles, not eyeballed from a screenshot), 375px mobile including the sidebar's open/closed state (caught and fixed a real overlap bug there: .tu-chat-window__body needed flex-direction: column on mobile so an open sidebar stacks above the transcript instead of fighting it for row space), and keyboard-only tab order with visible focus throughout. Confirmed no regression on another page (Dashboard) after making .tu-app-layout__main a flex container. Backend: tests/api/test_chat.py 12/12, ruff clean. Frontend: lint and build clean, 246 modules. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Ask Trueup worked correctly (a real pass a day earlier fixed the tool-trace field-name bug, added auto-scroll, markdown rendering, and the streaming composer) but never got a real visual pass — that commit touched zero layout CSS. Investigation found the "very basic" feel had one root structural cause plus several real, specific gaps:
.tu-app-layout__mainhad nomin-height: 0, so the composer never pinned to the bottom and the transcript never scrolled internally — the auto-scroll logic that already existed was a silent no-op.daily_chat_query_cap_exceeded,chat_turn_in_progress) — a customer hitting the daily cap saw "Something went wrong."execute_read_only_sql) — not appropriate on a financial platform's customer-facing surface.listSessions/getMessagesexisted but were unused — every page visit destroyed the conversation.Three scope calls were confirmed with the user directly before building: skip a backend "tool in progress" SSE event (frontend-only pass); build a basic session-history sidebar; add table rendering.
Implementation
flex: 1, notheight: 100%— a flex item's flex-resolved size doesn't count as a "definite height" for a percentage-height child to resolve against (a real CSS gotcha, caught by inspecting live computed styles in the browser, not assumed from the diff — the firstheight:100%attempt looked plausible and was still wrong two levels down).design-system.md's own "one authoritative voice" framing of the ink token, marked by a small circular monogram reusing the wordmark's serif letterform. User messages stay a bounded, right-aligned bubble.onRetryinto the stream error state (resends the same text without duplicating the user bubble).stopicon.SessionHistorysidebar:useChatSessionresumes the most recent session on mount instead of always creating a fresh one; explicit "New chat" and per-session switching, disabled mid-stream (no safe way to abandon an in-flight turn). Switching resetsuseChatStream's state so a stale error/streaming state can't bleed into the newly-selected transcript.Tablecompound component — plus one line added to the backend system prompt asking for tables on multi-row results.Validation
Verified against the real running app with genuine end-to-end OpenAI streaming (a real API key is configured locally — not the
FakeAgentAdaptertest double):Table→ paragraph) — this test account has no multi-row account data for the model to honestly report on.tu-chat-window__bodyneededflex-direction: columnon mobile so an open sidebar stacks above the transcript instead of fighting it for row space).tu-app-layout__maina flex containerBackend:
tests/api/test_chat.py12/12,ruffclean. Frontend: lint and build clean, 246 modules.Security / DB / API impact
🤖 Generated with Claude Code