Garrett Mills d3154738d2 Add Android support via Tauri
The Rust core was already portable (caller-provided db paths, rustls rather
than openssl) and the shell already had the mobile entry point, so this is
mostly toolchain, provisioning, and platform-gating.

Toolchain:
- scripts/android-setup.sh installs what Android Studio doesn't: SDK
  cmdline-tools, a pinned stable NDK, and the Rust Android targets. The NDK is
  pinned rather than "latest" because sdkmanager's newest is often a beta
  (30.0.15729638 is r30-beta2) -- not something to compile SQLite with. Prefers
  Android Studio's bundled JDK 21, since Gradle rejects newer JDKs.
- Track src-tauri/gen/android (excluding build output): AndroidManifest.xml is
  hand-edited and Android permissions have no tauri.conf.json equivalent.
- vite: serve on TAURI_DEV_HOST so a device/emulator can reach the dev server.

Card data: downloading streams ~740MB to build a ~73MB db, which is rough on a
phone. Add import_reference_db to adopt a desktop-built file instead, validated
by db::inspect_reference_db first so picking the wrong file is a clean error
rather than a broken install. Installs via copy-then-rename so an interrupted
copy can't leave a partial db.

Ingest hardening (helps desktop too):
- Give ureq connect/read timeouts. Cancellation is only polled per parsed
  record, so a stalled socket previously hung forever with no way to cancel.
- Drop the remove_file before rename; POSIX rename already replaces atomically,
  and the delete-first window could leave no reference.db at all if the process
  was killed -- much likelier on Android.

