MCPcopy Create free account
hub / github.com/tiann/hapi / SessionChatInner

Function SessionChatInner

web/src/components/SessionChat.tsx:302–1193  ·  view source on GitHub ↗
(props: SessionChatProps)

Source from the content-addressed store, hash-verified

300}
301
302function SessionChatInner(props: SessionChatProps) {
303 const { haptic } = usePlatform()
304 const { t } = useTranslation()
305 const navigate = useNavigate()
306 const sessionInactive = !props.session.active
307 const inactiveCanResume = inactiveSessionCanResume(props.session, props.messages.length)
308 const terminalSupported = isRemoteTerminalSupported(props.session.metadata)
309 const normalizedCacheRef = useRef<Map<string, { source: DecryptedMessage; normalized: NormalizedMessage | null }>>(new Map())
310 const blocksByIdRef = useRef<Map<string, ChatBlock>>(new Map())
311 const visibleGroupsRef = useRef<ToolGroupBlock[]>([])
312 const [forceScrollToken, setForceScrollToken] = useState(0)
313 const [outlineOpen, setOutlineOpen] = useState(false)
314 const [cursorSelectedBase, setCursorSelectedBase] = useState('auto')
315 const lastSyncedCursorModelRef = useRef<string | null | undefined>(undefined)
316 const scratchlist = useScratchlist(props.session.id)
317 const [scratchlistMode, setScratchlistMode] = useState(false)
318 // Mode resets across sessions implicitly: SessionChat is keyed by
319 // session.id at the public-export boundary, so a session switch
320 // remounts SessionChatInner from scratch and `scratchlistMode`
321 // initializes to false again. (Previous effect-based reset was
322 // racy on first paint - see public-export comment for context.)
323 const handleScratchlistToggle = useCallback(() => {
324 setScratchlistMode((m) => !m)
325 }, [])
326 /**
327 * Global keyboard shortcut: Ctrl/Cmd + Shift + S toggles scratchlist
328 * mode (open/close drawer + flip composer routing).
329 *
330 * Convention matches the v1 always-visible panel's shortcut so muscle
331 * memory carries over. Other composer-adjacent globals in the app use
332 * the same modifier shape: Ctrl/Cmd-m cycles agent model in
333 * HappyComposer. Ctrl/Cmd-Shift-S is unreserved by Chrome / Firefox /
334 * Safari at the app level (browser Save As is Ctrl-S / Cmd-S, no
335 * Shift), so requiring Shift keeps the user's save-page muscle memory
336 * working. Bound at SessionChat scope (not the drawer) because the
337 * drawer is unmounted while mode is off — a drawer-scoped listener
338 * couldn't reopen it.
339 *
340 * Skipped when focus is inside an open dialog or single-line input
341 * (see isScratchlistHotkeyBlockedTarget). Otherwise fires for any
342 * focus target - composer textarea is the expected case so it's
343 * deliberately allowed. Window-level shortcut without target
344 * filtering would silently toggle mode "behind" modal dialogs
345 * (rename, schedule picker, FUE callout); the bot caught this on
346 * PR #798.
347 */
348 useEffect(() => {
349 const onKeyDown = (e: globalThis.KeyboardEvent) => {
350 if (!isScratchlistToggleHotkey(e)) return
351 if (isScratchlistHotkeyBlockedTarget(e.target)) return
352 e.preventDefault()
353 setScratchlistMode((m) => !m)
354 }
355 window.addEventListener('keydown', onKeyDown)
356 return () => window.removeEventListener('keydown', onKeyDown)
357 }, [])
358 /**
359 * onSend wrapper: when scratchlist mode is on AND the submission is

Callers

nothing calls this directly

Calls 15

usePlatformFunction · 0.90
useTranslationFunction · 0.90
inactiveSessionCanResumeFunction · 0.90
useScratchlistFunction · 0.90
useCodexModelsFunction · 0.90
useOpencodeModelsFunction · 0.90
useCursorModelsFunction · 0.90
mergeCursorCliModelSkusFunction · 0.90

Tested by

no test coverage detected