Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
# mtgc — Personal Magic: The Gathering Library Catalog
|
|
|
|
|
|
2026-08-07 22:08:35 -05:00
|
|
|
A desktop and Android 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, price, format
|
|
|
|
|
legality), and deck organization with per-card deck membership. All logic lives
|
|
|
|
|
in a platform-agnostic Rust core, so both platforms share it.
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
|
|
|
|
|
## 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
|
|
|
|
|
```
|
|
|
|
|
|
2026-08-07 22:10:38 -05:00
|
|
|
Two files:
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
|
2026-08-07 22:08:35 -05:00
|
|
|
- **your library `.json`** — owned cards, decks, deck contents. Small,
|
2026-08-07 22:10:38 -05:00
|
|
|
readable, and the only file you name or move. Put it somewhere synced; see
|
|
|
|
|
[Sync](#sync).
|
|
|
|
|
- **`reference.db`** — read-only card data built from Scryfall bulk. Bulky and
|
|
|
|
|
re-downloaded per device, so it's never synced. Lives in the OS app-data dir
|
|
|
|
|
(e.g. `~/.local/share/com.garrettmills.mtgc/`).
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
|
|
|
|
|
## First-time setup
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./scripts/devel-setup.sh # Tauri system libs (webkit2gtk/gtk3) + Tauri CLI
|
|
|
|
|
npm install # frontend deps
|
|
|
|
|
```
|
|
|
|
|
|
2026-07-07 19:49:50 -05:00
|
|
|
> **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](https://github.com/tauri-apps/tauri/issues/7335),
|
|
|
|
|
> [tao#1104](https://github.com/tauri-apps/tao/pull/1104),
|
|
|
|
|
> [wry#1228](https://github.com/tauri-apps/wry/issues/1228). Revisit once Tauri
|
|
|
|
|
> ships GTK4 support (likely Tauri v3).
|
|
|
|
|
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
## Run
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./scripts/devel-setup.sh # once, if not already done
|
|
|
|
|
cargo tauri build
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Outputs (under the workspace `target/release/`):
|
|
|
|
|
|
Rebrand to mtgc: pirate-ship logo, lowercase app name, mtgc binary
- New logomark: an original pirate-ship silhouette (hull, twin billowing
sails, swallow-tail pennant) in the terracotta brand gradient on a dark
square — a nod to the MTG card "Pirate Ship", not a reproduction of its
art. Source at src-tauri/icons/logo-source.svg; regenerate the full
platform icon set (desktop/Windows Store/iOS/Android) with
`cargo tauri icon src-tauri/icons/logo-source.svg`.
- Apply it in-app via a new inline ShipLogo component (App.tsx), replacing
the old "M" sidebar mark and "✦" empty-state mark with dark rounded
squares containing the ship glyph.
- Rename the app to lowercase "mtgc" everywhere: window title and
productName (tauri.conf.json), <title> (index.html), sidebar brand text,
empty-state heading.
- Rename the Tauri package/binary mtgc-app -> mtgc (and its lib crate
mtgc_app_lib -> mtgc_lib), so `cargo tauri build` now produces
target/release/mtgc; updated main.rs and every README reference.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 21:53:36 -05:00
|
|
|
- **`target/release/mtgc`** — the standalone executable. The web UI and all
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
```bash
|
Rebrand to mtgc: pirate-ship logo, lowercase app name, mtgc binary
- New logomark: an original pirate-ship silhouette (hull, twin billowing
sails, swallow-tail pennant) in the terracotta brand gradient on a dark
square — a nod to the MTG card "Pirate Ship", not a reproduction of its
art. Source at src-tauri/icons/logo-source.svg; regenerate the full
platform icon set (desktop/Windows Store/iOS/Android) with
`cargo tauri icon src-tauri/icons/logo-source.svg`.
- Apply it in-app via a new inline ShipLogo component (App.tsx), replacing
the old "M" sidebar mark and "✦" empty-state mark with dark rounded
squares containing the ship glyph.
- Rename the app to lowercase "mtgc" everywhere: window title and
productName (tauri.conf.json), <title> (index.html), sidebar brand text,
empty-state heading.
- Rename the Tauri package/binary mtgc-app -> mtgc (and its lib crate
mtgc_app_lib -> mtgc_lib), so `cargo tauri build` now produces
target/release/mtgc; updated main.rs and every README reference.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 21:53:36 -05:00
|
|
|
cargo tauri build --no-bundle # only the bare target/release/mtgc binary
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Notes:
|
|
|
|
|
|
Rebrand to mtgc: pirate-ship logo, lowercase app name, mtgc binary
- New logomark: an original pirate-ship silhouette (hull, twin billowing
sails, swallow-tail pennant) in the terracotta brand gradient on a dark
square — a nod to the MTG card "Pirate Ship", not a reproduction of its
art. Source at src-tauri/icons/logo-source.svg; regenerate the full
platform icon set (desktop/Windows Store/iOS/Android) with
`cargo tauri icon src-tauri/icons/logo-source.svg`.
- Apply it in-app via a new inline ShipLogo component (App.tsx), replacing
the old "M" sidebar mark and "✦" empty-state mark with dark rounded
squares containing the ship glyph.
- Rename the app to lowercase "mtgc" everywhere: window title and
productName (tauri.conf.json), <title> (index.html), sidebar brand text,
empty-state heading.
- Rename the Tauri package/binary mtgc-app -> mtgc (and its lib crate
mtgc_app_lib -> mtgc_lib), so `cargo tauri build` now produces
target/release/mtgc; updated main.rs and every README reference.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 21:53:36 -05:00
|
|
|
- The `mtgc` binary is a single file but relies on the system WebKit/GTK libs
|
2026-07-07 19:49:50 -05:00
|
|
|
being present (installed by `devel-setup.sh`).
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
- The binary does **not** embed card data — first run still downloads the Scryfall
|
2026-08-07 22:08:35 -05:00
|
|
|
bulk into the OS app-data dir. Your library document defaults to that directory
|
|
|
|
|
too; move it into a synced folder with **New Library** (see [Sync](#sync)).
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
- 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
|
2026-08-07 22:08:35 -05:00
|
|
|
identity ⊆ chosen), mana value, power/toughness, price, format legality,
|
|
|
|
|
type/subtype/keyword, or deck. Hover a
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
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.
|
2026-08-07 22:08:35 -05:00
|
|
|
- **Card Search** — the same filters over *every* card, owned or not. Select
|
|
|
|
|
rows (or tiles) and bulk-add them to a deck; one undo covers the whole batch.
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
- Deck contents offer the same List / Tiles toggle and hover previews.
|
|
|
|
|
- **Decks** — create decks and assign owned cards; set per-deck quantities.
|
|
|
|
|
|
2026-08-07 22:08:35 -05:00
|
|
|
Both filter panels share a row of quick toggles: hide **Universes Beyond**
|
|
|
|
|
(cards that exist only as another property — Lord of the Rings, Warhammer
|
|
|
|
|
40,000, Marvel), hide **digital-only** (Arena/MTGO cards such as Alchemy
|
|
|
|
|
rebalances), hide **Un-set** joke cards, or keep only cards that **can be a
|
|
|
|
|
commander**. The three exclusions are derived as *every* printing, so an
|
|
|
|
|
ordinary card reprinted into one of those sets — Lightning Bolt appears in
|
|
|
|
|
Marvel Commander — isn't hidden.
|
|
|
|
|
|
|
|
|
|
> Those three, plus the power/toughness filters, live in `reference.db`, which
|
|
|
|
|
> is rebuilt rather than migrated. After upgrading, click **Update card data**
|
|
|
|
|
> once to enable them; until then they're inert rather than broken. Price,
|
|
|
|
|
> format legality and *can be a commander* work immediately.
|
|
|
|
|
|
|
|
|
|
## Sync
|
|
|
|
|
|
|
|
|
|
Your collection is a single JSON file — the only file you manage. Put it in a
|
|
|
|
|
folder your sync tool watches (Syncthing, Dropbox, Nextcloud, Seafile, or a USB
|
|
|
|
|
stick) and every device stays in step. Choose it with **Open Library** /
|
|
|
|
|
**New Library**, or from the **Info** tab.
|
|
|
|
|
|
|
|
|
|
Editing in two places merges rather than one side overwriting the other. Sync
|
|
|
|
|
is a three-way merge against the last version of the document that device read,
|
|
|
|
|
which is what lets it tell "you changed this" from "they deleted it": edit
|
|
|
|
|
different cards on a phone offline and on the desktop, and both survive. When
|
|
|
|
|
both sides change the *same* field, the other copy's value wins and the change
|
|
|
|
|
is reported so you can correct it.
|
|
|
|
|
|
|
|
|
|
It runs by itself — at launch, on every switch away from and back to the app,
|
|
|
|
|
and every few minutes while open. **Sync now** in the Info tab forces a pass.
|
|
|
|
|
|
|
|
|
|
Nothing here depends on a particular sync tool. If two devices publish while
|
|
|
|
|
both are offline, some tools leave a conflict copy behind; mtgc merges any it
|
|
|
|
|
finds beside the document and clears it once the contents are safely published.
|
|
|
|
|
|
|
|
|
|
> **Upgrading from an older build?** Earlier versions had you manage
|
|
|
|
|
> `library.db` yourself. The Info tab offers **Convert old library** once; the
|
|
|
|
|
> original file is left untouched, so it doubles as a backup.
|
|
|
|
|
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
## Develop / test
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-08-07 22:08:35 -05:00
|
|
|
cargo test -p mtgc-core # core: db, search, filters, decks, ingest, sync merge
|
|
|
|
|
cargo test -p mtgc # shell: document read/write, conflict-copy matching
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
npm run build # typecheck + build frontend
|
|
|
|
|
cargo build # build everything incl. the desktop app
|
|
|
|
|
```
|
|
|
|
|
|
2026-08-07 22:08:35 -05:00
|
|
|
The sync merge is a pure function over plain structs (`mtgc_core::sync::merge3`),
|
|
|
|
|
so the cases that matter — concurrent edits, a delete racing an edit, repeated
|
|
|
|
|
merges being a no-op — are unit tests rather than something you have to
|
|
|
|
|
reproduce across two devices.
|
|
|
|
|
|
|
|
|
|
To typecheck the Android Kotlin without building the APK (it doesn't invoke
|
|
|
|
|
cargo, so it won't disturb a running dev session):
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cd src-tauri/gen/android && ./gradlew :app:compileUniversalDebugKotlin
|
|
|
|
|
```
|
|
|
|
|
|
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
|
|
|
## Android
|
Initial commit: MTG personal library catalog (Tauri v2 + React/TS)
Full working app with:
- Rust core (mtgc-core): Scryfall ingester, nucleo fuzzy search, SQLite two-DB
architecture (reference.db + library.db via ATTACH), criteria filters, deck ops
- Tauri v2 shell exposing the core as typed IPC commands
- React/TS frontend: card entry, library browser, deck editor
- Features: fuzzy card search, color/CMC/type/commander-identity filters,
tile/list view toggle, hover card previews, deck format + commander support,
per-card location field, grouping by type/subtype/CMC/color, localStorage
persistence for active tab/deck/grouping, Discord-inspired dark UI (#C8553D accent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-07 19:37:56 -05:00
|
|
|
|
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
|
|
|
The Rust core and frontend are platform-agnostic, so the Android build shares
|
2026-08-07 22:08:35 -05:00
|
|
|
all app logic with the desktop; only data provisioning and file access differ.
|
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
|
|
|
|
|
|
|
|
### One-time setup
|
|
|
|
|
|
|
|
|
|
Install Android Studio and **launch it once** so its wizard downloads the SDK,
|
|
|
|
|
then:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
./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
|
|
|
|
|
|
2026-08-02 13:19:24 -05:00
|
|
|
Every Android command needs `ANDROID_HOME` / `NDK_HOME` / `JAVA_HOME`. Source
|
|
|
|
|
the helper once per shell — it auto-detects all three:
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-08-02 13:22:36 -05:00
|
|
|
source scripts/android-env.sh # bash / zsh
|
2026-08-02 13:19:24 -05:00
|
|
|
```
|
|
|
|
|
|
2026-08-02 13:22:36 -05:00
|
|
|
```fish
|
|
|
|
|
source scripts/android-env.fish # fish
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Only the env helper is shell-specific (it has to mutate the calling shell);
|
|
|
|
|
`android-setup.sh` is executed rather than sourced, so it works from any shell.
|
|
|
|
|
|
2026-08-02 13:19:24 -05:00
|
|
|
Then, from the project root:
|
|
|
|
|
|
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
|
|
|
```bash
|
2026-08-02 13:19:24 -05:00
|
|
|
cargo tauri android dev --open # dev server + open in Android Studio
|
|
|
|
|
cargo tauri android build --apk # standalone release APK
|
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
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`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.
|
|
|
|
|
|
2026-08-02 13:19:24 -05:00
|
|
|
### Working in Android Studio
|
|
|
|
|
|
|
|
|
|
Use **`cargo tauri android dev --open`** rather than opening
|
|
|
|
|
`src-tauri/gen/android` directly. Studio's Run button delegates the Rust build
|
|
|
|
|
to `cargo tauri android android-studio-script`, which expects a running Tauri
|
|
|
|
|
dev session — it reads the dev-server address from
|
|
|
|
|
`/tmp/<identifier>-server-addr`. Open the Gradle project on its own and the
|
|
|
|
|
build fails with `failed to read missing addr file`.
|
|
|
|
|
|
|
|
|
|
With the dev session running, Studio's device dropdown, Device Manager
|
|
|
|
|
(emulator), and debugger all work normally.
|
|
|
|
|
|
2026-08-02 13:27:24 -05:00
|
|
|
> **JetBrains Toolbox users:** `--open` finds Studio by scanning XDG data dirs
|
|
|
|
|
> for a `.desktop` entry whose `Name` is *exactly* `Android Studio`. Toolbox
|
|
|
|
|
> writes a versioned name instead (e.g. `Android Studio Quail 2 2026.1.2 Patch
|
|
|
|
|
> 1`), so the lookup misses and the launch fails with a confusing
|
|
|
|
|
> `Cmd(["Android Studio"]) ... No such file or directory`. `android-setup.sh`
|
|
|
|
|
> drops in a hidden shim entry (`~/.local/share/applications/android-studio.desktop`)
|
|
|
|
|
> to fix this; delete that file to undo it.
|
|
|
|
|
|
2026-08-02 13:19:24 -05:00
|
|
|
To build without Studio and without a dev server, use `cargo tauri android
|
|
|
|
|
build` — that path needs no addr file.
|
|
|
|
|
|
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
|
|
|
### 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.
|
Rebrand to mtgc: pirate-ship logo, lowercase app name, mtgc binary
- New logomark: an original pirate-ship silhouette (hull, twin billowing
sails, swallow-tail pennant) in the terracotta brand gradient on a dark
square — a nod to the MTG card "Pirate Ship", not a reproduction of its
art. Source at src-tauri/icons/logo-source.svg; regenerate the full
platform icon set (desktop/Windows Store/iOS/Android) with
`cargo tauri icon src-tauri/icons/logo-source.svg`.
- Apply it in-app via a new inline ShipLogo component (App.tsx), replacing
the old "M" sidebar mark and "✦" empty-state mark with dark rounded
squares containing the ship glyph.
- Rename the app to lowercase "mtgc" everywhere: window title and
productName (tauri.conf.json), <title> (index.html), sidebar brand text,
empty-state heading.
- Rename the Tauri package/binary mtgc-app -> mtgc (and its lib crate
mtgc_app_lib -> mtgc_lib), so `cargo tauri build` now produces
target/release/mtgc; updated main.rs and every README reference.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 21:53:36 -05:00
|
|
|
|
2026-08-07 22:08:35 -05:00
|
|
|
### Sync on Android
|
|
|
|
|
|
|
|
|
|
Android can't hand out a filesystem path to a shared folder, so instead of
|
|
|
|
|
naming a file you grant access to a *folder* through the system picker:
|
|
|
|
|
**Info → Choose folder…**, then pick the one your sync tool watches. mtgc keeps
|
|
|
|
|
`library.json` inside it and everything in [Sync](#sync) applies unchanged.
|
|
|
|
|
|
|
|
|
|
That grant is per-folder and persists across restarts, so the app declares **no
|
|
|
|
|
storage permission at all** — no "All files access" prompt. The alternative,
|
|
|
|
|
`MANAGE_EXTERNAL_STORAGE`, would hand the app the entire shared volume just to
|
|
|
|
|
reach one directory.
|
|
|
|
|
|
Rebrand to mtgc: pirate-ship logo, lowercase app name, mtgc binary
- New logomark: an original pirate-ship silhouette (hull, twin billowing
sails, swallow-tail pennant) in the terracotta brand gradient on a dark
square — a nod to the MTG card "Pirate Ship", not a reproduction of its
art. Source at src-tauri/icons/logo-source.svg; regenerate the full
platform icon set (desktop/Windows Store/iOS/Android) with
`cargo tauri icon src-tauri/icons/logo-source.svg`.
- Apply it in-app via a new inline ShipLogo component (App.tsx), replacing
the old "M" sidebar mark and "✦" empty-state mark with dark rounded
squares containing the ship glyph.
- Rename the app to lowercase "mtgc" everywhere: window title and
productName (tauri.conf.json), <title> (index.html), sidebar brand text,
empty-state heading.
- Rename the Tauri package/binary mtgc-app -> mtgc (and its lib crate
mtgc_app_lib -> mtgc_lib), so `cargo tauri build` now produces
target/release/mtgc; updated main.rs and every README reference.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 21:53:36 -05:00
|
|
|
## 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:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cargo tauri icon src-tauri/icons/logo-source.svg
|
|
|
|
|
```
|