Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Cross-platform build gate: every PR and push to main must compile, type-check,
# and pass tests on all three shipping platforms. `tauri build --no-bundle` runs
# the full production pipeline (frontend build, per-platform config resolution,
# release compile) without bundling, so no signing secrets are needed here.
name: Build

on:
pull_request:
push:
branches: [main]

jobs:
build:
strategy:
fail-fast: false
matrix:
platform: [macos-latest, ubuntu-22.04, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- uses: dtolnay/rust-toolchain@stable

- uses: swatinem/rust-cache@v2
with:
workspaces: src-tauri

- name: Install Tauri system dependencies (Linux)
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev

- run: npm ci

- run: npm run check

- run: npm test

- run: cargo test --workspace --manifest-path src-tauri/Cargo.toml

- run: npm run tauri -- build --no-bundle
41 changes: 33 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Push a v* tag and GitHub builds, signs, and publishes the release,
# including the latest.json manifest the in-app updater polls.
# Push a v* tag and GitHub builds every platform and assembles a draft release,
# including the latest.json manifest the in-app updater polls. The three matrix
# jobs upload into one draft; tauri-action merges each platform into latest.json,
# so the draft must stay unpublished until all jobs finish. Publish after the
# smoke test to ship.
# Requires repo secrets: TAURI_SIGNING_PRIVATE_KEY, TAURI_SIGNING_PRIVATE_KEY_PASSWORD.
name: Release

Expand All @@ -12,8 +15,15 @@ permissions:

jobs:
release:
# macos-latest runners are arm64; we ship Apple Silicon only.
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
include:
# macos-latest runners are arm64; macOS ships Apple Silicon only.
- platform: macos-latest
- platform: ubuntu-22.04
- platform: windows-latest
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4

Expand All @@ -28,13 +38,20 @@ jobs:
with:
workspaces: src-tauri

- name: Install Tauri system dependencies (Linux)
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev

- run: npm ci

# Release notes = the CHANGELOG section for this tag. tauri-action writes
# this into both the GitHub release body and latest.json's "notes", so the
# in-app updater shows real "What's new" text, not install boilerplate.
- name: Extract release notes
id: notes
shell: bash
run: |
version="${GITHUB_REF_NAME#v}"
notes="$(awk -v v="$version" '
Expand All @@ -50,7 +67,13 @@ jobs:
printf '%s\n' "$notes"
echo ""
echo "---"
echo "First install: download the \`.dmg\`, open it, and drag InstantNotes to Applications. macOS blocks the first launch of this unnotarized build — run \`xattr -d com.apple.quarantine /Applications/InstantNotes.app\` or use System Settings > Privacy & Security > \"Open Anyway\". Existing installs update in place from inside the app."
echo "First install on macOS: download the \`.dmg\`, open it, and drag InstantNotes to Applications. macOS blocks the first launch of this unnotarized build: run \`xattr -d com.apple.quarantine /Applications/InstantNotes.app\` or use System Settings > Privacy & Security > \"Open Anyway\"."
echo ""
echo "First install on Windows: download and run the \`-setup.exe\` installer. SmartScreen flags the unsigned build: click \"More info\", then \"Run anyway\"."
echo ""
echo "First install on Linux: download the \`.AppImage\`, make it executable (\`chmod +x\`), and run it."
echo ""
echo "Existing installs update in place from inside the app."
echo "__NOTES_EOF__"
} >> "$GITHUB_OUTPUT"

Expand All @@ -62,8 +85,10 @@ jobs:
with:
tagName: ${{ github.ref_name }}
releaseName: "InstantNotes ${{ github.ref_name }}"
# Publish on a successful build so a forgotten draft can't silently
# block delivery. Pushing a v* tag is the deliberate ship gate.
releaseDraft: false
# Draft while the matrix assembles: publishing early would expose a
# latest.json missing the platforms still building. Publishing the
# smoke-tested draft is the ship gate.
releaseDraft: true
includeUpdaterJson: true
updaterJsonPreferNsis: true
releaseBody: ${{ steps.notes.outputs.body }}
48 changes: 34 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,59 @@
# InstantNotes

Instant notes for macOS. Capture, organize, and search your thoughts.
Instant notes for macOS, Windows, and Linux. Capture, organize, and search your thoughts.

InstantNotes is a desktop notes app built around fast capture and a focused library. Save a thought from anywhere with a global shortcut, then organize and retrieve it without being forced into a folder system.

## Features

