fix: split category fetch, safe localStorage, aria-labels on icon buttons

- TreeLibraryPage: split categories into a mount-only fetch so filter
  changes only re-fetch trees (not categories every time)
- Add safeGetItem/safeSetItem/safeRemoveItem helpers in utils.ts to
  prevent crashes in private browsing or when storage is unavailable
- Replace raw localStorage calls in ScratchpadSidebar, TreeNavigationPage,
  TreeEditorPage, and treeEditorStore with safe wrappers
- Add aria-label to 20 icon-only buttons across 8 component files
  for screen reader accessibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-02-10 18:41:46 -05:00
parent 0c9fbdc90b
commit d155c83ef0
14 changed files with 81 additions and 28 deletions

View File

@@ -1,5 +1,5 @@
import { useState, useEffect, useRef, useCallback } from 'react'
import { cn } from '@/lib/utils'
import { cn, safeGetItem, safeSetItem } from '@/lib/utils'
import { MarkdownContent } from '@/components/ui/MarkdownContent'
import { StickyNote, X, Eye, Pencil, Loader2 } from 'lucide-react'
@@ -14,7 +14,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
const [content, setContent] = useState(initialContent)
const [lastSaved, setLastSaved] = useState(initialContent)
const [isCollapsed, setIsCollapsed] = useState(() => {
return localStorage.getItem('scratchpad-collapsed') !== 'false'
return safeGetItem('scratchpad-collapsed') !== 'false'
})
const [isSaving, setIsSaving] = useState(false)
const [showPreview, setShowPreview] = useState(false)
@@ -43,7 +43,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
// Persist collapse state and notify parent
useEffect(() => {
localStorage.setItem('scratchpad-collapsed', String(isCollapsed))
safeSetItem('scratchpad-collapsed', String(isCollapsed))
onOpenChange?.(!isCollapsed)
}, [isCollapsed, onOpenChange])
@@ -130,6 +130,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
isCollapsed ? 'opacity-100' : 'pointer-events-none opacity-0'
)}
title="Open scratchpad (Ctrl+/)"
aria-label="Open scratchpad (Ctrl+/)"
>
<StickyNote className="h-5 w-5" />
{hasUnsavedChanges && (
@@ -176,6 +177,7 @@ export function ScratchpadSidebar({ sessionId, initialContent, onSave, onOpenCha
onClick={() => setIsCollapsed(true)}
className="rounded p-1 text-white/40 hover:bg-white/10 hover:text-white"
title="Close scratchpad"
aria-label="Close scratchpad"
>
<X className="h-4 w-4" />
</button>