Skip to content

Commit acad0ea

Browse files
committed
fix(renderer): auto-save flush, a11y IDs, UX polish
- WorkflowEditor: flush pending save on tab close (REL-5) - useProjects: check all 3 collections in skip guard (CQ-2) - toggleSidebar: read state after set() via get() (CQ-3) - ConfirmDialog: unique aria IDs via useId() (CQ-4) - AgentSelector: show display names not raw IDs (CQ-7) - TemplateEditor: confirm before Delete key (CQ-8) - HomeScreen: remove stale greeting memoization (CQ-6) Co-Authored-By: Rooty
1 parent 395d325 commit acad0ea

7 files changed

Lines changed: 44 additions & 26 deletions

File tree

src/renderer/components/HomeScreen/HomeScreen.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ export function HomeScreen({
153153
} | null>(null)
154154
const cardMenuRef = useRef<HTMLDivElement>(null)
155155

156-
const dateStr = useMemo(() => formatDate(), [])
157-
const greeting = useMemo(() => getGreeting(), [])
156+
const dateStr = formatDate()
157+
const greeting = getGreeting()
158158

159159
const pinned = useMemo(() => projects.filter((p) => p.pinned), [projects])
160160

src/renderer/components/TemplateEditor/TemplateEditor.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,16 @@ export function TemplateEditor(): React.JSX.Element {
241241
const tag = (e.target as HTMLElement)?.tagName
242242
if (tag === 'INPUT' || tag === 'TEXTAREA') return
243243
e.preventDefault()
244-
void handleDelete()
244+
// CQ-8: Confirm before deleting to prevent accidental loss
245+
const tplName = templates.find((t) => t.id === selectedId)?.name ?? 'this template'
246+
if (window.confirm(`Delete "${tplName}"? This cannot be undone.`)) {
247+
void handleDelete()
248+
}
245249
}
246250
}
247251
window.addEventListener('keydown', handleKeyDown)
248252
return () => window.removeEventListener('keydown', handleKeyDown)
249-
}, [handleSave, handleDelete, selectedId])
253+
}, [handleSave, handleDelete, selectedId, templates])
250254

251255
return (
252256
<div className="template-editor">

src/renderer/components/shared/AgentSelector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import './AgentSelector.css'
66
const KNOWN_AGENTS = AGENTS.map((a) => ({
77
type: a.id,
88
icon: a.icon,
9-
name: a.id,
9+
name: a.name,
1010
desc: a.description,
1111
}))
1212

src/renderer/components/shared/ConfirmDialog.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useCallback } from 'react'
1+
import { useEffect, useCallback, useId } from 'react'
22
import { useFocusTrap } from '../../hooks/useFocusTrap'
33
import './ConfirmDialog.css'
44

@@ -23,6 +23,9 @@ export function ConfirmDialog({
2323
extraAction,
2424
}: ConfirmDialogProps): React.JSX.Element | null {
2525
const trapRef = useFocusTrap<HTMLDivElement>()
26+
const uniqueId = useId()
27+
const titleId = `${uniqueId}-title`
28+
const messageId = `${uniqueId}-message`
2629

2730
// Close on Escape
2831
useEffect(() => {
@@ -56,15 +59,15 @@ export function ConfirmDialog({
5659
onClick={handleBackdropClick}
5760
role="alertdialog"
5861
aria-modal="true"
59-
aria-labelledby="confirm-dialog-title"
60-
aria-describedby="confirm-dialog-message"
62+
aria-labelledby={titleId}
63+
aria-describedby={messageId}
6164
>
6265
<div className="confirm-dialog">
6366
<div className="confirm-dialog-body">
64-
<h3 id="confirm-dialog-title" className="confirm-dialog-title">
67+
<h3 id={titleId} className="confirm-dialog-title">
6568
{title}
6669
</h3>
67-
<p id="confirm-dialog-message" className="confirm-dialog-message">
70+
<p id={messageId} className="confirm-dialog-message">
6871
{message}
6972
</p>
7073
</div>

src/renderer/hooks/useProjects.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ export function useProjects(): UseProjectsReturn {
3131
useEffect(() => {
3232
let cancelled = false
3333
async function load(): Promise<void> {
34-
if (useAppStore.getState().projects.length > 0 || loadingInFlight) return
34+
const state = useAppStore.getState()
35+
if (
36+
(state.projects.length > 0 && state.templates.length > 0 && state.roles.length > 0) ||
37+
loadingInFlight
38+
)
39+
return
3540
loadingInFlight = true
3641
try {
3742
const [p, t, r] = await Promise.all([

src/renderer/screens/WorkflowEditor/WorkflowEditor.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,18 @@ export default function WorkflowEditor({ workflowId }: WorkflowEditorProps): Rea
9494

9595
useEffect(() => {
9696
return () => {
97-
if (saveTimerRef.current) clearTimeout(saveTimerRef.current)
97+
if (saveTimerRef.current) {
98+
clearTimeout(saveTimerRef.current)
99+
saveTimerRef.current = null
100+
// Flush pending save on unmount (fire-and-forget)
101+
if (latestWorkflowRef.current) {
102+
window.agentDeck.workflows.save(latestWorkflowRef.current).catch((err: unknown) => {
103+
window.agentDeck.log.send('error', 'workflow-editor', 'Unmount flush failed', {
104+
err: String(err),
105+
})
106+
})
107+
}
108+
}
98109
}
99110
}, [])
100111

src/renderer/store/slices/ui.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export interface UiSlice {
7373
clearWorktreePath: (sessionId: string) => void
7474
}
7575

76-
export const createUiSlice: StateCreator<AppState, [], [], UiSlice> = (set) => ({
76+
export const createUiSlice: StateCreator<AppState, [], [], UiSlice> = (set, get) => ({
7777
currentView: 'home',
7878
setCurrentView: (view) => set({ currentView: view }),
7979

@@ -177,11 +177,8 @@ export const createUiSlice: StateCreator<AppState, [], [], UiSlice> = (set) => (
177177
wfLogPanelWidth: 320,
178178

179179
toggleSidebar: () => {
180-
let next = false
181-
set((state) => {
182-
next = !state.sidebarOpen
183-
return { sidebarOpen: next }
184-
})
180+
set((state) => ({ sidebarOpen: !state.sidebarOpen }))
181+
const next = get().sidebarOpen
185182
window.agentDeck.layout.set({ sidebarOpen: next }).catch((err: unknown) => {
186183
window.agentDeck.log.send('debug', 'layout', 'Layout persist failed', { err: String(err) })
187184
})
@@ -195,15 +192,13 @@ export const createUiSlice: StateCreator<AppState, [], [], UiSlice> = (set) => (
195192
},
196193

197194
toggleSidebarSection: (key) => {
198-
let sections: UiSlice['sidebarSections'] | undefined
199-
set((state) => {
200-
sections = { ...state.sidebarSections, [key]: !state.sidebarSections[key] }
201-
return { sidebarSections: sections }
195+
set((state) => ({
196+
sidebarSections: { ...state.sidebarSections, [key]: !state.sidebarSections[key] },
197+
}))
198+
const sections = get().sidebarSections
199+
window.agentDeck.layout.set({ sidebarSections: sections }).catch((err: unknown) => {
200+
window.agentDeck.log.send('debug', 'layout', 'Layout persist failed', { err: String(err) })
202201
})
203-
if (sections)
204-
window.agentDeck.layout.set({ sidebarSections: sections }).catch((err: unknown) => {
205-
window.agentDeck.log.send('debug', 'layout', 'Layout persist failed', { err: String(err) })
206-
})
207202
},
208203

209204
setRightPanelWidth: (w) => {

0 commit comments

Comments
 (0)