refactor: split NoteEditor into focused components + consolidate contexts

Phase 1: NoteEditor Split (64KB → 9 focused components)
- components/note-editor/: types.ts, context, toolbar, title-block,
  content-area, metadata-section, full-page, dialog compositions
- Maintains backwards compatibility via re-export from note-editor.tsx

Phase 2: Context Consolidation (5 → 3 contexts)
- NotebooksContext absorbs LabelContext (labels CRUD)
- EditorUIContext merges HomeViewContext + NotebookDragContext
- Removed: LabelContext, home-view-context, notebook-drag-context

Phase 3: React Query Infrastructure
- Added QueryProvider with @tanstack/react-query
- lib/query-keys.ts: centralized query key definitions
- lib/query-hooks.ts: useNotes, useNotebooksQuery, useLabelsQuery
- lib/use-refresh.ts: hybrid invalidateQueries + triggerRefresh helper
- NotebooksContext: invalidateQueries on mutations (with triggerRefresh fallback)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Antigravity
2026-05-08 14:31:08 +00:00
parent a58610003d
commit 91b1201112
35 changed files with 2606 additions and 344 deletions

View File

@@ -12,14 +12,13 @@ import { MemoryEchoNotification } from '@/components/memory-echo-notification'
import { NotebookSuggestionToast } from '@/components/notebook-suggestion-toast'
import { Button } from '@/components/ui/button'
import { Plus, ArrowUpDown } from 'lucide-react'
import { useLabels } from '@/context/LabelContext'
import { useNoteRefresh } from '@/context/NoteRefreshContext'
import { useReminderCheck } from '@/hooks/use-reminder-check'
import { useAutoLabelSuggestion } from '@/hooks/use-auto-label-suggestion'
import { useNotebooks } from '@/context/notebooks-context'
import { cn } from '@/lib/utils'
import { useLanguage } from '@/lib/i18n'
import { useHomeView } from '@/context/home-view-context'
import { useEditorUI } from '@/context/editor-ui-context'
import { NoteHistoryModal } from '@/components/note-history-modal'
import { toast } from 'sonner'
import { AnimatePresence, motion } from 'motion/react'
@@ -71,9 +70,11 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
const [isCreating, startCreating] = useTransition()
const [sortOrder, setSortOrder] = useState<SortOrder>('newest')
const [showSortMenu, setShowSortMenu] = useState(false)
const notesRef = useRef(notes)
notesRef.current = notes
const { refreshKey, triggerRefresh } = useNoteRefresh()
const { labels } = useLabels()
const { setControls } = useHomeView()
const { labels, notebooks } = useNotebooks()
const { setControls } = useEditorUI()
const { shouldSuggest: shouldSuggestLabels, notebookId: suggestNotebookId, dismiss: dismissLabelSuggestion } = useAutoLabelSuggestion()
const [autoLabelOpen, setAutoLabelOpen] = useState(false)
@@ -84,18 +85,17 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
}
}, [shouldSuggestLabels, suggestNotebookId])
// BUG FIX: forceList param from sidebar carnet click → reset to editorial view
// Sidebar carnet / inbox: forceList → liste éditoriale + fermer l'éditeur plein écran (comme la ref. architectural-grid)
useEffect(() => {
const forceList = searchParams.get('forceList')
if (forceList === '1') {
setNotesViewMode(prev => (prev === 'tabs' ? 'masonry' : prev))
const params = new URLSearchParams(searchParams.toString())
params.delete('forceList')
const newUrl = params.toString() ? `/?${params.toString()}` : '/'
router.replace(newUrl, { scroll: false })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams])
if (forceList !== '1') return
setNotesViewMode(prev => (prev === 'tabs' ? 'masonry' : prev))
setEditingNote(null)
const params = new URLSearchParams(searchParams.toString())
params.delete('forceList')
const newUrl = params.toString() ? `/?${params.toString()}` : '/'
router.replace(newUrl, { scroll: false })
}, [searchParams, router])
const notebookFilter = searchParams.get('notebook')
const handleNoteCreated = useCallback((note: Note) => {
@@ -196,27 +196,25 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
useReminderCheck(notes)
// Garder openNote dans l'URL tant que l'éditeur est ouvert → le sidebar peut surligner la note (comme activeNoteId dans la ref.)
useEffect(() => {
const openNoteId = searchParams.get('openNote')
if (!openNoteId) return
const openNote = async () => {
const existing = notes.find(n => n.id === openNoteId)
if (existing) {
setEditingNote({ note: existing, readOnly: false })
} else {
const fetched = await getNoteById(openNoteId)
if (fetched) {
setEditingNote({ note: fetched, readOnly: false })
}
}
const params = new URLSearchParams(searchParams.toString())
params.delete('openNote')
router.replace(params.toString() ? `/?${params.toString()}` : '/', { scroll: false })
let cancelled = false
const run = async () => {
const existing = notesRef.current.find(n => n.id === openNoteId)
const note = existing ?? (await getNoteById(openNoteId))
if (cancelled || !note) return
setEditingNote(prev => {
if (prev?.note.id === note.id && prev.readOnly === false) return prev
return { note, readOnly: false }
})
}
run()
return () => {
cancelled = true
}
openNote()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams])
useEffect(() => {
@@ -305,7 +303,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams, refreshKey])
const { notebooks } = useNotebooks()
const currentNotebook = notebooks.find((n: any) => n.id === searchParams.get('notebook'))
useEffect(() => {
@@ -340,8 +337,12 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
const handleEditorClose = useCallback(() => {
setEditingNote(null)
const params = new URLSearchParams(searchParams.toString())
params.delete('openNote')
const qs = params.toString()
router.replace(qs ? `/?${qs}` : '/', { scroll: false })
triggerRefresh()
}, [triggerRefresh])
}, [triggerRefresh, router, searchParams])
return (
<div