Wikki's Cooldown Pulse — Full Changelog
========================================
Latest version : 2.2.4 (r63)
Last updated   : 2026-06-24

Entries are listed newest first.

========================================================================
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
  The "Actions" section has been extracted from the General configuration
  page into its own dedicated sidebar entry ("Actions"), positioned
  immediately below "General" in the navigation list.
  This gives Actions the same prominence as Morale and Pet Abilities.

NEW FEATURE — Enable Pulse Sound on Actions page
  A new "Enable Pulse Sound" checkbox on the Actions page lets you
  independently mute the pulse sound for action cooldowns without
  affecting Morale, Pet, or Low Health sound settings.
  Saved as db.abilities.actions.sound (default: true).

NEW FEATURE — Enable Pulse Sound on Pet Abilities page
  Same checkbox added to the Pet Abilities page.
  Saved as db.abilities.pet.sound (default: true).

CHANGE — Remove version number from Help page
  The "2.x.x" label previously shown under the Help page title has been
  removed. The version is still visible in the main configuration window
  title bar.

========================================================================
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 (r59 introduced uniform 50 px)
  After setting all eight numeric EditBoxes to 50 px (r59), the fields on
  the General and Morale tabs were reported as too narrow: values like
  "0.40" and "60.0" left insufficient visual margin inside the box.
  Pet Abilities and Low Health Alert were reported correct at 50 px
  (shorter values: "4", "10", "1.5").

  makeNumericField() now accepts an optional width parameter:
    NUMERIC_FIELD_WIDTH      = 50  — default (Pet, Low Health)
    NUMERIC_FIELD_WIDTH_WIDE = 60  — explicit (General, Actions, Morale)

  Fields updated to 60 px:
    General  > Pulse Start Alpha   (was 50 px → 60 px)
    General  > Pulse End Alpha     (was 50 px → 60 px)
    General  > Pulse Speed         (was 50 px → 60 px)
    Actions  > Minimum Cooldown    (was 50 px → 60 px)
    Morale   > Minimum Cooldown    (was 50 px → 60 px)

  Fields unchanged at 50 px:
    Pet Abilities  > Minimum Cooldown
    Low Health     > Low Health Threshold
    Low Health     > Repeat Interval

  Height unchanged at 26 px for all eight fields.
  The 60 px value is intentionally between the original 65 px (too wide)
  and the harmonized 50 px (too tight for 4-character values).

========================================================================
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
  enUS.lua / T["help_intro"]:
    Before: "...an ability, morale, pet skill, or potion finishes its cooldown..."
    After:  "...an ability, morale, or pet skill finishes its cooldown..."
  The addon does not expose a user-facing potions feature.  No other
  user-visible text (XML, other tabs, tooltips) referenced potions.

FIX — GCD sentences truncated on Actions tab and Help tab
  Both affected strings are complete in enUS.lua; the display issue was
  Label widget heights too small for the wrapped text.

  Actions tab > Minimum Cooldown description:
    String (complete): "Only pulse if the total cooldown was at least this
      long. The GCD (1.61s) is always excluded."
    Previous height: 36 px (≈1.8 lines at font_chat_text → clipped "excluded.")
    New height: 54 px (3 lines — full text fits)

  Help tab > How It Works > Actions & Pet paragraph:
    String (complete): "Actions & Pet - The icon pulses when the cooldown
      exceeds the configured minimum and reaches zero.  The global GCD
      (1.61s) is always filtered out."
    Previous height: 54 px (borderline 3 lines → clipped "filtered out.")
    New height: 72 px (4 lines available — safe margin)

  Help tab > How It Works > Morale and Low Health paragraphs:
    Both had the same 54 px height with similarly long text.  Increased
    to 72 px proactively to prevent equivalent clipping.

  Help window total height raised from 680 px to 760 px to accommodate
  the three +18 px label increases (3 × 18 = 54 px) plus restore the
  existing 6 px overflow that was already clipping the Credits section.

========================================================================
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

  A report stated that General, Morale, and Pet Abilities tabs still had
  wider EditBoxes than Low Health Alert.  Full code audit performed.

  Source of truth — WCDPEditBoxNumeric template (WCDPConfig.xml):
    <AbsPoint x="50" y="26"/>   → 50 px wide, 26 px tall

  All eight fields go through the same makeNumericField() helper (r59),
  which calls CreateWindowFromTemplate("WCDPEditBoxNumeric") then
  e:Resize(NUMERIC_FIELD_WIDTH=50).  LibGUI reads the template height (26)
  at creation time and preserves it through every Resize call.

  Confirmed dimensions per field:
    General  > Pulse Start Alpha          (L119)  50 x 26 px
    General  > Pulse End Alpha            (L139)  50 x 26 px
    General  > Pulse Speed                (L159)  50 x 26 px
    Actions  > Minimum Cooldown           (L232)  50 x 26 px
    Morale   > Minimum Cooldown           (L281)  50 x 26 px
    Pet      > Minimum Cooldown           (L378)  50 x 26 px
    Low Hlth > Low Health Threshold       (L427)  50 x 26 px  ← reference
    Low Hlth > Repeat Interval            (L447)  50 x 26 px  ← reference

  All tabs: strictly identical, pixel-for-pixel.

  If tabs appear different in game, the most likely cause is that the
  installed addon files predate 2.2.3 r58 (where the 65→50 px change
  was made).  Copy the 2.2.3 directory to the addons folder and use
  /reloadinterface to load the updated version.

