A native macOS TODO app. Lists per day, drag-to-reorder, notes + tags, full-text search, day-streak stats, and Markdown export. Built with Rust + Tauri 2 + Svelte 5 + Tailwind v4. Stores everything locally in SQLite — no accounts, no cloud, fully offline.
See REQUIREMENTS.md for the full spec.
Versions are pinned in .tool-versions. If you use asdf, they install with asdf install in this directory.
| Tool | Version |
|---|---|
| Rust | 1.95.0 |
| Node | 25.9.0 |
| pnpm | 9.15.0 |
You also need Xcode Command Line Tools (xcode-select --install) so the macOS bundler can find a linker.
# Install pinned toolchains (skip if you already have them)
asdf install
# Install JS dependencies
pnpm installThe Rust side fetches its dependencies on the first cargo / tauri invocation — no separate step.
pnpm tauri devThis starts Vite at localhost:1420 and launches a Tauri window pointing at it. The first launch compiles the Rust side (~30 s); after that it's hot-reload for the frontend and incremental for the backend.
The local database lives at:
~/Library/Application Support/com.alertmedia.bigpicture/todos.db
Migrations run automatically on startup. Delete that file to start fresh.
pnpm tauri build
# or, to skip the DMG step (which sometimes fails and isn't required for personal use):
pnpm tauri build --bundles appThe resulting bundle is at:
src-tauri/target/release/bundle/macos/Alexandria.app
Drag it into /Applications. The build is unsigned, so the first time you open it macOS will refuse to launch it by double-click — right-click the app and choose Open, then confirm the security prompt once. Subsequent launches open normally.
Once you've installed the app to /Applications, here's how to push code changes through to it.
Your data is safe. The SQLite database lives at ~/Library/Application Support/com.alertmedia.bigpicture/todos.db — outside the .app bundle. Replacing the bundle never touches it. If you want a clean slate, delete that file before relaunching.
- Quit the running app with
⌘Q. macOS will refuse to overwrite a running.app. - Rebuild from the project root:
pnpm tauri build --bundles app
- In Finder, open
src-tauri/target/release/bundle/macos/, dragAlexandria.apponto/Applications, and pick Replace when prompted. - Open the new copy. Because the bundle identifier (
com.alertmedia.bigpicture) is unchanged, macOS usually skips the Gatekeeper prompt; if it shows up, right-click → Open like the first install.
Same flow, scripted:
pnpm tauri build --bundles app && \
osascript -e 'quit app "Alexandria"' 2>/dev/null; \
sleep 1 && \
rm -rf /Applications/Alexandria.app && \
cp -R src-tauri/target/release/bundle/macos/Alexandria.app /Applications/ && \
open /Applications/Alexandria.apposascript asks the app to quit cleanly (rather than killall, which would force-terminate it).
If your changes add or alter SQLite tables, add a new migration file under src-tauri/migrations/ (e.g. 0002_add_column.sql) — don't edit 0001_initial.sql. sqlx records which migrations have already run on each database, so existing installs will pick up the new file automatically on next launch and your stored todos survive the upgrade.
If a migration breaks data (renames a column, drops a table), test it against a copy of your real db first:
cp ~/Library/Application\ Support/com.alertmedia.bigpicture/todos.db /tmp/before.db
# run the new build, then diff if you want to inspectThere are two visually distinct things to customize: the macOS app icon (square, used in Dock / Launchpad / Finder) and the in-app logo (rectangular wordmark shown at the top of the sidebar).
You don't need to make every size by hand. Provide one source PNG and let Tauri regenerate everything:
pnpm tauri icon path/to/your-icon.png- Source format: square PNG, ideally 1024×1024 with transparency.
- The command overwrites every file in
src-tauri/icons/— all PNG sizes,icon.icns(the only file macOS actually packages),icon.ico(Windows), and theSquare*.pngWindows variants. - Don't pre-apply rounded corners; macOS adds its own squircle mask. Draw to the edges of the canvas.
- After regenerating, rebuild and reinstall:
Follow the Upgrading an installed copy section to push the new icon into
pnpm tauri build --bundles app
/Applications.
The Sidebar looks for two files in static/:
static/logo.png used in light mode (and as fallback)
static/logo-dark.png used when the system is in dark mode (optional)
If neither file exists, the sidebar falls back to the app name in plain text — so the app still ships without any logo, the slot is just blank text.
- Aspect ratio: roughly 4:1 to 5:1 wordmark (e.g. 400×100 PNG). Anything wider than ~200px or taller than ~24px will be scaled down.
- Format: PNG with transparent background. The strip sits over the window's vibrancy effect, so a solid-background image will look like a sticker.
- Display height: 24px. Provide a 2× asset (e.g. 480×120) for crisp rendering on Retina displays — the image is scaled by
width: auto; height: 24px. - Light + dark: if you only ship one image, pick neutral tones that read on both backgrounds (mid-gray often works). For maximum polish, ship both
logo.pngandlogo-dark.png.
Drop the file(s) into static/ and reload — Vite picks them up immediately. No build step required for the in-app logo.
The app opens to today's list (auto-created on first open of each day). Past lists stay in the sidebar, grouped by month.
Adding todos — type in the input at the top of the main pane and press Enter.
Editing — click a todo's text to edit in place; Enter commits, Escape cancels.
Reordering — drag a row; drop anywhere in the list.
Notes and tags — click the chevron (>) on the right side of a row to open the inspector. Type notes (saves on blur). Add tags with the input at the bottom — Enter creates a new tag if it doesn't exist and links it.
Searching — type in the search box at the top of the sidebar. It matches text and notes across every list. Click a result to jump there.
Renaming a list — click the list title in the main pane.
Multiple lists per day — press ⌘N or click + in the sidebar. They stack under the "Today" header.
Deleting a list — click the trash icon in the list header. The list is archived (soft-deleted); the data isn't removed from the database.
| Shortcut | Action |
|---|---|
⌘N |
New list (date = today) |
⌘F |
Focus search |
⌘E |
Save current list as .md |
⌘⇧C |
Copy current list to clipboard as Markdown |
Esc |
Close inspector |
Inside an editable field, Enter commits and Esc cancels.
Click Export… in the list header for a menu, or use the shortcuts above. Four range presets are available:
- Current list (copy or save)
- This week (last 7 days through today)
- This month (1st of current month through today)
- Everything
Range exports include a table of contents with anchors. Format details live in REQUIREMENTS.md §7.
The Rust side has 38 unit tests covering CRUD, cascade deletes, Markdown rendering, search (including LIKE-wildcard escaping), and streak computation. They use in-memory SQLite, so they're fast and isolated.
cargo test --manifest-path src-tauri/Cargo.tomlFrontend type check:
pnpm check.
├── REQUIREMENTS.md spec & design notes
├── .tool-versions asdf-pinned toolchain
├── package.json frontend deps + scripts
├── src/ SvelteKit frontend
│ ├── app.css global styles + theme tokens
│ ├── app.html
│ ├── routes/+page.svelte three-pane shell + global shortcuts
│ └── lib/
│ ├── ipc.ts typed wrapper over Tauri invoke()
│ ├── stores/
│ │ └── app.svelte.ts AppStore (Svelte 5 class with runes)
│ └── components/
│ ├── Sidebar.svelte
│ ├── ListView.svelte
│ ├── TodoRow.svelte
│ └── Inspector.svelte
└── src-tauri/ Rust backend
├── Cargo.toml
├── tauri.conf.json window config + vibrancy
├── capabilities/
│ └── default.json permissions for plugins
├── migrations/
│ └── 0001_initial.sql
└── src/
├── main.rs
├── lib.rs pool init + command registry
├── error.rs AppError (serializable across IPC)
├── markdown.rs single-list + range renderers
├── db/
│ ├── mod.rs pool, migrations, test helper
│ └── models.rs
└── commands/
├── lists.rs
├── todos.rs
├── tags.rs
├── search.rs search + stats
└── export.rs md export + file save