You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Systematic source-code review of all interactive dashboard components. Found 26 bugs across modals, approval flow, mobile, and keyboard navigation.
π΄ P0 β Broken (2)
1. CreateSessionModal.tsx β Loading state gets stuck on validation failure
~Line 97-110 β In single-mode handleSubmit, setLoading(true) is called, then validateWorkDir() runs outside the try/finally block. If validation fails and returns early, finally never executes, and the submit button stays permanently disabled. User must close and reopen the modal to retry. Fix: Move validation inside the try block, or call setLoading(false) in the validation-failure return path.
2. useSessionApproval.ts β Countdown timer is frozen (never decrements)
~Lines 73-99 β remainingMs is computed via useMemo keyed on pendingApproval?.expiresAt (a string that never changes). The interval tries to force re-renders with setPendingApproval(prev => prev ? {...prev} : null), but since expiresAt doesn't change, useMemo returns the cached value every tick. The TTL display is frozen at its initial value. Fix: Replace with useState for remainingMs, updated directly by the interval:
3. ApprovalBanner.tsx β No loading/disabled state on APPROVE/REJECT buttons
~Lines 77-99 β Buttons have no disabled prop or loading indicator. Double-tap (common on phone) sends duplicate approve API calls. Fix: Accept isLoading prop, wire to disabled.
~Line 240 β Rapid clicks can fire onSubmit twice before React batches the setLoading(true) state update. Fix: Use a synchronous ref guard (if (submittingRef.current) return; submittingRef.current = true;).
5. CreateSessionModal.tsx β workDirError rendered under Name field, not WorkDir
~Lines 174-180 β Error message is visually attached to the wrong input. Fix: Move the workDirError conditional render to the WorkDir field's container.
6. NewSessionDrawer.tsx β No AbortController on session creation
~Lines 78-100 β If user closes drawer during submission, the request continues. On success, it navigates to the new session even though the user explicitly closed the drawer. Fix: Add AbortController with signal passed to createSessionWithFallback, abort on close.
7. NewSessionDrawer.tsx β addRecentDir called on failure
~Line 97 β finally block adds the workdir to recents regardless of success/failure. Failed attempts pollute the list. Fix: Move addRecentDir to the success path only.
8. SaveTemplateModal.tsx β AbortController created but signal never passed to API
~Line 48 β AbortController exists but signal isn't passed to createTemplate(). Abort is a no-op. Fix: Pass signal to the API call.
9. TemplateModal.tsx β Same abort signal issue
~Lines 97-106 β AbortController created but never connected to createTemplate/updateTemplate. Fix: Pass signal to the API calls.
~Line 75 β If a touch is cancelled (system gesture, notification), stopHold never fires. The setInterval timer runs indefinitely. Fix: Add onTouchCancel={stopHold}.
~Line 75 β On mobile, long-press triggers browser context menu, breaking the hold gesture. Fix: Add onContextMenu={(e) => e.preventDefault()}.
12. useKeyboardShortcuts.ts β Sequence shortcuts (g o, g s, etc.) declared but never matched
~Line 52 β The hook explicitly continues past any shortcut with .sequence. Five navigation shortcuts are listed in help but do nothing. Fix: Implement sequence matching: track first key, wait for second within timeout.
~Lines 57-61 β For variant="danger", the confirm button gets focus. Safer pattern is to focus cancel to prevent accidental keyboard confirmation. Fix: Auto-focus cancel button when variant === "danger".
π‘ P2 β Polish (10)
15. StreamSplitView.tsx β No touch support for pane resize (mobile can't resize)
Frontend UI Bug Audit β May 28, 2026
Systematic source-code review of all interactive dashboard components. Found 26 bugs across modals, approval flow, mobile, and keyboard navigation.
π΄ P0 β Broken (2)
1.
CreateSessionModal.tsxβ Loading state gets stuck on validation failure~Line 97-110 β In single-mode
handleSubmit,setLoading(true)is called, thenvalidateWorkDir()runs outside thetry/finallyblock. If validation fails and returns early,finallynever executes, and the submit button stays permanently disabled. User must close and reopen the modal to retry.Fix: Move validation inside the
tryblock, or callsetLoading(false)in the validation-failure return path.2.
useSessionApproval.tsβ Countdown timer is frozen (never decrements)~Lines 73-99 β
remainingMsis computed viauseMemokeyed onpendingApproval?.expiresAt(a string that never changes). The interval tries to force re-renders withsetPendingApproval(prev => prev ? {...prev} : null), but sinceexpiresAtdoesn't change,useMemoreturns the cached value every tick. The TTL display is frozen at its initial value.Fix: Replace with
useStateforremainingMs, updated directly by the interval:π P1 β Frustrating (12)
3.
ApprovalBanner.tsxβ No loading/disabled state on APPROVE/REJECT buttons~Lines 77-99 β Buttons have no
disabledprop or loading indicator. Double-tap (common on phone) sends duplicate approve API calls.Fix: Accept
isLoadingprop, wire todisabled.4.
CreateSessionModal.tsxβ Template mode double-click creates duplicate sessions~Line 240 β Rapid clicks can fire
onSubmittwice before React batches thesetLoading(true)state update.Fix: Use a synchronous ref guard (
if (submittingRef.current) return; submittingRef.current = true;).5.
CreateSessionModal.tsxβworkDirErrorrendered under Name field, not WorkDir~Lines 174-180 β Error message is visually attached to the wrong input.
Fix: Move the
workDirErrorconditional render to the WorkDir field's container.6.
NewSessionDrawer.tsxβ No AbortController on session creation~Lines 78-100 β If user closes drawer during submission, the request continues. On success, it navigates to the new session even though the user explicitly closed the drawer.
Fix: Add
AbortControllerwith signal passed tocreateSessionWithFallback, abort on close.7.
NewSessionDrawer.tsxβaddRecentDircalled on failure~Line 97 β
finallyblock adds the workdir to recents regardless of success/failure. Failed attempts pollute the list.Fix: Move
addRecentDirto the success path only.8.
SaveTemplateModal.tsxβ AbortController created but signal never passed to API~Line 48 β
AbortControllerexists butsignalisn't passed tocreateTemplate(). Abort is a no-op.Fix: Pass
signalto the API call.9.
TemplateModal.tsxβ Same abort signal issue~Lines 97-106 β
AbortControllercreated but never connected tocreateTemplate/updateTemplate.Fix: Pass
signalto the API calls.10.
HoldButton.tsxβ MissingonTouchCancelhandler (interval leak)~Line 75 β If a touch is cancelled (system gesture, notification),
stopHoldnever fires. ThesetIntervaltimer runs indefinitely.Fix: Add
onTouchCancel={stopHold}.11.
HoldButton.tsxβ MissingonContextMenuprevention~Line 75 β On mobile, long-press triggers browser context menu, breaking the hold gesture.
Fix: Add
onContextMenu={(e) => e.preventDefault()}.12.
useKeyboardShortcuts.tsβ Sequence shortcuts (g o,g s, etc.) declared but never matched~Line 52 β The hook explicitly
continues past any shortcut with.sequence. Five navigation shortcuts are listed in help but do nothing.Fix: Implement sequence matching: track first key, wait for second within timeout.
13.
CommandPalette.tsxβ Escape handler doesn'tstopPropagation~Line 105 β Both the palette's Escape handler and parent handlers fire from the same keypress.
Fix: Add
e.stopPropagation()+e.preventDefault().14.
ConfirmDialog.tsxβ Destructive actions auto-focus confirm button instead of cancel~Lines 57-61 β For
variant="danger", the confirm button gets focus. Safer pattern is to focus cancel to prevent accidental keyboard confirmation.Fix: Auto-focus cancel button when
variant === "danger".π‘ P2 β Polish (10)
15.
StreamSplitView.tsxβ No touch support for pane resize (mobile can't resize)~Lines 18-36 β Only mouse events. Touch users cannot resize terminal/transcript split.
16.
TranscriptView.tsxβj/knavigation doesn't scroll into view~Lines 105-116 β
focusedIndexupdates butvirtualizer.scrollToIndex()is never called. Focus is invisible for off-screen items.17.
TranscriptView.tsxβ Clipboard copy fails silently on non-HTTPS~Lines 117-126 β
navigator.clipboard.writeText()not wrapped in try/catch.18.
TranscriptView.tsxβ Global keyboard listeners conflict across components~Lines 105-130 β
j/khandlers onwindowdon't check if transcript is active. Can fire while typing in other inputs.19.
DriverControlBar.tsx+PauseControlBar.tsxβ No local double-click guard~Lines 46-52 / 94-101 β Brief race window between click and parent
isLoadingpropagation.20.
SessionTable.tsxβ Dual page state (local + API) can desync~Lines 80-86 β Local
pageandpaginationfrom API response both track current page. UI shows stale page number on slow networks.21.
useSessionIntervention.tsβ No SSE subscription (state goes stale)Entire file β Only fetches on mount. Server-side intervention changes aren't reflected until manual refresh.
22.
ConnectionBanner.tsxβ Countdown loops infinitely with no attempt counter~Line 28 β Resets to 5s forever. No indication of retry count or progress.
23.
Layout.tsxβ Mobile users have no way to open command palette~Line 40 β Cmd+K disabled on touch, footer button hidden on mobile. Zero access.
24.
CommandPalette.tsxβ Hardcoded English placeholder~Line 138 β Uses literal string instead of
t()i18n function.25.
useSessionEvents.tsβvoid clearSessionis a no-op dead expression~Line 95 β Evaluates function reference and discards.
26.
KeyboardShortcutsHelp/index.tsx+ToastContainer.tsxβ Variabletshadows i18nuseT()~Lines 17 / 155 β
const t = setTimeout(...)shadows the i18n function.Summary
Recommended fix order: