# Event Avatars & Location Images Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Add character avatars and optional location images to event displays in the iOS app

**Architecture:** Extend backend event classes to include serialized character data and location images, update iOS models to parse this data, and enhance UI components to render avatars and location overlays.

**Tech Stack:** Python (backend), Swift/SwiftUI (iOS), WebSocket communication

**Design Document:** `docs/plans/2025-11-13-event-avatars-locations-design.md`

---

## Task 1: Backend - Add characters field to messageEvent class

**Files:**
- Modify: `ws/events/base.py:26-39`

**Step 1: Read current messageEvent class**

Check the existing structure in `ws/events/base.py`.

**Step 2: Add characters field to messageEvent.__init__**

In `ws/events/base.py`, update the `messageEvent` class:

```python
class messageEvent:
    def __init__(self):
        self.id = ""
        self.date = ""
        self.hour = ""
        self.type = 'messageEvent'
        self.energyCost = 0
        self.diamondCost = 0
        self.moneyCost = 0
        self.affinityChange = 0
        self.message = ""
        self.title = ""
        self.image = ""
        self.characters = []      # NEW: Array of character data
```

**Step 3: Verify syntax**

Run: `python3 -m py_compile ws/events/base.py`
Expected: No errors

**Step 4: Commit**

```bash
git add ws/events/base.py
git commit -m "feat: add characters field to messageEvent class"
```

---

## Task 2: Backend - Update messageFunction to serialize characters

**Files:**
- Modify: `ws/events/base.py:65-80`

**Step 1: Update messageFunction signature**

In `ws/events/base.py`, update the `messageFunction` to accept characters parameter:

```python
def messageFunction(fname,message,player=False,check=False,title="",image="",energyCost = 0,diamondCost = 0, moneyCost = 0,affinityChange = 0,characters=None):
    if (check):
        m = messageEvent()
        m.id = fname
        m.message = message
        m.date = player.date
        m.hour = player.hourOfDay
        m.title = title
        m.image = image
        m.energyCost = energyCost
        m.diamondCost = diamondCost
        m.moneyCost = moneyCost
        m.affinityChange = affinityChange

        # NEW: Serialize characters to reduce payload size
        if characters:
            m.characters = [
                {
                    'id': char.id,
                    'firstname': char.firstname,
                    'lastname': char.lastname,
                    'image': char.image
                }
                for char in characters
            ]

        return m
```

**Step 2: Verify syntax**

Run: `python3 -m py_compile ws/events/base.py`
Expected: No errors

**Step 3: Test with existing event**

Manually verify that calling `messageFunction` without characters parameter still works:

```python
# In Python REPL or test script
from events.base import messageFunction
from core.models import playerClass

player = playerClass()
event = messageFunction('test', 'Test message', player, True)
print(event.characters)  # Should be []
```

Expected: Empty list, no errors

**Step 4: Commit**

```bash
git add ws/events/base.py
git commit -m "feat: update messageFunction to serialize characters parameter"
```

---

## Task 3: Backend - Add characters and image to questionEvent class

**Files:**
- Modify: `ws/events/base.py:19-24`

**Step 1: Add fields to questionEvent.__init__**

In `ws/events/base.py`, update the `questionEvent` class:

```python
class questionEvent:
    def __init__(self):
        self.query = ""
        self.answers = [answerOption("Yes"),answerOption("No")]
        self.type = 'questionEvent'
        self.characters = []      # NEW
        self.image = ""           # NEW
```

**Step 2: Verify syntax**

Run: `python3 -m py_compile ws/events/base.py`
Expected: No errors

**Step 3: Commit**

```bash
git add ws/events/base.py
git commit -m "feat: add characters and image fields to questionEvent class"
```

---

## Task 4: Backend - Update questionFunction to serialize characters

**Files:**
- Modify: `ws/events/base.py:82-100`

**Step 1: Update questionFunction signature and implementation**

In `ws/events/base.py`, update the `questionFunction`:

```python
def questionFunction(fname,message,player=False,check=False,answerOptions=False,characters=None,image=""):
    if (check):
        player.previousGameSpeed = player.gameSpeed
        player.gameSpeed = config.SPEED_QUESTION_PAUSE
        print('question!' + fname)
        m = questionEvent()
        m.id = fname
        if (hasattr(message,'message')):
            m.message = message.message
            m.objectId = message.id
        else:
            m.message = message
        if (answerOptions):
            if (type(answerOptions[0]) == str):
                # create answer option classes with numeric ids
                answerOptions = [answerOption(option,str(i)) for i,option in enumerate(answerOptions)]
            m.answers = answerOptions

        # NEW: Add characters and image
        if characters:
            m.characters = [
                {
                    'id': char.id,
                    'firstname': char.firstname,
                    'lastname': char.lastname,
                    'image': char.image
                }
                for char in characters
            ]

        m.image = image

        return m
```

**Step 2: Verify syntax**

Run: `python3 -m py_compile ws/events/base.py`
Expected: No errors

**Step 3: Commit**

```bash
git add ws/events/base.py
git commit -m "feat: update questionFunction to serialize characters and image parameters"
```

---

## Task 5: iOS - Create SimplePerson model

**Files:**
- Modify: `../lichunWebsocket/lichunWebsocket/Core/Models/MessageEvent.swift:1-10`

**Step 1: Add SimplePerson struct**

In `MessageEvent.swift`, add the new struct at the top of the file (after imports):

```swift
// MARK: - Simple Person
// Lightweight person representation for events
struct SimplePerson: Codable, Identifiable {
    let id: String
    let firstname: String
    let lastname: String
    let image: String
}
```

**Step 2: Build to verify syntax**

From the `lichunWebsocket` directory:

Run: `xcodebuild -scheme lichunWebsocket -sdk iphoneos -configuration Debug clean build`
Expected: Build succeeds

**Step 3: Commit**

```bash
cd ../lichunWebsocket
git add lichunWebsocket/Core/Models/MessageEvent.swift
git commit -m "feat: add SimplePerson model for event character data"
```

---

## Task 6: iOS - Add characters field to MessageEvent

**Files:**
- Modify: `../lichunWebsocket/lichunWebsocket/Core/Models/MessageEvent.swift:35-36`

**Step 1: Add characters field to MessageEvent struct**

In `MessageEvent.swift`, add the field after the `image` property:

```swift
struct MessageEvent: Identifiable, Codable {
    var id: String
    var message: String
    var type: String
    var date: String?
    var hour: String?
    var energyCost: Int?
    var diamondCost: Int?
    var moneyCost: Int?
    var affinityChange: Int?
    var title: String?
    var image: String?
    var characters: [SimplePerson]?  // NEW: People involved in event

    // NEW: Claiming state
    var claimed: Bool = false
    var claimedAt: Date?

    // NEW: Event categorization
    var category: EventCategory = .neutral

    // ... rest of struct ...
}
```

**Step 2: Update init method**

Add `characters` parameter to the init:

```swift
init(id: String, message: String, type: String, date: String?, hour: String?, energyCost: Int?, diamondCost: Int?, moneyCost: Int?, affinityChange: Int?, title: String?, image: String?, characters: [SimplePerson]? = nil, claimed: Bool = false, claimedAt: Date? = nil, category: EventCategory = .neutral) {
    self.id = id
    self.message = message
    self.type = type
    self.date = date
    self.hour = hour
    self.energyCost = energyCost
    self.diamondCost = diamondCost
    self.moneyCost = moneyCost
    self.affinityChange = affinityChange
    self.title = title
    self.image = image
    self.characters = characters  // NEW
    self.claimed = claimed
    self.claimedAt = claimedAt
    self.category = category
}
```

**Step 3: Build to verify**

