v23: React + TypeScript SPA migration

Complete frontend rewrite from vanilla HTML/CSS/JS to React 19 + TypeScript + Tailwind CSS v4.

Frontend (frontend/commander-ai-lab-ui/):
- Vite + React 19 + TypeScript + Tailwind CSS v4 + React Router
- 7 pages: BatchSim, Collection, Decks, AutoGen, Simulator, Coach, Training
- Reusable components: Spinner, StatusBadge, CardImage, ManaSymbols, EmptyState
- Full typed API client layer (8 modules matching all backend endpoints)
- Recharts for mana curves, win rate charts, progress bars
- Lucide icons throughout
- Custom dark theme with MTG mana color tokens
- Code splitting (recharts + react in separate chunks)

Backend (lab_api.py):
- SPA-aware static file serving with catch-all for client-side routing
- Fallback to legacy ui/ folder if React dist not built
- Updated startup log with SPA route list

Build system:
- .gitignore added (was missing - removes pyc, db, node_modules, etc.)
- build-full-installer-v2.py: v23 with Node.js check + React build step
- Vite build config with manual chunks for optimal loading

Also includes: V3 deck generator fixes, coach improvements, DeepSeek sim modules
This commit is contained in:
KoalaTrapLord committed 2026-03-12 01:12:02 +00:00
1 parent e6b5e0cedb
commit a5fe8cb3fa
376 files changed
+18367 -22656

No files matched your search

@@ -701,8 +701,20 @@ public class BatchRunner {
try {
Path path = Path.of(deckFilePath);
if (!Files.exists(path)) {
System.err.println("[COACH] Deck file not found: " + deckFilePath);
return names;
// Try adding .dck extension
Path withExt = Path.of(deckFilePath + ".dck");
if (Files.exists(withExt)) {
path = withExt;
} else {
// Try resolving against Forge commander decks directory
Path resolved = resolveForgeDecksPath(deckFilePath);
if (resolved != null && Files.exists(resolved)) {
path = resolved;
} else {
System.err.println("[COACH] Deck file not found: " + deckFilePath);
return names;
}
}
}
List<String> lines = Files.readAllLines(path);
String section = "Main";
@@ -737,6 +749,36 @@ public class BatchRunner {
return names;
}
/**
* Resolve a deck name to its full path in the Forge commander decks directory.
* Checks %APPDATA%/Forge/decks/commander/ on Windows.
*/
private Path resolveForgeDecksPath(String deckName) {
try {
String appdata = System.getenv("APPDATA");
if (appdata == null || appdata.isEmpty()) return null;
Path decksDir = Path.of(appdata, "Forge", "decks", "commander");
if (!Files.isDirectory(decksDir)) return null;
// Try with .dck extension
String name = deckName;
if (!name.endsWith(".dck")) name = name + ".dck";
Path candidate = decksDir.resolve(name);
if (Files.exists(candidate)) return candidate;
// Case-insensitive fallback
String lowerName = name.toLowerCase();
try (var stream = Files.list(decksDir)) {
return stream
.filter(p -> p.getFileName().toString().toLowerCase().equals(lowerName))
.findFirst()
.orElse(null);
}
} catch (Exception e) {
return null;
}
}
/**
* Build per-card game stats from verbose Forge log output.
*