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

@@ -57,6 +57,9 @@ export interface NotebooksContextValue {
createNotebookOptimistic: (data: CreateNotebookInput) => Promise<void>
updateNotebook: (notebookId: string, data: UpdateNotebookInput) => Promise<void>
deleteNotebook: (notebookId: string) => Promise<void>
trashNotebook: (notebookId: string) => Promise<void>
restoreNotebook: (notebookId: string) => Promise<void>
permanentDeleteNotebook: (notebookId: string) => Promise<void>
updateNotebookOrderOptimistic: (notebookIds: string[]) => Promise<void>
setCurrentNotebook: (notebook: Notebook | null) => void
@@ -215,6 +218,52 @@ export function NotebooksProvider({ children, initialNotebooks = [] }: Notebooks
triggerRefresh()
}, [queryClient, triggerNotebooksRefresh, triggerRefresh])
const trashNotebook = useCallback(async (notebookId: string) => {
const response = await fetch(`/api/notebooks/${notebookId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ trashedAt: new Date().toISOString() }),
})
if (!response.ok) {
throw new Error('Failed to trash notebook')
}
queryClient.invalidateQueries({ queryKey: queryKeys.notebooks() })
triggerNotebooksRefresh()
triggerRefresh()
}, [queryClient, triggerNotebooksRefresh, triggerRefresh])
const restoreNotebook = useCallback(async (notebookId: string) => {
const response = await fetch(`/api/notebooks/${notebookId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ trashedAt: null }),
})
if (!response.ok) {
throw new Error('Failed to restore notebook')
}
queryClient.invalidateQueries({ queryKey: queryKeys.notebooks() })
triggerNotebooksRefresh()
triggerRefresh()
}, [queryClient, triggerNotebooksRefresh, triggerRefresh])
const permanentDeleteNotebook = useCallback(async (notebookId: string) => {
const response = await fetch(`/api/notebooks/${notebookId}`, {
method: 'DELETE',
})
if (!response.ok) {
throw new Error('Failed to permanently delete notebook')
}
queryClient.invalidateQueries({ queryKey: queryKeys.notebooks() })
triggerNotebooksRefresh()
triggerRefresh()
}, [queryClient, triggerNotebooksRefresh, triggerRefresh])
const updateNotebookOrderOptimistic = useCallback(async (notebookIds: string[]) => {
const response = await fetch('/api/notebooks/reorder', {
method: 'POST',
@@ -378,6 +427,9 @@ export function NotebooksProvider({ children, initialNotebooks = [] }: Notebooks
createNotebookOptimistic,
updateNotebook,
deleteNotebook,
trashNotebook,
restoreNotebook,
permanentDeleteNotebook,
updateNotebookOrderOptimistic,
setCurrentNotebook,
createLabel,
@@ -403,6 +455,9 @@ export function NotebooksProvider({ children, initialNotebooks = [] }: Notebooks
createNotebookOptimistic,
updateNotebook,
deleteNotebook,
trashNotebook,
restoreNotebook,
permanentDeleteNotebook,
updateNotebookOrderOptimistic,
createLabel,
updateLabel,