1.0.0-release
What's new
commit f83a5a9c35515031a59f6deaed7a7a489f5c08f1
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 16:07:35 2026 +0530
release: 1.0.0 — the addon's first published version
0.1.0 was never released. It was set in the initial commit and never moved
across all 108 commits, no tag has ever existed, and CurseForge reports no
file for project 1642836. So this is not an upgrade from 0.1.0 — it is the
first thing anyone can install, and the docs now say that rather than
implying a predecessor.
Release gate (automated-tests-§3), read from the manifest the run wrote,
before any file here was edited:
lint pass 0 warnings / 0 errors in 25 files
tests pass 717 passed, 0 failed
perf skip no tests/perf.lua — the no-scenarios exception
complexity pass 0 functions above CCN 15
verdict green
PERF WAS NOT MEASURED. The gate's narrow exception covers a skip caused by
there being no scenarios to run, which is this one — not a missing
interpreter, and not a ratified performance-§12 exemption, which this addon
does not qualify for (modules/Canvas.lua:577 runs a shared 10Hz OnUpdate,
failing criterion (a)). 1.0.0 therefore ships verified against three suites,
not four, and the README's Version History row says so in as many words.
Version History, not "What's new". The 0.1.0 row described a release that
does not exist — "First release", with an em dash where its date should be
— so it is REPLACED by a 1.0.0 row rather than joined by one, on the owner's
explicit call, since a table naming two first releases helps nobody. No
`## What's new` section is written: with nothing published before it, it
could only restate the feature list directly above it. The
documentation-§1 (item 5) deviation row stands, and its re-check trigger is
corrected — it read "the first version bump after 0.1.0", which fired on
this release, the one release that by definition has nothing to report. It
now names the first release AFTER 1.0.0.
Version strings rolled: the TOC, NS.version, the five test expectations that
would otherwise fail, and the two smoke-test steps that quote `/pm version`.
Also rolled: the docs and comments asserting what the SHIPPING version does
— "0.1.0 ships English-only", the events-frames-taint-§8 decline, the
localization-§1 row — because those are live positions, not dated decisions,
and a claim about a superseded version reads as stale rather than historical.
Deliberately not rolled: CLAUDE.md's `Bundles [LibKa0s] … v1.8.3` (the
vendored library's tag and the vendor gate's input, not the addon's version)
and media/poster/artwork-poster.txt (generated; it records the state at
generation time and moves when the poster is regenerated).
The release bundle is committed with the release, so the tagged tree carries
the evidence for itself. Its ANALYSIS.md and the RESULTS.md watch list carry
two things forward that the green columns hide: max CCN has been exactly 15
for four runs with the gate set above 15, so R.ApplyArtSize and
Compat.AddOnFolders sit on the line and one added branch in either refuses
the next release; and settings/PanelEditor.lua moved for the first time in
four runs, 1064 to 1091. The automated-tests-§4 shelf-life clock starts here
for all three band entries, this being the first release run they can be
counted against.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qp3u6v8mFe6iTEiwDPWutP
commit 91de582a9d2aa75ff1c4099559b102d546d4364b
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 15:50:40 2026 +0530
docs(media): record why two assets carry a JPEG twin, and file the screenshots
`e0a868f`'s message called the screenshot set "mid-conversion, not a
settled convention" and flagged the mixed formats as something to decide.
That reading was wrong and this corrects it: the convention is settled and
the mix is the convention working.
PNG is the repo default — lossless, re-derivable, what a capture or a
render is kept as. CurseForge caps an uploaded image at 2 MB, and exactly
two assets exceed it: the 3439x1439 hero capture (8.1 MB) and the artwork
poster (2.1 MB). Each carries a JPEG twin compressed only to clear that
limit, and it is the twin the README links, because the README points at
the CDN copies rather than at repo paths — CurseForge serves the project
page from its own CDN, so a repo-relative path renders on GitHub and
nowhere else. Screenshots 01 to 03 are under the cap and stay PNG on both
sides. The format follows the file size.
So the rule for a future capture is not "convert it": under 2 MB it needs
no JPEG, over it gets one, and the PNG stays as the master either way.
Also documents media/screenshots/ itself, which nothing under docs/
mentioned, and notes on the poster that its --check fingerprint is the
PNG's — the JPEG is a lossy derivative and could not round-trip a
pixel-level comparison.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qp3u6v8mFe6iTEiwDPWutP
commit e7850accfabcd8ebcf9ba45422f9624d88f9b543
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 15:45:48 2026 +0530
docs: settle the two release deviations and cover the profile-switch checks
**Screenshots row retired.** `documentation-§1` (item 6) said the README's
`## Screenshots` section carried a placeholder because the addon was
unpublished; its re-check trigger was the first published release filling
the section. That has happened — four captures are on the CurseForge CDN,
embedded in the README, and their sources committed in e0a868f — so the
row goes rather than being kept for the history, per this section's own
"not a graveyard" rule. Issue #1 is closed as `[done]` with the detail.
**What's-new row added.** `documentation-§1` (item 5) requires a
`## What's new` section and the README no longer has one. Ratified as a
deviation rather than restored, on the owner's decision: 0.1.0 is the
first release, so there is nothing new to report — the section exists to
tell a returning player what changed since the build they had, and on an
initial release every reader is a new one. It returns at the first update,
which is the first moment it can say something true, and
`/wow-addon:bump-version` rolls it with `## Version History` anyway — so
that command is the re-check trigger and retires the row.
**Smoke tests.** 12b.7 already asked for the panel dropdown to list the
new profile's panels after a switch. That check is older than the bug it
catches and had gone unrun, which is how the defect shipped; it now also
names the Copy-settings-from list and the open editor, and says why one
rebuild covers all three.
New 12b-2 covers what 12b.7 never did: the session state keyed by panel
id. Ids are per profile, so preview's tracked ids, the preview flag, the
per-panel unlocks and the combat-queued unlocks all named someone else's
panel after a switch. Step 3 is the destructive one — preview on, switch,
preview off used to delete the real panels of the profile you switched to.
All six verified in a live client before this commit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qp3u6v8mFe6iTEiwDPWutP
commit e0a868f0e43270e52635872dbe3c3aa473873e2c
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 15:33:41 2026 +0530
assets: commit the project-page screenshots and the poster JPEG
The four screenshots behind README's `## Screenshots` section, plus the
JPEG render of the artwork poster the artwork section embeds. Both are
already live on the CurseForge CDN and the README points at those URLs,
so this commits the sources rather than changing what any reader sees.
None of it ships to players. `.pkgmeta` ignores `media/screenshots`
wholesale and `media/poster` likewise: WoW cannot load a .png or .jpg at
all, so shipping them would add megabytes to every download for files the
client physically cannot use. They are committed for the same reason the
logo renders and the raw artwork plates are — so an asset can be
re-derived without going back to whoever produced it.
Two things a later reader should not have to work out:
- `panelmaster.screenshot.04` is here TWICE, as an 8.1 MB .png and a
1.9 MB .jpg of the identical 3439x1439 image. That is the master/render
pair the logo already uses, but only 04 has been converted — 01 to 03
are .png only, and the README serves 01 to 03 as PNG and 04 as JPEG to
match. So this is a mid-conversion set, not a finished convention.
- Consequently 14 MB lands in git history permanently. Worth a decision
before the set grows: either convert 01 to 03 and keep the PNGs as
masters, or drop the PNG masters for screenshots specifically, which
are re-takeable from the client in a way the artwork plates are not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qp3u6v8mFe6iTEiwDPWutP
commit 8fc3d38378df30d13556be44cc993c99e2902d6d
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 15:33:20 2026 +0530
docs(readme): close the hero screenshot's image link
The URL was split across a newline before the closing paren, so the
markdown never formed an image — it rendered as literal text on GitHub,
and CurseForge's stricter renderer would not have done better. It is the
first thing a visitor sees on both surfaces. The other three screenshots
were already well formed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qp3u6v8mFe6iTEiwDPWutP
commit c596092269bd90da8dd107a223e3833a66186c1d
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 15:33:11 2026 +0530
refactor(panel): drop P:RefreshPanels, now that nothing calls it
Its one caller was settings/PanelEditor.lua's MSG_PANEL handler, which
now calls the library's O.RefreshPanel(ctx, false) instead — the same
refresher run, but the shown/hidden decision and the dirty flag belong to
LibKa0s rather than being hand-rolled per host. Nothing else ever called
it: no other module, no slash verb, no test.
P:Refresh stays. It is the same shape but genuinely live — the console
checkbox mirroring the debug window calls it by name, and it is still
worth having beside O.RefreshScalars for the reason the comment above it
gives: the library's version sweeps every registered ctx, which would
refresh three pages to repaint one.
The comment block above the survivor said "these two"; it now says what
happened to the other one, so the next reader does not go looking for a
symmetry that has been removed on purpose.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qp3u6v8mFe6iTEiwDPWutP
commit 630d1ea6a47459d36576ca551c8940ae12c11fc2
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 15:26:11 2026 +0530
fix(profiles): scope the Panels page and session state to the active profile
Switching or creating a profile hid the panels from the screen but left
them in Settings > Panels > the Edit Panel dropdown, the copy-from list
and the open editor. Four bugs, one family: state keyed to a profile that
outlived the switch. Panel STORAGE was never the problem — panels and
nextID have always lived in db.profile, and every Registry entry point
re-reads it live.
1. The page never re-rendered. settings/PanelEditor.lua hand-rolled the
hidden-page branch and marked `ctx.dirty`; LibKa0s's SetRenderer OnShow
gate reads `ctx._dirty`. The flag was written in four places and read
in none, so the deferred repaint was dropped. A profile switch happens
from the Profiles page, i.e. with the Panels page hidden, so the page
kept the widget tree it built for the previous profile. Canvas is not a
settings page, never consulted the flag, and correctly cleared the
screen — which is exactly the split the report described.
Fixed upstream rather than by correcting the flag name: a private field
the host has to guess IS the defect. LibKa0s v1.8.3 publishes
O.RefreshPanel(ctx, structural), which owns the shown/hidden decision,
the flag and both tiers. Both handlers are now one call to it and the
host-owned `dirty` field is deleted outright, so there is no second
copy of the rule to drift.
2. Preview could delete real panels. nextID is per profile, so every
profile issues ids from 1. NS.State.previewIDs survived a switch and
`/pm preview off` called DeleteBatch with the OUTGOING profile's ids,
which resolve against the current one: preview on, switch, preview off
destroyed the panels the user had made in the profile they switched to.
NS.State.preview also stayed true, so preview could not be restarted to
clear it.
3. Inherited unlocks. NS.State.unlockedPanels and Unlock's deferred
pendingPanels survived the same way, so a panel in the incoming profile
came up individually unlocked wearing a drag handle nobody asked for.
4. Silent wrong selection. PanelEditor's selectedID survived, and the
rebuilder's Registry:Get(selectedID) check cannot tell that the id now
names a different panel, so the editor opened on a panel nobody chose.
Only observable once (1) was fixed.
2-4 are one fix: Registry:ReloadProfile now drops every session table
keyed by panel id BEFORE it sanitizes or broadcasts, via new
Unlock:ForgetPending and PanelEditor:ForgetSelection seams. Same sweep
R:DeleteAll already does, for the same reason. The global unlock flag is
deliberately kept — it is a mode the user put the screen in, not a claim
about a panel.
Re-vendored libs/LibKa0s from v1.8.3 (byte-identical) and moved the
CLAUDE.md provenance line in the same commit, as the vendor gate requires.
Also in here, found while verifying: PanelMaster.toc gains
X-Curse-Project-ID 1642836 in the slot toc-file-§1 pins, and the matching
toc-file-§1 deviation row retires from docs/ARCHITECTURE.md now that the
project id exists.
Tests 713 -> 717. The three new profile-switch cases were verified to fail
against the unfixed ReloadProfile. tests/test_panel.lua's dirty case
asserted the WRONG flag name, which is how this shipped green; it now
asserts the library's flag and a second case checks the repaint actually
lands on the show. tests/test_libka0s.lua's widgetsOfType counted every
widget the whole run had ever created — correct only while the Panels page
never built itself in the harness, which stopped being true here — and is
now scoped to the window the General page's render opened.
Gate: 717/717, luacheck 0/0 in 25 files, vendor diff empty on both
readings.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qp3u6v8mFe6iTEiwDPWutP
commit d71fa72facbc173b918ae227df4dc56536883908
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 13:59:55 2026 +0530
docs: track the in-game capture store's rename to docs/perf-analysis/
The standard retired the flat `docs/perf-runs/` store and replaced it with
`docs/perf-analysis/`, where each capture is a frozen dated bundle holding
the record and its reading together. This addon ships neither store and
still ships none: it declines `LibKa0s-Perf`, registers no `perf` verb and
has nothing to capture, so the four live docs that named the old directory
only needed the new name.
Nothing about the position changed. `docs/performance.md` still says the
`performance-§12` no-combat-path exemption is not claimable here — the
mouseover `OnUpdate` at `modules/Canvas.lua:577` runs in combat — and the
sweep behind that argument is untouched. The frozen audit and review
bundles keep their original wording, as frozen history should.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgcWFzX8WzaWuDHSU9kqZp
commit 090dc5d565f16d2a8b4bcda6997df996ffc97e02
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 12:08:35 2026 +0530
automated-tests: record run 20260807-114409 — green
Four-suite run through the vendored kit (LibKa0s v1.8.2, test-kit
revision 10). lint 0 warnings / 0 errors over 25 files; tests
713 passed, 0 failed, 0 skipped; perf skipped — no tests/perf.lua;
complexity 0 warnings, max CCN 15 (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 69ceb3cbe3f8119d22c833e1ab43c7e69fbc0a9a
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 11:41:01 2026 +0530
chore(gitattributes): mark the vendored upscaler binary, as a ratified deviation
`tools/artwork/bin/realesrgan-ncnn-vulkan` is an extensionless ELF
executable, so no `*.ext` rule in the canonical binary list can reach it.
Unmarked, it was the one working-tree stray line-endings-§7 reported for
this addon — a genuine §4 failure, not a false positive of the old check.
Marking it by path means .gitattributes is no longer byte-identical to
the canonical body, which §5 requires, so the two MUSTs collide. Records
it as a ratified row in ARCHITECTURE.md's deviation register rather than
leaving an audit to re-file it every cycle; the row retires if §4/§5 gain
a provision for extensionless binaries upstream.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LE5Tjrv46E2iZfFVS47WmK
commit b6e7db8753be6d8499d4f4d67949be7a6d1d0fda
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 11:37:54 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 43e6e228fab19e36c9d25ad83a74653e22272c7d
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 11:07:18 2026 +0530
vendor: re-vendor LibKa0s v1.8.2 — test kit revision 10
Both payloads move together, from the tag rather than master, in the same
commit as the provenance line — which is what the vendor gate compares
against and what the last attempt got wrong.
- libs/LibKa0s/ and tests/_kit/ taken wholesale from LibKa0s v1.8.2
(ac857fd); all four diffs against the sibling checkout are empty, both
content and bytes.
- Kit.VERSION 9 -> 10. run-automated-tests.sh now reads .gitattributes per
path with `git check-attr text eol` and writes the bundle in the endings
the repo declares, instead of leaving LF stragglers that git status never
mentions and --renormalize never fixes.
- CLAUDE.md provenance line 1.8.1 -> 1.8.2.
- Options.lua carries an options-ui section-reference correction.
Green: 713 passed / 0 failed / 713 total, luacheck 0/0 in 25 files, and
both tests/test_vendor_sync.lua cases PASS rather than SKIP.
The 20260807-110543 bundle is the first written by the fixed runner and the
proof it works: every file lands CRLF on disk, CR count equal to LF count.
commit 90d997460b9c8b36f22ee09bbf8784062a1b3f72
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 03:22:56 2026 +0530
docs: land the deviation register and the full docs/ set in CLAUDE.md
Two sentences in root CLAUDE.md had drifted from the upstream standard
(v2.28.0), and both are read as instructions by every agent that opens
the repo:
- "Standards compliance (read first)" item 1 still told the agent to
record an accepted deviation "in the TOC/README/docs/ and in the audit
bundle". documentation-§3 has since made docs/ARCHITECTURE.md ->
"## Documented deviations" the single home, with a fixed row shape and
a filename-§N Rule cell; a deviation outside that register is not
ratified, so the old wording sent the decision somewhere an audit does
not look and got it re-filed as an open MUST every cycle.
- "The canonical docs/ set is exactly three files" was a count with an
open slot, which is exactly the shape that invites a model to
reconstruct a member from memory. It now names the trio, the
verification-and-record docs, the six Tier 1 topic docs, and points at
the Documentation map for the conditional ones.
Reference only; no rule text from the standard was copied in and no
version stamp was written.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LE5Tjrv46E2iZfFVS47WmK
commit 04a7f1a2c0795c52f9815254631c8b4df180a75e
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 02:35:49 2026 +0530
automated-tests: record 20260807-023000 — 713/713 green on master
The recorded run was three commits stale against the tree: the newest bundle
was taken on feat/fix-ccn at 645868a, before the audit-remediation merge, the
.gitattributes pin, the documentation tier model and the kit rev-9 re-vendor
landed. This is the first four-suite run on master since.
Full default run, no --suite, no --no-bundle. lint 0/0 over 25 files, tests
713/713 with zero skipped, perf skip (this addon ships no tests/perf.lua),
complexity 0 warnings over 1352 functions with max CCN still 15. Verdict green.
The suite is up seven net — eleven cases added, four collapsed or rewritten —
all of it harness and seam-parity cover following the re-vendor, none of it
behavioural. Complexity is flat where it matters: avg CCN 2.0 and max CCN 15
unchanged, both 15s still R.ApplyArtSize and Compat.AddOnFolders. The +56 NLOC
is entirely in tests/.
RESULTS.md's four standing sections are rewritten against this bundle. The
Lint section carried a false claim worth calling out: it said luacheck runs
over "the addon's own source and its tests/", but .luacheckrc excludes tests/,
so roughly 6,000 lines of harness are unlinted and the 0/0 row could never
show it. automated-tests-§4 asks specifically for what the config excludes;
it now says so.
Every figure comes from this bundle's manifest.json or a suite artifact.
Nothing was hand-written and no frozen bundle was touched.
Also swept the comment-citation check over the repo's own .lua/.toc/.pkgmeta
(excluding libs/ and tests/_kit/). Nothing was applied: every surviving hit is
deliberate history (modules/DebugLog.lua, docs/pending/LEDGER.md — both
correct as past-tense prose), a citation into third-party SunnArt source, or a
stale rationale rather than a bad path. Details in the run report.
commit d91615f2da5485b947babb35c6c0f16db83b695e
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Aug 7 02:13:12 2026 +0530
docs: land every cross-reference and file:line citation on what it names
A pointer that resolves to nothing is read as "the answer is elsewhere" and
the reader stops looking, so each of these cost a lookup rather than merely
being untidy.
- CLAUDE.md sent the seam question to `docs/ARCHITECTURE.md` > *The LibKa0s
seams*, a section that does not exist; the seam table and the TOC order it
pins are in `docs/module-map.md`. Its release note pointed at
`docs/testing.md` > *The complexity report*, retired with the standalone
report in standard v2.19.0 -- now *Automated test records*.
- ARCHITECTURE.md's module-map summary put all four LibKa0s seams in `core/`.
Two are: `CoreSetup` and `DebugLogSetup`. `Slash` and `OptionsSetup` are
under `settings/`, which the same sentence already half-said.
- smoke-tests.md's LSM-dropdown step cited `ARCHITECTURE.md` > Options UI;
the AceGUI value-push workaround is `settings-panel.md` > *Three widget
workarounds*.
- automated-tests/README.md linked `../perf-runs/`, which has never existed
here -- this addon declines `LibKa0s-Perf` and registers no `perf` verb, so
the link now says that instead of dangling.
- DEPENDENCIES.md's kit citations had drifted with the re-vendor:
vendor_sync.lua:140/:139 -> :154/:153 (the `git -C` shell-out and its
io.popen guard), :96 -> :104 (`localNames`), and make_poster.py:387-389 ->
:386-388 (the toolchain tuple).
Green gate: 713/713 tests, luacheck 0/0 over 25 files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuCbofydwVeUp61YW4DiJ4
commit a8e7a2a471a7a067b8a223feddba6a512098cc93
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 23:14:08 2026 +0530
docs: collapse the duplicate ISS-01 citation onto PLAN-05's issue
ISS-01 and PLAN-05 are the same item — ISS-01's own rationale says so and
names #1 as the single tracker. The migration briefly gave ISS-01 its own
issue (#32), which duplicated #1 and carried nothing #1 did not; #32 has
been deleted, so this citation now points only at #1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QuCbofydwVeUp61YW4DiJ4
commit be6d8749bd683ace8b5bf304b86bb75d5837d4db
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 21:21:35 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 fcdacda2375baa194b2c455c10bdcad873754195
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 18:39:54 2026 +0530
docs: move the LibKa0s provenance line to CLAUDE.md; re-vendor kit rev 9
Adopts LibKa0s v1.8.1, whose only change is test kit revision 9:
`vendor_sync.lua` now reads the `Bundles [LibKa0s](...) vX.Y.Z (MIT).`
provenance line out of the consuming repo's CLAUDE.md instead of its
README.md, with no fallback. The line, the vendored bytes and the
regenerated inventory therefore all move in this one commit — a
provenance line and a payload that disagree is the drift the gate exists
to catch, so they cannot land separately.
Why CLAUDE.md: "which LibKa0s does this build carry?" is a maintainer's
question, and README.md is the player-facing page. This repo's README was
rewritten for players on 2026-08-05; the library inventory was the last
contributor-facing thing left in it, and the gate was reading its input
out of a file whose job is to stop mentioning it.
README.md
- `## Credits and libraries` becomes a plain `## Credits`. The LibKa0s
sentence and the Ace3 / LibStub / CallbackHandler / LibSharedMedia
inventory are gone; the warcraft.wiki.gg artwork attribution stays,
because that credit is genuinely external and CC BY-SA 4.0 requires it.
- The Standard badge loses its link wrapper. The badge markdown itself
is unchanged.
CLAUDE.md gains the provenance line naming v1.8.1, in the paragraph that
already describes what this addon vendors and where, with a note that it
is an input to the gate rather than a remark.
tests/_kit/ re-vendored whole-folder from the v1.8.1 tag; `diff -r
--strip-trailing-cr` against the tag is empty. `libs/LibKa0s/` is NOT
recopied — no shipped library file moved in v1.8.1, so the existing copy
is already byte-identical, and both payloads are compared against the tag
the line now names.
docs/test-cases.md regenerated: kit revision 9 renamed one case to
"libs/LibKa0s is the LibKa0s release CLAUDE.md says this addon bundles".
A rename is not a count change — still 713/713, so the README [tests]
badge does not move.
docs/testing.md's fifth vendor check and tests/test_vendor_sync.lua's
header prose now name CLAUDE.md. docs/pending/LEDGER.md ▸ LIBKA0S-32
records that its "README.md names the bundled version" line is superseded.
Green: `lua tests/run.lua` 713 passed, 0 failed, 0 skipped, 713 total;
`luacheck .` 0 warnings / 0 errors in 25 files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y4FjrPatWaye1gFsfS4vpb
commit 7287642fb4410584b343ea792f64e22afb1e82c6
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 17:23:43 2026 +0530
Pin line endings with the canonical .gitattributes (line-endings-§2)
This is the commit LIBKA0S-09 was deferred for. PanelMaster carried a
five-line `.gitattributes` holding the `*.sh` carve-out and nothing else —
the exception with no rule — while its tracked text sat LF against the
collection's evident intent. The canonical client-bound body replaces it.
`git add --renormalize .` moved 75 blobs to LF storage and the working-tree
stragglers were re-materialized from the index. Two source-scanning cases in
tests/test_panel.lua anchored on "\nend\n" and "\n\n" and went red on the
flip; both slurp helpers now CR-strip, the same normalization
tests/_kit/vendor_sync.lua already does. test_vendor_sync.lua's header no
longer claims the tree is unpinned, and the ledger row closes.
713 tests pass unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A9nTGYaHsqCEf6obganps6
commit 16fea6d6213d65b63026f99588f441f096425bc2
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 16:00:06 2026 +0530
docs: adopt the documentation tier model (standard v2.23.0)
ARCHITECTURE.md was 1071 lines — the largest hub in the collection, and the
case documentation-§3's new spill rule was written against. Nothing required
the doc set to split, so every subject landed in one file: the artwork
pipeline, the Sunn adapter, the frame ladder, the widget workarounds and the
data model all lived there with no landmark between them.
The hub goes 1071 -> 180 lines, and no mandated section now exceeds 60. The
content is carved out, not rewritten.
Created (Tier 1): scope, module-map, schema, settings-panel, data-flow,
common-tasks. Created (Tier 2, triggers fired): slash-dispatch (17 verbs,
threshold 8), profiles (AceDB profiles are user-visible — the Profiles page),
debug (D:Diagnose and NS.DebugBuild are the addon's own beyond the library
console). Created (Tier 3): rendering, localization, media, alongside the
existing artwork-spec.
Four Tier 2 docs are recorded as "Not applicable" rows in the new
`## Documentation map` with the trigger that would re-arm each. midnight-quirks
is the one worth stating: core/LSMPatch.lua fixes a vendored WIDGET, not a
client behavior, so it is not a client-version workaround and belongs in
module-map.md — filing it as a quirk would misdescribe what it is.
Verified: 713 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 2957283e1d1992ab8db97b0470862ac22805bea9
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 01:11:42 2026 +0530
docs: the four file:line citations this changeset moved now land on the line they name
Doc-content sync only — no document created, moved or restructured, and no
generated doc touched. Four citations drifted off their site during the
remediation batch; each is re-derived from the current tree rather than nudged
by eye.
The OnUpdate install site. CLAUDE.md, docs/ARCHITECTURE.md ▸ Documented
deviations and docs/performance.md (prose plus the hot-path table) all name
`modules/Canvas.lua:565` as the line that installs the shared 10Hz mouseover
driver — the whole basis for this addon NOT qualifying for performance-§12's
no-combat-path exemption. 565 is now `f:SetAlpha(spec.alpha)`, a line inside
`updateMouseover`; the `mouseoverDriver:SetScript("OnUpdate", ...)` the argument
actually rests on is at 577. The claim is unchanged and stays true; only the
pointer moves. A reader checking the deviation register against the code is
exactly the reader this citation exists for, and 565 sent them to the wrong
statement.
DEPENDENCIES.md's two toolchain rows. M3-01/M3-02 emptied
tests/test_vendor_sync.lua down to registration — 41 lines, the ~140
hand-copied ones having moved into the vendored tests/_kit/vendor_sync.lua — so
the `git` row's `tests/test_vendor_sync.lua:53` and its `:52` io.popen guard,
and the POSIX-shell row's `tests/test_vendor_sync.lua:89`, all pointed past the
end of the file. The shell row's other half, `tests/test_harness.lua:30`,
described an `ls tests/test_*.lua` that is now the kit's `listDir` behind
`T.assertSuiteInventory`. Both rows now cite `tests/_kit/vendor_sync.lua:140`
/ `:139` / `:96` and `tests/_kit/framework.lua:202`, with the LuaFileSystem
rationale pointed at `framework.lua:197-201` where it now lives. The git row
says out loud that the shell-out moved into the kit while the requirement did
not: a dependency whose only evidence sits in a vendored file is still a
dependency, and a reader who greps the named test, finds three lines of
`dofile`, and concludes git is no longer needed would be wrong.
Verified in sync and left alone: docs/test-cases.md is byte-identical to
`lua5.1 tests/run.lua --list`; the README test badge reads 713/713 and the
Totals table says 713; NS.COMMANDS carries 18 entries and the README command
table carries the same 18, both directions; the three bus messages, their
senders and their consumers match modules/Registry.lua and settings/Schema.lua;
139 exported functions, none without a caller.
Verification: 713 passed, 0 failed, 0 skipped; luacheck 0 warnings / 0 errors in
25 files; lizard 1352 functions, avg CCN 2.0, 0 above threshold; all four
vendor-drift diffs against ../LibKa0s empty, content and byte.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KQxehYAMgZtvAJDCJxXYRs
commit 2957283e1d1992ab8db97b0470862ac22805bea9
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Thu Aug 6 01:11:42 2026 +0530
docs: the four file:line citations this changeset moved now land on the line they name
Doc-content sync only — no document created, moved or restructured, and no
generated doc touched. Four citations drifted off their site during the
remediation batch; each is re-derived from the current tree rather than nudged
by eye.
The OnUpdate install site. CLAUDE.md, docs/ARCHITECTURE.md ▸ Documented
deviations and docs/performance.md (prose plus the hot-path table) all name
`modules/Canvas.lua:565` as the line that installs the shared 10Hz mouseover
driver — the whole basis for this addon NOT qualifying for performance-§12's
no-combat-path exemption. 565 is now `f:SetAlpha(spec.alpha)`, a line inside
`updateMouseover`; the `mouseoverDriver:SetScript("OnUpdate", ...)` the argument
actually rests on is at 577. The claim is unchanged and stays true; only the
pointer moves. A reader checking the deviation register against the code is
exactly the reader this citation exists for, and 565 sent them to the wrong
statement.
DEPENDENCIES.md's two toolchain rows. M3-01/M3-02 emptied
tests/test_vendor_sync.lua down to registration — 41 lines, the ~140
hand-copied ones having moved into the vendored tests/_kit/vendor_sync.lua — so
the `git` row's `tests/test_vendor_sync.lua:53` and its `:52` io.popen guard,
and the POSIX-shell row's `tests/test_vendor_sync.lua:89`, all pointed past the
end of the file. The shell row's other half, `tests/test_harness.lua:30`,
described an `ls tests/test_*.lua` that is now the kit's `listDir` behind
`T.assertSuiteInventory`. Both rows now cite `tests/_kit/vendor_sync.lua:140`
/ `:139` / `:96` and `tests/_kit/framework.lua:202`, with the LuaFileSystem
rationale pointed at `framework.lua:197-201` where it now lives. The git row
says out loud that the shell-out moved into the kit while the requirement did
not: a dependency whose only evidence sits in a vendored file is still a
dependency, and a reader who greps the named test, finds three lines of
`dofile`, and concludes git is no longer needed would be wrong.
Verified in sync and left alone: docs/test-cases.md is byte-identical to
`lua5.1 tests/run.lua --list`; the README test badge reads 713/713 and the
Totals table says 713; NS.COMMANDS carries 18 entries and the README command
table carries the same 18, both directions; the three bus messages, their
senders and their consumers match modules/Registry.lua and settings/Schema.lua;
139 exported functions, none without a caller.
Verification: 713 passed, 0 failed, 0 skipped; luacheck 0 warnings / 0 errors in
25 files; lizard 1352 functions, avg CCN 2.0, 0 above threshold; all four
vendor-drift diffs against ../LibKa0s empty, content and byte.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KQxehYAMgZtvAJDCJxXYRs
commit 926480e697ad3c675a115fe53a75c48dc15a9c7c
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 23:14:16 2026 +0530
test(db): observe that the v1 ladder RUNS, not the shape of defaults.global [fixes M4-22 test]
"the schema stamp is WRITTEN at init, never served as a default" asserted
`NS.defaults.global.schemaVersion == nil` — the key the same commit deleted from
defaults/Global.lua — and the same precondition was repeated inside the
migration case. core/Database.lua:94 floors the stamp with
`g.schemaVersion = g.schemaVersion or 1`, so seeding 1 in defaults is
behaviorally identical to omitting it: the `<` gate still opens, the v1 -> v2
body still runs, no user can tell the difference — yet both assertions failed.
Its companion, `NS.db.global.schemaVersion == NS.SCHEMA_VERSION`, said nothing
about the regression it was written for.
Both defaults-shape assertions are gone. The migration case at
tests/test_database.lua:21 already carries the real guarantee — copy
NS.defaults.global into a fresh global, hand RunMigrations a profile with an
unstamped panel, assert
`NS.db.profile.panels[1].frameName == "PanelMaster_Panel_Old_Panel"` — verified
before deleting. The first case is retargeted to the one thing left that only it
observes: NS:InitDB invokes the runner (core/Database.lua:19), so the live DB
comes back stamped.
Production mutations used to prove them red:
* defaults/Global.lua seeds `schemaVersion = NS.SCHEMA_VERSION` (the real
regression): migration case RED — "the v1 -> v2 body did not run for a v1
SavedVariables file (expected PanelMaster_Panel_Old_Panel, got nil)", 12
failures in all.
* defaults/Global.lua seeds `schemaVersion = 1` (correct for the user, and
what the deleted literals reddened): 713 passed, 0 failed.
* `NS:RunMigrations()` commented out of NS:InitDB: retargeted case RED — "the
live DB is unstamped — InitDB did not run the migration runner (expected 2,
got nil)".
Reverted: 713 passed, 0 failed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 1548383632844582250d5c826b0e2d457586d14f
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 23:11:51 2026 +0530
test(harness): observe the majors the runner actually loaded, not run.lua's own bytes [fixes M3-03 test]
"the runner derives the vendored library's load list from LibKa0s.xml" grepped
tests/run.lua for the literal `Loader.xmlFiles("libs/LibKa0s/LibKa0s.xml")` —
the exact bytes the same commit added — and then compared one parse of the XML
against another parse of the same XML. Neither half observes what the harness
LOADED. Hoisting the path to a local while loading precisely the right eight
files reddens it with the environment perfect; conversely, loading six of the
eight files (PM-A-10, which survived a year of green runs) leaves it green.
The literal grep is relaxed to a real lint — the runner must go through
Loader.xmlFiles and must not type a `libs/LibKa0s/*.lua` path — and a new case,
"Harness: every module LibKa0s.xml declares is live in the loaded environment",
reads the already-built environment instead: LibKa0s-Core-1.0 and
LibKa0s-Perf-1.0 are registered, Perf.lua published its own minor, PerfPanel.lua
attached (it has no major of its own, so it is observed through
lib.MODULES.PerfPanel), and every major found by scanning the XML-declared files
resolves through LibStub.
Production mutation used to prove it red: tests/run.lua's load call rewritten to
feed Loader.loadAll the first six of the eight XML entries — the original defect
— so Perf.lua and PerfPanel.lua never load. New case RED
("LibKa0s-Perf-1.0 did not register — Perf.lua was not loaded"); the old
parse-versus-parse case and the lint both stayed GREEN, which is the point.
Reverted: 713 passed, 0 failed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 429ed3ee007776fac4b1bd9e5db7070e352555b1
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 15:12:24 2026 +0530
docs(arch): re-grade the pre-formatting sites under the scoped events-frames-taint-§8 [M4-27]
DISPOSITION, NOT A FIX. M1-STD-11 was resolved as option (a): §8's
pre-formatting MUST NOT is now scoped to call sites whose arguments are, or
derive from, a return of a named combat-protected API, and everything outside
that trigger set is a SHOULD whose stated reason is drift, not secrets. No call
site is converted here and no cited code is touched.
Checked whether any of this repo's ~25 sites sits on a named API: none does. A
whole-repo sweep of core/ modules/ settings/ defaults/ locales/ for the trigger
set — UnitGetTotalAbsorbs, UnitGetTotalHealAbsorbs, UnitGetIncomingHeals,
UnitHealth, UnitHealthMax, UnitThreatSituation, UnitDetailedThreatSituation, the
aura amount/points fields and UNIT_AURA — returns nothing, and the only
unit/client APIs PanelMaster calls at all are UnitClass and
C_AddOns.GetAddOnMetadata. So there is no real fix hiding in this half: every
site formats values the addon owns.
Both of §8's unrelaxed points were verified rather than assumed. No file calls
the global print() for user-facing output; every one takes
`local print = NS.Print`. The seam's guarantee is unconditional:
core/CoreSetup.lua publishes the library's IsConcatSafe / SafeToString and
builds the printer from lib:New, so every argument is stringified through the
table.concat probe whatever a call site hands it.
Records the re-grade as a register row in docs/ARCHITECTURE.md ▸ Documented
deviations with its re-check trigger — the first line whose arguments derive
from a trigger-set API, which converts as a MUST, plus a re-check when §8's
list grows upstream.
Closes PM-A-08.
Verification: luacheck 0 warnings / 0 errors in 25 files; 712 passed, 0 failed;
lizard Warning cnt 0; git status shows only docs/ARCHITECTURE.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 829cb029b991467d6440c37963b8305d9db4bb81
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 15:10:59 2026 +0530
fix(debug): the degraded console acks in its own words, not the library's line format [M4-26]
debug-logging-§7 requires the ack — logging is a session flag the ADDON owns,
so a degraded install that flipped it silently would look like the flag was
stuck off. But `core/DebugLogSetup.lua`'s stub produced that ack by
hand-copying the library's own line: the ACK format "debug logging %s" from
`libs/LibKa0s/DebugLog.lua`'s STRINGS table and both state hexes it composes at
SetEnabled (ON green 40ff40, OFF red ff4040).
That is the transcription this file was created to end (anti-pattern #47), and
it is a copy that cannot be kept true: the stub only runs when the library is
ABSENT, so nothing on that path can read lib.STRINGS, and nothing would notice
the day the library restyles its own ack. The stub now states the same fact in
its own plain words — "debug logging is on" / "debug logging is off" — and
explainOnce still says why the window is not there to match. The live path is
untouched, so a normal install's ack is byte-identical to before.
Tests: "Degraded install: /pm debug on|off still flips the flag and
acknowledges" now asserts an ack is actually emitted on each edge, that the two
edges differ, and that neither line carries either of the library's state hexes.
Deliberate violation: restoring the hand-copied ack reddened that case
(711 passed, 1 failed); reverted, green restored.
Closes PM-R-03.
Verification: luacheck 0 warnings / 0 errors in 25 files; 712 passed, 0 failed;
lizard Warning cnt 0; `grep -nE 'cff40ff40|cffff4040|<ts>' core/DebugLogSetup.lua`
returns nothing (exit 1). Test count unchanged, so docs/test-cases.md is
unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit febf1084f455756ff2097f1b6a8fd859edfdeeb4
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 15:08:33 2026 +0530
fix(db): the schema stamp stops being an AceDB default, so the migration bodies are reachable [M4-22]
Three PanelMaster one-offs.
1. savedvariables-§1 (PM-R-04). `defaults/Global.lua` seeded
`schemaVersion = NS.SCHEMA_VERSION` as an AceDB default. A default is
served for any key the SavedVariables file does not carry, and
NS:RunMigrations only WRITES the field from inside its own `<` gate — so
`db.global.schemaVersion` read as current on every account that had never
been stamped, i.e. every account, the gate never opened, and the v1 -> v2
frame-name body was dead code for every real install. The seed is removed;
the field is written, never defaulted.
`core/Database.lua`'s profile-callback `NS:RunMigrations()` is removed
rather than kept: the stamp is ACCOUNT-WIDE and already written by InitDB
before any profile can be switched, so the call could only ever be a no-op
dressed as a safety net. The repair an incoming profile actually needs is
per-RECORD and is R.Sanitize's, reached through Registry:ReloadProfile,
which performs the same frame-name backfill.
2. PM-R-06. `modules/Canvas.lua` reaches every sibling directly and unguarded
in all thirteen places; the review's premise that the majority guard first
was false. The convention is now recorded in the file header, together with
why the two `NS.Unlock` overlay reaches are method-presence checks rather
than load-order guards. No call site changed.
3. library-stack-§4 (PM-R-12). `settings/OptionsSetup.lua`'s `onAceGUI`
stashed `NS.AceGUI`, which had no reader anywhere and whose comment claimed
an effect it did not have. Removed: the library already publishes the handle
on the instance as `O.AceGUI`. The three `LibStub("AceGUI-3.0", true)` calls
each resolve the one singleton once, and none of them can be served from a
build-time seam — Panel.lua and PanelEditor.lua run at file scope before the
instance exists, LSMPatch.lua inside a one-shot PLAYER_LOGIN fixup. Recorded
in place.
Tests: adds "Database.RunMigrations: a v1 SavedVariables file reaches the
v1 -> v2 body", which drives the runner against a global built from the shipped
defaults and a profile of unstamped records. Rewrites the two cases the fix
made false or vacuous. Deliberate violations: re-seeding `schemaVersion` in
defaults/Global.lua reddened both new database cases (710 passed, 2 failed);
disabling the Sanitize frame-name backfill reddened the profiles case plus the
Registry.FrameName case (710 passed, 2 failed). Both reverted, green restored.
Closes PM-R-04, PM-R-06, PM-R-12.
Verification: luacheck 0 warnings / 0 errors in 25 files; 712 passed, 0 failed;
lizard Warning cnt 0; docs/test-cases.md regenerated and CR-normalized diff
against `--list` empty; README test badge 711 -> 712.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 81269137ad48a6ff85b6cf2e41ac05f55d548407
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 15:01:11 2026 +0530
docs(arch): record the English-only decision as a register row [M4-17]
`localization-§3` names two terminal compliant states for the routing
SHOULD: strings routed through `NS.L`, or an English-only decision in the
`## Documented deviations` register with a re-check trigger. PanelMaster
is in the second state — the decision was reasoned at `locales/enUS.lua:8-14`
but never ratified in the register, so every audit cycle re-filed it as an
open localization row.
Adds the `localization-§1` row: what differs, why 0.1.0 ships English-only,
that both MUSTs (the exported seam with the key-returning fallback at
`locales/enUS.lua:6`, and `enUS.lua` shipping) are met unconditionally, and
the re-check trigger — the first non-English locale file added to `locales/`.
Carries forward the file's own note that panel names and the stored
`point` / `strata` tokens must never route through `NS.L`.
Closes PM-A-17.
Verification: luacheck 0 warnings / 0 errors in 25 files; 711 passed,
0 failed; lizard Warning cnt 0; git status shows only docs/ARCHITECTURE.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit fb626b3a2c92a4a2139ae599ae1414cc99b1df80
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 14:58:31 2026 +0530
fix(schema): boot validation reports a typo'd path whether or not the row has a default [M4-05]
settings/Schema.lua's S:Register counted a path as unresolved only when
`S:ReadPath(p, row.path) == nil AND row.default == nil`. All nine rows in
S.Schema declare a default, so the second conjunct was never true and the
architecture-§5 boot check was structurally incapable of reporting anything —
0 for a typo'd path exactly as for a correct one. tests/test_schema.lua's
`assertEqual(S:Register(), 0)` was therefore asserting a constant.
The two facts are independent. `row.default` is what the widget shows and what
Defaults restores; resolving against NS.defaults.profile is what says the
setting has somewhere to be written. A row with a good default and a typo'd
path is the worst case, not the exempt one — the panel renders, the widget
reads its default, the write lands on a key nothing reads, and nothing says
so. Dropped the conjunct, so a path that does not resolve is reported full
stop, and corrected the printed line, which said "missing default" for what is
actually an unresolvable path. Session-only rows stay the one exemption: they
route through their own get/set and have no db-backed home by design.
Extended the existing case at tests/test_schema.lua:7-10 rather than adding
one, so no case name moves. It now injects three probe rows and restores the
schema after each: a typo'd path WITH a default and a typo'd path with none
must each return 1 — two probes because the bug's whole shape was that only
the second was reachable — and a session-only row with an unresolvable path
must still return 0. A closing assertEqual(S:Register(), 0) pins that the
probes are off again for every case below.
Closes PM-R-02 (M4-05, PanelMaster half; spec C19+C24+C28, "Boot validation
can fire").
Verification: luacheck . 0 warnings / 0 errors in 25 files; lua5.1
tests/run.lua 711 passed, 0 failed; lizard Warning cnt 0. Case count unchanged
at 711 and `tests/run.lua --list` diffs clean against docs/test-cases.md
(CR-normalized both sides). Red/green: restoring the `and row.default == nil`
conjunct failed the case with "typo'd path, WITH a default was not reported
(expected 1, got 0)" (710 passed, 1 failed); reverting returned 711/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 5f482188be96aafdab63baf34c3517d778b299ec
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 14:56:20 2026 +0530
refactor(bus): Canvas subscribes through NS.Schema.MSG_SETTINGS, not the literal [M4-04]
modules/Canvas.lua:743 registered for "Ka0s_PanelMaster_SettingsChanged" as a
raw string while the two lines above it used NS.Registry.MSG_PANELS and
NS.Registry.MSG_PANEL. settings/Schema.lua:21 has published S.MSG_SETTINGS all
along and NS.Schema is the same table, so the constant was there to be used —
a receiver quietly holding its own copy of a sender's name is exactly what
architecture-§4's one-declaration rule is for.
Switched to NS.Schema.MSG_SETTINGS and noted why the TOC order does not bear
on it: settings/Schema.lua loads after modules/Canvas.lua, but Enable() runs
at OnEnable, so all three names resolve at call time.
Every `Ka0s_PanelMaster_*` occurrence left in core/, modules/ and settings/ is
now either the single declaration site (modules/Registry.lua:19-20,
settings/Schema.lua:20) or prose in a comment; no code path spells a message
name twice. docs/ARCHITECTURE.md:298-300's bus catalog already names
settings/Schema.lua as the sender and Canvas as the receiver, and still
matches.
Closes PM-R-11 (M4-04, PanelMaster half; spec architecture-§4).
Verification: luacheck . 0 warnings / 0 errors in 25 files; lua5.1
tests/run.lua 711 passed, 0 failed; lizard Warning cnt 0. Red/green: putting a
literal back with one character dropped ("...SettingsChangd") failed 5 cases
including 'Canvas: OnEnable subscribes the renderer to the bus' with "the
renderer is not listening for Ka0s_PanelMaster_SettingsChanged" (706 passed, 5
failed); reverting returned 711/0. That spread is the point — a literal that
drifts from its sender silently deafens the renderer, and the constant makes
the drift unrepresentable.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 3fc4ec9f8788c71f7529e40c0ae01a2c3f687a84
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 14:54:44 2026 +0530
docs(readme): ### Settings panel leads with the Tab | Covers table [M4-08]
documentation-§1 item 7 requires `### Settings panel` to open with a
**Tab | Covers** table, one row per settings subcategory, and MAY follow it
with per-panel prose. This README opened straight into a
`| Setting | What it does |` table of the General page's nine rows, so the
mandated per-tab overview was absent and a reader could not tell what the
other tabs held without reading to the end of the section.
Added the table with one row per registered subcategory — the landing page
plus General, Panels and Profiles, exactly the four pages
settings/Panel.lua:348-476 registers (SetBuildMain, then three
O.RegisterOptionsPage calls). The existing Setting table stays as the
permitted follow-on prose, now labelled as General's. The Profiles and Panels
paragraphs lose the sentences the table now states and keep only what it does
not (the shared Default profile, the immediate redraw, the name-then-Enter
flow, the one-panel-at-a-time layout).
The General table's nine rows are the nine schema rows at
settings/Schema.lua:27-98, one for one.
Closes PM-A-12 (M4-08, PanelMaster half; spec C5).
Verification: luacheck . 0 warnings / 0 errors in 25 files; lua5.1
tests/run.lua 711 passed, 0 failed; lizard Warning cnt 0. README-only, no
case count moves, docs/test-cases.md untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 5d820eca4021d1b8a4bafc0d096357d74e661abe
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 14:52:17 2026 +0530
docs(comments): the printer's upvalue list names files that exist, and the CRLF claim is true [M4-10]
Two stale comments, both cited in the plan, plus the count claim they carry.
core/CoreSetup.lua's TOC-ordering note listed six files taking the printer as
a `local print = NS.Print` file-scope upvalue and named modules/DebugLog.lua
among them. That file has not existed since the LibKa0s-DebugLog-1.0 adoption
(LIBKA0S-10) deleted it; its replacement, core/DebugLogSetup.lua, deliberately
does NOT take an upvalue — it hands the descriptor a
`function(line) NS.Print(line) end` closure precisely so it is immune to the
AceConsole reclaim, which is why it carries no ordering constraint. The real
list is five: modules/Unlock.lua, settings/Schema.lua, settings/Slash.lua,
settings/PanelEditor.lua, settings/Panel.lua. Removed the phantom entry,
corrected six -> five, and said why DebugLogSetup is absent so the omission
does not read as a bug next time.
The same "six" appeared verbatim in three more places, all restating this one
claim: core/Util.lua's MOVED note, tests/test_libka0s.lua's Core-seam header,
and docs/ARCHITECTURE.md's load-order bullet — which said "six" while listing
exactly five. All corrected; leaving them would have left the finding half
closed against a self-contradicting sentence.
tests/test_vendor_sync.lua's normalization note said the working tree is CRLF
"because `.gitattributes` pins `* text=auto eol=crlf`". No such pin exists —
.gitattributes carries `*.sh text eol=lf` and nothing else, and the tree is in
fact LF here. The CR strip is still correct, for the real reason: the blob is
always LF while the working tree follows the local checkout, and git's
core.autocrlf is on by default in a Windows install. Rewrote the paragraph to
state that.
Closes PM-R-09, PM-R-10 (M4-10, PanelMaster half; spec C3).
Verification: every path now named in these comments resolves (ls
core/DebugLogSetup.lua, tests/_kit/vendor_sync.lua; the five upvalue sites are
the exact output of `grep -rn "local print = NS.Print" core modules settings`;
README.md:378 is the provenance line the kit's default pattern matches).
luacheck . 0 warnings / 0 errors in 25 files; lua5.1 tests/run.lua 711 passed,
0 failed; lizard Warning cnt 0. Comment-only — no case count moves, so
docs/test-cases.md is untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 1d207e24319a82b6161a7a9da52de0d22f92ee3d
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 14:49:31 2026 +0530
refactor(core): the Core seam publishes exactly what the addon calls [M4-07]
core/CoreSetup.lua published NS.Format = printer.Format, the only occurrence
of the name in the repo — no production caller, no test caller, and the
degraded branch never answered it. That made it the one asymmetric member of
a seam whose own comment states its member set is exactly what the addon
calls, and it forced the Core parity case to carry a permanent `ignore` entry
for a member written for nobody.
Dropped the assignment and replaced it with a note saying why Format is not
taken. tests/test_libka0s.lua's PUBLISHED list loses "Format" and the parity
ignore list loses its Format clause, so the case now compares the full live
surface against the full degraded surface with no carve-out but NS.Core
itself.
Closes PM-R-08 (M4-07, PanelMaster half; spec C7).
Verification: luacheck . 0 warnings / 0 errors in 25 files; lua5.1
tests/run.lua 711 passed, 0 failed (unchanged — no case added or removed);
lizard Warning cnt 0. Red/green: re-adding NS.Format = printer.Format with
"Format" back in PUBLISHED failed the Core parity case with 'Core stub: the
degraded stub diverges from the live surface in 1 place(s) - Format is
missing (live: function)' (710 passed, 1 failed); reverting returned 711/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 856ced2b802835777467866cd7e0e9b4478bcd88
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 13:15:06 2026 +0530
docs(perf): commit the whole-repo sweep — PanelMaster does NOT qualify for performance-§12 [M3-09 BLOCKED]
M3-09 asked this repo to claim the performance-§12 no-combat-path exemption.
The sweep the item itself requires as evidence disproves the claim, so the
exemption is NOT claimed and no register row asserting it is written.
Criterion (a) requires "no OnUpdate handler, no repeating ticker, and no event
handler doing more than occasional work while the player is in combat".
modules/Canvas.lua:565 installs a shared 10Hz OnUpdate driver — updateMouseover,
one NS.Compat.MouseIsOver + SetAlpha per mouseover-tracked panel — the moment any
panel has "Show on mouseover only" ticked (mouseover defaults to false at
core/Constants.lua:306, and the driver is never destroyed once created). There is
no InCombatLockdown gate and none is wanted: the fade is meant to keep working
while the player is busy. Criterion (b) fails with it — a bucket around that loop
would not read 0.000 by construction — and (c) does not apply, because this addon
records nothing suspend could suppress.
Neither 03_SPEC.md's C1 nor 04_EXECUTION_PLAN.md's M3-09 mentions this OnUpdate;
PanelMaster is listed among the five exempt repos on the assumption that it has
no combat path. The measurement disagrees.
What this commit does deliver, all of it honest:
- docs/performance.md — required unconditionally by documentation-§3 and absent
until now (PM-A-05). It carries the committed sweep in full: 1 OnUpdate hit,
4 RegisterEvent hits with the per-event work named, 0 C_Timer, 0 repeating
timers; the per-hit "runs in combat?" column; and why (a), (b) and (c) each
fail. Re-running the sweep is how anyone checks whether the answer changed.
- The performance-§12 row added to ## Documented deviations in the previous
commit (M3-08) is REMOVED. It asserted an exemption this evidence contradicts,
and a false row in the register is worse than no row. The section now states
explicitly why the Perf decline is deliberately not a row: it is reasoned only
at pending/LEDGER.md ▸ PLAN-06, which per documentation-§3 means it is NOT
ratified, so performance-§1 is a genuinely open MUST here and an audit is
right to keep filing it.
- CLAUDE.md says the same rather than pointing at a register row that no longer
exists.
NOT delivered, and blocked on an owner decision: the register row, and therefore
PM-A-01…PM-A-06 and PM-A-09. Wiring performance-§1's harness and bracketing
updateMouseover, or changing the rule, are both decisions above this milestone.
The other three M3-08 rows (documentation-§1 item 6, toc-file-§1,
documentation-§4) are unaffected and stand.
Verification: luacheck 0/0 in 25 files; 711 passed / 0 failed / 0 skipped;
lizard Warning cnt 0. Documentation-only — no code changed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 8a26740de6ee2c8b70dc77c2071d1b42e8268055
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 13:11:31 2026 +0530
docs(arch): add the ## Documented deviations register and move this repo's records into it [M3-08]
documentation-§3's ninth mandated ARCHITECTURE.md section, with the exact row
shape | Rule | What differs | Why | Decided | Re-check trigger |. Rule is a
filename-§N reference rather than a paraphrase, because a paraphrase cannot be
checked against the rule it claims to deviate from, and every row carries a
trigger — a row without one is a permanent opt-out wearing a table's clothes.
Four rows, all moved in from records that already existed somewhere else:
- `performance-§12` — the Perf decline, from CLAUDE.md:36 ("Perf is declined"),
reasoned at pending/LEDGER.md ▸ PLAN-06. M3-09 completes this row: it adds the
committed whole-repo sweep, names which of §12's (b)/(c) applies, and writes
docs/performance.md. The row is written here so there is one row rather than
two for one decision.
- `documentation-§1` (item 6) — the README ## Screenshots placeholder (PM-A-18),
audit PM-014, carried from D-004, ledger PLAN-05 / ISS-01, GitHub #1.
- `toc-file-§1` — no X-Curse-Project-ID and a four-badge README row (PM-A-19),
audit PM-015, carried from D-001/D-002, ledger PLAN-03 / PLAN-04. A project id
does not exist until the first upload, so the field cannot hold a true value.
- `documentation-§4` — pending work lives in docs/pending/LEDGER.md rather than
a root TODO.md (PM-A-20), audit PM-016. §4 forbids a TODO.md once released; it
does not mandate one before.
CLAUDE.md now points at the register rather than being a second home for the
Perf decision — a ledger entry declining a rule with no register row is itself
the deviation.
Closes PM-A-18, PM-A-19, PM-A-20 (PanelMaster's half of M3-08).
Verification: luacheck 0/0 in 25 files; 711 passed / 0 failed / 0 skipped;
lizard Warning cnt 0. Documentation-only.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 51f70428e4c61c70f13efaf4c1f556dcb58faf95
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 13:09:09 2026 +0530
docs(refs): the last retired §N.M reference resolves by filename [M3-07]
The retired global section notation, swept across code, tests and config with
libs/, tests/_kit/, docs/audits/, docs/reviews/ and docs/automated-tests/
excluded — all three docs/ paths are frozen evidence.
PanelMaster carried exactly ONE site, and it is code:
settings/OptionsSetup.lua — "Ka0s standard §3.4:" on the onAceGUI seam
§3.4 does not resolve to anything in the current standard. The rule it means is
`options-ui-§1` (*The options library*), which is where the descriptor's
optional-seam table lists `onAceGUI` and where the library's "resolve AceGUI
through LibStub once at panel-build time" behaviour is written. The comment now
cites that and says what the seam is for, rather than pointing at a number.
Repo has no `standalone-windows-§N` or `packaging-§N` reference to convert to a
bare filename, and no malformed or out-of-range reference: every one of the 40
distinct `filename-§N` references in this repo range-checks clean against the
current section counts (architecture 7, automated-tests 7, debug-logging 12,
documentation 7, events-frames-taint 8, layout 3, library-stack 7,
localization 5, options-ui 11, performance 12, savedvariables 5,
slash-commands 6, testing 13).
Acceptance: grep -rEn '§[0-9]+\.[0-9]' with all five exclusions returns 0.
Comment-only edit; no behaviour changed.
Verification: luacheck 0/0 in 25 files; 711 passed / 0 failed / 0 skipped;
lizard Warning cnt 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 88cdf31fbc2eedcfb5e000b7fc23cae2cbaa82e1
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 13:07:52 2026 +0530
docs(gates): every gate statement names its checkpoint [M3-06]
Sweep 3f. "perf and complexity never fail a run" is true and, standing alone,
misleading: it reads as "these two gate nothing", which automated-tests-§3's
release gate contradicts. There are TWO checkpoints — the commit and the tag —
and those two suites answer differently at each, so a single "Gates?" column
could only ever describe one of them.
- docs/testing.md: the Gates? table splits into "Gates the commit?" and "Gates
the tag?"; the paragraph below it states the release gate (all four suites at
pass plus zero functions above CCN 15, evaluated by /wow-addon:bump-version
from the run's manifest.json, never by the runner, whose exit code is
unchanged because the same script is the commit gate) and that a skip is NOT
EVALUATED rather than passed. "Commits are gated on lint + tests only" becomes
"The commit gate is lint + tests only".
- docs/automated-tests/README.md § "What gates, and what only records": the same
two-column split and the same release-gate paragraph, plus a note that
suites.<name>.gates is descriptive and nothing reads it.
- Both now record that this addon ships no tests/perf.lua, which is the one
sanctioned skip that still passes the release gate and which
automated-tests-§3 requires the release notes to state.
ONE DEVIATION FROM THE ITEM AS WRITTEN, flagged rather than hidden. M3-06 says
"the RESULTS.md lead-in comes from the re-vendored runner, not from this sweep."
The v8 runner only writes that lead-in when RESULTS.md does not yet exist — it
will not rewrite a file whose rows are the trend line (testkit version-8-docs.md,
"The emitted RESULTS.md lead-in names the checkpoint"). So a fresh run in this
repo would NOT have replaced the old paragraph, and M3-06's own acceptance grep
could never have come back clean. The lead-in is therefore replaced by hand here,
byte-for-byte with what run-automated-tests.sh:425-433 emits. Only the lead-in
paragraph changed: no dated bundle was touched and no row in the trend table was
altered or re-worded.
Acceptance: grep -rn "never fail a run" docs CLAUDE.md, excluding the frozen
docs/audits/, docs/reviews/ and dated docs/automated-tests/ bundles, returns
three hits, all checkpoint-qualified (docs/testing.md:126,
docs/automated-tests/RESULTS.md:10, docs/automated-tests/README.md:31).
Closes PM-A-13 (PanelMaster's half of M3-06).
Verification: luacheck 0/0 in 25 files; 711 passed / 0 failed / 0 skipped;
lizard Warning cnt 0. Documentation-only — no code or test changed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 6f88fe7ac33a713783c1d028d88b93b64fa052b4
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 13:05:17 2026 +0530
chore(tests): record the vendored runner as executable in the git index [M3-05]
tests/_kit/run-automated-tests.sh was mode 100644 in the index. Nothing could
see it: this repo sets core.fileMode=false, so git never complains, and the tree
is DrvFs, so `ls -l` reports rwxrwxrwx for a file the index calls
non-executable. `ls -l` is not evidence here — `git ls-files -s` is.
`git update-index --chmod=+x` (not `chmod +x`, which the index would ignore).
The bit does not travel with `cp`, so it survives no re-vendor and is not in the
file's bytes, which is why the byte-identity vendor-sync case cannot see it;
testkit v8's tests/test_kitsync.lua is the library-side gate that makes
forgetting loud rather than surviving three audit cycles.
Verified: `git ls-files -s tests/_kit/run-automated-tests.sh` -> 100755;
`tests/_kit/run-automated-tests.sh --help` runs with no `bash` prefix.
Closes PM-A-11 (PanelMaster's half of M3-05).
Verification: luacheck 0/0 in 25 files; 711 passed / 0 failed / 0 skipped;
lizard Warning cnt 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 80e2e2b49785d92cd7a4e3ee63282e783fde5a0c
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 13:04:39 2026 +0530
test(degraded): assert every LibKa0s stub surface as a set, and /pm config answers every time [M3-04]
Four adopted seams — Core, DebugLog, Slash, Options — each carry an
`if not lib then` branch whose member set is what a degraded install runs on.
Each now has one Kit.assertSurfaceParity case (testkit v8), with the producing
grep named in the case comment and every `ignore` entry justified beside it as
data, so an intentional live-only member and a bug no longer read the same.
The degraded arm of every case comes from the REAL loader fed a PARTIAL file
list — loadPartial{ Slash = true } omits exactly libs/LibKa0s/Slash.lua and
loads the rest — so the seam under test takes its stub branch while the addon
runs on the real library everywhere else. Nothing is hand-stubbed. loadDegraded
is now the empty end of the same mechanism rather than a second one.
PM-R-07 fixed: settings/OptionsSetup.lua's stub latched its explanation behind a
`said` flag, so the first /pm config on a degraded install said why the panel
was unavailable and every one after it did nothing at all — which reads as a
broken command, not a missing panel. The latch is gone. The once-per-session
latches on the Core printer's notice and the console's stay: those lines RIDE
OTHER OUTPUT, where repeating would drown the line the user asked for.
/pm config's line rides nothing; it is the verb's whole answer. That rule is now
written down in docs/ARCHITECTURE.md's seam table and smoke-tests.md step 13.
M2-05's interim hand-written stub-member case ("Degraded install: the Slash stub
answers the WHOLE member set, FormatKV included") is DELETED in this commit, as
M3-04 specifies — the Slash parity case replaces it. Two things it carried that
the primitive does not are kept inside the replacement: the reverse direction (a
stub that grew a member the live path lacks) and the FormatKV byte-equality pin
against lib.FormatKV, which is exactly the "right member set, wrong
implementation" limit the primitive states in its own docs.
Case count 707 -> 711: -1 (M2-05's case), +4 (one parity case per seam),
+1 (the /pm config repetition case). docs/test-cases.md regenerated; the README
badge corrected to 711 (it read 706, stale since M2-05).
Planted violations, each shown red then reverted green:
- Sl.FormatKV renamed out of the degraded branch -> Slash parity: "FormatKV is
missing (live: function)", 710/1.
- D:BufferSize removed from the DebugLog stub -> DebugLog parity, 710/1.
- RefreshScalars removed from the Options stub -> Options parity, 710/1.
- the `said` latch put back in OptionsSetup -> "/pm config went silent after
the first invocation (expected 3, got 1)", 710/1.
- the Core stub stops publishing NS.Util.print -> Core parity plus four
existing degradation cases, 706/5.
All reverted: 711 passed, 0 failed.
Closes PM-R-07 (PanelMaster's half of M3-04); supersedes M2-05's interim case.
Verification: luacheck 0/0 in 25 files; 711 passed / 0 failed / 0 skipped;
lizard Warning cnt 0; --list diff against docs/test-cases.md empty after
CR-normalizing both sides.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit ccbe6b2cee84b1659a4fbab883ee19c05c0f80f2
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 12:57:27 2026 +0530
test(harness): derive the vendored library's load list from LibKa0s.xml [M3-03]
The runner and tests/test_libka0s.lua each carried a hand-typed copy of the
vendored library's load list, and both copies were short: SIX of the eight
scripts LibKa0s.xml pulls in. Perf.lua and PerfPanel.lua were never loaded by
the headless suite. A short load list does not raise — it leaves the modules
undefined for whichever cases never reach them — which is exactly how PM-A-10
survived a year of green runs.
Both copies are now Loader.xmlFiles("libs/LibKa0s/LibKa0s.xml") (testkit v8),
which returns directory-prefixed paths in XML order (load order) and RAISES on
an XML it cannot open rather than degrading to an empty list that reads clean.
tests/test_harness.lua adopts the kit primitive alongside it:
- the two hand-rolled directional suite-list cases collapse into one
T.assertSuiteInventory("tests/", declaredSuites()) case, which reports every
divergence in both directions in a single message. Kit.run also calls it
automatically now that `dir` is passed explicitly, but that call dies before
any case registers, so the named case is what docs/test-cases.md counts.
- a new case pins that run.lua derives the library list from the XML, and
compares the derived list against the XML entry-by-entry in order.
docs/testing.md's harness description no longer says the runner hand-lists
libs/LibKa0s/*.lua.
Case-count delta, since M3-03 warned the count would move: it did NOT.
707 -> 707. The library files loaded moved 6 -> 8, but no case in this repo was
counted per library file; the only case that enumerated them (test_libka0s.lua's
"the vendored library registered for real") is one case that now walks eight
paths instead of six. Within the suite the arithmetic is -1 (two harness cases
-> one) +1 (the new XML-derivation case) = 0. docs/test-cases.md regenerated.
Planted violations, shown red then reverted green:
- tests/test_planted_violation.lua, undeclared -> Kit.run dies with
'exists but is not declared in the suites list'; with suiteInventory=false
the named case reports the same, 706 passed / 1 failed.
- run.lua reverted to the hand-typed six -> the XML-derivation case fails
'hand-lists the vendored library's files again instead of reading its XML',
706 passed / 1 failed.
- OptionsScroll.lua removed from LibKa0s.xml with the file left on disk ->
4 failed, including the vendor-sync gate and both Options-seam cases.
All three reverted: 707 passed, 0 failed.
Closes PM-A-10 (PanelMaster's half of M3-03).
Verification: luacheck 0/0 in 25 files; 707 passed / 0 failed / 0 skipped;
lizard Warning cnt 0; --list diff against docs/test-cases.md empty after
CR-normalizing both sides.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 6f19f335f97e9e57c1457e840e07b28a68d8b4d6
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 12:49:40 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 —
`libs/LibKa0s/` and `tests/_kit/` — and bumps README.md:378's provenance
line to v1.8.0. The gate reads that line as its input and compares both
pairs against it, so a kit-only copy would point the gate at a tag whose
`LibKa0s/` bytes this repo does not hold.
Rewrites tests/test_vendor_sync.lua from ~140 hand-copied lines to a
`VendorSync.register(_G.PM_TEST, {})` call into the newly vendored
tests/_kit/vendor_sync.lua. Case names are unchanged, so
docs/test-cases.md does not move (707 passed, unchanged). The kit's
default README pattern and default pairs both match this repo, so no
opts overrides are needed. The one `gsub("\r\n","\n")` on the
working-tree side lives on inside the kit — the other side is a
`git show` blob (LF) — with AbsorbTracker's header explaining why
carried in.
The absent-sibling path now reports SKIP with a reason instead of a bare
`return` that registered as PASS.
Closes: PM-R-05 (and the M3-01/M3-02 halves for this repo).
Verification:
diff -r ../LibKa0s/LibKa0s/ libs/LibKa0s/ -> empty
diff -r ../LibKa0s/testkit/ tests/_kit/ -> empty
lua5.1 tests/run.lua -> 707 passed, 0 failed, exit 0
luacheck . -> 0 warnings / 0 errors, 25 files
lizard -l lua -x ./libs/* -x ./tests/_kit/* -> Warning cnt 0
--list vs docs/test-cases.md (CR-normalized both sides) -> empty
grep -c "if not tag then return end" tests/test_vendor_sync.lua -> 0
Planted violation in libs/LibKa0s/Core.lua -> FAIL case #1, exit 1; reverted -> green
Planted violation in tests/_kit/loader.lua -> FAIL case #2, exit 1; reverted -> green
Skip path proven WITHOUT moving ../LibKa0s (other agents are using it):
a scratch harness outside the repo registered the same gate with
opts.sibling pointed at a non-existent path -> both cases SKIP with the
reason naming the missing checkout, exit 0.
Exec bit deliberately NOT set here (core.filemode=false; that is M3-05).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 12f9e1cabab8c78701fee5141cb9fc2b20157202
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 10:27:46 2026 +0530
fix(slash): the degraded stub assigns FormatKV so the panel verbs survive [M2-05]
`settings/Slash.lua`'s `if not lib then` branch (opens :285, returns :316)
re-implements the dispatcher so the host-owned PANEL verbs keep working, but
never assigned `Sl.FormatKV` — assigned only on the live path at :381. Every
panel verb that renders a `key = value` line calls it: `:101` (`/pm panel
<name>`), `:124`/`:125` (`fitart`), `:181` (field read) and `:188` (field write
echo). On an install with no `libs/LibKa0s/`, `/pm panel <name>` raised
"settings/Slash.lua:101: attempt to call field 'FormatKV' (a nil value)" —
reproduced before the fix, ok=true after.
The stub reproduces the library's one line rather than routing to it, because
on this path there is no library to route to; the case below pins it
byte-for-byte against `lib.FormatKV` so the two cannot drift.
Test: one hand-written case in `tests/test_libka0s.lua` asserts the degraded
`NS.Slash` function-member set in BOTH directions (a missing member and an
unlisted extra both redden) plus FormatKV's bytes. Deliberately NOT built on
`Kit.assertSurfaceParity` — that primitive is M1-LK-05, adopted in M3-04, which
replaces this case; M2 carries no M1 dependency.
Closes: PM-R-01
Verified: luacheck 0 warnings / 0 errors in 25 files; lua5.1 tests/run.lua
707 passed, 0 failed (706 -> 707, the +1 being this case); lizard Warning cnt 0;
`--list` byte-identical to the regenerated docs/test-cases.md. Red-then-green:
(1) stub FormatKV removed -> "cannot answer FormatKV"; (2) an extra `Sl.Bogus`
-> "grew unlisted members (Bogus)"; (3) lowercase colour escapes -> assertEqual
byte mismatch. All three reverted, suite green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
commit 1b5849b06f995d4b1cac7ba8a47dc1aa3a28a94a
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 10:18:57 2026 +0530
docs: commit frozen 2026-08-05 review and standards-audit bundles
These bundles are the evidence input to the consolidated remediation plan.
They are frozen artifacts and are never rewritten.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KQxehYAMgZtvAJDCJxXYRs
commit 3469f3ee642459d69d0a0dbce10b4a4827d989f9
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 01:44:09 2026 +0530
docs: the README's FAQ, Troubleshooting and tail match the Ka0s collection
AbsorbTracker, BankLedger and LootHistory all render FAQ and Troubleshooting as
two-column tables; this repo was the only one still using bold-question prose.
Converted both -- `Question | Answer` and `Problem | Fix` -- with the same
content, tightened to fit a row, plus one new FAQ row for the question the Sunn
section invites ("Do I need Sunn - Viewport Art installed?").
Three more shape differences closed at the same time:
1. A `Credits and libraries` section, which every other addon in the collection
carries and this one did not. The bundled-library list and the artwork's
CC BY-SA attribution both live there now.
2. The LibKa0s provenance sentence moves off the top of the page into that
section, phrased the way LootHistory phrases it. The vendor gate is
unaffected -- test_vendor_sync.lua matches the sentence anywhere in the file
and accepts the lowercase mid-sentence form, which is the case its own
comment was written about. Note the wrapping is load-bearing: the pattern
wants `bundles [LibKa0s](...)` adjacent, and a line break between them makes
the gate fail.
3. Version History becomes `Version | Date | Highlights` with <br>-separated
highlights, replacing one wall-of-semicolons row. The date reads -- because
0.1.0 has not shipped; it gets filled in at release.
Green gate: 706/706 tests, luacheck 0/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XhE1LqKvBP1TZDkkAnni9b
commit 26e6067b459fe475b65396bf69ccadd574f9ff57
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 01:10:49 2026 +0530
docs: README stops duplicating the artwork pipeline; the deviation note moves with it
sync-docs output that was left uncommitted by the finalize pass.
README.md is the player-facing document (documentation-§1). Its 'Contributing
artwork' and 'The artwork pipeline' sections restated what
docs/artwork-spec.md already covers across seventeen sections -- quick start,
the folder tree, naming rules, what the cleaner does, the tool reference -- so
the README carried a second copy that could drift from the first. They are
replaced by one player-facing 'Using your own artwork' section, and the README
still points at docs/artwork-spec.md for the rest.
One paragraph was NOT duplication: the note recording that tools/artwork/ is an
accepted, documented deviation from the standard, which defines no location for
build tooling. That moves into docs/artwork-spec.md rather than being dropped,
with its acceptance date and the related ARTWORK-04/05/06 ledger entries.
Documentation only. 706 tests pass, luacheck 0/0, zero functions over CCN 15.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit 6f7990bc0cabfd3e68cbf3532ff0f0e978f09c72
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 00:51:27 2026 +0530
automated-tests: put the recorded Max CCN back, and correct PM-004's ownership
Two corrections to yesterday's doc pass, both of the same family: a document
saying something the evidence beside it does not.
1. Commit 21cffd8 edited a GENERATED table row in place, changing run
20260804-215132's Max CCN from the recorded 0 to '15 dagger'. That is the one
thing performance-§10 forbids: a table edited to read what it should have
measured is indistinguishable from one that measured it, and the row then
disagreed with the manifest.json it links, which still records 0. The 0 is
restored. The trend column reads 51 -> 0 -> 15 again and the prose beside it
explains that the middle figure is an instrument fault -- the annotation does
the work the edit was doing, without falsifying the record.
2. ANALYSIS.md claimed 'no deviation ID or review finding owns' the missing
tests/perf.lua. PM-004 owns it exactly, against performance-§9, naming the
same gap and the same remedy. Corrected to cite it.
The bundle's measured artifacts -- manifest.json, complexity.txt, lint.txt,
tests.txt -- are untouched. Only the written analysis, added hours ago in this
same session and never yet read, is corrected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit b25c34d2563cb39f1d06ad79634edc47c7bd98cb
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 00:32:58 2026 +0530
docs: write the newest bundle's ANALYSIS.md and re-anchor the RESULTS.md prose
The 20260804-233329 bundle shipped without an ANALYSIS.md — the runner does not
write one and Step 2 of the automated-tests playbook was skipped when the bundle
was regenerated, which left the newest run, the one RESULTS.md points at as
"Current state", with no write-up at all. Written now against the playbook's
template: every figure cited to that bundle's own manifest.json or a suite
artifact in it, every suite row linking its evidence, complexity reported in full
with totals AND averages, and perf recorded as the skip it is rather than
softened into a pass.
The run's headline is that it closes the CCN work: zero functions over CCN 15
over 1348 functions, with the ceiling at exactly 15 in two named functions. The
previous run had the same ceiling and an instrument that could not report it, so
the analysis says that plainly rather than letting 51 -> 0 -> 15 read as
complexity vanishing and returning.
RESULTS.md standing sections re-anchored to the newest run:
- Test suite: 706 was "up ten on the previous run" when 20260804-215132 was
newest; it is flat there now and up ten on the baseline.
- Complexity watch list: the prose said "the previous run warned on nine
functions", which was true of the baseline, not of the previous run. Corrected,
and all nine are named with their CCNs rather than counted — read off
20260804-182223/complexity.txt.
- A plain paragraph naming which run stamps carry a Max CCN of 0, why (the kit
read CCN_MAX from lizard's warnings block, empty at zero warnings), and where
the true figure is (that same bundle's complexity.txt; 15 for 20260804-215132).
The frozen rows keep their numbers — a hand-edited record is worse than a wrong
one because it reads as measured.
Two more counts named rather than stated:
- docs/ARCHITECTURE.md: NS.LIBKA0S_MISSING's "three other files" are now named —
core/DebugLogSetup.lua, settings/OptionsSetup.lua, settings/Slash.lua.
- docs/superpowers/plans/2026-08-04-ccn-elimination.md: step 8 specifies the
fifteen-positional-argument buildSectionQuads, which 645868a replaced with one
named bar table (PARAM 3 in the newest bundle). The plan text stands as the
reasoning at the time; a dated outcome note carries the correction.
No version bump, no CHANGELOG entry, no tag. Gate: luacheck 0/0 in 25 files,
706/706 tests, lizard zero functions over CCN 15.
commit 21cffd894b769d4b91a434dc97ae4dad96540a5b
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 00:12:38 2026 +0530
docs: correct the complexity trend line and the watch list's stale numbers
sync-docs over the live doc set after the CCN branch. The generated inventory
docs/test-cases.md already matched `lua tests/run.lua --list` byte for byte, the
README's 706/706 badge agrees with it, and DEPENDENCIES.md's file:line citations
all still resolve — so the drift was confined to the automated-test record.
RESULTS.md
- The 20260804-215132 row reported Max CCN 0. Its own frozen complexity.txt maxes
at 15; the old kit read the figure out of lizard's warnings block, which is
empty at zero warnings, so the field had no input rather than a value. Left
uncorrected the trend read 51 -> 0 -> 15, i.e. complexity vanishing and coming
back. The bundle keeps its wrong number (bundles are frozen); the table is
corrected with a footnote naming the cause and the kit fix.
- The complexity watch list still said "as of 20260804-215132" while the newest
run in the table is 20260804-233329.
- modules/Artwork.lua's band row read 1166 LOC and "334 lines off the 1500 band".
It is 1188 and 312, up 101 across this branch from 1087.
ARCHITECTURE.md
- Registry.FitToArtwork -> Registry:FitToArtwork; it is a method, and every other
method in that document is written with the colon.
No version bump, no CHANGELOG entry. docs/performance.md and docs/perf-runs/ are
still absent and are left absent rather than invented.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit 21cffd894b769d4b91a434dc97ae4dad96540a5b
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Wed Aug 5 00:12:38 2026 +0530
docs: correct the complexity trend line and the watch list's stale numbers
sync-docs over the live doc set after the CCN branch. The generated inventory
docs/test-cases.md already matched `lua tests/run.lua --list` byte for byte, the
README's 706/706 badge agrees with it, and DEPENDENCIES.md's file:line citations
all still resolve — so the drift was confined to the automated-test record.
RESULTS.md
- The 20260804-215132 row reported Max CCN 0. Its own frozen complexity.txt maxes
at 15; the old kit read the figure out of lizard's warnings block, which is
empty at zero warnings, so the field had no input rather than a value. Left
uncorrected the trend read 51 -> 0 -> 15, i.e. complexity vanishing and coming
back. The bundle keeps its wrong number (bundles are frozen); the table is
corrected with a footnote naming the cause and the kit fix.
- The complexity watch list still said "as of 20260804-215132" while the newest
run in the table is 20260804-233329.
- modules/Artwork.lua's band row read 1166 LOC and "334 lines off the 1500 band".
It is 1188 and 312, up 101 across this branch from 1087.
ARCHITECTURE.md
- Registry.FitToArtwork -> Registry:FitToArtwork; it is a method, and every other
method in that document is written with the colon.
No version bump, no CHANGELOG entry. docs/performance.md and docs/perf-runs/ are
still absent and are left absent rather than invented.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit 55e9d72a2b3fe98d6e62d90259377dd618a62656
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 23:34:26 2026 +0530
re-vendor LibKa0s v1.7.0 (testkit rev 6)
Whole-folder copy of libs/LibKa0s and tests/_kit from the v1.7.0 tag, with the
README provenance line moved in the same commit (library-stack-§7).
testkit rev 6 fixes two silent runner bugs that both showed up here: Max CCN
reported 0 for any addon at zero warnings, and RESULTS.md rows never appended
at all in a CRLF repo. The bundle in this commit is the first run produced by
the fixed kit, so its Max CCN is the real repo ceiling and its row actually
reached the trend line.
Earlier bundles keep the figures they were written with -- they are frozen
records (automated-tests-§1) and the true value was always in their own
complexity.txt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit 645868a3f0cf91cdd448fd313a594a3970018b0a
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 23:09:06 2026 +0530
buildSectionQuads: one named bar table, not thirteen positional arguments
The signature took 15 positional arguments to avoid a per-call hash table, on
the measurement that the table costs +616 bytes/call and +10.8% on the
composite path. The measurement is real; the conclusion did not hold, because
this is not a per-frame path.
BuildArtSpec is reached only through Canvas.BuildSpec <- Canvas:Render(id),
which runs on a config change, a panel drag, a profile switch or load. The
module's one OnUpdate is the 10Hz mouseover driver, and it only calls SetAlpha
on a cached f.__spec -- it never reaches Render. So the allocation lands a few
dozen times when someone moves a panel, not sixty times a second, and 616 bytes
at user-action frequency buys nothing worth a 15-argument boundary.
Params 15 -> 3. CCN unchanged at 12; lizard's parameter threshold is 100, so
this never affected the gate in either direction. Behavior unchanged: the
twelve composite cases cover every field moved into the table -- crop, flip,
rotation, tile, section order -- and all 706 pass.
The comment records that the tests were mutation-checked rather than assumed:
swapping flipH/flipV in the destructuring fails one case, fv0/fv1 fails three.
So this is a readability change the suite already stood behind, not a fix for a
silent failure -- and the comment says so rather than overselling it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit f75ab41e587652579334b90bb1f47acae65450d8
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 21:57:24 2026 +0530
ccn: pin the orphan tally, drop a dead seam, make the record true
Closes feat/fix-ccn. Two code fixes and the generated artifacts.
The DebugLog frame-count regression test did not pin what it names. Mutation
check: deleting the orphan branch in addFrames entirely left the suite at
706/706 green, because the fixture had no orphan and "0 orphaned" passes just
as happily against code that never counts one. It now makes a real orphan —
the record lifted straight out of the array Registry owns, leaving Canvas
holding its frame — and asserts 1. Both mutations (branch removed, branch
inverted) now fail the suite.
core/DebugLogSetup.lua's activeFrames() was a dead seam the CCN split left
behind, and its comment stated a reason that was no longer true: "asked for
twice below — once to find orphans, once to count". master read the map twice;
addFrames does both in one pass, so the wrapper had one caller and its stated
justification had evaporated. Inlined. The header comment above it said "the
four writers below" when only three of the four were writers; it now names
addHeader, addPanel and addFrames.
Artifacts brought into line with the branch:
- README test badge 696 -> 706. docs/test-cases.md was already current at 706
and regenerating it produced a byte-identical file (CRLF preserved).
- A fresh automated-test bundle, docs/automated-tests/20260804-215132/, with
its ANALYSIS.md, and the RESULTS.md row prepended by the runner. Not a
release run.
- RESULTS.md's watch list rewritten against it. The functions table now reads
"None." — nine warnings to zero, no threshold moved and nothing suppressed.
The old dispositions are not carried forward; every function they described
is gone. The FILES table keeps its real entries against the new numbers, and
modules/Artwork.lua is noted at 1166 LOC (was 1087) — the split that fixed
the CCN is what grew it.
Deliberate, disclosed, and left standing: buildSectionQuads' 15 positional
arguments (modules/Artwork.lua). The ctx table was measured at +616 bytes/call
on the composite render path and reverted; the transposition risk is real,
lizard cannot catch it, and it is recorded in the bundle's ANALYSIS.md rather
than churned.
No behavior change, no version bump, no CHANGELOG entry.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit ee1ad6a6c2f04cae2938b99db7928f92b58f936e
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 21:12:26 2026 +0530
ccn: drop the ctx bag from buildSectionQuads, keep GetScale's `or`
Two review findings against the CCN work, both repaired by going back to what
master did rather than by moving the assertion.
buildSectionQuads took a 15-field ctx table assembled at the call site, which
put one extra hash table on every composed BuildArtSpec call. Measured over
100k iterations with the collector stopped, same fixture throughout:
composite FILL/FIT bytes/call master 5680 pre-fix 6296 now 5592
composite TILE bytes/call master 4368 pre-fix 4984 now 4280
single texture bytes/call master 2496 pre-fix 2408 now 2408
The arguments are positional now. Fifteen of them is the right price for a
helper on a render path; a shorter signature was the only thing the bag bought.
CCN is unchanged, so this costs nothing at the gate.
wow_mock's GetScale had turned `f.__scale or 1` into an `== nil` test via the
shared reader(). The two differ only for a stored false, which nothing in the
addon can produce, but `or` is what every existing assertion was written
against, so GetScale goes back to being spelled out and reader() loses its
default argument (no other caller passed one, and with no default the two
spellings are identical).
Both the reader comment and the test comment claimed `or` would swallow a
recorded 0. It would not — 0 is truthy in Lua, so `(0 or 1)` is 0, and only nil
and false ever reach an `or` default. Corrected in both places, and the test now
pins all three cases: unset reads 1, a recorded 0 reads 0, a stored false reads
the default.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit f481a878916a4c3d8a94e23b0b294405cae9fcad
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 19:41:03 2026 +0530
ccn: Sanitize 40->9, stub __index 33->4, R:Set 29->6, S.Themes 22->1, CliPanel 17->12
Chunk 2 of the CCN elimination pass. Behavior unchanged throughout; the rejection
strings, the chat output, the SavedVariables shape and the sort order are all
byte-identical.
R.Sanitize (modules/Registry.lua) 40 -> 9. The flat list of ~45 defaulting
statements becomes five module-level rule tables (CLAMPED, FREE_NUMBERS,
NONEMPTY_STRINGS, POINT_FIELDS, ENUM_FIELDS, BOOL_FIELDS) applied by three
helpers — sanitizeNumbers, sanitizeTokens, sanitizeFlags. The carve-outs stay
written out: enabled is ~= false, an empty accentEdges is not repopulated, an
empty artCustomPath survives, strata keeps its own predicate. Every per-field
comment that carried a decision moved onto its table row.
R:Set (modules/Registry.lua) 29 -> 6. The nine-arm elseif chain on `kind`
becomes a module-level COERCE table, one function per kind, built once at load.
A kind absent from the table is stored verbatim, which is what the old
fall-through did for "string". Precedence and every rejection sentence are
unchanged.
stubFrame's __index (tests/wow_mock.lua) 33 -> 4. The thirty-arm chain becomes a
module-level METHOD table of factories built once at load, with reader/writer/
boolWriter/rgbaWriter collapsing the two dozen trivial recorders. reader keeps
the explicit nil test so a recorded 0 or false survives. Eight characterization
cases pinning the stub were added to tests/test_harness.lua FIRST and passed
against the unrefactored stub.
S.Themes (modules/SunnArt.lua) 22 -> 1. Split into clampSections (which removes
a real duplication — the same three-branch clamp was written twice in two
spellings), isRegisterable, collectRegistered, collectKnownPacks, and the
comparator hoisted to module level.
Sl:CliPanel (settings/Slash.lua) 17 -> 12. doDeleteAll, doFitArt and
parseFieldValue extracted; what is left is pure routing, every branch a distinct
user-facing outcome.
docs/test-cases.md regenerated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit a3fa02ab161ed7e0893cc77a7d2a1e26aa9536a1
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 19:30:01 2026 +0530
ccn: BuildArtSpec 51->13, BuildSpec 24->6, release 17->6, Diagnose 21->2
Chunk 1 of the CCN elimination: every function over lizard's 15 in
core/DebugLogSetup.lua, modules/Artwork.lua and modules/Canvas.lua, brought
under the bar with no behavior change.
Artwork.BuildArtSpec 51 -> 13. The five-arm fill matrix becomes a MODULE-LEVEL
FILL dispatch table built once at load, keyed by fill name with TILE as the
fall-through arm the old `else -- TILE` was. Eight more file-local helpers own
one job each: nativeSize (also killing the duplicate block in NativeSize),
presentedExtent, toTextureAxes, artPlacement, artTint, artFlips,
artRenderHints, contentCropV0, clampTiledComposite, singleTextureQuad and
buildSectionQuads.
Canvas.BuildSpec 24 -> 6, split into addGeometry / addAppearance /
buildAccentSpec. The artwork call now reads spec.width/spec.height, so the
"fit art to the CLAMPED size" rule is structural rather than a convention.
Canvas release 17 -> 6, via clearBackdrop (the idiom the panel frame and each
accent bar both used), releaseAccents, releaseArt and poolName.
D:Diagnose 21 -> 2, via activeFrames / addHeader / addPanel / addFrames, all
hoisted to file scope so they are built once rather than per attachDiagnose
call, and all still resolving through NS at CALL time — the property that lets
this file sit in core/. The unreadable counting IIFE is gone.
Two characterization tests written and seen passing against the unrefactored
code first, both covering behavior the plan flagged as unpinned: a released
frame is inert (accent bars hidden, backdrops and art textures cleared), and
Diagnose's active/pooled/orphaned line.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit c56197be428f7c7fa10a18a82f76222f82a11b8b
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 19:15:57 2026 +0530
docs: CCN elimination plan
Per-function refactor plan for every lizard CCN>15 warning in this addon.
Design: LibKa0s/docs/superpowers/specs/2026-08-04-ccn-elimination-design.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit 63b7aa10e02c4694e03bc7151d1be60088b4db3b
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 18:34:36 2026 +0530
automated-tests: the watch list is two real tables, with a Band column
Two rendering faults in RESULTS.md and ANALYSIS.md.
THE FUNCTION ROWS HAD NO HEADER. They were markdown table rows with no header
line and no separator, so they rendered as literal pipes rather than a table.
Now: Function | CCN | Location | Disposition, with the header and rule.
THE FILE BAND WAS A RUN-ON SENTENCE. "Files in the 1000-1500 band: a.lua (1256)
-- accepted...; b.lua (1306) -- already tracked as BL-24." Two files were
readable; four (KickCD) were not, and there was nowhere to put a second band.
It is now a table with the band as a COLUMN, not a heading:
| Band | File | LOC | Disposition |
| 1000-1500 (on notice) | tests/test_x.lua | 1256 | accepted -- case count |
| > 1500 (over cap) | data/Gen.lua | 2893 | already tracked as ... |
A column rather than a heading per band because the bands are open-ended: two
exist today, the standard may add or move one, and a heading-per-band makes
every addon's record need restructuring when that happens. It also keeps a file
that MOVED between bands on one line in the diff instead of two.
Both tables now say "None." / "None -- no file reaches 1000 LOC" when empty
rather than being dropped, so an empty watch list reads as a result.
Dispositions are unchanged in substance -- they are the same ones recorded with
their evidence earlier today, re-rendered. Where a summary sentence stood in for
a long tail (BankLedger's fifteen, KickCD's twenty), every entry is now its own
row with its own disposition rather than being folded into prose.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit f0283bfe1a13bdb4fef0bd38f237f9662bce1ef3
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 18:24:41 2026 +0530
automated-tests: RESULTS.md gains size + averages, and prose for all four suites
Two gaps in the trend record.
THE TABLE WAS COMPLEXITY-THIN AND SIZE-BLIND. It carried CCN warn and Max CCN
but no average and no size, so a row could not be read against the next one
across a change in the addon. Columns are now Run, Version, Lint w/e, Files,
Tests, Perf, NLOC, Funcs, Avg NLOC, Avg CCN, Max CCN, CCN warn, Verdict. An
average without its total, or a total without its average, cannot be compared.
THE ONLY PROSE WAS ABOUT COMPLEXITY. The watch list came first and it was about
to become the only narrative the record ever carried, which teaches a reader
that the other three suites are pass/fail lights. They are not, and each now has
a standing section:
Test suite -- case count and what it covers, so a count that stops moving
while the addon grows is visible; the table cannot show that.
Lint -- clean over how many files, AND what the config excludes. A 0/0
row means nothing without knowing what was in scope.
Perf -- the scenarios and what they pin, or a plain statement that the
addon ships none: the record is then silent about runtime cost
and performance-§9's zero-overhead evidence does not exist for
it. A permanent skip is a standing fact, not a tooling gap.
tests/_kit re-vendored to LibKa0s v1.6.3 (kit revision 5) and the README
provenance moved with it. That revision also stops two ways the table could
mislead: a suite that was not SELECTED now renders as an em dash rather than its
zeroed counters (a --suite lint run used to write "0/0" into Tests, which reads
as a full run finding no tests), and a changed column set no longer silently
recreates the file and drops every previous row.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit 559e06dcdfaf02cc95ea48eb17b090239efb0dbd
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 12:28:45 2026 +0000
automated-tests: ANALYSIS.md links every artifact and reports complexity in full
Two gaps in the write-up, both in the Suites table.
The perf row named its scenario count but linked nothing, so the one suite whose
output is a JSON record was also the one you could not click through to. Every
row now links its artifact -- lint.txt, tests.txt + test-cases.md, perf.txt +
perf.json, complexity.txt -- and a skipped suite links nothing and says so,
because there is no artifact to link.
The complexity row reported four numbers and dropped four. lizard's footer is
Total nloc Avg.NLOC AvgCCN Avg.token Fun Cnt Warning cnt Fun Rt nloc Rt
7532 6.5 1.7 45.9 1047 2 0.00 0.02
and the manifest was keeping only the totals plus AvgCCN. The averages are what
make one run comparable to the next ACROSS A CHANGE IN SIZE: a total that rose
because the addon grew is a different fact from an average that rose because it
got denser, and only the second is a complexity signal. Reporting totals alone
makes a growing addon look like a degrading one every release, until nobody
reads the row. All eight fields are now recorded and reported, in their own
table, alongside the two derived file counts.
tests/_kit re-vendored to LibKa0s v1.6.2 (kit revision 4, which is what records
the extra fields) and the README provenance line moved with it.
The bundle from earlier today was removed rather than kept beside this one: it
was produced minutes ago by a superseded kit revision and its analysis cannot
carry the metrics, so keeping it would seed the trend line with a row that can
never be compared against its successors. Retention applies to genuine runs over
time, not to churn from getting the tooling right within one hour.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit 45521f2dc6cd3742c1f9a333ef5f814ff45a6443
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 11:50:57 2026 +0000
automated-tests: kit rev 3 — ANSI-clean artifacts, local-time YYYYMMDD-HHMMSS, ANALYSIS.md
Three fixes to the record produced during adoption, all found by looking at the
artifacts rather than by a test.
lint.txt and tests.txt held raw ANSI escapes. luacheck and the harness colour
their output when they think a terminal is attached, so the stored evidence read
Checking core/Compat.lua <0x1b>[0m<0x1b>[32m<0x1b>[1mOK<0x1b>[0m
-- unreadable in an editor and pure noise in a diff between two runs, which is
most of what a stored artifact is for. Fixed upstream in LibKa0s v1.6.1.
Run directories are now stamped in LOCAL time as YYYYMMDD-HHMMSS, not UTC as
YYYY-MM-DD-HHMMSS. A record is read by the person who ran it, usually minutes
later; a folder name that disagrees with their clock costs a mental conversion
every glance. manifest.json's startedAt carries an explicit UTC offset rather
than a Z, so the instant stays unambiguous once the record outlives the machine.
ANALYSIS.md was missing. The runner writes data and a reader writes the
analysis, which is the design -- but automated-tests-§5 makes it a SHOULD for
any run, and shipping eight bundles with none was a gap rather than a choice.
Each bundle now carries one to the playbook's uniform template, and RESULTS.md
carries the complexity watch list the section requires below its table.
tests/_kit re-vendored to LibKa0s v1.6.1 (kit revision 3) and the README
provenance line moved with it. The bundles were regenerated rather than renamed:
a folder name, the manifest's `run` field and its startedAt all have to agree,
and rewriting two of them by hand to match a rename is exactly the hand-edited
record the section forbids.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit 8efb499c9db698017c02060a9d0409a18858551f
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 10:27:09 2026 +0000
Adopt automated-tests: consolidated test records, retire docs/complexity.md
Standard v2.19.0's `automated-tests` section. All four out-of-game suites now go
through one vendored runner and every run is recorded.
tests/_kit/run-automated-tests.sh -- from LibKa0s v1.6.0 (kit rev 2)
docs/automated-tests/README.md -- what it is, how to run it
docs/automated-tests/RESULTS.md -- one row per run, ALL four suites
docs/automated-tests/<stamp>/ -- first frozen bundle
lint and tests GATE; perf and complexity are RECORDED, NEVER GATING. That is a
deliberate refusal to do the obvious thing now that all four sit side by side:
a threshold that fails a run teaches everyone to reach for --no-verify, after
which the gate protects nothing and the habit remains. They yield `amber`.
A missing tool is a SKIP RECORDED WITH ITS REASON, never a pass -- a green run
that silently measured nothing is worse than a red one, because it is believed.
docs/complexity.md is RETIRED. Its raw output is each bundle's complexity.txt
and its trend line is RESULTS.md, which stays ONE file overwritten in place --
performance-§10 warned that a dated pile "would scatter across files the one
comparison the report exists to make", and that is exactly why the trend line
did not move into the bundles with the evidence.
docs/perf-runs/ narrows to IN-GAME captures, where the addon has one: a script
cannot produce those, so they keep their standing cumulative store. Offline runs
belong to the bundle for the run that produced them.
.gitattributes gains `*.sh text eol=lf`. Everything else here is CRLF, and a
shebang followed by CRLF makes the kernel look for an interpreter literally
named "bash\r". Without this the vendored runner is broken on EVERY checkout.
tests/_kit re-vendored whole-folder from LibKa0s v1.6.0 and the README
provenance line moved with it, so test_vendor_sync stays green -- the gate
compares the file SET first, and a consumer still holding four files fails there
before it reaches a content diff.
Docs synced: docs/testing.md's complexity section replaced with the consolidated
runner and its gating table; CLAUDE.md, DEPENDENCIES.md, ARCHITECTURE.md,
file-index.md and common-tasks.md updated wherever they named complexity.md.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit 3142d25524852083006d5a8846ff4c4e1a70c173
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 07:38:09 2026 +0000
docs: sync the doc set to the code — Desaturate, Blend and the current catalog
Documentation drift, all of it in one direction: the docs still describe the
addon as it was before `feat(artwork): tint every piece, add Desaturate and
Blend mode` and before the catalog was rebuilt from the folder tree. No code
changes.
Desaturate and Blend mode were shipped and documented nowhere. Both are real
editor controls (settings/PanelEditor.lua:853, :858) and real record fields
(C.PANEL_TEMPLATE), and every doc that enumerates the artwork surface omitted
them: the README's per-panel table, its "What's new" bullet, ARCHITECTURE's
panel record and artwork-field table. Worse, two docs asserted the OPPOSITE
of what ships — the README said full-color art "is left alone by the tint"
when every piece takes it now, and smoke test 5e-3.6 told a tester to expect
that "there is no blend-mode control", which is a smoke test that FAILS on
correct code. 5e-4 was checking for a tintable split that no longer exists
(the color pair is unconditional on purpose, so it stops shoving the rows
below it up and down as you page the dropdown).
Counts and citations, each re-derived rather than copied forward:
- "Fifteen art* fields" -> sixteen. Sanitize snaps four of the closed lists,
not "the four enum values": artBlend is deliberately NOT snapped there, it
is defended in BuildArtSpec instead, and that asymmetry is now stated.
- "The five closed lists" -> six, with which kind validates each one.
- Bundled art is 1024x1024, not 512x512 — 101 files, all of them, verified
against the TGA headers and against the catalog's declared w/h.
- The Sunn manifest measures 250 of 270 section files at 512x256, not of 262.
- Three DEPENDENCIES.md evidence citations had drifted by a line or two
(test_vendor_sync.lua:88 -> :89 and :73-76 -> :74-76, make_poster.py
:386-388 -> :387-389). An evidence doc whose file:line is off is the one
kind of wrong that reads as right.
Stale names, which are the ones a user actually trips over: the README's CLI
example and smoke test 5e.2 both named `runic-sigil` / `General: Runic Sigil
(B&W)`, an id and a category that stopped existing when the pipeline started
deriving both from the folder tree. Now `class-death-knight` /
`Class: Death Knight`. And Sunn rows are listed under `Sunn -> Art Pack 2` —
the category separator is ASCII, not the "▸" the docs were showing, so
anything hunting for that glyph in the dropdown will not find it.
Per-section Sunn rows were dropped when the composite adapter landed, but
ARCHITECTURE's module map and the README's feature bullet still advertised
"one section at a time". Both now say one entry per theme, which is what the
adapter synthesizes and what the rest of both documents already said.
docs/complexity.md is untouched — it is generated, and regenerating it is a
release checkpoint (performance-§10), not a doc-sync step. The frozen
audit/review bundles under docs/ are untouched for the same reason they are
frozen.
Gates after the change: luacheck 0 warnings / 0 errors in 25 files;
lua tests/run.lua 696 passed, 0 failed, 696 total.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit 453565efdd28c317e8a5cfab55f4dbab17703963
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 05:52:01 2026 +0000
docs: commit the 2026-08-03 review and 2026-08-04 standards-audit bundles
Frozen dated bundles as produced by /wow-addon:review and
/wow-addon:standards-audit. Not edited after the fact.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit 453565efdd28c317e8a5cfab55f4dbab17703963
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 05:52:01 2026 +0000
docs: commit the 2026-08-03 review and 2026-08-04 standards-audit bundles
Frozen dated bundles as produced by /wow-addon:review and
/wow-addon:standards-audit. Not edited after the fact.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EhKAb8o8K8KUjpHArvE1ky
commit 7321a80fb7659318860f0be24368f583ca51f3d3
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Tue Aug 4 05:51:21 2026 +0000
docs: adopt standard v2.18.0 — DEPENDENCIES.md and docs/complexity.md
Two new normative rules land together, and both answer a question this repo
could previously only answer by running something and reading the error.
Root DEPENDENCIES.md (documentation-§7). Everything needed to build, run,
test or release, split runtime / development / release-and-assets, because
most readers need exactly one of the three. Every entry cites its evidence —
a file:line, an import, a documented command. The Lua 5.1 requirement is
stated as a requirement with its reason (tests/_kit/loader.lua uses setfenv,
removed in 5.2), lizard and luacheck are honestly "any recent". The install
line for lizard is pipx, not pip: Ubuntu 24.04 marks its Python
EXTERNALLY-MANAGED and pip refuses outright, so a pip instruction here would
be broken and look correct. The release section is loud about being optional
— Pillow for three of the four Python scripts, numpy for artwork_cleaner.py
alone, and libvulkan1 for the VENDORED upscaler at tools/artwork/bin/, which
is not installed by any package manager and is verified with ldd rather than
a --version. Bundled fonts and models are assets, not dependencies, and say
so.
docs/complexity.md (performance-§10). Generated with the mandated invocation,
verbatim, from the repo root. Nine functions warn on CCN and three files sit
in layout-§1's 1000-1500 band; each carries a one-line disposition, and the
report says plainly that it is the first one and has nothing to diff against.
Two dispositions point at work already tracked: F-002 on R.Sanitize and
PM-007/F-005 around Sl:CliPanel. Artwork.BuildArtSpec (CCN 51) and its file
are marked peel-next rather than accepted. Closes PM-011.
The release checkpoint is recorded in docs/testing.md and CLAUDE.md, in both
cases stating that it happens at RELEASE and is NOT a commit gate — a
threshold that fails a build is how a collection learns to reach for
--no-verify.
Gates after the change: luacheck 0 warnings / 0 errors in 25 files;
lua tests/run.lua 696 passed, 0 failed.
commit c73a636ae21cefdaaab40bb68d1c1e65bbf8ae00
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 3 19:20:26 2026 +0000
docs+i18n: adopt standard v2.17.1 — US English spelling throughout
localization-§5 makes US English the source dialect for every word this
addon authors, and anti-pattern #46 is the drift that follows when a
British form survives in a string that later becomes a locale key.
NO LOCALE KEYS CHANGED. locales/enUS.lua and locales/PostLoad.lua were
already clean, so nothing in NS.L moved and no call site or test needed
a matching edit — the failure mode that rule warns about did not arise.
What moved:
core/Constants.lua, modules/Canvas.lua judgement -> judgment
modules/Canvas.lua artefacts -> artifacts
modules/Registry.lua travelled -> traveled
core/CoreSetup.lua synthesised -> synthesized
tests/test_libka0s.lua synthesises -> synthesizes
tests/test_accent.lua fixture panel name "Levelled" -> "Leveled"
docs/ARCHITECTURE.md favours -> favors
docs/pending/LEDGER.md favour/organising/judgement
docs/superpowers/specs/*.md favour, behaviour, centred, normalised
tools/artwork/make_poster.py colour, centre/centring/centred, unlabelled
The guard is widened so none of this can come back quietly. The BRITISH
word list in tests/test_spelling.lua gains the whole -ize family plus
travell/levell/judgement/artefact/dialogue/practise and the rest of the
standard's canonical mapping; "analyse"/"paralyse" stay spelled in full
because "analysis" and "paralysis" are correct US English and a bare
"analys" stem would flag them.
Two files the scan did not reach are now covered: docs/pending/LEDGER.md
and the tools/ generators. The ledger takes a NARROWER form of the
per-file exemption — a word set rather than a blanket true — because row
ARTWORK-11 cannot record why the SunnArtPacks exemption exists without
naming the theme, "Grey"; every other British form in that ledger is
still a failure there. Widening the scan to the .py generators caught an
"unlabelled" the manual sweep had missed, which is the argument for the
guard in one line.
695 tests pass, luacheck clean. docs/test-cases.md is byte-identical —
no test NAME changed, only a fixture argument.
commit 3fc345df795fc0afc28afa14655d463034ab47b2
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 3 19:04:43 2026 +0000
assets: use the pvp_rune_random icon for the addon list
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177r5TAPxBMvy7kqskat86N
commit 21a5051fddf77e0fa15f3b85081b7f50544e92b4
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 3 19:04:23 2026 +0000
docs(claude): state that docs/agent-context.md is gone and must not return
The Ka0s WoW Addon Standard deleted docs/agent-context.md in v2.17.0
(documentation-§3, anti-pattern #49), but nothing in this repo said so.
A fresh agent — /wow-addon:sync-docs in particular — therefore rebuilt the
pre-v2.17.0 docs set from memory and reported the file as missing.
Add a section to the root CLAUDE.md naming the three canonical docs inline
(ARCHITECTURE.md, testing.md, smoke-tests.md) so the membership cannot be
guessed, stating the prohibition, and flagging that the audit bundles,
ledgers and plans under docs/ which still name the file are frozen history
rather than a live requirement.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177r5TAPxBMvy7kqskat86N
commit 0503f8e1f703a3aadfced580374cacd0a9881686
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 3 18:42:53 2026 +0000
docs(ledger): record that ARTWORK-10's premise was verified in-game
The known-pack manifest exists so a disabled or unloadable SunnArt cannot take the
feature down with it, and that rests entirely on one behavior: a texture in a
DISABLED addon's folder still loads. Two commits shipped carrying that unconfirmed.
It has now been checked in a live client and holds. Recorded on the row a future
audit actually reads, so the next person finds a settled question rather than a
load-bearing assumption. docs/smoke-tests.md section 18 step 16 is the repeatable
form of the check.
No code or behavior changes. Gate: 695 passed / 0 failed, luacheck 0/0 in 25 files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EpSVgkkKQ3G781F9YP6Gf
commit 0562030b334758c45564c7371dfc0a482d0144e6
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 3 18:34:48 2026 +0000
feat(artwork): draw Sunn composites, fit panels to art, scale panels
Closes #12. The renderer the last commit deferred, plus what fell out of finally
reading SunnArt's own source instead of reasoning about it.
COMPOSITE RENDERING, as a virtual atlas. A composed bar is treated as ONE image of
the whole bar's size; BuildArtSpec runs against it completely unchanged, and only
then is its single rectangle sliced into N. That is the whole design, and it is why
fill, scale, anchor, crop, flip, rotation, tint, desaturate and blend all keep
meaning exactly what they mean for a single texture -- the alternative was a second
set of semantics to define, document and keep in agreement with the first.
* transformRect is composeUV's screen-space counterpart, and its turn permutation
is READ OFF composeUV's own corner shuffle rather than derived independently. A
rotated bar therefore stacks its sections vertically and a horizontal flip
reverses their order; neither is special-cased anywhere.
* a band is intersected with the crop the fill already chose, so a FILL that pushes
the left section off the panel simply does not emit it. The test is against a
fraction of the span, not `hi > lo`: FILL centers its window, so a crop landing
exactly on a band edge is the COMMON case, and in floating point the vanishing
sections otherwise survive by an ulp as sub-pixel slivers that draw as a seam.
* TILE splits only the horizontal axis -- the vertical repeat stays a REPEAT wrap,
being inside one file. MAX_ART_QUADS (24) caps the total; over budget the copy
count drops and each tile grows, so the panel stays COVERED rather than going
partly bare.
* every spec carries art.quads and a single texture is a ONE-ELEMENT list, so
applyArtwork has one path and no branch that can rot.
THREE CORRECTIONS to what the last commit shipped, all from reading SunnArt 4.01:
* `overlap` is NOT overlap between sections. Sections are flush (Core.lua:322
anchors each to the previous one's TOPRIGHT). It is the transparent band at the
TOP of the artwork (CustomTheme.lua:28-33), which SunnArt hangs over the game
world and a panel has nothing to hang over. It is cropped instead, so autosize
and FIT shape a panel around art a player can see. TILE drops the crop: a REPEAT
wrap repeats a whole file, not a sub-range of one.
* a precedence inversion. ImportThemes merges panels/overlap in the OPPOSITE order
to theme names (Core.lua:132-152), so a pack's declared section count beats a
stale saved-globals entry. Pairing each count table with its name table had it
backwards. Both orders are now reproduced and asserted.
* SunnCustomPanels/SunnCustomOverlap are declared in CustomTheme.lua and never read
by SunnArt, so a player who sets them is silently given 3 sections. We read them,
ordered last. Kept as a deliberate divergence rather than matched.
THE KNOWN-PACK MANIFEST (modules/SunnArtPacks.lua, 88 themes from 12 packs). Every
pack TOC carries `## Dependencies: SunnArt`, enforced before any Lua runs -- so a
disabled SunnArt means nothing registers and the feature goes silent, while the
textures remain ordinary drawable files. Sunn has not shipped since 2024-08, so that
is the expected end state rather than a hypothesis. Live registration still wins per
theme, and a theme is offered only when its pack folder is actually installed.
It also fixes ARTWORK-07, which was WRONG. 2:1 was reasoned from Core.lua:259's
`h = w * scale / 2` -- sound about SunnArt, wrong about the art. Reading the TGA
headers: 250 of 262 section files are 512x256, but five themes are SQUARE and three
are 1024 wide. SunnArt draws those squashed; declaring 2:1 would have reproduced its
bug faithfully. The manifest carries MEASURED dimensions and is consulted for every
theme it knows, however discovered.
THE FOLDER GATE APPLIES TO LIVE DISCOVERY TOO, which is not obvious: db.global.themes
is saved variables and SunnCustomTheme is hand-edited, so both can name a pack deleted
long ago and put an entry in the dropdown that draws nothing. S.Installed is now
defined as #S.Themes() > 0 so it cannot claim an install the dropdown then fails to
populate.
PER-SECTION ROWS ARE GONE. They turned 88 themes into 270 dropdown entries, four
fifths of them fragments of something listed three lines above. A single strip is
still reachable through Custom path. The `-bar` id suffix survives the removal:
ids are the saved-variables contract, and a rename is breaking whether or not the
reason for the name still holds.
FIT TO ARTWORK replaces the artAutosize flag. As a stored mode it reshaped the panel
on every width change, overwrote a hand-set height, and needed `height` explicitly
carved out of the re-derive set or typing one undid itself on every keystroke. As a
button none of that is true and `height` is an ordinary field again. It adopts the
art's PRESENTED size on both axes -- native size, transposed by rotation, times
artScale -- which is what STATIC draws at. FIT is deliberately not the target: it
contains and THEN scales, so fitting to its output would be a fixed point only at
scale 1 and a shrinking spiral below it.
PANEL SCALE, a new `scale` field applied by applySpec BEFORE the size and the anchor
-- a frame's scale is the unit both are expressed in, so setting it afterwards leaves
the panel one repaint behind. Bounded 0.25-4.0 rather than artScale's 0.1-4.0: that
one resizes a texture inside a panel whose clickable area is unchanged, this resizes
the frame, and 0.1 would walk straight around the MIN_SIZE floor that keeps a panel
grabbable.
Gate: 695 passed / 0 failed, luacheck 0 warnings / 0 errors in 25 files. Vendor gate
empty on both readings and on tests/_kit. All three generator --checks clean.
docs/test-cases.md regenerated and in sync, so the [tests] badge moves 644 -> 695.
Not verified in a live client, and it is the assumption everything above rests on:
that a texture in a DISABLED addon's folder still loads. docs/smoke-tests.md section
18 step 16 tests exactly that.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EpSVgkkKQ3G781F9YP6Gf
commit 0562030b334758c45564c7371dfc0a482d0144e6
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 3 18:34:48 2026 +0000
feat(artwork): draw Sunn composites, fit panels to art, scale panels
Closes #12. The renderer the last commit deferred, plus what fell out of finally
reading SunnArt's own source instead of reasoning about it.
COMPOSITE RENDERING, as a virtual atlas. A composed bar is treated as ONE image of
the whole bar's size; BuildArtSpec runs against it completely unchanged, and only
then is its single rectangle sliced into N. That is the whole design, and it is why
fill, scale, anchor, crop, flip, rotation, tint, desaturate and blend all keep
meaning exactly what they mean for a single texture -- the alternative was a second
set of semantics to define, document and keep in agreement with the first.
* transformRect is composeUV's screen-space counterpart, and its turn permutation
is READ OFF composeUV's own corner shuffle rather than derived independently. A
rotated bar therefore stacks its sections vertically and a horizontal flip
reverses their order; neither is special-cased anywhere.
* a band is intersected with the crop the fill already chose, so a FILL that pushes
the left section off the panel simply does not emit it. The test is against a
fraction of the span, not `hi > lo`: FILL centers its window, so a crop landing
exactly on a band edge is the COMMON case, and in floating point the vanishing
sections otherwise survive by an ulp as sub-pixel slivers that draw as a seam.
* TILE splits only the horizontal axis -- the vertical repeat stays a REPEAT wrap,
being inside one file. MAX_ART_QUADS (24) caps the total; over budget the copy
count drops and each tile grows, so the panel stays COVERED rather than going
partly bare.
* every spec carries art.quads and a single texture is a ONE-ELEMENT list, so
applyArtwork has one path and no branch that can rot.
THREE CORRECTIONS to what the last commit shipped, all from reading SunnArt 4.01:
* `overlap` is NOT overlap between sections. Sections are flush (Core.lua:322
anchors each to the previous one's TOPRIGHT). It is the transparent band at the
TOP of the artwork (CustomTheme.lua:28-33), which SunnArt hangs over the game
world and a panel has nothing to hang over. It is cropped instead, so autosize
and FIT shape a panel around art a player can see. TILE drops the crop: a REPEAT
wrap repeats a whole file, not a sub-range of one.
* a precedence inversion. ImportThemes merges panels/overlap in the OPPOSITE order
to theme names (Core.lua:132-152), so a pack's declared section count beats a
stale saved-globals entry. Pairing each count table with its name table had it
backwards. Both orders are now reproduced and asserted.
* SunnCustomPanels/SunnCustomOverlap are declared in CustomTheme.lua and never read
by SunnArt, so a player who sets them is silently given 3 sections. We read them,
ordered last. Kept as a deliberate divergence rather than matched.
THE KNOWN-PACK MANIFEST (modules/SunnArtPacks.lua, 88 themes from 12 packs). Every
pack TOC carries `## Dependencies: SunnArt`, enforced before any Lua runs -- so a
disabled SunnArt means nothing registers and the feature goes silent, while the
textures remain ordinary drawable files. Sunn has not shipped since 2024-08, so that
is the expected end state rather than a hypothesis. Live registration still wins per
theme, and a theme is offered only when its pack folder is actually installed.
It also fixes ARTWORK-07, which was WRONG. 2:1 was reasoned from Core.lua:259's
`h = w * scale / 2` -- sound about SunnArt, wrong about the art. Reading the TGA
headers: 250 of 262 section files are 512x256, but five themes are SQUARE and three
are 1024 wide. SunnArt draws those squashed; declaring 2:1 would have reproduced its
bug faithfully. The manifest carries MEASURED dimensions and is consulted for every
theme it knows, however discovered.
THE FOLDER GATE APPLIES TO LIVE DISCOVERY TOO, which is not obvious: db.global.themes
is saved variables and SunnCustomTheme is hand-edited, so both can name a pack deleted
long ago and put an entry in the dropdown that draws nothing. S.Installed is now
defined as #S.Themes() > 0 so it cannot claim an install the dropdown then fails to
populate.
PER-SECTION ROWS ARE GONE. They turned 88 themes into 270 dropdown entries, four
fifths of them fragments of something listed three lines above. A single strip is
still reachable through Custom path. The `-bar` id suffix survives the removal:
ids are the saved-variables contract, and a rename is breaking whether or not the
reason for the name still holds.
FIT TO ARTWORK replaces the artAutosize flag. As a stored mode it reshaped the panel
on every width change, overwrote a hand-set height, and needed `height` explicitly
carved out of the re-derive set or typing one undid itself on every keystroke. As a
button none of that is true and `height` is an ordinary field again. It adopts the
art's PRESENTED size on both axes -- native size, transposed by rotation, times
artScale -- which is what STATIC draws at. FIT is deliberately not the target: it
contains and THEN scales, so fitting to its output would be a fixed point only at
scale 1 and a shrinking spiral below it.
PANEL SCALE, a new `scale` field applied by applySpec BEFORE the size and the anchor
-- a frame's scale is the unit both are expressed in, so setting it afterwards leaves
the panel one repaint behind. Bounded 0.25-4.0 rather than artScale's 0.1-4.0: that
one resizes a texture inside a panel whose clickable area is unchanged, this resizes
the frame, and 0.1 would walk straight around the MIN_SIZE floor that keeps a panel
grabbable.
Gate: 695 passed / 0 failed, luacheck 0 warnings / 0 errors in 25 files. Vendor gate
empty on both readings and on tests/_kit. All three generator --checks clean.
docs/test-cases.md regenerated and in sync, so the [tests] badge moves 644 -> 695.
Not verified in a live client, and it is the assumption everything above rests on:
that a texture in a DISABLED addon's folder still loads. docs/smoke-tests.md section
18 step 16 tests exactly that.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EpSVgkkKQ3G781F9YP6Gf
commit 9f4b94b8e5085e92308cbee121873baed769d967
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 3 15:17:31 2026 +0000
feat(artwork): discover Sunn - Viewport Art packs, and autosize to art
Toward #12. Two halves of it: user-installed Sunn packs are discovered and
offered in the artwork dropdown, and a panel can take its shape from whatever
art it draws. The composite RENDERER is not here yet -- see the caveat below.
Nothing is redistributed. The player installs SunnArt and whichever packs they
want; modules/SunnArt.lua only reads what is already on their disk. That is why
this route carries none of ARTWORK-01's licensing weight -- not one pack byte
is shipped, copied or hosted.
ENUMERATION, from the pack's own registration rather than a shipped manifest or
path probing (the issue's option 1, and its acceptance criterion). SunnArt
merges four sources and so do we, in its order, later sources winning:
* SunnArtPack -- community packs, which SELF-INITIALIZE this global and
work with no SunnArt installed at all
* SunnCustomTheme -- SunnArt/CustomTheme.lua, hand-edited by the player
* SunnArt.options.args.theme.values
-- where the official packs 1-6 register; SunnArtPack2.lua
is five lines of exactly this and nothing else
* SunnArt.db.global.themes
-- themes built in SunnArt's own Advanced options screen
Read directly, NOT by calling SunnArt:GetThemeList(). That builder mutates
ThemeDB, sorts its index and fires ImportThemes as a side effect; reading
another addon's state is safe, but driving its rebuild from our load path would
make its bugs ours. Verified against an installed SunnArt 4.01 + Art Pack 2 and
the community SunnArtErgoZPack, and every test fixture is those files' real
shape rather than one invented to match this code.
Injection runs at OnEnable, not file scope: a pack is a separate addon whose
Lua has not necessarily run when our module loads.
EACH THEME YIELDS BOTH SHAPES, which is the decision the issue left open:
Sunn -> Art Pack 2: Blackrock the whole bar
Sunn -> Art Pack 2: Blackrock (left) section 1
Sunn -> Art Pack 2: Blackrock (middle) section 2
Sunn -> Art Pack 2: Blackrock (right) section 3
Sections are named by POSITION because that is what they are -- SunnArt lays
them left to right into one bar -- and "2/3" made a player count. Only the ends
are always unambiguous, so middles are numbered once a theme has more than one.
The composite wears the bare theme name, so scanning for "Blackrock" finds the
whole thing and the parts read as qualified variants of it.
DIMENSIONS are declared, and the assumption is stated rather than buried. No
pack publishes its pixel size and no WoW API reports a texture file's source
size -- the wall already documented at Artwork.CUSTOM_NATIVE_SIZE. SunnArt does
not know either: its `length` is a percentage for pushing art off-screen, not a
measurement. Every section of every pack inspected is 2:1 (SunnArt's own five
themes and Art Pack 2's five are all 512x256 -- 30 files; SunnArtErgoZPack is
512x256 and 1024x256), so 512x256 is declared. Only the ASPECT is consumed, since
STATIC/FIT/TILE divide panel size by native size, so a 4:1 pack tiles at half the
frequency a purist wants and nothing worse.
Artwork rows grew an absolute `path` that wins over the bundled derivation:
C.ARTWORK_PATH_PREFIX is rooted at THIS addon and cannot reach another's folder.
Paths are extensionless and rooted at Interface\Addons, matching
SunnArt_Core.lua:309 -- appending .tga would break any pack shipping .blp.
AUTOSIZE (artAutosize, off by default) derives a panel's HEIGHT from its width
and the art's native aspect. One axis, not native pixel size: a bundled piece is
1024x1024 and a Sunn bar is 1536x256, so adopting native size would throw a wall
across the screen for one and a letterbox for the other. Square art therefore
gives 1:1, a Sunn section 2:1, a three-section bar 6:1, a Custom path 1:1.
* off by default -- it overwrites a hand-set height, and no upgrade should
reshape a panel someone already had
* `height` is deliberately NOT a re-derive trigger, or typing a height would be
undone on every keystroke; autosize reasserts on the next width/art change
* art that is not installed leaves the shape alone, so uninstalling a pack
cannot silently reshape a layout
* runs BEFORE Sanitize, so the derived height meets the same MIN/MAX clamp
Artwork.NativeSize is the one place that answers "how big is this piece", so the
autosize seam and the fill math cannot disagree.
CAVEAT, and the reason this is not "Closes #12": applyArtwork still draws one
texture, so a composed row currently renders its LEFT section stretched across
the panel instead of all sections edge to edge. Canvas creates a single f.art at
frame-build time (modules/Canvas.lua:210); the composite needs frame
construction, applyArtwork and the pool reset to carry N textures, scoped to
these rows alone. Autosize already measures the full bar, so the composed row's
proportions are right ahead of the renderer that will fill them. Docs land with
that commit rather than describing a half-drawn feature now.
Gate: 644 passed / 0 failed, luacheck 0 warnings / 0 errors in 24 files. Vendor
gate empty on both readings and on tests/_kit; both artwork --checks green.
docs/test-cases.md regenerated and in sync at 644, so the [tests] badge moves
618 -> 644.
Not verified in a live client: that a real pack's textures resolve in-game. The
paths are asserted against SunnArt's own construction, not against a load.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EpSVgkkKQ3G781F9YP6Gf
commit 475ff5fcbd61d0edfc63f38f41e973fbc2483ae5
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 3 14:34:46 2026 +0000
feat(artwork): generate a deterministic poster of the bundled set
Closes #14. One PNG showing every artwork that ships, grouped under its
catalog category, embedded in the README and ready for the CurseForge media
tab. tools/artwork/make_poster.py draws it.
It does not walk media/artwork/ itself. It imports update_catalog.py and calls
that script's scan(), sorts by the SORT_KEY that script now exports, and shares
its fatal-problem policy through is_fatal() -- so the poster and the dropdown
are two renderings of one list and cannot disagree about what shipped. Two
walks would have been two chances to drift, and the drift would be invisible: a
poster showing art the addon does not have still looks fine.
Determinism is stated about PIXELS, not file bytes, because only one of those
is ours. Five things are pinned so the pixels cannot move: fonts resolved only
from tools/artwork/fonts/ with no system fallback, the text layout engine
forced to BASIC, every string placed on a whole pixel, the stamp taken from the
TOC version rather than a clock, and every loop running over the sorted rows.
The file's bytes are deflate's output and a zlib-ng build can compress
identical pixels differently, so --check fingerprints the decoded image and a
plain run rewrites nothing when the pixels already match. A contributor on a
different toolchain therefore cannot churn 2 MB of visually identical binary
into the history. Verified across Pillow 10.2.0/FreeType 2.13.2 and Pillow
11.3.0/FreeType 2.13.3 -- same fingerprint, --check green on both.
That leaves one input a script cannot pin: a FreeType whose glyph
rasterization changes would move the pixels themselves. It is recorded instead.
media/poster/artwork-poster.txt carries the fingerprint, the font hashes and
the three library versions of the build that made the committed image, and
--check names the component that differs when a mismatch appears.
The fonts are vendored at tools/artwork/fonts/ rather than media/fonts/: media/
is scoped by the standard to SHIPPED assets and these never ship, being
packaged out by .pkgmeta's `tools` ignore. Recorded as ARTWORK-06 with the
rationale, and repeated in the module docstring, because the risk is not that
the choice is wrong but that a fast read of layout-§3 "fixes" it later.
.pkgmeta ignores media/poster on the same reasoning as the logo renders and the
raw plates -- WoW cannot load a .png at all, so shipping one would add
megabytes to every download for a file the client physically cannot use.
The poster is stamped with the TOC version, which means a version bump stales
it; docs/testing.md, docs/artwork-spec.md and the README all say so. No
mandated gate can detect that staleness -- luacheck walks only Lua and the
harness derives its load list from the TOC and cannot shell out to Python -- so
both --check commands are written down in docs/testing.md ▸ The artwork gate,
explicitly outside the green gate on the precedent testing-§7 sets. The
exposure is ARTWORK-05; it is pre-existing on the catalog side, where
update_catalog.py --check had been documented nowhere.
Doc drift fixed while here: three files claimed the artwork scripts all use
numpy when only artwork_cleaner.py imports it; ARCHITECTURE named a
tools/artwork/import.py that does not exist; testing.md and the LEDGER cited a
tests/perf.lua that is a construct of the standard, not a file in this repo.
Gate: 618 passed / 0 failed, luacheck 0 warnings / 0 errors in 23 files, both
--check commands green, vendor gate empty on both readings and on tests/_kit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EpSVgkkKQ3G781F9YP6Gf
commit 475ff5fcbd61d0edfc63f38f41e973fbc2483ae5
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 3 14:34:46 2026 +0000
feat(artwork): generate a deterministic poster of the bundled set
Closes #14. One PNG showing every artwork that ships, grouped under its
catalog category, embedded in the README and ready for the CurseForge media
tab. tools/artwork/make_poster.py draws it.
It does not walk media/artwork/ itself. It imports update_catalog.py and calls
that script's scan(), sorts by the SORT_KEY that script now exports, and shares
its fatal-problem policy through is_fatal() -- so the poster and the dropdown
are two renderings of one list and cannot disagree about what shipped. Two
walks would have been two chances to drift, and the drift would be invisible: a
poster showing art the addon does not have still looks fine.
Determinism is stated about PIXELS, not file bytes, because only one of those
is ours. Five things are pinned so the pixels cannot move: fonts resolved only
from tools/artwork/fonts/ with no system fallback, the text layout engine
forced to BASIC, every string placed on a whole pixel, the stamp taken from the
TOC version rather than a clock, and every loop running over the sorted rows.
The file's bytes are deflate's output and a zlib-ng build can compress
identical pixels differently, so --check fingerprints the decoded image and a
plain run rewrites nothing when the pixels already match. A contributor on a
different toolchain therefore cannot churn 2 MB of visually identical binary
into the history. Verified across Pillow 10.2.0/FreeType 2.13.2 and Pillow
11.3.0/FreeType 2.13.3 -- same fingerprint, --check green on both.
That leaves one input a script cannot pin: a FreeType whose glyph
rasterization changes would move the pixels themselves. It is recorded instead.
media/poster/artwork-poster.txt carries the fingerprint, the font hashes and
the three library versions of the build that made the committed image, and
--check names the component that differs when a mismatch appears.
The fonts are vendored at tools/artwork/fonts/ rather than media/fonts/: media/
is scoped by the standard to SHIPPED assets and these never ship, being
packaged out by .pkgmeta's `tools` ignore. Recorded as ARTWORK-06 with the
rationale, and repeated in the module docstring, because the risk is not that
the choice is wrong but that a fast read of layout-§3 "fixes" it later.
.pkgmeta ignores media/poster on the same reasoning as the logo renders and the
raw plates -- WoW cannot load a .png at all, so shipping one would add
megabytes to every download for a file the client physically cannot use.
The poster is stamped with the TOC version, which means a version bump stales
it; docs/testing.md, docs/artwork-spec.md and the README all say so. No
mandated gate can detect that staleness -- luacheck walks only Lua and the
harness derives its load list from the TOC and cannot shell out to Python -- so
both --check commands are written down in docs/testing.md ▸ The artwork gate,
explicitly outside the green gate on the precedent testing-§7 sets. The
exposure is ARTWORK-05; it is pre-existing on the catalog side, where
update_catalog.py --check had been documented nowhere.
Doc drift fixed while here: three files claimed the artwork scripts all use
numpy when only artwork_cleaner.py imports it; ARCHITECTURE named a
tools/artwork/import.py that does not exist; testing.md and the LEDGER cited a
tests/perf.lua that is a construct of the standard, not a file in this repo.
Gate: 618 passed / 0 failed, luacheck 0 warnings / 0 errors in 23 files, both
--check commands green, vendor gate empty on both readings and on tests/_kit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EpSVgkkKQ3G781F9YP6Gf
commit 6c793515cefed256259f5d09d10b1ff4a7196b51
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 3 09:21:17 2026 +0000
fix(panels): stamp the frame name at create, so a rename keeps it
Closes #6, #7 and #3. The first two were one root cause wearing two faces.
Canvas.BuildSpec derived frameName from rec.name on every render, so a rename
produced a different frame name -- and a frame's name is immutable after
CreateFrame. Canvas:Render therefore had to retire the old frame and acquire a
new one, which orphaned every external anchor pointed at the old global (#7)
and parked a frame in the pool that nothing would ever claim again (#6). One
permanently-live frame per distinct name the user typed.
The frame name is now stamped onto the record at create as rec.frameName and
never recomputed. That makes it identity, alongside id and name:
* Registry.FrameName reads the stored field, falling back to the derived
name for records that predate it
* R:Reset preserves it through the template rewrite; COPY_EXCLUDED keeps
CopyFrom from handing one panel another's global; create stamps it after
the overrides loop, so no caller can supply one
* R:Rename does not touch it -- and its frame-name collision check is GONE,
because a rename claims no new global. Renaming to a name that merely
slugs the same as another panel ("Chat-BG" while "Chat BG" exists) is now
allowed, where it used to be refused.
Schema v1 -> v2 (NS:RunMigrations) stamps every existing panel by deriving from
its name, which reproduces exactly the global the previous build already gave
that frame, so the upgrade moves nobody's anchors. R.Sanitize fills the field
the same way for a record arriving by another route -- an imported profile, a
test -- which is what covers profiles other than the one active at migration.
#3 was independent and small: R:Reset rewrites a record from C.PANEL_TEMPLATE,
which deliberately carries no preview marker, so resetting a placeholder
stripped its marker and promoted it to a permanent panel that survived the next
sweep -- the last path by which test mode could leave litter in a real layout.
Reset now refuses a marked record with a reason the caller prints, per the
issue's own recommendation. CopyFrom needed no guard; COPY_EXCLUDED already
held the marker. A test walks every write seam in sequence to pin that the
marker cannot be lost.
DELIBERATE CONTRACT CHANGE, and the reason both issues' acceptance criteria
allowed for it: after a rename the frame name no longer matches the panel's
name and stops being derivable from it. Predictability at create is the lesser
half of that contract -- you look the name up once, when you wire something to
the panel, and the name box's tooltip is where. Anchors that silently stop
tracking days later are the worse failure. A second consequence, recorded as a
known limitation: creating a panel can be refused by a name that looks free,
because a panel created as "Alpha" and since renamed still holds
PanelMaster_Panel_Alpha. The refusal names the holder.
Docs synced alongside: both old Known-limitations bullets deleted and replaced
by the two the new behavior actually has; ARCHITECTURE's "Frame names and the
pool" rewritten around identity rather than derivation; the panel record and
its identity fields; the preview-marker section; README anchoring, settings
table and troubleshooting; smoke tests 2, 5c/5d/5e, 11b. Three items of
pre-existing drift fixed while there: the tests badge said 605 against a
609-test suite, and three smoke steps described a "Frame name" line in the
editor that has been a tooltip on the Panel name box for some time.
Gate: 618 passed / 0 failed, luacheck 0 warnings / 0 errors in 23 files.
Vendor gate empty on both readings and on tests/_kit. docs/test-cases.md
regenerated and in sync at 618, so the [tests] badge moves 605 -> 618.
Not verified in a live client: that a real frame's GetName() survives a rename
and a /reload. That is smoke tests 11b.5-8, which need a session.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EpSVgkkKQ3G781F9YP6Gf
commit 6c793515cefed256259f5d09d10b1ff4a7196b51
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Mon Aug 3 09:21:17 2026 +0000
fix(panels): stamp the frame name at create, so a rename keeps it
Closes #6, #7 and #3. The first two were one root cause wearing two faces.
Canvas.BuildSpec derived frameName from rec.name on every render, so a rename
produced a different frame name -- and a frame's name is immutable after
CreateFrame. Canvas:Render therefore had to retire the old frame and acquire a
new one, which orphaned every external anchor pointed at the old global (#7)
and parked a frame in the pool that nothing would ever claim again (#6). One
permanently-live frame per distinct name the user typed.
The frame name is now stamped onto the record at create as rec.frameName and
never recomputed. That makes it identity, alongside id and name:
* Registry.FrameName reads the stored field, falling back to the derived
name for records that predate it
* R:Reset preserves it through the template rewrite; COPY_EXCLUDED keeps
CopyFrom from handing one panel another's global; create stamps it after
the overrides loop, so no caller can supply one
* R:Rename does not touch it -- and its frame-name collision check is GONE,
because a rename claims no new global. Renaming to a name that merely
slugs the same as another panel ("Chat-BG" while "Chat BG" exists) is now
allowed, where it used to be refused.
Schema v1 -> v2 (NS:RunMigrations) stamps every existing panel by deriving from
its name, which reproduces exactly the global the previous build already gave
that frame, so the upgrade moves nobody's anchors. R.Sanitize fills the field
the same way for a record arriving by another route -- an imported profile, a
test -- which is what covers profiles other than the one active at migration.
#3 was independent and small: R:Reset rewrites a record from C.PANEL_TEMPLATE,
which deliberately carries no preview marker, so resetting a placeholder
stripped its marker and promoted it to a permanent panel that survived the next
sweep -- the last path by which test mode could leave litter in a real layout.
Reset now refuses a marked record with a reason the caller prints, per the
issue's own recommendation. CopyFrom needed no guard; COPY_EXCLUDED already
held the marker. A test walks every write seam in sequence to pin that the
marker cannot be lost.
DELIBERATE CONTRACT CHANGE, and the reason both issues' acceptance criteria
allowed for it: after a rename the frame name no longer matches the panel's
name and stops being derivable from it. Predictability at create is the lesser
half of that contract -- you look the name up once, when you wire something to
the panel, and the name box's tooltip is where. Anchors that silently stop
tracking days later are the worse failure. A second consequence, recorded as a
known limitation: creating a panel can be refused by a name that looks free,
because a panel created as "Alpha" and since renamed still holds
PanelMaster_Panel_Alpha. The refusal names the holder.
Docs synced alongside: both old Known-limitations bullets deleted and replaced
by the two the new behavior actually has; ARCHITECTURE's "Frame names and the
pool" rewritten around identity rather than derivation; the panel record and
its identity fields; the preview-marker section; README anchoring, settings
table and troubleshooting; smoke tests 2, 5c/5d/5e, 11b. Three items of
pre-existing drift fixed while there: the tests badge said 605 against a
609-test suite, and three smoke steps described a "Frame name" line in the
editor that has been a tooltip on the Panel name box for some time.
Gate: 618 passed / 0 failed, luacheck 0 warnings / 0 errors in 23 files.
Vendor gate empty on both readings and on tests/_kit. docs/test-cases.md
regenerated and in sync at 618, so the [tests] badge moves 605 -> 618.
Not verified in a live client: that a real frame's GetName() survives a rename
and a /reload. That is smoke tests 11b.5-8, which need a session.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EpSVgkkKQ3G781F9YP6Gf
commit ed850dace980e874d7a233d2869f75e886d58e14
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 18:33:30 2026 +0000
feat(artwork): tint every piece, add Desaturate and Blend mode
The artwork color control now applies to all 101 bundled pieces instead of
almost none of them. It used to exist only while the selected art was
"tintable", and every piece the new pipeline generates is full-color — so in
practice the control appeared for exactly two selections, None and Custom path.
It read as a setting that did not exist.
Removing the gate on its own would have been worse than leaving it. Tinting
finished art is a multiply, so a blue tint on a gold-and-crimson crest returns
muddy brown rather than blue, which is precisely what the old force-white
branch was protecting against. DESATURATE is what makes it work: it drains the
art to grayscale in hardware first, so the tint multiplies against neutral gray
and comes back as a clean, saturated version of the chosen color. The default
tint is white — a no-op — so nothing changes for anyone who has not picked one.
BLEND MODE returns as a two-item setting, Normal and Glow. It was removed once
on the reasoning that MOD needs transparency to be WHITE while ADD needs it
BLACK, and one texture cannot satisfy both. That stalemate is over: every plate
artwork_cleaner.py produces normalizes transparent pixels to (0,0,0,0), so black
is the answer and ADD is correct by construction. The other three modes stay
out — MOD is now permanently wrong, DISABLE ignores alpha outright, and ALPHAKEY
hard-edges the art while defeating the opacity slider. Blizzard's UI has used
only BLEND and ADD since 2.2.0, and WeakAuras ships exactly these two.
`tintable` is therefore gone entirely: from the generator, the catalog rows, the
tests and the docs. Its only consumer was the branch this removes.
The editor's artwork section is re-laid-out, which also fixes a wrap. The color
pair emits TWO half-width widgets — the swatch and its Class color companion —
so sharing a row with the opacity slider put three half-width controls on one
line and the third wrapped underneath. It now has a row to itself. Artwork and
Custom texture path go full width, the latter because catalog labels carry their
whole derived category and truncate to nothing at half width. And the pair being
unconditional retires makeArtColorRow, which released and rebuilt widgets and
re-laid-out the scroll frame whenever you paged between tintable and non-tintable
art, shoving every row below it up and down.
Two unrelated fixes ride along, both reported from the game:
Panel dropdowns sort by name. Registry:All() returns creation order, which is
right for storage and arbitrary for a list you have to find a name in. Sorted on
a COPY at the two display sites — All() hands back the live saved-variables
array, so sorting in place would reorder the user's file and shift the index
Registry:FindByName returns alongside its record.
A new panel's accent color defaults to white rather than the BenikUI green, which
looked deliberate without anyone having chosen it. Class color is still on by
default, so the white shows once that is unticked.
Gate: 609 pass, luacheck 0/0, catalog in sync with 101 files on disk.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit cb334493a29cb19118015f1eb9b626945d1066a2
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 15:05:20 2026 +0000
Re-vendor LibKa0s v1.5.0 (DebugLog minor 7)
DebugLog's gated sink now pcalls its string.format, so a combat secret logged
through a numeric slot lands as a line instead of raising inside the sink. A
satisfiable format renders byte-for-byte as at minor 6, so nothing this addon
prints changes.
diff -r and diff -r --strip-trailing-cr against ../LibKa0s/LibKa0s are both
empty. Suite unchanged at 607 passed, 0 failed; luacheck 0/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PXMksCakSxnoNEcxSgDK47
commit 3a52a603338ccb220412dc6a24a8a473bf12d284
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 15:02:55 2026 +0000
fix(artwork): delete wiki_import.py, left behind by the rename
The rewrite created artwork_cleaner.py as a new file and removed import.py,
plate.py and manifest.tsv — but never removed wiki_import.py, the very file
artwork_cleaner.py replaces. It shipped as a broken duplicate: it still does
`from plate import ...`, and plate.py is gone, so it raises ModuleNotFoundError
on any invocation.
Nothing referenced it. Caught by listing tools/artwork/ after the merge rather
than by anything automated, which is the gap worth noting: the suite gates Lua,
and a dead Python file in tools/ is invisible to it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit 1d36934a3d2c707dbc9bc5c25f302b05c896014f
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 15:01:38 2026 +0000
refactor(artwork): one pipeline, driven by the folder tree
Replaces two importers and a manifest with two scripts and no configuration.
The generated-plate route is gone entirely — `import.py`, its white-on-black and
magenta-chroma keys, and every AI-generation prompt in the docs. What remains
handles art that already exists, whatever its source.
`artwork_cleaner.py` converts, in two modes: `--single FILE` writes
FILE-panelmaster.tga beside its source, `--batch SRC DST` mirrors a whole tree.
`update_catalog.py` then reads media/artwork/ and rewrites the catalog block in
modules/Artwork.lua from what is actually on disk.
The manifest is gone because the folder tree can carry everything it did.
media/artwork/faction/expansion/12-midnight/harati.tga yields the id, the file
stem, the label "Harati" and the category "Faction -> Expansion -> 12 Midnight".
Renaming a file renames it in the UI; moving it regroups it. There is nothing to
keep in sync because there is only one place the answer lives.
That makes categories DYNAMIC, which is the change that reaches into the addon.
C.ARTWORK_CATEGORIES and its membership set are deleted, and Artwork.List now
sorts categories alphabetically rather than against a declared rank. Alphabetical
is not a fallback: a child's category string starts with its parent's, so folders
stay adjacent to their children for free, and nothing needs editing when a folder
appears.
`tintable` is measured rather than declared. Art whose opaque pixels are both
unsaturated and near-white carries its shape in the alpha channel and takes a
tint cleanly; anything else has its own palette and forces the tint to white.
Both conditions are required — mid-gray line art is unsaturated but not tintable,
because gray times a tint returns a dark desaturated tint instead of the tint.
`credit` is dropped from the catalog row; attribution lives in the README.
Two fixes prompted by running it in anger. The cleaner narrated nothing while the
CPU upscaler worked, which is indistinguishable from a hang — every stage now
prints as it starts, with timings, and the upscaler's own progress is streamed
rather than swallowed. And a scale requirement just above a power of four chained
an entire extra pass: a 184x255 source needing 4.02x built a 2944x4080
intermediate, twelve million pixels, nearly all discarded on the way back to
1024. One pass already reaches 736x1020, four pixels short, so the chain now
stops when Lanczos can cover the remainder within 5%. That file went from 56.5s
to 4.6s for the same output.
modules/Artwork.lua's catalog is now a generated block between markers; the prose
and logic around it stay hand-owned. tests/test_artwork.lua moves off the deleted
seed row onto a real shipped one, and the assertions that encoded the old
declared-category ordering are rewritten for the derived one.
Gate: 607 pass, luacheck 0/0, catalog in sync with 101 files on disk.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit 1d36934a3d2c707dbc9bc5c25f302b05c896014f
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 15:01:38 2026 +0000
refactor(artwork): one pipeline, driven by the folder tree
Replaces two importers and a manifest with two scripts and no configuration.
The generated-plate route is gone entirely — `import.py`, its white-on-black and
magenta-chroma keys, and every AI-generation prompt in the docs. What remains
handles art that already exists, whatever its source.
`artwork_cleaner.py` converts, in two modes: `--single FILE` writes
FILE-panelmaster.tga beside its source, `--batch SRC DST` mirrors a whole tree.
`update_catalog.py` then reads media/artwork/ and rewrites the catalog block in
modules/Artwork.lua from what is actually on disk.
The manifest is gone because the folder tree can carry everything it did.
media/artwork/faction/expansion/12-midnight/harati.tga yields the id, the file
stem, the label "Harati" and the category "Faction -> Expansion -> 12 Midnight".
Renaming a file renames it in the UI; moving it regroups it. There is nothing to
keep in sync because there is only one place the answer lives.
That makes categories DYNAMIC, which is the change that reaches into the addon.
C.ARTWORK_CATEGORIES and its membership set are deleted, and Artwork.List now
sorts categories alphabetically rather than against a declared rank. Alphabetical
is not a fallback: a child's category string starts with its parent's, so folders
stay adjacent to their children for free, and nothing needs editing when a folder
appears.
`tintable` is measured rather than declared. Art whose opaque pixels are both
unsaturated and near-white carries its shape in the alpha channel and takes a
tint cleanly; anything else has its own palette and forces the tint to white.
Both conditions are required — mid-gray line art is unsaturated but not tintable,
because gray times a tint returns a dark desaturated tint instead of the tint.
`credit` is dropped from the catalog row; attribution lives in the README.
Two fixes prompted by running it in anger. The cleaner narrated nothing while the
CPU upscaler worked, which is indistinguishable from a hang — every stage now
prints as it starts, with timings, and the upscaler's own progress is streamed
rather than swallowed. And a scale requirement just above a power of four chained
an entire extra pass: a 184x255 source needing 4.02x built a 2944x4080
intermediate, twelve million pixels, nearly all discarded on the way back to
1024. One pass already reaches 736x1020, four pixels short, so the chain now
stops when Lanczos can cover the remainder within 5%. That file went from 56.5s
to 4.6s for the same output.
modules/Artwork.lua's catalog is now a generated block between markers; the prose
and logic around it stay hand-owned. tests/test_artwork.lua moves off the deleted
seed row onto a real shipped one, and the assertions that encoded the old
declared-category ordering are rewritten for the derived one.
Gate: 607 pass, luacheck 0/0, catalog in sync with 101 files on disk.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit 4dcddf013a2049bba0e11abe7237e44fbf5639fc
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 13:40:48 2026 +0000
feat(artwork): the converted wiki set, in a source-mirroring layout
104 imported plates land under media/artwork/, and the tree now mirrors the
download folder they came from — media/artwork/class/warrior.tga against
raw/class/Warrior_Crest.png — so the two can be diffed and read as the same
thing. The four originally generated plates and their raw sources are removed.
This replaces grouping by catalog category, which put the same art in a
different place from its source and made the two trees impossible to compare.
The consequence is worth stating plainly: **directory and `category` are now
unrelated**. The directory answers "where did this come from"; the row's
`category` answers "which dropdown group does this appear under". A piece filed
at faction/expansion/12-midnight/ is a Factions row. Nothing derives one from
the other.
`wiki_import.py` emits the new layout rather than the old one, via
`output_stem()` reading the manifest's own `source` column — without which the
next run would have silently recreated the category folders and undone this.
`--emit-catalog` follows, so `file` now carries nested backslash segments
(`faction\expansion\12-midnight\harati`), which `modules/Artwork.lua:128`
concatenates verbatim exactly as before. `--only` additionally accepts the
on-disk stem or a bare subject, since the catalog id and the file path no longer
coincide. All 104 emitted rows were parsed with `lua` and checked against disk:
0 structural problems, 0 missing files.
KNOWN RED: `lua tests/run.lua` is 606/607. `modules/Artwork.lua` still carries
the four original catalog rows, whose files this commit deletes, so the
path-existence assertion fails. Swapping in the 104 new rows touches 43
references in tests/test_artwork.lua — which uses `runic-sigil-bw` as its
fixture throughout and asserts it is the tintable, square seed row, a property
none of the 104 has — so it is a deliberate follow-up rather than a rename.
`luacheck .` remains 0/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit c914b5b50e19feb338c0d04886ea05d6b18fd4ff
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 12:42:59 2026 +0000
feat(artwork): a batch import pipeline for art that already has alpha
`import.py` converts one generated plate that has NO alpha and must have some
derived — by luminance, or by a magenta chroma key. Downloaded art is the
opposite problem: 104 of 107 Warcraft Wiki sources arrive WITH a real alpha
channel, and running `to_tintable` or `chroma_key` over them would throw that
alpha away and re-derive a worse one from brightness. Hence a sibling script
rather than a flag, sharing only its downstream half via the new `plate.py`.
Output moves to `media/artwork/<category>/<subject>.tga`, 1024x1024 letterboxed,
32-bit RLE. No addon code changes: `modules/Artwork.lua:128` already composes
`C.ARTWORK_PATH_PREFIX .. row.file .. ".tga"`, so a `file` stem of
`classes\warrior` resolves with no resolver change, and the existing
path-existence test validates every row for free.
The stage that matters most is `solidify`. Sources disagree about what RGB sits
under a transparent pixel — (0,0,0,0) on most, (255,255,255,0) across the
MajorFactionsIcons set, (217,182,103,0) on Human_Crest.png. Nothing renders it,
so nothing complains; but an upscaler's kernel samples those pixels, and a
white-under-alpha source grows a white halo along every emblem edge.
Manifest-driven because catalog ids are written into saved variables and can
never be renamed. Names are reviewed as data before any file is written, and
every drop the scaffold makes on the author's behalf is printed.
Measured, not assumed: the vendored upscaler runs on software Vulkan (llvmpipe)
and preserves alpha; the full 104-asset batch is ~33 minutes, not the 1-3 hours
first estimated. One model for the whole catalog rather than per-scale
selection — the latter measured 1.7x faster and was rejected because these
assets sit adjacent in a dropdown and three models sharpen differently.
Hardened against silent failure, which is what a long unattended run cannot
afford. A `--scaffold` at a mistyped path used to overwrite the manifest with a
bare header and exit 0, destroying every hand-measured erase box; it now
validates, refuses to orphan hand-authored rows, and writes atomically. The
up-to-date check compared source mtime alone, so an edited erase box or a
changed --size left a stale TGA in place and reported success — replaced with a
fingerprint over every input that determines the output bytes.
`import.py` output verified byte-identical across all three conversion paths
before and after the extraction. Three accepted deviations from the Ka0s WoW
Addon Standard recorded in docs/pending/LEDGER.md as ARTWORK-01..04.
No artwork is converted or catalogued by this commit; `media/` is untouched.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit 4abaac4773118e3928f1665e210466e083b19c12
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 10:18:28 2026 +0000
test: gate the vendored payload against the LibKa0s tag the README names
This repo carried LibKa0s and its test kit with nothing checking that either
matched a release. It did not stay theoretical: the kit's first numbered
revision was vendored here straight off LibKa0s master, while that revision was
in no tag, and the README provenance line went on naming a release the bytes no
longer matched. Nothing here noticed. prettychat was the only repo in the
collection with this check, it refused the same vendor, and LibKa0s v1.4.0 was
cut so the revision could be taken from a tag.
Two cases, ported from prettychat's tests/test_harness.lua: libs/LibKa0s and
tests/_kit must each match `git show <tag>:...` in the sibling library repo,
file set first and then byte for byte, README.md included — the file that
actually diverged in this collection was a README.
The tag comes from THIS README's provenance line rather than a constant, so a
line and a payload that disagree is the failure rather than something the check
is blind to. It compares against the released tag, never the sibling's working
tree: LibKa0s can be mid-release, and reddening this suite for upstream work
this addon has not adopted invites a "fix" that ships bytes existing at no ref.
The one place it may go quiet is a missing sibling checkout, and that is said in
the case name instead of hidden. Verified both ways before landing: a one-line
edit to a vendored file fails it by name, and a provenance line rolled back to
v1.3.1 fails it too.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01573xwgHgpvTJwWGQHqGogs
commit 4348bc5ae46b70d9ec7fd39a392ed228b3d77ac8
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 10:05:49 2026 +0000
docs: name LibKa0s v1.4.0 in the provenance line
The vendored library is unchanged — v1.4.0's payload is byte-identical to
v1.3.1 and no LibStub minor moved. What moved is tests/_kit, which took the
kit's first numbered revision before that revision was in any release, leaving
this line naming a tag its vendored tree no longer matched.
The line is not decoration: prettychat's harness resolves the tag it names and
asserts both vendored trees against it file by file, which is how the drift was
found. Pointing it at v1.4.0 makes it true again.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01573xwgHgpvTJwWGQHqGogs
commit 6754148b6ed9d3678b1f8b2a0dc7f5ab90c8f2a8
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 08:51:12 2026 +0000
chore(tests): re-vendor the test kit at revision 1
The shared harness under tests/_kit/ now carries a revision number —
Kit.VERSION at the top of framework.lua, reaching suites as KIT_VERSION — so
this addon can name which copy of the kit it is running rather than leaving
that answerable only by diffing against LibKa0s at the right commit.
Nothing in this addon's own code or tests changes, and nothing negotiates on
the number: the kit is not a LibStub library and the vendoring gate is still
byte-identity. Its full surface is documented at
https://github.com/tusharsaxena/LibKa0s/tree/master/docs/api/testkit
Vendored copy — never edit tests/_kit/ here. A kit problem is a finding to fix
in LibKa0s and re-vendor.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01573xwgHgpvTJwWGQHqGogs
commit 1a88d3b5532bf7e53f0f3ac2e1c5fc33d05af1da
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 06:42:48 2026 +0000
docs: delete the scaffolding pack; it is fetched at runtime now
WowAddonStandards v2.17.0 removes docs/agent-context.md from the canonical
docs/ set (documentation-§3, the CRITICAL subsection) and makes shipping it
anti-pattern #49. The file here was the Ka0s Addon Context Pack v2.7.0,
dropped in at scaffold time.
The reason it has to go rather than be maintained: it is scaffolding, and
every question it answers -- kickstart walkthrough, starter tree, starter
snippets, "definition of done for v0.1.0" -- was answered the moment this
addon existed. Worse, it is the file CLAUDE.md sent every session to FIRST,
so it is loaded as working context and its instructions get followed. A
stale copy does not go quiet, it actively misleads: this one still carried
a hand-written debug console, a hand-written slash dispatcher and a
hand-written harness in an addon that replaced all three with LibKa0s two
commits ago. No gate can see that -- no test covers a doc and lint does not
read prose. The pack is one curl away and always current.
Migrated OUT before deleting, one item, the only thing in the file that was
genuinely about THIS repo and not already recorded elsewhere:
* the shared test kit, into docs/testing.md ▸ How the harness works.
tests/_kit/ is vendored from ../LibKa0s/testkit and owns the registry,
the assertions, the --list renderer and the loader; run.lua is a thin
consumer deriving its load order from the TOC; wow_mock.lua EXTENDS
_kit/mock_base.lua rather than replacing it, because the base is the
only source of a LibStub with a real NewLibrary (without which no
vendored major registers headlessly) and of a fireable AceGUI. That
section still listed a tests/loader.lua deleted in the adoption, so the
file tree is corrected in the same edit.
Everything else was already covered and is dropped rather than duplicated:
the LibKa0s adoption, the never-edit-libs/ rule and the seam load order are
in CLAUDE.md and ARCHITECTURE.md ▸ The LibKa0s seams; the green gate and the
vendor gate are in docs/testing.md. The rest is the pack's own generic
content -- Ka0s identity facts, the layout boilerplate, the TOC/entry/
Compat/Locale/Database/Settings/luacheckrc/pkgmeta snippets, the hard-rules
and forbidden-pattern lists that restate the standard verbatim, the v0.1.0
definition of done, the patterns table and the skills list -- deleted
outright, which is what documentation-§3 requires.
References fixed: the CLAUDE.md pointer list (documentation-§2 item 4 now
forbids naming it), ARCHITECTURE.md's "read this first" line, and
tests/test_spelling.lua's authored-doc list, which scans a hardcoded set and
would have gone red on the missing file. The frozen dated bundles under
docs/audits/, docs/reviews/ and the LEDGER's DOC-08 row still name it and
are deliberately untouched: they are records of what was true on a past day.
-620 lines.
Gate: 605 passed / 0 failed, luacheck 0 warnings / 0 errors in 23 files.
Suite count unchanged and docs/test-cases.md still in sync at 605, so the
[tests] badge does not move.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit 06fd61272b6a7fb03a95b605fd959b499fa8f5c5
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 06:29:01 2026 +0000
docs(artwork): tell the generator to start from scratch, not to edit a reference
Three prompt refinements to the asset-authoring spec, carried over from
working with the generators in practice:
* "Generate a new image from scratch" up front on both plate prompts.
Without it, handing a service a reference image gets an EDIT of that
image rather than a new emblem in the same language -- which fails the
B&W plate outright, since the whole point is a clean alpha key.
* "Use the attached image as a reference (DO NOT COPY)" where a
reference is being supplied, so the style transfers and the subject
does not.
* the third prompt's [SUBJECT] placeholder drops its inline examples, so
all three read identically and the substitution is the only thing a
contributor changes.
Three mechanical slips fixed in passing, not rewording: a missing
apostrophe in "dont", an unbalanced "DO NOT COPY)", and a missing blank
quote line that would have rendered the reference line and the Style block
as one paragraph in the third prompt where the other two separate them.
Docs only; no code, no assets. This change predates the LibKa0s adoption
and was deliberately kept out of it.
Gate: 605 passed / 0 failed, luacheck 0 warnings / 0 errors in 23 files.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit c124a247093e87d3a140757ae8321afa3cd3b465
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 06:20:21 2026 +0000
docs: point the agent brief at LibKa0s instead of at hand-built copies
docs/agent-context.md is the file CLAUDE.md sends every session to first,
and it had zero mentions of LibKa0s. An agent following it would hand-write
the debug console, the slash dispatcher, the options shell and the harness
loader that the adoption just deleted -- which is the most expensive kind
of doc drift this repo can carry.
* a new "The shared library" section: what is vendored, which majors are
adopted, and the three rules with no exceptions (never edit libs/ or
tests/_kit/, re-vendor whole-folder, every seam degrades through the
shared NS.LIBKA0S_MISSING clause).
* the starter TOC gains the LibKa0s line with the ordering reason, and
the core/ and settings/ blocks gain the three seam files.
* four "patterns to reproduce" rows now say the library owns them rather
than describing how to rebuild them: the console, the harness, the lazy
Defaults button and the lazy first-OnShow body.
* two orphaned references cleared -- tests/loader.lua (deleted in favour
of the kit's) and ensureDefaultsButton(panel) (now O.EnsureDefaultsButton).
CLAUDE.md gains the vendoring rule and names the vendor gate as a third
check, because neither `lua tests/run.lua` nor `luacheck .` can see a stale
libs/ -- the library's own suite passes against the library, and this
addon's passes against a stale copy that still works.
No code touched. README.md needed nothing: its COMMANDS <-> documented-verb
parity is 18/18 in both directions, and it carries no module map or file
list to go stale. No dead exports found.
Gate: 605 passed / 0 failed, luacheck 0 warnings / 0 errors in 23 files,
both halves of the vendor diff empty.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit c124a247093e87d3a140757ae8321afa3cd3b465
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 06:20:21 2026 +0000
docs: point the agent brief at LibKa0s instead of at hand-built copies
docs/agent-context.md is the file CLAUDE.md sends every session to first,
and it had zero mentions of LibKa0s. An agent following it would hand-write
the debug console, the slash dispatcher, the options shell and the harness
loader that the adoption just deleted -- which is the most expensive kind
of doc drift this repo can carry.
* a new "The shared library" section: what is vendored, which majors are
adopted, and the three rules with no exceptions (never edit libs/ or
tests/_kit/, re-vendor whole-folder, every seam degrades through the
shared NS.LIBKA0S_MISSING clause).
* the starter TOC gains the LibKa0s line with the ordering reason, and
the core/ and settings/ blocks gain the three seam files.
* four "patterns to reproduce" rows now say the library owns them rather
than describing how to rebuild them: the console, the harness, the lazy
Defaults button and the lazy first-OnShow body.
* two orphaned references cleared -- tests/loader.lua (deleted in favour
of the kit's) and ensureDefaultsButton(panel) (now O.EnsureDefaultsButton).
CLAUDE.md gains the vendoring rule and names the vendor gate as a third
check, because neither `lua tests/run.lua` nor `luacheck .` can see a stale
libs/ -- the library's own suite passes against the library, and this
addon's passes against a stale copy that still works.
No code touched. README.md needed nothing: its COMMANDS <-> documented-verb
parity is 18/18 in both directions, and it carries no module map or file
list to go stale. No dead exports found.
Gate: 605 passed / 0 failed, luacheck 0 warnings / 0 errors in 23 files,
both halves of the vendor diff empty.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit fd7e535b33c2579b8dfccc9d0745519876ff7904
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 06:01:47 2026 +0000
docs(libka0s): the vendor gate, the seam contract, and four smoke sections
Perf is DECLINED (LIBKA0S-31), and the reason is what this addon does
rather than a judgement about thresholds. Perf measures two combat-gated
windows differing only in whether the host is inert; PanelMaster draws
static backdrop panels. Every event it registers is PLAYER_LOGIN,
PLAYER_ENTERING_WORLD or PLAYER_REGEN_ENABLED -- none fires DURING combat
-- and every repaint path is driven by the user having the options panel
open, which options-ui-§2 refuses in combat outright. So every bucket but
one would read 0.000 by construction. The exception is the shared mouseover
ticker: 10 Hz, one MouseIsOver and one SetAlpha per tracked panel, a
per-panel opt-in that is off by default. Buying that one near-noise-floor
figure would mean putting a Perf.suspended check inside the renderer's own
show decision, since the host contract requires visibility be enforced at
the source. Real code in the render path for a diagnostic that cannot
answer a question.
docs/testing.md gains The vendor gate: the four diffs run in pairs, with a
table saying what each of the three possible answers means and what to do.
Neither green gate can see a stale vendored copy -- the library's suite
passes against the library, and this addon's passes against a stale copy
that still works. It also records that luacheck's figure is scoped, not
repo-wide.
docs/ARCHITECTURE.md gains a section on the seams and the load order they
pin, with NS.LIBKA0S_MISSING documented as a CROSS-FILE CONTRACT three
other seams depend on rather than an implementation detail of one file --
including the table of what each seam appends to it.
docs/smoke-tests.md gains §14-§17 for what no headless suite can reach: the
degraded install (rename libs/LibKa0s aside, confirm zero Lua errors, a
complete listing, the notice said exactly once, and the cause clause
matching the other adopters word for word), the L-trap sweep across every
page and both console windows, the nine deliberate rendered changes with a
parity list beside them framed as "nothing moved", and the destructive
path's confirmation on BOTH entry points.
Gate: 605 passed / 0 failed, luacheck 0 warnings / 0 errors in 23 files,
both halves of the vendor diff empty for libs/LibKa0s and tests/_kit, and
the [tests] badge, docs/test-cases.md and the suite all agree at 605.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit ee80646bfeb37bbaafa5bc8d7a4180fcccdd5a7d
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 05:58:35 2026 +0000
feat(libka0s): adopt LibKa0s-Options-1.0 for the settings canvas
settings/OptionsSetup.lua (135 lines) holds the descriptor; NS.Helpers IS
the library instance rather than a wrapper around one (options-ui-§1),
which is what lets settings/Panel.lua decorate it in place. Panel.lua goes
691 -> 492 lines, and a large share of what remains is prose recording what
moved.
Taken outright: CreatePanel (with the header, the breadcrumb and the
Blizzard canvas contract), EnsureDefaultsButton, EnsureScroll,
PatchAlwaysShowScrollbar, Section, AddSpacer, AttachTooltip, ClearScroll,
RenderField, RenderSchema, SetRenderer, RegisterOptionsPage,
CreateOptionsPanel, OpenOptionsPanel, RefreshScalars and lib.LAYOUT.
Four surfaces are knowingly declined with reasons (LIBKA0S-26):
RestoreAllDefaults (this addon's global reset must reach session-only rows
through each row's own set), InlineButtonPair (it owns its row; this
addon's paired buttons go INTO a row someone else is building), LSMValues
and the colour codecs (no schema row here is media-backed or colour-typed).
Two adapters, both wrapped ON THE INSTANCE rather than beside it, and the
distinction is load-bearing: RenderRows resolves RenderField and
EnsureScroll from the instance table at call time, so a plain host helper
is bypassed by every page the flow engine draws. A test caught exactly that
for real -- the dropdown-close hooks landed on the landing page and the
panel editor, and were missing from the one page that has a dropdown.
The registration also gains what it did not have: each page builder is
pcall'd separately, so a raising one costs only itself and is reported by
key rather than killing every page after it; and every page gets
SetRenderer's combat guard, which matters because the Blizzard AddOns
sidebar reaches a panel without going through /pm config.
The schema -> widget -> write path had NO coverage before this and now has
eight cases (LIBKA0S-29). This addon's AceGUI mock returned nil from
Create(), so the whole path was unreachable; the kit's fireable widgets
make it testable, and a mutation immediately found that the General page
rendered through RenderRows with an explicit list and left the descriptor's
rowsForPage never called. Switched to RenderSchema.
One test assertion changed to accommodate a deliberate library behaviour
and is recorded as such (LIBKA0S-28): OnDefault is a forwarder, not an
assignment, so the case moves from identity to behaviour.
Five mutations were run against the new assertions; all five went red with
the expected message once two bad mutations were corrected.
Gate: 605 passed / 0 failed, luacheck 0 warnings / 0 errors in 23 files,
both halves of the vendor diff empty.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit cd660f0487c43d6bf1daa38268b59505923028b3
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 05:50:01 2026 +0000
feat(libka0s): adopt LibKa0s-Slash-1.0 and converge both user-visible surfaces
The dispatcher, the help renderer, the landing-page row formatter, the
schema CLI (list/get/set/reset/resetall/version), the type-aware parser and
FormatKV all move to the library. settings/Slash.lua keeps what the library
has no business owning: NS.COMMANDS, and every PANEL verb -- those act on
registry records rather than schema rows.
NS.COMMANDS flips from keyed {name=,desc=,fn=} to the positional
{name, description, handler} triples the library reads. Eighteen entries,
the dispatcher, the help renderer, the landing page and seven test cases
moved together. The table stays this addon's and is passed in rather than
owned: the landing page renders the same rows, and a library that owned it
would force the options major to resolve the slash major to read it, which
is a real dependency cycle between two majors at load time.
settings/Schema.lua moves to the library's row vocabulary -- type "boolean"
becomes "bool" (6 rows) and `options` becomes `values` (1 row). An unmapped
field here is not an error but something worse: a `set` that answers
"unknown setting type", and a row that will silently vanish from the page
once Options lands.
Both convergences are ADOPTED and recorded (LIBKA0S-22, -23):
* `reset` takes a path. Already true here before the adoption, which is
not the same as "not applicable" -- both verbs now delegate to the
library, no working command was removed, and the one destructive verb
this addon has (`/pm panel deleteall`) is not a schema reset and keeps
its confirmation on both entry points.
* one command-row formatter. settings/Panel.lua's landing page carried a
second one for the same NS.COMMANDS data, and the two had already
drifted: double spaces around the em dash, the dash white-wrapped, the
description bare. Found by following the render path, not by grepping
for a name.
Five rendered changes a user will notice, all pinned by tests and recorded
at LIBKA0S-17: uppercase colour escapes, an em dash in the help header,
two-space indents on `/pm help` rows, two-line parse refusals, and an
out-of-range number that clamps instead of being refused.
Two descriptor adapters, both host-shaped and both closures in the seam
file: groupKey (this schema groups by row.group, the library defaults to
row.page) and parse (the library matches an enum case-sensitively, and
`/pm set settings.defaultStrata low` has always worked here).
The hand-maintained LIST_GROUP_ORDER constant is deleted rather than ported:
it named the three groups in the order they are already declared in, and its
own comment admitted that a name matching nothing would fail invisibly.
Five mutations were run against the new assertions. Four went red as
expected; the fifth found a VACUOUS pre-existing test -- the lower-case enum
case set "low" against a row whose default is already LOW, so it passed
whether the parse adapter existed or not. Rewritten to move off the value
first, and it now catches the mutation.
Gate: 596 passed / 0 failed, luacheck 0 warnings / 0 errors in 22 files,
both halves of the vendor diff empty.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit 51682f31b15dc81cc0b02a43d6a6db817695a14b
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 05:42:54 2026 +0000
feat(libka0s): adopt LibKa0s-DebugLog-1.0 and delete modules/DebugLog.lua
modules/DebugLog.lua (429 lines) deletes outright; core/DebugLogSetup.lua
(208) replaces it. Net -221 lines for the same console.
The swap is faithful by construction rather than by care: both formatters
were ALREADY byte-identical to the library's, MAX_BUFFER was already 500,
and the frame globals the descriptor derives from `name` --
PanelMasterDebugWindow and PanelMasterDebugCopyWindow -- are exactly the two
this addon hardcoded, so nothing anchored to them moves. The 25 cases in
tests/test_debuglog.lua pass unchanged against the library instance.
Two survivors have no library equivalent and stay: NS.DebugBuild (the gated
sink for a call site whose arguments cost something to produce) and
D:Diagnose() (the structured dump verb, which reports what THIS addon
believes is on screen). Diagnose is defined once and attached on both the
present-library and degraded paths, so `/pm debug dump` answers either way.
The console moves from modules/ up into core/: every host reference in the
descriptor is a closure, so nothing resolves at load and no dependency
inverts.
Two user-visible changes, both deliberate (LIBKA0S-11, -12):
* the console wears Core.SKIN -- a flat 1px black edge, a 1px light-gray
inner highlight, a gold title, a gray divider -- instead of the plain
background texture this file painted. As of Core minor 3 the Ka0s window
edge is the library's rather than each host's, so the collection's
consoles read as one suite of addons.
* it wears Core's x rather than the flat "X" drawn here. Pinned through
_frameForTest.titleBarOffsets, which are derived from the button's width
and are the only readable evidence of which factory built it.
And one bug fixed by the adoption, recorded so it is not mistaken for drift
(LIBKA0S-13): the old console synced the settings checkbox inline from
D:Show()/D:Hide(), but the window is in UISpecialFrames, so closing it with
Esc never went through D:Hide and left the checkbox stale. The library's
onVisibilityChanged fires from the frame's own OnShow and OnHide.
DebugLog is one of the three majors that CAN express the L trap, so it gets
a real rendered assertion rather than a tripwire. Four mutations were run
against the new assertions; all four went red with the expected message.
Gate: 591 passed / 0 failed, luacheck 0 warnings / 0 errors in 22 files,
both halves of the vendor diff empty.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit ca2de21423da8eeb483de56f13fd8f92d8f2707a
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Aug 2 05:37:33 2026 +0000
feat(libka0s): adopt the shared test kit and LibKa0s-Core-1.0
Vendors LibKa0s v1.3.1 whole-folder into libs/LibKa0s/ and the shared test
kit into tests/_kit/, then takes the first of the five majors.
The kit comes FIRST, before any library module, for one reason: mock_base.lua
is the only source of a LibStub with a real NewLibrary, and without it no
vendored library can register headlessly at all — every seam would silently
take its degraded path while the suite stayed green.
* tests/wow_mock.lua becomes an EXTENDER over tests/_kit/mock_base.lua
rather than being replaced by it. Ten base behaviours are deliberately
overridden, each named in that file's header with its reason; the two
taken from the base are the LibStub above and a FIREABLE AceGUI, which
replaces a mock whose Create() returned nil and left the whole
schema -> widget -> write path untestable.
* tests/loader.lua is deleted in favour of the kit's, and tests/run.lua
derives the addon's load order from PanelMaster.toc instead of keeping a
second hand-maintained copy of it.
* core/CoreSetup.lua holds the LibKa0s-Core-1.0 seam. The secret-safe guard
and the prefixed chat printer leave core/Util.lua (-34 lines). NS.Print,
NS.Util.print, NS.SafeToString and NS.IsConcatSafe keep their names --
six files take the printer as a file-scope upvalue -- and the AceConsole
reclaim in core/PanelMaster.lua stays load-bearing and correct.
* NS.LIBKA0S_MISSING is the shared cause clause every later seam appends
its own consequence to, set outside the `if not lib` branch because they
read it on both paths.
* Core's window-chrome half is declined: this addon's only standalone
window is the debug console, which LibKa0s-DebugLog-1.0 will draw.
tests/test_libka0s.lua proves the seam is real rather than merely working:
it asserts function IDENTITY against the live library, and drives the
degraded install by loading the addon with libs/LibKa0s absent rather than
by hand-stubbing a branch. Seven mutations were run against the new
assertions and all seven went red with the expected message.
tests/test_harness.lua closes the kit's silently-green hole, where a listed
suite with no file on disk is skipped rather than failed.
Gate: 583 passed / 0 failed, luacheck 0 warnings / 0 errors in 22 files,
and both halves of the vendor diff empty for libs/LibKa0s and tests/_kit.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AHJypyBccFqBfgds4bQBY7
commit c3ebd12adcd00f38b53551462670cf540d13428a
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Jul 31 20:40:00 2026 +0000
docs(pending): track issue numbers in the ledger and close two settled rows
The ledger's dedup rule needs a machine-readable link from a row to the
GitHub issue covering it — without one, an issue found by the next sweep
gets raised a second time alongside the row it already belongs to. Add
the Issue column and backfill all 28 rows from their rationales.
Also record the untriaged state in the legend, so a row that was merely
seen can never be misread as a decision, and settle the two git rows
whose deferrals the user has since resolved by hand: GIT-01 (origin is
level with HEAD) and GIT-03 (the artwork paths landed in 47f507a).
ISS-10, the load-on-demand artwork packs, is deferred against issue #10.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012in5BXk49W7V9hKUiGP1WM
commit 50e0073f085586f30f4ae9eb6af5afb1c881b7a7
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Jul 31 20:22:46 2026 +0000
chore: drop the artwork generator; art is authored by hand now
Artwork is authored outside the repo from here on, so the procedural
Pillow generator has no remaining caller. tools/artwork/import.py is
now the only artwork tool, and every doc that pointed at generate.py
points at the importer instead.
The frozen design spec keeps its generator section, marked superseded,
rather than being rewritten -- it is the record of what shipped on the
feat/panel-artwork branch, not instructions.
media/artwork/runic-sigil-bw.tga stays in the catalog. It was the one
asset the generator produced and it has no plate under
media/artwork/raw/, so it is no longer re-derivable from the repo.
Also retargets the GIT-01 ledger locator at origin/master after the
default branch was renamed from main. The evidence hash is unchanged:
the Source column is a locator, not the hashed evidence text.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012in5BXk49W7V9hKUiGP1WM
commit 47f507acecb994983b445fd9029556fc1d142e72
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Jul 31 19:45:10 2026 +0000
feat(artwork): draw per-panel artwork inside a panel's bounds
Closes #9.
A panel can carry a picture as well as a color, from a bundled catalog or a
texture path of the user's own. Off by default (artTexture "None"), so no
existing panel or profile changes appearance on upgrade.
modules/Artwork.lua owns the catalog and BuildArtSpec, the pure record-to-
geometry function. It touches no frames and calls no WoW API, which is what
makes all five fill types verifiable headlessly against resize -- the part of
this most likely to be wrong and least visible when it is.
Quarter turns transpose the art's effective dimensions, not just its texture
coordinates. Rotating only the UV quad squashed STATIC, FIT, FILL and TILE at
90 and 270 degrees; square art hides it entirely, so the fill matrix now runs
wide and tall art against wide and tall panels at every rotation.
The background fill moves onto its own child frame so artwork can be drawn
behind it -- a child frame always draws above its parent's textures. Two
consequences had to be handled: frame level outranks draw layer, so the fill
began covering the unlock outline (the overlay now has its own frame above the
whole ladder), and a panel's footprint grew from three rungs to eight, so panel
levels are strided to stop adjacent ones interleaving.
Blend mode was built and then removed. DISABLE ignores alpha, so art defined by
its alpha channel can only ever draw as a rectangle; MOD needs transparent
regions white while ADD needs them black, and one texture cannot be both. Two
of five options could only produce bug reports, so the setting is gone and
artwork always draws with normal transparency.
564 tests pass (79 new), luacheck clean, in-client smoke tests in
docs/smoke-tests.md 5e-5e-6 pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153GTF527WasaoCrontHQ1d
commit e6cc65e62a345538d3d5b1c9648d15abd392b0bd
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Jul 31 08:50:13 2026 +0000
docs(pending): record the pending-audit decision ledger
Adds the ledger /wow-addon:pending-audit maintains, so a settled item stops
re-surfacing on every sweep. Rows are matched on ID plus an evidence hash, so
editing the underlying comment, plan row or issue title correctly re-opens the
question.
Covers the 2026-07-31 sweeps: three limitations filed as issues #6-#8, the
release-blocked audit steps and publishing deviations, the deferred
localization pass, and the panel-artwork issue #9.
Decisions are written as marker plus word (green done, blue wont-do, yellow
deferred) with the legend in the file itself — the word is the data so it
survives grep and a screen reader, the marker is the affordance.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153GTF527WasaoCrontHQ1d
commit 224e945cbda1dba399f93ac5782bbe41ecef673c
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Jul 31 08:17:36 2026 +0000
refactor(debug): defer expensive log arguments past the sink's gate
C-08 removed the restated `if NS.State.debug` gate from fourteen call
sites but left two, because their arguments are not free to produce:
Registry:Set wrapped R.FormatField (a string.format on a seam that fires
on every field write) and Unlock:SetPanelUnlocked wrapped Registry:Get
(an O(n) scan). Both guarded the arguments, not the sink — so the fix was
never a second gate, it was a way to defer the arguments.
NS.DebugBuild(tag, fmt, build, ...) calls `build` only after NS.Debug's
own gate. Both sites now use it with a plain file-local builder
(describeWrite, describeLock), so `grep -rn "NS.State.debug"` outside
modules/DebugLog.lua is empty and C-08's exit criterion is met literally
rather than by documented exception.
The builder is a plain function reference with its arguments passed
unbound, deliberately not a closure. A closure capturing upvalues is
allocated at the CALL SITE, before NS.Debug is entered, so the obvious
`function() return f(x) end` shape would have paid the exact cost the
deferral exists to avoid — strictly worse than the gate it replaced. The
off case is now one call and one boolean test, allocating nothing. A test
pins the unbound-argument shape so the closure form cannot creep back.
The four sites left un-gated (Database migrate, Canvas RenderAll,
Registry ReloadProfile, Panel safeRun) stay un-gated and are ratified as
intended; any can move to DebugBuild if its cost ever changes.
Docs: ARCHITECTURE gains a Localization section and module-map rows for
locales/enUS.lua and locales/PostLoad.lua — the only two loaded files it
never named. NS.L, the key-is-the-string fallback, the "no string routes
through NS.L in 0.1.0" scope decision and the two never-localize rules
(panel names are user data; point/strata are stable tokens) existed only
as comments inside those files.
The review bundle records the decisions taken on 2026-07-31: the two
gates and the four un-gated sites are closed, PanelEditor.lua's 814 LOC
is accepted against C-07's pre-peel ~500 estimate, F-018 is recorded as
did-not-reproduce, and R:DeleteBatch and offsetRange's relPoint keying
are ratified. Deferrals B2/B3/B5 moved to GitHub issues #3, #4 and #5.
Gate: lua tests/run.lua 477/477 (was 474), luacheck . 0/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153GTF527WasaoCrontHQ1d
commit 26b7739596c3e85b8eb8bba7093308b6892b03ee
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Fri Jul 31 07:58:23 2026 +0000
fix(canvas): rotate the accent texture on left and right edges
A LibSharedMedia statusbar texture is authored as a horizontal bar: its
height carries the bevel, its width is the fill direction. Stretched into
a tall thin left or right bar with no rotation, that bevel ran along the
bar's length instead of across its thickness, so a vertical bar read as a
smear while the top and bottom bars looked correct.
The vertical edges now turn the texture a quarter turn, via the
eight-argument SetTexCoord — the only form that can transpose the axes;
the four-argument form crops and flips but cannot rotate. Left and right
take the same rotation rather than mirrored ones, matching top and bottom,
which are also drawn identically.
Two details the tests pinned down:
- the coords are re-applied on every repaint, not once at creation.
SetTexture resets a texture's coords, and frames are pooled, so a frame
reused for another panel would inherit the previous orientation.
- top and bottom set the flat coords explicitly rather than leaving them
alone, so no prior state can leak in.
SetTexCoord was not modeled in tests/wow_mock.lua at all, so texture
orientation was previously untestable; it now records what it was told,
the same way SetColorTexture and SetVertexColor already do.
Docs synced alongside: the README's Edges row mentions the rotation and
its tests badge moves 471 -> 474 (docs/testing.md requires the badge and
test-cases.md move together).
Gate: lua tests/run.lua 474/474, luacheck . 0/0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0153GTF527WasaoCrontHQ1d
This mod has no additional files

