Skip to content

feat(notes): keep todos, ideas and notes per project - #45

Merged
THPubs merged 13 commits into
devfrom
feat/todo-ideas
Nov 1, 2026
Merged

THPubs merged 13 commits into
devfrom
feat/todo-ideas

Conversation

@THPubs

@THPubs THPubs commented Nov 1, 2026

Copy link
Copy Markdown
Member

What changed

Each project now has its own list of todos, ideas and notes. They are the user's own planning: they belong to the project, not a session, every session in the project sees the same list, and nothing in it is sent to Claude.

  • Notes tab in the Details rail, beside Details and Activity. It shows the active session's project, with the open todo count on the tab. A capture box adds a todo, idea or note on Enter. Filters show one kind at a time, and done todos fold away under Done.
  • Opening a note edits its text, a longer body, its kind and its pin. Edits save when a field loses focus, on Back, and when the view closes. Deleting asks first.
  • Sidebar: the project's ⋯ menu has "Open notes", which shows that project's notes without opening a session (so a parked project's notes can be read without bringing it back). A project header shows its open todo count.
  • ⌘⇧N opens a small capture overlay from anywhere, including a focused terminal. ⌘K offers "Add a note…" once a project exists.
  • Storage: a project_notes table in kriyon.db with a foreign key to projects. kriyon-core checks every note from the webview: ids are bounded, text is trimmed and capped at 500 characters, the body at 20,000, and only a todo can be done. An update never moves a note to another project.
  • Failures are never silent. A change shows at once; if the save fails it is rolled back and a toast says so. A failed load at launch says so too.

ADR 0053 records the decisions, including why notes live in the app database rather than in the repository. The glossary gains Project note and Note capture.

Why

People have thoughts while working that are not prompts: a todo for later, an idea to try, a fact worth keeping. Today they go to another app, or into a terminal where they get lost or sent to Claude by mistake. This keeps them next to the work, per project.

How I tested it

  • Rust: project_notes validation tests in kriyon-core (checked they fail when validation is removed), store round-trip tests for the new table, and tests for the save command's glue.
  • Frontend: tests for the notes store (ordering, counts, filters, rollback on a failed save or delete), boot hydration, the Notes tab (add, filter, done, open, edit, save on close, delete with confirm, pin), the Inspector tab, the sidebar menu and todo count, the ⌘⇧N overlay, the reserved chord, and the palette entry.
  • pnpm check passes.
  • Not yet checked by hand in the running app at the time of writing; a smoke build is being tested.

Checklist

  • The PR targets dev.
  • I wrote or updated tests, and pnpm check passes.
  • I read and tested every line, and I can explain it in review.
  • AI tools wrote most of this change. (Tick if true. See Using AI tools.)

🤖 Generated with Claude Code

THPubs and others added 13 commits November 1, 2026 08:15
Add a project_notes table to kriyon.db and three commands to list, save
and delete notes. A note is a todo, an idea or a plain note that belongs
to a project, not a session. kriyon-core checks every note from the
webview before it is written: ids are bounded, text is trimmed and
capped, and only a todo can carry a done mark. An update never moves a
note to another project.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Details rail gets a third tab, Notes, that lists the todos, ideas
and notes of the active session's project. Every session in the project
sees the same list, and nothing in it is sent to Claude.

- A capture box adds a note of the picked kind on Enter.
- Filters show one kind at a time; done todos fold away under Done.
- Opening a note edits its text, a longer body, its kind and pin.
  Edits save on blur, on Back and when the view closes. Delete asks
  first.
- The project menu gains "Open notes", which shows that project's notes
  without opening a session, and a project header counts open todos.

The store shows a change at once and rolls it back with a toast when the
save fails, so a note never vanishes quietly.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A small overlay takes a todo, idea or note without leaving what you are
doing. It files under the project the Notes tab would show, can be
pointed at another project, and closes with a toast naming where the
note went. The chord is reserved so a focused terminal passes it to the
app, and the command palette offers "Add a note…" once a project exists.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The pin button's class was built as a string expression, which the
hover-seam check cannot see and the formatter rewrote. The reveal now
sits on a wrapper span, so the button carries a plain kr-hover-surface.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
ADR 0053 records why notes are one list with three kinds, why they live
in the app database rather than the repository, and where they show.
The glossary gains Project note and Note capture.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A save looked up nothing: an id already kept under one project could be
saved under another and overwrite that note, and the command returned
the webview's copy even where the store kept a different project or
creation time. The save now refuses an id owned by another project
(kriyon-core check_owner) and returns the row as read back from the
store, so the frontend holds what the database holds.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A failed save put back the copy from before that edit, whatever had
happened since. With two saves in flight it could undo a later edit,
restore a copy that was never saved, or bring back a note deleted
meanwhile. The store now remembers the last copy known to be stored and
only touches a note while it still shows that save's own copy; tests
cover saves that settle out of order.

An edit with a cleared title was dropped whole, so a body typed under it
was lost without a word. A blank title now keeps the stored one, the
detail view puts it back in the field, and the rest of the edit saves.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The project picked with "Open notes" was kept with the session active at
the time and came back whenever that session was opened again, so the
Notes tab and Cmd+Shift+N could show the wrong project. The pick is now a
plain project id that installNotesPickReset clears as soon as another
session becomes active.

With no session open, the Notes view showed the tab strip, but Details
then had no strip at all, leaving no way back to Notes. Details keeps the
strip while there are notes to show.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cmd+Shift+N was written twice, in the capture overlay and in the chords a
terminal lets through, so a rebind in one would leave a focused terminal
swallowing it. Both now use isNoteCaptureChord. On a Mac it is Cmd only,
like every chord a terminal passes on; Ctrl stays with the shell.

"Add a project first" was checked by each caller rather than by
openNoteCapture, so a new entry point could open a panel with no project
to file under. The opener now checks and explains, and the palette entry
relies on it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The frontend's text limit was a second copy of kriyon-core's with nothing
tying them, and the detail body had no limit at all, so a long paste
failed on save. Both limits now sit beside each other with a test that
reads them from the Rust module, and the body field is capped too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Notes tab header copied the sidebar's project square. It now lives
in theme/projectColor.ts as projectSquareStyle, used by both.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The detail view repeated the store's blank-title rule before calling it,
so the store's own rule never ran for that caller and a change to it
would not reach the view. update() now returns the note as it stands and
skips edits that change nothing; the view sends what was typed and shows
what the store kept.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
update() compared text, body, kind and pinned by hand, a second list of
the fields NotePatch already names. A field added to NotePatch alone
would have been dropped without a word. It now compares only the keys
the edit carries.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@THPubs

THPubs commented Nov 1, 2026

Copy link
Copy Markdown
Member Author

Review and fix — pass 1

Full review of the pull request, then fixes and re-checks over 3 passes.

Fixed 11 issues; 0 left open. All local checks and CI are green on the new head.

  • Saves from the webview now check note ownership in Rust and return the stored row.
  • Overlapping saves can no longer undo a later edit or revive a deleted note.
  • A cleared title no longer throws away the body typed under it.
  • The Open notes pick no longer sticks to a session, and the rail keeps a way back to Notes.
  • The capture chord, the project rule, the length limits and the project square each live in one place.

Notes

Scored below the bar and left alone: a hand-drawn filter toggle, a blank list for an empty filter, timestamp bounds, newlines in pasted text, and the test-only seeding of the saved-copy map.

Fixed this pass

  • src/notes/NoteDetail.tsx:88 — Keep body edits when the title is blank, or say why
  • src/state/projectNotes.ts:89 — Guard rollback so a failed save cannot overwrite newer state
  • src/state/projectNotes.ts:216 — Clear the notes pick once another session becomes active
  • src/inspector/Inspector.tsx:477 — Keep a way back to Notes from the no-session view
  • src-tauri/src/notes_cmd.rs:58 — Return the stored row, not the webview copy, after upsert
  • src/state/projectNotes.ts:10 — Tie NOTE_TEXT_MAX to the Rust text limit
  • src/notes/NotesTab.tsx:36 — Reuse the sidebar project colour square instead of copying it
  • src/notes/NoteCaptureOverlay.tsx:69 — Share the capture chord predicate with reservedChords.ts
  • src/notes/NoteCaptureOverlay.tsx:92 — Move the no-project guard into openNoteCapture
  • src/notes/NoteDetail.tsx:93 — Let the store own the blank-title rule; render its result
  • src/state/projectNotes.ts:159 — Derive the unchanged check from NotePatch keys, not a hand list

Checked by 3 finders (boundary, contract, saved data, correctness, tests, public text, lifecycle, frontend rules, errors, step 5 re-check of the fixes) · architecture review · scorer: 26 scored, 11 kept.


Ready to merge, as far as this review can see. Nothing is open from this pass or any pass before it.

@THPubs
THPubs merged commit d773d78 into dev Nov 1, 2026
13 checks passed
@THPubs THPubs mentioned this pull request Nov 1, 2026
3 of 4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant