fix(#915): derive theme lists, and classify scripthammer-forge as dark - #928
Merged
Conversation
Six test files each typed a theme list by hand against src/config/themes.ts, and every one missed `scripthammer-forge` — the third house theme, which shipped after they were written. The sharpest instance is color-contrast.spec.ts, the AAA gate. #411 built it after a hand-written FOUR-ROUTE list let a 6.44:1 eyebrow reach main behind 17 green checks; it enumerates its routes for exactly that reason and hand-wrote its themes. Its comment read "Both custom themes covered" — and "Both" was the completeness claim, stale the day forge landed. Same defect, same file, other axis. Measured, not asserted: color-contrast.spec.ts goes from 1,223 to 1,834 cases, with 611 forge cases where there were 0. A REAL BUG FELL OUT, and it is user-visible. `scripthammer-forge` ships base-100 #0f0d0b — near black — and was absent from DARK_THEMES in src/utils/theme-utils.ts, whose own docblock says it drives "map tiles, Disqus, Calendly, Cal.com, and Leaflet CSS". On forge, every one of those rendered in LIGHT mode over a near-black page. Found only because embed-theme-contrast.spec mirrored that list by hand and the new guard flagged the copy; the mirror had NOT drifted (13 against 13) — both were wrong together, which is precisely why a mirror could never have revealed it. - src/config/themes.ts exports HOUSE_THEMES, and HOUSE_THEME_COUNT now derives from it. The count was already correct; the PROSE beside it said "34 total, of which 2 are ours" — written for two house themes, in the one module whose stated job is stopping numbers from drifting. - Five specs import instead of restating. depth-tokens.spec.ts had BOTH patterns: two hand-typed at :286 while :367 derived all three correctly. - theme-switching.spec.ts already imported THEME_COUNT to assert the count, then listed 32 of 35 members beside it. Two guards, both mutation-tested. The first forbids a hand-typed house theme in any of the five, stripping comments first because several now DISCUSS the names while explaining why they derive them. The second is the one that matters: it computes each house theme's base-100 luminance and requires DARK_THEMES to agree, so the answer is derived from the theme's own colours. Asserting "forge is in DARK_THEMES" would pass the moment someone typed it in and would say nothing about the fourth house theme. Verified red on: removing forge from DARK_THEMES (the actual bug), hand-typing a house theme back into the AAA gate, and un-deriving HOUSE_THEME_COUNT. NOT DONE: tests/e2e/pages/ThemePage.ts also holds a hand-typed list of 31. It is excluded because the file is entirely unused — nothing outside ThemePage.ts references ThemePage — so its list is dead code, not a coverage gap. Deleting an unused page object belongs with #547's decision about unused components, not here. type-check, lint clean. test:scripts 539 passed. theme unit tests 79 passed. Closes #915 Co-authored-by: TurtleWolfe <TurtleWolfe@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
TortoiseWolfe
enabled auto-merge (squash)
August 22, 2026 07:39
This was referenced Aug 22, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #915
The defect
Six test files each typed a theme list by hand against
src/config/themes.ts, and every one missedscripthammer-forge— the third house theme, which shipped after they were written.The sharpest instance is
color-contrast.spec.ts, the AAA gate. #411 built it after a hand-written four-route list let a 6.44:1 eyebrow reachmainbehind 17 green checks. It enumerates its routes for exactly that reason — and hand-wrote its themes. Its comment read "Both custom themes covered", and "Both" was the completeness claim, stale the day forge landed. Same defect, same file, other axis.Measured:
A real bug fell out, and it is user-visible
scripthammer-forgeshipsbase-100: #0f0d0b— near black — and was absent fromDARK_THEMESinsrc/utils/theme-utils.ts, whose own docblock says it drives "map tiles, Disqus, Calendly, Cal.com, and Leaflet CSS."On forge, every one of those rendered in light mode over a near-black page.
It surfaced only because
embed-theme-contrast.spec.tsmirrored that list by hand and the new guard flagged the copy. The mirror had not drifted — 13 against 13. Both were wrong together, which is exactly why comparing a mirror to its source could never have revealed it.What changed
src/config/themes.tsHOUSE_THEMES;HOUSE_THEME_COUNTderives from itsrc/utils/theme-utils.tsDARK_THEMESThe count in
themes.tswas already correct. The prose beside it said "34 total, of which 2 are ours" — written for two house themes, in the one module whose stated job is stopping numbers from drifting.depth-tokens.spec.tshad both patterns in one file: two hand-typed at:286while:367derived all three correctly.theme-switching.spec.tsalready importedTHEME_COUNTto assert the count, then listed 32 of 35 members beside it.Two guards, and the second is the one that matters
The first forbids a hand-typed house theme in any of the five. It strips comments first, because several of those files now discuss the theme names while explaining why they derive them — matching that prose would fail a correct file.
The second computes each house theme's base-100 luminance and requires
DARK_THEMESto agree. Asserting "forge is in DARK_THEMES" would pass the moment someone typed it in, and would say nothing about the fourth house theme.DARK_THEMES(the actual bug)HOUSE_THEME_COUNTDeliberately not done
tests/e2e/pages/ThemePage.tsalso holds a hand-typed list of 31, and the audit counted it as a sixth instance. The file is entirely unused — nothing outsideThemePage.tsreferencesThemePage. Its list is dead code, not a coverage gap, and deleting an unused page object belongs with #547's decision about unused components.A note on risk
This adds 611 whole-page axe cases on a theme that has never been swept. I checked the token-level verdict first —
src/config/theme-contrast.jsonrates forge AAA, textRatio 13.37, uiRatio 7.05 — so a red gate is unlikely. If one appears, it is a real accessibility finding on a shipped theme, which is the entire point of closing the hole.Verification
🤖 Generated with Claude Code