GRIP - Guild Recruitment

Automates guild recruitment — scans /who for unguilded players, queues whispers and guild invites, and schedules Trade/General channel posts.

File Details

v0.5.0-beta

  • B
  • Mar 4, 2026
  • 129.44 KB
  • 2
  • 12.0.1
  • Retail

File Name

GRIP-v0.5.0-beta.zip

Supported Versions

  • 12.0.1

GRIP – Guild Recruitment Automation

v0.5.0-beta (2026-03-04)

Full Changelog Previous Releases

  • Update GRIP.toc
  • Migrate Blacklist panel from FauxScrollFrame to ScrollBox + DataProvider
    Replace the deprecated FauxScrollFrame in the Blacklist panel (UI_Home_Blacklist.lua) with WowScrollBoxList + MinimalScrollBar + DataProvider, matching the pattern already proven in the Potential list migration. Eliminates manual row pool management (CreateFramePool, ResizeBlacklistRows, FauxScrollFrame offset tracking) in favor of ScrollBox's automatic row recycling and element initializer pattern. Updates the no-DB clearing path in UI_Home.lua to use empty DataProvider instead of iterating bl.rows.
  • Migrate Potential list from FauxScrollFrame to ScrollBox + DataProvider
    Replaces the deprecated FauxScrollFrame + manual row pool pattern in the Home page Potential list with the modern ScrollBox + DataProvider API. Row recycling is now handled automatically by ScrollBox, eliminating ResizePotentialRows and ~100 lines of manual pool management. Column widths are stored centrally for the element initializer to read on each display pass. All existing behavior (class colors, tooltips, right-click menu, empty state, stripe alternation) is preserved.
  • Fix resize drag jump, dynamic max bounds, and debug message isolation
    Replace native StartSizing with manual OnUpdate resize to eliminate the visual jump when clicking the resize grip. Max bounds now refresh dynamically on each drag start to match the current game window size, and SCREEN_MARGIN is removed so the frame can fill the full screen.
    Debug and GateTrace messages no longer fall back to DEFAULT_CHAT_FRAME — they only appear in the dedicated Debug chat tab. /grip debug on now auto-creates the Debug chat window and enables ring buffer capture; /grip debug off disables capture. The unused _warnedMissingDebugWindow config key is removed.
  • Deduplicate local AddButtonAccent in UI_Home → shared W.AddButtonAccent
    Remove the local AddButtonAccent helper from UI_Home.lua and replace all 4 call
    sites with the identical shared W.AddButtonAccent already in UI_Widgets.lua.
    Pure dedup — no behavior change, no other files modified.
  • Add visual polish to Settings and Ads pages (tooltips, separators, accents, fonts)
    Brings Settings and Ads pages to visual parity with the already-polished Home page. Adds descriptive tooltips to all 39 interactive elements, inserts 7 section separators (1px, 8% white), applies gold/red button accent underlines to 10 action buttons, upgrades 5 section headers from GameFontNormalSmall to GameFontNormal, and applies destructive text color (1, 0.6, 0.6) to Clear Selections and Remove buttons. Promotes AddButtonAccent from UI_Home local to shared W.AddButtonAccent in UI_Widgets.lua. No functional behavior changes.
  • Add visual polish to Home page (class hover, status panel, tab underline, ghost border, gradients)
    Visual-only polish pass on the Home page — 11 cosmetic items with zero functional changes. Adds class-colored row hover, bordered status panel with upgraded font, gold active tab underline, ghost strip backdrop with pulse animation, potential table header gradient, blacklist panel border, button accent underlines, empty state search icon, and content accent line. All frames use BackdropTemplate correctly for 12.0.1. Y-offset math updated to accommodate the new 38px status panel.
  • Resolve all remaining Deep Audit #2 findings (M1-M3, L1-L4)
    Fix 7 remaining Deep Audit #2 findings:
    • M1: Move ghost auto-invite no-response timeout from queue time to execution time (Invite.lua)
    • M2: Add 0.2s elapsed throttle to Ghost Mode updater OnUpdate (GhostMode.lua)
    • M3: Rename shadowing local rad variables and use upvalued trig functions (Minimap.lua)
    • L1: Add explicit nil guard on GRIPDB_CHAR.counters before daily cap check (Whisper.lua)
    • L2: Use structured BuildGateCtx() for BL_ExecutionGate calls in Who.lua
    • L3: Optimize debug log ring buffer trim from O(n*over) to O(n) bulk copy (Core.lua)
    • L4: Document Phase 1 FlushOne/FlushAll as zero-caller public API (GhostMode.lua)
  • Add combat guard to SetPropagateKeyboardInput in UI OnShow
    Wraps the SetPropagateKeyboardInput(false) call in UI.lua's OnShow handler
    with an InCombatLockdown() check to prevent taint errors when the UI frame
    is shown during combat. Skipped calls are logged via GRIP:Debug. This fixes
    beta blocker M4 and matches the defensive pattern already used in GhostMode.lua.
  • Summary Add right-click context menu to addon compartment button
    Description
    Add a right-click dropdown menu to the addon compartment button using MenuUtil.CreateContextMenu (same pattern as UI_Home_Menu.lua). The menu provides quick access to Toggle UI, Status, Build Scan Queue, Ghost Mode session toggle, and Whisper queue toggle. The compartment click handler now supports both string and table button arguments for WoW version compatibility. Tooltip updated to reflect the new right-click action.
  • Fix UI QA findings: stale row pool, HookScript, UISpecialFrames guard, TOC placeholders
    Release row pool entries when scroll area collapses to zero height in both
    Potential and Blacklist panels, preventing stale hidden rows. Switch
    HookDirtyTracking from SetScript to HookScript so template-based edit box
    handlers aren't overwritten. Add duplicate guard for UISpecialFrames
    insertion. Add X-Curse/Wago/WoWI placeholder IDs to the TOC.
  • Update .gitignore
  • Fix UI QA pre-beta P0/P1/P2 issues across 4 files
    Fix P0 nil arithmetic crash on state.whoIndex in Home status bar. Fix P1 BuildInsertedTextAtCursor to return cursor byte position so token insertion places the cursor correctly. Add minimap button click-without-drag guard (_gripDragged flag) matching UI.lua's resize grip pattern. Wrap Ghost overlay gamepad API calls in pcall for clients without gamepad support. Clear _gripDirty on edit box focus lost so auto-refresh resumes after user clicks away.
  • Localize remaining bare globals in 3 UI files
    Add upvalue locals for string.upper, string.sub, table.sort, and
    InCombatLockdown in UI_Home.lua, UI_Home_Blacklist.lua, and
    UI_Home_Menu.lua. Updates call sites to use the localized aliases,
    matching the convention used across all other GRIP source files.
  • Summary Fix resize handle click-without-drag bug and add screen bounds clamping
    Description
    Fix resize grip clicking without dragging causing unintended frame resize by adding a 3-pixel drag threshold — clicks that don't exceed this revert to the original size. Add screen bounds clamping (20px margin) to SetResizeBounds, RestoreFrameGeometry, and SaveFrameGeometry so the frame can never exceed screen dimensions or get saved off-screen. Add /grip reset command to restore UI position and size to defaults as a recovery mechanism.
  • Add Home page button icons, Ghost strip tint, and class-color row borders
    Phase 3 visual polish for the Home page. Adds 14×14 icons (magnifying glass, chat bubble, broadcast) to the left of Scan, Whisper+Invite, and Post Next button labels. The Ghost Mode status strip now shows a subtle green background during active sessions and orange during cooldown. Each Potential list row gets a 2px class-colored left border bar that's always visible, with header and row columns shifted 4px right to accommodate.
  • Add Home page visual polish — tooltips, class colors, status bar, separators
    Phase 1+2 visual polish for the Home page. Adds rich tooltips on all buttons, column headers (W/I status legends), and potential/blacklist rows. Candidate names are now class-colored. Status bar uses color-coded ON/OFF indicators and pipe separators. Row stripes are more visible. Phase 2 adds section separators, contextual hint lines that adapt to current state, a red-tinted Clear button with confirmation popup (>10 candidates), and a restructured blacklist header with separated title and column labels.
  • Filter out instance/PvP/dungeon zones from dynamic zone discovery
    Replace allDescendants=true with a controlled two-level C_Map walk in
    GatherZonesGroupedByContinent(). Direct Zone children of each continent
    (outdoor zones) plus their direct Zone sub-children (e.g. "Azj-Kahet -
    Lower") are included; zones nested inside Dungeon-type parents are
    structurally excluded. BuildExcludedZoneNames is now force-called at the
    top so EJ/BG exclude names are ready before filtering.
  • Add dynamic zone discovery grouped by continent via C_Map
    Settings zone filter checklist now dynamically enumerates zones at runtime via C_Map.GetMapChildrenInfo(), grouped by continent with expansion-friendly display names (newest first). This auto-discovers all zones including new Midnight areas without manual list maintenance. Static ZONES_BY_EXPANSION kept as fallback if C_Map fails. Cache clears on /grip zones reseed.
  • Split UI_Home.lua into focused modules for popups, blacklist panel, and context menu
    Split UI_Home.lua (1518 lines) into 4 files:
    • UI_Home_Popups.lua: StaticPopup dialogs for blacklist add/remove
    • UI_Home_Blacklist.lua: Permanent blacklist panel (shell, rows, layout, update)
    • UI_Home_Menu.lua: Right-click context menu (MenuUtil)
    • UI_Home.lua: Potential panel, buttons, Ghost strip, layout orchestration
      Slash.lua (877 lines) stays as-is — it's a flat dispatcher with no natural split points.
      Shared helpers promoted to GRIP table methods for cross-file access.
  • Replace UIDropDownMenu with MenuUtil.CreateContextMenu in Home page
    Replace the legacy UIDropDownMenu context menu on potential-list rows with
    the modern MenuUtil.CreateContextMenu API. Removes EnsureRowMenu helper,
    the "GRIP_PotentialRowMenu" global frame, and all UIDropDownMenu_* calls.
    Same Blacklist and Invite to Guild actions with identical guard logic.
  • UTF-8-safe budget trim, slower UI ticker, Ghost queue dedup
    Three P3 cleanup fixes in one pass:
    • Replace byte-at-a-time budget trim loops in UI_Settings.lua and UI_Ads.lua with a shared GRIP:TrimToBudget() helper (Utils.lua) that respects UTF-8 character boundaries via binary search, preventing split multi-byte characters in accented/non-Latin guild names.
    • Reduce UI refresh ticker from 0.2s to 0.5s while the GRIP window is open (UpdateUI coalescing already prevents redundant work).
    • Add actionType+target dedup safety net in Ghost Mode's QueueAction to prevent double-queued whispers/invites from race conditions.
  • Replace fragile self parameter with explicit GRIP in Whisper.lua and Invite.lua local helpers
    Remove the self parameter from local helper functions in Whisper.lua
    (IsWhisperBlocked, WhisperBlacklistGate, PurgeBlacklistedFromPendingAndQueue)
    and Invite.lua (IsInviteBlocked, InviteBlacklistGate, PurgeBlacklistedPending).
    All internal self:Method() calls replaced with GRIP:Method() directly, and all
    17 call sites updated to drop the now-unnecessary first argument. Completes the
    pattern established in Post.lua (P2 #8) across all three Recruit/ pipeline files.
  • Replace fragile self parameter with explicit GRIP in Post.lua local functions
    Remove the self parameter from three local helper functions in Post.lua
    (IsPostBlocked, PostBlacklistGate, PurgeBlacklistedFromPostQueue) and
    replace all internal self:Method() calls with GRIP:Method() directly.
    Updated all 7 call sites to drop the now-unnecessary first argument.
    Colon-syntax methods that call these helpers are unchanged.
  • Centralize duplicated utility functions into shared Utils.lua and UI_Widgets.lua
    Move three duplicated utility patterns into Core/Utils.lua: BuildGateCtx (gate
    context builder, was copied in Post/Whisper/Invite), CfgBool and CfgNum (typed
    config accessors, were in GhostMode). All consumer files now use one-liner local
    wrappers that delegate to the centralized versions, preserving identical call
    signatures at all existing call sites.
  • Fix remaining Post.lua PurgeBlacklistedFromPostQueue(self) → GRIP in PostNext
    Change the two remaining PurgeBlacklistedFromPostQueue(self) calls in
    GRIP:PostNext() to pass GRIP explicitly, completing the self→GRIP safety
    fix across all four call sites in Post.lua.
  • Fix Debug ring buffer O(n²) trim and Post.lua self→GRIP safety
    Replace O(n²) table.remove loop in Debug.lua PersistAppend with a single-pass
    batch shift for ring buffer trimming. Fix two PurgeBlacklistedFromPostQueue
    calls in Post.lua that passed self instead of GRIP explicitly, making them
    safe regardless of call syntax.
  • Centralize duplicated utility functions into shared Utils.lua and UI_Widgets.lua
    Promotes ~180 lines of duplicated local helper functions from 10 consumer files into two shared locations: Core/Utils.lua (8 pipeline/data helpers on GRIP table) and UI/UI_Widgets.lua (6 UI widget helpers on GRIP.UIW table). All consumer call sites updated to use the shared versions. Eliminates maintenance drift risk from having identical logic copied across GhostMode, Slash, Events, Who, Whisper, Invite, Post, UI_Home, UI_Settings, and UI_Ads.
  • Fix zone data duplicates and make Current button use player's actual zone
    Removes duplicate "Zuldazar" from the Midnight expansion group (it's a BfA zone only) and removes "Wintergrasp" from the WotLK zone list since it's already correctly excluded as a PvP zone in STATIC_ZONE_EXCLUDE_EXACT. Fixes the Settings page "Current" zone button to use GetRealZoneText() for the player's actual zone instead of incorrectly selecting the first expansion group. The button now additively adds the current zone to the filter, allowing multi-zone selection across sessions.
  • Pre-Beta-Realese-Update
  • Fix stale comment in Utils.lua referencing GRIPDB instead of GRIPDB_CHAR
    The whisper echo suppression comment on line 29 of Core/Utils.lua still
    referenced GRIPDB.config, but config moved to GRIPDB_CHAR in the
    SavedVariables split. Updated the comment to match. No logic changes.
  • Fix stale comment in Core.lua referencing GRIPDB instead of GRIPDB_CHAR
    The Ghost Mode session comment on line 114 of Core/Core.lua still referenced
    GRIPDB.config for ghostCooldownUntil, but config moved to GRIPDB_CHAR in the
    SavedVariables split. Updated the comment to match. No logic changes.
  • Split GRIPDB into account-wide and per-character SavedVariables
    Split the single GRIPDB SavedVariable into GRIPDB (account-wide) and GRIPDB_CHAR (per-character). Blacklists and no-response counters are now shared across all characters on the account, preventing alts from re-contacting blacklisted players. Per-character data (config, potential list, filters, zone lists, minimap position, debug log) remains isolated per character. Includes idempotent migration that moves existing data from the old single-table schema to the new split schema on first load.
  • Add ChatThrottleLib detect-and-use integration for WHISPER sends
    Integrate ChatThrottleLib (CTL) for WHISPER sends only in SendChatMessageCompat.
    When CTL is present (via Ace3 or standalone), whispers route through CTL at
    NORMAL priority for CPS bandwidth fairness with other addons. CHANNEL sends
    are never routed through CTL (hardware-event restriction). Without CTL loaded,
    behavior is unchanged. Gate trace logs "SEND ROUTE: ChatThrottleLib" at
    verbosity 3.
  • Add upvalue localizations for WoW API and Lua stdlib across all modules
    Add file-top upvalue blocks to all 22 .lua source files, caching frequently-used
    Lua standard library functions and WoW API references as locals. Each block is
    grouped by category (-- Lua / -- WoW API) and placed immediately after the addon
    table line. Data/Maps_Zones.lua excluded (pure data). No GRIPDB or CreateFrame
    localized per design rules. Reduces _G hash lookups on hot paths (Ghost Mode
    drain loop, whisper ticker, /who processing, UI refresh).
  • Add Ghost Mode UI surface (Settings checkbox + Home page session status)
    Add the missing UI controls for Ghost Mode, which was previously slash-command only.
    Settings page gets a new section with master enable checkbox, session max (5-120 min) and cooldown (1-60 min) edit boxes, and an Apply button. Home page gets a live status strip showing session state (Active with timer/queue/actions, Cooldown with remaining time, or Ready), plus a Start/Stop toggle button. Sub-controls grey out when Ghost Mode is disabled. Strip hides entirely for non-Ghost users.
  • Summary Add post auto-queue through Ghost overlay and campaign tracking (Phase 2e)
    Description
    When a Ghost Mode session is active, the post scheduler ticker now automatically drains queued Trade/General posts through the Ghost overlay frame instead of waiting for manual /grip post. AutoDrainPostQueueGhost loops all queue items, resolves channels, checks blacklist gates, and queues valid posts for hardware-event execution. Channel resolution failures leave items in queue for retry. Also adds RecordCampaignAction("post") to all three post send paths (ghost-auto, ghost via PostNext, direct send) for campaign cooldown consistency.
  • Summary Add auto-queue guild invite after whisper success in Ghost Mode (Phase 2d)
    Description
    When a Ghost Mode session is active and a whisper succeeds (OnWhisperInform), the candidate's guild invite is now automatically queued through the Ghost overlay frame instead of waiting for manual /grip invite. The new AutoQueueGhostInvite function validates all preconditions (blacklist gate, guild permissions, entry state), queues the invite action with a re-check at execution time, and starts the standard 70-second no-response timeout with escalation. This closes the whisper→invite gap during automated ghost sessions.
  • Summary Add whisper auto-start on Ghost Mode session (Phase 2c)
    Description
    Ghost Mode sessions now auto-start the whisper ticker, completing the scan→whisper
    pipeline. WhisperTick becomes self-sustaining during ghost sessions: when its queue
    empties, it rebuilds from Potential to pick up candidates added by ongoing /who scans.
    The ticker starts directly (bypassing StartWhispers toggle behavior) and stops cleanly
    when the ghost session ends.
  • Summary Add /who scan integration to Ghost Mode (Phase 2b)
    Description
    Ghost Mode sessions now automatically queue /who scans through the invisible overlay
    frame instead of requiring manual keybind presses. SendNextWho() detects active ghost
    sessions and routes through QueueAction("scan"), with auto-chain after processing
    results, auto-rebuild of exhausted queues on session start, and proper cleanup of
    ghost-queued state on session stop. Normal (non-ghost) scan path is unchanged.
  • Rewrite Ghost Mode with overlay frame, universal queue, and session management
    Replaces Phase 1 CHANNEL-only Ghost Mode with full architecture: invisible overlay frame captures any hardware input (mouse, keyboard, gamepad) to drain a universal action queue one item per event. Adds session management (1-hour max with auto-timeout, 10-minute persistent cooldown across reload/relog), combat safety (overlay hidden during InCombatLockdown), and /grip ghost slash commands. Invite.lua and Post.lua now queue their hardware-event-restricted calls through Ghost Mode when a session is active, while preserving backward compatibility with Phase 1 API.
  • Add campaign cooldown reminder for session fatigue protection
    Implement a campaign cooldown that warns after extended continuous recruiting (default 30 min) and optionally auto-pauses the whisper queue at 2x the threshold. Uses a sliding activity window with configurable gap reset. Controlled via /grip set cooldown <min>|on|off and visible in /grip status output.
  • Add frame pooling for dynamic potential/blacklist row counts on resize
    Replace hardcoded 30 potential rows and 12 blacklist rows with CreateFramePool-based dynamic sizing. Row count now adapts to the visible scroll area on every resize — large monitors get more rows (no blank space), small frames get fewer rows (no wasted frames). Pools lazily create Button frames on first Acquire and reuse them on subsequent resize cycles.
  • Replace single 8s guild link retry with 15s×8 ticker + bulk request API
    CLUB_FINDER_RECRUITMENT_POST_RETURNED takes ~95 seconds to fire after
    RequestPostingInformationFromClubId — the single 8-second retry missed this
    window entirely. Replaced with a C_Timer.NewTicker(15s, max 8 ticks = 2 min)
    that re-requests posting data each tick and adds RequestSubscribedClubPostingIDs
    (Blizzard's own CommunitiesFrame startup path). Ticker cancels on success or
    when CLUB_FINDER_RECRUITMENT_POST_RETURNED fires. Also removed the one-shot
    _gripGuildLinkRequested flag — no longer needed since the ticker manages retry.
  • Fix resolution with multi-path ClubFinder API chain + byte budget
    Fix never resolving to a clickable link. Root cause:
    ClubFinderGetCurrentClubListingInfo requires the Communities frame to
    have been opened, even after LoadAddOn. Added C_ClubFinder.GetRecruitingClubInfoFromClubID
    as primary path, SV cache for /reload survival, async RequestPostingInformation
    warm-up, and delayed retry. Also fixes byte budget counter to reserve 120 bytes
    for worst-case link length instead of using the short guild name fallback.
  • Beta-ready docs & defaults — README rewrite, CHANGELOG stamp, safer config values
    Rewrites README.md with full feature coverage (daily cap, opt-out detection, templates, sound feedback), updated API names, complete slash command reference, and user-facing disclaimers about Silence penalties and Guild Finder requirements. Stamps CHANGELOG as 0.5.0-beta, removes phantom doc references, and updates the planned/future section. Makes defaults more conservative for beta safety (whisperDelay 3.0s, blacklistDays 14, postInterval 20min). Adds /grip set ghostmode on|off slash command and bumps TOC version to 0.5.0-beta.
  • Add Addon Compartment support — GRIP appears in minimap addon bag dropdown
    GRIP now registers with WoW's Addon Compartment (10.1.0+), the dropdown from the minimap "addon bag" icon. Clicking the compartment entry toggles the GRIP window, same as the minimap button left-click. Hover tooltip shows basic usage hints. Existing minimap button behavior is unchanged — the compartment is a secondary access point.
  • Add /grip link command + EnsureDB whisperMessages cap
    Adds /grip link slash command that prints the current guild name and Guild Finder link resolution for troubleshooting template tokens. EnsureDB now enforces a 10-template cap on whisperMessages to protect against manual WTF/SavedVariables edits. Help text updated with guild finder listing note.
  • Sound Feedback — optional audio cues for recruitment events
    COWORK VERIFICATION — Task 4 (R20: Sound Feedback)
    Files modified (7):
    • DB/DB_Init.lua — 5 sound boolean config keys in DEFAULT_DB + type-checks in EnsureDB
    • Core/Utils.lua — GRIP:PlayAlertSound(soundKitID) helper
    • Recruit/Whisper.lua — StopWhispers sound + cap warning sounds (80% + hard cap)
    • Recruit/Invite.lua — OnInviteSystemSuccess sound
    • Recruit/Who.lua — ProcessWhoResults sound (when added > 0)
    • UI/UI_Settings.lua — Sound Feedback section with master + 4 sub-checkboxes
    • Core/Slash.lua — /grip set sound on|off + help + status
      Verify:
    1. All SOUNDKIT references use fallback IDs (SOUNDKIT and SOUNDKIT.X or ID)
    2. PlayAlertSound checks soundEnabled master toggle
    3. soundScanComplete defaults to false (truthy check in Who.lua)
    4. Other sound keys default to true (~= false check pattern)
    5. Sub-checkboxes grey out when master is disabled
    6. No stale references to sound config outside these 7 files
    7. UpdateScrollContentHeight considers all 6 sound elements (hdr + 5 checkboxes)
  • Whisper Template Variety — multi-template rotation (R19)
    Support multiple whisper message templates with automatic rotation.
    Templates can be managed via the Settings page multi-template editor
    (Prev/Next navigation, Add/Remove, Save All) or via /grip templates
    slash commands. Sequential (round-robin) and random rotation modes
    reduce spam filter detection and improve player perception.
  • Add opt-out response detection — auto-blacklist candidates who reply with refusal phrases
    Detects incoming whisper replies from GRIP candidates containing opt-out phrases ("no thanks", "not interested", "stop", "already in a guild", etc.) and immediately permanent-blacklists them with reason "opt-out". Only acts on whispers from players in the Potential list or pending maps — random whispers are ignored. Enabled by default; toggle with /grip set optout on|off.
  • Add daily whisper cap — configurable limit with auto-reset, UI display, slash command
    Adds a configurable daily whisper cap (default 500, 0 = unlimited) to prevent Silence penalties from excessive automated whispers. Counter persists in SavedVariables, auto-resets at calendar day change, and prints a soft warning at ~80% usage. Only GRIP's automated whispers count — manual player whispers are not tracked. Configure via /grip set dailycap <N>, view via /grip status or the Home page status bar.
  • UI/UI_Ads.lua — Complete rewrite addressing all 5 reported issues:
    Byte budget enforcement — Each editor now has a remaining-bytes counter (bottom-right, matching Settings style), live enforcement on typing/paste that trims excess characters, and clamping so the counter never goes negative. Save is disabled when either editor is over budget.
    Missing token buttons — Each editor now has its own "Insert " and "Insert " buttons that insert at cursor position (not append), with all-or-nothing budget checking (won't insert a partial token if it would exceed 255 bytes).
    Preview buttons — Each editor now has its own "Preview" button that expands the template and prints the result, matching the whisper editor's behavior.
    Button label consistency — Changed from "Append " to "Insert " to match Settings. All buttons now use "Insert" consistently.
    Button ownership clarity — Buttons are now per-editor (each editor has its own Insert/Preview row directly below it). Save remains shared at the bottom since it saves both editors, alongside Queue Now and Post Next.
    Bonus: Token ordering bug fix (3 files):
    UI/UI_Ads.lua, UI/UI_Settings.lua, Core/Utils.lua — Fixed gsub running before , which mangled the longer token. Now is always replaced first. This was a latent bug that would have caused incorrect template expansion whenever both tokens were used in the same message.
  • Zone Overhaul — Part 2
    UI/UI_Widgets.lua — Added CreateGroupedChecklist
    New widget function appended after the existing CreateChecklist (which remains untouched for Races/Classes)
    Renders expansion groups with gold header text (GameFontNormal, color 1/0.82/0/1)
    Per-group All / None buttons inside the scrollable area (scroll with content)
    Zone checkboxes indented 12px under group headers, separated by 6px gap between groups
    Handles dynamic label width recalculation on resize via OnSizeChanged hook
    Reuses existing W.EnsureCheckLabel and W.SetCheckLabelText helpers
    UI/UI_Settings.lua — Four changes
    Change A: Zone list now uses CreateGroupedChecklist at 250×200 (was CreateChecklist at 250×140)
    Change B: Top-level All button rewired to use GetZonesGroupedForUI() for sync with displayed groups. Added Current button that selects only the first expansion group (Midnight). Layout: [Current] [All] [None]
    Change C: Render() call passes GRIP:GetZonesGroupedForUI() instead of the flat GRIPDB.lists.zones
    Change D: zoneCurrent button included in both the disable and enable blocks for DB-not-ready state
  • Zone system overhaul — Part 1
    Maps_Zones.lua — Full rewrite
    Replaced flat STATIC_ZONES (208 entries with tons of junk) with ZONES_BY_EXPANSION — 13 expansion groups (Midnight → Starting Zones) containing only zones where players realistically appear in /who
    Flattened STATIC_ZONES is built automatically from ZONES_BY_EXPANSION for backward compat
    Added SEASONAL_ZONES table with Darkmoon Island
    Strengthened STATIC_ZONE_EXCLUDE_PATTERNS — added "BfA -", "Cataclysm -", "N'zoth Assault", changed " - Disabled" to just "Disabled"
    Expanded STATIC_ZONE_EXCLUDE_EXACT — continents, 15 arenas, scenarios, world PvP, removed zones, micro sub-zones, seasonal entries
    DB_Zones.lua — Seasonal detection + dungeon filtering
    Added IsDarkmoonFaireActive() — calendar API primary, date-math fallback
    Added IsSeasonalZoneActive(), GetActiveSeasonalZones(), RefreshSeasonalFromCalendar()
    Added isDungeonMap() helper — checks Enum.UIMapType.Dungeon (value 4)
    Updated all zone iteration loops in GatherAllZoneNames to skip dungeon maps
    Updated GetBestZonesListForUI() to append active seasonal zones
    Added GetZonesGroupedForUI() for the Part 2 UI grouped checklist
    DB_Init.lua — SeedZones fix
    Removed if #list > 10 then return end guard
    Always wipes and rebuilds from best source (same pattern as races fix)
    Appends active seasonal zones via GetActiveSeasonalZones()
    Events.lua — Calendar event registration
    Registered CALENDAR_UPDATE_EVENT_LIST event
    Added handler that calls GRIP:RefreshSeasonalFromCalendar()
    Added C_Calendar.OpenCalendar() call in PLAYER_LOGIN to request calendar data
  • Fix ESC opening Game Menu while GRIP UI is open
    Remove SetPropagateKeyboardInput(true) from OnHide handler. Hide() fires
    OnHide synchronously during OnKeyDown, which restored propagation before the handler
    returned — causing ESC to leak to the keybind system and open the Game Menu.
  • Fixes
    Fix 1 — DB_Init.lua: Added wipe(list) before the PLAYABLE_RACE_IDS loop so stale entries (Windborne Velocidrake, etc.) are cleared on every login. Orphaned filter selections are already handled by PruneFilterKeys in EnsureDB.
    Fix 2 — Events.lua: Changed GUILD_ROSTER_UPDATE logging from level 2 (Debug) to level 3 (Trace), so the ~30s periodic cache-warm messages no longer flood the debug window at default verbosity.
    Fix 3 — UI.lua: Added PRINTSCREEN early-return with SetPropagateKeyboardInput(true) in all three OnKeyDown handlers (main frame, editbox HookScript, editbox SetScript). PrintScreen now passes through to WoW/OS while ESC and WASD behavior is unchanged.
  • Update DB_Init.lua
  • Filter SeedRaces to playable races only
    The ID 1–100 iteration included NPC-only races (Ice Troll, Tuskarr, etc.).
    Replaced with an explicit PLAYABLE_RACE_IDS table covering Classic, Allied
    Races, Dracthyr, Vulpera, Mechagnome, Earthen, and Haranir. Duplicate names
    (Pandaren×3, Dracthyr×2, Earthen×2) are already handled by SortUnique.
    Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com
  • Fix empty Races checklist — replace character-creation-only API in SeedRaces
    SeedRaces() relied on C_CharacterCreation.GetAvailableRaces() which returns
    an empty table during normal gameplay (only works on the character creation
    screen). Replaced with direct iteration over C_CreatureInfo.GetRaceInfo(1..100),
    which works in-game and covers all player races including Allied Races,
    Dracthyr, Earthen, Haranir.
    Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com
  • Title: Repo tidy: .gitignore, README, Claude/ cleanup
    Description:
    • .gitignore now excludes Claude/, CLAUDE.md, Docs/, and sync files from GitHub
    • Removed tracked Claude/ and Docs/ files from git index (files remain on disk)
    • Replaced placeholder README.md with full user-facing documentation
    • Deleted 3 completed PROMPT files from Claude/
    • Added superseded warning to Research_02
    • Updated Research_Index with status tracking and completed work log
  • Create PROMPT_Tidy_GitIgnore_README.md
  • Fix and template replacement (Lua gsub count leak)
    string.gsub() returns two values: the modified string and the replacement count. When used inline as the last argument to an outer gsub(), Lua expands both return values — the count becomes the n (max replacements) parameter, resulting in zero replacements. Wrapping the inner gsub() call in parentheses discards the count, passing only the escaped string.
    Line 221: self:GetGuildName():gsub("%%", "%%%%") returned ("Temptation", 0) — the 0 became n=0 for the outer gsub, replacing nothing. Fixed by wrapping in (...).
    Line 244: Same issue with link:gsub("%%", "%%%%") for the token.
    / were unaffected because they stored the gsub result in a local variable first, which naturally discards the second return value.
    Files Modified
    Core/Utils.lua — 2 lines changed (lines 221 and 244)
    Verification
    resolves to guild name in Preview
    resolves to link or fallback in Preview
    / still work (unchanged code path)
    No Lua errors (parentheses are valid Lua syntax for discarding extra return values)
  • Update CLAUDE.md
  • GuildLink Fix

    Changes Applied (4 files)

  • Core/Utils.lua
    • GetGuildName(): Removed dead C_GuildInfo.GetGuildInfo code path (doesn't exist). Reordered logic: return cache first (survives early-login nil from GetGuildInfo("player")), then try GetGuildInfo("player") as the sole source.
    • GetGuildFinderLink(): Added 5-minute cache (state._gripGuildLinkCache + state._gripGuildLinkCacheAt). Reduced trace spam to one-per-session via state._gripGuildLinkTraced flag (resets on successful resolution). Removed all the per-step Trace calls that fired on every template render.
  • Core/Events.lua
    • Registered 3 new events: PLAYER_GUILD_UPDATE, GUILD_ROSTER_UPDATE, INITIAL_CLUBS_LOADED
    • Added handlers that call GetGuildName() / GetGuildFinderLink() to warm caches as soon as guild data becomes available during the login sequence.
  • Core/Debug.lua
    • Added GRIP:ShowDebugCopyFrame(n) — creates a movable DIALOG-strata frame with a scrollable multi-line EditBox, pre-populated with the last n persisted debug lines, fully selected for Ctrl+C. Frame is reused across invocations.
  • Core/Slash.lua
    • Added /grip debug copy [n] subcommand
    • Updated help text and usage string to include copy [n]
  • Research Update
    More info!
  • Codebase Cleanup — Complete.
    Summary of all changes:
    Task 1: Header blocks — All 24 .lua files now have standardized 2-line headers (-- GRIP: <Module> / -- <description>). Revision changelogs removed from every file.
    Task 2: TOC file — Removed ## Rev 10 metadata and ## CHANGED block. Simplified 3-line ======== section separators to single # -- Section comment lines. Load order unchanged.
    Task 3: Bindings.xml — Removed comment and multi-line NOTE block. All 4 binding elements preserved.
    Task 4: Dead code removed:
    GRIP.STATIC_ZONES_BY_GROUP scaffolding (Maps_Zones.lua) — unused, no references
    U.AnySelected() (DB_Util.lua) — dead export; DB_Filters.lua has its own local copy
    Task 5: Inline Rev N comments tidied (7 occurrences):
    Core/GhostMode.lua — full header was still old (missed earlier); now cleaned
    Recruit/Who.lua — (Rev 6/8) removed from section divider
    Recruit/Invite.lua — 3 inline Rev 10:/Rev 11: prefixes stripped
    UI/UI_Home.lua — (Rev 18 hardened) removed
    DB/DB_Init.lua — (Rev 3) removed from comment
    Zero functional code changes. All files parse as valid Lua/XML.
  • Research Update
    Cleanup
  • GRIP 12.0.1 API Migration — Changes Applied
    Files Modified
    Core/Core.lua — Updated comment (GuildInvite → C_GuildInfo.Invite)
    Core/Utils.lua — Added GRIP:SafeGuildInvite() compat wrapper
    Recruit/Invite.lua — Replaced GuildInvite(name) callsite
    Hooks/UnitPopupInvite.lua — Replaced GuildInvite(targetName) callsite + updated availability check
    Recruit/Who.lua — Fixed scanMaxLevel fallback + added origin param to SendWho()
    Data/Maps_Zones.lua — Added Harandar zone
    Change Details
    1. GuildInvite → C_GuildInfo.Invite compat wrapper
      Added: GRIP:SafeGuildInvite(name) in Core/Utils.lua (line 406–417) — tries C_GuildInfo.Invite() first, falls back to GuildInvite(), prints error if neither exists
      Replaced: GuildInvite(name) → self:SafeGuildInvite(name) in Recruit/Invite.lua (line 340)
      Replaced: GuildInvite(targetName) → GRIP:SafeGuildInvite(targetName) in Hooks/UnitPopupInvite.lua (line 193)
      Updated check: not GuildInvite → not (C_GuildInfo and C_GuildInfo.Invite) and not GuildInvite in UnitPopupInvite.lua (line 127)
      Updated comment: Core/Core.lua line 7 — -- - GuildInvite() → -- - C_GuildInfo.Invite() (compat: GuildInvite deprecated 10.2.6)
    2. scanMaxLevel fallback
      File: Recruit/Who.lua line 352
      Change: cfg.scanMaxLevel or 80 → cfg.scanMaxLevel or 90
    3. Midnight zones
      File: Data/Maps_Zones.lua line 210–211
      Added: "Harandar" (new Midnight zone)
      Skipped: Eversong Woods, Ghostlands, Zul'Aman — already present in the list
    4. SendWho origin parameter
      File: Recruit/Who.lua line 434
      Change: C_FriendList.SendWho(filter) → C_FriendList.SendWho(filter, Enum.SocialWhoOrigin and Enum.SocialWhoOrigin.Social or 1)
      Nil-guarded for older clients that don't have Enum.SocialWhoOrigin
  • Research
  • Bug Fix Report — GRIP Addon
    Critical Fixes (4)
    1. CHAT_MSG_WHISPER_INFORM wrong arg position (Core/Events.lua:232)
      Target name was extracted from arg2 (always empty string) instead of arg5. This completely broke whisper confirmation tracking — pending whispers never cleared, whisperSuccess never set, downstream invite-after-whisper logic never triggered.
      Changed: local msg, target = ... → local msg, _, _, _, target = ...
    2. Ghost Mode parameter order mismatch (Core/Utils.lua:394)
      SendChatMessageCompat called gm:Send(msg, chatType, ...) but Ghost:Send() expects (chatType, msg, ...). When Ghost Mode is enabled, CHANNEL sends would have message body and chat type swapped. Dormant since Ghost Mode defaults to off.
      Changed: gm:Send(msg, chatType, ...) → gm:Send(chatType, msg, ...)
    3. SeedRaces passes struct instead of numeric raceID (DB/DB_Init.lua:162)
      C_CharacterCreation.GetAvailableRaces() returns structs with a .raceID field, not raw IDs. The code passed the whole struct to GetRaceInfo(), which returned nil. Race filter checklist never populated.
      Changed: extracts raceData.raceID from the struct, falls back to raw value for forward compat.
    4. EnsureRowMenu returns nil on 2nd+ call (UI/UI_Home.lua:565)
      Early return if home._potMenu then return end returned nil instead of the cached menu. ShowRowMenu received nil and silently exited. Right-click context menu (Blacklist/Invite to Guild) only worked once.
      Changed: return end → return home._potMenu end
      Medium Fixes (6)
    5. Table mutation during pairs() iteration (9 locations across 6 files)
      Setting keys to nil inside pairs() is undefined behavior in Lua — may skip entries. Replaced all instances with collect-then-remove pattern.
      Files: DB/DB_Blacklist.lua (4 loops), DB/DB_Init.lua (2 loops), DB/DB_Util.lua (1), Recruit/Who.lua (1), Recruit/Invite.lua (2), Recruit/Whisper.lua (1).
    6. UISpecialFrames hash vs array (UI/UI.lua:366)
      UISpecialFrames[name] = true used hash syntax; Blizzard iterates with ipairs. ESC-close fallback was dead code.
      Changed: tinsert(UISpecialFrames, name)
    7. ADDON_LOADED never unregistered (Core/Events.lua, Hooks/UnitPopupInvite.lua)
      After GRIP processed its own load, the handler kept firing for every other addon. Added UnregisterEvent("ADDON_LOADED") after processing in both files. Also unregisters PLAYER_LOGIN in the hook file after it fires.
    8. ApplyTemplate gsub replacement not %-escaped (Core/Utils.lua:259-284)
      gsub replacement strings treat % as special. Player names, guild names, and guild finder links were used as raw replacements. WoW hyperlinks can contain %. All replacement values now escape % via :gsub("%%", "%%%%").
    9. /grip set hidewhispers doesn't sync config alias (Core/Slash.lua:588)
      Set suppressWhisperEcho but not hideOutgoingWhispers. Alias pair drifted until next login. Now sets both.
      Low Fix (1)
    10. Whisper tick missing UpdateUI + dead code (Recruit/Whisper.lua:226-227, Recruit/Who.lua:241)
      When whisper tick consumed a queue entry but found no matching entry or already-attempted, it returned without UpdateUI(). Added self:UpdateUI() to both early-return paths. Also removed dead #pot > 0 check on hash table in Who.lua (always evaluated to false).
  • Create .gitignore
  • Add GRIP addon files
    Initial upload of all GRIP World of Warcraft addon files
  • Initial commit