Planned work

Backlog

Open ideas, in-progress items, and completed work by category. Status-tagged so you can see what's coming.

Planned features, known limitations, and open questions — grouped by category. Status tells you whether something is just an idea, being actively researched, in progress, or blocked on something external. — view shipped changes →

In progress 4 Researching 0 Blocked 1 Ideas 19

Identification & Matching

Confidence-scored scan chain with fallback

in-progress M

v0.14.0 partial: composite confidence score is computed (see confidence.py) — image similarity 40%, AI agreement 30%, OCR/back-stamp text overlap 20%, AI confidence-note language 10%. Surfaced as a 0-100 score with color-coded label on the scan results page. Still TODO: actually use the low-confidence threshold to trigger a fallback wider search (currently just warns the user). Open question: what to escalate to — Claude Opus deep with extra context? Google search via Gemini grounding?

Cross-validate AI-claimed catalog IDs against catalog images

in-progress M

v0.14.0 partial: Pin&Pop side is now wired — the new-pin scan page computes cosine similarity between user scan and the Pin&Pop catalog's first reference image (via existing DINOv2 embeddings, no extra API calls). Surfaced in the new Match Confidence panel. PinPics side is blocked pending either bookmarklet imports (which the user already controls) or the Playwright path above. Composite confidence also factors in cross-provider agreement on pinandpop_id and OCR-vs-back-stamp text overlap.

Better auto-checklists from set membership

done S

Done (v0.14.42 + v0.14.44). Inline /new-pin Series create now passes scan_id to /api/collections and seeds the new Series with set members from three sources (chosen-AI set_members + Pin&Pop series scrape + PinPics linked_pins), name-keyed deduped across sources. Background image enrichment runs for each seeded pin so checklist thumbnails populate after a refresh. Also: /series/{cid}/refresh-from-url for re-running the same logic on existing Series.

Local catalog database (scrape Pin&Pop / PinPics)

done L

Done (v0.14.48). New CatalogPin table + catalog_ingest.py module. Pre-warm a Pin&Pop series URL on the /catalog page and the app fetches every pin, downloads the front image to catalog_uploads/, and computes a DINOv2 embedding. Idempotent re-runs (existing rows updated, no dupes). Used for local visual search on /new-pin via catalog_search.py. Still TODO: PinPics-side pre-warm (PinPics doesn't expose a clean series-page structure so would need a separate scraper); incremental refresh strategy for series that gain new pins over time.

Headless-browser scraping for JS-rendered catalog pages

in-progress M

v0.14.3: Playwright wiring shipped as opt-in module (headless_scrape.py) with lazy import. New catalog_sites.fetch_pinpics() uses it as a fallback when the cheap requests-based path hits a Cloudflare challenge. Setup: pip install playwright && playwright install chromium (one-time, ~200MB). Browser launched once per process, reused across calls (~300-500ms warm). Known risk: Cloudflare Turnstile / managed challenges may still block Chromium without a stealth plugin. The bookmarklet route remains the most reliable path for PinPics-authenticated data. Still TODO: integrate playwright-stealth if Turnstile becomes a recurring problem in practice.

Higher-resolution pin photos & guided capture

idea S

Encourage / require higher-resolution uploads — warn when incoming images are below some minimum (e.g. 1024px on the long side). On mobile, the camera capture flow could offer a guided framing overlay so users center the pin and avoid glare. Tied to fake-pin detection: high-res photos are needed for the edge/surface analysis in pin_authenticity.py to deliver good signal.

Barcode / pin-code scanning at entry

done M

Disney pins often have a SKU barcode on the packaging or a printed pin code on the backstamp. Adding ZXing / QuaggaJS (browser) or pyzbar (server) for barcode extraction would let the user scan a barcode directly for instant catalog lookup — much faster than image-based AI identification when the barcode is visible. Surfaces: home-page Quick Add, /scan, new-pin form, and the ‘Add missing pin’ flow on Series detail. Open question: how much of Disney's pin catalog actually ships with a stable scannable barcode? Some mystery sets have a generic set-level barcode rather than per-pin.

CLIP embeddings on catalog mirror + text→image search

done L

Done in v0.15.78. embeddings_clip.py uses openai/clip-vit-base-patch32 to encode catalog images + text queries into the same 512-dim space. New catalog_pins.embedding_clip column + /catalog/backfill-clip-embeddings route. /catalog/text-search?q=... ranks catalog rows by semantic similarity to the query text. ~200ms for a top-K scan over ~100k rows via vectorized numpy.

Cross-source catalog ID cross-reference

done M

Done in v0.15.79. _cross_reference_catalog_ids() in main.py fills blank catalog IDs on /new-pin when the local mirror has a sibling row from another source matching by canonical (name + year-or-series). Surfaced as a blue “Cross-referenced” banner above the IDs row with confidence (exact vs fuzzy).

eBay UPC search on barcode-no-match

idea S

v0.15.82: when a barcode scan finds no local-catalog match, the user gets web-search buttons (Google, eBay active, eBay sold, Disney Pins Blog, Barcode Lookup) but no inline matches. Worth trying: fire an eBay UPC-search query inline and surface the top 2-3 listings with thumbnails so the user can one-click “Use this as a scan hint”. Caveats: Disney pin packaging UPCs are rarely indexed by sellers in eBay listings, so hit-rate would be low. PinPics + Pin&Pop do NOT support UPC lookup (those sites organize by pin name/year/series, not retailer barcodes), so this is eBay-only. Measure hit-rate on a small batch (~20 scans) before investing in UI.

Fake Detection

Fake pin detection (big feature)

in-progress XL

v0.14.46 + v0.14.48 partial. Tiers 1 + 2 + community DB shipped:
· Tier 1 (local CV): pin_authenticity.analyze_edge_quality() (Pillow FIND_EDGES + numpy variance) + compare_colors_to_reference() (LAB ΔE).
· Tier 2 (AI visual review): compare_pins_via_ai() sends user photo + catalog ref to Gemini Flash with structured scrapper-detection prompt.
· Community-flagged DB: scrapper_db.py + KnownScrapper table + admin UI for configurable forum/Reddit sources. Banner on visual-auth panel when a pin's catalog ID is in the known list.
Still backlogged (Tier 3): local trained CNN on labeled (real, fake) pairs. Needs a labeling habit first — could bootstrap by tagging pins as “confirmed_authentic” (bought from Disney direct) over time. Data shape in compute_authenticity_score() already accommodates a future ML finding without schema changes. Drew's ask: handle different angles, lighting variations, shiny finishes, and back-of-pin views.

Back-stamp validation

done M

Done (v0.14.46). pin_authenticity.extract_backstamp_text() uses Gemini Flash vision to read the backstamp text, returning structured JSON: text, © year, manufacturer code, pin code, trademark_present, depth_quality, confidence, notes. Renders in the visual-authenticity panel under “Backstamp OCR”. Far more reliable than EasyOCR for stylized backstamp fonts. Auto-runs when a back-view photo exists on the pin. Still TODO: actively compare the extracted © year + mfr code against the catalog's expected values and flag mismatches explicitly.

pintradingdb.com as alt scrapper / catalog source

done S

Done in v0.15.35 + v0.15.77. v0.15.35 added PTDB as a first-class crawler source alongside PinPics + Pin&Pop — rows land in catalog_pins with source='ptdb' and feed the local mirror enrichment + set-members linking. v0.15.77 promoted Pin.ptdb_id to a primary identifier on the Pin model so PTDB IDs route through the same vote/veto/override machinery as the other two sources. The /catalog-crawler page has a PTDB toggle alongside PinPics + Pin&Pop.

Background-remove pin before visual authenticity analysis

done S

pin_authenticity.analyze_edge_quality() currently assumes the pin is the dominant edge structure in the photo — works on clean backgrounds but a busy carpet / desk / wood-grain shot lights up the edge map and inflates the roughness score, leading to false “concerning” verdicts. Fix: run the user's front photo through rembg (already an optional scan-time dep) before calling the edge + color analyzers. The catalog reference image is usually already background-free, so this makes the comparison apples-to-apples. Same treatment would help the color-extraction step (_dominant_colors currently includes background pixels in its palette). Surfaces a flag if rembg isn't installed so the analysis degrades to “include background” rather than failing.

New Releases & Feeds

Email notifications on new pin drops

idea M

Watch the new-releases feed sources for items matching interest filters and email when matches arrive. Filter primitives: character keywords (e.g. “Pascal”, “Yzma”), series keywords (e.g. “Hidden Mickey 2026”, “Villains”), retailer (BoxLunch, DPB Store, Hot Topic, Disney Parks), price ceiling. Implementation: store filters on a new AlertFilter table; nightly cron walks the DPB cache + (new) WDWNT cache, computes the diff against last_seen_at, sends a single digest email per filter via SMTP env vars. UI for managing filters on /settings or a new /alerts page. Defer the email infra decision (SMTP / Resend / SES) — SMTP is the lowest-effort first cut. Pairs with the WDWNT source addition below for broader coverage.

Wishlist & Discovery

Bulk wishlist add (paste multiple Pin&Pop URLs)

idea S

v0.14.64 added /wishlist/add for single-URL paste. Power-user follow-up: paste a list of URLs (or a series page's linked-pins list) and queue all of them for wishlist with one confirmation. Could pair with the Pin&Pop series scrape we already do (fetch_pinandpop_series) to offer “add all 8 pins from this series as wishlist” from a series-detail page. Skip pins already owned/wished.

Inventory Quality

Missing-metadata scanner

idea S

Dedicated view that lists pins missing key fields: no back image, no year, no PinPics or Pin&Pop ID, no front view, etc. Click through to fill in. Useful for bulk-cleaning your collection after you've gathered a backlog of quick scans.

Character dictionary + franchise synonyms for auto-tag

done M

Done in v0.15.77 + v0.15.79. v0.15.77: character_dict.py with ~150 canonical Disney characters + variants, prepended to auto-tag candidates with word-boundary case-insensitive matching. v0.15.79: FRANCHISES table maps each character to its parent franchise(s) so e.g. Stitch → lilo-and-stitch tag also applied.

Follow-up flag on /new-pin + /validation surfacing

done S

Done in v0.15.73 + v0.15.76. v0.15.73 added Pin.followup_flagged + reason + note + timestamp columns and the “🚩 Flag for follow-up” checkbox on /new-pin. v0.15.76 added a Follow-up section to /validation, an amber count badge on the Validation nav link, and a filter chip on /pins.

Pricing & Value

eBay sold-listings (Marketplace Insights API)

blocked S

Sold/completed listings need the Marketplace Insights API which requires production-tier approval. Currently only Browse API (active listings) is available. Active listings are useful for asking price but don't reflect actual sale prices. Blocked on eBay developer approval.

Rarity score per pin

done M

Done. rarity.py ships compute_rarity(pin) which returns a tier + score from edition size, year, set membership, and eBay-listing count. Rendered as a badge on /pins cards, the /new-pin preview card (using a synthesized pin shape before save), and the pin detail page. Registered as a Jinja global so templates can call rarity_for(pin) directly.

Highest-selling pins Top List

done M

New Top List ranking pins by recent sold price across the catalog (not just pins the user already owns). Data sources to combine: (1) eBay sold listings — we already have per-pin fetch_sold_listings() via the existing scraper, would need to bulk-run it across the local CatalogPin mirror and cache the median sold price per pin; (2) Pin&Pop “trade value” if their pages expose it (unclear — would need to inspect); (3) PinPics “Cash” / “Trade” values that users contribute. eBay is the most reliable signal but the slowest to gather (one HTTP request per pin); could run as a background refresh on a per-day schedule, storing median + sample-count + last-fetched-at on the CatalogPin row. Surfaces on /top-lists as “Hottest pins right now (by recent sold price)”.

Platform & Distribution

Native Android app talking to the server

idea L

Phone is where pin photos get taken. Options: (1) PWA — easiest, leverages existing web UI, no app store; (2) React Native — real native UI, more work, can leverage device camera APIs; (3) Kotlin native — most native, most effort. PWA route gets us 80% there with installable home-screen icon, offline shell, and access to camera via the existing getUserMedia flow.

Public server deployment / remote access

idea M

Currently runs on LAN only. For phone access while out shopping for pins, expose via Tailscale or Cloudflare Tunnel — both let you reach your home server from anywhere without opening ports. Open question: do we need authentication added if reachable from the internet? Yes, even via Tailscale, basic password protection is prudent.

Server-side browser for catalog scraping

idea M

Some catalog sites require JS execution to render content. Already partially solved via regex on raw HTML for Pin&Pop, but PinPics fully blocks server-side scraping (CAPTCHA-protected). Browser-in-the-cloud (Playwright with stealth plugin, or hosted browser-as-a-service) is the heavy-handed fix. The bookmarklet / extension flow side-steps this by running in the user's already-authenticated browser — that's the current path.

PWA install + offline shell

done S

Done in v0.15.79. manifest.webmanifest + sw.js served from the root so the SW scope is the whole origin. DisPindable can be added to home screen and runs in standalone mode; service worker caches the shell (CSS, favicon) so cold launch is instant. HTML and API responses pass-through so dynamic content stays fresh. Requires https or localhost (the LAN URL silently skips SW registration).

UX & UI Polish

Mobile testing pass + UI cleanup

idea M

Drew flagged: nav menus, page padding, button sizes, and overall clunkiness on phone viewports. v0.14.40 added the 768px + 480px media-query block in base.html but it was never end-to-end tested across the actual flows (scan / new-pin / series detail / catalog admin). Walk every page on a real phone and capture concrete issues. Also: tap-target sizes on the checklist cards and toggle buttons may be too small.

Phone-camera capture flow: extensive testing

idea M

Distinct from the general mobile UI pass — this is the camera pipeline specifically. Things to validate on an actual phone: (1) getUserMedia + the “use camera” button on /scan and Quick Add, across iOS Safari and Android Chrome; (2) auto-focus / exposure behavior on close-up pin shots (most phones default to face-detection focus which misses pins); (3) multi-image capture flow — can the user take front + back + macro in one session without leaving the page; (4) retake behavior when the first shot is blurry; (5) image-quality preview before upload (the existing _compute_scan_quality heuristic could warn the user before they hit Save); (6) orientation handling — portrait-mode photos sometimes come through rotated thanks to EXIF strip variance.

Test out the new visual-authenticity validation (Tier 1 + Tier 2)

idea S

QA task for the v0.14.46 visual-authenticity panel. Run the “Analyze now” button against a mix of pins: (1) a pin known to be authentic from Disney direct — should score 80+; (2) a pin known to be a scrapper / fake if Drew has one in the collection — should score below 40 and surface red flags; (3) a pin with a busy / non-white background photo — important to see how lighting and background interfere with the Tier 1 edge + color analysis (related: “Background-remove pin before visual authenticity analysis” backlog item); (4) a pin with no catalog reference image — should gracefully skip Tier 2 with a clear “no reference available” note; (5) a pin with a back photo on file — should also surface the backstamp OCR results. Capture failure cases so we know what to tune.

End-to-end test pass for Series + Collection add flows

idea S

Drew's “TEST COLLECTION ADD — LINK, NEW PIN, ETC.” All paths to creating a Series: (1) /series → + Add Series form; (2) /series/new-from-pinandpop preview flow; (3) /series/import-url one-shot; (4) inline + Create from /new-pin; (5) /series/{cid}/refresh-from-url on existing. Plus manual /collections add. Each path has slightly different field handling and a smoke-test checklist would catch drift before users hit it.

Better colors + scannability

idea S

Pico CSS defaults are clean but the dark-mode contrast on muted-color text + secondary borders is sometimes too subtle. Audit text contrast ratios + tune the muted color palette. Also: the four left-border accent colors used on panels (teal for visual-auth, purple for confidence, red for eBay sold, etc.) are currently arbitrary — a consistent semantic palette would help.

UI redesign A/B test (vs Claude-designed alt)

idea M

Drew's ask: have Claude propose a redesign of one or two key pages (likely /new-pin and /scan, since those are where the most clutter and the highest decision-load live) and ship them as an A/B togglable variant. Implementation options: (1) feature-flag in app_settings — ui_variant=claude swaps templates at render time; (2) parallel template files in templates/v2/ that subclass base.html differently. Approach: ask Claude to redesign one focused page (e.g. /new-pin), produce a parallel template, ship both behind a toggle, A/B in your own usage, keep the winner. Specifically watch: information hierarchy, panel ordering, color usage, default-collapsed vs default-expanded choices, mobile breakpoints. Backlog rather than next-up because it's open-ended — a single session producing one variant is the right first step.

Sort-by across collections/series/pins

done S

Currently /pins, /series, /collections all default to name-asc with no user control. Add a sort dropdown: by name, by year, by recently-added, by acquired-date, by value, by completion %. Persists via URL param so the sort survives navigation.

“Recently added” flag on pins / series / collections

done S

Highlight rows added in the last N days with a small “NEW” badge so the user can quickly see what they've added recently. N configurable in Settings (default 7 days). Pairs well with sort-by-recently-added above.

Hide / collapse advanced UI areas

done S

The /new-pin page in particular has accumulated a lot of disclosure panels (AI provider cards, PinPics candidates, Pin&Pop candidates, visual-similarity, set members, linked-pins, reference-image selection, and now local-catalog matches). Default many of these to collapsed via <details> and let the user expand. Power users can flip a setting to default-expand.

Rationalize the AI scan-type matrix

idea S

Six providers (Claude / Opus / Gemini / Gemini Flash / OpenAI / OpenAI Deep) + cheap-mode toggle. The current scan/identify UI surfaces all of them with toggles, which is overwhelming. Consolidate into a smaller set of presets (“fast and cheap” / “balanced” / “throw everything at it”) with an advanced mode that exposes the full matrix for people who want it.

Mobile-responsive validation

idea M

Promoted from OPEN_QUESTIONS v0.15.81. The web UI works on phones via Pico CSS defaults, but heavy-data pages (scan results with all the AI panels + Pin&Pop block + Lens result) get tight on small screens. Needs a focused mobile pass: hit /scan/N/new-pin on a real phone with real data, list what breaks (overflow, unreachable buttons, tiny tap targets), fix in one version bump.

Pin detail page redesign (tab split)

done L

Done in v0.15.80. The 2063-line monolith of pin_detail.html split into five tabbed panels: Overview, Photos, Authenticity, Catalog, History. Progressive enhancement — all content still renders server-side; JS toggles hidden. URL hash drives the active tab (#tab=photos); back-button works.

/new-pin skeleton-hydrate (eBay async-load)

done M

Done in v0.15.80. The eBay active + sold scrapes (2-5s each, blocking) moved to /scan/{scan_id}/ebay-async with a skeleton placeholder + JS hydration. v0.15.81 cleanup deleted the dead synchronous block + the ebay_data / ebay_sold_data placeholder variables. Other heavy /new-pin sections (AI fan-out, set members, visual similarity) still render synchronously but are either cached or DB-local so they don't block meaningfully.

Cost & Efficiency

Smarter caching across AI calls

idea M

ai_response_cache.py already keys by image-bytes hash + model + grounding flag, which is good. Possible improvements: (1) cache across prompt-version bumps when only minor text changed (currently any prompt edit busts the cache); (2) extract intermediate results (catalog IDs, name) and reuse them across prompts that ask different questions of the same image; (3) negative caching for “no result” responses so re-running an unrecognized scan doesn't re-spend the full provider budget.

Data Integrity

Auto-backup / cloud sync of the DB

idea M

Currently the SQLite DB + uploads/ live in a single directory and rely on the user to back them up manually. Auto-backup options: (1) periodic sqlite3 .dump to a dated file in a configurable backup dir; (2) rsync to a remote target on a cron; (3) S3-compatible upload of the DB + uploads tarball; (4) Git-based snapshot of the DB-dump file. Open question: is this needed before exposing the server to the internet, or is a manual export from /settings enough for now? (Previously in OPEN_QUESTIONS as “skip for now”; promoted to backlog because Drew explicitly asked for it.)

Engineering Hygiene

Code cleanup: unused modules, scripts, tests, dead branches

idea M

The codebase has accumulated cruft from rapid iteration — modules / functions / templates / settings that are no longer referenced anywhere. Sweep for:
· Unused modules. Walk every top-level .py file, search for imports of it across the tree, drop the orphans. Suspects to check: scripts in the repo root that were one-off migrations, old prototype modules superseded by current ones.
· Unused functions. vulture or ruff --select F401 to find unreferenced functions / imports. Eyeball each hit since some are legitimately called via getattr / template names / route decorators.
· Dead branches. Feature-flag conditionals where the flag has been on (or off) for 5+ versions and the other branch is effectively unreachable. Resolve to the live branch + delete.
· Stale tests / pending/ fixtures. Anything in pending/ or test scratch dirs that pre-dates the current data shape and would 500 if actually re-run. Delete or update.
· Unused templates. Templates in templates/ that aren't referenced in any TemplateResponse call. Easy grep -L across main.py to find.
· CHANGELOG entries describing now-removed features. Don't edit the history (audit trail) but flag any current docs / settings page text that still references removed code paths.
Approach: do it in batches by category rather than one big sweep; each batch is one focused PR / version bump so a regression caught later is easy to bisect.

❓ Open questions

Things flagged that need a decision before they can become proper backlog items. Either answer them or tell me they don't matter and I'll drop them.

Fallback escalation strategy

When the new confidence score is low (<40%), what should the scan chain do? Options: (1) escalate to Claude Opus deep with explicit 'previous providers were uncertain' context, (2) trigger an extra grounded web search, (3) just warn the user with no escalation. Currently doing (3). (1) is roughly the same cost as a normal deep scan; (2) is free if we already called Gemini once.

Set member auto-add UX

When the catalog tells us a pin belongs to a set of 8, should we (a) silently auto-create 7 missing-pin rows, (b) show a 'Build out this set' button on the new-pin page that does it on click, or (c) leave it manual? Recommend (b) — auto-creating could surprise users with rows they didn't ask for, but a one-click action is valuable for checklist completeness.

This page is rebuilt from the BACKLOG list in main.py on every request. To edit, modify the list and reload — no migrations needed.