- **Instant capture**: a lightweight capture panel summoned from the system tray or via `Cmd+Shift+N`, with drafts preserved if dismissed
- **Instant capture**: a lightweight capture panel summoned from the system tray or via a global hotkey (`Option+Space` on macOS, `Ctrl+Shift+Space` on Windows and Linux), with drafts preserved if dismissed
- **Focused library**: a two-section sidebar (All Notes and Workspaces) over a note list and editor, with pinned notes floated to the top and a status filter for archived and trashed notes
- **Workspaces**: named collections that group related notes; a note can live in many workspaces, and deleting a workspace never deletes its notes
- **Full-text search**: SQLite FTS5 search over titles and bodies with ranked results, using plain-language queries with no search syntax to learn
- **Command palette**: a `Cmd+K` palette for running actions and switching themes, with arrow-key navigation and recents; search reaches into sub-menus (typing a theme name jumps straight to it), and the Themes sub-menu applies each theme live so you can preview as you arrow through
- **Command palette**: a `Cmd+K` (`Ctrl+K`) palette for running actions and switching themes, with arrow-key navigation and recents; search reaches into sub-menus (typing a theme name jumps straight to it), and the Themes sub-menu applies each theme live so you can preview as you arrow through
- **Tags, not folders**: lightweight labels, including tags extracted from `#inline` text
- **Local and private**: all data stored locally in SQLite; note content never appears in logs or diagnostics

## Installation

InstantNotes runs on macOS (Apple Silicon). Download the latest `.dmg` from the [releases page](../../releases), open it, and drag InstantNotes to Applications.
Download the latest build for your platform from the [releases page](../../releases). The builds are unsigned, so each OS asks for a one-time confirmation on first launch; the in-app updater applies later versions without any of it.

The app is not notarized, so macOS blocks the first launch with an "Apple could not verify" message. Clear the quarantine flag and it opens normally from then on:
### macOS (Apple Silicon)

Download the `.dmg`, open it, and drag InstantNotes to Applications. The app is not notarized, so macOS blocks the first launch with an "Apple could not verify" message. Clear the quarantine flag and it opens normally from then on:

```sh
xattr -d com.apple.quarantine /Applications/InstantNotes.app
```

Alternatively, after the blocked first launch, open System Settings, go to Privacy and Security, scroll down, and click "Open Anyway". On macOS 14 and earlier, right-click the app and choose Open instead. This is a first-install step only: the in-app updater applies later versions without any of it.
Alternatively, after the blocked first launch, open System Settings, go to Privacy and Security, scroll down, and click "Open Anyway". On macOS 14 and earlier, right-click the app and choose Open instead.

### Windows (x64)

Download and run the `-setup.exe` installer. SmartScreen flags the unsigned build: click "More info", then "Run anyway".

### Linux (x64)

Download the `.AppImage`, make it executable, and run it:

```sh
chmod +x InstantNotes_*.AppImage
./InstantNotes_*.AppImage
```

The app lives in the system tray; on desktops without tray support (such as stock GNOME, which needs the AppIndicator extension), use the in-window File menu to quit and the library window to work.

