Skip to content

feat(group-chat): render the shared composer toolbar in a room - #1945

Open
reachrazamair wants to merge 7 commits into
refactor/composer-capabilitiesfrom
refactor/composer-room-toolbar
Open

reachrazamair wants to merge 7 commits into
refactor/composer-capabilitiesfrom
refactor/composer-room-toolbar

Conversation

@reachrazamair

Copy link
Copy Markdown
Contributor

Stacked on #1892 (and #1891 below it; #1890 is merged). Base is refactor/composer-capabilities. Step 5 of the Group Chat composer parity plan (Plans/group-chat-composer-parity.md). No visual or behaviour change in AI chat.

What changed

GroupChatInput drops its own bottom row and renders ToolbarControls with GROUP_CHAT_COMPOSER_CAPABILITIES. The room's Read-Only toggle moves into the toolbar unchanged: toolbarExtras on desktop, and a new sheetExtras slot on a phone, so the room keeps the control in every layout.

What a room user sees:

  • Desktop: the toolbar row is laid out as in AI chat. The Read-Only tooltip shows its live, rebindable chord (Ctrl+R), and the Prompt Composer tooltip reads the live binding (Ctrl+Shift+P) the same way AI chat's does.
  • Web-desktop: Attach reads "Upload Image to Host", and an Upload button copies local files to the host and mentions them in the draft (the existing handler, which already accepted a room; the draft is flushed first, as a drop does).
  • Touch devices with speech recognition: a mic button dictates into the draft, which persists like typing.
  • Narrow window: Read-Only and the Enter toggle fold behind a "..." button (Show options / Hide options).
  • Phone: the row holds attach, upload (web-desktop), mic, Send and "...". The bell column and the Prompt Composer button are not shown, as in AI chat. Send keeps the room's rules: disabled without text, and "Queue message" in the warning tint while the room is busy. "..." opens a sheet with a Read-Only switch. The Enter toggle is not shown on a phone, as in AI chat.
  • Never shown in a room: Save to History, the permission pill, Thinking, and the model and effort pills.

Shared pieces:

  • PhoneSheetSwitchRow (PhoneSheetRows): the on/off row extracted from the sheet's History row, which now renders through it unchanged.
  • ComposerOptionsSheet gains extraRows, drawn after its sections.
  • ToolbarControls gains sheetExtras, phone Send props (sendTitle, sendDisabled, sendTone, same meaning as ComposerSendColumn), and the image input's id, report name and file check as props (defaults are the AI composer's values). The per-tab agent props are now optional, read only when their capability is on. The sheet mounts when it has a section or surface rows, and the phone "..." is drawn only when it opens something, except in AI terminal mode, which keeps its existing "..." with no sheet behind it (a known gap, not changed here).
  • CLI-UI-PARITY.md records composer upload as an open gap (no verb: an agent is already on the host).

In-App Verification

Exercised in the running app with a seeded synthetic Group Chat demo dataset across desktop dev (scripts/dev.mjs) and web-desktop (dist/web-desktop).

Verified Live in the Running App

  • Desktop wide (1200x800):
    • Room toolbar row layout matches AI chat side-by-side.
    • Read-Only toggle button active and inactive styling verified; tooltip shows live chord (Ctrl+R).
    • Prompt Composer button opens PromptComposerModal with room session context; tooltip shows live chord (Ctrl+Shift+P).
    • Enter toggle cycles between Enter and Ctrl+Enter to send.
    • Send button state and styling matrix verified with real typing events:
      • Idle Empty: disabled = true, title "Send message", border grey rgb(68, 71, 90).
      • Idle Typed: disabled = false, title "Send message", accent purple rgb(189, 147, 249).
      • Busy Typed: disabled = false, title "Queue message", warning orange rgb(255, 184, 108).
    • Excluded controls (History, Thinking, Model, Effort, Permission pill) confirmed absent.
    • AI Chat side-by-side parity: all AI controls (History, Enter toggle, Prompt Composer, Attach, Bell, Send) verified unchanged at 1200x800 with active draft text.
  • Desktop narrow (700x800):
    • Read-Only toggle and Enter toggle fold behind "..." button (Show options).
    • Clicking "..." unfolds toggle group (Hide options).
    • Send button state verified: disabled when empty (rgb(68, 71, 90)), enabled accent when typed (rgb(189, 147, 249)), warning orange when busy (rgb(255, 184, 108)).
  • Web-desktop (1200x800):
    • Attach button tooltip reads "Upload Image to Host".
    • Upload button is visible with title "Upload local files to the host".
    • Live Upload: pushing a file through the upload input flushes the draft first, saves the attachment on the host, and appends the @... path to the draft.
    • Live Attach: pushing an image through group-chat-image-input stages the preview thumbnail in the room's StagedImagesStrip.
    • Send button state verified: disabled when empty (rgb(68, 71, 90)), enabled accent when draft contains upload mention and staged image (rgb(189, 147, 249)), warning orange when busy (rgb(255, 184, 108)).
  • Phone layout (390x844 web-desktop):
    • Single row layout holds Attach, Upload, Send in center, and "..." options button.
    • Prompt Composer, Enter toggle, and Bell column are not shown.
    • Send button state verified: disabled when empty (rgb(68, 71, 90)), enabled accent when typed (rgb(189, 147, 249)), warning orange when busy (rgb(255, 184, 108)).
    • Clicking "..." opens ComposerOptionsSheet with Read-Only switch row (PhoneSheetSwitchRow).
    • Toggling the switch flips aria-checked from "false" to "true" and status text from "Off" to "On".

