Skip to content

Docs site: adopt the calm theme (landing + internal pages) - #135

Merged
adnaan merged 5 commits into
mainfrom
calm-theme
Aug 8, 2026
Merged

Docs site: adopt the calm theme (landing + internal pages)#135
adnaan merged 5 commits into
mainfrom
calm-theme

Conversation

@adnaan

@adnaan adnaan commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Closes #133.

One visual system across the landing and the docs shell: #2F5D8A instead of emerald, IBM Plex Sans instead of Inter, no gradients, no shadows, no transforms.

How the colors are applied

By overriding tinkerdown's own semantic custom properties in brand.css, not by hardcoding hexes into rules — so the shell's dark theme keeps working off the same variables and no dark palette had to be invented.

The override is scoped :root:not([data-theme="dark"]). That is not decoration: [data-theme="dark"] and :root are both specificity (0,1,0) and brand.css loads after the theme's inline <style>, so a bare :root block would win on source order and silently break the theme toggle.

styling.site_css takes a single path, so docs.css reaches pages via @import from brand.css. CSP allows it (style-src 'self'). Staying on TINKERDOWN_REF=v0.3.9styling.tokens is v0.4.0 and drags in the whole ephemeral-UI reframe for no gain here.

Deviations from the design, and why

  • Archetypes A and C ship two-column, not three. tinkerdown's client injects the page TOC inside the active page's sidebar <li>. It is restyled where it lands; pulling it out to a fixed right gutter leaves a hole behind it and makes page width depend on 100vw arithmetic, which is the fastest way to fail the no-horizontal-overflow criterion. A real right-hand <aside> is an upstream change.
  • Archetype C's mono entry names apply only to h3:has(> code). The design's samples are symbol names; in the real content 804 of 807 h3s are prose and the symbols live in tables, which get the definition grid instead.
  • Archetype B's rows are inline, not left/right justified. That split needs a wrapper markdown never emits, and flex-on-li is not a substitute — every inline <code> in a gloss would become its own flex item.
  • Category chips are dropped. No markdown source, and reference/api.md is sync-generated.
  • Callout labels come from the markdown's own bold lead-in rather than a fabricated ::before, so a "Security note" does not render as "NOTE / Security note".

Beyond the issue's file list

Prism's syntax palette is neutralised to three hues (it loads before brand.css and would keep painting code blocks), and four example templates that still carried emerald are retinted — they render inside the landing's demo cards. greet-nojs restates the whole palette because it is shown in an iframe and CSS does not cross that boundary. The greet-wall heartbeat dot keeps its pulse: the rule is that nothing moves unless the app is updating, and that dot marks a server push.

Found by measuring, not by reading

  • 1rem is a flat 14px at every viewport, so three elements were already under the 11.5px floor — .page-source-meta code at 10.12px (0.85rem × 0.85em), .nav-group-title at 10.92px, .search-button kbd at 11.0px.
  • The theme bleeds <pre> outward with margins sized for its own 800px measure; against a 720px column that overflows the page at 924px.
  • Goldmark ends an HTML block at the first blank line — a blank line inside a <pre> re-parsed the Go sample as markdown, stripping its indentation.
  • pre.install is inline-block, so on a 393px phone it sized to the full go get line and scrolled the page 12px sideways. make sweep caught this; no test covers 393px on the landing.

Verification

Against the pinned v0.3.9 image:

  • New e2e/calm_theme_test.go — the issue's acceptance section as assertions at 924px and 1440px across the landing and all three archetypes: no horizontal overflow, <pre> scrolls internally, no text under 11.5px, nothing at ≤13px lighter than #6B6862, gopher served locally with its CC BY 3.0 attribution. A new file because responsive_test.go owns 1280/768/393 and passes unchanged.
  • responsive_test.go, docs_ia_test.go, breadcrumb_test.go pass unchanged; the light/dark toggle still round-trips. All eight TestSpine* pass — the five embed-lvt mounts and both greet-nojs iframes are unchanged in path and sandbox.
  • make test green (20 packages). make sweep: 196 page-viewport visits, 0 overflow. Two flags remain on /recipes/ui-patterns/lists/large-table, a pre-existing 4.9 MB page that renders in 9.6s on production and 7.3s on this branch.
  • TestThemeAccentInjected updated to the new accent (it is not in the issue's pass-unchanged list).

🤖 Generated with Claude Code

https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ

adnaan and others added 5 commits August 4, 2026 02:44
Issue #133, phases 1-2 of 4. Replaces the emerald accent and Inter with
the calm system's #2F5D8A and IBM Plex Sans, and adds content/assets/
docs.css for the three page archetypes. The landing is unchanged so far
and still ships its own emerald stylesheet; phase 3 rewrites it.

Colors are applied by overriding tinkerdown's semantic custom properties
rather than hardcoding hexes in rules, so the shell's dark theme keeps
working off the same variables. The override is scoped
:root:not([data-theme="dark"]) because [data-theme="dark"] and :root are
both specificity (0,1,0) and brand.css loads after the theme's inline
<style> — a bare :root block would win on source order and silently
break the theme toggle.

docs.css reaches pages via @import from brand.css: styling.site_css
takes a single path, so that is the only way to keep the file split the
issue asks for. CSP allows it (style-src 'self').

Three things measured against the shipped theme rather than assumed:

  - 1rem resolves to a flat 14px at every viewport (tinkerdown's
    --pico-font-size: 87.5% beats pico's responsive ladder), so three
    elements were already under the design's 11.5px floor and are now
    set in px: .page-source-meta code (10.12px, 0.85rem x 0.85em),
    .nav-group-title (10.92px) and .search-button kbd (11.0px).
  - The theme bleeds pre blocks outward with margins sized for its own
    800px measure. Against a 720px column that overflows the page at
    924px wide, so the bleed is neutralised.
  - Archetype C sets entry names in mono because the design's samples
    are symbol names. In the real content 804 of 807 h3s are prose and
    the symbols live in tables, so mono is applied only to headings
    that are themselves code, and the tables get the definition grid.

Section-index link rows key off :has(), the only signal available — no
page-type class is emitted and <body> carries no attributes. Both
authored shapes are matched (`[Title](/x) —` and `**[Title](/x)** —`),
covering 47 all-link lists and leaving all 434 prose lists alone.

Verified in the pinned v0.3.9 image at 924px and 1440px on the landing
and all three archetypes: no horizontal overflow, no text under 11.5px,
nothing at 13px or below lighter than #6B6862, pre blocks scrolling
internally. responsive_test.go, docs_ia_test.go and breadcrumb_test.go
pass unchanged, and the light/dark toggle still round-trips.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
Issue #133, phases 3-4 of 4. The landing was the presenting problem: the
same greeting demo eight times, two stacked code blocks each, and no
wayfinding. It is now the design's six-step spine with a sticky rail,
and landing.css drops from 31KB of emerald gradients to the calm layout.
Copy is lifted verbatim from the design reference.

Every live demo is still a real app: the five embed-lvt mounts and both
greet-nojs iframes are unchanged in path and sandbox, so the whole
TestSpine* suite passes untouched. Their min-heights come down to fit
the calm cards — min-height, so a growing wall still pushes the card
open rather than clipping.

The four example templates that still carried emerald are retinted. Only
greet-nojs restates the whole palette: it is shown in an iframe and CSS
does not cross that boundary, so unlike the other demos (which the
landing styles directly) it has to carry the theme itself or the JS-off
card would not match the JS-on card beside it. The greet-wall heartbeat
dot keeps its pulse — the design's rule is that nothing moves unless the
app is updating, and that dot marks a server push, which is exactly the
app updating.

Four things found by measuring rather than by reading the stylesheet:

  - Goldmark ends an HTML block at the first blank line. A blank line
    inside a <pre> silently re-parsed the rest of the Go sample as
    markdown: indentation stripped, <p> tags inside the <code>. The
    snippets are compact now and index.md carries a note saying why.
  - `.doc h1` is a descendant selector, and the demos inline a whole
    running app into the page — so the greeting app's own <h1> was
    rendering at the hero's 44px. Headings are scoped to direct
    children of a section.
  - Pico sizes inputs with input:not([type=checkbox]){width:100%},
    which ties with a bare `input` on specificity and wins on order, so
    the iframe's input spanned the card until the rule was made
    specific enough.
  - pre.install is inline-block, so on a phone it sized to the full
    go-get line, dragged its flex parent with it and scrolled the page
    12px sideways. Caught by `make sweep`, not by any test.

New e2e/calm_theme_test.go turns the issue's acceptance section into
assertions at 924px and 1440px across the landing and all three
archetypes: no horizontal overflow, <pre> scrolls internally, no text
under 11.5px, nothing at 13px or below lighter than #6B6862, and the
gopher served locally with its CC BY 3.0 attribution. It is a new file
because responsive_test.go owns 1280/768/393 and must pass unchanged.
TestThemeAccentInjected moves to the new accent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
/simplify pass over the calm-theme diff. Four findings, all in docs.css:

  - .page-toc-link.active could never match. The client's scroll tracking
    toggles .active on the .page-toc-item <li>, not on the <a> inside it,
    so the sibling selector was the only live one.
  - The lede rule restated the paragraph rule's font-size and line-height
    verbatim; only its extra margin was ever doing anything.
  - The base content-link rule reset background, padding and margin, but
    the theme sets none of those until :hover. The resets belong on the
    hover rule, which is where the 0.2rem twitch they undo comes from.
  - The bleed-neutralising selector list names block types content/ does
    not author. Kept, with a comment: it mirrors the theme's own list, and
    dropping them would let the overflow return the day a page adds one.

Checked and left alone: all three IBM Plex Sans and all three JetBrains
Mono weights are genuinely requested by the browser, and landing.css has
no selector without markup behind it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
…samples

Two problems, one visible and one that read as "highlighting is off".

The landing's template samples were plain <pre> with no language class, so
Prism never touched them — the old landing hand-wrote <span class="tag">
colouring and the rewrite dropped it. They now carry language-html, which
also settles an inconsistency: every Go block on the page had a copy button
and the HTML blocks beside them did not. Verified that tokenising leaves
{{lvtClientScriptURL}}, {{.Name}} and {{if .lvt.Pending}} as literal text.

The palette was also flatter than the design intends. Reducing to three
hues left function names, types and HTML tag names in the same near-black
as punctuation, so ~19% of tokens carried any colour and a block read as
undifferentiated. Declared and invoked names — .token.tag, .token.function,
.token.class-name — get --lt-code-name #5A5560, a desaturated slate that is
darker than --lt-meta and so still clears the <=13px contrast floor that
calm_theme_test.go enforces. Coloured tokens go from ~19% to ~28%; the
background, borders and absence of shadows are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
… timestamps as errors

Reported: "the server said hi at" is not horizontally aligned with the list
of greetings.

Two causes. .from-server carried justify-content:center while the wall rows
were left-aligned behind the list's 40px marker padding, so the heartbeat
floated in the middle of a column it belongs at the head of. Both now start
at the container's left edge and the wall's markers are gone — Pico sets
`ul li{list-style:square}` on the li, so a list-style on the ul never wins.

The second cause was mine: landing.css styled every <small> inside a demo
as a field error, block-level and full-width in --danger. That rule exists
for .lvt.ErrorTag output in greet-validate, but the wall emits <small> for
greeting timestamps — so every timestamp rendered rust-red on its own line,
which is also what wrapped each greeting onto two lines. Scoped to
`form small`.

The wall's own presentation now lives in wall.tmpl beside .from-server,
so it travels with the app to the recipe pages that embed it too.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
@adnaan adnaan closed this Aug 6, 2026
@adnaan adnaan reopened this Aug 6, 2026
@adnaan adnaan closed this Aug 8, 2026
@adnaan adnaan reopened this Aug 8, 2026
@adnaan
adnaan merged commit 2b10c5d into main Aug 8, 2026
4 checks passed
adnaan added a commit that referenced this pull request Aug 10, 2026
* docs: write down the voice, and fix the pages that lost it worst

The landing copy has been rewritten four times (#65, #83, #135, #136) and
drifted back to the same flat register every time, because nothing recorded
what the target was. VOICE.md is that record.

An audit of the 67 docs-native pages found two hand-written voices, not one.
The ten pages under content/recipes/apps/ were ported from example READMEs and
then cut loose: 9 exclamation marks, 14 emoji and 36 Title Case headings,
against 2, 5 and 7 across the other 53 files. Everything else was already
restrained — zero occurrences of powerful, effortless, robust, out of the box.

The marketing register was also producing wrong claims. chat.md said "Zero
manual broadcasting code required!" while examples/chat/main.go has an explicit
Subscribe and three explicit Publish calls. A reader who believed it would
wonder why their own app doesn't sync.

Also in here:
- scripts/voice-check.sh, a ratchet. Ceilings are today's counts, so the build
  fails when a number goes up. Bring one down, lower the ceiling in the same
  commit. It will not catch a page that is merely flat; most of VOICE.md is not
  checkable, which is why it is written down.
- source-of-truth.md claimed recipes/apps/* was mirrored from livetemplate/examples.
  It hasn't been since the consolidation, and source-of-truth.yaml said so. That
  stale row is why those ten pages went unmaintained.

Verified: tinkerdown validate 98/98, e2e green on :8084, sweep 194/196 (the two
are the pre-existing 4.9 MB large-table timeouts), and the rewritten copy read
back out of a real browser on all ten pages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ

* ci: run the voice ratchet on every PR

Without this the checker is a script nobody runs, which ratchets nothing. It
goes in the validate job next to `tinkerdown validate` — both are content
gates and neither needs the browser stack.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ

* docs(recipes/apps): bring the ten ported pages into the same voice

These were ported once from examples/<slug>/README.md and then cut loose, so
they still read like READMEs while the rest of the site moved on. Against the
other 53 docs-native pages they carried 9 exclamation marks to 2, 14 emoji to 5,
and 36 Title Case headings to 7.

Counts across all docs-native pages: emoji 19 -> 0, Title Case headings 43 -> 0,
exclamations 11 -> 3, passives 114 -> 83. The three exclamations left are quoted
UI copy ("Changes saved!"), which is what a flash message actually says.

Two more false claims went with the register, both in chat.md:
- "Just modify state - broadcasting happens automatically!" Mutating state does
  nothing on its own; Mount subscribes and each action publishes.
- The comparison table's "Auto-broadcasting" framing, same problem.

Two carve-outs, both in VOICE.md and the checker:
- Emoji in a comparison table are scan markers doing real work (the "does this
  scale?" matrix in recipes/counter/index.md). The checker skips table rows.
- H1 carries the page NAME and must equal the front-matter title. Downcasing it
  renames the page — breadcrumb_test.go caught me doing exactly that on
  App Recipes. Added an h1-vs-title check so it can't happen again, and verified
  the check fires by breaking a title on purpose.

Not fixed here, flagged instead: chat.md's tutorial documents an API that does
not exist (a Change(ctx *ActionContext) switch on ctx.Action, and an undefined
`controller` in main). The real examples/chat/main.go uses the controller
pattern. That is a correctness fix, not a voice one, and it needs its own PR.

Verified: voice-check green at the new ceilings, tinkerdown validate 98/98,
e2e green uncached on :8084, sweep 194/196 (the 2 are the pre-existing 4.9 MB
large-table timeouts), and all ten pages read back clean from a real browser.
Anchor links checked against main: 15 broken on both, 0 new.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ

* docs: finish the register pass on the remaining native pages

Phase 2b, across the 57 docs-native pages outside recipes/apps/.

Every countable tell is now at its floor:

  claudisms         14 -> 0
  product-as-subject 3 -> 0
  royal we           4 -> 0
  meta-commentary    3 -> 0
  hype lexicon       1 -> 0
  triadic negation   5 -> 1
  passive voice    114 -> 34

The passive number is where the work was. What is left is mostly not passive
voice at all — "the title is required" describes an HTML attribute, "action
methods are exported" is Go, "Email is required" is a quoted flash message, and
"is untouched" is an adjective. The regex cannot tell those apart, so the
ceiling stays at 34 rather than pretending 0 is reachable.

The one triadic negation left is the landing's "no hx-post, no onClick, no route
to register". Each item names a real thing you would otherwise write, so it is
an argument rather than a drumbeat.

Two judgement calls worth naming:

- "literate primitives" in source-of-truth.md was not upstream terminology —
  nothing else in the repo or in tinkerdown uses it. Renamed to "literate
  blocks", which is what the section actually documents.
- "reach for" stays. At 24 files it is house idiom, not drift, and it is
  deliberately absent from the checker.

Also fixed two checker false positives found by running it: "the rest of the
page" is usually literal ("without blocking the rest of the page"), so
meta-commentary now requires a following verb; and the h1-vs-title comparison
strips backticks, which a front-matter title cannot carry.

chat.md's remaining "automatic syncing" claims went too — leaving them would
have contradicted the correction made to the same file in the previous commit.

Verified: voice-check green at the new ceilings, tinkerdown validate 98/98,
e2e green uncached on :8084, sweep 194/196 (the 2 are the pre-existing 4.9 MB
large-table timeouts), anchors 15 broken on both branch and main so 0 new, and
14 rewrites read back out of a real browser. No mirrored page touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ

---------

Co-authored-by: Claude Opus 5 (1M context) <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.

Docs site: adopt the calm theme (landing + internal pages)

1 participant