jekyll-theme-zer0 1.28.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +430 -13
  3. data/_data/backlog.yml +356 -2
  4. data/_data/consumers.yml +109 -4
  5. data/_data/features.yml +157 -0
  6. data/_data/i18n/fr.yml +12 -7
  7. data/_data/i18n/manifest.yml +39 -12
  8. data/_data/ingredient_densities.yml +122 -0
  9. data/_data/navigation/main.yml +16 -0
  10. data/_data/recipe_courses.yml +64 -0
  11. data/_data/theme-manifest.yml +68 -40
  12. data/_data/ui-text.yml +8 -0
  13. data/_includes/README.md +15 -1
  14. data/_includes/components/background-customizer.html +2 -2
  15. data/_includes/components/cookie-consent.html +4 -4
  16. data/_includes/components/info-section.html +8 -3
  17. data/_includes/components/language-toggle.html +168 -21
  18. data/_includes/components/page-views-init.html +55 -0
  19. data/_includes/components/page-views.html +33 -0
  20. data/_includes/components/recipe-card.html +67 -0
  21. data/_includes/components/recipe-duration.html +50 -0
  22. data/_includes/components/recipe-grams.html +58 -0
  23. data/_includes/components/recipe-index.html +96 -0
  24. data/_includes/components/recipe-ingredients.html +90 -0
  25. data/_includes/components/recipe-meta.html +96 -0
  26. data/_includes/components/recipe-nutrition.html +57 -0
  27. data/_includes/components/recipe-qty.html +73 -0
  28. data/_includes/components/recipe-ratio.html +151 -0
  29. data/_includes/components/recipe-scaler.html +73 -0
  30. data/_includes/components/recipe-steps.html +86 -0
  31. data/_includes/components/recipe-temp.html +45 -0
  32. data/_includes/components/search-modal.html +2 -2
  33. data/_includes/components/shortcuts-modal.html +3 -0
  34. data/_includes/components/theme-info.html +8 -1
  35. data/_includes/content/jsonld-software.html +22 -3
  36. data/_includes/core/footer.html +18 -16
  37. data/_includes/core/head.html +12 -0
  38. data/_includes/core/header.html +7 -4
  39. data/_includes/navigation/section-sidebar.html +93 -11
  40. data/_includes/navigation/sidebar-left.html +2 -1
  41. data/_includes/navigation/sidebar-right.html +2 -1
  42. data/_includes/setup/wizard.html +173 -86
  43. data/_includes/stats/stats-metrics.html +2 -2
  44. data/_layouts/README.md +2 -0
  45. data/_layouts/admin.html +2 -2
  46. data/_layouts/article.html +5 -0
  47. data/_layouts/cookbook.html +88 -0
  48. data/_layouts/default.html +4 -4
  49. data/_layouts/recipe.html +274 -0
  50. data/_layouts/root.html +22 -3
  51. data/_layouts/section.html +39 -10
  52. data/_sass/components/_callout.scss +1 -1
  53. data/_sass/components/_footer.scss +37 -1
  54. data/_sass/components/_page-views.scss +36 -0
  55. data/_sass/components/_recipe.scss +506 -0
  56. data/_sass/components/_setup-wizard.scss +235 -0
  57. data/_sass/components/_ui-enhancements.scss +6 -6
  58. data/_sass/core/_navbar.scss +250 -15
  59. data/_sass/layouts/_landing.scss +2 -2
  60. data/_sass/tokens/_color.scss +6 -0
  61. data/_sass/tokens/_index.scss +2 -0
  62. data/_sass/tokens/_radius.scss +21 -0
  63. data/_sass/tokens/_typography.scss +4 -0
  64. data/_sass/utilities/_focus.scss +14 -0
  65. data/assets/css/main.scss +3 -0
  66. data/assets/js/modules/navigation/navbar.js +55 -0
  67. data/assets/js/page-views.js +372 -0
  68. data/assets/js/recipe-scaler.js +501 -0
  69. data/assets/js/search-modal.js +10 -0
  70. data/assets/js/setup-wizard.js +322 -20
  71. data/scripts/README.md +29 -0
  72. data/scripts/bin/audit-consumer +39 -7
  73. data/scripts/bin/giscus-discussions +213 -14
  74. data/scripts/bin/manifest +35 -12
  75. data/scripts/ci/agent_review_result.py +164 -0
  76. data/scripts/ci/test_agent_review_result.py +172 -0
  77. data/scripts/design-system-check.rb +170 -0
  78. data/scripts/lib/audit.sh +42 -2
  79. data/scripts/lint-liquid-raw.rb +137 -0
  80. data/scripts/test/lib/run_tests.sh +2 -1
  81. data/scripts/test/lib/test_agent_review_result.sh +27 -0
  82. data/scripts/translate.rb +71 -15
  83. metadata +31 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b72b3e6bf60fc9df7f31ecd56d157770fede085973db6d2153980c3004bc0fc
4
- data.tar.gz: 369c0158b06d41d52c3f737d9a88a44e2cb9265653db55bf376e0651a502980b
3
+ metadata.gz: 22bc691450211a10284e51255f65418727383515f13a57a8fe8c28d10ac8396d
4
+ data.tar.gz: 7f0beac42b974ae3b60d43977a4e01c614e03d1936bd50b726e208dfe18f26b0
5
5
  SHA512:
6
- metadata.gz: 0d89cbf414b78be0b73fd22f8102dfacf8e38f10c561b2cc50fb4b492b8840d15e38f3b83243a589cd32f7fe7d72e4eb009a8dba44f09634e5e6611195971566
7
- data.tar.gz: cbcbec935073100919c21c9f3f542124a291529a93212706b8c7e82773a4361d7b272cc60ec966f077213c6c7ae23d36a60ad129c08bfd33506d382172109a88
6
+ metadata.gz: c101f2069a24c1d0c2745f1230575470492d621bca406d39ae036802ec450c9442d1c738cc949e60fd15dac2a8ced421d51b46a4737c59327038fbecd1a3de43
7
+ data.tar.gz: 7e019f09b5658412bfb82779b2aa3d94bcc9fd3c0022f97d980aaffdb0ef8b84d02fa2247b085f0d7ebbd23b0d22182f802a77eb25cc1ca892f7bf8c46f872a4
data/CHANGELOG.md CHANGED
@@ -12,6 +12,398 @@ hand-written prose covering the same releases in more depth; they sit below
12
12
  their version because release-please inserts each new release at the top of the
13
13
  file. Only `## [Unreleased]` describes work that has not shipped yet.
14
14
 