Verified via Unit Tests Only

  • Touch devices with speech recognition: mic button dictating into draft (GroupChatInput.toolbar.test.tsx):
    • Tested via unit test suite because the test host is a headless Linux environment without audio input hardware or browser Web Speech API backend (SpeechRecognition / webkitSpeechRecognition).
    • Wiring and prop propagation between useVoiceInput, GroupChatInput, and ToolbarControls are tested in GroupChatInput.toolbar.test.tsx.

What Was Not Checked Live

  • Physical microphone and Web Speech API engine (no audio hardware or browser speech engine in test environment).
  • Physical iOS and Android devices (exercised via phone viewport emulation at 390x844 under data-runtime="web-desktop").
  • Windows native OS desktop (tested on Linux).

Note on Queued Items Investigation

During initial testing, an empty queued message card was observed above the composer when injecting synthetic test items into groupChatQueues. Investigation confirmed:

  • Canonical type GroupChatQueuedItem (src/shared/group-chat-types.ts:433) defines text: string, not content.
  • The synthetic test script had passed { content: "..." } instead of { text: "..." }, leaving entry.text undefined and QueuedItemRow rendering empty text.
  • The mapper (entry) => ({ ... text: entry.text ... }) in GroupChatInput.tsx was untouched by commit 58c0df2df and behaves identically on refactor/composer-capabilities (step 4).
  • Testing with canonical { text: "..." } displays the card correctly on both branches.

