Commit Graph

94 Commits

Author SHA1 Message Date
f1f6591319 Add design doc for deck card tags
Proposed, not implemented: user-defined tags on cards within a deck,
filtering by them in the deck builder, and the sync format bump (v5)
they'd need.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:47:45 -05:00
eff96a1fba Deck builder: show prices in the Not owned card search
CardSearchBox gains a showPrice option that puts each result's price at
the right of its row; the deck builder's Not owned box turns it on.

Closes #21

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:39:39 -05:00
6e524ccefa Deck builder: advanced card filter in a popup
A filter button beside the deck's quick filter opens the Library's full
card filter as a popup. The deck's sections narrow to the cards it
matches, found with the same reference search scoped to the deck
(main board and maybeboard). The button lights up while a filter is on,
and the popup shows how many cards match, with Clear filters and Done.
CardFilterPanel gains options to hide Name / In deck and to stay
expanded.

Closes #6

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:31:28 -05:00
1e0cd50c7d Deck builder: sorting and a Price column
Adds the Library's Sort by control (plus a Quantity in deck option) to the
deck builder, applied within each section and group. The card table gets
an optional Price column showing the per-copy price, with the total for
the deck's copies in its tooltip. The toolbar now wraps on narrow screens.

Refs #6

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:25:15 -05:00
70b4d5427b Deck builder: sticky section bar for jumping between sections
A bar below the toolbar pins under the top bar and jumps between the card
sections (Available / In other decks / Not owned, To pull / In deck, or
All cards, plus Maybeboard), marking the one in view. Where it fits it
shows inline tabs; on a narrow bar, one button names the current section
and opens a menu of them all, so nothing scrolls sideways. The switch
follows the bar's own width via a container query. It also holds the
deck filter and an up button back to the overview. Group headers and
column headers pin below the bar.

Closes #23

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:13:29 -05:00
1a23bd83a4 Pin list headers flush to the top of the scroll area
Sticky headers pinned at the content area's padding edge, leaving a strip
above them where scrolled-past rows showed through. Offset them by that
padding. In grouped lists, column headers now pin below the sticky group
heading instead of behind it.

Closes #5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:59:35 -05:00
ef1579c978 Add Cards: re-adding a card moves its Just added row to the top
Previously it got a second row, and the older one kept a stale count.

Closes #30

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:54:42 -05:00
717ae437c8 Add Cards: keep the Just added list across tabs, open card details
The list now lives for the app session (per library) instead of resetting
whenever the tab is left. Each row gets a button opening the card details
modal to set copies' printings, plus the deck's printing and cover when
the card was sent to a deck; owned-count changes there update the row.

Closes #28

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:51:40 -05:00
b3f659005d Add Cards: send new cards straight to a deck
A deck picker beside the location field adds each card to the library and
then to the chosen deck. The copy fills a slot the deck is short on
(unowned or claimed elsewhere for idea decks, not yet pulled for
building/built decks), otherwise the deck gets one more copy. A
maybeboarded card moves to the main board, and building/built decks count
the copy as pulled instead of filing it under a location. Retired decks
aren't offered.

Closes #16

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:45:33 -05:00
d9f92c74fd List views: choose which columns to show
A Columns menu next to the List/Tiles toggle hides or shows optional
columns in the Library, Card Search, deck builder and Decks list row
views. Choices are saved per view on each device. The selection checkbox
and name always show.

Closes #8

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:35:21 -05:00
a92b650dab Printing picker: quick-filter box for cards with many printings
Filters by set code, collector number, year, rarity, language and finish,
matching each word against the start of a field. Shown when a card has 7+
printings.

Closes #31

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 01:04:31 +00:00
ea689b0f2b Honor cards' own copy limits in deck legality
Cards like Relentless Rats (any number), Nazgûl (up to nine), and Once
More with Feeling (only one) override the format's copy limit. The limit
is parsed from oracle text at card-data import into a new
oracle_cards.copy_limit column; older reference dbs fall back to the
format rule until re-imported. The legality check and illegal-card
selection use it. Also count snow basics as basic lands.

Closes #27

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 23:10:23 +00:00
99a579a697 Deck status (Idea/Building/Built/Retired) and pulled-card tracking
Only building and built decks now claim owned copies, so several idea
decks can share one copy without an over-assignment warning. Deck cards
track how many copies are physically pulled: physical decks list cards
to pull with their source (a storage box, or another deck to take a copy
from), and idea decks split cards into available / in other decks / not
owned. The deck list shows status chips with to-pull counts, filters by
status, and collapses retired decks. Status and pulled counts sync,
which bumps the library document to version 4.

Existing decks migrate as ideas.

Closes #25

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 22:35:41 +00:00
eb342f5fbb Deck builder: select illegal cards, with legality issues grouped by category
Legality issues now carry a category (deck size, commander, color identity,
too many copies, banned/restricted) and the offending card. The legality
tile groups them and offers per-category and all-issue selection; over-limit
copies select only the extras, so bulk remove trims to the legal count.

Closes #26

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:44:16 +00:00
cf49d2a68d Add Cards search: wrap arrow-key selection around the result list
Closes #29

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:26:31 +00:00
d286d3420c Decks tab: a deck list page, with the builder on its own screen
The deck list is no longer a sidebar pinned beside the builder; on every
screen size the Decks tab opens to a deck list page (DeckList), and picking
a deck opens its builder full-width with an "All decks" back button. The
list shows each deck as an art tile (or a compact row, toggleable) with its
colors, size against its format, owned %, cost and legality, and can be
searched, filtered by format and sorted (recently opened, name, format,
size, cost, owned %). Rename/duplicate/export/delete live in each deck's
menu and beside the builder's title; New deck now asks for a format too,
defaulting to the last one used.

A deck's cover art is the commander, else its priciest nonland, unless a
card is chosen with "Use as cover" in the card preview. The cover is stored
in decks.cover_oracle_id (migration 0007) and synced as DeckDoc.cover; a
document carrying one is version 3, so the sync server must be updated
before clients publish covers. The list's stats come from one
all_deck_contents query, summarised with the builder's own analysis code,
now in deckAnalysis.ts.

Closes #9
Closes #11
Closes #12
2026-10-25 15:04:41 -05:00
fae1aa2b05 Card preview: clicking empty space beside the info tile closes it 2026-10-25 14:23:48 -05:00
226878fdd7 Share a card's image from the preview
A share button beside the preview's close button sends the face being
shown at Scryfall's large size: on desktop the image is decoded and put on
the clipboard (tauri-plugin-clipboard-manager); on Android it's saved to
the cache and handed to the system share sheet by a small in-repo plugin
(SharePlugin.kt) through the app's FileProvider. The preview's corner
buttons are also a little larger.
2026-10-24 15:20:27 -05:00
7415289257 Info page: show the app version, two-column layout on wide screens
An About panel shows the installed version (from Tauri at runtime, since
release builds stamp it at build time; dev builds are labelled as such).
On wide screens the page splits into panels in two columns — card
database, library stats and libraries on the left; sync and about on the
right — while narrow screens keep the single-column order. The sync panel
is now headed "Sync" and the Info page's summary "Library stats".

Closes #22
2026-10-23 23:27:58 -05:00
52873a2180 Deck analysis shows total cost; tidier analysis layout
A "cost" tile totals the USD price of every copy in the deck (commander
included, maybeboard not). The stat tiles now span the panel and size to
their values, with the color bars and mana curve sharing the row below,
and the left-hand panels no longer spread out to match the analysis
panel's height.

Closes #20
2026-10-23 23:18:27 -05:00
b96931c7b8 Deck chips open their deck
Deck-name chips in the Library, Card Search and the card preview modal are
now buttons (DeckChip) that open that deck in the Decks tab — on mobile,
straight to its building screen. App provides the open-deck callback via
context and hands the deck id to DeckView.

Closes #19
2026-10-23 23:10:40 -05:00
945e5ed766 Accent-insensitive card search
A fold() (lowercase + strip accents, using the same table as nucleo's own
normalization) is registered as a SQLite function and used by the name,
oracle-text and location filters, so "khazad-dum" finds Troll of
Khazad-dûm. The fuzzy name search folds the query too (accented queries
used to match nothing) and its exact-name promotion; decklist/ManaBox
import falls back to a folded name match; the deck quick filter and tag
suggestions fold client-side.

Closes #17
2026-10-23 23:05:19 -05:00
d4d19dcdb4 Add not-owned deck cards to the library from the deck view
Cards with no owned copies get an "Add to library" button (tile corner and
list row) that records the deck's copy count as owned, and the selection
bar gains a bulk "Add to library" that adds just the missing copies.

Closes #10
2026-10-23 22:56:59 -05:00
e4956d8231 Shared card filter panel: News set browser gets every Library filter
Extract the filter fields and LibraryFilter-building state into
src/cardFilter.tsx (CardFilterPanel + useCardFilter) and use it in the
Library, Card Search and News set browser, replacing three diverging
copies. search_reference now honors in_deck/location too, so every
filter works in every tab.

Closes #13
2026-10-23 22:46:45 -05:00
89ef5d0848 Owned-quantity stepper in the card preview modal
The preview's Owned row gets the same −/+ stepper as the library list,
refetching copy groups (which shrink with the total) and refreshing the
caller. Card Search now passes a preview context too, so its previews get
the stepper and printing controls.

Closes #3
2026-10-23 22:35:03 -05:00
089b96e501 Bulk remove takes cards out of the deck immediately
Remove-selected went through the single-card stepper path, so every card
lingered dimmed for the 5-second undo grace period. A deliberate bulk
removal now deletes and drops the rows at once.
2026-10-23 22:29:56 -05:00
bfad4c1f27 Partner commanders: Partner, Partner—<variant>, Partner with, Background, Doctor
A Commander/Brawl deck can now have two commanders when their abilities
allow it. Partner abilities are parsed from oracle text (Scryfall's keyword
list files every "Partner—Survivors"-style variant under plain "Partner"),
and a second search box offers only cards that can pair with the commander.
The legality check accepts a valid pair and checks color identity against
both; suggestions and decklist import/export handle both commanders.

Closes #4
2026-10-23 22:29:56 -05:00
5f19ddfa4a Read picked import files through the fs plugin so Android content URIs work
On Android the open dialog returns a content:// URI, which std::fs can't
open, so ManaBox/library and deck imports failed with "no such file or
directory". Read them via tauri-plugin-fs, which resolves content URIs and
falls through to plain paths on desktop.

Closes #24
2026-10-23 22:01:33 -05:00
18948efd17 Debian packaging 2026-10-22 21:33:31 -05:00
82f10f835b Debian packaging 2026-10-22 21:19:23 -05:00
4b651b5108 Library import asks for a location to file the cards under
After picking a file, Import cards… asks where the cards are kept,
pre-filled with the file's name (a ManaBox export is named after its
binder). A location is set on every imported card, replacing any it had,
as Add Cards does; blank leaves locations alone. It's applied inside the
import's single transaction.
2026-10-17 21:58:44 -05:00
1641d9aa47 Android: build on JDK 25 with Gradle 9.6.1, AGP 9.4.1, Kotlin 2.2.10
Android Studio now bundles JBR 25, which Gradle 8.14's embedded Kotlin
can't parse. Follow Tauri's current template: Gradle 9.6.1, Kotlin 2.2.10,
the builtInKotlin/newDsl/failOnMissingFiles compatibility flags, the
compilerOptions DSL, and ExecOperations in BuildTask (project.exec is gone
in Gradle 9). AGP goes to 9.4.1 rather than the template's 9.3.1.

Release lint vital is turned off: AGP 9.3/9.4's lint crashes analysing the
.gradle.kts scripts (findFirCompiledSymbol), failing every release build.

The env scripts now accept JDK 17 through 25, preferring Studio's JBR.
2026-10-16 22:39:36 -05:00
89378a41fb Android env: require JDK 21/17 by version, since Studio now bundles JBR 25
Check each JDK candidate's major version instead of assuming Android Studio's
JBR is 21, look for Adoptium temurin-{21,17}-jdk (Fedora 44 dropped
java-21-openjdk), and propagate android-env.sh failures to scripts that
source it so fdroid-publish.sh stops before building.
2026-10-16 22:08:47 -05:00
8301371679 Add multi-library to readme 2026-10-16 21:58:28 -05:00
507df81f5f ManaBox import, with progress and a format explainer
Library → Import cards… now reads a ManaBox collection export, either the
.csv or the .txt. The CSV is matched by Scryfall ID, falling back to name,
set and collector number, and its Foil column picks the copy group's finish.
Condition, language and prices are ignored; proxies count as owned.

A decklist line that names a printing now resolves to that printing's card
when several cards share the name (a card and its token, or an art-series
entry), and a bare name prefers the real card.

Imports run in one transaction (nestable via SAVEPOINT), which took the
942-row sample from 22s to 0.2s, and off the main thread, with a progress
dialog for slow ones. An explainer of the supported formats now comes
before the file picker.
2026-10-16 21:57:52 -05:00
81b1df8c9f Named libraries: switch between several, each syncing on its own
The app can keep any number of named libraries and switch between them
from the top bar or Info → Libraries. Each can sync to the account's other
devices or stay on this one (a scratch library to try things in).

The sync server now keeps several libraries per user under
/api/libraries; a migration makes each existing library the user's default
"Main", which /api/library keeps serving for older app builds. Every sync
lines the device's libraries up with the account's: new ones elsewhere are
downloaded, new ones here created on the server, ones deleted elsewhere
flagged rather than thrown away. The existing library keeps its files and
merge base, so upgrading asks nothing.
2026-10-16 21:44:55 -05:00
6072d44ebe Tiles view: +/- control for cards per row
Adjusts the tile grid's column count as an offset from the existing
width-based auto-fill default, shared across Library, Decks, Card search,
and set card lists. Clicking the count resets to the default.
2026-10-16 21:14:43 -05:00
214a535588 Printing tracking for owned copies and deck art, and an outdated-card-data notice
Owned copies can now be pinned to a printing + finish (nonfoil / foil /
etched), activating the long-dormant library_card_copies table as groups
of identical copies. library_cards.quantity_owned stays authoritative:
groups are a subset of it, and the rest are "unspecified". Tracking more
copies than are unspecified grows ownership to fit; lowering ownership
trims the most recently added groups first. Migration 0006 adds the
group quantity, a unique (card, printing, finish) index, and
deck_cards.preferred_scryfall_id.

A deck slot can show a chosen printing's art, both faces from that
printing, falling back to the default when the id no longer resolves.
It survives quantity/board changes and is kept by duplicate_deck.

