Skip to content

feat(web): add Read aloud for stored bot replies - #194

Merged
leoisadev1 merged 5 commits into
mainfrom
t3code/read-aloud-playback-controls
Sep 8, 2026
Merged

feat(web): add Read aloud for stored bot replies#194
leoisadev1 merged 5 commits into
mainfrom
t3code/read-aloud-playback-controls

Conversation

@leoisadev1

Copy link
Copy Markdown
Member

Users could only hear a bot during a live ChatGPT call. This adds Read aloud on settled replies, playback controls, and an opt-in automatic readout preference that stays off by default.

Playback speaks the stored reply. It does not start a call or generate another turn. Markdown is converted to spoken text, code blocks and images are skipped, and one playback owner stops on navigation, disconnect, edit/delete, or a live call. Stored-reply synthesis is still reported as unavailable until the shared speech operation lands.

Verification

  • Passed 115 focused tests covering spoken text, playback ownership, cancellation, automatic readout, settings, and web/native media adapters.
  • Targeted lint and typecheck passed for client-runtime, web, and mobile.
  • Exercised actual audio in Chromium on an isolated fixture over Tailscale (100.84.34.117:58402). Desktop 1280×900 and narrow 390×844. Time advanced, analyser RMS was 0.088 while playing, and pause/stop/switch/retry/call/navigation behaved as specified.
  • Copy, reply, and react still worked beside Read aloud.
  • No ChatGPT, OpenAI, ElevenLabs, Cartesia, or Fish Audio synthesis was used. PulseAudio sink was auto_null.
  • Native mobile was not verified on this Linux host (adb missing, XcodeBuildMCP timed out). Electron shell was not launched separately; the renderer is shared with web.

Screenshots

Idle, playing, paused, error, and narrow states from the isolated fixture (local test audio, not provider speech).

Users could only hear a bot during a live ChatGPT call. This adds Read aloud on settled replies, playback controls, and an opt-in automatic readout preference, without starting a call or generating another turn.

Stored-reply synthesis is still reported as unavailable until the shared speech operation lands.

Made-with: grok-4.6-auto in T3 Code / Grok
@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
akeru-bot-landing Building Building Preview Sep 8, 2026 3:33am UTC

Request Review

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Sep 8, 2026
Isolated landing tests call components as functions with a partial React mock, so real useContext and environment connection hooks crashed. Mock the playback hooks the same way the tests already mock live-call controls.

Made-with: grok-4.6-auto in T3 Code / Grok
@greptile-apps

greptile-apps Bot commented Sep 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds shared stored-reply read-aloud support across web and mobile, including playback controls, automatic readout preferences, browser and native audio adapters, and focused test coverage. It also ensures playback is stopped or released when a thread exits, connectivity is lost, content changes, live calls begin, or audio resources are disposed.

Confidence Score: 5/5

Safe to merge.

The unresolved playback-on-thread-exit issue is fully fixed by ownership-aware context cleanup. The connection-state, disposal-cleanup, and revealed-thread-context threads were resolved by greptile-apps[bot] without explanation; the current implementation forwards mobile connection state, contains disposal failures before resetting playback state, and restores thread context through navigation focus.

Reviews (4): Last reviewed commit: "Merge branch 'main' into t3code/read-alo..." | Re-trigger Greptile

Comment thread apps/web/src/lib/replyPlaybackThread.ts Outdated
Comment on lines +37 to +51
useEffect(() => {
if (!session) return;
if (!options.environmentId || !options.threadId) {
session.setContext(null);
return;
}
session.setContext({
environmentId: options.environmentId,
threadId: options.threadId,
provider: session.synthesis.provider,
voice: session.synthesis.voice,
connected: !voiceEnvironmentConnectionLost(connection.data),
mediaBlocked: options.mediaBlocked,
});
session.observe(options.messages);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Stop playback on exit

Unmounting a thread does not clear the root-scoped playback context. Audio that was playing for the former thread remains playing and available after navigating away, because the effect sets context only while mounted and returns no cleanup. Clear this hook's owned context during cleanup so leaving a chat stops its playback; the mobile thread hook has the same omission.

Artifacts

Evidence from the check

  • The executable React lifecycle test mounts a thread, starts playback, unmounts it, and asserts the observed retained state; it is the exact source used for the proof.

Command output from the check

  • Captured `cat` output records the exact executed test source and command metadata; it shows the lifecycle assertions used to reproduce the defect.

Command output from the check

  • Verbose test output records active playback before unmount and retained action, playing status, and undisposed audio after unmount; it demonstrates the lifecycle defect.

Command output from the check

  • A second executed run of the same lifecycle repro completed successfully and reproduced the retained playback behavior; it confirms the observation is repeatable.

View artifacts

T-Rex Ran code and verified through T-Rex

Fix in Claude Code

Comment thread apps/mobile/src/features/threads/ThreadFeed.tsx
Comment thread packages/client-runtime/src/replyPlayback/controller.ts
Leaving a chat, disconnecting mobile, or a failed native dispose could leave playback running. Clear thread context on unmount, treat only a connected environment as playable, and still go idle if dispose throws.

Made-with: grok-4.6-auto in T3 Code / Grok
Comment thread apps/mobile/src/features/replyPlayback/useReplyPlaybackThread.ts Outdated
Clearing playback on every unmount left the still-mounted chat without context after a nested thread was popped. Cleanup now only releases the owning chat, and mobile restores context when the revealed route is focused.

Made-with: grok-4.6-auto in T3 Code / Grok
@leoisadev1
leoisadev1 merged commit 4314798 into main Sep 8, 2026
11 checks passed
@leoisadev1
leoisadev1 deleted the t3code/read-aloud-playback-controls branch September 8, 2026 04:20
@github-actions github-actions Bot mentioned this pull request Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant