From 8bdcc2369a2e88293696c29529df7e222194b531 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sun, 3 May 2026 12:19:51 +0700 Subject: [PATCH] docs: README + CHANGELOG + journal + plan for v0.3 - README: add layout/ogImage/ogImageUrl rows; new "Layout variants" + "i18n" sections; correct stale "< 4 KB CSS" claim to "< 3 KB gzipped CSS". - CHANGELOG: v0.3.0 entry; fresh Unreleased placeholder. - Journal: post-mortem covering layout variants, i18n gotchas (defaultContentLanguage), and the OG auto-gen scope cut. - Plan dir: phase files + status reflecting the scope-reduced Phase 2. --- themes/bonsai/CHANGELOG.md | 20 +++- themes/bonsai/README.md | 43 +++++++- .../docs/journals/2026-05-03-v0-3-release.md | 61 +++++++++++ .../phase-01-layout-variants.md | 76 +++++++++++++ .../phase-02-og-image-generation.md | 103 ++++++++++++++++++ .../phase-03-i18n-string-externalization.md | 79 ++++++++++++++ .../phase-04-demo-docs-polish.md | 76 +++++++++++++ .../phase-05-v0-3-release.md | 70 ++++++++++++ .../plans/260503-1120-v0-3-release/plan.md | 52 +++++++++ 9 files changed, 577 insertions(+), 3 deletions(-) create mode 100644 themes/bonsai/docs/journals/2026-05-03-v0-3-release.md create mode 100644 themes/bonsai/plans/260503-1120-v0-3-release/phase-01-layout-variants.md create mode 100644 themes/bonsai/plans/260503-1120-v0-3-release/phase-02-og-image-generation.md create mode 100644 themes/bonsai/plans/260503-1120-v0-3-release/phase-03-i18n-string-externalization.md create mode 100644 themes/bonsai/plans/260503-1120-v0-3-release/phase-04-demo-docs-polish.md create mode 100644 themes/bonsai/plans/260503-1120-v0-3-release/phase-05-v0-3-release.md create mode 100644 themes/bonsai/plans/260503-1120-v0-3-release/plan.md diff --git a/themes/bonsai/CHANGELOG.md b/themes/bonsai/CHANGELOG.md index c364edf..5c6f349 100644 --- a/themes/bonsai/CHANGELOG.md +++ b/themes/bonsai/CHANGELOG.md @@ -5,7 +5,25 @@ All notable changes to this project are documented here. Format follows [Keep a ## [Unreleased] ### Added -- (placeholder for v0.3 — layout variants, i18n support) +- (placeholder for v0.4 — auto-generated OG images, RSS opt-in, multi-section bio) + +## [0.3.0] — 2026-05-03 + +Layout, social-preview, and i18n release. All additions are strictly opt-in; v0.2 sites upgrade with no config edits. + +### Added +- **Layout variants** — `params.layout` accepts `stack` (default), `grid` (responsive 2-col), or `inline` (icon-only horizontal row). Pure CSS, zero JS. Unknown values warn at build time and fall back to `stack`. Live gallery at `/variants/`. +- **OG image controls** — `params.ogImageUrl` for an explicit 1200×630 social-preview image (auto-upgrades `twitter:card` to `summary_large_image`); `params.ogImage = false` to suppress all `og:image` / `twitter:image` tags. Avatar still serves as the fallback when neither is set. +- **i18n string externalization** — every theme-rendered string (`nav` aria-label, theme-toggle labels, default footer template) sourced from `i18n/{lang}.toml`. Bundles for `en` (default) and `vi` ship with the theme. Adding a language is a single-file change. + +### Changed +- `layouts/partials/bio-card.html` validates `params.layout` against the allowlist and emits `bio__links bio__links--{layout}` so variant rules scope under a parent class (avoids the v0.2-style specificity collision). +- `layouts/partials/head.html` resolves OG image via the new `ogImage`/`ogImageUrl` ladder; emits `twitter:image` whenever `og:image` is present. +- `layouts/partials/footer.html` uses `i18n "footer_default"` when no `params.footerText` override is set. +- `exampleSite/hugo.toml` switches the demo to `layout = "grid"` to make the new feature visible on the live site. + +### Deferred +- Auto-generated OG images (text overlay on per-palette base PNGs). Required vendoring a TTF + 4 base PNGs (~150 KB binary) into a theme that prides itself on `< 4 KB CSS`. Moved to v0.4 once a smaller path is found. ## [0.2.0] — 2026-05-03 diff --git a/themes/bonsai/README.md b/themes/bonsai/README.md index 0212f7b..0168c71 100644 --- a/themes/bonsai/README.md +++ b/themes/bonsai/README.md @@ -6,7 +6,7 @@ A minimalist Hugo theme for link-in-bio pages, inspired by [Linktree](https://linktr.ee) and the Japanese art of [bonsai](https://en.wikipedia.org/wiki/Bonsai) — *small, curated, intentional*. -**→ [Live demo](https://tiennm99.github.io/bonsai/)** · **[Icon gallery](https://tiennm99.github.io/bonsai/icons/)** +**→ [Live demo](https://tiennm99.github.io/bonsai/)** · **[Icon gallery](https://tiennm99.github.io/bonsai/icons/)** · **[Layout variants](https://tiennm99.github.io/bonsai/variants/)** · **[Color themes](https://tiennm99.github.io/bonsai/themes/)** > 盆栽 (bonsai): "tray planting" — the art of growing miniature trees through patient, deliberate cultivation. Every branch placed with care. @@ -19,7 +19,7 @@ Bonsai treats your bio page the same way: a quiet, well-pruned page that surface - **35 icons out of the box** — 25 brand (GitHub, Mastodon, Bluesky, X, Threads, LinkedIn, Instagram…) + 10 utility (mail, globe, rss…). Vendored from [Simple Icons](https://simpleicons.org) and [Lucide](https://lucide.dev). - **Light & dark mode** — respects `prefers-color-scheme`; optional toggle. - **Zero JavaScript by default** — pure HTML + CSS; opt-in JS for theme toggle only. -- **Fast** — < 4 KB CSS, < 4 KB HTML, no web fonts (system stack), no runtime fetches. +- **Fast** — < 3 KB gzipped CSS, no web fonts (system stack), no runtime fetches. - **Accessible** — semantic HTML, focus-visible outlines, `prefers-reduced-motion`. - **Responsive** — mobile-first, looks right at every viewport. @@ -93,7 +93,10 @@ disableKinds = ["taxonomy", "term", "RSS", "sitemap", "404"] | `avatarBg` | string (CSS color) | `var(--bonsai-accent)` | Background color of the initials circle. | | `favicon` | string (URL) | `/favicon.ico` | Favicon path. | | `colorTheme` | string | `bonsai` | Palette: `bonsai`, `sakura`, `sumi`, or `koi`. See [Color themes](#color-themes). | +| `layout` | string | `stack` | Link arrangement: `stack`, `grid`, or `inline`. See [Layout variants](#layout-variants). | | `themeToggle` | bool | `false` | Render a sun/moon button in the footer + load the toggle script. | +| `ogImage` | bool | `true` | Set `false` to suppress all `og:image` / `twitter:image` tags. | +| `ogImageUrl` | string (URL) | — | Explicit OG preview image (1200×630 recommended). Overrides the avatar fallback and upgrades Twitter card to `summary_large_image`. | | `schema` | bool | `true` | Emit schema.org `Person` JSON-LD in ``. Set `false` if you provide your own. | | `jobTitle` | string | — | Optional `Person.jobTitle` field for JSON-LD. | | `location` | string | — | Optional `Person.address` field for JSON-LD. | @@ -123,6 +126,42 @@ Four built-in palettes, each with light + dark variants. Set `colorTheme` in `[p Live preview: **[tiennm99.github.io/bonsai/themes/](https://tiennm99.github.io/bonsai/themes/)**. +## Layout variants + +Three arrangements for `[[params.links]]`. Pick one via `layout` in `[params]`: + +| Value | Look | Best for | +|-------|------|----------| +| `stack` *(default)* | Full-width vertical buttons | ≤ 6 link bios; the classic Linktree shape. | +| `grid` | Two-column responsive grid (collapses to one column under 480 px) | 6–12 links; balances density and tap-target size. | +| `inline` | Icon-only horizontal row | Lots of accounts, short page; titles stay in DOM for screen readers. | + +Live preview: **[tiennm99.github.io/bonsai/variants/](https://tiennm99.github.io/bonsai/variants/)**. + +## i18n + +Theme-rendered strings (nav landmark, theme-toggle labels, default footer) live in `i18n/{lang}.toml`. Bundles for `en` and `vi` ship with the theme. + +To use another language, set Hugo's `defaultContentLanguage` and add a matching `i18n/{lang}.toml`: + +```toml +defaultContentLanguage = "fr" +``` + +```toml +# i18n/fr.toml +[nav_links_label] +other = "Liens" +[theme_toggle_label] +other = "Basculer le thème clair / sombre" +[theme_toggle_title] +other = "Basculer le thème" +[footer_default] +other = "© {{ .year }} {{ .name }}" +``` + +Missing keys fall back to `en`. User content (`name`, `tagline`, `bio`, link `title`s, `footerText`) is never auto-translated — it stays user-owned. + ## Available Icons
diff --git a/themes/bonsai/docs/journals/2026-05-03-v0-3-release.md b/themes/bonsai/docs/journals/2026-05-03-v0-3-release.md new file mode 100644 index 0000000..b329ef5 --- /dev/null +++ b/themes/bonsai/docs/journals/2026-05-03-v0-3-release.md @@ -0,0 +1,61 @@ +# Bonsai v0.3: Layout Variants and i18n Shipped Same Day, OG Auto-Generation Cut for Theme Weight + +**Date**: 2026-05-03 12:10 +**Severity**: Low +**Component**: Layout system, i18n bundle, OG meta tags, demo site +**Status**: Resolved + +## What Happened + +Shipped v0.3 features ~2 hours after v0.2 wrapped: `params.layout` for stack/grid/inline link arrangements, `i18n/{en,vi}.toml` bundles for theme strings, and `params.ogImageUrl` + `ogImage` controls for social previews. **Cut OG image auto-generation mid-implementation** — vendoring a TTF font + 4 base PNGs (~150 KB) conflicted directly with the theme's `< 4 KB CSS` minimalist principle. Reduced Phase 2 scope to URL override + smart `twitter:card` upgrade; deferred auto-gen to v0.4. All 5 phase files written, 4 of 5 phases done; tag/push pending user authorization. + +## The Brutal Truth + +The hardest thing about v0.3 wasn't writing code — it was killing a feature mid-implementation when its weight didn't match the theme's identity. Plan said "ship OG auto-generation," code said "vendor a 120 KB font," principle said "stay under 4 KB CSS." Three votes, principle won. Better to ship four solid features than five with one obese addition. + +The same-day cadence held because v0.2's modularity work paid dividends: layout variants slot in as one CSS section + one template line, i18n is one Hugo function call replacing literal strings, OG params are six lines of template logic. Zero refactors, zero broken tests, zero rollbacks. + +Caught one real bug in testing: my first i18n smoke test set `languageCode = "vi-VN"` and watched English render. Spent a minute confused before remembering Hugo uses `defaultContentLanguage` (not `languageCode`) to select i18n bundles. That's now documented in the README so the next person doesn't waste the same minute. + +## Technical Details + +1. **CSS specificity discipline carried from v0.2** — Layout variant rules scope under `.bio__links--{variant} .link { ... }`, never naked `.link { ... }`. The v0.2 theme-toggle bug (where `.theme-toggle svg` silently beat `.theme-toggle__sun` at specificity 0,1,1 vs 0,1,0) is fresh enough that the discipline came automatically. No specificity collisions in v0.3. + +2. **Inline layout uses screen-reader-friendly visual hiding** — `.bio__links--inline .link__title` clips the title to a 1×1 px region with `clip: rect(0,0,0,0)` instead of `display: none`. Titles stay in the DOM and accessible to screen readers; visually only the icon shows. Standard pattern, but worth being deliberate about — `display: none` would have hidden them from AT, breaking the link's purpose for non-sighted users. + +3. **Hugo `languageCode` ≠ i18n bundle selector** — `languageCode = "vi-VN"` sets the HTML `lang` attribute and metadata, but Hugo picks `i18n/*.toml` bundles based on `defaultContentLanguage`. For a single-language site, both must be set. Documented this in the README's new i18n section because it's the kind of gotcha that costs people 20 minutes the first time. + +4. **OG image auto-generation killed for theme-weight reasons** — Original plan: vendor a TTF (~50–120 KB Latin+Vietnamese subset of Inter) and 4 base PNGs (~5–20 KB each), use Hugo's `images.Text` filter to overlay name + tagline at build time. Total binary weight: ~100–200 KB. Theme today: ~12 KB CSS + ~13 KB icon SVGs. Ratio: auto-gen would have nearly doubled the theme's binary footprint to support an opt-in feature. Decision: ship the URL override (6 lines of template logic, zero new bytes), defer auto-gen to v0.4 once a smaller path is found. **The feature didn't change; the implementation timeline did.** + +5. **README's `< 4 KB CSS` claim was already stale** — Inherited from v0.1. v0.2's themes-gallery + v0.3's variants-gallery pushed raw CSS to 11.8 KB. Gzipped is still 2.9 KB. Updated the claim to `< 3 KB gzipped CSS`, which is the meaningful number for transfer cost. Lesson: marketing-style claims rot fast; quote sizes that match reality. + +## Root Cause Analysis + +- **Plan optimism vs implementation reality** — On paper, "use Hugo's `images.Text` filter" sounds like a one-partial change. In practice, it's a font-vendoring decision with license review, glyph-coverage analysis, and a binary asset commitment. The plan should have flagged "binary asset weight" as a Phase 2 risk dimension, not just "Hugo template quirks." + +- **Hugo locale resolution is under-documented for single-language sites** — Most Hugo i18n examples assume multilingual setups with `[languages]` blocks. Single-language with non-English bundle is a less-traveled path; the `defaultContentLanguage` requirement is easy to miss. + +- **CSS bundle drift went uncaught for a release cycle** — v0.2 grew the bundle past the README claim and nobody (me) noticed until v0.3 added more. A doctests-style "verify README claims" hook would catch this; not worth building yet, but worth knowing exists as a category of stale-doc bug. + +## Lessons Learned + +1. **Kill features mid-implementation when weight contradicts identity.** A theme that brags about being small can't ship a feature that doubles its binary footprint, even if the feature is desirable. Defer is a perfectly fine answer; ship-anyway-with-bloat is not. + +2. **CSS specificity discipline becomes muscle memory after one incident.** The v0.2 theme-toggle bug was acutely painful enough that v0.3's variant CSS auto-scoped under parent classes without conscious effort. Real bugs teach better than style guides. + +3. **Document the gotchas you trip over in real time.** The `defaultContentLanguage` confusion took 60 seconds to resolve and would take 20 minutes for a fresh user. Three lines in the README's i18n section makes it a non-issue forever. + +4. **Quote real numbers, not aspirational ones.** `< 4 KB CSS` was true once. `< 3 KB gzipped CSS` is true now. The second one will stay true longer because it's tied to a transfer-cost reality, not a frozen-at-launch ideal. + +5. **Same-day cadence works when phases are independent.** Phases 1, 2, 3 touched disjoint files (`bio-card.html`+CSS, `head.html`, `i18n/`+two partials). They could have shipped in any order. That's not architectural luck — it's a result of v0.2's component carve-up making each phase a leaf change instead of a refactor. + +## Next Steps + +- Regenerate `images/screenshot.png` + `images/tn.png` from a real browser (no headless tooling available in the dev environment); needed for Hugo themes-site refresh. +- Commit + tag `v0.3.0` after screenshots land. +- Verify OG previews on Twitter / Facebook / LinkedIn debuggers post-deploy. +- v0.4 candidates: revisit OG auto-generation with a smaller path (system-font CSS-rendered SVG behind a rasterizer? Single-palette generic base? User-supplied font path?), RSS opt-in, multi-section bio, more icons. +- Consider extracting the gallery CSS (`.themes-gallery__*`, `.variants-gallery__*`) to a separate `gallery.css` loaded only on demo pages — saves ~2 KB on every user's site for CSS that only the demo needs. + +**Owner**: @tiennm99 +**Timeline**: Tag pending screenshot regen diff --git a/themes/bonsai/plans/260503-1120-v0-3-release/phase-01-layout-variants.md b/themes/bonsai/plans/260503-1120-v0-3-release/phase-01-layout-variants.md new file mode 100644 index 0000000..8bf2708 --- /dev/null +++ b/themes/bonsai/plans/260503-1120-v0-3-release/phase-01-layout-variants.md @@ -0,0 +1,76 @@ +--- +phase: 1 +title: "Layout variants (stack/grid/inline)" +status: pending +priority: P2 +effort: "2h" +dependencies: [] +--- + +# Phase 1: Layout variants (stack/grid/inline) + +## Overview +Introduce `params.layout` to let users pick how `[[params.links]]` are arranged. Pure CSS — no JS, no template forks. Default unchanged (stack). + +## Requirements +- Functional: + - New string param `layout` with values: `stack` (default), `grid`, `inline`. + - `stack`: current full-width vertical buttons. + - `grid`: 2-col responsive grid (collapses to 1-col below ~480px). + - `inline`: icon-only horizontal row (titles become `aria-label`/`title` for a11y); great for users with many short links. + - Unknown value → falls back to `stack` and emits a Hugo warning via `errorf`. +- Non-functional: + - Zero JS additions. + - Visible layout class lives on `