========================================================================
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
  All eight numeric input fields (General: 3, Actions: 1, Morale: 1, Pet: 1,
  Low Health: 2) previously used an identical four-line creation pattern
  repeated verbatim.  Any future size or anchor adjustment required eight
  synchronised edits — a divergence hazard.

  Replaced with a local helper:
    local NUMERIC_FIELD_WIDTH = 50
    local function makeNumericField( parentWindow, anchorTarget )
        local e = parentWindow( "Textbox", nil, "WCDPEditBoxNumeric" )
        e:Resize( NUMERIC_FIELD_WIDTH )
        e:Show( true )
        e:AnchorTo( anchorTarget, "right", "left", 10, 0 )
        return e
    end

  All eight call sites are now one-liners (e.g.
    window.W.General.StartAlpha_TextBox =
      makeNumericField( window.W.General, window.W.General.StartAlpha_Label )
  ).  The template (WCDPEditBoxNumeric, 50 x 26 px) and anchor pattern
  ("right" -> "left", 10, 0) are shared by every field; any future
  resize needs only NUMERIC_FIELD_WIDTH or the template XML to change.

VERIFICATION — All four tabs confirmed uniform
  Investigation prior to this revision found that all eight EditBox
  instances already produced identical 50 x 26 px boxes in 2.2.3 r58:
  same CreateWindowFromTemplate call, same LibGUI stored height (26 px
  from the WCDPEditBoxNumeric template), same e:Resize(50) call.
  The factorization makes that guarantee structural rather than incidental.

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

IMPROVEMENT — Numeric EditBox height increased and width reduced
  In r57 the EditBox height was set to 22 px.  In-game observation showed
  the text cursor bar still visually overflowed the box boundaries because
  EA_EditBox_DefaultFrame draws the cursor slightly taller than the text
  glyphs.  Height raised from 22 px to 26 px, providing ~4-5 px of
  vertical margin above and below font_chat_text (approx. 16 px) — cursor
  is now fully contained within the visible box frame.

  Width reduced from 65 px to 50 px.  The longest value that can appear
  in any of the eight fields is "300.0" (5 chars); at font_chat_text
  metrics this requires ~35 px of glyph space.  50 px gives adequate
  padding while eliminating the disproportionate empty space that was
  visible on short values such as "0.5", "4.0" or "20".

  Both dimensions are defined once in the WCDPEditBoxNumeric XML template
  (WCDPConfig.xml) and applied uniformly to all 8 fields via LibGUI's
  stored-height mechanism: the template size is read at creation time and
  propagated by every subsequent e:Resize(50) call.  Fields affected:
    General     — Pulse Start Alpha, Pulse End Alpha, Pulse Speed
    Actions     — Minimum Cooldown
    Morale      — Minimum Cooldown
    Pet Abilities — Minimum Cooldown
    Low Health  — Threshold (%), Repeat Interval

========================================================================
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
  textalign="leftcenter" on EA_EditBox_DefaultFrame moves rendered text to
  the vertical centre of the box but the caret's Y reference in the base
  template is fixed at the "topleft" position, producing a visible gap
  between the insertion cursor and the text — and the gap varied per field
  because each label's auto-resize height differed slightly from the next.
  Fix: removed textalign="leftcenter" from WCDPEditBoxNumeric; reduced box
  height from 32 px to 22 px.  With a 22 px box the default top-left text
  rendering lands naturally near the visual centre, cursor and text share
  the same Y reference, and the per-field variation disappears.
  Affected fields: General (3), Actions (1), Morale (1), Pet (1),
  Low Health (2).

BUG FIX — Config UI retains pending (unapplied) changes when re-opened
  WCDP.ToggleConfig() called WindowSetShowing() without first reverting the
  UI widgets to the saved db state.  Consequence: if the user changed values
  in the config window, closed it without clicking Apply (via the X button,
  the Close button, the minimap icon, or /wcdp), and then re-opened the
  window, the edited (unsaved) values were still displayed.  Additionally,
  settings written directly to db outside the config UI (e.g. "Hide Minimap
  Icon" from the minimap context menu) were not reflected in the config
  window until the player clicked Revert manually.
  Fix: WCDP.ToggleConfig() now calls WCDPConfig.RevertDialog() immediately
  before showing the window (not on hide, to preserve Apply-then-close
  workflows).  All four paths that can show the config (minimap left-click,
  minimap right-click > Configuration, /wcdp, /wcdpconfig) share the same
  ToggleConfig() call, so the fix covers all of them.

IMPROVEMENT — Harmonized title-to-first-checkbox gap across all config sections
  General and Actions sections used a 5 px gap between the section title and
  the first checkbox, while Morale, Pet Abilities and Low Health Alert used
  8 px (the value set as standard in r49).  Standardised to 8 px on all
  sections.  Window heights adjusted: General 460 -> 463 px, Actions
  180 -> 183 px.

REVIEW — Full functional verification (2.2.3)
  Code review of all advertised features against the current source:
    Pulse icon (alpha animation)  : WindowStartAlphaAnimation with
      db.alpha.start/finish/speed; WindowStopAlphaAnimation guard before
      each start prevents stacked LOOP animations.  No issue found.
    Sound playback                : time-based 0.15 s queue (SOUND_COOLDOWN_SECS)
      drained by OnUpdate.  pcall wrappers on PlaySound/Sound.Play.  No issue.
    Minimum cooldown filter       : peak <= GENERAL_GCD or peak < mincooldown
      suppresses pulse.  Applies independently per category via cooldownLookup.
    Morale/Pet/Low Health independence : each category has its own runtime
      window (WCDPMoraleFrame, WCDPPetFrame, WCDPHealthFrame) and its own
      Layout Editor anchor.  FirePulse routes by category; no cross-category
      interference found.
    Minimap button show/hide      : db.general.showMinimapButton gated at
      OnLoad, OnProfileChanged, and HideMinimapButton().  ShowMinimapButton
      config checkbox writes on Apply.  No issue.
    Apply / Revert / Close        : Apply -> SaveDialog -> per-page Apply ->
      WCDP.Reinitialize().  Revert -> RevertDialog -> per-page Revert.
      Close (X or button) -> WindowSetShowing(false) only - no data loss
      because db is only written by Apply.  Revert on re-open now handled
      by ToggleConfig (see bug fix above).
    Profile load/save             : WCDP.OnProfileChanged() swaps db reference,
      calls upgradeProfile() (updateSettings migration), reinitialises, and
      updates all windows.  No corruption path found.

========================================================================
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
  All eight numeric EditBox fields in the configuration window showed their
  value (e.g. "0.40", "4.0") pressed against the top-left corner instead of
  being vertically centred.  Root cause: LibGUI creates Textbox widgets from
  the EA_EditBox_DefaultFrame template, which defaults to top-left text
  alignment.  No post-creation Lua API exists in WAR/RoR to change an
  EditBox's text alignment at runtime.

  Fix: added a named template WCDPEditBoxNumeric in WCDPConfig.xml that
  inherits EA_EditBox_DefaultFrame and overrides textalign="leftcenter"
  (left-aligned horizontally, vertically centred — confirmed attribute on
  EditBox elements by zMailMod/zMailmodSend.xml).  All eight numeric Textbox
  creation calls in WCDPConfig_General.lua now pass this template as the
  base argument to LibGUI, requiring a single XML definition with no code
  duplication.

  Fields affected across all five tabs:
    General           — Pulse Start Alpha, Pulse End Alpha, Pulse Speed (sec)
    Actions           — Minimum Cooldown (sec)
    Morale            — Minimum Cooldown (sec)
    Pet Abilities     — Minimum Cooldown (sec)
    Low Health Alert  — Low Health Threshold (%), Repeat Interval (sec)

========================================================================
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.
  r50 through r54 experimented with rightcenter + topright anchoring to
  align textbox columns, but this misaligned option labels with their
  descriptions (right-aligned text vs left-aligned description).
  Reverted all 8 field labels to leftcenter, width 200px, and all 8
  textboxes back to AnchorTo(label, "right", "left", 10, 0).
  Subsection labels (Actions, Morale, Pet, Low Health) also reverted
  from offset (-10, 10) to (10, 10) relative to their section checkbox,
  restoring the r49 indentation behaviour.

========================================================================
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)
  MinimumCooldown, Threshold, and Interval field labels were anchored with
  x=+10 offset from their section checkbox, placing them 20px further right
  than the General section labels (which start at x=0).  Since textboxes are
  anchored to the label "topright", they also shifted right by 20px, breaking
  column alignment when scrolling through the config page.
  Fix: changed the label AnchorTo x offset from +10 to -10 for Actions,
  Morale, Pet and Low Health sections, aligning label.left=0 across all
  sections and placing all textboxes at the same absolute x position.

