promotional bannermobile promotional banner

GreenWall - Revived

Bridged Guild Chat for WoW Confederations
Back to Files

GreenWall-v1.15.3

File nameGreenWall-GreenWall-v1.15.3.zip
Uploader
PmptastyPmptasty
Uploaded
Oct 17, 2026
Downloads
572
Size
138.4 KB
Flavors
RetailMoP ClassicClassic TBCClassic
File ID
9233378
Type
R
Release
Supported game versions
  • 11.2.7
  • 5.5.4
  • 4.4.2
  • 3.4.5
  • 2.5.6
  • 1.15.9

What's new

Change Log

This project uses Semantic Versioning.

[v1.15.3] (2026-10-17) - Relay mode: one bridge per guild, released version checks, GreenWallAPI handler order

Bug Fixes

  • Logging in made you a second bridge while your guild already had one. The operator, 2026-10-17: "one person was on and was the bridge, i logged on and then was also the bridge". At login the bridge election ran at once, before the sitting bridge's answer to the login could arrive, and a newcomer that sorted first among the guildmates it knew claimed the bridge. The guild then had two bridges until the next co-guild line settled them, and in that window their reposts were forwarded and reposted again (the echo below). Now nobody claims at login. A newcomer waits for the sitting bridge's answer. The election runs only when something needs a bridge and nothing has answered: a co-guild line to repost, a forward from another guild, or a guildmate's line to forward. The line that ended the wait is still carried by whoever wins. There is no timer, per the no-timers rule. A bridge that logs in again, after a /reload for example, comes back knowing nothing, so the next guildmate in line whispers it a new yours message ("you are still our bridge") and it resumes its term without a new claim. The cost: with nobody online who already knew the bridge, a guild has no bridge until its first line that needs one, which that line then elects. A 1.15.2 or older newcomer still claims at login until it updates. Pinned by bridge_spec "claims nothing at login, and the first co-guild line elects the first candidate", "never makes a newcomer a second bridge while one sits", "still claims when the guild has no bridge and a forward arrives", "still claims when a guildmate without GreenWall speaks and nobody is the bridge" and "gives a bridge that logged in again its term back, from the next in line", and bridge_e2e_spec "elects nobody at login, and the first line that needs a bridge elects one and is carried". Location: Bridge.lua:GwBridge:settle, GwBridge:evaluate, GwBridge:onHello, GwBridge:receive.
  • When nobody knew the bridge, a newcomer could still be elected over guildmates already online. Found reading the code after the fix above, not seen in game. The line that ends the wait reaches every GreenWall member at once, each runs the election, and each picked the lowest name it knew, so a newcomer whose name sorted first went to the front of the line. Each player's leaf now carries the server time it logged in (since), stamped once per session and kept across every later leaf, and candidates are ordered by time online, then by name, the preferred ones first as before. A newcomer is always last in line. A 1.15.2 or older leaf has no login time and sorts after every one that has. The limit: a member that has not yet received another's leaf still puts itself first, and two such claims are settled by the claim rule as before. Separately, a claim from a guildmate VersionCheck has seen on 1.15.2 or older, on our term or an earlier one, never replaces a bridge we hold, and our bridge whispers its own claim back only when ours beats theirs: two bridges that each believed the other outdated answered each other for ever, which bridge_e2e_spec found while this was being built (the previous session's account; not re-run since). A claim on a later term still goes through the normal rule: it is not a login claim (a fresh client claims term 1) but a takeover or hand-over by a client that knew our term, and the first version of this rule ignored it, leaving two bridges that nothing settled (peer review 8fe83074, G1). An old client's login claim already loses to a sitting bridge by the claim rule, so this guards the rarer case of one that would win on time alone. Pinned by bridge_spec "picks whoever has been online longest when several are waiting, whatever the names", "hands over to whoever has been online longest, not to a newcomer whose name sorts first", "never lets an outdated guildmate's claim displace the bridge, and the bridge corrects it once", "follows an outdated guildmate's claim on a later term: a takeover, not a login claim" and "follows an outdated guildmate's claim when it holds no bridge: that may be the sitting one", sync_spec "carries the time its player logged in, kept across every later leaf of the session" and "keeps a leaf without a login time, as 1.15.2 sends, and drops a login time that is not a number", and bridgewiring_spec "lists the online guildmates and the player, the preferred ones first, each by time online" and "calls a guildmate outdated only when VersionCheck saw it run a GreenWall that claims at login". Locations: Sync.lua:GwSync:stampLeaf, GwSync:sendLeaf, GwSync:receive, BridgeWiring.lua:gw.BridgeCandidates, gw.BridgeIsOutdated, Bridge.lua:GwBridge:receive.
  • The Guild Information minimum version (GW:v) never warned a released client. Found in the same self-audit as the next entry, for the same reason: the installed version is the TOC's, GreenWall-v1.15.2 once packaged, and semver() of it was nil, so the check was skipped. The test env had always reported a bare 1.13.4. It now reads the version through the same gw.VersionText. Pinned by config_spec "warns a released client, whose installed version carries the packager's tag name", which fails without it. Location: Config.lua:GwConfig:load, Utility.lua:gw.VersionText.
  • Relay mode could not read a released GreenWall's version, and treated every one as current. Found in this release's self-audit, read in the code, not seen in game. VersionCheck reports the TOC's ## Version as it is, and the packager fills that with the tag name: GreenWall-v1.15.2 (the packaged GuildRoster TOC on this machine reads GuildRoster-v1.3.0). Relay mode parsed it with SemanticVersion, which needs a leading digit, so every released version came back unparsed and was taken for a dev build. A 1.14 contact was therefore never skipped (the 1.15.1 fix for peer review afc1fd93, N3, never worked for a released client), a 1.14 guildmate counted as running relay mode, and the outdated-claim rule above would never have fired. Every spec had fed bare 1.15.0-style versions. The tag prefix is now cut before parsing and only the x.y.z core is kept (gw.VersionText, in Utility.lua; a suffix with a second hyphen, as an untagged build's may have, also parsed nil, peer review 8fe83074, L1, and a pre-release now compares as its release; it returns text, not a parsed version, because Lua 5.1 compares two versions only when one SemanticVersion instance made both), and the three checks share it. Pinned by bridgewiring_spec "reads a released client's version, which carries the packager's tag name", which fails without it, and utility_spec "gw.VersionText". Location: BridgeWiring.lua:parse, gw.BridgeIsUser, gw.BridgeIsOlder, gw.BridgeIsOutdated.
  • A guild's own line could still come back to it, reposted inside another guild's repost. In game, 2026-10-17: Testthese typed test and their own guild then saw [Steakys]: [Xpromoonbear-Myzrael]: [Testthese-Azuresong]: test. Xpromoonbear, the other guild's bridge, reposted the line correctly; then something in that guild forwarded the repost as a line Xpromoonbear had typed, and Steakys reposted it back into Testthese's guild. Only a bridge forwards, so the likeliest reading is a second member there who also believed it was the bridge (or ran an older GreenWall) and saw the repost before its own hidden-channel copy of Testthese's line arrived. No trace was captured to confirm which. My forwarding check refused a line naming another guild's player only once it had heard that player, while the guild chat filter in the same file already treats any [Name-Realm]: text from a guildmate naming a non-guildmate as a repost. The two now share one test (quotes(..., outsider)), so a bridge never forwards a line its own filter hides. And a bridge receiving a forward now drops it when the text quotes one of its own guildmates, so the loop is closed at the receiving end whatever version sent it. The cost is the same as the filter's: a member without GreenWall who types [Name-Realm]: text naming a player outside the guild is not forwarded; and another guild's member without GreenWall who quotes one of our guildmates as [Name-Realm]: text is not reposted to us. The limit: both checks need the name's realm, so an echo relayed by a 1.15.1 bridge, which writes a same-realm speaker as a bare [Name]:, still passes the receiving check (peer review 4f5a33b4, G1); matching bare names would also drop a stranger's quote of a same-named player, and the case goes away as 1.15.1 clients update. The second line in that screenshot, [Steakys]: [Teleportia-Azuresong]: nobody told me..., is a correct repost of another guild's member, not an echo. Pinned by bridge_spec "are not forwarded by a second bridge that sees the repost before its own channel copy" and "are not reposted when a forward quotes the receiving bridge's own guildmate". Location: Bridge.lua:GwBridge:onGuildChat, GwBridge:isRepost, GwBridge:receive.
  • GreenWallAPI.AddMessageHandler ignored the priority it documents. The handler list was sorted by addon name, so a '*' handler always ran before an 'AddonA' one whatever their priorities, and two handlers for one addon ran in whatever order Lua's unstable table.sort left them (peer review a9425470, F1). Handlers now run lowest priority first, and equal priorities in the order they were registered. Pinned by api_spec "runs the lower priority first, whatever the registration order", "orders '*' and named handlers by priority, not by addon name" and "runs equal priorities in registration order". Location: API.lua:GreenWallAPI.AddMessageHandler.
  • A handler registered again after one of its earlier registrations was removed could get an id still in use, so RemoveMessageHandler could remove only one of the two. The id counted the handler's live registrations; it now comes from a counter that only increases (peer review a9425470, F2). Pinned by api_spec "never reuses an id that is still registered". Location: API.lua:GreenWallAPI.AddMessageHandler.
  • Leaving out the priority raised an error instead of using the documented default of 0. Found while fixing the two above. Pinned by api_spec "defaults a missing priority to 0". Location: API.lua:GreenWallAPI.AddMessageHandler.

[v1.15.2] (2026-10-13) - Relay mode: no reposts of reposts

Bug Fixes

  • A co-guild bridge's repost could come back reposted again, with a second attribution in front. In game, 2026-10-12, the operator's line showed up a second time as [Grandwazoo]: [Babankah-OldBlanchy]: [Pickyminer]: (Pimptasty): if you want it, it's yours. A bridge forwards only lines from guildmates without GreenWall, and it judged that from VersionCheck's peer versions alone. A guildmate VersionCheck had not heard from counted as having no GreenWall, so its lines could be forwarded, even when that guildmate was a second bridge whose lines were reposts, and the other guilds reposted them again. The bridge's own check, that it reposted this line itself, did not catch it either on a connected realm: each bridge drops the speaker's realm only when it matches its own, so two bridges on different realms write [Pickyminer]: x and [Pickyminer-OldBlanchy]: x for one line (peer review 33f1c762, N2). That is the likeliest path to the line seen, read in the code; no trace was captured to confirm it. A bridge now judges a guild line by what it says, not by who posted it, so this holds even when the other bridge's messages never arrived (peer review 33f1c762, N1). A line with a [Name]: text attribution is never forwarded when the text is a piece of a co-guild line this bridge heard (hidden channel or forward), cut exactly as the poster would cut it under that name, colour codes and the name's realm set aside. A short line that only shares a few words with one is not a piece (peer review f9aa0e5e, P2). Nor is one forwarded when the name is another guild's player: a forward reaches only one bridge per guild, so the other bridge never heard that line, and a line quoting another guild's player is already in front of that guild (peer review f9aa0e5e, P1). That takes positive evidence, not merely a name outside our guild: the bridge has heard that player speak from a co-guild, or one of GuildRoster's sister rosters lists them. A trade or raid tag such as [WTS]: Lionheart Helm names nobody and is forwarded as before (peer review f92cb2d8, Q1). The cost: a member without GreenWall who types a quote in that shape, naming such a player, is no longer forwarded. The limit: where GuildRoster's sister guilds are not set up, a bridge that never heard the quoted player still cannot tell, and the loop through a forward only one bridge heard stays open there. An attribution with nothing after it is skipped rather than raising an error (P3). A guildmate whose relay-mode leaf has arrived also counts as running GreenWall now, not only one VersionCheck has heard. Pinned by bridge_spec "are known by content, contested once per rival and term, and never forwarded", "do not start from a forward only one of two bridges heard", "are not contested for a few words that are not a piece of the line", "are not forwarded when they quote a player of another guild", "know another guild's player by having heard them, with no sister roster", "are forwarded when the attribution names no player of another guild" and "are not taken from an attribution with nothing after it", and bridgewiring_spec "give the bridge the co-guild, presence, versions and the chat paths". Locations: Bridge.lua:GwBridge:onGuildChat, BridgeWiring.lua:gw.BridgeIsMember, gw.BridgeHooks.
  • Two members of one guild could both act as its bridge and stay that way, reposting every co-guild line twice. Nothing made them settle once each had missed the other's claim. The cause of the split in game is not known (no trace was captured). A bridge that sees a guildmate post a repost of a line it knows now broadcasts its claim, once per rival and term, and the rule every client already applies picks one. A bridge whose claim loses now broadcasts a release naming the winner with the winner's term and time, so guildmates that followed it switch too and judge the next claim as everyone else does. Before this, they kept hiding only the loser's reposts and forwarding lines to it. Pinned by bridge_spec "do not start when two members both believe they are the bridge, and the guild settles on one" (two realms, nothing known about the rival) and "end with the losing bridge handing its followers to the winner, with the winner's claim". Location: Bridge.lua:GwBridge:onGuildChat, GwBridge:receive.
  • The settling above never started for two bridges whose reposts were the same text. Found reading the code after the 2026-10-13 log, not seen in game: the pair in that log (Rakhar and Ackadacka, both on the released 1.15.1) spelled Appleface differently, so their reposts differed. Two bridges on one realm post the very same text, and a bridge returned early on any guild line it had posted itself, before asking whether a guildmate had just posted a repost. Seeing a guildmate post a line it reposted itself now contests the claim too. Since reposts now always name the speaker in full (below), every pair of bridges posts the same text, so this is the path that settles them. Pinned by bridge_e2e_spec "settles two bridges in one guild after one doubled line, then reposts each line once", which fails without the change. Location: Bridge.lua:GwBridge:onGuildChat.
  • GreenWall members saw a bridge's raw repost, [Name-Realm]: text under the bridge's name, whenever their client did not count the poster as the bridge. Seen in game, 2026-10-13, in the operator's screenshot from a GreenWall character. The guild chat filter hid reposts only from the one guildmate this client had agreed was the bridge, and that agreement comes from guild-wide broadcasts, which are exactly what was failing in both guilds. The filter now hides a repost from any guildmate, and draws it once under the real speaker: a guildmate's line counts as a repost when its line already arrived (hidden channel, or drawn from an earlier repost), when it is a piece of a co-guild line this client heard, or when it reads [Name-Realm]: text and that player is not a guildmate. Every attribution in the line is checked, so a [Rank]: tag in front does not hide the player behind it, and the line is drawn under that player. A line from a sender outside the guild is never touched, because the filter also runs on the lines GreenWall draws itself. Each drawn line is remembered with the guildmate who posted it, so a second bridge's repost of it is hidden without being drawn again, while the same bridge posting it again is the speaker repeating the line and is drawn again (peer review f2027b78, G1). Lines are compared without colour codes, which the server may drop from a repost (Q5), and a 1.15.1 bridge's short-name repost ([Amy]: x) matches the line shown as [Amy-Realm]: x. The cost: a guildmate who types a line in exactly that shape, naming a player outside the guild (or a tag with a hyphen, such as [WTS-Ony]: helm), is shown to GreenWall members under that name; without GreenWall it shows as typed. This does not settle which member is the bridge, so a guild with several bridges still posts each line several times for members without GreenWall. Pinned by the bridge_spec group "a repost from a guildmate that is not the bridge". Location: Bridge.lua:arrived, GwBridge:isRepost, GwBridge:filter.

