mantimon-tcg/frontend/project_plans/PHASE_F2_deck_management.json
Cal Corum 059536a42b Implement frontend phases F1-F3: auth, deck management, Phaser integration
Phase F1 - Authentication:
- OAuth callback handling with token management
- Auth guards for protected routes
- Account linking composable
- Profile page updates

Phase F2 - Deck Management:
- Collection page with card filtering and display
- Decks page with CRUD operations
- Deck builder with drag-drop support
- Collection and deck Pinia stores

Phase F3 - Phaser Integration:
- Game bridge composable for Vue-Phaser communication
- Game page with Phaser canvas mounting
- Socket.io event types for real-time gameplay
- Game store with match state management
- Phaser scene scaffolding and type definitions

Also includes:
- New UI components (ConfirmDialog, EmptyState, FilterBar, etc.)
- Toast notification system
- Game config composable for dynamic rule loading
- Comprehensive test coverage for new features
2026-01-31 15:43:56 -06:00

625 lines
28 KiB
JSON

{
"meta": {
"phaseId": "PHASE_F2",
"name": "Deck Management",
"version": "1.3.0",
"created": "2026-01-30",
"lastUpdated": "2026-01-31",
"totalTasks": 12,
"completedTasks": 12,
"status": "COMPLETE",
"completedDate": "2026-01-31",
"auditStatus": "PASSED",
"auditDate": "2026-01-31",
"auditNotes": "0 issues, 3 minor warnings. All previous issues resolved: DeckContents refactored (527->292 lines), hardcoded values replaced with useGameConfig, toast feedback added, validation errors surfaced.",
"description": "Collection viewing, deck building, and card management with drag-and-drop deck editor and real-time validation.",
"designReference": "src/styles/DESIGN_REFERENCE.md"
},
"stylingPrinciples": [
"Follow patterns in DESIGN_REFERENCE.md for visual consistency",
"Type-colored accents on all card components (borders, badges)",
"Hover states with scale/shadow transitions on interactive cards",
"Skeleton loaders for all loading states (not spinners)",
"Empty states with icon, message, and CTA",
"Mobile-first responsive grids",
"Business rules (deck size, card limits) from constants/API, never hardcoded"
],
"dependencies": {
"phases": ["PHASE_F0", "PHASE_F1"],
"backend": [
"GET /api/collections/me - Get user's card collection",
"GET /api/collections/me/cards/{card_id} - Get specific card quantity",
"GET /api/decks - List user's decks",
"POST /api/decks - Create new deck",
"GET /api/decks/{id} - Get deck by ID",
"PUT /api/decks/{id} - Update deck",
"DELETE /api/decks/{id} - Delete deck",
"POST /api/decks/validate - Validate deck without saving",
"GET /api/cards/definitions - Get card definitions (for display)"
]
},
"tasks": [
{
"id": "F2-001",
"name": "Create collection and deck stores",
"description": "Create Pinia stores for managing collection and deck state",
"category": "stores",
"priority": 1,
"completed": true,
"tested": true,
"dependencies": [],
"files": [
{"path": "src/stores/collection.ts", "status": "create"},
{"path": "src/stores/collection.spec.ts", "status": "create"},
{"path": "src/stores/deck.ts", "status": "create"},
{"path": "src/stores/deck.spec.ts", "status": "create"}
],
"details": [
"Create useCollectionStore with state: cards (CollectionCard[]), isLoading, error",
"Add getters: totalCards, uniqueCards, getCardQuantity(definitionId)",
"Add actions: fetchCollection(), clearCollection()",
"Create useDeckStore with state: decks (Deck[]), currentDeck, isLoading, error",
"Add getters: deckCount, getDeckById(id), starterDeck",
"Add actions: fetchDecks(), fetchDeck(id), createDeck(), updateDeck(), deleteDeck(), setCurrentDeck()",
"Follow setup store pattern from auth.ts",
"Do NOT fetch via apiClient directly in store - just state management"
],
"acceptance": [
"Both stores created with typed state",
"Stores follow setup store pattern",
"Unit tests cover all getters and actions",
"Stores integrate with existing type definitions"
]
},
{
"id": "F2-002",
"name": "Create useCollection composable",
"description": "Composable for fetching and managing the user's card collection",
"category": "composables",
"priority": 2,
"completed": true,
"tested": true,
"dependencies": ["F2-001"],
"files": [
{"path": "src/composables/useCollection.ts", "status": "create"},
{"path": "src/composables/useCollection.spec.ts", "status": "create"}
],
"details": [
"Wrap collection store with loading/error handling",
"Implement fetchCollection() calling GET /api/collections/me",
"Parse response into CollectionCard[] format",
"Handle empty collection gracefully",
"Provide filtering helpers: byType(type), byCategory(category), byRarity(rarity)",
"Provide search helper: searchByName(query)",
"Return readonly state wrappers per composable pattern",
"Handle network errors with user-friendly messages"
],
"acceptance": [
"Composable fetches collection from API",
"Filtering and search work correctly",
"Loading and error states properly managed",
"Tests cover API success, empty collection, and errors"
]
},
{
"id": "F2-003",
"name": "Create useDecks composable",
"description": "Composable for CRUD operations on user decks",
"category": "composables",
"priority": 3,
"completed": true,
"tested": true,
"dependencies": ["F2-001"],
"files": [
{"path": "src/composables/useDecks.ts", "status": "create"},
{"path": "src/composables/useDecks.spec.ts", "status": "create"}
],
"details": [
"Wrap deck store with loading/error handling",
"Implement fetchDecks() calling GET /api/decks",
"Implement fetchDeck(id) calling GET /api/decks/{id}",
"Implement createDeck(data) calling POST /api/decks",
"Implement updateDeck(id, data) calling PUT /api/decks/{id}",
"Implement deleteDeck(id) calling DELETE /api/decks/{id} with confirmation",
"Return result objects: { success, error?, data? }",
"Handle 404 (deck not found), 403 (not owner), validation errors",
"Track separate loading states for different operations if needed"
],
"acceptance": [
"All CRUD operations work correctly",
"Error handling covers common cases",
"Result objects returned consistently",
"Tests cover success and error paths for each operation"
]
},
{
"id": "F2-004",
"name": "Create card display components",
"description": "Reusable components for displaying cards at various sizes",
"category": "components",
"priority": 4,
"completed": true,
"tested": true,
"dependencies": [],
"files": [
{"path": "src/components/cards/CardImage.vue", "status": "create"},
{"path": "src/components/cards/CardDisplay.vue", "status": "create"},
{"path": "src/components/cards/CardDisplay.spec.ts", "status": "create"},
{"path": "src/components/cards/TypeBadge.vue", "status": "create"}
],
"details": [
"CardImage: Simple img wrapper with loading state and fallback",
"CardImage props: src (string), alt (string), size ('sm' | 'md' | 'lg')",
"CardImage: Handle image load error with placeholder",
"CardDisplay: Full card component with name, HP, type badge",
"CardDisplay props: card (CardDefinition), size, showQuantity (number), selectable, selected, disabled",
"CardDisplay emits: click, select",
"TypeBadge: Reusable type indicator with icon and colored background",
"Apply type-colored border based on card.type (use type color mapping from DESIGN_REFERENCE)",
"Mobile-friendly touch targets (min 44px)",
"Support thumbnail (grid), medium (list), large (detail) sizes"
],
"styling": [
"Use 'Card with Type Accent' pattern from DESIGN_REFERENCE.md",
"Hover: scale-[1.02], -translate-y-1, shadow-xl with transition-all duration-200",
"Selected: ring-2 ring-primary ring-offset-2 ring-offset-background",
"Disabled: opacity-50 grayscale cursor-not-allowed",
"Quantity badge: absolute positioned, bg-primary, rounded-full (see 'Quantity Badge' pattern)",
"Type badge: inline-flex with type background color (see 'Type Badge' pattern)",
"Card container: bg-surface rounded-xl border-2 shadow-md",
"Image loading: pulse animation placeholder, smooth fade-in on load"
],
"acceptance": [
"CardImage handles loading and errors gracefully",
"CardDisplay shows card info with type-appropriate styling",
"Three sizes work correctly",
"Hover and selection states visible and smooth",
"Disabled state clearly distinguishable",
"Tests verify rendering and events"
]
},
{
"id": "F2-005",
"name": "Create card detail modal",
"description": "Full card view with all details in a modal overlay",
"category": "components",
"priority": 5,
"completed": true,
"tested": true,
"dependencies": ["F2-004"],
"files": [
{"path": "src/components/cards/CardDetailModal.vue", "status": "create"},
{"path": "src/components/cards/CardDetailModal.spec.ts", "status": "create"},
{"path": "src/components/cards/AttackDisplay.vue", "status": "create"},
{"path": "src/components/cards/EnergyCost.vue", "status": "create"}
],
"details": [
"Props: card (CardDefinition | null), isOpen (boolean), ownedQuantity (number)",
"Emits: close",
"Large card image with CardImage component",
"Display all card fields: name, HP, type, category, rarity",
"AttackDisplay: Reusable attack row with energy cost, name, damage, effect",
"EnergyCost: Row of energy type icons for attack costs",
"Display weakness, resistance, retreat cost",
"Display owned quantity badge",
"Display set info (setId, setNumber)",
"Use Teleport to render in body",
"Close on backdrop click, Escape key, or close button",
"Trap focus within modal for accessibility",
"Animate in/out with transition"
],
"styling": [
"Backdrop: bg-black/60 backdrop-blur-sm (see 'Modal Backdrop' pattern)",
"Container: bg-surface rounded-2xl shadow-2xl max-w-lg (see 'Modal Container' pattern)",
"Header: border-b border-surface-light with close button (see 'Modal Header' pattern)",
"Enter animation: fade-in zoom-in-95 duration-200",
"Card image: centered with type-colored border glow effect",
"Attack rows: bg-surface-light/50 rounded-lg p-3 with hover highlight",
"Energy icons: small colored circles or actual energy symbols",
"Stats section: grid layout for weakness/resistance/retreat",
"Owned quantity: prominent badge in corner of card image area"
],
"acceptance": [
"Modal displays all card information",
"Attacks rendered with energy costs",
"Owned quantity visible",
"Close methods all work",
"Smooth enter/exit animations",
"Keyboard accessible (Escape closes, focus trapped)"
]
},
{
"id": "F2-006",
"name": "Create collection page",
"description": "Grid view of owned cards with filtering and search",
"category": "pages",
"priority": 6,
"completed": true,
"tested": true,
"dependencies": ["F2-002", "F2-004", "F2-005"],
"files": [
{"path": "src/pages/CollectionPage.vue", "status": "create"},
{"path": "src/pages/CollectionPage.spec.ts", "status": "create"},
{"path": "src/components/ui/FilterBar.vue", "status": "create"},
{"path": "src/components/ui/SkeletonCard.vue", "status": "create"},
{"path": "src/components/ui/EmptyState.vue", "status": "create"}
],
"details": [
"Fetch collection on mount using useCollection()",
"Display cards in responsive grid (2 cols mobile, 3 md, 4 lg, 5 xl)",
"FilterBar component: type dropdown, category dropdown, rarity dropdown, search input",
"Show card count: 'Showing X of Y cards'",
"Each card shows quantity badge overlay",
"Click card to open CardDetailModal",
"EmptyState component: reusable empty state with icon, message, CTA",
"SkeletonCard component: reusable loading placeholder",
"Loading state: grid of SkeletonCards (match expected count or 8-12)",
"Error state: inline error with retry button",
"Debounce search input (300ms)",
"Filters persist in URL query params for sharing/bookmarking"
],
"styling": [
"Page header: text-2xl font-bold with card count subtitle",
"Filter bar: bg-surface rounded-xl p-4 mb-6 (see 'Filter Bar' pattern)",
"Search input: with SearchIcon, focus:border-primary focus:ring-1",
"Dropdowns: matching input styling for consistency",
"Grid: gap-3 md:gap-4, cards fill width of column",
"Skeleton cards: pulse animation, match card aspect ratio (see 'Skeleton Card' pattern)",
"Empty state: centered, py-16, icon + message + CTA (see 'Empty States' pattern)",
"Error state: bg-error/10 border-error/20 with AlertIcon (see 'Inline Error' pattern)",
"Smooth fade-in when cards load (transition-opacity)"
],
"acceptance": [
"Collection grid displays all owned cards",
"All filters work correctly",
"Search filters by card name",
"Card modal opens on click",
"Loading state shows skeleton grid",
"Empty state is visually polished",
"Error state allows retry",
"Mobile responsive"
]
},
{
"id": "F2-007",
"name": "Create deck list page",
"description": "View all user decks with create/edit/delete actions",
"category": "pages",
"priority": 7,
"completed": true,
"tested": true,
"dependencies": ["F2-003"],
"files": [
{"path": "src/pages/DecksPage.vue", "status": "create"},
{"path": "src/pages/DecksPage.spec.ts", "status": "create"},
{"path": "src/components/deck/DeckCard.vue", "status": "create"},
{"path": "src/components/deck/DeckCard.spec.ts", "status": "create"},
{"path": "src/components/ui/ConfirmDialog.vue", "status": "create"}
],
"details": [
"Fetch decks on mount using useDecks()",
"Display decks as cards in a grid (1 col mobile, 2 sm, 3 lg)",
"DeckCard component shows: name, card count (from API), validation status icon, starter badge",
"DeckCard shows mini preview of 3-4 Pokemon thumbnails from deck",
"Click deck to navigate to deck editor (/decks/:id)",
"'Create New Deck' button in header -> navigate to /decks/new",
"Delete button (trash icon) with ConfirmDialog",
"ConfirmDialog: reusable confirmation modal component",
"Show deck limit from API response: 'X / Y decks'",
"Empty state: 'No decks yet. Create your first deck!' with + button",
"Loading state: skeleton DeckCards",
"Starter deck: show badge, hide/disable delete button"
],
"styling": [
"Page header: flex justify-between with title and 'New Deck' button",
"New Deck button: btn-primary with PlusIcon",
"Grid: gap-4, cards have consistent height",
"DeckCard: bg-surface rounded-xl p-4 shadow-md hover:shadow-lg transition",
"DeckCard hover: subtle lift effect (translate-y, shadow)",
"Card preview: row of 3-4 mini card images (32x44px) with overlap",
"Validity icon: CheckCircle (text-success) or XCircle (text-error)",
"Starter badge: small pill 'Starter' with bg-primary/20 text-primary",
"Delete button: icon-only, text-text-muted hover:text-error, positioned top-right",
"ConfirmDialog: danger variant with red confirm button",
"Skeleton: match DeckCard dimensions with pulse animation",
"Empty state: centered with DeckIcon, use EmptyState component"
],
"acceptance": [
"All user decks displayed in polished cards",
"Can create new deck (navigates to builder)",
"Can delete non-starter decks with confirmation",
"Deck validity status clearly visible",
"Starter deck visually distinguished",
"Loading and empty states polished"
]
},
{
"id": "F2-008",
"name": "Create useDeckBuilder composable",
"description": "Composable for deck editing state and validation",
"category": "composables",
"priority": 8,
"completed": true,
"tested": true,
"dependencies": ["F2-003"],
"files": [
{"path": "src/composables/useDeckBuilder.ts", "status": "create"},
{"path": "src/composables/useDeckBuilder.spec.ts", "status": "create"}
],
"details": [
"Manage draft deck state (not yet saved)",
"State: deckName, deckCards (Map<cardDefinitionId, quantity>), energyConfig",
"Track isDirty (has unsaved changes)",
"addCard(cardDefinitionId, quantity=1) - respect 4-card limit per card (except basic energy)",
"removeCard(cardDefinitionId, quantity=1)",
"setDeckName(name)",
"clearDeck() - reset to empty",
"loadDeck(deck) - populate from existing deck for editing",
"Computed: totalCards, cardList (sorted), canAddCard(cardId)",
"Validation: call POST /api/decks/validate with debounce (500ms)",
"Track validationErrors array, isValid computed",
"save() - calls createDeck or updateDeck based on isNew flag"
],
"acceptance": [
"Can add/remove cards with limits enforced",
"Draft state separate from persisted decks",
"Validation runs on changes with debounce",
"isDirty tracks unsaved changes",
"save() creates or updates correctly"
]
},
{
"id": "F2-009",
"name": "Create deck builder page",
"description": "Two-panel deck editor with collection picker and deck contents",
"category": "pages",
"priority": 9,
"completed": true,
"tested": true,
"dependencies": ["F2-002", "F2-004", "F2-008"],
"files": [
{"path": "src/pages/DeckBuilderPage.vue", "status": "create"},
{"path": "src/pages/DeckBuilderPage.spec.ts", "status": "create"},
{"path": "src/components/deck/DeckEditor.vue", "status": "create"},
{"path": "src/components/deck/DeckContents.vue", "status": "create"},
{"path": "src/components/deck/CollectionPicker.vue", "status": "create"},
{"path": "src/components/deck/DeckCardRow.vue", "status": "create"},
{"path": "src/components/ui/ProgressBar.vue", "status": "create"}
],
"details": [
"Route: /decks/new (create) or /decks/:id (edit)",
"Load existing deck if editing (from route param)",
"Two-panel layout: CollectionPicker (left/top) + DeckContents (right/bottom)",
"Mobile: Stack panels vertically with tab switcher (Collection | Deck)",
"CollectionPicker: Filterable collection grid, click/tap to add card to deck",
"DeckContents: Scrollable list of cards with quantity controls (+/-)",
"DeckCardRow: Single card row with thumbnail, name, quantity stepper",
"Show cards grayed out if not available (quantity in collection exhausted)",
"Deck name input at top (editable)",
"ProgressBar: Reusable progress component with current/target props",
"Card count progress bar showing current/target from API/config",
"Validation errors displayed inline below progress bar",
"Save button (disabled if invalid or not dirty)",
"Cancel button with unsaved changes confirmation",
"Energy configuration section (collapsible, basic energy type buttons)"
],
"styling": [
"Page layout: sticky header with name input + actions, scrollable content below",
"Header: bg-surface border-b, flex items-center justify-between p-4",
"Deck name input: text-xl font-bold, minimal border (border-transparent focus:border-primary)",
"Two-panel: lg:flex lg:gap-6, CollectionPicker lg:w-2/3, DeckContents lg:w-1/3",
"Mobile tabs: sticky below header, bg-surface-light rounded-lg p-1, active tab bg-surface",
"CollectionPicker: bg-surface rounded-xl p-4, includes FilterBar and card grid",
"Cards in picker: show remaining quantity badge, disabled state if exhausted",
"DeckContents: bg-surface rounded-xl p-4, sticky on desktop",
"DeckCardRow: flex items-center gap-3, small thumbnail, name, quantity stepper",
"Quantity stepper: rounded-lg border, - and + buttons with number between",
"Progress bar: use 'Card Count Bar' pattern from DESIGN_REFERENCE (current/target props)",
"Validation errors: use 'Inline Error' pattern, list format if multiple",
"Save button: btn-primary, Cancel: btn-secondary",
"Energy section: collapsible with ChevronIcon, grid of energy type buttons"
],
"acceptance": [
"Can create new deck from scratch",
"Can edit existing deck",
"Collection filtering works in picker",
"Cannot add more cards than owned",
"Card limits enforced (from API validation)",
"Validation errors shown clearly with proper styling",
"Save/cancel work correctly with appropriate feedback",
"Unsaved changes prompt on navigation",
"Mobile tab switching works smoothly"
]
},
{
"id": "F2-010",
"name": "Add drag-and-drop support",
"description": "Optional drag-and-drop for adding/removing cards",
"category": "components",
"priority": 10,
"completed": true,
"tested": true,
"dependencies": ["F2-009"],
"files": [
{"path": "src/composables/useDragDrop.ts", "status": "create"},
{"path": "src/composables/useDragDrop.spec.ts", "status": "create"},
{"path": "src/components/deck/DeckEditor.vue", "status": "modify"},
{"path": "src/components/deck/CollectionPicker.vue", "status": "modify"},
{"path": "src/components/deck/DeckContents.vue", "status": "modify"}
],
"details": [
"Use HTML5 Drag and Drop API (no library needed)",
"Create useDragDrop composable for drag state management",
"Track: isDragging, draggedCard, dropTarget",
"Make collection cards draggable (draggable='true')",
"DeckContents as drop target",
"Touch support: use long-press (500ms) to initiate drag on mobile",
"Fallback: click-to-add still works (drag is enhancement)",
"Drop on collection area to remove from deck (or drag out of drop zone)",
"Accessible: all actions available via click as well"
],
"styling": [
"Dragging card: opacity-50 on original, cursor-grabbing",
"Drag ghost: slightly rotated (rotate-3), scale-105, shadow-2xl",
"Valid drop target: ring-2 ring-primary ring-dashed, bg-primary/5",
"Invalid drop target (card limit reached): ring-2 ring-error ring-dashed, bg-error/5",
"Drop zone highlight: animate pulse when dragging over",
"Card being dragged over deck: subtle insert indicator line",
"Long-press feedback on touch: scale down slightly before drag starts",
"Transition all visual states smoothly (duration-150)"
],
"acceptance": [
"Can drag cards from collection to deck",
"Can drag cards out of deck to remove",
"Visual feedback clearly indicates valid/invalid drops",
"Click-to-add still works as fallback",
"Touch devices can use long-press",
"All animations are smooth"
],
"notes": "This is an enhancement - if time constrained, click-to-add/remove is sufficient for MVP"
},
{
"id": "F2-011",
"name": "Add routes and navigation",
"description": "Configure routes for collection and deck pages",
"category": "setup",
"priority": 11,
"completed": true,
"tested": true,
"dependencies": ["F2-006", "F2-007", "F2-009"],
"files": [
{"path": "src/router/index.ts", "status": "modify"}
],
"details": [
"Add /collection route -> CollectionPage",
"Add /decks route -> DecksPage",
"Add /decks/new route -> DeckBuilderPage (create mode)",
"Add /decks/:id route -> DeckBuilderPage (edit mode)",
"All routes require auth and starter deck (meta: requiresAuth, requiresStarter)",
"Lazy load pages for code splitting",
"Add navigation guard for unsaved deck changes"
],
"acceptance": [
"All routes work correctly",
"Routes protected by auth",
"Lazy loading configured",
"Navigation warns about unsaved changes in deck builder"
]
},
{
"id": "F2-012",
"name": "Update types and API contracts",
"description": "Ensure frontend types match backend API contracts",
"category": "api",
"priority": 12,
"completed": true,
"tested": true,
"dependencies": ["F2-001"],
"files": [
{"path": "src/types/index.ts", "status": "modify"},
{"path": "src/types/api.ts", "status": "create"}
],
"details": [
"Review backend schemas: deck.py, collection.py, card.py",
"Ensure Deck, DeckCard, CollectionCard types match response shapes",
"Add API request/response types: DeckCreateRequest, DeckUpdateRequest",
"Add validation types: DeckValidationResponse, ValidationError",
"Add energy configuration types: EnergyConfig, EnergyCard",
"Export all types from index.ts",
"Document any frontend-only derived types"
],
"acceptance": [
"Frontend types match backend API exactly",
"All API operations are type-safe",
"No runtime type mismatches"
]
}
],
"apiContracts": {
"collection": {
"get": {
"endpoint": "GET /api/collections/me",
"response": {
"total_unique_cards": "number",
"total_card_count": "number",
"entries": [
{
"card_definition_id": "string",
"quantity": "number",
"source": "string",
"obtained_at": "ISO datetime"
}
]
}
}
},
"decks": {
"list": {
"endpoint": "GET /api/decks",
"response": {
"decks": ["DeckResponse[]"],
"deck_count": "number",
"deck_limit": "number"
}
},
"get": {
"endpoint": "GET /api/decks/{id}",
"response": "DeckResponse"
},
"create": {
"endpoint": "POST /api/decks",
"request": {
"name": "string",
"description": "string | null",
"cards": "Record<cardDefinitionId, quantity>",
"energy_cards": "Record<energyType, quantity>",
"deck_config": "DeckConfig | null"
},
"response": "DeckResponse"
},
"update": {
"endpoint": "PUT /api/decks/{id}",
"request": "Partial<DeckCreateRequest>",
"response": "DeckResponse"
},
"delete": {
"endpoint": "DELETE /api/decks/{id}",
"response": "204 No Content"
},
"validate": {
"endpoint": "POST /api/decks/validate",
"request": {
"cards": "Record<cardDefinitionId, quantity>",
"energy_cards": "Record<energyType, quantity>"
},
"response": {
"is_valid": "boolean",
"errors": ["string[]"]
}
}
},
"deckResponse": {
"id": "UUID",
"name": "string",
"description": "string | null",
"cards": "Record<cardDefinitionId, quantity>",
"energy_cards": "Record<energyType, quantity>",
"is_valid": "boolean",
"validation_errors": ["string[]"],
"is_starter": "boolean",
"starter_type": "string | null",
"created_at": "ISO datetime",
"updated_at": "ISO datetime"
}
},
"notes": [
"F2-003 (starter selection) was already implemented in F1 - this phase focuses on post-starter deck management",
"Drag-and-drop (F2-010) is an enhancement - click-based editing is sufficient for MVP",
"Energy configuration may be simplified if backend doesn't require it yet",
"Card images may need placeholder/fallback handling if CDN not yet configured",
"Consider virtualized scrolling for large collections in future optimization",
"All UI tasks include 'styling' arrays - follow patterns in src/styles/DESIGN_REFERENCE.md",
"Create reusable UI components (EmptyState, SkeletonCard, ConfirmDialog, ProgressBar, FilterBar) for consistency"
]
}