Roadmap

Where TCG Ledger is headed.

A straightforward look at what's live today, what we're finishing up, and what's next.

July 2026

Where it all began

TCG Ledger started as a focused tool for tracking a single collection's real value. It's grown into a full platform since — but the core idea hasn't changed: every valuation stays tied to a card you actually own.

Live now

Track, value, and sell your collection — on the web

Photo-verified cards and sealed product, live marketplace pricing, interactive value history, and sell-ready marketplace exports are all live today at tcgledger.nl.

In progress

TCG Ledger for Android and iOS

The same account, the same collection, now as native mobile apps — so adding a card is as quick as opening your camera. The Android build is functionally complete and in final release prep; iOS is right behind it. We're holding both back to launch together rather than shipping one first.

Android — release prep iOS — in development
Live now

One Piece and Yu-Gi-Oh! are live

Card recognition and live pricing now cover One Piece and Yu-Gi-Oh! alongside Pokémon, tested end to end against real cards. Every collection, valuation, and sale you've already tracked carries over unchanged.

One Piece Yu-Gi-Oh! More to follow
Live now

Subscriptions are live — 10 free AI scans a day

Every account gets 10 free AI-powered card scans a day, with manual search always available once that's used up — never a dead end. Three paid tiers sit above free for anyone who wants more, the top one with unlimited scans, with checkout supporting both card and PayPal payments.

Live now

Disney Lorcana is live

The fourth game on TCG Ledger — photo-verified tracking and live marketplace pricing, the same as Pokémon, One Piece, and Yu-Gi-Oh!. Every collection, valuation, and sale you've already tracked carries over unchanged.

Disney Lorcana
Live now

Japanese-print pricing is live

The first non-English language on TCG Ledger — real market pricing for Japanese-print cards and sealed product, not just an English price with a disclaimer. Japanese is Pokémon-only for now; the other games don't have a reliable Japanese pricing source yet, so the option only appears when a card's game is Pokémon.

Pokémon only
Live now

A full security review — completed and acted on

A thorough security pass across the website, the web app, the backend, and the Android build — real gaps found and closed the same day, not a compliance checkbox. We don't publish the specifics of what a security review finds (that's just good practice), but every surface has now been through this, and it'll happen again as the app grows.

Live now

Recent Graded Sales — real eBay comps, by grade

See what a card has actually sold for at a specific grade before you price it: average, median, and price range pulled from real eBay sold listings, broken out by PSA, CGC, Beckett, and ACE grade with a total sale count and trend direction. Built for vendors and resellers who need an accurate number fast — available on any single card in your collections, inventory, or wants.

Live now

Scan a card straight into My Wants

Point your camera at a card you're hunting for instead of typing its name — the same AI scan that identifies cards you own now works from My Wants too, pre-filling the search so you just tap the right result. It draws from your regular daily scan allowance, the same as adding a card to your collection.

Live now

My Wants now searches One Piece, Yu-Gi-Oh!, and Lorcana too

A real game picker on Add to My Wants — search and price a card from any of the four games this app tracks, not just Pokémon. Scanning a card picks the right game automatically from what the AI actually sees, so there's nothing to set by hand first.

Live now

My Wants: choose raw or graded

A want can now be for a raw copy or a specific graded slab — tap to choose the grading company and grade, and the price shown is a real number for that exact grade, not a generic reference. Change your mind later from a real edit screen, no need to delete and re-add.

Live now

A real desktop experience

TCG Ledger on a wide browser window now looks like a proper desktop app — sidebar navigation, and grids and lists that actually use the space instead of just being a wider version of the phone layout. Phones and tablets keep the exact experience they already have.

Live now

Riftbound is live

The fifth game on TCG Ledger — card recognition, catalog search, and live pricing for Riot's League of Legends TCG, the same as every other game. Every collection, valuation, and sale you've already tracked carries over unchanged.

Riftbound
Advanced roadmap

Under the hood

For the curious — a real look at how TCG Ledger actually got built: the numbers, and a genuine changelog of what shipped and what broke along the way.

~22,800
Lines of code
124
Source files
16
Consecutive build days
52+
Real bugs fixed

Backend foundation & pricing engine

Jul 22–28
  • Express API + Supabase schema built from scratch — auth, collections, cards, price history.
  • Photo-based card identification wired up end to end.
  • A real multi-source pricing fallback chain, each source only spent when the one before it comes up empty.
  • Currency conversion, portfolio value-history reconstruction, and the seller/collector rank tier system.

The Flutter rewrite — "Foil" design system

Jul 27–30
  • Full redesign implemented screen by screen: Collections, Inventory, Account, Card & Collection detail, the capture flow, sell/export flow.
  • Interactive hover-tooltip value charts with standardized 7D / 1M / 3M / 6M timeframes, rolled out across every screen that shows a price history.
  • Fixed: a card's own value chart was showing quantity × price instead of the true per-unit price sitting right above it.
  • Fixed: tapping an Inventory row only ever toggled selection — there was no way to actually open a card from that screen at all.

Social features

Jul 30
  • Friends (mutual, full visibility) and follows (one-directional, public-only) with a proper request/accept flow.
  • Per-collection privacy, plus opt-in Power-Seller public inventory showcases — searchable by username, no follow required.

Chasing down sealed-product pricing

Jul 30–31
  • Root cause 1: Inventory and Collections screens only ever read cached prices — a card that had never been individually opened had no price to read and never would.
  • Root cause 2: for TCGPlayer users, sealed product frequently has real Cardmarket data but zero TCGPlayer coverage — added a cross-marketplace reference-price fallback.
  • Root cause 3: the pricing query was accidentally poisoning its own search text (a duplicated set-era prefix), returning zero results for products that were genuinely in the catalog.

Native Android build

Jul 31
  • Fixed: a transitive dependency pinned a retracted package version that broke every Gradle build outright.
  • Fixed: the release manifest declared zero permissions — INTERNET and CAMERA were only ever present in Flutter's dev-only manifest variants.
  • Verified end-to-end on a real emulator: sign-in against the live backend, native camera capture, tab navigation, graceful offline/export messaging.

This site, and the infrastructure behind it

Jul 31–Aug 1
  • This marketing site, the privacy policy, and this roadmap — built and shipped the same day it was asked for.
  • Custom domain, DNS moved to Cloudflare, and contact@tcgledger.nl set up as a real send-and-receive address.

Full performance overhaul

Aug 1
  • Fixed: a card that matched no pricing source re-ran three real external API calls on every single page load, forever — added a negative cache so a recent miss is remembered instead of retried endlessly.
  • Fixed: the Collections list looked up each card's price with a separate sequential database round-trip in a loop — replaced with one batched query per collection.
  • Fixed: three endpoints fetched the exact same price row twice — once to refresh it, then again immediately to read it back.
  • Root cause: the backend was running on a different continent than the database. Every single query was paying a transatlantic round-trip. Migrated regions to close that gap.
  • Net result: every measured endpoint landed 4–13× faster, verified with real before/after timing, not estimates.

Verifying and shipping One Piece & Yu-Gi-Oh!

Aug 1
  • Tested end to end with real, expensive cards from both games, in both Inventory and a Collection, before claiming anything was live.
  • Fixed: our Pokemon-only pricing sources have no concept of other games — one of them silently matched a Yu-Gi-Oh! card's name to an unrelated Pokemon listing and returned a wrong price. Now skipped entirely for anything that isn't Pokemon.
  • Fixed: nothing actually told the app which game a newly-scanned card belonged to, so every real card would have hit that same bug regardless of the fix above. Card recognition now identifies the game directly from the photo.
  • Re-verified against four real cards across both games and both screens after each fix — every price came back correct and consistent.

Subscription tiers & scan paywall

Aug 1
  • Measured real AI cost per scan directly against the live API before pricing anything — cheap enough that the free tier could stay genuinely generous rather than deliberately stingy.
  • 10 free AI scans a day, three paid tiers above it (the top one uncapped), and manual search always available the moment a limit is hit — never a dead end.
  • Hosted checkout supporting both card and PayPal payments in one integration, with a real webhook pipeline: idempotent against duplicate/out-of-order delivery, and self-healing if a payment ever lapses so a missed event can't silently strand someone on a paid tier forever.
  • Fixed a pre-existing bug surfaced while building this: any failed automatic card scan used to dead-end the whole "add card" flow with no way to continue — it now always falls back to manual search instead.
  • Verified end to end against real Stripe test-mode checkouts and the live deployed backend, not just unit tests.

Android release prep & native deep links

Aug 1–2
  • Generated the app's real release signing key — every future update has to be signed with the same one, so this only happens once, carefully.
  • Native "forgot password" now works from inside the app itself (not just a browser), verified with a real deep link against the real deployed backend, not just reasoned about.
  • Fixed: a crash that would have broken "Forgot password" entirely on mobile — one line of code assumed a browser environment that doesn't exist outside the web build, only ever caught because the native flow was actually exercised end to end instead of assumed to work.
  • Re-verified "stay signed in" and "sign out" for real on a native build: force-closing the app mid-session leaves you logged in as expected, and signing out actually clears the saved session rather than just the screen you're looking at.

Closing an app-store compliance gap, and starting iOS

Aug 2
  • Caught a real rejection risk before it could bite: both major app stores require their own in-app billing for a subscription purchased inside the app — a direct link to hosted checkout would very likely have gotten either mobile build rejected on review. Both platforms now show plans read-only in-app and point to the website to actually subscribe, until native in-app billing is built for each.
  • Stood up the project's first real build pipeline for iOS specifically — building and signing an iOS app requires tooling this project doesn't run locally, so it now builds in the cloud instead. Checked the pipeline's configuration against current documentation directly rather than trusting memory, catching a real mistake in it before it could waste a build.
  • Fixed: the app icon carried a transparency layer outside its rounded-mark shape — harmless on Android, but an automatic rejection on the App Store, which refuses any icon upload with transparency at all.

A misleading green "0%" on nearly every card

Aug 2
  • Root cause: the per-card change was being read from a free-text field that no pricing source has ever actually filled in with a real percentage — parsing it silently failed and fell back to a flat 0.0%, which the UI then rendered as "up" on nearly every card, for every user, since the feature first shipped.
  • Fixed: each card's 24h change is now computed from its real price history, reusing the same reconstruction logic already powering the portfolio and collection value charts — and it's hidden entirely on a card too new to have a genuine comparison point, instead of guessing.
  • Verified against the live backend with real price data across Inventory, Card detail, and Collection detail — the three screens affected — before and after the fix.

Shipping Disney Lorcana

Aug 2
  • Checked one of our multi-game pricing sources against its live catalog before assuming anything — confirmed a real, working Lorcana game scope (a same-name search from a different game correctly returned zero results) and a genuine live price on a real card, not a guess or a placeholder.
  • Card recognition now returns Lorcana as its own real game instead of lumping it in with "anything else we don't specifically support."
  • Verified end to end against the live backend: created a real Lorcana card, confirmed a genuine market price came back attached to it, matching condition and language exactly as it does for the other three games.

Japanese-print pricing — and two real bugs it surfaced

Aug 2–3
  • Confirmed one of our pricing sources has a genuine, separate Japanese catalog before building anything on top of it — two real, distinctive prices matched the live source exactly, not an estimate.
  • Fixed: a card-matching bug that could resolve to the wrong print entirely when multiple promos share the same leading card number (e.g. two different promo lines both numbered "227") — an exact full-number match now wins over the loose one wherever it's available.
  • Fixed: a sealed item whose own name already contained its set name produced a duplicated, garbled search query that matched nothing — confirmed with a real product that returned zero results until the query was de-duplicated.
  • Japanese cards now skip straight to the source that actually has real Japanese data, instead of risking a silent wrong-language price from a source that doesn't.
  • Cardmarket does list real Japanese-print copies — we just don't have a reliable way to pull that pricing from it yet. Rather than quietly pass off a substitute price as if it were a genuine Cardmarket one, a Japanese card on a Cardmarket-preferred account says so plainly for now, while still showing the price correctly converted to the account's own currency.
  • Restricted to Pokémon end to end, not just hidden in the UI: the app won't offer Japanese for another game's card, and the backend independently refuses to store that combination even if asked directly — the other games don't have a reliable Japanese pricing source yet.
  • Verified against the live backend across every real case: a Pokémon card allowed, a non-Pokémon card forced back to English, and a card's language self-healing when its game changes away from Pokémon mid-edit.

A full security review, across every surface

Aug 3
  • Went through the website, the web app, the backend, and the Android build looking specifically for real, exploitable gaps — not a checklist pass.
  • Found and fixed several genuine issues along the way: database access rules, file upload handling, response headers across every surface, Android session storage, and a payment safety check all got hardened.
  • We're deliberately not detailing what was actually wrong here — publishing the specifics of a fixed vulnerability is a bad habit, not a transparency win.
  • Every single fix was verified against the real, live, deployed app before and after — including one case where an initial fix attempt was caught breaking a real feature during testing and corrected before it ever reached production.

Blocking slurs, hate speech, and brand impersonation

Aug 3
  • Every public-facing and user-editable text field — usernames, collection names, public inventory showcase names, card details, wishlist entries — now runs through a real evasion-resistant content filter, not a simple word-list.
  • Catches leetspeak, spacing, punctuation, and letter-stretching tricks people use to dodge a naive filter, verified against dozens of real evasion attempts before shipping.
  • A separate, narrower check specifically blocks usernames that impersonate the TCG Ledger brand itself (spacing, case, and leetspeak variations included), without over-blocking a collection someone genuinely names after the app.
  • Verified against the live deployed backend with real accounts: every blocked case actually rejected, every legitimate name still works, and no orphaned data left behind from a rejected attempt.

Recent Graded Sales

Aug 3
  • Checked all three of our real pricing sources directly against their live APIs before designing anything — confirmed none of them expose individual per-sale listings (a specific date, price, and link per sale); all three only return aggregated per-grade stats. Built the feature around the real data that actually exists rather than inventing per-listing rows to match a reference screenshot.
  • Real per-grade breakdown — average, median, min/max range, total sale count, last-sale date, and trend — across PSA, CGC, Beckett, and ACE, sourced from the richest of our three sources for this specific kind of data.
  • Deliberately on-demand only, not a passive background fetch: the data source behind this has a genuinely scarce daily quota, so it's gated behind an explicit tap plus a same-day cache shared across every user looking at the same card.
  • Available from any single card — in a collection, in inventory, or on your wants list — never for sealed product, which isn't gradeable.
  • Verified end to end against the live backend and the live app with a real, high-volume card (460 tracked eBay sales at one grade alone), across all three entry points.

A real user bug-hunt — six real bugs found and fixed

Aug 3
  • Root cause: a graded card's price history could silently ping-pong between two currencies — two different call sites disagreeing on which currency a refresh should write meant each one "corrected" the other's value on every check, which is also what was quietly corrupting 24h change% (and, separately, price alerts) into a flat, misleading 0% or an occasionally wrong swing. Fixed at the source, and hardened the historical comparison itself to stay correct even against old, already-mixed data.
  • Fixed: editing a card's language, grade, or condition didn't refresh its price — the old price for the old details kept showing for up to a day. An edit that actually changes anything price-relevant now forces a genuinely fresh lookup instead.
  • Root cause: a promo card's number search could rank an oversized "Jumbo" print above the correct normal-sized one sharing the same number, silently pricing the wrong physical item. Confirmed and fixed against a real card — went from 2 matched grades to 41.
  • Fixed: a security header was blocking wishlist card images from a legitimate source from ever loading at all.
  • Added a real full-quality photo viewer — card photos were always deliberately shown at reduced quality for fast scrolling, with no way to actually see the original.
  • Reworked Recent Graded Sales' grade picker into two tap-to-choose fields instead of a horizontal scroll row, after real mobile-web use showed the scrolling version was awkward.
  • Investigated building real itemized eBay sold-listing links (exact date/price/link per sale, not just an aggregate) — confirmed this isn't something we can build ourselves right now: eBay retired the relevant public API in 2020, and its intended replacement is currently closed to new applicants industry-wide, reserved for approved large partners. Not pursuing a workaround that skirts eBay's own terms. Staying on the aggregate-stats view for now; revisiting if that access situation changes.
  • Every fix verified against the real, live backend and the real, live app — including the exact real card and account state that first surfaced each bug.

TCG Ledger is now on X and Instagram

Aug 4
  • Real, live accounts — x.com/TCGLedger and instagram.com/tcgledger.
  • Linked from the footer on every page of this site — home, pricing, roadmap, and privacy — as small icon links alongside the existing site navigation.
  • Where real market-data breakdowns, shipped features, and fixes get posted as they happen, alongside this devlog.

A small closed beta has started

Aug 4
  • Signups are capped at 10 accounts for now — enforced at the database level, not just in the UI, so it holds regardless of which client (web, Android, iOS) someone signs up from.
  • Subscriptions are switched off for the same reason: we want real usage data from a small first group before turning payments on, not a guess dressed up as a decision.
  • The landing page shows a real, live count of how many of the 10 spots are taken, sourced from the same place the cap itself is enforced — it can't drift out of sync with reality.
  • Verified end to end against the real, live database: filled a set of disposable test accounts to the cap, confirmed the 11th signup was genuinely rejected, then removed the test accounts again.
  • That test also caught a real bug — Supabase's own auth layer didn't pass our rejection message through to the app, so a real user hitting the cap would've seen a blank, broken-looking error. Fixed before this shipped: the app now checks capacity up front and always shows a clear explanation.

My Wants — real card images, and scan to add

Aug 4
  • Root cause: our own security policy allowed card images to load as plain pictures but silently blocked the app's rendering engine from actually fetching their bytes — a distinction that only matters because of how the web app draws to the screen. It was invisible with your own uploaded photos (those live on our own storage, already allowed) and only broke stock card images pulled from the catalog, which is every single image in My Wants. Confirmed directly against the real image host: fetching it the way the app does failed with our policy in place, and succeeded the moment the fix was applied.
  • Same underlying issue affected the manual card search on the regular add-a-card screen — fixed at the same time.
  • New: scan a card straight into My Wants instead of typing its name — reuses the exact same AI scan as adding an owned card, so it draws from your regular daily scan allowance rather than a separate free pass.
  • The scan doesn't guess blind — it pre-fills and runs a real catalog search, so you're always confirming against a real card and a real photo before it's added, not trusting raw AI text.
  • Verified end to end against the real, live app: confirmed the exact failing request first, confirmed the fix resolves that same request, then confirmed the whole app still loads clean.

A card's price now refreshes every time you open it

Aug 5
  • Root cause: a card's price only ever re-checked once every 24 hours. For most cards that's a non-issue, but a genuinely volatile one — an ultra-cheap bulk card where the "lowest listed" price can legitimately swing several times over in a day just based on which few listings happen to be active — could sit on a real but hours-stale number long enough to look flat-out wrong.
  • Opening a card's detail screen now always re-checks the fast, free pricing sources instead of trusting the 24-hour cache. This still can't spend AI credit or your daily price-check allowance either way — that path was already walled off for this kind of passive check, cache or no cache — so this is strictly free lookups, just more often.
  • Deliberately scoped to the single card you're actually looking at, not your whole collection — opening your full collection list still uses the 24-hour cache, so it won't fire a burst of requests for every card at once.
  • Verified against a real account's real card: two requests seconds apart both produced a genuinely fresh, independently-timestamped price check, not a repeated cached one.

Password managers now recognize login, signup, and reset

Aug 5
  • Root cause: none of the four account screens (login, signup, forgot password, reset password) told the browser what each field actually was — no autofill hints, no shared form grouping, and nothing signaled "this form is done" after a successful submit. Browsers and password managers had nothing to go on, so they never offered to autofill or save credentials.
  • All four now declare proper autofill hints (email, password, new password, first name) and group their fields so a browser understands them as one real login or signup form — and explicitly signal completion after a successful submit, which is what actually triggers the "save this password?" prompt.
  • Also fixed the login screen looking oddly wide compared to the rest of the app: its card had no maximum width, so on desktop web a single full-width button silently stretched the whole thing almost edge to edge. Capped it, and along the way found it was never actually centered on the page either — the stretched button just happened to visually mask that. Both fixed across all four screens.
  • Verified with real screenshots at both a wide desktop width and a narrow mobile width, confirming the fix without regressing the layout that already worked fine on phones.

My Wants was silently Pokémon-only — now genuinely multi-game

Aug 5
  • Root cause: the catalog My Wants searched against is structurally Pokémon-only — its search endpoint is literally scoped to Pokémon's own catalog, with no concept of any other game. Worse than just missing coverage: the pricing lookup for an already-added non-Pokémon want had no guard against this at all, so it could silently cross-match an unrelated Pokémon card and attach that card's price and picture to it. Confirmed live before fixing anything: a Yu-Gi-Oh! want came back priced and pictured as a Pokémon card.
  • Both search and pricing now route through a real multi-game catalog for anything that isn't Pokémon, confirmed live with real One Piece, Yu-Gi-Oh!, and Lorcana results — correct cards, correct sets, correct images, no cross-game mismatches.
  • Along the way, found and fixed a second, unrelated cause of missing card pictures: that catalog's own image host sends no cross-origin permission headers at all, which silently breaks image loading specifically on the web app's rendering engine, no matter what our own security policy allows for it — confirmed directly against a real image request. Fixed by serving those images through our own backend instead of linking directly to theirs.
  • Also investigated a report that My Wants doesn't refresh automatically after adding a card. Reproduced the exact flow live, repeatedly, end to end, including watching the network traffic — it refreshed correctly and instantly every single time. Most likely explanation: a browser tab left open across one of today's several deploys, serving an already-out-of-date copy of the app until it was refreshed. Flagging this openly rather than claiming a fix for a cause we couldn't actually confirm — if it happens again on today's build specifically, that's real signal worth another look.

A real cheap card wrongly showed no price at all

Aug 5
  • Root cause: a safety floor meant to catch a specific data glitch on graded slabs — a real graded slab worth tracking never actually sells for pennies — was also being applied to ordinary ungraded cards, where a genuinely common card can legitimately be worth well under a euro. A real, high-confidence price was being silently thrown out for looking "too cheap to be real."
  • Fixed: ungraded and graded prices now use separate floors, tuned to what's actually plausible for each.
  • Verified against a real want on a real account: the price went from missing entirely to the same number the data source actually reports.

My Wants: raw vs. graded, and a way to actually edit one

Aug 5
  • A want can now be marked graded, with a real company and grade — priced off the same real per-grade sold data used everywhere else in the app, not a generic reference number.
  • A raw want now carries a real condition instead of a hardcoded assumption, and honestly says so when the reference price doesn't match it — none of our pricing sources actually track a price per condition, so this reflects that truthfully instead of pretending to a precision that doesn't exist.
  • Fixed: there was no way to edit a want at all after adding it — a mistake meant deleting and re-adding it from scratch. A real edit screen now covers every field, including switching raw/graded after the fact.
  • Grading company and grade started as free-text fields, which meant a typo silently produced "no price found" with no explanation — replaced with tap-to-choose pickers that can't produce a value nothing recognizes.
  • Verified end to end against the real, live app and real want data throughout.

A graded price silently matching the wrong card

Aug 5
  • Root cause: two genuinely different cards can share a loosely-matching number, and there was no real tiebreak between them when neither was an exact match — it fell back to whichever the data source happened to list first. Confirmed with a real, well-known vintage card: its graded price came back a small fraction of reality because it had silently matched a completely different, far more common modern card that just happened to share a number.
  • Fixed: the card's own recorded set name now breaks the tie, the same technique already used elsewhere in the app for this exact class of problem.
  • Not scoped to one screen — this pricing path is shared by collections, inventory, and wants alike, so the fix applies everywhere a graded price shows up.
  • Verified directly against the real card: confirmed the wrong match, applied the fix, confirmed the correct one — a realistic number in place of one that was off by roughly two orders of magnitude.

A real desktop layout, not a stretched phone screen

Aug 5
  • Every screen was built mobile-first with no upper bound on width, so on a wide browser window everything — cards, rows, buttons — just stretched edge to edge.
  • First attempt capped the page to a fixed, centered width, which fixed the stretching but read as a phone screen floating in the middle of a big monitor — caught the same day and replaced rather than left as-is.
  • Shipped instead: real sidebar navigation on wide windows in place of the bottom tab bar, and grids, lists, and detail screens that actually reflow to use the space — more columns, side-by-side layouts — instead of just being a wider single column.
  • Phones, tablets, and narrow windows are untouched throughout — every change only applies above a real desktop-width breakpoint.
  • Verified at both a real desktop width and a real phone width after every single change, confirming zero difference to the mobile experience the whole way through.

Daily scan usage, visible where your plan already shows

Aug 5
  • The daily AI scan limit already existed and was already enforced — it just wasn't shown anywhere, so there was no way to see how many you'd used or had left for the day.
  • Now shown right next to your plan on the Account tab, as a real "used / limit" count pulled from the same data that already gates scanning.
  • Verified against a real account switched through all four plan tiers, including the uncapped one showing correctly as uncapped rather than "0 / nothing."

A real performance pass, backend and frontend

Aug 6
  • Fixed: a couple of internal queries fetched a user's entire price-history table with no upper bound at all — harmless today, but the kind of thing that quietly gets slower every month as the app ages. Capped, the safe way: kept the newest data in scope rather than the oldest, so nothing about the numbers you actually see changed.
  • Fixed: our exchange-rate cache only ever remembered one currency pair at a time — with real accounts using different display currencies, two people's requests could keep evicting each other's cached rate, so the cache was doing far less than intended under real mixed traffic. Now remembers every pair independently.
  • Fixed: the public homepage's live beta-signup counter re-ran a full account lookup on every single visit, with no cache at all. Added one — the number is accurate to the minute, which is all it ever needed to be.
  • Fixed: one screen loaded its secondary stats one after another instead of at the same time, the same mistake already caught and fixed elsewhere in the app a few days ago — brought in line with the rest of the codebase.
  • Verified every fix against real data before shipping: the query cap reproduces the exact same real portfolio total as before, and the currency cache serves correct rates for two different pairs used back to back.

A real security pass, backend and frontend

Aug 6
  • Went through the whole app again looking specifically for real, exploitable gaps — not a checklist pass, and not limited to what's changed recently.
  • Found and fixed several genuine issues: a payment-redirect URL that wasn't being checked against a real allowlist, a pricing lookup with no caching or throttling of its own (unlike the equivalent owned-card pricing, which already had both), an internal image-relay endpoint with no rate limit at all, and a field being sent to the app that quietly disclosed a piece of server configuration nobody actually needed and, it turned out, nothing even read.
  • Same policy as our last security pass: we're not detailing the specifics of what was actually exploitable here — that's not a transparency win, it's just a bad habit.
  • Every fix verified against real requests before and after, not just reasoned about.

Shipping Riftbound

Aug 6
  • The fifth trading card game on TCG Ledger, following the same path as One Piece, Yu-Gi-Oh!, and Lorcana before it — card recognition, catalog search, and live pricing, wired through the same multi-game pricing source those three already use.
  • Verified against real, live data before shipping, not just added on trust: real catalog searches, a real want created and priced end to end, and a real price returned — then cleaned up, same as every other verification pass in this log.
  • Every existing collection, valuation, and sale carries over unchanged — this is additive, nothing about how the other four games work changed.

My Wants was missing a real pricing fallback owned cards already had

Aug 6
  • Fixed: My Wants only ever tried one pricing source per card — if that source didn't carry a particular print, the want just showed no price at all, even on cards our own backend already knows how to price for cards you actually own.
  • Root cause: owned cards already fall back through two more pricing sources whenever the first one comes up empty; My Wants never had that same fallback wired in, so it gave up right where an owned card would have kept looking. Fixed for both raw and graded wants.
  • Verified against a real card with genuine zero coverage in the primary source — confirmed it now correctly falls through and returns the real price instead of nothing, with no change to any want that was already pricing correctly.

Made it harder to farm free accounts for scan quota

Aug 6
  • Added: new signups are now checked against a real, maintained list of throwaway/disposable email providers — enforced at the database level, so it can't be skipped by going around the app itself.
  • Added: a per-network limit on how many signup attempts can happen in a short window, plus an optional automated-traffic check that can sit in front of account creation.
  • Fixed: found while building the above — one of our existing per-network safety limits had quietly been sharing its counter across every visitor instead of tracking each one separately, because the app wasn't correctly reading which network a request actually came from behind our hosting provider. Fixed; two existing limits now behave the way they were always meant to.
  • Verified against real known throwaway-email domains (correctly rejected) and a real account (correctly allowed) before shipping, plus a real safety-limit boundary test to confirm the per-network counting was actually per-network.

Card-matching bugs in the pricing pipeline

Aug 13
  • Root cause: one pricing source only ever searched the first page of results — a common card can have hundreds of real prints, so the exact one you own could sit just past that page and look identical to "not found," silently falling through to slower, costlier sources instead.
  • Fixed: the same card number gets reused across completely different eras and sets. A card number alone was picking whichever same-numbered print happened to come back first, which could silently attach a wrong print's price instead of correctly reporting that the exact print you own isn't tracked. Now disambiguated by real set overlap, the same technique already used elsewhere in the pricing chain.
  • Fixed: a faster lookup path added during this pass turned out to filter by card number only, regardless of the card's actual name — caught before shipping by testing against a real card and finding it had matched a completely unrelated one that just happened to share a number.
  • Fixed: a sealed-item lookup could match a single booster pack instead of the Elite Trainer Box actually being priced, because the search text it built duplicated the set name into a garbled query.
  • Verified against real cards pulled from a live account, including several worst-case naming patterns (apostrophes, accents, promo-style numbering) — every fix confirmed against the live pricing sources themselves, not assumed from documentation.

A 19× wrong price, caught by re-scanning real cards

Aug 13
  • Root cause: re-ran real card photos back through card recognition and compared the result against known-correct data already on file, instead of just reading the identification logic. A real card came back with its set name shortened down to a raw internal code, and a variant marker on its number silently dropped — both were the only things separating it from a completely different, much rarer print.
  • Fixed: traced that exact miss through the full pricing pipeline and confirmed the impact directly — it would have shown roughly 19× too high a price for a real card. Card recognition is now told explicitly to transcribe names, set names, and card numbers exactly as printed, rather than paraphrasing or shortening them, with real examples for every supported game's own numbering convention.
  • Fixed: a defensive fix on the pricing side too, not just the prompt — a card-number parser was reading the first number it found in a code, which for one game's numbering convention silently picked up a set-code fragment instead of the card's own number. Now reads the correct part regardless of what card recognition outputs, so this can't quietly break again later.
  • Re-verified across every supported game by re-scanning real photos and tracing each result through to a final price — the one real miss found is fixed, with no regressions on the cards that were already correct.

A free, faster pricing source for two more games

Aug 13
  • Found that one of our free, unlimited pricing sources actually covers two more of our five games than it was being used for — confirmed live rather than assumed from docs. Card pricing, sealed-product pricing, and manual search all now use it for those two games as well, the same fast, trend-aware path Pokémon already had, before falling back to a slower metered source.
  • Fixed: found and fixed a real mismatch while wiring this up — one game has two genuinely different real cards whose only difference is a single small marker on the card number, and one worth roughly 12× more than the other. That marker was being silently dropped, which could have shown the far cheaper card's price for the rare one. Fixed by matching the number exactly as printed first, rather than a simplified version of it.
  • Fixed: a smaller issue caught in the same pass — this source reports a literal zero (not "no data") when it has nothing for a specific listing, which was one step away from showing a real card as worth $0.00.
  • Verified live across all five games with no regressions before shipping.
Real numbers from the actual codebase and a real, ongoing build log — not a marketing estimate.

Have a game you'd like to see supported?

We're prioritizing what our collectors actually ask for. Let us know what you're tracking.