UI: the click preview's info tile gets a Printings section when opened
from the Library or a deck (copy groups with steppers, "Add a
printing…", and "Shown in this deck" with Change…/Default), backed by a
printing picker grid. The picker takes Escape in the capture phase so
it closes without closing the preview under it.

Sync: CardEntry.printings (keyed "<scryfall_id>/<finish>") and
DeckCardEntry.printing. Cards now merge field by field with groups per
key, so a quantity edit and a printing added on another device both
survive; merged groups are clamped deterministically. A document is
written as version 2 only once it holds printing data, so older builds
and servers refuse it instead of dropping the data and syncing the loss.

Decklists: "(SET) NUM", optionally "*F*"/"*E*", round-trips through deck
and library export/import. Library export writes a line per group plus
one for unspecified copies.

reference.db printings gain lang and released_at for picker labels and
ordering. When the installed card data lacks any column this build's
reference schema has (found by diffing against the schema applied
in-memory, so no list to maintain), a notice under the top bar offers
Update card data (and Import card data… on phones); "Not now" is
remembered until something more goes missing.
2026-10-16 21:06:17 -05:00
1c5e045591 Update docker tag 2026-10-16 20:12:11 -05:00
5d8549af2e Windows installer: cross-build on Linux and publish to a downloads folder
scripts/windows-publish.sh builds the NSIS setup.exe for
x86_64-pc-windows-msvc through cargo-xwin, versioned like the RPM and
APK, and copies it into a folder to serve: the newest three installers,
a stable mtgc-latest-x64-setup.exe, and SHA256SUMS.
2026-10-16 20:11:02 -05:00
067e85dc69 Spare/rarity filters, owned-only suggestions, and a Game night tab
Library filters:
- "Only spare copies" quick filter (Library): cards with more copies
  owned than the decks' main boards use, the opposite of the ⚠
  over-assignment warning. Rows show "N spare" while it's on.
- Rarity toggles (C/U/R/M) in the shared criteria panel, so Library,
  Card Search and the set browser all get them. Any printing counts,
  since the library tracks cards rather than printings; within a set
  it's that set's printing. Arena-only printings don't count for a card
  that also exists on paper, so a digital re-rarity can't make a paper
  common read as rare.

Suggestions:
- "From my collection" toggle (remembered) narrows candidates to owned
  cards before scoring and paging, so "Suggest more" keeps finding them.
- Each suggestion reports copies other decks already use, shown as
  "N spare" / "all in other decks". suggest_cards now takes a
  SuggestOptions struct instead of a bare `unusual` flag.

Game tab: a life counter for 2-6 players with tap/hold ±1/±10, a
running change bubble, poison and per-opponent commander damage (which
also comes off life), out-of-the-game markers, dice and a coin, and a
random first player. A seat's deck puts its commander's art behind the
life total. On phones the board fills the screen with the far row
turned to face across the table, and asks to keep the screen awake.
The game is per-device scratch state in localStorage, not synced.

Plain filter chips now show an accent background when active; before,
"Unusual picks" only changed its text colour.
2026-10-14 22:09:39 -05:00
00a76a0f19 Android back returns from a deck to the deck list on narrow screens
Same as the deck screen's own back button. Only claimed below the 720px
.deck-layout breakpoint, where the list and the builder are separate
screens; side by side, flipping mobileShowList changes nothing visible.
2026-10-14 21:14:50 -05:00
b39b89be20 Android back button closes modals and leaves the News set browser
Adds useBackButton, a stack of back-button claimants (last claimed wins)
backed by Tauri's app-plugin back-button event. The listener is only
registered while something has claimed the button, so at the root back
keeps its native leave-the-app behavior.

Claimed by: the click-to-preview modal, DialogFrame (so Suggest cards,
Sample hand and every other dialog), and NewsView while a set's card
list is open. A preview opened from inside a dialog closes first.
2026-10-14 21:12:30 -05:00
5888e07586 Click preview compact mode: toolbar below, fixed size, Battles fit too
- Toolbar (pane toggle + close) moves below the pane for thumb reach.
- The compact modal gets a fixed box sized to its largest pane, with each
  pane anchored just above the toolbar, so switching to the (shorter)
  info tile no longer moves the buttons. A too-tall info tile scrolls
  within itself.
- Horizontal (Battle) art now uses the same post-toolbar height budget as
  portrait art, computed once in JS from the measured available height.
- Fix useRotatedPreviewBox's width/height bounds being swapped, which let
  rotated art overflow its width cap (e.g. ~469px wide on a 375px phone).
- Only apply the narrow side-by-side DFC budget at the >=860px breakpoint
  where the faces actually sit side by side; below it a DFC Battle's front
  was rendered at ~43vw.
2026-10-14 20:35:50 -05:00
7d1184a762 Click preview: one-pane-at-a-time toggle when it won't fit the screen
When the full layout (card art + info tile) would be taller than the
viewport, typically on phones where the tile wraps below the art, the
modal switches to a compact mode: a toolbar with a Front/Back/Info (or
Card/Info) toggle and the close button, showing one pane at a time with
the art shrunk to fit the remaining height. Decided by measuring the full
layout on mount and after each resize; the art is now sized explicitly
from its known 488x680 aspect so that measurement doesn't depend on the
image having loaded.
2026-10-14 20:29:32 -05:00
e4151a501d Fix click-preview modal clipping under the status bar on mobile
Flex-centering in a scrolling container overflows off both edges when the
card art + info tile is taller than the viewport, and the part pushed above
the top can't be scrolled to. Center with margin: auto instead.
2026-10-14 20:23:51 -05:00
8d510b8ffd Publish desktop releases to a self-hosted dnf repo
scripts/rpm-publish.sh builds the RPM, signs it and the repo metadata
with a dedicated GPG key, and adds it to a dnf repo in a given directory,
keeping the newest three versions for downgrades. The directory holds
nothing secret, so it can be served as is.

Both publish scripts now share the version scheme in
scripts/lib/release-version.sh: <major>.<minor>.<commit count>, plain
enough for RPM, MSI and semver alike. Android's versionCode stays the
commit count.

Fixes the publish scripts failing under set -u when the repo URL
variable isn't set, which is every run after the first. tauri.conf.json
now declares the license and homepage for the package metadata.
2026-10-13 22:05:17 -05:00
348a950489 fdroid-publish: require a /repo URL and recover from a failed update
fdroidserver refuses a repo_url that doesn't end in /repo, but only at
`fdroid update`, after the APK was already copied in. The leftover APK
then made every retry think that version was published. Check the URL
up front, and take the APK back out if the update fails.

FDROID_REPO_URL now also updates repo_url in an existing repo, instead
of being read only when the repo is created.
2026-10-13 21:29:19 -05:00
cec61b886e Run the sync server image as a numeric UID/GID; explain unwritable /data
The image now runs as USER 10001:10001 with a matching group, so
Kubernetes can verify runAsNonRoot and fsGroup: 10001 grants it a mounted
volume. When the database can't be opened because its directory isn't
writable, the server says so and how to fix it, instead of SQLite's bare
"unable to open database file". The README covers the pod securityContext.
2026-10-13 21:21:36 -05:00
3a72780027 Publish Android releases to a self-hosted F-Droid repo
scripts/fdroid-publish.sh builds a signed arm64 APK and adds it to an
F-Droid repo in a given directory, creating the app signing key and the
repo on first use. The versionCode is the commit count, so any commit can
ship without a manual version bump.

Release builds are signed when src-tauri/gen/android/keystore.properties
exists and stay unsigned otherwise. The manifest picks up the deep-link
plugin's intent filter, which every build regenerates and was never
committed.
2026-10-13 21:20:28 -05:00