- Restored _run_deepseek_batch_thread and _get_deepseek_brain
(accidentally removed in prior commit)
- Changed '-sim' to 'sim' — Forge expects positional mode, not flag
- 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
- Sonar does not support response_format type json_schema
- Switch to {"type": "json_object"} which Sonar supports
- Keep json_schema/schema_name params for call-site compatibility (ignored)
- Add exc_info=True to V3 generate error logging for full tracebacks
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.
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.
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.
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.
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.
The API returns {"models":[...]} rather than a bare array.
Added MlModelsResponse wrapper class and updated LoadModelHistory
to unwrap the list before iterating, preventing the deserialization
exception in TrainingController.
- Replace blocking urlopen with async httpx.AsyncClient
- Add await at both call sites (deck_research, deck_generate_ai)
- Uvicorn event loop no longer stalled during Perplexity API calls
- Replace SELECT * + Python-side color loop in _get_collection_for_colors()
with projected SELECT + NOT EXISTS json_each() sub-query
- Apply same pattern to _build_collection_summary(), removing card_fits_identity()
- Add partial index idx_ce_quantity on collection_entries(quantity) WHERE qty > 0
- Handle NULL and empty-string color_identity via CASE guard
- Fix colorless commander bug: old code skipped color check entirely for []
- Add scripts/bench_deckgen.py benchmark (3-5x speedup on filtered queries)
- Add tests/test_deckgen_color_filter.py with 7 regression tests