# LibMatic CHANGELOG

## Unreleased

### LibMatic-IgnoreCharacters (new library)

- **MINOR 1**: GearMatic's ignore-this-character feature moved into the shared library: realm normalizing, the per-addon ignore store (is ignored, set ignored, keep only the current realm) and the AceConfig "Ignore Characters" tab (description, one-click ignore of every other realm, class-coloured multiselect with level), built from `lib:New{ store, characters, realmOf, label, strings, onChange, extraArgs }`. GearMatic and AppearanceMatic both run it. Offline test: tests/ignore_characters_spec.lua.

### LibMatic-OverlayGuard

- **MINOR 7**: renown coverage. Watches `ExpansionLandingPage` (the current renown panel; `MajorFactionRenownFrame` no longer exists in 12.x but stays hooked for older clients), `CovenantRenownFrame` and the `CovenantRenownToast` level-up banner, alongside the renown EventToast that was already covered.
- **MINOR 6**: alerts now also wait for Blizzard's own boss and loot presentation. New default watches on `BossBanner` (the boss-kill banner with its loot list) and `LevelUpDisplay`, plus a polled condition that blocks while any `AlertFrame` toast subsystem has visible or queued alerts (loot won, new appearance, achievement, mount, toy). `RegisterCondition(key, fn)` / `UnregisterCondition(key)` let an addon add its own polled blockers; `lib.AnyBlizzardToastActive` is the built-in check. Offline test: tests/test_overlayguard_toasts.lua.

### LibMatic-MarkerPack (new library)

- **MINOR 1**: GearMatic's upgrade/downgrade chevron and arrow textures (plus ALT variants) and the three best-in-slot skulls (fanged, grim, classic) now ship in the shared library with path helpers and AceConfig option-value builders, so any addon can mark items with the same family look.

### LibMatic-Settings

- **MINOR 21**: `BuildImageSelect` and `ImageOptionValues`, the generic dropdown-with-texture-preview control (one or several textures per row), so any "pick a look" setting renders like GearMatic's indicator and skull pickers. Offline test: tests/test_image_select.lua.
- **MINOR 20**: `BuildMarkerControls{ addon, indicatorStyle, skullStyle, size }` emits the "Indicator style" and "Skull style" pickers (texture previews inline) and a "Marker size" range, backed by LibMatic-MarkerPack. Offline test: tests/test_markerpack.lua.

### LibMatic-Settings

- **MINOR 19**: seven family control builders promoted from hand-rolled addon code so every -Matic addon exposes the same options: `BuildMinimapControls` (show, lock), `BuildFrameControls` (show, lock, scale, opacity, reset position for readouts/widgets/log windows), `BuildAlertIconControls` (show icon, icon picker with texture preview, icon size), `BuildAnimationControls` (animate icon, icon pop scale, stagger), `BuildTooltipControls` (show in tooltip, include other characters), `BuildSoundExtrasControls` (sound channel, rate limit presets, once per login, play in combat), `BuildGeneralExtrasControls` (login message, update notice, chat notifications, debug). `BuildAlertGlobalsControls` gained an "Alert cooldown" range. Fixed the display-duration reset button, which was labeled "Reset Start Delay". Preset tables exported as `lib.SOUND_RATE_LIMITS` and `lib.SOUND_CHANNELS`. Offline test: tests/test_family_controls.lua. Sourced from a settings audit of CritMatic, GearMatic, RepMatic, MailMatic and XPMatic.

### LibMatic-AlertStack

- **MINOR 4**: `Push()`'s OnHide hook no longer drops a frame from the shared stack on an ancestor hide. OnHide also fires when an ancestor (UIParent during a cinematic) hides while the frame's own shown flag stays true; the hook used to call `lib:Remove(f)` unconditionally, so a cinematic silently untracked every visible alert frame in the shared stack: not reanchored when the stack reflowed, not counted against `maxCount`, a stale-position ghost until it happened to fade and hide itself for real. The hook now checks `f:IsShown()` first and only removes on a genuine self-hide, mirroring the guard CritMatic's own frame pool has carried since its pooling change landed. Found while porting RepMatic's ancestor-hide ghost-alert fix (RepMatic commit 10e56b6) to CritMatic; every addon that symlinks `Libs/LibMatic` to this repo picks up the fix from this one change.

### LibMatic-SoundThrottle (new library)

- **MINOR 1**: shared 2-second sound rate limit for the addon family. `Allow(key, now?)` admits one play per 2 seconds per key (per-addon keys keep addons independent); `Play(key, path, channel?)` and `PlayKit(key, soundKitID, channel?)` wrap PlaySoundFile / PlaySound behind the gate. The timestamp is injectable for offline tests.

### LibMatic-VersionCheck (new library)

- **MINOR 1**: family-wide update notifier. Each embedding addon registers its slug and version; the library shares one addon-message prefix ("LibMaticVC") for the whole family, asks peers for their versions at login and on debounced roster changes, and prints one localized once-per-session chat notice per addon when a strictly newer stable version is seen. Works while ungrouped via the GUILD channel; PARTY/RAID/INSTANCE_CHAT are layered on only when actually usable (real-player home groups, instance groups in instances), with send-time re-validation so ERR_NOT_IN_GROUP can never fire. Prerelease (alpha/beta) versions are never advertised and never trigger notices. Offline unit tests in tests/versioncheck_compare_test.lua.

### LibMatic-SoundPack

- **MINOR 5**: seven new bell and chime sounds join the pack, normalized to the pack loudness like the rest: Darkmoon Bell, Grand Bell, Karazhan Bell Toll, Chapel Bell, Warning Bell, Temple Bell, Scenario Chime.

### LibMatic-OverlayGuard

- **MINOR 5**: hardening pass over the loading-screen and queue logic.
  - Default watches now re-arm on a LibStub upgrade. Arming is keyed to the loaded MINOR instead of a bare `_armed` flag, so when a newer copy loads over an already-armed older one it re-registers events and reinstalls the `OnEvent` handler on the existing watcher frame instead of silently keeping the stale event set. Without this a future MINOR's new events never bound whenever an older-MINOR addon loaded first.
  - The loading failsafe is re-anchored on every phase transition. PLAYER_ENTERING_WORLD and LOADING_SCREEN_ENABLED now refresh the existing "loading" blocker's `openedAt` (not just open it once), and they stop any in-flight render-settle monitor first. A back-to-back loading screen can no longer flush queued alerts behind the second screen, and a slow load no longer trips the watch timeout mid-screen because the window is measured from the latest screen.
  - Render-settle is generalized to every freeze-backed overlay. CINEMATIC_STOP and STOP_MOVIE now run the same settle monitor as LOADING_SCREEN_DISABLED instead of flushing synchronously on the event frame, so alerts released after a cinematic or movie no longer play unseen during the post-overlay streaming freeze. The settle monitor carries a `settleTimeout` (5s) deadline so hardware that never produces a smooth frame still flushes the alert (visible during mild hitching) rather than falling through to the destructive watch-timeout drop.
  - Cinematic and movie blockers now carry the `mediaTimeout` (600s) failsafe, so a missed CINEMATIC_STOP / STOP_MOVIE can no longer wedge the guard for the rest of the session.
  - The login window is covered. When the library arms while the player is not yet in the world (`IsLoggedIn()` false), it opens the loading blocker immediately, so alerts fired from PLAYER_LOGIN handlers before PLAYER_ENTERING_WORLD are deferred instead of playing behind the initial loading screen.
  - Load-on-demand frame watches retry. EventToastManagerFrame, TalkingHeadFrame, and MajorFactionRenownFrame are re-hooked on ADDON_LOADED, so frames that do not exist at arm time (their Blizzard addon loads later) are still watched once they appear.
  - The deferred queue can no longer grow without bound. `Poll` prunes items past their hold every tick even while a blocker is still open, so a stuck blocker no longer retains queued closures (and everything they capture) for the whole session.
  - `_flush` runs each callback under `pcall`. One erroring alert callback no longer aborts the shared cross-addon flush or drops every later-queued alert; the error is routed to `geterrorhandler`.
  - `RunWhenClear` flushes any pending queue before running an immediate callback, so when a blocker has silently expired the older queued alerts still fire before the new one (no newest-first inversion). A non-table second argument is now ignored instead of raising, and `SetClock` rebases live blocker and queue timestamps so swapping the clock cannot corrupt timeout math.
- **MINOR 4**: LOADING_SCREEN_DISABLED no longer clears the loading blocker immediately; it arms a render-settle monitor that waits for `settleFrames` (10) consecutive rendered frames each under `settleFrameTime` (0.1s) before unblocking. Right after a loading screen the client commonly freezes for several seconds while streaming assets; an alert flushed at LOADING_SCREEN_DISABLED ran its whole display-and-fade inside that freeze (animations catch up their missed wall-clock time in one step when frames resume), so the player heard it but never saw it. A hitch frame resets the settle counter; the blocker's watch timeout and the queue ticker remain the failsafe if rendering never settles.
- **MINOR 3**: the guard now watches loading screens. PLAYER_ENTERING_WORLD and LOADING_SCREEN_ENABLED open a "loading" blocker (with the standard watch timeout failsafe) and LOADING_SCREEN_DISABLED clears it, so alerts queued at login or during a zone transition fire only after the loading screen drops instead of playing unseen behind it. Default watches now arm eagerly at library load (previously lazily on first IsBlocked call), guaranteeing the login PLAYER_ENTERING_WORLD is not missed. `RunWhenClear(fn, opts)` accepts an optional per-call `opts.maxHold` so long-lived alerts (for example a login mail notice) can outlive the default 10s queue hold during a slow loading screen. Fully backward compatible: one-argument RunWhenClear calls keep the default hold.
- Soundpack test brought back in sync with the de-duped, renamed sound list from the earlier soundpack normalization (37 registrations, Zombie Attack / Low Boom / Combo Breaker canonical names); the registration count now also asserts against `#lib.sounds + #lib.builtins` so the bound cannot silently drift again.

### Library consolidation

- LibMatic-CombatTextBounce-1.0 (MINOR 5), LibMatic-FitText-1.0 (MINOR 1), and LibMatic-RosterFrame-1.0 (MINOR 2) moved into this repo from the individual addons. LibMatic is now the single source of truth for all five shared modules. Consumer addons symlink `Libs/LibMatic` to this repo for development and fetch it as a BigWigs packager external for release.

### LibMatic-Settings

- **MINOR 13**: per-addon descriptions in the General page cross-addon nav block. Each released addon's blurb renders as a full-width, word-wrapped node between its icon and its CurseForge link, localized into every shipped client locale (enUS, deDE, esES, esMX, frFR, itIT, koKR, ptBR, ruRU, zhCN, zhTW; enGB falls back to enUS) and resolved by `GetLocale()`. New `lib:GetAddonDescription(key)` accessor returns the locale-resolved blurb. CritMatic, GearMatic, and RepMatic ship descriptions; the running addon is still excluded from its own block, and addons without a description (or not yet released) render unchanged.
- **MINOR 12**: `BuildShadowControls` returns a shared "Shadow Strength" range node
  (0..1, step 0.05) so consumers source the alert shadow-opacity slider from the
  library instead of hand-rolling it. Mirrors `BuildCountUpControls`.
- **MINOR 11**: `BuildCountUpControls` accepts an optional `startDelay` spec (`startDelay.get`/`startDelay.set`, `names.startDelay`/`names.startDelayDesc`, and `startDelayOrder`/`startDelayWidth`/`startDelayMin`/`startDelayMax`/`startDelayStep` tuning). When supplied it returns a `startDelay` range node, so consumers source the fade-delay-when-animation-off control from the shared builder instead of hand-rolling it. Backward compatible: with no `startDelay` spec the builder returns the prior `toggle`/`duration`/`hold` set unchanged.
- **MINOR 6**: `BuildSoundPage` accepts `opts.scan`, adding a "Scan for Custom Sounds" button that calls `LibMatic-CustomMedia-1.0:Scan(opts.scan.addon)` and reports the result via optional `opts.scan.onResult(sounds, fonts)`.
- **MINOR 3**: `BuildAlertPage` accepts `opts.position`, adding a "Position Alert" button to the Alert page's General tab. The button opens `LibMatic-AlertPositioner-1.0`; `opts.position` carries the `get`/`set`/`default`/`close`/`reopen` callbacks.
- **MINOR 2**: roster entries carry a `released` flag. The General page cross-addon nav block renders an addon's icon and CurseForge link only when its roster entry is `released`, so an ILA addon not yet on CurseForge does not show a dead link. RareMatic, QuestMatic, and MountMatic added to the roster as not-yet-released.
- **MINOR 1**: initial release. `BuildGeneralPage`, `BuildAlertPage`, `BuildSoundPage`, `BuildSocialPage`, `BuildChangeLogPage`, `BuildProfilesPage`, plus `ResolveShow` and `GetRoster`. Ace3 page-builder helper; the consumer keeps its own `RegisterOptionsTable` / `AddToBlizOptions`. Per-page and per-setting developer `show` flag, closure or `bind` get/set, a canonical cross-addon roster with per-faction link icons (the running addon is excluded from its own nav block), configurable alert notification format strings, and a two-option alert bounce-style picker driven by consumer-supplied get/set.

### LibMatic-AlertShadow

- **MINOR 1**: initial release. `ApplyTextShadow` and `ApplyIconShadow` give alert
  text and icons a strength-adjustable drop shadow (0 = none, 1 = full). The icon
  shadow is a cached black silhouette behind the icon, since icon textures have no
  native shadow. Pure, flavor-agnostic.

### LibMatic-OverlayGuard

- **MINOR 2**: `SimulateOverlay(style, seconds)` shows a faithful look-alike of a
  native dark screen (level-up, renown, cinematic) and defers a queued alert behind
  it, so the deferral can be previewed in-game on any flavor without a real event.
- **MINOR 1**: initial release. `RunWhenClear`, `IsBlocked`, `Block`, `Unblock`,
  `Poll`, `RegisterWatch`. Holds an alert while a native dark overlay is on screen
  (cinematics and movies on every flavor, the EventToast level-up and celebration
  banner, the talking head; retail addons can register the renown celebration
  frame), then replays it once the overlay clears. Stale callbacks past a 10s hold
  are dropped; a 20s per-watch timeout plus a 0.5s poll keep the queue from
  stalling.

### LibMatic-AlertPositioner

- **MINOR 1**: initial release. `lib:Open(opts)`. A drag-to-place overlay for on-screen alerts: a movable frame with a looping mock crit, Accept/Cancel/Reset buttons, and Escape-to-cancel. Works in resolution-independent center-relative percentages. `LibMatic-CombatTextBounce-1.0` soft dependency for the bounce, with a local fallback.

### LibMatic-CustomMedia

- **MINOR 2**: `fontFileLoads` now wraps the candidate `SetFont` probe in `pcall`. On current clients `SetFont` raises a hard "Invalid font asset: file not found" error for a missing slot file, which previously propagated out of `Register`/`Scan` and could abort the caller's `OnEnable`. Missing or unsupported font slots are now skipped silently, the normal case where a consumer registers more slots than the player has filled.
- **MINOR 1**: initial release. `Register`, `Scan`, `GetRegistered`. Drop-in custom sounds and fonts: a player renames a file to a fixed slot (`Sound5.ogg`, `Font5.ttf`) in the addon's `CustomSounds`/`CustomFonts` folder; the library load-checks each slot and registers the valid ones into LibSharedMedia-3.0 under per-addon namespaced names. Fonts validate silently at login; sounds probe (`PlaySoundFile` + `StopSound`) behind a Scan button and register from a consumer-persisted cache. Only `.ogg`/`.mp3` and `.ttf`/`.otf` paths are ever referenced. LibSharedMedia-3.0 soft dependency.

### LibMatic-SoundPack

- **MINOR 3**: 15 sounds added to the pack: Alarm Clock, Anima Cast, Artifact Unlock, Banshee, Deathwing Wound, Epic Loot Toast, Icy Touch, Legendary Toast, LFG Denied, Mission Complete, Shop, Shop 2, Ten, Titan Light, World Quest Complete. Every consumer (CritMatic, GearMatic, RepMatic, and the rest) picks them up automatically through `Register`; no addon-side change required.

### LibMatic-Broadcast

- **MINOR 2**: the leader/assistant `RAID_WARNING` shortcut now runs only when election is enabled. With election off, `Broadcast` is a plain direct send with no raid-warning path. An explicit `opts.channel` (for example `GUILD`) is now honored even when the player is not in a party or raid, so election-off consumers can broadcast to guild and other channels.
- **MINOR 1**: initial release. `Register`, `Get`, `Broadcast`, `GetPeers`, `SetElectionEnabled`. Group chat broadcast with an opt-in single-announcer election: one elected peer sends, receiver-side dedupe collapses simultaneous triggers, raid leader/assistant sends a `RAID_WARNING` directly, LFR uses `INSTANCE_CHAT`. The `electionEnabled` dev flag is off by default. `marker` is emitted as the locale-safe `{rtN}` token. AceComm-3.0 soft dependency with a direct-send fallback.

### LibMatic-Startup

- **MINOR 1**: initial release. `Register`, `Get`, `PrintBanner`, `GetBannerText`, `PrintHelp`, `GetHelpLines`, `GetAddOnVersion`, `GetBrandColor`. Renders the chat-frame load banner and slash-command help listing, with optional faction colouring (Alliance blue, Horde red). Renders text only and never registers slash commands.

### LibMatic-ChangeLog

- **MINOR 2**: changelog text renders in one uniform colour, default black, with no coloured version or section headers. `BuildOptionsTable` accepts `opts.profile` as a function so a consumer can register its options panel before its database exists.
- **MINOR 1**: initial release. `lib:Show(spec)`, `lib:ShowIfNew(spec, lastSeenVersion)`, `lib:Close()`, `lib:BuildOptionsTable(opts)`. Structured changelog entries, AceGUI popup with LibSharedMedia styling, StaticPopup fallback.

### LibMatic-IconPicker

- **MINOR 2**: bump strata to `FULLSCREEN_DIALOG` and set `Toplevel`. Fixes z-order conflicts where a host addon's window (also at `DIALOG` strata) painted its tab buttons and widgets through the picker.
- **MINOR 1**: initial release. `lib:Open(currentIcon, callback)`, `lib:Close()`, `lib:IsOpen()`. Callback receives chosen fileDataID or nil.

### Test harness

- `/libmatictest` (alias `/lmt`) opens the picker for visual verification. The `changelog`, `startup`, `settings`, `media`, and `broadcast` subcommands exercise the matching modules. The `settings` subcommand also asserts the alert positioner button and the custom-media scan button are built; the `media` subcommand registers and scans custom media.
