# Character List Scene - Implementation Plan

**Status:** Draft - Pending Approval
**Date:** November 20, 2025

---

## Overview

This document outlines the implementation plan for the Character List scene in the Godot client. This scene displays all characters owned by the authenticated user and allows them to select a character to play or create a new one.

---

## API Endpoint Reference

**Endpoint:** `GET /api/v1/characters`
**Authentication:** Required
**Source:** `api/app/api/characters.py:140-221`

### Response Format

```json
{
  "result": {
    "characters": [
      {
        "character_id": "char_001",
        "name": "Thorin Ironheart",
        "class": "vanguard",
        "class_name": "Vanguard",
        "level": 5,
        "experience": 250,
        "gold": 1000,
        "current_location": "town_square",
        "origin": "soul_revenant"
      }
    ],
    "count": 1,
    "tier": "free",
    "limit": 1
  }
}
```

---

## Scene Architecture

### File Structure

```
godot_client/
├── scenes/
│   └── character/
│       ├── character_list.tscn       # Main scene
│       └── character_card.tscn       # Reusable card component
└── scripts/
    └── character/
        ├── character_list.gd         # List controller
        └── character_card.gd         # Card component script
```

### Scene Hierarchy

```
CharacterList (Control)
├── VBoxContainer
│   ├── Header (HBoxContainer)
│   │   ├── TitleLabel ("Your Characters")
│   │   └── TierInfo (Label - "Free: 1/1")
│   ├── LoadingIndicator (CenterContainer)
│   │   └── ProgressIndicator
│   ├── ErrorContainer (MarginContainer)
│   │   └── ErrorLabel
│   ├── CharacterScrollContainer (ScrollContainer)
│   │   └── CharacterGrid (GridContainer or VBoxContainer)
│   │       └── [CharacterCard instances]
│   └── ActionContainer (HBoxContainer)
│       ├── CreateButton ("Create New Character")
│       └── RefreshButton
└── EmptyState (CenterContainer)
    └── EmptyStateCard
        ├── Icon
        ├── MessageLabel ("No characters yet")
        └── CreateFirstButton
```

---

## Implementation Steps

### Phase 1: Scene Setup

1. **Create character_list.tscn**
   - Root node: Control (full rect)
   - Add VBoxContainer with margins
   - Set up header with title and tier info
   - Add ScrollContainer for character list
   - Add action buttons at bottom

2. **Create character_card.tscn** (reusable component)
   - Use existing Card component as base
   - Layout: horizontal with left info, right actions
   - Display: name, class icon, level, gold, location
   - Buttons: Select, Delete

### Phase 2: Script Implementation

#### character_list.gd