Changes

  • Relay mode spells every player's name with LibGuildRoster's normalization, and a repost always names the speaker in full, as [Appleface-Azuresong]: where are you? even when the speaker is on the bridge's realm. Before this, a repost dropped the speaker's realm when it matched the bridge's, and every reader had to rebuild the bridge's spelling to recognise the repost; that one choice was behind the connected-realm double line and the realmless hidden repost fixed in 1.15.1, the reposts of reposts above, and the same-realm gap just above. Relay mode also had its own realm handling (gw.GlobalName) on top of names DeltaSync had already normalized with LibGuildRoster. Now every name entering relay mode (a chat sender, a DeltaSync sender, a server reply, the 1.14 relay, the player itself) goes through gw.BridgeName / gw.BridgePlayer, which ask LibGuildRoster (NormalizeName, GetNormalizedPlayer), and gw.BridgeDisplay and the bridge's realm-dropping and realm-restoring are gone. The cost: same-realm speakers show with their realm in reposts. While players update, a 1.15.1 bridge still posts the short form for a same-realm speaker. A 1.15.2 member hides it and, since the filter change below, recognises it as the line it already has under the full name, so it is not drawn again; a 1.15.2 next in line still never sees the repost it expects and takes over after GW_BRIDGE_TAKEOVER seconds, which ends it once the 1.15.1 bridge accepts the newer claim. A 1.15.2 bridge does not forward the short form: a bare name in an attribution matches a heard speaker of that name on any realm, so the repost is recognised by its content even when no leaf or VersionCheck sighting says the 1.15.1 bridge runs GreenWall (peer review 8c9d0685, R1; pinned by bridge_spec "know a 1.15.1 bridge's short-form repost by content, even with nothing saying it runs GreenWall", which fails without it). Not seen in game; the takeover is pinned by bridge_spec "from a 1.15.1 bridge's short form are hidden and not drawn again, and the next in line takes over". The hidden channel, the 1.14 relay and the rest of GreenWall still use gw.GlobalName. Pinned by bridgewiring_spec "spells every name, the player's own included, as LibGuildRoster normalizes it" and "draws a repost under the full name it carries", bridge_spec "name the speaker in full, so every reader matches the bridge's repost whatever realm it is on", and the bridge_e2e_spec repost lines. Locations: BridgeWiring.lua:gw.BridgeName, gw.BridgePlayer, gw.BridgeRender, gw.BridgeOnLeft, Bridge.lua:lines, repostOf, GwBridge:filter.
  • New trace category filter (/gw trace filter on): for every guild line shaped like a repost, the guild chat filter's decision, the sender as the client gave it and as LibGuildRoster normalizes it, the bridge this client holds, whether the roster counts the sender as a guildmate, and whether relay mode is live. Added because the operator's 2026-10-13 screenshot showed Testthese's own reposts shown raw on Testthese, which every path through the filter should hide, and the code alone does not say why. Pinned by bridgewiring_spec "trace the filter's decision for a line shaped like a repost, and nothing else". Locations: BridgeWiring.lua:gw.BridgeFilter, Constants.lua:GW_TRACE_CATEGORIES.

[v1.15.1] (2026-10-11) - Relay mode fixes from peer review

Bug Fixes

  • On a connected realm, GreenWall members could see a repost twice and the bridge could bounce between two players. Peer review afc1fd93 (N1): each client named the speaker in the repost it expected by dropping the realm when it matched its own realm, so a reader on another realm of the cluster expected [Zed-OldBlanchy]: hi while the bridge posted [Zed]: hi. The reader drew the line a second time, and as next in line never saw the repost it was waiting for and took over. Every client now names the speaker from the bridge's realm, so the keys and the split points of a long line match. Locations: Bridge.lua:lines, BridgeWiring.lua:gw.BridgeDisplay.
  • A bridge kept whispering to another guild's bridge after it logged off. Peer review afc1fd93 (N2): the offline notice that cleared it comes from LibGuildRoster, which watches our own guild only, so another guild's bridge was never cleared and every non-GreenWall line was whispered to an offline player. Leaving the guild bridge channel now forgets that guild's bridge, and its guild is reached through a live contact again. Locations: BridgeWiring.lua:gw.BridgeOnLeft, Bridge.lua:GwBridge:forgetPeer.
  • Relay-mode messages for another guild could go to a member still on 1.14. Peer review afc1fd93 (N3): the contact for each other guild comes from the 1.14 relay, which can be a 1.14 client with no handler for relay mode. A contact VersionCheck has seen on an older GreenWall is now skipped; one VersionCheck has not heard from is still used. While every reachable member of a guild runs an older version, that guild may need its own officer to switch relay mode on. Location: BridgeWiring.lua:gw.BridgeTargets.
  • Lines another add-on posted with GreenWallAPI.SendChat were never reposted, so members without GreenWall missed them. Peer review afc1fd93 (N5). They now reach the bridge as a typed co-guild line does. Location: BridgeWiring.lua:gw.BridgeOnDeliver.
  • A guild change without a /reload kept the old guild's relay-mode record, bridge and contacts. Peer review afc1fd93 (N8). Relay mode now starts over for the new guild, but only once the guild configuration has been parsed for it: until then guild_id can still be the old guild's, so the first restart could send its hello into the new guild with the old co-guild id (self-audit af3376b0, F3; read in the code, not seen in game). GwConfig now records the guild it parsed (guild_name), and until it matches, relay mode is not running at all: no line from the old confederation is reposted into the new guild chat, no relay-mode addon message from it is handled (a forward to the old bridge would otherwise be reposted there; peer review c16361fb, NEW-A), and the filter hides nothing. Guild info missing while the client still reports a guild (IsInGuild) is a moment before it loads, and relay mode carries on; guild info missing and no guild, after /gquit or a kick, stops it (peer review c16361fb, NEW-B). A guild with no GreenWall configuration never gets relay mode. Pinned by bridgewiring_spec "starts over for a new guild without a reload once its configuration is parsed". Locations: BridgeWiring.lua:gw.BridgeStart, gw.BridgeLive, gw.BridgeFilter, Config.lua:GwConfig:load.
  • On a connected realm, a hidden repost was drawn under a name without a realm, so a reader on another realm of the cluster saw the speaker as a player of its own realm, and a click on the name targeted the wrong player (self-audit af3376b0, F2). The repost drops the speaker's realm only when it is the bridge's, so the name is now drawn with the bridge's realm put back; the chat frame shortens it for display as it does any guild line (ChatFrameUtil.GetDecoratedSenderName). Pinned by bridgewiring_spec "draws a repost under its speaker, on the bridge's realm unless it names one" and bridge_e2e_spec "shows a line from a player without GreenWall to the other guild once". Location: BridgeWiring.lua:gw.BridgeRender.
  • On a realm whose name has a hyphen or a period, every GreenWall name was spelled differently from the game's. GreenWall removed only spaces from the realm, but the client's normalized realm also drops hyphens and periods (warcraft.wiki.gg, GetNormalizedRealmName: "Azjol-Nerub" -> "AzjolNerub"), which is how sender fields arrive (per the wiki; not checked in game). So the player's own name could not match its own messages there, and a name like Ann-Azjol-Nerub split at the wrong hyphen. Both spellings now strip the same characters; apostrophes stay, as in the client's. An already-qualified name is normalized too, split at its first hyphen, so a GW:p: line written as Guild-Azjol-Nerub still matches the guild. Realms with only spaces, Old Blanchy among them, are unaffected. During the update, an older GreenWall on such a realm still spells names the old way. Pinned by utility_spec "normalizes the realm as the client does" and "normalizes the realm of an already-qualified name". Locations: Utility.lua:gw.GlobalName, Globals.lua (gw.player).
  • A bridge with a guildmate on its ignore list never handed over, even when a member who ignores nobody was online, because the hand-over check asked whether the bridge was still a candidate, and since 1.15.0 it always is (self-audit 0497ba9a, item 1). It now steps down to the first candidate when that one ignores nobody and it does not. Location: Bridge.lua:GwBridge:evaluate.
  • A guild could keep a newcomer as another guild's bridge after the sitting bridge corrected it, when the newcomer's losing claim was on a lower term (self-audit 582c13a8, item 4). The sitting bridge now re-announces itself after every claim it beats. Location: Bridge.lua:GwBridge:receive.
  • A member who logged in after another guild's bridge left could be told it was still there, and as bridge would then whisper every line to an offline player. A sitting bridge answers a login with the other guilds' bridges it knows, and one that missed the leave (or a 1.15.0 bridge, which never forgets one) passes the departed one on; the newcomer never saw it leave (self-audit 8504c38e). The server's "No player named '...' is currently playing." reply to a whisper is now handled as that player's logoff: another guild's bridge is forgotten, and our own guild's bridge (a member forwards lines to it) is replaced (peer review G1 on 8504c38e). When LibGuildRoster already has the player offline, DeltaSync declines the whisper without sending it, so no reply ever comes; that decline is now handled the same way. The line whose whisper bounced is lost; every later line goes through a live contact. An event, not a timeout. Pinned by bridgewiring_spec "forget another guild's bridge when a whisper to it bounces" and "forgets another guild's bridge when DeltaSync declines a whisper to it as offline". Locations: BridgeWiring.lua:gw.BridgeOnNotFound, gw.BridgeWhisper.
  • Only the member who noticed another guild's bridge was gone forgot it; the rest of the guild kept it. A new bridge was already passed round the guild, but a departed one was not, so whoever became our bridge next whispered a player who had left. When our bridge forgets another guild's bridge (logoff, leaving the bridge channel, a bounced or declined whisper) it now broadcasts that to its guild over the addon channel (drop), and every member forgets it too. A member accepts a drop only from its own bridge, and only for the name it still holds, so a drop that arrives after the successor's announcement leaves the successor in place. Requested by the operator, 2026-10-10. Pinned by bridge_spec "tells its guild by broadcast when another guild's bridge is gone, and every member drops it". Location: Bridge.lua:GwBridge:forgetPeer, GwBridge:receive.
  • A line GreenWall drew itself could raise bad argument #1 to IsChatLineCensored. The Classic Era chat handler passes the line id (arg11) to C_ChatInfo.IsChatLineCensored, whose chatLine is a non-nilable number (ChatInfoDocumentation.lua, classic_era), and gw.ReplicateMessage passed nil when its caller had no line id. It now passes 0, as it already does for the other unused numeric arguments. The client does not treat line 0 as censored: such a line shows the speaker as a clickable name (operator, in game, 2026-10-11; self-audit 8504c38e, F1). Pinned by chat_spec. Location: Chat.lua:gw.ReplicateMessage.
  • GreenWall said nothing in chat when it could not register with VersionCheck-1.0, so it was simply missing from /vc. VersionCheck-1.0 is a required dependency, so this is now an error in chat naming the cause (library not found, or a loaded copy without Enable) instead of a debug line (self-audit 8504c38e, F3). Pinned by greenwall_spec "reports a missing library in chat". Location: GreenWall.lua:gw.RegisterVersionCheck.
  • GreenWall was still missing from /vc in the operator's client. In game, GreenWall was loaded and VersionCheck-1.0 was at minor 18, but VersionCheck held no GreenWall host (/dump, 2026-10-11). The code on disk does not explain it: the call at load is the only path, and only its error branch registers nothing. GreenWall now tries again at PLAYER_LOGIN, once every addon has loaded, if VersionCheck does not already hold it. The attempt at load logs a failure at debug level only, and the login attempt reports one in chat, with Enable returned nothing as a third named cause. The underlying cause is not known. The registration call has been in GreenWall.lua since v1.13.6 (2026-09-13), so a client still running an older file is ruled out. With this change the GreenWall tab appears in /vc (operator, in game, 2026-10-11). Pinned by greenwall_spec "registers at PLAYER_LOGIN when the library was not there at ADDON_LOADED" and three neighbours. Location: GreenWall.lua:gw.RegisterVersionCheck, GreenWall_OnEvent.
  • With Guild Roster Manager installed, GreenWall members saw every co-guild line twice: once from the hidden channel and once as the bridge's repost (operator's screenshot, 2026-10-10). GRM's chat filter puts a coloured main tag such as (M) in front of the text (GRM_Core.lua:GRM.AddMainToChat), and when it registered before GreenWall's, the filter looked for the [Name]: attribution only at the first character, so the repost was neither hidden nor matched to the copy already shown. The filter now finds the attribution wherever it starts, so a tag any addon puts in front is ignored; when the text holds more than one [x]:, the one whose line already arrived on the hidden channel wins (peer review 10ff04e7, G1). A line from the bridge player that merely contains [x]: in the middle is now also read as a repost (filed, not fixed). Pinned by bridge_spec "finds a repost behind another addon's tag, and still knows a line it already showed" and "prefers the attribution of a line it already showed". Location: Bridge.lua:GwBridge:filter.

