# Commission board / accept UI — plan

Implementation plan for the forum **commission board** UI (GDD §13.1 / §13.2). **Not implemented yet** —
this is the design to review before coding. Pairs with `COMMISSIONS.md` (objective backlog) and the
runtime system in `Source/NakedDesire/Commissions/` (`UMissionSubsystem`, `UCommission`,
`UCommissionObjective`).

## Goal & scope

The forum is the player-facing surface for commissions (§13). For the vertical slice this is the
**minimal board** PLAN VS‑4 calls for: list offered commissions, accept/abandon, and track active ones
with live objective progress. No threads, no other-user feed, no profile tab yet (§13 forum scope note).

The runtime already does the work; this is **pure presentation + input**:
- `UMissionSubsystem::GetOfferedCommissions()` / `GetAcceptedCommissions()`
- `AcceptCommission(UCommission*)` / `AbandonCommission(UCommission*)`
- `OnBoardChanged` (rebuild signal) / `OnCommissionCompleted(UCommission*)` (feedback)
- per-commission: `GetTitle / GetPosterUsername / GetTier / GetReward / GetObjectives / GetState`, plus
  `OnStateChanged` / `OnCompleted`
- per-objective: `GetDescription()`, `GetProgress()` (0..1), `IsSatisfied()`, `OnStateChanged`

## Reuse the established pattern

Follow `WardrobeScreenWidget` / `WardrobeInventoryWidget` exactly (CommonUI):
- Screen = `UCommonActivatableWidget` pushed onto `UGameLayoutWidget`'s `WidgetStack`.
- Lists rebuilt from the subsystem on a change delegate; rows are child widgets with click delegates.
- Subscribe in `NativeOnActivated`, unsubscribe in `NativeDestruct`.
- C++ owns logic + `BindWidget` references; Blueprint owns layout/visuals (GDD §17.5).

## Widget breakdown (new C++ classes, BP subclasses for visuals)

### 1. `UCommissionBoardScreenWidget : UCommonActivatableWidget`
The screen. Two sections: **Board** (offered) and **Active** (accepted).
- BindWidgets: `UVerticalBox* OfferedList`, `UVerticalBox* ActiveList` (or `UScrollBox`), optional
  `UCommonTextBlock* EmptyBoardLabel`.
- EditDefaultsOnly: `TSubclassOf<UCommissionEntryWidget> EntryWidgetClass`.
- `NativeOnActivated`: grab `UMissionSubsystem`, bind `OnBoardChanged → Rebuild`,
  `OnCommissionCompleted → HandleCompleted` (toast/flash), call `Rebuild()`.
- `NativeDestruct`: unbind both.
- `Rebuild()`: `OfferedList/ActiveList->ClearChildren()`; for each commission create an entry widget,
  `Init(Commission, Subsystem)`, add to the right list. (Membership only changes on accept / complete /
  abandon / day‑roll, all of which fire `OnBoardChanged`, so a full rebuild is cheap and correct.)

### 2. `UCommissionEntryWidget : UCommonUserWidget`
One commission row.
- BindWidgets: `UCommonTextBlock* TitleText, PosterText, TierText, RewardText`,
  `UVerticalBox* ObjectiveList`, `UCommonButtonBase* ActionButton`, `UCommonTextBlock* ActionLabel`.
- EditDefaultsOnly: `TSubclassOf<UCommissionObjectiveRowWidget> ObjectiveRowClass`.
- `Init(UCommission*, UMissionSubsystem*)`: cache both, subscribe `Commission->OnStateChanged →
  RefreshActionState`, render header (title/poster/tier), reward summary from `FCommissionReward`
  (money / XP / followers — hide zero fields), build an objective row per `GetObjectives()`.
- Action button is state-driven (`Commission->GetState()`):
  - `Offered` → label **Accept** → `Subsystem->AcceptCommission(Commission)`.
  - `Accepted` → label **Abandon** → `Subsystem->AbandonCommission(Commission)`.
  - `Completed` / `Expired` → button hidden/disabled (the rebuild usually removes it from the board).
