# TOAST Addon

Companion WoW addon for the TOAST Discord bot (guild TOAST/TIDE, Mythic+ organization).
Language: Lua. This folder lives inside the WoW AddOns directory AND is the git repo,
so any edit here is read directly by the game.

## Dev loop
- Edit .lua, then `/reload` in-game, then observe. Claude Code CANNOT run WoW.
- The human is the test harness. There are no automated tests.
- Add every new .lua file to TOAST.toc load order or it will not load.

## Pinned API (verified for patch 12.0.5 / Interface 120005)
Do NOT trust API from training data without checking. Several functions are deprecated.
If unsure about a signature, check warcraft.wiki.gg. Do not invent functions.

- Invite a player: C_PartyInfo.InviteUnit("Name-Realm")
  NOT the old global InviteUnit() (deprecated since 8.2.5).
  Strip spaces from realm: realm = realm:gsub(" ", "")
- M+ rating: C_ChallengeMode.GetOverallDungeonScore() -> number
- Owned keystone: C_MythicPlus.GetOwnedKeystoneChallengeMapID(), C_MythicPlus.GetOwnedKeystoneLevel()
- Slotted keystone: C_ChallengeMode.GetSlottedKeystoneInfo() -> mapID, affixIDs, level
- Dungeon name from mapID: C_ChallengeMode.GetMapUIInfo(mapID)
- Addon comms:
  - C_ChatInfo.RegisterAddonMessagePrefix(prefix)  (prefix max 16 chars)
  - C_ChatInfo.SendAddonMessage(prefix, message, "GUILD")  (message max 255 chars)
  - Receive via the CHAT_MSG_ADDON event.
  - There is a per-prefix server throttle, so batch and throttle broadcasts.

## Design principles
- Before planning or implementing any pass, read `ARCHITECTURE.md` in full and
  show in the plan how the change follows its runtime lifecycle contract.
- Prime directive: functionality that is not active must cost practically
  nothing. No permanent `OnUpdate`, ticker, timer, event, or hook without an
  explicit owner, activation condition, and stop condition.
- Build UI lazily. Hidden views do no refresh, sort, scan, render, or animation
  work. Activity-scoped services start with their first task and stop after
  their last task.
- Every implementation plan must state what remains active when the feature is
  unused and how the human verifies that it returns to sleep.
- Prefer lazy initialization on addon open over PLAYER_LOGIN for API calls
  that depend on game systems being ready (C_Club, C_MythicPlus, etc.).
  PLAYER_LOGIN fires before some systems finish loading, so a value cached
  there can be stale or wrong (e.g. C_Club.GetSubscribedClubs() returning
  nil/empty before CLUB_STREAMS_LOADED). Re-check on the action that actually
  needs the value (e.g. /toast opening) instead of trusting a login-time cache.

## Comms architecture (keys + online status)

### Proactief (stuur zonder verzoek)
- Eigen key broadcast → GUILD (bij login + na dungeon)
- LibKS key broadcast → GUILD (automatisch via BigWigs/DBM/Details)

### Reactief (stuur alleen op verzoek)
- KEY_REQUEST via GUILD → BroadcastKey() via GUILD
- KEY_REQUEST via WHISPER → antwoord via WHISPER naar sender

### Getimed (gespreid, niet allemaal bij login)
- DiscoveryWhispers() → bij PLAYER_LOGIN, 0.4s per target,
  alleen niet-guild roster-leden
- MaybeRefreshUnknowns() → bij dashboard/leaderboard openen,
  5 min cooldown, alleen bekende maar verouderde entries

### Kanaal regels
- GUILD: broadcast naar eigen guild (één-naar-allen)
- WHISPER: punt-naar-punt met community-leden buiten guild
- LibKS prefix: keystone data (level, mapID, rating)
- TOAST prefix: eigen protocol (KEY_REQUEST, KEY:level:short)

### Online status (twee bronnen)
Primair: C_Club API (guild + community "TIDE and Crew")
- C_Club.GetSubscribedClubs() → vind guild en community clubId
- C_Club.GetClubMembers(clubId) → alle member IDs
- C_Club.GetMemberInfo(clubId, memberId).presence
  → Online/Offline/Away/Busy
- BELANGRIJK: wrap in InCombatLockdown() check — C_Club returns
  worden "secret" tijdens chat messaging lockdown in combat
- ClubMemberInfo bevat ook: name, classID, level, zone, guid
- Guild = ClubType.Guild, Community = ClubType.Character

Secundair (fallback): lastSeen timestamp op TOAST.keys entries
- < 60 min: waarschijnlijk online
- >= 60 min: known maar niet recent
- nil: onbekend

### Toekomstig (niet in v0.9.1)
- Score broadcast via GUILD (v0.9.2)
- P2P roster sync via GUILD + WHISPER (post v0.9.2)
- Bridge captain SavedVariables polling (najaar)

## Comms constraint (architecture)
Addon message channels: PARTY, RAID, INSTANCE_CHAT, GUILD, OFFICER, WHISPER, SAY, YELL.
There is NO channel for WoW Communities/clubs, and CHANNEL was removed.
=> Guild-wide sharing uses "GUILD" only. Community-but-not-guild members are
   unreachable in-game; cross-guild and Discord coverage is the bot's job, not the addon's.

## Conventions
- Namespace via vararg: local addonName, TOAST = ...  Put everything on TOAST, avoid globals.
- Persistence: SavedVariables TOAST_DB (declared in .toc), written on logout/reload.
- Chat color codes: |cffRRGGBB text|r
- Fonts: never use Fonts\FRIZQT__.TTF or any other system font path, and never
  reuse Blizzard's GameFont* objects (UI packs mutate them, which changes text
  metrics and breaks fixed TOAST layouts on other clients).
  The addon ships Barlow Condensed Bold in media/fonts/.
  - FontStrings: inherit a TOAST font object -- CreateFontString(nil, "OVERLAY", "TOASTFontNormal").
    Available: TOASTFontNormal/Highlight, ...Small, TOASTFontNormalLarge, TOASTFontNormalHuge.
  - EditBoxes (cannot inherit a font object): SetFont(TOAST.FONT_PATH, size, "").
  Both are defined in modules/fonts.lua, which loads early in the .toc so
  TOAST.FONT_PATH is available to every later module.
- Planned modules (separate files, each added to the .toc):
  core.lua (init/events/slash), invite.lua, comms.lua, keys.lua, leaderboard.lua, ui.lua

## Working style
Direct and concise. Approve the plan before implementing. Review before committing.

## Feature order
1. invite.lua  - port the working invite string from the bot (paste -> one click -> invites)
2. comms.lua + keys.lua - live guild key board over GUILD addon channel
3. leaderboard.lua - in-game M+ score ranking