UI: hide Open/New Library on mobile (scoped storage has no arbitrary-path
equivalent, and SQLite can't open a content:// URI), reported via a cfg!(mobile)
flag on Status rather than user-agent sniffing. Use 100dvh so the soft keyboard
doesn't break the shell.

Verified: 70/70 core tests, desktop build clean, and the aarch64 APK builds
(bundled SQLite C and ring both cross-compile under NDK 28). Not yet run on a
device.
2026-08-02 13:16:34 -05:00
2026-08-02 13:16:34 -05:00
2026-08-02 13:16:34 -05:00
2026-08-02 13:16:34 -05:00
2026-08-02 13:16:34 -05:00
2026-08-02 13:16:34 -05:00
2026-08-02 13:16:34 -05:00

mtgc — Personal Magic: The Gathering Library Catalog

A desktop app (Tauri v2 + React) for cataloging your MTG collection: rapid fuzzy card entry, rich criteria search (colors, commander color-identity compatibility, mana value, type/subtype/keyword), and deck organization with per-card deck membership. Built so the eventual mobile port is cheap — all logic lives in a platform-agnostic Rust core.

Architecture

src/                 React + TypeScript UI (swappable presentation)
src-tauri/           Tauri v2 shell — exposes the core as typed commands
crates/mtgc-core/    Pure Rust: db, fuzzy search, filters, decks, ingester

Two SQLite databases, joined at runtime via ATTACH:

  • reference.db — read-only card data built from Scryfall bulk. Bulky, re-downloadable per device, not synced.
  • library.db — your owned cards + decks. Small, portable, the file you sync (drop it in Syncthing/Dropbox/Nextcloud). Point the app at it via Choose library file….

Both default to the OS app-data dir (e.g. ~/.local/share/com.garrettmills.mtgc/).

First-time setup

./scripts/devel-setup.sh     # Tauri system libs (webkit2gtk/gtk3) + Tauri CLI
npm install                  # frontend deps

Why GTK3, not GTK4? Tauri v2's Linux backend (wry/tao) hard-depends on GTK3 + webkit2gtk-4.1; there's no GTK4/webkitgtk-6.0 option yet. GTK4 support is tracked upstream but unmerged with no ETA: tauri#7335, tao#1104, wry#1228. Revisit once Tauri ships GTK4 support (likely Tauri v3).

Run

cargo tauri dev              # launches the desktop app (dev mode)

On first launch, click Download card data — it fetches the Scryfall Oracle Cards + Default Cards bulk (~a few hundred MB) and builds reference.db. After that the app is fully offline.

You can also build the card database from the command line:

cargo run -p mtgc-core --bin mtgc-ingest -- /path/to/reference.db

Production build

Build an optimized app with the frontend embedded into the executable:

./scripts/devel-setup.sh   # once, if not already done
cargo tauri build

Outputs (under the workspace target/release/):

  • target/release/mtgc — the standalone executable. The web UI and all app logic are baked into this one file. It dynamically links the system WebKit/GTK libraries (webkit2gtk4.1, gtk3), so those must be present on the machine that runs it — they are on any modern desktop Linux and are installed by devel-setup.sh.
  • target/release/bundle/rpm/*.rpm and .../deb/*.deb — native installers (Fedora / Debian-based) that add a menu entry and icon.

Useful variants:

cargo tauri build --no-bundle          # only the bare target/release/mtgc binary

Notes:

  • The mtgc binary is a single file but relies on the system WebKit/GTK libs being present (installed by devel-setup.sh).
  • The binary does not embed card data — first run still downloads the Scryfall bulk into the OS app-data dir, and your synced library.db lives there by default (override with Choose library file…).
  • The [profile.release] settings in Cargo.toml (strip + LTO) make the release build take a few minutes but keep the binary small.

Using it

  • Add Cards — type a name (typos OK); ↑/↓ then Enter adds a copy.
  • Library — filter owned cards by name, colors, commander identity (color identity ⊆ chosen), mana value range, type/subtype/keyword, or deck. Hover a row's thumbnail for a full-size preview that tracks the cursor. Rows show which decks a card is in and a warning (⚠) when it's assigned to more decks than you own. Toggle List / Tiles (top-right) to switch between the table and a grid of full card images; the choice is remembered.
  • Deck contents offer the same List / Tiles toggle and hover previews.
  • Decks — create decks and assign owned cards; set per-deck quantities.

Develop / test

cargo test -p mtgc-core      # core unit tests (db, search, filters, decks, ingest)
npm run build                # typecheck + build frontend
cargo build                  # build everything incl. the desktop app

Android

The Rust core and frontend are platform-agnostic, so the Android build shares all app logic with the desktop; only data provisioning differs.

One-time setup

Install Android Studio and launch it once so its wizard downloads the SDK, then:

./scripts/android-setup.sh

That adds what Android Studio doesn't install by default: the SDK command-line tools, a pinned stable NDK, and the Rust Android targets. It prints the three environment variables to export (ANDROID_HOME, NDK_HOME, JAVA_HOME).

Why the NDK? mtgc-core builds rusqlite with the bundled feature (compiles SQLite from C) and rustls pulls in ring (C/asm), so a C cross-toolchain is required. The bundled SQLite can't be swapped for Android's system libsqlite3 either — the card schema needs FTS5 with the trigram tokenizer, which platform SQLite often lacks.

Why JAVA_HOME? Gradle/AGP reject very new JDKs. The script prefers Android Studio's bundled JetBrains Runtime (JDK 21), which needs no extra install.

Build and run

cargo tauri android init      # once — scaffolds src-tauri/gen/android
cargo tauri android dev       # run on an emulator or adb-connected device
cargo tauri android build --apk

src-tauri/gen/android is committed (unlike the rest of gen/) because AndroidManifest.xml is hand-edited — Android permissions have no tauri.conf.json equivalent.

Card data on a phone

Downloading card data streams ~740MB of JSON to build a ~73MB database, which is slow on mobile and may be killed if the app is backgrounded. Faster: copy reference.db from a desktop install onto the device and use Import card data…. The file is validated before it replaces anything.

Branding

The logo is an original pirate-ship silhouette in the terracotta brand color (#C8553D) — a nod to the MTG card "Pirate Ship", not a reproduction of its art. Source: src-tauri/icons/logo-source.svg. To regenerate the full platform icon set after editing it:

cargo tauri icon src-tauri/icons/logo-source.svg
Description
No description provided
Readme 4.9 MiB
Languages
Rust 57.5%
TypeScript 29.4%
CSS 7.9%
Shell 4.4%
Kotlin 0.6%