Skip to content

feat(chat): queue follow-up messages while a response streams#4050

Merged
senamakel merged 3 commits into
tinyhumansai:mainfrom
hgunduzoglu:feat/queue-followups-while-streaming
Jun 24, 2026
Merged

feat(chat): queue follow-up messages while a response streams#4050
senamakel merged 3 commits into
tinyhumansai:mainfrom
hgunduzoglu:feat/queue-followups-while-streaming

Conversation

@hgunduzoglu

@hgunduzoglu hgunduzoglu commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Ship the frontend for queuing follow-up messages while a response is streaming (Settings-free chat composer). Closes the gap left by feat(agent): add active-run steering and queue controls #3317, which added the backend run-queue (steer/followup/collect modes) but no UI.
  • While the selected thread is streaming, the composer no longer locks: you can type and submit a follow-up. Plain Enter / Send queues it (queueMode: 'followup'); the backend dispatches it as a fresh turn once the current turn finishes. Cmd/Ctrl+Enter still forks a parallel branch (unchanged).
  • Queued follow-ups appear in a compact strip above the composer with a Clear action (wipes the backend run-queue via openhuman.channel_web_queue_clear + the local pills). They auto-clear when the turn ends (they then arrive as real messages on their dispatched turns).
  • Frontend-only — no schema/dispatch/Rust change; the backend run-queue already supports all of this.

Problem

#3317 shipped the backend run-queue and openhuman.channel_web_chat already accepts queue_mode, but the frontend hard-locked the composer for the whole stream: textarea/Send were gated by isSending, so the only way to send mid-stream was the (effectively unreachable) Cmd+Enter parallel path. Users who thought of a follow-up mid-response had to wait and risk forgetting it (#3478).

Solution

  • ChatComposer: while allowParallelSend (the selected thread is streaming) keep the textarea + Send button editable and show a follow-up hint instead of the in-flight spinner.
  • Conversations: route a plain-Enter / Send submission during a streaming turn to a new handleSendFollowup (sends queueMode: 'followup', no optimistic transcript insert — the queued text reappears as a real message when dispatched). A QueuedFollowups strip lists the queued texts with a single Clear (chatClearQueueopenhuman.channel_web_queue_clear).
  • chatRuntimeSlice: queuedFollowupsByThread + enqueueFollowup/removeFollowup/clearFollowupsForThread; cleared on endInferenceTurn / clearRuntimeForThread / clearAllChatRuntime.
  • The in-flight Cancel control moved into the floating composer footer (above the queued strip) so it stays reachable now that the footer is taller and the composer is interactive mid-stream.

Verified live: typing while the agent streams queues follow-ups (the strip shows them), they dispatch in order after the turn finishes, Clear drops them, Cmd/Ctrl+Enter still forks a parallel branch, and Cancel no longer overlaps the strip.

Local validation

  • pnpm --filter openhuman-app run test on the touched suites → 104/104 ✅ (slice, QueuedFollowups, ChatComposer, chatService, Conversations.render)
  • pnpm typecheck ✅ · Prettier ✅ · ESLint (0 errors) ✅
  • pnpm i18n:check parity ✅ (0 missing / 0 extra across all locales; 3 new keys added to every locale)

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) — new slice reducer tests, QueuedFollowups component tests, ChatComposer follow-up-mode tests, chatService.chatClearQueue tests (incl. RPC-throws), and Conversations integration tests (Enter-queues / Send-queues / Clear). Updated the silence-timer regression test to assert activeThreadIds directly (the old Send-disabled proxy no longer holds — see Behavior Changes).
  • Diff coverage ≥ 80% — the new slice/service/composer/component logic and the Conversations follow-up handlers + strip are exercised by the tests above.
  • Coverage matrix updated — N/A: surfaces an existing backend capability (run-queue) in the chat composer; no new feature row.
  • All affected feature IDs from the matrix are listed under ## RelatedN/A: no matrix rows affected.
  • No new external network dependencies introduced — none.
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: chat composer enhancement; no release-cut flow change.
  • Linked issue closed via Closes #NNN in the ## Related section.