- **Compact mode** for the HUD tracker: a `bCompact` flag (or a BP variant) hides poster / reward /
  action button and shows just title + objective rows, so the tracker (#4) reuses this same widget.
- `NativeDestruct`: unsubscribe from the commission.

### 3. `UCommissionObjectiveRowWidget : UCommonUserWidget`
One objective line.
- BindWidgets: `UCommonTextBlock* DescriptionText`, `UProgressBar* ProgressBar` (BindWidgetOptional),
  `UImage* DoneCheck` (BindWidgetOptional).
- `Init(UCommissionObjective*)`: set `GetDescription()`, subscribe `OnStateChanged → RefreshDone`.
- Progress: objectives have **no per-frame progress delegate** — only `OnStateChanged` (fires on
  satisfied). For the continuous ones (`RequiredHoldSeconds` holds, `RunNakedDistance`) drive the bar
  from `NativeTick` polling `GetProgress()` **while the screen is open** (cheap, self-contained). Binary
  objectives just toggle `DoneCheck` on `OnStateChanged`. (If polling ever feels wasteful, add an
  `OnProgressChanged` delegate to `UCommissionObjective` later — not needed for the slice.)

### 4. `UCommissionTrackerWidget : UCommonUserWidget`
Always-on HUD element showing accepted commissions' current objectives + live progress while the player
is in the world. **Passive HUD — not activatable, never captures input.**
- Hosted as a `BindWidget` child of the existing `UHUDWidget` (the always-on HUD), so it shows during
  normal play and not just at the PC.
- BindWidgets: `UVerticalBox* TrackerList`; EditDefaultsOnly `TSubclassOf<UCommissionEntryWidget>
  TrackerEntryWidgetClass`.
- `NativeConstruct`: grab `UMissionSubsystem`, bind `OnBoardChanged → Rebuild`, call `Rebuild()`.
  `NativeDestruct`: unbind.
- `Rebuild()`: clear; for each `GetAcceptedCommissions()` add a `UCommissionEntryWidget` in **compact
  mode** (title + objective rows, no reward/poster/button). Collapse the whole widget when the active
  list is empty.
- Live progress comes from the reused objective rows' own `NativeTick` poll — no extra wiring here.

## Entry point (how the board opens)

§13 says phone or PC; the phone stack is Phase 8, so for the slice use the **apartment PC** as an
interactable, mirroring `AWardrobe`:
- New `AForumTerminal` (or `AApartmentPC`) `IInteractable` → `Interact` calls
  `HUD->GetGameLayoutWidget()->OpenCommissionBoard()`.
- `UGameLayoutWidget`: add `OpenCommissionBoard()` + `TSubclassOf<UCommissionBoardScreenWidget>
  CommissionBoardScreenWidgetClass` + the instance ptr (same shape as `OpenWardrobe`).
- Add a debug input action (or console exec) to open it directly for testing before the PC art exists.
- Phone "Forum app" entry point is added in Phase 8 and calls the same `OpenCommissionBoard()`.

## Data flow & live updates

- **Membership** (which list a commission is in): `OnBoardChanged` → full `Rebuild()`.
- **Action state** of a visible entry (e.g. accepted→completed while open): `Commission->OnStateChanged`
  → `RefreshActionState`; the subsequent `OnBoardChanged` (fired by the subsystem on completion) rebuilds
  and moves it out of Active.
- **Objective progress**: `OnStateChanged` for done state + `NativeTick` poll for the progress bar.
- **Completion feedback**: `OnCommissionCompleted` → brief toast / reward popup (reuse existing
  `UI/` + `Audio/UI` tone). Optional for the slice.

## Layout sketch

```
┌─ Commission Board ───────────────────────────────────────────┐
│  BOARD (offered)                  ACTIVE (accepted)          │
│  ┌────────────────────────────┐   ┌──────────────────────┐  │
│  │ Flash a Stranger    [Daily]│   │ Beach Streak  [Daily] │  │
│  │ posted by sk8r_gurl        │   │ ▸ Run 50 m naked  62% │  │
│  │ ▸ Expose boobs to 1 person │   │ ▸ while at the beach  │  │
│  │ $150 · 20 XP · +5 followers│   │            [ Abandon ]│  │
│  │                  [ Accept ]│   └──────────────────────┘  │
│  └────────────────────────────┘                             │
│  … more offered …                 (empty → "No active       │
│                                     commissions")           │
└──────────────────────────────────────────────────────────────┘
```
(Two columns is the simplest; a Board/Active tab pair is an equivalent alternative — see decisions.)

## C++ vs Blueprint split

- **C++ (this plan):** the four widget classes above (screen, entry, objective row, HUD tracker) +
  `OpenCommissionBoard` on `GameLayoutWidget` + the PC interactable. All subsystem calls, subscriptions,
  list rebuilds, and button handlers live here.
- **Blueprint:** `WBP_CommissionBoardScreen`, `WBP_CommissionEntry`, `WBP_CommissionObjectiveRow`,
  `WBP_CommissionTracker` — visual layout only, satisfying the `BindWidget` names above and setting the
  `…WidgetClass` defaults. `WBP_CommissionEntry` carries the full and compact looks (compact = tracker).

## Subsystem changes needed

None required — the existing API covers it. Nice-to-haves to consider during build:
- A `GetCompletedCommissions()` getter if a "completed today" section is wanted (data already tracked).
- Confirm `OnBoardChanged` fires on **every** membership change (it does: accept / abandon / complete /
  day‑roll all call it).

## Edge cases to handle

- Empty board / empty active list → show a label, not a blank panel.
- A commission completing while the screen is open → entry shows done, then rebuild removes it from
  Active (and the completion toast fires once).
- Day roll while the screen is open → `OnBoardChanged` rebuilds; accepted-but-unfinished entries vanish
  (expired) and a fresh offered set appears. Make sure entry widgets unsubscribe on rebuild (`NativeDestruct`).
- Abandon returns the commission to the board (Offered) in the same session.
- Re-entrancy: accepting an already-satisfiable commission completes instantly; the entry should tolerate
  `Accepted → Completed` within one frame (drive purely off `GetState()` + the rebuild).

## Locked decisions (slice)

1. **Layout — two columns** (Board | Active), not tabs. Everything visible at once; simplest.
2. **Entry point — apartment PC interactable** (`AForumTerminal`) calls `OpenCommissionBoard()`, plus a
   debug console exec to open it before the PC art exists. Phone "Forum app" reuses the same call (Phase 8).
3. **Progress display — both:** a progress bar (driven by `NativeTick` polling `GetProgress()`) for the
   continuous objectives and a checkmark for binary ones, via `BindWidgetOptional` on the row widget.
4. **Completion feedback — minimal toast** on commission completion (reuse existing `UI/` + `Audio/UI`).
5. **In-world HUD objective tracker — yes, in scope.** A separate always-on HUD widget
   (`UCommissionTrackerWidget`, see breakdown #4) shows accepted commissions' current objectives + live
   progress while the player is out in the world (GDD §0.5 'objective-tracker widget'). The board is for
   browse/accept at the PC; the tracker is for following objectives in the field. It reuses the shared
   entry/row widgets in a compact form, so it adds one widget — not a parallel hierarchy.
6. **Modal behavior — mirror the inventory/wardrobe screens.** The board uses the same input-config /
   pause setup the existing `UCommonActivatableWidget` screens use; read `UInventoryScreenWidget` /
   `UWardrobeScreenWidget` (+ their BP activation / `GetDesiredInputConfig` settings) and match them for a
   consistent menu UX. The HUD tracker is **not** an activatable widget — it's passive HUD and never
   captures input or pauses anything.

## Build order (when greenlit)

1. Read `UInventoryScreenWidget` / `UWardrobeScreenWidget` input-config + activation settings and reuse
   the same convention for the board (decision #6).
2. `UCommissionObjectiveRowWidget` → `UCommissionEntryWidget` (incl. `bCompact`) → `UCommissionBoardScreenWidget`.
3. `UGameLayoutWidget::OpenCommissionBoard()` + class ref; debug console exec to open.
4. `UCommissionTrackerWidget` on `UHUDWidget` (reuses the entry/row widgets in compact mode).
5. Author the `WBP_*` assets (board screen, entry, objective row, tracker) against the BindWidget contracts.
6. `AForumTerminal` interactable (apartment PC) → `OpenCommissionBoard`.
7. Author a `UCommissionBoardConfig` with a few test commissions and play-test the full accept → satisfy →
   reward → expire loop, plus the HUD tracker updating in the field (ties into the VS‑4 / VS‑7 exit checks).