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.
This commit is contained in:
2026-10-16 21:06:17 -05:00
parent 1c5e045591
commit 214a535588
23 changed files with 2092 additions and 138 deletions

View File

@@ -1,6 +1,8 @@
# Design: optional per-printing tracking
Status: **not implemented** — design only, for a future session.
Status: **implemented.** The as-built decisions are in
[As built](#as-built) at the end, which takes precedence over the original
design below wherever the two differ.
## Context
@@ -148,3 +150,42 @@ later is a compatible follow-up, not a redesign.
`crates/mtgc-core/src/model.rs`, `src-tauri/src/commands.rs`, `src/api.ts`,
`src/components.tsx`, `src/views/DeckView.tsx`, `src/views/LibraryBrowser.tsx`,
`src/views/CardEntry.tsx`.
## As built
Where the implementation settled the open questions, or departed from the
design above:
- **Migration** is `0006_printings.sql` (0005 had gone to deck uuids). It adds
`library_card_copies.quantity`, a unique index on
`(library_card_id, scryfall_id, finish)`, and `deck_cards.preferred_scryfall_id`.
Everything lives in `crates/mtgc-core/src/printings.rs`.
- **No `remove_copy(copy_id)`.** `set_library_copy(.., quantity)` with
`quantity <= 0` removes a group, mirroring `set_deck_card`. Row ids are local
and are rebuilt on every sync, so they would be a poor handle anyway.
- **Quantity rules (open question 3).**
- *Grow:* setting a group above the unspecified remainder raises
`quantity_owned` to fit.
- *Clamp:* lowering `quantity_owned` below the tracked total trims the
highest-id (most recently added) groups first.
- Lowering a group never lowers `quantity_owned`; those copies become
unspecified.
- All of this is pinned by tests in `printings.rs`.
- **Library thumbnails (open question 1)** always show the oracle default.
Only deck slots show a chosen printing.
- **Decklists (open question 2)** round-trip in this first cut, as
`(SET) NUM`, optionally followed by `*F*` / `*E*`. See `decklist.rs`.
- **UI.** There is no per-row button. A *Printings* section in the
click-preview info tile appears whenever the caller passes a
`PreviewContext`, and it opens a `PrintingPicker`. Both are in
`src/components.tsx`.
- **Sync, which postdates this design.**
- `CardEntry.printings` is keyed `"<scryfall_id>/<finish>"`, and
`DeckCardEntry.printing` holds the chosen printing.
- Cards now merge field by field, and their groups merge per key, so edits
to one card from two devices both survive. A merge result is clamped by
trimming from the end of key order, which comes out the same on every
device.
- A document is written as version 2 only once it holds printing data, so
older builds refuse it rather than dropping the data and syncing the loss.
- **Follow-up:** choosing a printing while entering cards on Add Cards.