Run: `xcodebuild -scheme lichunWebsocket -sdk iphoneos -configuration Debug clean build`
Expected: Build succeeds

**Step 4: Commit**

```bash
git add lichunWebsocket/Core/Models/MessageEvent.swift
git commit -m "feat: add characters field to MessageEvent model"
```

---

## Task 7: iOS - Add characters and image to Question model

**Files:**
- Modify: `../lichunWebsocket/lichunWebsocket/Core/Models/Question.swift:11-18`

**Step 1: Add fields to Question struct**

In `Question.swift`, update the struct:

```swift
struct Question: Identifiable, Equatable {
    let id: String
    let question: String
    let answers: [AnswerOption]
    let characters: [SimplePerson]?  // NEW
    let image: String?               // NEW

    static func == (lhs: Question, rhs: Question) -> Bool {
        return lhs.id == rhs.id && lhs.question == rhs.question
    }
}
```

**Step 2: Build to verify**

Run: `xcodebuild -scheme lichunWebsocket -sdk iphoneos -configuration Debug clean build`
Expected: Build succeeds

**Step 3: Commit**

```bash
git add lichunWebsocket/Core/Models/Question.swift
git commit -m "feat: add characters and image fields to Question model"
```

---

## Task 8: iOS - Add character avatars to LifeEventCard

**Files:**
- Modify: `../lichunWebsocket/lichunWebsocket/Features/Home/Components/LifeEventCard.swift:19-92`

**Step 1: Add helper function to convert SimplePerson**

At the bottom of the `LifeEventCard` struct (before the closing brace), add:

```swift
// MARK: - Helpers

/// Convert SimplePerson to Person for AvatarView
private func personFrom(_ simple: SimplePerson) -> Person {
    let person = Person()
    person.id = simple.id
    person.firstname = simple.firstname
    person.lastname = simple.lastname
    person.image = simple.image
    return person
}
```

**Step 2: Add avatars row in body**

In the `body` var, inside the `VStack` right after the opening (before the `HStack` with category icon), add:

```swift
var body: some View {
    Button(action: { ... }) {
        VStack(alignment: .leading, spacing: AppSpacing.xs) {

            // NEW: Character avatars row
            if let characters = event.characters, !characters.isEmpty {
                HStack(spacing: -8) {
                    ForEach(characters.prefix(4)) { character in
                        AvatarView(
                            person: personFrom(character),
                            size: 32,
                            showBorder: true,
                            borderColor: .white
                        )
                    }
                }
                .padding(.bottom, 4)
            }

            // EXISTING: Event content
            HStack(alignment: .top, spacing: AppSpacing.xs) {
                // ... existing code ...
            }
        }
        // ... rest of code ...
    }
}
```

**Step 3: Build to verify**

Run: `xcodebuild -scheme lichunWebsocket -sdk iphoneos -configuration Debug clean build`
Expected: Build succeeds

**Step 4: Commit**

```bash
git add lichunWebsocket/Features/Home/Components/LifeEventCard.swift
git commit -m "feat: add character avatars display to LifeEventCard"
```

---

## Task 9: iOS - Add location background to LifeEventCard

**Files:**
- Modify: `../lichunWebsocket/lichunWebsocket/Features/Home/Components/LifeEventCard.swift:93-96`

**Step 1: Create background computed property**

After the existing computed properties (like `shadowRadius`), add:

```swift
@ViewBuilder
private var backgroundContent: some View {
    ZStack {
        // Base background color
        backgroundColor

        // Optional location image overlay
        if let imageURL = event.image, let url = URL(string: imageURL) {
            AsyncImage(url: url) { image in
                image
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .opacity(0.15)
                    .blur(radius: 2)
            } placeholder: {
                EmptyView()
            }
        }
    }
}
```

**Step 2: Replace background modifier**

Find the line `.background(backgroundColor)` in the `body` and replace with:

```swift
.background(backgroundContent)
```

**Step 3: Build to verify**

