Skip to content

Repository files navigation

Alexandria

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.


Requirements

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.


First-time setup

# Install pinned toolchains (skip if you already have them)
asdf install

# Install JS dependencies
pnpm install

The Rust side fetches its dependencies on the first cargo / tauri invocation — no separate step.


Run in development

pnpm tauri dev

This 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.


Build a production .app

pnpm tauri build
# or, to skip the DMG step (which sometimes fails and isn't required for personal use):
pnpm tauri build --bundles app

The 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.


Upgrading an installed copy

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.dboutside the .app bundle. Replacing the bundle never touches it. If you want a clean slate, delete that file before relaunching.

Manual (Finder)

  1. Quit the running app with ⌘Q. macOS will refuse to overwrite a running .app.
  2. Rebuild from the project root:
    pnpm tauri build --bundles app
  3. In Finder, open src-tauri/target/release/bundle/macos/, drag Alexandria.app onto /Applications, and pick Replace when prompted.
  4. 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.

One-liner

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.app

osascript asks the app to quit cleanly (rather than killall, which would force-terminate it).

Schema changes

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 inspect

Custom icons and branding

There 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).

macOS app icon

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 the Square*.png Windows 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:
    pnpm tauri build --bundles app
    Follow the Upgrading an installed copy section to push the new icon into /Applications.

In-app logo (sidebar top)

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.png and logo-dark.png.

Drop the file(s) into static/ and reload — Vite picks them up immediately. No build step required for the in-app logo.


Using the app

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.


Keyboard shortcuts

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.


Markdown export

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.


Tests

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.toml

Frontend type check:

pnpm check

Project layout

.
├── 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

About

My personal To-Do list

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages