promotional bannermobile promotional banner

SplitWatch

Auto-split a 10-40 man raid into 2 balanced teams for split-mechanic encounters.

File Details

v0.2.0

  • R
  • May 12, 2026
  • 919.08 KB
  • 6
  • 12.0.7+4
  • MoP Classic + 1

File Name

SplitWatch-v0.2.0.zip

Supported Versions

  • 12.0.7
  • 12.0.5
  • 12.0.1
  • 12.0.0
  • 5.5.0

SplitWatch

v0.2.0 (2026-05-12)

Full Changelog Previous Releases

  • Merge beta → main: v0.2.0 final — .pkgmeta + release workflow + gitignore
  • Add .pkgmeta + GitHub release workflow (BigWigsMods/packager)
    .pkgmeta:
    • package-as: SplitWatch
    • curse-project-id: 1541355
    • wago-project-id: QNlzMEKe
    • 5 externals fetched at build time (LibStub, CallbackHandler, LSM,
      LibDataBroker, LibDBIcon) — same set as the bundled Libs/ folder, so
      release zips contain upstream-fresh copies instead of our snapshots.
    • ignore list strips .gitignore, .github, README.md, LICENSE, root
      logo.png, CLAUDE.md, CHANGELOG.md, .pkgmeta, scripts/ from the zip.
      .github/workflows/release.yml:
    • Fires on any tag push.
    • Runs BigWigsMods/packager@v2 with CF_API_KEY / WAGO_API_TOKEN /
      GITHUB_TOKEN secrets. Same shape as BossWatch — drop in, tag,
      and the packager handles CurseForge + Wago + GitHub release in one go.
      User still needs to add the two repo secrets (CF_API_KEY from
      curseforge.com/account/api-tokens and WAGO_API_TOKEN from
      addons.wago.io/account/api) before the first tag.
  • Honor expanded .gitignore — untrack CLAUDE.md
    User expanded the .gitignore to cover OS junk, editor scratch, the full
    .claude/ + .claude.local/ workspace + CLAUDE.md, .env secrets, packager
    output (.release/ .pkgmeta.local *.zip *.tar.gz), runtime SavedVariables
    (WTF/ *.lua.bak) and *.log. CLAUDE.md was the only file already tracked
    that the new patterns cover — git rm --cached so the local copy stays
    but the repo no longer contains it.
  • Merge beta → main: v0.2.0 — constraints, Item Level source, resize, CF/Wago IDs
    v0.2.0 release contents (all features shipped on beta since 0.1.0):
    • Team-composition constraints: Battle Rez, Bloodlust (default ON), melee/ranged, Mass Dispel, Decurse (default OFF)
    • Item Level (inspect) as 5th weight source via Blizzard API
    • Resizable panel with persisted size + UIParent clamp at load
    • Logical re-org: Contraintes on Joueurs tab, live Source preview on Réglages
    • Per-player ilvl/DPS suffix on team columns
    • Source-used line on Preview tab
    • Team size rebalance after per-role snake (no more 11/9 in a 20-man)
    • Escape closes the panel
    • Refresh-chain fix after ScrollFrame wrap
    • README realigned, CurseForge + Wago project IDs + badges added
    • .claude/ and .env added to .gitignore
  • Add CurseForge + Wago project IDs + untrack .claude state
    TOCs: X-Curse-Project-ID 1541355, X-Wago-ID QNlzMEKe.
    README: badges linking to both publishing portals.
    .gitignore: ignore .claude/ (Claude Code workspace state) and .env / .env.*
    (secrets). Untrack the previously-committed .claude/settings.local.json.
  • README: realign with v0.2.0 surface area
    Reflects the actual current addon: 5 weight sources (added Item Level via
    inspect), 5 toggleable team-composition constraints (BR / Lust / melee-
    ranged / Mass Dispel / Decurse), resizable panel, ilvl/DPS suffix on team
    columns, source-used line on Preview, Escape-to-close, full FR locale.
    Drops the per-feature blurbs that no longer matched the UI (the 'Players
    tab is just sliders' description, the missing constraint mention, the
    missing resize feature). Added a new 'Standalone — 0 required addons'
    badge to the title block for instant scannability.
  • Réglages: bring back the live source preview
    Removed too aggressively in the previous re-org. The inline [ilvl X] /
    [245.6k] suffixes on team columns only show up AFTER computing a split,
    so users have no way to verify their damage meter is wired up before
    hitting Calculer. Put the live preview back on Réglages between Source
    DPS and Permission status (y=-340), with a hint that calls out its
    diagnostic purpose. Auto-refresh ticker (1.5s) still attached to the
    Réglages tab's outer ScrollFrame so it only ticks while the tab is
    visible.
  • Resizable main frame + logical re-org (Constraints → Joueurs)
    Resize:
    • Panel min 720x500, max 1400x1100, default 720x540. Persisted size restored
      on open and clamped against UIParent dimensions (no off-screen overflow
      when moving to a smaller monitor).
    • BOTTOMRIGHT grip with UI-ChatIM-SizeGrabber textures; updates panelW/panelH
      on mouse-up. ScrollFrame OnSizeChanged grows content height to fill the
      viewport when the panel gets taller so no empty band shows under the last
      section.
      Re-org — Réglages was a dumping ground (5 sections); split logically:
    • Réglages now contains only Général + Source DPS + Permissions + Classic
      banner. Removed the 'Aperçu de la source' section: the data is now visible
      inline next to each player on the Aperçu tab's team columns (the [ilvl X]
      / [245.6k] suffix shipped in v0.2.0), so the dedicated live preview was
      redundant.
    • Joueurs gains the Contraintes section at the top — composition rules
      conceptually belong with the player roster + Manual weight sliders, not
      with the addon-level settings. Manual-weights section now starts at
      y=-210, scroll at -258 with 280px height, action buttons at -550.
    • Manual-weights hint now reminds the reader the sliders are only used
      when source = Manual.
  • v0.2.0: constraint resolver — Battle Rez / Lust / melee-ranged / dispels
    The RL can now require composition rules that the algorithm enforces with
    minimal-disruption swaps. Each constraint is independently toggleable on a
    new 'Constraints' section of the Réglages tab. Defaults: Battle Rez ON,
    Bloodlust ON, the rest OFF.
    Splitter additions:
    • CONSTRAINTS table: class sets for BR / LUST / MASS_DISPEL / DECURSE.
    • CLASS_RANGE table: melee/ranged classification (Druid/Shaman/Hunter
      default to RANGED — the more common DPS spec). entry.attackRange
      overrides the default so a future inspect-spec scan can refine it.
    • ensurePresence(constraint): if one team lacks the class, find the DPS
      pair with the smallest score delta and swap. Records
      CONSTRAINT_MISSING_<id> or CONSTRAINT_UNSWAPPABLE_<id> in warnings
      when the swap is impossible.
    • Melee/ranged equaliser: count melee per team and swap melee↔ranged DPS
      until the diff is ≤ 1. 20-iteration guard against pathological cases.
    • Constraint pass runs AFTER the team-size rebalance, so basic shape
      guarantees (gap ≤ 1) are preserved before composition rules kick in.
      UI:
    • Setup → new 'Constraints' section with 5 NEW-badged checkboxes.
    • Preview → new warning strings for every CONSTRAINT_MISSING_* and
      CONSTRAINT_UNSWAPPABLE_* case (with full FR translations).
      TOCs bumped to 0.2.0. CHANGELOG.md gains a full [0.2.0] section and
      the in-addon Changelog tab leads with the v0.2.0 entry above v0.1.0.
  • Panel: close on Escape via UISpecialFrames
    Register SplitWatchOptionsPanel in Blizzard's UISpecialFrames list so the
    panel responds to Escape like every other Blizzard UI window. One-liner
    that I missed when porting the BossWatch panel skeleton.
  • Fix refresh chain after ScrollFrame wrap — content holds .refresh
    Regression introduced when each tab page got wrapped in a ScrollFrame:
    pages[id] now points at the SF, but builder code sets parent.refresh on
    the content child (sf.content). Both the module-level refresh closure
    (used by every widget factory's onChange) and selectTab's tab-switch
    hook called pages[id]:refresh() directly — which is nil, so the call
    was a no-op and side-effects like _syncIlvlBtn() never ran. Symptom
    reported by the user: selecting the Item Level source left the
    'Scan raid ilvl' button greyed out forever.
    Both call sites now unwrap p.content first.
  • Setup: 'Scan raid ilvl' button is disabled unless ILVL source is selected
    The inspect cache feeds only the Item Level source — scanning while a
    damage meter is active is wasted work. Wire a _syncIlvlBtn() callback
    into parent.refresh so picking a source in the dropdown immediately
    re-evaluates the button enabled state. Tooltip updated to spell out the
    requirement.
  • Preview: surface which damage-meter source is feeding the algorithm
    Adds a 'Source used for the split: <label>' line below the roster-status
    line on the Preview page (label same as Réglages → Active source — e.g.
    'Details!', 'Item Level (inspect) — 12 cached', 'Manual', etc.). Saves
    the round-trip to the Réglages tab when you just want to confirm what
    the algorithm is reading.
    Layout: Before/After labels and stats blocks pushed down by 18px to make
    room. Team-column titles unchanged at y=-200.
  • Splitter: rebalance team sizes after per-role distribution
    Bug: snake-distributing tanks, healers, and DPS independently can produce
    team counts that diverge by 2 when role counts are odd. Example from a
    real 20-man pull (2T + 1H + 17DPS):
    • tanks alternate → A=1, B=1
    • 1 healer to A → A=2, B=1
    • 17 DPS snake by DPS→ A=2+9=11, B=1+8=9 ← gap of 2
      Add a rebalance pass after the per-role loops: while |#A - #B| > 1, take
      the weakest DPS from the larger team (last entry, since DPS list is sorted
      desc) and move them to the smaller team. Picking the weakest minimises the
      score-balance disruption — that DPS contributes the least to the score
      gap, so moving them keeps DPS scores close.
      Result for the same 20-man: 11/9 → 10/10. For a 19-man (1H + 17DPS) the
      expected 10/9 stays 10/9 since gap is already 1. The earlier 'weakest
      player fills the larger team for odd raids' contract still holds.
  • Preview: show per-player ilvl/DPS suffix in team columns + tighten layout
    • Team A/B title now includes the player count badge: 'Équipe A (5)'.
    • After-stats block collapsed from 4 lines per team to 1: 'A: 5 (1T 0H 4DPS — DPS 200)'. HPS suffix only added when at least one team has heal data, so empty raids don't get noise. Matches the Before block's 2-line shape.
    • Each team-list line now appends a grey suffix after the player name: '[ilvl 432]' when source = ILVL, or '[245.6k]' (k/M-formatted DPS) otherwise. Players see at a glance who's strongest on which side.
    • fmtNum hoisted to module scope so both the Source preview and the team-column formatter share the same formatting.
    • Empty-split branch resets the team titles to the bare label so the player count doesn't stick from a previous compute.
  • Preview: tighten Team A/B columns
    • Drop the redundant '[N players: T H DPS — score X]' line at the top of
      each team list — the same info is shown in the 'After' stats block right
      above the columns, so duplicating it just adds noise.
    • Bump role icons from 14 → 16 inside the listing so they read cleanly next
      to the class-coloured name.
    • Two-space gap between icon and name (was one) for visual breathing.
    • listFS width 300 → 320 to fit Name-Realm suffixes on EU/PvP realms without
      early wrap. SetSpacing(4) between rows so multi-line lists don't feel
      cramped.
    • listFS y offset from title BOTTOMLEFT 0,-6 → 0,-10 for a clearer header
      separation.
  • CLAUDE.md: document the on-screen position clamp convention
    Mirror the BossWatch CLAUDE.md note (lines 118-139 of u:\WoW_BossWatch\CLAUDE.md):
    SetClampedToScreen(true) does not protect the initial SetPoint at load, so
    saved coords from a different monitor / resolution need an explicit
    UIParent:GetWidth/GetHeight comparison before being applied. Records the
    pattern actually in use (restorePosition + the mirror in ShowOptionsAt) so
    future persisted-position widgets follow the same recipe.
  • Off-screen window recovery: clamp saved position to current UIParent
    If the user drags the panel on a 4K monitor and later launches WoW on a
    1080p screen (or windowed mode at a smaller size), SetClampedToScreen alone
    doesn't help — clamp only kicks in during drag/SetPoint, not for a
    SetPoint(...) call with already-off-screen offsets at load.
    restorePosition() now validates abs(x) <= UIParent.width and abs(y) <=
    UIParent.height before applying. If either is exceeded, the saved position
    is wiped from the DB and the panel falls back to CENTER. Same guard applied
    to ShowOptionsAt (sister-addon handoff path) so a bad handoff doesn't lock
    the panel off-screen.
    Matches BossWatch's v0.7.3 behaviour. Documented in CHANGELOG under
    [Unreleased].
  • Add Item Level source (Blizzard inspect API, zero deps)
    New 5th source 'Item Level (inspect)' wired into DPSSource:
    • ilvlQueue + ilvlBusy throttle: one NotifyInspect at a time, 1.5s between
      inspects, 3s safety timeout if INSPECT_READY never fires (out of range etc).
    • INSPECT_READY handler resolves the GUID to a raid/party unit, reads
      C_PaperDollInfo.GetInspectItemLevel(unit), caches the ilvl per name with
      a 90s TTL, then ClearInspectPlayer + chains the next queue entry.
    • RefreshIlvl(): enqueue every raid/party member except self (own ilvl is
      read directly via GetInspectItemLevel('player') / GetAverageItemLevel).
    • GetDPS and GetHPS both return the same cached ilvl when source='ILVL' —
      it's a single character-power number used for both halves of the snake
      distribution.
    • ListActors returns every cached entry so the Source preview shows what
      the inspect sweep has gathered so far (trickles in over ~30s for a 20-man).
    • IsAvailable always true (no addon dependency, Blizzard API only).
    • ActiveSourceLabel shows 'Item Level (inspect) — N cached'.
      UI: Setup page gains a 'Scan raid ilvl' button next to the active-source
      label. README + CHANGELOG callout the new source as the recommended
      zero-dependency fallback when no damage meter is loaded.
      User-facing: same as right-click → Inspect a player. Pure Blizzard API, no
      'sniffing' beyond what every PvE addon already does.
  • README: GitHub-callout warning recommending Details!/Recount/Skada
    Without a damage meter loaded, GetEffectiveWeight/GetEffectiveHPS fall back
    to the Manual slider value (default 50). The split still computes, but it's
    not informed by who actually deals/heals in your raid. Make this explicit at
    the top of the README so users don't install bare and wonder why the
    balance feels random. Details! recommended (most accurate combat parsing).
  • Add README.md, CHANGELOG.md (Keep a Changelog), MIT LICENSE
    Pre-publish package — mirrors the BossWatch / TankWatch family layout. README
    highlights the standalone (0 required addons) positioning, the sister-addon
    family, the 10-40 man support range, and the optional Details/Recount/Skada
    integrations. CHANGELOG documents v0.1.0 in Keep-a-Changelog format with the
    combat-lockdown / built-in-tracker technical notes captured at the bottom.
  • Changelog: enforce '0 required addons' + sister-family convention
    User-confirmed convention for the Timikana addon family (BossWatch, TankWatch,
    SplitWatch, future siblings): every changelog batch must call out at the top
    that the addon is standalone (0 required deps, optional integrations listed)
    AND that it belongs to the sister-addon family.
    v0.1.0 entry now leads with:
    1. '0 required addons — works standalone. Details!/Recount/Skada optional.'
    2. 'Sister addon to BossWatch + TankWatch — shared side-tab nav + gold UI.'
      Both lines translated in frFR. Also tweaked the damage-meter line to mention
      that unavailable ones grey out (recently shipped feature).
  • Bump max raid size to 40 (Classic Era ceiling)
    Splitter:GetTargetGroups now caps perTeam at 4 — a WoW raid has 8 subgroups
    total (1-8), so the maximum addressable team layout is groups 1-4 vs 5-8 =
    20 vs 20. The math.min(4, ceil(N/5)) cap stops the helper from returning
    target subgroups beyond 8 (which SetRaidSubgroup would reject).
    Coverage:
    • 10-man → A=group 1, B=group 2
    • 11-20 → A=1+2, B=3+4
    • 21-30 → A=1+2+3, B=4+5+6
    • 31-40 → A=1+2+3+4, B=5+6+7+8 (maxes out subgroups)
      About + locale strings updated from '10-30 man' to '10-40 man'.
  • Details!: use GetActorList instead of GetContainer for solo reads
    GetContainer returns a structured object whose iteration (._ActorTable /
    GetIterator) varies between Details! versions, so my pairs() walk over the
    container was inconsistently surfacing actors in solo. Switch to
    combat:GetActorList(attr), which always returns a plain array of actor
    tables.
    Also fall back to Details:GetCombat(1) (last completed combat) when the
    current one is empty — needed so the preview still shows the player's
    DPS/HPS between pulls.
    Listing: drop only the synthetic [*] aggregate Details inserts at index 1
    (name starts with '['); keep everything else (player, pets, environment)
    so solo damage/heal lines are visible in the source preview.
  • Dropdown: grey out + disable unavailable sources
    makeDropdown takes an optional availabilityFn(value)->bool. When provided and
    returning false for an option:
    • the radio text gets a grey '(non installé)' suffix
    • the onClick handler refuses to update the DB
    • if the descriptor supports SetEnabled, it's disabled (further suppresses click)
      Wired to the DPS source dropdown so Details!/Recount/Skada are visibly
      unselectable when the corresponding addon isn't loaded. Manual stays always
      available.
  • Pages: leave room for the outer scrollbar by trimming widget widths
    Two text widgets had no SetWidth so they rendered on a single overflowing line
    that the outer ScrollFrame scrollbar then visually clipped:
    • previewEmpty ('Pas de données — ...') on the Source preview section.
    • row_empty ('Aucun membre de raid détecté ...') on the Joueurs page.
      Both now SetWidth(560) + SetJustifyH('LEFT') so they wrap cleanly.
      Everything that was 640px wide (hint texts, status lines, mode FS, warnings,
      banner, slash-cmd list, perm status FS, inner Weights scroll, source-preview
      scroll) is now 600px, and inner scroll content/rows shrink from 620 to 580.
      This keeps content well clear of the UIPanelScrollFrameTemplate scrollbar
      that lives on the right edge of every page.
  • UI polish: test-toggle label flips, team separator, scrollbar fix, role icons, Players tab
    • Test-mode button text now flips between 'Activer le mode test' / 'Désactiver le mode test' on click (Preview + Weights pages). Preview's parent.refresh syncs the label so toggling from one page is reflected on the other.
    • Add a vertical gold separator between Team A and Team B columns on the Preview page (1px gradient, y=-200 to y=-450).
    • Stop forcing min content height to 800 in makePage — use max(viewport, naturalSpan) instead. The outer Blizzard scrollbar now stays hidden when nothing overflows (was double-stacking with the inner Weights scrollbar).
    • Weights rows: role icon moves BEFORE the name (was sandwiched between name and slider) and the icon size bumps from 14px to 20px via a new size arg on roleIcon().
    • Rename tab 'Poids' to 'Joueurs' (FR) — the source key changes from L['Weights'] to L['Players'] so future locales translate accordingly.
  • Pages: wrap in ScrollFrame, kill BOTTOMLEFT anchors
    Scrolling:
    • Each tab page is now a UIPanelScrollFrameTemplate ScrollFrame with a content
      child, mirroring BossWatch. Page builders receive the content frame as their
      parent, so widgets sit on a scrollable canvas. Long pages (Setup with 4
      chained sections, About with the expanded changelog, Preview with 20-30 man
      team lists, Weights with the per-player slider list) all get a Blizzard
      scrollbar when content exceeds the viewport.
    • After build, content height is sized to cover the lowest section's bottom +
      24px padding (deferred 50ms so anchors resolve).
    • Setup-page auto-refresh ticker now hooks the ScrollFrame's OnShow/OnHide
      instead of the content frame (OnShow on a frame inside a ScrollFrame doesn't
      fire when the SF is shown/hidden).
      Overlap fixes — every BOTTOMLEFT anchor on a page-level widget was breaking
      because _captureAndReparent rebound them to the section container's bottom,
      which is itself derived from children GetBottom (circular sizing). Converted
      to fixed-Y TOPLEFT anchors:
    • Preview warnings line: was BOTTOMLEFT 14,14 → TOPLEFT 14,-460 (below team
      columns, scrolls if needed for 30-man).
    • Weights inner scroll: was BOTTOMRIGHT-relative → fixed 640x360.
    • Weights action buttons (Reset / Refresh / Toggle test): were BOTTOMLEFT
      14,14 + LEFT-chained → TOPLEFT 14,-428 / 182,-428 / 350,-428.
    • Setup Classic banner: was BOTTOMLEFT 14,14 → TOPLEFT 14,-580.
  • Sections: chained containers — collapsing one reflows everything below
    BossWatch-style section chain:
    • makeSection now builds a Frame container that anchors to the previous
      section's BOTTOMLEFT/BOTTOMRIGHT (so consecutive sections stack vertically
      with a fixed gap). The first section on a page uses its passed (x, y) as
      the anchor; subsequent sections ignore (x, y) and just chain.
    • Container TOPRIGHT pins to parent TOPRIGHT, so every section is full-width
      (no more cramped left/right columns on the Setup page).
    • _captureAndReparent: when a widget registers, it gets reparented onto the
      container and its anchors translated from page-relative-Y to container-
      relative-Y (page_y - section._originY). When the container's height shrinks,
      the widget moves with it.
    • SetCollapsed sets container height to COLLAPSED_HEIGHT (22) or natural span.
      UpdateNaturalHeight scans children's GetBottom and sizes the container to fit.
      Deferred via C_Timer.After(0) so widgets that register after the call get
      picked up.
    • Setup page reordered as single full-width column: General → DPS source →
      Source preview → Permission status. Permission status no longer lives in
      the right column.
    • About page reflowed: header block (logo + title) unchanged, then chained
      Slash commands → Panel (opacity slider + reset window button side-by-side)
      → Changelog.
      Net result: collapsing the General section moves DPS source and everything
      below it up by the height difference. Same for any section on any page.
  • About page: BossWatch-style layout (logo + 2 columns + changelog)
    • Logo (120x120) top-left as visual anchor.
    • Right of logo: title 'SplitWatch vX.Y.Z', subtitle, author, slash command + alias, sister-addons line.
    • Left column at y=-160: 'Slash commands' section listing /splitw, /splitw preview, /splitw apply, /splitw test, /splitw reset (each in yellow).
    • Right column at y=-160: 'Panel' section with opacity slider + 'Reset window position' button.
    • Full-width below: Changelog with expanded v0.1.0 entries (damage meters, HPS heal balancing, 10-30 raid support, collapsible sections).
  • Source preview: filter nameless actors from meter fallback
    Details!/Recount/Skada containers can include entries without a usable string
    name (pets, environment actors, or otherwise-broken rows in the meter's data).
    Pushing those into the preview list crashes nameFS:SetText with 'bad argument #1'.
    Filter to type(a.name) == 'string' before adding, and default to '?' as a final
    guard in the row builder.
  • Source preview: list actors from Details/Recount/Skada when roster is empty
    Adds DPSSource:ListActors() that enumerates everyone the active meter has data
    for — walks Details GetContainer(1)+(2), Recount's Fight table, or Skada's
    players list — and returns name/class/dps/hps tuples. The Setup-page preview
    now falls back to that list when SplitW.Roster:Scan() returns no members, so
    solo players can still verify their damage meter is hooked up and the
    DPS/HPS values are flowing.
    Roster path is unchanged; it stays preferred since it provides accurate role
    icons + class colors. The fallback skips role icons (everyone defaults to
    DAMAGER) but keeps class coloring and the raw numbers.
  • Drop built-in combat-log tracker — keep Details!/Recount/Skada/Manual
    pcall does not suppress ADDON_ACTION_FORBIDDEN because that's a UI event fired
    by Blizzard's taint system, not a Lua error — BugGrabber still reports it. The
    root cause of why SplitWatch specifically can't register COMBAT_LOG_EVENT_UNFILTERED
    (while Details!/Recount/Skada/BigWigs all can) is undiagnosed; deferring the
    registration to PLAYER_LOGIN doesn't help either.
    Pragmatic resolution: stop registering the event at all. DPSSource._builtinAvailable
    stays false, the tracker frame is created but has no events wired, the OnEvent
    handler never fires, and getDPSFromBuiltin/getHPSFromBuiltin always return nil.
    Remove the 'Built-in (combat log)' option from the source dropdown so users don't
    land on a dead option. Their saved-DB BUILTIN value still works (returns nil →
    falls back to manual weight), but the picker only offers Details!/Recount/Skada/Manual.
  • Fix Source Preview overlap with Permissions + harden COMBAT_LOG registration
    Layout fix:
    • Move Source preview from y=-250 to y=-340 so it no longer overlaps with the
      right-column Permission Status section at y=-250 (the source preview was using
      full width 640, which crossed into the right column at x=360).
      Event registration hardening:
    • Wrap clf:RegisterEvent calls in pcall to survive WoW 12.0's
      ADDON_ACTION_FORBIDDEN on COMBAT_LOG_EVENT_UNFILTERED (cause unclear — other
      damage meters succeed, but ours triggers Blizzard's taint check). Without the
      wrap the error was firing 6× on load and the addon never finished initialising.
    • Track DPSSource._builtinAvailable and surface 'Built-in (blocked by Blizzard
      event protection)' in the Active source line when registration failed, so the
      user knows why the live DPS numbers stay at —.
    • Change default dpsSource from BUILTIN to DETAILS so a fresh install doesn't
      land on the broken source. Built-in stays in the dropdown for users who want
      to retry it.
  • Sections: fix click area + register free-floating widgets
    Two bugs causing collapse to look broken:
    1. Click area was 30x20 only — header:GetStringWidth() returns 0 at frame-build
      time (FontString not yet measured), and Lua's '0 or 100' evaluates to 0 since
      0 is truthy. So the area only covered the first ~3 characters of the header.
      Fixed by switching to two SetPoints (TOPLEFT + BOTTOMRIGHT) so the area
      spans the full section-line width minus the reset button slot, regardless of
      the FontString's measured size. Also bumped frame level above siblings.
    2. Many widgets (hint FontStrings, ScrollFrame previews, stats lines, team-column
      list FontStrings, changelog entries, warning line, alpha slider) were created
      directly via CreateFontString/CreateFrame and never passed through the widget
      factories, so they weren't tracked in _currentSection.children — collapse hid
      nothing visible. Explicit _registerInSection() calls added everywhere a free-
      floating widget exists, on all four pages.
  • Panel: collapsible sections + per-section reset (BW parity)
    • Replace makeHeader with makeSection: each section has a click area on the
      header (toggle collapse), a chevron texture (UI-PlusButton-Up/UI-MinusButton-Up),
      and a reset button (UI-RefreshButton) that resets only the dbKeys registered
      in that section to SplitW.Defaults.
    • Widget factories (makeCheck/makeSlider/makeDropdown/makeButton/makeLabel) push
      themselves and their dbKey into _currentSection so collapse hides them and
      reset wipes their values.
    • Collapsed state persists in SplitWatchDB.collapsedSections keyed per section
      (setup.general, setup.dps_source, setup.source_preview, setup.permissions,
      weights.main, preview.main, about.info, about.panel, about.changelog).
    • Phase 1 fix: Preview-page status FontString moved to its own row (y=-68, full
      width) so 'Pas de raid détecté — active le mode test pour visualiser' no longer
      overflows off-screen. Before/After labels and team columns pushed down to fit.
    • Phase 3: panel-opacity slider moved from Setup (right column) to About (right
      column), since it's a cosmetic/window pref, not a per-character setting.
  • Setup page: live DPS/HPS preview of the active source
    Adds a scrollable list under the source dropdown showing each roster member with the current DPS and HPS readings from the selected source. Updates every 1.5s while the Setup tab is visible (cancelled on hide) and on dropdown change via the shared refresh hook. Numbers formatted as k/M for readability; dash for no data. Moves Permission status to the right column to make room.
  • Support 10-30 man raids + HPS-based heal balancing + built-in meter
    Algorithm:
    • Dynamic team→subgroup mapping in Splitter:GetTargetGroups: 10-man uses groups 1 vs 2, 11-20 uses 1+2 vs 3+4, 21-30 uses 1+2+3 vs 4+5+6.
    • Healers no longer alternate by count — they snake-distribute by HPS (greedy: weakest healer goes to the lower-throughput team). Matches the DPS algorithm so weak/strong asymmetry filters into both roles.
    • Compute() now returns healScoreA/healScoreB alongside scoreA/scoreB.
    • Drop UNEVEN_25 warning (raid sizes >20 now handled natively); add UNEVEN_TEAMS info warning when team counts differ by more than 1.
      DPS / HPS sources:
    • Add built-in combat-log tracker (parses COMBAT_LOG_EVENT_UNFILTERED, sums damage + effective healing per source name, snapshots on PLAYER_REGEN_ENABLED). Zero addon dependency.
    • Add Skada alongside Details!/Recount; each meter now exposes both DPS and HPS.
    • New default = BUILTIN so the addon works out of the box without Details!/Recount/Skada installed.
    • Panel: source dropdown lists all 5 (Built-in, Details!, Recount, Skada, Manual) with a single localised tooltip.
    • Preview shows per-team DPS + HPS scores so the user can see both balances.
      Note: PR description-grade summary kept here intentionally because the surface area touched is large.
  • Preview: surface test-mode button + roster status line
    When the user isn't in a raid the roster scan returns 0 members and the preview shows empty teams with no clue why. Add a 'Toggle test mode' button on the Preview page (was only on Weights) and a status line that says 'no raid detected — enable test mode' / 'live roster (N members)' / 'test mode ON' so the empty-state is no longer mysterious.
  • Panel: clear bottom-tabs strip + fix missing glyphs
    • Move pageHolder.BOTTOMRIGHT from y=8 to y=32 so docked bottom tabs aren't visually clipped by page content (BW gets away with 8 because its pages are scrollable).
    • Replace unicode → arrow (U+2192) and ⚠ warning (U+26A0) with Blizzard texture inlines — FRIZQT__ doesn't include those glyphs and renders them as empty boxes.
  • Panel UI: match BossWatch look — bottom tabs, alpha slider, TGA portrait
    • Switch bottom tabs from PanelTopTabButtonTemplate to PanelTabButtonTemplate (mirrors BossWatch).
    • Add multi-row tab wrap layout so narrow panels stack tabs cleanly.
    • Apply panel:SetAlpha (0.85 default) + opacity slider on Setup page, account-wide DB key.
    • Convert logo.png to logo.tga (WoW textures need TGA/BLP; PNG paths render as empty squares).
    • Set portrait via panel.portrait and panel.PortraitContainer.portrait (covers both PortraitFrameTemplate variants).
    • Side-tab icons now reference .tga explicitly (Media/logo.tga for self + BossWatch sister).
    • pageHolder bounds match BossWatch (8,-60 / -8,8).
  • Initial v0.1.0 — MVP manual weights mode
    • Roster scan (real raid + simulated 20-man test mode)
    • Snake-distribution algorithm: tanks alternate, healers alternate, DPS by descending weight greedy
    • Apply.lua: permission-gated SetRaidSubgroup queue with combat-lockdown deferral
    • DPSSource.lua: Details! and Recount fallbacks (manual is the only active source for v0.1.0)
    • Options panel: Setup / Weights / Preview (before+after stats) / About+Changelog
    • Side tabs to BossWatch + TankWatch (cross-addon panel handoff)
    • Minimap launcher, Blizzard Settings host, /splitw + /splitwatch slash
    • frFR locale at parity with enUS
    • Multi-toc: SplitWatch.toc (retail 12.x) + SplitWatch-Mists.toc (MoP Classic 5.5)