Conversation
Post-0.8 batch addressing accumulated user feedback. Added: - Settings front page is now a dashboard: live library stats (notes, tags, Spaces, attachments, capture readiness, trash) and the installed version's release notes parsed from the bundled changelog. - Images settings page with copy-in vs link-original storage, a preview height control, and attachment location, count, and size. An Image button in the editor toolbar inserts from a file honoring the storage mode; linked files render through a narrow per-file asset scope rather than widening it. - Editor settings page with a "show exact save time" toggle; the exact save time, to the minute, is always available on hover over any note date. - Contexting image handling with an absolute-path default, so copying a note as context can hand a tool the real image path, keep the reference, or drop it. - In-app feedback: a Feedback page that saves a local record and opens a prefilled GitHub issue, backed by a submit_feedback command. Fixed: - Stray caret when switching notes: each note now loads a clean editor state, so no caret lingers in a note being viewed, and undo no longer reaches into the previously open note. - Links set to open with Cmd/Ctrl+Click now show the pointer cursor while the modifier is held, so they read as clickable. Verified: svelte-check clean, vitest (298) green, cargo test green, build ok.
Only the bump-version half of 0decff9; its NoteEditor hunk cleaned up a duplication introduced by the whiteboard merge and is not needed here.
Default WEBKIT_DISABLE_DMABUF_RENDERER=1 on Linux in both the app entry point and the dev script, without overriding an explicit setting; the WebKit compositor otherwise renders a blank or corrupted window on many Linux systems. README gains Linux prerequisites and Arch/CachyOS build notes.
SEQUENCE.md defines the order work lands in and the definition of done that gates each unit. It records that everything between v0.8.0 and this branch is unfinished: ten streams, none of them decided, including a whiteboard backend that migration v4 already has a column for. feat-portable-vault-sync carries the vault and sync design, blocked on that decision.
The branch held ten unfinished streams, seven of them entangled in the already pushed 44bea94, so those are finished in place as four grouped changes. The whiteboard was local-only and comes off cleanly onto feat/note-whiteboard, taking migration v4, three runtime deps, and four defects out of the release. Sequencing it after the vault makes its sidecar an additive file type instead of a user-data migration.
Settle the props on SegmentedRow and ToggleRow, give them the keyboard and focus behavior their roles promise, and move the row shell each of them was carrying a copy of into one PrefRow component. SegmentedRow is a real radio group now: the selected segment is the only tab stop, the arrow keys move and wrap, and Home and End reach the ends. The Links page hand-rolled both controls and now uses them, which is what proves the API holds before three more settings pages depend on it.
Describe library_stats and every field it returns, and say where each number comes from: seven counts out of the store, and the two attachment numbers off the filesystem, where a missing directory reports zero rather than failing the whole call. Cover the two changelog states the dashboard has to render, an undated heading and a version with no section at all, and correct the release note to the tiles that actually ship.
Tick the tasks that are done and write down what was settled: the frozen row API, why it carries no error state, and where the row shell now lives. The one task left needs a running app, so it says so.
cargo test, npm test, and npm run check all pass clean. The live dashboard-number check and the archive step stay open for jam to verify in the running app.
The "Insert image..." toolbar action hand-wrote the same attachments/<name> markdown that editor/images.ts already builds for paste and drop. Reuse that helper so the two insertion paths produce their reference from one place instead of two copies that happen to match.
…exting SettingsImages.svelte (210 lines) and the imagePrefs store it reads from were both untested. imagePrefs.init() gates a single settings read behind a private flag, which breaks Svelte's effect context under vi.resetModules() + dynamic import, so it's covered separately from the mounted component: images.svelte.test.ts drives init/persistence/clamping directly, SettingsImages.test.ts covers what the page renders and persists. Also extends contexting-format.test.ts: a linked image (an absolute path, not an attachments/ reference) now has explicit coverage across keep, absolute, and strip, alone and alongside a copied image.
import_image_file, allow_image_file, and open_attachments_folder were missing from the attachments section. Also notes how link mode's absolute-path reference relates to the vault portability requirement, which feat-portable-vault-sync's serializer will need.
…mage-handling Resolves the open decision feat-portable-vault-sync deferred to this unit: link mode survives as a live-editing convenience, and the vault serializer materializes a copy into attachments/ at flush time rather than carrying the vault's portability guarantee on a path that can break on another device. Recorded in tasks.md with the reasoning, since the vault serializer reads it. Every task in the unit is done and its suites are green, so the change moves to openspec/changes/archive/.
An earlier truncated terminal read missed the 22-test migration_tests module. The real count was always 92 Rust tests, not 70.
feedback.jsonl accumulates every submission with nothing surfacing it to the user, unlike attachments, which already had "Open attachments folder". Add open_feedback_log, mirroring that command: it reveals the file in the OS file manager rather than opening it, creating an empty file first if none exists yet. Wire a "Reveal saved feedback" button and a line on the Feedback page stating nothing is pruned automatically. Also fixes the send() error handling: a GitHub hand-off failure after a successful local save used to report "Couldn't send feedback", which is wrong since the feedback was saved. The two failures are now distinguished, and the draft only survives a local-save failure.
Only the title was capped; a long feedback body had no limit, and open_url hands the result to the OS shell, whose URL length tolerance is tighter than a browser address bar in some environments. The message is now truncated past 1500 characters with a note that the full text is saved locally; the diagnostics block is never truncated.
SettingsFeedback.svelte (185 lines) was untested. Covers category switching, empty-body rejection, submit success, a local-save failure that keeps the draft, a GitHub-hand-off-only failure that still clears it, and the reveal-log button. feedback.test.ts gains coverage for the new body-length cap, including that diagnostics survive truncation.
The Feedback section was missing entirely. Documents FeedbackInput's shape and states plainly that no note content ever reaches this path.
…app-feedback Recorded: no pruning (a submission is bytes, not the megabytes attachments needed a different answer for), but revealed, since invisibility was the real gap the proposal called out. Reasoning is in tasks.md. Every task in the unit is done and its suites are green, so the change moves to openspec/changes/archive/.
Neither existing suite exercised loadDoc's fix for the stray caret and undo-reaches-into-the-previous-note bug: library.svelte.test.ts's "undo" coverage is workspace-delete undo, and kernel.test.ts's "caret" coverage is fold behavior over a headless state. Mounts the real component with a real EditorView in jsdom, recovers it via EditorView.findFromDOM, and switches notes on the same instance via rerender rather than a fresh render, which would mount a new view and pass trivially either way. Verified by temporarily reverting loadDoc to reuse state across notes and confirming the test fails against it before restoring the fix.
editorPrefs.init() reads three settings, then assigns all three unconditionally once the read resolves. A setter called after init() started but before it resolved (setShowExactTime, setZoom, toggleToolbar) got silently overwritten back to the old persisted value moments later. Guard each field with a #touched set so a live change always wins over a slower stale read. Found via a test asserting exactly this sequence in editor.svelte.test.ts (6 tests), which also covers init()'s read/default/error-fallback behavior directly, since a component mount can't drive a #loaded-guarded singleton's read path without breaking Svelte's effect context under vi.resetModules().
SettingsEditor.svelte (57 lines, one toggle) had no test.
The cursor feedback for the two link-open modes that need Cmd/Ctrl (preview + modclick, and edit mode) depends on ModKeyCursor toggling cm-mod-held correctly; linkMarkClass's existing tests only proved which modes lack cm-link-clickable; the fallback mechanism itself was untested. Covers meta/ctrl detection, release, window blur, and cleanup on destroy so a stale modifier state can't leak into the next note.
Every task is done: the missing regression test exists and was verified to actually catch the regression, SettingsEditor and editorPrefs are tested (surfacing and fixing a real race in the process), and the modifier-held cursor mechanism is covered across all three link-open modes. Suites are green, so the change moves to openspec/changes/archive/.
The prior commit's git add ran against both the new and old (already-moved, nonexistent) paths in one call; git appears to reject the whole add when one pathspec doesn't match, so it silently staged only the earlier git-mv snapshot rather than the fully-edited content sitting in the working tree. Same content this unit's task list always described; only the commit that carried it was wrong.
Opening a database with a user_version past the last known migration returned an error, but the setup hook let it flow into build().expect(), which panics inside a call that cannot unwind and aborts the whole process. Give AppError a distinct SchemaTooNew variant so setup can catch it before that expect, show a message telling the user to update, and exit cleanly instead.
The pinned 1.91.1 channel had no rust-analyzer component available, so VS Code's extension fell back to a bundled version too new for the toolchain to satisfy.
…gs-dashboard Verified against dev-0.9.0.db, a real v3-schema library, after the app's own pre-migration backup made that possible without touching the drifted-to-v4 primary database. All counts matched.
Every task is done: the frozen row primitives, the dashboard counts confirmed live against real data, the attachments and capture-readiness gaps closed, and the changelog view's edge cases covered. Suites are green, so the change moves to openspec/changes/archive/.
Dates the changelog and adds the Linux and release-script fixes that were already done but had no changelog line of their own.
Archive feat-graph-view with its decisions (tags and Spaces as edges, a sidebar place, nothing stored), add the Graph View requirement, and mark unit 13 done.
Several Tauri projects share the default 1420 dev port, so running InstantNotes alongside another one collides. Give this project its own port (1422) across vite.config.js, tauri.conf.json, tauri.dev.conf.json, and the tauri-dev script so multiple projects can run side by side.
Move @tauri-apps/plugin-updater from 2.10.1 to 2.12.0, window-vibrancy from 0.6 to 0.8, and rusqlite from 0.32 to 0.40, along with the resulting lockfile churn in package-lock.json and Cargo.lock.
Replace the update-available modal with a synthetic Update Space in the sidebar: a green asterisk that holds two notes, the version you are going to and the one you are coming from, the install button, and the release notes as a readable note. It removes the old 'remind me later' snooze in favor of a Space you can simply ignore, and the welcome pill and tray check now open it directly.
Introduce src-tauri/src/error.rs, src-tauri/src/events.rs, src/lib/api/error-codes.ts, and src/lib/api/events.ts as the single source of each language's error codes and event names, plus src/lib/api/contract.test.ts to hold the two halves equal. Replace roughly 40 hand-rolled code: literals and 12 bare event-name literals with references into the registries. This is behaviour-preserving: it comes from docs/CODE_SMELL_AUDIT.md phases 0-4 and 6, and the VALIDATION code it removes was latent and never user-visible, since the paths that emitted it surface e.message directly rather than going through friendlyMessage.
The unit landed on 0.9.0-pre on 2026-09-24 with every task checked, but no entry was ever made in SEQUENCE.md, so the file that says what a release contains left it out. It takes 13a by landing order and the boundary registries move to 13b. Add a test that fails when an archived change records landing on a release branch and the ledger does not name it, since nothing was checking.
…resolved Dependabot's three grouped PRs were closed rather than merged: the cargo group was superseded by 6197b97, and the npm and github-actions groups are stale and belong to the dependency baseline unit. Unit 6 now says so, and corrects its own claim that the github-actions group is CI only, since tauri-action v0 to v1 is the action that builds and publishes the release artifacts. The two whiteboard safety refs are deleted, with the evidence that 0.9.0 holds everything they carried. Also clears the em dashes this file had collected.
A whiteboard could only be created from the File menu or the command palette, so the feature was invisible to anyone reading the UI: the toolbar's one create button always made a document. The button becomes a split control. The + still makes a document in one click, and a chevron beside it opens the existing context menu with both kinds, each labelled with its keyboard shortcut so the menu teaches the keys rather than replacing them. Right-clicking the control opens the same menu. ContextMenu grows an optional shortcut hint per item and an optional anchor element. Presses on the anchor no longer count as outside the menu, so the control that opened it can own the toggle instead of the menu closing on press and reopening on the click.
Set the release date to 2026-09-25, the day it is cut. The Whiteboards entry now names the note list's new create button as the first way to start a board, since that is the one a reader will see without knowing the shortcut.
A Windows checkout converts CHANGELOG.md to CRLF, and the parser split it on "\n" alone, so every line kept a trailing carriage return. The bullet pattern ends in `(.+)$` with no `\s*`, and `.` does not match a carriage return, so no bullet matched and the Settings dashboard showed an empty "What's new" on Windows. Split on either ending instead, which fixes it for every pattern rather than one.
The new CRLF tests replaced "\n" to make their fixture, which on a Windows checkout turned the already-CRLF file into "\r\r\n" and tested an input no checkout produces. Convert from either ending instead, so the fixture is the same on every platform.
The export path test hardcoded "/tmp/a.md". On Windows a leading separator is not an absolute path without a drive prefix, so every fixture was rejected for being relative before its extension was considered, and the test failed there. Build the paths from temp_dir(), which is absolute on every platform, and keep one explicit relative case for the check it is meant to cover.
The old faceted crystal read as the Obsidian logo. The new icon follows the Jam-Sw Faceted Stipple style on the standard 824 plate, every bundle size is regenerated from icon.svg, and the menu bar template is now the bolt.
Any note or whiteboard can leave the library for a small frameless window that floats, sits like an ordinary window, or lies on the desktop, and comes back in with its edits on disk. The sticky is the note's only editor while it is out, quit waits for every window that holds edits, and stickies reopen where they were left.
The app binary answers `instantnotes mcp` over stdio with the 2026-07-28 stateless protocol and the 2025-11-25 handshake, exposing search, read, and gated write tools that go through the same Store rules as the UI. Agent work shows on the notes it touches, every save names the version it was based on so an agent never overwrites unseen text, and Settings > Agents controls access. Also drops the unused get_or_create_tag command.
Record each known debt with why it exists and when it should be paid, starting with the findings from the agent access audit.
A sticky loaded its note once and never followed an agent's write, so the next keystroke there saved over the agent's text without a word. It now adopts the write in place when nothing is unsaved and names the agent with a restore otherwise, through the same SaveQueue read the library uses.
A note opening with a bold line was titled with its asterisks, and one opening with a screenshot was titled with the image's file path. Titles now come from the first line with words on it, with emphasis markers stripped from the ends of each word.
Settings > Import on macOS reads the Stickies folder through the system picker, previews each sticky in its own color, and imports the chosen ones with formatting, lists, links, images, and original dates. Re-importing brings in only new stickies. The RTF reader and folder parser are plain Rust and tested on every platform; imported and typed notes share one insert path, and image storing and the folder picker are shared with their first users.
On first launch, and when either document gets a new version, the library opens into a License Space holding the source license and the EULA as notes, each agreed on its own; until both are, the rest of the library, every shortcut and menu command, capture, and stickies stand down. It renders the shared agreement gate generated by installers in place of the retired EulaGate modal.
Shorter entries throughout, with the license gate, agent access, Stickies import, and the title fixes each kept as a line.
Replaces the placeholder DMG background and NSIS images before 0.9.0 ships.
Dates 0.9.0 for today's release and brings the core crate's version to 0.9.0 with the app, as in 0.8.0.
scripts/bump-version.mjs versions the core crate independently, so the 0.9.0 cut keeps it at 0.8.0.
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.
Why
InstantNotes keeps notes in a SQLite library that only InstantNotes can read. That is fast and safe, but it asks the user to trust a single opaque file with everything they write, and it makes the library a dead end: nothing else can open it, and nothing carries it to another machine. 0.9.0 answers that directly with a Vault, a plain Markdown folder the app keeps in step with the library, readable in any editor with the app closed.
The release then widens what a note can be. A note has been one thing since 0.1.0, a Markdown document, so anything spatial had to live outside the app. Whiteboards make a note a canvas without making it a second class of object: a board is still a note, still searchable, still tagged, still exported. Graph answers the companion question, which is what the library looks like as a whole rather than as a list.
It also lets a note leave the library: a sticky note sits on the desktop in its own window, Apple Stickies come in as notes, and an agent (Claude Code, Codex, Cursor) can read and, if allowed, write notes over the Model Context Protocol, visibly and without overwriting what the user is typing. And it is the first release under the PolyForm Strict license and the Jam-Sw EULA (#58), which every user now agrees to before using the app.
The rest is debt this growth exposed. Images accumulated with no way to reclaim the space. The two halves of the app named error codes and events in two places that could silently drift apart. Updates arrived as a modal dialog that interrupted the user to ask a question they could not act on yet.
What Changes
.excalidrawfile beside its note. Text on a board stays searchable and its#tagsstill tag the note. Creating one is now visible in the note list rather than only in the File menu and the command palette.installers(installers#3), which also supplies the final installer art.instantnotes mcpserves the library over MCP (2026-07-28 stateless and 2025-11-25), off until Settings > Agents turns it on. Every save names the version it was based on, so an agent's write is never silently overwritten: an idle note takes it in place, and typing wins with a "Restore theirs" toast, in the library and in stickies alike.ADDED Requirements
Requirement: the vault mirrors the library without owning it
The app SHALL keep every non-deleted note as a Markdown file in the chosen folder, carrying its tags and Spaces, and MUST treat the SQLite library as the source of truth. A vault write failure MUST NOT fail the note write that triggered it.
Scenario: a note edit reaches the folder
Requirement: a whiteboard is a note, not a separate object
A note SHALL be either a document or a whiteboard, and a whiteboard MUST remain subject to every behavior a note has: search, tagging, Spaces, trash, and export. Converting a document to a board MUST carry its text onto the canvas.
Scenario: searching for text written on a board
#portson the canvas#portstagRequirement: a whiteboard can be created from the note list
The note list SHALL offer creating a whiteboard from its create control, so a board can be started without knowing a keyboard shortcut or opening the command palette. The plain create action MUST continue to create a document in one click.
Scenario: starting a board from the list
Requirement: removing images never removes one still in use
Deleting a note permanently SHALL delete only the images no surviving note references. Images referenced by a note in the Trash or the Archive MUST be kept, because those notes can be restored.
Scenario: emptying the Trash with a shared image
Requirement: the license and the EULA are agreed before use
The app SHALL show the source license and the EULA in full and MUST require a separate agreement to each, at its current version, before any window, shortcut, or menu command does its work. Declining SHALL quit.
Scenario: first launch
Requirement: an agent never silently overwrites the user
A save SHALL name the note version it was based on. When another process wrote in between, the user's typing MUST win and the user MUST be offered the other version back, in the library and in a sticky window.
Scenario: an agent edits a sticky while the user types
MODIFIED Requirements
Requirement: a newer library is refused without crashing
Opening a library whose
user_versionis past the last migration this build knows SHALL surface a message telling the user to update. The app MUST NOT abort at launch.Scenario: opening a library from a newer build
Requirement: an update informs rather than interrupts
An available update SHALL be presented as a Space in the sidebar and MUST NOT open a modal dialog or require the user to dismiss a reminder. Installing MUST remain the user's explicit action.
Scenario: an update arrives while the user is writing
Requirement: the dashboard reads the changelog on every platform
The bundled changelog SHALL parse identically whichever line ending the checkout produces. A CRLF copy MUST yield the same release, sections, and items as an LF copy.
Scenario: a Windows checkout renders What's new
Verification
.github/workflows/build.yml:1.1
npm run checkpasses: 636 files, 0 errors, 0 warnings.1.2
npm testpasses: 63 files, 553 tests, including the ten shared agreement-gate cases.1.3
cargo test --workspace --manifest-path src-tauri/Cargo.tomlpasses: 282 tests.1.4
cargo clippy --workspace --all-targets --manifest-path src-tauri/Cargo.toml -- -D warningsis clean.1.5
cargo fmt --all --check --manifest-path src-tauri/Cargo.tomlis clean.1.6 All three platform jobs pass, including windows-latest.
2.1 A 0.8.0 library (schema v3) opens and applies v4, v5, and v6 with every note intact.
2.2 A library touched by a pre-release whiteboard build opens instead of being refused, and its boards render as whiteboards.
2.3 A library at a schema past v6 shows the update message and does not abort the app.
3.1 Turning the vault on writes every note, and "Check vault" reports no mismatch.
3.2 Editing a note updates its file; trashing one moves the file to the vault's trash folder.
3.3 A whiteboard writes a
.excalidrawfile beside its note.4.1 The note list create control makes a document on click and offers New whiteboard from its menu, as does a right-click on the control.
4.2 Cmd+Shift+N and the File menu still create a board, and the palette still converts a note.
4.3 Text written on a board is returned by search, and a
#tagon the canvas tags the note.5.1 The real binary's MCP replies validate against the official JSON Schema of 2026-07-28 and 2025-11-25.
5.2 A write while access is Read is refused; with Read and write, a created note lands in its Space and is queued for the vault.
6.1
installers/scripts/apply.mjsreports InstantNotes already up to date, so LICENSE.md, the EULA, the gate, and the art matchinstallersmain.6.2
dev-standards/org-check.shpasses every licensing and gate rule for InstantNotes.7.1
package.json,src-tauri/tauri.conf.json, andsrc-tauri/Cargo.tomlall read 0.9.0. The core crate versions independently by design and stays at 0.8.0, perscripts/bump-version.mjs.7.2
CHANGELOG.mddates 0.9.0 as 2026-09-30 and the dashboard's "What's new" finds that section for the running version.