Implements the design spec's §2 — "notes cached in zustand keyed by
project id" — which the plan substituted with a hook-local `useState`.
Sharing the `NotesPanel` *component* between the Project Home sub-tab and
the dock did not share the *cache*. Both resolve to the same project, so
two panels mount two `useNotes(P)`, each with its own list. Edit a note
in the dock and blur; the tab's copy is still pre-edit, and the tab's
next blur commits `{...staleRecord, title, body}` — the dock's edit gone
from disk with no error and no indicator. That is the feature's own
primary workflow: take notes in the dock while the agent runs, which is
the reason the dock exists, then go back to the tab.
`notesByProject` plus a per-project in-flight flag now hold the list.
Both surfaces render from one array; two panels mounting for one project
make one read; and because the write is keyed by project, a response
that lands after the user has moved on updates the project it belongs to
rather than whichever is on screen. This is also the boundary §8 says a
detached notes window needs.
Three more bugs in the same code, fixed with it:
- Delete-after-edit could resurrect the note. Clicking Delete with the
textarea focused fires blur first, so `save_note` and `delete_note` go
out back to back; Rust's `write_lock` stops them interleaving but does
not order them, and a delete that wins the lock is undone by the
upsert behind it. A project's mutations now go through one promise
chain, module-scoped for the reason `useTerminal`'s input queue is.
- An unsaved draft vanished when any other note was saved, because the
re-read replaced the list with the backend's. "New note" now persists,
so the backend owns the row from the start — chosen over merging local
drafts because a local-only row in a *shared* cache would exist in the
panel that made it and nowhere else.
- The save outcome was reported for the wrong project after a switch:
the guard covered only the list replacement, so the new project's
SaveIndicator flashed "Saved ✓" for the old project's write. The
indicator now resets on a project change and reports only its own.
`NotesPanel` also re-seeds its draft when the *stored* text of the note
it has selected changes, so an edit made in the other surface reaches
the editor and not only the list. It never overwrites something
half-typed; that still blurs into a last-writer-wins save, as any
blur-commit editor does.
NotesPanel.shared.test.tsx is the configuration none of the existing
tests had: two panels, one project, the real hook. Four of its six
assertions fail against the previous implementation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HjL1E2JFNctUqCYotUwqqb