Skip to content

Chat: extract useChatComposer, useVoiceComposer and the Composer component #842

Open

Description

@smart-ssh-planner

Goal

The chat input (draft, attachments, drop, prompt history, composer menu, extension composer actions, voice input) is one component with its own hooks, separate from transcript and sending.

Context

apps/smart-ssh-community/frontend/src/components/ChatPanel.tsx has 3,058 lines. The component function ChatPanel spans :401–1737 (JSX from ~:1444) with 44 useState and 17 useEffect. One effect (:662–998) subscribes to about 26 chat events (onChatAiRequestStarted, onChatTextDelta, onChatActionProposed, onRiskAssessmentUpdated, … onChatDocumentGenerated, wrappers in src/events.ts) and reduces them with many setters per event (setRetrying, setAiWait, setItems, …). About 1,320 further lines hold 15 sub-components and helpers: RiskBadge (:1737), RiskBadges (:1758), RiskHintFootnote (:1816), errorCardText/ErrorCardExtras (:1829–1895), ChatItemView (:1896–2292, exported), RunningCommandIndicator (:2293), ConfirmActionForm (:2336), reasonList/CoverageList (:2430–2510), QuickRuleButton (:2511–2720, exported), BinaryFileChangeHint (:2721), ActionResultView (:2734), TakeIntoNoteButton (:2803), ScrollablePre (:2855), DocumentCard (:2863), AssistantMessageView (:2951), attachmentLine (:3047); exported helpers ChatItem type (:119) and splitInjectionReason (:323). Tests: ChatPanel.test.tsx and 13 ChatPanel*.test.tsx files (about 130 tests). Line numbers are from develop before the colour-token migration and may shift slightly.

Composer state and handlers in ChatPanel on develop: draft (:409), attachments (:413), composer extension actions and drop handlers via useSyncExternalStore(subscribeComposer, …) (:411–412), prompt history navigation (src/promptHistoryNav.ts), handleInputKeyDown (:1266), handleDraftChange (:1390), voice state voiceEnabled/voicePhase/voiceFocusPending (:554–556) with VoiceHandlers from src/useVoiceInput.ts, and the composer JSX in the render block. Tests: ChatPanelInput.test.tsx, ChatPanelAttachments.test.tsx, ChatPanelVoiceInput.test.tsx.

End state of the series: no file over 800 lines, no function component over 400 lines, at most 12 useState per component; sub-components and hooks under src/components/chat/.

Requirements

  • src/components/chat/useChatComposer.ts: draft, attachments, drop routing, prompt-history navigation, key handling; exposes what handleSubmit needs (current draft and attachments, clear after send).
  • src/components/chat/useVoiceComposer.ts: voice phase, enable state, focus handling, the handlers passed to the voice input, insertion of recognised text into the draft.
  • src/components/chat/Composer.tsx: the input bar JSX (textarea, attachment chips, composer menu, extension actions, voice button, send/stop button), receiving the hooks' state and callbacks via props.
  • The composer extension slot behaves exactly as before (subscribeComposer/listComposerActions/listComposerDropHandlers).

Acceptance Criteria

  • useChatComposer, useVoiceComposer and Composer exist under src/components/chat/ and are used by ChatPanel.
  • Hook tests with renderHook: prompt-history up/down restores the draft; adding and removing an attachment; a drop routed to an extension drop handler; voice text is inserted at the cursor.
  • The tests in ChatPanelInput.test.tsx, ChatPanelAttachments.test.tsx and ChatPanelVoiceInput.test.tsx pass without changes to their assertions.
  • ChatPanel has at most 16 useState calls left.
  • The test count of ChatPanel*.test.tsx is not lower than before; all pass.
  • npm run lint, npx vitest run, npm run build and npm run test:e2e pass.

Technical Notes

  • Pure refactoring: no change to behaviour, visible text, i18n keys, CSS classes, data-testids, ARIA attributes, keyboard handling, invoked Tauri commands or listened events. Behaviour changes, if you find a bug, go into a separate issue (mention it in the PR).

  • Colour classes in these files are being migrated to theme tokens in a separate issue that must be merged first; keep exactly the classes that are on develop when you start.

  • Exports that other files or tests import from the original file keep working: either update the importers or re-export from the original path. The extension API (src/extensions/*, src/api.ts, src/components/ui) does not change.

  • Tests: count it(/test( in the listed test files before and after; the number must not decrease. Tests may move next to an extracted unit, but their assertions stay the same. Tests drive the user-visible behaviour through FileBrowserPanel/ChatPanel/ServerForm as today.

  • New code comments in English; existing German comments may move unchanged.

  • No filter, risk, redaction or credential logic is touched (frontend only).

  • Gate: npm run lint, npx vitest run, npm run build in apps/smart-ssh-community/frontend, plus npm run test:e2e if a browser test covers the component.

  • Do not add React.memo, useMemo around item rendering, or list virtualisation in this series. DocumentCard reads the document-action registry with listDocumentActions() during render (:2913); actions registered later only appear because the chat re-renders. Memoising the item views is only safe once that registry is reactive (subscribe + useSyncExternalStore, like the composer registry at :411–412), which is a separate change.

Spec impact: none

Related Work

Activity

  1. added this to the 0.8.0 milestone on Oct 26, 2026
  2. 21 remaining items

  3. marked this as blocking 15 issues on Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    frontendFrontend (React/TypeScript)

    Type

    Fields

    Priority

    P2 - Normal

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions