Skip to content

Commit d9210e6

Browse files
Harden Independent Panel state handling and async safety
- Guard before syncing to avoid null access - Make robust: clear selection when no valid sessions - Add and guard deletion flow to avoid setState after unmount - Keep debounce and index-based filtering intact for performance
1 parent 8f27ec4 commit d9210e6

File tree

2 files changed

+30
-7
lines changed

2 files changed

+30
-7
lines changed

src/components/DeleteButton/index.jsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ function DeleteButton({ onConfirm, size, text }) {
2929
fontSize: '10px',
3030
...(waitConfirm ? {} : { display: 'none' }),
3131
}}
32+
disabled={confirmingRef.current}
33+
aria-busy={confirmingRef.current ? 'true' : 'false'}
3234
onMouseDown={(e) => {
3335
e.preventDefault()
3436
e.stopPropagation()
3537
}}
3638
onBlur={() => {
37-
setWaitConfirm(false)
39+
if (!confirmingRef.current) setWaitConfirm(false)
3840
}}
3941
onClick={async (e) => {
4042
if (confirmingRef.current) return
@@ -43,9 +45,13 @@ function DeleteButton({ onConfirm, size, text }) {
4345
confirmingRef.current = true
4446
try {
4547
await onConfirm()
48+
setWaitConfirm(false)
49+
} catch (err) {
50+
// Keep confirmation visible to allow retry; optionally log
51+
// eslint-disable-next-line no-console
52+
console.error(err)
4653
} finally {
4754
confirmingRef.current = false
48-
setWaitConfirm(false)
4955
}
5056
}}
5157
>
@@ -57,7 +63,8 @@ function DeleteButton({ onConfirm, size, text }) {
5763
role="button"
5864
tabIndex={0}
5965
aria-label={text}
60-
style={waitConfirm ? { display: 'none' } : {}}
66+
aria-hidden={waitConfirm ? 'true' : undefined}
67+
style={waitConfirm ? { visibility: 'hidden' } : {}}
6168
onKeyDown={(e) => {
6269
if (e.key === 'Enter' || e.key === ' ') {
6370
e.preventDefault()

src/pages/IndependentPanel/App.jsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,14 @@ function App() {
4646
const setSessionIdSafe = async (sessionId) => {
4747
stopCurrentPort()
4848
const { session, currentSessions } = await getSession(sessionId)
49-
if (session) setSessionId(sessionId)
50-
else if (currentSessions.length > 0) setSessionId(currentSessions[0].sessionId)
49+
if (session && session.sessionId) {
50+
setSessionId(session.sessionId)
51+
} else if (Array.isArray(currentSessions) && currentSessions.length > 0) {
52+
setSessionId(currentSessions[0].sessionId)
53+
} else {
54+
setSessionId(null)
55+
setCurrentSession(null)
56+
}
5157
}
5258

5359
useEffect(() => {
@@ -78,10 +84,10 @@ function App() {
7884

7985
// Sync collapsed state from persisted config
8086
useEffect(() => {
81-
if ('independentPanelCollapsed' in config) {
87+
if (config && typeof config === 'object' && 'independentPanelCollapsed' in config) {
8288
setCollapsed(!!config.independentPanelCollapsed)
8389
}
84-
}, [config.independentPanelCollapsed])
90+
}, [config && config.independentPanelCollapsed])
8591

8692
useEffect(() => {
8793
// eslint-disable-next-line
@@ -154,6 +160,15 @@ function App() {
154160
return () => clearTimeout(id)
155161
}, [searchQuery])
156162

163+
// Track mount state to guard async setState after unmount
164+
const isMountedRef = useRef(true)
165+
useEffect(() => {
166+
isMountedRef.current = true
167+
return () => {
168+
isMountedRef.current = false
169+
}
170+
}, [])
171+
157172
// Keyboard shortcuts: Ctrl/Cmd+F and '/' to focus search
158173
useEffect(() => {
159174
const focusSearch = () => {
@@ -298,6 +313,7 @@ function App() {
298313
onConfirm={async () => {
299314
const deletedId = session.sessionId
300315
const updatedSessions = await deleteSession(deletedId)
316+
if (!isMountedRef.current) return
301317
setSessions(updatedSessions)
302318
if (!updatedSessions || updatedSessions.length === 0) {
303319
stopCurrentPort()

0 commit comments

Comments
 (0)