import { useEffect, useMemo, useState } from "react"; import { useNotes } from "../../hooks/useNotes"; import NoteEditor from "./NoteEditor"; import Button from "../ui/Button"; import SaveIndicator from "../ui/SaveIndicator"; interface Props { projectId: string; } const UNTITLED = "Untitled note"; /** * The notes surface itself, shared by the Project Home tab and the dock so the * two cannot drift into different behaviour. * * Master/detail: titles on the left, one editor on the right. The editor holds * draft text locally and commits on blur, which is how every other editable * field in the app behaves (`ClaudeInstructionsEditor`, the Config tab). */ export default function NotesPanel({ projectId }: Props) { const { notes, loading, saveState, createNote, saveNote, deleteNote } = useNotes(projectId); const [selectedId, setSelectedId] = useState(null); const [title, setTitle] = useState(""); const [body, setBody] = useState(""); const selected = useMemo( () => notes.find((n) => n.id === selectedId) ?? notes[0] ?? null, [notes, selectedId], ); // Load the selected note's stored text into the draft. Keyed on the id, not // the note object, so a save round trip does not stomp what is being typed. useEffect(() => { if (!selected) { setTitle(""); setBody(""); return; } setTitle(selected.title); setBody(selected.body); }, [selected?.id]); // eslint-disable-line react-hooks/exhaustive-deps const commit = () => { if (!selected) return; // Reading is not editing: clicking through notes must not rewrite the file. if (title === selected.title && body === selected.body) return; void saveNote({ ...selected, title, body }); }; const onCreate = async () => { const note = await createNote(); if (note) setSelectedId(note.id); }; if (loading) { return (

Loading notes…

); } return (
{notes.length === 0 ? (

No notes yet. Keep reminders here, and send any of them straight to a running Claude session.

) : (
    {notes.map((n) => (
  • ))}
{selected && ( void deleteNote(selected.id)} /> )}
)}
); }