Skip to content

feat: give the README panel a heading ladder, its own code colours and full-width dividers - #65

Merged
stanlyzoolo merged 5 commits into
mainfrom
worktree-research-glow-preprocessor
Aug 8, 2026
Merged

feat: give the README panel a heading ladder, its own code colours and full-width dividers#65
stanlyzoolo merged 5 commits into
mainfrom
worktree-research-glow-preprocessor

Conversation

@stanlyzoolo

Copy link
Copy Markdown
Owner

Panel [3] rendered a README with glamour's stock config re-accented from Theme. 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 in docs/plans/completed/20260808-readme-typography.md.

What changed

  • Heading ladder. One colour per level from ui.HeadingColors, bold at every level, plus a depth marker from H3 down. H6 takes the Dim role.
  • Code fences take five syntax colours of keepkit's own (keyword, string, comment, number, function). The chroma repaint is now bounded to a named seven instead of two, and TestKeepkitStyleRepaintsChroma's DeepEqual guard was narrowed rather than dropped.
  • Dividers span the panel. keepkitStyle gained a width parameter and spends it only on this; renderReadme passes the clamped wrap width, so a narrow panel cannot get a rule wider than itself.
  • Task lists get /, blockquotes are italic as well as dim.
  • Fence plate fixed. 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 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. chromaFormatterFor maps the profile onto chroma's formatter.

Contract change

Panel [3]'s headings and fence accents move off Theme roles onto fixed shades in internal/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 meaning Theme has 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

HeadingColors descends — 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 the Dim role 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. TestHeadingColorsDescend pins the direction, since reordering the values breaks nothing else.

Deliberate absences

Three things cannot be expressed through StyleConfig and are recorded as code comments rather than worked around:

  1. List markersItem.Color/Enumeration.Color are no-ops. BaseElement.doRender paints the prefix with the enclosing block's style; a red Item.Color still yields document-gray bullets.
  2. Tables — separators are drawn by lipgloss/table with no colour call, and Table's primitive styles cell content, header and body alike.
  3. Ragged fence plate — only highlighted tokens carry a background. glamour paints a code block's left indent with the enclosing block's style and never pads out to the wrap width, so a wrapped fence line's remainder sits on the page background. The only lever is 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.Color passes 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.

TestFencePlateMatchesTheCard is 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") emits 52;56;69 where the hex says 52;57;69, so the card is the side that rounds and has for every Surface plate since the role existed.

Every new assertion was mutation-checked (revert the edit, confirm red, restore); the hex-shape smoke guard in internal/ui is 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.

stanlyzoolo and others added 5 commits August 9, 2026 00:32
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
stanlyzoolo force-pushed the worktree-research-glow-preprocessor branch from 775b3ec to b4afbfe Compare August 8, 2026 21:33
@stanlyzoolo
stanlyzoolo merged commit a1d200d into main Aug 8, 2026
3 checks passed
@stanlyzoolo
stanlyzoolo deleted the worktree-research-glow-preprocessor branch August 8, 2026 22:01
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant