A deckbuilder for the commander format of Magic: The Gathering
Find a file
mwisnowski 03e839fb87
Merge pull request #16 from mwisnowski/maintenance/testing-and-responsivenes
test: convert tests to pytest assertions; add server-availability ski…
2025-09-12 11:26:35 -07:00
.github/workflows chore(release): v2.2.9 misc land variety, land alternatives randomization, scroll flicker fix 2025-09-10 16:20:38 -07:00
code chore: fix the test_bracket_policy_applier test from overwriting the files in confg/card_files when ran 2025-09-12 11:21:55 -07:00
config fix(docker): seed all default card list JSONs & brackets.yml; update changelog 2025-09-10 08:53:00 -07:00
tests/e2e feat: complete include/exclude observability, fix validation bugs, and organize tests 2025-09-09 20:18:03 -07:00
.dockerignore Fixed an issue with files uploaded to dockerhub, causing it to include some csv and text files that don't need to be there 2025-08-21 11:50:27 -07:00
.env.example chore(release): v2.2.9 misc land variety, land alternatives randomization, scroll flicker fix 2025-09-10 16:20:38 -07:00
.gitattributes web: DRY Step 5 and alternatives (partial+macro), centralize start_ctx/owned_set, adopt builder_* 2025-09-02 11:39:14 -07:00
.gitignore feat: complete include/exclude observability, fix validation bugs, and organize tests 2025-09-09 20:18:03 -07:00
CHANGELOG.md test: convert tests to pytest assertions; add server-availability skips; clean up warnings and minor syntax/indent issues 2025-09-12 10:50:57 -07:00
docker-compose.yml chore:fixed untabbed lines in compose files 2025-09-11 15:07:50 -07:00
DOCKER.md release: v2.0.1 — Web UI major upgrade, theming reset, diagnostics polish, docs refreshed 2025-08-28 16:44:58 -07:00
Dockerfile web: DRY Step 5 and alternatives (partial+macro), centralize start_ctx/owned_set, adopt builder_* 2025-09-02 11:39:14 -07:00
dockerhub-docker-compose.yml chore:fixed untabbed lines in compose files 2025-09-11 15:07:50 -07:00
entrypoint.sh fix(docker): seed all default card list JSONs & brackets.yml; update changelog 2025-09-10 08:53:00 -07:00
LICENSE Removed references to an old card_info script I had originally thought to include. 2025-01-21 09:26:36 -08:00
mtg_deckbuilder.spec build(windows): fix PyInstaller packaging\n\n- Add spec file with hiddenimports and data dirs\n- Use spec in release workflow; fallback to --paths code\n- Insert ./code into sys.path when frozen to resolve local imports 2025-08-25 09:57:02 -07:00
mypy.ini Finished v2 of deck_builder, should be largely functional, but could use refinements. WIll continue to work on it, but largely satisfied with how it works. 2025-01-17 17:04:02 -08:00
pyproject.toml web(ui): move theme controls to sidebar bottom, tighten Test Hand fan arc, set desktop to 280x392; mobile banner cleanup; bump version to 2.2.10 and update compose APP_VERSION; cache-bust CSS 2025-09-11 14:54:35 -07:00
pytest.ini Web UI polish: thumbnail-hover preview, white thumbnail selection, Themes bullet list; global Scryfall image retry (thumbs+previews) with fallbacks and cache-bust; standardized data-card-name. Deck Summary alignment overhaul (count//name/owned grid, tabular numerals, inset highlight, tooltips, starts under header). Added diagnostics (health + logs pages, error pages, request-id propagation), global HTMX error toasts, and docs updates. Update DOCKER guide and add run-web scripts. Update CHANGELOG and release notes template. 2025-08-27 11:21:46 -07:00
README.md chore(release): v2.2.9 misc land variety, land alternatives randomization, scroll flicker fix 2025-09-10 16:20:38 -07:00
RELEASE_NOTES_TEMPLATE.md test: convert tests to pytest assertions; add server-availability skips; clean up warnings and minor syntax/indent issues 2025-09-12 10:50:57 -07:00
requirements.txt feat(combos): add Combos & Synergies detection, chip-style UI with dual hover; JSON persistence and headless honoring; stage ordering; docs and tests; bump to v2.2.1 2025-09-01 16:55:24 -07:00
run-from-dockerhub.bat feat(owned-cards): add owned-only workflow, multi-file parsing, and recommendations export\n\n- Prompt to use only owned cards (gated by presence of lists)\n- Support .txt/.csv owned lists, multi-select; commander exempt\n- Owned-only filtering + add guard; recommendations CSV/TXT when incomplete\n- CSV Owned column when not owned-only\n- Docs and Docker updated (owned_cards + config mounts)\n- CI: Windows EXE on tag; Docker Hub tag fix (no major.minor)\n- Changelog added; RELEASE_NOTES.md ignored 2025-08-25 09:48:05 -07:00
run-from-dockerhub.sh feat(owned-cards): add owned-only workflow, multi-file parsing, and recommendations export\n\n- Prompt to use only owned cards (gated by presence of lists)\n- Support .txt/.csv owned lists, multi-select; commander exempt\n- Owned-only filtering + add guard; recommendations CSV/TXT when incomplete\n- CSV Owned column when not owned-only\n- Docs and Docker updated (owned_cards + config mounts)\n- CI: Windows EXE on tag; Docker Hub tag fix (no major.minor)\n- Changelog added; RELEASE_NOTES.md ignored 2025-08-25 09:48:05 -07:00
run-web-from-dockerhub.bat release: v2.0.1 — Web UI major upgrade, theming reset, diagnostics polish, docs refreshed 2025-08-28 16:44:58 -07:00
run-web-from-dockerhub.sh Web UI polish: thumbnail-hover preview, white thumbnail selection, Themes bullet list; global Scryfall image retry (thumbs+previews) with fallbacks and cache-bust; standardized data-card-name. Deck Summary alignment overhaul (count//name/owned grid, tabular numerals, inset highlight, tooltips, starts under header). Added diagnostics (health + logs pages, error pages, request-id propagation), global HTMX error toasts, and docs updates. Update DOCKER guide and add run-web scripts. Update CHANGELOG and release notes template. 2025-08-27 11:21:46 -07:00
WINDOWS_DOCKER_GUIDE.md release: v2.0.1 — Web UI major upgrade, theming reset, diagnostics polish, docs refreshed 2025-08-28 16:44:58 -07:00

# 🃏 MTG Python Deckbuilder

License: MIT Python 3.11+ Docker

A fast, menu-driven deckbuilder for MTG Commander/EDH with both CLI and Web UI. Supports interactive and headless runs, CSV/TXT exports, owned-card libraries, and Docker.

🔹 Features

  • Web UI and CLI, powered by the same builder
  • Commander search with smart theme tagging (primary/secondary/tertiary)
  • Power bracket selection and ideal count prompts
  • Include/Exclude Card Lists: Must-include and must-exclude functionality with fuzzy matching, visual validation, and strict enforcement (set ALLOW_MUST_HAVES=true)
  • CSV and TXT exports with stable filenames
  • Headless mode (non-interactive) and a headless submenu in the main menu
  • Config precedence: CLI > env > JSON > defaults
  • Visual summaries in Web UI: Mana curve, Color pips and Sources with hover-to-highlight and copyable tooltips
  • Web UI: Locks, Replace flow, Compare builds, and shareable permalinks
  • Unified “New Deck” modal (steps 13): commander search, themes, bracket, options, and optional Name (used in export filenames)
  • Multi-copy archetypes (opt-in modal): choose quantity for supported archetypes and optionally add Thrumming Stone; added early with target adjustments and a hard 100-card clamp
  • Combos & Synergies: detect curated pairs, show chip-style lists with badges, and dual-card hover previews; optional auto-complete adds missing partners based on your plan.

Highlights

  • Smart tagging and suggestions for commander + themes, with AND/OR combine modes
  • Exports CSV and TXT decklists to deck_files/
  • Owned-cards mode: point to one or more .txt/.csv files in owned_cards/ and choose "Use only owned cards?"
  • If you opt out, final CSV marks owned cards with an Owned column
  • Build options include Prefer-owned: bias picks toward owned cards while allowing unowned fallback
  • Interactive menu with a headless submenu
  • Works locally or in Docker (Windows PowerShell friendly)
  • Card images and data via Scryfall (attribution shown in the Web UI)
  • Web visual summaries: cross-highlight cards from charts; sources include non-lands and colorless 'C' with an on/off toggle
  • New Deck modal keyboard UX: Enter selects the first suggestion; arrow navigation removed; browser autofill disabled
  • Export naming: optional Name in the modal becomes the filename stem for CSV/TXT/JSON; decks include a sidecar .summary.json with meta.name
  • Finished Decks and deck banners prefer your custom Name when present
  • Compare page includes a Copy summary button to quickly share diffs
  • New Deck modal shows your selected themes and their order (1 → 3) while picking; Bracket options are labeled (e.g., "Bracket 3: Upgraded"). Default bracket is 3.
  • Bracket policy enforcement: disallowed categories (e.g., Game Changers in Brackets 12) are pruned from the pool up-front; UI blocks progression until violations are resolved, with inline Replace to pick compliant alternatives.
  • Bracket compliance UX: the compliance panel auto-opens when non-compliant and shows a colored overall chip (green/WARN amber/red). WARN thresholds (e.g., tutors/extra turns) are advisory—tiles show with a subtle style and no gating; FAIL continues to gate and enables enforcement.

🚀 Quick start

Docker Hub (PowerShell)

docker run -it --rm `
  -v "${PWD}/deck_files:/app/deck_files" `
  -v "${PWD}/logs:/app/logs" `
  -v "${PWD}/csv_files:/app/csv_files" `
  -v "${PWD}/owned_cards:/app/owned_cards" `
  -v "${PWD}/config:/app/config" `
  mwisnowski/mtg-python-deckbuilder:latest

From source

pip install -r requirements.txt
python code/main.py

From this repo with Docker Compose (PowerShell)

docker compose build
docker compose run --rm mtg-deckbuilder

🌐 Web UI

Run the browser-based UI backed by the same deckbuilding engine.

From source

pip install -r requirements.txt
uvicorn code.web.app:app --host 127.0.0.1 --port 8080

Open http://127.0.0.1:8080

Docker Compose (PowerShell)

docker compose build web
docker compose up --no-deps web

Then open http://localhost:8080

Notes:

  • Exports/logs/configs use the same folders and will appear under your mounted volumes (see compose file).
  • The footer includes Scryfall attribution per their guidelines.
  • Favicon is bundled; browsers load /favicon.ico (auto-falls back to PNG).
  • Bracket enforcement is applied in both web and headless runs; see "Bracket policy & enforcement" below.

Run with Docker Compose (Docker Hub image)

Use the prebuilt image with the provided dockerhub-docker-compose.yml. The image defaults to the Web UI.

Web (default):

docker compose -f dockerhub-docker-compose.yml up -d

Pin to a version (edit the compose file image tag to :X.Y.Z) to avoid latest drift.

CLI mode (override default):

docker compose -f dockerhub-docker-compose.yml run --rm `
  -e APP_MODE=cli `
  web

Notes:

  • Volumes under ${PWD} persist your exports/logs/configs locally next to the compose file.
  • Health checks are built into the image for the Web UI. In CLI, APP_MODE=cli bypasses health.

Theme defaults and flags

  • Enable the Theme selector with ENABLE_THEMES=1 (on by default in web in compose).
  • Set the initial default with THEME=system|light|dark (applies only when the browser has no prior preference saved):
    • THEME=system (recommended): follows the OS theme; Light uses the consolidated Light (Blend) palette.
    • THEME=light: maps to the consolidated Light (Blend) theme in the UI.
    • THEME=dark: uses the dark theme.
  • You can also force a one-off override via URL: ?theme=system|light|dark|high-contrast|cb-friendly.
    • Example: http://localhost:8080/?theme=dark
    • The choice is saved in localStorage; the URL parameter is removed after applying.
    • Reset: Use the “Reset theme” control in the header to clear your preference and reapply the server default (or system).
  • New Deck modal (Steps 13 unified): search for your commander, pick themes, choose bracket (defaults to 3), and optionally set Name. Submitting starts the build immediately. Name becomes the export filename stem and display name.
  • Lock a card: click the card image or the lock control in Step 5. Locked cards are pinned for reruns. A small “Last action” chip confirms lock/unlock.
  • Replace a card: toggle Replace in Step 5, then click a card to open Alternatives. Filter (including Owned-only) and pick a swap; the deck updates in place.
    • When bracket violations exist, Step 5 shows them first and disables Continue/Rerun. Pick replacements inline; alternatives exclude commanders, locked, in-deck, and just-added cards and prefer role-consistent options.
  • Permalink: use “Copy permalink” from Step 5 or a Finished deck to share/restore a run (commander, themes, bracket, ideals, flags). “Open Permalink…” lets you paste one to restore to review.
  • Compare: from Finished Decks, open Compare and pick A and B (quick actions: Latest two, Swap A/B). Use Changed-only to focus diffs. Copy summary or Download .txt.
  • Keyboard: In the New Deck modal, Enter selects the first commander result. Browser autofill is disabled for cleaner inputs. In Step 5, use L to lock/unlock the focused card, R to open Alternatives, and C to copy a permalink.

Setup speed: parallel tagging (Web)

  • On first run or when data is stale, the Web UI prepares and tags the card database.
  • This tagging runs in parallel by default for faster setup.
  • Tuning via env vars (set on the web service in docker-compose.yml):
    • WEB_TAG_PARALLEL=1|0 — enable/disable parallel workers (default: 1/enabled)
    • WEB_TAG_WORKERS=<N> — number of worker processes (default: 4 in compose; omit to auto-pick)
  • The UI shows progress and falls back to sequential tagging automatically if parallel init fails.

Virtualized lists and image polish (opt-in)

  • Opt-in with WEB_VIRTUALIZE=1 to enable virtualization in Step 5 lists/grids and the Owned library for smoother scrolling on large sets.
  • Virtualization diagnostics overlay (for debugging): enable SHOW_DIAGNOSTICS=1, then press v to toggle an overlay per grid and a global summary bubble; shows visible range, total, rows, row height, render time, and counters.
  • Thumbnails use lazy-loading with srcset/sizes; LQIP blur/fade-in is applied to Step 5 and Owned thumbnails, and the commander preview image.
  • Respects reduced motion: when the OS prefers-reduced-motion, blur/fade transitions and smooth scrolling are disabled for accessibility.

Build options (Web)

  • Use only owned cards: builds strictly from your owned_cards/ lists (commander exempt).
  • Prefer owned cards: gently prioritizes owned cards across creatures and spells but allows unowned when theyre better fits.
    • Implemented via a stable owned-first reorder and small weight boosts; preserves existing sort intent. Notes:
  • Steps 13 are consolidated into a single New Deck modal. Submitting starts the build immediately, skipping the old review page.
  • The modal includes an optional Name; when set, it is used as the export filename stem and display name in the UI.
  • The modal displays selected themes in order (1, 2, 3). The Bracket selector shows numbers (e.g., "Bracket 3: Upgraded") and defaults to 3 for new decks.

Staged build visibility

  • Step 5 can optionally “Show skipped stages” so you can see phases that added no cards with a clear annotation.

Multi-copy archetypes (Web)

  • When your commander + themes suggest a multi-copy strategy (e.g., petitioners, approach, apostles), the Web UI shows a one-time modal to add a package.
  • Choose how many copies (bounded by the printed cap) and optionally include 1× Thrumming Stone when it synergizes.
  • The package is applied as the first stage so later phases account for the volume.
  • Targets adjust automatically (reduce creatures for creature packages, or spread reductions across spells). The UI shows an “Adjusted targets” note on that stage.
  • A final safety clamp trims overflow from this stage to keep the deck at 100. A “Clamped N” chip appears when this happens.
  • Suggestions wont re-prompt repeatedly for the same commander+theme context unless you change selections; you can also dismiss the modal.

Visual summaries (Web)

  • Mana Curve bars with hover-to-highlight of matching cards in both list and thumbnail views
  • Color Pips (requirements) and Sources (generation) bars with per-color tooltips listing cards
  • Cross-highlighting from charts to the card views; list-mode highlights only the name span
  • Sources detection includes non-land producers (artifacts/creatures/etc.) and colorless 'C'
  • Fetch lands are not counted as mana sources; basic lands and Wastes are handled reliably
  • Optional: Show colorless (C) toggle for Sources; persisted per browser
  • Tooltips include a Copy button to copy the card list

Combos & Synergies (Web)

  • Detection: curated two-card combos/synergies are detected from the final deck (commander included) and shown with list version badges.
  • UI: chip-style rows with badges (cheap/early, setup). Hover a row to preview both cards side-by-side; hover an individual name for single-card preview.
  • Auto-Complete Combos: when enabled in the New Deck modal, the builder completes up to N pairs before theme fill/monolithic spells so partners stick.
    • Settings: Prefer combos (on/off), How many combos (target), Balance (early/late/mix) to bias partner selection.
    • Existing completed pairs are counted first; only missing partners are added.
    • Color identity enforced via the filtered card pool; off-color/unavailable partners are skipped.

Owned page (Web)

  • View and manage your owned lists with:
    • Export TXT/CSV, sort controls, and a live “N shown” counter
    • Color identity dots and exact color-identity combo filters (including 4-color)
    • Viewport-filling list with styled scrollbar
    • Optional virtualization when WEB_VIRTUALIZE=1 (improves performance on very large libraries)
  • Uploading .txt or .csv lists enriches and deduplicates entries at upload-time and persists them, so page loads are fast.

Finished Decks (Web)

  • Theme filters are a dropdown with shareable state for easier browsing of saved builds.
  • Each finished deck has a permalink you can copy and revisit; Compare mode lets you diff against another run. The Compare page has a Copy summary button to copy a plain-text diff.
  • Locks and Replace flow: lock-in picks you like or replace a card with an alternative during iteration.

Diagnostics (Web)

  • Health endpoint: GET /healthz returns { status, version, uptime_seconds }
  • Responses include X-Request-ID for easier error correlation; unhandled errors return JSON with request_id

Logs and system tools:

  • Logs page (/logs, enable with SHOW_LOGS=1):
    • Auto-refresh toggle with adjustable interval
    • Level filter (All/Error/Warning/Info/Debug) and keyword filter
    • Copy button to copy the visible log tail
  • System summary (GET /status/sys, shown on /diagnostics when SHOW_DIAGNOSTICS=1):
    • Returns { version, uptime_seconds, server_time_utc, flags }
    • Shows resolved theme and stored preference; includes a Reset preference button.

Compose: enable diagnostics/logs (optional)

services:
  web:
    environment:
      - SHOW_LOGS=1
      - SHOW_DIAGNOSTICS=1

🤖 Headless mode (no prompts)

  • Auto-headless: set DECK_MODE=headless
    • Example (PowerShell):
      docker compose run --rm -e DECK_MODE=headless mtg-deckbuilder
      
  • Use a JSON config: mount ./config to /app/config and set DECK_CONFIG=/app/config/deck.json
    • Example (PowerShell):
      docker compose run --rm `
        -e DECK_MODE=headless `
        -e DECK_CONFIG=/app/config/deck.json `
        mtg-deckbuilder
      
  • Override via env vars (subset): DECK_COMMANDER, DECK_PRIMARY_CHOICE, DECK_SECONDARY_CHOICE, DECK_TERTIARY_CHOICE, DECK_ADD_LANDS, DECK_FETCH_COUNT, DECK_BRACKET_LEVEL
    • Precedence: CLI > env > JSON > defaults

Enhanced CLI with Type Safety & Theme Names

The headless runner now features comprehensive CLI arguments with type indicators and intelligent theme selection:

# Show all available options with type information
python code/headless_runner.py --help

# Build with theme names instead of index numbers
python code/headless_runner.py `
  --commander "Aang, Airbending Master" `
  --primary-tag "Airbending" `
  --secondary-tag "Exile Matters" `
  --bracket-level 4

# Override ideal deck composition counts
python code/headless_runner.py `
  --commander "Krenko, Mob Boss" `
  --primary-tag "Goblin Kindred" `
  --creature-count 35 `
  --land-count 33 `
  --ramp-count 12

# Include/exclude specific cards (semicolon for comma-containing names)
python code/headless_runner.py `
  --commander "Jace, Vryn's Prodigy" `
  --include-cards "Sol Ring;Jace, the Mind Sculptor" `
  --exclude-cards "Chaos Orb;Shahrazad" `
  --enforcement-mode strict

New CLI Features:

  • Type-safe help text: All arguments show expected types (PATH, NAME, INT, BOOL)
  • Ideal count arguments: --ramp-count, --land-count, --creature-count, etc.
  • Theme tag names: --primary-tag "Theme Name" instead of --primary-choice 1
  • Include/exclude CLI: --include-cards, --exclude-cards with semicolon support
  • Console diagnostics: Detailed summary output for validation and results

Headless submenu notes:

  • If one JSON exists in config/, it auto-runs it
  • If multiple exist, theyre listed as "Commander - Theme1, Theme2, Theme3"; deck.json shows as "Default"
  • CSV/TXT are exported as usual; JSON run-config is exported only in interactive runs

Run locally (no Docker)

# Show resolved settings (no run)
python code/headless_runner.py --config config/deck.json --dry-run

# Run with a specific config file
python code/headless_runner.py --config config/deck.json

# Point to a folder; if exactly one config exists, it's auto-used
python code/headless_runner.py --config config

# Override via CLI
python code/headless_runner.py --commander "Pantlaza, Sun-Favored" --primary-choice 2 --secondary-choice 0 --add-lands true --fetch-count 3

CLI flags

  • --config Path to JSON config file or a folder to discover configs (uses DECK_CONFIG by default)
  • --commander Commander name to search and select
  • --primary-choice Primary theme index
  • --secondary-choice <n|none> Secondary theme index or omit with "none"
  • --tertiary-choice <n|none> Tertiary theme index or omit with "none"
  • --add-lands Include land building steps (true/false)
  • --fetch-count Requested number of fetch lands
  • --dual-count Requested number of dual lands (optional; not exported)
  • --triple-count Requested number of triple lands (optional; not exported)
  • --utility-count Requested number of utility lands (optional; not exported)
  • --add-creatures Add creatures
  • --add-non-creature-spells Add non-creature spells orchestrator
  • --add-ramp Add ramp (when not using orchestrator)
  • --add-removal Add removal (when not using orchestrator)
  • --add-wipes Add board wipes (when not using orchestrator)
  • --add-card-advantage Add card draw (when not using orchestrator)
  • --add-protection Add protection (when not using orchestrator)
  • --bracket-level <1-5> Power bracket selection (or use DECK_BRACKET_LEVEL)
  • --dry-run Print resolved config and exit

Booleans accept: 1/0, true/false, yes/no, on/off.

JSON export in headless

  • By default, headless runs do not export a JSON run-config to avoid duplicates.
  • Opt-in with:
    $env:HEADLESS_EXPORT_JSON = "1"
    python code/headless_runner.py --config config/deck.json
    
  • Tip: when opting in, prefer using --config instead of a DECK_CONFIG file path to avoid creating both a stem-based JSON and a second explicit-path JSON.

Example JSON (config/deck.json):

{
  "commander": "Pantlaza",
  "bracket_level": 3,
  "primary_choice": 2,
  "secondary_choice": 2,
  "tertiary_choice": 2,
  "tag_mode": "OR", // OR or AND; Web UI default is OR
  "add_lands": true,
  "fetch_count": 3,
  "ideal_counts": { "ramp": 10, "lands": 36, "basic_lands": 16, "creatures": 28, "removal": 8, "wipes": 3, "card_advantage": 8, "protection": 3 }
}

Notes: headless honors ideal_counts (leave prompts blank to accept). Only fetch_count is tracked/exported for lands.

JSON fields for Combos (Web Configs)

When running from a JSON config in the Web UI, the following fields are supported and exported from interactive runs:

{
  "prefer_combos": true,
  "combo_target_count": 3,
  "combo_balance": "mix"
}

Auto-Complete Combos runs before theme fill/monolithic spells when prefer_combos is true.

🔒 Bracket policy & enforcement

  • Policy source: config/brackets.yml defines per-bracket limits for categories like game_changers, extra_turns, mass_land_denial, tutors_nonland, and two_card_combos.
  • Authoritative lists: JSON under config/card_lists/ provides names for enforcement and reporting (game_changers.json, extra_turns.json, mass_land_denial.json, tutors_nonland.json). A list_version may be included for badges.
  • Global safety prune: when a category has a limit of 0, the builder removes matching cards from the card pool up-front so they cannot be selected (Game Changers by name; others by tags when present). This runs in both Web and headless builds.
  • Preemptive filters: spells and creatures phases also apply bracket-aware pre-filters.
  • Inline enforcement (Web): if violations still occur, Step 5 shows them before the summary. You must replace or remove flagged cards before you can Continue or Rerun. Alternatives are role-consistent, exclude the replaced/in-deck/locked/commander cards, and bias toward owned when enabled.
  • Game Changer fallback: if config/card_lists/game_changers.json is empty, enforcement and reporting fall back to the in-code builder_constants.GAME_CHANGERS list so low-bracket decks still exclude those cards.
  • Auto-enforce (optional): set WEB_AUTO_ENFORCE=1 to automatically apply the enforcement plan after build and re-export CSV/TXT when violations are detected.

Status levels

  • PASS: within limits; panel remains collapsed by default.
  • WARN: advisory thresholds met (from <category>_warn keys in config/brackets.yml or conservative defaults for low brackets on tutors/extra turns). Panel opens automatically and shows WARN tiles with a subtle amber border and badge; no gating or automatic enforcement.
  • FAIL: over hard limits or disallowed categories for the selected bracket. Panel opens automatically, tiles show with red accents, and Continue/Rerun are disabled until resolved; enforcement actions are available.

Compliance report

  • Every run writes [stem]_compliance.json next to your deck exports, including per-category counts/limits, status (PASS/FAIL), detected cheap/early two-card combos, and list versions.
  • Headless JSON builds and Web builds use the same engine; summary and exports are consistent.

🧩 Theme combine mode (AND/OR)

  • OR (default): Recommend cards that match any selected themes, prioritizing overlap.
  • AND: Prioritize multi-theme intersections. For creatures, an AND pre-pass first picks "all selected themes" creatures up to a cap, then fills by weighted overlap.

UI tips:

  • Step 2 includes AND/OR radios with a tooltip explaining trade-offs.
  • In staged build view, "Creatures: All-Theme" shows which selected themes each card hits.

🕹️ Usage (interactive)

  1. Start the app (Docker or from source)
  2. Pick Build a New Deck
  3. Search/confirm commander
  4. Pick primary/secondary/tertiary themes (or stop at primary); choose AND/OR combine mode
  5. Choose power bracket and review ideal counts
  6. Deck builds; CSV/TXT export to deck_files/

⚙️ Environment variables (common)

  • DECK_MODE=headless
  • DECK_CONFIG=/app/config/deck.json
  • DECK_COMMANDER, DECK_PRIMARY_CHOICE, DECK_SECONDARY_CHOICE, DECK_TERTIARY_CHOICE
  • DECK_ADD_LANDS, DECK_FETCH_COUNT
  • DECK_ADD_CREATURES, DECK_ADD_NON_CREATURE_SPELLS, DECK_ADD_RAMP, DECK_ADD_REMOVAL, DECK_ADD_WIPES, DECK_ADD_CARD_ADVANTAGE, DECK_ADD_PROTECTION
  • DECK_BRACKET_LEVEL
  • ALLOW_MUST_HAVES=true (enables include/exclude card lists feature with enhanced UI validation)

Optional name-based tag overrides (mapped to indices for the chosen commander):

  • DECK_PRIMARY_TAG, DECK_SECONDARY_TAG, DECK_TERTIARY_TAG

Combine mode in headless:

  • JSON: set "tag_mode": "AND" | "OR"
  • Env var: DECK_TAG_MODE=AND|OR (if configured in your environment)

Web UI performance tuning:

  • WEB_TAG_PARALLEL=1|0
  • WEB_TAG_WORKERS=
  • WEB_VIRTUALIZE=1 (enable list virtualization)
  • SHOW_DIAGNOSTICS=1 (optional: diagnostics tools and virtualization overlay toggle using v)
  • WEB_AUTO_ENFORCE=1 (optional; after building, auto-apply enforcement and re-export when the compliance report fails)

Misc utility land tuning (Step 7):

  • MISC_LAND_DEBUG=1 Write debug CSVs for misc land step (candidates/post-filter). Otherwise suppressed unless SHOW_DIAGNOSTICS=1.
  • MISC_LAND_EDHREC_KEEP_PERCENT_MIN=0.75 Lower bound of dynamic EDHREC keep range (01). When MIN & MAX are both set, a random % in [MIN,MAX] is rolled per build.
  • MISC_LAND_EDHREC_KEEP_PERCENT_MAX=1.0 Upper bound of dynamic EDHREC keep range (01).
  • MISC_LAND_EDHREC_KEEP_PERCENT=0.80 Legacy fixed keep % used only if MIN/MAX not both present.
  • MISC_LAND_THEME_MATCH_BASE=1.4 Base multiplier when at least one selected theme tag matches a candidate land.
  • MISC_LAND_THEME_MATCH_PER_EXTRA=0.15 Incremental multiplier per additional matching theme tag beyond the first.
  • MISC_LAND_THEME_MATCH_CAP=2.0 Cap for total theme multiplier after stacking base + extras.

Notes:

  • Fetch lands are fully excluded from the misc step (handled earlier).
  • Mono-color decks auto-filter broad rainbow/any-color lands except an explicit always-keep list.
  • Land Alternatives endpoint: for land seeds, returns land-only suggestions; 12 random picks each request from a randomly sized window within the top 60100 ranked candidates (per-card, uncached) for variety.

📁 Folders

  • deck_files/ — CSV/TXT exports
  • csv_files/ — card data
  • logs/ — logs
  • config/ — JSON configs (optional)
  • owned_cards/ — your owned cards lists (.txt/.csv); used for owned-only builds and Owned flagging

🧰 Troubleshooting

  • Use docker compose run --rm (not up) for interactive sessions
  • Ensure volumes are mounted so files persist (deck_files, logs, csv_files)
  • For headless with config, mount ./config:/app/config and set DECK_CONFIG
  • Card data refresh: if csv_files/cards.csv is missing or older than 7 days, the app refreshes data and re-tags automatically. A .tagging_complete.json file in csv_files/ indicates tagging completion.
  • Web health: the header dot polls /healthz — green is OK, red is degraded. If red, check logs.
  • Error details: HTMX errors show a toast with a “Copy details” button including X-Request-ID; include that when filing issues.
  • Logs page: set SHOW_LOGS=1 to enable /logs and /status/logs?tail=200 (readonly) for quick diagnostics.
  • Diagnostics page: set SHOW_DIAGNOSTICS=1 to enable the nav link and /diagnostics test tools.

Data integrity notes:

  • Banned cards: per-color/guild CSVs now consistently respect the Commander banned list using exact, caseinsensitive name matching across name and faceName.

🧪 Development and tests

Set up a virtual environment, install dependencies, and run the test suite.

# From repo root (PowerShell)
python -m venv .venv
& ".venv/Scripts/Activate.ps1"
pip install -r requirements.txt
pip install -r requirements-dev.txt

# Run tests (pytest is configured to look under code/tests)
python -m pytest -q

Notes:

  • Test discovery is set to code/tests in pytest.ini.
  • pytest.ini disables the built-in debugging plugin to avoid a stdlib module name clash with the project folder code/.
  • Feature flags for local diagnostics: set SHOW_DIAGNOSTICS=1 and/or SHOW_LOGS=1 to expose /diagnostics and /logs.

Whats new (quick summary)

  • Faster browsing with optional virtualized grids/lists in Step 5 and Owned (WEB_VIRTUALIZE=1).
  • Image polish: lazy-loading, responsive srcset/sizes, and LQIP blur/fade for Step 5, Owned, and commander preview.
  • Diagnostics overlay (opt-in with SHOW_DIAGNOSTICS=1): press v to see visible range, totals, render time, and counters.
  • Accessibility: respects reduced-motion (disables blur/fade and smooth scrolling).
  • Small caching wins: shortTTL fragment caching for summary partials and suggestions.

📦 Releases

  • Release notes are maintained in RELEASE_NOTES_TEMPLATE.md. Automated workflows read from this file to populate Docker Hub and GitHub releases.

Collecting diagnostics for an issue

  • Note the Request-ID from the toast or error page.
  • Copy logs from /logs (enable with SHOW_LOGS=1) or /status/logs?tail=200.
  • Include your /healthz JSON and environment flags (SHOW_LOGS/SHOW_DIAGNOSTICS/SHOW_SETUP) when reporting.

🧩 Owned cards library

  • Place .txt or .csv lists in owned_cards/ (one card name per line for .txt; any .csv with a name column works).
  • After commander selection, youll be prompted: "Use only owned cards?"
    • Yes: deck builds from owned cards only; if fewer than 100 cards, it stays incomplete and prints a note.
    • No: the build uses the full pool, but the final CSV marks owned cards with an Owned column.
  • If an owned-only build is incomplete, a recommendations file is exported: deck_files/[stem]_recommendations.csv and .txt. The app prints: "Recommended but unowned cards in deck_files/[stem]_recommendations.csv".
  • Web uploads: owned lists are parsed/enriched at upload-time, header rows are skipped, and duplicates are deduped. The enriched result is stored so subsequent page loads dont re-parse your files.

📄 License & help

  • License: MIT (see LICENSE)
  • Issues/Requests: GitHub Issues/Discussions
  • Docker details: see DOCKER.md