Wikki's Cooldown Pulse — Full Changelog
========================================
Latest version : 2.3.3 (r91)
Last updated   : 2026-06-29

Entries are listed newest first.

========================================================================
2.3.3 r91 | community | 2026-06-29 | r91 | UI-E: Test Pulse shows real ability icon; DynamicImage anchor fix
========================================================================

UI-E (Source/wcdp.lua, Source/Templates.xml) — Two improvements to the pulse
  preview visual:

  (1) Test Pulse real icon: the "Test Pulse" button in the General config page now
      displays the icon of the most recently pulsed ability (or the first ability
      found in the type cache) instead of the static wcdp_heart placeholder.
      Falls back to wcdp_heart if no ability has been seen yet in the session.
      Reinitialize() now also resets _lastAbilityPulsed to 0 for full state
      isolation between profile switches.

  (2) DynamicImage tiling fix (Source/Templates.xml): all four runtime frames
      (WCDPFrame, WCDPMoraleFrame, WCDPPetFrame, WCDPHealthFrame) previously
      anchored their DynamicImage to fill the entire parent frame via
      topleft+bottomright anchors. WAR/RoR tiles the texture to fill the image
      bounds, so a 96x96 frame with TexDims=48 produced a 2x2 grid of icons.
      Fixed by replacing the fill anchors with a single center anchor and an
      explicit Size matching TexDims (64x64 for ability/morale/pet, 48x48 for
      health). One icon is now always displayed, centered in the anchor frame,
      regardless of the frame size the player sets via the Layout Editor.

Total tests: 94 passed, 0 failed (2 new cases: wcdp_heart fallback + real icon path).

========================================================================
2.3.3 r90 | community | 2026-06-29 | r90 | UI-D: all config page windows unified at 830 px (WCDPWindowPlain)
========================================================================

UI-D (Configuration/WCDPConfig_General.lua) — Fix: the Enable Debug Messages and
  Show Minimap Button checkboxes in the General page were partially or fully outside
  the WCDPWindowPlain parent window bounds (height was 640 px; the Display section
  content reaches ~645 px).

  Root cause: WAR/RoR clips input hit-testing to the parent window bounds.  Child
  windows positioned beyond the bottom edge of the parent lose their input events,
  which are instead caught by WCDPConfigScrollChild beneath.  The Abilities page had
  the same architecture but a taller window (830 px), so no issue there.

  Fix: General and Low Health page windows raised to 830 px, matching Abilities.
  All three now use WCDPWindowPlain (required for windows > 500 px to avoid
  EA_Window_Default tiling artefacts).  Low Health was previously WCDPWindowDefault
  at 340 px — switched to WCDPWindowPlain together with the height change.

  Final heights: General = 830 px, Abilities = 830 px, Low Health = 830 px,
  Help = 960 px.  All pages use WCDPWindowPlain.

========================================================================
2.3.3 r89 | community | 2026-06-28 | r89 | UI-C: General checkboxes click area extended to label + CaptureInput
========================================================================

UI-C (Configuration/WCDPConfig_General.lua) — UX improvement: clicking the text
  label of the Enable Sound, Enable Debug Messages, and Show Minimap Button
  checkboxes in the General page now toggles the checkbox, matching standard UI
  conventions.

  Context: the root cause of the WCDPConfigScrollChild input problem was the
  General page window being too short (fixed in r90 — UI-D).  However, even with
  the correct window height, clicking on a label text would not toggle its checkbox
  because LibGUI Label windows are handleinput=false by default; only the small
  icon button itself was clickable.

  Fix (two parts):
  (1) e:CaptureInput() called explicitly on each of the three checkboxes as a
      defensive measure — EA_Button_DefaultCheckBox handleinput is not guaranteed
      true when created via LibGUI (Enemy/ConfigurationWindow.xml uses it with
      handleinput="false" explicitly).
  (2) Label click passthrough: each text label now calls CaptureInput() and has
      OnLButtonDown/OnLButtonUp handlers that delegate to the corresponding
      checkbox toggle + the BUG-B radio-group save/restore logic.  Clicking
      anywhere on the label text is now equivalent to clicking the checkbox icon.

  Note: part (1) alone did not fix the WCDPConfigScrollChild symptom for
  EnableDebug and ShowMinimapButton — that required the window height fix (r90).

  The BUG-B radio-group fix (r88) is fully preserved: shared named functions are
  reused by both the checkbox and its label handler, ensuring no state divergence.

Total tests: 93 passed, 0 failed (no new test cases — UI behaviour, not engine logic).

========================================================================
2.3.3 r88 | community | 2026-06-28 | r88 | BUG-A/B: Filters crash + General checkbox radio-group fix; dead code removal; test coverage
========================================================================

BUG-A (Configuration/WCDPConfig_Filters.lua) — Crash when "Add Ability" is clicked
  after a right-click clear in the Filters page.
  WCDPConfig_Filters_ClearAbility() was setting currentAbility = nil.  A subsequent
  click on "Add Ability" (before any new ability was dropped) reached
  currentAbility.id, causing a Lua "attempt to index a nil value" error.
  Two changes applied:
  (1) ClearAbility now resets currentAbility = {} (empty table) instead of nil,
      preserving the invariant that currentAbility is always a table.
  (2) WCDPConfig_Filters_AddAbilityToFilter now has an explicit early-return guard
      `if currentAbility == nil or currentAbility.id == nil then return end`
      as defence-in-depth.

BUG-B (Configuration/WCDPConfig_General.lua) — General page checkboxes behaved as
  radio buttons: clicking one unchecked the other two.
  WAR/RoR makes same-parent checkboxes mutually exclusive (CLAUDE.md §5).  The
  General page had three checkboxes (Enable Sound, Enable Debug, Show Minimap
  Button) in the same parent window (WCDPWindowPlain) without the OnLButtonDown/Up
  save-and-restore pattern already applied to Abilities and Low Health pages.
  Fix: added three module-level state variables (_genSoundEnabled, _genDebugEnabled,
  _genMinimapEnabled) and the standard OnLButtonDown/Up handlers on each checkbox.
  RevertGeneral() now initialises these variables from db so state is always
  consistent after a Revert or profile switch.

