GreenWall-v1.15.3
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
/reloadfor example, comes back knowing nothing, so the next guildmate in line whispers it a newyoursmessage ("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 bybridge_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", andbridge_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, whichbridge_e2e_specfound 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 review8fe83074, 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 bybridge_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", andbridgewiring_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.2once packaged, andsemver()of it was nil, so the check was skipped. The test env had always reported a bare1.13.4. It now reads the version through the samegw.VersionText. Pinned byconfig_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
## Versionas it is, and the packager fills that with the tag name:GreenWall-v1.15.2(the packaged GuildRoster TOC on this machine readsGuildRoster-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 reviewafc1fd93, 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 bare1.15.0-style versions. The tag prefix is now cut before parsing and only thex.y.zcore is kept (gw.VersionText, in Utility.lua; a suffix with a second hyphen, as an untagged build's may have, also parsed nil, peer review8fe83074, 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 bybridgewiring_spec"reads a released client's version, which carries the packager's tag name", which fails without it, andutility_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
testand 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]: textfrom 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]: textnaming a player outside the guild is not forwarded; and another guild's member without GreenWall who quotes one of our guildmates as[Name-Realm]: textis 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 review4f5a33b4, 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 bybridge_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.AddMessageHandlerignored 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 unstabletable.sortleft them (peer reviewa9425470, F1). Handlers now run lowest priority first, and equal priorities in the order they were registered. Pinned byapi_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
RemoveMessageHandlercould remove only one of the two. The id counted the handler's live registrations; it now comes from a counter that only increases (peer reviewa9425470, F2). Pinned byapi_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]: xand[Pickyminer-OldBlanchy]: xfor one line (peer review33f1c762, 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 review33f1c762, N1). A line with a[Name]: textattribution 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 reviewf9aa0e5e, 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 reviewf9aa0e5e, 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 Helmnames nobody and is forwarded as before (peer reviewf92cb2d8, 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 bybridge_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", andbridgewiring_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]: textunder 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]: textand 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 reviewf2027b78, 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 thebridge_specgroup "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 throughgw.BridgeName/gw.BridgePlayer, which ask LibGuildRoster (NormalizeName,GetNormalizedPlayer), andgw.BridgeDisplayand 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 afterGW_BRIDGE_TAKEOVERseconds, 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 review8c9d0685, R1; pinned bybridge_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 bybridge_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 usegw.GlobalName. Pinned bybridgewiring_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 thebridge_e2e_specrepost 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 bybridgewiring_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]: hiwhile 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.SendChatwere never reposted, so members without GreenWall missed them. Peer reviewafc1fd93(N5). They now reach the bridge as a typed co-guild line does. Location: BridgeWiring.lua:gw.BridgeOnDeliver. - A guild change without a
/reloadkept the old guild's relay-mode record, bridge and contacts. Peer reviewafc1fd93(N8). Relay mode now starts over for the new guild, but only once the guild configuration has been parsed for it: until thenguild_idcan 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-auditaf3376b0, F3; read in the code, not seen in game).GwConfignow 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 reviewc16361fb, 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/gquitor a kick, stops it (peer reviewc16361fb, NEW-B). A guild with no GreenWall configuration never gets relay mode. Pinned bybridgewiring_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 bybridgewiring_spec"draws a repost under its speaker, on the bridge's realm unless it names one" andbridge_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 likeAnn-Azjol-Nerubsplit 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 aGW:p:line written asGuild-Azjol-Nerubstill 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 byutility_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 on8504c38e). 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 bybridgewiring_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 bybridge_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) toC_ChatInfo.IsChatLineCensored, whosechatLineis a non-nilable number (ChatInfoDocumentation.lua, classic_era), andgw.ReplicateMessagepassed nil when its caller had no line id. It now passes0, as it already does for the other unused numeric arguments. The client does not treat line0as censored: such a line shows the speaker as a clickable name (operator, in game, 2026-10-11; self-audit8504c38e, F1). Pinned bychat_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 withoutEnable) instead of a debug line (self-audit8504c38e, F3). Pinned bygreenwall_spec"reports a missing library in chat". Location: GreenWall.lua:gw.RegisterVersionCheck. - GreenWall was still missing from
/vcin 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 atPLAYER_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, withEnable returned nothingas 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 bygreenwall_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 review10ff04e7, 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 bybridge_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-0returns 404); it now points atversioncheck, the slug.pkgmetaalready 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 helpliststraceas 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
SendChatlines 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-auditaf3376b0, F1). gw.BridgeDisplayalways takes the bridge's name; its fallback to our own realm had no caller left (self-auditaf3376b0, 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_TAKEOVERseconds. Location: Bridge.lua. /gw bridgeshows each guild's bridge, the term and the candidates. Location: BridgeWiring.lua:gw.BridgeCommand.- Comm tracing:
/gw traceswitches per-category tracing (relay,bridge,sync,leaf,forward,repost,channel),/gw debugtaband/gw debugtabremoveopen and close aGW Debugchat 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,
.pkgmetaand the README. Every relay-mode addon message goes through one DeltaSync-1.0 host (namespaceGW_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) andGreenWallTrace(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 bridgein The Old Gods showedbridge: 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 bridgesays 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 onwas 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 ongw.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'sIsOfficer, the officer-note permission (C_GuildInfo.CanViewOfficerNote) the other addons already share.gw.IsOfficeris 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.BridgeIsUserlooked up the bare name, but VersionCheck files every peer underName-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
MarkOnlinein 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 bridgeshowed 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 toGW_BRIDGE_SEPcannot silently change every stored hash),bridgewiring_specandbridge_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_specpins 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
okaycallback, but the Blizzard Settings panel in every flavour calls onlyOnRefresh,OnDefaultandOnCommiton a canvas (Blizzard_SettingsPanel.lua), sookaynever ran and no change was ever stored. Hooking the save toOnCommitwould 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
SaveUpdatesand a callback namedokay, 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 noOnCommit.GreenWallInterfaceFrame_SaveUpdatesis 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.SendMessageand GreenWall's own roster notices raisedADDON_ACTION_BLOCKEDblamed on GreenWall whenever they were sent from an event. Peer-review finding from GuildRoster (inboxd49c5edb), seen in game on Classic Era: 52 errors in one session, from LibGuildRoster's live-presence sends out ofGUILD_ROSTER_UPDATE. 1.14.0 sentBandEmessages on the relay and on the bridge channel, so older GreenWalls would still hear them. I assumed aCHANNELSendChatMessagewithout 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, soGwChannel:sendnow hands roster notices and API data to the relay only and never callstl_sendfor them. Typed chat still goes on the channel, because it always has a key press behind it.SendMessagekeeps 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 (inboxd621dcbf). 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
rosterdescription now lists online and offline notices as the slash-command one already did. The CurseForge page now describesSendMessageas working without a key press, listsRemoveMessageHandlerandClearMessageHandlers, and saysBandEsegments 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
- Six weak spec assertions strengthened (writ inbox
9975452a): eachis_nilon a literal key is now paired with a positive assertion on the same key, or derives its keys from the settings schema. Locations: Tests/holddown_spec.lua, Tests/settings_spec.lua, Tests/compat_spec.lua. - Three specs that required the channel copy now require its absence, plus a new spec that a channel with no relay still sends a broadcast on the channel. Suite: 511 examples, 100% line coverage on all 14 files. Locations: Tests/relay_spec.lua, Tests/greenwall_spec.lua.
[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 (inbox898b1e4c), 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 theSendAddonMessageresult codes), cut to fit, or a Retail secret value refused before it reached the client.GreenWallAPI.versionis now2, 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 flagsHasRestrictions(ChatInfoDocumentation.lua:403-406, Classic Era), so a channel send leaves only inside a key press.SendAddonMessagecarries no such flag, but Classic removed itsCHANNELchat 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.luabroadcasts fromCHAT_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 withinGW_RELAY_DUAL_HOLD(10 s). Locations: Channel.lua:send, Relay.lua:RecentlySeen. GreenWallAPI.SendMessagehad 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 theguildandechoflags set, as the channel's loopback always gave them. A message too long for one addon message after encoding is refused withtoo-longrather 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.
SendChatlines reach only co-guild members on 1.14.0 or later.SendMessagedata 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 saysno-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:loadonly prints an error and the addon keeps working, so it now says that, and its exampleGWv:1.1.00(a leading-zero patch number) becameGWv:1.13.0. The/gw modedefault read "on" where the setting isaccount, androstercovers 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.tocstays at110207. The live tree marksSendAddonMessageSecretArguments = "NotAllowed", and both send calls refuse a secret value (feature-detected throughissecretvalue), 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) andTests/HARNESS_CONTRACT.md(eight contracts, all delivered by the harness in65de3af) were imported and closed, then removed;git logon either path keeps the history. CLAUDE.md now sends harness requests through the inbox. - Harness pin moved from
2c7279ftode64643. One spec assertedSettings.GetCategory('GreenWall'), which the client answers nil for every name because it looks categories up by numeric id (harness noticecb55fdcd); 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
strsplitstand-in in Tests/env_greenwall.lua. The client'sstrsplittakes apieceslimit (the last piece keeps the rest of the string, per the wiki'sstring.splitpage). 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 (inboxffd525af).
[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
/vcshows who in the guild is on which version.gw.RegisterVersionCheck()runs once from GreenWall's ownADDON_LOADED: it reaches the library throughLibStub:GetLibrary('VersionCheck-1.0', true), guards onVC.Enable, and calls the string formVC:Enable('GreenWall'). The library reads our## Version:itself, raw — an unsubstitutedGreenWall-v1.15.3is how it recognises a development build and suppresses the popup — so nothing on GreenWall's side reformats it. VersionCheck detects GreenWall's own API atPLAYER_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:Ace3andVersionCheck-1.0. GreenWall was a zero-dependency addon with everything vendored underLib/; it now needs four standalone addons at load — the two above, plus VersionCheck's ownGuildRosterandLibAceGUIWidgets, 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.tocdeclared50503; MoP Classic is50504, 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-Interfacemoved with it. The other five TOCs were checked and are current — includingGreenWall_Wrath.tocat30405, which is correct: Titan Reforged (38002) is a separate server line, not a Wrath Classic patch. Location: GreenWall_Mists.toc.- Every
CHAT_MSG_SYSTEMevent wrote a global namedhandlerinto_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. Nowlocal. 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 forGetGuildRosterInfo's unused returns was never declared. Nowlocal. Location: Utility.lua:GetGMOfficerNote.
Repo Tooling
- Added a
luacheckgate: .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 onGreenWall.luaalone — 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.2GetAddOnInfocrash. Every addon file and every spec is clean..luarc.jsonalso learned theGW_*constants,GetChatWindowChannels, the two Interface-panel XML handlers that were missing, and the test-onlyio/os. - Added Tests/manifest_spec.lua — nine specs for what line coverage cannot see. The six TOCs agree on every directive except the two
Interfacelines and on the file list; every listed file exists;## Dependenciesis exactlyAce3, VersionCheck-1.0;.pkgmeta required-dependenciesnames the same set through a slug table;Testsanddocsare excluded from the zip; and everyGW_*constant inConstants.luais 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'sADDON_LOADEDand 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) to2c7279f(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()restoringLibStub) were read and none required a change here.verify-manifest,verify-addonsandverify-xmlall pass (17/17 files load; every frame the XML declares is built).verify-xmlinitially reported a false failure because it ran all TOC Lua before any TOC XML and GreenWall'sLib\Load.xmlcomes first; raised with the harness (inbox36c18d33) and delivered the same evening in0ca808d, 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.mdandTests/HARNESS_CONTRACT.mdremain as the historical record.
Notes
- Verified in game on Classic Era: after login,
/vcshows 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_OPTIONSin 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.tocis deliberately left at110207. Retail is on 12.x, and 12.0 changed the API surface around chat and secret values; GreenWall's entire transport isSendChatMessage/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.WorldChannelFoundandGwConfig:is_containeras 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, butGwSettings:reset(svtable, meta)required the settings table and indexed it immediately — so the first key it looked at raisedattempt 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 defaultif svtable[k] == nil or self:validate(k, svtable[k]), i.e. only for keys that were absent or invalid. That guard is correct ininitialize(), which fills gaps, and was copied intoreset(), whose whole job is to overwrite values that are present and valid — making it a no-op on any real settings store. Now defaultssvtableto the store for the current mode and assigns unconditionally, stampingupdated. 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", butgw.APIDispatchertestedaddon == e[2] or addon == '*'— asking whether the sending addon was literally named'*'rather than whether the registration was the wildcard. BothGreenWallAPI.SendMessageandAddMessageHandlerassertaddon == 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 testse[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:andGW:o:mv=handlers gated onstrmatch(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. SoGW:v:1.2.3betapassed the regex,semver()returned nothing at all, andtostring()with no argument raised, unwinding out ofGwConfig: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 withif guild == self:GetGuildName(), butGwConfighas no such method — the helper isgw.GetGuildName()— so any call died withattempt 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 whoseGW: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 separateGetChatWindowChannels(i), and Blizzard reads the two intoRegisterForMessagesandRegisterForChannelsrespectively. 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 forChatFrameMixin.RemoveChannel, which takes the frame asself, so a match would have raised onpairs(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, overNUM_CHAT_WINDOWSrather than a hard-coded 10. Location: Channel.lua:join. - Hold-down timers could stop firing silently, and leaked a frame on every start.
GwHoldDown:startcreated itsOnUpdateframe into a bare local that went out of scope the moment the method returned; nothing else referenced it, because the handler takesframeas 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. Sincerefresh_channelsrestarts 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, sostring.formatraised 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-, so1.0.0+20130313144700, which semver 2.0.0 section 10 explicitly permits, parsed to nothing and aGW:v:line using it was silently discarded. (2) The comparison was not antisymmetric: an alphanumeric-versus-numeric identifier returned "less than" in both directions, so1.0.0-alpha < 1.0.0-1and1.0.0-1 < 1.0.0-alphawere 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", making1.0.0-alpharank above1.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_MINORis bumped from 1 to 2, and that bump is load-bearing rather than bookkeeping:LibStub:NewLibraryreturns nil whenoldminor >= minor, so at an unchanged minor this corrected copy would lose to any other addon vendoring the same very genericSemanticVersion-1.0major 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 documentedtargetparameter had been silently ignored since the rank lookup was replaced by the GM-officer-note check (a workaround for 7.3.0 makingGuildControlSetRank()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.luafrom the addon root, withlua 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:Testsis ignored by the packager. - Removed the previous
tests/suite. The luaunit-based cases, the vendoredluaunit.lua,MockAPI.lua,Loader.luaandrun-tests.ps1are gone, their coverage folded into the new specs. That suite needed thebitlibC module (and therefore a C toolchain) to run at all; the harness suppliesbitin pure Lua. The upstream PR branches (pr/era-fixes-testsand friends, cut fromupstream-*) 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/, sodocs/becamedocs//*and matched nothing; itscase-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.mdrecords what GreenWall needed that the harness did not model: the chat-window subscription queries, the chat frames and their edit boxes, a steerabletime(), the four missingERR_GUILD_*strings,C_GuildInfo.GetInfoText,GetChannelDisplayInfo/GetChannelList,getglobal, andC_AddOns.GetAddOnInfo/IsAddOnLoaded. All eight were delivered upstream and the local stand-ins deleted; the submodule is pinned at65de3af. - Added the
.bustedshim, taught.luarc.jsonthe spec globals, and normalised the test directory toTests— it had been tracked as lowercasetests, which the case-sensitive Linux packager would not have matched against the.pkgmetaentry.
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.luais 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.luasits at 98.86%: the single remaining line is acmp(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.luais 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 valueon every inbound message. The same chat-frame refactor that broke outbound bridging in v1.13.3 also removed the globalChatFrame_MessageEventHandleroutright — 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 capturedniland every decoded peer message errored instead of displaying. Inbound receipt and decoding still worked (the debug log showedopcode=Csegments 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 globalChatFrame_MessageEventHandlerwhere 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
GetAddOnInfologin crash, v1.13.3 fixed outbound/gand/obridging (the deadChatEdit_ParseTexthook), 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_ChatFrameBasesystem. GreenWall forwarded your outgoing/gand/omessages to the bridge viahooksecurefunc("ChatEdit_ParseText", ...), but in the new system the globalChatEdit_ParseTextis only a deprecation-fallback alias (ChatEdit_ParseText = ChatFrameEditBoxMixin.ParseText) — the client now calls the edit box's ownParseTextmethod, so the hook on the global never fired. The addon still loaded and the bridge channel still connected (inbound worked), which is why/gw statusshowedconnected=truewhile nothing you typed ever crossed; adebug 5capture confirmed thetype=GUILD, message=...hook line was absent on send. Verified against the 1.15.9 UI source that the newChatFrameEditBoxBaseMixin:ParseText(send, ...)still exposesGetAttribute("chatType")and the stripped message viaGetText(), so GreenWall's handler body is unchanged — only the hook target was wrong. Now hooks each chat edit box instance'sParseTextmethod 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
GetAddOnInfologin 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 throughCHAT_MSG_CHANNEL/ChatFrame_MessageEventHandler, not the edit-box hook.
Repo Tooling
- Added
ChatFrame1EditBoxto 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
GreenWallAPIcrashed withattempt to call a nil valuewhenever a third-party addon touched the API — a WoW client update removed the loose globalGetAddOnInfo, which now exists only asC_AddOns.GetAddOnInfo(confirmed in the generatedAddOnsDocumentationunderNamespace = "C_AddOns", with no deprecation-fallback shim, so the bare global is simplynil). Unlike the softer guild/chat deprecations below, this one is a hard removal: any caller reachingGreenWallAPI.SendMessage/AddMessageHandler/ClearMessageHandlersfor a non-'*'addon id hitassert(addon == GetAddOnInfo(addon))and threw immediately — observed in the wild asVersionCheck-1.0callingAddMessageHandlerat login. The rest of the addon had already migrated to theC_AddOnsnamespace in v1.12.0 (Globals.lua, Compat.lua); API.lua was the one file missed. Replaced all three call sites withC_AddOns.GetAddOnInfo. Location: API.lua.
Changes
- Proactively migrated two now-deprecated globals off the client deprecation-fallback path. The same client update that removed
GetAddOnInfoalso demotedGetGuildInfoTextandSendChatMessageto deprecated shims that live inBlizzard_DeprecatedGuildScript/Blizzard_DeprecatedChatInfo— they are now thin aliases forC_GuildInfo.GetInfoTextandC_ChatInfo.SendChatMessage, load only when theloadDeprecationFallbacksCVar 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 forC_GuildInfo.GuildRoster:if C_GuildInfo and C_GuildInfo.GetInfoText then ... else GetGuildInfoText() endin Config.lua:load, andif C_ChatInfo and C_ChatInfo.SendChatMessage then ... else SendChatMessage(...) endin 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 namespacedC_ChatInfo.SendChatMessageis 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.luaenumeration, and theBlizzard_Deprecated*shim files. Results:GetAddOnInfowas the only hard removal (fixed above);GetGuildInfoTextandSendChatMessagewere 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); andGetCVar(a real global defined inBlizzard_SharedXMLBase/CvarUtil.lua, GreenWall.lua). No further changes were required. - This supersedes the v1.13.0 note that listed
GetGuildInfoTextandSendChatMessageas "verified portable" loose globals — that was accurate when written, before Blizzard moved them behind the deprecation-fallback CVar.
Repo Tooling
- Added
TestConfigInfoTextregression coverage for the new guild-info-text source selection, mirroring the existingTestConfigReloadcases: verifiesGwConfig:load()prefersC_GuildInfo.GetInfoTextwhen present, and falls back to theGetGuildInfoTextglobal both whenC_GuildInfois absent and when the table exists but lacks the method. UpdatedTestAPI'ssetUpto stubC_AddOns.GetAddOnInfoinstead 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 withattempt 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 movedGuildRoster()into theC_GuildInfonamespace years ago (it remains as a loose global only on Classic Era 1.15.x); on every other flavor the global isnil, soGwConfig:reload()threw on the very first config load and the addon was unable to bootstrap its bridge configuration. Replaced the bareGuildRoster()call withif 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/### Fixedformat, but the project switched to the togpm format (## [vX.Y.Z] (YYYY-MM-DD) - Titleheaders with prose-rich### Bug Fixes/### New Features/### Changes/### Notes/### Lint Pass/### Repo Toolingsections) in v1.12.0; updated the rules to document the togpm format. Added a new TOC rules section: TOCs now use theGreenWall-v1.15.3packager 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
markdownlinterrors in CLAUDE.md so the file lints clean under the project's .markdownlint.json config. (1) Saved-variables andGW:directive tables used the compact alignment-row style (|---|---|---|) while their data rows used padded pipes — mismatched style flagged asMD060/table-column-style. Normalized both alignment rows to padded style (| --- | --- | --- |). (2) TheGreenWallMetarow contained an unescaped|inside a code span (mode = 'account' | 'character') whichMD056/table-column-countparsed as an extra column separator. Escaped the pipe to\|. (3) The wire-segment example fence had no language hint — flagged asMD040/fenced-code-language. Tagged it astext.
[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:
- GreenWall_TBC.toc — Burning Crusade Classic / Anniversary 2.5.x (Interface 20505)
- GreenWall_Wrath.toc — Wrath Classic 3.4.x (Interface 30405)
- GreenWall_Cata.toc — Cataclysm Classic 4.4.x (Interface 40402)
- GreenWall_Mists.toc — Mists Classic 5.5.x (Interface 50503)
- GreenWall_Mainline.toc — Retail 11.x (Interface 110207)
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 localizedERR_*constants used by SystemEventHandler.lua,GetGuildInfoTextand the rest of the guild-roster API used by Config.lua and Utility.lua, and thehooksecurefunc("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:holdhad an inverted comparison since v1.5.3 (2014-11-11):if self.cache[s] > t + self.interval then rv = trueis 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 returnedfalse. Every member of every co-guild logging in produced two notifications: one from the naturalCHAT_MSG_SYSTEMevent (ERR_FRIEND_ONLINE_SS), and one generated by GreenWall'sCHAT_MSG_CHANNEL_JOINhandler. The cache's pruning logic was also broken:#self.cache > self.soft_maxalways evaluates 0 because the cache is hash-keyed by player name, andtable.remove(self.cache, k)is the wrong API for a string key. Replaced the comparison witht - self.cache[s] < self.interval, the length check with apairs()enumeration, and the deletion withself.cache[k] = nil. Location: HoldDown.lua:GwHoldDownCache:hold.Stale channels not cleared after Guild Information edits —
GwConfig:load()'s cleanup loop usedfor _, channel in ipairs(self.channel) dooverself.channel = { guild = ..., officer = ... }.ipairsonly 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 resetor relogged. Switched topairs. Location: Config.lua:load.GreenWallAPI.RemoveMessageHandlerwas broken since the API was introduced in v1.7.0 — the function had three bugs in its 14 lines:rv = falsewas a leaked global; theif addon ~= '*' then addon = GetAddOnInfo(addon); assert(addon ~= nil) endblock referencedaddon, which is not a parameter of this function (it was copy-pasted fromClearMessageHandlers); andgw.api_table[i] = nilleft a hole that stopped subsequentipairswalks 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 totable.remove. Location: API.lua:RemoveMessageHandler.gw.IsLegendaryleaked_andrarityglobals on every call —_, _, rarity = GetItemInfo(item)had nolocaldeclaration. Function was an orphan from the legendary-loot-replication feature removed in v1.11.0 (Blizzard madeSendChatMessagepartially protected in patch 8.2.5, killing the feature). Deletedgw.IsLegendaryandgw.GetItemString. Also deleted other v1.11.0 orphans missed at the time:GwPromoteSystemEventHandler(never dispatched by the factory), therankfield destructured fromGW_MTYPE_BROADCASTpayloads ingw.handlerGuildChat, and an unusedlocal semver = LibStub:GetLibrary(...)import. Locations: Utility.lua, SystemEventHandler.lua, Chat.lua.Invalid Lua 5.1 escape sequence in the
gw.Debugcall-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 \\ \" \' \[ \] \xxxare recognized). WoW's interpreter accepted it (treating unknown escapes as the literal char), butlua-language-serverflagged 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 twogw.Debugcalls in API.lua —%,is not a valid format specifier;string.formatwould error if the message was ever logged at debug level 4+ (GW_LOG_INFOfor theadd API handler/remove API handlermessages). Replaced with%s. Location: API.lua.GreenWallAPI.ClearMessageHandlersleft holes when removing multiple matching handlers —gw.api_table[i] = nilwhile iterating withipairscauses 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 withtable.removeso deletions properly compact the array. Location: API.lua:ClearMessageHandlers.Redundant
self:initialize_param(true)argument inGwConfig:reset()— the method takes no parameters; thetruewas silently ignored. Cosmetic but flagged as aredundant-parameterwarning by lua-language-server. Location: Config.lua:reset.
Lint Pass
- Cleaned trailing whitespace, dead
localdeclarations, 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.jsonconfiguration.
Changes
- Updated TOC for WoW Classic 1.15.8 (Interface 11508).
- Modernized the TOC. Switched
## Versionto the CurseForge packager substitution tokenGreenWall-v1.15.3. Replaced legacy## X-Category: Guildwith 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 viaX-Curse-Project-IDand.pkgmeta). Location: GreenWall.toc. - Future changelog entries use the togpm format —
## [vX.Y.Z] (YYYY-MM-DD) - Short Titleheaders, 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/GwSettingsclass roles, theGW:configuration-directive grammar, the hidden-channel wire protocol with CRC-16 loopback detection, the publicGreenWallAPIsurface, 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
SendChatMessagefunction 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.GetChannelNumbersfunction 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()toGwHoldDown: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
GuildRostercall to PLAYER_LOGIN handler. - Limited conditions under which reinitialization occurred on PLAYER_GUILD_UPDATE.
- Renamed
gwPlayerGuildtogwGuildName. - 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()forgwPeerTablechecks 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