15
+ ## [1.29.0](https://github.com/bamr87/zer0-mistakes/compare/v1.28.0...v1.29.0) (2026-09-01)
16
+
17
+
18
+ ### Features
19
+
20
+ * **analytics:** track and display per-page view counts ([#413](https://github.com/bamr87/zer0-mistakes/issues/413)) ([e488c4c](https://github.com/bamr87/zer0-mistakes/commit/e488c4c3101da78e98eecbf8299ce7c8705ed281))
21
+ * **design:** fill Fab, PostCard, Skeleton + motion/layers gaps in the design-system mirror ([#391](https://github.com/bamr87/zer0-mistakes/issues/391)) ([3b49741](https://github.com/bamr87/zer0-mistakes/commit/3b497417564b4c5e3d71eaa113cb136d2cdaf15c))
22
+ * **includes:** icon-only language trigger and a compact, positive menu ([#431](https://github.com/bamr87/zer0-mistakes/issues/431)) ([353bfa5](https://github.com/bamr87/zer0-mistakes/commit/353bfa5f3a8dd949ca6e513213a155d648218642)), closes [#406](https://github.com/bamr87/zer0-mistakes/issues/406)
23
+ * **includes:** live preview, vertical stepper and draft persistence in the setup wizard ([#432](https://github.com/bamr87/zer0-mistakes/issues/432)) ([73bdc55](https://github.com/bamr87/zer0-mistakes/commit/73bdc5547350c448e1617ec6cf5b9f49b10d65fb)), closes [#408](https://github.com/bamr87/zer0-mistakes/issues/408)
24
+ * **layouts:** add cookbook collection with recipe scaling and unit conversion ([#414](https://github.com/bamr87/zer0-mistakes/issues/414)) ([bb8eefa](https://github.com/bamr87/zer0-mistakes/commit/bb8eefa2beea0104fec28d173a1e57f369acada8))
25
+ * **pages:** publish the design system and page canvas on the live site ([#392](https://github.com/bamr87/zer0-mistakes/issues/392)) ([6cf73cd](https://github.com/bamr87/zer0-mistakes/commit/6cf73cdf2cca9ec02394b6ae1ba7e39c19969134))
26
+ * **sass:** align design tokens with the Claude Design system ([#389](https://github.com/bamr87/zer0-mistakes/issues/389)) ([7fc2fe0](https://github.com/bamr87/zer0-mistakes/commit/7fc2fe02683d652c0326536158bd48b3690df51f))
27
+
28
+
29
+ ### Bug Fixes
30
+
31
+ * **a11y:** dialog titles use a bare <h5>, skipping heading levels ([#387](https://github.com/bamr87/zer0-mistakes/issues/387)) ([98fd6f9](https://github.com/bamr87/zer0-mistakes/commit/98fd6f9e63dfb75c58efc3b15a642fb3e4c4095b))
32
+ * **ci:** fail the content review when it did not actually run ([#424](https://github.com/bamr87/zer0-mistakes/issues/424)) ([f048cb2](https://github.com/bamr87/zer0-mistakes/commit/f048cb22e565b3122d6682c026e13521b6fe52d9)), closes [#418](https://github.com/bamr87/zer0-mistakes/issues/418)
33
+ * **ci:** fail the Pages build on Liquid errors, unmangle two live pages ([#439](https://github.com/bamr87/zer0-mistakes/issues/439)) ([afd2ec8](https://github.com/bamr87/zer0-mistakes/commit/afd2ec83879a793d77dfac21b41d74d5204126c4))
34
+ * **ci:** make the pixel gate see the data files that move pixels ([#425](https://github.com/bamr87/zer0-mistakes/issues/425)) ([02ea639](https://github.com/bamr87/zer0-mistakes/commit/02ea639b3152900682ca298bd172925133d10dea)), closes [#417](https://github.com/bamr87/zer0-mistakes/issues/417)
35
+ * **ci:** stop discarding good translations when one page fails ([#388](https://github.com/bamr87/zer0-mistakes/issues/388)) ([19cea46](https://github.com/bamr87/zer0-mistakes/commit/19cea462a7822d6db536fc9c23218abd4a13d830))
36
+ * **ci:** wait past the cold bundle install for Jekyll in the snapshot gate ([#426](https://github.com/bamr87/zer0-mistakes/issues/426)) ([de1d1ca](https://github.com/bamr87/zer0-mistakes/commit/de1d1ca426ce7a25d5a8548c4a696c0002877200))
37
+ * **cookie-consent:** text-dark on bg-body made privacy rights invisible in dark mode ([#386](https://github.com/bamr87/zer0-mistakes/issues/386)) ([9bde7df](https://github.com/bamr87/zer0-mistakes/commit/9bde7dfa13f426908e00e53f8e4013a049fd928d))
38
+ * **giscus:** comments were broken sitewide — add an end-to-end health check and correct the docs ([#378](https://github.com/bamr87/zer0-mistakes/issues/378)) ([45e222c](https://github.com/bamr87/zer0-mistakes/commit/45e222c3c2c5b7c507af4df2f5f0b48bc783801e))
39
+ * **includes:** footer Info/Cookie Preferences are buttons, not anchors ([#427](https://github.com/bamr87/zer0-mistakes/issues/427)) ([f9041b9](https://github.com/bamr87/zer0-mistakes/commit/f9041b98b8a267be59f560724821fd941090f52d)), closes [#320](https://github.com/bamr87/zer0-mistakes/issues/320)
40
+ * **layouts:** match section topic filters on whole tags, not substrings ([#420](https://github.com/bamr87/zer0-mistakes/issues/420)) ([338ad1d](https://github.com/bamr87/zer0-mistakes/commit/338ad1ddfb71c8d1dd9c48b36fb4634daf93f2de))
41
+ * **layouts:** move keyboard focus to the skip-link target (tabindex="-1") ([#395](https://github.com/bamr87/zer0-mistakes/issues/395)) ([f5db650](https://github.com/bamr87/zer0-mistakes/commit/f5db65048e48ca71eedebc04d02d051e0b3152a6)), closes [#278](https://github.com/bamr87/zer0-mistakes/issues/278)
42
+ * **layouts:** stop emitting dead sub-topic anchors in section sidebars ([#402](https://github.com/bamr87/zer0-mistakes/issues/402)) ([f50e790](https://github.com/bamr87/zer0-mistakes/commit/f50e790c4bfd56fefa4a74d7876de90c9776c689))
43
+ * **layouts:** upgrade the no-js class to "js" so the hook works ([#428](https://github.com/bamr87/zer0-mistakes/issues/428)) ([a2d31f5](https://github.com/bamr87/zer0-mistakes/commit/a2d31f5533a4fb4793042e32326c1a78c9954268)), closes [#319](https://github.com/bamr87/zer0-mistakes/issues/319)
44
+ * **navigation:** fit all nav items, full-bleed bar, language into Settings ([#423](https://github.com/bamr87/zer0-mistakes/issues/423)) ([689006e](https://github.com/bamr87/zer0-mistakes/commit/689006e7654e900a4509c30539fcf12bd7f397c6))
45
+ * **scripts:** correct consumer auditor, plugin contract, and JSON-LD scope ([#400](https://github.com/bamr87/zer0-mistakes/issues/400)) ([b466cad](https://github.com/bamr87/zer0-mistakes/commit/b466cad526501e7e7a0eef5f24a12c5bbd181dc7))
46
+ * **scripts:** recover from a rejected Anthropic credential, and link the cookbook ([#419](https://github.com/bamr87/zer0-mistakes/issues/419)) ([fac6bee](https://github.com/bamr87/zer0-mistakes/commit/fac6beeb7af7dcae96f727bf0cf581b080e95f15))
47
+ * **ui-audit:** serve without --detach so the sweep can capture, and fail loudly when it cannot ([#434](https://github.com/bamr87/zer0-mistakes/issues/434)) ([fac6415](https://github.com/bamr87/zer0-mistakes/commit/fac641574c951b2887d94f8b39561fb8b6f8fe8c)), closes [#321](https://github.com/bamr87/zer0-mistakes/issues/321)
48
+
49
+ ## [Unreleased]
50
+
51
+ ### Changed
52
+
53
+ - **Setup wizard: live preview, vertical stepper, draft persistence (T-040, #408)** —
54
+ the `_config.yml` preview is now a **persistent sticky panel** shown at every
55
+ step rather than an element inside step 5, regenerated on every keystroke,
56
+ with Copy and Download always enabled. The nav-pills tab row is replaced by a
57
+ left **vertical stepper** with done/active/upcoming state: a step is locked
58
+ until every earlier step validates, and Back is always allowed. The form is
59
+ mirrored to `localStorage` under `zer0-setup-draft` (debounced 300ms),
60
+ restored on load with a "Draft saved" chip, and cleared once the file is
61
+ downloaded. Email and URL fields are validated **on blur** with `is-invalid`
62
+ and an explanatory message, cleared as soon as you retype; the Review step
63
+ lists any unfilled recommended fields as warnings. Every step pane shares one
64
+ CSS grid cell, so the container is always as tall as the tallest step, and the
65
+ nav row is pinned to the bottom — **Back/Next no longer move vertically**
66
+ between steps. New
67
+ `_sass/components/_setup-wizard.scss`, and the wizard gains its first
68
+ automated coverage in `test/visual/features/setup-wizard.spec.js`.
69
+
70
+ Holding the container's height turned out to be only half of "Back/Next no
71
+ longer move": the nav row's own height still varied. Step 1's row was a block
72
+ box, so its button sat on a baseline and rode ~2px high, and in the real
73
+ ~270px middle column `Review & Download` wrapped to a second line, pushing
74
+ that button's top up a further 16px. The row is now always a flex box and its
75
+ buttons never wrap. Draft persistence gained a matching fix: the 300ms
76
+ debounce meant the last edit before a reload was still only a queued timer and
77
+ was lost, so a pending write is now flushed on `pagehide` and on a hidden
78
+ `visibilitychange`. Before/after measurements are in
79
+ `test/visual/evidence/setup-wizard-nav-alignment/`.
80
+
81
+ - **Language switcher: icon-only trigger and a compact, positive menu (T-038, #406)** —
82
+ the navbar trigger drops its `EN` text span and the Bootstrap caret for a
83
+ 38px square icon button, with `title` and `aria-label` carrying the language
84
+ name. In the menu (both the navbar dropdown and the Settings panel variant),
85
+ the current language is now marked with a tint and a check icon instead of
86
+ Bootstrap's `.active` primary fill, which read as a selected nav item rather
87
+ than "you are here". **Untranslated languages are no longer disabled rows** —
88
+ they link to the source page and still record the `zer0-lang` preference, so
89
+ the menu has no dead ends. Machine-generated translations carry a small
90
+ `auto` chip, and the per-row "(Not yet translated)" text — the main driver of
91
+ menu width — collapses into one footnote under a divider, referenced from
92
+ each marked row via `aria-describedby`. The dropdown menu is capped at 220px.
93
+ New `ui` keys `lang_machine_translated`, `lang_machine_translated_title` and
94
+ `lang_untranslated_note` in `_data/ui-text.yml`; each use carries a literal
95
+ fallback, because `core/i18n.html` replaces the `ui` map wholesale on a
96
+ translated page and `_data/i18n/<lang>.yml` will not have them until
97
+ `scripts/translate.rb` next runs.
98
+
99
+ ### Added
100
+
101
+ - **Cookbook & recipes collection (ZER0-084)** — a `recipes` collection that
102
+ renders structured front matter as a working recipe page: `layout: recipe`
103
+ builds a fact bar (prep/cook/rest/total, yield, difficulty, cuisine, oven),
104
+ a grouped ingredient checklist, a numbered method with per-step timings and
105
+ temperatures, a baker's-percentage ratio table computed at build time, a
106
+ per-serving nutrition panel, and one `schema.org/Recipe` JSON-LD block.
107
+ `assets/js/recipe-scaler.js` adds serving scaling and live US↔metric
108
+ conversion — including volume↔weight via `_data/ingredient_densities.yml`
109
+ — as progressive enhancement, so every amount is still rendered exactly as
110
+ authored with JavaScript disabled. `layout: cookbook` indexes recipes into
111
+ course sections from `_data/recipe_courses.yml`. Twelve new
112
+ `components/recipe-*.html` includes, `_sass/components/_recipe.scss` (with
113
+ print styles), a demo cookbook at `/recipes/`, and docs at
114
+ [Cookbook collection](pages/_docs/features/cookbook-collection.md). Regression
115
+ coverage: `test/visual/features/recipes.spec.js` (smoke tier) and "Cookbook
116
+ Recipe Collection" in `test/test_core.sh` (evidence:
117
+ [`test/visual/evidence/recipes-collection/`](test/visual/evidence/recipes-collection/README.md)
118
+ — 0px page overflow across 320–1440px; 1 cup butter → 227 g, 500 g flour →
119
+ 4 cups; ratio percentages identical at 1× and 3×).
120
+
121
+ - **Page-view counter (ZER0-083)** — the theme now tracks a view for the page
122
+ being read and displays the count in the article meta row, next to the
123
+ reading time (`👁 12 views`). Two providers, configured under `page_views:`
124
+ in `_config.yml`: `local` (the default — counts live in the visitor's
125
+ `localStorage`, so it works on GitHub Pages with no server, no network, and
126
+ no third party) and `remote` (counts come from an HTTP counter endpoint you
127
+ control, with `{path}` substitution, a configurable method, and a dotted
128
+ `count_key` for the response shape). Privacy gates mirror the `posthog:`
129
+ block — Do Not Track, Global Privacy Control, and an optional
130
+ `require_consent` tie-in to the cookie-consent "analytics" category — and
131
+ they suppress *recording* only, never the display of a count that is already
132
+ known; a view blocked pending consent is recorded when consent arrives.
133
+ `dedupe: session` counts one view per page per browser session so reloads do
134
+ not inflate it. The badge include (`components/page-views.html`) ships hidden
135
+ and is revealed only once a count exists, carrying its own leading separator
136
+ so a page with no data leaves no dangling bullet. New:
137
+ `_includes/components/page-views.html`,
138
+ `_includes/components/page-views-init.html`, `assets/js/page-views.js`,
139
+ `_sass/components/_page-views.scss`. Regression spec:
140
+ `test/visual/features/page-views.spec.js`. Docs:
141
+ [`docs/features/page-views.md`](docs/features/page-views.md)
142
+ (evidence: [`test/visual/evidence/page-views/`](test/visual/evidence/page-views/README.md)).
143
+
144
+ - **Search shortcut affordances (ZER0-032)** — the navbar search button now
145
+ advertises its keyboard shortcuts, matching the design-system navbar
146
+ pattern: a quiet `/` kbd chip (xl-up), a hover title ("Search — press / or
147
+ Ctrl+K", shown as `⌘K` on macOS), and `aria-keyshortcuts`; the sidebar (`b`)
148
+ and table-of-contents (`t`) hide toggles gained shortcut hover hints, and
149
+ the keyboard-shortcuts modal now lists `⌘/Ctrl+K`
150
+ (evidence: [`test/visual/evidence/search-shortcut-hint/`](test/visual/evidence/search-shortcut-hint/README.md)).
151
+
152
+ - **Live design-system pages (ZER0-082)** — the design system is now
153
+ part of the site itself: `_design-system/` is force-included in the Jekyll
154
+ build so `styles.css`, every token file, specimen cards, and component
155
+ sources get real URLs under `/_design-system/`; a new `/design/` hub page
156
+ renders the foundation specimens in live iframes (colors, skins, type,
157
+ spacing, radii, motion, layers, iconography), catalogs the component twins,
158
+ and links the Claude Design page canvas (all 24 layouts as a browsable
159
+ presentation). Navbar: About → Design System. Regression spec:
160
+ `test/visual/features/design-hub.spec.js`.
161
+
162
+ - **Claude Design system round-trip (ZER0-081)** — the design-token layer is now
163
+ aligned with the zer0-mistakes Claude Design project
164
+ (`claude.ai/design/p/e75121c0-9210-42d1-ade3-2c8af9111cbe`): new
165
+ `--zer0-radius-*`, `--zer0-logo-*`, and `--zer0-text-display-*` tokens in
166
+ `_sass/tokens/`; the design project vendored as a git mirror under
167
+ `_design-system/` (plain-CSS tokens, React component specs, guideline cards,
168
+ website UI kit — sync contract in `_design-system/SYNC.md`); and
169
+ `scripts/design-system-check.rb` wired into the core test suite so the theme
170
+ and the mirror cannot drift silently. Callouts, landing feature cards, mobile
171
+ nav, tables, and focus rings now consume the radius tokens (value-preserving —
172
+ rendered output unchanged, pinned by a new runtime token assertion in
173
+ `test/visual/core/styling.spec.js`).
174
+ - **Design-system component twins (PR #391)** — `Fab`/`FabStack`, `PostCard`,
175
+ and `Skeleton` join Button/Badge/Card/FeatureCard/Callout/Input in
176
+ `_design-system/components/`, plus new Motion and Layers foundation cards;
177
+ all pushed live to the Claude Design project. The feature registry is
178
+ re-synced (`features/features.yml` ↔ `_data/features.yml` — #389's ZER0-081
179
+ entry had landed in the `_data` mirror only, which the registry validator
180
+ rejects) and `docs/ui/components.md` gains a theme-source →
181
+ design-system-twin map.
182
+
183
+ ### Changed
184
+
185
+ - **Language selector moved from the navbar into Settings → Appearance** — the
186
+ header utility cluster is now Search + Settings only, returning ~70px to the
187
+ menubar. `components/language-toggle.html` grew a `variant` parameter: the new
188
+ `panel` variant (shipped) renders a self-contained **Language** section — an
189
+ always-visible list of languages with the active one checked and
190
+ untranslated targets disabled — as the first block of the Settings
191
+ Appearance tab, next to Color Mode and Theme Skin. The original header
192
+ dropdown remains available as `variant="navbar"` for consumers that prefer
193
+ it. Same data, same `localStorage("zer0-lang")` preference, same
194
+ never-a-dead-link contract; `test/visual/features/language-toggle.spec.js`
195
+ was updated to exercise it through the Settings panel.
196
+
197
+ ### Fixed
198
+
199
+ - **Footer "Info" and "Cookie Preferences" are buttons, not links (#320)** —
200
+ both controls open in-page UI (the `#info-section` offcanvas and the
201
+ `#cookieSettingsModal` modal) but shipped as `<a href="#" data-bs-toggle>`, so
202
+ assistive tech announced them as links — WCAG 4.1.2 Name, Role, Value — and
203
+ whenever Bootstrap's toggle did not fire the browser followed `href="#"` and
204
+ jumped the page to the top. All three occurrences are now
205
+ `<button type="button">`: the Info trigger is emitted **twice** by
206
+ `_includes/core/footer.html`, once in each branch of the powered-by loop, and
207
+ a site with no `powered_by` credits configured renders the branch a
208
+ single-site fix would have missed. `.powered-by-link` is now element-agnostic
209
+ (it already styled `<a>`, `<span>` and now `<button>`) and carries the
210
+ `<button>` reset Bootstrap Reboot does not supply — `appearance`,
211
+ `background`, `border`, `colour` — with the new `.footer-inline-button` doing
212
+ the same for a button in running text, so the swap is visually inert. The
213
+ back-to-top control keeps its `<a href="#">`: scrolling to the top is its
214
+ actual behaviour, not a side effect. Regression coverage: "Accessibility —
215
+ footer in-page toggles are buttons" in
216
+ `test/visual/core/accessibility.spec.js`, which asserts the element identity
217
+ directly — axe-core has no rule that fires on an anchor used as a button, so
218
+ an axe-based assertion passes on the broken markup too. "powered-by credits
219
+ are real links" in `test/visual/core/layout-chrome.spec.js` already banned
220
+ `href="#"` but sampled only the first five links, and `site.powered_by` pushed
221
+ the offending Info anchor past index 4; it now checks every credit link.
222
+ (evidence:
223
+ [`test/visual/evidence/footer-button-semantics/`](test/visual/evidence/footer-button-semantics/README.md)
224
+ — shipped vs. reset-stripped at 1280px, footer overflow 0px at 375/768/1280)
225
+
226
+ - **`<html class="no-js">` is upgraded to `js` again (#319)** — `_layouts/root.html`
227
+ shipped the standard progressive-enhancement hook but nothing ever swapped
228
+ it: the string `no-js` occurred in exactly one file in the repository, and in
229
+ no SCSS, JS, test or doc. Every page on every downstream site permanently
230
+ carried `no-js` with JavaScript fully enabled, so a consumer writing
231
+ `html.no-js .foo {…}` / `html.js .foo {…}` got the no-JS branch for everyone
232
+ — the class advertised a capability the theme did not deliver. An inline
233
+ script is now the **first child of `<head>`**, above
234
+ `{% include core/head.html %}` (which opens with Google Tag Manager, so
235
+ "first in that include" is not "first in `<head>`") and deliberately neither
236
+ `defer` nor `async`, since it has to run before the first stylesheet is
237
+ fetched or `html.js` rules would flash. The served HTML still carries
238
+ `class="no-js"`, so the JS-disabled branch stays correct and the change is
239
+ purely additive for consumers already styling against the served markup.
240
+ Regression coverage: `test/visual/core/progressive-enhancement.spec.js`
241
+ (smoke tier) pins both directions of the contract — `js` with scripts on,
242
+ `no-js` with them off — plus the delivery mechanism, because a fix that
243
+ simply deleted `class="no-js"` would satisfy a naive one-sided assertion
244
+ while removing the hook entirely.
245
+
246
+ - **Pages build: Liquid errors no longer ship mangled pages silently.** The
247
+ `github-pages` gem renders every non-excluded Markdown file through Liquid, so
248
+ prose that merely *looks* like a template is executed as one. Jekyll 3 reports
249
+ that as a `Liquid Warning`, drops the expression, and still exits 0 — so the
250
+ Pages workflow stayed green while publishing damaged pages. Two were live:
251
+ `/CHANGELOG/` rendered "Added `,,` and to the template renderer" because
252
+ `{% raw %}{{DEFAULT_BRANCH}}{% endraw %}` and its siblings were evaluated to
253
+ nothing, and `/_design-system/components/feedback/Skeleton.prompt/` rendered
254
+ `<div style=>` because the JSX `style={% raw %}{{ … }}{% endraw %}` was a
255
+ Liquid syntax error. A third defect was invisible: a literal
256
+ `{% raw %}{% raw %}{% endraw %}` written as prose in a changelog entry opened a
257
+ real block that swallowed the next 945 lines of the published page. All the
258
+ affected spans are now escaped, `pages.yml` fails the build on
259
+ `Liquid Warning`/`Liquid Exception` for both the theme and the example site,
260
+ and `scripts/lint-liquid-raw.rb` (wired into the `quality` suite) catches the
261
+ silent half — an unbalanced raw block never raises a warning to gate on.
262
+
263
+ - **Navbar: every top-level item now fits, at every desktop width** — the bar
264
+ was pinned inside a centred `.container-xl` (max 1140–1320px), so the menubar
265
+ track never got more than ~740px against the ~815px the theme's seven items
266
+ need. Every label rendered truncated — `Quick …`, `N…`, `Note…`, `A…` — while
267
+ the page had 100+px of unused margin on either side, and below ~1050px the
268
+ whole menu collapsed to bare icons. Four changes give the labels their room:
269
+ the bar is now `.container-fluid` and spans the full screen width (opt back
270
+ into a cap with `--zer0-navbar-max-width`); the brand cluster is capped at
271
+ `30cqi` of the bar so a long title cannot starve the menubar; label density
272
+ degrades in three measured tiers instead of two (icon + label ≥ 51rem →
273
+ label-only 41–50.99rem → icon-only < 41rem), each boundary set to the width
274
+ the tier below it actually needs; and the home-link icons wait for 86rem,
275
+ where they no longer cost the menubar more than it can spare. Measured on the
276
+ demo site: **0 truncated labels from 992px to 2560px** (was: all 7 truncated
277
+ at 1440px, all 7 icon-only at 992px). Regression coverage: "no nav label is
278
+ CSS-truncated at any desktop width" / "the bar spans the full viewport width"
279
+ / "the brand title stays legible next to the menubar" in
280
+ `test/visual/features/navbar.spec.js` — the pre-existing label test only
281
+ checked for a literal `...` in `textContent`, which CSS `text-overflow` never
282
+ writes, so it passed throughout (evidence:
283
+ [`test/visual/evidence/navbar-fit/`](test/visual/evidence/navbar-fit/README.md)
284
+ — up to 7 truncated labels and 300px of dead bar margin → 0 across 8 widths).
285
+
286
+ - **Settings no longer opens behind the mobile nav menu** — on phones, tapping
287
+ **Settings** inside the open navigation menu appeared to do nothing: the
288
+ Settings offcanvas and the nav menu are both `offcanvas-end` panels at
289
+ Bootstrap's `$zindex-offcanvas`, and `_navbar.scss` lifts the fixed header
290
+ (which contains the nav menu) to 1046 while that menu is open — so Settings
291
+ painted underneath it, fully hidden. `navbar.js` now intercepts the offcanvas
292
+ `show` event and closes the host panel first, opening Settings once it has
293
+ finished sliding out; `_navbar.scss` releases the header lift, and raises
294
+ `#info-section` above it, for browsers without `:has()`. Covered by "Settings
295
+ opened from the nav menu paints ABOVE it" in
296
+ `test/visual/features/mobile-overlay-stacking.spec.js` (evidence:
297
+ [`test/visual/evidence/navbar-fit/`](test/visual/evidence/navbar-fit/README.md)
298
+ — `03-settings-stacking.png`: top element at the panel centre `a.nav-link`
299
+ → the panel's own content; header `z-index` 1046 → 1030).
300
+
301
+ - **Section topic filters match whole tags, not substrings** — filtering a
302
+ section by a topic silently revealed posts that did not carry it. `data-tags`
303
+ was built as `tags | join: ' ' | slugify`, which slugifies *after* joining and
304
+ so turns the separators into hyphens — making a tag boundary indistinguishable
305
+ from a hyphen inside a slug — and the click handler then matched with
306
+ `String.includes`. Filtering `/news/technology/` by **ai** returned 6 cards
307
+ while the sidebar badge for that topic said 5, because a post tagged `edge-ai`
308
+ matched too. `_layouts/section.html` now slugifies each tag separately into a
309
+ space-separated list and matches whole tokens, so the rendered count always
310
+ agrees with the badge (which Liquid computes with exact `contains` membership).
311
+ Regression coverage in `test/visual/features/section-topic-controls.spec.js`
312
+ asserts token membership and pins the filtered count to the badge (evidence:
313
+ [`test/visual/evidence/section-topic-filter/`](test/visual/evidence/section-topic-filter/README.md)
314
+ — filtering /news/technology/ by "ai" showed 6 cards against a badge of 5, now
315
+ 5; topics whose badge disagrees with what is shown: 2 → 0).
316
+
317
+ - **Nightly smoke tier is green again** — three `smoke` tests had been failing
318
+ every night since 2026-08-15 (the tier the PR gate does not run, so `main`
319
+ stayed green). Two were the topic-filter bug above; the third asserted the
320
+ sidebar's "All Articles" control only as an `<a href="#all-posts">`, but the
321
+ sidebar renders a `<button data-filter="all">` for the grid/list styles —
322
+ currently every section — so it could never pass. The topic-button selector
323
+ also used `.filter({ hasNot })`, which excludes elements *containing* a match
324
+ rather than matching elements themselves, so it selected the "All Articles"
325
+ button and asserted against an unfiltered list; it now uses `:not()`.
326
+
327
+ - **A rejected Anthropic credential now falls back to the next one** — the
328
+ translation pipeline picked the first credential that was *set* and never
329
+ reconsidered, so a revoked `CLAUDE_CODE_OAUTH_TOKEN` shadowed a working
330
+ `ANTHROPIC_API_KEY` and took the whole run down with a `401 OAuth access
331
+ token has been revoked`. `scripts/translate.rb` now treats 401/403 as a
332
+ credential failure rather than a request failure and retries with the next
333
+ configured credential, rebuilding the payload so the OAuth-only Claude Code
334
+ identity block is not sent with an API key. The switch is sticky, so a dead
335
+ credential costs one rejection per run rather than one per chunk.
336
+
337
+ - **A failed translation run no longer opens a PR containing nothing** —
338
+ `_data/i18n/manifest.yml` stamped `updated_at` on every save, so a run in
339
+ which *every* page failed still produced a one-line diff. That was enough
340
+ for `translate.yml` to open (and keep updating) a PR that read like a routine
341
+ translation refresh and contained no translations. The manifest is now
342
+ written only when the mapping itself changed; a partially successful run
343
+ still pushes the pages that succeeded, and the run still exits non-zero.
344
+
345
+ - **The cookbook is reachable from the navbar** — the `recipes` collection
346
+ (ZER0-084) shipped with a landing page at `/recipes/` that nothing linked to,
347
+ so the deployed cookbook could only be found by typing the URL.
348
+ `_data/navigation/main.yml` now carries a Recipes entry with the demo
349
+ recipes and the feature docs.
350
+
351
+ - **Section sidebars no longer emit dead sub-topic anchors** — the sidebar
352
+ produced `<a href="#tag">` for every tag, but the matching
353
+ `<section id="tag">` target exists only in the `magazine` branch of
354
+ `_layouts/section.html`, and only for the first 5 sub-categories that keep a
355
+ post once the featured hero is excluded. `grid` and `list` sections (and
356
+ `grid` is the default) therefore emitted one dead anchor per tag, and
357
+ `magazine` emitted them for tags 6–15 — 64 of them on this repo's own demo
358
+ site. Dead anchors are invalid markup, a dead click for keyboard/AT users,
359
+ and they left the sidebar scrollspy unable to activate. Anchors are now
360
+ emitted only where a target provably exists; every other style renders
361
+ `<button data-filter>`, which the layout's existing handler already wires to
362
+ the `[data-tags]` cards. The `list` branch gained the `data-tags` attribute
363
+ it was missing (without it those buttons would have been inert), and the
364
+ filter handler now applies pill colours only to actual pills, so the pills
365
+ and the sidebar stay in sync instead of fighting over each other's classes.
366
+ Regression cover: `test/visual/features/section-topic-controls.spec.js`
367
+ - **Consumer homepages no longer claim to be this theme** — the
368
+ `SoftwareApplication` JSON-LD in `_includes/content/jsonld-software.html` is
369
+ now opt-in via `jsonld_software_application` (set on this site only). The
370
+ block hardcodes this theme's RubyGems URL, GitHub repository, MIT licence,
371
+ feature list and author, but was wired unconditionally into every consumer's
372
+ homepage — so a consulting firm's site was telling search engines it was a
373
+ free open-source Ruby gem. Consumers need no action: absent config is falsey.
374
+ Evidence: [`test/visual/evidence/jsonld-software-optin/`](test/visual/evidence/jsonld-software-optin/README.md).
375
+ - **`audit-consumer` misread most consumers' theme mode** — `detect_consumer_mode()`
376
+ matched `^remote_theme:` with no tolerance for whitespace before the colon, so
377
+ the column-aligned YAML style that 6 of 9 fleet consumers use never matched and
378
+ every one of them was silently reported as `gem` mode. That skipped the plugin
379
+ checks entirely, with no warning that they had been skipped.
380
+ - **`audit-consumer --format json` emitted unparseable JSON** — every entry
381
+ carried a trailing comma, so the array always closed on `},\n]}`. The
382
+ human-readable log lines also went to stdout, mixing into the payload. Entries
383
+ are now comma-separated correctly and logging moves to stderr under
384
+ `--format json`, so the output pipes straight into `jq`.
385
+ - **`audit-consumer` ignored Jekyll's `source:`** — a consumer that relocates its
386
+ site tree (e.g. `source: pages`) was audited at the repo root, reporting a
387
+ falsely clean "no overrides, no unique files". It now follows `source:` for the
388
+ file scan while still reading config and `.theme-overrides.yml` from the root.
389
+ - **The Obsidian plugin was demanded of consumers that cannot run it** — the
390
+ manifest listed `_plugins/obsidian_links.rb` as required for every
391
+ `remote_theme` consumer, contradicting `obsidian.instructions.md`, which
392
+ documents it as opt-in and "skipped under the `github-pages` gem". GitHub Pages
393
+ builds in safe mode and loads no local plugins, so nothing the theme ships is
394
+ mandatory; `required_plugin_paths` is now empty and the plugin is optional.
395
+
396
+ ### Changed
397
+
398
+ - **`_data/consumers.yml` now covers the whole fleet** — added `zer0-pages`,
399
+ `irony-works`, `wargames` and `zer0-pages-remote`, which were unregistered and
400
+ therefore invisible to `propagate.rb` in both directions (no release dispatch,
401
+ never in the drift report). The `it-journey` risk note was corrected: its
402
+ "~55 shadowed files / ~75 lines of !important CSS working around #338" claim
403
+ is not supported by `audit-consumer` — only 2 files shadow a theme file, the
404
+ CSS is 31 lines in this theme's own documented `user_overrides` hook, and no
405
+ fix numbered #338 could be substantiated in that repo.
406
+
15
407
  ## [1.28.0](https://github.com/bamr87/zer0-mistakes/compare/v1.27.0...v1.28.0) (2026-08-09)
16
408
 
17
409
 
@@ -42,8 +434,33 @@ file. Only `## [Unreleased]` describes work that has not shipped yet.
42
434
 
43
435
  ## [Unreleased]
44
436
 
437
+ ### Fixed
438
+
439
+ - **The skip link never moved keyboard focus (#278)** — `_layouts/root.html`
440
+ rendered `<main id="main-content">` with no `tabindex`, and `<main>` is not natively focusable, so activating "Skip to main content" scrolled the page while focus stayed on `<body>`: the next `Tab` returned the user to the header nav they had just skipped. That defeats WCAG 2.4.1 (Bypass Blocks) on every consuming site, and the theme's own docs already prescribed the fix (`skip-to-content.md` → *Focus Not Moving* → "Add `tabindex=\"-1\"` to target") while the shipped layout omitted it. Now `<main id="main-content" tabindex="-1">`, paired with `#main-content:focus { outline: none; }` in `_sass/utilities/_focus.scss` so the container does not paint a ring — descendants keep theirs, and the rule is safe only because `-1` keeps the container out of the sequential `Tab` order. Measured across all three engines: focus lands on `#main-content` in Chromium, Firefox **and WebKit** (the engine the report named), where before it moved in none of them. The stale `<div id="main-content">` example in the English and French docs now matches the shipped markup.
441
+ - **Seven theme dialogs labelled themselves with a bare `<h5>`** — the search
442
+ modal, cookie settings modal, statistics help modal and the settings / background / section / admin offcanvases each opened with `<h5 class="modal-title">` or `<h5 class="offcanvas-title">`, so anyone navigating by heading met a level-5 heading as the dialog's title, skipping h3 and h4. Six other dialogs already used the correct `<h2 class="…-title h5">` form, so the theme contradicted itself half the time. All seven now match. `.h5` reproduces the exact sizing, so the change is visually inert. `_includes/docs/bootstrap-docs.html` and the docs prose examples are deliberately untouched — they reproduce upstream Bootstrap's own markup and should keep doing so. (evidence: [`test/visual/evidence/dialog-heading-levels/`](test/visual/evidence/dialog-heading-levels/README.md) — heading walk h2 → h5 becomes h2 → h2 on all six live dialogs, renders byte-identical)
443
+ - **Cookie modal's "Your Privacy Rights" list was invisible in dark mode** — the
444
+ list used Bootstrap's fixed `text-dark` utility inside a `bg-body` panel. `.text-dark` resolves to `rgba(var(--bs-dark-rgb), 1)`, and `--bs-dark-rgb` is **not** remapped by `[data-bs-theme=dark]` — it stays `33,37,41`, which is byte-for-byte the dark `--bs-body-bg` (`#212529`). Measured in a browser: **1.00:1**, against WCAG 1.4.3 (AA)'s 4.5:1 minimum. Not "hard to read" — the same colour as its background, on the one dialog a visitor is most likely forced to read, in the theme's default colour mode, shipped to every consumer. Now `text-body` (**11.85:1** dark, 15.43:1 light, unchanged). `.cookie-category`'s hardcoded `#dee2e6` border became `var(--bs-border-color)`, which is the identical colour in light mode and a visible one in dark. A regression test opens the dialog and asserts the measured contrast ratio — `axe` cannot catch this class of bug on this theme, because the skin system's `body::after` background image makes axe report every contrast check in the dialog as `incomplete` rather than a violation. (evidence: [`test/visual/evidence/cookie-consent-contrast/`](test/visual/evidence/cookie-consent-contrast/README.md) — dark-mode contrast 1.00:1 → 11.85:1, light mode unchanged)
445
+ - **`--page` lookups never matched a real Giscus thread** — `normalize_page()`
446
+ prepended a leading slash to build the discussion title, but Giscus's `pathname` mapping searches for `location.pathname` with the leading slash **removed** (verified live: `/posts/2025/01/21/remote-work-revolution/` requests `term=posts/2025/01/21/remote-work-revolution/`). So `seed` created discussions Giscus would never find, and `thread`/`draft`/`post --page` missed every thread Giscus created itself — the entire "build conversations from page comments" workflow was inoperable against real data. Lookups now match all four title spellings (with/without leading and trailing slash) so both new and legacy/hand-made threads resolve.
447
+ - **`discussion_number_for_page` silently ignored pagination** — the query
448
+ declared `$after` and selected `pageInfo`, but never used either, so on a site with more than 100 discussions no thread past the first page could ever be found. Now uses `gh api graphql --paginate`.
449
+ - **`set -e` aborted the script on the success path** — `[[ -z "$NUMBER" ]] &&
450
+ error ...` as the last statement of a branch returns 1 when the lookup *succeeds*, which `set -e` turned into a silent exit with no output at all. Latent and unreachable while lookups always failed; the fix above exposed it immediately. Rewritten as `if` blocks in `resolve_number_arg` and `cmd_post`.
451
+ - **Giscus comments were broken sitewide** — every page rendering the comment
452
+ widget showed `An error occurred: giscus is not installed on this repository` instead of a comment box, on `zer0-mistakes.com` and locally, because the [giscus GitHub App](https://github.com/apps/giscus) was never installed on the repository. The `_config.yml` values were all correct (`data-repo-id` and `data-category-id` both verified against the live GitHub API, repo public, Discussions enabled, `Announcements` category), which is exactly why it went unnoticed: **no check in the repo could see it**. The `Giscus Comments Configuration` core test only inspects config keys and Liquid, and `test/visual/features/comments.spec.js` stubs `giscus.app/client.js` out entirely — so all 16 core tests passed at 100% against a completely non-functional feature. **Resolved**: the app is now installed and `doctor` reports the chain healthy; comments render and accept sign-in on production. Verified with a seeded demo thread ([#379](https://github.com/bamr87/zer0-mistakes/discussions/379)) that the widget correctly resolves and displays.
453
+ - **Docs claimed the widget "won't load on localhost"** — it does. Giscus keys
454
+ off the `data-repo` attribute, not the page origin, so the iframe renders and reports real errors locally. The false claim told developers to expect a non-working widget in dev, which is precisely what hid this bug. `pages/_docs/features/giscus-comments.md` now documents localhost as a genuine end-to-end check, with a table mapping each rendered state to its cause.
455
+
45
456
  ### Added
46
457
 
458
+ - **`giscus-discussions doctor`** — a new subcommand that verifies the entire
459
+ comment chain end-to-end: repository public, Discussions enabled, `giscus.enabled: true`, `data-repo-id` actually belonging to this repo (the fork trap), `data-category-id` resolving to a real category, and — the part nothing else could check — whether the giscus GitHub App is installed, by asking the Giscus API itself. Exits non-zero with a specific remediation line per failure.
460
+ - **Giscus health check in CI** — `.github/workflows/giscus-digest.yml` (already
461
+ scheduled weekly) now runs `doctor` and fails the job with a summary when comments are down, so a sitewide comment outage surfaces on its own instead of waiting for a reader to report it.
462
+ - **Troubleshooting: "The giscus app is not installed"** — a new section in the
463
+ feature docs with captured screenshots of both the broken widget and the giscus.app configurator rejecting the repo, an explanation of why this step is uniquely easy to skip, a fork-specific warning, and a copy-pasteable `curl` health check.
47
464
  - **Obsidian graph frontend revamp** — the graph views now mirror the Obsidian
48
465
  desktop experience. The full graph page gains a floating **Graph settings**
49
466
  card (Filters / Display / Forces): search with `tag:`/`path:` operators,
@@ -369,13 +786,13 @@ file. Only `## [Unreleased]` describes work that has not shipped yet.
369
786
  `index.md`, and navigation. Empty now serialises to `[]`; `deploy` is
370
787
  deploy-only and also skips agent-file rewrites.
371
788
  - **github-pages / remote profile emitted a broken `remote_theme`.** The remote
372
- `_config.yml` template resolved `{{GITHUB_REPO}}` to the *site's* repo; it now
373
- uses a dedicated `{{THEME_REMOTE}}` variable (default `bamr87/zer0-mistakes`,
789
+ `_config.yml` template resolved `{% raw %}{{GITHUB_REPO}}{% endraw %}` to the *site's* repo; it now
790
+ uses a dedicated `{% raw %}{{THEME_REMOTE}}{% endraw %}` variable (default `bamr87/zer0-mistakes`,
374
791
  overridable via `THEME_REMOTE`).
375
792
  - **Deploy workflow templates had unsubstituted variables.** Added
376
- `{{DEFAULT_BRANCH}}`, `{{RUBY_VERSION}}`, and `{{SITE_NAME}}` to the template
793
+ `{% raw %}{{DEFAULT_BRANCH}}{% endraw %}`, `{% raw %}{{RUBY_VERSION}}{% endraw %}`, and `{% raw %}{{SITE_NAME}}{% endraw %}` to the template
377
794
  renderer, so the generated `jekyll-gh-pages.yml` (and docker-prod/azure-swa
378
- artifacts) no longer contain literal `{{…}}` tokens. Regression tests assert
795
+ artifacts) no longer contain literal `{% raw %}{{…}}{% endraw %}` tokens. Regression tests assert
379
796
  no unresolved tokens survive in any deploy artifact.
380
797
  - **Agent files were written twice** when `agents` appeared in both the task
381
798
  list and `SPEC_AGENTS`; `apply.sh` now runs the agents task at most once.
@@ -522,7 +939,7 @@ file. Only `## [Unreleased]` describes work that has not shipped yet.
522
939
  - **Author avatars never render as protocol-relative `//assets/…` URLs** —
523
940
  `author-avatar-url.html` now builds the relative-path branch in a capture,
524
941
  collapses doubled slashes, and applies `relative_url` exactly once, instead
525
- of the manual `{{ site.baseurl }}/{{ site.public_folder }}{{ avatar }}`
942
+ of the manual `{% raw %}{{ site.baseurl }}/{{ site.public_folder }}{{ avatar }}{% endraw %}`
526
943
  concatenation that produced `src="//assets/…"` (a URL browsers resolve
527
944
  against a host named `assets`) on consumer sites where `public_folder` is
528
945
  unset or carries a leading slash
@@ -1068,7 +1485,7 @@ file. Only `## [Unreleased]` describes work that has not shipped yet.
1068
1485
  to it from the Bootstrap, Layouts, and Styles docs.
1069
1486
  - **Bylines now use the shared author component.** The `article`, `note`,
1070
1487
  `notebook`, `news`, and `section` layouts plus `components/post-card.html`
1071
- previously printed `{{ page.author }}` as bare text; they now render
1488
+ previously printed `{% raw %}{{ page.author }}{% endraw %}` as bare text; they now render
1072
1489
  `components/author-card.html` (`inline`), so a known author key resolves to a
1073
1490
  display name, avatar, and a link to their profile page. The inline
1074
1491
  "About the Author" block that was hard-coded in `_layouts/article.html` was
@@ -1215,7 +1632,7 @@ file. Only `## [Unreleased]` describes work that has not shipped yet.
1215
1632
  - Version bump: patch release
1216
1633
 
1217
1634
  ### Commits in this release
1218
- - ae76a61f fix(content-review): correct code-fence detection (closing fences + {% raw %}) (#155)
1635
+ - ae76a61f fix(content-review): correct code-fence detection (closing fences + {% raw %}{% raw %}{% endraw %}) (#155)
1219
1636
  - f00fb654 docs(seo): strengthen SEO docs index metadata + fix agent-tier workflow (#154)
1220
1637
 
1221
1638
  ### Fixed
@@ -1225,10 +1642,10 @@ file. Only `## [Unreleased]` describes work that has not shipped yet.
1225
1642
  a file's score (e.g. `pages/_about/features/jekyll.md` scored 0/100 almost
1226
1643
  entirely from this false positive). The check now tracks fence open/close state
1227
1644
  and only validates opening fences.
1228
- - **content-review: ignore Liquid `{% raw %}` blocks** in the quality and style
1229
- checks. Code fences, headings, images, and terminology inside `{% raw %}…
1230
- {% endraw %}` are literal display examples, not page structure, and were being
1231
- counted as real findings.
1645
+ - **content-review: ignore Liquid `{% raw %}{% raw %}{% endraw %}` blocks** in the quality and style
1646
+ checks. Code fences, headings, images, and terminology inside a Liquid
1647
+ `{% raw %}{% raw %}{% endraw %}` block are literal display examples, not page
1648
+ structure, and were being counted as real findings.
1232
1649
 
1233
1650
  ## [1.18.0] - 2026-06-13
1234
1651
 
@@ -1656,7 +2073,7 @@ file. Only `## [Unreleased]` describes work that has not shipped yet.
1656
2073
  - **Sidebar collapse — VS Code style**: Left sidebar (`#bdSidebar`) and right TOC (`#tocContents`) now collapse to a slim 36 px rail (`--zer0-sidebar-rail-width`) instead of being fully hidden on desktop. The visibility toggle icon (`bi-layout-sidebar-inset` / `bi-layout-sidebar-inset-reverse`) stays mounted on the rail so users can re-expand the panel with a single click — the floating action buttons (`.bd-sidebar-fab`, `.bd-toc-fab`) are now hidden at `≥992 px` since the rail toggle replaces them. `_sass/core/_docs-layout.scss`, `_sass/layouts/_navbar-extras.scss`.
1657
2074
  - **Smooth transitions**: `.bd-layout` and `.bd-main` now animate `grid-template-columns` and `gap` over `--zer0-motion-duration-base` (0.3 s) with `--zer0-motion-ease-standard`; sidebar contents cross-fade via `opacity` + delayed `visibility`. Honors `@media (prefers-reduced-motion: reduce)` by disabling all related transitions.
1658
2075
  - **Toggle behavior**: `sidebar-visibility.js` and `toc-visibility.js` no longer set `button.hidden = true` on the rail toggle when collapsed, keeping it interactive in the collapsed state. Aria labels (`Hide…` / `Show…`) update on each toggle.
1659
- - **Cache-bust**: Added `?v={{ site.time | date: '%s' }}` to the navigation ES-module `<script type="module">` tag in `_includes/components/js-cdn.html` to force re-fetch on rebuild (browsers cache ES modules indefinitely by URL).
2076
+ - **Cache-bust**: Added `{% raw %}?v={{ site.time | date: '%s' }}{% endraw %}` to the navigation ES-module `<script type="module">` tag in `_includes/components/js-cdn.html` to force re-fetch on rebuild (browsers cache ES modules indefinitely by URL).
1660
2077
  - **Navbar dropdown**: Dropdown toggle button set to `align-self: stretch` so it spans the full navbar height, making it easier to invoke on touch/small screens; chevron icon `font-size` increased to `1em` for better legibility. `_sass/core/_navbar.scss`.
1661
2078
  - **Syntax highlighting**: Dual-palette system — `_sass/core/_syntax.scss` now uses a GitHub Light palette for `.highlight` (light mode) and scopes the Material Dark base16 palette to `[data-bs-theme="dark"] .highlight`, fixing near-invisible token colors on light backgrounds.
1662
2079
  - **Theme preview gallery**: Expanded to 20 sections with 6 new components: Callouts (5 types), Accordion, Progress & Spinners, Breadcrumb & Pagination, Tooltips & Popovers, and Icons showcase. TOC updated accordingly; Bootstrap tooltip/popover JS initializer added. `_includes/components/theme-preview-gallery.html`, `pages/_about/settings/theme-preview.md`.
@@ -2701,7 +3118,7 @@ See [`docs/installation/migration-from-0.x.md`](docs/installation/migration-from
2701
3118
  - **Troubleshooting Guide**: Added sections for Docker issues, Jekyll build errors, front matter problems, performance optimization
2702
3119
  - **Jekyll Guide**: Added directory structure, configuration files, content collections, essential commands, topic index
2703
3120
  - **Bootstrap Guide**: Added CDN loading patterns, key components, responsive breakpoints, custom styles, icons
2704
- - **Liquid Guide**: Added syntax examples with `{% raw %}` tags, filters, control flow, includes
3121
+ - **Liquid Guide**: Added syntax examples with `{% raw %}{% raw %}{% endraw %}` tags, filters, control flow, includes
2705
3122
  - **Ruby Guide**: Added version commands, common commands, key files, Docker usage, troubleshooting
2706
3123
  - **Front Matter Guide**: Added required/optional fields, layout options, collection-specific fields, complete examples
2707
3124
  - All documentation pages now include `sidebar: nav: docs` for consistent navigation