To build from source instead, see [Development](#development).

## Development

### Prerequisites

- macOS
- [Rust](https://rustup.rs/) (stable)
- Node.js 20+
- macOS, Windows, or Linux
- [Rust](https://rustup.rs/) via rustup (the version is pinned by `rust-toolchain.toml`)
- Node.js 22+
- Linux only: the [Tauri system dependencies](https://v2.tauri.app/start/prerequisites/#linux) (webkit2gtk 4.1 and friends)
- Windows only: the Visual Studio Build Tools with the C++ workload

### Run the app

Expand All @@ -59,7 +78,7 @@ npm test # frontend unit tests (Vitest)
npm run tauri build
```

Produces an `.app` bundle and `.dmg` under `src-tauri/target/release/bundle/`. Without an Apple Developer ID the bundle is ad-hoc signed and not notarized, so downloaded copies require the first-launch steps described under [Installation](#installation). Builds made locally on your own machine are not quarantined and open normally.
Produces the platform's bundles under `src-tauri/target/release/bundle/`: an `.app` and `.dmg` on macOS, an NSIS `-setup.exe` on Windows, and an `.AppImage` on Linux. The builds are unsigned (macOS is ad-hoc signed, not notarized), so downloaded copies require the first-launch steps described under [Installation](#installation). Builds made locally on your own machine open normally.

### Release (with self-update)

Expand All @@ -69,17 +88,18 @@ The app checks GitHub Releases for updates on launch and every 6 hours, via `lat
# 1. Add a "## [X.Y.Z]" section to CHANGELOG.md describing the release.
# 2. Bump every version file in lockstep:
npm run bump X.Y.Z
# 3. Commit, tag, and push; .github/workflows/release.yml builds, signs, and
# publishes the release on a successful build (no draft step to forget).
# 3. Commit, tag, and push; .github/workflows/release.yml builds macOS, Windows,
# and Linux in a matrix and assembles a draft release with a merged latest.json.
git commit -am "chore: bump version to X.Y.Z"
git tag vX.Y.Z && git push origin vX.Y.Z
# 4. Smoke test the draft's artifacts, then publish the draft to ship.
```

`npm run bump` updates package.json, src-tauri/tauri.conf.json, src-tauri/Cargo.toml, and src-tauri/Cargo.lock together (the `instantnotes-core` crate versions independently). Pushing a `v*` tag is the deliberate ship gate.
`npm run bump` updates package.json, src-tauri/tauri.conf.json, src-tauri/Cargo.toml, and src-tauri/Cargo.lock together (the `instantnotes-core` crate versions independently). Publishing the smoke-tested draft is the deliberate ship gate; the draft stays invisible to the in-app updater until then.

CI signs the updater artifact with the minisign key stored in the repo secrets `TAURI_SIGNING_PRIVATE_KEY` and `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`, and the app verifies downloads against the matching public key in `tauri.conf.json`. If the secret is ever lost, generate a new keypair with `npm run tauri signer generate`, update both the secret and the pubkey, and ship one manual release so installs can cross over.

For a fully local release without CI, build with `TAURI_SIGNING_PRIVATE_KEY` set, run `./scripts/make-update-manifest.sh`, and upload the dmg, `InstantNotes.app.tar.gz`, and `latest.json` with `gh release create`. Release downloads must be publicly reachable for the in-app check to work.
For a fully local release without CI (macOS-only fallback: `make-update-manifest.sh` writes just the `darwin-aarch64` entry), build with `TAURI_SIGNING_PRIVATE_KEY` set, run `./scripts/make-update-manifest.sh`, and upload the dmg, `InstantNotes.app.tar.gz`, and `latest.json` with `gh release create`. Release downloads must be publicly reachable for the in-app check to work.

### Project structure

Expand Down
19 changes: 19 additions & 0 deletions openspec/changes/feat-list-tab-indent/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Change: Tab and Shift+Tab nest list items in the editor

## Why
The editor had no Tab handling for lists, so there was no way to create a sublist or
un-nest one with the keyboard. Nesting is a basic outlining need: pressing Tab on a list
item should indent it into a sublist, and Shift+Tab should outdent it back.

## What Changes
- Add a pure `listIndentChanges` helper that computes the indent/outdent change set for
every list line a selection touches.
- Bind Tab and Shift+Tab in the editor (before the default keymap) to apply it: Tab nests
by two spaces, Shift+Tab un-nests by up to two. Bullet (-, *, +) and ordered (1.)
markers are recognized.
- Leave non-list lines to the editor's existing default Tab behavior.

## Impact
One new pure module with unit tests and one keymap in the editor. Ordered lists are not
renumbered (markdown renderers handle nesting). Always on, no new setting. CodeMirror
remaps the selection through the line-anchored changes so the caret stays with its text.
6 changes: 6 additions & 0 deletions openspec/changes/feat-list-tab-indent/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Tasks

- [x] Add a pure listIndentChanges helper (bullet + ordered, multi-line selection)
- [x] Unit-test indent, outdent, non-list, mixed, and multi-line cases
- [x] Bind Tab / Shift+Tab in the editor before the default keymap
- [ ] Confirm nesting and un-nesting work in the running editor
24 changes: 24 additions & 0 deletions openspec/changes/feat-settings-contexting/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Change: Settings wiki navigation and the Contexting copy format

## Why
The settings view shipped as a sidebar with a single About tab and an empty placeholder.
Two needs push it forward: settings should grow without becoming a long scroll, and the
app needs a foundation for AI features. The answer is a wiki-style settings shell, a
landing grid of categories that open focused sub-pages, plus a first new category,
Contexting, that controls what copying a note hands to other tools.

## What Changes
- Replace the settings sidebar with a landing grid of category cards; each card opens a
focused sub-page with a breadcrumb back to the grid. Escape steps back to the grid
before closing the view.
- Keep the existing About page; drop the empty Default New Tab placeholder.
- Add a Contexting category: a user-editable copy template with {title}, {tags}, {date},
and {content} placeholders, a live preview, and the list of available variables.
- Add a "Copy note as context" command to the palette that renders the template for the
selected note and writes it to the clipboard.
- Persist the template to the settings KV, and render it from a pure, tested module.

## Impact
A new pure module (contexting-format) with tests, a small rune store, one palette command,
and a rewritten SettingsView. Clipboard uses the WebView's navigator.clipboard, so no new
native dependency or capability is added. The core crate is untouched.
8 changes: 8 additions & 0 deletions openspec/changes/feat-settings-contexting/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Tasks

- [x] Add a pure renderTemplate module with {title}/{tags}/{date}/{content} and tests
- [x] Add a contexting rune store persisting the template to the settings KV
- [x] Add the "Copy note as context" palette command (clipboard write)
- [x] Rewrite SettingsView as a wiki grid with About and Contexting sub-pages
- [x] Init the contexting store on app start
- [ ] Confirm in the running app: edit the template, the preview updates, and the copy fills variables
20 changes: 20 additions & 0 deletions openspec/changes/feat-titlebar-theme-sync/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Change: Native titlebar follows the in-app dark/light theme

## Why
The library window keeps the native macOS titlebar, but the theme store never tells
macOS when the resolved light/dark variant changes. The titlebar and traffic lights
stay locked to whatever the system appearance was at launch, so switching to a dark
theme under a light system (or the reverse) leaves a mismatched bright titlebar above
a dark app.

## What Changes
- Add a thin `set_window_theme` command that sets the library window's native theme
(Light or Dark) through Tauri's window API.
- Expose it as `setWindowTheme` in the IPC client.
- Sync it from the theme store whenever the resolved variant changes, alongside the
existing vibrancy sync, so every theme or mode change repaints the chrome.

## Impact
Three small touch points: one Tauri command, one IPC wrapper, and one private sync
method on the theme store. The core crate is untouched. A no-op off macOS and in browser
dev. The borderless capture window has no native chrome and is left alone.
6 changes: 6 additions & 0 deletions openspec/changes/feat-titlebar-theme-sync/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Tasks

- [x] Add the set_window_theme Tauri command and register it in the handler
- [x] Add the setWindowTheme IPC client wrapper
- [x] Sync the window theme from the theme store on every resolved-variant change
- [ ] Confirm the native titlebar flips live when toggling light/dark in the running app
19 changes: 19 additions & 0 deletions openspec/changes/fix-palette-long-title/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Change: Keep the action label readable for long note titles in the palette

## Why
Note-scoped commands in the command palette render the note title as a breadcrumb
prefix before the action label, for example "My very long note title > Pin note".
The title and the action shared a single ellipsized line, so a long title consumed
the whole row and pushed the action label out of view. The user could no longer tell
which action a row would run.

## What Changes
- Render the palette title region as a flex row so the action label keeps its width.
- Give the note-title and folder prefix its own max-width and ellipsis, so a long
title truncates on its own while the separator and action stay fully visible.
- Keep the breadcrumb separator, the leading icon, and the action label from shrinking.

## Impact
A presentational change confined to `src/lib/components/CommandPalette.svelte` (one
markup wrap plus CSS). No command logic, store, or Rust changes. The folder breadcrumb
shown on searched leaves benefits from the same prefix cap.
6 changes: 6 additions & 0 deletions openspec/changes/fix-palette-long-title/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Tasks

- [x] Wrap the action title in its own element so it can stay non-shrinking
- [x] Make the title region a flex row and cap the prefix with its own ellipsis
- [x] Keep icon, separator, and action label from shrinking
- [x] Confirm svelte-check and Vitest stay green
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"version": "0.6.2",
"description": "InstantNotes — instant capture, organized knowledge",
"type": "module",
"engines": {
"node": ">=22"
},
"scripts": {
"dev": "vite dev",
"build": "vite build",
Expand Down
4 changes: 4 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# One toolchain everywhere: rustup reads this on every cargo invocation, locally
# and on all three CI runners, so builds cannot drift between machines.
[toolchain]
channel = "1.91.1"
4 changes: 2 additions & 2 deletions scripts/bump-version.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node
// Bump the app version in every file that must stay in lockstep, in one shot,
// so a release can never ship with the manifests disagreeing.
// so a release can never ship with the manifests disagreeing. Run via
// `npm run bump` (no shebang: Windows vitest cannot parse the file with one).
//
// npm run bump 0.6.0
//
Expand Down
Loading
Loading