Skip to content

feat(group-chat): add phone fold handle and quit-when-idle pill to room composer - #1958

Open
reachrazamair wants to merge 1 commit into
refactor/composer-textareafrom
refactor/composer-step7
Open

reachrazamair wants to merge 1 commit into
refactor/composer-textareafrom
refactor/composer-step7

Conversation

@reachrazamair

Copy link
Copy Markdown
Contributor

Summary

Step 7 of the Group Chat composer parity plan (Plans/group-chat-composer-parity.md), covering rows 52 (the phone fold handle, completing what step 5 started) and 50 (the app-wide quit-when-idle indicator). Stacked on #1952 (refactor/composer-textarea); review that first.

  • Phone fold (PhoneComposerHandle + usePersistedToggle):
    • Re-exports the session-free PhoneComposerHandle from src/renderer/components/ChatComposer/index.ts.
    • GroupChatInput exports GROUP_CHAT_PHONE_COMPOSER_COLLAPSED_KEY = 'phone.groupChatComposer.collapsed' and calls usePersistedToggle(GROUP_CHAT_PHONE_COMPOSER_COLLAPSED_KEY, true).
    • On a phone (usePhoneLayout()), the room composer starts folded behind PhoneComposerHandle and remembers its state across room switches and reloads independently of AI chat's PHONE_COMPOSER_COLLAPSED_KEY.
    • When folded, the handle shows the pulsing busy dot (busy={state !== 'idle'}) and the draft pencil (hasDraft={message.trim().length > 0 || stagedImages.length > 0}), wrapped in toastAvoidRef so toasts still clear the handle. All hooks (useDraftPersistence, useVoiceInput, useAutosizeTextarea, useMentionPicker) run above the fold branch, so the draft, staged images, voice state and autosize survive folding and unfolding.
    • When unfolded on a phone, the handle sits at the top of the composer (px-3 pb-3 pt-0, matching InputArea).
  • Quit-when-idle pill (QuitWhenIdleIndicator):
    • Renders <QuitWhenIdleIndicator theme={theme} /> at the top of the unfolded room composer, matching InputArea.
    • Confirmed that useQuitWhenIdle -> collectActiveOperations() already counts busy group chats via getBusyGroupChatIds(gcStore.groupChats, gcStore) (moderator thinking or any participant working, across both the active room and background rooms), so the pill's "Quitting When All Operations Finish" label is accurate in a room with no change to the quit watcher.

