feat: give the README panel a heading ladder, its own code colours and full-width dividers - #65
Merged
Merged
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second non-role palette in internal/ui, and an exception for a different reason than ui.LanguageColor: those are linguist's brand marks, these are keepkit's own invented shades. A heading level is not a meaning Theme has a word for, and six levels cannot be six roles, so the ladder lives here instead of growing Theme by five shades-as-roles. HeadingColors descends — bright H1 to dark H5. Measured against a dark background the five run 7.2 : 5.4 : 4.1 : 3.6 : 2.4, so the other order puts H1 below the 3:1 floor for bold text and below the Dim role used for captions. TestHeadingColorsDescend pins the direction because reordering is a one-line edit that breaks nothing else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The stock glamour config renders H3 through H6 identically, so a README's own structure was invisible below the second level. Every heading level now has its own colour plus a › depth marker from H3 down, H6 takes the Dim role, and `---` becomes a rule across the panel instead of a short dash run floating mid-line. Task lists get ✓/☐ rather than the source's reprinted brackets, and a blockquote is italic as well as dim. Code fences take five syntax colours of keepkit's own — keyword, string, comment, number, function — bounding the chroma repaint to a named seven rather than the two it covered before. Fixes a fence's plate along the way. glamour's chroma formatter defaults to terminal256 for every profile, ignoring the WithColorProfile next to it, so Theme.Surface #343945 was quantized to index 237 (#3a3a3a) — one plate reaching the screen in two colours, since the card's changelog code line and the panel's own inline code both emit the exact value. chromaFormatterFor maps the profile onto chroma's formatter instead. keepkitStyle takes a width, which it spends only on the divider; renderReadme passes the clamped wrap width so a narrow panel cannot get a rule wider than itself. Three things stay unstyled and are recorded as such: list markers (Item.Color is a no-op — glamour paints the bullet with the enclosing block's style), tables (separators are lipgloss-drawn with no colour call), and the fence plate's ragged edge (only highlighted tokens carry a background, so a wrapped line's remainder sits on the page). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CLAUDE.md, ARCHITECTURE.md and docs/design/readme-pipeline.md carried the old contract — one Theme value switches every colour in keepkit. That now holds for the panel's chrome only: its headings and fence accents follow ui/readme_palette.go and do not repaint on a theme switch. Each of the three states the split where it already stated the old rule. Also records what the renderer confirmed rather than what was assumed: Text.BackgroundColor carries the whole fence plate (chroma resolves an unset token background up to Text, not to Background), H1/H2's empty Prefix is a load-bearing assignment, and H6 needs its own Bold. Plan moved to docs/plans/completed/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
stanlyzoolo
force-pushed
the
worktree-research-glow-preprocessor
branch
from
August 8, 2026 21:33
775b3ec to
b4afbfe
Compare
stanlyzoolo
added a commit
that referenced
this pull request
Aug 8, 2026
Three documents claimed the contract change from #65 was recorded in theme.go. It was not: the file still said switching every color in keepkit is switching one Theme value, which stopped being true for panel [3]'s heading ladder and code-fence accents. The file that defines the rule is the one a contributor reads before adding a color. Also drops two stale claims the same pass turned up: CLAUDE.md called LanguageColor the one palette a theme switch must not repaint while its own internal/ui row already said there were two, and the readme pipeline doc said no general sanitizer is pulled in when glamour v1.0.0 holds a bluemonday.StrictPolicy and has put it in go.mod all along. Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.
Panel
[3]rendered a README with glamour's stock config re-accented fromTheme. That left three things wrong: H3 through H6 render identically, so a document's structure was invisible below the second level; code fences kept the stock pink/violet syntax accents; and---came out as a short dash run floating mid-panel.Full rationale in
docs/design/readme-pipeline.md; the plan is indocs/plans/completed/20260808-readme-typography.md.What changed
ui.HeadingColors, bold at every level, plus a›depth marker from H3 down. H6 takes theDimrole.TestKeepkitStyleRepaintsChroma'sDeepEqualguard was narrowed rather than dropped.keepkitStylegained awidthparameter and spends it only on this;renderReadmepasses the clamped wrap width, so a narrow panel cannot get a rule wider than itself.✓/☐, blockquotes are italic as well as dim.terminal256for every profile, ignoring theWithColorProfilenext to it, soTheme.Surface#343945was quantized to index 237 (#3a3a3a). One plate was reaching the screen in two colours — the card's changelog code line and this panel's own inline code both emit the exact value.chromaFormatterFormaps the profile onto chroma's formatter.Contract change
Panel
[3]'s headings and fence accents move offThemeroles onto fixed shades ininternal/ui/readme_palette.go. A future theme switch repaints the panel's body, links, quotes and inline code and not H1–H5 or the fence accents.This is the second non-role palette, and an exception for a different reason than
ui.LanguageColor: those are linguist's brand marks, these are keepkit's own invented shades. A heading level is not a meaningThemehas a word for, and six levels cannot be six roles. All three places that stated the old contract (ui/theme.go,keepkitStyle's doc comment, the design doc) are updated.Ladder direction
HeadingColorsdescends — bright H1 to dark H5. The plan originally specified the other order; measured against a dark background the five run 7.2 : 5.4 : 4.1 : 3.6 : 2.4 in contrast, which put H1 below the 3:1 floor for bold text and below theDimrole used for captions. A top-level heading nobody can find is an inverted ladder.Accepted in exchange: on a white terminal the ends swap (H1 measures ~2.4 there), and on dark H5 (2.4) sits below H6's
Dim(4.2), so the last two steps read as one.TestHeadingColorsDescendpins the direction, since reordering the values breaks nothing else.Deliberate absences
Three things cannot be expressed through
StyleConfigand are recorded as code comments rather than worked around:Item.Color/Enumeration.Colorare no-ops.BaseElement.doRenderpaints the•prefix with the enclosing block's style; a redItem.Colorstill yields document-gray bullets.Table's primitive styles cell content, header and body alike.Document's background, which would flood the whole document.Testing
Struct-level assertions on the returned
StyleConfig, since tests have no TTY and glamour strips every colour it would emit. A struct assertion only proves the field changed, never that glamour reads it —Item.Colorpasses one and does nothing — so every override was confirmed against the renderer once before its test was written, each chroma variant in its own process because glamour registers the style under a process-global one-shot name.TestFencePlateMatchesTheCardis on rendered output instead: passing the formatter option proves nothing about what chroma emits, and the defect was a value that survived every struct-level check. It allows one unit per channel, which is termenv's —TrueColor.Color("#343945")emits52;56;69where the hex says52;57;69, so the card is the side that rounds and has for everySurfaceplate since the role existed.Every new assertion was mutation-checked (revert the edit, confirm red, restore); the hex-shape smoke guard in
internal/uiis the one exemption, recorded as such. Rendered output was read at 80 and 120 columns in both variants.Preflight green: build, vet,
test -race ./...,golangci-lint run, plus the Windows/darwin cross-compile.