STRUCT-A (Source/wcdp.lua) — Removed WCDP.UpdateLayoutWindow() and
  WCDP.UpdateRuntimeWindow().  These two functions were marked "legacy wrappers kept
  for config Apply compatibility" but no config file called them (verified by reading
  all four config files in full).  Dead code removed.

STRUCT-B (Configuration/WCDPConfig_Filters.lua) — Removed two commented-out lines
  (--e:SetText(...) and --e:Texture(...)) left in the Ability Drop Button creation
  block.  Dead comments with no functional relevance.

STRUCT-C (Source/Profiles.lua) — Added `; break` to the name-to-index loop in
  DeleteProfile(), matching the identical pattern already present in ActivateProfile()
  and CopyProfileToActive() (documented as "BUG-3: stop at first match").  No
  functional impact since AddProfile() prevents duplicate names, but the inconsistency
  was a latent defensive gap.

TEST-A (tests/unit/test_firePulseRouting.lua) — Added 3 cases covering the
  per-category sound gate in FirePulse (wcdp.lua:658-668):
  - actions.sound=false suppresses PlayPulseSound
  - morale.sound=false suppresses PlayPulseSound
  - pet.sound=false suppresses PlayPulseSound
  These paths existed since 2.2.4 (r63) but had no unit test coverage.

Total tests: 93 (90 + 3 sound gate), 0 failures.

========================================================================
2.3.2 r87 | community | 2026-06-28 | r87 | BUG: Test Pulse invisible without prior real pulse
========================================================================

FIX (Source/wcdp.lua — WCDP.TestPulse) — TestPulse was invisible on first use
  because it only called WindowStartAlphaAnimation but never set a texture on
  WCDPFrameImageSquare. A real FirePulse call sets the texture via
  DynamicImageSetTexture, but TestPulse bypasses FirePulse entirely, so the
  frame had no texture until at least one ability pulse had fired in the session.
  Fix: TestPulse now calls DynamicImageSetTexture("WCDPFrameImageSquare",
  "wcdp_heart", 0, 0) before starting the animation, ensuring a recognisable
  icon is always visible as a placeholder.

ADD (tests/unit/test_testPulse.lua) — New case verifies that TestPulse calls
  DynamicImageSetTexture on WCDPFrameImageSquare with "wcdp_icon" texture.

Total tests: 90 (89 + 1 TestPulse texture), 0 failures.

========================================================================
2.3.2 r86 | community | 2026-06-28 | r86 | UI-5: fix x-alignment drift in General and Low Health pages
========================================================================

FIX (Configuration/WCDPConfig_General.lua) — General and Low Health pages had
  elements drifted to x=35 instead of x=25, inconsistent with the Abilities
  page which is now the reference for all section layouts.
  - General / Animation section: StartAlpha_Label anchor changed from +20 to
    +10 relative to Animation_SubTitle. All subsequent labels (End Alpha,
    Pulse Speed, Test Pulse button) cascade to x=25 automatically.
  - General / Display section: Display_SubTitle correction changed from -20 to
    -10 (offset from TestPulse_Button, which now sits at x=25 after the
    Animation fix). EnableDebug_Checkbox anchor changed from +20 to +10
    relative to Display_SubTitle.
  - Low Health: Threshold_Label anchor changed from +10 to +0 relative to
    Enable_Checkbox, matching the Abilities pattern where MinCD_Label uses
    x=+0 (not +10) from its section's Enable checkbox.

ADD (tests/unit/test_layout_heights.lua) — 2 new alignment tests verify:
  General Animation/Display use +10 indent (not legacy +20), and Low Health
  Threshold_Label uses +0 from Enable_Checkbox (not legacy +10).

Total tests: 89 (87 + 2 alignment), 0 failures.

========================================================================
2.3.2 r85 | community | 2026-06-28 | r85 | UI-4: standardize description label heights across all config pages
========================================================================

FIX (Configuration/WCDPConfig_General.lua) — All multi-line description labels
  now use two shared constants (DESC_H=54, DESC_H_L=72) instead of ad-hoc
  literal values. Previously, labels in the same section used inconsistent
  heights (e.g., Actions desc = 54px but Morale/Pet/OnAvailable descs = 36px),
  causing visible text truncation for longer descriptions like
  desc_min_cooldown_morale and desc_pulse_speed.
  Changes by page:
    General   : StartAlpha/EndAlpha 36->54, PulseSpeed 36->72; window 560->640px.
    Abilities : MoraleMinCD 36->72, OnAvailable/Pet 36->54; window 750->830px.
    LowHealth : Threshold/Interval 36->54; window 300->340px.
    Help      : Tip1-4, Slash2, Credits 36->54; window 850->960px.

ADD (tests/unit/test_layout_heights.lua) — 5 new tests verify: DESC_H >= 54,
  DESC_H_L >= 54, no Resize(350,N<54) literal, no Resize(370,N in (30,54))
  literal, and all 4 page windows have the expected heights {340,640,830,960}.

Total tests: 87 (82 + 5 layout), 0 failures.

========================================================================
2.3.2 r84 | community | 2026-06-28 | r84 | Fix locale regression: AceLocale proxy aliasing broke all config descriptions
========================================================================

FIX (enUS.lua) — Reverted T[x]=T[y] aliasing introduced in r83 for the 4
  "Enable Sound" keys. In WAR/RoR's AceLocale-3.0, reading T[key] inside
  NewLocale returns nil (the proxy only supports __newindex writes during
  locale loading). The aliasing silently stored nil for all 4 keys and
  triggered an error that prevented all subsequent keys from loading —
  causing every config page to display raw key names instead of strings.
  All 4 keys now use direct L"Enable Sound" assignments.

ADD (tests/unit/test_locale.lua) — New regression test suite that loads
  enUS.lua with a proper read/write AceLocale proxy stub (not the existing
  write-only stub). 6 cases verify that keys return translated strings, that
  the Enable Sound aliases are registered, and that late keys (defined after
  the aliases) are not silently broken. The test suite will catch any future
  aliasing regression before it reaches the game.

Total tests: 82 (76 + 6 locale), 0 failures.

========================================================================
2.3.2 r83 | community | 2026-06-28 | r83 | Code quality: bugs, perf, sound, localization, dead code removal, new tests
========================================================================

BUG-3 (Profiles.lua:ActivateProfile, CopyProfileToActive) — Added `break` after
  the name-to-index resolution in both profile-name lookup loops. Without it,
  a second profile with the same name (prevented by AddProfile but defensively
  handled) would overwrite the resolved index with the last match.

BUG-4 (WCDPConfig_Filters.lua:AddAbilityToFilter, DeletedSelectedFilter) — Added
  `if abilityList == nil then return end` guard. If the filter combo returned an
  unexpected index (not 2 or 3), abilityList stayed nil and the subsequent
  ipairs/tremove call would error. The button is normally disabled via
  UpdateEnableBits but the function-level guard was missing.

PERF-1 (wcdp.lua:Reinitialize) — Removed `abilityTypeCache = {}` from
  Reinitialize(). Ability types are stable properties that do not change
  during a session; clearing the cache on every Apply/profile-switch caused
  an unnecessary re-fetch spike on the next frame.

SON-1 (wcdp.lua:PlayPulseSoundDirect) — Fixed `_soundCooldown` being set even
  when no sound was actually played (unknown soundKey -> soundId=nil). The 150ms
  throttle now only activates when PlaySound/Sound.Play was actually called,
  so a queued sound is no longer delayed by a silent "play" attempt.

UI-2 (Localization/enUS.lua) — Removed dead key `help_version_label` (L"Version:")
  that was defined but never referenced anywhere in the codebase.

UI-3 (Localization/enUS.lua) — Consolidated the four distinct "Enable Sound" keys
  (Enable Sound With Pulse, Enable Pulse Sound, Enable Sound With Low Health Pulse,
  Enable Sound With Morale Pulse) — all translating identically — into a single
  canonical key T["Enable Sound"]. Old keys kept as aliases for compatibility.

STRUCT-1 (Profiles.lua) — Removed WCDP.Set() and WCDP.Get() — unused vestiges
  of the original Squared.lua source, not called anywhere in 2.3.x.

STRUCT-2 (Profiles.lua) — Removed 5 unused local captures: unpack, max, min,
  wstring_sub, wstring_format. These were declared for performance but never
  used in Profiles.lua.

STRUCT-3 (Profiles.lua + WCDPConfig_Filters.lua) — Deduplicated deepcopy.
  Exposed as WCDP.DeepCopy() in Profiles.lua; WCDPConfig_Filters.lua now
  delegates to it via a local alias instead of maintaining its own copy.

STRUCT-4 (WCDPConfig.lua) — Removed WCDPConfig.UpdateColorSelection() and
  WCDPConfig.UpdateAlphaSelection() — unused vestiges of an old slider system,
  not called anywhere in 2.3.x.

STRUCT-5 (wcdp.lua:upgradeProfile) — Removed dead `local oldVersion = db.general.version`
  capture. Version migration is handled by updateSettings() in ActivateProfile;
  only the version stamp update was needed.

TEST-1 (tests/unit/test_testPulse.lua) — New test suite for WCDP.TestPulse()
  (added r80/P11). 4 cases: valid passthrough, nil fallback to db.alpha.*,
  out-of-range alpha clamp [0,1], negative speed clamp to 0.

TEST-2 (tests/unit/test_hooks.lua) — New integration test suite for
  WCDP.OnAbilityUpdate and WCDP.OnMoraleUpdate hooks. 3 cases: pulse after
  cooldown completes via OnAbilityUpdate, morale guard (_lastMoraleAbilityCast)
  via OnMoraleUpdate, and pre-hook maxCooldown capture correctness.

Total tests: 76 (69 + 4 testPulse + 3 hooks), 0 failures.

========================================================================
2.3.2 r82 | community | 2026-06-28 | r82 | Bug fixes: filter icon, profile delete, Low Health login flash, Help page tiling
========================================================================

BUG-1 (WCDPConfig_Filters.lua:328-329) — Fixed undefined variable 'mode' in
  WCDPConfig_Filters_OnLButtonUp_Ability. 'mode' was always nil, causing the
  SquareIcon to always show and the CircleIcon (morale) to always be hidden
  in the filter drop zone, regardless of ability type. Replaced with
  abilityData.abilityType.

BUG-2 (Profiles.lua:DeleteProfile) — Fixed stale active profile index after
  deleting a profile with a lower index than the currently active one.
  tremove() shifts all indices down but 'active' was not adjusted, causing
  out-of-bounds access on subsequent profile operations. Now decrements
  WCDP.Profiles.active when source < active.

BUG-5A (Templates.xml + .mod) — Fixed large red heart icon appearing in the
  top-left corner during character login. Two changes:
  (1) Added show="false" to all four runtime pulse frames in the .mod so they
      start hidden before OnLoad positions and alpha-zeroes them.
  (2) Removed the static texture="wcdp_heart" from WCDPHealthFrameImageSquare
      in Templates.xml. The heart is now loaded on demand via
      DynamicImageSetTexture in FirePulse, consistent with the other frames.

BUG-5B (wcdp.lua:CheckLowHealthPulse) — Fixed false Low Health pulse firing
  immediately after zone transition. Added a 3-second startup grace period
  (_lowHealthStartupTimer) reset by Reinitialize(). CheckLowHealthPulse
  returns early while the timer counts down, absorbing transitional zero-HP
  values that occur while zone data is still loading.

UI-1 (WCDPConfig_General.lua:571) — Fixed visual tiling artefacts in the Help
  page ScrollWindow. The Help window (850 px) was using WCDPWindowDefault
  which tiles EA_Window_Default at regular intervals when scrolled. Changed
  to WCDPWindowPlain, consistent with the General (560 px) and Abilities
  (750 px) pages (CLAUDE.md §5.0).

========================================================================
2.3.1 r81 | community | 2026-06-28 | r81 | Help page: fix outdated label name + add Test Pulse tip
========================================================================