Room behaviour changes

  • Phone layout: the room composer starts folded behind PhoneComposerHandle (remembered under phone.groupChatComposer.collapsed, separate from AI chat's phone.composer.collapsed). Tap or swipe up reveals the composer; tap or swipe down on the top handle folds it back. While folded, the handle shows a pulsing warning dot when the room is busy (moderator-thinking or agent-working) and a pencil icon when unsent text or staged images are waiting.
  • Quit when idle (desktop and phone): when a deferred quit is armed, the "Quitting When All Operations Finish" pill with its Cancel button now appears at the top of the room composer, just as it does above the AI composer.
  • Desktop layout: unchanged when no deferred quit is armed (QuitWhenIdleIndicator returns null, and PhoneComposerHandle is gated on usePhoneLayout()).

CLI parity and docs

  • docs/agent-guides/CLI-UI-PARITY.md: under Not mirrored by design, extends the renderer localStorage view-preferences entry to cover the phone composer fold keys (phone.composer.collapsed and phone.groupChatComposer.collapsed) and adds an entry for Quit When Idle (armed from the native desktop quit-confirmation modal; read-only indicator with Cancel).
  • docs/agent-guides/WEB-MOBILE.md: updates the Composer row in the phone layout table to cover both InputArea and GroupChatInput and their per-surface localStorage keys.

Not in this step

  • No ChatComposer shell yet. That is step 8.
  • No change to useQuitWhenIdle or collectActiveOperations. Both already wait for busy rooms and moderators.

Test plan

  • npm run lint
  • npm run lint:eslint
  • npm run docs:verify
  • npx vitest run on the stack's test set (40 files, 971 tests): composerKeyDown.test.ts, GroupChatInput.test.tsx, GroupChatInput.toolbar.test.tsx, useInputKeyDown.test.ts, useInputKeyDown.focus.test.tsx, useMainKeyboardHandler.test.ts, useInputHandlers.test.ts, InputArea.test.tsx, components/InputArea/, components/ChatComposer/, ui/PhoneBottomSheet.test.tsx, FilePreview/FilePreviewHeaderPhone.test.tsx, useVoiceInput.test.ts
  • New tests in GroupChatInput.toolbar.test.tsx:
    • Phone composer starts folded by default, unfolds on tap, and folds back.
    • Room fold state persists under phone.groupChatComposer.collapsed independently of AI chat's phone.composer.collapsed.
    • Draft text and staged images survive folding and unfolding, and the folded handle renders phone-composer-handle-busy and phone-composer-handle-draft (including image-only drafts).
    • Desktop renders no phone fold handle.
    • QuitWhenIdleIndicator renders in a room only while useQuitWhenIdleStore is armed, and clicking Cancel disarms it.

…om composer

Step 7 of the Group Chat composer parity plan (rows 52 and 50).

Phone fold:
- Re-export the session-free PhoneComposerHandle from ChatComposer/index.ts.
- GroupChatInput exports GROUP_CHAT_PHONE_COMPOSER_COLLAPSED_KEY
  ('phone.groupChatComposer.collapsed') and tracks its fold with
  usePersistedToggle(GROUP_CHAT_PHONE_COMPOSER_COLLAPSED_KEY, true), starting
  folded on a phone and persisting separately from AI chat's
  PHONE_COMPOSER_COLLAPSED_KEY.
- Every hook in GroupChatInput runs before the folded branch, so the draft,
  staged images, voice state and autosize survive folding and unfolding, and
  toastAvoidRef wraps the folded handle so toasts still clear it.

Quit-when-idle pill:
- Render QuitWhenIdleIndicator at the top of the unfolded room composer,
  matching InputArea. useQuitWhenIdle -> collectActiveOperations() already
  counts busy group chats (moderator thinking or any participant working) via
  getBusyGroupChatIds, so no change to the quit watcher is needed.

Group Chat behaviour changes:
- Phone: the room composer starts folded behind PhoneComposerHandle (remembered
  under phone.groupChatComposer.collapsed). While folded, the handle shows a
  pulsing warning dot when the room is busy and a pencil icon when unsent text
  or staged images are waiting. Tap or swipe up reveals the composer; tap or
  swipe down on the top handle folds it back.
- Desktop and phone (unfolded): when Quit When Idle is armed, the "Quitting
  When All Operations Finish" pill with its Cancel button appears at the top of
  the room composer.
- Desktop with no quit armed is unchanged.

CLI-UI-PARITY.md records the phone composer fold keys and Quit When Idle under
Not mirrored by design, and WEB-MOBILE.md updates the phone composer row.
@coderabbitai

coderabbitai Bot commented Oct 30, 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 30, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The PR changes Group Chat composer behavior on phones and adds a quit-status view to room composers.

The PR appears safe to merge, with a non-blocking draft-height issue on phones.

Findings

  1. P2 Reopened drafts stay one row ▶

Summary

Adds a remembered phone fold handle and the existing quit-when-idle pill to the Group Chat composer.

  • Group Chat’s composer folds behind a handle on phones.
  • Group Chat shows the quit-when-idle pill when a quit is armed.

Reviews (1) · Last reviewed commit: "feat(group-chat): add phone fold handle ..." · Reviewed by Greptile

Comment on lines +600 to +611
if (phone && composerFold.value) {
return (
<div ref={toastAvoidRef}>
<PhoneComposerHandle
theme={theme}
collapsed
onToggle={composerFold.toggle}
busy={isBusy}
hasDraft={phoneHandleHasDraft}
/>
</div>
);

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 Reopened drafts stay one row

Folding and reopening a multiline draft leaves the textarea at its single-row height until the user edits it. The fold removes the textarea, but reopening changes none of useAutosizeTextarea's dependencies, so the new textarea never gets measured. Saved multiline drafts first opened from the default folded state have the same problem.

Pass the fold state through resetKey so reopening measures the draft again, and add a test for this case.

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