```gdscript
extends Control
## Character List Screen
##
## Displays user's characters and allows selection or creation.

#region Signals
signal character_selected(character_id: String)
signal create_requested
#endregion

#region Node References
@onready var character_grid: Container = $VBoxContainer/CharacterScrollContainer/CharacterGrid
@onready var loading_indicator: Control = $VBoxContainer/LoadingIndicator
@onready var error_container: Control = $VBoxContainer/ErrorContainer
@onready var error_label: Label = $VBoxContainer/ErrorContainer/ErrorLabel
@onready var empty_state: Control = $EmptyState
@onready var tier_label: Label = $VBoxContainer/Header/TierInfo
@onready var create_button: Button = $VBoxContainer/ActionContainer/CreateButton
@onready var refresh_button: Button = $VBoxContainer/ActionContainer/RefreshButton
#endregion

#region Service References
@onready var http_client: Node = get_node("/root/HTTPClient")
@onready var state_manager: Node = get_node("/root/StateManager")
#endregion

#region Private Variables
var _characters: Array = []
var _is_loading: bool = false
var _tier: String = "free"
var _limit: int = 1
#endregion

#region Lifecycle
func _ready() -> void:
    _connect_signals()
    load_characters()

func _connect_signals() -> void:
    create_button.pressed.connect(_on_create_button_pressed)
    refresh_button.pressed.connect(_on_refresh_button_pressed)
#endregion

#region Public Methods
func load_characters() -> void:
    """Fetch characters from API."""
    if _is_loading:
        return

    _set_loading(true)
    _hide_error()

    http_client.http_get(
        "/api/v1/characters",
        _on_characters_loaded,
        _on_characters_error
    )

func refresh() -> void:
    """Refresh character list."""
    load_characters()
#endregion

#region Private Methods
func _set_loading(loading: bool) -> void:
    _is_loading = loading
    loading_indicator.visible = loading
    character_grid.visible = not loading
    refresh_button.disabled = loading

func _show_error(message: String) -> void:
    error_label.text = message
    error_container.visible = true

func _hide_error() -> void:
    error_container.visible = false

func _update_ui() -> void:
    # Update tier info
    tier_label.text = "%s: %d/%d" % [_tier.capitalize(), _characters.size(), _limit]

    # Enable/disable create button based on limit
    create_button.disabled = _characters.size() >= _limit

    # Show empty state or character list
    if _characters.is_empty():
        empty_state.visible = true
        character_grid.visible = false
    else:
        empty_state.visible = false
        character_grid.visible = true

func _populate_character_list() -> void:
    # Clear existing cards
    for child in character_grid.get_children():
        child.queue_free()

    # Create card for each character
    var card_scene = preload("res://scenes/character/character_card.tscn")

    for char_data in _characters:
        var card = card_scene.instantiate()
        card.set_character_data(char_data)
        card.selected.connect(_on_character_card_selected)
        card.delete_requested.connect(_on_character_delete_requested)
        character_grid.add_child(card)
#endregion

#region Signal Handlers
func _on_characters_loaded(response: APIResponse) -> void:
    _set_loading(false)

    if not response.is_success():
        _on_characters_error(response)
        return

    var result = response.result
    _characters = result.get("characters", [])
    _tier = result.get("tier", "free")
    _limit = result.get("limit", 1)

    _populate_character_list()
    _update_ui()

func _on_characters_error(response: APIResponse) -> void:
    _set_loading(false)

    var message = response.get_error_message()
    if message.is_empty():
        message = "Failed to load characters"

    if response.status == 401:
        # Redirect to login
        get_tree().change_scene_to_file("res://scenes/auth/login.tscn")
        return

    _show_error(message)

func _on_character_card_selected(character_id: String) -> void:
    state_manager.set_value("current_character_id", character_id)
    character_selected.emit(character_id)
    # Navigate to main game or character detail
    # get_tree().change_scene_to_file("res://scenes/game/main_game.tscn")

func _on_character_delete_requested(character_id: String) -> void:
    # Show confirmation dialog
    # Then call DELETE endpoint
    pass

func _on_create_button_pressed() -> void:
    create_requested.emit()
    # Navigate to character creation wizard
    # get_tree().change_scene_to_file("res://scenes/character/character_create.tscn")

func _on_refresh_button_pressed() -> void:
    refresh()
#endregion
```

#### character_card.gd

```gdscript
extends Control
class_name CharacterCard
## Character Card Component
##
## Displays a single character's summary information.

#region Signals
signal selected(character_id: String)
signal delete_requested(character_id: String)
#endregion

#region Node References
@onready var name_label: Label = $Card/HBox/InfoVBox/NameLabel
@onready var class_label: Label = $Card/HBox/InfoVBox/ClassLabel
@onready var level_label: Label = $Card/HBox/InfoVBox/LevelLabel
@onready var gold_label: Label = $Card/HBox/InfoVBox/GoldLabel
@onready var location_label: Label = $Card/HBox/InfoVBox/LocationLabel
@onready var select_button: Button = $Card/HBox/ActionVBox/SelectButton
@onready var delete_button: Button = $Card/HBox/ActionVBox/DeleteButton
#endregion

#region Private Variables
var _character_id: String = ""
var _character_data: Dictionary = {}
#endregion

#region Lifecycle
func _ready() -> void:
    select_button.pressed.connect(_on_select_pressed)
    delete_button.pressed.connect(_on_delete_pressed)
#endregion

#region Public Methods
func set_character_data(data: Dictionary) -> void:
    """Set character data and update display."""
    _character_data = data
    _character_id = data.get("character_id", "")

    name_label.text = data.get("name", "Unknown")
    class_label.text = data.get("class_name", "Unknown Class")
    level_label.text = "Level %d" % data.get("level", 1)
    gold_label.text = "%d Gold" % data.get("gold", 0)
    location_label.text = _format_location(data.get("current_location", ""))

func get_character_id() -> String:
    return _character_id
#endregion

#region Private Methods
func _format_location(location_id: String) -> String:
    # Convert location_id to display name
    return location_id.replace("_", " ").capitalize()
#endregion

#region Signal Handlers
func _on_select_pressed() -> void:
    selected.emit(_character_id)

func _on_delete_pressed() -> void:
    delete_requested.emit(_character_id)
#endregion
```