Changes

  • The README's VersionCheck-1.0 link pointed at a page that does not exist (versioncheck-1-0 returns 404); it now points at versioncheck, the slug .pkgmeta already uses. Location: README.md.
  • The CurseForge page no longer claims Retail 11.x support: Retail is on 12.x, where GreenWall is untested and shows as out of date. Location: docs/curseforge_description.html.
  • /gw help lists trace as two forms, listing and switching, instead of one ambiguous line. Location: BridgeWiring.lua.
  • Mixed versions while players update: a 1.15.1 next-in-line expects another add-on's SendChat lines to be reposted, and a 1.15.0 bridge never reposts them, so once per such line the 1.15.1 member takes over the bridge; after that the new bridge reposts them and it settles. A 1.15.0 reader on a connected realm keeps the old double line until it updates (self-audit af3376b0, F1).
  • gw.BridgeDisplay always takes the bridge's name; its fallback to our own realm had no caller left (self-audit af3376b0, F4). Location: BridgeWiring.lua:gw.BridgeDisplay.
  • A redundant online-sighting call was removed from relay mode's data handler; DeltaSync already reports every sender it hears from (peer review afc1fd93, N6). Location: BridgeWiring.lua:gw.BridgeOnData.

[v1.15.0] (2026-10-09) - Relay mode: co-guild chat for members without GreenWall

New Features

  • Relay mode. Requested on Discord (inbox 3de3351b): guild members who do not run GreenWall saw nothing of the other co-guilds. With relay mode on, one GreenWall member per guild, the bridge, reposts every other guild's chat into its own guild chat as [Name]: text, and whispers its own guild's non-GreenWall speakers to the other guilds' bridges. GreenWall members get the same lines through the hidden channel as before; a guild-chat message filter hides the bridge's reposts from them, and draws a repost under the real speaker when the line did not already arrive on the channel. Guild chat only; officer chat stays on the officer channel. Locations: Bridge.lua, BridgeWiring.lua.
  • Relay mode is an officer-only switch for the whole confederation: /gw relay on|off, or a new checkbox under "Bridge officer chat" in the options panel. The setting is a record { mode, setAt, setBy, canon } that every GreenWall in every co-guild applies; the newest save wins. Location: Sync.lua.
  • Bridge election without timers. Terms with a claim time: the first eligible member claims the next term, and everyone accepts the higher term, then the earlier claim, then the lower name, so a sitting bridge survives a newcomer's early claim (it whispers its claim back). A member with a guildmate on its ignore list goes to the back of the line, since that guildmate's lines never reach it. A logout hands over through LibGuildRoster's offline notice; a bridge whose reposts stop appearing is replaced by the next in line when a later co-guild line finds an earlier one unreposted for GW_BRIDGE_TAKEOVER seconds. Location: Bridge.lua.
  • /gw bridge shows each guild's bridge, the term and the candidates. Location: BridgeWiring.lua:gw.BridgeCommand.
  • Comm tracing: /gw trace switches per-category tracing (relay, bridge, sync, leaf, forward, repost, channel), /gw debugtab and /gw debugtabremove open and close a GW Debug chat tab, and /gw debuglog [clear] counts or empties the saved trace lines (GreenWallTrace). Location: Trace.lua.

Changes

  • Six required add-ons now: DeltaSync joins Ace3, AceCommQueue-1.0, GuildRoster, LibAceGUIWidgets and VersionCheck-1.0 in all six TOCs, .pkgmeta and the README. Every relay-mode addon message goes through one DeltaSync-1.0 host (namespace GW_BRIDGE_NAMESPACE) on a plain table with AceComm-3.0 and AceCommQueue-1.0 embedded; reposts go through ChatThrottleLib. I first built relay mode on a hand-rolled copy of DeltaSync's protocol; the operator, 2026-10-09: "why would you do a delta-sync style, hand roll it then have to maintain it, when you could... JUST USE DELTA SYNC?" DeltaSync now owns the prefixes, serialisation, integrity check, dropping our own messages and the offline-whisper guard. Locations: BridgeWiring.lua:gw.BridgeHost, GreenWall.toc and its five siblings, .pkgmeta.
  • Two new per-character saved variables: GreenWallSync (the relay-mode record, wiped on a guild change) and GreenWallTrace (trace switches and recent traced lines). Location: GreenWall.toc.

Bug Fixes

  • A guild could have no bridge at all, so its members without GreenWall saw nothing. Seen in game on the dev build (2026-10-09): relay mode was on in both guilds, but /gw bridge in The Old Gods showed bridge: nil; candidates: .. Its only relay-capable member, Pickyminer, had a guildmate on the ignore list, and I had made that rule a member out entirely. That member now only goes to the back of the line, after every member with no guildmate ignored, so any guild with a GreenWall member online gets a bridge. /gw bridge says when the ignore list applies. The player's own eligibility is also recorded before its first send, so a failed send can't leave it unrecorded. Locations: BridgeWiring.lua:gw.BridgeCandidates, Sync.lua:GwSync:hello.
  • An officer's /gw relay on was refused with "only an officer can switch relay mode (not-officer)". Seen in game on the dev build (2026-10-09): Pickyminer, an officer of The Old Gods, was refused while an officer of the other guild was not. I gated the switch on gw.IsOfficer, which decides from whether the guild master's officer note has text in it, so an empty GM note makes every officer a non-officer. Relay mode now asks LibGuildRoster's IsOfficer, the officer-note permission (C_GuildInfo.CanViewOfficerNote) the other addons already share. gw.IsOfficer is unchanged for officer-chat bridging. Location: BridgeWiring.lua:gw.BridgeIsOfficer.
  • A bridge would have forwarded GreenWall members' lines as if they had no addon. Found by the new wiring spec running the real VersionCheck-1.0: gw.BridgeIsUser looked up the bare name, but VersionCheck files every peer under Name-Realm. It now reads the qualified name first and the bare name as a fallback for an older VersionCheck. Location: BridgeWiring.lua:gw.BridgeIsUser.
  • Second-hand names were reported to LibGuildRoster as seen online. A contact named inside a relay announcement, and a bridge named inside another bridge's announcement, were passed to MarkOnline in its first-hand form, which could keep a logged-off player "online" and let whispers through to them. Only players this client actually heard from are reported now. Location: BridgeWiring.lua:gw.BridgeOnData, gw.BridgeOnRelay.

Notes

  • Partly verified in game (2026-10-09, dev build, two guilds). An officer switched relay mode on in both guilds, /gw bridge showed a bridge in each, and a line typed in The Other Gods reached a member of The Old Gods without GreenWall as a repost. GreenWall members on an older version saw that line twice (the hidden-channel copy and the repost), because only 1.15.0 hides reposts; that clears as they update. Not yet seen in game: the reverse direction (a non-GreenWall speaker forwarded to the other guild), handover on logout, and takeover of a silent bridge.
  • Known limitation: any GreenWall member can author a relay-mode switch. The client exposes no other member's officer permission, so a received record is refused only when its author is known not to be an officer; for anyone but the local player that is unknown. A record therefore cannot be proven to come from an officer. Location: Sync.lua:GwSync:receive.
  • Known edge: if a GreenWall member's own hidden-channel copy of a line arrives after the bridge's repost of it, that member sees it twice.
  • A player logging in with no saved record is answered by every guildmate holding one. Each answer is one small whisper and every copy after the first is ignored; left as is.

Repo Tooling

  • New specs: sync_spec, bridge_spec (including the claim-correction path and a pin on the canonical hash, so a change to GW_BRIDGE_SEP cannot silently change every stored hash), bridgewiring_spec and bridge_e2e_spec, which runs relay mode across six simulated players with the real DeltaSync, LibGuildRoster, AceCommQueue, ChatThrottleLib and VersionCheck in each player's own library registry. manifest_spec pins DeltaSync in the TOCs and .pkgmeta. Locations: Tests/, Tests/env_greenwall.lua.

[v1.14.2] (2026-10-08) - Options panel changes are saved

