feat(notes): liens internes, onglet Réseau, living blocks et consentement IA
Some checks failed
CI / Lint, Test & Build (push) Failing after 1m19s
CI / Deploy production (on server) (push) Has been skipped

Rend les liens entre notes visibles et persistants (sync NoteLink au save, auto-save, graphe réseau rafraîchi), ajoute living blocks, Memory Echo, recherche globale, consentement IA explicite et consolide les prototypes design en architectural-grid.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Antigravity
2026-05-24 14:27:29 +00:00
parent 077e665dfc
commit e2672cd2c2
323 changed files with 20670 additions and 42431 deletions

View File

@@ -4,15 +4,21 @@ import React, { useState, useEffect, useCallback, useRef, useTransition, useMemo
import { useSearchParams, useRouter } from 'next/navigation'
import dynamic from 'next/dynamic'
import { Note } from '@/lib/types'
import { getAllNotes, searchNotes, enableNoteHistory, getNoteById, createNote } from '@/app/actions/notes'
import { NotesEditorialView } from '@/components/notes-editorial-view'
import { getAllNotes, searchNotes, enableNoteHistory, getNoteById, createNote, deleteNote, togglePin, toggleArchive, updateNote, updateFullOrderWithoutRevalidation } from '@/app/actions/notes'
import { NotesListViews, type NotesLayoutMode, type NotesViewType } from '@/components/notes-list-views'
import {
NOTES_LAYOUT_STORAGE_KEY,
NOTES_VIEW_TYPE_STORAGE_KEY,
parseNotesLayoutMode,
parseNotesViewType,
setNotesLayoutPreference,
setNotesViewTypePreference,
} from '@/lib/notes-view-preference'
import { MemoryEchoNotification } from '@/components/memory-echo-notification'
import { NotebookSuggestionToast } from '@/components/notebook-suggestion-toast'
import { Button } from '@/components/ui/button'
import { Plus, ArrowUpDown, Search, Sparkles, FileText, FolderOpen, ChevronRight, Tag as TagIcon, X, Menu } from 'lucide-react'
import { useNoteRefresh } from '@/context/NoteRefreshContext'
import { useRefresh } from '@/lib/use-refresh'
import { Plus, ArrowUpDown, Search, Sparkles, FileText, FolderOpen, ChevronRight, Tag as TagIcon, X, Menu, LayoutGrid, List, Table } from 'lucide-react'
import { emitNoteChange } from '@/lib/note-change-sync'
import { useReminderCheck } from '@/hooks/use-reminder-check'
import { useAutoLabelSuggestion } from '@/hooks/use-auto-label-suggestion'
import { useNotebooks } from '@/context/notebooks-context'
@@ -58,9 +64,16 @@ type InitialSettings = {
interface HomeClientProps {
initialNotes: Note[]
initialSettings: InitialSettings
initialLayoutMode?: NotesLayoutMode
initialViewType?: NotesViewType
}
export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
export function HomeClient({
initialNotes,
initialSettings,
initialLayoutMode = 'list',
initialViewType = 'notes',
}: HomeClientProps) {
const searchParams = useSearchParams()
const router = useRouter()
const { t } = useLanguage()
@@ -86,9 +99,11 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
const searchDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const notesRef = useRef(notes)
notesRef.current = notes
const { refreshKey, triggerRefresh } = useNoteRefresh()
const { refreshNotes } = useRefresh()
const { labels, notebooks, refreshNotebooks } = useNotebooks()
const labelsRef = useRef(labels)
labelsRef.current = labels
const initialNotesRef = useRef(initialNotes)
initialNotesRef.current = initialNotes
const { setControls } = useEditorUI()
const { shouldSuggest: shouldSuggestLabels, notebookId: suggestNotebookId, dismiss: dismissLabelSuggestion } = useAutoLabelSuggestion()
@@ -99,6 +114,41 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
const [selectedTagIds, setSelectedTagIds] = useState<string[]>([])
const [isTagsExpanded, setIsTagsExpanded] = useState(false)
const [tagSearchQuery, setTagSearchQuery] = useState('')
const [viewType, setViewType] = useState<NotesViewType>(initialViewType)
const [layoutMode, setLayoutMode] = useState<NotesLayoutMode>(initialLayoutMode)
useEffect(() => {
const storedLayout = parseNotesLayoutMode(localStorage.getItem(NOTES_LAYOUT_STORAGE_KEY))
const storedViewType = parseNotesViewType(localStorage.getItem(NOTES_VIEW_TYPE_STORAGE_KEY))
if (storedLayout !== initialLayoutMode) {
setLayoutMode(storedLayout)
setNotesLayoutPreference(storedLayout)
}
if (storedViewType !== initialViewType) {
setViewType(storedViewType)
setNotesViewTypePreference(storedViewType)
}
}, [initialLayoutMode, initialViewType])
useEffect(() => {
setNotesViewTypePreference(viewType)
}, [viewType])
useEffect(() => {
setNotesLayoutPreference(layoutMode)
}, [layoutMode])
useEffect(() => {
const onLayoutChange = (e: Event) => {
const detail = (e as CustomEvent<{ layout?: NotesLayoutMode }>).detail?.layout
if (detail === 'grid' || detail === 'list' || detail === 'table') {
setLayoutMode(detail)
setViewType('notes')
}
}
window.addEventListener('memento-notes-layout-change', onLayoutChange)
return () => window.removeEventListener('memento-notes-layout-change', onLayoutChange)
}, [])
// Sidebar carnet / inbox: fermer l'éditeur plein écran (comme la ref. architectural-grid)
useEffect(() => {
@@ -112,12 +162,86 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
}, [searchParams, router])
const notebookFilter = searchParams.get('notebook')
const fetchNotesForCurrentView = useCallback(
async (options?: { silent?: boolean }) => {
const search = searchParams.get('search')?.trim() || null
const labelFilter = searchParams.get('labels')?.split(',').filter(Boolean) || []
const colorFilter = searchParams.get('color')
const notebook = searchParams.get('notebook')
const sharedOnly = searchParams.get('shared') === '1'
const remindersOnly = searchParams.get('reminders') === '1'
if (!options?.silent) {
setIsLoading(true)
}
let allNotes = search
? await searchNotes(search, true, notebook || undefined)
: await getAllNotes(false, notebook || undefined)
if (sharedOnly) {
allNotes = allNotes.filter((note: Note) => note._isShared)
} else if (remindersOnly) {
allNotes = allNotes.filter((note: Note) => note.reminder !== null)
} else if (!notebook && !search) {
allNotes = allNotes.filter((note: Note) => !note.notebookId && !note._isShared)
}
if (labelFilter.length > 0) {
allNotes = allNotes.filter((note: Note) =>
labelFilter.every((label: string) => note.labels?.includes(label))
)
}
if (colorFilter) {
const labelNamesWithColor = labelsRef.current
.filter((label) => label.color === colorFilter)
.map((label) => label.name)
allNotes = allNotes.filter((note: Note) =>
note.labels?.some((label: string) => labelNamesWithColor.includes(label))
)
}
setNotes((prev) => {
const localSizeMap = new Map(prev.map((n) => [n.id, n.size]))
return allNotes.map((n) => ({ ...n, size: localSizeMap.get(n.id) ?? n.size }))
})
setPinnedNotes(allNotes.filter((n: Note) => n.isPinned))
setIsLoading(false)
},
[searchParams]
)
const filterNotesForCurrentView = useCallback((source: Note[]) => {
const notebook = searchParams.get('notebook')
const sharedOnly = searchParams.get('shared') === '1'
const remindersOnly = searchParams.get('reminders') === '1'
if (notebook) {
return source.filter((n) => n.notebookId === notebook && !n._isShared)
}
if (sharedOnly) {
return source.filter((n) => n._isShared)
}
if (remindersOnly) {
return source.filter((n) => n.reminder !== null)
}
return source.filter((n) => !n.notebookId && !n._isShared)
}, [searchParams])
const handleNoteCreated = useCallback((note: Note) => {
const search = searchParams.get('search')?.trim() || null
if (search) {
void fetchNotesForCurrentView({ silent: true })
emitNoteChange({ type: 'created', note })
return
}
setNotes((prevNotes) => {
const notebookFilter = searchParams.get('notebook')
const labelFilter = searchParams.get('labels')?.split(',').filter(Boolean) || []
const colorFilter = searchParams.get('color')
const search = searchParams.get('search')?.trim() || null
if (notebookFilter && note.notebookId !== notebookFilter) return prevNotes
if (!notebookFilter && note.notebookId) return prevNotes
@@ -135,11 +259,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
if (!noteLabels.some((label: string) => labelNamesWithColor.includes(label))) return prevNotes
}
if (search) {
router.refresh()
return prevNotes
}
const isPinned = note.isPinned || false
const pinnedNotes = prevNotes.filter(n => n.isPinned)
const unpinnedNotes = prevNotes.filter(n => !n.isPinned)
@@ -150,6 +269,7 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
return [...pinnedNotes, note, ...unpinnedNotes]
}
})
emitNoteChange({ type: 'created', note })
if (!note.notebookId) {
const wordCount = (note.content || '').trim().split(/\s+/).filter(w => w.length > 0).length
@@ -157,7 +277,7 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
setNotebookSuggestion({ noteId: note.id, content: note.content || '' })
}
}
}, [searchParams, labels, router])
}, [searchParams, labels, fetchNotesForCurrentView])
// Always fetch fresh from server — avoids stale state after a save regardless of
// whether the notes list has re-fetched yet.
@@ -210,7 +330,134 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
setPinnedNotes(prev => prev.map(n => n.id === noteId ? { ...n, size } : n))
}, [])
useReminderCheck(notes)
const patchNoteInList = useCallback((noteId: string, patch: Partial<Note>) => {
setNotes((prev) => prev.map((n) => (n.id === noteId ? { ...n, ...patch } : n)))
}, [])
const removeNoteFromList = useCallback((noteId: string) => {
setNotes((prev) => prev.filter((n) => n.id !== noteId))
setEditingNote((prev) => (prev?.note.id === noteId ? null : prev))
}, [])
const noteVisibleInCurrentView = useCallback(
(note: Pick<Note, 'notebookId' | '_isShared'>) => {
const notebook = searchParams.get('notebook')
const sharedOnly = searchParams.get('shared') === '1'
if (sharedOnly) return !!note._isShared
if (notebook) return note.notebookId === notebook && !note._isShared
return !note.notebookId && !note._isShared
},
[searchParams]
)
const handleTogglePin = useCallback(
async (note: Note) => {
const nextPinned = !note.isPinned
patchNoteInList(note.id, { isPinned: nextPinned })
emitNoteChange({ type: 'updated', note: { ...note, isPinned: nextPinned } })
try {
await togglePin(note.id, nextPinned, { skipRevalidation: true })
} catch {
patchNoteInList(note.id, { isPinned: !nextPinned })
emitNoteChange({ type: 'updated', note: { ...note, isPinned: !nextPinned } })
toast.error(t('general.error'))
}
},
[patchNoteInList, t]
)
const handleDeleteNoteFromList = useCallback(
async (note: Note) => {
removeNoteFromList(note.id)
emitNoteChange({ type: 'deleted', noteId: note.id, notebookId: note.notebookId })
try {
await deleteNote(note.id, { skipRevalidation: true })
toast.success(t('notes.deleted') || 'Note supprimée')
} catch {
setNotes((prev) => [note, ...prev])
emitNoteChange({ type: 'created', note })
toast.error(t('general.error'))
}
},
[removeNoteFromList, t]
)
const handleArchiveNoteFromList = useCallback(
async (note: Note) => {
const nextArchived = !note.isArchived
if (nextArchived) removeNoteFromList(note.id)
else patchNoteInList(note.id, { isArchived: nextArchived })
emitNoteChange({ type: 'updated', note: { ...note, isArchived: nextArchived } })
try {
await toggleArchive(note.id, nextArchived, { skipRevalidation: true })
toast.success(
nextArchived ? (t('notes.archived') || 'Archivée') : (t('notes.unarchived') || 'Désarchivée')
)
} catch {
if (nextArchived) setNotes((prev) => [note, ...prev])
else patchNoteInList(note.id, { isArchived: !nextArchived })
toast.error(t('general.error'))
}
},
[patchNoteInList, removeNoteFromList, t]
)
const handleMoveNoteToNotebook = useCallback(
async (note: Note, notebookId: string | null) => {
const moved = { ...note, notebookId }
if (noteVisibleInCurrentView(moved)) {
patchNoteInList(note.id, { notebookId })
} else {
removeNoteFromList(note.id)
}
emitNoteChange({ type: 'updated', note: moved })
try {
await updateNote(note.id, { notebookId }, { skipRevalidation: true })
toast.success(t('notebookSuggestion.movedToNotebook') || 'Note déplacée')
} catch {
if (noteVisibleInCurrentView(note)) {
patchNoteInList(note.id, { notebookId: note.notebookId ?? null })
} else {
setNotes((prev) => [note, ...prev])
}
toast.error(t('general.error'))
}
},
[noteVisibleInCurrentView, patchNoteInList, removeNoteFromList, t]
)
const handleNoteContentPatch = useCallback((noteId: string, patch: Partial<Note>) => {
setNotes((prev) => {
const next = prev.map((n) => (n.id === noteId ? { ...n, ...patch } : n))
const updated = next.find((n) => n.id === noteId)
if (updated) emitNoteChange({ type: 'updated', note: updated })
return next
})
}, [])
const handleNoteIllustrationGenerated = useCallback(async (noteId: string) => {
const fresh = await getNoteById(noteId)
if (!fresh) return
patchNoteInList(noteId, fresh)
emitNoteChange({ type: 'updated', note: fresh })
}, [patchNoteInList])
const handleGridReorder = useCallback(
async (orderedIds: string[]) => {
setSortOrder('manual')
setNotes((prev) => {
const orderMap = new Map(orderedIds.map((id, index) => [id, index]))
return prev.map((n) => (orderMap.has(n.id) ? { ...n, order: orderMap.get(n.id)! } : n))
})
try {
await updateFullOrderWithoutRevalidation(orderedIds)
} catch {
toast.error(t('general.error'))
}
},
[t],
)
// Garder openNote dans l'URL tant que l'éditeur est ouvert → le sidebar peut surligner la note (comme activeNoteId dans la ref.)
useEffect(() => {
@@ -248,86 +495,27 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
return () => window.removeEventListener('label-deleted', handler)
}, [])
const prevRefreshKey = useRef(refreshKey)
useEffect(() => {
const search = searchParams.get('search')?.trim() || null
const labelFilter = searchParams.get('labels')?.split(',').filter(Boolean) || []
const colorFilter = searchParams.get('color')
const notebook = searchParams.get('notebook')
const sharedOnly = searchParams.get('shared') === '1'
const remindersOnly = searchParams.get('reminders') === '1'
const hasActiveFilter = !!(search || labelFilter.length > 0 || colorFilter || sharedOnly || remindersOnly)
const isBackgroundRefresh = refreshKey > prevRefreshKey.current
prevRefreshKey.current = refreshKey
const hasActiveFilter = search || labelFilter.length > 0 || colorFilter
const load = async () => {
if (!isBackgroundRefresh) {
setIsLoading(true)
}
let allNotes = search
? await searchNotes(search, true, notebook || undefined)
: await getAllNotes(false, notebook || undefined)
const sharedOnly = searchParams.get('shared') === '1'
const remindersOnly = searchParams.get('reminders') === '1'
if (sharedOnly) {
allNotes = allNotes.filter((note: any) => note._isShared)
} else if (remindersOnly) {
allNotes = allNotes.filter((note: any) => note.reminder !== null)
} else if (!notebook && !search) {
allNotes = allNotes.filter((note: any) => !note.notebookId && !note._isShared)
}
if (labelFilter.length > 0) {
allNotes = allNotes.filter((note: any) =>
labelFilter.every((label: string) => note.labels?.includes(label))
)
}
if (colorFilter) {
const labelNamesWithColor = labels
.filter((label: any) => label.color === colorFilter)
.map((label: any) => label.name)
allNotes = allNotes.filter((note: any) =>
note.labels?.some((label: string) => labelNamesWithColor.includes(label))
)
}
setNotes(prev => {
const localSizeMap = new Map(prev.map(n => [n.id, n.size]))
return allNotes.map(n => ({ ...n, size: localSizeMap.get(n.id) ?? n.size }))
})
setPinnedNotes(allNotes.filter((n: any) => n.isPinned))
setIsLoading(false)
if (hasActiveFilter || notebook) {
void fetchNotesForCurrentView()
return
}
if (refreshKey > 0 || hasActiveFilter) {
const cancelled = { value: false }
load().then(() => { if (cancelled.value) return })
return () => { cancelled.value = true }
} else {
let filtered = initialNotes
const sharedOnly = searchParams.get('shared') === '1'
const remindersOnly = searchParams.get('reminders') === '1'
if (notebook) {
filtered = initialNotes.filter((n: any) => n.notebookId === notebook && !n._isShared)
} else if (sharedOnly) {
filtered = initialNotes.filter((n: any) => n._isShared)
} else if (remindersOnly) {
filtered = initialNotes.filter((n: any) => n.reminder !== null)
} else {
filtered = initialNotes.filter((n: any) => !n.notebookId && !n._isShared)
}
setNotes(prev => {
const localSizeMap = new Map(prev.map(n => [n.id, n.size]))
return filtered.map(n => ({ ...n, size: localSizeMap.get(n.id) ?? n.size }))
})
setPinnedNotes(filtered.filter(n => n.isPinned))
}
}, [searchParams, refreshKey])
const filtered = filterNotesForCurrentView(initialNotesRef.current)
setNotes((prev) => {
const localSizeMap = new Map(prev.map((n) => [n.id, n.size]))
return filtered.map((n) => ({ ...n, size: localSizeMap.get(n.id) ?? n.size }))
})
setPinnedNotes(filtered.filter((n) => n.isPinned))
}, [searchParams, fetchNotesForCurrentView, filterNotesForCurrentView])
const currentNotebook = notebooks.find((n: any) => n.id === searchParams.get('notebook'))
@@ -437,19 +625,13 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
params.delete('openNote')
const qs = params.toString()
router.replace(qs ? `/home?${qs}` : '/home', { scroll: false })
// Invalidate notes cache and trigger refresh
refreshNotes(searchParams.get('notebook') || null)
}, [refreshNotes, router, searchParams])
}, [router, searchParams])
// Called by NoteEditor when a save succeeds — update local state immediately
// so the user sees fresh data if they reopen the note before getAllNotes() completes
const handleNoteSaved = useCallback((savedNote: Note) => {
setNotes(prev => prev.map(n => n.id === savedNote.id ? { ...n, ...savedNote } : n))
setPinnedNotes(prev => prev.map(n => n.id === savedNote.id ? { ...n, ...savedNote } : n))
setEditingNote(prev => prev?.note.id === savedNote.id ? { ...prev, note: savedNote } : prev)
// Refresh sidebar note titles so the new title appears immediately
refreshNotes(savedNote.notebookId || null)
}, [refreshNotes])
setNotes((prev) => prev.map((n) => (n.id === savedNote.id ? { ...n, ...savedNote } : n)))
setEditingNote((prev) => (prev?.note.id === savedNote.id ? { ...prev, note: savedNote } : prev))
emitNoteChange({ type: 'updated', note: savedNote })
}, [])
return (
<div
@@ -626,7 +808,78 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
</button>
)}
</div>
<div className="flex items-center gap-6">
<div className="flex items-center gap-4 flex-wrap">
<div className="bg-foreground/[0.03] dark:bg-white/[0.04] p-0.5 rounded-full flex border border-border/30">
<button
type="button"
onClick={() => setViewType('notes')}
className={cn(
'px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider transition-all',
viewType === 'notes'
? 'bg-foreground text-background shadow-sm'
: 'text-muted-foreground hover:text-foreground',
)}
>
{t('notes.viewNotes')}
</button>
<button
type="button"
onClick={() => setViewType('tasks')}
className={cn(
'px-3 py-1 rounded-full text-[10px] font-bold uppercase tracking-wider transition-all',
viewType === 'tasks'
? 'bg-foreground text-background shadow-sm'
: 'text-muted-foreground hover:text-foreground',
)}
>
{t('notes.viewTasks')}
</button>
</div>
{viewType === 'notes' && (
<div className="bg-foreground/[0.03] dark:bg-white/[0.04] p-0.5 rounded-full flex border border-border/30 items-center">
<button
type="button"
onClick={() => setLayoutMode('grid')}
className={cn(
'p-1.5 rounded-full transition-all',
layoutMode === 'grid'
? 'bg-foreground text-background shadow-sm'
: 'text-muted-foreground hover:text-foreground',
)}
title={t('notes.layoutGridTitle')}
>
<LayoutGrid size={13} />
</button>
<button
type="button"
onClick={() => setLayoutMode('list')}
className={cn(
'p-1.5 rounded-full transition-all',
layoutMode === 'list'
? 'bg-foreground text-background shadow-sm'
: 'text-muted-foreground hover:text-foreground',
)}
title={t('notes.layoutListTitle')}
>
<List size={13} />
</button>
<button
type="button"
onClick={() => setLayoutMode('table')}
className={cn(
'p-1.5 rounded-full transition-all',
layoutMode === 'table'
? 'bg-foreground text-background shadow-sm'
: 'text-muted-foreground hover:text-foreground',
)}
title={t('notes.layoutTableTitle')}
>
<Table size={13} />
</button>
</div>
)}
{searchParams.get('notebook') && (
<button
onClick={() => setSummaryDialogOpen(true)}
@@ -733,46 +986,36 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
<div className="px-4 sm:px-8 md:px-12 flex-1 pb-10 sm:pb-16 md:pb-20">
{isLoading ? (
<div className="text-center py-8 text-muted-foreground">{t('general.loading')}</div>
) : (
<div className="max-w-3xl space-y-16">
{sortedPinnedNotes.length > 0 && (
<div className="mb-6">
<h2 className="text-[10px] font-bold text-muted-foreground tracking-widest uppercase mb-4 px-2">
{t('notes.pinned')}
</h2>
<NotesEditorialView
notes={sortedPinnedNotes}
onOpen={(note: Note, readOnly?: boolean) => handleOpenNoteFresh(note.id, readOnly ?? false)}
notebookName={currentNotebook?.name}
onOpenHistory={handleOpenHistory}
/>
</div>
)}
{sortedNotes.filter((note) => !note.isPinned).length > 0 && (
<NotesEditorialView
notes={sortedNotes.filter((note) => !note.isPinned)}
onOpen={(note, readOnly) => handleOpenNoteFresh(note.id, readOnly ?? false)}
notebookName={currentNotebook?.name}
onOpenHistory={handleOpenHistory}
/>
)}
{notes.length === 0 && (
<div className="h-64 flex flex-col items-center justify-center text-center space-y-4">
<p className="font-memento-serif text-xl italic text-muted-foreground">
{t('notes.emptyState')}
</p>
<button
onClick={handleAddNote}
disabled={isCreating}
className="px-6 py-2 border border-foreground text-[13px] uppercase tracking-[0.2em] hover:bg-foreground hover:text-background transition-all"
>
{t('notes.createFirst')}
</button>
</div>
)}
) : notes.length === 0 ? (
<div className="h-64 flex flex-col items-center justify-center text-center space-y-4">
<p className="font-memento-serif text-xl italic text-muted-foreground">
{t('notes.emptyState')}
</p>
<button
onClick={handleAddNote}
disabled={isCreating}
className="px-6 py-2 border border-foreground text-[13px] uppercase tracking-[0.2em] hover:bg-foreground hover:text-background transition-all"
>
{t('notes.createFirst')}
</button>
</div>
) : (
<NotesListViews
notes={sortedNotes}
pinnedNotes={sortedPinnedNotes}
viewType={viewType}
layoutMode={layoutMode}
onOpen={(note, readOnly) => handleOpenNoteFresh(note.id, readOnly ?? false)}
onOpenHistory={handleOpenHistory}
notebookName={currentNotebook?.name}
onTogglePin={handleTogglePin}
onDeleteNote={handleDeleteNoteFromList}
onArchiveNote={handleArchiveNoteFromList}
onMoveToNotebook={handleMoveNoteToNotebook}
onNotePatch={handleNoteContentPatch}
onNoteIllustrationGenerated={handleNoteIllustrationGenerated}
onGridReorder={handleGridReorder}
/>
)}
</div>
@@ -784,8 +1027,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
</div>
)}
<MemoryEchoNotification onOpenNote={(noteId) => handleOpenNoteFresh(noteId)} />
{notebookSuggestion && (
<NotebookSuggestionToast
noteId={notebookSuggestion.noteId}
@@ -798,7 +1039,7 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
<BatchOrganizationDialog
open={batchOrganizationOpen}
onOpenChange={setBatchOrganizationOpen}
onNotesMoved={() => router.refresh()}
onNotesMoved={() => fetchNotesForCurrentView({ silent: true })}
/>
)}
@@ -810,7 +1051,7 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
if (!open) dismissLabelSuggestion()
}}
notebookId={suggestNotebookId}
onLabelsCreated={() => router.refresh()}
onLabelsCreated={() => fetchNotesForCurrentView({ silent: true })}
/>
)}
@@ -846,8 +1087,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
notebookName={currentNotebook.name}
onDone={() => {
refreshNotebooks()
triggerRefresh()
router.refresh()
}}
/>
)}