Impact

  • Platform: desktop chat UI only. No backend/schema/migration impact.
  • Behavior: the composer stays usable while a turn streams; submitted messages queue as follow-ups and dispatch after the turn instead of being blocked.
  • Risk: moderate — touches the chat composer/send path. Guarded by unit + integration tests; the parallel-branch path and idle-thread send are unchanged.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

N/A — human-authored PR.

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: N/A
  • Commit SHA: N/A

Validation Run

  • pnpm --filter openhuman-app format:check — Prettier on changed files ✅
  • pnpm typecheck
  • Focused tests: touched suites (104/104) ✅
  • Rust fmt/check (if changed): N/A — no Rust changed.
  • Tauri fmt/check (if changed): N/A — no Tauri/app/src-tauri changed.

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: the chat composer is no longer locked while a turn streams — plain Enter / Send queue a follow-up.
  • User-visible effect: typing mid-stream queues a follow-up (visible in a strip, clearable) instead of being blocked; the Cancel control now sits in the composer footer.

Parity Contract

  • Legacy behavior preserved: idle-thread send and the Cmd/Ctrl+Enter parallel-branch path are unchanged.
  • Guard/fallback/dispatch parity checks: follow-ups use the existing queueMode: 'followup' on openhuman.channel_web_chat; Clear uses openhuman.channel_web_queue_clear.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: N/A
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • New Features

    • Queue follow-up messages while a response is streaming, including Enter/send-button follow-up behavior.
    • Added a queued follow-ups strip above the composer (with localized labels and a clear action).
    • Follow-up mode stays editable during streaming, with updated follow-up hint text and composer behavior.
  • Bug Fixes

    • Improved composer send/spinner/disabled states for follow-ups submitted mid-stream.
    • More reliable transcript insertion of queued follow-ups when the turn completes; clearing failures keep the queue visible.
  • Tests

    • Expanded coverage for follow-up/parallel mode and queued follow-ups UI + clearing error handling.

