# Phase 16.1: Discord Username Inline Editing, Avatar Fetching & Caching - Context

**Gathered:** 2026-03-23
**Status:** Ready for planning

<domain>
## Phase Boundary

Enable inline editing of Discord username in the summary popover with GH Project write-back, resolve Discord user ID from username via Discord API, fetch Discord profile images and cache them locally, display real avatar images on cards, and redesign the popover field layout. Includes adding Discord bot token to WCM/Settings and updating DATA-FLOW.md. Does not add Discord messaging, new discovery modes, or other new capabilities.

</domain>

<decisions>
## Implementation Decisions

### Discord username inline editing
- Discord username appears in the summary popover as display text with a pencil icon (Unicode glyph) for edit affordance
- Click pencil to enter edit mode (TextInput field, same pattern as Vision Rx)
- On blur or Enter: save triggers GH Project write-back via `update_field_text` (existing infrastructure)
- Shows "Saved" indicator after successful write-back (existing popover pattern)
- After save: Discord API call to resolve new username to discord_user_id
- User ID resolution triggers avatar re-fetch for the new user

### Vision Rx edit affordance update
- Retroactive change: Vision Rx OD and OS fields also get the pencil icon for edit affordance (currently click-to-edit with no visible icon)
- Same Unicode pencil glyph as Discord username

### Discord bot token storage
- Discord bot token stored in Windows Credential Manager (WCM) via keyring — same pattern as Shopify token (Phase 12.1)
- New section in Settings modal for Discord bot token entry, validation, and clear
- When no token configured: avatars show initials circle, Settings shows subtle hint that a Discord bot token enables avatar images

### Discord user ID resolution
- When Discord username is edited and saved, call Discord API (Get User by username) using the bot token from WCM
- Updates discord_user_id on the recipient
- If resolution fails (user not found, API error): keep old user_id, show error toast

### Avatar image fetching strategy
- **Primary approach (research needed):** Upload Discord avatar image to GH Project draft issue body (GitHub hosts via CDN). During GH sync, read image URL from draft body. This makes GitHub the avatar cache — no Discord API needed at runtime for display.
- **Fallback approach:** Fetch avatar from Discord CDN directly (`cdn.discordapp.com/avatars/{user_id}/{hash}.png`), cache to local disk.
- Research must determine if GH Project V2 draft items support programmatic body/image upload via GraphQL.

### Avatar caching (fallback path)
- Cache location: `%APPDATA%/WITwhat/avatars/{discord_user_id}.png`
- Refresh triggers: on Discord username change (immediate re-fetch) + weekly staleness check during sync
- When no bot token configured: no fetching attempted, initials circle displayed

### Avatar display on cards
- Initials circle is the default/fallback (no change from current behavior)
- When a cached avatar image exists: instant swap from initials to real image (no animation/transition)
- Avatar circle remains 26px with the Purpose-colored ring from Phase 16

### Popover layout redesign
- **New field order (top to bottom):**
  1. **Purpose** — editable inline selector (no change from Phase 16)
  2. **Email** — from Shopify customer data, display only. Removed from card face, lives only in popover
  3. **Discord Username** — inline editable with pencil icon, GH write-back
  4. **Products in Possession** — renamed from "Recipient Products (GH)". Replaces "Items in Possession" section
  5. **Last Activity** — replaces both "Last Shipment Date" and "Last Received". Shows: status label + timestamp + product list from the relevant card
  6. **Last Status Update** — unchanged
  7. **Vision Rx** — inline editable with pencil icon (added pencil affordance)

### Last Activity field logic
- Shows the most recent card for this recipient that reached one of: Shipped, Delivered, Returning, or Returned
- Cards with other statuses (Not Shipped, Label Created) are disregarded
- Display format: `{Status} - {date}` followed by product list from that card
- Status labels use exactly: "Shipped", "Delivered", "Returning", "Returned" — never "In Transit" (ambiguous between outbound and return)
- Mapping from existing status enum: `InTransit` -> "Shipped", `ReturnInTransit` -> "Returning"

### Email removal from card face
- Email currently shown as contact-secondary fallback on card face (when no Discord username)
- Move email to popover only
- Card face contact-secondary: show Discord username when available, "No contact info" otherwise (no email fallback on card face)

### DATA-FLOW.md updates
- Document avatar_url or avatar_cache_path as a new derived field
- Document discord_bot_token as a new credential in WCM
- Document popover field changes (email move, Items in Possession removal, Last Activity derivation)
- Document the GH draft body avatar approach (if viable) or the local cache approach

### Claude's Discretion
- Exact Unicode glyph choice for pencil icon (must render in Slint — verify, as vertical ellipsis does NOT render)
- Discord API error handling details and retry strategy
- Exact GH Project draft body image upload approach (research-dependent)
- Avatar image format and size optimization
- Exact "Last Activity" date formatting

</decisions>

<canonical_refs>
## Canonical References

**Downstream agents MUST read these before planning or implementing.**

### Data architecture
- `.planning/DATA-FLOW.md` — Authoritative data flow reference. Must be updated with avatar fields, discord_bot_token credential, popover field changes. RULE-03 (SQLite single read source) applies to any new fields.

### Prior phase context
- `.planning/phases/08-discord-enrichment-and-external-jumps/08-CONTEXT.md` — Discord identity decisions: avatar initials, DM deep link, contact-secondary pattern, ellipsis menu structure
- `.planning/phases/16-gh-project-new-columns-ingestion/16-CONTEXT.md` — Inline editing pattern (Vision Rx, Purpose), GH write-back via `update_field_text`, popover current structure

### Existing code (Discord + avatar)
- `crates/app/src/dashboard/external.rs` — `contact_secondary()`, `open_discord_dm()`, clipboard helpers
- `crates/core/src/domain/recipient.rs` — `RecipientIdentity` with discord_username, discord_user_id; `RecipientOverride` with discord_username
- `crates/app/ui/card.slint` — Avatar circle (line ~258), summary popover (line ~836), inline edit pattern (Vision Rx, Purpose)

### GH Project write-back
- `crates/integrations/src/github/project_client.rs` — `update_field_text` for GH Project column writes
- `crates/integrations/src/github/project_mapping.rs` — Field mapping from GH Project columns

### SQLite persistence
- `crates/service/src/db/sqlite.rs` — SqliteStore, recipients table

### Credential storage
- Phase 12.1 pattern: keyring v3 with windows-native feature for WCM FFI

</canonical_refs>

<code_context>
## Existing Code Insights

### Reusable Assets
- `update_field_text` in project_client.rs: reuse for Discord username GH Project write-back
- `contact_secondary()` in external.rs: needs modification to remove email fallback from card face
- Popover inline edit pattern (Vision Rx OD/OS, Purpose selector): extend for Discord username
- `copy_to_clipboard` in external.rs: available for any clipboard needs
- keyring WCM pattern from Phase 12.1: reuse for Discord bot token storage

### Established Patterns
- Card data pipeline: GithubMappedRecipient -> Recipient -> RecipientCardSnapshot -> DashboardCardViewModel -> CardData (Slint)
- GH write-back: fire-and-forget on background thread, failures logged to stderr (Phase 16-03 pattern)
- Popover saved indicator: `popover-saved` bool + 2-second timer reset
- Settings modal token sections: existing pattern from Shopify token (Phase 12.1)

### Integration Points
- `CardData` struct in dashboard.slint: needs avatar-image field (Slint `image` type)
- `DashboardCardViewModel` in view_model.rs: needs avatar_image_path or avatar_bytes
- RecipientIdentity: already has discord_username, discord_user_id — may need avatar_hash or avatar_url
- Settings modal: new Discord bot token section
- Slint Image loading: investigate `@image-url()` for dynamic file paths or `slint::Image::load_from_path()`

</code_context>

<specifics>
## Specific Ideas

- GH Project draft issue body as avatar CDN is the preferred approach — research whether GH Project V2 items support programmatic body editing with embedded images via GraphQL mutation
- Unicode pencil glyph must be tested in Slint before committing (known issue: vertical ellipsis `⋮` does NOT render)
- "In Transit" language is explicitly forbidden in the Last Activity field — use "Shipped" and "Returning" instead
- Email moves from card face to popover only — this simplifies the card and makes the popover the authoritative contact detail view
- "Items in Possession" section eliminated, replaced by "Products in Possession" (sourced from GH Project parallel-arrays)

</specifics>

<deferred>
## Deferred Ideas

None — discussion stayed within phase scope.

</deferred>

---

*Phase: 16.1-discord-username-inline-editing-in-summary-popover-with-gh-project-write-back-discord-user-profile-image-fetching-and-avatar-caching*
*Context gathered: 2026-03-23*
