diff --git a/memento-note/components/sidebar.tsx b/memento-note/components/sidebar.tsx
index 942b5f2..45cf235 100644
--- a/memento-note/components/sidebar.tsx
+++ b/memento-note/components/sidebar.tsx
@@ -17,10 +17,13 @@ import {
MessageSquare,
Sparkles,
Trash2,
+ User,
+ LogOut,
+ Shield,
} from 'lucide-react'
import { useLanguage } from '@/lib/i18n'
-import { useNoteRefreshOptional } from '@/context/NoteRefreshContext'
-import { useEffect, useState } from 'react'
+import { useNotebooksQuery } from '@/lib/query-hooks'
+import { useEffect, useMemo, useState } from 'react'
import { getAllNotes } from '@/app/actions/notes'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { useNotebooks } from '@/context/notebooks-context'
@@ -29,6 +32,14 @@ import { motion, AnimatePresence } from 'motion/react'
import { getNoteDisplayTitle } from '@/lib/note-preview'
import { CreateNotebookDialog } from './create-notebook-dialog'
import { NotificationPanel } from './notification-panel'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from '@/components/ui/dropdown-menu'
+import { signOut } from 'next-auth/react'
type NavigationView = 'notebooks' | 'agents'
type SortOrder = 'newest' | 'oldest' | 'alpha'
@@ -71,10 +82,11 @@ function SidebarCarnetItem({
}: {
carnet: { id: string; name: string; initial: string; isPrivate?: boolean }
isActive: boolean
+ /** Notes for this carnet — always passed (like architectural-grid ref); visibility toggled by isActive */
notes: { id: string; title: string }[]
activeNoteId: string | null
onCarnetClick: () => void
- onNoteClick: (noteId: string) => void
+ onNoteClick: (noteId: string, carnetId: string) => void
}) {
return (
@@ -127,7 +139,7 @@ function SidebarCarnetItem({
key={note.id}
title={note.title}
isActive={activeNoteId === note.id}
- onClick={() => onNoteClick(note.id)}
+ onClick={() => onNoteClick(note.id, carnet.id)}
/>
))}
{notes.length === 0 && (
@@ -145,7 +157,6 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
const searchParams = useSearchParams()
const router = useRouter()
const { t } = useLanguage()
- const { refreshKey } = useNoteRefreshOptional()
const { notebooks } = useNotebooks()
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
const [notebookNotes, setNotebookNotes] = useState
>({})
@@ -160,32 +171,48 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
const isInboxActive =
pathname === '/' &&
!searchParams.get('notebook') &&
- !searchParams.get('label') &&
+ !searchParams.get('labels') &&
!searchParams.get('archived') &&
!searchParams.get('trashed')
- // Sync activeView with current route
+ // Sync toggle with route (fixes staying on "Agents" tab after navigating home)
useEffect(() => {
- if (pathname.startsWith('/agents') || pathname.startsWith('/lab')) {
- setActiveView('agents')
- }
+ setActiveView(
+ pathname.startsWith('/agents') || pathname.startsWith('/lab') ? 'agents' : 'notebooks'
+ )
}, [pathname])
const displayName = user?.name || user?.email || ''
const initial = displayName ? displayName.charAt(0).toUpperCase() : '?'
- useEffect(() => {
- if (!currentNotebookId) return
- if (notebookNotes[currentNotebookId]) return
+ const notebookIdsKey = useMemo(() => notebooks.map(nb => nb.id).sort().join(','), [notebooks])
- getAllNotes(false, currentNotebookId).then(notes => {
- const mapped = notes.map((n: Note) => ({
- id: n.id,
- title: getNoteDisplayTitle(n, t('notes.untitled') || 'Untitled'),
- }))
- setNotebookNotes(prev => ({ ...prev, [currentNotebookId!]: mapped }))
- })
- }, [currentNotebookId, refreshKey])
+ /** Load note titles for every notebook (like ref: filter per carnet).
+ * Refetch when notebooks list changes (added/removed/reordered).
+ * Note: individual note changes (create/edit/delete) don't need to trigger this
+ * because React Query cache handles invalidation separately. */
+ useEffect(() => {
+ if (!notebookIdsKey) return
+ let cancelled = false
+ const load = async () => {
+ const mappedEntries = await Promise.all(
+ notebooks.map(async (nb: Notebook) => {
+ const notes = await getAllNotes(false, nb.id)
+ const mapped = notes.map((n: Note) => ({
+ id: n.id,
+ title: getNoteDisplayTitle(n, t('notes.untitled') || 'Untitled'),
+ }))
+ return [nb.id, mapped] as const
+ })
+ )
+ if (cancelled) return
+ setNotebookNotes(Object.fromEntries(mappedEntries))
+ }
+ load()
+ return () => {
+ cancelled = true
+ }
+ }, [notebookIdsKey, notebooks, t])
// BUG FIX: clicking a carnet always forces list (editorial) view
const handleCarnetClick = (notebookId: string) => {
@@ -200,8 +227,9 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
router.push('/?forceList=1')
}
- const handleNoteClick = (noteId: string) => {
+ const handleNoteClick = (noteId: string, notebookId: string) => {
const params = new URLSearchParams(searchParams.toString())
+ params.set('notebook', notebookId)
params.set('openNote', noteId)
params.delete('forceList')
router.push(`/?${params.toString()}`)
@@ -225,26 +253,63 @@ export function Sidebar({ className, user }: { className?: string; user?: any })
<>