Tests

  • src/__tests__/renderer/components/GroupChatInput.toolbar.test.tsx (new tests covering room toolbar integration and capabilities).
  • src/__tests__/renderer/components/GroupChatInput.test.tsx.
  • src/__tests__/renderer/components/InputArea/components/ToolbarControls.test.tsx.
  • src/__tests__/renderer/components/InputArea/ComposerOptionsSheet.test.tsx.
  • src/__tests__/renderer/components/ui/PhoneBottomSheet.test.tsx.
  • src/__tests__/renderer/components/ChatComposer/*.
  • npm run docs:verify passes with 0 findings.
  • npm run lint clean.

Raza Rauf added 2 commits October 22, 2026 11:41
Step 5 of the Group Chat composer parity plan. GroupChatInput drops its own
bottom row and renders ToolbarControls with GROUP_CHAT_COMPOSER_CAPABILITIES.
The room's Read-Only toggle moves into the toolbar unchanged: toolbarExtras on
desktop, and a new sheetExtras slot on a phone, so the room keeps the control
in every layout.

What a room user sees:
- Desktop: the toolbar row is laid out as in AI chat. The Read-Only tooltip
  now shows its live, rebindable chord, and the Prompt Composer tooltip reads
  the live binding the same way AI chat's does.
- Web-desktop: Attach reads "Upload Image to Host", and an Upload button
  copies local files to the host and mentions them in the draft (the existing
  handler, which already accepted a room; the draft is flushed first, as a
  drop does).
- Touch devices with speech recognition: a mic button dictates into the
  draft, which persists like typing.
- Narrow window: Read-Only and the Enter toggle fold behind a "..." button.
- Phone: the row holds attach, upload (web-desktop), mic, Send and "...".
  The bell column and the Prompt Composer button are not shown, as in AI
  chat. Send keeps the room's rules: disabled without text, and "Queue
  message" in the warning tint while the room is busy. "..." opens a sheet
  with a Read-Only switch. The Enter toggle is not shown on a phone, as in AI
  chat.
- Never shown in a room: Save to History, the permission pill, Thinking, and
  the model and effort pills.

Shared pieces:
- PhoneSheetSwitchRow (PhoneSheetRows): the on/off row extracted from the
  sheet's History row, which now renders through it unchanged.
- ComposerOptionsSheet gains extraRows, drawn after its sections.
- ToolbarControls gains sheetExtras, phone Send props (sendTitle,
  sendDisabled, sendTone, same meaning as ComposerSendColumn), and the image
  input's id, report name and file check as props (defaults are the AI
  composer's values). The per-tab agent props are now optional, read only when
  their capability is on. The sheet mounts when it has a section or surface
  rows, and the phone "..." is drawn only when it opens something, except in
  AI terminal mode, which keeps its existing "..." with no sheet behind it (a
  known gap, not changed here).

AI chat is unchanged; every existing ToolbarControls, ComposerOptionsSheet
and InputArea test passes without modification. CLI-UI-PARITY.md records
composer upload as an open gap (no verb: an agent is already on the host).
…bar (composer index lines trimmed to symbols and paths)
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review: 2
  • main
  • rc

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@greptile-apps

greptile-apps Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The changes affect composer workflows across Group Chat layouts and shared phone controls.

Fix late dictation results overwriting room drafts before merging.

Findings

  1. P1 Dictation overwrites room drafts ▶

Summary

Group Chat now uses the shared composer toolbar.

  • Group Chat rooms use the shared toolbar and keep their room-specific controls.
  • Phone composer sheets can show caller-specific settings and Send behavior.

Intentional limits stated by reachrazamair: phone layouts omit the bell, Prompt Composer, and Enter toggle; AI terminal mode keeps its known empty options button; room image support remains independent of the moderator; upload has no planned CLI verb because agents already run on the host.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Room[GroupChatInput] --> Toolbar[ToolbarControls]
  Toolbar --> Desktop[Desktop controls]
  Toolbar --> Phone[Phone Send and options]
  Phone --> Sheet[ComposerOptionsSheet]
  Sheet --> Switch[Read-Only switch]
  Toolbar --> Voice[useVoiceInput]
  Voice --> Draft[Room draft]
Loading

Reviews (1) · Last reviewed commit: "Merge refactor/composer-capabilities int..." · Reviewed by Greptile

Comment on lines +470 to +474
const voice = useVoiceInput({
currentValue: message,
onTranscriptionChange: setDraftValue,
focusRef: inputRef,
});

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 Dictation overwrites room drafts

Dictation keeps running when the user switches rooms or sends a message. useVoiceInput keeps the text and setDraftValue callback from when listening started. After a room switch, another speech result replaces the new room's visible draft while saving it under the old room's ID. After sending, another result puts the sent text back into the cleared draft.

Cancel recognition and ignore late results at both boundaries. Stopping alone can still deliver a final result.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed: useVoiceInput gained cancelVoiceInput(), which aborts the recognizer and drops every event the cancelled dictation still delivers (results, error, end) and returns the mic to idle. The room calls it on a room switch and on send, with tests for a late result after each and for uninterrupted dictation.

reachrazamair pushed a commit that referenced this pull request Oct 28, 2026
The Progress section and the Step sequence table still said steps 2-5
were on unmerged stacked branches. Step 2 merged into rc as #1890
(9d6f5b2), so its Progress row cites the rc merge commit as step 1's
does, and its Step sequence note says merged. Steps 3, 4 and 5 name
their PRs (#1891, #1892, #1945), and step 3 now sits off rc rather than
on step 2.

The step commits in both tables are unchanged: each branch's tip moved
on 2026-10-28 only by a CLAUDE.md index trim and the merges that carry
it up the stack, which complete no rows. The Progress intro says so.
Raza Rauf added 3 commits October 29, 2026 09:38
…bar (IME guard and picker Escape fixes)

# Conflicts:
#	docs/agent-guides/CANONICAL-UTILITIES.md
…ate results

useVoiceInput's only way out was stopVoiceInput, which asks the recognizer
to stop() and lets its final result land in the draft. That is right when
the user taps the mic off, and wrong when the draft no longer belongs to
the dictation (it was sent, or the composer now shows another
conversation): the result is written with the text and callback captured
when listening started.

cancelVoiceInput() abort()s instead, which per the spec raises no further
result, and marks the dictation cancelled, because a browser can still
deliver an event it had already queued. Every later onstart, onresult,
onerror and onend of a cancelled dictation is ignored, so it cannot write
the draft, refocus the input, or flip isListening on a dictation started
after it. isListening is false as soon as cancel returns, so the mic
shows idle. Each start opens its own session for this.

Additive: stopVoiceInput, toggle and unmount behave as before, and AI chat
does not call the new function, so its behaviour is unchanged.
…is sent

Greptile P1 on #1945. A dictation keeps the draft text and setDraftValue
from when it started, so a result arriving after a room switch replaced
the new room's visible draft while saving it under the old room's id, and
a result arriving after a send put the sent text back into the cleared
draft.

GroupChatInput now calls the hook's cancelVoiceInput() in the room-switch
effect (before the old draft is flushed and the new one loaded) and in
handleSend (with the draft cleared). Cancel aborts the recognizer and drops
any result it still delivers, since a stop alone can still produce a final
result, and the mic shows idle again. The useVoiceInput call and
setDraftValue move above the room-switch effect so both can reach it.

Group Chat behaviour changes:
- Switching rooms or sending ends a dictation in progress; the mic returns
  to idle and words spoken after that point are not written anywhere.
Raza Rauf added 2 commits October 30, 2026 15:16
Brings current origin/rc up through step 4. Resolves the conflict in
docs/agent-guides/CLI-UI-PARITY.md: rc now keeps Open gaps for buildable
work only and lists actions left without a verb on purpose under Not
mirrored by design. The composer Upload entry (no CLI verb, none planned)
therefore moves there as a bullet with its text unchanged; rc's rows are
kept as they are.

This branch has not been deployed

No deployments
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