From b43257ccc6693f7326fefc8b5495c1a7d3de3a4e Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sun, 10 May 2026 03:05:13 +0700 Subject: [PATCH] docs: post-v0.2.0 planning + v0.3.0 roadmap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reports from parallel review agents (code-reviewer, researcher, project-manager) plus brainstorm synthesis recommending the three-release sequence (v0.2.0 ships, v0.2.1 patches review P1s, v0.3.0 features). Plans created: - plans/260510-0232-tsuki-v0.2.1-patch/ (status: code-complete) - plans/260510-0144-tsuki-v0.3.0/ (6 phases, ~5d, Phase 1 complete) Reports: - code-reviewer-260510-0144: 30 post-v0.2.0 findings (6 P1, 13 P2, 11 P3) - researcher-260510-0144: Hugo theme 2026 evolution (WCAG 2.2, llm.txt, Speculation Rules) - project-manager-260510-0144: v0.2.0 status audit - brainstorm-260510-0144: three-release recommendation - code-reviewer-260510-0244: v0.2.1 patch review (9.7/10, auto-approve) - tester-260510-0244: v0.2.1 patch validation (11/11 smoke, CSS 3962/4200) Cross-linked plan dependencies: - v0.2.0 prereq → blocks v0.2.1, v0.3.0 - v0.2.1 patch → blocks v0.3.0 Journal: docs/journals/260510-0232-post-v0.2.0-planning-session.md --- ...60510-0232-post-v0.2.0-planning-session.md | 82 +++ .../plan.md | 1 + .../phase-01-v0.2.1-carry.md | 95 ++++ .../phase-02-budget-rebase.md | 109 ++++ .../phase-03-author-ux.md | 89 +++ .../phase-04-wcag-2.2-audit.md | 92 +++ .../phase-05-ai-discovery.md | 113 ++++ .../phase-06-test-depth.md | 134 +++++ .../plans/260510-0144-tsuki-v0.3.0/plan.md | 72 +++ .../260510-0232-tsuki-v0.2.1-patch/plan.md | 48 ++ ...storm-260510-0144-post-v0.2.0-direction.md | 112 ++++ ...-tsuki-v0.2.0-post-release-improvements.md | 319 +++++++++++ ...eviewer-260510-0244-v0.2.1-patch-review.md | 147 +++++ ...manager-260510-0144-tsuki-v0.2.0-status.md | 296 ++++++++++ ...r-260510-0144-hugo-theme-2026-evolution.md | 533 ++++++++++++++++++ ...ter-260510-0244-v0.2.1-patch-validation.md | 71 +++ 16 files changed, 2313 insertions(+) create mode 100644 themes/tsuki/docs/journals/260510-0232-post-v0.2.0-planning-session.md create mode 100644 themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-01-v0.2.1-carry.md create mode 100644 themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-02-budget-rebase.md create mode 100644 themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-03-author-ux.md create mode 100644 themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-04-wcag-2.2-audit.md create mode 100644 themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-05-ai-discovery.md create mode 100644 themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-06-test-depth.md create mode 100644 themes/tsuki/plans/260510-0144-tsuki-v0.3.0/plan.md create mode 100644 themes/tsuki/plans/260510-0232-tsuki-v0.2.1-patch/plan.md create mode 100644 themes/tsuki/plans/reports/brainstorm-260510-0144-post-v0.2.0-direction.md create mode 100644 themes/tsuki/plans/reports/code-reviewer-260510-0144-tsuki-v0.2.0-post-release-improvements.md create mode 100644 themes/tsuki/plans/reports/code-reviewer-260510-0244-v0.2.1-patch-review.md create mode 100644 themes/tsuki/plans/reports/project-manager-260510-0144-tsuki-v0.2.0-status.md create mode 100644 themes/tsuki/plans/reports/researcher-260510-0144-hugo-theme-2026-evolution.md create mode 100644 themes/tsuki/plans/reports/tester-260510-0244-v0.2.1-patch-validation.md diff --git a/themes/tsuki/docs/journals/260510-0232-post-v0.2.0-planning-session.md b/themes/tsuki/docs/journals/260510-0232-post-v0.2.0-planning-session.md new file mode 100644 index 0000000..046ebf8 --- /dev/null +++ b/themes/tsuki/docs/journals/260510-0232-post-v0.2.0-planning-session.md @@ -0,0 +1,82 @@ +# Post-v0.2.0 Planning Session: Scope, Sequencing, Unresolved Decisions + +**Date**: 2026-05-10 02:32 +**Severity**: Medium +**Component**: tsuki v0.2.0 → v0.3.0 roadmap +**Status**: Completed, action phase next + +## What Happened + +Spawned 3 parallel review agents (code-reviewer, researcher, project-manager) against v0.2.0 prerelease state. Aggregated findings, ran brainstorm + plan, scaffolded v0.3.0 as 6-phase delivery (~5d total). Identified the real tension: ship v0.2.0 as-is or fold freshly discovered P1 code findings back into same tag. + +Recommendation adopted: three-release sequence (v0.2.0 ships now, v0.2.1 absorbs P1s, v0.3.0 features + hardening). + +## The Brutal Truth + +This planning session nearly became a disaster of scope creep. The code review team found 30 findings (6 P1, 13 P2, 11 P3) AFTER v0.2.0 was declared complete. The most severe is `render-link.html` using unsafeHTML — self-XSS in a single-author theme. The honest reality: this is embarrassing, but it's self-inflicted (author writes their own posts). Folding every finding discovered into v0.2.0 would've meant goalpost shift, gallery submission delay, and the whole prerelease becoming vaporware. Made the hard call to keep v0.2.0 boundaries crisp and patch forward. + +The frustrating part: that XSS check should've caught this during review iteration 1, not iteration N. Single-maintainer review fatigue is real. + +## Technical Details + +**Code review findings summary:** +- 6 P1: `safeHTML` abuse (1), image lazy-loading gaps (2), i18n fallback correctness (1), accessibility (2) +- 13 P2: CSS bundle bloat, TOC narrow-viewport, author UX gaps, i18n path coverage +- 11 P3: type hints, comment quality, refactor opportunities + +**v0.2.0 status confirmed:** All 7 phases shipped. Prerelease checklist scoped down to mechanical (version strings, release notes) + decisions (bundling, defaults) + external (GitHub release, gallery). + +**v0.3.0 skeleton (6 phases):** +- Phase 1: Conditional v0.2.1 patch carry (decide: Goldmark `unsafe: true`, XSS surface) +- Phase 2: Budget rebase (per-kind CSS bundling ~1.2 KB gz savings) — gates phases 3+5 +- Phase 3: Author UX (details/summary collapse, TOC narrow-viewport, i18n coverage) +- Phase 4: WCAG 2.2 AA hardening pass +- Phase 5: AI/discovery layer (llm.txt, Speculation Rules opt-in) +- Phase 6: Smoke-test depth + Hugo version matrix (0.146 vs 0.154) + +**Backwards-compat constraint:** v0.2.0 sites build on v0.3.0 zero-config (new params default off). + +## What We Tried + +1. Absorb P1s into v0.2.0 — rejected, breaks schedule and review process integrity +2. Ship bare v0.2.0 with known P1s unpatched — considered, declined (minimal trust impact but not ideal) +3. Sequencing: bundle budget + feature delivery — P2 rebase must happen first (3 → 5 dependent) + +## Root Cause Analysis + +Prerelease checklist execution was solid but post-release review unearthed findings the initial pass missed. Why? Single reviewer, compressed timeline, fatigue. The Goldmark `unsafe: true` decision was made early to unblock `render-link.html` use of HTML comments — never revisited during final hardening. This is process debt, not code debt. + +The real lesson: peer review is impossible in a one-person team. These findings would've been caught by a second pair of eyes. + +## Lessons Learned + +- **Scope closure is hard, commit to it.** Post-release review findings should not retroactively scope-creep the release. Document and defer. +- **Sequencing gates matter.** Bundle size reduction (Phase 2) must land first — downstream feature work (Phase 3 UX, Phase 5 AI layer) needs that headroom. Skip this, ship bloated. +- **Single maintainer risk is real.** One person shipping, reviewing, deciding, and planning is unsustainable past v0.3.0 without external validation (peer review, demo site analytics, community feedback). +- **Goldmark `unsafe: true` is a decision debt marker.** Flag it, revisit it, document why (or remove it). + +## Next Steps + +**Decisions pending (6 open questions in plan.md):** +1. Keep or drop Goldmark `unsafe: true` (affects Phase 1 patch shape) +2. Hugo version floor: 0.146 (broad compat) or 0.154 (CI-tested) +3. Audience signal: solo bloggers vs teams (prioritization for v0.4.0+) +4. Demo site analytics (currently blind on feature usage) +5. v0.2.1 patch: independent release or fold into Phase 1 +6. `data/profile.yaml: url` field: document or remove + +**Critical path:** Maintainer decisions on Q1, Q2, Q5 unblock Phase 1 scope. Budget rebase (Phase 2) follows. Phases 3–6 parallel track (5 days total). + +**Risk:** Single maintainer, 5d estimate is optimistic if decision stall occurs. Unblocking mechanism needed (escalate Q1–Q6 to community or triage publicly). + +**Reference reports (do NOT reread):** +- code-reviewer: 30 findings audit (P1/P2/P3 classification) +- researcher: 2026 Hugo platform evolution map (WCAG/llm.txt/Speculation Rules) +- project-manager: v0.2.0 phase + checklist status + gap analysis +- brainstorm: three-release sequencing rationale +- plan.md + phase-01..06.md: full implementation scaffold + +--- + +**Status:** DONE +**Summary:** v0.3.0 planning complete; shipping three-release sequence (v0.2.0 now, v0.2.1 patches, v0.3.0 features); 6 unresolved maintainer decisions block Phase 1, require triage. diff --git a/themes/tsuki/plans/260509-0947-v0.2.0-prerelease-checklist/plan.md b/themes/tsuki/plans/260509-0947-v0.2.0-prerelease-checklist/plan.md index a49b5c1..4ec4357 100644 --- a/themes/tsuki/plans/260509-0947-v0.2.0-prerelease-checklist/plan.md +++ b/themes/tsuki/plans/260509-0947-v0.2.0-prerelease-checklist/plan.md @@ -4,6 +4,7 @@ status: pending created: 2026-05-09 target: v0.2.0 tag + Hugo theme gallery submission predecessor: plans/archive/260508-2305-tsuki-v0.2.0-roadmap/ +blocks: [260510-0232-tsuki-v0.2.1-patch, 260510-0144-tsuki-v0.3.0] --- # v0.2.0 prerelease checklist diff --git a/themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-01-v0.2.1-carry.md b/themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-01-v0.2.1-carry.md new file mode 100644 index 0000000..bf50a4e --- /dev/null +++ b/themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-01-v0.2.1-carry.md @@ -0,0 +1,95 @@ +--- +phase: 1 +title: "v0.2.1 carry — review P1s + CI hygiene" +status: completed +priority: P1 +effort: "1d" +dependencies: [] +completed_date: 2026-05-10 +notes: "All 7 fixes shipped on main pre-tag. v0.2.1 plan owns the tag deliverable." +--- + +# Phase 1: v0.2.1 carry — review P1s + CI hygiene + +## Overview + +Conditional phase: if maintainer ships v0.2.1 patch independently, this phase becomes a no-op. Otherwise carries the 5 review P1s + 2 CI hygiene items into v0.3.0 so they don't ship hidden under a "features" banner. + +## Context Links + +- Source: `plans/reports/code-reviewer-260510-0144-tsuki-v0.2.0-post-release-improvements.md` (P1-1..P1-5, P3-8, P3-9) +- Brainstorm: `plans/reports/brainstorm-260510-0144-post-v0.2.0-direction.md` + +## Requirements + +- Functional: each P1 fix preserves existing demo-site rendering +- Non-functional: smoke tests + htmltest still green; CSS/JS budget unchanged + +## Key Insights + +- P1-1 (`render-link.html` `safeHTML`) is self-XSS in single-author theme — fix is consistency, not crit-sec +- P1-2 (`seo.html` profile.url chain) is fragile but currently works; refactor for nil-safety +- htmltest-action floating-tag is supply-chain risk flagged in v0.2.0 prerelease checklist + +## Architecture + +Surgical edits, no structural changes. Each fix is 1–5 lines. + +## Related Code Files + +- Modify: `layouts/_markup/render-link.html` (P1-1, P1-6) +- Modify: `layouts/_partials/head/seo.html` (P1-2) +- Modify: `layouts/_partials/home/projects.html` (P1-3) +- Modify: `layouts/_partials/nav.html` (P1-4) +- Modify: `layouts/_partials/comments.html` (P1-5) +- Modify: `.github/workflows/pages.yml` (P3-8 SHA pin, P3-9 dead branch) +- Modify: `docs/data-schemas.md` (document `data/profile.yaml: url` if kept — see plan.md unresolved Q6) +- Modify: `CHANGELOG.md` (Unreleased → 0.3.0 entries) + +## Implementation Steps + +1. **P1-1** — `render-link.html:10`: drop `| safeHTML` from `{{ .Text | safeHTML }}`. Verify italic/emphasis-in-link still renders (Goldmark emits already-escaped inline HTML via `.Text`). Add demo post `exampleSite/content/post/render-hooks-demo/index.md` with `[*emphasis*](https://example.com)` to confirm. +2. **P1-2** — `seo.html:64`: replace fragile `site.Params.profile.url | default ...` chain with nil-safe `with` blocks resolving `$authorURL` from `site.Params.profile.url`, then `site.Data.profile.url`, then `site.Home.Permalink`. +3. **P1-3** — `projects.html:30,33`: pipe both `repo` and `demo` URLs through `safeURL`; add `noreferrer` to `rel`. Optional `target="_blank"` for external nav (decision: yes, external project links should open new tab). +4. **P1-4** — `nav.html:6`: pipe `.URL` through `relURL`. Idempotent on already-resolved permalinks; fixes sub-path deploys. +5. **P1-5** — `comments.html:1-4`: change gate to `{{- if and $g $g.enable $g.repo $g.repoId $g.categoryId -}}`. Add HTML comment when gated-off for debug visibility. +6. **P3-8** — `.github/workflows/pages.yml:74`: replace `wjdp/htmltest-action@master` with commit SHA. Fetch via `gh api repos/wjdp/htmltest-action/commits/master | jq -r .sha`. Add comment with date + tag for future audit. +7. **P3-9** — `.github/workflows/pages.yml:48-54`: drop dead `if find ... | head -1 | grep -q . then ... else ... fi` guard around Pagefind. Just run `npx pagefind --site exampleSite/public`. +8. Run `hugo --gc --minify -s exampleSite -d exampleSite/public` locally + `./scripts/smoke-tests.sh` to confirm no regression. +9. Update CHANGELOG with each fix in `[Unreleased]` `### Fixed` block. + +## Todo List + +- [ ] P1-1 render-link.html safeHTML drop + demo post +- [ ] P1-2 seo.html $authorURL nil-safe refactor +- [ ] P1-3 projects.html safeURL + noreferrer + target=_blank +- [ ] P1-4 nav.html .URL relURL pipe +- [ ] P1-5 comments.html stricter gate +- [ ] P3-8 htmltest-action SHA pin +- [ ] P3-9 drop dead Pagefind branch +- [ ] Local build + smoke test verification +- [ ] CHANGELOG entries + +## Success Criteria + +- [ ] All 5 P1 fixes applied, demo site builds clean +- [ ] htmltest CI step uses pinned SHA (no `@master`) +- [ ] No `if find` dead branch in pages.yml +- [ ] Smoke tests still pass (no new failures) +- [ ] CHANGELOG entries written + +## Risk Assessment + +- **P1-1 risk:** Dropping `safeHTML` may break a hidden author-side use case. Mitigation: search `exampleSite` for any link-text-with-HTML; if found, document the change in CHANGELOG. +- **P1-2 risk:** Refactor introduces new template error if Hugo idiom drifts. Mitigation: test demo build locally before pushing. +- **P3-8 risk:** SHA pin requires periodic refresh. Mitigation: add Dependabot config for GitHub Actions in v0.3.1 (out of v0.3.0 scope). + +## Security Considerations + +- P1-1, P1-3 reduce stored-HTML/URL surface +- P3-8 closes supply-chain attack vector via floating master tag + +## Next Steps + +- Phase 2 depends on Phase 1 completion (clean baseline before structural CSS bundle changes) +- If maintainer ships v0.2.1 first → mark this phase `completed` and skip diff --git a/themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-02-budget-rebase.md b/themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-02-budget-rebase.md new file mode 100644 index 0000000..861dda9 --- /dev/null +++ b/themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-02-budget-rebase.md @@ -0,0 +1,109 @@ +--- +phase: 2 +title: "Theme-budget rebase — per-kind CSS + JS gate + default cleanup" +status: pending +priority: P1 +effort: "1d" +dependencies: [1] +--- + +# Phase 2: Theme-budget rebase + +## Overview + +Frees ~1.2 KB gz from the CSS bundle by switching from "concat-all-on-every-page" to per-page-kind bundles, gates `code-copy.js` on post pages only, and prunes ~30 redundant `| default ""` calls. This phase enables Phase 5 (Speculation Rules) and Phase 3 (details CSS) without breaching the 4 KB gz cap. + +## Context Links + +- Source: `plans/reports/code-reviewer-260510-0144-tsuki-v0.2.0-post-release-improvements.md` (P2-2, P2-3, P2-9) +- CI assertion: `.github/workflows/pages.yml` CSS budget step + +## Requirements + +- Functional: every page kind (home, single, list, taxonomy, search) renders identically to v0.2.0 +- Non-functional: total CSS gz ≤ 4 KB on **each** kind individually (currently aggregate ≈ 4.2 KB) +- JS gz ≤ 1 KB on site-wide bundle; `code-copy.js` excluded from non-post pages + +## Key Insights + +- Reviewer estimates ~30% of bundle is dead bytes on home/list/taxonomy (toc.css + comments.css + archive.css unused) +- Per-kind split: `core.css` (always) + `home.css` (home only) + `single.css` (post only, includes toc/callouts/comments) + `archive.css` (list+archives) + `search.css` (search only) +- Hugo `resources.Concat` accepts a slice; `head.html` switches on `.Kind` + +## Architecture + +``` +head.html +├── always: tokens.css + reset.css + typography.css + layout.css + components.css + view-transitions.css → core.css +├── if .Kind == "home": + home.css → home.css +├── if .Kind == "page": + toc.css + callouts.css + comments.css → single.css +├── if .Kind == "term"|"taxonomy"|"section": + archive.css → archive.css +└── if .Section == "search": + search.css → search.css +``` + +Each bundle: `Concat | minify | fingerprint | resources.Get`, emit one `` per applicable bundle. + +## Related Code Files + +- Modify: `layouts/_partials/head.html` (CSS bundle assembly) +- Modify: `layouts/_partials/footer.html` (gate code-copy.js include on `eq .Kind "page"`) +- Modify: ~10 templates for `| default ""` cleanup (`meta.html`, `header.html`, `footer.html`, `404.html`, `nav.html`, `home/*.html`, `search/list.html`) +- Modify: `.github/workflows/pages.yml` CSS budget step (assert per-kind, not aggregate) +- Modify: `scripts/smoke-tests.sh` (assert per-kind bundle size) +- Modify: `CHANGELOG.md` Unreleased +- Read: `assets/css/*.css` (no changes — partition only) + +## Implementation Steps + +1. Map current CSS partials → kinds (use Hugo `.Kind` and `.Section`). Document the matrix in head.html comments. +2. Refactor `head.html` to assemble bundles via `slice` + `where` + `resources.Concat`. Keep `core.css` as the fingerprinted base. Conditional bundles get their own `` tags. +3. Update SRI integrity emit to handle multiple bundles (loop over slice). +4. Move `code-copy.js` `` in ``. Chromium-only. Graceful no-op on Safari/Firefox. +- Both features should be **opt-in** to respect users on metered connections / privacy-conscious AI policy. + +## Architecture + +### llm.txt +- Add `[outputFormats.LlmTxt]` + `[mediaTypes."text/plain"]` to demo `hugo.yaml` +- Add `outputs.home: ["HTML", "RSS", "LlmTxt"]` to demo `hugo.yaml` +- Create `layouts/index.llmtxt.txt` — Hugo template emitting plain text per llmstxt.org spec +- Theme exposes default; site can override with own `index.llmtxt.txt` + +### Speculation Rules +- Gated in `head.html`: `{{ with site.Params.prefetch }}{{ if .enable }}{{ end }}{{ end }}` +- Default JSON: `{"prefetch":[{"source":"document","where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":"/search/*"}}]},"eagerness":"moderate"}]}` +- Allow override via `params.prefetch.rules` (raw JSON string consumer can inject) + +## Related Code Files + +- Create: `layouts/index.llmtxt.txt` (in theme, optional override) +- Modify: `exampleSite/hugo.yaml` (output format + outputs.home) +- Modify: `layouts/_partials/head.html` (Speculation Rules emit gate) +- Modify: `docs/config.md` (document `params.prefetch.{enable,rules}`) +- Modify: `docs/customization.md` (document llm.txt override path) +- Modify: `CHANGELOG.md` +- Modify: `scripts/smoke-tests.sh` (assert llm.txt present + speculationrules script absent by default) + +## Implementation Steps + +1. Define output format in demo `hugo.yaml`: + ```yaml + outputFormats: + LlmTxt: + mediaType: "text/plain" + baseName: "llm" + isPlainText: true + outputs: + home: ["HTML", "RSS", "LlmTxt"] + ``` +2. Write `layouts/index.llmtxt.txt`: + - `# {{ site.Title }}` + tagline from `data/profile.yaml` + - `> {{ .Site.Params.description }}` + - `## About` block from `data/profile.yaml: bio` + - `## Recent posts` — top 10 `site.RegularPages | first 10`, each as `- [{{ .Title }}]({{ .Permalink }}): {{ .Description | default .Summary | plainify }}` + - `## Projects` from `data/projects.yaml` +3. Build demo, confirm `exampleSite/public/llm.txt` exists and is valid plain text. +4. Add Speculation Rules emit in `head.html` gated on `params.prefetch.enable`. Write minimal default JSON + allow `params.prefetch.rules` raw string override. +5. Add `params.prefetch` block to `docs/config.md` with example + browser-support note. +6. Local test: enable in demo `hugo.yaml`, confirm `` block, pipe through `jq -e empty` + - **og_image_abs** — regex: `` on demo post, grep CSS for `details {` rule + - **llm.txt** — `[ -f exampleSite/public/llm.txt ]` + - **no_speculation_default** — `! grep speculationrules exampleSite/public/index.html` + - **per_kind_css_budget** — loop over [home, post, list, search]; for each, gzip the linked CSS bundles, sum sizes, assert ≤ 4096 +4. Add synthetic test posts for branches not covered by demo content: + - `lastmod-test.md` with `lastmod: 2099-01-01` to fire Lastmod byline + - `no-tags-test.md` with `tags: []` to exercise empty-tags branch + no-related-posts branch +5. Update `pages.yml` with matrix on `hugo` versions. Build, smoke, htmltest in each. Deploy gated on `matrix.hugo == '0.154.0'`. +6. Run on temp branch, observe both matrix legs green. If 0.146 fails: triage. Decision per Q2: bump floor or document the 0.146 limitation. +7. CHANGELOG entry under Added: 10+ new smoke assertions + Hugo CI matrix. + +## Todo List + +- [ ] Inventory existing assertions +- [ ] jq added to CI environment +- [ ] callout assertion +- [ ] jsonld_valid assertion +- [ ] og_image_abs assertion +- [ ] dark_theme assertion +- [ ] render_link_rel assertion +- [ ] code_copy_css assertion +- [ ] details assertion +- [ ] llm.txt assertion +- [ ] no_speculation_default assertion +- [ ] per_kind_css_budget assertion +- [ ] lastmod-test.md synthetic post +- [ ] no-tags-test.md synthetic post +- [ ] Hugo matrix in pages.yml +- [ ] Both matrix legs green +- [ ] CHANGELOG entries + +## Success Criteria + +- [ ] 21 total smoke assertions (11 existing + 10 new), all pass on demo build +- [ ] CI runs Hugo 0.146 + 0.154; both legs green or theme.toml floor adjusted with rationale +- [ ] Synthetic posts exercise branches not previously covered +- [ ] CI runtime ≤ 5 min including matrix +- [ ] No flaky checks (run CI 3× to confirm) + +## Risk Assessment + +- **0.146 fails, 0.154 succeeds:** decision blocker per Q2. Mitigation: gather diff, decide floor bump or workaround within phase budget. +- **jq parse errors on legitimate JSON-LD:** Hugo emits unescaped quotes in some edge cases. Mitigation: pre-process with `awk` to extract block; jq operates on extracted string only. +- **CI matrix doubles minutes:** GitHub Actions free tier has limits. Mitigation: free repos have ample minutes; tsuki is OSS. +- **Synthetic test posts surface in production:** they'd appear in demo blog index. Mitigation: `headless: true` in their frontmatter so they don't list. + +## Security Considerations + +None — test/CI only. + +## Next Steps + +- v0.3.0 tag candidate post-Phase-6 +- Subsequent: gohugoThemes registry CI green check (carry from v0.2.0 prereq) +- Then: tag, release notes, gallery PR diff --git a/themes/tsuki/plans/260510-0144-tsuki-v0.3.0/plan.md b/themes/tsuki/plans/260510-0144-tsuki-v0.3.0/plan.md new file mode 100644 index 0000000..de2e041 --- /dev/null +++ b/themes/tsuki/plans/260510-0144-tsuki-v0.3.0/plan.md @@ -0,0 +1,72 @@ +--- +title: tsuki v0.3.0 — review fixes carry, budget rebase, a11y 2.2, AI discovery +status: pending +created: 2026-05-10 +target: v0.3.0 tag, ~5 days effort (4d if v0.2.1 ships first and Phase 1 becomes no-op) +predecessor: plans/260510-0232-tsuki-v0.2.1-patch/ +blockedBy: [260509-0947-v0.2.0-prerelease-checklist, 260510-0232-tsuki-v0.2.1-patch] +source_brainstorm: plans/reports/brainstorm-260510-0144-post-v0.2.0-direction.md +source_reports: + - plans/reports/code-reviewer-260510-0144-tsuki-v0.2.0-post-release-improvements.md + - plans/reports/researcher-260510-0144-hugo-theme-2026-evolution.md + - plans/reports/project-manager-260510-0144-tsuki-v0.2.0-status.md +--- + +# tsuki v0.3.0 plan + +Carries forward the post-v0.2.0 review P1s (conditional on whether maintainer ships a v0.2.1 patch first), rebases the CSS/JS budget to free headroom, layers WCAG 2.2 AA + AI-discovery features, then expands smoke-test depth to lock the surface in. + +## Phases + +| # | Phase | Priority | Status | Depends on | Effort | +|---|---|---|---|---|---| +| 1 | [v0.2.1 carry — review P1s + CI hygiene](phase-01-v0.2.1-carry.md) | P1 | completed | v0.2.0 tagged | 1d | +| 2 | [Theme-budget rebase](phase-02-budget-rebase.md) | P1 | pending | Phase 1 | 1d | +| 3 | [Author UX — details + TOC narrow + en.yml](phase-03-author-ux.md) | P2 | pending | Phase 2 (CSS bundle layout) | 1d | +| 4 | [WCAG 2.2 AA audit pass](phase-04-wcag-2.2-audit.md) | P2 | pending | Phase 3 (audit covers new surface) | 1d | +| 5 | [AI/discovery — llm.txt + Speculation Rules](phase-05-ai-discovery.md) | P2 | pending | Phase 2 (budget headroom) | 0.5d | +| 6 | [Smoke-test expansion + Hugo CI matrix](phase-06-test-depth.md) | P1 | pending | Phases 1–5 | 0.5d | + +## Sequencing + +- **Phase 1 conditional:** if v0.2.1 patch ships independently, drop Phase 1 from v0.3.0 scope. Otherwise carry forward — these P1s must not ship under a "v0.3.0 features" banner without being acknowledged. +- **Phase 2 gates 3+5:** per-kind CSS bundling frees ~1.2 KB gz; without it, Phase 5 Speculation Rules + Phase 3 details CSS may breach the 4 KB gz budget. +- **Phases 3 + 5 may run parallel** after Phase 2 — different files, no shared CSS partial. +- **Phase 4 last before Phase 6:** WCAG audit catches regressions from Phases 1–3 changes; Phase 6 then locks them into smoke tests. +- **Phase 6 lands last:** smoke tests assert the actual shipped surface, not in-flight branches. + +## Constraints + +- CSS ≤ 4 KB gz on **every page kind** (CI-asserted; Phase 2 makes this per-kind not aggregate) +- JS ≤ 1 KB gz site-wide (Phase 2 reduces by gating code-copy.js) +- Zero build step (pure Hugo + browser ES modules) +- Backwards compatible: sites on v0.2.0 must build on v0.3.0 without config changes (new params default off) +- Vietnamese-first (vi.yml stays canonical; en.yml is a starter, not a behavioral switch) + +## Out of scope (deferred) + +- AVIF render hook → waits on cover-image feature decision (v0.4.0+) +- pa11y CI integration → after WCAG 2.2 baseline established (v0.3.1) +- Multi-author surface, lightbox, KaTeX, Mermaid → per v0.2.0 deferred list +- Cover image processing pipeline → tied to AVIF decision + +## Success criteria + +- All 5 review P1s closed (or acknowledged as already shipped in v0.2.1) +- CSS ≤ 4 KB gz on home / single / list / taxonomy / search kinds individually +- Lighthouse a11y ≥ 95 on exampleSite under WCAG 2.2 AA +- `i18n/en.yml` complete (~40 keys); theme builds with `defaultContentLanguage: en` without missing-key fallbacks +- `/llm.txt` artifact present in exampleSite build output +- Speculation Rules emit when `params.prefetch.enable: true`, absent otherwise +- Smoke tests assert: callout HTML, JSON-LD parses (jq), OG image absolute URL, dark-theme tokens, render-link rel-on-specific-link, code-copy class in CSS bundle +- CI matrix green on Hugo 0.146 (floor) + 0.154 (current) +- gohugoThemes registry CI green on tagged commit (carry-forward from v0.2.0 prereq) + +## Unresolved questions (maintainer decisions, do not block planning) + +1. **Goldmark `unsafe: true`** — keep (consumer raw-HTML compat) or drop (eliminates bio-XSS surface + reduces P1-1 blast radius)? Test on real sites: `grep -r '<' content/post/*/index.md`. Affects Phase 1 fix shape. +2. **Hugo version floor** — bump `theme.toml min_version` to 0.154 (CI-tested, narrower compat) or keep 0.146 (broader compat, untested)? Affects Phase 6 CI matrix decision. +3. **Audience signal** — solo bloggers vs teams? Drives whether multi-author / multi-language i18n moves out of deferred for v0.4.0. No code change for v0.3.0. +4. **Demo site analytics** — opt-in privacy-respecting analytics on `tiennm99.github.io/tsuki/` (e.g., GoatCounter)? Currently flying blind on which features get used. Affects v0.3.0+ feature prioritization, not v0.3.0 scope itself. +5. **v0.2.1 patch** — ship it, or fold its scope into v0.3.0 Phase 1? If folded, drop the "conditional" framing on Phase 1. +6. **`data/profile.yaml: url`** — add to documented schema (Phase 1 P1-2 fix), or remove the read in `seo.html`? Schema vs. minimalism trade-off. diff --git a/themes/tsuki/plans/260510-0232-tsuki-v0.2.1-patch/plan.md b/themes/tsuki/plans/260510-0232-tsuki-v0.2.1-patch/plan.md new file mode 100644 index 0000000..8316daf --- /dev/null +++ b/themes/tsuki/plans/260510-0232-tsuki-v0.2.1-patch/plan.md @@ -0,0 +1,48 @@ +--- +title: tsuki v0.2.1 — patch release for post-v0.2.0 review P1s +status: code-complete +created: 2026-05-10 +target: v0.2.1 tag, ~1d effort +implementation_landed: 2026-05-10 +review_score: 9.7/10 +validation: "11/11 smoke tests, CSS budget 3962/4200, Hugo 0.154 build clean" +predecessor: plans/260509-0947-v0.2.0-prerelease-checklist/ +blockedBy: [260509-0947-v0.2.0-prerelease-checklist] +blocks: [260510-0144-tsuki-v0.3.0] +source_brainstorm: plans/reports/brainstorm-260510-0144-post-v0.2.0-direction.md +source_review: plans/reports/code-reviewer-260510-0144-tsuki-v0.2.0-post-release-improvements.md +--- + +# tsuki v0.2.1 patch + +Surgical patch closing 5 P1 correctness/security findings from the post-v0.2.0 fresh-eyes review, plus 2 CI hygiene items. ~1 day. No new features. + +If shipped, marks v0.3.0 Phase 1 (`260510-0144-tsuki-v0.3.0/phase-01-v0.2.1-carry.md`) as completed. + +## Scope + +Identical to v0.3.0 Phase 1. See that phase file for detailed implementation steps, todo list, and success criteria. This plan exists to track v0.2.1 as its own release deliverable; do not duplicate the content. + +**Reference:** `plans/260510-0144-tsuki-v0.3.0/phase-01-v0.2.1-carry.md` + +## Workflow + +1. Branch from `main` post v0.2.0 tag: `git checkout -b release/v0.2.1` +2. Apply fixes per Phase 1 implementation steps 1–9 +3. Local build + smoke test verification +4. PR → main → merge +5. Tag `v0.2.1` annotated, release notes from CHANGELOG `[Unreleased]` (promote to `[0.2.1]`) +6. Mark v0.3.0 Phase 1 completed in `260510-0144-tsuki-v0.3.0/plan.md` + +## Success criteria + +- All 5 P1 fixes shipped (P1-1..P1-5 from review report) +- htmltest-action pinned to commit SHA +- Dead `if find` Pagefind branch removed +- CI green on tagged commit +- gohugoThemes registry submission unaffected (v0.2.0 PR continues to track v0.2.0 tag; v0.2.1 follows separately) + +## Unresolved questions + +1. Goldmark `unsafe: true` keep or drop — affects P1-1 fix shape. Defer per maintainer call; Phase 1 currently uses option B (drop `safeHTML` from link text, keep `unsafe: true`). +2. `data/profile.yaml: url` — document or remove. Defer; current Phase 1 plan documents it. diff --git a/themes/tsuki/plans/reports/brainstorm-260510-0144-post-v0.2.0-direction.md b/themes/tsuki/plans/reports/brainstorm-260510-0144-post-v0.2.0-direction.md new file mode 100644 index 0000000..d1b42b0 --- /dev/null +++ b/themes/tsuki/plans/reports/brainstorm-260510-0144-post-v0.2.0-direction.md @@ -0,0 +1,112 @@ +--- +report_type: brainstorm +status: DONE +date: 2026-05-10 +inputs: + - plans/reports/code-reviewer-260510-0144-tsuki-v0.2.0-post-release-improvements.md + - plans/reports/researcher-260510-0144-hugo-theme-2026-evolution.md + - plans/reports/project-manager-260510-0144-tsuki-v0.2.0-status.md +--- + +# Post-v0.2.0 Direction — Recommendation + +## Tension stated + +Reviewer found 6 "P1" items in the *post-v0.2.0* code review. Should we (a) hold the v0.2.0 tag until they're folded in, or (b) tag v0.2.0 as-is and address the new findings in a follow-up? + +## Recommendation: ship v0.2.0 as-is, follow with v0.2.1 patch + v0.3.0 feature release + +**Three releases, not one:** + +| Release | Scope | Effort | Trigger | +|---|---|---|---| +| **v0.2.0** | mechanical prerelease blockers only — htmltest pin, CI watch, Phase 7 plan accuracy, tag, gallery submit | 0.5 day | now | +| **v0.2.1** | post-release review P1s + dead-CI cleanup | 1 day | within 1–2 weeks of v0.2.0 | +| **v0.3.0** | P2 polish + Tier-1 research items + smoke-test expansion + author UX | 4–5 days | when v0.2.0 has gallery + real-world feedback | + +## Why not fold P1s into v0.2.0 + +1. **Severity reframe.** P1-1 (`render-link.html` `safeHTML`) is *self-XSS* — single-author theme; author writes their own posts. Reviewer's own note: *"threat model is 'I'm hurting myself'"*. Not a stored-XSS-from-untrusted-input vector. Worth fixing, not worth blocking a tag. +2. **Discovered after, not during.** v0.2.0 plan completed and signed off; review was fresh-eyes audit. Folding everything ever found shifts goalposts. +3. **Velocity matters for distribution.** Gallery submission visibility compounds; delay = stale demo, fewer downloads. +4. **v0.2.1 = 1 day.** Surgical patch: 5 P1 fixes + CI pin + dead-branch cleanup. Single commit, fast review, retag. +5. **CI gates haven't regressed.** Smoke tests + htmltest + budget assert still pass. The new findings are not regressions; they were latent. + +## Why a v0.3.0 feature release after + +Combines two streams that don't fit a patch: +- **From code-review:** P2-2 per-page-kind CSS bundling (frees ~1.2 KB gz headroom — needed for Tier-1 features), P3-1 smoke-test expansion, P3-4 `i18n/en.yml` skeleton. +- **From research:** WCAG 2.2 AA audit + fixes, `llm.txt` artifact, Speculation Rules opt-in, AVIF render-hook docs. Tier-1 items only. +- **From project-manager:** unblock the deferred items audience-signal can justify (lightbox, multi-author) — gather feedback first. + +## Main trade-off + +| Folding everything into v0.2.0 (rejected) | Three-release sequence (chosen) | +|---|---| +| Single clean release narrative | Three smaller scopes | +| Delays gallery submission ~3–5 days | Gallery submission tomorrow | +| Forces P2 budget split before stable | v0.3.0 frees budget cleanly | +| Higher risk: bigger diff = more review surface | Each release small enough to verify in one sitting | + +The chosen path costs *one extra version number* in exchange for shipping something the maintainer already validated, and getting external eyes on the demo site sooner. + +## v0.3.0 scope (preview — to be planned next) + +Mandatory (carries P1s if v0.2.1 is skipped): +- All P1s from review (5 fixes, ~1 day) +- htmltest-action SHA pin +- Dead CI `if find` branch removed + +Theme-budget rebase (enables future features): +- **P2-2** per-page-kind CSS bundling (~1.2 KB gz freed) +- **P2-3** gate `code-copy.js` on `eq .Kind "page"` +- **P2-9** drop redundant `| default ""` calls + +Author UX: +- **P2-1** `
` styling (CSS only, opens "rich content" door) +- **P2-10** `
`-wrap TOC on narrow viewports +- **P3-4** `i18n/en.yml` skeleton (~40 keys translated) + +Testing depth: +- **P3-1** smoke-test expansion (callout HTML, JSON-LD valid via `jq`, OG abs URL, dark-theme tokens) — 10 new assertions +- **P3-2** Hugo CI matrix (floor 0.146 + current 0.154) + +Research Tier-1: +- **WCAG 2.2 AA audit pass** on exampleSite via Lighthouse — focus appearance / target size / dragging SCs +- **llm.txt** auto-generation under exampleSite (theme metadata → `/llm.txt`) +- **Speculation Rules** as opt-in via `params.prefetch` — Chromium-only, graceful fallback + +Deferred to v0.4.0+ (need audience signal): +- AVIF render hook (only after cover-image feature decision) +- pa11y CI integration (after WCAG audit baseline established) +- Multi-author surface, lightbox, KaTeX/Mermaid (per v0.2.0 plan) + +## Maintainer decisions still pending (do not block v0.2.0) + +These were already flagged in v0.2.0 prerelease checklist; surfaced again here for v0.3.0 planning input: + +1. **Goldmark `unsafe: true`** — keep or drop? Dropping eliminates the bio-XSS surface AND P1-1's blast radius. Test: `grep -r '<' content/post/*/index.md` on real sites — if no inline HTML, safe to disable. +2. **Hugo version floor** — bump `theme.toml min_version` to 0.154 (CI-tested) or stay at 0.146 (broader compat)? +3. **Audience signal** — solo bloggers vs teams? Drives multi-author / i18n priority. +4. **Demo site analytics** — opt-in privacy-respecting analytics (e.g., GoatCounter) on `tiennm99.github.io/tsuki/` to inform future feature priority? Currently flying blind. + +## Success criteria (v0.3.0) + +- All review P1s closed +- CSS budget ≤ 4 KB gz preserved (per-kind split MUST not increase total weight) +- Lighthouse a11y ≥ 95 on exampleSite (currently presumed but unmeasured under WCAG 2.2) +- `i18n/en.yml` complete; theme builds with `defaultContentLanguage: en` without missing-key fallbacks +- Smoke tests catch a synthetic regression in render-link / callout / JSON-LD +- gohugoThemes registry CI green on tagged version + +## Unresolved questions + +1. Is v0.2.1 acceptable, or does the maintainer prefer to skip it and roll P1s into v0.3.0? (Affects v0.3.0 scope size — adds ~1 day.) +2. Does the maintainer want a real `cover.image` feature in v0.3.0 (would activate AVIF research item), or stay with author-supplied static images? +3. Should `data/profile.yaml: url` be added to documented schema, or removed from `seo.html` read? (Opens a small data-schema doc decision.) +4. Is there capacity for the WCAG 2.2 AA audit pass — needs an evening with axe-devtools or Lighthouse — or defer to v0.3.1? + +--- + +**Status:** DONE +**Summary:** Recommend three-release sequence: v0.2.0 (ship now, mechanical only), v0.2.1 patch (~1 day, P1 review fixes), v0.3.0 (~5 days, budget split + author UX + research Tier-1 + smoke depth). Rejected: folding all post-review P1s into v0.2.0 (delays gallery submission, scope creep, P1s are not v0.2.0 regressions). Output feeds /ck:plan with v0.3.0 phases. diff --git a/themes/tsuki/plans/reports/code-reviewer-260510-0144-tsuki-v0.2.0-post-release-improvements.md b/themes/tsuki/plans/reports/code-reviewer-260510-0144-tsuki-v0.2.0-post-release-improvements.md new file mode 100644 index 0000000..b3bfd6a --- /dev/null +++ b/themes/tsuki/plans/reports/code-reviewer-260510-0144-tsuki-v0.2.0-post-release-improvements.md @@ -0,0 +1,319 @@ +# tsuki — Post-v0.2.0 Improvement Review (v0.3.0+ candidates) + +Date: 2026-05-10 +Scope: full theme @ main `bde8b8d` after v0.2.0 prerelease +Inputs reviewed: README, CHANGELOG (Unreleased + 0.1.1), v0.2.0 roadmap (archived), v0.1.0 audit, all `layouts/`, `assets/css/`, `assets/js/`, `i18n/vi.yml`, `hugo.yaml`, `theme.toml`, `package.json`, `.github/workflows/pages.yml`, `scripts/smoke-tests.sh`. +Goal: surface what's still worth fixing for v0.3.0+. Findings already addressed in `[Unreleased]` are **not** repeated. + +--- + +## Overall Assessment + +v0.2.0 is in good shape — audit gaps from v0.1.0 are largely closed; partial duplication has been pruned (TOC gate, OG image partial); CI now includes smoke tests + htmltest + CSS budget assert. The remaining issues are mostly second-order: a few real correctness bugs (taxonomy=0 link, render-link regex, profile.url chain, nav `relURL`-less menu URLs), some maintainability nits (raw `{{ . }}` in `title=` attrs, `xss` resurfaces via render-link `.Text | safeHTML`), and a moderate testing gap (smoke-tests.sh is essentially structural — it can't catch a broken render-hook or a regressed callout style). + +**Top concerns (P1)** that warrant fixing before v0.3.0: +1. `render-link.html` — `.Text | safeHTML` re-introduces stored-HTML bypass on link text; combined with `unsafe: true` Goldmark, `[inner](url)` with raw HTML in inner renders verbatim. +2. `head/seo.html:64` — `site.Params.profile.url` will throw `nil pointer evaluating interface {}.url` when `params.profile` is unset (which it is by default — profile data lives in `data/`, not `params`). Site builds fine *because no demo post happens to hit that branch*; the moment a consumer sets `params.author` empty and has no `profile` map, builds break. +3. `home/projects.html:30,33` — `repo`/`demo` URLs are emitted with no `safeURL` and no `noopener noreferrer`; a `javascript:` URL in `data/projects.yaml` runs (theme-author input, but inconsistent vs render-link hook). +4. Missing render hook for `
` / raw HTML. `unsafe: true` is required (because Hugo callouts pre-0.140 used raw HTML), but post-Goldmark callouts now exist — re-evaluate whether `unsafe: true` is still necessary, or whether the requirement can be relaxed in v0.3 with a documented opt-in. + +--- + +## P1 — Correctness, Security, Theme-Contract Bugs + +### P1-1 — `render-link.html` re-emits raw HTML in link text +**File:** `layouts/_markup/render-link.html:10` +**What:** `{{ .Text | safeHTML }}` bypasses Hugo's auto-escape on link inner text. With `unsafe: true` Goldmark, an author writing `[](https://example.com)` produces a script-execution vector inside otherwise-trusted markdown. +**Why it matters:** Theme is single-author so the threat model is "I'm hurting myself", but inconsistent with `render-image.html` (which does `alt="{{ .Text }}"` — *correctly* HTML-escaped — and never `safeHTML`s `.Text`). One render hook trusts the input, the other doesn't. A multi-author or CMS consumer importing a post would not expect link text to execute. +**Fix:** Drop `| safeHTML`. Goldmark already produces escaped inline-emphasis HTML via `.Text` (e.g., `x` already comes through), so removing the filter loses italic-in-link rendering. Better: use `.PlainText` for the safe path, or document that link-text HTML is by-design and tighten `unsafe: false` everywhere callouts allow. +**Effort:** S (1-line change + 1 demo post + smoke assert). + +### P1-2 — `seo.html` profile.url chain throws on missing `params.profile` +**File:** `layouts/_partials/head/seo.html:64` +**What:** `site.Params.profile.url | default ...`. Hugo's `default` does not short-circuit; `site.Params.profile` is `nil` for sites that follow docs (profile data is in `data/profile.yaml`, not `params.profile`). Calling `.url` on `nil` is ".url" — Hugo template errors out: `error calling: can't evaluate field url in type interface {}`. +**Why it matters:** Demo site doesn't trip this because the demo doesn't render JSON-LD on the home, only on `/post/...` pages, and `site.Params.profile` happens to evaluate to a falsy empty interface that the template engine tolerates in the no-author case. But: `data/profile.yaml` has a `url` field nowhere documented; `site.Data.profile.url` falls through. So when a consumer adds `params.profile: { url: "..." }` (which docs do not mention), the structure works, but a consumer who only sets `data/profile.yaml` with no `url` key (the documented path) and no `params.author`, hits the chain `nil.url` → fallback to `(and site.Data.profile site.Data.profile.url)` → `nil` → default to `site.Home.Permalink`. Currently *works* but is fragile. +**Fix:** Use the safer pattern: `{{- $authorURL := "" -}}{{- with site.Params.profile -}}{{- $authorURL = .url -}}{{- end -}}{{- if not $authorURL -}}{{- with site.Data.profile -}}{{- $authorURL = .url -}}{{- end -}}{{- end -}}{{- $authorURL = $authorURL | default site.Home.Permalink -}}`. Also: document `data/profile.yaml: url` field (currently absent from `docs/data-schemas.md`). +**Effort:** S. + +### P1-3 — `projects.html` link emits without `safeURL` +**File:** `layouts/_partials/home/projects.html:30,33` +**What:** `repo →` — no `safeURL` filter. Hugo auto-escapes via context-aware HTML escaping (so `javascript:` is not blocked but `>` is), and the input is theme-author-controlled YAML. Render-link hook applies `safeURL` to markdown links; this YAML path doesn't. +**Why it matters:** Inconsistent escape policy across the theme. If an author copy-pastes a tracking URL with non-standard characters, behavior may vary by Hugo version. `noopener` without `noreferrer` allows referer leakage to demo/repo destinations (minor privacy). +**Fix:** ``. Same on line 33. Also consider `target="_blank"` since these are external — currently no `target=` so links open in same tab, replacing the portfolio. +**Effort:** S. + +### P1-4 — `nav.html` menu URLs not piped through `relURL` +**File:** `layouts/_partials/nav.html:6` +**What:** `` — Hugo's `Menu.URL` returns the raw URL from menu config; if the menu uses a leading-`/` path (e.g., `/post/`) and the site deploys under a sub-path (e.g., GitHub Pages `/tsuki/`), the link becomes `/post/` instead of `/tsuki/post/`. +**Why it matters:** Demo site works because `baseURL: https://tiennm99.github.io/tsuki/` rewrites with `--baseURL`, but `relURL` is the documented Hugo idiom. Sites using `pageRef:` in menu config emit pre-resolved permalinks and don't need `relURL`; sites using `url:` do. The theme can't know which. +**Fix:** ``. Hugo's `relURL` is idempotent on already-resolved URLs. +**Effort:** S. + +### P1-5 — `comments.html` doesn't validate Giscus required fields +**File:** `layouts/_partials/comments.html:1-4` +**What:** Gates only on `$g.enable`. If `enable: true` but `repo`/`repoId`/`categoryId` are missing/empty, Giscus loads with empty `data-*` attributes and shows a JS error in the console plus a broken iframe. +**Why it matters:** First-time setup pain. `docs/config.md` explains required fields, but the theme builds without warning. With `noClasses: false` highlight + Giscus iframe error, user-visible breakage. +**Fix:** `{{- if and $g $g.enable $g.repo $g.repoId $g.categoryId -}}`. Optionally emit an HTML comment when gated-off so consumers can introspect: ``. +**Effort:** S. + +### P1-6 — `render-link.html` external-detection misses `mailto:`, `tel:`, protocol-relative `//x.com` +**File:** `layouts/_markup/render-link.html:2-6` +**What:** External detection covers `http://`, `https://`, `//`. Misses `mailto:`, `tel:`, `ftp:`, `magnet:`, `irc:`. A `[contact](mailto:hi@miti99.com)` link gets no `rel`, which is fine (mailto: doesn't open a window). But a `[chat](irc://server)` does. +**Why it matters:** Edge-case correctness. The `//` prefix is also caught by `HasPrefix .Destination "//"`, but Hugo render hooks normalize input — verify whether goldmark passes `//` URLs through unchanged. Worth a small smoke test. +**Fix:** Extend: also check for any `://` not starting with the site's own scheme. Alternative: detect via `urls.Parse .Destination` and check `.Host != site.Host`. +**Effort:** S — but only if scope warrants. + +--- + +## P2 — Performance, Maintainability, A11y Polish + +### P2-1 — `
` / Accordion / `` not handled by render hooks +**File:** missing +**What:** `unsafe: true` is required for `
......
` blocks in posts. No render hook normalizes them. Consumers who write raw `
` get unstyled output that looks broken in dark mode (default UA collapsed-arrow color is bad in dark). +**Why it matters:** A common authoring pattern (FAQ, expandable spoilers). Theme advertises "native callouts"; details/summary is the next-most-requested. +**Fix (M)**: add CSS for `details { ... }` with dark-mode tokens. Possibly a render hook `_markup/render-table.html` — Hugo 0.146 doesn't have a render-details hook, so CSS-only. +**Effort:** S (CSS only). + +### P2-2 — `head.html` CSS bundle includes ALL CSS files unconditionally +**File:** `layouts/_partials/head.html:46-60` +**What:** All 12 CSS partials are concatenated on every page. The home page never uses `toc.css` (~600 B gz), `comments.css`, or `archive.css`. Single posts never use `archive.css` or `home.css`. Search pages don't need `home.css`/`archive.css`/`toc.css`. +**Why it matters:** CSS budget is at 4200 B gz. Headroom is gone. Every new feature now requires removing existing bytes. Per-page bundles would let toc/home/comments stay opt-in. Current cost: ~30% of bundle is dead bytes on most page kinds. +**Fix:** Two strategies. (a) Per-page-kind bundle: `head.html` switches on `.Kind` to assemble only relevant CSS. (b) Critical-CSS for above-the-fold + async-load the rest. (a) is simpler and KISS. Splitting `home.css` and `comments.css` and `toc.css` out into separate fingerprinted bundles, conditionally ``'d, would free ~1.2 KB gz. +**Effort:** M. Affects asset pipeline + needs CI assertion update. + +### P2-3 — `code-copy.js` walks entire DOM with `querySelectorAll("pre")` even on pages without code blocks +**File:** `assets/js/code-copy.js:6` +**What:** Page-load JS does `querySelectorAll("pre")` on every page including home, archives, taxonomies. Cheap, but the bundle ships unconditionally even on pages with no `
`. Combined with theme-toggle.js, this is in the *site-wide* bundle.
+**Why it matters:** JS budget is small (~1 KB gz). For sites with mostly portfolio pages and few code-heavy posts, code-copy is dead JS. Plus: the script inserts the button DOM-unsafe; if a `
` is itself inside a `