Deck actions UI, consistent icon buttons, native select fix, sidebar stats

- DeckView: rename/duplicate/delete deck via row-hover icon actions, with
  a ConfirmDialog for delete and PromptDialog (with friendly name-
  collision errors) for rename/duplicate.
- Add a shared IconButton (plus overlay/danger variants) and replace
  every ad hoc close/remove button in the app with it: deck actions,
  remove-card-from-deck (list and tile), clear-commander, the card-
  preview close, and the ingest-progress dismiss.
- Fix native <select> (Group by, Format, In deck): only color/border were
  overridden, leaving native OS chrome (height, arrow, popup) in place.
  Drop appearance and draw a custom chevron so they match text inputs.
- Click-to-preview: show both faces of a double-faced card side by side
  on wide viewports instead of requiring Flip, and size the preview
  images larger overall.
- Sidebar footer: replace the plain "N cards" line with a "Your library"
  panel (unique/copies/decks tiles + a WUBRG color-distribution bar),
  polling get_library_stats while card data is loaded.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 21:21:47 -05:00
parent 3e41c9d055
commit e57c3dec51
7 changed files with 760 additions and 94 deletions

View File

@@ -55,6 +55,14 @@ export interface Status {
card_count: number;
}
export interface LibraryStats {
unique_cards: number;
total_copies: number;
deck_count: number;
color_counts: [number, number, number, number, number]; // W, U, B, R, G
colorless_count: number;
}
export interface IngestStageInfo {
id: string;
label: string;
@@ -104,11 +112,15 @@ export const api = {
searchLibrary: (filter: LibraryFilter) =>
invoke<LibraryCard[]>("search_library", { filter }),
getLibraryStats: () => invoke<LibraryStats>("get_library_stats"),
listDecks: () => invoke<Deck[]>("list_decks"),
createDeck: (name: string, commanderOracleId: string | null) =>
invoke<Deck>("create_deck", { name, commanderOracleId }),
deleteDeck: (deckId: number) => invoke<void>("delete_deck", { deckId }),
renameDeck: (deckId: number, name: string) => invoke<void>("rename_deck", { deckId, name }),
duplicateDeck: (deckId: number, newName: string) =>
invoke<Deck>("duplicate_deck", { deckId, newName }),
setDeckFormat: (deckId: number, format: string) =>
invoke<void>("set_deck_format", { deckId, format }),
setDeckCommander: (deckId: number, oracleId: string | null) =>