FIX — help_lowhealth text referenced the old label "Enable Sound With Low Health
  Pulse" which was renamed to "Enable Sound" in r80 (P8). Updated to match.

ADD — New tip "Tip4" in the Help page Tips section: explains the Test Pulse
  button added in r80 (P11). Help window height extended 800 -> 850 px.

========================================================================
2.3.1 r80 | community | 2026-06-28 | r80 | P5-P11: UX polish — descriptions, labels, button order, Copy confirm, Test Pulse
========================================================================

P5  — Clarified desc_pulse_speed: replaced vague "half-cycle duration" with
       concrete blink-rate examples (0.20 s ~ 2.5/sec; 0.40 s ~ 1.25/sec).

P6  — Clarified desc_min_cooldown_morale: now explicitly states the field is
       only active when "Pulse When Morale Available" is OFF, and explains
       what it does in that mode.

P7  — Reordered Apply/Revert/Close buttons (WCDPConfig.xml).
       Previous order (left->right): Apply · Revert · Close
       New order: Close · Revert · Apply
       Apply (primary action) is now rightmost, matching RoR dialog conventions.
       Pure XML anchor change — no Lua logic affected.

P8  — Unified all "Enable Sound" checkbox labels to "Enable Sound" (enUS.lua).
       Four distinct strings (Enable Sound With Pulse, Enable Pulse Sound,
       Enable Sound With Morale Pulse, Enable Sound With Low Health Pulse) now
       all display "Enable Sound". Key names unchanged; only display values updated.

P9  — Copy Profile now requires explicit confirmation (WCDPConfig_Profiles.lua
       + WCDPConfig.xml).
       Removed the OnSelChanged auto-copy from WCDPProfileComboBox_CopyProfile.
       Added a "Copy" button next to the combo. Clicking it shows a
       MakeTwoButtonDialog confirmation before calling CopyProfileToActive().

P10 — Highlighted the "NOTE: Profile changes take effect immediately!" label
       in gold (250, 213, 63) using font_clear_small_bold so it stands out.

P11 — Added "Test Pulse" button in the General page Animation section.
       Clicking it calls WCDP.TestPulse(a0, a1, sp) which reads the current
       field values (without Apply) and fires a preview animation on WCDPFrame.
       New public function WCDP.TestPulse() added to wcdp.lua.

========================================================================
2.3.1 r79 | community | 2026-06-28 | r79 | P3+P4: General page sub-section titles + simplified alpha labels
========================================================================

P3 — General page: added visual sub-section separators ("Sound", "Animation",
  "Display") as font_clear_small_bold labels at x=15 (same style as the
  Abilities page sub-sections).  Window height extended from 463px to 560px
  to accommodate the extra labels.

  - "Sound" inserted between the "General" page title and the Enable Sound
    checkbox.  Enable Sound checkbox re-anchored to x=25 from the new label.
  - "Animation" inserted between the Sound ComboBox and the Pulse Start Alpha
    label.  Start Alpha label re-anchored to x=35 from the new label.
  - "Display" inserted between the Pulse Speed description and the Enable Debug
    checkbox.  Enable Debug checkbox re-anchored to x=35 from the new label.

P4 — General page: simplified alpha field labels in enUS.lua.
  Removed the redundant "(0 to 1)" suffix from both labels (the grey
  description text below each field already explains the 0-1 range).
    "Pulse Start Alpha (0 to 1):" -> "Pulse Start Alpha:"
    "Pulse End Alpha (0 to 1):"   -> "Pulse End Alpha:"
  Key names in enUS.lua are unchanged; only the display strings were updated.

========================================================================
2.3.1 r78 | community | 2026-06-28 | r78 | FIX: uniform textbox width across all config tabs
========================================================================

FIX — Configuration UI: all numeric input fields now use the same width
  Two constants existed (NUMERIC_FIELD_WIDTH=50 and NUMERIC_FIELD_WIDTH_WIDE=60).
  Pet Abilities MinCD, Low Health Threshold, and Low Health Interval used the
  50 px value while General and Abilities (Actions/Morale) used 60 px.

  Unified to a single NUMERIC_FIELD_WIDTH = 60 px constant.  All five
  explicit NUMERIC_FIELD_WIDTH_WIDE arguments removed from makeNumericField
  calls.  Visual result: textboxes are identically sized on every page.

  Files changed:
    Configuration/WCDPConfig_General.lua  — constants + 5 makeNumericField calls

========================================================================
2.3.1 r77 | community | 2026-06-28 | r77 | FIX: Abilities page alignment and tile artifacts
========================================================================

FIX — Configuration UI: Abilities page layout corrections
  Two visual defects in the merged Abilities page (introduced in r76):

  1. Tile artifacts (small squares along the left edge): EA_Window_Default
     uses a tiled background texture.  At 750 px the tile seams were visible
     inside the scroll viewport.  The Abilities container now uses
     WCDPWindowPlain (plain <Window>, no chrome) to avoid this.

  2. Horizontal misalignment: the anchor x-offset in the element chain was
     drifting rightward with each section.  "Actions" appeared at x=25
     instead of x=15; "Morale" and "Pet Abilities" at x=35.  Minimum
     Cooldown labels were at x=35 instead of x=25.  Fixed by:
       - Sub-section titles (Actions, Morale, Pet Abilities): x=0 from the
         previous title / x=-10 from the last element of the previous section
         (brings all sub-titles back to x=15, aligned with "Abilities").
       - Enable checkboxes: x=10 from their sub-title (-> x=25).
       - Minimum Cooldown labels: x=0 from their checkbox (-> x=25).

  Rule added to CLAUDE.md §5 to prevent recurrence.

  Files changed:
    Configuration/WCDPConfig.xml          — added WCDPWindowPlain template
    Configuration/WCDPConfig_General.lua  — window template + 9 x-offsets

========================================================================
2.3.1 r76 | community | 2026-06-28 | r76 | UI: merge Actions/Morale/Pet into single Abilities page
========================================================================