BUG FIX — Help page "intro" paragraph still showing garbled character
  T["help_intro"] still contained an em-dash (U+2014) that was missed in r52.
  Replaced with ASCII hyphen-minus " - ".

========================================================================
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)
  WAR/RoR LabelSetText resets the label window dimensions to the rendered
  text width, overriding a Resize() call made before SetText.  Right-aligned
  labels still had variable "topright" anchor positions because the window
  was being shrunk to text content width.
  Fix: call e:Resize(215, 30) AFTER e:SetText(...) on all 8 field labels
  (Start Alpha, End Alpha, Pulse Speed, Min Cooldown x3, Threshold, Interval).
  WindowSetDimensions called after LabelSetText locks the width to 215 px;
  "topright" is now always at label.x + 215 regardless of text length.

BUG FIX — Help page version label showed garbled characters
  wstring.format(L"%s %s", T[...], towstring(...)) produced garbage because
  wstring.format does not accept a wstring T[] value as a %s argument in
  the WAR/RoR runtime.
  Fix: the version label now calls SetText(towstring(WCDP.DisplayVersion))
  directly; the label shows only the version string (e.g. "2.2.1 r52").

BUG FIX — Help page text contained Unicode em-dashes (U+2014) and curly
  apostrophes (U+2019) which the WAR/RoR wstring engine renders as garbled
  multi-byte sequences.  All such characters replaced with ASCII hyphen "-"
  and straight apostrophe "'".

FEATURE — Help page moved to last position in the sidebar (after Profiles)
  Previously registered immediately after Low Health Alert, Help now
  appears below Profiles.  Implemented via deferred registration:
  WCDPConfig_General.lua stores the config in WCDPConfig.pendingHelpConfig;
  WCDPConfig_Profiles.lua registers it after the Profiles page.

========================================================================
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)
  r50's "topleft+210" approach was mathematically correct but did not
  visually resolve the misalignment because the user perceives the gap
  between label TEXT and textbox, not between label WINDOW EDGE and textbox.

  Root fix: all 8 field labels that carry a textbox are now:
    • Resize: 215 px (safely fits longest label "Pulse Start Alpha (0 to 1):")
    • Align: "rightcenter"  (text always ends at the right edge of the label window)
    • Textbox anchor: AnchorTo(label, "topright", "topleft", 10, 4)
      → textbox left = label right + 10, always at the same x
      regardless of label text length or WAR/RoR anchor internals.

  With right-aligned labels, the label text ALWAYS ends at x = label.x+215.
  The 10 px gap is consistent.  The textbox column is visually uniform
  across all four pages (General/Actions, Morale, Pet Abilities, Low Health).

========================================================================
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
  The WAR/RoR engine resolves the "right" anchor point of a Label window
  dynamically from the rendered text width, NOT from the Resize() width.
  Changing labels from 300 px to 200 px had no effect on alignment:
  "Pulse Speed (sec):" (short text) still anchored further left than
  "Pulse Start Alpha (0 to 1):" (long text).

  Fix: changed all 8 field-textbox anchors from
    AnchorTo(label, "right", "left", 10, 0)
  to
    AnchorTo(label, "topleft", "topleft", 210, 4)
  This pins the textbox x to label.x + 210 = 35 + 210 = 245 (fixed),
  independent of the rendered text width.  The y offset +4 vertically
  centers a 22 px textbox inside a 30 px label row ((30-22)/2 = 4).
  Affected fields: Start Alpha, End Alpha, Pulse Speed, Min Cooldown
  (Actions), Min Cooldown (Morale), Min Cooldown (Pet), Threshold (LH),
  Repeat Interval (LH).

========================================================================
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)
  FirePulse used `if db.lowhealth.sound then` (falsy on nil) while the
  Morale path uses `if category ~= "morale" or db.abilities.morale.sound ~= false`.
  A fresh or migrated profile where lowhealth.sound was not yet set by
  updateSettings would silently skip sound.  Fixed to `~= false` for
  consistency — nil now plays sound (matching the DefaultSettings intent
  of sound = true).

IMPROVEMENT — Config UI: field label width 300 → 200 px (textbox alignment)
  All field labels that carry a textbox to their right were 300 px wide.
  In a 400 px window with a 35 px left offset, the textbox started at
  x = 345 and extended to x = 410 — 10 px past the window boundary.
  Reduced to 200 px: textboxes now start at x = 245, fully visible and
  consistently aligned across General, Actions, Morale, Pet Abilities,
  and Low Health Alert.

IMPROVEMENT — Morale min-cooldown description clarified
  desc_min_cooldown_morale now reads:
  "Fallback mode only: applies when 'Pulse When Morale Available' is disabled."
  The previous wording omitted the dependency on the OnAvailable toggle.

FEATURE — New "Help" configuration page
  A read-only page added after "Low Health Alert" in the sidebar,
  visible in the configuration window under the name "Help".
  Contents:
    • Addon title and live version string (WCDP.DisplayVersion)
    • One-sentence description of what the addon does
    • "How It Works" — one paragraph each for Actions/Pet, Morale, Low Health
    • "Tips" — filters, profiles, anchor positioning
    • "Slash Commands" — /wcdp and /wcdp debug
    • "Credits" — original author + community maintainers
  No Apply/Revert needed; all labels are static.
  WCDP.DisplayVersion exposed from wcdp.lua (was a file-local string).
  Version label uses wstring.format(L"%s %s", ...) — wstring concatenation
  with .. and a plain Lua string literal crashes the WAR/RoR runtime.

========================================================================
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"
  Root cause: the General sub-window height (390 px) was smaller than
  the actual content height (~435 px after description labels were
  added in r46/r47).  ReanchorConfigDisplay stacks sub-windows using
  their declared height, so the Actions window started 390 px below
  General while ShowMinimapButton extended to y≈415 inside General —
  the two overlapped on screen.
  Fix: General window height raised from 390 px to 460 px (25 px
  safety margin above the last element at y≈435).

IMPROVEMENT — Uniform spacing across all config pages
  Previous gaps between elements were inconsistent and too tight.
  Standardised to:
    • label  → description : 5 px  (was 3 px)
    • description → next label : 8 px  (was 5 px)
    • title → first checkbox : 8 px  (was 5 px on Morale/Pet/LowHealth)
    • description → Sound checkbox : 10 px  (was 7 px on Morale/LowHealth)
    • Debug → Minimap checkbox : 10 px  (was 8 px)
    • Minimap checkbox gap after Debug : 10 px  (was 8 px)

  Window heights updated accordingly:
    Actions    160 → 180 px
    Morale     285 → 285 px (unchanged — existing margin sufficient)
    Pet        160 → 180 px
    Low Health 285 → 300 px

========================================================================
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
  Following in-game feedback that descriptions were cut off and
  indistinguishable from field labels, three corrections were applied:

  1. Word wrap enabled (LabelSetWordWrap = true) and label height set
     to 36 px (two lines of font_chat_text) on all description labels
     so that text wrapping to a second line is fully visible.

  2. Grey color applied via LabelSetTextColor(name, 170, 170, 170) to
     all description labels, making them visually distinct from the
     gold field labels above them.  Confirmed API: used in BuffHead
     (SetupFilter.lua, SetupLayoutProperties.lua, SetupTrackers.lua)
     and AggroMeter.

  3. Two missing descriptions added:
       Morale    > Minimum Cooldown (sec) — desc_min_cooldown_morale
       Pet Abilities > Minimum Cooldown (sec) — desc_min_cooldown_pet
     Both pages gained the corresponding window height increase:
       Morale 210→285 px, Pet 110→160 px.

  Localization strings simplified to fit within two lines at 350 px:
    desc_start_alpha, desc_end_alpha, desc_pulse_speed,
    desc_min_cooldown_actions revised (r46 versions were borderline).
  Total new keys in enUS.lua: 9 (was 7 in r46).

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

IMPROVEMENT — Inline field descriptions in the configuration UI
  A short descriptive label is now displayed beneath each non-obvious
  setting across all configuration pages.  Text is in English and
  stored in Localization/enUS.lua (7 new keys).

  General page — Pulse Start Alpha, Pulse End Alpha, Pulse Speed.
  Actions page — Minimum Cooldown.
  Morale page  — Pulse When Morale Available.
  Low Health   — Low Health Threshold (%), Repeat Interval (sec).

  Window heights adjusted: General 305→390 px, Actions 110→160 px,
  Morale 175→210 px, Low Health 220→285 px.

========================================================================
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
  OnLayoutEditorFinished had a typo: "anc.relpoint" was used for two
  consecutive left-hand side variables instead of "anc.relwin, anc.relpoint".
  As a result, anc.relwin was never updated when the player repositioned a
  WCDP anchor in the Layout Editor.  On the next login, any anchor previously
  moved relative to a non-root window (e.g. the action bar) would snap back
  to "Root" coordinates.  Fix: corrected the assignment to:
    anc.point, anc.relwin, anc.relpoint, anc.x, anc.y = WindowGetAnchor(...)
  Affects all 4 category anchors (Abilities, Morale, Pet, Low Health).

IMPROVEMENT — Mincooldown input validation in Apply (Actions, Morale, Pet)
  The "Minimum Cooldown (sec)" textboxes in the Actions, Morale and Pet
  Abilities pages now clamp their input to [0, 300] seconds on Apply,
  consistent with the Low Health fields which already validated their ranges.
  Values below 0 are raised to 0; values above 300 are lowered to 300.
  Note: the hardcoded GCD guard (peak <= 1.61 s) in PerformAbilityUpdate
  still applies independently — setting mincooldown below GENERAL_GCD has
  no additional effect on pulse triggering.

========================================================================
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 (previously all stacked under General).
  Navigation order: General | Morale | Pet Abilities | Low Health Alert |
  Filters | Profiles.

FEATURE — "Enable Sound With Morale Pulse" checkbox added to the Morale page.
  Stored in db.abilities.morale.sound (default true).  When unchecked, FirePulse
  skips PlayPulseSound() for the morale category only; the global sound toggle still
  acts as a master gate.

FIX — Radio-group mutual-exclusion bug extended to Morale (Enable + OnAvailable +
  Sound, 3-way fix).  The Low Health fix from r32 is preserved unchanged.

FIX — Wrong LibGUI parent on "Enable Sound With Pulse" label (was window.W.Morale,
  now correctly window.W.General) and on the Pet "Minimum Cooldown" label (was
  window.W.Actions, now correctly petConfig.W.Main).

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

IMPROVEMENT — Robustness: pcall wrappers on external API calls (inspired by CoolDownTracker)
  Player.GetAbilityData, GetIconData and DynamicImageSetTexture are now called via
  pcall in FirePulse, PerformAbilityUpdate and OnPlayerBeginCast.
  If any of these calls fails (API unavailable, unexpected value), the pulse is
  silently skipped without crashing the addon.

IMPROVEMENT — Animation: WindowStopAlphaAnimation before each WindowStartAlphaAnimation
  Two rapid pulses on the same window could stack LOOP animations.
  A conditional call (type-guard) to WindowStopAlphaAnimation is now inserted before
  each WindowStartAlphaAnimation in FirePulse (both health and ability paths).

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

IMPROVEMENT — Simplified sound list
  Removed 4 non-essential entries (QUEST_OBJECTIVES_COMPLETED, QUEST_COMPLETED,
  QUEST_ACCEPTED, TOME_CLOSE) from WCDP.SoundList.
  Only the two confirmed, in-game-proven entries are kept: NONE (disabled) and
  HELP_TIPS_NEW (default).  Unit test updated accordingly (69/69).

========================================================================
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
  Root cause: WAR/RoR fonts (font_clear_medium_bold + font_chat_text) are taller
  than initially estimated.  Sound_Checkbox ended up at y~145 within the 170px
  LowHealth window, leaving only ~25px of padding.  Any sub-pixel rounding in
  ScrollWindowUpdateScrollRect caused the icon to overflow the scroll viewport,
  rendering partially outside the scroll window boundary onto WCDPConfig.

  Fix: LowHealth window height raised from 170px to 220px (50px padding below
  Sound_Checkbox), giving ~75px of margin.  No scroll calculation changes needed.

========================================================================
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
  Root cause: WAR/RoR does not auto-resize WCDPConfigScrollChild from its
  stacked section children.  ScrollWindowUpdateScrollRect reads the child's
  reported height; without an explicit set it stays at its XML default and the
  scrollbar stops short, clipping the bottom section.

  Fix in ReanchorConfigDisplay (WCDPConfig.lua):
    During the anchor loop, accumulate the height of each section window via
    WindowGetDimensions.  After the loop call:
      WindowSetDimensions(configWindowScrollChild, sw, totalHeight)
    where sw = current scroll child width (preserves anchor-driven width) and
    totalHeight = sum of all section heights (835 px for the General tab).
    This is called just before ScrollWindowUpdateScrollRect, which then sees
    the correct content height and computes the right scroll range.

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

BUG FIX — Clicking the Sound checkbox icon triggers WCDPConfigScrollChild
  UI Debugger showed: cursor on checkbox ICON (y=902) → WCDPConfigScrollChild;
  cursor on text label 2px higher (y=900) → LIBGUI_window13Checkbox53.

  Root cause: EA_Button_DefaultCheckBox renders its visual icon 2-3px below the
  button window's bottom boundary.  The WCDPWindowDefault section windows have
  handleinput="false", so those overflowing pixels fall through to
  WCDPConfigScrollChild which does not dispatch OnLButtonDown to our handler.

  Fixes applied:
  1. WindowSetDimensions(e.name, 20, 24) — expand the Sound_Checkbox window
     height from ~20px to 24px so the icon is fully within the click region.
  2. LowHealth window height raised from 160px to 170px — adds 10px bottom
     padding below Sound_Checkbox so the element is never pressed against the
     scroll viewport boundary when scrolled to maximum.

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

CODE QUALITY — WCDPConfig.GetSlotRowNumForActiveListRow
  Three bugs, all harmless in practice (slotNum == rowNum always), now fixed:
  1. Typo: local "slowNumber" instead of "slotNumber" made the real slotNumber
     assignment write to an implicit global instead of a local.
  2. Return value 4 was "window" (nil — no such local exists in WCDPConfig.lua).
     Removed the unused 4th return value.
  3. Caller OnLButtonUpConfigList had its two local names swapped relative to
     what the function returned.  Corrected to "slotNumber, rowNumber, config".

========================================================================
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)
  UI Debugger showed "LIBGUI_window1Checkbox1" when hovering over Sound_Checkbox.
  Root cause: ButtonSetCheckButtonFlag(name, false) disables the button window's
  handleinput flag — the button becomes transparent to all mouse events.  Clicks
  fall through to whatever window is behind it (hence the wrong window in debugger).

  Fix: both checkboxes keep ButtonSetCheckButtonFlag(true) so handleinput stays
  enabled.  The radio-group mutual exclusion is broken by a save-restore pattern:
  - OnLButtonDown: save the sibling's current state (before radio group fires)
  - OnLButtonUp: read the just-clicked checkbox's new state; restore the sibling

  Local variables _lhEnabled and _lhSoundEnabled track both states.
  Revert sets both; Apply reads GetValue() from both (native ButtonGetPressedFlag).

BUG FIX — Window height fix now applied on every config open
  r35 fixed the height in WCDPConfig.OnLoad() but savesettings timing meant the
  500px saved size could be restored AFTER OnLoad ran.  The fix is now also in
  WCDP.ToggleConfig() so it runs every time the config window is opened — the
  first open with a saved height < 540 resizes and recalculates the scroll rect.

========================================================================
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
  WCDPConfig has savesettings="true": the client restores the window to its
  saved dimensions (500px from pre-2.2.0 installs) on every load, overriding
  the 540px default set in WCDPConfig.xml.

  Fix: WCDPConfig.OnLoad() now checks WindowGetDimensions and calls
  WindowSetDimensions(800, 540) if the saved height is below 540. This runs
  once per install; after that the saved size is >=540 and the code is a no-op.

  OnResizeBegin minimum also raised from 800x500 to 800x540 so the user
  cannot manually drag the config window below the new minimum height.

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

BUG FIX — Sound checkbox click offset (OnLButtonDown instead of OnLButtonUp)
  After ButtonSetCheckButtonFlag(false), OnLButtonUp only fires when the mouse
  is released while still inside the button window.  Any sub-pixel movement
  during click cancelled the event, requiring the user to click outside the
  visual icon area.  Changed to OnLButtonDown (fires on mouse press, not
  release) so the toggle is instantaneous and does not require a steady hand.

BUG FIX — Sound checkbox cut off at bottom of scroll area
  The default WCDPConfig window height (500px) gave a scroll area of ~400px.
  With all General-page panels stacked (825px total), the last checkbox of
  LowHealth was cut off when scrolled to the bottom.  Default height raised
  to 540px (~440px scroll area), enough to display the full LowHealth section.
  (The window is resizable and savesettings="true", so a one-time manual
  resize by the user was the previous workaround.)

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

BUG FIX — Low Health sound checkbox conflict (corrected fix)
  r32 attempted to fix this by moving Sound_Checkbox to a different parent
  window (window.W.Actions).  This broke the click target: the checkbox
  appeared visually in the LowHealth panel but input was routed to the
  Actions window at a different screen position, making the checkbox
  impossible to click at all.
  Real root cause: ButtonSetCheckButtonFlag(name, true) in LibGUI's
  LIBGUI_Checkbox:New creates a WAR/RoR radio group for all check buttons
  sharing the same parent window.  When Enable_Checkbox (member of the
  group) is checked, WAR/RoR disables and visually grays out Sound_Checkbox.
  Real fix: Sound_Checkbox stays in window.W.LowHealth (correct parent for
  click routing).  After LibGUI creates it, ButtonSetCheckButtonFlag(name,
  false) removes it from the radio group.  Since this also removes sticky
  toggle behaviour, an OnLButtonUp handler re-applies ButtonSetPressedFlag
  each click, driven by the module-level _lhSoundEnabled variable.
  Apply/Revert continue to use GetValue()/SetValue() unchanged.

========================================================================
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.

BUG FIX — Low Health sound checkbox conflict (broken fix, superseded by r33)
  Sound_Checkbox moved to window.W.Actions parent — broke click target.

IMPROVEMENT — Heart icon for Low Health pulse
  WCDPHealthFrame now displays a heart icon (wcdp_heart.tga) instead of the
  generic WCDP logo, making the low-health alert visually distinct from other
  pulse categories.  The 48x48 RGBA TGA is generated from the mathematical
  heart formula ((nx2+ny2-1)3 <= nx2*ny3), red (#D21414) on transparent.

IMPROVEMENT — Pulse stops on player death
  CheckLowHealthPulse() now checks for hp.current == 0.  When the player
  dies, the health frame is hidden via WindowSetShowing("WCDPHealthFrame",
  false) and no further pulses fire.  The frame is restored when hp.current
  returns above zero (resurrection), and the pulse resumes immediately if HP
  is still below the threshold.

TESTS
  2 new tests added to tests/unit/test_lowHealth.lua (9 -> 11):
    • lowHealth: no pulse when player is dead (hp=0)
    • lowHealth: pulse resumes immediately after resurrection
  Total: 69 tests, 0 failures (Lua 5.4).

IMPROVEMENT — Heart icon for Low Health pulse
  WCDPHealthFrame now displays a heart icon (wcdp_heart.tga) instead of the
  generic WCDP logo, making the low-health alert visually distinct from other
  pulse categories.  The 48x48 RGBA TGA is generated from the mathematical
  heart formula ((nx2+ny2-1)3 <= nx2*ny3), red (#D21414) on transparent.

IMPROVEMENT — Pulse stops on player death
  CheckLowHealthPulse() now checks for hp.current == 0.  When the player
  dies, the health frame is hidden via WindowSetShowing("WCDPHealthFrame",
  false) and no further pulses fire.  The frame is restored when hp.current
  returns above zero (resurrection), and the pulse resumes immediately if HP
  is still below the threshold.

TESTS
  2 new tests added to tests/unit/test_lowHealth.lua (9 -> 11):
    • lowHealth: no pulse when player is dead (hp=0)
    • lowHealth: pulse resumes immediately after resurrection
  Total: 69 tests, 0 failures (Lua 5.4).

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

CHANTIER 1 — 4 INDEPENDENT PULSE ANCHORS
  Previously all categories (abilities, morale, pet) flashed at the same
  position in WCDPFrame / WCDPLayoutFrame.

  Each category now has its own runtime frame and Layout Editor anchor,
  all independently movable via Controls > Windows:

    "WCDP Anchor"               — Abilities (name unchanged: players who
                                  moved it in 2.1.x keep their saved position)
    "WCDP Anchor - Morale"      — Morale abilities
    "WCDP Anchor - Pet"         — Pet abilities
    "WCDP Anchor - Low Health"  — Low-health alert (see Chantier 2)

  Pet abilities now route to WCDPPetFrame instead of sharing WCDPFrame.
  Morale pulses (both onavailable and fallback cooldown paths) now route to
  WCDPMoraleFrame.  Alpha/speed settings remain global for UX simplicity.
  New anchor positions default to offset so they do not overlap at first load.
  Existing profiles receive the new anchor keys via updateSettings migration.

CHANTIER 2 — LOW HEALTH PULSE
  A new "Low Health Alert" repeats on WCDPHealthFrame while the player's HP
  is at or below a configurable threshold.

    • Default threshold: 10 %
    • Default repeat interval: 1.5 s
    • Separate "Enable Sound With Low Health Pulse" checkbox
    • No ability icon — the animated frame border is the visual alert
    • Stops immediately when HP rises above the threshold

  Data source: GameData.Player.hitPoints.current / .maximum (confirmed in
  pure/PurePlayer.lua, BuffHead/LibGroup, MiracleGrowLight, Enemy/EnemyPlayer).
  Polled via OnUpdate(timePassed) — no PLAYER_HEALTH_UPDATED event for the
  player's own HP was found in any Game\ROR addon.

  New DefaultSettings keys: lowhealth.enabled, .threshold, .repeatInterval,
  .sound.  Existing profiles receive defaults via updateSettings migration.

CHANTIER 4 — SOUND QUEUE: REAL DIAGNOSIS AND REAL FIX
  ROOT CAUSE (why 2.1.3 failed in-game):
    The 2.1.3 fix set _soundCooldown = 30 (a hook-call counter) decremented
    in OnAbilityUpdate().  OnAbilityUpdate is called once per visible action
    button per frame — typically 10-30 times per frame.  "30 hook calls"
    therefore expired in only 1-3 real frames (33-100 ms), too short for the
    client's audio slot to reset between two PlaySound() calls.  Worse, when
    no action buttons were visible (morale-only or pet-only frames), the
    counter never decremented and queued sounds were stuck indefinitely.

  FIX:
    WCDPTimerFrame (new invisible 1x1 window) carries an OnUpdate handler that
    fires exactly once per rendered frame with a real time delta in seconds.
    _soundCooldown is now a float (seconds); SOUND_COOLDOWN_SECS = 0.15 s
    (~4-5 frames at 30 fps) is long enough for the audio slot and
    imperceptible to the player.  TickSoundQueue(dt) drains the queue
    independently of how many action buttons are visible.

  OnUpdate also drives CheckLowHealthPulse(dt) (Chantier 3).

BUG FIX — Config UI crash / addon fails to load
  wstring.format(L"%.0f", ...) — used for the "Low Health Threshold" textbox
  — is not supported by WAR/RoR's wstring.format.  The resulting Lua error
  propagated from RevertConfigSettings() through WCDPConfig.OnLoad() up to
  WCDP.OnLoad(), stopping execution before hooks, event handlers, Layout
  Editor registrations, and slash commands were installed.  Symptoms:
    • "Errors Detected!" in the UI Mods list
    • No WCDP anchors in Controls > Windows
    • "Unknown command: /wcdp" in chat
    • Empty textboxes and unlabelled buttons in the config window
  Fix: replaced wstring.format(L"%.0f", n) with towstring(n) for integer
  display.  The nil guard "local lh = db.lowhealth or DefaultSettings.lowhealth"
  was also added to protect against profiles not yet migrated.

TESTS
  47 tests carried over and updated from 2.1.3 (all still pass).
  20 new tests added:
    test_firePulseRouting.lua (8 cases) — per-category window routing
    test_lowHealth.lua        (9 cases) — low-health threshold/repeat/stop
  test_soundSelection.lua updated (time-based float, OnUpdate end-to-end).
  Total: 67 tests, 0 failures (Lua 5.4).

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

ROOT CAUSE
  PlaySound(soundId) in the WAR/RoR client uses a single audio slot for
  addon sounds.  When two abilities expire within the same rendered frame
  (or on back-to-back frames), FirePulse() calls PlaySound() twice in
  rapid succession and the client silently discards the second call.
  Both pulses were visible, but only one sound was audible.
  Settings (Start/End Alpha, Speed, mincooldown) do not influence this —
  a valid configuration was confirmed as the trigger for the report.

FIX
  A small FIFO queue (_soundQueue) and a hook-call counter (_soundCooldown)
  delay the second PlaySound() call by ~30 hook invocations (≈ 1 rendered
  frame on a typical 10-30 button setup, i.e. 33-100 ms at 30 fps):

    • PlayPulseSound() checks _soundCooldown.  If > 0, the key is pushed
      onto _soundQueue instead of calling PlaySound() immediately.
    • _soundCooldown is set to 30 by PlayPulseSoundDirect() after each
      actual PlaySound() call.
    • OnAbilityUpdate() (called once per visible button per frame) ticks
      the counter down and, when it reaches zero, pops and plays the next
      queued key via PlayPulseSoundDirect().
    • Reinitialize() resets _soundQueue and _soundCooldown on profile
      changes and /reload.

  New public helpers (for the test suite):
    WCDP.PlayPulseSoundDirect(key)  — plays immediately, resets counter
    WCDP.TickSoundQueue()           — one drain tick (also called by hook)
    WCDP.GetSoundQueueState()       — returns _soundCooldown, _soundQueue

NOTE
  This fix was superseded in 2.2.0 (Chantier 4) after in-game testing
  revealed the hook-call counter expired too fast.  The time-based
  mechanism in 2.2.0 replaces it completely.

TESTS
  3 new unit tests in tests/unit/test_soundSelection.lua:
    • soundQueue: second PlayPulseSound is queued, not played immediately
    • soundQueue: queued sound plays after cooldown drains via TickSoundQueue
    • soundQueue: Reinitialize clears queue and cooldown
  Total: 47 tests, 0 failures (Lua 5.4.6).

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

FEATURE — Sound Selection
  Five confirmed GameData.Sound.* constants are selectable via a
  ComboBox in Configuration > General:

    Help Tips (default)        — GameData.Sound.HELP_TIPS_NEW              [Enemy]
    Quest Objectives Done      — GameData.Sound.QUEST_OBJECTIVES_COMPLETED [Enemy]
    Quest Completed            — GameData.Sound.QUEST_COMPLETED            [CombatLogger]
    Quest Accepted             — GameData.Sound.QUEST_ACCEPTED             [CombatLogger]
    Tome Close                 — GameData.Sound.TOME_CLOSE                 [zMailMod]
    None (disabled)            — sentinel key: silences sound without
                                 un-ticking "Enable Sound With Pulse".

  Selection saved in db.general.soundKey; older profiles receive
  "HELP_TIPS_NEW" automatically via the updateSettings migration path.

NOT IMPLEMENTED — Volume Slider
  PlaySound(soundId) takes a single argument.  No volume parameter,
  Sound.SetVolume(), or SetMasterVolume() was found in any Game\ROR
  addon.  A Lua-level volume control is not possible with the current
  RoR API.

TESTS — infrastructure added (tests/ directory, Lua 5.4, no framework)
  tests/stubs/api_stubs.lua            stubs for all WAR/RoR client globals
  tests/unit/test_updateSettings.lua   profile migration (updateSettings)
  tests/unit/test_peakTracking.lua     PerformAbilityUpdate peak-tracking
  tests/unit/test_moraleAllowed.lua    MoraleAllowed filter logic
  tests/unit/test_moraleAvailable.lua  OnPlayerMoraleUpdated edge-detect
  tests/unit/test_soundSelection.lua   PlayPulseSound all soundKey variants
  tests/run_tests.lua                  runner (44 tests, 0 failures)
  Run: lua run_tests.lua  (from the tests/ directory)

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

FIX (priority)
  PerformAbilityUpdate permanently blacklisted any ability whose
  Player.GetAbilityData() returned nil on a given frame, silencing that
  ability's pulse for the entire zone session.  The nil can be transient
  (data not yet loaded by the client).  Fix: on nil, return without
  writing to blackList or abilityTypeCache so the next frame retries.

CHANGE
  GameData.Sound.WINDOW_CLOSE was not found in any installed RoR addon
  and is therefore unconfirmed.  Replaced with GameData.Sound.HELP_TIPS_NEW,
  confirmed in Enemy/DefaultSettings.lua.  The pcall wrapper is preserved.

VERIFY (no code change)
  Player.AbilityType.* vs GameData.AbilityType.*: both namespaces coexist.
  Enemy/ClickCasting compares .abilityType against GameData.AbilityType.TACTIC;
  MoraleCircle passes Player.AbilityType.MORALE as a second parameter to
  Player.GetAbilityData().  Since the code worked in 2.0.4 and no
  contradiction was found, no change was made.

FEATURE
  The minimap button right-click now opens a context menu
  (EA_Window_ContextMenu, same pattern as Enemy) with two entries:
    • "Configuration"    — opens config window (same as left-click)
    • "Hide Minimap Icon" — hides the button and saves the state.
  A "Show Minimap Button" checkbox in the General config page lets
  players re-enable it.  Stored in db.general.showMinimapButton (default
  true); existing profiles get the key added automatically by updateSettings.

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

NEW
  A minimap button (gold pulse icon) appears next to the overhead map.
  Left or right click opens the configuration window; mouseover shows a
  short tooltip.  The button can be repositioned via the Layout Editor and
  its position is saved.  Built the same way Enemy builds its minimap icon:
  DynamicImage anchored to EA_Window_OverheadMapMapDisplay, 48x48 TGA.

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

FIX
  The pulse sound never played.  The addon used Sound.Play(), which the
  RoR client does not expose, so all calls were silent.  Sound now plays
  through PlaySound() (the function RoR actually provides), wrapped in
  pcall with a fallback so a missing API can never break the pulse.
  Controlled by the existing "Enable Sound With Pulse" option.

CHANGE
  Debug messages now print to the chat window (same channel as the init
  line) instead of the internal DEBUG() console, so "Enable Debug
  Messages" / "/wcdp debug" output is actually visible to the player.

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

NEW
  Morale can now pulse the instant a rank becomes available as the morale
  bar fills (or its post-use cooldown ends), not only for the last morale
  cast.  Driven by the PLAYER_MORALE_UPDATED event and the morale-bar API
  (GetMoraleBarData / GetMoraleCooldown / GetMoralePercentForLevel),
  evaluated the same way the default morale UI does.  Each rank flashes
  once on the not-available to available edge.
  Toggle: "Pulse When Morale Available" in the Morale section (on by
  default).  When off, morale falls back to the 2.0.6 cooldown path.

NEW
  "Enable Debug Messages" checkbox in General + "/wcdp debug" slash command.

FIX
  The Morale and Pet "Minimum Cooldown" fields were saving into the
  Actions minimum cooldown.  They now save to their own settings correctly.

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

FIX
  Morale abilities never pulsed after the 2.0.5 rewrite.  Morale buttons
  do not count down; the client streams "cooldown=0 / max=60" updates for
  them, so the 2.0.5 peak tracker never recorded a peak and suppressed
  the pulse entirely.  Morale is now handled on its own path (triggered
  off m_MaxCooldown, the proven 2.0.4 behavior, with a guard against
  repeated 0/60 updates), while abilities and pets keep peak tracking.
  The pulse display was factored into a shared WCDP.FirePulse() helper.

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

FIX
  Pulses randomly failed to fire when another ability was cast (global
  cooldown overwriting the button's max cooldown) or when the bar
  refreshed while moving.  Cooldown peaks are now tracked per ability
  internally so the pulse always triggers on the actual expiry.
  Ability types are cached to avoid calling GetAbilityData() every frame.

========================================================================
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.
