Notes: an app-wide jot pad with an assistant that can read but never write - #18
Merged
Conversation
Closing or unparenting an agent tab unloads its WebView2 and nulls CoreWebView2. AppendRawAsync dereferenced that property with no null check, so a tab closed mid-replay threw a NullReferenceException on the next chunk: swallowed by the catch, so nothing crashed, but it broke into the debugger on every occurrence and abandoned the rest of the transcript replay. AppendRawAsync deliberately cannot use the CanScript guard — it runs while _webViewReady is still false so that live blocks keep queueing behind the restored history — so it now checks _shutDown and the core itself. The other call sites had a subtler version of the same bug: CanScript reads the property, then the call site reads it again after an await, which races the WebView unloading. All of them now capture the core into a local first, and an unrenderable block in AppendHtml is queued rather than dropped.
…t write
A place to write things down without leaving the app. A new Notes rail icon opens
a docked panel beside the chat; New creates a plain text file under
~/.mandocode/notes and opens an editor that autosaves as you type.
Notes are app-wide, the same call as snapshots and session history. A note is
something you want to write down now — often between projects, or before an agent
is open — so nothing here requires one. What survives of "which project was this
about" is a plain subfolder: a new note is filed under the active agent's folder
name when there is one, and sits loose at the top when there isn't. Grouping
therefore costs no metadata and cannot drift; you re-file a note by dragging it
in Explorer.
The filesystem is the store — no index, no JSON — which is also why the pad lives
in ~/.mandocode beside the CLI's config rather than in LocalAppData: these are the
user's files, meant to be greppable, syncable, and openable anywhere. Discovery
walks one folder plus its immediate subfolders, so a note written in Notepad shows
up and one deleted outside the app disappears, with no row pointing at a file that
isn't there.
Both surfaces carry a prompt bar. Replies land in the bar's own strip and reach a
note only through Insert (at the cursor, replacing the selection) or Replace note.
On an open note the question carries the LIVE editor buffer, so the model sees the
note as it is right now, including keystrokes autosave hasn't written yet; only
the current message carries it, so a long thread doesn't ship stale copies. On the
list the question covers the pad — every title and first line, plus the full text
of whatever the search box matches — and the bar states what it was given
("12 notes listed · 3 read in full"), because a capped read that looks total is the
one thing an "ask about all my notes" box must not do.
NoteAssistant builds a bare Ollama kernel with no plugins, filters, or tools, the
same shape as SnapshotEnhancer. That is the design, not an optimization: with no
file access, "nothing writes your note but you" is true by construction rather than
by policy, so no approval machinery is needed. It is not an AIService agent —
those exist to change your files, and are scoped to a project root the pad
deliberately sits outside of.
The editor is not the only thing that writes these files. A FileSystemWatcher
compares the file against what the editor last wrote: identical means the write was
ours, changed-while-clean is adopted silently, and changed-while-you-were-typing
raises a conflict the user resolves. A note deleted from under unsaved edits offers
to save it back. No path silently discards typing.
NoteText owns the newline round trip. A WinUI TextBox normalizes every newline to a
bare CR, which caused two bugs found by watching the app rather than the compiler:
writing Editor.Text straight back out turned a Notepad-authored CRLF note into one
endless line, and comparing the loaded file against Editor.Text made merely OPENING
a note look like an edit, autosaving untouched files.
Also here: the docked left column now tracks one current-panel value instead of a
bool per panel (three mutually exclusive panels had states that shouldn't exist),
and two ambiguous automation names are fixed — the ask bar's send button no longer
shares the name "Send" with the chat's, and Insert/Replace carry stable accessible
names now that their visible labels change with the selection.
42 store/entry tests and 8 newline tests, driven against real temp folders (the pad
root is a constructor argument, not a hard-coded path); 177 total. Verified in the
running app through UI Automation: New, autosave, ask, streamed reply, Insert
landing in the file, external-edit adoption, CRLF preservation, and rename.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A Notes panel in the sidebar for writing things down without leaving the app. Click New, start typing, and it saves as you go — no Save button, no dialog. Notes sit next to the chat, so you can jot while an agent works.
The problem it solves: notes about a project were living in Notepad or Obsidian, disconnected from the work and from the tool doing the work. Now they're one click away, and the assistant at the bottom of the panel can read what you've written.
What a user can do
Notes are grouped by the project you were working in when you created them, and the panel reopens on whatever note you had open last.
Decisions a reviewer should know about
Notes are app-wide, not per-project. An earlier version of this stored notes inside each project folder. That turned out to be wrong: it meant you couldn't write a note unless an agent was already open, and it put a
notes/folder into people's repos. Notes now live in one place,~/.mandocode/notes, the same way snapshots and history are app-wide. Project context is kept as a plain subfolder name, so grouping still works without anything to keep in sync.They're ordinary text files, and that's deliberate. No database, no index. You can grep them, sync them, back them up, or open them in any editor, and the app agrees with whatever you do — a note added in Notepad appears in the panel, and one deleted outside the app disappears from it. The cost is a quick folder scan when the panel opens; the benefit is that the panel can never show a note that isn't there.
The assistant cannot write to your notes — structurally, not as a promise. It's built with no file access at all, so the only path from a reply into a note is you pressing Insert or Replace. That's why there's no approval prompt here like there is for agent file edits: there's nothing to approve, because nothing can happen without a click. It's also why this isn't one of the regular agents — those exist to change your files.
It's honest about how much it read. When you ask a question across all your notes, the bar states what it was actually given (e.g.
12 notes listed · 3 read in full). Sending every note in full would be slow and expensive, so it sends titles plus the full text of whatever your search box is matching — and says so, rather than implying it read everything.Nothing silently overwrites your writing. These files can be changed by other programs (VS Code, a sync client, git). If that happens while you have no unsaved edits, the editor just updates. If it happens while you're typing, you get a choice — keep yours or take theirs. If a note is deleted from under unsaved text, you're offered the chance to save it back.
Also in this PR
One unrelated bug fix, in its own commit (
91ecd74): closing an agent tab while its transcript was still being restored threw an exception that was caught but left the transcript half-restored, and interrupted anyone running under a debugger. Not caused by this feature — it just surfaced while testing it.Risk and testing
Additive: one new sidebar icon and one new panel. Existing behaviour is unchanged apart from the transcript fix above and an internal tidy-up of how the sidebar tracks which panel is open.
Not in scope