IMPROVEMENT — Configuration UI: Actions, Morale and Pet Abilities merged into one page
  The sidebar previously had three separate navigation entries for ability-type
  settings (Actions, Morale, Pet Abilities), each with identical structure
  (enable toggle + minimum cooldown + sound toggle).  The user had to click
  three times to review or compare all three types.

  They are now combined into a single "Abilities" page with three clearly
  labelled sub-sections separated by bold sub-titles (Actions / Morale /
  Pet Abilities).  The sidebar shrinks from 8 entries to 6:
    General | Abilities | Low Health Alert | Filters | Profiles | Help

  All Apply/Revert logic is preserved: clicking Apply on the Abilities page
  writes all three categories (actions, morale, pet) to db simultaneously,
  same as before but in a single round-trip.

  Changes:
    Configuration/WCDPConfig_General.lua
      - actionsConfig, moraleConfig, petConfig tables replaced by abilitiesConfig.
      - InitializeAll creates one 400x750px window with three sub-sections,
        each opened by a font_clear_small_bold section title label.
      - Widget names updated: ActionMinCD_TextBox, MoraleMinCD_TextBox,
        PetMinCD_TextBox, ActionSound_Checkbox, MoraleSound_Checkbox,
        PetSound_Checkbox (previously shared generic names per-window).
      - ApplyAbilities() / RevertAbilities() replace the three separate
        Apply/Revert functions.
      - RegisterWindow called once for abilitiesConfig (was three calls).
    Localization/enUS.lua
      - New key: T["Abilities"] = "Abilities" (page title in sidebar).

  No behaviour change for the pulse engine (wcdp.lua / Profiles.lua).
  Tests: 69/69 unchanged (no test covers UI widget structure).

========================================================================
2.3.0 r75 | community | 2026-06-28 | r75 | Use specific morale sound label instead of generic "Enable Pulse Sound"
========================================================================

IMPROVEMENT — Morale config page: sound checkbox now uses its specific label
  The "Enable Pulse Sound" checkbox on the Morale page was using the generic
  T["Enable Pulse Sound"] key, while the Low Health page already used its own
  specific T["Enable Sound With Low Health Pulse"].  T["Enable Sound With Morale
  Pulse"] existed in enUS.lua but was orphaned (never referenced in code).
  Fixed: WCDPConfig_General.lua now uses T["Enable Sound With Morale Pulse"]
  for the Morale sound checkbox label.  The label shown in-game changes from
  "Enable Pulse Sound" to "Enable Sound With Morale Pulse", consistent with
  the Low Health page pattern.  No behaviour change.

========================================================================
2.3.0 r74 | community | 2026-06-28 | r74 | Code cleanup: remove dead potion stubs, fix WindowGetAnchor test stub, remove WCDPConfig.GetDBOptions
========================================================================

CODE CLEANUP — Remove dead potion stub code
  CHANTIER 5 (potion cooldown pulse) was planned but never implemented.
  The function WCDP.GetPotionTrackingState() referenced two undefined
  local variables (_potionTracked, _potionScanPending), silently returning
  nil, nil.  Calling the result as a table would have crashed in-game.
  - Removed WCDP.GetPotionTrackingState() from Source/wcdp.lua.
  - Removed the empty CHANTIER 5 comment block from Source/wcdp.lua.
  - Removed tests/unit/test_potionCooldown.lua (tested non-existent functions).
  No behaviour change in-game.

BUG FIX — WindowGetAnchor stub had inverted return order (tests only)
  tests/stubs/api_stubs.lua returned ("center", "Root", "center", 0, 0)
  for WindowGetAnchor, placing relativePoint and relativeWindow in the
  wrong positions — exactly the same bug fixed in wcdp.lua r73.
  The real API returns (point, relativePoint, relativeWindow, x, y).
  Corrected to ("center", "center", "Root", 0, 0).
  No in-game impact; only affects future tests that exercise
  OnLayoutEditorFinished.

CODE CLEANUP — Remove WCDPConfig.GetDBOptions (dead code)
  WCDPConfig.GetDBOptions() referenced an undefined variable dbOptions,
  which would cause a Lua error if called.  The function was never called
  anywhere in the codebase (leftover from a previous iteration).
  Removed from Configuration/WCDPConfig.lua.

========================================================================
2.2.6 r73 | community | 2026-06-28 | r73 | Fix anchor positions reset after reloadui (WindowGetAnchor return order)
========================================================================

BUG FIX — Anchor positions reset to top-left after every reloadui following a Layout Editor drag
  Root cause: WindowGetAnchor(win, 1) returns values in the order
  (point, relativePoint, relativeWindow, x, y) — but OnLayoutEditorFinished
  was assigning them as (point, relWin, relPoint, x, y), silently swapping
  relativeWindow and relativePoint.

  On the next load, UpdateLayoutWindowForCategory called:
    WindowAddAnchor(win, point, relWin, relPoint, x, y)
  which maps to:
    WindowAddAnchor(win, point, relativeWindow="center", relativePoint="Root", x, y)
  The client found no window named "center", so the anchor failed and the frame
  fell to position (0,0) — top-left corner.

  This only manifested after the first Layout Editor drag.  Before any drag,
  db.anchor came from DefaultSettings (relwin="Root", relpoint="center"), which
  happened to be in the correct order for WindowAddAnchor, so the bug was invisible.

  Diagnosed via debug output of OnLayoutEditorFinished (r72):
    point=center  relwin(stored)=center  x=-300  y=1.18
  The 2nd return value "center" was being stored as the window name, not as the
  anchor point — proving the swap.

  Fix: explicitly capture the 5 return values in correct order:
    local _pt, _rpt, _rwin, _x, _y = WindowGetAnchor(wins.layout, 1)
    anc.relwin  = _rwin   -- 3rd return: the window name (e.g. "Root")
    anc.relpoint = _rpt   -- 2nd return: the anchor point (e.g. "center", "left")

  Migration: no profile reset required.  Simply drag the anchors once in the
  Layout Editor, close it, then reloadui — positions are saved correctly from
  that point on.

========================================================================
2.2.6 r72 | community | 2026-06-28 | r72 | [diagnostic only — superseded by r73]
========================================================================

  Added d() debug output to OnLayoutEditorFinished to identify why anchor positions
  were resetting.  Revealed the WindowGetAnchor return-order swap (see r73).

========================================================================
2.2.6 r71 | community | 2026-06-28 | r71 | Revert LE registration to 2.2.4 architecture (fix anchor reset on profile switch and reloadui)
========================================================================

BUG FIX — Anchor positions reset after reloadui and on profile/new profile creation
  Root cause: r67-r70 over-engineered the LE registration, introducing two regressions:
  1. Removing UnregisterWindow+RegisterWindow from UpdateLayoutWindowForCategory broke
     the LE's ability to see the window at its new (programmatically set) position after
     a profile switch.
  2. Replacing table.insert(LayoutEditor.EventHandlers) with RegisterEditCallback (r68)
     and removing UpdateAllLayoutWindows from OnLoad (r70) + savesettings="true" (r70)
     broke the db.anchor update cycle entirely on some code paths.

  Fix: revert UpdateLayoutWindowForCategory to the 2.2.4 pattern that provably worked:
  UnregisterWindow -> WindowAddAnchor -> RegisterWindow(saveable=false).
  Revert OnLoad to call UpdateAllLayoutWindows() (positions restored from db.anchor).
  Revert to table.insert(LayoutEditor.EventHandlers) for the callback.
  Revert Templates.xml layout frames to savesettings="false".
  Keep only the two real fixes: r65 (LayoutEditor.EDITING_END) and r66 (runtime frame
  anchored to layout frame).

  NOTE: anchor reset after reloadui was NOT fixed by r71 — that bug had a separate,
  deeper root cause (WindowGetAnchor return-order swap, fixed in r73).

========================================================================
2.2.6 r70 | community | 2026-06-28 | r70 | [SUPERSEDED by r71 — incorrect fix, reverted]
========================================================================

  NOTE: r70 diagnosis was wrong. savesettings="true" and removing UpdateAllLayoutWindows
  from OnLoad broke profile switching (anchors went left on new profile creation).
  Everything introduced in r70 was reverted by r71. Do not reference this entry.

========================================================================
2.2.6 r69 | community | 2026-06-28 | r69 | [SUPERSEDED by r71 — incorrect fix, reverted]
========================================================================

  NOTE: r69 diagnosis was wrong. saveable=true in RegisterWindow does not work as
  expected — the LE may ignore programmatic WindowAddAnchor calls when saveable=true,
  preventing profile switching from moving frames. Reverted by r71.

========================================================================
2.2.6 r68 | community | 2026-06-28 | r68 | [SUPERSEDED by r71 — incorrect fix, reverted]
========================================================================

  NOTE: r68 diagnosis was wrong on two points:
  1. Calling RegisterWindow once (without UnregisterWindow before each position set)
     broke the LE's ability to accept programmatic WindowAddAnchor — frames did not
     move on profile switch.
  2. Replacing table.insert(LayoutEditor.EventHandlers) with RegisterEditCallback
     broke OnLayoutEditorFinished — db.anchor was not updated after LE drag, so
     positions reset on next reloadui. Reverted by r71.

========================================================================
2.2.6 r67 | community | 2026-06-28 | r67 | [SUPERSEDED by r71 — incorrect fix, reverted]
========================================================================

  NOTE: r67 correctly identified that RegisterWindow ordering matters, but the
  true fix required reverting to the full 2.2.4 architecture (r71). The
  RegisterEditCallback change introduced in r68 made things worse. Reverted by r71.

========================================================================
2.2.6 r66 | community | 2026-06-28 | r66 | Fix runtime frame not tracking layout frame after LE drag
========================================================================

BUG FIX — Pulse animation stays at original position after moving the Layout Editor anchor
  Root cause: UpdateRuntimeWindowForCategory anchored the runtime frame (WCDPFrame,
  WCDPMoraleFrame, WCDPPetFrame, WCDPHealthFrame) to "Root" using the saved x/y values
  from db.anchor.  When the user dragged a layout anchor in the Layout Editor, only the
  layout frame moved; the runtime frame stayed at its old Root-relative position because
  it was independently anchored.  OnLayoutEditorFinished was supposed to re-anchor it,
  but that happened only after the LE closed — the pulse would appear at the wrong spot
  until the next reload, and only if the session outlived that LE close.

  Fix: anchor each runtime frame directly TO its layout frame (topleft->topleft,
  bottomright->bottomright, offset 0,0) instead of to Root.  The runtime frame now
  tracks the layout frame automatically at all times — no explicit re-anchor needed
  when the user moves the LE anchor.  This mirrors the XML pattern used for
  WCDPFrameImageSquare (anchored to $parent in Templates.xml).

========================================================================
2.2.6 r65 | community | 2026-06-28 | r65 | Fix pulse frames positioned at wrong location after anchor move
========================================================================

BUG FIX — Pulse animation appears at wrong position (outside the Layout Editor anchor)
  Root cause: UpdateRuntimeWindowForCategory divided anc.x and anc.y by
  InterfaceCore.GetScale() when positioning the runtime frame (WCDPFrame,
  WCDPMoraleFrame, WCDPPetFrame, WCDPHealthFrame), while UpdateLayoutWindowForCategory
  used the same values WITHOUT division.  For the default position (x=0, y=0)
  the division is a no-op, but for any non-zero offset (Morale at 210px, Pet at
  210px, Health at 210px, and any custom Abilities position) the runtime frame
  landed at a DIFFERENT pixel position than the layout frame.  Result: the pulse
  animation appeared outside — or well away from — the visible anchor box.

  The pure/Source/PurePlayer.lua addon (confirmed Game\ROR reference) repositions
  its window with the raw values from WindowGetAnchor, without any scale division.
  Fix: removed / InterfaceCore.GetScale() from the WindowAddAnchor call in
  UpdateRuntimeWindowForCategory.  Runtime frames now use the same anchor values
  as their layout counterparts and appear exactly inside the anchor box.

MAINTENANCE — Replace hardcoded 2 with LayoutEditor.EDITING_END constant
  WCDP compared editorCode == 2 (hardcoded integer) instead of
  editorCode == LayoutEditor.EDITING_END.  The r72 diagnostic later confirmed
  LayoutEditor.EDITING_END == 2 in the RoR client, so the original == 2 was
  not causing a functional bug.  This change is a best-practice alignment with
  all other Game\ROR addons (pure, GuardLine, DAoCBuff, BuffHead, TurrentRange)
  and adds a nil-safe fallback (or 2) in case a future client build changes the value.
  Fix: replaced == 2 with == (LayoutEditor.EDITING_END or 2) in OnLayoutEditorFinished.

========================================================================
2.2.5 r64 | community | 2026-06-27 | r64 | Fix truncated text in Help page (intro and debug slash command)
========================================================================

BUG FIX — Help page: intro paragraph truncated
  The intro label height (54px) was too small to display all four wrapped
  lines. The last line ("watching the action bar.") was clipped.
  Fixed by increasing the label height to 80px.

BUG FIX — Help page: "/wcdp debug" command text truncated
  The debug slash command label had no WordWrap and a height of 22px (one
  line). The text "chat." was cut off at the label's right edge.
  Fixed by adding WordWrap and increasing the label height to 36px.

========================================================================
2.2.4 r63 | community | 2026-06-24 | r63 | Actions as separate config page; per-category sound toggle for Actions and Pet; remove version from Help page
========================================================================

NEW FEATURE — Actions config page separated from General
NEW FEATURE — Enable Pulse Sound on Actions page (db.abilities.actions.sound)
NEW FEATURE — Enable Pulse Sound on Pet Abilities page (db.abilities.pet.sound)
CHANGE — Remove version number from Help page

========================================================================
2.2.3 r62 | community | 2026-06-24 | r62 | Widen General and Morale EditBoxes (50->60 px); Pet and Low Health unchanged
========================================================================

IMPROVEMENT — EditBox width differentiated by tab.

========================================================================
2.2.3 r61 | community | 2026-06-24 | r61 | Fix Help text (remove "potion"); fix GCD sentence clipping on two tabs
========================================================================

FIX — Incorrect "potion" mention removed from Help tab intro.
FIX — GCD sentences truncated on Actions tab and Help tab.

========================================================================
2.2.3 r60 | community | 2026-06-24 | r60 | Audit: confirm all numeric EditBoxes are uniform 50x26 px
========================================================================

VERIFICATION — All eight numeric EditBox fields confirmed identical.

========================================================================
2.2.3 r59 | community | 2026-06-24 | r59 | Factorize numeric EditBox creation; confirm uniform dimensions
========================================================================

IMPROVEMENT — Single-source definition for all numeric EditBox fields.

========================================================================
2.2.3 r58 | community | 2026-06-24 | r58 | Adjust numeric EditBox dimensions (height+width)
========================================================================

IMPROVEMENT — Numeric EditBox height increased and width reduced.

========================================================================
2.2.3 r57 | community | 2026-06-24 | r57 | Fix caret alignment, config revert on open, layout harmonization
========================================================================

BUG FIX — Cursor (caret) misaligned relative to text in numeric EditBoxes.
BUG FIX — Config UI retains pending (unapplied) changes when re-opened.
IMPROVEMENT — Harmonized title-to-first-checkbox gap across all config sections.

========================================================================
2.2.2 r56 | community | 2026-06-24 | r56 | Fix numeric field text alignment in config window
========================================================================

BUG FIX — Numeric input fields display text at the top-left of the box.

========================================================================
2.2.1 r55 | community | 2026-06-23 | r55 | Revert field label alignment to r49 state (leftcenter + right anchor)
========================================================================

REVERT — Field labels and textbox anchoring reverted to r49 approach.

========================================================================
2.2.1 r53 | community | 2026-06-23 | r53 | Fix textbox x-alignment across all config sections
========================================================================

BUG FIX — Textbox values misaligned across config sections (x offset).
BUG FIX — Help page "intro" paragraph still showing garbled character.

========================================================================
2.2.1 r52 | community | 2026-06-23 | r52 | Resize-after-SetText fix, Help glyphs, Help last in sidebar
========================================================================

BUG FIX — Textbox alignment still broken after r51 (LabelSetText auto-resize).
BUG FIX — Help page version label showed garbled characters.
BUG FIX — Help page text contained Unicode em-dashes and curly apostrophes.
FEATURE — Help page moved to last position in the sidebar (after Profiles).

========================================================================
2.2.1 r51 | community | 2026-06-23 | r51 | Field labels right-aligned for guaranteed textbox alignment
========================================================================

IMPROVEMENT — Definitive textbox alignment fix (right-aligned field labels).

========================================================================
2.2.1 r50 | community | 2026-06-23 | r50 | Fix textbox alignment (text-width anchor bug)
========================================================================

BUG FIX — Textbox alignment incorrect after r49 label-width change.

========================================================================
2.2.1 r49 | community | 2026-06-23 | r49 | Help page + field alignment + lowhealth sound fix
========================================================================

BUG FIX — Low Health sound silent on fresh profiles (nil guard).
IMPROVEMENT — Config UI: field label width 300 -> 200 px (textbox alignment).
IMPROVEMENT — Morale min-cooldown description clarified.
FEATURE — New "Help" configuration page.

========================================================================
2.2.1 r48 | community | 2026-06-23 | r48 | Config UI: spacing fix + Actions/General overlap
========================================================================

BUG FIX — "Actions" section title overlapping "Show Minimap Button".
IMPROVEMENT — Uniform spacing across all config pages.

========================================================================
2.2.1 r47 | community | 2026-06-23 | r47 | Config UI descriptions: wordwrap + grey color + missing fields
========================================================================

IMPROVEMENT — Description labels now wrap and are visually distinct.

========================================================================
2.2.1 r46 | community | 2026-06-23 | r46 | Config UI: field descriptions on all pages
========================================================================

IMPROVEMENT — Inline field descriptions in the configuration UI.

========================================================================
2.2.1 r45 | community | 2026-06-23 | r45 | Layout Editor anchor save fix + mincooldown clamping
========================================================================

BUG FIX — Layout Editor anchor: relwin never saved after window move.
IMPROVEMENT — Mincooldown input validation in Apply (Actions, Morale, Pet).

========================================================================
2.2.0 r44 | community | 2026-06-22 | r44 | Config UI split into per-category pages + morale/potions sound toggles
========================================================================

FEATURE — Config UI: Morale, Pet Abilities and Low Health Alert are now separate
  navigation pages in the left sidebar.
FEATURE — "Enable Sound With Morale Pulse" checkbox added to the Morale page.
FIX — Radio-group mutual-exclusion bug extended to Morale.

========================================================================
2.2.0 r42 | community | 2026-06-22 | r42 | pcall guards + WindowStopAlphaAnimation
========================================================================

IMPROVEMENT — Robustness: pcall wrappers on external API calls.
IMPROVEMENT — Animation: WindowStopAlphaAnimation before each WindowStartAlphaAnimation.

========================================================================
2.2.0 r41 | community | 2026-06-22 | r41 | SoundList trimmed to NONE + HELP_TIPS_NEW
========================================================================

IMPROVEMENT — Simplified sound list (2 entries confirmed in-game).

========================================================================
2.2.0 r40 | community fix | 2026-06-22 | r40 | LowHealth window height 170->220
========================================================================

BUG FIX — Sound checkbox still clipped at bottom of scroll area.

========================================================================
2.2.0 r39 | community fix | 2026-06-22 | r39 | scroll child height fix + checkbox icon
========================================================================

BUG FIX — Sound checkbox still partially clipped even after r38.

========================================================================
2.2.0 r38 | community fix | 2026-06-22 | r38 | checkbox icon click region fix
========================================================================

BUG FIX — Clicking the Sound checkbox icon triggers WCDPConfigScrollChild.

========================================================================
2.2.0 r37 | community fix | 2026-06-22 | r37 | GetSlotRowNumForActiveListRow cleanup
========================================================================

CODE QUALITY — WCDPConfig.GetSlotRowNumForActiveListRow: three bugs fixed.

========================================================================
2.2.0 r36 | community fix | 2026-06-22 | r36 | real checkbox fix (handleinput)
========================================================================

BUG FIX — Sound checkbox still unclickable (root cause found via UI Debugger).

========================================================================
2.2.0 r35 | community fix | 2026-06-22 | r35 | force window height >= 540 on load
========================================================================

BUG FIX — LowHealth section still clipped after r34 on existing installs.

========================================================================
2.2.0 r34 | community fix | 2026-06-22 | r34 | checkbox click + scroll height
========================================================================

BUG FIX — Sound checkbox click offset.
BUG FIX — Sound checkbox cut off at bottom of scroll area.

========================================================================
2.2.0 r33 | community fix | 2026-06-22 | r33 | checkbox fix (real fix)
========================================================================

BUG FIX — Low Health sound checkbox conflict (corrected fix).
IMPROVEMENT — Heart icon for Low Health pulse.
IMPROVEMENT — Pulse stops on player death.

========================================================================
2.2.0 r32 | community fix | 2026-06-22 | r32 | heart icon, dead-player stop (checkbox fix BROKEN)
========================================================================

  NOTE: The checkbox fix in r32 was incorrect — see r33 above.

========================================================================
2.2.0 | community update | 2026-06-21 | r31 | 4 anchors, low health, sound fix
========================================================================

CHANTIER 1 — 4 INDEPENDENT PULSE ANCHORS
CHANTIER 2 — LOW HEALTH PULSE
CHANTIER 4 — SOUND QUEUE: REAL DIAGNOSIS AND REAL FIX
BUG FIX — Config UI crash / addon fails to load.
TESTS — 67 tests, 0 failures (Lua 5.4).

========================================================================
2.1.3 | community fix | 2026-06-21 | r30 | sound queue — double pulse
========================================================================

FIX — Sound queue (_soundQueue / _soundCooldown hook-counter implementation).
NOTE — Superseded in 2.2.0 by time-based mechanism.

========================================================================
2.1.2 | community feature | 2026-06-21 | r29 | sound selection
========================================================================

FEATURE — Sound Selection (ComboBox in General config, 5 GameData.Sound.* constants).
TESTS — infrastructure added (tests/ directory, Lua 5.4).  44 tests, 0 failures.

========================================================================
2.1.1 | community fix+feature | 2026-06-21 | nil-retry, sound, minimap menu
========================================================================

FIX — PerformAbilityUpdate: permanent blacklist on nil GetAbilityData() removed.
CHANGE — Sound: GameData.Sound.WINDOW_CLOSE replaced by HELP_TIPS_NEW.
FEATURE — Minimap button right-click context menu (Configuration / Hide Minimap Icon).

========================================================================
2.1.0 | community feature | 2026-06-21 | minimap button
========================================================================

NEW — Minimap button (gold pulse icon) next to the overhead map.

========================================================================
2.0.8 | community fix | 2026-06-21 | pulse sound + visible debug
========================================================================

FIX — Pulse sound never played (Sound.Play() -> PlaySound()).
CHANGE — Debug messages now print to chat window.

========================================================================
2.0.7 | community feature | 2026-06-21 | morale-available pulse + debug
========================================================================

NEW — Morale pulses when rank becomes available (PLAYER_MORALE_UPDATED).
NEW — "Enable Debug Messages" checkbox + "/wcdp debug" slash command.
FIX — Morale and Pet min cooldown saving into Actions min cooldown.

========================================================================
2.0.6 | community fix | 2026-06-21 | morale pulse regression
========================================================================

FIX — Morale abilities never pulsed after 2.0.5 rewrite.

========================================================================
2.0.5 | community fix | 2026-06-11 | peak tracking
========================================================================

FIX — Pulses randomly failed to fire (global cooldown overwrite). Peak tracking added.

========================================================================
2.0.4 | wikki | 2011-01-06 | original release
========================================================================

SVN r23 — Fixed an issue where the same ability wouldn't pulse back-to-back.
SVN r24 — Tagged as 2.0.4.
