8 Commits
Author SHA1 Message Date
rgriffith 310a408509 feat: polish precon picker — card art, color pips, grid layout
- Add full CSS for precon grid: responsive card grid, hover effects,
  install button states, color pips, dark theme styling
- Build commander art_crop image URLs from set_code/number during
  precon download (Scryfall direct image redirect, no API call needed)
- Batch-fetch color_identity from Scryfall /cards/collection endpoint
  (2 requests for ~145 unique commanders) and store in precon index
- Widen precon modal to 900px for grid layout
- Auto-invalidate precon cache when enrichment fields are missing
- JS renderer now shows commander art crop + card body layout
- Bump cache-busting version to v=4
2026-03-19 10:10:40 +00:00
rgriffith 8eba39cca7 fix: precon picker empty — mutate PRECON_INDEX in-place
The PRECON_INDEX list was imported by value in routes/shared.py.
When download_precon_database() or load_precon_index() reassigned it
with 'PRECON_INDEX = ...', the new list object was only visible in
precon_service module scope — shared.PRECON_INDEX (and thus the
/api/lab/precons endpoint) still pointed to the original empty [].

Fix: use PRECON_INDEX.clear() + .extend() instead of reassignment
so all modules sharing the same list reference see the updated data.
2026-03-19 10:01:51 +00:00
rgriffith bc9656a59f fix: add cache-busting query params to ai-lab.js and ai-lab.css
Browser was serving the old Base64-encoded ai-lab.js from cache.
Adding ?v=3 query parameter forces the browser to fetch the new
decoded JavaScript file.
2026-03-19 09:55:00 +00:00
rgriffith 86b96c09f9 fix: decode ai-lab.js from Base64 back to proper JavaScript
The entire ai-lab.js file (139KB) was stored as a single Base64-encoded
string instead of actual JavaScript. The browser loaded it as a script
but couldn't parse it — AiLab was never defined, so the batch sim panel
never rendered.

Decoded the file back to its proper 2531-line JavaScript source. The
file already includes auto-initialization at the bottom:
  if (document.readyState === 'loading')
    document.addEventListener('DOMContentLoaded', AiLab.init);
  else AiLab.init();

Also removed the now-redundant DOMContentLoaded listener from
index.html since the JS file handles init itself.
2026-03-19 09:47:18 +00:00
rgriffith 9a46b0f80c fix: remove duplicate Scryfall/helper defs from collection.py that shadowed working imports
collection.py re-defined _ScryfallCache, _scryfall_cache,
_scryfall_rate_limit, _fetch_scryfall_api, _enrich_from_scryfall,
_parse_finish, _parse_text_line, _auto_infer_mapping, _parse_csv_content,
_TYPE_PRIORITY, and _TYPE_TARGETS locally (lines 333-509).

These local definitions shadowed the correct imports from
routes/shared.py -> services/scryfall.py. Critically, the local
_fetch_scryfall_api used Request and urlopen without importing them,
causing a NameError at runtime that was silently swallowed by the
broad except clause — making every card fail Scryfall enrichment
during collection import.

Removed all ~470 lines of duplicate code. The import endpoint now
correctly uses the functions imported from shared.py, which have
proper urllib imports and a working Scryfall cache.
2026-03-19 09:43:26 +00:00
rgriffith 0d2da18783 fix: call AiLab.init() on DOMContentLoaded so batch sim panel renders
The ai-lab.js IIFE defines the AiLab global with an init() method that
calls renderPanel(), detectBackend(), loadAvailableDecks(),
loadMetaCommanders(), loadPrecons(), and bindEvents() — but nothing
ever invoked it. Added a DOMContentLoaded listener after the script tag
in index.html to trigger initialization.
2026-03-19 09:34:27 +00:00
rgriffith 9b892e5289 fix: remove unsupported cursor:auto and normalize rgb() spacing in USS files 2026-03-19 08:05:54 +00:00
rgriffith ce80b6d9c5 feat: implement SimulationController, fix API paths, wire Simulator scene 2026-03-19 07:46:44 +00:00