Bug Fixes

  • Unticking an option in the GreenWall options panel did nothing; the tick came back as soon as the window closed. Reported on Discord (#greenwall-revived, 2026-10-08). I built the panel to save on the legacy okay callback, but the Blizzard Settings panel in every flavour calls only OnRefresh, OnDefault and OnCommit on a canvas (Blizzard_SettingsPanel.lua), so okay never ran and no change was ever stored. Hooking the save to OnCommit would have been worse: it runs on every addon canvas, opened or not, and would have written a never-opened panel's blank checkboxes over the stored settings. Each control now saves the moment it changes: the tag, roster and officer-chat boxes, the join-delay slider, and the account/character mode box, which now switches the mode immediately and shows that store's values. Filling the panel while it opens is not counted as a change. The Defaults button now also redraws the panel. Locations: Interface.lua, GreenWall.lua:GreenWall_OnLoad, GreenWall.xml.

Changes

  • The account/character mode box switches the mode at once and shows that store's values. It used to preview the other store and wait for an OK that never came; with save-on-click a preview would show values that are not in effect. It now matches /gw mode. Location: Interface.lua:GreenWallInterfaceFrameOptionMode_OnClick.

Notes

  • Verified in game on Classic Era by the operator (2026-10-08): an unticked option stays unticked after closing and reopening the options window.

Repo Tooling

  • The interface specs required the broken design: they tested SaveUpdates and a callback named okay, which the client never calls. They now require save-on-click, that a change survives reopening the panel, that opening the panel stores nothing, and that the panel defines no OnCommit. GreenWallInterfaceFrame_SaveUpdates is removed. Suite: 517 examples, 100% line coverage on all 14 files. Locations: Tests/interface_spec.lua, Tests/greenwall_spec.lua, .luarc.json.

[v1.14.1] (2026-09-30) - No more ADDON_ACTION_BLOCKED from sends without a key press

Bug Fixes

  • GreenWallAPI.SendMessage and GreenWall's own roster notices raised ADDON_ACTION_BLOCKED blamed on GreenWall whenever they were sent from an event. Peer-review finding from GuildRoster (inbox d49c5edb), seen in game on Classic Era: 52 errors in one session, from LibGuildRoster's live-presence sends out of GUILD_ROSTER_UPDATE. 1.14.0 sent B and E messages on the relay and on the bridge channel, so older GreenWalls would still hear them. I assumed a CHANNEL SendChatMessage without a key press is silently dropped; on this client it raises instead, once per segment. An addon cannot tell whether it is inside a key press, so GwChannel:send now hands roster notices and API data to the relay only and never calls tl_send for them. Typed chat still goes on the channel, because it always has a key press behind it. SendMessage keeps its signature and result table. Location: Channel.lua:send.

Changes

  • Co-guild members on GreenWall older than 1.14.0 no longer receive API data or roster notices from 1.14.1 senders. This is the cost of the fix above. Those members only ever got them when a key press happened to be behind the send. A 1.14.1 client still shows a 1.14.0 sender's two copies once. Locations: API.md, API.lua.
  • The addon title is orange (|cffFF8000) in the in-game AddOns list, in all six TOCs, matching the rest of the author's addons. Requested by DeltaSync (inbox d621dcbf). Location: GreenWall.toc and its five siblings.

Notes

  • Not yet verified in game. The operator should confirm that BugSack stays clean on Classic Era while GuildRoster is running.
  • Documentation brought in line with the code. The README described the relay as a second route alongside the chat channel; it is now the only route for roster notices and add-on data. The README gains short Compatibility (ElvUI, Prat-3.0, Identity-2, Name2Chat, Incognito) and For Add-on Authors sections, which were only on the CurseForge page and in API.md. The options-panel roster description now lists online and offline notices as the slash-command one already did. The CurseForge page now describes SendMessage as working without a key press, lists RemoveMessageHandler and ClearMessageHandlers, and says B and E segments no longer go on the bridge channel. CLAUDE.md records the relay-only rule so it is not reverted. Locations: README.md, docs/curseforge_description.html.

Repo Tooling

[v1.14.0] (2026-09-23) - Sends from events reach the sister guilds: the addon-message relay and GreenWallAPI.SendChat

New Features

  • GreenWallAPI.SendChat(addon, text, chatType): any addon can show a line in every co-guild's guild or officer chat, from an event handler or a timer, with no key press. Asked for by TOGTools (inbox 898b1e4c), whose level-up congratulations were being dropped. Every co-guild member on 1.14.0 or later sees the line exactly as a bridged typed line, with the player as the sender. The sender's own guild is not sent a copy, the same rule typed lines follow. It returns a result table (ok, reached, reason, truncated, codes), so a caller can tell its player the truth: nobody reachable, refused by the client (with the SendAddonMessage result codes), cut to fit, or a Retail secret value refused before it reached the client. GreenWallAPI.version is now 2, which is the flag a caller tests before using it. Locations: API.lua:SendChat, API.md.
  • The addon-message relay, the carrier underneath it. Every bridge send ends in SendChatMessage(..., 'CHANNEL', ...), which the client flags HasRestrictions (ChatInfoDocumentation.lua:403-406, Classic Era), so a channel send leaves only inside a key press. SendAddonMessage carries no such flag, but Classic removed its CHANNEL chat type in 1.13.3, so it cannot use the bridge channel either. The relay uses the two chat types Classic does allow for addon messages: a whisper to one contact in each co-guild, then a guild-wide addon message inside that co-guild. Contacts are learned with no roster lookup: guildmates on 1.14.0 greet each other, the lowest name is its guild's relay, the relay introduces itself to everyone who joins the bridge channel, and it re-introduces its guild to every other guild whenever a guildmate leaves. That covers the case where the player another guild was using as its contact logs off. There is one relay for the guild bridge and one for the officer bridge. Location: Relay.lua.

Bug Fixes

  • Roster notices (joined, left, removed from the guild) were dropped whenever they were sent from the system event rather than a key press, which is always. Peer-review finding from TOGTools (inbox 13bcb140): SystemEventHandler.lua broadcasts from CHAT_MSG_SYSTEM, and the bridge-channel send that followed was dropped. Broadcasts now also travel on the relay. The channel copy still goes out, because older GreenWalls hear only that. A player on 1.14.0 who receives both sees the notice once, because the two copies are matched on content within GW_RELAY_DUAL_HOLD (10 s). Locations: Channel.lua:send, Relay.lua:RecentlySeen.
  • GreenWallAPI.SendMessage had the same defect, and every addon that calls it from an event handler lost its message. It keeps its signature, still sends on the bridge channel, and now also rides the relay. It returns the relay's result instead of nothing. Handlers in the sender's own guild, and the sender's own client, still receive it with the guild and echo flags set, as the channel's loopback always gave them. A message too long for one addon message after encoding is refused with too-long rather than cut, because cut data does not decode. Location: API.lua:SendMessage.

Notes

  • What a mixed-version confederation sees. Typed guild and officer chat is unchanged and reaches every version. SendChat lines reach only co-guild members on 1.14.0 or later. SendMessage data and roster notices reach older versions exactly as before (only when a key press was behind the send) and reach 1.14.0 always. A co-guild with nobody on 1.14.0 or later online is not reached, and the result says no-peers.
  • Not yet verified in game. The relay is covered offline (a simulated three-guild network: every co-guild gets a line exactly once, the sender's guild never does, and delivery survives the relay and a contact logging off). No whisper-then-guild round trip has been observed on a live client yet. The source says neither addon-message chat type is restricted, and the wiki lists both as allowed on Classic. The client's own behaviour is the one thing the suite cannot show.
  • Documentation checked against the code and corrected. The README said a GW:v: minimum version "disables the GreenWall client"; GwConfig:load only prints an error and the addon keeps working, so it now says that, and its example GWv:1.1.00 (a leading-zero patch number) became GWv:1.13.0. The /gw mode default read "on" where the setting is account, and roster covers online and offline as well as join and leave. Manual installation pointed at the original project's GitHub releases, which are single-flavour builds without VersionCheck; it now points at this project's CurseForge Files tab. Support pointed at the original project's e-mail and issue tracker; it now points at this project's CurseForge page and credits the original. Both the README and the CurseForge description now name LibAceGUIWidgets, which VersionCheck-1.0 requires (its TOC: ## Dependencies: Ace3, LibAceGUIWidgets), and both state that relayed lines are the one exception to "no gateway member". Locations: README.md, docs/curseforge_description.html.
  • GreenWall_Mainline.toc stays at 110207. The live tree marks SendAddonMessage SecretArguments = "NotAllowed", and both send calls refuse a secret value (feature-detected through issecretvalue), but Retail 12.x remains untested.

Repo Tooling

  • Onboarded to writ; the two markdown boards are imported into its inbox and deleted. docs/AUDIT.md (one finding, already fixed in v1.13.6) and Tests/HARNESS_CONTRACT.md (eight contracts, all delivered by the harness in 65de3af) were imported and closed, then removed; git log on either path keeps the history. CLAUDE.md now sends harness requests through the inbox.
  • Harness pin moved from 2c7279f to de64643. One spec asserted Settings.GetCategory('GreenWall'), which the client answers nil for every name because it looks categories up by numeric id (harness notice cb55fdcd); it now finds the category the registration built and looks it up by its id.
  • Tests/relay_spec.lua, 65 specs. The protocol runs against a small network in the spec file (a whisper reaches one player; a guild send reaches the sender's whole guild, echo included). The production wiring runs against the harness. Suite: 510 examples, 100% line coverage on all 14 files.
  • A strsplit stand-in in Tests/env_greenwall.lua. The client's strsplit takes a pieces limit (the last piece keeps the rest of the string, per the wiki's string.split page). The harness ignores it, so a relay payload containing a tab lost its tail offline. Raised with the harness; the stand-in yields as soon as the harness honours the limit.
  • CurseForge and README links point at GreenWall - Revived (curseforge.com/wow/addons/greenwall-revived), not the original project, so bug reports reach this fork. Peer-review finding from TOGWiki (inbox ffd525af).

[v1.13.6] (2026-09-13) - VersionCheck-1.0 as a required dependency, a MoP TOC fix, and a lint gate

New Features

  • GreenWall now registers with VersionCheck-1.0, so a guildmate running a newer release can tell you yours is out of date, and /vc shows who in the guild is on which version. gw.RegisterVersionCheck() runs once from GreenWall's own ADDON_LOADED: it reaches the library through LibStub:GetLibrary('VersionCheck-1.0', true), guards on VC.Enable, and calls the string form VC:Enable('GreenWall'). The library reads our ## Version: itself, raw — an unsubstituted GreenWall-v1.15.3 is how it recognises a development build and suppresses the popup — so nothing on GreenWall's side reformats it. VersionCheck detects GreenWall's own API at PLAYER_ENTERING_WORLD, not at load, so the new load order (VersionCheck before GreenWall) leaves its confederation transport intact. Location: GreenWall.lua:RegisterVersionCheck.

Changes

  • Two required dependencies, declared in all six TOCs and in .pkgmeta: Ace3 and VersionCheck-1.0. GreenWall was a zero-dependency addon with everything vendored under Lib/; it now needs four standalone addons at load — the two above, plus VersionCheck's own GuildRoster and LibAceGUIWidgets, which its manifests pull in. The CurseForge app installs all of them alongside GreenWall; a manual install that omits any of them will not load, because ## Dependencies: is enforced by the client. Ace3 is on the line for VersionCheck's sake only — GreenWall itself still contains no Ace3 code, and CLAUDE.md says so. Locations: GreenWall.toc and its five siblings, .pkgmeta.

Bug Fixes

  • GreenWall_Mists.toc declared 50503; MoP Classic is 50504, so the addon showed as out of date on that client. Peer-review finding, verified against the TOC-format reference on the day of the fix. X-Min-Interface moved with it. The other five TOCs were checked and are current — including GreenWall_Wrath.toc at 30405, which is correct: Titan Reforged (38002) is a separate server line, not a Wrath Classic patch. Location: GreenWall_Mists.toc.
  • Every CHAT_MSG_SYSTEM event wrote a global named handler into _G. The system-event dispatch assigned its factory result to an undeclared local, so GreenWall's handler object was visible to — and overwritable by — any other addon using the same very ordinary global name. Now local. Found by the new lint gate below. Location: GreenWall.lua:GreenWall_OnEvent.
  • gw.GetGMOfficerNote() leaked a global _ on every roster scan for the same reason: the throwaway receiver for GetGuildRosterInfo's unused returns was never declared. Now local. Location: Utility.lua:GetGMOfficerNote.

Repo Tooling

  • Added a luacheck gate: .luacheckrc, whose globals list is derived from .luarc.json rather than copied, so the language server and luacheck cannot disagree about which names are WoW APIs. Before it, luacheck reported 191 warnings on GreenWall.lua alone — a report nobody could read — and the two real defects above sat inside it. The cosmetic families are off for the upstream-derived addon files to keep the fork re-mergeable; the 1xx "undefined variable" family stays on everywhere, since that is the class that found the v1.13.2 GetAddOnInfo crash. Every addon file and every spec is clean. .luarc.json also learned the GW_* constants, GetChatWindowChannels, the two Interface-panel XML handlers that were missing, and the test-only io/os.
  • Added Tests/manifest_spec.lua — nine specs for what line coverage cannot see. The six TOCs agree on every directive except the two Interface lines and on the file list; every listed file exists; ## Dependencies is exactly Ace3, VersionCheck-1.0; .pkgmeta required-dependencies names the same set through a slug table; Tests and docs are excluded from the zip; and every GW_* constant in Constants.lua is declared in .luarc.json. Each pins a value kept in two places that had nothing asserting they agreed.
  • Six specs for the VersionCheck bootstrap in Tests/greenwall_spec.lua: the right library name, the string form, the raw version, no error when the library is absent or predates Enable, and that it fires from GreenWall's ADDON_LOADED and nobody else's. A recorder registered under the real LibStub stands in for the library, because loading the real one means loading three more addons' working trees to test one call. Suite: 445 examples, 100% line coverage on all 13 files.
  • Harness pin moved from 615cc6f (2026-08-05) to 2c7279f (2026-09-13) — five weeks and roughly a hundred adoption-log entries. The suite was green on the first run against the new pin; the entries that touch what GreenWall does (the chat-window rework, the clock contract, load-time frame retention, wow.reset() restoring LibStub) were read and none required a change here. verify-manifest, verify-addons and verify-xml all pass (17/17 files load; every frame the XML declares is built). verify-xml initially reported a false failure because it ran all TOC Lua before any TOC XML and GreenWall's Lib\Load.xml comes first; raised with the harness (inbox 36c18d33) and delivered the same evening in 0ca808d, which walks the TOC in order and names each Lua file that fails to load — a change that immediately exposed two unrelated tool defects against another addon.
  • Two stale review boards migrated to writ's inbox; docs/AUDIT.md and Tests/HARNESS_CONTRACT.md remain as the historical record.

Notes

  • Verified in game on Classic Era: after login, /vc shows a GreenWall tab in the version roster, so the registration works end to end on the real library, not only against the suite's recorder.
  • Documentation corrected while checking it against the code. The README's options-panel steps said Esc → Interface → AddOns; the client's Game Menu button is Options (GAMEMENU_OPTIONS in the Classic Era source), and it has been for some time. The CurseForge description claimed the bridge channel "never appears in your channel list"; it is removed from every chat window, but it is a joined temporary channel and occupies a slot in the Social window's Chat tab — which the Requirements section already said. Both now say what the addon does. The README overview and the CurseForge feature list gain a version-checking section, and the API section names VersionCheck-1.0 as a real consumer of the bridge. Locations: README.md, docs/curseforge_description.html.
  • GreenWall_Mainline.toc is deliberately left at 110207. Retail is on 12.x, and 12.0 changed the API surface around chat and secret values; GreenWall's entire transport is SendChatMessage / CHAT_MSG_CHANNEL. A TOC declaring 12.x is a claim the bridge works there, and that claim has not been tested. It will be bumped together with a Retail login that sees the bridge channel send and receive, not before. Recorded in CLAUDE.md's TOC rules.
  • The duplicate-code scan reports gw.WorldChannelFound and GwConfig:is_container as having no production caller. Both are upstream's (AIE-Guild/GreenWall), so they are kept for re-mergeability rather than deleted here.

[v1.13.5] (2026-08-05) - Offline test suite, and the ten defects it found

Bug Fixes

  • The Interface Options "Defaults" button raised a Lua error and, had it not, would have done nothing. Interface.lua:GreenWallInterfaceFrame_SetDefaults calls gw.settings:reset() with no arguments, but GwSettings:reset(svtable, meta) required the settings table and indexed it immediately — so the first key it looked at raised attempt to index local 'svtable' (a nil value). Underneath that was a second defect that would have surfaced the moment the first was fixed: the body only assigned a default if svtable[k] == nil or self:validate(k, svtable[k]), i.e. only for keys that were absent or invalid. That guard is correct in initialize(), which fills gaps, and was copied into reset(), whose whole job is to overwrite values that are present and valid — making it a no-op on any real settings store. Now defaults svtable to the store for the current mode and assigns unconditionally, stamping updated. Location: Settings.lua:reset.
  • Third-party addons that registered an API handler with '*' never received a single message. API.md documents '*' as "messages from all addons will be handled", but gw.APIDispatcher tested addon == e[2] or addon == '*' — asking whether the sending addon was literally named '*' rather than whether the registration was the wildcard. Both GreenWallAPI.SendMessage and AddMessageHandler assert addon == C_AddOns.GetAddOnInfo(addon), so no real sender can ever be named '*' and that clause was dead: every wildcard handler was registered, sorted, and never called. Now tests e[2] == '*'. Location: API.lua:APIDispatcher.
  • A single mistyped version line in the Guild Information panel aborted the entire configuration parse for every member of the confederation. The GW:v: and GW:o:mv= handlers gated on strmatch(field[2], '^%d+%.%d+%.%d+%w*$'), which is looser than the vendored semantic-version parser accepts — that parser requires a - before a pre-release suffix. So GW:v:1.2.3beta passed the regex, semver() returned nothing at all, and tostring() with no argument raised, unwinding out of GwConfig:load() before the channel, peer and officer directives on the following lines were ever read. Validation is now done by parsing rather than by regex: an unparseable version is logged and skipped, and the rest of the page still loads. Location: Config.lua:load.
  • GwConfig:is_container() raised on every call. It opened with if guild == self:GetGuildName(), but GwConfig has no such method — the helper is gw.GetGuildName() — so any call died with attempt to call method 'GetGuildName' (a nil value). Fixed, and the guild-tag test tightened to reject the empty string as well as nil, so a confederation whose GW:p: list omits the local guild is correctly reported as not configured rather than claiming membership on an empty tag. Location: Config.lua:is_container.
  • The bridge channel was never hidden from the chat windows, and the code that tried would have raised if it had ever matched. Channel.lua:join scanned GetChatWindowMessages(i) looking for the bridge channel's name, but that API returns message group names (GUILD, SAY, SYSTEM) — the channel list is the separate GetChatWindowChannels(i), and Blizzard reads the two into RegisterForMessages and RegisterForChannels respectively. A group name can never equal a channel name, so the loop matched nothing and the hide step silently did nothing. Inside it, ChatFrame_RemoveChannel(frame, self.name) passed the frame's name string; since patch 1.15.9 that global is only a deprecation alias for ChatFrameMixin.RemoveChannel, which takes the frame as self, so a match would have raised on pairs(self.channelList). This is the fourth site broken by the same 1.15.9 refactor (after v1.13.2, v1.13.3 and v1.13.4) and the only one not reported by a player. Now scans the channel list with the correct stride and passes the frame object, over NUM_CHAT_WINDOWS rather than a hard-coded 10. Location: Channel.lua:join.
  • Hold-down timers could stop firing silently, and leaked a frame on every start. GwHoldDown:start created its OnUpdate frame into a bare local that went out of scope the moment the method returned; nothing else referenced it, because the handler takes frame as a parameter rather than capturing it. Whether the timer survived therefore depended entirely on the client never reclaiming the frame — and when it does not fire there is no error, the callback simply never runs. Since refresh_channels restarts these timers for the whole session, the flip side was that every start created a new frame that is never released. The frame is now created once, kept on the instance, and reused. Found by the new suite, which fails deterministically under the coverage runner and passes without it. Location: HoldDown.lua:start.
  • /gw logsize lots (and any non-numeric value for a numeric setting) threw a Lua error instead of printing the message explaining the mistake. gw.Error('%s setting must be numeric: %s', key) supplies two format specifiers and one argument, so string.format raised before the message could be written. Location: GreenWall.lua:GwSettingCmd.
  • Three defects in the vendored semantic-version library, all in the pre-release comparison layer that had never executed offline. Found once the vendored Lib/ files were brought under test. (1) A version carrying build metadata but no pre-release was rejected outright — the suffix pattern required a leading -, so 1.0.0+20130313144700, which semver 2.0.0 section 10 explicitly permits, parsed to nothing and a GW:v: line using it was silently discarded. (2) The comparison was not antisymmetric: an alphanumeric-versus-numeric identifier returned "less than" in both directions, so 1.0.0-alpha < 1.0.0-1 and 1.0.0-1 < 1.0.0-alpha were both true and any ordering built on it depended on argument order. (3) A shorter pre-release outranked a longer one — running out of fields on the left returned "greater than", making 1.0.0-alpha rank above 1.0.0-alpha.1, the reverse of the specification's own worked example. The library is GreenWall's own (Copyright 2010-2020 Mark Rogaski) and its output never crosses the wire — the minimum-version check is evaluated independently by each client — so fixing it changes no peer-visible behaviour. VERSION_MINOR is bumped from 1 to 2, and that bump is load-bearing rather than bookkeeping: LibStub:NewLibrary returns nil when oldminor >= minor, so at an unchanged minor this corrected copy would lose to any other addon vendoring the same very generic SemanticVersion-1.0 major that happened to load first — the fixes would ship and never execute. At minor 2 GreenWall's copy upgrades an older one in place, on the same table, so references captured by whoever registered first pick up the corrected behaviour. Location: Lib/SemanticVersion.lua.

Changes

  • gw.IsOfficer() no longer takes a target. Its documented target parameter had been silently ignored since the rank lookup was replaced by the GM-officer-note check (a workaround for 7.3.0 making GuildControlSetRank() protected). The rank-based helper it left behind was unreachable dead code and has been removed, along with the parameter — a signature that advertises a contract it does not honour is worse than none, and no caller passed one. Location: Utility.lua:IsOfficer.

Repo Tooling

  • Added an offline test suite: 427 examples, 100% line coverage across all 13 addon files and the three vendored libraries GreenWall owns. Built on the shared WoWAPITesting harness, carried as a git submodule at Tests/wowapi. It needs nothing but a Lua 5.1 interpreter — no LuaRocks, no busted, no C modules — and runs in well under a second: lua Tests/wowapi/run.lua from the addon root, with lua Tests/wowapi/coverage.lua <files> gating coverage at 100%. Every one of the defects above was found by writing the spec for how the code should behave and then reading the failure. Nothing here ships: Tests is ignored by the packager.
  • Removed the previous tests/ suite. The luaunit-based cases, the vendored luaunit.lua, MockAPI.lua, Loader.lua and run-tests.ps1 are gone, their coverage folded into the new specs. That suite needed the bitlib C module (and therefore a C toolchain) to run at all; the harness supplies bit in pure Lua. The upstream PR branches (pr/era-fixes-tests and friends, cut from upstream-*) keep their own copy and are unaffected.
  • Fixed .pkgmeta, which was shipping the entire development tree to players. Every folder entry carried a trailing slash (docs/, tests/) and the script globs used **/ (**/*.ps1). The BigWigs packager trims a trailing /* and not a bare /, so docs/ became docs//* and matched nothing; its case-based glob matching has no recursive **, so those globs never matched either. Both folders and both script globs were therefore being packaged. Rewritten to bare folder names and single-star repo-relative globs, with the no-op dot-entries dropped — the packager prunes those unconditionally.
  • Raised eight harness contracts and adopted the answers. Tests/HARNESS_CONTRACT.md records what GreenWall needed that the harness did not model: the chat-window subscription queries, the chat frames and their edit boxes, a steerable time(), the four missing ERR_GUILD_* strings, C_GuildInfo.GetInfoText, GetChannelDisplayInfo/GetChannelList, getglobal, and C_AddOns.GetAddOnInfo/IsAddOnLoaded. All eight were delivered upstream and the local stand-ins deleted; the submodule is pinned at 65de3af.
  • Added the .busted shim, taught .luarc.json the spec globals, and normalised the test directory to Tests — it had been tracked as lowercase tests, which the case-sensitive Linux packager would not have matched against the .pkgmeta entry.

Notes

  • No behaviour visible to players changed except the fixes above; the TOC, wire protocol and configuration grammar are untouched.
  • Lib/ is now under test too, with one deliberate exception and one structural limit. Lib/LibStub.lua is the canonical public-domain stub and GreenWall's copy correctly stands down in favour of whichever identical copy registered first, so almost none of it is reachable in a suite — that is the file working as designed, not a gap. Lib/SemanticVersion.lua sits at 98.86%: the single remaining line is a cmp(nil, nil) guard that cannot be reached, since the loop that calls it never runs past the longer operand. Left in place rather than deleted to keep the diff minimal for the upstream offer.
  • Lib/Base64BCA.lua is deliberately not changed. Its output is the only vendored library's that crosses the wire (a sender encodes an API payload and a peer decodes it), so a behavioural change there would break interop between GreenWall versions across a confederation. It is now covered at 100% and round-trips every byte value, so the encoding is pinned rather than merely assumed.

[v1.13.4] (2026-07-22) - Restore inbound bridging after the 1.15.9 chat-frame refactor

Bug Fixes

  • Co-guild messages stopped displaying on the stock UI after patch 1.15.9, throwing Chat.lua:110: attempt to call a nil value on every inbound message. The same chat-frame refactor that broke outbound bridging in v1.13.3 also removed the global ChatFrame_MessageEventHandler outright — it is now a method on each chat frame (ChatFrameMixin:MessageEventHandler), with no deprecation-fallback shim. GreenWall bound that global once at load in Compat.lua and uses it in Chat.lua:ReplicateMessage to inject received peer messages into the chat windows, so on the default UI it captured nil and every decoded peer message errored instead of displaying. Inbound receipt and decoding still worked (the debug log showed opcode=C segments arriving from peers) — only the final display call failed. This is why the breakage looked partial in the field: GreenWall overrides that same binding with ElvUI's and Prat's own handlers (Compat.lua), which still exist, so ElvUI/Prat users' inbound kept working while stock-UI users saw nothing (matching the reported "only some people can see messages" pattern). Now feature-detects: uses the global ChatFrame_MessageEventHandler where it still exists, otherwise adapts to the frame method (frame:MessageEventHandler(...)); the ElvUI/Prat overrides are untouched. Location: Compat.lua.

Notes

  • This completes the trio of WoW 1.15.9 (interface 11509) breakages, all stemming from the same client refactor of the chat frame and add-on APIs: v1.13.2 fixed the GetAddOnInfo login crash, v1.13.3 fixed outbound /g and /o bridging (the dead ChatEdit_ParseText hook), and this fixes inbound display of co-guild messages on the stock UI.

[v1.13.3] (2026-07-22) - Restore outbound bridging after the 1.15.9 chat-frame refactor

Bug Fixes

  • Guild and officer chat stopped bridging outbound after WoW patch 1.15.9 — messages reached local guild chat but were never sent to co-guilds. 1.15.9 (interface 11509) rebuilt the chat edit box into the mixin-based Blizzard_ChatFrameBase system. GreenWall forwarded your outgoing /g and /o messages to the bridge via hooksecurefunc("ChatEdit_ParseText", ...), but in the new system the global ChatEdit_ParseText is only a deprecation-fallback alias (ChatEdit_ParseText = ChatFrameEditBoxMixin.ParseText) — the client now calls the edit box's own ParseText method, so the hook on the global never fired. The addon still loaded and the bridge channel still connected (inbound worked), which is why /gw status showed connected=true while nothing you typed ever crossed; a debug 5 capture confirmed the type=GUILD, message=... hook line was absent on send. Verified against the 1.15.9 UI source that the new ChatFrameEditBoxBaseMixin:ParseText(send, ...) still exposes GetAttribute("chatType") and the stripped message via GetText(), so GreenWall's handler body is unchanged — only the hook target was wrong. Now hooks each chat edit box instance's ParseText method where the mixin exists (if ChatFrame1EditBox and ChatFrame1EditBox.ParseText then ... hooksecurefunc(editbox, 'ParseText', ...) ... end), and falls back to the legacy global hook on older clients — so every supported flavor keeps working. This same refactor already shipped on Retail (11.0) and Mists Classic, where the global hook was likewise dead. Location: GreenWall.lua.

Changes

  • Bumped the Classic Era TOC to interface 11509 for WoW 1.15.9. Only GreenWall.toc (Era) changed; the other five per-flavor TOCs keep their own interface versions. Location: GreenWall.toc.

Notes

  • This is the second half of the 1.15.9 breakage. v1.13.2 fixed the GetAddOnInfo login crash that the same patch introduced; this release fixes the separate, quieter outbound-bridging regression from the chat-frame refactor. Inbound replication (peer messages arriving in your guild window) was unaffected — it flows through CHAT_MSG_CHANNEL / ChatFrame_MessageEventHandler, not the edit-box hook.

Repo Tooling

  • Added ChatFrame1EditBox to the .luarc.json known-globals list so the new feature-detect reads clean under the Lua language server.

[v1.13.2] (2026-07-22) - Fix GetAddOnInfo nil-call crash and migrate deprecated chat/guild globals

Bug Fixes

  • GreenWallAPI crashed with attempt to call a nil value whenever a third-party addon touched the API — a WoW client update removed the loose global GetAddOnInfo, which now exists only as C_AddOns.GetAddOnInfo (confirmed in the generated AddOnsDocumentation under Namespace = "C_AddOns", with no deprecation-fallback shim, so the bare global is simply nil). Unlike the softer guild/chat deprecations below, this one is a hard removal: any caller reaching GreenWallAPI.SendMessage / AddMessageHandler / ClearMessageHandlers for a non-'*' addon id hit assert(addon == GetAddOnInfo(addon)) and threw immediately — observed in the wild as VersionCheck-1.0 calling AddMessageHandler at login. The rest of the addon had already migrated to the C_AddOns namespace in v1.12.0 (Globals.lua, Compat.lua); API.lua was the one file missed. Replaced all three call sites with C_AddOns.GetAddOnInfo. Location: API.lua.

Changes

  • Proactively migrated two now-deprecated globals off the client deprecation-fallback path. The same client update that removed GetAddOnInfo also demoted GetGuildInfoText and SendChatMessage to deprecated shims that live in Blizzard_DeprecatedGuildScript / Blizzard_DeprecatedChatInfo — they are now thin aliases for C_GuildInfo.GetInfoText and C_ChatInfo.SendChatMessage, load only when the loadDeprecationFallbacks CVar is set, and are documented in-client as "will be removed in the future." GreenWall still worked because that CVar defaults on, but the fork should not depend on a fallback that can be switched off or dropped in a later patch. Migrated both using the same feature-detect-with-fallback idiom v1.13.1 introduced for C_GuildInfo.GuildRoster: if C_GuildInfo and C_GuildInfo.GetInfoText then ... else GetGuildInfoText() end in Config.lua:load, and if C_ChatInfo and C_ChatInfo.SendChatMessage then ... else SendChatMessage(...) end in Channel.lua:tl_flush. The namespaced call is used wherever it exists (Era 1.15.x and every other current flavor) and the original global is preserved as the fallback, so no flavor regresses. The namespaced C_ChatInfo.SendChatMessage is the same restricted function as the global — this is a rename, not a taint/protection change — so the wire protocol and CRC-16 loopback accounting are unaffected. Locations: Config.lua, Channel.lua.

Notes

  • Full audit of the addon's WoW API surface against the updated Classic Era docs. Every loose global the addon calls was cross-checked against the generated API docs, the GlobalAPI.lua enumeration, and the Blizzard_Deprecated* shim files. Results: GetAddOnInfo was the only hard removal (fixed above); GetGuildInfoText and SendChatMessage were the only two deprecations (migrated above). Everything else the addon uses is still a first-class global with no namespaced-only replacement — GetChannelName, JoinTemporaryChannel, LeaveChannelByName, GetChannelList, GetChatWindowMessages (Channel.lua / Chat.lua / GreenWall.lua); GetGuildInfo, GetNumGuildMembers, GetGuildRosterInfo (Utility.lua); GetRealmName, UnitName, GetBuildInfo (Globals.lua / Utility.lua / SystemEventHandler.lua); and GetCVar (a real global defined in Blizzard_SharedXMLBase/CvarUtil.lua, GreenWall.lua). No further changes were required.
  • This supersedes the v1.13.0 note that listed GetGuildInfoText and SendChatMessage as "verified portable" loose globals — that was accurate when written, before Blizzard moved them behind the deprecation-fallback CVar.

Repo Tooling

  • Added TestConfigInfoText regression coverage for the new guild-info-text source selection, mirroring the existing TestConfigReload cases: verifies GwConfig:load() prefers C_GuildInfo.GetInfoText when present, and falls back to the GetGuildInfoText global both when C_GuildInfo is absent and when the table exists but lacks the method. Updated TestAPI's setUp to stub C_AddOns.GetAddOnInfo instead of the removed global. Full offline harness green (60 cases). Locations: tests/TestConfig.lua, tests/TestAPI.lua.

[v1.13.1] (2026-05-19) - Fix GuildRoster() nil-call crash on non-Era flavors

Bug Fixes

  • GuildRoster() crashed on TBC Classic / Wrath / Cata / Mists / Retail with attempt to call global 'GuildRoster' (a nil value) — v1.13.0 added per-flavor TOCs but did not audit the Lua source for API calls that were Era-only. Blizzard moved GuildRoster() into the C_GuildInfo namespace years ago (it remains as a loose global only on Classic Era 1.15.x); on every other flavor the global is nil, so GwConfig:reload() threw on the very first config load and the addon was unable to bootstrap its bridge configuration. Replaced the bare GuildRoster() call with if C_GuildInfo and C_GuildInfo.GuildRoster then C_GuildInfo.GuildRoster() else GuildRoster() end — modern API on flavors that have it, original global preserved for Era so existing 1.15.x users see no behavior change. Reported on TBC/Anniversary. Location: Config.lua:reload.

Repo Tooling

  • Refreshed CLAUDE.md to match post-v1.12.0 / post-v1.13.0 reality. The "Changelog rules" section described the legacy ## X.Y.Z -- YYYY-MM-DD / ### Fixed format, but the project switched to the togpm format (## [vX.Y.Z] (YYYY-MM-DD) - Title headers with prose-rich ### Bug Fixes / ### New Features / ### Changes / ### Notes / ### Lint Pass / ### Repo Tooling sections) in v1.12.0; updated the rules to document the togpm format. Added a new TOC rules section: TOCs now use the GreenWall-v1.15.3 packager substitution token (no manual ## Version: bump), there is no ## X-Date: line, and there are 6 flavor-specific TOC files to keep in sync — the previous CLAUDE.md still instructed bumping ## Version: and ## X-Date: in a single GreenWall.toc, neither of which exists anymore.
  • Fixed three pre-existing markdownlint errors in CLAUDE.md so the file lints clean under the project's .markdownlint.json config. (1) Saved-variables and GW: directive tables used the compact alignment-row style (|---|---|---|) while their data rows used padded pipes — mismatched style flagged as MD060/table-column-style. Normalized both alignment rows to padded style (| --- | --- | --- |). (2) The GreenWallMeta row contained an unescaped | inside a code span (mode = 'account' | 'character') which MD056/table-column-count parsed as an extra column separator. Escaped the pipe to \|. (3) The wire-segment example fence had no language hint — flagged as MD040/fenced-code-language. Tagged it as text.

[v1.13.0] (2026-04-30) - Multi-flavor support: per-flavor builds for Classic Era, BCC, Wrath, Cata, Mists, and Retail

GreenWall has been Classic-Era-only since the fork started, by virtue of a single-flavor TOC. The Lua source itself was already portable — every API the addon touches has been present on Retail and the intermediate Classic flavors for years. This release is the packaging change that lets the BigWigs packager produce a build per flavor from one source tree.

New Features

  • Per-flavor TOC files added. The CurseForge / BigWigs packager auto-detects flavor-suffixed TOCs and builds one zip per flavor on every tagged release. The unsuffixed GreenWall.toc remains the Classic Era 1.15.x build (Interface 11508). New siblings:

    All TOC bodies are byte-for-byte identical except for ## Interface: and ## X-Min-Interface:. The packager workflow at .github/workflows/release.yml is unchanged and picks them up automatically.

Notes

  • No Lua source changes. Surfaces verified portable across all supported flavors: Settings.RegisterCanvasLayoutCategory / Settings.RegisterAddOnCategory (GreenWall.lua), C_AddOns.GetAddOnMetadata (Globals.lua), C_AddOns.IsAddOnLoaded (Compat.lua), C_ChatInfo.RegisterAddonMessagePrefix (GreenWall.lua), the channel API surface (JoinTemporaryChannel, LeaveChannelByName, GetChannelName, GetChannelDisplayInfo, SendChatMessage/'CHANNEL', ChatFrame_RemoveChannel, Channel.lua/Utility.lua), the localized ERR_* constants used by SystemEventHandler.lua, GetGuildInfoText and the rest of the guild-roster API used by Config.lua and Utility.lua, and the hooksecurefunc("ChatEdit_ParseText", ...) outbound hook in GreenWall.lua. Defensive compat shims (Settings.* or InterfaceOptions_AddCategory, C_AddOns or _G) were considered and skipped — there is no live flavor where any of these is absent.
  • Transport is unchanged and works on every flavor. The custom-chat-channel transport (JoinTemporaryChannel + SendChatMessage(..., 'CHANNEL', ...)) carries across connected-realm clusters on every flavor of WoW. Guild membership is itself bounded to a cluster, so no cross-cluster transport rework was needed for Retail.

[v1.12.0] (2026-04-29) - First Pimptasty fork release: long-standing bug fixes, dead-code cleanup, modernized TOC

This is the first release of the Pimptasty fork. Mark Rogaski's original work covers releases 0.9.00 (2010-11-01) through 1.11.18 (2025-05-24); see LICENSE for full attribution.

Bug Fixes

  • Roster announcements duplicating for every co-guild member crossing the bridge channel — GwHoldDownCache:hold had an inverted comparison since v1.5.3 (2014-11-11): if self.cache[s] > t + self.interval then rv = true is structurally never true (a stored timestamp can't be greater than a later timestamp plus an interval), so the comember-cache that was supposed to dedupe online/offline announcements always returned false. Every member of every co-guild logging in produced two notifications: one from the natural CHAT_MSG_SYSTEM event (ERR_FRIEND_ONLINE_SS), and one generated by GreenWall's CHAT_MSG_CHANNEL_JOIN handler. The cache's pruning logic was also broken: #self.cache > self.soft_max always evaluates 0 because the cache is hash-keyed by player name, and table.remove(self.cache, k) is the wrong API for a string key. Replaced the comparison with t - self.cache[s] < self.interval, the length check with a pairs() enumeration, and the deletion with self.cache[k] = nil. Location: HoldDown.lua:GwHoldDownCache:hold.

  • Stale channels not cleared after Guild Information edits — GwConfig:load()'s cleanup loop used for _, channel in ipairs(self.channel) do over self.channel = { guild = ..., officer = ... }. ipairs only iterates contiguous numeric keys starting at 1, so the loop body never ran. When officers edited Guild Information to remove a channel directive, the addon would keep transmitting on the now-orphaned channel until the player ran /gw reset or relogged. Switched to pairs. Location: Config.lua:load.

  • GreenWallAPI.RemoveMessageHandler was broken since the API was introduced in v1.7.0 — the function had three bugs in its 14 lines: rv = false was a leaked global; the if addon ~= '*' then addon = GetAddOnInfo(addon); assert(addon ~= nil) end block referenced addon, which is not a parameter of this function (it was copy-pasted from ClearMessageHandlers); and gw.api_table[i] = nil left a hole that stopped subsequent ipairs walks at the gap. Any third-party caller using the documented API hit undefined behavior on every call. Rewrote to match the documented contract and switched to table.remove. Location: API.lua:RemoveMessageHandler.

  • gw.IsLegendary leaked _ and rarity globals on every call — _, _, rarity = GetItemInfo(item) had no local declaration. Function was an orphan from the legendary-loot-replication feature removed in v1.11.0 (Blizzard made SendChatMessage partially protected in patch 8.2.5, killing the feature). Deleted gw.IsLegendary and gw.GetItemString. Also deleted other v1.11.0 orphans missed at the time: GwPromoteSystemEventHandler (never dispatched by the factory), the rank field destructured from GW_MTYPE_BROADCAST payloads in gw.handlerGuildChat, and an unused local semver = LibStub:GetLibrary(...) import. Locations: Utility.lua, SystemEventHandler.lua, Chat.lua.

  • Invalid Lua 5.1 escape sequence in the gw.Debug call-stack parser — the pattern string 'in function \`([%a_-]+)\'' used a backslash followed by a backtick, which is not a valid escape sequence per the Lua 5.1 spec (only \a \b \f \n \r \t \v \\ \" \' \[ \] \xxx are recognized). WoW's interpreter accepted it (treating unknown escapes as the literal char), but lua-language-server flagged it as an error-severity diagnostic. Replaced with a literal backtick — runtime behavior unchanged. Location: Utility.lua:gw.Debug.

  • Malformed 'id=%, addon=%s, priority=%d' debug format string in two gw.Debug calls in API.lua — %, is not a valid format specifier; string.format would error if the message was ever logged at debug level 4+ (GW_LOG_INFO for the add API handler / remove API handler messages). Replaced with %s. Location: API.lua.

  • GreenWallAPI.ClearMessageHandlers left holes when removing multiple matching handlers — gw.api_table[i] = nil while iterating with ipairs causes the iterator to stop at the first nil. If two handlers for the same addon were registered, only the first one would be cleared. Switched to reverse-iteration with table.remove so deletions properly compact the array. Location: API.lua:ClearMessageHandlers.

  • Redundant self:initialize_param(true) argument in GwConfig:reset() — the method takes no parameters; the true was silently ignored. Cosmetic but flagged as a redundant-parameter warning by lua-language-server. Location: Config.lua:reset.

Lint Pass

  • Cleaned trailing whitespace, dead local declarations, and unused loop variables (_ substitution) across Config.lua, Utility.lua, Channel.lua, and Chat.lua. All Lua source files now lint clean under the bundled .luarc.json configuration.

Changes

  • Updated TOC for WoW Classic 1.15.8 (Interface 11508).
  • Modernized the TOC. Switched ## Version to the CurseForge packager substitution token GreenWall-v1.15.3. Replaced legacy ## X-Category: Guild with the modern ## Category: Guild. Added ## X-Min-Interface: 11508, ## OptionalDeps: ElvUI, Prat-3.0, Identity-2, Name2Chat, Incognito (one per chat-addon GreenWall has compat shims for in Compat.lua), ## X-License: MIT, and ## X-Curse-Project-ID. Dropped ## DefaultState: enabled (default behavior anyway), ## X-Date: 2025-05-24 (CHANGELOG is the source of truth), and the two ## URL: lines (now declared via X-Curse-Project-ID and .pkgmeta). Location: GreenWall.toc.
  • Future changelog entries use the togpm format — ## [vX.Y.Z] (YYYY-MM-DD) - Short Title headers, prose-rich Bug Fixes / New Features / Changes / Improvements sections, root-cause + symptom + fix structure, file-link locations. Older entries (v1.11.18 and below) keep their original format.

Repo Tooling

  • Filled in LICENSE. It was the unfilled MIT template (Copyright (c) <year> <copyright holders>) since the project began. Mark Rogaski credited for 2010-2025 (releases 0.9.00 through 1.11.18); Pimptasty credited for 2026+ modifications. The MIT license body is unchanged.
  • Added CLAUDE.md — architectural guidance for working in the codebase: file load order, GwChannel / GwConfig / GwSettings class roles, the GW: configuration-directive grammar, the hidden-channel wire protocol with CRC-16 loopback detection, the public GreenWallAPI surface, and the changelog/commit conventions used by this fork.
  • Added docs/curseforge_description.html — CurseForge listing copy, the source of truth for the project's listing page. Updated on every release alongside the changelog.
  • Added repo configs: .pkgmeta (CurseForge packager), .luarc.json (lua-language-server config tuned to GreenWall's API surface), .markdownlint.json, .markdownlintignore.
  • Author line in GreenWall.toc now reads Mark Rogaski <stigg@aie-guild.org>, Pimptasty.

1.11.18 -- 2025-05-24

  • Fixed TOC date.

1.11.17 -- 2025-05-24

  • Fixed Lua errors. Vielen dank, Jan Heise.
  • Updated TOC for WoW Classic 1.15.7.

1.11.16 -- 2024-11-10

  • Fixed interface options registration for removal of InterfaceOptions_AddCategory.

1.11.15 -- 2024-10-24

  • Updated TOC for WoW Classic 1.15.4.

1.11.14 -- 2024-08-13

Fixed

  • Replaced deprecated GetAddOnMetadata and IsAddOnLoaded with C_AddOns namespaced functions.

1.11.13 -- 2024-07-31

Fixed

  • Removed reference to undefined variable.

1.11.12 -- 2024-07-30

Fixed

  • Fix for Greenwall settings not displaying in Options>AddOns Tab and the related 'InterfaceOptions_AddCategory' (a nil value) error.

Updated

  • Updated the TOC for WoW 11.0.0.

1.11.11 -- 2023-11-11

Updated

  • Updated the TOC for WoW 10.2.0.

1.11.10 -- 2023-05-06

Updated

  • Updated the TOC for WoW 10.1.0.

1.11.9 -- 2023-02-17

Updated

  • Updated the TOC for WoW 10.0.5.

1.11.8 -- 2022-10-26

Updated

  • Updated the TOC for WoW 10.0.0.

Removed

  • Removed an unsupported anchor positioning element.

1.11.7 -- 2022-06-20

Updated

  • Updated the TOC for WoW 9.2.5.

1.11.6 -- 2021-09-03

Updated

  • Updated the TOC for WoW 9.1.0.

1.11.5 -- 2021-03-14

Updated

  • Updated the TOC for WoW 9.0.5.

1.11.4 -- 2020-12-23

Updated

  • Updated the README to point to the current connected realm documentation.

1.11.3 -- 2020-12-19

Changed

  • Updated the TOC for WoW 9.0.2.

1.11.2 -- 2020-10-13

Fixed

  • Replaced usage of deprecated GuildRoster() with C_GuildInfo.GuildRoster().

1.11.1 -- 2020-08-09

Fixed

  • Added a conditional check for successfully parsed addon version for minimum version enforcement. This is a tactical fix to address the semantic version parsing.

1.11.0 -- 2020-04-27

Removed

  • Removed replication of achievements, promotions, demotions, and loot announcements between co-guilds. The SendChatMessage function was made partially protected in 8.2.5, and events that are not triggered by hardware events cannot use the function.
  • Removed unnecessary local co-guild use of the addon communication channel.

Added

  • GreenWallAPI.GetChannelNumbers function to query the custom chat channels in use by GreenWall

Changed

  • Updated the TOC for WoW 8.3.0.
  • Refactored CHAT_MSG_SYSTEM handling to use an abstract factory and added full unit testing for the polymorphic classes.

Fixed

  • Updated debug message call stack parsing for 8.3.

1.10.1 -- 2019-09-24

Updated

  • Updated the TOC for WoW 8.2.5.

Added

  • Added unit test coverage reporting through Coveralls.

1.10.0 -- 2019-09-05

Changed

  • Moved to customer branch approach for WoW Classic releases.

Removed

  • Removed transitional code for handling PLAYER_ENTERING_WORLD.

1.9.15 -- 2019-08-27

Changed

  • Cleaned up project to support CurseForge automatic packaging.

1.9.14 -- 2019-08-22

Added

  • Added compatibility support for Incognito.

1.9.13 -- 2019-07-02

Updated

  • Updated the TOC for WoW 8.2.

Removed

  • Removed Gitter badge from documentation.

1.9.12 -- 2019-01-10

Fixed

  • Added missing message alteration for Name2Chat.

1.9.11 -- 2019-01-06

Fixed

  • Added ID tagging support for Identity-2 and Name2Chat under the WoW 8.1.0 changes.
  • Changed legendary loot notifications from second-person to third-person.

1.9.10 -- 2019-01-04

Fixed

  • Fixed GreenWall_ParseText message filtering to filter blank messages.

Added

  • Added warning for any transmission of a blank message on a channel.

Changed

  • Removed unused CRC import.

1.9.9 -- 2018-12-13

Fixed

  • Added chat interception workaround for guild chat changes in BFA 8.1.

Thank you to Ashayo for the patch.

1.9.8 -- 2018-08-23

Changed

  • Refactored system message pattern matching.

1.9.7 -- 2018-08-11

Fixed

  • Removed a debug statement that was raising format errors.

Thank you to Legracen from AIE for helping with the fault isolation.

Changed

  • Refactored gw.Debug to test the debug level before formatting the message.

1.9.6 -- 2018-08-10

Fixed

  • Restored context menu availability for speakers in other co-guilds.

1.9.5 -- 2018-07-19

Fixed

  • Checks for ElvUI compatibility now recognize ElvUI user profiles.

Thank you, again, to Simpy from the ElvUI team.

1.9.4 -- 2018-07-19

Fixed

  • Applied workaround for chat channel sender regression introduced in 8.0.1.

Removed

  • Removed pre-8.0 call to bare SendLocal.

1.9.3 -- 2018-07-17

Updated

  • Updated the TOC for WoW 8.0.

1.9.2 -- 2018-06-11

Fixed

  • Updated compatibility mode for ElvUI 10.74 changes to chat.
  • Removed combat log from chat window scanning.

Thank you to Simpy from the ElvUI team for the fixes.

1.9.1 -- 2018-04-28

Fixed

  • SendAddonMessage is now called in the C_ChatInfo namespace for 8.0.x.

Changed

  • Moved WoW build information to gw.build global.

1.9.0 -- 2018-01-08

Added

  • Added support for shared, account-level settings per character. Each character can either use the account settings or a character-specific configuration. If an existing GreenWall configuration exists for the character character mode will be the default, otherwise account mode is the default.
  • Added confederation announcements of legendary items looted.
  • Moved configuration handling to a proxy class to allow a single point of access for all configuration data.
  • Added Travis CI build testing with luaunit for unit tests.
  • Added a dedication in memoriam to Roger K White (aka Ralff), we will always miss you.

Fixed

  • Corrected item string matching for legendary loot toasts.

Changed

  • Pointed documentation to new CurseForge URL.

Updated

  • Rewrote the project README.

Removed

  • Removed luadoc HTML output.
  • HTML copies of other documentation.

1.8.5 -- 2017-08-29

Fixed

  • Applied a workaround for 7.3.0 where GuildControlSetRank() was made protected. Checking for rank access is now determined by reading the officer note of the GM.

1.8.4 -- 2017-08-29

Updated

  • Updated the TOC for WoW 7.3.

1.8.3 -- 2017-04-16

Updated

  • Updated the TOC for WoW 7.2.

1.8.2 -- 2017-01-12

Added

  • The version command now give WoW version information.

1.8.1 -- 2016-10-25

Fixed

  • Corrected configuration whitespace grooming.

Changed

  • Updated TOC for WoW 7.1.

1.8.0 -- 2016-09-20

Fixed

  • Added whitespace trimming to configuration parser.

Changed

  • Compatibility messages are now raised as debugging output.

1.7.3 -- 2016-08-03

Fixed

  • Corrected global name parsing to account for UTF-8.
  • Updated API documentation.

1.7.2 -- 2016-07-20

Fixed

  • Workaround for delayed bridge channel join caused by missing CHAT_MSG_CHANNEL_NOTICE events.

1.7.1 -- 2016-07-20

Changed

  • Updated TOC for WoW 7.0.3.

1.7.0 -- 2016-01-01

Fixed

  • Improved message validation during adaptation layer decoding.
  • Fixed message handling logic.

Changed

  • Updated color of addon messages in chat.

Added

  • Confederation bridging API for third-party add-ons.
  • Automatic Prat-3.0 compatibility mode.

Removed

  • Removed version number from options screen title.

1.6.6 -- 2015-12-16

Fixed

  • A check is now done for officer status before sending a gratuitous officer announcement.
  • Added officer note data validation before parsing.

Added

  • Automatic ElvUI compatibility mode. Thank you, Blazeflack.
  • Added Markdown change log.
  • Added channel-specific hold downs for join failures.

1.6.5 -- 2015-06-24

Changed

  • Updated TOC for WoW 6.2.

1.6.4 -- 2015-03-07

Changed

  • Refactored the user option validation.
  • Modified the GwHoldDown object. Renamed GwHoldDown:set() to GwHoldDown:start() and created a new 'GwHoldDown:set()' mutator to change the interval.
  • Moved the Semantic Versioning parser to a LibStub library.

Added

  • Added a user option (joindelay) to control the channel join hold-down.

1.6.3 -- 2015-03-04

Changed

  • Restored original channel hold-down timer value of 30 seconds.

1.6.2 -- 2015-03-01

Changed

  • Removed lazy sweeper case in the event loop and replaced it with a callback for handling hold-down expiry.
  • Reduced channel hold-down to 10 seconds.

1.6.1 -- 2015-02-28

Fixed

  • Added conditional to check achievements flag on receipt of achievement spam.

Added

  • Added guild ID to debugging output on message receipt.

1.6.0 -- 2015-02-24

Changed

  • Comprehensive refactor to allow new features in 2.0.
  • Refactored configuration parser.
  • Split GreenWall_Core.lua into multiple files.
  • Removed excess semicolons ... Lua is not Perl.

Added

  • Added GwConfig object to contain configuration.
  • Added GwChannel objects for channel management, implementing transport and adaptation layers for communication.
  • Added GwHoldDown and GwHoldDownCache objects.
  • Added LibStub libraries for SHA256, Salsa20, CRC16-CCITT, and Base64.

1.5.4 -- 2014-12-12

Fixed

  • Corrected comparisons of character names to account for capitalization normalization in the API.

Changed

  • Updated luadoc.

1.5.3 -- 2014-11-11

Fixed

  • Added comember cache updates for channel join/leave events. This stops flapping roster announcements for characters in peer co-guilds.

1.5.2 -- 2014-10-31

Fixed

  • Fixed the General chat delay lockout.
  • Improved the tests for officer status.

1.5.1 -- 2014-10-15

Fixed

  • Fixed regular expression for realm name in GwGlobalName.

1.5.0 -- 2015-10-14

Fixed

  • Updated for fully qualified names.

Changed

  • Switched to MIT license.
  • Minor changes to debug messages.

Added

  • Added support for guilds on connected realms.

1.4.1 -- 2014-08-10

Fixed

  • Corrected the processing of the reload request.

1.4.0 -- 2014-03-22

Changed

  • Update documentation for Interface Options.
  • Cleaned up debugging levels.

Added

  • Added Interface Options panel for GreenWall options.

1.3.6 -- 2014-02-18

Fixed

  • Fixed sender identification under WoW 5.4.7.

Added

  • Added realm name to gwPlayerName.

1.3.5 -- 2014-0120

Added

  • Added missing roster notification functionality.

1.3.4 -- 2013-11-17

Changed

  • Changed officer note format and updated parsing.

1.3.3 -- 2013-09-10

Changed

  • Updated TOC for WoW 5.4.0.

1.3.2 -- 2013-08-07

Fixed

  • Fixed message integrity checking for duplicated messages.
  • Fixed messages generated on guild join, leave, or kick.
  • Corrected formatting of documentation.

1.3.1 -- 2013-08-06

Changed

  • Project moved to GitHub.
  • All text documentation has been converted to Markdown and HTML.
  • All URLs have been updated in the TOC.

Added

  • Guild configuration format documentation has been added.

1.3.0 -- 2013-06-09

Fixed

  • Fixed handling of a kick from a guild.
  • Fixed variable names for input validation in GwStringHash().

Changed

  • Simplified the unpacking of inter-guild messages.

Added

  • Added support for a newer, compact configuration format.

1.2.7 -- 2013-02-27

Changed

  • Updated TOC for WoW 5.2.0.

1.2.6 -- 2012-12-04

Changed

  • Updated TOC for WoW 5.1.0.

1.2.5 -- 2012-09-01

Fixed

  • Localized _ to avoid the taint issues with glyphs in 5.0.4.

1.2.4 -- 2012-08-29

Changed

  • Updated TOC for WoW 5.0.4.

1.2.3 -- 2012-08-25

Changed

  • Replaced the 32-bit string hash used to obfuscate channel names in the debugging output with a standard CRC-16-CCITT implementation to avoid overflow issues with string.format() in MoP.
  • Made some changes to the debugging code to improve visibility into message passing and replication.

Added

  • Added extra debugging information for current guild information.
  • Added value checking for missing coguild ID in GwSendConfederationMsg().

1.2.2 -- 2012-11-12

Fixed

  • Fixed officer chat bridging for the guild leader.

1.2.1 -- 2011-11-29

Changed

  • Updated TOC for WoW version 4.3.

Added

  • Added link to LemonKing's add-on in the documentation.
  • Documented prequisites.

1.2.0 -- 2011-11-26

Fixed

  • Fixed unnecessary channel resets due to configuration reload.
  • Fixed behavior during UI reload.
  • Corrected join/leave handling.
  • Corrected case statement for handling chat events.

Changed

  • Switched to Semantic Versioning.
  • Separated guild info parsing from the GwRefreshComms() function.
  • Factored out some ugly flags.
  • Cleaned up CLI configuration.
  • Cleaned up debugging output.
  • Cleaned up guild info handling.
  • Parameterized all channel control functions.
  • Masked all channel names and passwords in debugging output.

Added

  • Added officer chat support.
  • Added message queuing.
  • Added a GwIsOfficer() check to the officer chat configuration phase to avoid pointless work.
  • Added broadcast message type.
  • Added broadcasts of guild join and leave events.
  • Added broadcasts of promote and demote messages.
  • Added hold-down for reconfiguration.
  • Added broadcast receiver code.
  • Added string hash to determine changes in text fields.
  • Added error checking for officer note parsing failure.
  • Added README.txt and GUILD_QUICKSTART.txt.

Removed

  • Removed channel protection code.
  • Removed SHA1 library.

1.1.07 -- 2011-06-28

Changed

  • Updated TOC for WoW version 4.2.

1.1.06 -- 2011-03-21

Changed

  • Babel now disabled by default.

1.1.05 -- 2011-03-21

Changed

  • Moved scan of chat windows to chat message event handlers.
  • Sorted clauses in the main event switch for legibility.

Added

  • Added GwReplicateMessage() function.
  • Added RegisterAddonMessagePrefix call for 4.1 changes.
  • Added Babel.

1.1.04 -- 2011-01-15

Changed

  • Minor updates for Curse packager.
  • Cleaned up status display code.

Added

  • Added BSD-derived license.

1.1.03 -- 2011-01-14

Changed

  • Moved GuildRoster call to PLAYER_LOGIN handler.
  • Limited conditions under which reinitialization occurred on PLAYER_GUILD_UPDATE.
  • Renamed gwPlayerGuild to gwGuildName.
  • Cleaned up prep/refresh/join flow for connecting to the common channel.

Added

  • Added a delay mechanism to prevent hijacking of general channel.
  • Added connection statistics gathering.
  • Added LuaDoc data for functions and procedures.

1.1.02 -- 2010-12-11

Fixed

  • Fixed missing assignment of channel number on join.

Changed

  • Redacted sensitive data in the status output.

Added

  • Added hold-downs for join and configuration messages.
  • Added a configuration flag to enable replication of achievement messages.
  • Added frame identifier to Tx debug messages.
  • Added help text with command listing.

Removed

  • Removed tabs from the source code!
  • Removed event registration for channel leave events.
  • Removed squelch message on reload flood.

1.1.01 -- 2010-12-06

Fixed

  • Fixed status output when no channel has been configured.

Added

  • Added co-guild tagging.

1.1.00 -- 2010-12-05

Added

  • Added announcement flag check for logout announcements.

Removed

  • Removed unused moderation release code.

1.0.18 -- 2010-12-04

Added

  • Added options line to configuration.

1.0.17 -- 2010-12-04

Fixed

  • Fixed SavedVariables processing.

1.0.16 -- 2010-12-04

Fixed

  • Removed moderator handling and switched to better handling of owner status.

1.0.15 -- 2010-12-03

Fixed

  • Removed faulty tContains() for gwPeerTable checks to stop prolific kicking.

1.0.14 -- 2010-12-03

Fixed

  • Fixed missing argument to GetGuildInfo().

1.0.13 -- 2010-12-03

Changed

  • Updated guild change/update handling.

1.0.12 -- 2010-12-03

Fixed

  • Fixed /who and channel join event processing.

1.0.11 -- 2010-12-03

Fixed

  • Limited handling of channel owner/moderator changes to the common channel.

Changed

  • Simplified and improved GwIsConnected().

Added

  • Added a nil result check for the system message regex.
  • Added gratuitous container officer response on channel join.
  • Suspend confederation messages until container ID is known.
  • Limit scope of the channel debugging.

Removed

  • Removed proactive channel leave before a join.

1.0.10 -- 2010-11-22

Changed

  • Rewrote guild lookups.

Added

  • Added extra slash commands.

1.0.09 -- 2010-11-21

Fixed

  • Missing negation in channel defense code.

1.0.08 -- 2010-11-21

Fixed

  • Corrected guild join handling.

Added

  • Added more debugging code.

Removed

  • Removed channel bans.

1.0.07 -- 2010-11-17

Fixed

  • Typos in variable names.

1.0.06 -- 2010-11-13

Added

  • Added online/offline notices.

1.0.05 -- 2010-11-13

Fixed

  • Fixed the placement and use of GwLeaveChannel().

Changed

  • Changed container messaging system to generalize the request message type.

1.0.04 -- 2010-11-13

Fixed

  • Fixed container recognition in configuration processing.

Added

  • Added debugging output to slash command handling.

1.0.03 -- 2010-11-12

Changed

  • Changed configuration to support common configurations across co-guilds.
  • Refactored configuration parsing.
  • Cleaned up slash command handling.

Added

  • Added variable field to the saved variables.

1.0.02 -- 2010-11-12

Added

  • Added container IDs to channel messages to avoid duplicates within the same co-guild.

1.0.01 -- 2010-11-12

Added

  • Brought back GUILD_ROSTER_UPDATE to get around the guild info loading delay.

1.0.00 -- 2010-11-11

Changed

  • Cleaned up debugging statements.

Added

  • Added moderator/owner status handling.
  • Added kick/ban handling for interlopers.
  • Added channel leave if player leaves the guild.
  • Finished defensive ownership/moderation handling.
  • Added handling for guild achievements.
  • Added forced reload.

Removed

-Removed GUILD_ROSTER_UPDATE event handling.

0.9.02 -- 2010-11-06

Fixed

  • Fixed parsing of peer entries in configuration.

Changed

  • Expanded debugging code.

Removed

  • Removed slash command code, left stub.

0.9.01 -- 2010-11-06

Changed

  • Abstracted several functions.

Added

  • Added peer configuration entries.

0.9.00 -- 2010-11-01

Initial commit.

This mod has no additional files