From c7790f29856ddc3eed826013d910e65ea83b66a2 Mon Sep 17 00:00:00 2001 From: Adnaan Badr Date: Tue, 4 Aug 2026 02:44:10 +0000 Subject: [PATCH 1/5] feat(theme): calm design tokens, IBM Plex Sans, and the docs shell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 diff --git a/examples/greet-wall/wall.tmpl b/examples/greet-wall/wall.tmpl index 7739b5a..463593b 100644 --- a/examples/greet-wall/wall.tmpl +++ b/examples/greet-wall/wall.tmpl @@ -12,13 +12,17 @@ anything in is lost once this app is mounted on the landing. -->
From 02d0e54e86942bce26de6d44d0783aa6968913f0 Mon Sep 17 00:00:00 2001 From: Adnaan Badr Date: Wed, 5 Aug 2026 02:32:53 +0000 Subject: [PATCH 3/5] refactor(theme): drop rules that can never match or already hold /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
  • , not on the 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) Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ --- content/assets/docs.css | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/content/assets/docs.css b/content/assets/docs.css index e345a76..1edd124 100644 --- a/content/assets/docs.css +++ b/content/assets/docs.css @@ -57,7 +57,12 @@ nav.page-nav { of a 640px container at 924px wide — horizontal page overflow, which is the one thing the acceptance criteria name. The calm design has no bleed: code sits in the column with a border. Specificity (0,1,1) beats the theme's bare - type selectors inside its own media query. */ + type selectors inside its own media query. + + The selector list deliberately mirrors the theme's, block types included that + content/ does not currently use (it authors embed-lvt and nothing else). They + are cheap to carry, and dropping them would let the overflow come back the + day someone adds a wasm or interactive block. */ @media (min-width: 900px) { .content-wrapper pre, .content-wrapper .tinkerdown-wasm-block, @@ -142,24 +147,25 @@ nav.page-nav { color: var(--text-primary); } -/* The lede: the first paragraph after the page title. */ -.content-wrapper h1 + p { font-size: 17px; line-height: 1.7; margin-bottom: 20px } +/* The lede: the first paragraph after the page title. Size and leading already + come from the paragraph rule above; only the extra breathing room is new. */ +.content-wrapper h1 + p { margin-bottom: 20px } /* ══ Links ═══════════════════════════════════════════════════════════════ */ /* The theme underlines every content link with a hardcoded rgba(0,102,204) - border and shifts padding on hover — a 0.4px jitter on every link. Visited - is hardcoded purple, so it needs naming explicitly rather than inheriting. */ + border and bolds it to 500. Visited is a hardcoded purple, so it has to be + named explicitly rather than left to inherit. */ .content-wrapper a:not(.prev-next-link), .content-wrapper a:not(.prev-next-link):visited { color: var(--accent); border-bottom: 0; text-decoration: none; font-weight: inherit; - background: none; - padding: 0; - margin: 0; } +/* The theme's hover adds a tinted background and shifts padding by 0.2rem with + a negative margin — every link twitches under the cursor. Undoing the shift + is what the padding and margin resets are for. */ .content-wrapper a:not(.prev-next-link):hover, .content-wrapper a:not(.prev-next-link):visited:hover { color: var(--lt-accent-hover); @@ -522,8 +528,8 @@ nav.page-nav .label { font-size: 15px; font-weight: 500; color: var(--text-headi text-decoration: none; } .page-toc-link:hover { color: var(--text-heading); background: none } -.page-toc-item.active > .page-toc-link, -.page-toc-link.active { +/* Scroll tracking toggles .active on the
  • , not on the inside it. */ +.page-toc-item.active > .page-toc-link { color: var(--text-heading); border-left-color: var(--accent); background: none; From 6b88246eb2ab19397cc76c0283fb2e83d624c6aa Mon Sep 17 00:00:00 2001 From: Adnaan Badr Date: Thu, 6 Aug 2026 03:02:22 +0000 Subject: [PATCH 4/5] fix(theme): give code a fourth hue, and highlight the landing's HTML samples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two problems, one visible and one that read as "highlighting is off". The landing's template samples were plain
     with no language class, so
    Prism never touched them — the old landing hand-wrote 
    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) 
    Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
    ---
     content/assets/brand.css | 15 +++++++++------
     content/index.md         | 20 ++++++++++----------
     2 files changed, 19 insertions(+), 16 deletions(-)
    
    diff --git a/content/assets/brand.css b/content/assets/brand.css
    index 62fdf73..290a645 100644
    --- a/content/assets/brand.css
    +++ b/content/assets/brand.css
    @@ -67,12 +67,14 @@
       --lt-ok:           #5C9169;  /* live-demo status dot — never used for text */
       --lt-accent-hover: #1B3E60;
     
    -  /* Syntax highlighting, reduced to three hues over --pre-text. All three are
    -     darker than --lt-meta because code blocks set 12.5-13px, inside the range
    -     the contrast floor governs. */
    +  /* Syntax highlighting, reduced to four hues over --pre-text: enough that
    +     structure reads at a glance, not so much that a code block becomes a paint
    +     chart. All four are darker than --lt-meta, because code blocks set
    +     12.5-13px and that is inside the range the contrast floor governs. */
       --lt-code-comment: #6B6862;
       --lt-code-string:  #4A6B52;
       --lt-code-keyword: #2F5D8A;
    +  --lt-code-name:    #5A5560;  /* declared names: func names, types, HTML tags */
     }
     
     /* ── Prism, neutralised ──
    @@ -83,10 +85,11 @@ code[class*="language-"],
     pre[class*="language-"] { color: var(--pre-text); text-shadow: none }
     
     .token.comment, .token.prolog, .token.doctype, .token.cdata { color: var(--lt-code-comment) }
    +/* What the code declares or invokes, and what an HTML element IS. */
    +.token.tag, .token.function, .token.class-name { color: var(--lt-code-name) }
     .token.punctuation, .token.operator, .token.entity, .token.url,
    -.token.property, .token.tag, .token.boolean, .token.number,
    -.token.constant, .token.symbol, .token.deleted,
    -.token.function, .token.class-name, .token.namespace,
    +.token.property, .token.boolean, .token.number,
    +.token.constant, .token.symbol, .token.deleted, .token.namespace,
     .token.regex, .token.important, .token.variable { color: var(--pre-text) }
     .token.selector, .token.attr-name, .token.string, .token.char,
     .token.builtin, .token.inserted,
    diff --git a/content/index.md b/content/index.md
    index a0cee97..b2b83e3 100644
    --- a/content/index.md
    +++ b/content/index.md
    @@ -69,7 +69,7 @@ layout: landing
       
    app.tmpl — the entire template
    -
    <!DOCTYPE html>
    +
    <!DOCTYPE html>
     <html><head>
       <script defer src="{{lvtClientScriptURL}}"></script>
     </head><body>
    @@ -78,7 +78,7 @@ layout: landing
         <input name="name" placeholder="Your name">
         <button name="greet">Say hi</button>
       </form>
    -</body></html>
    +</body></html>
    app.go — the entire program
    @@ -138,12 +138,12 @@ func main() {
    app.tmpl — one form, either transport
    -
    <!-- the only line that flips the transport -->
    +
    <!-- the only line that flips the transport -->
     <script defer src="{{lvtClientScriptURL}}"></script>
     <form method="POST">   <!-- JS on → fetch + patch · JS off → native POST -->
       <input name="name">
       <button name="greet">Say hi</button>
    -</form>
    +</form>
    @@ -165,8 +165,8 @@ func main() {
    app.tmpl · the rule, written once
    -
    <input name="name" required {{.lvt.AriaInvalid "name"}}>
    -{{.lvt.ErrorTag "name"}}
    +
    <input name="name" required {{.lvt.AriaInvalid "name"}}>
    +{{.lvt.ErrorTag "name"}}
    app.go · re-check, then add your own rule
    @@ -209,8 +209,8 @@ func main() {
    -
    <button {{if .lvt.Pending}}type="button" aria-busy="true"
    -  disabled{{else}}name="greet"{{end}}>Say hi</button>
    +
    <button {{if .lvt.Pending}}type="button" aria-busy="true"
    +  disabled{{else}}name="greet"{{end}}>Say hi</button>
    lvt.Async(ctx,
         func(context.Context) (string, error) {
             time.Sleep(700 * time.Millisecond)
    @@ -234,9 +234,9 @@ func main() {
     
     
    -
    <button name="greet"
    +
    <button name="greet"
       lvt-el:addClass:on:pending="is-loading"
    -  lvt-el:removeClass:on:done="is-loading">Say hi</button>
    + lvt-el:removeClass:on:done="is-loading">Say hi</button>
    func (a *App) Greet(s State, ctx *lvt.Context) (State, error) {
         time.Sleep(700 * time.Millisecond)
         if name := strings.TrimSpace(
    
    From 6e39148798fefae8af00cb7d729bcad59e1d0463 Mon Sep 17 00:00:00 2001
    From: Adnaan Badr 
    Date: Thu, 6 Aug 2026 20:39:23 +0000
    Subject: [PATCH 5/5] fix(wall): align the server line with the greetings, and
     stop styling timestamps as errors
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    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  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  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) 
    Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
    ---
     content/assets/landing.css    |  6 ++++--
     examples/greet-wall/wall.tmpl | 12 +++++++++++-
     2 files changed, 15 insertions(+), 3 deletions(-)
    
    diff --git a/content/assets/landing.css b/content/assets/landing.css
    index f7f8e0d..8124853 100644
    --- a/content/assets/landing.css
    +++ b/content/assets/landing.css
    @@ -270,8 +270,10 @@ section { scroll-margin-top: 72px }
       border-color: var(--border);
       color: var(--meta);
     }
    -/* Field errors come back from Go as  next to the input. */
    -.demo-body small { display: block; width: 100%; font-size: 13px; color: var(--danger); font-family: var(--mono) }
    +/* Field errors come back from Go as a  next to the input, via
    +   .lvt.ErrorTag. Scoped to the form: the wall app also emits , for
    +   greeting timestamps, and they are not errors. */
    +.demo-body form small { display: block; width: 100%; font-size: 13px; color: var(--danger); font-family: var(--mono) }
     .demo-body input[aria-invalid="true"] { border-color: var(--danger) }
     /* An iframe card supplies its own padding inside the frame. */
     .demo-body:has(> .nojs-frame) { padding: 0 }
    diff --git a/examples/greet-wall/wall.tmpl b/examples/greet-wall/wall.tmpl
    index 463593b..210034f 100644
    --- a/examples/greet-wall/wall.tmpl
    +++ b/examples/greet-wall/wall.tmpl
    @@ -11,8 +11,18 @@