Skip to content

Chat composition: task-level UX and accessibility fixes - #1435

Merged
blove merged 4 commits into
mainfrom
blove/threadplane-ui-ux-eval-dbca6c
Nov 3, 2026
Merged

blove merged 4 commits into
mainfrom
blove/threadplane-ui-ux-eval-dbca6c

Conversation

@blove

@blove blove commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Summary

A rubric-based UI/UX evaluation of the chat composition (send, stop, recover from an error, read history while a reply streams, act on an earlier answer) across Chromium, Firefox and WebKit found six major and several moderate problems. This PR fixes them.

Adapter (@threadplane/langgraph)

  • The first reply on a new conversation looked finished while it was still streaming: no Stop, Send enabled, and Enter could start a second, concurrent run. The agent's derived-state reset fired when the host mirrored the transport-created thread id into its threadId signal; it now follows the bridge's own adoption rule.

Chat (@threadplane/chat)

  • Enter during a running reply keeps the draft and shows a polite hint instead of starting a concurrent run.
  • Keyboard focus continues in the composer when a control removes itself: the welcome view on first send, Stop, Retry, scroll-to-latest.
  • A reply arriving no longer moves a reader who scrolled up while it was pending.
  • A "Response stopped" marker after a stopped turn; a polite status announces "Response complete." / "Response stopped."
  • The transcript is a named, keyboard-scrollable region; messages get visually hidden speaker labels (new optional speaker input) and dir="auto".
  • Light --tplane-chat-error-text now meets 4.5:1 (was 4.41:1); error banner aligned to the composer; composer focus cue; Stop uses the enabled fill; Send/Stop keep a border in forced colours; message actions show on hover-less touch devices; scroll-to-latest named for its action while streaming.

Consumer-visible changes

  • Enter no longer submits while a reply runs (programmatic submitMessage() unchanged).
  • Light error-text token default changed to #b91c1c.
  • New DOM: role="region" + aria-label="Conversation" + tabindex="0" on the transcript, a visually hidden speaker span per message, and two polite role="status" regions.
  • Streaming scroll-bubble accessible name is now "Scroll to latest, response in progress".

Testing

  • chat vitest: 139 files / 1375 tests; langgraph vitest: 27 files / 527 tests plus runtime-quality; examples-chat-angular unit tests.
  • Lint (0 errors) and production builds for chat and langgraph.
  • examples-chat-angular e2e: 64 passed.
  • Scripted Playwright task probes against the canonical example (fixture replay) on Chromium, Firefox and WebKit, before and after: every task passes on all three engines, including a held-out task, contrast, reflow, reduced motion, forced colours and RTL.
  • React parity inventory refreshed with --write-baseline.

Not covered: a real screen-reader session (VoiceOver/NVDA) for the new announcements and labels.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
threadplane Ready Ready Preview Nov 2, 2026 11:49pm UTC

Request Review

blove and others added 4 commits November 2, 2026 15:42
…d id

On the first message of a new conversation the transport creates a thread
and the host mirrors its id into the bound threadId signal (null -> id).
The bridge already treats that as adoption and keeps streaming, but the
agent's derived-state subscription reset status to Idle. The running reply
then looked finished: no Stop control, Send enabled, and Enter could start
a second, concurrent run.

Reset derived thread state only when the bridge itself would switch
threads, or when no run is in flight.
…ition

Findings from a rubric-based evaluation of send, stop, error recovery,
reading while a reply streams, and acting on earlier answers (Chromium,
Firefox and WebKit):

- Enter during a running reply keeps the draft and says why nothing was
  sent, instead of starting a concurrent run.
- Keyboard focus continues in the composer when a control removes itself:
  the welcome view on first send, Stop, Retry, and scroll-to-latest.
- A reply arriving no longer moves a reader who scrolled up; the person's
  own turn, a run started on their prompt, a replaced transcript and
  regenerate still re-pin.
- A "Response stopped" marker after a stopped turn, and a polite status
  announcing "Response complete." / "Response stopped."
- The transcript is a named, keyboard-scrollable region; messages carry
  visually hidden speaker labels (new optional `speaker` input) and
  dir="auto" for right-to-left text.
- Light error text meets 4.5:1; error banner aligned to the composer;
  composer focus cue; Stop uses the enabled fill; Send/Stop keep a border
  in forced colours; message actions show on hover-less touch devices.
- Scroll-to-latest is named for its action while streaming.
The actions row was revealed under (hover: none), but its
.chat-message__controls wrapper still matched only max-width: 768px. On
touch tablets wider than that, earlier answers' buttons stayed invisible
inside the transparent wrapper while still receiving taps, so a tap could
regenerate an answer without a visible control. Both now share one reveal
query, with a style contract that keeps them in sync.
@blove
blove force-pushed the blove/threadplane-ui-ux-eval-dbca6c branch from 3f8b1b3 to 31704b7 Compare November 2, 2026 23:44
@blove
blove merged commit db88560 into main Nov 3, 2026
132 checks passed
@blove
blove deleted the blove/threadplane-ui-ux-eval-dbca6c branch November 3, 2026 01:04

This branch was successfully deployed

1 active deployment
Preview – threadplane — 31704b71 Deployed Nov 2, 2026 by vercel[bot]
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.

1 participant