1.4.0-release
What's new
commit 7ff1823d00daea1d3b85e6b179ffb3266b2e7080
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Sep 11 00:55:31 2026 +0530
sync-docs: four citations that had drifted off their lines
DEPENDENCIES.md's evidence column pointed at docs/testing.md by line, and four of
those ranges had slipped: the diff -r block is 218-221 not 217-220, the
renormalize hint is 261 not 259, the vendored runner is 338-340 not 336-338, and
the no-CI statement is 174-176 not 173-175. Each was off by enough to miss a
command or fall on a fence.
ARCHITECTURE.md said the legacy GetSpell* globals are the fallback rung for each
of four C_Spell APIs. GetSpellLink has no rung -- core/Compat.lua:52-57 degrades
straight to nil -- which is what docs/compat-layer.md:15 already recorded.
WhatGroup.toc cited core/WhatGroup.lua:121 for the NS.FONT_MONO assignment that
makes the following load order load-bearing; it is :128. .luacheckrc said a
top-level ignore would reach all 40 files; luacheck reads 41.
Target interface to 120100 in DEPENDENCIES.md, ARCHITECTURE.md, scope.md,
module-map.md and common-tasks.md's worked example.
docs/perf-analysis/README.md stays absent and that is correct: ARCHITECTURE.md's
ratified performance-§12 row names 'no docs/perf-analysis/' among the declined
wiring, and the Documentation map files the doc Not applicable with its trigger.
docs/test-cases.md regenerated. luacheck clean at 41 files, 568 tests green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G4Xy2KrRa8NmD1843rgKnE
commit bed07dd61ae6d093978db4982257b45a8d44140d
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Sep 10 23:55:14 2026 +0530
Release 1.4.0
Show and Close are combat-safe now — the popup is no longer asked to hide while
protected — a popup you closed stays closed, the visibility gate is re-asked
when combat starts and ends, and each capture is paired to its own application
rather than to whichever answered first. 136 commits since the 1.3.0 tag.
Release gate passed at 53da443: luacheck 0/0 across 41 files, 568 tests green,
zero functions above CCN 15. The perf suite did NOT run — no tests/perf.lua
ships here — so the gate covered three suites, not four, as the Version History
row and ANALYSIS.md both state.
Proportionally the largest test growth of the nine, +14 cases, which is the
combat-safety work pinning itself down. The version fixtures in tests/ move
with the TOC so the two cannot drift.
What's new is deliberately not recreated (documentation-§1, standard v2.42.0).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G4Xy2KrRa8NmD1843rgKnE
commit 53da4438d59a34e0a534483e834f4c78eb99b337
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Sep 10 23:45:02 2026 +0530
Target patch 12.1.0 — the TOC interface becomes 120100
Retail moved to 12.1.0, and an addon whose TOC still declares 120007 shows as
out of date in the client's addon list even though nothing in it broke.
The line is the only thing that changed. LibKa0s needed nothing alongside it:
it ships no TOC of its own, reads the client build from GetBuildInfo() at
runtime rather than declaring one, and the harness vendored as tests/_kit/
carries no build number at all — so no re-vendor was involved.
Verified before committing: luacheck clean and the headless suite green in
every repo.
Living documentation still says 120007 / 12.0.7 in the README badge and
DEPENDENCIES.md, and in several repos ARCHITECTURE.md, scope.md, module-map.md
and smoke-tests.md as well. That sweep is /wow-addon:sync-docs' job, not this
one-line change's. Comments recording measured 12.0.7 behavior stay as they
are — they are still true.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G4Xy2KrRa8NmD1843rgKnE
commit fab575c0b96e42535f150ee56346bd8292303287
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Sep 10 23:26:23 2026 +0530
README: say it is the Group Finder, drop the settings shot
"Apply through the Premade Group Finder, join, shut the LFG window" made a
reader assemble the trigger themselves. It now says what it is: the group
you signed up for through the Group Finder, once you are accepted. The logo
block and the settings-panel screenshot go, and the popup and chat-link
shots move to the new attachment IDs with sentence-case captions.
The logo bitmap is refreshed to match the upload.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G4Xy2KrRa8NmD1843rgKnE
commit 8298d324854d7d797fa4710a24a4f3a5bcbdf8a3
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Sep 10 03:31:57 2026 +0530
media: refresh the screenshot set
Local source images only. Every README serves its screenshots from the
CurseForge CDN, so nothing here is referenced by a doc and no page
changes until the new set is uploaded there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVx7o2xdz6H1jH2CZviDw8
commit 8257927846b25957756e165c599f779cbcf84994
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Sep 10 02:51:51 2026 +0530
README: drop `## What's new` (standard v2.42.0)
documentation-§1 no longer carries the section. It was bound to the top
`## Version History` row -- the two "MUST agree" and rolled forward
together on every bump -- so its content was a copy of another MUST's
content by definition, and the only work it could generate was keeping
the copy true. Version History is now the addon's single player-facing
history.
Anti-pattern #40 is retired in place upstream; the item list renumbers
6-13 to 5-12.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVx7o2xdz6H1jH2CZviDw8
commit 8d99008b3abb1b1ecb6d870717a683e456dfac75
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Sep 9 21:43:25 2026 +0530
README: adopt the standard's prose Usage section (v2.41.0)
documentation-§1 item 7 no longer mandates the two tables that made
Usage a contents page. The `Command | What it does` table said what
exists without ever saying what to do, and it was a second copy of a
list the addon already generates from NS.COMMANDS for `/<slash> help`,
so it could only be in lockstep or wrong. The `Tab | Covers` table has
moved to docs/settings-panel.md, its canonical home, where the finer
page-tab-row tree already sat beneath it.
Usage is now prose in the order a new player meets the addon: how the
display is shown, hidden, moved and locked, what preview or test mode
is for, what the chrome's controls do, and the core interactions. It
closes on one sentence pointing at Settings > AddOns and the slash
command list.
The whole README was humanized in the same pass -- em dashes cut,
`**Bold lead.**` bullets removed, see-saw pairs and repeated rhetorical
moves broken up, sentence and paragraph length varied.
Reviewed by an adversarial pass that checked every factual claim
against source; the corrections it found are folded in.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVx7o2xdz6H1jH2CZviDw8
commit e581517382b3152b4632f77e874d04fb031cfe3d
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Sep 9 01:41:55 2026 +0530
re-vendor LibKa0s v1.29.0 — the JSON dump folds into the report step
`report` printed the summary and `dump` printed the run as one line of JSON.
Both went to the same log, both described the same finished run, and
perf-analysis asks for both -- so two verbs and two panel rows meant a second
click, a second thing to remember, and a run reported without its dump was the
easy mistake to make. `report` writes both now, JSON last, because the summary
is what you read and the JSON is what you copy.
`perf dump` is no longer a verb and `Progress().dump` is gone. Folded rather
than aliased: an alias is the duplication the fold removes, and the unknown-verb
path prints the usage, where `report` now says it renders the JSON too.
Payload and kit copied whole from a clean checkout of the tag; the CLAUDE.md
provenance line rolls in this commit. Kit revision unchanged at 15.
diff -r against a clean v1.29.0 checkout: byte-empty, both payloads.
568 passed, 0 failed, 0 skipped, 568 total
Total: 0 warnings / 0 errors in 41 files
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 4bccc0e132fc78a3c8e3501249de34b053a36fc0
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Sep 9 01:28:04 2026 +0530
re-vendor LibKa0s v1.28.0 — the perf usage block renders correctly
Perf.lua 9 only. The usage block printed `<start|measure|finish|canceleport|
dump|showideoggle>`: the verbs are pipe-separated and the client reads `|r` as
a color reset, `|h` as a hyperlink and `|t` as the end of a texture, so it ate
all three and the words fused. The eaten `|r` also swallowed the reset closing
the gold run, which is why the line stayed yellow. The pipes are doubled now.
The block also hand-aligned a second column with spaces and wrapped each
description onto a continuation line, which a proportional chat font cannot
honor. It is one row per verb through the collection's own FormatRow now, the
same formatter the slash-command help uses.
Payload and kit copied whole from a clean checkout of the tag, not from the
library's working tree, and the CLAUDE.md provenance line rolls in this commit.
Kit revision is unchanged at 15. No member added, removed or renamed, so nothing
in this addon had to adopt anything.
diff -r against a clean v1.28.0 checkout: byte-empty, both payloads.
568 passed, 0 failed, 0 skipped, 568 total
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 6ba6d83da72c4db2e0f9d57d2367248fac0a8b0c
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Sep 9 00:08:22 2026 +0530
M2-31: Show is protected in combat too, so stop calling it there
Reported from the client: `/wg test` mid-fight raised ADDON_ACTION_BLOCKED on
WhatGroupFrame:Show(). The popup appeared anyway, which is worse than if it had
not -- the error was the only signal that anything was wrong.
This file asserted the opposite in as many words: "f:Show() on an already-built
frame is not a secure write -- the secure work is buildFrame's, and it has
already happened by the time f exists." Wrong for exactly the reason Hide is
protected. The rule is about changing a PROTECTED FRAME's visibility, and
showing an ancestor changes it as surely as hiding one does. M2-28 found the
Hide half, wrote that sentence about Show in the same commit, and did not test
it.
The mock made the same mistake and is the reason this shipped: it modelled Hide
protection and left Show alone, so `once built, showing during combat is
allowed` passed for a year while the client refused the call. Show now walks the
same subtree, and turning it on lit up exactly three cases -- all three
asserting the bug.
ShowFrame refuses in combat and defers to PLAYER_REGEN_ENABLED with the line the
locale already carries. The one exception needs no protected call at all: a
popup still SHOWN at alpha 0 comes back on its alpha alone, which is what keeps
"close it during a fight" reversible instead of one-way. That is the owner's
second requirement, and it is the mirror of M2-30's close.
The finding this leaves open, stated rather than buried: `inCombat` asks for a
popup that appears DURING a lockdown, which is the one thing Show cannot do. It
now builds, stays off screen, and does not open -- silently correct instead of
loudly broken. Delivering it means keeping the frame shown at alpha 0 for the
whole time the player is OUT of combat so the edge needs only an alpha change,
at the cost of an invisible 420x260 click-target at rest. That is a trade for
the owner, not for this commit, and both docs/frame.md and smoke 4.5 step 9 say
so.
Four cases red first: a show requested in combat deferring rather than firing,
the alpha-0 popup returning without a Show, and the two `inCombat` cases
rewritten from what they wished were true to what the client permits.
568 passed / 0 failed (was 567), luacheck 0/0 in 41 files. Smoke 4.5 gains steps
7-9 and is NOT RUN; no client here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 6726383e2725d9598383f44ea6c4348e392bdc8c
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 23:47:34 2026 +0530
M2-30: Close works in combat, and 'out of combat' clears on the pull
The owner's ruling, 2026-09-08: it must be possible to close an open popup in
combat. Not opening one in combat is fine, provided it opens by itself
afterwards.
M2-28 concluded the opposite -- that the popup simply had to stay up for the
fight -- and that conclusion was true of Hide and false of the requirement. The
client refuses Hide on an ancestor of a SecureActionButtonTemplate button, so
Hide was never the tool. Alpha is, and this file had already ruled so two
functions above: ApplyFrameAlpha is deliberately un-guarded because opacity
moves nothing, where ApplyFrameSize and ApplyFrameScale both refuse because they
move the secure child. The answer was on the page the whole time and M2-28 did
not go looking for it.
So hidePopup takes the frame to alpha 0 in combat and owes the real Hide to the
next legal edge, which ApplyFrameVisibility settles before it asks the gate
anything. Close now works in both states, and `outOfCombat` clears on the pull
rather than a fight later and comes back on its own when combat drops.
What alpha does not buy is the frame leaving hit-testing: it is invisible, not
absent, so until the lockdown lifts the title bar still drags and the teleport
button still occupies its 24px -- a teleport cannot be cast in combat, so a
click there does nothing. That residue is why pendingHide exists and why the
soft state is never a resting one. Smoke 4.5 records it as known and accepted
rather than leaving the next reader to find it.
dismissPending is gone, subsumed: pendingHide carries the owed Hide and
gateWithheld carries who owns the dismissal, and ApplyFrameVisibility saves
gateWithheld across settling the debt because the real Hide fires OnHide, which
clears it. Without that, a gate hide would come back as a player dismissal and
never reopen -- the exact bug M2-29 fixed, re-entering through the back door.
Five cases: Close in combat taking it off screen at once, the owed Hide landing
with the alpha restored, the alpha restored being the player's own rather than a
literal 1, `outOfCombat` clearing on the pull, and it reopening by itself
afterwards. All five watched red against the previous implementation. Two older
cases asserted IsShown alone, which now passes with the popup fully drawn; both
tightened so they cannot pass vacuously.
567 passed / 0 failed (was 562), luacheck 0/0 in 41 files. Smoke 4.5 rewritten
to the new contract and NOT RUN -- no client here. Step 6 and step 7 are the
ones that matter most: they prove the fix for closing did not take the
legitimate re-show with it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 15dd590d5793b8396b0d4e6ce07a847b7625e343
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 23:34:42 2026 +0530
M2-29: a popup you closed stays closed
Reported from the client: `/wg test`, close the popup, pull something, and it
springs open again. No Lua error, because nothing about the call is wrong.
M2-21 added the re-show half of the visibility gate as
`if pendingInfo and not f:IsShown() then f:Show() end`. That treats "hidden" as
one state when it is four, and only two of them are the gate's doing:
never shown -- leave it shut
the gate DECLINED a requested show -- reopen when the gate opens
the gate HID a shown popup -- reopen when the gate opens
the player dismissed it -- leave it shut
The sharpest way to put it: under `always`, the SHIPPED DEFAULT, the gate never
hides at all. So on the default value every firing of the re-show arm was a
popup the player had closed. It had no legitimate case whatsoever, and that is
how it reached a client through 559 green cases.
M2-28 half-hid this. It added dismissPending for a Close pressed IN combat and
refused by the client, so that path was already covered -- but an ordinary
out-of-combat Close succeeds, returns true, and leaves no trace at all. The
common case was the uncovered one.
gateWithheld now carries why the popup is off screen, and the bookkeeping is
inverted so the next hide path cannot forget it: OnHide clears it for EVERY
hide, and only the gate's own two sites -- ShowFrame's refusal and
ApplyFrameVisibility's hide -- set it back, immediately after. ESC goes through
UISpecialFrames to a bare f:Hide() and leaves no other trace, so anything keyed
off the Close button's handler would have been wrong by construction; this is
keyed off the frame.
Three cases, all watched red first: a Close-dismissed popup surviving a pull, an
ESC-dismissed one doing the same, and a dismissal outliving the whole fight
across both edges. The existing case that proves the LEGITIMATE re-show -- the
gate withholding under `inCombat` and releasing on the pull -- stays green, and
smoke step 4.6 step 5 is the in-client version of it, because a fix that killed
the real case along with the bug would look identical from the outside.
562 passed / 0 failed (was 559), luacheck 0/0 in 41 files. Step 4.6 is NOT RUN;
no client is available here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 0a4809d88dd21f3d6f54d02adc69e21bf55f1cf5
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 22:18:40 2026 +0530
M5-06-C1: the lint citations name a section that does not exist
The 2026-09-08 standards audit found these, and this cycle wrote them. M4c-03,
M4c-04 and M4c-06 removed the blanket luacheck ignores across the collection and
cited the rule as `lint-§1` in the configs, the gate cases and the testing docs.
standards/lint.md carries exactly one heading and no numbered subsections, so
`lint-§1` resolves to nothing. The house rule for a section-less file is the bare
filename -- the same ruling M1-STD-04 made for standalone-windows.md, whose
line-number citations exist for this reason.
Seventy citations across nine repositories, all authored 2026-09-08, all now
reading `lint.md`. No behaviour changes and no count moves: every hit is a
comment or an assertion message.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit f56dcd3936c0578a448954e6858c44d542f8f899
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 21:51:21 2026 +0530
M5-06: the amended rules, measured against the repository they govern
A fresh read-only standards audit against v2.39.0 — the pass that checks the
fifteen Group B amendments describe reality, rather than that a keyword was
typed. Bundle only; no addon code, TOC or config is touched, and
docs/audits/2026-09-07/ is untouched.
14 roots / 15 total on 2026-09-07 → 7 roots / 8 total today. 0 High, 0 Medium,
5 Low, 2 Info; 5 MUST failures, every one graded Low because none is reachable
by a user, their SavedVariables or their session.
Eleven roots and the one dependent closed, measured rather than assumed: the
line-ending tree is clean at 0 strays (was 6), all four load-bearing TOC
positions are annotated, the record is runner-generated with a passed/skipped/
total cell, the register's §33 citation is a bare filename, docs/compat-layer.md
exists over v2.39.0's new three-shim threshold, the README no longer names a tab
that does not exist, and the fourth doc-map table this repo already shipped is
now the rule.
Three did not close and two are new — and both new ones were written by this
cycle, hours after the sweep that closed their predecessors:
WG-54 travelled and behaviour, both landed by M4c-04 into core/WhatGroup.lua
after M4-13 had swept the file. Only localization-§5's newly published
BRITISH list catches the first; the old five-word grep could not.
WG-62 ten citations that resolve to nothing — lint-§1 at eight sites, written
by M4-11 and M4c-04 while adopting the lint amendment, against a
section documentation-§6 names as bare-filename-only; and
code-quality-§3 at two, a section file that has never existed.
WG-61 two register rows cite evidence ids that do not resolve — WG-R-06
lands on an unrelated 2026-09-07 review finding, WG-A-08 on nothing at
all. Visible only against audit-review-history's third MUST, new in
v2.39.0. Its dependent WG-63 is the repo's own gate, green against both
for two separate reasons its own header comment predicted.
WG-51 the vendored gate still asserts no 100755. M1-LK-07 closed its sibling
and not this half; LibKa0s HEAD has no assertion either.
WG-58 .pkgmeta still does not ignore .superpowers. The .pkgmeta half closed
by rule change; this half did not.
Every count in the bundle is a recorded command with its scope stated, and
every file:line was re-read and quoted beside its citation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 13ae17ae0820e2bc10d1d9ca1861e65ac0e9e6a1
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 19:36:33 2026 +0530
M5-08: § 12b, and the login § 7a has been waiting five milestones for
This addon had no non-English-client step, and almost everything it puts on
screen about a group comes out of the client in the player's language: the
activity's fullName and shortName from C_LFGList, the server-rendered
playstyleString, the four GROUP_FINDER_GENERAL_PLAYSTYLE globals -- read once
at file load, so a nil there is nil for the session -- and the spell name that
goes straight into the teleport button's /cast macrotext. Nothing here has
ever seen a German one: tests/wow_mock.lua answers enUS throughout, and the
/wg test fixture spells the activity name out in English, which § 12b says
plainly so nobody files the fixture as a bug.
§ 12b is written from that enumeration, and each step names its failure: an
Unknown in the Instance row where the client plainly has a name, a name that
overruns the popup's border (German activity names are the longest the client
produces, and nothing else would notice), a playstyle global that came back
nil, a teleport button drawn ready whose click casts nothing.
Step 5 is the point of scheduling this at all. Session 6 owns § 7a as well as
the locale pass -- one login, both jobs -- and § 7a is the
C_SpellBook.IsSpellKnown observation WHATGROUP-R-06 has been gated on since it
was filed. M5-10 correctly refused to ship the rung without it. So § 12b
carries a fill-in block for the six readings the finding needs, including the
client build and GetLocale(), and states both outcomes explicitly: agreement
means M5-10 can add the rung in the shape the five siblings use, disagreement
means the rung is NOT added and the disagreement is recorded on issue #15.
Both close session 6's obligation. A blank is the only result that does not.
The checklist and § 13's standing note about 7a both now point at it.
Nothing here has been performed. No WoW client was available to this
execution, so § 12b is marked NOT YET RUN, no step in this repository is
recorded as passed, and WHATGROUP-R-06 stays exactly where M5-10 left it.
The gate lands with the section: a case in tests/test_doc_structure.lua
asserting the document still carries a non-English-client section, that it
names a client to run it on, that it says what failing looks like, that it is
more than a heading with a sentence under it, and -- the one assertion
peculiar to this repo -- that it names C_SpellBook.IsSpellKnown, so the single
login this addon needs cannot be spent without § 7a. It was watched failing on
the "has no heading naming a non-English client" message before the section
was written, and it claims nothing about the section having run.
luacheck . is 0 warnings / 0 errors in 41 files; lua tests/run.lua is 559
passed, 0 failed, 0 skipped, 559 total. The count moved by the one new case,
so docs/test-cases.md is regenerated and the README badge moves with it here.
No version bump: decision 5.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 13ae17ae0820e2bc10d1d9ca1861e65ac0e9e6a1
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 19:36:33 2026 +0530
M5-08: § 12b, and the login § 7a has been waiting five milestones for
This addon had no non-English-client step, and almost everything it puts on
screen about a group comes out of the client in the player's language: the
activity's fullName and shortName from C_LFGList, the server-rendered
playstyleString, the four GROUP_FINDER_GENERAL_PLAYSTYLE globals -- read once
at file load, so a nil there is nil for the session -- and the spell name that
goes straight into the teleport button's /cast macrotext. Nothing here has
ever seen a German one: tests/wow_mock.lua answers enUS throughout, and the
/wg test fixture spells the activity name out in English, which § 12b says
plainly so nobody files the fixture as a bug.
§ 12b is written from that enumeration, and each step names its failure: an
Unknown in the Instance row where the client plainly has a name, a name that
overruns the popup's border (German activity names are the longest the client
produces, and nothing else would notice), a playstyle global that came back
nil, a teleport button drawn ready whose click casts nothing.
Step 5 is the point of scheduling this at all. Session 6 owns § 7a as well as
the locale pass -- one login, both jobs -- and § 7a is the
C_SpellBook.IsSpellKnown observation WHATGROUP-R-06 has been gated on since it
was filed. M5-10 correctly refused to ship the rung without it. So § 12b
carries a fill-in block for the six readings the finding needs, including the
client build and GetLocale(), and states both outcomes explicitly: agreement
means M5-10 can add the rung in the shape the five siblings use, disagreement
means the rung is NOT added and the disagreement is recorded on issue #15.
Both close session 6's obligation. A blank is the only result that does not.
The checklist and § 13's standing note about 7a both now point at it.
Nothing here has been performed. No WoW client was available to this
execution, so § 12b is marked NOT YET RUN, no step in this repository is
recorded as passed, and WHATGROUP-R-06 stays exactly where M5-10 left it.
The gate lands with the section: a case in tests/test_doc_structure.lua
asserting the document still carries a non-English-client section, that it
names a client to run it on, that it says what failing looks like, that it is
more than a heading with a sentence under it, and -- the one assertion
peculiar to this repo -- that it names C_SpellBook.IsSpellKnown, so the single
login this addon needs cannot be spent without § 7a. It was watched failing on
the "has no heading naming a non-English client" message before the section
was written, and it claims nothing about the section having run.
luacheck . is 0 warnings / 0 errors in 41 files; lua tests/run.lua is 559
passed, 0 failed, 0 skipped, 559 total. The count moved by the one new case,
so docs/test-cases.md is regenerated and the README badge moves with it here.
No version bump: decision 5.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 5f7272bbcdf3f3ac26ec515ac1b7b8de973e7ea7
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 19:21:38 2026 +0530
M4c-04: the blanket ignore goes, and fifteen of the twenty-four were real
`.luacheckrc:19` carried `ignore = { "211/addonName", "212", "542" }`. All three
codes named something that exists in this tree, but the suppression reached all
40 files, and `M4-11` is explicit about what that is worth: an ignore that
silences the wall reads as coverage and provides none.
It provided none here, and the measurement is in the commit. Removing the three
lines took `luacheck .` from 0/0 to **24 warnings** — ten `211/addonName`,
thirteen `212`, one `542` — and the more useful number is that **fifteen of the
twenty-four were not conventions at all**:
* Ten files opened `local addonName, NS = ...` over a folder name they never
read. Six files in this addon do read it — CoreSetup, EnvSetup, MediaSetup,
DebugLogSetup, WhatGroup and settings/Panel, each handing it to a vendored
library that cannot infer which folder it was copied into. The other ten had
it because the line was copied. They now open `local _, NS = ...`, which is
what ConsumableMaster does and why its config carries no `211` entry at all.
* The two file-load `hooksecurefunc` post-hooks mirrored the client's full
signatures and forwarded them into handler bodies that read none of it.
`OnApplyToGroup` took a `...` it never touched; `OnSetItemRef` took
`linkArg, text, button, ...` and touched none of the four — by the time
control reaches it the closure's prefix test has already answered the only
question those arguments could, and there is exactly one `WhatGroup:` link.
Both closures and both handlers now take only what they read. Deleted, not
re-listed: an argument this addon chose to accept and then ignored is dead
code, and a stanza naming it would have been the blanket again, smaller.
Nine remain, and each is a receiver a calling convention forces. Three per-file
stanzas in luacheck's `<code>/<variable>` form — `212/self` on the two capture
methods reached as `self:Capture…`, on the four `ApplyFrame*` appliers that read
the popup through modules/Frame.lua's own upvalue, and on the AceConsole handler;
`212/event` on the AceEvent handler that is named after its own event — with a
comment above each saying which obligation forces the argument. The single 542 is
answered inline, `-- luacheck: ignore 542` on the `invited` branch line in
core/WhatGroup.lua, because it is the only empty branch in the repository and a
file-wide suppression would hide the next one.
The narrowing is real, and it was measured rather than assumed: a dead second
parameter added to `WhatGroup:RunTest` reports under the new config
(`core/WhatGroup.lua:855:28: (W212) unused argument deadArg`) and reports
nothing under the old one — the same tree, re-linted with
`luacheck --config <the blanket> .`, comes back 0 warnings / 0 errors.
The gate is the other half, and `M4c-03` in ConsumableMaster is the shape it
follows: a top-level `ignore` is one line for anyone to re-add.
tests/test_lintconfig.lua loads `.luacheckrc` as Lua under a sandbox that
auto-creates tables the way luacheck's own config loader does, so it inspects the
table luacheck obeys rather than text a different spelling would slip past. Four
cases, all four watched red in the working tree before this commit: a top-level
`ignore`; a class switched off wholesale (`unused_args = false` and eight
relatives); a `files[...]` ignore whose key is a directory and whose entry names
no variable; and a bare `-- luacheck: ignore` anywhere in tracked Lua. It fails
rather than skips when it cannot look, the bargain test_docmap and test_eol
already strike here.
The working tree also went red on the way through, and that was the EOL gate
doing its job: the first pass rewrote `.luacheckrc` through a text-mode filter
and left it 114 LF terminators against the `eol=crlf` `.gitattributes` declares.
Repaired in place before anything was staged.
Documentation moved with the code. docs/data-flow.md quoted both pre-change
signatures in its `SetItemRef` section and now quotes the shipped ones with the
reasoning beside them; docs/ARCHITECTURE.md said "every source file starts with
`local addonName, NS = ...`" in two places and now says which six spell it that
way and why the other ten spell it `_`; docs/testing.md gains the suppression
gate beside the lint-scope section it belongs to.
Verification, observed. `luacheck .` 0 warnings / 0 errors in 41 files.
`lua tests/run.lua` 558 passed, 0 failed, 0 skipped — 554 before, plus the
gate's four. docs/test-cases.md regenerated through `--list` (which writes CRLF
itself) and its Totals row reads 558; the README `[tests]` badge moves 554 to 558
in this commit. Both re-run from a clean clone of this commit, not from the
working tree. No version anywhere: Decision 5 stands and the badge is a count.
No smoke step, and none is claimed. Two lines that reach the client did change —
the parameter lists of the two file-load hook closures — and that is a signature
narrowing with no behavioural edge: Lua drops arguments a function does not
declare, which is already what happened to these. The paths are covered by
smoke-tests.md sections 4.2 (chat-link round trip) and 5.1 (a real LFG apply),
and tests/test_lifecycle.lua drives both hooks through `mock.fireHook`: four
cases push the client's three `SetItemRef` arguments into what is now a
one-parameter closure, and one pushes a search-result id at `ApplyToGroup`. Nothing else
in the change ships: `.luacheckrc` and tests/test_lintconfig.lua are not packaged,
and the ten header edits rename a local nothing read.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit d54a8dc9f14aaff667dde8bbcde247a8e55a3db7
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 18:32:42 2026 +0530
M5-01: the record is regenerated, and the band table stops being empty
The watch list this replaces said "No file is within 250 lines of the 1000-LOC threshold."
`tests/test_frame.lua` is 1063 — the first file this repository has ever had in the
`layout-§1` band, and now the largest file here, ahead of `core/WhatGroup.lua` at 870. It was
627 at the previous run's commit, crossed at 987 with `M2-21`'s combat-start re-ask, and
reached 1063 with `M2-28`. It is 293 lines longer than the 770-line module it covers, and that
ratio rather than the count is what its disposition says to watch.
The staleness was structural, not careless. Until test-kit revision 15 the runner wrote one
table row and a fixed lead-in, so `automated-tests-§4`'s two tables had no producer and the
only way to have them was by hand. Everything in `RESULTS.md` is now the runner's, out of this
run's own manifest and `lizard` output, with Disposition the one authored column.
Green. Lint 0/0 over 40 files — 16 before `M4-11` put the test tree in scope — 554 cases passed
with none failed and none skipped, zero functions above CCN 15. `perf` is a permanent skip, no
`tests/perf.lua` here, so the record says nothing about runtime cost rather than implying it.
Totals rose 21% and 15% against `20260825-103505`, the largest proportional growth in the
collection this cycle, with the averages moving a decimal each. The ceiling has not moved at
all: `WhatGroup:LFG_LIST_APPLICATION_STATUS_UPDATED` is still exactly 15, reported at `@759-821`
now against `@634-697` when it was named — the function slid down the file and its score did
not budge. It is one event handler doing one job, with no seam of the kind that retired the
four entries before it, and the next branch added to it blocks a tag.
The regeneration dropped the hand-written history, and the bundle's ANALYSIS.md keeps it,
because it is the most useful thing this record has ever carried: every entry this watch list
has ever held was retired by splitting the function and not one by renewing an accept, four of
four. So is the diagnosis that went with the last of them — `lizard` scores Lua closures as
separate functions, so the two nested closures contributed nothing to `ConfigureTeleportButton`'s
20; it was twelve genuine top-level branches, and the extraction order proved it.
`20260807-110421` and `20260825-103505` still have no ANALYSIS.md and are not getting one.
Dating a reading to a day nobody took it is worse than a gap. This bundle has one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit e735453c80869ba5e5b58fdc2332ee1c67a769e3
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 18:00:48 2026 +0530
M5-02: a row that recorded its own expiry, and a section number that never existed
The register carried two `performance-§12` rows. The first claimed the
no-combat-path exemption on 2026-08-02; the second recorded that the first's
re-check trigger had fired on 2026-08-06, when `modules/Frame.lua`'s teleport
cooldown got a repeating ticker. The first row said so of itself, in its own
trigger cell — **FIRED 2026-08-06** — and was kept "only as the record of what was
claimed and on what evidence".
That is a graveyard entry, and it is the case `audit-review-history`'s new third
MUST was written against: an audit must evaluate every trigger against the tree
and report any row whose condition has already come true, because that deviation
ended on the day the condition came true and every day after it the document
asserts a live deviation that is not one. This one asserted one for a month. So
the row goes, and nothing is lost with it: the claim and the date it ended are now
the opening sentence of the row that survives, which is where a reader looking at
today's deviation is actually standing, and the evidence is where it always was —
issue #7 and `performance.md`'s regenerated sweep. The surviving row's heading and
its first trigger are reworded so neither points at a row that is no longer there.
`standalone-windows-§33` resolved to nothing. `standalone-windows.md` carries one
subsection heading and no numbered `§N` subsections at all, so §33, and §29 and
§32 in the same row's body, were three citations into a numbering scheme that file
does not use. The decision itself is sound and stays: the footer *Close* keeps its
label without a mark, because the SHOULD is about buttons whose action lands
somewhere outward and dismissing a dialog is neither outward nor ambiguous. What
changes is that the row now cites the section by bare filename and quotes the
SHOULD's own words in *What differs*, so the claim can be checked against the rule
it claims to depart from — which `documentation-§3` requires and a paraphrase
cannot do. `WG-52`.
The preamble gains the shorthand's expansion. This repository writes review
findings as `WG-R-NN` — in the register, in `core/WhatGroup.lua`, in
`modules/Frame.lua` and in two suites — and `docs/reviews/2026-09-07/` assigns
them as `WHATGROUP-R-NN`. Saying so once makes every use of the short form
resolvable without renaming a convention spread across the repository, which is a
decision for its own commit.
`tests/test_register.lua` is the gate over the mechanical half: slice
`## Documented deviations`, pull every `WG-`-shaped deviation id out of it and
require each to be ASSIGNED by a bundle under `docs/audits/` — heading a table
cell, a heading or a bullet, not merely quoted, because a bundle reporting a dead
citation quotes the dead id while doing so. The `-R-` shorthand is skipped
deliberately and the file says why. Watched go red on a planted `WG-99`.
luacheck 0/0; 554 passed, 0 failed. The count moved by one, so `docs/test-cases.md`
and the README `[tests]` badge moved in this commit. No version, no TOC, no
CHANGELOG. `libs/` and `tests/_kit/` untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 3a561580031ee4a6b5ae3a1af54f83a6df64f9aa
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 17:51:17 2026 +0530
M5-03: the README stops naming a settings tab that does not exist
Two README shapes documentation-§1 fixes, and one of them was a lie a player
could act on.
`## Version History`'s 1.3.0 row said "add a delay under Notify". There is no
Notify tab. `notify` is the row's SECTION — where the value is stored and how
`/wg list` groups it — and `settings/Schema.lua` files `notify.delay` under
group **Chat**, which is the tab it is edited on. The same feature's
`## What's new` bullet twelve lines above already said Chat, so the README
contradicted itself and the wrong half was the one a player scrolls to when
looking for what changed. Corrected to Chat.
The `### Settings panel` table was headed **Tab | Covers** with three rows for
this addon's one settings subcategory. §1 asks for one row per PAGE and puts the
per-tab breakdown in docs/settings-panel.md, which already carries it under
`## The tab strip` with every row and where it is stored. So the three rows
collapse into one `| Page |` row naming the three tabs in a sentence each, with
a link to that section. The per-panel prose under it stays — §1 explicitly
allows it where a panel is rich enough, and this one is.
Two of the gate's seven cases are this repository's. The tab case reads the
declared tab names out of settings/Schema.lua rather than restating them —
plus `Master controls`, whose rows are composed by LibKa0s and carry no
`group =` line here at all, and whose name options-ui-§15 mandates — and then
checks every "under <Tab>" the README uses to point a player somewhere. It
splits a bold breadcrumb at the arrow, because `**Chat → Notification Delay**`
names a tab and then a row on it and only the first half is a tab. The other
five are the collection's shared doc cases: the ten mandated hub sections, the
60-line spill threshold over all but the two registers, the anchors into the
hub, the two allowed history surfaces, and the README's sections in §1's order.
Watched both red in the working tree first. With the old README restored the tab
case names `Notify` and nothing else, and the table case quotes the
`| Tab | Covers |` header back.
546 -> 553 — docs/test-cases.md regenerated and the badge moved here. luacheck
0/0 over 39 files, 553 passed, 0 failed. No version touched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit e3a0503972a79eafafa1fda4fa6a94c49449f15b
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 17:44:54 2026 +0530
M5-04: docs/compat-layer.md, and the row that called seven shims none
The Tier 2 row read "core/Compat.lua normalizes LFG and unit APIs with no
addon-specific shim to document separately". There are seven, at :24, :40, :52,
:62, :83, :105 and :125, and documentation-§3's threshold is three. The row was
not a judgment call that went the other way; it was a description of a file
nobody had counted.
The doc covers the seven as three groups and spends its length on the parts a
reader gets wrong. Why GetSpellName falls through when the modern API is present
and answers nil rather than only when it is absent — the client can carry
C_Spell.GetSpellName and still have nothing to say about an uncached spell, and
going quiet there blanks a label the deprecated reader would have filled. Why
the two cooldown readers are two: the remaining-seconds one floors at the GCD,
because without it casting anything at all makes an eight-hour teleport report
"on cooldown" for a second and a half, and the swipe one floors at nothing,
because a swipe is a shape rather than a sentence. And why every default is what
it is, chosen from what the caller does with the answer rather than from habit.
IsSpellKnown gets its own section, because it is the one accessor with no modern
rung and that is recorded rather than fixed. WHATGROUP-R-06 conditions the
C_SpellBook rung on an in-client observation that both APIs agree, smoke-tests.md
§ 7a is that observation, and it has not been run — so the page says so, and says
what the failure looks like when it comes: every teleport desaturated with
"Teleport spell not learned" on a character who has learned all of them.
tests/test_docmap.lua holds the map to the directory: Present names a file that
exists, Not applicable names one that does not. Whether a trigger has fired stays
prose and stays a human's to judge; the status is not prose. Watched it fail with
the doc moved aside; it named the row.
545 to 546 cases; docs/test-cases.md and the README badge move with it. No
version bump, no CHANGELOG: this cycle does not ship.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit e0a7c5c80569b30505604d39a09dff3d73bc12f6
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 17:21:25 2026 +0530
M5-10: the IsSpellKnown rung waits, and the wait is now written down
WHATGROUP-R-06 asks for a C_SpellBook.IsSpellKnown rung above the bare
global in core/Compat.lua:62-67, so the one accessor of six with no modern
rung matches its five siblings at :24, :40, :52, :83 and :105. It does not
ask for it unconditionally. The finding's own fix text says the rung goes in
"only once an in-client check confirms both APIs present and agreeing", and
06_SMOKE_TESTS.md § 6.3 is that check.
No WoW client is available to this execution. The check has not been made,
so the rung does not land. Writing it anyway would be manufacturing the
evidence the finding gates itself on, which is the one thing the finding
explicitly refuses. core/Compat.lua is untouched.
What lands instead is the step, in the place this collection keeps steps
that need a login: docs/smoke-tests.md gains § 7a under patch-day smoke,
which is where the shim's failure would surface. It names the two dumps, the
learned and unlearned spell, the pass reading and the fail reading, and it
says what a fail means — if the two APIs disagree they are not
interchangeable, and the answer is a decision rather than a fallback ladder.
It also says what the headless suite can and cannot see: test_compat.lua:143
already nils the global and asserts false, which proves the shim degrades as
designed and proves nothing about whether false is the right answer on a
client that knows better.
The symptom is worth naming because it is loud. If Blizzard retires the
global, every teleport draws desaturated with "Teleport spell not learned"
beside it on a character who has learned all of them, and every chat row is
tagged (not learned). That is why the step is filed under patch day and not
under a one-off.
The finding is on the books as issue #15, state:untriaged, carrying the
blocked state and the two outcomes so the next person with a client knows
what to record and which of the two answers they are allowed to write.
luacheck 0/0, 545 passed 0 failed. No test count moved, so no test-cases.md
and no badge. No version anywhere: this cycle does not ship.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit ef2247cb924bdfa29ac17b2ba7b6ee8dc05bbab7
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 14:35:29 2026 +0530
M4-24: the exemption's evidence points at today's code
The plan's WhatGroup share of this item reads "amend the register row M2-21
makes accurate", and that half was already done: M2-21 amended it in its own
commit rather than deferring it here, so `ARCHITECTURE.md`'s live
`performance-§12` row has said "enforced rather than asserted" since 2026-09-08.
Reporting the item done on that basis would have been wrong, because the item's
acceptance is not the sentence -- it is that running the page's own grep
reproduces the table printed on it and that every `file:line` resolves. Neither
held.
Ten of the twelve call sites the sweep table cites had moved. Two are stale in
the ordinary way -- `settings/OptionsSetup.lua:219`, `:227` are now `:239`,
`:247`; `core/WhatGroup.lua:625` is `:638`; the three `modules/Frame.lua`
citations are `:351`, `:394`, `:733`. One had drifted in the way that actually
misleads: the row for `PLAYER_REGEN_DISABLED` cited `core/WhatGroup.lua:193`,
and `:193` still holds a `RegisterEvent` -- for
`LFG_LIST_APPLICATION_STATUS_UPDATED`. A citation that lands on a plausible
wrong line is worse than one that lands on a comment, because the reader
checking it stops checking.
The larger defect is substantive and it belongs to this cluster rather than to
line drift. `M2-28` established that `f` parents a `SecureActionButtonTemplate`
child, so the client refuses `Hide` on `f` under lockdown, and routed every hide
through one `hidePopup()` seam that returns false instead of calling into the
refusal. It did not update this page. So the sweep went on crediting the
`PLAYER_REGEN_DISABLED` row with "at most one `Hide`" -- the single thing that
edge cannot do -- and the `PLAYER_REGEN_ENABLED` row with "at most one `Show`",
omitting that the leaving edge is where every deferred hide lands, including a
`Close` the player pressed mid-fight that outranks the gate. Both cells are
rewritten to the asymmetry the code has: the entering edge can only show, the
leaving edge carries both directions. `ARCHITECTURE.md`'s event-table cell said
the same false thing one table above the register row and is corrected with
them.
The rows are now in the order the grep prints them, so a reader can hold the two
side by side, and the page reconciles its raw output rather than leaving it to
be inferred: seventeen lines, five of which are the pattern names appearing
inside comments, twelve call sites, eleven rows. That reconciliation is the
thing that makes a future drift visible instead of arguable.
The per-tick cost gains its third item. The ticker calls
`C_Spell.GetSpellCooldown`, builds a `NS.FormatDuration` string, and calls
`SetText`; the page, the register row and the ARCHITECTURE prose all said two of
those three. The string build does not change any conclusion, which is exactly
why it should be written down rather than rounded away.
The exemption is NOT withdrawn and is not re-litigated. It still rests on
criteria (b) and (c) with (a) recorded as fired on 2026-08-06, both register
rows keep their decided dates, and both re-check triggers are untouched. No Lua
changed.
`docs/frame.md` and `docs/module-map.md` cite the same timer as the thing that
ended the exemption, so their `:312` moves to `:351` here rather than being left
as the last two copies of a number this commit retires.
Deliberately not touched, and not fixed by stealth: the
`events-frames-taint-§8` register row cites `core/WhatGroup.lua:515`, `:523`,
`:531` for the join summary's gold-labeled rows and those have moved too, and
`docs/slash-dispatch.md` cites `core/WhatGroup.lua:180` and
`settings/OptionsSetup.lua:250`. They belong to a different deviation and a
different page, each with its own evidence, and repairing them from inside this
item would mean re-deriving claims nobody has re-read. They are reported as
follow-up.
Documentation only. luacheck 0/0 in 37 files, 545 passed / 0 failed, and the
`--list` inventory still matches `docs/test-cases.md` byte for byte -- no case
moved, so no `[tests]` badge movement. No smoke step: there is no runtime
behaviour here for a client to exercise, and the combat-edge asymmetry this page
now describes is already covered by `M2-28`'s smoke step 4.5, still NOT RUN.
No version moves. This cycle does not ship.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 3d252f0342a5b424ab4dba8c6cda4e3f6818cbb6
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 14:26:20 2026 +0530
M4-19: two sleeping cases wake up, and the third finding was never asleep
WHATGROUP-R-05, R-10, R-11. C21's rule is that a case which cannot go red is
worse than no case, because it reads as coverage. Three were named here; two
were real and are fixed below, and the third was a grep artefact.
WHATGROUP-R-05 is not-needed, and the proof is worth more than a change would
have been. The finding says "grep finds nothing pinning the suite list itself".
Nothing in tests/ does, and nothing needs to: tests/_kit/framework.lua:885-887
has Kit.run call Kit.assertSuiteInventory(dir, suites) itself whenever `dir` is
given, before a single suite is loaded, and tests/run.lua has always passed
`dir = "tests/"`. Both directions were watched red in the working tree:
deleting the "test_util" entry -> exit 1, "tests/test_util.lua exists but is
not declared in the suites list ... it is running zero cases today"
renaming tests/test_util.lua -> exit 1, "tests/test_util.lua is declared in
the suites list (position 6) but is not on disk"
That was already true before M4-01 re-vendored the kit -- the pre-M4-01
framework.lua carries the same call at :677 -- so this was never a gap, at
triage time either. Four sibling repos additionally call assertSuiteInventory
from a named case in test_harness; adding a fifth here would raise the case
count for its own sake, which C21 lists as a non-goal, and would fire strictly
later and quieter than an abort before load. So what lands is what 03_SPEC.md
gives LibKa0s for this same item: a comment naming the pin. The reason that is
not nothing is that this pin is INVISIBLE from the runner -- which is exactly
how a reviewer with a grep concluded it was absent, and how the next reader
would pin it a second time.
WHATGROUP-R-10 was real. "ships no private copy of the shared art" stat'd one
literal path, media/fonts/JetBrainsMono-Regular.ttf, so the only arrangement it
could ever see was a re-added copy of the same face under the same name. A
different face, the same face renamed, or a private duplicate of a catalog mark
all walked past a green case whose title promised to have looked. It now scans
every path git tracks under media/ for any .ttf/.otf and for any basename in the
library's ICONS catalog. Tracked rather than on-disk is deliberate: what ships is
what the repo carries. It fails rather than passes when it cannot look -- no
io.popen, a dead pipe, or an empty listing is a FAIL, because "no result" and
"nothing found" are the same string and only one of them is good news. Seen red
under both mutations its comment names, quoted there verbatim.
WHATGROUP-R-11 was real and had two halves. SEAM_FILES named four paths, the
four files that built descriptors the day it was typed; a fifth file growing a
`lib:New` descriptor was swept by nothing, forever, in silence. It is now
T.loadAddon.tocFiles -- the same derivation tests/loader.lua feeds the sandbox,
so the swept set and the loaded set cannot drift. Seen red with
`local d = { L = NS.L }` appended to core/Util.lua, which the old four-path form
reported PASS over. The `if src then` that wrapped the assertion is gone in
favour of assertTrue(src ~= nil).
What a reader would otherwise get wrong about that last line: it is a guard, not
a gate, and I did not see it fail. Once SEAM_FILES is TOC-derived, a path that
does not resolve kills tests/loader.lua's loadfile at runner start -- I checked,
and `mv core/Util.lua` aborts the whole run before any case executes. So the
unreadable-path red is real but belongs to the loader, not to this assertion.
It stays because the sweep must never be able to narrow in silence whatever the
list is derived from later, and the comment says so rather than claiming a red
nobody has watched.
luacheck 0/0 over 37 files; 545 passed, 0 failed -- unchanged either side, so
docs/test-cases.md and the README [tests] badge do not move, and `--list`
diffs clean against docs/test-cases.md. No smoke step: nothing outside tests/
changed and there is no in-game behaviour to observe. No version bump
(Decision 5).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 12619141562c1421e3e8058c2f3b92ad7bff6134
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 14:12:37 2026 +0530
M4-12: the four load-bearing TOC positions say what resolves at them
WG-47 / WHATGROUP-A-02. `toc-file-§5:144` binds load-bearing positions only, and
the collection audit that read it against all 23-81 lines of nine TOCs and
concluded the rule was unworkable was counting against the wrong denominator.
`M1-STD-02` settled that upstream; measured against its own denominator this
file had four unannotated load-bearing positions, and now has none.
What the reader would otherwise get wrong: the `# Core` block was already
annotated in three slots, which made the silence below it read as *examined and
free* rather than *never examined*. It was the second. All four constraints are
satisfied today by accident of the order someone typed, and all four fail the
same way if a future reorder is done from the file listing alone -- silently,
only in the client, with no test going red:
- `core\DebugLogSetup.lua` builds the console with `lib:New` at file scope
(`core/DebugLogSetup.lua:120`) and reads `NS.FONT_MONO`, which
`core/WhatGroup.lua:121` assigns at load. The library validates `font` as a
string, so above that line the console is built from nil.
- `defaults\Profile.lua` publishes `NS.C`, which `settings/Schema.lua:27`
takes as a file-scope upvalue and dereferences in every `add{}` at load.
- `settings\Schema.lua` creates `Settings.Schema`; `settings/Panel.lua:268`
splices the master rows into it at file scope.
- `settings\OptionsSetup.lua` publishes the LibKa0s-Options instance as
`Settings.Helpers` (`settings/OptionsSetup.lua:273`); `settings/Panel.lua:204`
calls `Helpers.MasterControls` at file scope. `Panel.lua:193-195` already
stated this dependency -- in the Lua file, which is not where the rule wants
it or where the person moving the TOC line is looking.
`settings\Panel.lua` itself carries nothing and is compliant: its position is
pinned by the two annotated lines above it, and `toc-file-§5`'s worked example
is explicit that a rule making every line restate its neighbour's comment
produces noise the next reader learns to skip.
The three group headers also pick up the *conventional* SHOULD note, once per
group, which is the reading `M1-STD-02` settled and `AbsorbTracker.toc:36-37`
is the only prior instance of. `# Settings` carries one extra fact: the group
as a whole must follow `# Core`, because `settings/Slash.lua:67` builds
`CLI_MISSING` from `NS.LIBKA0S_MISSING` at file scope. That is a real load-time
read the audit did not name, but it is a cross-group constraint pinned by
`core\CoreSetup.lua:34` being first in an already-annotated block, so it belongs
in the group note rather than as a fifth per-line MUST.
Comment-only. No file entry moved, no order changed, no behaviour changed.
No new smoke step: the client-side surface here is that the TOC still parses
and still enumerates all fourteen addon files, which is exactly what §1.1 Cold
load covers and it is already flagged NOT YET RUN for session 3. A step
asserting a comment is a comment would be noise.
Verified: `luacheck .` 0 warnings / 0 errors in 37 files; `lua tests/run.lua`
545 passed, 0 failed; `--list` diff against `docs/test-cases.md` empty. The case
count does not move, so no `docs/test-cases.md` or `[tests]` badge change. Per
Decision 5 this cycle does not ship: no version bump anywhere.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 5abc522d28a4b0b12b6c16e8610585dbed3461a9
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 14:06:05 2026 +0530
M4-11: the test tree comes into lint scope
`.luacheckrc:9` excluded bare `tests/`, which is what `lint`'s own template shipped until
`M1-STD-01` amended it, so all 21 of this repo's own test files sat outside the gate. `luacheck .`
covered 16 files and reported 0/0; it now covers 37 and reports 0/0, and only the second figure
means what every RESULTS.md table has been quoting it to mean. `tests/_kit/` stays excluded for a
reason that does not generalise: it is a byte copy of LibKa0s' `testkit/`, linted there as source,
so linting the copy would report every finding twice and let the copy drift green while the
original went red — the one state `tests/test_vendor_sync.lua` exists to forbid.
`_dev/` joins the list in the same line, closing `WHATGROUP-A-14`. It does not exist in the tree
today and that is exactly why it belongs there: `.pkgmeta:12` already reserves it whether or not it
exists, and a scratch directory that lint would happily read on the day someone creates one is a
gate that disagrees with packaging about what is and is not this addon's code. Both config files
now say the same thing about it.
The harness global is declared in a `files["tests/"]` stanza rather than in the top-level
`read_globals`, and the difference is not cosmetic. A name at the top level is a permission granted
to `core/`, `modules/` and `settings/` as much as to a suite, and a shipped file reaching for the
test harness is precisely what lint is here to refuse. It is spelled `_G.WHATGROUP_TEST` and not
bare, because every suite reads it through `_G` and `tests/run.lua:75` is the writer —
`globals`, therefore, not `read_globals`. `_G.WhatGroupDB` is named beside it as a field for a
narrower reason: `tests/loader.lua:91` sets it to nil before each boot, because the kit's AceDB
fake resolves a SavedVariables name against the real `_G` and would otherwise hand the next
instance the previous one's saved table. Its bare name stays writable above, for the shipped files
that own it.
Eight findings came out of the newly-linted files and all eight are fixed rather than ignored. An
ignore wide enough to silence the wall would read as coverage in every table quoting the 0/0 and
provide none, which is worse than the exclusion it replaces.
Five were dead bindings, and each one is a reader's trap rather than a style point.
`tests/test_database.lua:25` and `tests/test_settings.lua:120` both re-destructured assertions off
`T` mid-file: the first bound an `assertNil` nothing below it calls, the second re-bound an
`assertTrue` already bound at `:3`, so a reader hunting for where an assertion comes from finds two
answers. Three suites — `test_frame.lua:126`, `test_slash.lua:80`, `test_util.lua:213` — took the
third return of `T.bootAddon()` and never touched it. In a harness where taking `mock` is how a
case says "I am going to assert on what the client was told", an unused `mock` reads as an
assertion someone forgot to write. Dropping it says the case genuinely asserts through `env` and
the addon's own state.
The last three are one bug with three warnings. `tests/wow_mock.lua:494` named the AceGUI receiver
`self`, and the three widget methods defined inside it — `SetRelativeWidth`, `SetSliderValues`,
`ReleaseChildren` — each take their own implicit `self`, which is the WIDGET, not the library. Two
different tables under one name in one scope is how a mock grows a defect that reads as correct on
every line; renaming the outer parameter to `lib` costs nothing, since it is passed straight
through to `baseCreate` and never read again.
`docs/testing.md:275` said the quiet part out loud and was made wrong by this commit: it told a
reader that `tests/` is excluded, which was true when written and is now the opposite of the rule.
It is corrected in the same commit, and states which single directory inside the tree is still
carved out and why — a stale scope note is worse than none, because it is the document someone
consults before trusting a clean run.
No case count moves: 545 passed, 0 failed, before and after. `docs/test-cases.md` and the README
`[tests]` badge are therefore untouched, and `--list` still matches the document byte for byte.
Nothing here is reachable from the client — the lint config is not shipped and the test tree is not
loaded in game — so this item adds no smoke step.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 202ce8950fb614025172108097b8221a32fad0da
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 14:00:24 2026 +0530
M4-09: the stub-parity gate becomes a suite, and names its live half
The plan's premise for this repo is stale and the correction matters more than
the change. 04_EXECUTION_PLAN.md's row says six of the nine "have nothing today",
and 01_CONSOLIDATED_FINDINGS.md:153 says the six have "nothing checking the stub
against the live surface". That was true when the audit was triaged; it stopped
being true on this branch, at 7e4813a, which added four Kit.assertSurfaceParity
cases covering all four adopted seams. So nothing here closes a coverage gap. What
lands is a MOVE and a FORM change, and the form change is the part with a future
in it.
The four cases leave tests/test_libka0s.lua for tests/test_surface_parity.lua,
which is the filename the spec's acceptance names in all nine. Case count does not
move -- 545 either side -- so the README badge does not move; docs/test-cases.md
does, because the inventory groups by suite file, and it is regenerated here.
The three LIBRARY-BACKED cases move onto Kit.assertSurfaceParity's by-name form,
which arrived with kit 15 in M4-01. The four-argument form walks every key of the
live table, so the Options case had to exempt `__print` by hand -- one line the
v1.27.0 re-vendor added, and one that would have been joined by another on the
next re-vendor that grew an internal. The by-name form compares Kit.publicMembers,
which drops the whole `__` prefix, so those exemptions are the kit's rule now
rather than this file's typing. libs/LibKa0s/Options.lua's own comment at O.__print
already states that rule; the case now enforces it instead of restating it.
Core does NOT move onto the by-name form. It is not a major's surface as this
addon consumes it: core/CoreSetup.lua hangs its members on NS itself, so the
namespace IS the seam's surface and there is no name to look a live half up under.
THE LOAD-BEARING HALF IS ONE TABLE IN tests/run.lua, and it is what a reader
would otherwise get wrong. Kit.expose auto-wires a LibStub off the exposed table,
which is correct for a repo whose stubs mirror LIBRARY TABLES. This runner exposes
factories rather than a built addon, so nothing is auto-wired at all -- and all
three of this addon's library-backed stubs mirror an INSTANCE, what
lib:New(descriptor) returned, so even a reachable LibStub would answer the wrong
thing ("LibKa0s-Options-1.0" is a four-member table: LAYOUT, New,
PatchAlwaysShowScrollbar, STRINGS). Watched fail in the working tree with the
suite declared and the registration absent: all three red with the kit's own
"no surface source is registered ... this gate cannot run", which is its
deliberate bargain -- an unresolvable name reddens rather than quietly passing.
That is where AbsorbTracker's M4-09 differs from this one: there the auto-wiring
fires and resolves the wrong table, here it never fires. Same fix, different red.
The registration costs ONE extra addon load at runner start. It is read from and
never handed to a case, so the per-case isolation the factories exist for is
untouched -- and it is why this file's live half carries no test seams a previous
suite stamped on, which is an exemption AbsorbTracker's shared-instance runner has
to carry and this one does not. Set BEFORE Kit.expose, which is what makes it
stick: expose registers a source only when none is registered yet.
The degraded arm's file list is now DERIVED, as T.loadAddon.libFiles, instead of a
second hand-typed copy of libs/LibKa0s/LibKa0s.xml's load order. A file added to
the vendored library joins the skip on the re-vendor commit rather than on the day
somebody notices (anti-patterns #48). The copy in test_libka0s.lua stays because
its other degraded cases still use it.
ONE CONSEQUENCE WORTH KNOWING BEFORE READING THE STUB. The twelve `__` members in
settings/OptionsSetup.lua's degradation arm are no longer compared by anything --
that is exactly what the `__` filter means. Their comment claimed the parity case
compared the whole set, which was the reason they were there; it is corrected in
this commit to say plainly that the reason is now shape alone and that shape is a
weaker reason than a gate. They are kept rather than deleted because deleting
shipped lines was not what this item was for; the decision is stated so the next
person makes it deliberately instead of inheriting a dead justification. The two
other stale citations in that file, at the tabbed-page block and the composer-data
block, are repointed at the new filename in the same commit.
No smoke step. The row carries no warning mark and its verification is a headless
mutation, so nothing here schedules client time.
Verification. luacheck 0/0 in 16 files. `lua tests/run.lua` 545 passed, 0 failed,
unchanged either side. `--list` diff against docs/test-cases.md empty, CR-normalized
both sides. The new file is CRLF and `git ls-files --eol | grep -E 'w/(lf|mixed)'`
still returns only the eol=lf shell script. Each of the four cases watched RED in
the working tree under a planted mutation and green after revert:
Core -- deleting `function NS.MakeCloseButton() return nil end` from
core/CoreSetup.lua's degraded arm gives "MakeCloseButton is missing
(live: function)"
DebugLog -- deleting `Clear` from core/DebugLogSetup.lua's stub names Clear
Options -- deleting `H.SetRenderer` gives "SetRenderer is missing (live:
function)"
Slash -- degrading `Text` to `false` gives "Text is a function live but
boolean degraded", which is the arm a present-but-not-callable
member falls into and the one a plain "is the key set?" check
waves through
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit c6b6c3af0390cfa3902b8ebf51a0965ef9279add
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 13:09:59 2026 +0530
M4-13: the British spellings go, against the published list
localization-§5 now publishes a canonical BRITISH/ALLOWED pair (M1-STD-12) and
requires every mechanical gate to read it whole. This sweep is that list run over
WhatGroup's authored text: source comments, test case names and assertion
messages, and every live doc.
The audit named three comments — modules/Frame.lua:267, settings/Schema.lua:115,
settings/OptionsSetup.lua:92. Two still sit where it said. The Frame.lua one is
now at :333, because M2-21 and M2-28 both added lines above it; the comment is
the same comment. Against the full published list the real figure is 51 lines
across 22 files, and the difference is entirely the entries a colour|behaviour|grey
grep cannot see: honoured, neighbouring, favour, labelled, modelled, modelling,
acknowledgement, and the two uppercase MODELLED / UNMODELLED in the mock.
Three spellings stay, and they are the section's Blizzard-symbol exception rather
than misses. `cancelled` is an LFG application status the server sends, so
core/WhatGroup.lua:727's APPLICATION_ENDED key, the literal handed to
LFG_LIST_APPLICATION_STATUS_UPDATED at tests/test_capture.lua:433, and
docs/data-flow.md:56's transcription of the four status strings all keep the
spelling Blizzard uses. Correcting any of them would make the arm stop matching,
silently — the capture would sit in the tables until the next group-leave, which
is exactly the defect WG-R-07 fixed. The prose around them is swept.
The audit also said no player-facing string was affected. Re-checked against the
wider list and that holds: every hit outside the three above is a comment, a doc
line, or a test name.
Four case names moved, so docs/test-cases.md is regenerated here. The count does
not move, so the README [tests] badge stays at 545.
The plan's verification grep still returns one line, in docs/revendor/ — a frozen
dated bundle, which 03_SPEC.md names as a non-goal. Add --exclude-dir=revendor and
it returns nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit a8ef42c908360bb375fe6f6225784e550b72bc24
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 11:43:00 2026 +0530
M4-01: adopt LibKa0s v1.27.0, and wire the gate that came with it
Both payloads move whole from the v1.27.0 tag -- `libs/LibKa0s/` and `tests/_kit/`
-- and the CLAUDE.md provenance line moves with them, because that line is what
`tests/test_vendor_sync.lua` reads to decide which tag to check the bytes
against. A line ahead of the bytes and a copy taken from untagged master both
fail there, which is the point of it.
`diff -r` between a clean `v1.27.0` checkout and each vendored tree is empty
BYTE for byte, not merely content-with-CR-ignored.
TWO THINGS IN THE PAYLOAD REACH THIS REPO'S OWN FILES, and invariant 1 says a
gate and its fix land together rather than leaving the tree red between commits.
* The kit now SHIPS a suite. `tests/_kit/test_eol.lua` arrives with kit 15 --
the working-tree line-ending gate, over every path `git ls-files` reports --
and `Kit.assertSuiteInventory` fails the whole run until the runner declares
it, deliberately, so a gate cannot arrive with a re-vendor and then quietly
run nothing. It joins the suites list with its own `dir`. That is the +1 in
the case count, and `docs/test-cases.md` and the README badge move with it
here. It passes on this tree: the only tracked file that is LF on disk is
the kit's own `.sh`, which `.gitattributes` pins that way on purpose.
* Options minor 8 publishes `O.__print`, the one instance print sink the shell
hands OptionsWidgets so it stops building a second one from the same
descriptor. `grep -rn "__print" core modules settings` is empty here, so it
is live-only in exactly the sense the rest of that list is. The library's own
comment claims a stub need not mirror it because `Kit.assertSurfaceParity`
skips the `__` prefix; that holds for the kit's BY-NAME form, which filters
through `Kit.publicMembers`, and not for the four-argument form this case
uses, which walks every key of the live table.
No version bump anywhere: this cycle does not ship. The `[tests]` badge is a
count, not a version.
The client-only half is written into `docs/smoke-tests.md` as § 12a, named in
the re-vendor sentence at the foot of § 12 and in the quick-reference checklist.
NOT claimed here. This addon hands its whole strip to `RenderTabbedSchema` and
measures no band of its own, which is exactly why it can say nothing about one
out of game.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 7c8df489cc7b813588fe70ebf4cff505038e9222
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 11:04:38 2026 +0530
M4-C1: say when the vendored-copy diff is supposed to be non-empty
docs/testing.md gave four `diff -r` commands against `../LibKa0s` and marked two
of them "MUST be empty". Neither can be empty right now, and neither is wrong.
The library is tagged v1.27.0 and this addon vendors v1.26.0, so the library diff
reports 306 lines and the test-kit diff 947 — measured just now, not estimated —
and it stays that way for the whole of M4 until this repo re-vendors. A gate that
is red by design and does not say so trains a reader to ignore it, or worse,
invites them to re-vendor an untested library release purely to make it quiet.
What the section was missing is that the four commands compare against the
sibling checkout's WORKING TREE — whatever `../LibKa0s` has checked out — which
is a different question from "is the vendored payload the release this addon
claims". Those two questions only agree while the library has tagged nothing
newer than this addon has taken, and the gap between a release and the re-vendor
carrying it is normal rather than exceptional.
So the note names the authoritative check: against the TAG the root CLAUDE.md
names, extracted from the provenance line and pulled out of the sibling with `git
archive`. Run here it reports zero lines for both payloads, which is the answer
that means something. And it is not new work — tests/test_vendor_sync.lua already
asks precisely this inside the suite, greping the tag out of CLAUDE.md and
reading that blob from git, so a green suite has answered it. The run below is
0 SKIPPED, which matters more than usual here: those cases degrade to a skip when
the sibling or git is missing, and a skip is not a pass.
The two inline "MUST be empty" comments become "empty vs the CLAIMED tag" so the
code block no longer contradicts the paragraph under it, and the working-tree
diffs are re-framed as what they honestly answer — how far behind the library
this addon is, which is release planning rather than a gate.
The root CLAUDE.md's one-line summary of the gate said flatly that a non-empty
content diff is a real fork. That is true against the claimed tag and false
against the sibling's working tree, and it is the working tree the command it
quotes actually reads — so it is corrected here too rather than left as the short
version that contradicts the long one.
Verified here: luacheck 0 warnings / 0 errors in 16 files; 544 passed, 0 failed, 0 skipped, 544 total; the test inventory is in sync with
docs/test-cases.md. Documentation only — no behavior, no test moves, no
case-count change, so docs/test-cases.md and the README badge stay put. No client
needed and no smoke step added: nothing here reaches the game.
No version bump anywhere: this cycle does not ship.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit caf1cbdb05717e90a7abf287725dc0dae211c41f
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 10:04:57 2026 +0530
M3-05: re-vendor LibKa0s v1.26.0
The last of the five, and the same shape as the other four: both vendored
payloads copied whole from the tag, CLAUDE.md's provenance line rolled in the
same commit, no WhatGroup code touched. 544 cases before, 544 after.
WhatGroup consumes MasterControls and ColorPair and asks the composer for no
font, border or bar texture, so OptionsCompose minor 3 -- the empty-dropdown
fix this release exists for -- has nothing to repair here and travels because
the library ships whole. OptionsWidgets minor 14 is the piece that matters to
this addon: the tab strip acquires its buttons and content panel from a pool
rather than building a set per click, and this settings panel draws one.
Take the tag, not the sibling checkout. ../LibKa0s is at v1.27.0 already, so
`diff -r ../LibKa0s/LibKa0s libs/LibKa0s` answers a question nobody asked;
`git -C ../LibKa0s archive v1.26.0 LibKa0s testkit` is the reference, and it is
the right one because archive runs the content through eol=crlf and reproduces
the CRLF bytes on disk, which `git show` does not. Both diffs against it are
empty -- content with --strip-trailing-cr and bytes with plain `diff -r`.
The provenance line moved before the bytes so the gate could be seen refusing
the mismatch: 543 passed, 1 failed on "libs/LibKa0s is the LibKa0s release
CLAUDE.md says this addon bundles". That line is an input to
tests/test_vendor_sync.lua rather than documentation about it, which is the
whole reason the two have to land together.
tests/_kit is untouched by this release -- Kit.VERSION is 14 at v1.25.0 and at
v1.26.0 -- and was recopied anyway, since the kit and the library are
re-vendored as one act.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit d983ab2c7997d0fc38fc89b6c6cc152cdd36c327
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 02:24:47 2026 +0530
M2-28: the popup cannot be hidden in combat, so stop asking
Reported from the client: "AddOn 'WhatGroup' tried to call the protected
function 'WhatGroupFrame:Hide()'", raised by pressing Close during a fight.
buildFrame parents a SecureActionButtonTemplate button to f. The client refuses
Hide on a protected frame and on every ANCESTOR of one while in combat, so
f:Hide() is protected from every call site -- the Close button, ESC, and the
visibility gate alike.
modules/Frame.lua said the opposite in as many words: "this addon has always
taken f:Hide() as unprotected, and the alternative is a window the player cannot
dismiss". That premise was false, and it is the whole defect. ApplyFrameSize and
ResetFramePosition sit two functions away and are both combat-guarded, both
citing the secure child; the visibility seam was the one place that reasoned the
other way.
M2-21 built on that false premise and made it worse. It routed the gate onto
PLAYER_REGEN_DISABLED, so entering combat with the popup up and visibility set
to "out of combat" fired a refused Hide automatically on every pull, where
before it took a deliberate Close press.
Every hide now goes through one hidePopup() seam that returns false rather than
calling into a refusal. A Close pressed in combat is remembered and honoured at
PLAYER_REGEN_ENABLED, and it outranks the gate on that edge -- without that the
player's own dismissal is overwritten by the value that permits the popup. The
locale already carried "Popup deferred until combat ends." for this shape.
What the player gets is asymmetric, and the comment now says so rather than
claiming symmetry:
inCombat hides when combat ENDS -- legal, honoured on the edge
outOfCombat hides when combat STARTS -- refused, honoured one edge late
The popup genuinely stays up for the fight under outOfCombat. There is no way
around that while the secure child exists, and attempting it is strictly worse
than deferring: the frame does not go down either way and the player gets a red
error naming this addon.
The reason 528 cases stayed green through all of this is that tests/wow_mock.lua
modelled combat but not protection, so it answered "fine" to the exact call the
client blocks -- and M2-21 extended that mock without noticing. The mock now
tracks the parent chain, marks Secure* templates protected, and records every
Hide the client would refuse in mock.blocked. Four cases assert that list stays
empty; all four were seen red against the unfixed Frame.lua and green after.
Two existing cases asserted the popup hides on the combat edge. The assertions
were wrong rather than the code, and one of them tested the refusal path while
believing it tested the re-show, so it now drives the re-show off `inCombat`
where the hide is legal.
544 passed / 0 failed (was 541), luacheck 0/0 in 16 files. Smoke step 4.5 added
and NOT RUN -- no client here, and it is the only check that can see the real
error.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit cc3f20eec933271420920fdd24775d5a245127f0
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 01:30:30 2026 +0530
M2-22: pair a capture to its own application, not to whichever answers first
The apply hook has a searchResultID and no application id. The status event has
an application id and no searchResultID. Nothing in the client hands you both,
so the code had to pick a join, and what it picked was arrival order: applies
went onto the tail of a FIFO and every "applied" popped the head.
That is only correct if the server acknowledges applications in the order the
client sent them, and nothing promises that. Apply-order belongs to the client;
acknowledgement order belongs to the server. With one application outstanding
every possible pairing is the right one, which is why this has been shipping
quietly. With two, an out-of-order acknowledgement pairs each capture with the
other application's id and the popup names the wrong group -- and the group
title is the entire point of the addon.
The join that does exist is C_LFGList.GetApplicationInfo, whose first return is
the search-result id the application was made against. The addon already leans
on it at inviteaccepted (F-004); this makes it the key. Captures are filed under
their searchResultID and "applied" resolves its appID through the same bridge to
find one. ResolveSearchResultID is that hop split out of
CaptureGroupInfoFromApplication, because the status arm wants the id and not a
second round-trip for a table it already holds.
Removing the FIFO removes something that was doing work nobody had noticed: a
capture that never became an application used to get pushed off the front by
later applies. Keyed by id, nothing displaces it, so the decline and cancel
statuses now clear it explicitly. All three decline spellings are in that set.
Blizzard sends the bare "declined" only sometimes -- a full or delisted group
carries its reason in the status string, and those are the declines a player
actually meets, so matching only "declined" would have left the arm nearly dead.
Second, unrelated except that it is the same shape: autoShow was read when the
notify timer was armed and used when it fired. It is a decision about what to do
at fire time, so a player who switches the popup off during the delay window
means it now. The delay beside it stays where it is and that is not an
inconsistency -- it is the timer's own argument, and there is no later moment at
which it could be read. The existing case pinned the old behaviour and asserted
the popup opened anyway; it is inverted rather than added to, since both
directions cannot be true.
The two short status arms moved out to pairApplication and dropApplication. That
is not tidying: inline they took the handler from CCN 15 to 18 and put the
repo's first complexity warning on the board. The inviteaccepted merge is the
part that has to be read as a whole, so the other two pay for it by being named.
Written red first. Three new capture cases and the inverted notify case failed
against the old code -- "expected Second, got First" for the two-application
pairing, a live table where the declined capture should have been gone -- then
541 passed, 0 failed. luacheck 0/0 in 16 files; the four-suite run is green with
0 complexity warnings and max CCN back at 15, where it was before this change.
docs/smoke-tests.md 5.2 said it tested the FIFO pairing and asked for three
applications and one invite, which cannot fail for this reason. It now asks for
two, one accepted and one declined, and says what a swap looks like. That step
is unrun: no client is available here. The test badge moves 538 to 541 with
docs/test-cases.md. No version moves; nothing in this cycle ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit d51d04c100f54f3b5d1e3a975b203bde3d247349
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 01:26:26 2026 +0530
M2-19: the screenshots go the same way the logo renders already did
This file has explained since it was written that .png and .jpg are ignored
because WoW cannot load either format, so shipping them would add megabytes per
download for files the client physically cannot use. That reasoning was applied
to media/logos and stopped there. media/screenshots is three tracked .png files
in exactly those formats, 894,173 bytes, and the ignore list never named them --
so the argument the file already makes was true of a directory it did not cover.
WHATGROUP-R-09 is that inconsistency, and 880K here is the smallest of the four
addons in the collection with the same gap.
CLAUDE.md and DEPENDENCIES.md leave the package too. They are documentation for
people working on the addon, not for people running it. README.md and LICENSE
stay, and that is a decision rather than an oversight: players read those.
The .claude line was already here with a comment saying dev-only, never shipped
to players. True, but it undersold the situation and pointed the next reader at
the wrong question. .claude is untracked -- `git ls-files .claude` returns
nothing -- so a packager clone never carried it and removing or keeping this line
changes no downloaded byte at all. Its comment now says "untracked; listed under
packaging.md:28", which is the actual reason it belongs here: the standard's
strong form MUSTs every root dot-entry present in the repo be named or justified
whether or not git tracks it, because an enumeration goes stale silently. The
same evidence rejected the identical .superpowers filing in three other repos
this cycle, and it has to mean the same thing in both directions. .pkgmeta names
itself for that reason as well; the standard's own minimum template omitted it
until v2.39.0.
Verified with `git ls-files` filtered through this file's own ignore: list, which
is the only check available -- there is no packager dry-run, since no repo here
carries .github/workflows and packaging.md:32 puts CI out of scope. It was run
before the edit and named five shipped files at 910,932 bytes; it now reports 198
tracked files shipping, none of them a screenshot or a dev document.
A second agent was editing core/ and tests/ in this working tree while this
landed, so luacheck and the harness were run against a clean archive of HEAD
carrying only this file: 0 warnings / 0 errors, 536 passed, 0 failed, 2 skipped.
Only .pkgmeta is in this commit. No version moves; nothing in this cycle ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit a6c03ce3fd8b2b41d7b97a20873cd8affdcd74a3
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 00:48:54 2026 +0530
M2-25: key the Master controls hook off the library, not off a copy of it
Two literals in settings/Panel.lua that were correct on the day they were
typed and could only ever get less correct.
The afterGroup table keyed its reset button pair to "Master controls",
which is a hand-typed copy of OptionsCompose.lua:50 -- a string the library
already publishes as O.MASTER_GROUP and stamps onto every row the composer
emits. The two agree today, which is exactly why the copy survived a
review. What a copy cannot survive is the library renaming its own group:
that moves the group on every composed row, and RenderTabbedSchema fires
afterGroup PER GROUP, so a host keyed to the old spelling has a hook that
matches nothing and simply never runs. Reset position and Reset all
settings vanish from the tab. Nothing raises, no row goes missing, and the
page looks perfectly ordinary -- the class of failure you find by reading
the source a year later, if at all.
The hook is therefore added after the constructor rather than inside it,
under a presence check, and the check is not defensive padding. The
library-less stub in settings/OptionsSetup.lua carries the five composer
FUNCTIONS and none of their published DATA, deliberately, because copying
the value sets and the wording whose nine-way drift OptionsCompose exists
to end is the mistake the whole seam is built to refuse. So MASTER_GROUP is
nil on that path, and [nil] = inside a table constructor raises at FILE
LOAD -- which would take every row settings/Schema.lua had just registered
down with it, the precise half-load that stub was written to prevent.
Planting the unguarded version turns eighteen degraded cases red with
"table index is nil", which is the proof that the shape is load-bearing.
That path also composes no master rows and its tail draws nothing, so the
right number of hooks there is zero: the absence is the degraded shape
stated honestly, not a fallback. An `or` respelling the name would put the
copy straight back and hide all of the above.
The second literal was the composer's addonName, passed as "WhatGroup"
while the file binds the first vararg at :14 like every other file in the
repo. That vararg is the FOLDER the addon was installed into, and it is the
only string in the process that tracks a rename or a second copy dropped in
beside the first. It goes straight into "Enable <name>" -- a control whose
entire job is to say which addon you are turning off. Same silent staleness
the inline C_AddOns ladders had before core/EnvSetup.lua ended them.
Two cases, both watched red first. The group-name one is a source guard,
because the constant and the literal are the same interned string at
runtime and no assertion can tell them apart; it pairs the grep with a live
check that all six composed rows really do answer to the published
constant. The addonName one is behavioural: tests/loader.lua grew an
addonName option so a case can load the addon as WhatGroup_PTR and read the
label back, which is the only way a vararg read and a literal that happens
to match are distinguishable at all. 536 -> 538, badge and docs/test-cases.md
moved with them.
Two comments claimed nothing in this addon reads a published composer
constant. One of them now does, so both say so -- and both say why
MASTER_GROUP stays out of the stub anyway. settings-panel.md's code sample,
ARCHITECTURE.md, common-tasks.md and module-map.md all quoted the literal
key and now quote the constant; testing.md documents the new loader option.
No version moved: this cycle does not ship.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 9bcc54a677025cb91094edc32db12df948c425e5
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 00:37:10 2026 +0530
M2-21: re-ask the visibility gate when combat starts and ends
General visibility was wired the way Master scale is wired -- read it when
you draw, re-read it when the user changes it. That is right for a size and
wrong for this row, because two of its four values are functions of a
variable the user does not control and that changes without anyone touching
the panel. Only in combat and Only out of combat were therefore correct at
every fresh open and inert across the one transition they exist for: pull
with the popup up and it stayed up, drop combat with the popup gated away
and it never came back. A setting whose answer changes on its own needs an
event, not an onChange.
So OnEnable registers PLAYER_REGEN_DISABLED and PLAYER_REGEN_ENABLED, both
to one OnCombatStateChanged, and ApplyFrameVisibility -- which only ever
hid -- gained the other half. The edge is taken from the event NAME rather
than from InCombatLockdown(), and that is not fastidiousness: the client
fires PLAYER_REGEN_DISABLED at the start of the lockdown and the API can
still answer false on that same frame, so a handler that asked would
evaluate the gate against the state the player has just left and get the
answer backwards in both directions. visibilityAllows takes an optional
override for exactly that one caller; everyone else passes nothing and gets
the live read, which is correct for them because they are not on an edge.
The re-show is gated on pendingInfo. A No data popup appearing the moment
the player pulls is worse than no popup at all, and a transition never
builds the frame -- the lazy build is this addon's taint contract and a
combat edge is the worst imaginable moment to trip it.
The second half of the item is the cooldown ticker, and it is the same bug
seen from the other side. performance-§12's declined wiring rests on one
sentence -- the ticker cannot outlive the window that armed it -- and that
sentence had been false since the ticker landed. applyTeleportNote armed
before ShowFrame reached f:Show(), and OnHide, the only cancel site, fires
on a transition; so a popup the gate declined to show left a repeating
timer with no cancel site at all, running for the rest of the session. The
arm now sits behind f:IsShown() and is re-run from the popup's OnShow, the
exact mirror of the OnHide that cancels it. One seam each way, which is why
adding a second path to the screen in the same commit did not need a second
arm site. The register row in ARCHITECTURE.md and the sweep in
performance.md are amended to say enforced rather than asserted; the
exemption is not withdrawn and Perf is not wired, because only criterion
(a)'s supporting detail slipped and (b) and (c) never depended on it.
Eight cases, all seen red first. Five failed against today's code as
written. The remaining three are the guards, and each was proved by
planting the mistake it exists to catch: gating the arm on IsShown() with
no OnShow turned "a popup that reaches the screen later still gets its
ticker" red -- along with two cases that predate this work, which is the
review's stated risk arriving on cue; a symmetric ApplyFrameVisibility
without the pendingInfo clause turned "never opens a popup with nothing to
show" red; and letting the handler reach ShowFrame turned "a transition
with no popup built is a no-op" red. A verification that has only ever been
green has proved nothing, and this repo now has three fewer of them.
The mock grew a sixth modelled behaviour to make one of those honest.
RegisterEvent recorded only that an event had been registered, so a suite
could assert the registration exists and then call the handler by hand --
which passes just as happily when the two are not connected to each other.
It now records the handler name and mock.fireAddonEvent dispatches the way
AceEvent does. That gap is widest here, where two events share a handler
and the event name is the only thing telling it which edge it is on.
528 to 536 cases; docs/test-cases.md and the README badge move with them.
docs/smoke-tests.md gains section 3.8, in this commit rather than in the
remediation bundle, because a check living only in a frozen directory in a
thirteenth repository is runnable next cycle only by someone who knows the
directory exists. It walks both edges in both settings, asserts nothing
opens on a transition with no capture pending, and watches for a taint line
at the moment a live frame is hidden from a combat-edge handler -- which no
headless case can see. It has not been run; no client was available.
No version moves. This cycle does not ship.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit e0c99f6105ee2ffeaef0ef25fab717a347fbbe34
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 8 00:19:50 2026 +0530
M2-20: stop assigning the StaticPopupDialogs global
settings/Schema.lua's EnsureResetPopup opened with
StaticPopupDialogs = StaticPopupDialogs or {}
directly beneath a fourteen-line comment explaining that registration is
deferred precisely because writing to that global taints GameMenu's
button-init closures. The guard was the very thing the paragraph above it
forbids: an addon-author assignment to the protected table, run at the
moment the player asks for a reset rather than at file load, but an
assignment all the same. It also protected against nothing. Every retail
client builds StaticPopupDialogs long before an addon file runs, so the
`or {}` arm is unreachable and the whole line is dead.
Deleted, keeping the indexed write below it. The comment now says out loud
that the absence is deliberate, because the next reader who notices an
unguarded table index is going to want to add the guard back.
Nothing headless moves. 528 cases before and after, and the five that
exercise this function stay green -- breaking the surviving key name turns
them red, which is how I know the deletion sits on live ground and not on
code the suite never reaches. That is also the limit of what the harness
can say here: taint does not surface as a test failure, so the only real
proof is in the client.
Hence docs/smoke-tests.md gains section 1.4, in this commit rather than in
the remediation bundle. The bundle is a frozen directory in a thirteenth
repository; a check that lives only there is runnable next cycle only by
someone who knows the directory exists. 1.4 invokes the reset popup in
combat and out and then walks the logout path that section 1.3 has guarded
since the original leak, which is the step that would actually catch a
regression. It has not been run -- no client was available -- and is
recorded as pending.
No version moves. This cycle does not ship.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit 509a36d6af0e37a9436f1db4945c5d31d982bef9
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Sep 7 19:21:40 2026 +0530
docs: commit the frozen 2026-09-07 review and standards-audit bundles
Produced by /wow-addon:review and /wow-addon:standards-audit as part of a
collection-wide pass over all nine addons and LibKa0s. Both passes re-ran this
repo's out-of-game suites from scratch first, so every finding rests on the
numbers measured that day rather than on the committed record.
Frozen dated artifacts. Never rewritten after the fact — a later correction
belongs in the next dated bundle, not in this one.
The consolidation these fed is Ka0sAddonsCommonTasks
docs/2026-09-07-REVIEW_AND_STANDARDS_AUDIT_REMEDIATION/, which triaged both
bundles against the code, re-graded severity by reachable impact, and dropped
what did not survive re-reading. Read the consolidation for what is actually
being done; read these for the evidence behind it.
No code, TOC, doc or config file outside docs/ was touched by either pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016nbSbqagQQGGgyab6o86Mj
commit d82092c76ff7f9b45794033048d74b264685e7c9
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Sep 3 18:27:44 2026 +0530
docs: repoint the citations a one-line comment edit shifted
0458016 rewrote a comment block in core/WhatGroup.lua and grew it by one line.
Everything below line 82 in that file moved down by one, and seven `file:line`
citations in the doc set kept pointing at the line above the one they name.
docs/performance.md :185 -> :186 RegisterEvent("GROUP_ROSTER_UPDATE")
:186 -> :187 RegisterEvent("LFG_LIST_APPLICATION_STATUS_UPDATED")
:617 -> :618 the one-shot notify ScheduleTimer
docs/slash-dispatch.md :179 -> :180 RegisterChatCommand("wg", ...)
docs/ARCHITECTURE.md :514/:522/:530 -> :515/:523/:531 the join summary's
gold-labelled rows, cited as evidence in the
events-frames-taint-§8 deviation row
Each one now lands on the statement it claims. Nothing about the addon changed;
the numbers were right on master and only the file moved under them.
Also in README.md's "What's new": the notification delay is set under **Chat**,
not **Notify**. The tab strip renamed that group in 127baa1 and the rest of the
README already says Chat -- this was the last line still sending a player to a
section that is not on the page any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8JfG34HppHBvEHHVjCMTK
commit d82092c76ff7f9b45794033048d74b264685e7c9
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Sep 3 18:27:44 2026 +0530
docs: repoint the citations a one-line comment edit shifted
0458016 rewrote a comment block in core/WhatGroup.lua and grew it by one line.
Everything below line 82 in that file moved down by one, and seven `file:line`
citations in the doc set kept pointing at the line above the one they name.
docs/performance.md :185 -> :186 RegisterEvent("GROUP_ROSTER_UPDATE")
:186 -> :187 RegisterEvent("LFG_LIST_APPLICATION_STATUS_UPDATED")
:617 -> :618 the one-shot notify ScheduleTimer
docs/slash-dispatch.md :179 -> :180 RegisterChatCommand("wg", ...)
docs/ARCHITECTURE.md :514/:522/:530 -> :515/:523/:531 the join summary's
gold-labelled rows, cited as evidence in the
events-frames-taint-§8 deviation row
Each one now lands on the statement it claims. Nothing about the addon changed;
the numbers were right on master and only the file moved under them.
Also in README.md's "What's new": the notification delay is set under **Chat**,
not **Notify**. The tab strip renamed that group in 127baa1 and the rest of the
README already says Chat -- this was the last line still sending a player to a
section that is not on the page any more.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8JfG34HppHBvEHHVjCMTK
commit 90462ef8dc51d93c07b393222a1acf1e388cdf54
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Sep 3 18:03:29 2026 +0530
Carry LibKa0s v1.25.0
One file moved: OptionsCompose.lua, COMPOSE_MINOR 1 -> 2. MasterControls takes a
`leadButton` -- one page-wide act of the host's own, drawn beside options-ui-§15's
reset buttons: into the pair's empty right half on a frameless addon, or on its
own row above the full pair on a framed one. It exists because §15 fixes the
resets' wording and the composer is the only thing that writes it, so an addon
wanting a button beside them had to keep a second copy of "Reset all settings" in
its own source.
NOT ADOPTED HERE, and the reason is structural rather than a preference: this
addon passes the composer's afterGroup tail through unchanged, with no single
page-wide act of its own for the seam to place. A leadButton with nothing to put
in it is a field set to nil. Nothing is filed as a decline -- a decline records a
decision about work that was offered, and nothing was.
Both payloads copied WHOLE from the tag (git archive v1.25.0), never file by file
and never from the sibling working tree: tests/test_vendor_sync.lua resolves the
tag the provenance line names and compares both payloads against it, so a copy
taken from a dirty checkout passes a local diff and then fails the gate. The
provenance line moves in this same commit, which is what that gate reads.
The kit is unchanged at revision 14 and is copied whole anyway: the two payloads
move together so a consumer can never hold a kit that cannot compare the library
it ships.
No cross-major skew, and no file removed upstream -- so no deletion inside libs/
was warranted.
Frozen bundle at docs/revendor/2026-09-03/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8JfG34HppHBvEHHVjCMTK
commit 0458016df35cddf347e156ccd673db646bce77ce
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Sep 2 21:55:11 2026 +0530
docs(comments): correct citations the revamp left pointing at nothing
Comment text only. A comment naming a file, a line or a caller is documentation and
drifts exactly like a README, except that nothing reads it but the next person to
touch the function and no gate can see it — luacheck does not read prose and no test
covers a comment.
The recurring one is afterRestoreAll, a descriptor hook that stopped existing when
the global reset became a profile reset, still being cited by name. Alongside it: a
line number past end of file, a caller that does not call, and counts that no longer
match what the code returns.
Deliberate history was left alone — a comment explaining what a deleted file used to
do is doing its job, and rewriting it would delete the explanation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188vqPV1edgfp1WQsZnsJaF
commit 08a73c6d3ccbf01f26d674f7c05d619b2cbfd220
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Sep 2 18:25:22 2026 +0530
docs: sync the doc set to the revamped settings panel
The settings surface moved a long way in one pass — a Master controls tab, a strip
on every page, class-colour companions, the library's composers in place of
hand-written groups, and in some addons whole pages folded into General — so the
docs describing it had drifted correspondingly far.
Counts re-derived from the schema rather than carried across, the LibKa0s payload
description corrected to the file it gained, and the documentation map reconciled
against the directory in both directions.
Documentation only: no .lua, .xml, .toc, .luacheckrc or .pkgmeta was touched, and
the suites are unchanged and green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188vqPV1edgfp1WQsZnsJaF
commit 127baa1736b985bcf1b6ccfacaf27dc4c224fb7b
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Sep 2 17:56:21 2026 +0530
feat(settings): master controls and the mandatory tab strip
Re-vendors LibKa0s v1.24.0 and adopts the settings-revamp-v2 contract: a
General page opening on a composed Master controls tab, a tab strip on every page,
a class-colour companion beside every colour control, and the canonical font,
border and bar groups built by the library's composers rather than spelled out
per addon.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188vqPV1edgfp1WQsZnsJaF
commit 8122e402e22c0725ee15466de9b5e788559a7676
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 1 19:31:13 2026 +0530
Retab the settings page, and make the popup's size a setting
The General page was one scrolling column: ten schema rows under three
azure section headings -- General, Notify, Frame -- with the Notify block
alone running seven checkboxes down a single half-width column, because
every one of them carried `solo = true`. A player looking for the popup
toggle scrolled past six chat-line ticks to find it.
It is a TABBED page now (options-ui-§13). RenderTabbedSchema partitions
settings/Schema.lua by each row's `group`, in declaration order, and draws
one tab per distinct group, so the array order IS the strip and a group's
rows must stay contiguous. Three tabs, in the order a player meets the
addon: General (what the addon does at all), Chat (what the chat line
says), Popup (the window). The section headings are gone -- the strip
carries those names -- and `section` is untouched: it is /wg list's
grouping key, not the page's, so a row's tab is where it is EDITED and its
path is still where it is STORED. Nothing moved paths. A profile carried
over from the untabbed build opens with every setting where it was left,
and /wg list prints exactly what it printed before.
FRAME_WIDTH and FRAME_HEIGHT are settings, not file-locals. They were two
constants at the top of modules/Frame.lua; they ship as frame.width and
frame.height with defaults 420 and 260 -- the numbers they replaced -- so a
popup nobody has touched is drawn exactly as it was. Sliders travel 320..700
and 200..520, and modules/Frame.lua clamps both ON READ against the same
bounds, because the slider cannot produce an illegal value but a hand-edited
SavedVariable and `/wg set frame.width 4000` both can. WhatGroup:ApplyFrameSize
resizes a live popup and REFUSES in combat -- the popup parents a
SecureActionButtonTemplate anchored off the frame's own edges -- with every
ShowFrame re-applying, so a refused change lands on the next open.
Four recorded decisions are overturned, each because the tab strip removed
the thing that justified it:
- The vertical checklist. Every notify row was `solo` so the section read
as a column of "include this line" ticks. The tab says that now; six
half-empty lines became three full ones. The solo survives on the
master, "Print to Chat", which is paired against nothing on purpose.
- The "Show " label prefix. Under a tab called Chat, seven rows opening
with "Show" spend their first word restating the tab. Labels only --
notify.showInstance is still notify.showInstance.
- Notification Delay heading the Notify section. It reads as "how long
before the chat line", and that is half of what it does: one timer gates
the chat line AND the popup. A row governing both surfaces belongs on
neither of their tabs, so it sits on General with the master switch. It
keeps its solo -- a half-width slider beside a checkbox reads as gated
by it.
- The Debug console checkbox pairing with "Print to Chat". That partner
moved to the Chat tab; following it across would have put a debug
control on the tab that decides what the chat line says. It is keyed to
`enabled` now and lands beside Enable. Still session-only, still not a
schema row (WG-12), still only the console window's visibility.
The Test button's afterGroup hook is keyed to the General tab rather than
appended to the page, which is what keeps it off the other two.
No page banner (options-ui-§14) and none is possible: WhatGroup has no
per-window settings and no active-window state, so there is no instance
for a banner to name. db.global.windows stores the popup's position, which
is geometry, not a setting.
Docs follow the panel: settings-panel.md, schema.md, module-map.md,
smoke-tests.md, frame.md, scope.md, common-tasks.md, ARCHITECTURE.md and
the README's Settings-panel and FAQ sections. The stale FRAME_HEIGHT recipe
in common-tasks.md now points at the setting and the clamp pair, and the
file:line citations in performance.md, slash-dispatch.md, frame.md,
module-map.md and ARCHITECTURE.md were re-resolved against today's line
numbers. The 1.3.0 release history still names a "Notify" section; that
prose is /wow-addon:bump-version's and is left alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CwXwe2nXFJD4797u1U2fxb
commit 0b7d49dbdee86881e1694cf15f922afbb3ddda29
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Sep 1 19:30:27 2026 +0530
Re-vendor LibKa0s v1.23.0: the tabbed options page arrives
Options 9 -> 13 and OptionsWidgets 8 -> 12, and between them they carry
the two surfaces this addon's settings page had no way to draw before.
options-ui-§13 lands as O.RenderTabbedSchema and O.TabStrip: a page that
partitions its schema by the rows' `group` field, in declaration order,
and draws one tab per distinct group instead of stacking every section
down one scrolling column. options-ui-§14 lands as O.PageBanner, the
band that names which instance a page is editing. O.SetChromeHeight and
the reworked O.RenderRows (now taking afterGroup, pairWith and an opts
table) come with them, because a tab strip is chrome the flow engine has
to reserve height for.
None of it existed in the vendored v1.19.0 — not the function, not the
descriptor field, not the layout constant — so WhatGroup's General page
could not have been retabbed without carrying these bytes first. The
adoption itself is the next commit; this one is only the payload.
The kit moves with it, revision 13 -> 14: mock_base grows the frame
members the tab-strip widgets touch, which the headless panel cases
need in order to render a strip at all.
The provenance line in CLAUDE.md moves in this same commit, which is
what tests/test_vendor_sync.lua resolves the tag from — it reads the
version out of CLAUDE.md and compares both libs/LibKa0s/ and tests/_kit/
against that tag in the sibling checkout, so a split commit leaves the
gate red on either side of it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CwXwe2nXFJD4797u1U2fxb
commit 78463d981e43d0c95b15e34cb02472bde581748a
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 27 11:25:21 2026 +0530
Say six seam files where the re-run trigger says five
Section 9 of the smoke tests names all six LibKa0s seam files that fall back
when their major is absent, but the closing re-run trigger still said five —
a leftover from before the sixth seam existed. A reader who trusts the trigger
line would leave one seam unexercised after a re-vendor, which is exactly the
change that most needs all six checked.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FtWnLGKT5uimw6X63zTzr
commit 5a61ddd082af9ccb49be7dc724e00cd92a29d403
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 27 11:07:47 2026 +0530
Carry LibKa0s v1.19.0
The library's v1.19.0 release moves Widgets to minor 8, which adds a
ReorderList widget for settings that present an ordered list. The change
is purely additive: nothing this addon already calls has changed shape,
so no addon code moves with it and the six majors WhatGroup consumes
behave exactly as they did under v1.18.1.
The vendored payload and the CLAUDE.md provenance line move together in
this single commit, which is what the library's release doc requires and
what tests/test_vendor_sync.lua asserts — it reads the version out of
CLAUDE.md and compares both libs/LibKa0s/ and tests/_kit/ against that
tag, so a split commit would leave the gate red on either side of it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FtWnLGKT5uimw6X63zTzr
commit 497c96fb5b52cabced4b210c97cd5a7de0f3eb61
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 26 19:01:27 2026 +0530
Re-vendor LibKa0s v1.18.1: the landing logo stops pooling its texture
OptionsWidgets minor 7 -> 8, and nothing else moves. The library's
landing page drew its logo with frame:CreateTexture() on an AceGUI
SimpleGroup -- and a texture is not a widget, so when AceGUI recycled
that frame the texture rode along and drew again on whatever the page
used the frame for next. The settings page grew a second logo,
intermittently, on pool order alone.
Minor 8 keeps the texture on the frame and reuses it, and hides it when
the widget is released. No signature, descriptor field or spec key
moves, so this addon adopts it by carrying the bytes.
The kit is unchanged at revision 13. The provenance line moves in this
commit with the payload, which is what tests/test_vendor_sync.lua
resolves the tag from.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FtWnLGKT5uimw6X63zTzr
commit f98ef41407080ced91059555c036ac42fc7b9320
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 26 17:10:40 2026 +0530
Adopt options-ui-§12: the global reset is a profile reset, and wire the
profile callbacks it needs
The standard settles what the collection's global reset is: the same act
as AceDBOptions' Reset Profile -- db:ResetProfile() on the ACTIVE profile,
never a second walk of the schema -- behind one verbatim, addon-agnostic
confirmation.
Helpers.RestoreAllDefaults was closer than most: it already wiped
db.profile before re-threading each row's default, and the comment says
why -- a reset should yield a PRISTINE profile rather than default-valued
known keys, dropping any value from a removed or renamed schema row. Right
instinct, wrong mechanism. A row walk can only address ROWS, so it can
never restore a stored array, and it was doing by hand what AceDB does
correctly: emptying the profile IN PLACE, so anything holding db.profile
keeps the live table, then merging the defaults back.
AND THIS ADDON HAD NO PROFILE CALLBACKS AT ALL. Switching, copying or
resetting a profile replaces every stored value at once and nothing
reacted: an open settings panel kept showing the OLD profile's values
until it was closed and reopened, and the migrations never ran on an
incoming profile a copy could have authored at an older schema version.
It went unnoticed because nothing here switched profiles -- until the
global reset became one and started firing the same event.
core/WhatGroup.lua now registers all three, and each re-runs the
migrations and refreshes. RestoreAllDefaults therefore does NOT refresh
itself: that would be two reconciles for one action.
The [Reset] summary names the profile rather than a row count, because
there is no longer a loop to count and a tally of rows written would
understate what a profile reset empties.
Two cases move with the behavior. The deep-copy case appended its schema
row AFTER the db was built, which describes nothing the client can
produce -- every real row is declared at load, and a profile reset
restores AceDB's defaults table, built from the schema at AceDB:New time.
It now adds the row before OnInitialize.
Re-vendors LibKa0s v1.18.0 with it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019FtWnLGKT5uimw6X63zTzr
commit fa169e0f7692e667fa7dffd163965f4c97711acf
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 26 11:23:45 2026 +0530
feat(media): new logo art, plus the .jpg render alongside it
Replaces the logo with the new collection-wide art and adds the .jpg render
the repo was missing. The runtime .tga is Lanczos-downscaled from the
2000x2000 master at 300x300 — MAIN_LOGO_SIZE in settings/Panel.lua — so the
landing page draws it pixel-exact.
.pkgmeta now excludes media/logos/*.png and *.jpg. WoW cannot read either
format; only the .tga was ever loadable by the client.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YPd19uJ5DWdV4mKquWCSYZ
commit 24a00d6fe2d5050dbb25d9b2cdd258f17e8b22c2
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 25 16:52:46 2026 +0530
frame: drop the mark from the footer Close button
The 90px footer button now carries only its centered label. Removes
`decorateCloseButton` and its call; nothing else drew the catalog's
`close` mark on it.
standalone-windows-§33 (a wide action button SHOULD keep its label and
gain a mark) is ratified as an accepted deviation — the row is in
docs/ARCHITECTURE.md's deviation register, with the second-footer-button
re-check trigger.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 5ef05efbb7fc7cba586a388f36e738c3f9228247
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 25 15:35:20 2026 +0530
docs: correct stale line citations after the LibKa0s re-vendor
The re-vendor and the Env refactor shifted code under most of the
file:line references the docs carry, so the citations no longer landed
on what they name.
- performance.md: the six event/timer rows now cite the real lines
(WhatGroup.lua:159/:160/:591, Frame.lua:189/:520, OptionsSetup :173/:181).
- slash-dispatch.md: RegisterChatCommand is at WhatGroup.lua:153, and
OnSlashCommand at Slash.lua:288 — the old :294 was past EOF.
- ARCHITECTURE.md: the events-frames-taint-8 row's join-summary rows are
:488/:496/:504; the documentation map now names docs/revendor/, which
exists on disk.
- DEPENDENCIES.md: the .pkgmeta ignore list runs :6-14 since it gained
_dev, *.bak and .claude.
Green gate re-run after the edits: luacheck clean over 16 files,
485/485 tests passing.
commit ce69cccf3e9af58d12301d783da9ccd18875d07f
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 25 14:26:43 2026 +0530
Re-vendor LibKa0s v1.16.0
Both payloads whole, from the tag, with the CLAUDE.md provenance line in the same
commit — tests/test_vendor_sync.lua reads that line to pick the tag it compares
against, so a split would fail the gate.
Pool minor 2 (keyed pools, and ReleaseAll now raises on one instead of silently
recycling nothing), Widgets minor 7 (two optional CopyWindow fields, both absent
by default), DebugLog minor 12 (its copy window is Widgets.CopyWindow now, with a
hard NEEDS_WIDGETS floor), kit revision 13 (CreateFrame records its arguments, so
a suite can ask what a frame was named).
Nothing in this addon's own code changes. Lint and the headless suite green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6ncqntR9JgUoSgPf8PmCS
commit 57a651e1ad6c653c687fa949af3d1a1076e6fd20
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 25 12:32:40 2026 +0530
Record the 2026-08-25 LibKa0s re-vendor
The re-vendor found an empty delta: this addon was already carrying LibKa0s
v1.15.0, every shipped file at the tag's minor, both payloads byte-identical to
`git archive v1.15.0`, and test kit revision 12 paired as the rule requires. No
bytes were copied and the provenance line did not move.
The bundle is the record of that. 01_DELTA.md carries all five reads with the
command that produced each, including the consumption map and the majors this
addon does not consume; 05_SUMMARY.md carries the gate results that confirm it
independently — the addon's own tests/test_vendor_sync.lua passes both its
assertions inside a full green run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6ncqntR9JgUoSgPf8PmCS
commit d3a3a2a66dd074673724472d92907123e1803124
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 25 11:05:02 2026 +0530
automated-tests: record run 20260825-103505 — green, and a 3.19s gate
First bundle since the collection adopted the LibKa0s fast test gate
(test-kit revision 12). lint 0/0 over 16 files; tests 485/485, none
skipped; perf skip — this addon ships no tests/perf.lua; complexity 0
warnings, max CCN 15, 6377 NLOC over 906 functions.
What changed for this repo is the wall clock: the green gate was 7.5s
and is now 3.19s at 19% CPU. The CPU was never the problem and still
isn't — 0.31s before, 0.27s now. The 138 processes are: the vendored-
payload gate used to spawn one git read per blob and now batches them
through a single `git cat-file --batch`, so popen went 147 -> 9 while
loadfile went 24 -> 29.
The kit's other revision-12 fix, its chunk cache, does nothing here.
This repo calls none of Loader.load / loadAll / loadSource — every case
needs its own _G and the kit has no mode for that, so tests/loader.lua
keeps its own loadfile-and-cache. That code path is inert in this repo
and is not what made the run faster.
--jobs is left off. The suite is under the ~10s mark testing-§14 puts
the threshold at, and Kit.run's default is jobs = 1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6ncqntR9JgUoSgPf8PmCS
commit a416f23583907442f8f854377a01abea5fdf32d6
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 25 11:04:49 2026 +0530
docs: resync the LibKa0s seam/payload counts and toolchain evidence
Content-only drift pass; nothing structural moved.
The LibKa0s seam count was still four in two places and five in two
others. It is six — core/EnvSetup.lua and core/MediaSetup.lua joined it
and only CLAUDE.md and ARCHITECTURE.md's subsystem table had caught up.
Fixed in ARCHITECTURE.md's module-map diagram, testing.md's lint-scope
paragraph, smoke-tests.md's degraded-install section and module-map.md's
TOC-slot note.
libs/LibKa0s/LibKa0s.xml lists thirteen <Script> entries, not nine:
Env, Pool, Item and Widgets were missing from both file lists.
testing.md said tests/run.lua spells that list out — tests/loader.lua
does; run.lua holds the lifecycle factories and the suite order. The
count is gone from the prose there rather than re-stated, since it goes
stale on every library file that lands. Same for the two "eight minor
constants" asides in testing.md and common-tasks.md.
testing.md's suite list was missing test_envsetup and test_vendor_sync.
DEPENDENCIES.md: "the suites call neither io.popen nor os.execute" has
been wrong since the vendored-payload gate landed. The gate reads the
sibling checkout through git (tests/_kit/vendor_sync.lua:184, :225) and
the harness lists directories with ls -A (framework.lua:202-206); a
missing git or ../LibKa0s is a SKIP, not a failure, and the entry now
says so. Its setfenv/loadstring evidence lines and its five
docs/testing.md line citations had all shifted, and the lizard note
pointed at a testing.md section that no longer exists.
Gate green after: tests 485/485, luacheck 0/0 over 16 files,
docs/test-cases.md byte-identical to a fresh --list.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K6ncqntR9JgUoSgPf8PmCS
commit 28c09168d46dfea6a857ce87ca0895f8e71a8de4
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 25 04:10:33 2026 +0530
refactor(env): read the TOC through LibKa0s-Env-1.0
Both of this addon's copies of the metadata ladder were INLINE at the call site
— settings/Slash.lua's version() and settings/Panel.lua's addNotesLine — so an
audit of core/Compat.lua would have reported WhatGroup as having no copy at all.
core/EnvSetup.lua is now the one place either is read from.
Compat keeps what is genuinely this addon's: the version-variant spell and LFG
shims. No behaviour change; the seam's fallbacks repeat the ladder both copies
ran, so an install missing LibKa0s still reads its own TOC.
Two members only — this addon stamps neither a zone nor a map id. The version
fallback is WhatGroup.VERSION rather than the NS.version most of the collection
uses, read at call time because core/WhatGroup.lua loads after the seam.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gBvKiVQXxvbMBzpFYTbDN
commit 8edf4f4582c6d2bb94c795a1b822097b5d51a073
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 25 03:50:35 2026 +0530
chore(libs): re-vendor LibKa0s v1.15.0
Adds LibKa0s-Env-1.0, LibKa0s-Pool-1.0 and LibKa0s-Item-1.0 to the payload and
takes Widgets to minor 6 (CopyWindow) and DebugLog to minor 11 (the console
holds 1500 lines, not 500). Nothing in this addon adopts the new majors yet —
this commit is the copy and the provenance line.
The one behaviour change is the console history depth, which is the library's
and arrives with the copy. The prose that quoted the old cap moves with it:
docs/debug.md, and also docs/ARCHITECTURE.md, docs/smoke-tests.md and a
settings/Schema.lua comment, which named 500 too.
tests/_kit goes to revision 12 in the same commit. That is NOT a change this
release makes — LibKa0s' testkit is untouched by v1.15.0. Revision 12 shipped at
v1.14.0 ("testkit revision 12: the green gate stops waiting on the disk") and
this addon never took it. The kit moves now because it must: vendor_sync.lua
registers libs/LibKa0s and tests/_kit as two cases against the single tag
bundledVersion() greps out of CLAUDE.md, so one provenance line governs both
payloads and there is no green state in which the line says v1.15.0 and the kit
is still 11. Revision 12 is backward compatible (loader chunk cache, opt-in
--jobs/--shard defaulting to 1), so no runner change was needed.
diff -r against the source repo is empty for both payloads.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012gBvKiVQXxvbMBzpFYTbDN
commit 013ed010002318d5050555306d8ab41e318e53b0
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 24 21:05:35 2026 +0530
Teach the popup the Midnight season 2 dungeon teleports
Season 2 rotated the M+ pool and the table only knew season 1, so seven of
the eight dungeons showed no teleport at all: the popup skipped the row and
the join summary dropped its Teleport line for a player who owns the spell.
Five are new Midnight dungeons; Kings' Rest and Temple of Sethraliss are BfA
dungeons that got their first teleport with the season, so both are single
IDs rather than candidate lists. Ruby Life Pools is the eighth and keeps its
Dragonflight spell.
Every ID and name comes from a spellbook dump on a character that owns them,
not from a wiki lookup — the failure mode for a near-miss ID is a row that
renders desaturated rather than one that errors. The eight rows are pinned in
tests/test_labels.lua for the same reason the season 1 four are.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0115qZwkTz1iKY3s2Z8qqAmU
commit 5e636159ba2dee9158d0c647c951604b2f62e97b
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 24 02:26:38 2026 +0530
Take the console's font and its marks from the shared library payload
The debug console's title bar drew the words Copy and Clear beside a
multiplication sign, and the monospace face it renders in was this addon's own
copy of JetBrains Mono under media/fonts/. Both are LibKa0s' now
(LibKa0s-Media-1.0, v1.10.x), so this is the wiring.
SIX ADDONS SHIPPED SIX COPIES OF ONE FONT. Two copies of a face is two licenses
to track and two provenance stories, and the collection stops looking like one
author's work the first time one copy is regenerated and the rest are not. The
face moves into libs/LibKa0s/media/fonts/ with the icon catalog beside it, and
media/fonts/ is deleted. media/logos/ and media/screenshots/ stay: the brand TGA
the settings landing page draws is genuinely this addon's, and .pkgmeta ships
media/ to players.
WG-20 CLOSES WITH IT. The deviation existed to justify vendoring a non-Blizzard
font privately; there is nothing private left to justify, so it collapses into
the library-stack media rule. WG-21, the logo, is untouched.
TWO SEAMS NOW SAY WHO IS ASKING. A texture path is absolute from
Interface\AddOns\ and LibKa0s is vendored, so it cannot work out which folder it
was copied into; the addon folder name is the answer and only the addon has it.
core/CoreSetup.lua wraps MakeCloseButton to supply it -- one wrapper, so every
close control this addon ever builds gets the same mark without any call site
remembering -- and core/DebugLogSetup.lua passes addonName in the console
descriptor, which turns Copy, Clear and the multiplication sign into the shared
copy, clear and close marks on the console and on its copy window at once.
addonName is passed EXPLICITLY beside name rather than inferred from it. They are
the same string here and different questions everywhere: name seeds
WhatGroupDebugWindow and its siblings, and a host where the two diverge would
hand the library a path into nowhere -- which draws nothing and raises nothing.
core/MediaSetup.lua is new and its TOC slot is load-bearing rather than
conventional: core/WhatGroup.lua resolves NS.FONT_MONO from NS.MediaFont at load
and core/DebugLogSetup.lua hands that value to a descriptor the library validates
as a string, so it sits immediately after core/CoreSetup.lua and before both. The
resolution ends in `or _G.STANDARD_TEXT_FONT` on purpose: SetFont accepts a path
to a file that is not there, fails to load it, and the text simply does not draw,
so a degraded install has to land on a face the client definitely has. ARIALN is
still the debug-logging-§2 fetch-failure rung; it is now the third, not the
second. The addon's own LSM:Register call is gone, replaced by the library's
RegisterLSM at file load -- one key, one set of bytes, every Ka0s addon agreeing
instead of colliding.
The popup's footer Close button takes a mark BESIDE its label, not instead of it.
It is a 90px templated button in a footer, not a small square target in a title
strip, and the construction went into a named helper rather than inline: adding
an art path to an already-long window builder is what took a LibKa0s function one
over the release CCN ceiling. No title-bar close was added to the popup -- that
would be new chrome rather than a re-skin, and this addon's window has always
closed from its footer and from Esc.
Bundles LibKa0s v1.10.2: libs/LibKa0s/ and tests/_kit/ re-vendored from the tag,
provenance line moved with them, kit revision 11. v1.10.2 rather than the v1.10.1
the adoption brief named, because this addon vendors PerfPanel and v1.10.2 is the
release that stops its close button being built without the addon name -- the
same defect this commit fixes one window over.
THE TESTS PIN THE ARGUMENT, NOT THE APPEARANCE, because the appearance is exactly
what no out-of-game gate can see: the factory receives no name, builds no texture
path, and draws a perfectly good button. tests/test_mediasetup.lua is new and
cross-checks every icon this addon draws against both the library's catalog and
the files in the vendored copy; tests/test_libka0s.lua gains the two cases that
assert the folder name reaches the close-button factory as its third argument and
that the descriptor carries addonName. docs/smoke-tests.md gains section 12 for
the half only a player can check, including what each regression looks like: two
words and a multiplication sign on the console means the folder name stopped
being passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXv7Vni5ygBbZviZxcygqs
commit 9783d25c3fe9d5518f33b690c38a2c9775edb179
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 14:00:22 2026 +0530
docs: name the in-game capture store perf-analysis, not perf-runs
The Ka0s WoW Addon Standard retires the flat docs/perf-runs/ store and
replaces it with docs/perf-analysis/, where each capture is a frozen
dated bundle holding the record and its reading together.
This addon ships neither, and still ships neither. Its performance-§12
exemption is unchanged: nothing is wired, no capture is ever produced
here, and no docs/perf-analysis/ directory is created by this commit.
What changed is only the name the docs use when they say what is absent,
so a reader chasing the phrase in the standard finds the same words
here. The exemption prose, its criteria and its re-check triggers are
untouched.
Three live docs carried the old name: the ARCHITECTURE.md conditional-doc
row and both performance-§12 deviation rows, performance.md's list of
what is not wired, and automated-tests/README.md's pointer to where
in-game captures would live.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgcWFzX8WzaWuDHSU9kqZp
commit e617aa18d63e446283749402826c829d62452b01
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 12:22:28 2026 +0530
automated-tests: record run 20260807-121935 — the watch list is empty
First recorded run since b1511f6 split ConfigureTeleportButton, so the
record now agrees with the code: max CCN 20 -> 15, CCN warnings 1 -> 0.
That entry was the last thing blocking this addon's release gate, which
refuses zero functions above CCN 15.
lint 0/0 over 14 files; tests 462/462, none skipped; perf skip (ships no
tests/perf.lua); complexity 6114 NLOC / 870 funcs, avg NLOC 6.4, avg CCN
1.7 — both averages unmoved, so the addon did not get denser, it got the
same code across four more, smaller functions.
test-cases.md came out byte-identical to the previous run's despite a
real change to the file the teleport cases exercise. That is the point
worth recording: the 20 existing cases verified the refactor with none
added, renamed or adjusted to fit it.
ANALYSIS.md also keeps the diagnosis, because the obvious reading was
wrong — lizard scores Lua closures as separate functions, so the nested
handlers contributed nothing to the parent's 20; it was 12 genuine
top-level branches. Anyone reaching for "it's just or-defaulting" on a
future entry should check the token counts first.
New ceiling is core/WhatGroup.lua:634-697 at CCN 15 — passing by one
point, and with no seam of the kind that made this split easy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LE5Tjrv46E2iZfFVS47WmK
commit b1511f67f50b410f0e067af927d047565109edec
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 12:16:36 2026 +0530
refactor(frame): split ConfigureTeleportButton, CCN 20 -> 6
At CCN 20 it was the collection's only complexity warning and would have
refused the next tag — /wow-addon:bump-version gates a release on zero
functions above CCN 15.
The score was not an artifact of dense defaulting. The body carried 12
real branches doing five separable jobs, so this extracts four of them
as file-scope locals alongside the existing forward declarations, which
is what lets them reach `f`, `fields` and the recursive
ConfigureTeleportButton as upvalues:
deferTeleportUntilCombatEnds CCN 2 the lockdown stash + regen retry
resolveTeleportState CCN 11 spell, known, remaining, ready
applyTeleportNote CCN 3 the three-state note + ticker
applyTeleportAction CCN 4 macro-vs-cleared, tooltip, trace
ConfigureTeleportButton CCN 6 the state machine it always was
Pure extraction — no behaviour change, and the 20 teleport cases in
tests/test_frame.lua cover every branch: combat defer and rebuild,
unlearned, on-cooldown, ready, swipe arming, the ticker counting down
and re-arming the cast at zero, stale-macro clearing. 462/462 green
after each step; lint 0/0; repo-wide complexity warnings now 0.
The load-bearing comments moved with their code rather than being
summarised — why stopCooldownTicker() runs first, why :Disable() is the
wrong tool on a protected frame, why the note's three-state order
matters, why the swipe gets the raw pair.
Re-anchors the six docs citations of modules/Frame.lua:327 (the ticker)
to :146. The two in docs/reviews/2026-08-03/ are frozen evidence and
stay as recorded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LE5Tjrv46E2iZfFVS47WmK
commit 145efb13a9332bae97e1e1533c038ed1b2e8dd46
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 12:08:41 2026 +0530
automated-tests: record run 20260807-114405 — green
Four-suite run through the vendored kit (LibKa0s v1.8.2, test-kit
revision 10). lint 0 warnings / 0 errors over 14 files; tests
462 passed, 0 failed, 0 skipped; perf skipped — no tests/perf.lua;
complexity 1 warning — ConfigureTeleportButton at CCN 20 (recorded, non-gating).
First bundle written since kit r10's normalize_eol pass landed, so it
is also the evidence that a run no longer leaves LF stragglers in a
CRLF-pinned repo: every file in the bundle has CR count == LF count.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LE5Tjrv46E2iZfFVS47WmK
commit 4a32cbef035ace557250225ada862b3b3ebf9486
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 11:37:57 2026 +0530
chore(gitattributes): re-sync to the canonical body updated in v2.28.1
line-endings-§5 requires .gitattributes to match the canonical body
byte-for-byte. Correcting §7's working-tree check also rewrote the
verification advice inside that body — `file <path>` cannot report line
terminators for JSON or for a binary, so it passes files it never
examined — and no repo was re-synced when that landed.
Comment text only; no rule, pin or carve-out changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LE5Tjrv46E2iZfFVS47WmK
commit 17d0ad7c7b156fe4496ba16b5bd7548e198ebe9b
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 11:05:24 2026 +0530
vendor: re-vendor LibKa0s v1.8.2 — kit revision 10, CRLF-aware runner
Both payloads move together, from the tag: libs/LibKa0s/ and tests/_kit/ are
whole-folder copies of LibKa0s at v1.8.2, and the CLAUDE.md provenance line
moves in this same commit — tests/test_vendor_sync.lua reads that line and
compares both folders against the tag, so a half-move fails the gate.
Kit revision 10 writes each bundle file to whatever .gitattributes declares for
it (git check-attr text eol, per path), instead of down a plain redirect that
bypassed git's filters and left LF stragglers in a CRLF-pinned repo. Bundle
20260807-110421 is the first written by the fixed runner: every file lands CRLF.
Battery: lint pass (0/0, 14 files), tests pass (462/462, vendor_sync included),
perf skip (no tests/perf.lua), complexity pass (1 warning, max CCN 20,
recorded, non-gating). Verdict green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LE5Tjrv46E2iZfFVS47WmK
commit 8d05986f4e9b73af8fc5e276c4279338476d0894
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 09:54:10 2026 +0530
tests: name the debuglog scrollbar case with the canonical debug-logging-§11
The case cited a bare "§11", which does not say which standards file the
section belongs to and does not match the filename-§N form every other
citation in the suite uses. Regenerated docs/test-cases.md from
`lua tests/run.lua --list` so the inventory tracks the rename.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LE5Tjrv46E2iZfFVS47WmK
commit e52cc07b3cac4adec628fb968945b5d60b6f8e97
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 03:18:41 2026 +0530
docs: point the deviation rule at the register; resolve stale citations
CLAUDE.md's "Standards compliance (read first)" still told an agent to record
an accepted deviation as an in-code SHOULD-justification comment. documentation-§6
now names one home: a row in docs/ARCHITECTURE.md -> ## Documented deviations,
shaped | Rule | What differs | Why | Decided | Re-check trigger |. The repo
already keeps that register, so this is text catching up to practice; the
stop-and-flag directive and the two-way classification are unchanged.
Also resolves citations that no longer resolve, against standard v2.28.0:
.luacheckrc:1 (§14) -> (lint); lint.md carries no numbered
subsections, so it takes a bare filename
docs/debug.md:5 `debug-logging §` -> `debug-logging`
docs/module-map.md:16 (debug-logging §) -> (debug-logging)
docs/ARCHITECTURE.md:180 (§2) and §2's -> debug-logging-§2
docs/debug.md:76,291 §11 -> debug-logging-§11
The .luacheckrc edit is comment-only; no line that runs was touched.
commit 0446f45c8a55d18cd0c9385ffa0012cb6fd468b7
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 02:31:05 2026 +0530
automated-tests: record 20260807-022625; CCN 20 newly warned
The recorded run was three days and five commits stale, and the tree it
described no longer exists: 422 cases against today's 462, 5573 NLOC
against 6095. This is the full four-suite run over HEAD (4e99ba5, clean),
with the ANALYSIS.md and the four RESULTS.md standing sections written
from this bundle's manifest.json and suite artifacts only.
Both gating suites are green — luacheck 0/0 over 14 files, 462/462 cases
with zero skips — so nothing here blocks a commit. The finding is on the
non-gating side: ConfigureTeleportButton (modules/Frame.lua:240-396) has
newly crossed CCN 15, at 20, up from 12 last run. The WG-31 cooldown
states (c7d8e2d) roughly doubled it on every axis. It is genuine control
flow rather than the usual Lua or-defaulting artefact, so it is
dispositioned "Peel next", not Accepted — it is the entry's first
appearance, and it would fail the release gate as it stands.
perf remains a skip with zero scenarios, cited to the ratified
performance-§12 (re-check fired) row rather than left to read as an
unmeasured addon. The RESULTS.md lint section's excluded-tree figure is
also refreshed: the tests/ tree is now 7212 lines across 22 files, up
from the 6098/20 last stated.
The authorised comment-citation sweep applied nothing. Every path and
Symbol.Member citation in the addon's own source resolves; the three
that do not are deliberate history (core/DebugLog.lua in two past-tense
sentences describing the move to LibKa0s, and a cross-repo provenance
note), which are correct as written and were left alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuCbofydwVeUp61YW4DiJ4
commit 4e99ba5b015c05c0c67d84316fe65426e13517f1
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 02:11:45 2026 +0530
docs: correct the post-ticker perf claims and stale file:line citations
The teleport cooldown countdown ticker landed on 2026-08-06 and the deviation
table was updated for it, but three places still described the addon as having
no repeating timer at all: ARCHITECTURE's Event Subscriptions prose (which also
still called the countdown text "static"), performance.md's opening claim of the
no-combat-path exemption and its re-check section, and module-map.md's reason
for declining LibKa0s-Perf. All now say what the code does.
Line-number citations that drifted:
- modules/Frame.lua:321 -> :327 (the ScheduleRepeatingTimer) in ARCHITECTURE,
performance.md and frame.md
- modules/Frame.lua:447 -> :453 (the combat wait frame)
- core/WhatGroup.lua:563 -> :577 (the one-shot notify ScheduleTimer)
- core/WhatGroup.lua:460/:468/:476 -> :474/:482/:490 (the gold-labelled join
summary rows cited by the events-frames-taint-8 deviation row)
- libs/LibKa0s/Slash.lua:505 -> :586 (Sl:OnSlash)
DEPENDENCIES.md claimed the repo contains no .sh at all; tests/_kit/run-automated-tests.sh
is right there and is documented in docs/testing.md. Says so now, with bash
named as what it needs.
Docs only - no code, no version change.
commit 26eb52bf6dda66de8727e278245b4a708ae8dd0c
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 21:21:41 2026 +0530
docs: retire docs/pending/LEDGER.md; the pending store is GitHub issues
Implements Ka0s WoW Addon Standard v2.26.0. The decision record lived in
the working tree, which is the one place a decision record cannot be
reached from: a deferral was invisible to the issue list the maintainer
actually opens, nothing notified on it, and nothing outside this repo
could reference it.
Every row anything still refers to is now a GitHub issue on this repo,
with status carried as a title prefix — [deferred] and [untriaged] open,
[done] and [will-not-do] closed. Deferrals and reasoned refusals migrated;
finished work did not, except where live documentation cites it by id,
because the pointer is the thing being preserved.
Every doc and comment reference now points at its issue rather than at a
deleted file. The terminal rows the file held remain in git history at
this commit's parent.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuCbofydwVeUp61YW4DiJ4
commit 5278c0ac4bd2827c02b565387ab3f16087341cb5
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 18:38:27 2026 +0530
Move the LibKa0s provenance line to CLAUDE.md; re-vendor testkit r9 (LibKa0s v1.8.1)
The provenance line — "Bundles [LibKa0s](…) vX.Y.Z (MIT)." — is the input the
vendored-payload gate reads to decide which LibKa0s tag libs/LibKa0s/ and
tests/_kit/ are supposed to match. It used to live in README.md. README.md is
the player-facing page, and it is being reduced to what a player actually needs,
so a gate reading its input out of a file whose whole direction of travel is to
stop mentioning that input was on a countdown. The line now lives in the root
CLAUDE.md, beside the rest of the build facts a maintainer or an agent needs.
testkit goes from revision 8 to revision 9 (LibKa0s v1.8.1), whose only change
is that vendor_sync.lua reads CLAUDE.md instead of README.md, with no fallback:
a repo that has not moved its line reads as carrying no provenance line at all
and fails, naming the file it looked in. Re-vendor and line move land in the
same commit because the gate compares file sets and reads the line as its input;
they cannot be split. libs/LibKa0s/ is untouched — no shipped library file
changed in v1.8.1.
README.md also loses the bundled-library paragraph (the "everything ships
inside it" prose plus the Ace3 / LibSharedMedia / LibKa0s inventory). Nothing
in it was an external credit, so no Credits section survives it. The Standard
badge is unlinked.
docs/test-cases.md regenerated: revision 9 renames one vendor_sync case, counts
unchanged at 462/462, so the README tests badge does not move.
Prose swept where it still pointed at README.md for provenance: docs/testing.md,
docs/common-tasks.md, docs/module-map.md and the header comment on
tests/test_vendor_sync.lua. Frozen dated bundles under docs/audits/ and
docs/reviews/ left alone.
Green: 462 passed, 0 failed, 0 skipped, 462 total; luacheck 0 warnings / 0 errors.
commit 7a6d14794e1b4ec089e05ac7860f0156e67ef1db
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 17:23:22 2026 +0530
Pin line endings with the canonical .gitattributes (line-endings-§2)
This repo is client-bound, so the working tree is pinned CRLF on every
platform: `* text=auto eol=crlf`, the `*.sh text eol=lf` carve-out that
keeps the vendored test runner executable, and the shared binary block.
The body after the pin is byte-identical to every other repo in the
collection, so diffing a client-bound repo against a non-client one shows
one decision rather than two documents.
Working-tree stragglers written by tools that bypass git's filters were
re-materialized from the index; the headless suite passes unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A9nTGYaHsqCEf6obganps6
commit 6f3a37a79ea13bacd2e7d5fc6d6090ec1b890088
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 16:00:57 2026 +0530
docs: adopt the documentation tier model (standard v2.23.0)
This repo carried four of the collection's five naming collisions on its own,
which is what made the drift visible when the eight docs/ trees were read side
by side. Every one of these names is defensible in isolation and wrong in a
collection where the same content has a different name in seven other repos.
Renamed (git mv, history preserved):
capture-pipeline.md -> data-flow.md
settings-system.md -> settings-panel.md
wow-quirks.md -> midnight-quirks.md
debug-console.md -> debug.md
file-index.md -> module-map.md (this repo had no module-map.md; the
file index WAS the module map here)
Created: schema.md — the last missing Tier 1 doc. It records the deliberate
two-site split that reconciles savedvariables-§2 with architecture-§5: values
in defaults/Profile.lua as NS.C, structure in settings/Schema.lua with each
row's default REFERENCING C.<path> rather than restating the literal. It also
states what is deliberately not persisted — debug is session-only, and the
General panel's Debug console checkbox toggles the window's visibility alone,
never the logging flag and never db.profile, which is what keeps the WG-12
invariant true.
Retired: file-index.md (folded into the module map it duplicated).
ARCHITECTURE.md gains `## Documentation map`. message-bus.md is recorded as
"Not applicable" for the honest reason: this addon defines no cross-module
messages at all, being a single feature module.
Verified: 462 tests passing, luacheck clean, every relative doc link resolves.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KQxehYAMgZtvAJDCJxXYRs
commit c7d8e2d550828bfcceb66044953cc06e9cc54930
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 14:57:38 2026 +0530
feat(frame,notify): the teleport button says why it cannot be cast
A greyed-out teleport had one explanation in the popup and one in chat, and
the popup's was silence: the icon desaturated and nothing said why. Two
reasons produce that state and they call for opposite responses — learn the
spell, or wait — so the popup now names which, in a note beside the button,
and the chat line gains the cooldown tag alongside its existing one.
Cooldown is read through two new Compat shims (core/Compat.lua:74) that
normalize retail's C_Spell.GetSpellCooldown table against the legacy
multi-return. Two judgement calls are encoded there. Anything at or under the
1.5s GCD floors to zero, because otherwise casting anything at all makes an
eight-hour teleport blink "on cooldown". And isEnabled == false means "do not
draw a cooldown" — the spell is mid-cast — so it reads as ready rather than
greying the button during every cast.
The popup's three states, in modules/Frame.lua. Ready: full alpha, macro
armed. Recharging: desaturated, a CooldownFrameTemplate swipe over the icon,
and "On cooldown - 7h 58m 12s" counting down beside it. Not learned:
desaturated and "Teleport spell not learned". Not-learned is tested FIRST,
because an unlearned spell can still report a cooldown and naming the cooldown
buries the reason the icon is grey.
Clearing the secure attributes IS the disable — the button still takes the
click, the secure handler finds no action, nothing casts. :Disable() is the
obvious alternative and the wrong tool on a protected frame. The tooltip
survives a cooldown but not an unlearned spell: on cooldown it is where the
spell's own text and exact timing live, and a player who owns the teleport is
entitled to it.
The chat tag carries no time remaining, deliberately. That line is printed
once into scrollback with no way to refresh itself, so a figure there would be
wrong within a second and stay wrong.
STANDARDS: the 1-second countdown timer ends performance-§12's no-combat-path
exemption, whose re-check trigger names "the first repeating ticker" in those
words. Recorded as a ratified deviation row in docs/ARCHITECTURE.md rather
than resolved quietly, with the sweep in docs/performance.md regenerated to
name the timer and its per-tick cost. Only criterion (a) broke; (b) and (c)
stand, so the wiring is still declined. What makes it defensible is that the
timer cannot outlive the popup: one handle, replaced not stacked, cancelled
from OnHide, from the top of every ConfigureTeleportButton, and by the tick
that reaches zero. An amendment is filed upstream at WowAddonStandards
f3354b2 proposing that §12 exempt a window-bounded ticker; if it lands, the
row retires.
Also: /wg test moves from The Stonevault to Windrunner Spire (mapID 2805) so
the Midnight teleport path is what the manual affordance exercises. Its
activityID becomes synthetic — the old 2516 is itself a TeleportSpells key, so
the fixture would have resolved a teleport through the activityID fallback
with the mapID path broken.
Written test-first throughout: 29 new cases, each failing for its own reason
before the code existed. The mock gains ScheduleRepeatingTimer as a distinct
object from a one-shot, so a suite can prove a ticker was cancelled rather
than assume it.
Verification: 462 passed, 0 failed; luacheck 0 warnings / 0 errors in 14
files; vendor gate clean. No version bump.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ph2nSBmLiJNQJqvBNt6sD3
commit c7d8e2d550828bfcceb66044953cc06e9cc54930
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 14:57:38 2026 +0530
feat(frame,notify): the teleport button says why it cannot be cast
A greyed-out teleport had one explanation in the popup and one in chat, and
the popup's was silence: the icon desaturated and nothing said why. Two
reasons produce that state and they call for opposite responses — learn the
spell, or wait — so the popup now names which, in a note beside the button,
and the chat line gains the cooldown tag alongside its existing one.
Cooldown is read through two new Compat shims (core/Compat.lua:74) that
normalize retail's C_Spell.GetSpellCooldown table against the legacy
multi-return. Two judgement calls are encoded there. Anything at or under the
1.5s GCD floors to zero, because otherwise casting anything at all makes an
eight-hour teleport blink "on cooldown". And isEnabled == false means "do not
draw a cooldown" — the spell is mid-cast — so it reads as ready rather than
greying the button during every cast.
The popup's three states, in modules/Frame.lua. Ready: full alpha, macro
armed. Recharging: desaturated, a CooldownFrameTemplate swipe over the icon,
and "On cooldown - 7h 58m 12s" counting down beside it. Not learned:
desaturated and "Teleport spell not learned". Not-learned is tested FIRST,
because an unlearned spell can still report a cooldown and naming the cooldown
buries the reason the icon is grey.
Clearing the secure attributes IS the disable — the button still takes the
click, the secure handler finds no action, nothing casts. :Disable() is the
obvious alternative and the wrong tool on a protected frame. The tooltip
survives a cooldown but not an unlearned spell: on cooldown it is where the
spell's own text and exact timing live, and a player who owns the teleport is
entitled to it.
The chat tag carries no time remaining, deliberately. That line is printed
once into scrollback with no way to refresh itself, so a figure there would be
wrong within a second and stay wrong.
STANDARDS: the 1-second countdown timer ends performance-§12's no-combat-path
exemption, whose re-check trigger names "the first repeating ticker" in those
words. Recorded as a ratified deviation row in docs/ARCHITECTURE.md rather
than resolved quietly, with the sweep in docs/performance.md regenerated to
name the timer and its per-tick cost. Only criterion (a) broke; (b) and (c)
stand, so the wiring is still declined. What makes it defensible is that the
timer cannot outlive the popup: one handle, replaced not stacked, cancelled
from OnHide, from the top of every ConfigureTeleportButton, and by the tick
that reaches zero. An amendment is filed upstream at WowAddonStandards
f3354b2 proposing that §12 exempt a window-bounded ticker; if it lands, the
row retires.
Also: /wg test moves from The Stonevault to Windrunner Spire (mapID 2805) so
the Midnight teleport path is what the manual affordance exercises. Its
activityID becomes synthetic — the old 2516 is itself a TeleportSpells key, so
the fixture would have resolved a teleport through the activityID fallback
with the mapID path broken.
Written test-first throughout: 29 new cases, each failing for its own reason
before the code existed. The mock gains ScheduleRepeatingTimer as a distinct
object from a one-shot, so a suite can prove a ticker was cancelled rather
than assume it.
Verification: 462 passed, 0 failed; luacheck 0 warnings / 0 errors in 14
files; vendor gate clean. No version bump.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ph2nSBmLiJNQJqvBNt6sD3
commit cbb6661d0c04683028c44563bea72d82ef74cb58
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 13:12:38 2026 +0530
fix(teleports): Nexus-Point Xenas is 1254563 — 1254553 was a typo
A reporter joined a +10 Xenas group and got a desaturated teleport icon
and a chat line reading "Teleport: [Hero's Path] (not learned)", while a
second addon offered them a working teleport for the same dungeon.
1254553 is a real spell — named "Hero's Path" — so nothing errored.
GetTeleportSpell resolved known=false and the frame did exactly what it
should with an ID nobody holds. The Keystone Hero reward is 1254563,
"Path of the Fractured Core"; e30c1f6 transposed a digit while claiming
tooltip verification.
Siege of Boralus had the same defect from the same cause: [1822] carried
only 464256, a wiki ID, while players hold 445418. Both resolve to "Path
of the Besieged Harbor", so this is a re-issue like The MOTHERLODE!!, not
a wrong ID — the row becomes { 445418, 464256 } with the spellbook-
confirmed spell leading, which is also the fallback when a player knows
neither.
Both bugs came from looking spells up by dungeon name. That cannot work:
a teleport's name never contains its dungeon's, so the search returns
something adjacent rather than nothing. So every row now carries its
spell's real name, read back from C_Spell.GetSpellInfo in-game across all
78 IDs — the dungeon/name pair is a standing cross-check, and a name that
does not fit its dungeon is a wrong ID. All 75 rows check out. The header
explains the trap and the silent failure shape; docs/common-tasks.md
promotes the spellbook sweep over the wiki, which produced both errors.
Also: Dungeons/Raids subsections under each expansion, the "(raid)"
suffix dropped as redundant, Tazavesh shortened, and the Midnight-only
warning block moved to the file header where it always belonged.
Two regression tests pin the four Midnight rows to spellbook IDs and
Boralus' candidate ordering.
Verification: 435 passed, 0 failed; luacheck 0 warnings / 0 errors in 14
files. No version bump.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ph2nSBmLiJNQJqvBNt6sD3
commit ff7902b58c6d3615accb6d35e3c5d22105e1f252
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 01:08:23 2026 +0530
docs(frame,test-cases): the teleport button carries both click edges
e233cf3 restored `RegisterForClicks("AnyUp", "AnyDown")` in
modules/Frame.lua but left two docs describing the [M4-24] state that
commit reverted.
1. docs/frame.md:66 said the button was registered for `AnyDown` — "one
edge, deliberately" — and repeated the premise e233cf3 disproved, that
the pair casts twice per press. It now says what the code does and why:
`AnyUp` is the edge that actually executes the macro on a bare
SecureActionButtonTemplate, `AnyDown` alone casts nothing with no Lua
error to notice, and the PreClick `down` gate is what keeps two edges to
one debug line.
2. docs/test-cases.md regenerated — the renamed frame case still read
"registers exactly one click edge". Generated file, so this is
`lua tests/run.lua --list > docs/test-cases.md`, not a hand-edit.
Totals unchanged at 433, so the README badge already agrees.
Verification: 433 passed, 0 failed; luacheck 0 warnings / 0 errors in 14
files; lizard 0 warnings, no CCN > 15; vendor gate clean on all four
readings against ../LibKa0s (LibKa0s and testkit, content and bytes).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KQxehYAMgZtvAJDCJxXYRs
commit ff7902b58c6d3615accb6d35e3c5d22105e1f252
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 01:08:23 2026 +0530
docs(frame,test-cases): the teleport button carries both click edges
e233cf3 restored `RegisterForClicks("AnyUp", "AnyDown")` in
modules/Frame.lua but left two docs describing the [M4-24] state that
commit reverted.
1. docs/frame.md:66 said the button was registered for `AnyDown` — "one
edge, deliberately" — and repeated the premise e233cf3 disproved, that
the pair casts twice per press. It now says what the code does and why:
`AnyUp` is the edge that actually executes the macro on a bare
SecureActionButtonTemplate, `AnyDown` alone casts nothing with no Lua
error to notice, and the PreClick `down` gate is what keeps two edges to
one debug line.
2. docs/test-cases.md regenerated — the renamed frame case still read
"registers exactly one click edge". Generated file, so this is
`lua tests/run.lua --list > docs/test-cases.md`, not a hand-edit.
Totals unchanged at 433, so the README badge already agrees.
Verification: 433 passed, 0 failed; luacheck 0 warnings / 0 errors in 14
files; lizard 0 warnings, no CCN > 15; vendor gate clean on all four
readings against ../LibKa0s (LibKa0s and testkit, content and bytes).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KQxehYAMgZtvAJDCJxXYRs
commit 8335b73c91f89ba15ab54257c70d8b0258603226
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 22:17:55 2026 +0530
data(teleports): restore Halls of Infusion at its real mapID 2527
Completes b421c6d, which parked the row rather than guess its key after
luacheck caught it colliding with Uldaman on 2451.
[2527] = 393283, -- Halls of Infusion
mapID confirmed by the reporter. Both Dragonflight dungeons now resolve to
their own teleport instead of one shadowing the other, and the Dragonflight
block is back to sorted key order.
Verification: 433 passed, 0 failed; luacheck 0 warnings / 0 errors in 14
files; 2451 -> 393222, 2527 -> 393283. Table 74 -> 75 rows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit b421c6d76d70065b22bc81c1138c81a071878eff
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 22:17:23 2026 +0530
fix(teleports): Uldaman is 2451 — the Halls of Infusion row was misfiled
Three changes, all from in-client tooltips supplied by the reporter.
1. Uldaman: Legacy of Tyr enabled at its real mapID.
[2451] = 393222, -- Path of the Watcher's Legacy
It had sat commented out awaiting a mapID since the file was written.
2. Halls of Infusion parked, because it was occupying 2451.
luacheck caught the collision the moment Uldaman went in (W314, value
assigned to field 2451 overwritten before use). Since 2451 is Uldaman,
the pre-existing Halls of Infusion row was misfiled under it -- so
Uldaman groups were offered Halls' teleport (a wrong-spell cast, the
same failure class as the Nexus-Point Xenas row corrected in e30c1f6)
and Halls groups got no button at all.
Parked rather than guessed. The spell (393283) is kept in the comment;
only the key is unknown. Net effect is strictly an improvement: Uldaman
becomes correct, Halls stays absent, which it effectively already was.
It needs its real mapID to come back.
3. Dawn of the Infinite's "(validate mapID)" marker removed -- 424197
confirmed as Path of Twisted Time. Both Dragonflight rows now carry
their spell name, matching the Midnight block's convention, because the
"Path of <flavour name>" names do not contain the dungeon name and are
not recoverable from the row without them.
Verification: 433 passed, 0 failed; luacheck 0 warnings / 0 errors in 14
files (the W314 collision is gone); 2451 -> 393222, 2579 -> 424197,
2915 -> 1254553. Table holds at 74 rows: Uldaman in, Halls out.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit e30c1f63f85db1e5dcf78b6caa41f9b6e0cbe6e7
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 22:14:16 2026 +0530
fix(teleports): Nexus-Point Xenas is 1254553, not 1255391
Corrects the row added in 55f5567. I picked the wrong spell.
1255391 is "Teleport: Nexus-Point Xenas" -- a different spell that shares the
destination. The Keystone Hero reward the player actually learns is
"Path of the Fractured Core", spell 1254553, confirmed from the in-game
tooltip. With the wrong ID the button would have rendered desaturated and
its /cast would have named a spell the player does not have: the same shape
of silent failure this file's other rows exist to avoid.
Two claims in 55f5567 were also wrong and are removed:
- "Midnight renames these to Teleport: <Dungeon>". It does not. The Keystone
Hero teleports keep the "Path of <flavour name>" convention throughout.
- The implication that the spell can be found by searching the dungeon name.
It cannot: "Path of the Fractured Core" does not contain "Nexus-Point
Xenas" anywhere, which is exactly why a name-based web search found the
wrong spell and an in-game tooltip found the right one.
The comment above the Midnight block now says both things outright, and names
1255391 explicitly as the decoy, so the next person does not repeat this.
Each Midnight row gains its spell name in a trailing comment.
Verified against in-game tooltips supplied by the reporter -- 7 of the 8
Midnight S1 pool rows already matched (Windrunner Spire 1254400, Magisters'
Terrace 1254572, Maisara Caverns 1254559, Skyreach 159898, Pit of Saron
1254555, Seat of the Triumvirate 1254551, Algeth'ar Academy 393273); this
row was the only one wrong.
Verification: 433 passed, 0 failed; luacheck 0 warnings / 0 errors in 14
files; [2915] resolves to 1254553.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 55f55672898493285a0ce27dc9b423d5c659d807
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 22:10:03 2026 +0530
data(teleports): add Nexus-Point Xenas, completing the Midnight S1 pool
Reported in smoke testing: applying to a real Nexus-Point Xenas group showed
no teleport button, while Keystone Polaris showed one for the same group.
Not a regression from the remediation branch -- `git log main..HEAD --
defaults/TeleportSpells.lua` was empty before this commit. Plain data
staleness: NS.TeleportSpells had no row for mapID 2915, so GetTeleportSpell
returned nil and ConfigureTeleportButton hid the button, which is the correct
behaviour on a missing row. The reporter's own debug line is the diagnosis:
[Frame] teleport spellID=nil known=nil (activity=1768 map=2915)
[2915] = 1255391, -- Nexus-Point Xenas
mapID from the client's own [Apply] trace (map=2915). Spell ID confirmed in
the client rather than from a web source, per docs/common-tasks.md's recipe:
/dump C_Spell.GetSpellInfo(1255391)
-> name="Teleport: Nexus-Point Xenas", spellID=1255391, iconID=135760
Note the naming change, recorded in a comment above the Midnight block:
Midnight issues these as "Teleport: <Dungeon>", not the older "Path of
<Dungeon>". Only the ID is load-bearing -- the macro is built from the
spell's own name at configure time -- but it is why the wiki has no
"Path of Nexus-Point Xenas" page to check against.
This was the only gap in the Midnight Season 1 M+ pool; the other seven
(Windrunner Spire, Magisters' Terrace, Maisara Caverns, Algeth'ar Academy,
Seat of the Triumvirate, Skyreach, Pit of Saron) were already present.
Table now carries 74 rows.
Verification: 433 passed, 0 failed; luacheck 0 warnings / 0 errors in 14
files. Table loads under lua5.1 with [2915] resolving to 1255391.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit e233cf341af7cbe541be87f2b4b5cc41c81eadea
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 22:05:17 2026 +0530
fix(frame): restore both click edges — the up edge is the one that casts
Regression from [M4-24], reported in smoke testing: pressing the teleport
button did nothing. No Lua error, and the PreClick trace still printed
"teleport button pressed -> /cast Path of the Corrupted Foundry", which is
what made it look like the button was working.
Root cause. M4-24 narrowed RegisterForClicks("AnyUp", "AnyDown") to
"AnyDown" alone, on the premise that a SecureActionButtonTemplate runs its
secure action once per registered edge and two edges therefore cast twice.
That premise was reasoned from reading the code and was never tested in a
client. A bare SecureActionButtonTemplate with type="macro" does not run its
macro on the down edge -- Blizzard's own action buttons cast on down because
they opt into it, and this button inherits none of that. "AnyUp" was the edge
executing the /cast all along; "AnyDown" only ever fed the PreClick trace.
So the failure is silent by construction: the button still receives the down
edge (the trace proves it) and casts nothing.
WG-R-05 offered two resolutions -- register one click edge, OR document the
PreClick gate as the reason both are needed. This takes the second, which is
the one the evidence supports. Both edges restored; the comment at the
registration now carries why, including the shape of the silent failure.
tests/test_frame.lua: the M4-24 case asserted the edge COUNT was 1, so it
pinned the regression rather than the behaviour -- a unit test cannot observe
Blizzard's secure dispatch, so asserting the implementation detail was the
wrong thing to assert. Inverted: it now requires both edges and says why
losing either is undetectable in-game without pressing the button.
Verification: 433 passed, 0 failed; luacheck 0 warnings / 0 errors in 14
files. Planted violation: narrowing back to "AnyDown" alone -> 432/1 red;
reverted -> 433/0 green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 6d9eba5812bbae921cce50948d016602c8378693
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 15:19:12 2026 +0530
docs(architecture): record the events-frames-taint-§8 re-grade [M4-27]
DISPOSITION, not a fix. `M1-STD-11` resolved as option (a): §8's
pre-formatting MUST NOT is now scoped to call sites whose arguments can
reach a value read from one of the named combat-protected APIs, and is a
SHOULD NOT everywhere else. No call site is converted.
Sweep run against the live scoped rule. WhatGroup calls **none** of the
trigger-set APIs:
grep -rnE 'UnitGetTotalAbsorbs|UnitGetTotalHealAbsorbs|UnitGetIncomingHeals
|UnitHealth|UnitHealthMax|UnitThreatSituation|UnitDetailedThreatSituation
|C_UnitAuras|GetPlayerAuraBySpellID|"UNIT_AURA"'
over core/ defaults/ locales/ modules/ settings/ returns zero — the addon
reads LFG search-result data only (strings, integers, booleans from
`C_LFGList`). So no pre-formatting site in this repo sits on a named API,
none re-grades as a real fix, and every one of them is a SHOULD.
`grep -rcE 'print\(\(".*"\):format' settings/` is 0 in all four files.
Recorded as a register row in docs/ARCHITECTURE.md § Documented
deviations. The row is deliberately NOT a blanket absolution: §8 keeps
the no-global-`print` prohibition unqualified, so the two `pout`
fallbacks in settings/Panel.lua and settings/Schema.lua are stated as
standing on unreachability (the TOC loads core/WhatGroup.lua, which sets
`WhatGroup._print`, before settings/) rather than on the re-grade, and
the row carries a second re-check trigger that ends it the moment that
branch becomes reachable.
Closes WG-A-08.
Verification: luacheck 0 warnings / 0 errors in 14 files; 433 passed,
0 failed; lizard Warning cnt 0. Docs-only — no test case added, no
planted violation applicable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit f70c298c67726e5bce49c33bc9755ed9ef1d00db
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 15:16:49 2026 +0530
fix(frame,debuglog): the §2 font fallback, one click edge, and the shared playstyle helper [M4-24]
Three unrelated one-offs, all traced to a finding.
1. WG-A-13 (`debug-logging-§2`) — `core/DebugLogSetup.lua` handed
`NS.FONT_MONO` to the library with no Blizzard fetch-failure fallback.
The failure mode is silent: `SetFont` answers `false` when the client
cannot load the file (a packager that dropped `media/fonts/`, a corrupt
TTF, a case-mangled path) and does not raise, so the console would come
up in a proportional font and the aligned `HH:MM:SS | [tag]` columns the
monospace MUST exists for would be gone with nothing in the error log.
Added `resolveConsoleFont`, which probes the path once at load with a
`CreateFont` object and substitutes `Fonts\ARIALN.TTF` on failure. A
Font object, not a throwaway frame: lightest thing carrying `SetFont`,
never parented or shown, and it cannot disturb a shared Blizzard font.
2. WG-R-05 — `modules/Frame.lua` registered the secure teleport button for
`"AnyUp", "AnyDown"`. A SecureActionButtonTemplate runs its secure
action once per REGISTERED edge, so one press fired the `/cast` macro
twice, the second landing on a cast already going out. Now `"AnyDown"`
only — the edge Blizzard's own action buttons cast on, and the edge the
`PreClick` debug trace already gated for, so the trace is unchanged.
3. WG-R-04 — `modules/Frame.lua`'s playstyle field open-coded what
`Labels.GetPlaystyleLabel` already does, two lines below a call to its
sibling `Labels.GetGroupTypeLabel`. Now calls the helper.
Mock: `tests/wow_mock.lua` models `SetFont` (returns false for a path in
the new `mock.fontFetchFails`) and `RegisterForClicks` (records the edges)
rather than letting the PascalCase catch-all answer — in both cases the
catch-all's answer is exactly the one that hides the behavior.
Tests: three new cases in `test_debuglog.lua` (two) and `test_frame.lua`
(one). docs/test-cases.md regenerated, README badge 430 -> 433. Docs:
frame.md's field table and secure-button section, debug-console.md's
descriptor list and new "fetch-failure fallback" subsection,
ARCHITECTURE.md's vendored-font invariant, file-index.md's descriptor row.
Closes WG-A-13, WG-R-04, WG-R-05.
Verification: luacheck 0 warnings / 0 errors in 14 files; 433 passed,
0 failed; lizard Warning cnt 0; test-cases.md diff empty.
Deliberate violations, each shown red then reverted green:
- `font = NS.FONT_MONO` (bypassing the probe) -> 432/1 red, 433/0 green
- `RegisterForClicks("AnyUp", "AnyDown")` -> 432/1 red, 433/0 green
- playstyle open-coded to `info.playstyleString or ""` -> 432/1 red,
433/0 green
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit f5d1202b0820825a61293db3c74e9140da9fdcfa
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 15:08:08 2026 +0530
docs(localization): ratify English-only, and make enUS.lua state the real surface [M4-17]
`localization-§3`'s routing SHOULD has two terminal compliant states —
strings routed through `NS.L`, or an English-only decision recorded in the
deviation register with a re-check trigger. WhatGroup was in neither: the
decision lived only as prose in `scope.md` and a SCOPE comment, and five
keys in `locales/enUS.lua` had no `L[...]` reader at all, which is the
worse half — a key nothing looks up tells a translator a surface is
covered when it is not.
DISPOSITION on the five dead keys: four deleted, one wired.
- `"Slash Commands"` is WIRED — `settings/Panel.lua` now calls
`Helpers.Section(ctx, NS.L["Slash Commands"])`. Pure chrome, no
structural role, behavior-identical under the fall-back metatable.
- `"Ka0s WhatGroup"` and `"General"` are DELETED as unroutable. `"General"`
is at once the options page id, the `group` key on three `Schema.lua`
rows and the Blizzard subcategory label; translating the display copy
alone would unmatch the schema and empty the page. `"Ka0s WhatGroup"`
is the brand, carried by the TOC Title and the category label.
- `"Defaults"` and the combat-refusal notice are DELETED as not ours —
they are `LibKa0s-Options-1.0`'s `DEFAULTS_LABEL` / `COMBAT_REFUSED`,
and a copy here would be a second source of truth that drifts.
Added the `localization-§3` register row to `docs/ARCHITECTURE.md`
§ Documented deviations, naming all three unrouted classes, with
Decided 2026-08-05 and re-check trigger "the first non-English locale
file". Corrected the three places that claimed *every* authored string
routes through `NS.L` (ARCHITECTURE.md invariants, load-order note and
Non-goals; file-index.md's locale row; scope.md's localization bullet).
Closes WG-R-06.
Verification: luacheck 0 warnings / 0 errors in 14 files; 430 passed,
0 failed; lizard Warning cnt 0. No new test case, so no planted
violation; the existing `panel: the landing page renders a Slash
Commands heading` case covers the wired call site.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 96b01496bbeb58aee074d536e36579bf2cebf1c0
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 15:04:23 2026 +0530
fix(options): register the settings category regardless of combat [M4-01]
`Settings.Register()` carried an `InCombatLockdown()` early-return as
"defense in depth" atop `runConfig`'s open-path refusal. Registering a
canvas Settings category is not a secure write and never taints, and
`options-ui-§9` makes eager registration at load a MUST — so the guard
bought nothing and cost the AddOns-list entry on any `/reload` taken in
combat, since `_settingsRegistered` stayed clear and only a manual
`/wg config` re-armed it.
Removed the guard and the now-stale rationale comments in
`settings/Panel.lua` and `core/WhatGroup.lua`; replaced them with the
positive statement of why registration is ungated. Docs follow:
ARCHITECTURE.md's Taint Notes and Invariants ("three combat guards" ->
two), frame.md's Combat-defer list, settings-system.md's
`Settings.Register()` section, file-index.md's Panel.lua row.
Tests: `panel: registering during combat is refused and says why` and
`panel: a combat-time bail still registers once combat ends` inverted
into `panel: registering during combat still registers (options-ui-§9)`
and `panel: a login taken in combat needs no second registration`;
`lifecycle: /wg config registers the panel if login-in-combat skipped
it` becomes `lifecycle: a login taken in combat still registers the
panel`. docs/test-cases.md regenerated.
Closes WG-A-12.
Verification: luacheck 0 warnings / 0 errors in 14 files; 430 passed,
0 failed; lizard Warning cnt 0; test-cases.md diff empty. Deliberate
violation: reinstating the `InCombatLockdown()` early-return reddens
both new cases (428 passed, 2 failed); reverted, back to 430/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 19a4278ff05375d28437362df63fb648ec4e8d3f
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 14:58:18 2026 +0530
fix(settings): a read no longer materializes parent tables [M4-03]
settings/Schema.lua's `Resolve` walked the path creating an empty table for
every missing intermediate segment, and BOTH `Helpers.Get` and `Helpers.RawSet`
called it. So a read was a write: `Helpers.Get("brandnew.deep.leaf")` grew
db.profile a `brandnew` table and a `brandnew.deep` table, which then
round-tripped into SavedVariables — and made the very typo the caller was
probing for indistinguishable from a real-but-empty branch on the next read.
`Resolve` now takes a `create` flag. `Helpers.RawSet` passes true (a write must
still be able to materialize a row nested under a table SavedVariables has never
held — the existing "a write creates the intermediate tables it walks through"
and "replaces a non-table intermediate" cases both still pass). `Helpers.Get`
passes nothing and gets nil back, taking the existing "no path" branch, which
also emits the Debug line that was already there for the no-db case.
Renamed the existing case from "settings: Resolve creates the intermediate
tables it walks through" to "settings: a write creates ..." — it exercises
Helpers.Set, and after this change "Resolve creates" is only half true.
Closes WG-R-03 (savedvariables-§2, "a read does not write").
RED/GREEN, per the plan's plant-a-violation requirement:
planted — reverted the guard to the old
`if type(parent[k]) ~= "table" then parent[k] = {} end`
red — FAIL settings: Get on an unknown deep path returns nil and creates
no table / tests/_kit/framework.lua:89: the read materialized a
parent table (got <table>) — 429 passed, 1 failed, 430 total
reverted— restored the create-flagged guard
green — PASS settings: Get on an unknown deep path returns nil and creates
no table — 430 passed, 0 failed, 430 total
Verification: luacheck . 0 warnings / 0 errors in 14 files;
lua5.1 tests/run.lua 430 passed / 0 failed (429 → 430: the one added case,
no case removed);
lizard -l lua -x "./libs/*" -x "./tests/_kit/*" . Warning cnt 0;
docs/test-cases.md regenerated with `lua5.1 tests/run.lua --list` and the
CR-normalized diff against a fresh --list is empty; README tests badge moved
429/429 → 430/430 in the same commit, as docs/testing.md requires.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit dd9ad9382025e4d9900d8b33ac56d5c6a324fba9
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 14:55:18 2026 +0530
chore(packaging): complete the .pkgmeta ignore list [M4-13]
packaging (the live WowAddonStandards v2.22.0 section file, cited by bare
filename — it carries no numbered subsections) MUSTs that the package ignore
docs/, _dev/, tests/ and lockfiles, and its minimum template is
.luacheckrc / .gitignore / docs / tests / _dev / "*.bak".
The list carried the first four and omitted the last two. Added:
- _dev — the scratch dir the standard reserves
- "*.bak" — the lockfile/backup pattern; this is what "lockfiles" resolves
to in the canonical block, and the audit's own technical design
(docs/audits/2026-08-05/04_TECHNICAL_DESIGN.md, WG-36) reads it
the same way
- .claude — not in the template, but the acceptance is "the canonical block
plus every dev-only path present at root", and .claude/ is at
this repo's root
No externals: block is added or implied; the vendored-libs note is unchanged.
Parsed with yaml.safe_load to confirm the block is still valid YAML and the
quoted glob survives as the string *.bak.
Closes WG-A-07.
Verification: luacheck . 0 warnings / 0 errors in 14 files;
lua5.1 tests/run.lua 429 passed / 0 failed (baseline 429, unchanged);
lizard -l lua -x "./libs/*" -x "./tests/_kit/*" . Warning cnt 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 396f9b763de022441098631f81a503a7c093c169
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 14:53:50 2026 +0530
docs(architecture): carry every mandated heading [M4-09]
documentation-§3 mandates eight ARCHITECTURE.md sections — Overview, Module
Map, Settings Schema, Message Bus, Slash Commands, Event Subscriptions, Taint
Notes, Known Limitations — plus M3-08's "## Documented deviations".
Added, all from the code rather than from memory:
- Settings Schema — the ten profile rows with path/type/panel group, the
structure-vs-values split (settings/Schema.lua vs defaults/Profile.lua
NS.C), the global table, and the Get/Set write seam.
- Message Bus — "there is none, because…", which C25 permits: grep for
SendMessage/RegisterMessage over core modules settings defaults returns
nothing, and _TryFireJoinNotify is the coordination seam instead.
- Slash Commands — the eleven COMMANDS rows, host vs library ownership, the
positional-triple shape, and the reserved-but-unregistered `perf` verb.
- Event Subscriptions — the full surface from grep: GROUP_ROSTER_UPDATE,
LFG_LIST_APPLICATION_STATUS_UPDATED, two one-shot PLAYER_REGEN_ENABLED
frames, and the two file-load hooksecurefunc post-hooks.
- Taint Notes — the no-AceHook rule, hooks-at-load/secure-frames-at-first-use,
the three modules/Frame.lua combat guards, why category registration is not
combat-gated, and the two C_Timer.After(0) taint hops.
- Known Limitations — session-only capture, LFG-only capture, the
hand-maintained teleport table, English-only, no profiler wiring.
CITATION CORRECTION: WG-A-11 names four missing headings, but the file also
lacked literal `Overview` and `Module Map` — it carried "What it does" and
"Subsystems at a glance". The item's acceptance is that grep returns ALL EIGHT,
so those two are renamed to the mandated names. Content is untouched by the
rename and nothing linked either anchor (only #documented-deviations is linked,
from performance.md and pending/LEDGER.md). docs/file-index.md's one-line
description of the file is updated to match, so it does not become a C3 defect.
Closes WG-A-11.
Verification: luacheck . 0 warnings / 0 errors in 14 files;
lua5.1 tests/run.lua 429 passed / 0 failed (baseline 429, unchanged);
lizard -l lua -x "./libs/*" -x "./tests/_kit/*" . Warning cnt 0;
grep '^## ' docs/ARCHITECTURE.md returns Overview, Module Map, Settings Schema,
Message Bus, Slash Commands, Event Subscriptions, Taint Notes, Known
Limitations and Documented deviations.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit bff33ab057437c89f5f19661b5080a84207a72e4
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 14:50:03 2026 +0530
docs(readme): drop the non-canonical "## Bundled libraries" heading [M4-08]
documentation-§1 permits twelve README sections in order: H1 title, badge row,
logo, description, What's new, Screenshots, Usage, How <it> works, FAQ,
Troubleshooting, Issues and feature requests, Version History.
"## Bundled libraries" sat between "## How it works" and "## FAQ" as a
thirteenth.
The heading is gone; its paragraph moves verbatim into the description block
above "## What's new", which is an unheaded permitted region. The paragraph had
to be kept, not deleted: it carries the LibKa0s provenance line that
tests/_kit/vendor_sync.lua reads out of README.md as the INPUT to the
vendored-payload gate (DEFAULT_README pattern
"[Bb]undles %[LibKa0s%]%b() (v[%d%.]+)"). Deleting the sentence would have
turned both vendor-sync cases red.
`grep '^## ' README.md` now returns exactly the canonical set in order.
Closes WG-A-10.
Verification: luacheck . 0 warnings / 0 errors in 14 files;
lua5.1 tests/run.lua 429 passed / 0 failed (baseline 429), with
"libs/LibKa0s is the LibKa0s release the README says this addon bundles" and
"tests/_kit is the test kit that shipped with that release" both PASS, not SKIP;
lizard -l lua -x "./libs/*" -x "./tests/_kit/*" . Warning cnt 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 38f85993ec684cbba2a929775e2d8ddc179b18e8
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 14:48:12 2026 +0530
docs(core): annotate NS.Util.format as a deliberate parity export [M4-07]
`NS.Util.format` is published on both the library path (core/CoreSetup.lua,
`printer.Format`) and the degraded fallback branch, with zero production
callers repo-wide. It is not dead by accident: it is the parity half of the
printer surface, so the fallback exposes the same keys as the library path.
Per C7 ("an exported symbol has a caller or a documented reason"), both
publication sites now carry the reason explicitly, and each points at the
other so a future removal takes both halves together. No behavior change.
Closes WG-R-08.
Verification: luacheck . 0 warnings / 0 errors in 14 files;
lua5.1 tests/run.lua 429 passed / 0 failed (baseline 429, unchanged);
lizard -l lua -x "./libs/*" -x "./tests/_kit/*" . Warning cnt 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 38642aecf923779a7c6eb7aa0dc4063b3d9b8226
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 13:06:38 2026 +0530
docs(performance): claim the no-combat-path exemption, with the sweep [M3-09]
`performance-§12`. WhatGroup runs no code while the player is in combat, so
the wiring MUST does not apply — but the claim is only an exemption when it
is proven and recorded, which it was not.
- docs/performance.md (new, one screen): the COMMITTED whole-repo sweep of
`RegisterEvent` / `SetScript("OnUpdate"` / `C_Timer`, one row per hit
naming the per-event work — zero OnUpdate handlers, zero repeating
tickers, zero repeating timers; every scheduled item is one-shot or
self-unregistering; the only handler reachable inside a combat window is
GROUP_ROSTER_UPDATE, an IsInGroup() and three comparisons. Then criteria
(b) and (c), the re-check trigger, and what the exemption does NOT excuse
(whole-folder vendoring, the reserved `perf` verb, the `perf: skip` line).
- docs/ARCHITECTURE.md: M3-08's register row re-cited from `performance-§1`
to `performance-§12` — the exemption is a recorded exit, not an unmet MUST
— with the sweep named as the Why and the trigger in the section's own
words.
- docs/file-index.md: performance.md listed in the docs/ read-on-demand set.
Closes WG-A-01 … WG-A-06.
Verification: docs/performance.md exists and the register carries exactly one
`performance-§12` row with a Decided date and a re-check trigger; no `§N.M`
notation introduced; luacheck 0/0; tests 429 passed, 0 failed; lizard
Warning cnt 0; `--list` diff against docs/test-cases.md empty.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 8b65e0995d74e0466320f47af77b6eb73690d7ea
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 13:04:07 2026 +0530
docs(architecture): open the documented-deviation register [M3-08]
Adds `## Documented deviations` to docs/ARCHITECTURE.md — the ninth mandated
section and the SINGLE home for a ratified deviation (documentation-§3), in
the exact row shape `| Rule | What differs | Why | Decided | Re-check
trigger |`.
Moves WhatGroup's one existing record in: the Perf decline reasoned at
docs/pending/LEDGER.md's LIBKA0S-15 row (line 63), which was ratified with
the user on 2026-08-02 and lived only in the ledger. A deviation that is not
in the register is not ratified, so every audit cycle re-filed it as an open
performance-§1 MUST failure. The ledger row now opens by naming the register
as its home and keeps the long-form reasoning the Why cell cites.
The row carries both structural reasons (no hot path; `suspend` would destroy
capture data) and a re-check trigger that can be checked: the first OnUpdate,
repeating ticker, or in-combat event handler doing more than occasional work.
WG-A-13 is deliberately NOT here — nothing ratifies it, so it is a genuinely
unmet debug-logging-§2 SHOULD and is code work, not a register row.
Closes M3-08 (WhatGroup half).
Verification: `grep -n "Documented deviations" docs/ARCHITECTURE.md`
resolves; the row carries a `filename-§N` Rule, a Decided date and a
Re-check trigger; luacheck 0/0; tests 429 passed, 0 failed; lizard Warning
cnt 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 2f74372a65f103e2cabf9626e045d5835e7121b1
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 13:02:42 2026 +0530
docs: retire the global §N.M notation across the repo [M3-07]
The widened revendor-standards notation sweep, excluding libs/, tests/_kit/,
docs/audits/, docs/reviews/ and docs/automated-tests/ (all three docs/ paths
are frozen evidence). 13 sites, comment- and prose-only; no code changed.
- docs/smoke-tests.md, docs/wow-quirks.md, DEPENDENCIES.md: the §N and §N.M
forms were this repo's own internal cross-references, not standards
citations, but they are indistinguishable from the retired global notation
by any sweep. Rewritten as "section N" / "sections N.M, ...". The
`filename-§N` citations in the same files are the current form and are
untouched.
- .pkgmeta:4: `(§3.3, §13)` was a genuine reference in the retired global
numbering — now `(packaging, library-stack-§3)`, matching packaging.md:5's
own citation for the no-externals rule; `packaging` by bare filename since
the rule is the section's whole subject.
- docs/file-index.md:13: `compat-§1` is OUT OF RANGE — compat.md carries no
numbered sections at all, so the reference could never resolve
(documentation-§6 grades this a MUST, not the mechanical SHOULD). Replaced
with the bare filename.
Closes WG-A-15.
Verification: `grep -rEn '§[0-9]+\.[0-9]'` excluding all five paths → 0;
every remaining `filename-§N` range-checked against the fetched section
file's local heading count, all in range; luacheck 0/0; tests 429 passed,
0 failed; lizard Warning cnt 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 153ab39f6d58c81446deaf053364ec7235439a7b
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 13:00:03 2026 +0530
docs(testing): name the checkpoint on every gate statement [M3-06]
Sweep 3f. `no — recorded only` is true of a run and of a commit and false of
the tag, so standing unqualified it reads as "perf and complexity gate
nothing", which automated-tests-§3's release gate contradicts (testing-§6,
automated-tests-§4).
- docs/testing.md: the Gates? column splits into "Gates the run + the
commit?" and "Gates the tag?", every row answering both; the paragraph
below and the "At release, not at commit" paragraph now name the tag gate
(all four suites at `pass` plus zero functions above CCN 15, evaluated by
/wow-addon:bump-version from the run's manifest.json).
- docs/automated-tests/README.md § "What gates, and what only records": same
two-checkpoint table and prose.
- docs/automated-tests/RESULTS.md: the LEAD-IN paragraph only, replaced by
hand with the v8 runner's text — the runner writes that block only when
RESULTS.md does not yet exist, so an existing file keeps the old wording
until it is replaced. The trend rows are untouched; no dated bundle is
touched.
Closes WG-A-14.
Verification: `grep -rn "never fail a run" docs CLAUDE.md` returns only
checkpoint-qualified sentences outside the frozen docs/audits/ bundle;
luacheck 0/0; tests 429 passed, 0 failed; lizard Warning cnt 0; `--list`
diff against docs/test-cases.md empty (CR-normalized both sides).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 22d700416484be97fdbe27da789dc9237566de75
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 12:57:31 2026 +0530
chore(tests): record the vendored runner as executable in the index [M3-05]
`git update-index --chmod=+x tests/_kit/run-automated-tests.sh`. The bit does
not travel with `cp`, is not in the file's bytes, and this repo sets
core.fileMode=false on a DrvFs tree, so `ls -l` reported rwxrwxrwx for a file
the index called 100644 and nothing could see it (automated-tests-§2).
Closes AT-A-05-class finding for this repo; the collection-wide item is M3-05.
Verification: `git ls-files -s tests/_kit/run-automated-tests.sh` → 100755;
`tests/_kit/run-automated-tests.sh --help` runs with no `bash` prefix;
luacheck 0/0; tests 429 passed, 0 failed; lizard Warning cnt 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 7e4813a59fe8a185744f32b4ea071534a25cc2ef
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 12:57:02 2026 +0530
test(libka0s): pin every LibKa0s stub surface as a set [M3-04]
Adds one `Kit.assertSurfaceParity` case per adopted LibKa0s seam — Core,
DebugLog, Slash and Options — in tests/test_libka0s.lua's degraded section.
Both arms come from a real load; the degraded arm is the existing partial
file list (`skip = NO_LIBKA0S`), never a hand-stub of the member under test
(testing-§8, anti-patterns #56). Each case names the grep that produces the
live member list, and every `ignore` entry names the rule that makes the
member live-only: debug-logging-§3 for the two formatters the DebugLog stub
must not re-implement, options-ui-§1/§8 for the four layout scalars, plus
AceGUI and the three library-internal renderers WhatGroup never calls.
Nothing reddened on adoption — WhatGroup's four stubs were already whole —
so no seam code changed. README test badge and docs/test-cases.md
regenerated for the four new cases.
Closes M3-04 (WhatGroup half).
Verification: luacheck 0/0; `lua5.1 tests/run.lua` 429 passed, 0 failed
(425 + 4); lizard Warning cnt 0; `--list` diff against docs/test-cases.md
empty (CR-normalized both sides). Each case shown RED against a planted
deletion of one stub member and green after revert: Core/MakeCloseButton,
DebugLog/IsShown, Slash/SetRowAnnotator, Options/RenderRows.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit af550da5d7efbf4e13a3d3689b05045316ffd434
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 12:49:18 2026 +0530
chore(vendor): re-vendor LibKa0s v1.8.0 and adopt the shared vendor-sync gate [M3-01][M3-02]
Re-vendors BOTH payloads from the LibKa0s v1.8.0 tag in one commit, because the
gate compares file SETS and reads the README provenance line as its input:
* cp -r <LibKa0s>/LibKa0s/. libs/LibKa0s/ (6 files changed)
* cp -r <LibKa0s>/testkit/. tests/_kit/ (4 changed, vendor_sync.lua new)
and bumps README.md's provenance line from v1.7.0 to v1.8.0. A kit-only copy
would point the gate at a tag whose libs/LibKa0s/ bytes this repo does not hold.
M3-02: WhatGroup had no vendored-payload gate. It gains one as a ~10-line
VendorSync.register(_G.WHATGROUP_TEST, {}) call into tests/_kit/vendor_sync.lua,
with the default case names so they match the other seven repos, declared in
tests/run.lua's suite list. The AbsorbTracker header explaining the single
CR strip on the working-tree side is carried in; the strip itself now lives in
the kit module, where it is correct and stays (the other side is a `git show`
blob, which is LF, while this working tree is CRLF).
Also closes WG-R-09: the British spellings in libs/LibKa0s/ close on the
library-half re-vendor with no local edit (M3-14a) — grep now returns 0.
Verification:
luacheck . 0 warnings / 0 errors, 14 files
lua5.1 tests/run.lua 425 passed, 0 failed (was 423; +2, the gate)
lizard -l lua -x ./libs/* -x ./tests/_kit/* . Warning cnt 0
diff -r <LibKa0s>/LibKa0s/ libs/LibKa0s/ empty
diff -r <LibKa0s>/testkit/ tests/_kit/ empty
--list vs docs/test-cases.md (CR-normalized) empty
suite inventory pre-flight (14 on disk / 14 declared) balanced
planted content byte in libs/LibKa0s/Core.lua -> 1 failed (case #1) reverted -> green
planted extra tests/_kit/PLANTED.txt -> 1 failed (case #2) reverted -> green
planted stale provenance line v1.6.0 -> 2 failed reverted -> green
bogus sibling opt (in place of moving ../LibKa0s, which is racy while other
agents run against it) -> 2 SKIP with reason, exit 0
README [tests] badge moved 423 -> 425. Exec bit deliberately NOT set here (M3-05).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This mod has no additional files

