jekyll-theme-zer0 1.29.0 → 1.30.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +715 -2
- data/_data/README.md +2 -0
- data/_data/ai.yml +5 -3
- data/_data/ai_pricing.yml +36 -0
- data/_data/backlog.yml +169 -18
- data/_data/consumers.yml +48 -5
- data/_data/features.yml +146 -20
- data/_data/feedback_types.yml +17 -12
- data/_data/landing.yml +5 -2
- data/_data/navigation/quickstart.yml +4 -0
- data/_data/site_builder.yml +874 -0
- data/_data/theme-manifest.yml +122 -114
- data/_data/ui-text.yml +18 -0
- data/_includes/README.md +11 -1
- data/_includes/analytics/posthog.html +2 -2
- data/_includes/components/admin-links.html +2 -2
- data/_includes/components/admin-tabs.html +2 -2
- data/_includes/components/ai-chat.html +14 -11
- data/_includes/components/analytics-dashboard.html +8 -8
- data/_includes/components/author-bio.html +1 -1
- data/_includes/components/author-card.html +10 -2
- data/_includes/components/author-eeat.html +4 -4
- data/_includes/components/background-customizer.html +8 -8
- data/_includes/components/background-image.html +114 -0
- data/_includes/components/background-settings.html +28 -15
- data/_includes/components/collection-manager.html +5 -5
- data/_includes/components/component-showcase.html +13 -13
- data/_includes/components/config-editor.html +12 -12
- data/_includes/components/config-viewer.html +8 -8
- data/_includes/components/cookie-consent.html +11 -11
- data/_includes/components/cta-button.html +7 -2
- data/_includes/components/dev-shortcuts.html +7 -7
- data/_includes/components/env-dashboard.html +8 -8
- data/_includes/components/env-switcher.html +9 -9
- data/_includes/components/feature-card.html +2 -2
- data/_includes/components/halfmoon.html +2 -2
- data/_includes/components/info-section.html +36 -36
- data/_includes/components/js-cdn.html +15 -15
- data/_includes/components/language-toggle.html +4 -4
- data/_includes/components/mermaid.html +72 -435
- data/_includes/components/nanobar.html +5 -5
- data/_includes/components/nav-editor.html +2 -2
- data/_includes/components/nav-export.html +2 -2
- data/_includes/components/nav-overview.html +2 -2
- data/_includes/components/page-feedback.html +45 -30
- data/_includes/components/page-views-init.html +1 -1
- data/_includes/components/post-card.html +22 -22
- data/_includes/components/post-type-badge.html +2 -2
- data/_includes/components/powered-by.html +2 -2
- data/_includes/components/preview-image.html +6 -0
- data/_includes/components/quick-index.html +2 -2
- data/_includes/components/search-modal.html +27 -2
- data/_includes/components/searchbar.html +2 -2
- data/_includes/components/svg-background.html +2 -2
- data/_includes/components/theme-customizer.html +2 -2
- data/_includes/components/theme-info.html +6 -6
- data/_includes/components/theme-preview-gallery.html +22 -22
- data/_includes/content/giscus.html +2 -2
- data/_includes/content/intro.html +8 -8
- data/_includes/content/jsonld-faq.html +2 -2
- data/_includes/content/jsonld-software.html +2 -2
- data/_includes/content/seo.html +4 -4
- data/_includes/content/sitemap.html +27 -27
- data/_includes/content/toc.html +183 -183
- data/_includes/core/branding.html +6 -6
- data/_includes/core/console-capture.html +32 -74
- data/_includes/core/favicon.html +49 -7
- data/_includes/core/footer-fabs.html +17 -3
- data/_includes/core/footer.html +31 -18
- data/_includes/core/head.html +102 -90
- data/_includes/core/header.html +71 -52
- data/_includes/docs/bootstrap-docs.html +8 -8
- data/_includes/landing/landing-install-cards.html +2 -2
- data/_includes/landing/landing-quick-links.html +1 -1
- data/_includes/navigation/admin-nav.html +2 -2
- data/_includes/navigation/nav-tree.html +8 -8
- data/_includes/navigation/navbar.html +12 -12
- data/_includes/navigation/section-sidebar.html +16 -16
- data/_includes/navigation/sidebar-config.html +36 -2
- data/_includes/navigation/sidebar-left.html +15 -15
- data/_includes/navigation/sidebar-right.html +6 -6
- data/_includes/obsidian/full-graph.html +2 -2
- data/_includes/setup/claude-session.html +72 -0
- data/_includes/setup/prereq-checklist.html +90 -0
- data/_includes/setup/wizard.html +906 -291
- data/_includes/stats/stats-categories.html +8 -8
- data/_includes/stats/stats-header.html +14 -14
- data/_includes/stats/stats-metrics.html +12 -12
- data/_includes/stats/stats-no-data.html +12 -12
- data/_includes/stats/stats-overview.html +6 -6
- data/_includes/stats/stats-tags.html +8 -8
- data/_layouts/404.html +38 -24
- data/_layouts/admin.html +22 -22
- data/_layouts/article.html +39 -34
- data/_layouts/author.html +20 -20
- data/_layouts/authors.html +2 -2
- data/_layouts/book-abc.html +12 -12
- data/_layouts/book-story.html +15 -15
- data/_layouts/book.html +12 -12
- data/_layouts/collection.html +33 -33
- data/_layouts/cookbook.html +12 -12
- data/_layouts/default.html +27 -24
- data/_layouts/home.html +23 -23
- data/_layouts/index.html +10 -10
- data/_layouts/landing.html +17 -17
- data/_layouts/news.html +44 -44
- data/_layouts/note.html +38 -38
- data/_layouts/notebook.html +34 -34
- data/_layouts/recipe.html +24 -24
- data/_layouts/root.html +73 -55
- data/_layouts/section.html +23 -23
- data/_layouts/setup.html +3 -3
- data/_layouts/sitemap-collection.html +49 -49
- data/_layouts/stats.html +40 -40
- data/_layouts/tag.html +12 -12
- data/_layouts/welcome.html +21 -21
- data/_sass/components/_mermaid.scss +375 -0
- data/_sass/components/_setup-wizard.scss +569 -40
- data/_sass/core/_navbar.scss +11 -31
- data/_sass/layouts/_navbar-extras.scss +14 -4
- data/assets/css/main.scss +1 -0
- data/assets/js/ai-chat.js +47 -5
- data/assets/js/fleet-feedback-capture.js +124 -0
- data/assets/js/fleet-feedback.js +853 -0
- data/assets/js/mermaid-diagrams.js +1267 -0
- data/assets/js/modules/navigation/config.js +9 -6
- data/assets/js/modules/navigation/scroll-spy.js +315 -80
- data/assets/js/modules/theme/appearance.js +8 -2
- data/assets/js/obsidian-wiki-links.js +8 -3
- data/assets/js/page-feedback.js +125 -192
- data/assets/js/search-modal.js +26 -0
- data/assets/js/setup-wizard.js +2112 -361
- data/assets/js/site-builder.js +1834 -0
- data/assets/js/ui-enhancements.js +11 -3
- data/scripts/README.md +15 -0
- data/scripts/ai/README.md +38 -0
- data/scripts/ai/api_call.rb +124 -0
- data/scripts/ai/usage.rb +314 -0
- data/scripts/ai/usage_report.rb +225 -0
- data/scripts/ci/test_visual_evidence_autogen.py +341 -0
- data/scripts/ci/visual_evidence_autogen.py +1060 -0
- data/scripts/content-review.rb +20 -1
- data/scripts/test/integration/mermaid +22 -8
- data/scripts/test/lib/run_tests.sh +1 -0
- data/scripts/test/lib/test_visual_evidence_autogen.sh +24 -0
- data/scripts/translate.rb +23 -1
- metadata +19 -2
data/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,92 @@ 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.30.0](https://github.com/bamr87/zer0-mistakes/compare/v1.29.0...v1.30.0) (2026-09-12)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* **ci:** produce visual evidence and baselines on PRs, not just verify them ([#462](https://github.com/bamr87/zer0-mistakes/issues/462)) ([a377fae](https://github.com/bamr87/zer0-mistakes/commit/a377fae6ea31a0bb015479dea6d1a7c41e141c3f))
|
|
21
|
+
* **includes:** add background-image.html for CSS-painted cover art ([#451](https://github.com/bamr87/zer0-mistakes/issues/451)) ([9b15a67](https://github.com/bamr87/zer0-mistakes/commit/9b15a673f57b8f7f1f9dae9262191742d99d8080))
|
|
22
|
+
* **includes:** Claude or Grok sessions in the Site Builder ([#473](https://github.com/bamr87/zer0-mistakes/issues/473)) ([e1c5753](https://github.com/bamr87/zer0-mistakes/commit/e1c57531fc23be8d071757cd1941e1ae15285da7))
|
|
23
|
+
* **includes:** file the fleet issue contract from the feedback widget ([#472](https://github.com/bamr87/zer0-mistakes/issues/472)) ([24d4e2c](https://github.com/bamr87/zer0-mistakes/commit/24d4e2c6380541a894e9e671f0f0b0b859a29b05))
|
|
24
|
+
* **includes:** make theme-color track the page surface, not the accent ([#447](https://github.com/bamr87/zer0-mistakes/issues/447)) ([f1fda85](https://github.com/bamr87/zer0-mistakes/commit/f1fda857bb0cf5bd64bebfc1215ba0a906f05e5c))
|
|
25
|
+
* **includes:** themed Mermaid figures + toolbar ([#455](https://github.com/bamr87/zer0-mistakes/issues/455)) ([96776fa](https://github.com/bamr87/zer0-mistakes/commit/96776fa48bbe7b63058d1d246962019c745d7b93))
|
|
26
|
+
* **scripts:** adopt the shared ai-runner kit for claude-run; consumers registry fixes; hub baseline ([#475](https://github.com/bamr87/zer0-mistakes/issues/475)) ([feb4133](https://github.com/bamr87/zer0-mistakes/commit/feb41332eb15d2c13fe0c0f6d15b61007e14daa3))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Bug Fixes
|
|
30
|
+
|
|
31
|
+
* **a11y:** decouple heading size from heading level in theme chrome ([#450](https://github.com/bamr87/zer0-mistakes/issues/450)) ([bb92eb5](https://github.com/bamr87/zer0-mistakes/commit/bb92eb583c955e76339a9c7ab98532b35e226b36))
|
|
32
|
+
* **a11y:** give footer icon links an accessible name, announce search results ([#441](https://github.com/bamr87/zer0-mistakes/issues/441)) ([a1811e8](https://github.com/bamr87/zer0-mistakes/commit/a1811e81ac0c17b0836994be7c48eba51252bae7))
|
|
33
|
+
* **a11y:** stop emitting sidebar/TOC controls on layouts that render no offcanvas ([#443](https://github.com/bamr87/zer0-mistakes/issues/443)) ([619322d](https://github.com/bamr87/zer0-mistakes/commit/619322d8fd7c6e2a700ba154e065bf65caf67461))
|
|
34
|
+
* **ci:** run the autogen lane's tooling from the base branch, not the PR ([#464](https://github.com/bamr87/zer0-mistakes/issues/464)) ([8473050](https://github.com/bamr87/zer0-mistakes/commit/8473050be2b4ddfd994c7bea8a8c35e37a66b7f8))
|
|
35
|
+
* **ci:** stop the UI audit reporting a measurement blackout as a clean run ([#471](https://github.com/bamr87/zer0-mistakes/issues/471)) ([ab92619](https://github.com/bamr87/zer0-mistakes/commit/ab9261946e088417dabe4291590da1c7a964d4fa))
|
|
36
|
+
* **content:** raw-protect Liquid that is written as documentation ([#446](https://github.com/bamr87/zer0-mistakes/issues/446)) ([6123c1f](https://github.com/bamr87/zer0-mistakes/commit/6123c1f1e6ffd167376d4ef421b0a9f78af3985e))
|
|
37
|
+
* **includes:** declare charset inside the 1024-byte window, make the feed discoverable ([#440](https://github.com/bamr87/zer0-mistakes/issues/440)) ([4025205](https://github.com/bamr87/zer0-mistakes/commit/40252050ec21c70aa1d84820fdcde6d564e03478)), closes [#372](https://github.com/bamr87/zer0-mistakes/issues/372) [#371](https://github.com/bamr87/zer0-mistakes/issues/371)
|
|
38
|
+
* **includes:** expose skin-button selected state via aria-pressed ([#469](https://github.com/bamr87/zer0-mistakes/issues/469)) ([6579b16](https://github.com/bamr87/zer0-mistakes/commit/6579b1621a5eff147e224977633728aadc960505)), closes [#467](https://github.com/bamr87/zer0-mistakes/issues/467)
|
|
39
|
+
* **install:** stop seeding the theme's own navigation into consumers ([#449](https://github.com/bamr87/zer0-mistakes/issues/449)) ([232857c](https://github.com/bamr87/zer0-mistakes/commit/232857c66d7c3abccb166b5151a7f80a74dd17ca))
|
|
40
|
+
* **navigation:** scroll spy bolds the section actually being read ([#456](https://github.com/bamr87/zer0-mistakes/issues/456)) ([5ff38bf](https://github.com/bamr87/zer0-mistakes/commit/5ff38bf98faf96cdc3bf63ea3f0e7b10e21f3aab))
|
|
41
|
+
* **sass:** stop navbar controls flickering under a stationary pointer ([#453](https://github.com/bamr87/zer0-mistakes/issues/453)) ([fc84b9a](https://github.com/bamr87/zer0-mistakes/commit/fc84b9a4c714f8e04b8a0f940bed9e3795c144bc)), closes [#404](https://github.com/bamr87/zer0-mistakes/issues/404)
|
|
42
|
+
* **scripts:** restore nested translation placeholders instead of leaking them ([#452](https://github.com/bamr87/zer0-mistakes/issues/452)) ([00cab73](https://github.com/bamr87/zer0-mistakes/commit/00cab73d915f4e116040ef6ca4c0381669ae4eb2))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
### Performance Improvements
|
|
46
|
+
|
|
47
|
+
* **ci:** run the critical PR gate on 3 workers instead of 1 ([#481](https://github.com/bamr87/zer0-mistakes/issues/481)) ([b3fcbc7](https://github.com/bamr87/zer0-mistakes/commit/b3fcbc76a1af2a3a5e43eb28fbff7b3057751de6))
|
|
48
|
+
* **includes:** strip developer doc banners from delivered HTML ([#445](https://github.com/bamr87/zer0-mistakes/issues/445)) ([89b10f1](https://github.com/bamr87/zer0-mistakes/commit/89b10f1ff361c919fd631d0e7e664ed6d7c28a14))
|
|
49
|
+
|
|
50
|
+
## [Unreleased]
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **`claude-run` is now the fleet's shared `ai-runner` kit.** The composite
|
|
55
|
+
action and its new `scripts/ai/run.sh` are byte-identical copies of
|
|
56
|
+
lifehacker.dev's (the kit source of truth) instead of a hand-rolled variant.
|
|
57
|
+
What changes for the two callers (`issue-autopilot.yml`,
|
|
58
|
+
`visual-evidence-autogen.yml`): an AI call that was attempted and rejected —
|
|
59
|
+
revoked credential, exhausted quota, `is_error` payload, CLI install failure
|
|
60
|
+
— now fails the step with the reason as a `::error::` annotation, where the
|
|
61
|
+
old action exited 0 and a dead run read green; the OAuth-first rule is
|
|
62
|
+
enforced with `env -u ANTHROPIC_API_KEY`; the model override is the canonical
|
|
63
|
+
`AI_MODEL` (or the new `model` input) rather than `ZER0_AI_MODEL`, and
|
|
64
|
+
`max-turns` is a new input. Metering (`scripts/ai/usage.rb`,
|
|
65
|
+
`usage_report.rb`, prices in `_data/ai_pricing.yml`) and the Claude API
|
|
66
|
+
fallback (`scripts/ai/api_call.rb`) ride along as optional companions. The
|
|
67
|
+
exit-code contract is pinned by `scripts/ci/test_ai_runner.sh`, wired into
|
|
68
|
+
`./scripts/bin/test`. The action's six existing inputs are unchanged.
|
|
69
|
+
- **`claude-run` is consumed by reference from the hub.** `issue-autopilot.yml` and `visual-evidence-autogen.yml` now call `bamr87/bamr87/.github/actions/claude-run@main` (identical inputs) instead of a vendored `./.github/actions/claude-run`; the local action, `scripts/ai/run.sh`, and the vendored contract test (`scripts/ci/test_ai_runner.sh` + its `scripts/test/lib/` bridge) are deleted, while the consumer companions the hub runner probes for — `scripts/ai/usage.rb`, `usage_report.rb`, `api_call.rb`, `_data/ai.yml`, `_data/ai_pricing.yml`, `tools/unwrap-prose.py` — stay ([bamr87/bamr87#254](https://github.com/bamr87/bamr87/pull/254)).
|
|
70
|
+
- **Consumer registry corrections.** `ai-world-view/ai-world-view.github.io`
|
|
71
|
+
is `remote_theme_floating` (both its `_config.yml` and `hub.yml` pins are
|
|
72
|
+
untagged), not `remote_theme_pinned`; `amr-bash/bash-365.com` is registered
|
|
73
|
+
as a consumer (floating `remote_theme`, unconstrained gem on Azure, path gem
|
|
74
|
+
for dev).
|
|
75
|
+
|
|
76
|
+
### Fixed
|
|
77
|
+
|
|
78
|
+
- **The weekly UI/UX audit was blind, and reported it as clean.** `sweep.mjs`
|
|
79
|
+
built its pages with `browser.newPage()`, which `@axe-core/playwright`
|
|
80
|
+
refuses; the throw was caught by a single per-route `try` that also discarded
|
|
81
|
+
the overflow, console-error and link-collection data already gathered for
|
|
82
|
+
that route. Screenshots still succeeded, so the harness looked alive while
|
|
83
|
+
every accessibility, console, overflow and broken-link result on every route
|
|
84
|
+
was silently dropped — and the report rendered the absence as "0 axe
|
|
85
|
+
violations / 0 broken links". Pages are now built from `browser.newContext()`,
|
|
86
|
+
each measurement fails independently, a scan that errored is reported as
|
|
87
|
+
UNKNOWN rather than clean, and a measurement that fails on every route turns
|
|
88
|
+
the sweep red the way a total capture failure already did. On the demo site
|
|
89
|
+
the sweep goes from 18/21 captured with 21 blackout entries to **21/21
|
|
90
|
+
captured, 0 harness errors, 191 links crawled and 8 genuinely broken links
|
|
91
|
+
found** ([#468](https://github.com/bamr87/zer0-mistakes/issues/468)).
|
|
92
|
+
- **Theme-skin buttons now announce which skin is applied.** The Theme Skin
|
|
93
|
+
group in Settings → Appearance conveyed selection only through the Bootstrap
|
|
94
|
+
`.active` class, which carries no accessibility semantics, so screen readers
|
|
95
|
+
announced nine identical unlabelled buttons — WCAG 2.1 SC 4.1.2 (Name, Role,
|
|
96
|
+
Value). Each button now ships `aria-pressed`, and the click and "Reset
|
|
97
|
+
background" handlers keep it in sync with `.active` through one shared
|
|
98
|
+
helper, matching the sibling color-mode group
|
|
99
|
+
([#467](https://github.com/bamr87/zer0-mistakes/issues/467)).
|
|
100
|
+
|
|
15
101
|
## [1.29.0](https://github.com/bamr87/zer0-mistakes/compare/v1.28.0...v1.29.0) (2026-09-01)
|
|
16
102
|
|
|
17
103
|
|
|
@@ -48,7 +134,556 @@ file. Only `## [Unreleased]` describes work that has not shipped yet.
|
|
|
48
134
|
|
|
49
135
|
## [Unreleased]
|
|
50
136
|
|
|
51
|
-
###
|
|
137
|
+
### Added
|
|
138
|
+
|
|
139
|
+
- **Bring your own AI provider — Claude or Grok — and an open, coding-session-style
|
|
140
|
+
Site Builder (ZER0-087)** — the chat proxy gains a provider layer
|
|
141
|
+
(`templates/deploy/chat-proxy/providers.js`): xAI's Grok is a first-class
|
|
142
|
+
provider (`XAI_API_KEY`, `CHAT_PROVIDER`, `XAI_CHAT_MODEL`) for the chat
|
|
143
|
+
widget, the feedback triage and the Site Builder, translated server-side to
|
|
144
|
+
and from the Anthropic Messages dialect so no client changed. The Site
|
|
145
|
+
Builder's Connect step shows which providers the local dev proxy already has
|
|
146
|
+
keys for (masked), takes a token for the session (sent once to localhost,
|
|
147
|
+
kept in the proxy's memory, optionally saved to `.env` with mode 600), offers
|
|
148
|
+
the model catalog, an image renderer (Grok Imagine / OpenAI Images) and two
|
|
149
|
+
session modes: the guided nine steps, or an open session that can open an
|
|
150
|
+
existing site under the target root, read/edit/write/delete its files, run
|
|
151
|
+
`git status`/`diff`/`log`, generate images into `assets/`, run `jekyll build`
|
|
152
|
+
inside the container and stop a long tool run. Generated sites are
|
|
153
|
+
pre-configured for the chosen provider (`ai_chat.provider`, `preview_images`,
|
|
154
|
+
`.env.example`). The dev proxy now starts without a credential. Also fixes a
|
|
155
|
+
pre-existing 162px horizontal overflow of the whole `/setup/` page at phone
|
|
156
|
+
widths (the Connect step's command boxes could not shrink). (evidence:
|
|
157
|
+
[`test/visual/evidence/site-builder-providers/`](test/visual/evidence/site-builder-providers/README.md)
|
|
158
|
+
— Connect step: 2 provider cards, 2 session modes, token field is a password
|
|
159
|
+
input inert offline; mocked proxy: "needs a token" → "Grok connected"; Build:
|
|
160
|
+
existing sites listed, Open sets the target; mobile overflow 162px → 0px)
|
|
161
|
+
- **CI now produces a UI pull request's visual artifacts instead of only
|
|
162
|
+
checking for them (ZER0-085)** — `visual-evidence-autogen.yml` renders every
|
|
163
|
+
same-repo PR in the same jammy Playwright image the snapshot gate uses, runs
|
|
164
|
+
the PR's `test/visual/*-evidence.mjs` generators (or the new generic
|
|
165
|
+
base-vs-head generator `test/visual/pr-evidence.mjs`, which renders the base
|
|
166
|
+
branch and the head side by side), verifies the 9-skin baselines, and pushes
|
|
167
|
+
the montages + `metrics.json` to the branch. Stale baselines are refreshed
|
|
168
|
+
**only** when the new `visual-evidence-reviewer` agent, having viewed the
|
|
169
|
+
expected | actual | diff montage, judges the diff to be the change the PR
|
|
170
|
+
describes — a code step disposes, the model only proposes ([#417](https://github.com/bamr87/zer0-mistakes/issues/417)
|
|
171
|
+
is why). The evidence gate now requires generated proof (a README alone no
|
|
172
|
+
longer passes), `ci-self-repair` leaves a red `Visual Snapshots` job to this
|
|
173
|
+
lane, and `test/update-snapshots.sh` gained `PRE_TEST_SCRIPT` /
|
|
174
|
+
`POST_TEST_SCRIPT` / `SKIP_PLAYWRIGHT` hooks. Closes the gap that kept
|
|
175
|
+
[#454](https://github.com/bamr87/zer0-mistakes/pull/454) red: its authoring
|
|
176
|
+
agents ran where Docker was gated, and nothing in CI could render what they
|
|
177
|
+
could not. Kill switch: repo variable `VISUAL_EVIDENCE_AUTOGEN_ENABLED=false`.
|
|
178
|
+
The lane restores its own tooling from the base branch before it runs, so it
|
|
179
|
+
works on branches cut before it shipped: without that its first real run on
|
|
180
|
+
#454 died in 20 seconds (that branch has no copy of the orchestrator), and a
|
|
181
|
+
branch carrying an older `update-snapshots.sh` would have silently generated
|
|
182
|
+
nothing at all.
|
|
183
|
+
- **Site Builder — a Claude-guided setup wizard that ends with a running site
|
|
184
|
+
(ZER0-086, extends ZER0-067)** — `/setup/` (and the `welcome` layout) grew
|
|
185
|
+
from a five-step `_config.yml` form into a nine-step builder: Connect →
|
|
186
|
+
Prerequisites → Identity → URLs → Structure → Appearance → Voice →
|
|
187
|
+
Integrations → Build. An embedded Claude session rides alongside every step
|
|
188
|
+
through the local dev proxy (Claude Code OAuth, `claude setup-token`; the
|
|
189
|
+
token never reaches the page). It sees the whole wizard state on every turn
|
|
190
|
+
and acts on it with tools: fill in fields, override a generated file, run
|
|
191
|
+
live prerequisite checks (Docker, Git, gh, VS Code, Node, Claude CLI — a
|
|
192
|
+
fixed command table), read the theme's real source and search the docs,
|
|
193
|
+
resolve a project folder, write the generated project, run `docker compose
|
|
194
|
+
up|ps|logs|down`, and check the new site answers — each mutation behind a
|
|
195
|
+
confirmation card. The form half still works with no proxy at all (GitHub
|
|
196
|
+
Pages included): it generates `_config.yml`, `_config_dev.yml`, `Gemfile`,
|
|
197
|
+
`docker-compose.yml`, `index.md`, navigation, about page, welcome post, one
|
|
198
|
+
index page per collection, `.gitignore`, `zer0.install.yml`, `.env.example`
|
|
199
|
+
and `README.md`, with per-file download and a self-extracting bash bundle.
|
|
200
|
+
Forms gained a site brief, a URL **Suggest** button, a site-type quick-pick,
|
|
201
|
+
a navigation row editor, skin cards with a live **Preview on this page**
|
|
202
|
+
toggle, tone/audience pickers, integration switches with conditional
|
|
203
|
+
sub-fields, a copy button on every command, and a toast for feedback. New:
|
|
204
|
+
`_data/site_builder.yml` (steps, prerequisites mirroring
|
|
205
|
+
`machine-setup.md`, catalogs, framework brief),
|
|
206
|
+
`templates/deploy/chat-proxy/wizard-store.mjs` (the dev-only sandbox behind
|
|
207
|
+
`/api/wizard/*`), `assets/js/site-builder.js`, `_includes/setup/{claude-
|
|
208
|
+
session,prereq-checklist}.html`, the `site_builder:` config block, a
|
|
209
|
+
quickstart page and a feature reference. (evidence:
|
|
210
|
+
[`test/visual/evidence/site-builder/`](test/visual/evidence/site-builder/README.md))
|
|
211
|
+
A first recorded end-to-end run then shaped the build half: the generated
|
|
212
|
+
`docker-compose.yml` publishes LiveReload on the site's port + 1 (a fixed
|
|
213
|
+
35729 collided with the theme's own dev container and the new site never
|
|
214
|
+
started) and shares one gem cache across generated sites; every enabled
|
|
215
|
+
collection now ships a valid starter document (a doc, a quickstart step, a
|
|
216
|
+
note with a wiki-link, a structured recipe) and the cookbook index uses the
|
|
217
|
+
theme's `cookbook` layout over the real `recipes` collection; `_config.yml`
|
|
218
|
+
sets `collections_dir: pages` (without it every collection but posts was
|
|
219
|
+
invisible to Jekyll); each site gets its own `assets/images/logo.svg`
|
|
220
|
+
monogram and gem-based sites turn the SVG background layers off, because
|
|
221
|
+
the published gem ships no theme images; a revoked Claude credential now
|
|
222
|
+
says so and names the fix. Validation is a scenario
|
|
223
|
+
runner, `test/visual/site-builder-walkthrough.mjs`, that samples random
|
|
224
|
+
briefs per site type (`test/visual/site-builder-scenarios.mjs`), drives the
|
|
225
|
+
wizard on video, builds the site with Docker and asserts its routes, title
|
|
226
|
+
and skin — replayable by seed.
|
|
227
|
+
The builder then gained a **schema-driven site plan**: `plan_schema` in
|
|
228
|
+
`_data/site_builder.yml` (a JSON-Schema subset validated in the browser)
|
|
229
|
+
describes what the agent may produce — a landing page (five templates,
|
|
230
|
+
nine section types, hero + CTAs), navigation shape (flat or grouped
|
|
231
|
+
dropdowns, sidebar none/auto/docs tree), theme overrides (nine palettes or
|
|
232
|
+
custom colours, seven font pairings, three corner radii) and up to twelve
|
|
233
|
+
example pages with Markdown bodies — and the generators turn it into
|
|
234
|
+
`index.md` (a Liquid landing engine) + `_data/landing.yml`,
|
|
235
|
+
`_data/navigation/main.yml` and `docs.yml`, `assets/css/user-overrides.css`
|
|
236
|
+
+ `_includes/custom/head.html`, and one file per page. Claude submits plans
|
|
237
|
+
with `set_site_plan` behind a confirmation card; the Structure and
|
|
238
|
+
Appearance steps expose the same choices as controls, with a page planner
|
|
239
|
+
and a **Preview on this page** toggle that applies the generated overrides
|
|
240
|
+
to the wizard itself. The scenario runner randomises the plan too and
|
|
241
|
+
asserts the built site's landing template, palette colour, web fonts and
|
|
242
|
+
planned routes.
|
|
243
|
+
- **Mermaid diagrams are now accessible figures with a toolbar (ZER0-013)** —
|
|
244
|
+
every ```` ```mermaid ```` fence (and legacy `<div class="mermaid">`) renders
|
|
245
|
+
as a `<figure>` with a rendered SVG and a small toolbar: zoom out / in / reset
|
|
246
|
+
(50–400 %, real layout — the SVG is resized, so the frame scrolls and
|
|
247
|
+
drag-to-pan, `Ctrl`+wheel and the `+` / `-` / `0` keys all work), **View
|
|
248
|
+
fullscreen** (a native `<dialog>` — the fix for a wide diagram shrunk to
|
|
249
|
+
illegibility on a phone; `Esc` closes it and focus returns to the opener),
|
|
250
|
+
**Copy diagram source**, and **Download as SVG** (page background baked in so
|
|
251
|
+
a dark-mode export stays readable). On pointer devices the toolbar floats over
|
|
252
|
+
the top-right corner on hover/focus; on touch devices it is a static row above
|
|
253
|
+
the diagram so it never covers it. `accTitle` becomes the visible
|
|
254
|
+
`<figcaption>` and the diagram's accessible name, `accDescr` its `<desc>`,
|
|
255
|
+
and the scrollable frame is a focusable, labelled region. A syntax error no
|
|
256
|
+
longer blanks the block: the figure shows the parse message, a hint, and the
|
|
257
|
+
source in a `<details>`, with *copy* still enabled. Colours are **derived from
|
|
258
|
+
the live design tokens** (`--bs-primary`, `--bs-body-bg`, `--zer0-color-*`)
|
|
259
|
+
through Mermaid's `base` theme, so diagrams follow the colour mode, the
|
|
260
|
+
selected skin and any `theme_color` override, and re-render in place when
|
|
261
|
+
those change; dark mode is decided by background luminance, so wizard mode and
|
|
262
|
+
dark skins get legible ink, and a contrast guard keeps nodes visible when the
|
|
263
|
+
brand equals the page colour. Pie slices, git branches and mind-map branches
|
|
264
|
+
get a 12-colour series fanned out from the brand hue. `securityLevel` now
|
|
265
|
+
defaults to `strict` (`mermaid.security_level: loose` restores `click`
|
|
266
|
+
callbacks / HTML labels); `mermaid.toolbar`, `fullscreen` and `download` can
|
|
267
|
+
switch the controls off. Implemented as `_includes/components/mermaid.html`
|
|
268
|
+
(loader: config JSON + two `defer` scripts — the 3.3 MB bundle used to load
|
|
269
|
+
synchronously in `<head>`) + `assets/js/mermaid-diagrams.js` +
|
|
270
|
+
`_sass/components/_mermaid.scss`, replacing ~400 lines of inline
|
|
271
|
+
`<script>`/`<style>` in the include; the `!important` SVG overrides that
|
|
272
|
+
flattened `classDef`/`style` colours and pie slices are gone. Toolbar strings
|
|
273
|
+
are `diagram_*` keys in `_data/ui-text.yml`. The docs page
|
|
274
|
+
(`/docs/features/mermaid-diagrams/`) now renders a live example of every
|
|
275
|
+
diagram type, the caption directive, per-node styling and the error state.
|
|
276
|
+
Guarded by `test/visual/features/mermaid.spec.js` (12 tests, smoke tier)
|
|
277
|
+
(evidence: [`test/visual/evidence/mermaid/`](test/visual/evidence/mermaid/README.md)
|
|
278
|
+
— dark-mode toggle keeps the SVG (before: SVG lost, stylesheet text on
|
|
279
|
+
screen); page overflow 0px at 320/390px; 11/12 docs diagrams rendered, 1
|
|
280
|
+
shown as an error card with its source kept).
|
|
281
|
+
|
|
282
|
+
### Fixed
|
|
283
|
+
|
|
284
|
+
- **Generated posts and notes were invisible on GitHub Pages** — every dated
|
|
285
|
+
document the wizard wrote was stamped `T09:00:00.000Z` on the day it was
|
|
286
|
+
generated. Jekyll refuses to publish future-dated documents unless
|
|
287
|
+
`future: true`, and GitHub Pages builds with the default, so a site generated
|
|
288
|
+
before 09:00 UTC went live with its posts and notes missing: linked from the
|
|
289
|
+
collection index and listed in the sitemap, but 404 when clicked. Generated
|
|
290
|
+
content is now stamped with the actual moment it was written, and a
|
|
291
|
+
regression test fails if any generated document carries a future date.
|
|
292
|
+
Found by publishing two example sites and clicking the links.
|
|
293
|
+
- **Every page of every generated site 404'd on `user-overrides.js`** — the
|
|
294
|
+
Site Builder writes `user_overrides: true` so the theme loads the palette and
|
|
295
|
+
font overrides it generates, but that same flag also makes the theme load
|
|
296
|
+
`assets/js/user-overrides.js`, which the wizard never created. Generated
|
|
297
|
+
sites now ship a commented stub, so the request resolves and site owners have
|
|
298
|
+
an obvious place to put their own JavaScript. Found while publishing example
|
|
299
|
+
sites built by the wizard.
|
|
300
|
+
- **Every generated landing page shipped an invisible button** —
|
|
301
|
+
`components/cta-button.html` mapped `variant: outline` to `btn-outline-light`,
|
|
302
|
+
which is white text on a white border, and the Site Builder's own default
|
|
303
|
+
call to action uses `outline` on a light hero. `outline` is now an outlined
|
|
304
|
+
*primary* button; `outline-light` is the explicit variant for a dark surface,
|
|
305
|
+
and the generated landing engine picks between them from `hero.variant`.
|
|
306
|
+
The theme's own homepage hero is `bg-primary text-white`, so its two
|
|
307
|
+
outlined buttons moved to `outline-light` and render exactly as before —
|
|
308
|
+
a consumer who copied `_data/landing.yml` into a site with a dark hero
|
|
309
|
+
should make the same one-word change.
|
|
310
|
+
- **A planned `landing.hero.image` was accepted and then thrown away** — the
|
|
311
|
+
site plan's schema has offered `landing.hero.image` since the Site Builder
|
|
312
|
+
shipped, but the landing engine it generates never rendered it, so an agent
|
|
313
|
+
(or a human) could set a hero image, see it validated, watch it land in
|
|
314
|
+
`_data/landing.yml`, and get a landing page with no picture. `index.md` now
|
|
315
|
+
renders it inside the hero, decorative (empty `alt`) so a screen reader does
|
|
316
|
+
not hear the headline twice. Found by building a site end to end through the
|
|
317
|
+
new Grok path, where the "Hero image with …" shortcut pointed straight at the
|
|
318
|
+
dead field.
|
|
319
|
+
- **A partial hero patch silently wiped the rest of the hero** —
|
|
320
|
+
`set_site_plan` replaced `landing.hero` wholesale, so the natural agent move
|
|
321
|
+
of "write the copy now, add the artwork two turns later"
|
|
322
|
+
(`{landing: {hero: {image}}}`) discarded the headline, subheadline and every
|
|
323
|
+
call to action, and the landing page quietly reverted to the site title and
|
|
324
|
+
the default buttons. The hero now merges field by field; `sections`,
|
|
325
|
+
`navigation.items` and `pages` still replace, because a shorter list has to
|
|
326
|
+
mean a shorter list. The tool description says so, and the regression test
|
|
327
|
+
pins both halves.
|
|
328
|
+
|
|
329
|
+
- **A new post appeared at its own URL but on no index** — the generated
|
|
330
|
+
`_config_dev.yml` turned on Jekyll's experimental incremental regeneration,
|
|
331
|
+
which rebuilds only documents whose own source changed. Adding a post left
|
|
332
|
+
the collection index, the home page's latest-posts list and the feed showing
|
|
333
|
+
the old set, with the post reachable only by typing its URL. New sites are
|
|
334
|
+
now generated with `incremental: false`; a full rebuild of a fresh site takes
|
|
335
|
+
about a second and is always right.
|
|
336
|
+
|
|
337
|
+
- **The 15-second status poll handed the composer back mid-turn** — the Site
|
|
338
|
+
Builder's proxy poll rebuilt the panel's enabled state from the connection
|
|
339
|
+
alone, so during a long action (a Docker build, an image render) the input
|
|
340
|
+
and Send button re-enabled themselves and the Stop button vanished, while
|
|
341
|
+
tools were still running. A second message could then interleave with the
|
|
342
|
+
first. The poll now respects the busy state.
|
|
343
|
+
- **A newly added post never appeared on the running site** — Jekyll's
|
|
344
|
+
`--watch` only tracks collection documents that existed when `serve` started,
|
|
345
|
+
so a page or post the assistant added to a running project stayed invisible
|
|
346
|
+
with no explanation. `write_project_file` now says so in its result and
|
|
347
|
+
offers a restart, and `run_compose` gained a `restart` action.
|
|
348
|
+
|
|
349
|
+
- **Switching colour mode destroyed every Mermaid diagram** — the old include's
|
|
350
|
+
`MutationObserver` re-render emptied each `.mermaid` div and refilled it with
|
|
351
|
+
the div's *current* text, which after the first render is the SVG's own
|
|
352
|
+
stylesheet, and never cleared Mermaid's `data-processed` flag, so
|
|
353
|
+
`mermaid.run()` skipped the div. Toggling dark mode on `/quickstart/`,
|
|
354
|
+
`/about/` or `/docs/ruby-101/` replaced the diagram with a wall of
|
|
355
|
+
`#mermaid-… {font-family: …}` text and a permanent "Loading diagram…" label
|
|
356
|
+
(reproduced headlessly on `main`: `svgs: 1 → 0` after the toggle). Sources are
|
|
357
|
+
now kept per figure and every render goes through `mermaid.render()`, so a
|
|
358
|
+
mode or skin change re-renders from the original definition — asserted by the
|
|
359
|
+
regression test in `test/visual/features/mermaid.spec.js`.
|
|
360
|
+
- **The Obsidian wiki-link resolver rewrote the inside of inline SVGs** —
|
|
361
|
+
`assets/js/obsidian-wiki-links.js` skips text under `CODE`/`PRE`/`A`/
|
|
362
|
+
`SCRIPT`/`STYLE` by comparing `nodeName`, but elements in the SVG namespace
|
|
363
|
+
report it in lower case (`style`, `text`), so a Mermaid diagram's own
|
|
364
|
+
`<style>` was walked and every `#id` selector in it was turned into a
|
|
365
|
+
`<a class="obsidian-tag">` link. The stylesheet lost its scoping: one
|
|
366
|
+
diagram's `#arrowhead path { fill }` became a bare `path { fill }` for the
|
|
367
|
+
whole page, and pie slices rendered dark grey while their legend showed the
|
|
368
|
+
right colours. The old include escaped it only because the resolver
|
|
369
|
+
special-cases the `.mermaid` class. The name check is now case-insensitive
|
|
370
|
+
and any `<svg>` subtree (graphics, never prose) is skipped, with
|
|
371
|
+
`.zer0-diagram` added to the class exemptions. `test/test_resolver.js` gains
|
|
372
|
+
a fixture with a lower-case `style`/`text` inside `<svg>`, and the Mermaid
|
|
373
|
+
spec asserts a pie slice's computed fill equals its own attribute and that no
|
|
374
|
+
unscoped `path` rule exists on the page.
|
|
375
|
+
|
|
376
|
+
### Changed
|
|
377
|
+
|
|
378
|
+
- **The page-feedback widget now files the fleet's issue contract, not its own
|
|
379
|
+
(UPS-FB).** The theme built its own issue body, its own escaping, and its own
|
|
380
|
+
URL-length trimming — and so did the 404 page and the AI chat, three builders
|
|
381
|
+
in one repo with three different behaviours. All three now go through
|
|
382
|
+
`FleetFeedbackCore` in `assets/js/fleet-feedback.js`, vendored byte-identically
|
|
383
|
+
from the fleet kit (`bamr87/bamr87` `templates/feedback/`, held to the hub's
|
|
384
|
+
copy by the drift gate). It is the same builder the fleet's `<fleet-feedback>`
|
|
385
|
+
web component uses, so an issue filed from a page here and one filed from a
|
|
386
|
+
React app elsewhere are byte-identical to the pipeline that triages them.
|
|
387
|
+
|
|
388
|
+
What that adds to every filed issue: sections in a fixed order, a
|
|
389
|
+
`<!-- fleet-feedback v1 type=... -->` marker the issue pipeline reads to
|
|
390
|
+
recognise an already-structured report, and a real over-budget path. The AI
|
|
391
|
+
chat in particular used to `.slice(0, 6000)` its body — silently dropping
|
|
392
|
+
whatever Claude had written past that point — and now trims by section and
|
|
393
|
+
hands the full text to the clipboard.
|
|
394
|
+
|
|
395
|
+
The theme keeps its Bootstrap modal and its AI triage step; only the body
|
|
396
|
+
assembly moved. Two UIs, one contract.
|
|
397
|
+
|
|
398
|
+
- **Request-type labels map onto the fleet issue-pipeline taxonomy.**
|
|
399
|
+
`enhancement`/`documentation` become `feature`/`docs`; `area:docs`/`area:feat`
|
|
400
|
+
drop out. Every label the widget applies exists in this repo (GitHub silently
|
|
401
|
+
drops the ones that do not), and a widget-filed issue is now pipeline-eligible
|
|
402
|
+
on the next scan. The zero-`_data` fallback taxonomy carries them too, so
|
|
403
|
+
`remote_theme` consumers benefit the moment those labels exist in their repo.
|
|
404
|
+
|
|
405
|
+
- **Captured console lines are redacted before they enter the buffer.** Bearer
|
|
406
|
+
tokens, API keys, JWTs, GitHub tokens and email addresses are masked on the
|
|
407
|
+
way in, so a secret cannot be previewed, copied, or filed even by accident.
|
|
408
|
+
Credentials reach the console more often than anyone expects — an
|
|
409
|
+
`Authorization` header logged by a fetch wrapper, a signed URL in a 403.
|
|
410
|
+
|
|
411
|
+
- **The capture buffer hooks `console.warn` and `console.error` only.**
|
|
412
|
+
Previously `log`, `info` and `debug` were captured too. At a 40-entry ring,
|
|
413
|
+
debug chatter evicts the one line that explains the failure — which is the
|
|
414
|
+
line a report exists to carry. `_includes/core/console-capture.html` is now a
|
|
415
|
+
two-line loader for the vendored buffer rather than 60 lines of inline script,
|
|
416
|
+
which also *shrinks* `<head>` and buys back room for the charset meta that has
|
|
417
|
+
to land in the first 1024 bytes (#372).
|
|
418
|
+
|
|
419
|
+
### Fixed
|
|
420
|
+
|
|
421
|
+
- **The 404 page files a report a maintainer can act on.** "Submit an Issue on
|
|
422
|
+
GitHub" opened a hand-built form containing two sentences of boilerplate — no
|
|
423
|
+
page context, no environment, no captured errors. It now opens the feedback
|
|
424
|
+
widget pre-typed as `fix-page` with the URL that failed (and the page that
|
|
425
|
+
linked to it) already in the description.
|
|
426
|
+
|
|
427
|
+
- **The feedback FAB survives its script failing to load.** It was a `<button>`
|
|
428
|
+
whose only behaviour came from JavaScript; with the widget now depending on a
|
|
429
|
+
second script, a 404 on either one would have left a decorative circle in the
|
|
430
|
+
corner. It is an anchor to the issue form, upgraded in place when the script
|
|
431
|
+
runs — the same progressive enhancement the inline "Improve" link already had.
|
|
432
|
+
|
|
433
|
+
- **The page-feedback Playwright spec dismisses the cookie-consent banner.** The
|
|
434
|
+
banner is a full-width bar on a layer above the FAB stack, so it swallowed
|
|
435
|
+
every click aimed at the FAB. Every other spec that touches lower-screen chrome
|
|
436
|
+
already seeded the consent choice through the shared fixture; this one never
|
|
437
|
+
did.
|
|
438
|
+
|
|
439
|
+
### Changed
|
|
440
|
+
|
|
441
|
+
- **TOC scroll spy now bolds the section you are actually reading** — the
|
|
442
|
+
highlighted entry in the right-hand Table of Contents jumped around and sat
|
|
443
|
+
one to three sections ahead of the viewport. Measured on `/docs/features/toc/`
|
|
444
|
+
at 1280×820, the wrong entry was bolded at **19–23 of 25 scroll positions**
|
|
445
|
+
— the count moves between runs because which implementation won depended on
|
|
446
|
+
callback timing, which is the bug in one sentence.
|
|
447
|
+
|
|
448
|
+
Three implementations were toggling `.active` on the same
|
|
449
|
+
`#TableOfContents a` links, so whichever fired last won. Bootstrap's native
|
|
450
|
+
ScrollSpy was wired up twice — `data-bs-spy="scroll"
|
|
451
|
+
data-bs-target="#TableOfContents"` on `<body>` in `_layouts/root.html` *and*
|
|
452
|
+
on `.bd-main` in `_layouts/default.html` (whose `data-bs-offset="100"` had
|
|
453
|
+
been dead since Bootstrap 5.2 replaced that option with
|
|
454
|
+
`data-bs-root-margin`). `assets/js/ui-enhancements.js` ran a third observer
|
|
455
|
+
over *every* `a[href^="#"]` on the page, clearing `.active` from all of them
|
|
456
|
+
each time a `section[id]` intersected. Both Bootstrap hooks are removed —
|
|
457
|
+
the `<body>` one was also stripping server-rendered `.active` classes off
|
|
458
|
+
admin sidebar links, which `fixtures.gotoBeforeScrollSpy` exists to work
|
|
459
|
+
around — and the `ui-enhancements.js` observer is now scoped away from TOC
|
|
460
|
+
and sidebar links.
|
|
461
|
+
|
|
462
|
+
The theme's own spy (`assets/js/modules/navigation/scroll-spy.js`) was wrong
|
|
463
|
+
on its own terms too: it asked IntersectionObserver for the "most visible"
|
|
464
|
+
heading, but headings are a few pixels tall, so every heading inside the
|
|
465
|
+
observer band reports the same `intersectionRatio` — the winner was whichever
|
|
466
|
+
entry happened to be in that callback's batch, and a heading scrolling *out*
|
|
467
|
+
of the band triggered no re-evaluation at all. It now applies a positional
|
|
468
|
+
rule: the active heading is the last one whose top has crossed the reading
|
|
469
|
+
line (the document's `scroll-padding-top`, the same offset anchor navigation
|
|
470
|
+
uses), with the last heading winning once the page is scrolled to the bottom
|
|
471
|
+
so trailing sections shorter than the viewport are still reachable. The
|
|
472
|
+
answer is recomputed from scratch on each rAF-throttled scroll frame from
|
|
473
|
+
cached heading offsets, re-measured on resize and content reflow
|
|
474
|
+
(`ResizeObserver`), so it cannot drift out of sync.
|
|
475
|
+
|
|
476
|
+
Two smaller fixes ride along. Clicking a TOC entry now holds that entry
|
|
477
|
+
active while the smooth scroll animates, instead of flashing every heading
|
|
478
|
+
passed on the way. And keeping the active entry visible inside the TOC no
|
|
479
|
+
longer calls `scrollIntoView()`, which bubbles up and scrolls the *page* —
|
|
480
|
+
feeding straight back into the spy; it adjusts the TOC container's own
|
|
481
|
+
`scrollTop`, resolving that container at call time (`.bd-toc` on desktop,
|
|
482
|
+
`.offcanvas-body` on mobile) rather than assuming one. The active link also
|
|
483
|
+
carries `aria-current="true"`.
|
|
484
|
+
|
|
485
|
+
`config.scrollSpy.rootMargin`/`threshold` are replaced by
|
|
486
|
+
`config.scrollSpy.offset` (`null` = derive from `scroll-padding-top`) and
|
|
487
|
+
`tolerance`. Guarded by `test/visual/features/scroll-spy.spec.js` (smoke
|
|
488
|
+
tier), which is 5 failed / 1 passed against the pre-fix theme and 6 passed
|
|
489
|
+
with the fix. (evidence:
|
|
490
|
+
[`test/visual/evidence/scroll-spy/`](test/visual/evidence/scroll-spy/README.md)
|
|
491
|
+
— wrong TOC highlight at 19/25 scroll positions → 0)
|
|
492
|
+
- **Navbar controls no longer flicker under a stationary pointer (#404)** —
|
|
493
|
+
`.nav-link:hover`, the Search/Settings utility buttons and the two sidebar/TOC
|
|
494
|
+
FABs applied `transform: translateY(-1px)`. A 1px lift moves the element out
|
|
495
|
+
from under a pointer parked on the row it vacates, so hover exits, the element
|
|
496
|
+
drops back under the pointer, and hover re-enters — an infinite flicker with
|
|
497
|
+
no pointer movement at all. Nav items are thin enough for a 1px lift to
|
|
498
|
+
reliably do it.
|
|
499
|
+
|
|
500
|
+
Deleting those declarations was **not sufficient**, and the reason is the
|
|
501
|
+
substantive finding here: `_sass/components/_ui-enhancements.scss` applies
|
|
502
|
+
`transform: translateY(-2px)` to `.btn:hover` — *every* button in the theme.
|
|
503
|
+
The utility buttons and both FABs are `.btn`s, so with the navbar's own 1px
|
|
504
|
+
lift removed the buttons kept flickering on the inherited 2px one — measured
|
|
505
|
+
on the built site, the Settings button lost and regained hover 14 times in
|
|
506
|
+
800ms with the pointer completely still. The fix therefore cancels it
|
|
507
|
+
explicitly with `transform: none`, the corrective pattern this repo already
|
|
508
|
+
uses in `_post-navigation.scss` and its `prefers-reduced-motion` blocks. The
|
|
509
|
+
FABs get the same treatment by the same reasoning — they are the same kind of
|
|
510
|
+
`.btn` with the same lift — but they were not rendered at any viewport reached
|
|
511
|
+
during verification, so that half is reasoned, not observed.
|
|
512
|
+
|
|
513
|
+
Background, colour and shadow transitions are untouched, and the controls keep
|
|
514
|
+
a pressed cue as `:active { transform: translateY(1px) }` — safe, because
|
|
515
|
+
`:active` only holds while the button is held down, so the pointer cannot
|
|
516
|
+
leave and re-enter in a loop.
|
|
517
|
+
|
|
518
|
+
Three corrections to the issue as filed. The vacated row is the **bottom**
|
|
519
|
+
edge, not the top — a probe parked on the top edge passes without exercising
|
|
520
|
+
the bug at all. Only a transform on the **hovered element's own box** can
|
|
521
|
+
flicker: `&:hover i { transform: scale(1.1) }` moves a child and never shrinks
|
|
522
|
+
the parent's hit box, so those went for consistency, not as causes. And
|
|
523
|
+
`.nav-link:hover`, named as the primary culprit, was **dead code** — its lift
|
|
524
|
+
sat inside `@media (min-width: 992px) { @container navbar-main (max-width:
|
|
525
|
+
75rem) }`, and measured on the built site at 1040/1140/1200/1280px its
|
|
526
|
+
computed hover transform is `none` at every width, on `main` too. Every
|
|
527
|
+
flicker actually observed was on the utility buttons.
|
|
528
|
+
|
|
529
|
+
Guarded by `test/visual/core/hover-stability.spec.js`: with the pointer on the
|
|
530
|
+
control's centre the border box must be byte-identical to its resting box and
|
|
531
|
+
the computed transform must be `none`; with the pointer held half a pixel
|
|
532
|
+
inside the bottom edge for 800ms, `mouseleave` must stay at zero. The verdict
|
|
533
|
+
is the *computed* value rather than "no matching rule exists", because
|
|
534
|
+
`.btn:hover` does still match these controls and always will — it is
|
|
535
|
+
overridden, not deleted. `mouseleave`, not `mouseout`: the latter also fires
|
|
536
|
+
when the pointer crosses onto a descendant, which is not the element losing
|
|
537
|
+
hover, and it produces a false positive on every `.nav-hover-dropdown` parent.
|
|
538
|
+
Against `main` the spec is 4 failed / 1 passed; with the fix, 5 passed.
|
|
539
|
+
Evidence in `test/visual/evidence/hover-flicker/` — the Settings button lost
|
|
540
|
+
hover 14 times in 800ms before, 0 after.
|
|
541
|
+
|
|
542
|
+
Scoped to the navbar. The issue's original acceptance criterion — *no
|
|
543
|
+
transform in any `:hover` rule under `_sass/`* — is 42 rules across ~15 files
|
|
544
|
+
(card lifts, book-cover tilts, recipe and author hovers) and a visual redesign
|
|
545
|
+
of a published theme rather than a defect fix; it also flagged
|
|
546
|
+
`transform: none`, which is the corrective pattern this repo already uses in
|
|
547
|
+
`_post-navigation.scss` and its `prefers-reduced-motion` blocks. A theme-wide
|
|
548
|
+
motion policy belongs in its own task.
|
|
549
|
+
- **Mobile browser chrome now tracks the page surface (#281)** — `theme-color`
|
|
550
|
+
drives the iOS Safari address bar and the Chrome/Android task-switcher card.
|
|
551
|
+
It fell back to `theme_color.main`, which is the brand **accent** (`#007bff`
|
|
552
|
+
here), so a dark-first theme advertised a bright blue address bar; and being
|
|
553
|
+
config-gated it emitted **nothing at all** on `remote_theme` consumers, which
|
|
554
|
+
do not inherit this repo's `_config.yml` — the case the issue was originally
|
|
555
|
+
filed about. The include now emits scheme-aware tags sourced from
|
|
556
|
+
`--bs-body-bg` (light `#ffffff`, dark `#212529`, Bootstrap 5.3.3's own
|
|
557
|
+
surfaces) **with no configuration required**, and honors new optional
|
|
558
|
+
`favicon.theme_color_light` / `theme_color_dark` keys. A site that pins its
|
|
559
|
+
mode (`color_mode_default: dark`/`light`, or `color_mode_lock: true`) gets a
|
|
560
|
+
single unconditional tag instead, because `media="(prefers-color-scheme: …)"`
|
|
561
|
+
keys off the OS and a pair would hand light chrome to an OS-light visitor
|
|
562
|
+
reading a page the site renders dark. `favicon.theme_color` still pins one
|
|
563
|
+
color for both schemes, so existing consumer config is unchanged. Guarded by
|
|
564
|
+
four `@critical` tests in `test/visual/core/head-contract.spec.js` that
|
|
565
|
+
compare each value against the page's computed `--bs-body-bg` rather than a
|
|
566
|
+
constant, plus `test_theme_color_fallback_without_config` covering the
|
|
567
|
+
no-config consumer case.
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
- **Consumers no longer inherit the theme's own navigation (#332)** — `install.sh`
|
|
571
|
+
copied the theme's entire `_data/` into every consumer, so each site's *own*
|
|
572
|
+
`_data/navigation/*.yml` was a verbatim copy of the theme's and carried the
|
|
573
|
+
theme's page taxonomy. Measured on a real install: **106 of 129 seeded
|
|
574
|
+
navigation URLs pointed at pages the installer does not create** — ~47 per
|
|
575
|
+
doc page from `docs.yml` alone, which htmlproofer on a consumer counted as
|
|
576
|
+
~1,598 broken internal links across ~34 docs, none of them removable from
|
|
577
|
+
consumer content. `_data/navigation/` is now held back from the theme copy
|
|
578
|
+
and seeded from `templates/data/navigation-*.yml.template`, whose links
|
|
579
|
+
resolve against the pages the installer actually creates: **0 of 26
|
|
580
|
+
unresolvable**. All seven files are covered, not just `docs.yml`. Because
|
|
581
|
+
`create_from_template` skips a file that already exists, a re-run or upgrade
|
|
582
|
+
preserves navigation a consumer has edited. The sidebar renderer is
|
|
583
|
+
unchanged — `nav: tree` → `auto` → `page.collection` was always correct, it
|
|
584
|
+
was being handed the wrong data. Existing consumers are not fixed
|
|
585
|
+
retroactively (`_data` is not part of Jekyll's theme payload); see
|
|
586
|
+
[Migrating already-seeded navigation data](docs/systems/theme-propagation.md).
|
|
587
|
+
Guarded by `test/test_install_navigation_seed.sh`, which fails on the old
|
|
588
|
+
installer naming all 51 seeded dead links.
|
|
589
|
+
|
|
590
|
+
- **Translation placeholders leaked into published French pages** — before a
|
|
591
|
+
line is sent to the model, `scripts/translate.rb` masks every non-translatable
|
|
592
|
+
span as a `⟦N⟧` token and restores it afterwards. The patterns run in order,
|
|
593
|
+
coarse first, so a span can be masked **twice**: a Liquid expression inside a
|
|
594
|
+
link destination becomes `](⟦3⟧)` and is then masked again as a whole, and the
|
|
595
|
+
same happens to Liquid inside an inline code span. `unmask` used a single
|
|
596
|
+
`gsub`, and `gsub` never rescans its own replacement text — so the outer span
|
|
597
|
+
came back and the inner token was stranded as a literal `⟦3⟧` on the page.
|
|
598
|
+
That is 107 tokens across 23 generated pages on `main` today, and where the
|
|
599
|
+
masked span was a URL it is a dead link: `[couleurs](⟦3⟧)` in
|
|
600
|
+
`fr/about/design.md`, `href="⟦24⟧"` in the same file, the whole Liquid
|
|
601
|
+
variable table in `fr/docs/customization/layouts.md`. `unmask` now substitutes
|
|
602
|
+
until the text stops changing, and refuses to return anything still holding a
|
|
603
|
+
placeholder — a corrupt page fails its own job (counted, logged, run exits
|
|
604
|
+
non-zero) rather than shipping. The existing "no placeholder tokens leak"
|
|
605
|
+
assertions in `test/test_i18n.sh` were correct but toothless: no fixture line
|
|
606
|
+
nested a mask. Both shapes are now in the fixture, so the guard fails without
|
|
607
|
+
the fix.
|
|
608
|
+
|
|
609
|
+
The already-generated pages are not repaired by this change. Staleness is
|
|
610
|
+
decided by source SHA alone, so a masker fix does not make an unchanged page
|
|
611
|
+
stale; repairing them needs a `--full` re-translation.
|
|
612
|
+
- **`components/background-image.html` — cover art painted as a CSS background,
|
|
613
|
+
announced correctly (#401)** — the theme had a solid convention for art
|
|
614
|
+
rendered as {% raw %}`<img>`{% endraw %} (`preview-image.html` always emits an escaped `alt`)
|
|
615
|
+
and none for art painted as a **background**. A background image is invisible
|
|
616
|
+
to assistive technology, so such a box announces nothing at all. The correct
|
|
617
|
+
pattern already existed by hand in three places (`abc-letter.html`, the
|
|
618
|
+
article rating stars, `theme-customizer.html`) without being a named
|
|
619
|
+
convention; this generalizes it. Two branches: a real image gets
|
|
620
|
+
`role="img"` with an escaped `aria-label`, a decorative one gets
|
|
621
|
+
`aria-hidden="true"` and **neither** `role` nor `aria-label` — combining them
|
|
622
|
+
announces a graphic and then hides it, which is worse than either alone.
|
|
623
|
+
Decorative is the **default**, and the real-image branch must earn it with
|
|
624
|
+
both a `src` and a non-empty `alt`. Path handling follows
|
|
625
|
+
`preview-image.html`. Purely additive: no in-theme call site, no CSS, no
|
|
626
|
+
layout change, and `content/intro.html` is deliberately untouched — it paints
|
|
627
|
+
a background *and* wraps the page `<h1>`, so `role="img"` there would delete
|
|
628
|
+
the heading from the accessibility tree. Guarded by
|
|
629
|
+
`test_background_image_include_contract`, which renders the include and
|
|
630
|
+
checks both branches, the escaping and all three path cases.
|
|
631
|
+
- **Heading outlines no longer skip levels in theme chrome (#436)** — several
|
|
632
|
+
components picked a heading element for its default **font-size** rather than
|
|
633
|
+
its position in the document, so an article read
|
|
634
|
+
`h1 → h2 → h6 → h2 → h5 → h3 → h6` to anyone navigating by heading. Measured
|
|
635
|
+
across a 415-page build: **391 of 415 pages carried at least one skip, 1,060
|
|
636
|
+
in total**; now 66 and 173. Size and level are decoupled — `<h3 class="h6">`
|
|
637
|
+
keeps the small type and states the real depth — in the cookie dialog, the
|
|
638
|
+
settings offcanvas, the language panel, the author card and the related-posts
|
|
639
|
+
region. The language panel was not in the report and was doing the same thing
|
|
640
|
+
on every page. `author-card.html` renders under twelve different call sites,
|
|
641
|
+
so its level is now `heading_level` (default `5`, unchanged for callers that
|
|
642
|
+
do not pass it) rather than a guess. Fails **WCAG 1.3.1**. Nothing moves
|
|
643
|
+
visually. Guarded by `test/visual/core/heading-outline.spec.js`. One offender
|
|
644
|
+
is injected at **runtime** by `assets/js/modules/theme/appearance.js`, so no
|
|
645
|
+
grep over `_includes`/`_layouts` could find it — only a rendered assertion
|
|
646
|
+
does. Still open, deliberately: 66 pages with inline card markup in the news
|
|
647
|
+
and section index layouts, plus the `palette-generator` and `skin-editor`
|
|
648
|
+
admin tools — see the evidence bundle.
|
|
649
|
+
|
|
650
|
+
- **Liquid written as documentation was being executed, not displayed** — Liquid
|
|
651
|
+
runs before Markdown, so backticks and code fences never escaped it; they only
|
|
652
|
+
changed how its *output* was displayed. Two pages leaked as a result. The
|
|
653
|
+
CHANGELOG page carried a **second full copy of `<head>` inside its body**
|
|
654
|
+
(Bootstrap CSS, Bootstrap Icons and `X-UA-Compatible` all emitted twice, and
|
|
655
|
+
the Google Tag Manager snippet a second time on a production build) because an
|
|
656
|
+
{% raw %}`{% include core/head.html %}`{% endraw %} in prose was run rather than shown. The
|
|
657
|
+
Layout Variables table in `/docs/customization/layouts/` documented nothing:
|
|
658
|
+
{% raw %}`{{ content }}`{% endraw %} injected the whole rendered page into a table cell and
|
|
659
|
+
{% raw %}`{{ page.title }}`{% endraw %} rendered as the literal word "Layouts", leaving
|
|
660
|
+
196 KB of leaked layout output where a five-line code sample belonged. Both are
|
|
661
|
+
now wrapped in Liquid raw blocks, the idiom the same files already used
|
|
662
|
+
elsewhere. A `test_content_liquid_is_raw_protected` guard in
|
|
663
|
+
`test/test_core.sh` fails if injecting Liquid appears unprotected inside a code
|
|
664
|
+
fence or code span again. The layouts doc also had its layout **names** and
|
|
665
|
+
**hierarchy** corrected against `_layouts/`: `journals` has not existed since
|
|
666
|
+
the v1.0 rename to `article`, and three of the four children in the
|
|
667
|
+
inheritance diagram were wrong — `home` and `landing` inherit `root`, not
|
|
668
|
+
`default`, which decides whether a layout renders the sidebar and TOC at all.
|
|
669
|
+
|
|
670
|
+
- **Developer doc banners no longer ship to visitors (#375)** — the 1,065
|
|
671
|
+
multi-line {% raw %}`<!-- ... -->`{% endraw %} banners documenting `_includes/**` and
|
|
672
|
+
`_layouts/**` (file paths, dependency lists, design rationale) are now Liquid
|
|
673
|
+
{% raw %}`{% comment %}`{% endraw %} blocks, which Jekyll strips at build time. The
|
|
674
|
+
in-source documentation is unchanged; only the delimiters moved. Across a
|
|
675
|
+
415-page build the delivered HTML drops from 91.5 MB to 71.6 MB — **21.7%** —
|
|
676
|
+
because an include's banner is re-emitted at every call site, not once:
|
|
677
|
+
`/authors/default/` alone carried 130,840 bytes of it from `post-card.html`
|
|
678
|
+
rendering 1,100 times. Per page: 47,898 comment bytes in 344 blocks → 111
|
|
679
|
+
bytes in 3. The four boundary markers inside Google's own copy-paste
|
|
680
|
+
analytics snippets are kept verbatim, and comments inside
|
|
681
|
+
`<script>`, `<style>`, `<pre>` or a Liquid raw block are untouched. A new
|
|
682
|
+
`test_developer_doc_banners_are_liquid` guard in `test/test_core.sh` fails if
|
|
683
|
+
any reappear. Two side effects worth knowing: reading-time estimates were
|
|
684
|
+
counting the banners and are now correct (`/docs/` claimed 63 minutes, really
|
|
685
|
+
24), and three pages that leak unescaped Liquid emit less as a result — those
|
|
686
|
+
leaks are pre-existing and filed separately.
|
|
52
687
|
|
|
53
688
|
- **Setup wizard: live preview, vertical stepper, draft persistence (T-040, #408)** —
|
|
54
689
|
the `_config.yml` preview is now a **persistent sticky panel** shown at every
|
|
@@ -196,6 +831,83 @@ file. Only `## [Unreleased]` describes work that has not shipped yet.
|
|
|
196
831
|
|
|
197
832
|
### Fixed
|
|
198
833
|
|
|
834
|
+
- **Finished work kept reopening its own issues** — `scripts/sync-backlog.rb`
|
|
835
|
+
treats `_data/backlog.yml` as the source of truth and **reopens** any issue whose
|
|
836
|
+
task is not `done`. T-038, T-040 and T-045 shipped in v1.29.0 (#431, #432, #425)
|
|
837
|
+
and their `Closes #NNN` commits duly closed #406, #408 and #417 — then the next
|
|
838
|
+
sync reopened all three, because the backlog still said `open`. Marked done with
|
|
839
|
+
their PR links, which is the only thing that actually closes them.
|
|
840
|
+
- **The nightly sticky issue could be raised but never cleared (#326)** —
|
|
841
|
+
`nightly-extended.yml` files and updates a sticky issue on failure and does
|
|
842
|
+
nothing on recovery, so a fixed failure stayed open indefinitely. #326 sat open
|
|
843
|
+
five days past its own fix, carrying a P1 into every triage pass. A
|
|
844
|
+
`report-recovery` job now comments and closes it when the tier goes green. It is
|
|
845
|
+
gated on `preflight.outputs.go == 'true'` as well as both jobs succeeding: the
|
|
846
|
+
preflight short-circuits on an unchanged `main` and reports `skipped`, so a bare
|
|
847
|
+
`success()` would close the issue on a night that tested nothing.
|
|
848
|
+
- **37 pages shipped dead controls with dangling ARIA references (#373)** —
|
|
849
|
+
`#bdSidebar` and `#tocContents` are emitted by `_layouts/default.html` alone,
|
|
850
|
+
but their toggles live in `core/header.html` and `core/footer-fabs.html`, which
|
|
851
|
+
`_layouts/root.html` includes on **every** layout. On any layout inheriting
|
|
852
|
+
`root` directly (admin, stats, 404, section, home, book-*) the toggle rendered
|
|
853
|
+
pointing at an offcanvas that was never in the document: clicking did nothing,
|
|
854
|
+
and assistive tech followed `aria-controls` to a missing element. Measured
|
|
855
|
+
across a 415-page build: **19 pages with a dangling `#bdSidebar`** (the French
|
|
856
|
+
`/about/**` tree) and **18 with a dangling `#tocContents`** (404, CHANGELOG,
|
|
857
|
+
`_design-system/**`) — **0 after**. `navigation/sidebar-config.html` now
|
|
858
|
+
publishes `sidebar_offcanvas_rendered`, and both controls are gated on it.
|
|
859
|
+
`footer-fabs.html`'s previous **denylist** of layouts to skip is replaced by
|
|
860
|
+
that positive gate: a denylist fails open, and this one had already fallen
|
|
861
|
+
behind (`404` and the `book-*` layouts were missing). The layout list is pinned
|
|
862
|
+
against the real inheritance graph by
|
|
863
|
+
`test/test_core.sh :: test_sidebar_offcanvas_layout_gate`, which walks every
|
|
864
|
+
`_layouts/*.html` and fails if the two disagree in either direction.
|
|
865
|
+
|
|
866
|
+
- **Seven footer links had no accessible name on mobile (#435)** — the "Follow
|
|
867
|
+
Us" social links and the RSS link render an `<i>` that is `aria-hidden="true"`
|
|
868
|
+
beside a `<span class="d-none d-md-inline">` label. Below the `md` breakpoint
|
|
869
|
+
the span is `display:none`, so it leaves the accessibility tree too and the link
|
|
870
|
+
was announced as a bare "link" — measured: **7 nameless links at 375px**. Each
|
|
871
|
+
now carries an `aria-label` matching its visible text exactly, so the name is
|
|
872
|
+
correct on mobile and still contains the visible label at `md`+ (WCAG 2.5.3
|
|
873
|
+
Label in Name). Covered by `test/visual/core/accessible-names.spec.js`, which
|
|
874
|
+
asserts at **mobile** width — the same assertions pass at desktop on the
|
|
875
|
+
unfixed code, which is why the bug survived the existing suite.
|
|
876
|
+
- **Search results were never announced to screen readers (#279)** — the modal
|
|
877
|
+
rewrites its results container without moving focus, so assistive tech had no
|
|
878
|
+
signal that anything had happened. A visually-hidden `role="status"`
|
|
879
|
+
`aria-live="polite"` region now announces a concise count ("7 results for
|
|
880
|
+
jekyll", or "12 of 40 results…" when the list is truncated), and "No results
|
|
881
|
+
found." The **results list itself is deliberately not live**: announcing eight
|
|
882
|
+
titles and their snippets on every keystroke would be unusable. `aria-atomic`
|
|
883
|
+
keeps the summary a single message, and a repeated string is cleared first so an
|
|
884
|
+
unchanged announcement is not swallowed as a no-op.
|
|
885
|
+
|
|
886
|
+
- **`<meta charset>` shipped ~26 KB into the document, past the spec's 1024-byte
|
|
887
|
+
window (#372)** — the HTML spec only scans the first 1024 bytes for the
|
|
888
|
+
character-encoding declaration. The tag sat in `_includes/core/head.html` below
|
|
889
|
+
Google Tag Manager, console-capture, page-views, mermaid and nanobar — and below
|
|
890
|
+
two multi-line HTML doc banners that shipped verbatim to production — so a
|
|
891
|
+
served page declared its encoding at byte **26058**. Past the limit a browser
|
|
892
|
+
stops looking and decodes with its locale default, mangling every non-ASCII byte
|
|
893
|
+
until it re-parses; this theme ships UTF-8 throughout (`fr/**` translations,
|
|
894
|
+
em-dashes, curly quotes), so that is a live corruption risk rather than a lint
|
|
895
|
+
nit. The tag now sits at the very top of `_layouts/root.html` — measured at byte
|
|
896
|
+
**318**, verified across all 400 built pages — and `root.html`'s own banner is a
|
|
897
|
+
Liquid comment (stripped at build) rather than an HTML one, which is what freed
|
|
898
|
+
the budget. Regression coverage in `test/visual/core/head-contract.spec.js`
|
|
899
|
+
measures the **served bytes**, since a source-order assertion would have passed
|
|
900
|
+
both before and after.
|
|
901
|
+
- **The Atom feed was undiscoverable (#371)** — `jekyll-feed` generates
|
|
902
|
+
`/feed.xml` but emits no autodiscovery `<link>` unless `feed_meta` is called,
|
|
903
|
+
which the theme never did, so readers and crawlers could not find the feed even
|
|
904
|
+
though the footer linked it. `<head>` now carries
|
|
905
|
+
`<link rel="alternate" type="application/atom+xml">`, honouring `site.feed.path`.
|
|
906
|
+
Written out directly rather than via `feed_meta` so a consumer site that has not
|
|
907
|
+
enabled the plugin does not hard-fail with `Unknown tag`. A test asserts the
|
|
908
|
+
advertised URL actually returns a feed — a discoverable link to a 404 would be
|
|
909
|
+
worse than none.
|
|
910
|
+
|
|
199
911
|
- **Footer "Info" and "Cookie Preferences" are buttons, not links (#320)** —
|
|
200
912
|
both controls open in-page UI (the `#info-section` offcanvas and the
|
|
201
913
|
`#cookieSettingsModal` modal) but shipped as `<a href="#" data-bs-toggle>`, so
|
|
@@ -231,7 +943,8 @@ file. Only `## [Unreleased]` describes work that has not shipped yet.
|
|
|
231
943
|
`html.no-js .foo {…}` / `html.js .foo {…}` got the no-JS branch for everyone
|
|
232
944
|
— the class advertised a capability the theme did not deliver. An inline
|
|
233
945
|
script is now the **first child of `<head>`**, above
|
|
234
|
-
`{% include core/head.html %}` (which opens with Google Tag
|
|
946
|
+
{% raw %}`{% include core/head.html %}`{% endraw %} (which opens with Google Tag
|
|
947
|
+
Manager, so
|
|
235
948
|
"first in that include" is not "first in `<head>`") and deliberately neither
|
|
236
949
|
`defer` nor `async`, since it has to run before the first stylesheet is
|
|
237
950
|
fetched or `html.js` rules would flash. The served HTML still carries
|