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

@@ -4,6 +4,7 @@ import { shallow } from 'zustand/shallow'
import { immer } from 'zustand/middleware/immer'
import type { Tree, TreeStructure, TreeCreate, TreeUpdate, NodeType, TreeMarkdownValidationError, TreeMarkdownValidation } from '@/types'
import { treeStructureToMarkdownPreview } from '@/lib/treeMarkdownSync'
import { safeGetItem, safeSetItem, safeRemoveItem } from '@/lib/utils'
// Throttle helper: captures first call immediately, then throttles subsequent calls
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -304,7 +305,7 @@ export const useTreeEditorStore = create<TreeEditorState>()(
// Check for existing draft on init
initNewTree: () => {
const hasDraft = localStorage.getItem(DRAFT_STORAGE_KEY) !== null
const hasDraft = safeGetItem(DRAFT_STORAGE_KEY) !== null
set((state) => {
state.treeId = null
state.name = ''
@@ -360,7 +361,7 @@ export const useTreeEditorStore = create<TreeEditorState>()(
},
loadDraft: () => {
const draftJson = localStorage.getItem(DRAFT_STORAGE_KEY)
const draftJson = safeGetItem(DRAFT_STORAGE_KEY)
if (!draftJson) return false
try {
@@ -380,13 +381,13 @@ export const useTreeEditorStore = create<TreeEditorState>()(
})
return true
} catch {
localStorage.removeItem(DRAFT_STORAGE_KEY)
safeRemoveItem(DRAFT_STORAGE_KEY)
return false
}
},
discardDraft: () => {
localStorage.removeItem(DRAFT_STORAGE_KEY)
safeRemoveItem(DRAFT_STORAGE_KEY)
set((state) => {
state.hasDraft = false
})
@@ -868,12 +869,12 @@ export const useTreeEditorStore = create<TreeEditorState>()(
treeStructure: state.treeStructure,
savedAt: new Date().toISOString()
}
localStorage.setItem(DRAFT_STORAGE_KEY, JSON.stringify(draft))
safeSetItem(DRAFT_STORAGE_KEY, JSON.stringify(draft))
set((s) => { s.draftSavedAt = new Date() })
},
markSaved: () => {
localStorage.removeItem(DRAFT_STORAGE_KEY)
safeRemoveItem(DRAFT_STORAGE_KEY)
set((state) => {
state.isDirty = false
state.lastSavedAt = new Date()