feat: hierarchical notebook system - trash, selectors, breadcrumb, sidebar tree
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m9s

- Schema: soft delete with trashedAt on Notebook model
- API: PATCH/GET notebooks support trashedAt filtering with cascade
- Sidebar: recursive tree rendering with collapse/expand, visual guides, hover actions
- HierarchicalNotebookSelector: portal-based dropdown with search, breadcrumbs, dropUp support
- AI chat: context selector with Toutes mes notes + notebook selector
- Agent detail: flat selects replaced with HierarchicalNotebookSelector
- Breadcrumb: notebook path display on home page
- Trash view: card grid with countdown, restore/permanent delete
- CSS: design tokens (ink, paper, blueprint, concrete, etc.)
- Types: parentId, trashedAt added to Notebook interface
This commit is contained in:
Antigravity
2026-05-10 10:52:26 +00:00
parent 539c72cf6d
commit 916fb78dfb
20 changed files with 1319 additions and 391 deletions

View File

@@ -1,6 +1,6 @@
'use client'
import { useState, useEffect, useCallback, useRef, useTransition, useMemo } from 'react'
import React, { useState, useEffect, useCallback, useRef, useTransition, useMemo } from 'react'
import { useSearchParams, useRouter } from 'next/navigation'
import dynamic from 'next/dynamic'
import { Note } from '@/lib/types'
@@ -11,7 +11,7 @@ import { NotesEditorialView } from '@/components/notes-editorial-view'
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 } from 'lucide-react'
import { Plus, ArrowUpDown, Search, Sparkles, FileText, FolderOpen, ChevronRight } from 'lucide-react'
import { useNoteRefresh } from '@/context/NoteRefreshContext'
import { useRefresh } from '@/lib/use-refresh'
import { useReminderCheck } from '@/hooks/use-reminder-check'
@@ -332,6 +332,20 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
const currentNotebook = notebooks.find((n: any) => n.id === searchParams.get('notebook'))
const notebookPath = useMemo(() => {
if (!currentNotebook) return []
const trail: any[] = []
let current: any = currentNotebook
while (current) {
trail.unshift(current)
if (!current.parentId) break
const parent = notebooks.find((nb: any) => nb.id === current.parentId)
if (!parent) break
current = parent
}
return trail
}, [currentNotebook, notebooks])
useEffect(() => {
setControls({
isTabsMode: notesViewMode === 'tabs',
@@ -404,15 +418,29 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
isEditorialMode ? 'sticky top-0 bg-memento-paper/90 backdrop-blur-md z-30' : ''
)}>
<div className="flex justify-between items-start">
<h1 className="font-memento-serif text-4xl font-medium tracking-tight text-foreground leading-tight pr-12">
{currentNotebook
? currentNotebook.name
: searchParams.get('shared') === '1'
? (t('sidebar.sharedWithMe') || 'Partagées avec moi')
: searchParams.get('reminders') === '1'
? (t('sidebar.reminders') || 'Rappels')
: t('notes.title')}
</h1>
<div>
{currentNotebook && notebookPath.length > 0 && (
<div className="flex items-center gap-2 text-base font-semibold mb-1">
{notebookPath.map((nb: any, i: number) => (
<React.Fragment key={nb.id}>
{i > 0 && <ChevronRight size={16} className="text-foreground/40" />}
<span className={i === notebookPath.length - 1 ? 'text-foreground' : 'text-foreground/60'}>
{nb.name}
</span>
</React.Fragment>
))}
</div>
)}
<h1 className="font-memento-serif text-4xl font-medium tracking-tight text-foreground leading-tight pr-12">
{currentNotebook
? currentNotebook.name
: searchParams.get('shared') === '1'
? (t('sidebar.sharedWithMe') || 'Partagées avec moi')
: searchParams.get('reminders') === '1'
? (t('sidebar.reminders') || 'Rappels')
: t('notes.title')}
</h1>
</div>
</div>
<div className="flex items-center justify-between border-b border-foreground/5 pb-4">