Skip to content

Releases

v1.9.0

Choose a tag to compare

@thenotoriousJeremy thenotoriousJeremy released this 10 Nov 02:03

Highlights

  • Prices now come from Bindarr's own TCGCSV mirror (#102). TCGCSV blocked Bindarr after every install synced from it separately. One daily job in this repo now syncs from TCGCSV and publishes to the tcgcsv-mirror branch, and 1.9 reads from there by default. It covers Pokémon (3, 85), Riftbound (89), and Lorcana/Magic sealed product (71, 1). Thanks @shadow431 for working this out with TCGCSV.
  • Run Bindarr on a phone with no server. The mobile app's sign-in offers This device: your collection stays in SQLite on the phone, with on-device scanning for Magic and Pokémon after a one-time download. See MOBILE.md.
  • Sealed product: inventory and value tracking, recipes shared across the server, and sharing with the other accounts on your server, where the first person to open a unit claims it. There's also a gallery view. Thanks @shadow431 (#168).
  • Collection now holds Cards, Browse, Lists and Sealed. You can choose which tabs the navigation bar shows, and add any card to your Wishlist with one tap.
  • "Duplicates Only" filter in the collection (#164).
  • Full backup and restore of an account, which also moves a collection between servers.
  • Custom binder artwork with printable inserts (#104), a box map, and an "All cards" view in Storage.
  • Instance metrics, off by default: an Admin panel and an optional Prometheus endpoint, with an optional bearer token. Thanks @shadow431 (#166).

Fixed

  • A member's backup restore can no longer plant card data that other accounts see.
  • Exports work in the mobile apps (they open the share sheet).
  • Mythic and top-rarity Lorcana cards no longer show as common.
  • iOS release builds archive again (#167).

Upgrade notes

  • If you set TCGCSV_BASE_URL=https://tcgcsv.com, remove it. Unset, it reads the mirror. Pointing it at your own cache still works.
  • On an older release and seeing TCGCSV 403s? Upgrade, or set TCGCSV_BASE_URL=https://raw.githubusercontent.com/thenotoriousJeremy/bindarr/tcgcsv-mirror.
  • The database migrates on start (sealed sharing and claiming columns; recipes become instance-wide and duplicates are consolidated). A pre-migration snapshot is taken automatically.
  • Sealed recipes: only the creator or an admin can edit or delete one.

Full details: CHANGELOG.md

Full changelog: v1.8.8...v1.9.0

v1.8.8

Choose a tag to compare

@thenotoriousJeremy thenotoriousJeremy released this 26 Oct 18:59

Added

  • Cardmarket (EUR) prices for Magic. Admin → Instance Settings → "Magic price source" picks TCGplayer (USD, default) or Cardmarket (EUR), with no conversion and no mixed-currency rows. Switching re-prices owned cards right away, and price history records currency so charts don't show a fake drop. Thanks @Knartzpirat (#93, #96).
  • Binder pages match physical pages. 12-pocket pages lay out 4 × 3, and binders can set their own columns per page. Thanks @CPBPILOT (#90, #99).
  • Camera lens picker in the scanner, with macro / ultra-wide lenses avoided by default. Thanks @Gamerayers (#92, #100).
  • "Color" sort in the collection and shared-collection views (#88, #98).

Fixed

  • Single sign-on works in the Android and iOS apps: the login returns to the app via bindarr://oidc with a single-use code. No identity-provider change needed. Thanks @mconway (#94, #95).
  • Sort by type sorts Magic by card type, not color. Thanks @bonogo123 (#88, #98).
  • All tabs stay on screen with large system fonts, on the phone bottom bar and the landscape top bar. Thanks @Gamerayers (#92, #100).
  • The deck builder no longer creeps sideways as copies are added. Thanks @CojakSilverBack (#89, #97).

Upgrade notes

  • No action required for the server; the database migrates on start (new locations.page_columns, app_settings.mtg_price_source, price_history.currency).
  • Update the mobile app to get SSO sign-in, the lens picker and the tab bar fix.
  • Every 12-pocket binder page now shows 4 × 3. If you set 13 pockets per page as a workaround, you can set it back to 12.

Full changelog: v1.8.7...v1.8.8

v1.8.7

Choose a tag to compare

@thenotoriousJeremy thenotoriousJeremy released this 11 Oct 14:43

Added

  • Change a card's printing after saving. The card inspector has a "Change printing" action listing the other printings of the same card, so a copy filed under the wrong set can be corrected in place. Location, slot, condition, language, notes and purchase price are kept. Thanks @mconway (#79, #83).
  • Fixed slots by collector number for binders. Pin each card of a set to its own pocket (card #23 in slot 23, gaps left for missing cards), with a start slot per set so two sets can share a binder and an optional maximum number for special treatments. Off by default. Thanks @mheaysman (#74, #86).
  • Bulk "Set language…" in the collection view, next to "Set printing…" (#73, #84).

Fixed

  • Scanner works in the mobile app again, and is faster everywhere. The app's detector never loaded the model from your server and silently fell back to a slow contour detector. The model is now fetched once and cached on the device, the live loop does far less work per frame, and the fallback chain (wasm → contour → manual capture) is named in the debug readout. Non-English scan catalogs load at startup. Thanks @Doxlike (#80, #87).
  • CSV/JSON imports look cards up instead of saving blank "Imported Set" placeholders. Rows are matched by card id or by set + collector number (Pokémon, Magic in batches through Scryfall, Lorcana), common column names are recognized, and unmatched rows are listed in the import result. Thanks @gauntfield (#75, #85).
  • Non-English Magic cards are no longer valued at $0: they fall back to the English printing's price. Switching a card back to English restores the English printing. Thanks @Trejvic (#73, #84).
  • OIDC errors name the real cause instead of a bare "fetch failed", with a hint for certificate problems. Thanks @morgan-pup (#77, #82).
  • The Android app trusts user-installed CA certificates, fixing "Trust Anchor for certification path not found" with private CAs. Thanks @morgan-pup (#78, #81).
  • The legacy pokemontcg.io price-sweep log line no longer implies a paid key is needed; Pokémon prices come from TCGCSV (#75).

Upgrade notes

  • No action required for the server. Update the mobile app as well to get the scanner and CA fixes.
  • OIDC behind a private CA: set NODE_EXTRA_CA_CERTS in the container environment (see README "SSO with a private CA").
  • Cards imported earlier as "Imported Set" placeholders are not repaired automatically: delete them and re-import the file.

Full changelog: v1.8.6...v1.8.7

v1.8.6

Choose a tag to compare

@thenotoriousJeremy thenotoriousJeremy released this 30 Sep 04:58

Added

  • ManaBox imports. Settings → Import Backup accepts a ManaBox .txt export, resolving each card by its set and collector number and keeping foil markers. Deck Builder can create a deck from a ManaBox decklist, and Storage can build a Box from one, filing matching cards you already own from Unsorted into it. Thanks @batman-2099 (#65, #66).
  • Image-list view for storage containers, with card images scalable from 60% to 250%. Thanks @batman-2099 (#67).
  • Edit a deck's properties (name, description, format, category, accent color, target size) after creating it. Thanks @batman-2099 (#68).
  • Unavailable deck cards are flagged, naming the checked-out deck that holds the missing copies. Thanks @batman-2099 (#69).
  • Deck cards show where they are stored and can be sorted by location, and the collection can be filtered to cards not in a checked-out deck. Thanks @batman-2099 (#70).
  • Camera zoom in the scanner. Scan settings gain a zoom slider when the camera reports zoom support (e.g. Android Chrome). It is hardware zoom, so the guide-box crop gets more real pixels, and the level is remembered for the next session. Thanks @loginator (#61, #62).
  • Insert and reorder binder pages in place, and move a page to another binder. The destination is checked against that binder's filing rules first, and a move that doesn't fit is rejected with an error naming the cards. Pages cannot be moved to or from a locked binder. Thanks @shadow431 (#63).

Fixed

  • One collection row per copy everywhere. Rapid Add and imports (backups, CSVs, ManaBox) wrote a quantity onto a single row, so an "x5" row could be dropped into a four-slot box and could not be lent to a deck one copy at a time. They now add one row per copy, and startup splits any stacked row an earlier build left behind, keeping the copies in the original's slot. Thanks @shadow431 (#64, #72).

Upgrade notes

No action required. On first start, any stacked collection rows are split into single copies automatically.

Full changelog: v1.8.5...v1.8.6

v1.8.5

Choose a tag to compare

@thenotoriousJeremy thenotoriousJeremy released this 11 Sep 20:38

Added

  • OIDC / SSO login. Bindarr can sign users in through an external identity provider (Authelia, Authentik, Keycloak and other OIDC providers), linking an IdP identity to a Bindarr account by its sub claim. Both client_secret_post and client_secret_basic token-endpoint auth are selectable, so providers that accept only one of the two work without patching. Thanks @JGHCode (#43, #45, #46).
  • Optional pokemontcgapi.com Pokémon provider. Admins can select it after configuring POKEMONTCGAPI_KEY on the server. Adds English, Japanese and Simplified Chinese set/card browsing, search, artwork and scan-catalog caching, with Cardmarket EUR prices where available and TCGplayer USD fallback. Uses cursor pagination, persistent ETag caching and batched automatic refreshes for owned/decked cards, on the interval set under Admin → Instance Settings → Refresh prices and only for cards whose stored price has aged out. Listings are fetched without prices (one credit per 250-card page); a card is priced when it enters the collection and in that refresh, so search results show a price only for cards you own. Missing prices stay absent. Existing defaults, collection IDs and other providers' language support are preserved; TCGCSV does not overwrite the new provider's quotes. Thanks @Riccskywalker (#51, #52).
  • Configurable automatic price-refresh interval. Admin → Instance Settings takes a refresh interval in whole days from 0 (off) to 30. Daily stays the default, which is what every install did before the setting existed; a longer interval matters for the metered pokemontcgapi.com provider, which charges credits per card refreshed (#59).
  • Startup warning for the pokemontcg.io sunset. Installs still configured for pokemontcg.io get one boot-time notice naming the date, so the deprecation reaches admins who have not opened Settings in a while. The same notice is in the README, .env.example and the Admin provider hint (#54, #57).

Fixed

  • OIDC ID tokens are validated, not just decoded. The callback now verifies the token's signature and claims before trusting its subject (#56).
  • OIDC no longer links an IdP identity to a local account by username. Matching on a username alone let an identity provider claim an existing local account that happened to share a name; linking is opt-in and keyed on sub (#55).
  • Lorcana prices never refreshed automatically. lorcana was missing from the sweep-bookkeeping map, so its freshness gate answered "not due" forever. The daily timer's force flag hid it, and that flag is gone now (#59).

Performance

  • The Pokémon price sweep only refreshes cards that are actually stale, instead of re-pricing an entire collection on every run (#58).
  • Admin → Catalogs stopped scanning card_cache once per set, which made the page's cost grow with the number of sets (#53).

Upgrade notes

No action required. New and existing installs keep their configured Pokémon provider; the new provider is opt-in and needs both POKEMONTCGAPI_KEY on the server and an explicit selection in Admin → Instance Settings.

Full changelog: v1.8.4...v1.8.5

v1.8.4

Choose a tag to compare

@thenotoriousJeremy thenotoriousJeremy released this 22 Aug 21:40

Fixed

  • Card Inspector crash on card open. Fixed a \TypeError\ where opening a card in the inspector could attempt to evaluate properties on a
    ull\ active card reference when the modal mounted prior to card selection (#47, #48).

v1.8.3

Choose a tag to compare

@thenotoriousJeremy thenotoriousJeremy released this 22 Aug 02:39

Added

  • Disney Lorcana TCG support. Full integration for Disney Lorcana cards, sets, variants, and daily TCGplayer pricing via the Lorcast API. Includes dedicated Lorcana deck builder formats (Core Constructed, Casual, Draft), ink distribution analytics (Amber, Amethyst, Emerald, Ruby, Sapphire, Steel), card type breakdowns, and CollectorVision catalog embedding support (#41, #42).
  • Dedicated TCG themes. Custom theme palette options tailored for Pokémon, Magic: The Gathering, and Disney Lorcana styling across the application (#44).
  • Comprehensive Multilingual Localization & Translation Dictionaries.
    • Full official 1025-species Pokémon dictionary supporting translations across Japanese, French, German, Spanish, Italian, Korean, and Chinese with prefix/suffix parsing.
    • Disney Lorcana character and subtitle translation dictionary across 6 printed languages.
    • Game-scoped language filtering ensuring only printed languages appear for each game (e.g. restricting Lorcana to EN, FR, DE, IT, ZH, JA).
    • Dynamic /api/cards/:id/printing\ endpoint enabling instant language switching with localized titles, set codes, numbers, and art across Search, Scanner, and Card Inspector.
  • Scanner & Candidate Match Enhancements. Visual candidate sorting by similarity, full-resolution camera detection, zero-allocation buffers, and mobile card detection optimizations (#44).

Fixed

  • Card Inspector language selection mutation. Fixed state updates so changing languages in the inspector immediately updates the displayed card title, set, collector number, and artwork.
  • Bidirectional foreign search query translation. Fixed searching non-English Pokémon in English or English names in Japanese search.

v1.8.2

Choose a tag to compare

@thenotoriousJeremy thenotoriousJeremy released this 20 Aug 22:52

Added

  • Collection filters take more than one value. Set, Supertype, Type, Rarity, Condition,
    Printing, Grading, Mana Value, Language and Location are checklists now, so "Rare or
    Double rare" or three particular sets is one view instead of three passes. Game, Sort and
    the price range stay single-value. Thanks
    @JGHCode (#38).
  • Duplicates can share a binder pocket. A container's settings carry a new "Stack
    duplicates in one pocket" option: with it on, copies of the same printing of the same card
    in the same language sit in one pocket with a ×N badge, a page's occupancy counts slots
    used rather than cards held, auto-filing sends a duplicate to its twin's slot, and dropping
    a copy onto its twin joins that pocket instead of swapping the two. Off by default, and off
    it behaves exactly as before (#40).
  • A single container can be shared as itself. A share link with ?container=<id> renders
    that binder or box the way its owner sees it — pockets and page spreads, or the box
    coverflow — rather than as a flat card list. The links sit in Settings beside the
    collection, trade and wishlist ones, and appear while "share locations" is on, since that
    is the setting the view depends on (#40).

Fixed

  • A binder opened onto the wrong pages. Pages were paired 1-2, 3-4, so page 1 faced page
    2 — which no binder does: page 1 is a single leaf against the inside cover, its back is
    page 2, and the pairs run 2-3, 4-5. Spreads follow that now, so a binder with an even page
    count also ends on a page alone on the left
    (#40).
  • The container settings modal showed a raw placeholder. Its "cards currently stored" row
    puts the count in the value beside the label, but the label it used was a whole sentence
    with a {count} nothing ever filled in
    (#40).
  • The new filter popover ignored the theme and the keyboard. It painted itself from a CSS
    variable that does not exist, so it stayed dark in the light and LCARS themes, its summary
    and clear button were untranslated English, and its checkboxes were hidden inputs no
    keyboard or screen reader could reach
    (#40).

v1.8.1

Choose a tag to compare

@thenotoriousJeremy thenotoriousJeremy released this 19 Aug 22:56

Fixed

  • A row or page rename showed up nowhere. The name saved to compartments.label
    correctly, but every place that displays one reads the computed display_label, and
    compartmentLabel() always built "Row N" / "Page N" from the index without ever
    checking for a custom label — so the saved name was discarded at display time in the
    row selector, the row header and the deck-checkout locator alike. Thanks
    @JGHCode (#37).
  • A second copy of a card could not be added to a deck from Browse Collection.
    /api/collection returns one row per physical entry while /api/search groups by
    card_id, and the browse path fed the ungrouped rows straight into the picker: every
    row for a card was marked "added" as soon as any one copy was, which made a second
    basic land or Energy unaddable. Browse results are grouped client-side now, summing
    quantities into one owned_qty per card, so both paths hand the picker the same
    shape. Thanks @JGHCode (#37).
  • Card art left a gap under the coloured border. The image rendered inline, which
    reserves descender space below the baseline. Thanks
    @JGHCode (#37).
  • Catalog builds failed on Scryfall art. Its image CDN rejects a request with no
    User-Agent — a 400, which reads like a bad URL rather than a missing header. Thanks
    @JGHCode (#37).

v1.8.0

Choose a tag to compare

@thenotoriousJeremy thenotoriousJeremy released this 19 Aug 21:33

Scanning is a different pipeline. The perceptual-hash + bag-of-visual-words
recall stage and the ORB verify stage are gone, replaced by two small ONNX models:
cornelius finds the card's corners, milo embeds the dewarped card as a 128-d
unit vector, and a brute-force cosine sweep over a prebuilt catalog names it. On
the same 100-card noisy MTG sample the old stack scored 78.0% exact printing /
88.0% right card in 1187 ms; this scores 76.0% / 90.0% in 310 ms — and it replaces
~2.6 GB of per-set indexes and whole-game rollups with two model files and one
catalog per game and language, at roughly 5 MB per 10k cards. There is no index
build in the scan path at all, so scanning a set needs no "preparing set" wait.

Both models are AGPL-3.0 (milo,
cornelius) while Bindarr is MIT, so
shipping them enabled is a licensing decision and not only a technical one.

A card in another language is a card, not an English card with a label. The
language of a copy now decides which printing it references, so it carries that
printing's name, artwork and price rather than the English row's; the name shows as
the card is printed everywhere it appears, while the English name stays searchable
alongside it. Prices for those printings improved in the same pass — Cardmarket
quotes are read where TCGplayer has no listing, the currency shown is the one the
marketplace quoted, and a price standing in from the English printing says so.

Added

  • TCGdex is the Pokémon provider on a new install, and choosing is now possible.
    app_settings.pokemon_provider has existed since 1.7.2 with no way to set it, and
    only half of the app read it — the card lookups followed the setting while the set
    catalogue always came from pokemontcg.io, so an install switched by hand browsed a
    set list numbered by a provider none of its cards came from. The setting now drives
    the set sync too (218 English sets with release dates and series, against
    pokemontcg.io's 174), and there is a selector in Admin → Instance Settings that
    re-syncs the set list and rebuilds the TCGplayer product map behind it. Measured
    per card lookup: 57-206 ms via TCGdex against 971-1963 ms via pokemontcg.io, which
    also answers 5xx often enough to need a retry policy. TCGdex needs no API key and
    is the only one of the two that carries non-English cards.

    Upgrades keep pokemontcg.io. The two providers number the same sets
    differently (sv1/sv01, pgo/swsh10.5, me1/me01), and every cached card, scan catalog
    and collection row was built against one of those numberings — so only a database
    with no Pokémon sets and no Pokémon cards starts on TCGdex. Switching an existing
    install is an explicit admin action, and the set rows its cards still reference are
    kept even when the new provider does not list them.

  • First run creates its own owner account. A new install no longer generates an
    admin password and prints it to a log nobody reads (and few people then change).
    The users table starts empty, the login screen turns into a set-the-owner-password
    form, and POST /api/auth/bootstrap creates the account with admin rights, refusing
    the moment any account exists. DEFAULT_ADMIN_PASSWORD still seeds it up front for
    scripted deploys. Either way the account is named admin — the name is fixed, not
    the owner's to pick, so it is the same one whichever way the install started and the
    one the orphan-row adoption looks up. That adoption now runs from the bootstrap route
    too, so a database carrying pre-multi-user rows (user_id IS NULL) does not show an
    empty collection to an owner account created through the UI. Existing installs are
    untouched: they already have accounts.

  • The setup wizard covers the whole app, not just scanning. Six steps: language,
    which games you collect, the scanner's models and catalog, the optional provider
    keys, your binders and boxes, then a one-page tour of what each tab is for. The
    language comes first and is translated like the rest of the app, so the steps that
    explain a decision arrive in the language the admin reads; it is the same setting as
    Settings → Language.
    Completion is stored on the server (app_settings.setup_complete) rather than in
    localStorage, so closing it halfway resumes at the next login on any device.
    Every install is offered it once, upgrades included — the wizard is where
    catalogs, games and the provider keys are explained, and an install that predates
    it has never seen that tour. "Skip setup" dismisses it permanently.

  • Catalogs replace scan indexes (Admin → Catalogs). A catalog is one game and
    language, and building it does two things: walk every set the provider lists and
    pull its cards into card_cache, then embed each one's artwork. Those were never
    one job before, and the first half was the half nobody ran — card caching used to
    happen only as a side effect of indexing, so Pokémon held 7,118 of 20,460 English
    cards (35%), with 104 of 174 sets holding just the handful their owner happened to
    have. Both phases resume, a stop still writes the partial catalog, and a set with
    no data in the chosen language counts as a gap rather than a failure.

  • Coverage is shown with a denominator, because "built, 3,297 cards" reads as
    complete and is not. English counts against the set catalogue; other languages
    count against the provider's own set list for that language, so a Japanese
    Pokémon catalog reads 3,297 of 16,192.

  • Corner detection runs in the browser, on a worker thread, using the same
    cornelius model the server dewarps with — so the outline on screen is by
    construction the crop that gets matched, and the client uploads one rectified
    448×448 square instead of a JPEG per preview frame (~2.7 MB per minute of pointing
    the camera at a card).

  • scripts/measure-scan-floor.js — measures the scan gate instead of guessing
    it. For each sampled card it searches the catalog twice, once normally and once
    with the card's own row masked out (which is exactly what a card missing from the
    catalog looks like), under two degradation regimes, and prints what every
    candidate threshold would cost in wrong answers accepted and right answers
    rejected.

Fixed

  • Non-English Magic cards were mostly unpriced. Scryfall quotes usd from
    TCGplayer and eur from Cardmarket, and only the first was read — but TCGplayer
    lists English Magic almost completely and non-English barely at all, so whole
    languages sat at 0.00 while a Cardmarket price existed: measured against the real
    cache, 241 of 1,205 Spanish rows priced, 53 of 194 Italian, 11 of 61 Simplified
    Chinese, against 96,090 of 103,656 English. A printing with no USD price now takes
    its EUR one and records price_currency accordingly. Never a mix inside one row —
    a normal price in USD beside a foil price in EUR is a pair that cannot be compared.
    Owned cards pick this up on the next daily sweep.
  • Prices in another currency were shown with a dollar sign. Stored prices are not
    converted (an exchange rate is a live number this app has no source for), so a
    Cardmarket-priced card was quoted at €4.50 and displayed as $4.50. The symbol now
    follows the row's price_currency everywhere a card price is shown, including the
    price-history axis and tooltip. Amounts the owner typed or paid still show in the
    app's default.
  • A German or Korean Pokémon card was priced as if it were English. TCGplayer
    sells Pokémon in two catalogues, English and Japanese, so every other language is
    matched to the English product for the same set and number. That is the closest real
    number and better than 0.00, but it is not this printing's price — a German card
    usually trades below the English one, and a Korean one is often not listed at all.
    Such rows are recorded as their own price source and the card inspector now labels
    the price "TCGplayer (English printing)" instead of presenting it as a quote for the
    card in hand.
  • A card added in another language kept its English name. The copy's language and
    the printing it points at were two separate things: the localized name lives on the
    card_cache row (printed_name), so picking a card from an English search and then
    setting Language to Japanese filed the copy against the ENGLISH printing — English
    name, English art, English price, permanently. Only the camera scan resolved the
    localized printing. Every add now routes through the same swap (search, Quick Add,
    bulk add, rapid add, slab add, scan), for all eleven languages in
    shared/languages.json. A card never printed in the language asked for keeps the
    printing that was picked, which is still what the user owns.
  • Names showed in English outside the collection grid. Deck lists, the deck
    picker and preview, the checkout pull list and its "Missing …" warning, filing
    recommendations, the slab picker, the scanner's toasts and its debug candidates all
    read the English name column directly. They show the printed name now; name
    stays English where logic depends on it, so the four-copy deck rule still counts a
    Japanese and an English copy as the same card and CSV exports are unchanged.
  • A Japanese Pokémon card could only be found by typing Japanese. TCGdex
    publishes one name per language, so a localized row carried the localized name in
    both name and printed_name and nothing English was left to search. A card being
    added now learns its English name from its own English printing when one exists (one
    request, cached), and the TCGdex price sweep backfills rows added before this.
    Display still reads printed_name; search reads both columns.
  • Re-caching a card silently reset columns no provider writes. card_cache was
    written with INSERT OR REPLACE, which deletes the row and inserts a new one — so
    every column ...
Read more