### Phase 3: UI Polish

1. **Styling**
   - Use Card component for character cards
   - Apply theme colors from ThemeColors
   - Add class icons (placeholder or loaded)
   - Add hover/focus states

2. **Animations**
   - Fade in cards on load
   - Button press feedback
   - Loading spinner animation

3. **Responsive Layout**
   - GridContainer for desktop (2-3 columns)
   - VBoxContainer for mobile (single column)
   - Handle different screen sizes

### Phase 4: Integration

1. **Navigation Flow**
   - Login success → Character List
   - Character selected → Main Game
   - Create button → Character Creation Wizard
   - Empty state create → Character Creation Wizard

2. **State Management**
   - Store selected character_id in StateManager
   - Handle session expiration (401 errors)

3. **Delete Confirmation**
   - Add confirmation dialog component
   - Call DELETE /api/v1/characters/{id}
   - Refresh list on success

---

## UI Mockup

```
┌─────────────────────────────────────────┐
│  Your Characters              Free: 2/3 │
├─────────────────────────────────────────┤
│                                         │
│  ┌─────────────────────────────────┐    │
│  │ Thorin Ironheart     [Select]   │    │
│  │ Vanguard • Level 5              │    │
│  │ 1,000 Gold                      │    │
│  │ Town Square          [Delete]   │    │
│  └─────────────────────────────────┘    │
│                                         │
│  ┌─────────────────────────────────┐    │
│  │ Elara Shadowstep     [Select]   │    │
│  │ Assassin • Level 3              │    │
│  │ 500 Gold                        │    │
│  │ Dark Forest          [Delete]   │    │
│  └─────────────────────────────────┘    │
│                                         │
├─────────────────────────────────────────┤
│  [Create New Character]    [Refresh]    │
└─────────────────────────────────────────┘
```

---

## Error Handling

| Error Type | User Message | Action |
|------------|--------------|--------|
| 401 Unauthorized | "Session expired" | Redirect to login |
| 500 Server Error | "Server error. Try again." | Show retry button |
| Network Error | "Cannot connect to server" | Show retry button |
| Empty Response | "No characters found" | Show empty state |

---

## Testing Checklist

- [ ] Characters load on scene ready
- [ ] Loading indicator shows during fetch
- [ ] Error message displays on API error
- [ ] 401 redirects to login
- [ ] Empty state shows when no characters
- [ ] Tier info updates correctly
- [ ] Create button disabled when at limit
- [ ] Select button navigates correctly
- [ ] Delete shows confirmation
- [ ] Delete refreshes list on success
- [ ] Refresh button reloads list

---

## Dependencies

- **Existing:** HTTPClient, StateManager, APIResponse, Card component
- **New:** CharacterCard component, ConfirmDialog (for delete)

---

## Estimated Effort

- Phase 1 (Scene Setup): 1-2 hours
- Phase 2 (Scripts): 2-3 hours
- Phase 3 (UI Polish): 1-2 hours
- Phase 4 (Integration): 1-2 hours

**Total:** 5-9 hours

---

## Notes

- Consider caching character list in StateManager for faster navigation
- Add pull-to-refresh for mobile
- Consider adding character preview image in future
- Location names should come from a mapping (not raw IDs)
