108 Commits

Author SHA1 Message Date
b8aed07aa8 CI: add a manual run to deploy from
All checks were successful
ci/woodpecker/manual/manual Pipeline was successful
ci/woodpecker/deployment/builder Pipeline was successful
ci/woodpecker/deployment/release Pipeline was successful
Woodpecker only offers Deploy on a successful pipeline, and the tests can't
pass until the builder image exists, so nothing could start the first build.
2026-11-01 12:16:01 -06:00
a9ed6456da CI: build and publish every package from Woodpecker
Pushes to main run the tests. The Deploy button publishes the Windows, RPM,
deb and F-Droid packages to the same NAS directories a dev machine uses, with
the same keys, then runs update-repo.sh, so public URLs don't change.

Builds run on Debian 12, in an image defined in ci/builder, so the Linux
packages now need glibc 2.36 instead of Fedora 44's 2.43.
2026-11-01 11:54:49 -06:00
c7572ae7b0 Buy cards: store links, bulk buying from decks, and custom stores
The card preview gets Buy buttons that search TCGplayer, Card Kingdom and Star City Games by name. The deck bulk bar gets a Buy… action that sends the copies the selected cards are missing to TCGplayer or Card Kingdom as one pre-filled list.

The Info tab gets a Stores panel for adding your own shops, kept per device. An address is enough for TCGplayer Pro and Shopify storefronts, and any other shop can use a search link with {card} in it. Custom stores join the preview's Buy buttons and the bulk Buy… dialog, which opens a search per card because none of the shops tried can take a list from a link. On Android those open one tap at a time: once the browser is in front, Android blocks further links from the app as background activity starts.

On wide screens the Info tab's library stats move to the top of the left column.

Closes #32
2026-11-01 10:25:14 -06:00
27bc5c9844 Libraries: refresh the list after any sync that changes it
Only syncs started from the UI updated the list. One started in the backend, such as the sync after creating a library, left it stale: the new library kept saying it wasn't on the server yet, and Main stayed undeletable until the app reloaded. A sync that changes the libraries now emits an event the app refreshes on.
2026-10-30 20:56:15 -05:00
b61be5065b Library: remove selected cards from the library
The bulk bar gets a Remove from library button that removes every copy of the selected cards. Decks keep them. Undo puts back the counts, locations and printing groups.
2026-10-30 20:47:08 -05:00
58ff41a4c3 Libraries: let the main library be deleted from a device
Main can go once another library is synced to take over. The server keeps its default library, so this only removes the device's copy, and only Main's own files: its folder is the app data folder. A deleted Main stays deleted across launches, and a device always keeps at least one library, which is now checked before anything is deleted on the server.
2026-10-30 20:47:08 -05:00
349c1957f1 Log to a file, and explain a failed card data swap
A Windows release build has no console, so mlog! lines and panics went nowhere. They now also go to mtgc.log in the app data folder, which the Info tab's Show log file button reveals, and each launch logs the version. A card data update logs each stage, and a failed swap logs every attempt plus what's blocking it: both files' details, whether the folder refuses changes in general, and on Windows whether the files can be opened exclusively and which processes the Restart Manager says hold them.
2026-10-30 20:26:13 -05:00
c527e5d6f7 Card data update: release reference.db before replacing it
Windows refuses to replace a file SQLite has open, so every update after the first failed at the final swap with "Access is denied". Building and installing are now separate: the app stays usable during the build, then closes the catalog, swaps the file and reopens under the state lock. The swap retries briefly in case a virus scanner is holding the new file, and a failed one puts the previous data back in service and removes the temp file. Importing a card database shares the same swap.
2026-10-30 20:15:34 -05:00
70c89a5644 Deck builder: tag cards within a deck
Cards in a deck can carry free-form tags ("to remove", "ramp", …), set
from the card preview or, for several cards at once, from the bulk bar's
Tag… dialog. Tags belong to the card in that deck: they survive
maybeboard moves and quantity changes, go when the card leaves the deck,
and are copied when the deck is duplicated.

The list view gets a Tags column whose chips filter to their tag, tiles
show tags as footer badges, and the advanced filter popup gets a Tags
field (any / all, or untagged only). Select all now selects only the
cards shown, so a filtered view can be acted on as a whole.

Tags sync, which needs library document format 5; libraries without tags
keep writing their old version. Deploy the sync server before releasing
apps that write tags.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:42:40 +00:00
ef4a0d740d Android: write and read picked files through content URIs
On Android the file dialogs return content:// URIs, not paths. Exports
wrote them with std::fs, which failed after the system had already
created the document, leaving a 0 B file; deck, library-card and library
backup exports now write through tauri-plugin-fs, and a failed deck
export shows an error instead of failing silently.

Library-file and card-database imports read the pick through the fs
plugin too. A library file is told apart by its SQLite header rather
than its extension, and SQLite opens a local copy. The library-import
picker drops its filters on Android, where they hid .db and backup files.

Deck import suggests the file's display name rather than the URI's
opaque id, and the backup's "Saved to" message shows the file name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:48:36 -05:00
eeb426ab8f Deck builder: group cards by role
A Role option in the deck builder's Group by sorts cards into Ramp, Card
draw, Removal, Board wipe, Counterspell and Tutor, then the synergy
archetypes Suggest Cards already detects (Mill, Tokens, …), then Lands
and No role. A card with several roles shows in each group.

Roles come from the new roles module, which reads each deck card's
oracle text as the deck loads, so no re-ingest is needed. Reminder text
is ignored, and exiling or bouncing your own things isn't removal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:03:50 -05:00
9230968b19 Deck list: keep tile badges from overflowing
The maybeboard count moves to the footer beside "% owned", and the status
/ format / size row wraps instead of squeezing, with the size kept at the
right edge. The footer's left side truncates rather than wrapping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:03:45 -05:00
8a69ca9f73 Deck builder: cost tiles for unowned cards and cards in other decks
Idea and Building decks get two more Deck analysis tiles: what the copies
you don't own would cost, and what the owned copies other building/built
decks are holding are worth. Both count copies, not cards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:03:45 -05:00
04dc4f6e7b Card filter: show only Universes Beyond cards
The Universes Beyond toggle now cycles off → hide → only, backed by a new
only_universes_beyond library filter. Choosing "only" counts as a real
query, so Card Search runs it on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:03:45 -05:00
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