Run: `xcodebuild -scheme lichunWebsocket -sdk iphoneos -configuration Debug clean build`
Expected: Build succeeds

**Step 4: Commit**

```bash
git add lichunWebsocket/Features/Home/Components/LifeEventCard.swift
git commit -m "feat: add location image background to LifeEventCard"
```

---

## Task 10: iOS - Add character avatars to CozyEventModal

**Files:**
- Modify: `../lichunWebsocket/lichunWebsocket/Shared/Components/Modals/CozyEventModal.swift:10-16`
- Modify: `../lichunWebsocket/lichunWebsocket/Shared/Components/Modals/CozyEventModal.swift:17-55`

**Step 1: Add new parameters to struct**

Update the `CozyEventModal` struct properties:

```swift
struct CozyEventModal: View {
    let questionPretext: String
    let questionText: String
    let answerTexts: [String]
    let answers: [String]
    let characters: [SimplePerson]?  // NEW
    let locationImage: String?       // NEW
    let onAnswer: (String) -> Void
```

**Step 2: Add helper function**

At the bottom of the struct, add:

```swift
// MARK: - Helpers

private func personFrom(_ simple: SimplePerson) -> Person {
    let person = Person()
    person.id = simple.id
    person.firstname = simple.firstname
    person.lastname = simple.lastname
    person.image = simple.image
    return person
}
```

**Step 3: Add avatars to body**

In the `body`, inside the main `VStack` right after the top decorative bar, add:

```swift
var body: some View {
    ZStack {
        Color.black.opacity(0.5)
            .ignoresSafeArea()
            .onTapGesture { }

        VStack(spacing: AppSpacing.lg) {
            // Decorative top accent bar
            RoundedRectangle(cornerRadius: 4)
                .fill(...)

            // NEW: Character avatars at top
            if let characters = characters, !characters.isEmpty {
                HStack(spacing: 8) {
                    ForEach(characters.prefix(4)) { character in
                        AvatarView(
                            person: personFrom(character),
                            size: 48,
                            showBorder: true,
                            borderColor: .white
                        )
                    }
                }
            }

            // EXISTING: Question content
            VStack(spacing: AppSpacing.md) {
                // ... existing code ...
            }

            // ... rest of code ...
        }
    }
}
```

**Step 4: Build to verify**

Run: `xcodebuild -scheme lichunWebsocket -sdk iphoneos -configuration Debug clean build`
Expected: Build succeeds (may have warnings about preview - that's okay)

**Step 5: Commit**

```bash
git add lichunWebsocket/Shared/Components/Modals/CozyEventModal.swift
git commit -m "feat: add character avatars to CozyEventModal"
```

---

## Task 11: iOS - Add location background to CozyEventModal

**Files:**
- Modify: `../lichunWebsocket/lichunWebsocket/Shared/Components/Modals/CozyEventModal.swift:94-98`

**Step 1: Create background computed property**

After the `body` var and before the helpers section, add:

```swift
// MARK: - Background

@ViewBuilder
private var backgroundContent: some View {
    ZStack {
        AppColors.surfaceElevated

        if let imageURL = locationImage, let url = URL(string: imageURL) {
            AsyncImage(url: url) { image in
                image
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .opacity(0.1)
                    .blur(radius: 3)
            } placeholder: {
                EmptyView()
            }
        }
    }
}
```

**Step 2: Replace background modifier**

Find `.background(AppColors.surfaceElevated)` and replace with:

```swift
.background(backgroundContent)
```

**Step 3: Build to verify**

Run: `xcodebuild -scheme lichunWebsocket -sdk iphoneos -configuration Debug clean build`
Expected: Build succeeds

**Step 4: Commit**

```bash
git add lichunWebsocket/Shared/Components/Modals/CozyEventModal.swift
git commit -m "feat: add location background to CozyEventModal"
```

---

## Task 12: iOS - Update preview with new parameters

**Files:**
- Modify: `../lichunWebsocket/lichunWebsocket/Shared/Components/Modals/CozyEventModal.swift:105-118`

**Step 1: Update preview code**

In the `#if DEBUG` section, update the preview:

```swift
#if DEBUG
struct CozyEventModal_Previews: PreviewProvider {
    static var previews: some View {
        CozyEventModal(
            questionPretext: "You've been offered a new job opportunity!",
            questionText: "Do you want to accept the position?",
            answerTexts: ["Accept the offer", "Decline politely", "Ask for more time"],
            answers: ["accept", "decline", "wait"],
            characters: nil,  // NEW
            locationImage: nil  // NEW
        ) { answer in
            print("Selected: \(answer)")
        }
    }
}
#endif
```

**Step 2: Build to verify**

Run: `xcodebuild -scheme lichunWebsocket -sdk iphoneos -configuration Debug clean build`
Expected: Build succeeds

**Step 3: Commit**

```bash
git add lichunWebsocket/Shared/Components/Modals/CozyEventModal.swift
git commit -m "fix: update CozyEventModal preview with new parameters"
```

---

## Task 13: iOS - Update CozyEventModal call sites

**Files:**
- Find and modify all files that instantiate `CozyEventModal`

**Step 1: Search for CozyEventModal usage**

Run: `grep -r "CozyEventModal(" lichunWebsocket/`
Expected: Find call sites in views

**Step 2: Update each call site**

For each file found, add `characters: nil, locationImage: nil` parameters to maintain backward compatibility.

Example:
```swift
CozyEventModal(
    questionPretext: pretext,
    questionText: text,
    answerTexts: answers,
    answers: answerIds,
    characters: nil,  // NEW - will be updated when question data includes it
    locationImage: nil  // NEW
) { answer in
    // ... existing callback ...
}
```

**Step 3: Build to verify**

Run: `xcodebuild -scheme lichunWebsocket -sdk iphoneos -configuration Debug clean build`
Expected: Build succeeds

**Step 4: Commit**

```bash
git add <modified files>
git commit -m "fix: update CozyEventModal call sites with new parameters"
```

---

## Task 14: iOS - Update WebSocketService to parse characters field

**Files:**
- Modify: `../lichunWebsocket/lichunWebsocket/WebSocketService.swift` (find questionEvent parsing section)

**Step 1: Find question parsing code**

Search for where `Question` objects are created from WebSocket events.

**Step 2: Update Question initialization**

Ensure the Question is created with characters and image fields from the event:

```swift
// When parsing questionEvent from WebSocket
let question = Question(
    id: eventData.id,
    question: eventData.message,
    answers: eventData.answers,
    characters: eventData.characters,  // NEW
    image: eventData.image              // NEW
)
```

**Step 3: Build to verify**

Run: `xcodebuild -scheme lichunWebsocket -sdk iphoneos -configuration Debug clean build`
Expected: Build succeeds

**Step 4: Commit**

```bash
git add lichunWebsocket/WebSocketService.swift
git commit -m "feat: parse characters and image fields from question events"
```

---

## Task 15: Update LifeEventCard preview with test data

**Files:**
- Modify: `../lichunWebsocket/lichunWebsocket/Features/Home/Components/LifeEventCard.swift` (preview section)

**Step 1: Add test characters to preview**

In the `#Preview` section at the bottom, update one of the preview events:

```swift
#Preview {
    let testCharacter1 = SimplePerson(
        id: "1",
        firstname: "John",
        lastname: "Doe",
        image: "https://api.dicebear.com/7.x/avataaars/svg?seed=John"
    )
    let testCharacter2 = SimplePerson(
        id: "2",
        firstname: "Jane",
        lastname: "Smith",
        image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Jane"
    )

    return VStack(spacing: AppSpacing.md) {
        // Event with avatars and location
        LifeEventCard(event: MessageEvent(
            id: "1",
            message: "You had lunch with friends at the cafeteria",
            type: "messageEvent",
            date: nil,
            hour: nil,
            energyCost: nil,
            diamondCost: nil,
            moneyCost: nil,
            affinityChange: 5,
            title: nil,
            image: "https://cdn.discordapp.com/attachments/1106614533402931284/cafeteria.png",
            characters: [testCharacter1, testCharacter2],
            claimed: false,
            claimedAt: nil,
            category: .social
        ))

        // Existing previews...
    }
    .environmentObject(WebSocketService(urlSession: URLSession.shared, delegateQueue: OperationQueue()))
    .padding()
    .background(AppColors.background)
}
```

**Step 2: Build to verify preview works**

Run: `xcodebuild -scheme lichunWebsocket -sdk iphoneos -configuration Debug clean build`
Expected: Build succeeds

**Step 3: Commit**

```bash
git add lichunWebsocket/Features/Home/Components/LifeEventCard.swift
git commit -m "docs: add preview with character avatars and location image"
```

---

## Task 16: Backend - Create example event using new features

**Files:**
- Modify: `ws/events/childhood/childhood_milestones.py` (or similar event file)

**Step 1: Find an existing event to enhance**

Look for a suitable event that involves multiple people, like `playDate`.

**Step 2: Update event to include characters**

Example enhancement:

```python
def playDate(player, type='message'):
    fname = 'playDate'
    check = fname not in player.events and player.c.ageYears >= 4 and player.c.ageYears < 12

    if check:
        friend = find_character(player, role='friend')

        if friend:
            message = f"You had a playdate with {friend.firstname}! You played outside all afternoon."

            # NEW: Include both player and friend avatars
            return messageFunction(
                fname,
                message,
                player,
                True,
                title="Playdate",
                characters=[player.c, friend],  # Show both kids
                image="https://cdn.../playground.png",  # Playground location
                energyCost=5,
                affinityChange=3
            )
```

**Step 3: Verify syntax**

Run: `python3 -m py_compile ws/events/childhood/childhood_milestones.py`
Expected: No errors

**Step 4: Commit**

```bash
git add ws/events/childhood/childhood_milestones.py
git commit -m "feat: add character avatars to playDate event"
```

---

## Final Verification

**Step 1: Backend smoke test**

```bash
cd ws
python3 -c "from events.base import messageEvent, questionEvent; print('Backend models OK')"
```

Expected: "Backend models OK"

**Step 2: iOS build test**

```bash
cd ../lichunWebsocket
xcodebuild -scheme lichunWebsocket -sdk iphoneos -configuration Debug clean build
```

Expected: Build succeeds

**Step 3: Manual testing checklist**

- [ ] Start backend server
- [ ] Run iOS app in simulator
- [ ] Trigger an event with characters (if added example event)
- [ ] Verify avatars appear in LifeEventCard
- [ ] Verify location image appears as background
- [ ] Verify events without characters still work
- [ ] Verify question modals with characters work

---

## Success Criteria

- ✅ Backend: messageEvent and questionEvent have characters field
- ✅ Backend: messageFunction and questionFunction serialize characters
- ✅ iOS: SimplePerson model created
- ✅ iOS: MessageEvent and Question models updated
- ✅ iOS: LifeEventCard displays character avatars
- ✅ iOS: LifeEventCard displays location backgrounds
- ✅ iOS: CozyEventModal displays character avatars
- ✅ iOS: CozyEventModal displays location backgrounds
- ✅ iOS: WebSocketService parses new fields
- ✅ Backward compatible: Events without new fields work unchanged
- ✅ All builds succeed
- ✅ Preview code updated

---

## Notes

**Testing Strategy:**
- Most changes are additive (new optional fields)
- Backend changes are backward compatible
- iOS changes use optional binding (if let)
- Manual testing required for visual verification

**Future Enhancements:**
- Add more events with character avatars
- Create location image library
- Add avatar animations
- Web frontend support (out of scope for this plan)
