diff --git a/src/components/NoteList.test.tsx b/src/components/NoteList.test.tsx
index 6e00d480..6143e21e 100644
--- a/src/components/NoteList.test.tsx
+++ b/src/components/NoteList.test.tsx
@@ -1,6 +1,7 @@
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
-import { NoteList, getSortComparator, filterEntries } from './NoteList'
+import { NoteList } from './NoteList'
+import { getSortComparator, filterEntries } from '../utils/noteListHelpers'
import type { VaultEntry, SidebarSelection } from '../types'
const allSelection: SidebarSelection = { kind: 'filter', filter: 'all' }
diff --git a/src/components/NoteList.tsx b/src/components/NoteList.tsx
index c5849bab..99affcc3 100644
--- a/src/components/NoteList.tsx
+++ b/src/components/NoteList.tsx
@@ -11,14 +11,10 @@ import {
type SortOption, type SortDirection, type SortConfig, type RelationshipGroup,
getSortComparator,
buildRelationshipGroups, filterEntries,
- sortByModified, relativeDate, getDisplayDate,
+ relativeDate, getDisplayDate,
loadSortPreferences, saveSortPreferences,
} from '../utils/noteListHelpers'
-// Re-export for consumers
-export { sortByModified, filterEntries, buildRelationshipGroups, getSortComparator }
-export type { SortOption }
-
interface NoteListProps {
entries: VaultEntry[]
selection: SidebarSelection
@@ -41,6 +37,7 @@ function PinnedCard({ entry, typeEntryMap, onSelectNote, showDate }: {
const Icon = getTypeIcon(entry.isA, te?.icon)
return (
onSelectNote(entry)}>
+ {/* eslint-disable-next-line react-hooks/static-components */}
{entry.title}
{entry.snippet}
@@ -219,7 +216,7 @@ function NoteListInner({ entries, selection, selectedNote, allContent, modifiedF
}, [])
const toggleGroup = useCallback((label: string) => {
- setCollapsedGroups((prev) => { const next = new Set(prev); next.has(label) ? next.delete(label) : next.add(label); return next })
+ setCollapsedGroups((prev) => { const next = new Set(prev); if (next.has(label)) { next.delete(label) } else { next.add(label) }; return next })
}, [])
const typeEntryMap = useTypeEntryMap(entries)
diff --git a/src/components/SettingsPanel.tsx b/src/components/SettingsPanel.tsx
index e77cc1d7..7699c419 100644
--- a/src/components/SettingsPanel.tsx
+++ b/src/components/SettingsPanel.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useRef } from 'react'
+import { useState, useRef } from 'react'
import { X, Eye, EyeSlash } from '@phosphor-icons/react'
import type { Settings } from '../types'
@@ -9,10 +9,6 @@ interface SettingsPanelProps {
onClose: () => void
}
-function maskKey(key: string): string {
- if (key.length <= 8) return '•'.repeat(key.length)
- return key.slice(0, 7) + '•'.repeat(Math.min(key.length - 11, 12)) + key.slice(-4)
-}
interface KeyFieldProps {
label: string
@@ -70,21 +66,15 @@ function KeyField({ label, placeholder, value, onChange, onClear }: KeyFieldProp
}
export function SettingsPanel({ open, settings, onSave, onClose }: SettingsPanelProps) {
- const [anthropicKey, setAnthropicKey] = useState('')
- const [openaiKey, setOpenaiKey] = useState('')
- const [googleKey, setGoogleKey] = useState('')
- const [githubToken, setGithubToken] = useState('')
-
- useEffect(() => {
- if (open) {
- setAnthropicKey(settings.anthropic_key ?? '')
- setOpenaiKey(settings.openai_key ?? '')
- setGoogleKey(settings.google_key ?? '')
- setGithubToken(settings.github_token ?? '')
- }
- }, [open, settings])
-
if (!open) return null
+ return
+}
+
+function SettingsPanelInner({ settings, onSave, onClose }: Omit
) {
+ const [anthropicKey, setAnthropicKey] = useState(settings.anthropic_key ?? '')
+ const [openaiKey, setOpenaiKey] = useState(settings.openai_key ?? '')
+ const [googleKey, setGoogleKey] = useState(settings.google_key ?? '')
+ const [githubToken, setGithubToken] = useState(settings.github_token ?? '')
const handleSave = () => {
const trimmed: Settings = {
@@ -218,6 +208,3 @@ export function SettingsPanel({ open, settings, onSave, onClose }: SettingsPanel
)
}
-
-// Export maskKey for use in other components
-export { maskKey }
diff --git a/src/hooks/useAppKeyboard.ts b/src/hooks/useAppKeyboard.ts
index 183611e8..bbd13640 100644
--- a/src/hooks/useAppKeyboard.ts
+++ b/src/hooks/useAppKeyboard.ts
@@ -1,4 +1,4 @@
-import { useEffect, useMemo } from 'react'
+import { useEffect } from 'react'
import type { ViewMode } from './useViewMode'
interface KeyboardActions {
@@ -48,27 +48,29 @@ export function useAppKeyboard({
onQuickOpen, onCreateNote, onSave, onOpenSettings, onTrashNote, onArchiveNote,
onSetViewMode, activeTabPathRef, handleCloseTabRef,
}: KeyboardActions) {
- const withActiveTab = (fn: (path: string) => void): ShortcutHandler => () => {
- const path = activeTabPathRef.current
- if (path) fn(path)
- }
-
- const cmdKeyMap = useMemo((): Record => ({
- p: onQuickOpen,
- n: onCreateNote,
- s: onSave,
- ',': onOpenSettings,
- e: withActiveTab(onArchiveNote),
- w: withActiveTab((path) => handleCloseTabRef.current(path)),
- Backspace: withActiveTab(onTrashNote),
- Delete: withActiveTab(onTrashNote),
- }), [onQuickOpen, onCreateNote, onSave, onOpenSettings, onTrashNote, onArchiveNote, activeTabPathRef, handleCloseTabRef])
-
useEffect(() => {
+ const withActiveTab = (fn: (path: string) => void): ShortcutHandler => () => {
+ const path = activeTabPathRef.current
+ if (path) fn(path)
+ }
+
+ const cmdKeyMap: Record = {
+ p: onQuickOpen,
+ n: onCreateNote,
+ s: onSave,
+ ',': onOpenSettings,
+ e: withActiveTab(onArchiveNote),
+ w: withActiveTab((path) => handleCloseTabRef.current(path)),
+ Backspace: withActiveTab(onTrashNote),
+ Delete: withActiveTab(onTrashNote),
+ }
+
const handleKeyDown = (e: KeyboardEvent) => {
- handleViewModeKey(e, onSetViewMode) || handleCmdKey(e, cmdKeyMap)
+ if (!handleViewModeKey(e, onSetViewMode)) {
+ handleCmdKey(e, cmdKeyMap)
+ }
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
- }, [cmdKeyMap, onSetViewMode])
+ }, [onQuickOpen, onCreateNote, onSave, onOpenSettings, onTrashNote, onArchiveNote, activeTabPathRef, handleCloseTabRef, onSetViewMode])
}
diff --git a/src/hooks/useKeyboardNavigation.test.ts b/src/hooks/useKeyboardNavigation.test.ts
index 7b3e1805..a64369ee 100644
--- a/src/hooks/useKeyboardNavigation.test.ts
+++ b/src/hooks/useKeyboardNavigation.test.ts
@@ -65,11 +65,11 @@ describe('useKeyboardNavigation', () => {
// Track added listeners for cleanup verification
const origAdd = window.addEventListener
const origRemove = window.removeEventListener
- vi.spyOn(window, 'addEventListener').mockImplementation((type: string, handler: EventListenerOrEventListenerObject, opts?: any) => {
+ vi.spyOn(window, 'addEventListener').mockImplementation((type: string, handler: EventListenerOrEventListenerObject, opts?: boolean | AddEventListenerOptions) => {
addedListeners.push({ type, handler })
origAdd.call(window, type, handler, opts)
})
- vi.spyOn(window, 'removeEventListener').mockImplementation((type: string, handler: EventListenerOrEventListenerObject, opts?: any) => {
+ vi.spyOn(window, 'removeEventListener').mockImplementation((type: string, handler: EventListenerOrEventListenerObject, opts?: boolean | EventListenerOptions) => {
origRemove.call(window, type, handler, opts)
})
})
diff --git a/src/hooks/useNoteActions.ts b/src/hooks/useNoteActions.ts
index bb3be8b4..696a4a34 100644
--- a/src/hooks/useNoteActions.ts
+++ b/src/hooks/useNoteActions.ts
@@ -197,6 +197,7 @@ export function useNoteActions(config: NoteActionsConfig) {
const tabMgmt = useTabManagement()
const { setTabs, handleSelectNote, activeTabPathRef, handleSwitchTab } = tabMgmt
const tabsRef = useRef(tabMgmt.tabs)
+ // eslint-disable-next-line react-hooks/refs
tabsRef.current = tabMgmt.tabs
const updateTabContent = useCallback((path: string, newContent: string) => {
diff --git a/src/hooks/useSettings.test.ts b/src/hooks/useSettings.test.ts
index 6369d4ee..e8f2ddd7 100644
--- a/src/hooks/useSettings.test.ts
+++ b/src/hooks/useSettings.test.ts
@@ -19,10 +19,10 @@ const savedSettings: Settings = {
let mockSettingsStore: Settings = { ...defaultSettings }
-const mockInvokeFn = vi.fn((cmd: string, args?: any): Promise => {
+const mockInvokeFn = vi.fn((cmd: string, args?: Record): Promise => {
if (cmd === 'get_settings') return Promise.resolve({ ...mockSettingsStore })
if (cmd === 'save_settings') {
- mockSettingsStore = { ...args.settings }
+ mockSettingsStore = { ...(args as { settings: Settings }).settings }
return Promise.resolve(null)
}
return Promise.resolve(null)
@@ -34,7 +34,7 @@ vi.mock('@tauri-apps/api/core', () => ({
vi.mock('../mock-tauri', () => ({
isTauri: () => false,
- mockInvoke: (cmd: string, args?: any) => mockInvokeFn(cmd, args),
+ mockInvoke: (cmd: string, args?: Record) => mockInvokeFn(cmd, args),
}))
describe('useSettings', () => {
diff --git a/src/hooks/useVaultLoader.test.ts b/src/hooks/useVaultLoader.test.ts
index 7549c9ed..f17231cd 100644
--- a/src/hooks/useVaultLoader.test.ts
+++ b/src/hooks/useVaultLoader.test.ts
@@ -32,7 +32,7 @@ function defaultMockInvoke(cmd: string, args?: Record) {
if (cmd === 'get_modified_files') return Promise.resolve(mockModifiedFiles)
if (cmd === 'get_file_history') return Promise.resolve(mockGitHistory)
if (cmd === 'get_file_diff') return Promise.resolve('--- a/note.md\n+++ b/note.md')
- if (cmd === 'get_file_diff_at_commit') return Promise.resolve(`diff for ${args?.commitHash}`)
+ if (cmd === 'get_file_diff_at_commit') return Promise.resolve(`diff for ${(args as Record)?.commitHash}`)
if (cmd === 'git_commit') return Promise.resolve('committed')
if (cmd === 'git_push') return Promise.resolve('pushed')
return Promise.resolve(null)
@@ -46,7 +46,7 @@ vi.mock('@tauri-apps/api/core', () => ({
vi.mock('../mock-tauri', () => ({
isTauri: () => false,
- mockInvoke: (cmd: string, args?: any) => mockInvokeFn(cmd, args),
+ mockInvoke: (cmd: string, args?: Record) => mockInvokeFn(cmd, args),
}))
describe('useVaultLoader', () => {
@@ -170,7 +170,7 @@ describe('useVaultLoader', () => {
if (cmd === 'get_all_content') return Promise.resolve({})
if (cmd === 'get_modified_files') return Promise.resolve([])
return Promise.resolve(null)
- }) as any)
+ }) as typeof defaultMockInvoke)
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
const { result } = renderHook(() => useVaultLoader('/vault'))
diff --git a/src/utils/wikilinks.test.ts b/src/utils/wikilinks.test.ts
index d172928a..899ea953 100644
--- a/src/utils/wikilinks.test.ts
+++ b/src/utils/wikilinks.test.ts
@@ -1,8 +1,15 @@
import { describe, it, expect } from 'vitest'
import { preProcessWikilinks, injectWikilinks, splitFrontmatter, countWords } from './wikilinks'
-// eslint-disable-next-line @typescript-eslint/no-explicit-any -- test helper for asserting on opaque block structures
-type AnyBlock = any
+interface TestBlock {
+ type?: string
+ text?: string
+ content?: TestBlock[]
+ children?: TestBlock[]
+ props?: Record
+ href?: string
+ [key: string]: unknown
+}
describe('preProcessWikilinks', () => {
it('replaces [[target]] with placeholder tokens', () => {
@@ -43,15 +50,15 @@ describe('injectWikilinks', () => {
],
}]
- const result = injectWikilinks(blocks) as AnyBlock[] as AnyBlock[]
+ const result = injectWikilinks(blocks) as TestBlock[]
expect(result[0].content).toHaveLength(3)
- expect(result[0].content[0]).toEqual({ type: 'text', text: 'before ' })
- expect(result[0].content[1]).toEqual({
+ expect(result[0].content![0]).toEqual({ type: 'text', text: 'before ' })
+ expect(result[0].content![1]).toEqual({
type: 'wikilink',
props: { target: 'My Note' },
content: undefined,
})
- expect(result[0].content[2]).toEqual({ type: 'text', text: ' after' })
+ expect(result[0].content![2]).toEqual({ type: 'text', text: ' after' })
})
it('handles multiple wikilinks in one text node', () => {
@@ -61,11 +68,11 @@ describe('injectWikilinks', () => {
],
}]
- const result = injectWikilinks(blocks) as AnyBlock[]
- const wikilinkNodes = result[0].content.filter((n: AnyBlock) => n.type === 'wikilink')
+ const result = injectWikilinks(blocks) as TestBlock[]
+ const wikilinkNodes = result[0].content!.filter((n: TestBlock) => n.type === 'wikilink')
expect(wikilinkNodes).toHaveLength(2)
- expect(wikilinkNodes[0].props.target).toBe('A')
- expect(wikilinkNodes[1].props.target).toBe('B')
+ expect(wikilinkNodes[0].props!.target).toBe('A')
+ expect(wikilinkNodes[1].props!.target).toBe('B')
})
it('passes through non-text content items unchanged', () => {
@@ -75,8 +82,8 @@ describe('injectWikilinks', () => {
],
}]
- const result = injectWikilinks(blocks) as AnyBlock[]
- expect(result[0].content[0].type).toBe('link')
+ const result = injectWikilinks(blocks) as TestBlock[]
+ expect(result[0].content![0].type).toBe('link')
})
it('recursively processes children blocks', () => {
@@ -89,16 +96,16 @@ describe('injectWikilinks', () => {
}],
}]
- const result = injectWikilinks(blocks) as AnyBlock[]
- const childContent = result[0].children[0].content
+ const result = injectWikilinks(blocks) as TestBlock[]
+ const childContent = result[0].children![0].content!
expect(childContent).toHaveLength(2)
expect(childContent[1].type).toBe('wikilink')
- expect(childContent[1].props.target).toBe('Nested')
+ expect(childContent[1].props!.target).toBe('Nested')
})
it('handles blocks without content or children', () => {
const blocks = [{ type: 'heading', props: { level: 1 } }]
- const result = injectWikilinks(blocks) as AnyBlock[]
+ const result = injectWikilinks(blocks as unknown[]) as TestBlock[]
expect(result).toEqual(blocks)
})
@@ -109,10 +116,10 @@ describe('injectWikilinks', () => {
],
}]
- const result = injectWikilinks(blocks) as AnyBlock[]
- expect(result[0].content[0].type).toBe('wikilink')
- expect(result[0].content[0].props.target).toBe('First')
- expect(result[0].content[1].text).toBe(' text')
+ const result = injectWikilinks(blocks) as TestBlock[]
+ expect(result[0].content![0].type).toBe('wikilink')
+ expect(result[0].content![0].props!.target).toBe('First')
+ expect(result[0].content![1].text).toBe(' text')
})
it('handles text node that ends with wikilink', () => {
@@ -122,9 +129,9 @@ describe('injectWikilinks', () => {
],
}]
- const result = injectWikilinks(blocks) as AnyBlock[]
- expect(result[0].content[0].text).toBe('text ')
- expect(result[0].content[1].type).toBe('wikilink')
+ const result = injectWikilinks(blocks) as TestBlock[]
+ expect(result[0].content![0].text).toBe('text ')
+ expect(result[0].content![1].type).toBe('wikilink')
})
})