Repository navigation
Chat: useChatTranscript hook with a pure reducer for chat events #838
Description
Goal
How chat events turn into the visible transcript is one pure function that can be tested without a DOM, and the chat panel subscribes to events through one hook.
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.
The event effect (:662–998) updates items, retrying, aiWait, injectionChecks, context usage, budget-waiting cards (with timers), web-research notices, question cards and document cards with several setters per event (e.g. onChatTextDelta ~:676–684 copies the whole items array and appends the delta). The effect depends only on sessionId (previous issue in this series).
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/chatTranscriptReducer.ts: a pure reducerchatTranscriptReducer(state, action)with one action type per chat event (plus local actions the panel dispatches today through the same setters, e.g. clearing on "New chat", loading history). State covers everything the effect writes today.src/components/chat/useChatTranscript.ts:useChatTranscript(sessionId, options)subscribes to all chat events (filtered bysessionId), dispatches to the reducer and exposes state plus the dispatch functions the panel needs. The subscription effect depends only onsessionId; values such as the second-opinion setting are read through refs.- Timers (budget-waiting card removal) are owned by the hook and cleared on unmount and session change.
ChatPaneluses the hook; the moveduseStatecalls disappear from it.- Item identity: an update to the last assistant item creates a new object only for that item; all other items keep their object identity (prepares later memoisation, but no
memois added now).
Acceptance Criteria
-
chatTranscriptReducerhas unit tests (no React, no DOM) for every action type, including: deltas append to the last assistant item or start a new one; an action proposal ends the AI wait state; a risk-assessment update changes only the matching action item; retry/error/empty/refused/cancelled responses; context compaction; question asked; document generated. - A reducer test asserts that applying a text delta keeps the object identity of all items except the last.
- The test from the previous issue (toggle second opinion while deltas arrive, no delta lost) still passes.
-
ChatPanelhas at most 25useStatecalls left. - The test count of
ChatPanel*.test.tsx(plus the new reducer tests) is 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. -
One shared event hub (one Tauri listener per event name for all open tabs) would reduce listener count with many tabs, but is out of scope here.
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; next: Chat: extract useChatComposer, useVoiceComposer and the Composer component #842, 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