Skip to content

Redesign Ask Trueup as a professional chat interface - #23

Merged
Badri-Rajendran merged 1 commit into
mainfrom
feature_chat_redesign
Sep 7, 2026
Merged

Badri-Rajendran merged 1 commit into
mainfrom
feature_chat_redesign

Conversation

@Badri-Rajendran

Copy link
Copy Markdown
Owner

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:

  • 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.
  • Bubble differentiation was too weak (~4 hex points apart between user/assistant backgrounds).
  • Two chat-specific error codes were unmapped (daily_chat_query_cap_exceeded, chat_turn_in_progress) — a customer hitting the daily cap saw "Something went wrong."
  • The stream error state had no retry.
  • The composer disabled while streaming — no composing your next question while a reply arrived. The stop button used ✕ (reads as cancel, not stop).
  • Raw tool names leaked into "How this was answered" (execute_read_only_sql) — not appropriate on a financial platform's customer-facing surface.
  • listSessions/getMessages existed but were unused — every page visit destroyed the conversation.
  • No markdown table support, despite gpt-4o-mini commonly emitting tables unprompted for multi-row data.

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

  • Height-chain fix: every ancestor in the chat's height chain uses flex: 1, 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 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).
  • Assistant messages render as flowing ink-colored text, not a bubble — grounded in 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.
  • Mapped both chat error codes; wired onRetry into the stream error state (resends the same text without duplicating the user bubble).
  • Composer field stays typeable during streaming; only the send/stop control changes. New real stop icon.
  • Humanized both known tool names in the trace disclosure.
  • New SessionHistory sidebar: useChatSession resumes 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 resets useChatStream's state so a stale error/streaming state can't bleed into the newly-selected transcript.
  • GFM pipe-table parsing in the markdown renderer, rendered through the app's real Table compound 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 FakeAgentAdapter test double):

  • Empty state, real streaming with the transcript actually auto-scrolling now
  • Table-rendering path verified via the parsed React element tree (paragraph → Table → paragraph) — this test account has no multi-row account data for the model to honestly report on
  • Humanized tool trace, typing a follow-up while a reply streams (text survives intact)
  • New chat, session switching restoring the correct real messages
  • Light and dark themes (dark-mode avatar colors confirmed via computed styles, not eyeballed)
  • 375px mobile including the sidebar — 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)
  • Keyboard-only tab order with visible focus throughout
  • 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.

Security / DB / API impact

  • Security: none. No new data collected; the humanized tool-name mapping reduces (not increases) what reaches the customer.
  • DB: none.
  • API: none — no route, request, or response shape changed. The system prompt gains one line of formatting guidance only.

🤖 Generated with Claude Code

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>
Copilot AI lite review requested due to automatic review settings September 7, 2026 01:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Badri-Rajendran
Badri-Rajendran merged commit a49a428 into main Sep 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants