v2.0
PUG Helper
v2.0 (2026-06-23)
Full Changelog Previous Releases
- Add CurseForge packaging via BigWigs packager
- Tag .toc with X-Curse-Project-ID 1585189
- Add .pkgmeta (package-as PugHelper, ignore dev-only files)
- Add release workflow that packages and uploads on tag push
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Add per-line "drag to action bar" callout macros
Each callout line gets a drag handle (normal mode) that turns the line
into a self-contained WoW macro ("/pug send ") and puts it on
the cursor to drop on an action bar -- a one-click broadcast button.
The macro substitutes {TOKENS} and resolves the channel LIVE at click
time (same Chat.SendLine path as the in-window click), and is a snapshot
that survives /reload, relog, and updates with no saved-vars mapping.- Core/Macro.lua: PickupForLine/Fits/Explain; body-hash names (PH+base36,
<=16 chars) reuse one macro per line. All macro API is api.has-guarded
and CreateMacro is pcall'd (it errors when the macro list is full). - Core/Api.lua: api.InCombat shim; the gesture is refused in combat
(CreateMacro/PickupMacro are #nocombat and bar drops are protected). - Core/Slash.lua: new "/pug send " subcommand.
- UI/Window.lua: pooled per-row grip (normal mode only, hidden in edit
mode), reserved label width, tooltip that warns in amber on unset
{TOKEN}s (mirrors the in-window send guard), updated hint + once-ever tip. - Over-long callouts (>~245 chars) are refused, never truncated.
- Config/Namespace/.toc/.luacheckrc/CLAUDE.md updated.
Verified macro API signatures + #nocombat behavior against warcraft.wiki.gg.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Core/Macro.lua: PickupForLine/Fits/Explain; body-hash names (PH+base36,
- Set Names: edit roles + drag-to-reorder, scoped per tab
Add an Edit button (rename any role's label; change the {TOKEN} for custom
roles only, since built-in tokens are referenced by callouts) and drag-to-
reorder to the Set Names overlay, mirroring the callout section/line drag.
Engine (Config/Content):- customRoles gains per-tab
labels(token -> label override) andorder
(token list); merged into old saves and swept by PruneCustomization. - EffectiveRoles applies the per-tab label override + display order and
exposes baseLabel; new EditRole/MoveRole/SetRoleLabel. - A custom token change migrates the saved name and follows the token through
the order/label/hidden maps (renameTokenInMaps); deleting/reset clean up
stale label/order entries.
UI: - Role rows are draggable buttons with a per-row Edit button, a drop
indicator, and a trailing end-zone for the last slot; a modal role-edit
popup (label for all, token for customs). Row body is drag-only so it
never fights the dropdown's click area. - Editor popup closes on tab switch and window hide.
Editing/reordering are per-tab; Reset roles drops the tab's customs, hides,
labels, and order. luacheck clean; CLAUDE.md updated.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- customRoles gains per-tab
- Fix 14 issues from a comprehensive adversarial review
All findings were low/nit (the WoW-API surface came back clean). Grouped:
Display correctness (FontString '|' escaping): user-authored callouts,
section titles, the instance note, custom-role labels, and the editor
confirm/preview/send-confirm surfaces are now escaped via a shared
util.escapePipes (display-only; what's broadcast to chat is untouched).
Fork-on-edit: SetLine/SetSectionTitle/MoveSection/MoveLine now compare
against a non-forking sourceSections() and bail on a no-op BEFORE calling
materialize(), so re-saving identical text or a drop-in-place drag no
longer falsely flags a tab "(customized)".
Custom roles: RemoveCustomRole uses an explicit branch (noa and b or c
fall-through to the global list); a global AddCustomRole now rejects a
token already used per-instance (no silent shadowing); the add-role error
is preserved across background roster-update rebuilds.
Saved-vars lifecycle: Config.PruneCustomization + Content.PruneCustomization
reap per-instance data for removed/renamed content (run from Boot), and
PruneNames no longer lets orphaned roles pin saved names.
UI lifecycle / perf: the window's OnHide hides the Set Names overlay so a
reopen lands on the callouts; ToggleNames' hide path refreshes the pane;
the roster/leader watcher skips work while the window is hidden (channel
button catches up on reopen).
Nits: UTF-8-safe util.truncate replaces byte-based :sub truncation in the
delete-line/live/send previews.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Add a UI design-token module for a cohesive, professional look
Introduce UI/Theme.lua (ns.UI.Theme) as the single source of truth for
the UI's colours, the cross-file sizes, and font roles, then migrate every
UI module onto it. This is an internal token layer, not a user-facing
theming system - nothing is configurable or saved.
Collapses the value drift the UI had accumulated:- 4 near-identical dark panel fills -> one panelBg
- 3 different border greys (+ the Set Names panel had none) -> one
panelBorder, applied via a shared UI.PanelChrome helper - multiple blues/greens/oranges -> one accent / ok / unset token each
- the unset-{TOKEN} cue was literally two different oranges (vertex vs
inline text); both now derive from one token via Theme.hex, so they
can't drift again
Structure/polish: - UI.TitleBar gives the editor popup and Set Names overlay the same
title-bar strip as the main window - a vertical divider between the tab list and the message pane
- ROW_INSET 22 -> 18 so wrapped callout text fills the row (no dead strip)
- distinct flash token keeps the send-confirmation pulse vivid
Adds wrap-safe Theme.addLine so a coloured+wrapped GameTooltip line can't
truncate the colour's returns against the trailing wrap flag. All texture-
based, no :SetBackdrop, no new API; luacheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Third UX pass: unset-send guard, terminology, onboarding, feedback
Driven by a multi-agent UX review (9 lenses, findings adversarially verified
against the source and the project's constraints).
Send-loop safety:- Gate sending a line that still has an unset {TOKEN} behind a "Send anyway"
confirm showing the exact text + channel, so a click mid-pull can't broadcast
a literal "{MT} tanks ..." to the raid. Resolved lines still send on one click. - Double-send guard: swallow a repeat click within ~0.4s (guarded C_Timer shim;
never latches when no timer exists, so sends always work). - Flash "sent" only when a line actually went out (empty/whitespace sends none).
Terminology & microcopy: - Set Names panel: 5-man Heroics are "tab", not "raid"; fix "Reset raid"
references (the control is "Reset roles"); reset confirm reads "Reset callouts". - Slash: /pug show only shows; /pug edit forces edit ON (idempotent); /pug
channel reports the resolved channel; /pug names states its scope; unknown
commands warn; /pug reset clarified as window-position only. - Channel codes upper-cased in the tooltip; line-drag tooltip scoped
"within section" (cross-section drops are a no-op).
Onboarding & clarity: - Content hint adapts when a tab has no callouts yet (the framework model).
- Edit-button tooltip sets the "tabs ship blank, author here" expectation.
- Load message names the minimap button alongside /pug.
Other: - "Reset callouts" reachable whenever a tab is customized, not only in edit mode,
so the always-on "(customized)" badge has a remedy. - Stale-assignment cue backed with text, not color-only (accessibility).
- Add-role Tab key cycles Name<->Token; brighter edit-mode tint; clear the
left-list search filter on window reopen.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Gate sending a line that still has an unset {TOKEN} behind a "Send anyway"
- UX pass: modal signalling, channel safety, feedback, and clarity
From the third (UX-focused) adversarial review. Core one-click send keeps no
added friction; shipped content stays a framework (empty-state messaging only).
Modality (the headline gap):- Modal blockers now DIM the window behind them, so the lock is visible instead
of clicks silently dying on a transparent scrim. - Confirm dialog is named + in UISpecialFrames, so Escape cancels the confirm
instead of closing the whole window. - Edit mode tints the message pane amber, so "click edits, not sends" is obvious.
Channel safety: - The channel button turns red when the live target is SAY/GUILD (public), and
shows "(now: X)" instead of an unexplained ">". - Right-click the channel button to step back (one-click overshoot recovery).
- RAID_WARNING downgrades to RAID when you're in a raid but not lead/assist
(it was silently delivering nothing); new guarded api.CanRaidWarn() shim. - /pug channel (no arg) now reports the current channel + where it lands.
Feedback / clarity: - Unfilled {TOKEN}s are tinted orange in the line text (not just a color-only
bullet); a click flashes the row's bullet green to confirm a send. - Set Names: solo/pre-invite help text, "(not set)" placeholders, a reddish
flag for a name no longer in the group, scope-aware delete-X tooltips, inline
add-role error (was printed behind the overlay), a close-X, and a confirm on
"Clear names" (was unconfirmed). Picking a name updates its cue immediately. - Editor box is single-line with Enter-to-save (it was multiline; newlines were
stripped anyway). First-time Edit shows a one-off gesture tip in chat. - Clearer labels: "Reset tab"->"Reset callouts", "Reset raid"->"Reset roles",
"Clear All"->"Clear names", "Scope:" prefix on the toggle; richer /pug help.
luacheck: 0 warnings / 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Modal blockers now DIM the window behind them, so the lock is visible instead
- Fix 8 issues from the second adversarial review
Modal blocker (UI.MakeModal) hardening:- Scope the blocker to the addon window (UI.frame) instead of the whole
screen, so editing a callout no longer locks out action bars / the game UI. - Raise the blocker with its dialog, so a third-party DIALOG-strata frame
can't sit above it and leak clicks through. - Eat the mouse wheel too (EnableMouseWheel + absorbing handler); EnableMouse
alone let wheel events scroll panes behind the modal.
Other: - Window OnHide now also closes the editor popup, clearing its shown flag so a
reopen doesn't briefly re-show it (and dropping its modal blocker). - Config.CycleChannel routes its write through SetChannel, so the PugHelperDB
nil-guard + validation live in one place like every sibling accessor. - Cap the edit-popup live preview at 160 chars so a long callout can't grow it
down over the Save/Cancel buttons (the edit box keeps the full text). - Right-click on the "+ Add line"/"+ Add section" rows is now a no-op
(left-click only), restructured so it can't fall through to a nil-index
delete that table.remove would turn into "remove the section's last line". - Escape literal '|' in instance notes to '||' at display, or WoW's FontString
parser eats the separator (e.g. "25-player | Phase 1"). - Soften the Refresh drag-clear comment: the clear is a defensive guard, not a
response to a resize-mid-drag refresh (the window isn't resizable).
luacheck: 0 warnings / 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Scope the blocker to the addon window (UI.frame) instead of the whole
- Fix 10 issues found by adversarial review
Engine / WoW API:- Minimap drag: math.atan2 is nil on the post-8.0 Classic-era client; use
math.atan2 or math.atan so the drag math never calls a nil global. - Name tokens are now case-insensitive (Config.GetName/SetName uppercase),
so a callout written with lowercase {mt} fills from a name set under MT
instead of being sent to chat literally. - EffectiveRoles: scope the per-tab
hiddensuppression to built-ins, so a
user can re-add their own role under a token whose built-in they hid
(previously it passed the AddCustomRole duplicate check yet went invisible). - GUILD channel now downgrades when guildless (Config requires="guild",
api.InGuild shim, Chat.ResolveChannel guard) instead of erroring per line. - Config.Channel self-heals a stale/invalid saved channel to AUTO.
- util.wrap: hard-cut backs off UTF-8 continuation bytes (no split glyphs)
and a leading-whitespace run can't emit an empty chunk.
UI: - Edit popup and confirm dialog are now modal (UI.MakeModal): while editing,
the rest of the window is inert, so an unrelated gesture can no longer
trigger a refresh that silently discards an in-progress edit. - UI.Refresh takes preserveEditor; resize / name-set keep an open edit.
- Confirm dialog is hidden on window OnHide so Escape can't strand it.
- UI.Refresh clears any in-flight drag state + drop indicator, so a mid-drag
refresh can't leave the pane stuck in phantom-drag mode.
luacheck: 0 warnings / 0 errors (IsInGuild allowlisted).
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Minimap drag: math.atan2 is nil on the post-8.0 Classic-era client; use
- Add minimap launcher, line drag/duplicate, search, unset-token cues
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Simplify /pug name parser: one regex pass, no redundant recheck
Collapse the two-pass match (TOKEN+value, then bare TOKEN fallback) plus
the duplicateif not tokencheck into a single pattern that handles both
forms. Barename TOKENstill yields value "" (clears the name), so
behavior is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Simplify: own single-line invariant in Content; dedup PruneNames
- Add util.oneLine (collapse embedded newlines/tabs + trim) and use it in
the Content text mutators (SetLine/AddLine/SetSectionTitle/AddSection) so
a saved callout is always one chat line regardless of caller. Drop the
now-redundant local sanitize() from EditPanel (4 call sites pass raw text). - Collapse PruneNames' three identical role-token loops into one markLive().
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Add util.oneLine (collapse embedded newlines/tabs + trim) and use it in
- Simplify: O(N) row pool, shared instance-name helper, confirm Reset tab
- Window: replace the O(N^2) per-render pool scan (linear search for a free
frame on every row) with a forward cursor reset in ReleasePool. - Content.InstanceName: one home for the id -> display-name + fallback lookup
the names panel re-rolled; reused by Reset tab. - Reset tab now routes through the shared UI.Confirm like every other
destructive action instead of dropping a tab's edits unconfirmed. - Config.CustomRoles no-DB branch returns
hiddentoo, matching the shape
the accessor guarantees on its main path. - Inline the redundant EnableWheel alias; drop dead value nil-guards in /pug name.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Window: replace the O(N^2) per-render pool scan (linear search for a free
- Set Names: label-before-dropdown rows, confirm role deletes
Put the "{TOKEN} Name" label first (right of the delete X) with the
name dropdown in a reserved right column sized off the scroll width,
so the token bracket no longer overlaps the dropdown and long names
still wrap without truncating.
Route the row X through UI.Confirm with a scope-aware prompt: custom
roles confirm "Delete", built-ins confirm "Hide" (restorable via
Reset raid), matching the confirm-on-destroy rule used elsewhere.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Add user-defined roles (global + per-raid) with full customization
Set Names can now create custom {TOKEN} roles, scoped Global (every
tab) or This raid (current tab only) so the panel stays uncluttered.
Names remain keyed by bare token (picked from the live roster), so
substitution and the send path are unchanged.- Config: customRoles store { global, byInstance, hidden } + accessor.
- Content: EffectiveRoles (built-ins + global + per-raid, minus hidden),
AddCustomRole/RemoveCustomRole (token sanitized to {%w+}, collision-
checked), HideRole, ResetRoles; PruneNames keeps custom tokens live. - NamesPanel: single-column, full-width word-wrapping rows so names are
never truncated; every role deletable via a left-side X (custom ->
removed, built-in -> hidden per raid); Reset raid / Reset global
buttons; add-role inputs (name + token) with a scope toggle. - Helpers: shared UI.Confirm dialog and UI.EnableWheel.
- EditPanel/Window: right-click delete of a callout line or section now
asks for confirmation first. - Slash: /pug names lists effective (custom + raid) roles.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Simplify channel resolution and boot selection fallback
Extract bestAvailable() in Chat.ResolveChannel for the AUTO and
downgrade paths, and use Content.ResolveSelectedInstance() in Boot
instead of re-deriving the keep-or-fallback policy inline.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Simplify: shared helpers, single-copy Effective, Config-owned name pruning
- Add Config.PruneNames so Content.PruneNames no longer touches PugHelperDB
directly (restores the "only Config owns saved vars" invariant). - Add UI.Background helper; replace three hand-rolled background-fill blocks.
- Content.Effective: copy sections once instead of deep-copying defaults then
discarding them on the customized path; fold the "is customized?" predicate
into a shared customSections() used by Effective/materialize/HasCustom. - Drop redundant nm alias in NamesPanel; share drag-cancel via CancelDropTarget.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Add Config.PruneNames so Content.PruneNames no longer touches PugHelperDB
- Fix edit-popup target staleness, drag cancel, and duplicate ids
Adversarial review found three real interaction bugs:- Critical: the shared edit popup stayed open across tab switches and "Reset
tab", so clicking Save then wrote to the wrong (or just-reset) instance.
UI.Refresh now dismisses the popup (UI.CloseEditPopup), so it can never
outlive the content it was opened against; the legitimate Save still applies
first since the text is read before the refresh. - Major: a section drag released over dead space still applied the last hovered
drop target. Row/header OnLeave now clears the drag target during a drag, so
releasing off any target cancels the reorder. - Major: two instances whose names slug to the same id created a dead duplicate
tab with unreachable content. Content.AddInstance now skips the duplicate with
a data warning.
luacheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Critical: the shared edit popup stayed open across tab switches and "Reset
- Fix stale customization-model comments (Content.lua, Config.lua)
A correctness review of the recent changes found no runtime bugs but flagged
two comments still describing the old title-keyed override model
(overrides/added/hidden). Update them to the implemented fork-on-edit model
(PugHelperDB.custom[instanceId].sections). Comments only; no code change.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Simplify repeated patterns: UI builder helpers + string sections
UI/Helpers.lua adds UI.Button, UI.Tooltip, and UI.AddBorder, replacing the
duplicated Edge() border (defined in both Window.lua and EditPanel.lua), the
repeated GameTooltip OnEnter/OnLeave wiring on static buttons, and the
CreateFrame/SetSize/SetText/SetScript button boilerplate across the three UI
files. Dynamic row/header tooltips (conditional on edit/drag mode) are left
inline.
Content.AddInstance now normalizes section entries, so a section can be authored
as a bare title string ("Boss Name") instead of { title = "...", lines = {} }.
All 20 content files use the string-list form, shrinking each and trimming the
near-identical header comments.
No behavior change. luacheck clean (33 files).
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Add drag-and-drop section reordering in edit mode
Section title headers are now draggable (RegisterForDrag) to reorder sections.
While dragging, hovering a header/row marks the drop slot and an insertion line
shows where the section will land; releasing calls Content.MoveSection and
refreshes. A no-movement click still fires OnClick, so rename/delete coexist
with drag. Dropping on the "+ Add section" row moves a section to the end.
Adds Content.MoveSection (insert-before semantics, no-op when order unchanged)
and the drag/indicator wiring in UI/Window.lua.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Make section titles editable; add/rename/delete sections in-game
Move the customization layer to a fork-on-edit model: the first edit to an
instance copies its whole section list into PugHelperDB.custom.sections, which
then becomes authoritative (Content.Effective never mutates defaults; Reset tab
drops the copy). Sections and lines are addressed by display index, recomputed
each render, so section titles may repeat (e.g. multiple "Trash" sections).
Editor (Edit mode):- Section headers are clickable: left-click renames, right-click deletes.
- "+ Add section" row at the bottom; "+ Add line" per section unchanged.
- Refresh now preserves scroll position across edits (resets to top only on
tab switch).
Updates Content.lua mutators (SetLine/AddLine/DeleteLine by index +
SetSectionTitle/AddSection/DeleteSection), Window/EditPanel UI, and CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Refactor into modular architecture; add Phase 2 raid/heroic tabs
Split the monolithic Core.lua/Data.lua into focused modules sharing one
private namespace (ns): Core/ (Namespace, Util, Api, Config, Content, Chat,
Slash, Boot) and UI/ (Window, NamesPanel, EditPanel). Content moves to a
one-file-per-instance registry under Content/.- In-game callout editor with per-instance overrides persisted in
PugHelperDB.custom, layered over registered defaults (Content.Effective);
edit/add/delete lines + Reset tab. - Scrollable left tab list (UIPanelScrollFrameTemplate + mousewheel) so all
tabs fit. - Tabs for Phase 2 raids (Karazhan, Gruul, Magtheridon, SSC, The Eye) and all
15 launch heroics, plus Anzu and Yor; section titles only, empty lines. - Update .toc load order, .luacheckrc globals, and CLAUDE.md file roles.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- In-game callout editor with per-instance overrides persisted in
- Remove luacheck CI workflow
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Show full callout text in the message list, wrapped (no truncation)
The list previously truncated each line to ~78 chars with an ellipsis, so the
full callout was only reachable by hovering. Now each row label wraps to the
pane width and the row grows to fit (height from GetStringHeight), so the whole
substituted line is visible inline.- Remove the Truncate helper and LABEL_CHARS; add ROW_INSET/ROW_VPAD for the
wrapped-row metrics. Bullet is top-aligned; label is top-aligned and wraps. - Trim the hover tooltip back to the "click to send to " hint now that
the full text shows inline; drop the now-unused PREVIEW_CHARS. - Update the hint line accordingly.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Remove the Truncate helper and LABEL_CHARS; add ROW_INSET/ROW_VPAD for the
- Show full callout text in hover preview, never truncated
The preview added the whole line as one auto-wrapped GameTooltip line, which
sized the tooltip very wide; anchored to the right of a right-side row, long
callouts ran off the screen edge and looked cut off. Now the preview wraps the
text itself to a fixed width (PREVIEW_CHARS) and emits one tooltip line per
chunk, so the tooltip grows downward and the full line is always visible.
Factor the word-splitting into a shared WrapText() helper; SendLine reuses it at
the 240-char chat limit instead of duplicating the loop.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Polish: centralize channel list, prune stale names, name layout constants
- Channels are now defined once as a {name, requires} table; CHANNEL_NAMES and
CHANNEL_REQUIRES are derived from it. ResolveChannel readsrequiresinstead
of hardcoding RAID/PARTY, the cycle/validation paths iterate the derived list,
and the slash help text is built with table.concat -- the channel list no
longer appears in four places. - PruneNames() drops saved names whose token is neither a current role key nor
referenced by any callout line, so PugHelperDB.names stops accumulating
leftovers when roles/lines change. Tokens still in use (including custom slash
tokens) are kept; matching is case-insensitive. - Promote the window's defining numbers to named constants (FRAME_W/FRAME_H,
BUTTON_H, TITLE_H) alongside the existing LEFT_W/CONTENT_X, so the frame's
structural dimensions aren't inline literals while their siblings are.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- Channels are now defined once as a {name, requires} table; CHANNEL_NAMES and
- Keep channel button in sync; drop dead Edge params
- The toolbar channel button now updates after
/pug channel X, not just when
cycled by clicking. channelBtn is lifted to a module local and both input
paths route through a shared UpdateChannelButton(), so the label can't go
stale relative to PugHelperDB.channel. - Edge() no longer takes p1/p2/w/h: every caller invoked it with no args and
anchored/sized the texture itself, so the parameters were dead and the
signature misleading.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- The toolbar channel button now updates after
- Tolerate malformed Data.lua instead of throwing
Editing Data.lua is the intended workflow for non-coders, but the render path
trusted it completely: a raid missingsections, a section missinglines, or
a role missingkeythrew a cryptic Lua stack trace. Now:- asList() guards every ipairs over user content (raids, sections, lines, roles)
so missing/garbage values degrade to empty instead of erroring. - Non-string lines and roles without a key are skipped; unnamed raids/sections
render with placeholders rather than crashing on concatenation. - ValidateData() runs at load and prints friendly, actionable warnings
("raid 'Karazhan', section #3 has no lines") pointing at the offending entry. - The loader no longer indexes PugHelperRaids before confirming it's a table.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- asList() guards every ipairs over user content (raids, sections, lines, roles)
- Add CI workflow to run luacheck on push/PR
Local environments can't always run the linter, so enforce it in CI instead:
this is the durable fix for "we can't verify luacheck is green" and guards
against .luacheckrc drifting from the code again. Uses Lua 5.1 to match WoW's
Lua flavor and the std = "lua51" setting in .luacheckrc.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Harden channel/token handling; tidy constants and docs
- ResolveChannel now downgrades a manual RAID/RAID_WARNING/PARTY override to a
channel valid for the current group (Party or Say), so clicking a line while
solo can no longer trigger "You are not in a raid/party group". - /pug name rejects non-alphanumeric tokens up front, since substitution only
matches {%w+} — previously such a name was stored but could never fill in.
Documented the same constraint next to PugHelperRoles in Data.lua. - Promote the 240-byte chat split to a named CHAT_LIMIT constant.
- Use table.insert consistently (was a lone tinsert); anonymize the names panel
frame (nothing referenced it by global name); comment the fixed 2-column
names layout; set the .toc author.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com
- ResolveChannel now downgrades a manual RAID/RAID_WARNING/PARTY override to a
- Align luacheck config with actual API usage
read_globals had drifted from the code: it was missing globals the addon
actually calls (UnitName, GetRaidRosterInfo, GetNumSubgroupMembers, the
UIDropDownMenu_* family) — which luacheck flags as undefined-global access —
while listing several never used (GetTime, InCombatLockdown, PlaySound,
C_Timer, str*/wipe/format, tinsert/tremove). Rebuild the list to match what
Core.lua references soluacheck .is actually clean, and add a note to keep
it in sync.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Ignore Claude Code memory/ directory
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Harden CLAUDE.md WoW API guidance
Expand the single API bullet into a dedicated "WoW API rules" section:
verify-don't-assume, the modern-engine-reporting-2.0.5 reality, the
mandatory type-guard compat-shim pattern (grounded in the existing
RaidCount/InRaid helpers), and a list of specific landmines (C_* namespaces,
backdrop system, CreateFrame templates, UIDropDownMenu, SendChatMessage
channels, roster signatures, events).
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Add party/raid roster dropdowns to Set Names menu
Replace the manual name EditBoxes with UIDropDownMenus populated live
from the current group. Use modern group API (GetNumGroupMembers /
GetNumSubgroupMembers) with the old API as fallback, since the
Anniversary client runs on the modern engine where the TBC-era count
functions are nil. Also raise the names panel frame level so its
background covers the scroll rows.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Initial commit: PUG Helper addon for TBC Anniversary
Raid callout/assignment helper with token substitution.
Includes CLAUDE.md guidance and luacheck config.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com - Initial commit