feat(chat): queue follow-up messages while a response streams#4050
Conversation
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
📝 WalkthroughWalkthroughAdds 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. ChangesQueued Follow-ups While Streaming
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ 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
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. Comment |
There was a problem hiding this comment.
💡 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".
| dispatch( | ||
| enqueueFollowup({ threadId, id: `fup_${globalThis.crypto.randomUUID()}`, text: normalized }) | ||
| ); |
There was a problem hiding this comment.
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 👍 / 👎.
| dispatch(clearFollowupsForThread({ threadId })); | ||
| await chatClearQueue(threadId); |
There was a problem hiding this comment.
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 👍 / 👎.
There was a problem hiding this comment.
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+ChatComposerso 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.
|
|
||
| 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)); | ||
| } |
| 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 }, |
There was a problem hiding this comment.
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
📒 Files selected for processing (24)
app/src/components/chat/ChatComposer.tsxapp/src/components/chat/QueuedFollowups.tsxapp/src/components/chat/__tests__/ChatComposer.test.tsxapp/src/components/chat/__tests__/QueuedFollowups.test.tsxapp/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsapp/src/pages/Conversations.tsxapp/src/pages/__tests__/Conversations.render.test.tsxapp/src/services/__tests__/chatService.test.tsapp/src/services/chatService.tsapp/src/store/__tests__/chatRuntimeSlice.queue.test.tsapp/src/store/chatRuntimeSlice.ts
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.
There was a problem hiding this comment.
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
📒 Files selected for processing (20)
app/src/lib/i18n/ar.tsapp/src/lib/i18n/bn.tsapp/src/lib/i18n/de.tsapp/src/lib/i18n/en.tsapp/src/lib/i18n/es.tsapp/src/lib/i18n/fr.tsapp/src/lib/i18n/hi.tsapp/src/lib/i18n/id.tsapp/src/lib/i18n/it.tsapp/src/lib/i18n/ko.tsapp/src/lib/i18n/pl.tsapp/src/lib/i18n/pt.tsapp/src/lib/i18n/ru.tsapp/src/lib/i18n/zh-CN.tsapp/src/pages/Conversations.tsxapp/src/pages/__tests__/Conversations.render.test.tsxapp/src/providers/ChatRuntimeProvider.tsxapp/src/providers/__tests__/ChatRuntimeProvider.test.tsxapp/src/services/__tests__/chatService.test.tsapp/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
| 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(), |
| message: { | ||
| id: `msg_${globalThis.crypto.randomUUID()}`, | ||
| content: item.text, |
| 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(), | ||
| }, | ||
| }) | ||
| ); |
| /** 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; | ||
| } |
| 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 }) | ||
| ); |
| 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.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
app/src/store/chatRuntimeSlice.ts (1)
847-858: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract new reducer payload object shapes into interfaces.
The new
PayloadAction<{ ... }>payloads forenqueueFollowup/removeFollowupshould 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
📒 Files selected for processing (7)
app/src/components/chat/QueuedFollowups.tsxapp/src/components/chat/__tests__/QueuedFollowups.test.tsxapp/src/pages/Conversations.tsxapp/src/providers/ChatRuntimeProvider.tsxapp/src/providers/__tests__/ChatRuntimeProvider.test.tsxapp/src/store/__tests__/chatRuntimeSlice.queue.test.tsapp/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
Summary
steer/followup/collectmodes) but no UI.queueMode: 'followup'); the backend dispatches it as a fresh turn once the current turn finishes. Cmd/Ctrl+Enter still forks a parallel branch (unchanged).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).Problem
#3317shipped the backend run-queue andopenhuman.channel_web_chatalready acceptsqueue_mode, but the frontend hard-locked the composer for the whole stream:textarea/Send were gated byisSending, 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: whileallowParallelSend(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 newhandleSendFollowup(sendsqueueMode: 'followup', no optimistic transcript insert — the queued text reappears as a real message when dispatched). AQueuedFollowupsstrip lists the queued texts with a single Clear (chatClearQueue→openhuman.channel_web_queue_clear).chatRuntimeSlice:queuedFollowupsByThread+enqueueFollowup/removeFollowup/clearFollowupsForThread; cleared onendInferenceTurn/clearRuntimeForThread/clearAllChatRuntime.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 teston the touched suites → 104/104 ✅ (slice,QueuedFollowups,ChatComposer,chatService,Conversations.render)pnpm typecheck✅ · Prettier ✅ · ESLint (0 errors) ✅pnpm i18n:checkparity ✅ (0 missing / 0 extra across all locales; 3 new keys added to every locale)Submission Checklist
QueuedFollowupscomponent tests,ChatComposerfollow-up-mode tests,chatService.chatClearQueuetests (incl. RPC-throws), andConversationsintegration tests (Enter-queues / Send-queues / Clear). Updated the silence-timer regression test to assertactiveThreadIdsdirectly (the old Send-disabled proxy no longer holds — see Behavior Changes).Conversationsfollow-up handlers + strip are exercised by the tests above.N/A: surfaces an existing backend capability (run-queue) in the chat composer; no new feature row.## Related—N/A: no matrix rows affected.N/A: chat composer enhancement; no release-cut flow change.Closes #NNNin the## Relatedsection.Impact
Related
AI Authored PR Metadata (required for Codex/Linear PRs)
N/A— human-authored PR.Linear Issue
N/AN/ACommit & Branch
N/AN/AValidation Run
pnpm --filter openhuman-app format:check— Prettier on changed files ✅pnpm typecheck✅app/src-taurichanged.Validation Blocked
command:N/Aerror:N/Aimpact:N/ABehavior Changes
Parity Contract
queueMode: 'followup'onopenhuman.channel_web_chat; Clear usesopenhuman.channel_web_queue_clear.Duplicate / Superseded PR Handling
N/AN/AN/ASummary by CodeRabbit
New Features
Bug Fixes
Tests