Repository navigation
Chat: extract useChatComposer, useVoiceComposer and the Composer component #842
Description
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 whathandleSubmitneeds (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,useVoiceComposerandComposerexist undersrc/components/chat/and are used byChatPanel. - 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.tsxandChatPanelVoiceInput.test.tsxpass without changes to their assertions. -
ChatPanelhas at most 16useStatecalls left. - The test count of
ChatPanel*.test.tsxis not lower than before; all pass. -
npm run lint,npx vitest run,npm run buildandnpm run test:e2epass.
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
developwhen 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 throughFileBrowserPanel/ChatPanel/ServerFormas 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 buildinapps/smart-ssh-community/frontend, plusnpm run test:e2eif a browser test covers the component. -
Do not add
React.memo,useMemoaround item rendering, or list virtualisation in this series.DocumentCardreads the document-action registry withlistDocumentActions()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
- Previous: Chat: toggling the risk second opinion re-subscribes all chat events and can drop streamed text #829, Chat: move ChatItemView, the action card parts, QuickRuleButton, DocumentCard and AssistantMessageView into components/chat/ #832, Chat: useChatTranscript hook with a pure reducer for chat events #838; next: Chat: extract useChatSend and the action-decision handlers; ChatPanel within the size limits #847
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
21 remaining items