The backend run-queue (tinyhumansai#3317) already accepts queue_mode 'followup' and
dispatches queued messages as fresh turns after the current turn ends, but the
composer hard-locked for the whole stream, so users couldn't act on a follow-up
mid-response.

Keep the composer usable while a turn streams: plain Enter / Send queues a
follow-up (queueMode 'followup'); Cmd/Ctrl+Enter still forks a parallel branch.
Queued follow-ups show in a strip above the composer with a Clear action
(openhuman.channel_web_queue_clear) and auto-clear on turn end. The Cancel
control moves into the composer footer so it stays reachable.

Frontend-only; adds queuedFollowupsByThread to chatRuntime + i18n across locales.

Closes tinyhumansai#3478
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds queued follow-up support for streaming chat turns. The composer stays usable during streaming, queued follow-ups render as pills, queue state is stored in Redux, backend queue clearing is added, and queued text is flushed into the transcript when turns complete or error.

Changes

Queued Follow-ups While Streaming

Layer / File(s) Summary
Composer follow-up mode
app/src/components/chat/ChatComposer.tsx, app/src/components/chat/__tests__/ChatComposer.test.tsx
Updates composer locking, content detection, spinner behavior, and placeholder text for parallel/follow-up sending; tests cover editability, send enablement, spinner suppression, and placeholder changes.
Queued follow-ups strip
app/src/components/chat/QueuedFollowups.tsx, app/src/components/chat/__tests__/QueuedFollowups.test.tsx
Adds a queued-followups strip with count, item list, clear action, and empty-state null rendering; tests cover rendering and clear handling.
Queued follow-up runtime state
app/src/store/chatRuntimeSlice.ts, app/src/store/__tests__/chatRuntimeSlice.queue.test.ts
Adds queued-followup state, reducers, and lifecycle cleanup in chat runtime; tests cover enqueueing, removal, clearing, and reset behavior.
Queue clear RPC
app/src/services/chatService.ts, app/src/services/__tests__/chatService.test.ts
Adds chatClearQueue(threadId) for clearing backend queued chat lanes and tests its RPC contract and failure return value.
Conversation queue routing
app/src/pages/Conversations.tsx, app/src/pages/__tests__/Conversations.render.test.tsx
Routes streaming submissions through follow-up queueing, clears backend and local queues together, renders queued pills in the page, and updates integration tests for queueing and clear/failure cases.
Flush queued follow-ups
app/src/providers/ChatRuntimeProvider.tsx, app/src/providers/__tests__/ChatRuntimeProvider.test.tsx
Flushes queued follow-ups into the transcript on turn completion and error, then clears the thread queue; tests verify the appended user message and queue cleanup.
Follow-up translations
app/src/lib/i18n/ar.ts, app/src/lib/i18n/bn.ts, app/src/lib/i18n/de.ts, app/src/lib/i18n/en.ts, app/src/lib/i18n/es.ts, app/src/lib/i18n/fr.ts, app/src/lib/i18n/hi.ts, app/src/lib/i18n/id.ts, app/src/lib/i18n/it.ts, app/src/lib/i18n/ko.ts, app/src/lib/i18n/pl.ts, app/src/lib/i18n/pt.ts, app/src/lib/i18n/ru.ts, app/src/lib/i18n/zh-CN.ts
Adds follow-up hint, queued-followups label, clear action, and clear-failed translation keys across all locale files.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • oxoxDev

Poem

🐇 I hop along the streaming line,
and queue my thoughts in tidy time.
When turns conclude, they land just right,
like carrots tucked in soft moonlight. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: queuing follow-up chat messages while a response streams.
Linked Issues check ✅ Passed The PR matches #3478 by enabling queued follow-ups during streaming and shipping the frontend UI over the existing backend queue.
Out of Scope Changes check ✅ Passed The changes stay focused on chat follow-up queuing, UI, queue plumbing, tests, and translations, with no obvious unrelated scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d3ea352561

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread app/src/pages/Conversations.tsx Outdated
Comment on lines +1144 to +1146
dispatch(
enqueueFollowup({ threadId, id: `fup_${globalThis.crypto.randomUUID()}`, text: normalized })
);

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 Badge Persist queued follow-up prompts before dropping the pill

When the user queues a follow-up, this path never calls addMessageLocal (unlike the normal send path) and only records a temporary pill; the core web channel later emits assistant progress/done events, not a user-message event. For any queued follow-up, the prompt is never appended to the thread store, and endInferenceTurn clears the pill when the current turn ends, so the transcript shows an assistant answer to an invisible/missing user message and that prompt is lost on reload/cold resume.

Useful? React with 👍 / 👎.

Comment thread app/src/pages/Conversations.tsx Outdated
Comment on lines +1159 to +1160
dispatch(clearFollowupsForThread({ threadId }));
await chatClearQueue(threadId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep queued pills until the backend clear succeeds

If the queue-clear RPC fails or the socket/core is briefly unavailable, chatClearQueue returns 0 instead of throwing, but this handler has already removed the local pills and never restores them. In that failure case the backend queue is still intact and will dispatch the follow-ups the user just cleared, while the UI falsely indicates they were removed.

Useful? React with 👍 / 👎.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Implements the frontend UX for queuing “follow-up” messages while the selected thread is streaming, leveraging the existing backend run-queue (queueMode: 'followup') and adding a small queued-followups strip with a clear action.

Changes:

  • Adds Redux state + reducers for per-thread queued follow-ups and clears them when a turn ends.
  • Updates Conversations + ChatComposer so plain Enter/Send queues a follow-up during streaming, and renders a queued-followups strip with a Clear action.
  • Adds chatClearQueue() service wrapper and updates i18n strings across locales; adds/updates unit + integration tests.

Reviewed changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
app/src/store/chatRuntimeSlice.ts Add queuedFollowupsByThread state and reducers; clear on turn end/reset.
app/src/store/tests/chatRuntimeSlice.queue.test.ts Adds reducer coverage for queued follow-up state transitions.
app/src/services/chatService.ts Adds chatClearQueue() RPC helper for openhuman.channel_web_queue_clear.
app/src/services/tests/chatService.test.ts Tests chatClearQueue() success/empty/throw paths.
app/src/pages/Conversations.tsx Routes Enter/Send to follow-up queueing while streaming; renders strip; adds clear handler; moves Cancel UI into footer.
app/src/pages/tests/Conversations.render.test.tsx Adds integration tests for follow-up queueing and clearing while streaming.
app/src/components/chat/QueuedFollowups.tsx New UI strip listing queued follow-ups + Clear action.
app/src/components/chat/tests/QueuedFollowups.test.tsx Unit tests for queued-followups strip rendering and Clear behavior.
app/src/components/chat/ChatComposer.tsx Keeps composer editable during streaming; updates hint text and send-button gating/spinner behavior.
app/src/components/chat/tests/ChatComposer.test.tsx Adds tests for follow-up/parallel mode behavior (editable, enabled send, hint, spinner hidden).
app/src/lib/i18n/en.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/ar.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/bn.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/de.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/es.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/fr.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/hi.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/id.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/it.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/ko.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/pl.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/pt.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/ru.ts Adds follow-up hint + queued-followups label/clear keys.
app/src/lib/i18n/zh-CN.ts Adds follow-up hint + queued-followups label/clear keys.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1129 to +1151

setInputValue('');
setAttachments([]);
setSendError(null);
setAttachError(null);

try {
await chatSend({
threadId,
message: messageText,
model: modelOverride,
profileId: selectedAgentProfileId,
locale: uiLocale,
queueMode: 'followup',
});
dispatch(
enqueueFollowup({ threadId, id: `fup_${globalThis.crypto.randomUUID()}`, text: normalized })
);
trackEvent('chat_followup_queued');
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
setSendError(chatSendError('cloud_send_failed', msg));
}
Comment thread app/src/services/chatService.ts Outdated
Comment on lines +1069 to +1073
export async function chatClearQueue(threadId: string): Promise<number> {
try {
const res = await callCoreRpc<{ dropped?: number }>({
method: 'openhuman.channel_web_queue_clear',
params: { thread_id: threadId },
@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label Jun 24, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/pages/__tests__/Conversations.render.test.tsx`:
- Around line 2127-2201: The queued follow-ups test suite is reusing shared mock
call history, which can let later assertions pass because of earlier tests. Add
per-test mock cleanup inside the Conversations queued follow-ups describe block
by resetting the shared mocks such as chatSend, chatClearQueue, and any related
state before each test runs. Place the reset near renderStreamingConversation so
each case starts with a clean slate and its expectations only reflect the
current test behavior.

In `@app/src/pages/Conversations.tsx`:
- Around line 1130-1150: The follow-up send flow in Conversations.tsx clears the
draft too early, before chatSend with queueMode: 'followup' succeeds. Move the
setInputValue, setAttachments, setSendError, and setAttachError resets in the
follow-up handler so they only run after the await chatSend call completes
successfully, and keep the existing error handling path in the catch block; use
the follow-up send logic around chatSend, enqueueFollowup, and trackEvent as the
place to update.
- Around line 1156-1160: The queued follow-up pills are being cleared locally
before the backend RPC succeeds, which can desync the UI from the actual queue
state. In handleClearQueuedFollowups, wait for chatClearQueue to complete
successfully before dispatching clearFollowupsForThread, and only clear the
local state after the backend confirms the queue was cleared; if the RPC fails,
leave the pills visible and avoid dispatching the local removal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9abc2ab3-6631-4a3f-baad-28a714c6ab41

📥 Commits

Reviewing files that changed from the base of the PR and between 4fff156 and d3ea352.

📒 Files selected for processing (24)
  • app/src/components/chat/ChatComposer.tsx
  • app/src/components/chat/QueuedFollowups.tsx
  • app/src/components/chat/__tests__/ChatComposer.test.tsx
  • app/src/components/chat/__tests__/QueuedFollowups.test.tsx
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/Conversations.tsx
  • app/src/pages/__tests__/Conversations.render.test.tsx
  • app/src/services/__tests__/chatService.test.ts
  • app/src/services/chatService.ts
  • app/src/store/__tests__/chatRuntimeSlice.queue.test.ts
  • app/src/store/chatRuntimeSlice.ts

Comment thread app/src/pages/__tests__/Conversations.render.test.tsx
Comment thread app/src/pages/Conversations.tsx Outdated
Comment thread app/src/pages/Conversations.tsx Outdated
Addresses PR review (Codex/Copilot/CodeRabbit):

- Persist queued follow-ups on turn end. Chat messages are persisted by the
  frontend (addMessageLocal → appendMessage); the web channel never writes user
  messages. Skipping addMessageLocal for follow-ups meant a queued prompt was
  never stored — lost on reload, and the dispatched answer had no visible user
  message. Flush queued follow-ups into the transcript in ChatRuntimeProvider's
  done AND error paths, after the assistant reply, so the append-log order
  stays user → assistant → follow-up.
- chatClearQueue returns null on RPC failure; handleClearQueuedFollowups clears
  the backend queue first and only drops the local pills on success, otherwise
  keeps them + surfaces an error (the backend will still dispatch them).
- handleSendFollowup clears the composer only after the send succeeds, so a
  failed queue keeps the draft/attachments; attachments-only follow-ups get a
  non-empty pill label (file names) instead of a blank row.
- Tests: provider flush, clear-failure keeps pills, send-failure keeps draft;
  per-test mock reset in the follow-up suite. +1 i18n key across locales.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/providers/ChatRuntimeProvider.tsx`:
- Around line 354-371: The queued follow-up flush in ChatRuntimeProvider is
firing async addMessageLocal thunks without waiting for them to finish, so
queued prompts can be lost or reordered when endInferenceTurn clears
queuedFollowupsByThread. Update flushQueuedFollowups to await each
addMessageLocal dispatch (or otherwise await all queued writes) before any
caller invokes endInferenceTurn, and adjust the callers around the two flush
sites plus the turn-ending logic to preserve the queue on failure or handle
retries instead of unconditionally clearing it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b7ba204d-2a16-4f0c-9149-80f5825c89b1

📥 Commits

Reviewing files that changed from the base of the PR and between d3ea352 and be48d4c.

📒 Files selected for processing (20)
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/pages/Conversations.tsx
  • app/src/pages/__tests__/Conversations.render.test.tsx
  • app/src/providers/ChatRuntimeProvider.tsx
  • app/src/providers/__tests__/ChatRuntimeProvider.test.tsx
  • app/src/services/__tests__/chatService.test.ts
  • app/src/services/chatService.ts
✅ Files skipped from review due to trivial changes (7)
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/zh-CN.ts
  • app/src/lib/i18n/ar.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/fr.ts
  • app/src/pages/Conversations.tsx

Comment thread app/src/providers/ChatRuntimeProvider.tsx Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 6 comments.

Comment on lines +354 to +366
const flushQueuedFollowups = (threadId: string) => {
const queued = store.getState().chatRuntime.queuedFollowupsByThread[threadId] ?? [];
for (const item of queued) {
void dispatch(
addMessageLocal({
threadId,
message: {
id: `msg_${globalThis.crypto.randomUUID()}`,
content: item.text,
type: 'text',
extraMetadata: {},
sender: 'user',
createdAt: new Date().toISOString(),
Comment on lines +360 to +362
message: {
id: `msg_${globalThis.crypto.randomUUID()}`,
content: item.text,
Comment on lines +355 to +369
const queued = store.getState().chatRuntime.queuedFollowupsByThread[threadId] ?? [];
for (const item of queued) {
void dispatch(
addMessageLocal({
threadId,
message: {
id: `msg_${globalThis.crypto.randomUUID()}`,
content: item.text,
type: 'text',
extraMetadata: {},
sender: 'user',
createdAt: new Date().toISOString(),
},
})
);
Comment on lines +325 to +331
/** A follow-up message queued from the composer while a turn was streaming. */
export interface QueuedFollowup {
/** Client-generated id, used as the React key and removal handle. */
id: string;
/** The trimmed text the user typed (for display in the queued strip). */
text: string;
}
Comment thread app/src/pages/Conversations.tsx Outdated
Comment on lines +1127 to +1152
const modelOverride =
agentProfiles.find(p => p.id === selectedAgentProfileId)?.modelOverride ?? CHAT_MODEL_HINT;
const messageText = buildMessageWithAttachments(normalized, pendingAttachments);
// Never render a blank pill/row for an attachments-only follow-up: fall back
// to the attachment file names as the label.
const pillText = normalized || pendingAttachments.map(a => a.file.name).join(', ');

setSendError(null);
setAttachError(null);

try {
await chatSend({
threadId,
message: messageText,
model: modelOverride,
profileId: selectedAgentProfileId,
locale: uiLocale,
queueMode: 'followup',
});
// Only clear the composer once the backend has accepted the queue, so a
// failed send leaves the user's draft + attachments intact to retry.
setInputValue('');
setAttachments([]);
dispatch(
enqueueFollowup({ threadId, id: `fup_${globalThis.crypto.randomUUID()}`, text: pillText })
);
Comment thread app/src/pages/Conversations.tsx Outdated
Comment on lines +1149 to +1152
setAttachments([]);
dispatch(
enqueueFollowup({ threadId, id: `fup_${globalThis.crypto.randomUUID()}`, text: pillText })
);
…ck reset

Second review round (Codex/Copilot/CodeRabbit):

- Queue the full user message (content + attachment metadata), built like a
  normal send, instead of only a display string. Flushing it on turn end now
  persists the follow-up identically to an interactive send — attachments and
  metadata are preserved, and the transcript content matches what was sent.
  A separate 'label' drives the pill (falls back to attachment file names so an
  attachments-only follow-up is never a blank row).
- Generate the queued message id with the codebase's guarded crypto.randomUUID
  fallback; the provider no longer mints ids.
- flushQueuedFollowups is now async and awaits each append sequentially (so
  multiple queued prompts keep their order in the append log) and logs failures
  instead of ignoring the dispatch promise; finishChatDoneTurn awaits it before
  clearing the queue/lifecycle.
- Add a per-test mock reset (beforeEach) to the follow-up test suite to stop
  shared chatSend/chatClearQueue call history bleeding across cases.

Tests updated for the new QueuedFollowup shape; 133 focused tests green.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/src/store/chatRuntimeSlice.ts (1)

847-858: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract new reducer payload object shapes into interfaces.

The new PayloadAction<{ ... }> payloads for enqueueFollowup/removeFollowup should use named interfaces for consistency and readability.

Proposed refactor
+interface EnqueueFollowupPayload {
+  threadId: string;
+  message: ThreadMessage;
+  label: string;
+}
+
+interface RemoveFollowupPayload {
+  threadId: string;
+  id: string;
+}
+
     enqueueFollowup: (
       state,
-      action: PayloadAction<{ threadId: string; message: ThreadMessage; label: string }>
+      action: PayloadAction<EnqueueFollowupPayload>
     ) => {
@@
-    removeFollowup: (state, action: PayloadAction<{ threadId: string; id: string }>) => {
+    removeFollowup: (state, action: PayloadAction<RemoveFollowupPayload>) => {

As per coding guidelines, “Prefer interface for defining object shapes in TypeScript.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/store/chatRuntimeSlice.ts` around lines 847 - 858, The
enqueueFollowup and removeFollowup reducers in chatRuntimeSlice currently inline
their PayloadAction object shapes, which should be extracted into named
interfaces for consistency and readability. Define interfaces for the two
payloads near the reducer types, then update the PayloadAction annotations in
enqueueFollowup and removeFollowup to use those interfaces instead of anonymous
object literals.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/src/store/chatRuntimeSlice.ts`:
- Around line 847-858: The enqueueFollowup and removeFollowup reducers in
chatRuntimeSlice currently inline their PayloadAction object shapes, which
should be extracted into named interfaces for consistency and readability.
Define interfaces for the two payloads near the reducer types, then update the
PayloadAction annotations in enqueueFollowup and removeFollowup to use those
interfaces instead of anonymous object literals.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7703f4f4-cfe3-4584-b474-bcc7024b7a3a

📥 Commits

Reviewing files that changed from the base of the PR and between be48d4c and dee6b13.

📒 Files selected for processing (7)
  • app/src/components/chat/QueuedFollowups.tsx
  • app/src/components/chat/__tests__/QueuedFollowups.test.tsx
  • app/src/pages/Conversations.tsx
  • app/src/providers/ChatRuntimeProvider.tsx
  • app/src/providers/__tests__/ChatRuntimeProvider.test.tsx
  • app/src/store/__tests__/chatRuntimeSlice.queue.test.ts
  • app/src/store/chatRuntimeSlice.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/src/components/chat/QueuedFollowups.tsx
  • app/src/store/tests/chatRuntimeSlice.queue.test.ts
  • app/src/providers/ChatRuntimeProvider.tsx
  • app/src/providers/tests/ChatRuntimeProvider.test.tsx
  • app/src/pages/Conversations.tsx

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

@senamakel senamakel merged commit 689ef81 into tinyhumansai:main Jun 24, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: allow queuing follow-up messages while response is streaming

3 participants