jekyll-theme-zer0 1.17.0 → 1.17.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: addc113b917ada3a80a134299c5a37cf05109133f628a4195fac8fdbb402102d
4
- data.tar.gz: b07960a50b9812125d083310ba4afbf8f4d670d2fe69e901b4e3fca33010260a
3
+ metadata.gz: 50907837fd27d28fa1bea1cf163d7e2d71a4a848266c861656b8d8ebdb2e3d78
4
+ data.tar.gz: e3853573f9cca38ec51ce145a8279aedf221035afac88cf9cfc77d220dee23e5
5
5
  SHA512:
6
- metadata.gz: 0a37703e0433fc6bdceb50b35fdcfe37157d9b863bc62eff91190948881eb4c092476376d4c76b5807b0faec54334ebf3028249f004df7d4bbe3ce72019f2af3
7
- data.tar.gz: 2a57166a4b747c174111c778822d65e0fcc4a955480add45118fb449f54ca37a113ca2995490d7e90e4bdb0028c227ea5377a6447b735cbe1d8784d936edcb54
6
+ metadata.gz: b1dcd28c12fd2c48e14463bf4e7db63ed3c024262ca3bc04ba98a9ce8b3b53affef695846681fee898584184a58790a86361e00940d89a0ecbe76e0dddb54496
7
+ data.tar.gz: e28d9a6a97edefc45d7b772e363a1826b3a96e00de15e58e90be84cae1c7fa2ad23a24198769ea4f13cdbeac138cbf4f02fd75356d8066e8cb5c69ae9b40558a
data/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.17.1] - 2026-06-13
9
+
10
+ ### Changed
11
+ - Version bump: patch release
12
+
13
+ ### Commits in this release
14
+ - f2657b68 fix(a11y): resolve all navbar & site WCAG 2.1 AA violations (T-007) (#149)
15
+ - 30d836cb fix(admin): sync config-page copy with live _config.yml and redact the Raw tab (T-018) (#148)
16
+
17
+ ### Fixed
18
+ - **Navbar & site accessibility (T-007)**: resolved all WCAG 2.1 AA violations that kept three axe-core audits frozen — dropped the redundant ARIA `menubar`/`menuitem` roles from the nav (the nav landmark already provides semantics; menubars require menuitem children the search/settings buttons weren't), added an `aria-label` to the site-subtitle home link, kept the admin/footer separator a list item, gave the theme-preview disabled tab a `role="tab"` and an icon-only button an `aria-label`, made code blocks a single keyboard-focusable scroll region, and underlined prose links so they're distinguishable without color. The three `test.fixme` blocks in `test/visual/accessibility.spec.js` are now live `test()` calls — verified 0 violations across the homepage, FAQ, and all 8 admin pages (23/23 a11y, 223/223 smoke tier)
19
+
8
20
  ## [1.17.0] - 2026-06-12
9
21
 
10
22
  ### Changed
@@ -13,6 +25,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
13
25
  ### Commits in this release
14
26
  - ac36e1a3 feat(tests): plugin unit specs and coverage baseline — T-011, T-005 (#145)
15
27
 
28
+ ### Fixed
29
+ - **Admin config page sync (T-018)**: the page's config copy is now byte-synced with the live `_config.yml` (raw-wrapped so Liquid-looking comment text renders literally) and `validate` fails on drift; the **visible Raw-YAML tab** now applies the same sensitive-line redaction as the hidden copy element (it previously showed the raw file — the stale copy was the only thing keeping the live PostHog key off that tab); the raw-tab security test targets the real `code#cfg-raw-yaml` element and asserts presence instead of silently skipping
30
+
16
31
  ### Added
17
32
  - **Plugin unit specs (T-011)**: 19 Minitest specs for the previously-untested `preview_image_generator.rb`, `content_statistics_generator.rb`, and `admin_page_urls.rb` plugins (config merge, path normalization, index dedupe by relative path, hook output, edge cases); wired into the core suite as "Plugin Unit Specs"
18
33
  - **Coverage baseline (T-005)**: structural survey recorded at `docs/development/coverage-baseline.md` — 10/10 suites green; the two remaining zero-coverage subsystems filed as T-019 (migrate.sh + theme_version.rb) and T-020 (installer wizard/upgrade)
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  title: zer0-mistakes
3
3
  sub-title: AI-Native Jekyll Theme
4
4
  description: AI-native Jekyll theme for GitHub Pages — Docker-first development, AI-powered installation, multi-agent integration (Copilot, Codex, Cursor, Claude), AI preview-image generation, and AIEO content optimization with Bootstrap 5.3.
5
- version: 1.17.0
5
+ version: 1.17.1
6
6
  layout: landing
7
7
  tags:
8
8
  - jekyll
@@ -20,7 +20,7 @@ categories:
20
20
  - bootstrap
21
21
  - ai-tooling
22
22
  created: 2024-02-10T23:51:11.480Z
23
- lastmod: 2026-06-12T20:08:02.000Z
23
+ lastmod: 2026-06-13T04:17:52.000Z
24
24
  draft: false
25
25
  permalink: /
26
26
  slug: zer0
@@ -911,7 +911,7 @@ git push origin feature/awesome-feature
911
911
 
912
912
  | Metric | Value |
913
913
  |--------|-------|
914
- | **Current Version** | 1.17.0 ([RubyGems](https://rubygems.org/gems/jekyll-theme-zer0), [CHANGELOG](/CHANGELOG)) |
914
+ | **Current Version** | 1.17.1 ([RubyGems](https://rubygems.org/gems/jekyll-theme-zer0), [CHANGELOG](/CHANGELOG)) |
915
915
  | **Documented Features** | 43 ([Feature Registry](https://github.com/bamr87/zer0-mistakes/blob/main/_data/features.yml)) |
916
916
  | **Setup Time** | 2-5 minutes ([install.sh benchmarks](https://github.com/bamr87/zer0-mistakes/blob/main/install.sh)) |
917
917
  | **Documentation Pages** | 70+ ([browse docs](https://zer0-mistakes.com/pages/)) |
@@ -966,6 +966,6 @@ And these AI partners that make zer0-mistakes truly AI-native:
966
966
 
967
967
  **Built with ❤️ — and a little help from our AI partners — for the Jekyll community**
968
968
 
969
- **v1.17.0** • [Changelog](CHANGELOG.md) • [License](LICENSE) • [Contributing](CONTRIBUTING.md) • [AI Agent Guide](AGENTS.md)
969
+ **v1.17.1** • [Changelog](CHANGELOG.md) • [License](LICENSE) • [Contributing](CONTRIBUTING.md) • [AI Agent Guide](AGENTS.md)
970
970
 
971
971
 
data/_data/backlog.yml CHANGED
@@ -182,7 +182,7 @@ tasks:
182
182
 
183
183
  - id: T-007
184
184
  title: "Fix global navbar WCAG 2.1 AA violations to enable axe-core full-audit tests"
185
- status: open
185
+ status: done
186
186
  priority: P1
187
187
  area: a11y
188
188
  risk: standard
@@ -194,6 +194,14 @@ tasks:
194
194
  `aria-required-children` (menubar/button nesting), `link-name` (icon-only nav links),
195
195
  `list` (footer list structure), and `scrollable-region-focusable` (code blocks).
196
196
  Fix the HTML/ARIA in `_includes/` and `_layouts/` to make the full axe-core audit green.
197
+ Done 2026-06-13: all WCAG 2.1 AA violations resolved and verified with a
198
+ live axe-core run plus the unfrozen Playwright a11y spec (23/23 pass,
199
+ 223/223 smoke tier). The actual violations differed from the stale TODO:
200
+ dropped redundant ARIA menubar/menuitem roles (navbar.html), aria-label
201
+ on the site-subtitle home link, listitem-preserving admin separator,
202
+ role="tab" on the disabled preview tab + aria-label on an icon-only
203
+ button, single focusable scroll region for code blocks (code-copy.js +
204
+ .scss), and underlined prose links across content wrappers.
197
205
  acceptance:
198
206
  - "All four violations listed in the `test.fixme` comment are resolved in `_includes/` / `_layouts/`."
199
207
  - "The three `test.fixme` blocks in `test/visual/accessibility.spec.js` are changed to live `test()` calls and pass in CI."
@@ -201,7 +209,7 @@ tasks:
201
209
  - "No visual regression in smoke snapshot tests."
202
210
  links: { issue: null, pr: null, roadmap: "1.9" }
203
211
  created: 2026-06-01
204
- updated: 2026-06-01
212
+ updated: 2026-06-13
205
213
 
206
214
  - id: T-008
207
215
  title: "Fix theme-customizer YAML export to quote hex color values"
@@ -465,7 +473,7 @@ tasks:
465
473
 
466
474
  - id: T-018
467
475
  title: "Admin config page displays a stale copy of _config.yml — keep it in sync safely"
468
- status: open
476
+ status: done
469
477
  priority: P2
470
478
  area: feat
471
479
  risk: standard
@@ -479,13 +487,20 @@ tasks:
479
487
  refresh would inject the live `phc_` api_key into the *visible* Raw-YAML
480
488
  tab — any sync mechanism must sanitize the visible tab the same way
481
489
  T-009 sanitizes the hidden copy element, or redact at sync time.
490
+ Done 2026-06-12: copy synced (wrapped in Liquid raw markers so config
491
+ comments mentioning Liquid render literally); scripts/bin/validate
492
+ gains a drift check that fails when the copy diverges; the visible
493
+ Raw-YAML tab now renders the same sanitized capture as the hidden
494
+ element (live phc_ key verified redacted in both); the raw-tab
495
+ Playwright test targets code#cfg-raw-yaml and asserts presence
496
+ instead of silently skipping.
482
497
  acceptance:
483
498
  - "The config shown at /about/config/ matches the live `_config.yml` (automated sync step or build-time check that fails on drift)."
484
499
  - "The visible Raw-YAML tab applies the same sensitive-line redaction as the hidden `cfg-full-yaml` element."
485
500
  - "`test/visual/security.spec.js` raw-tab test passes without its silent skip path (locator matches the actual `code#cfg-raw-yaml` element)."
486
501
  links: { issue: null, pr: null, roadmap: "1.13" }
487
502
  created: 2026-06-11
488
- updated: 2026-06-11
503
+ updated: 2026-06-12
489
504
 
490
505
  - id: T-019
491
506
  title: "Unit tests for migrate.sh and theme_version.rb (coverage rank #1)"
@@ -146,7 +146,7 @@
146
146
  <button class="nav-link" type="button" role="tab" aria-selected="false">Hover me</button>
147
147
  </li>
148
148
  <li class="nav-item" role="presentation">
149
- <button class="nav-link disabled" type="button" disabled>Disabled</button>
149
+ <button class="nav-link disabled" type="button" role="tab" aria-selected="false" disabled>Disabled</button>
150
150
  </li>
151
151
  </ul>
152
152
  </section>
@@ -528,7 +528,7 @@ docker-compose up
528
528
  <div class="d-flex flex-wrap gap-3 mt-3">
529
529
  <span class="badge bg-primary p-2"><i class="bi bi-lightning-charge me-1"></i>Badge icon</span>
530
530
  <button class="btn btn-outline-primary btn-sm"><i class="bi bi-download me-1"></i>Icon button</button>
531
- <button class="btn btn-primary btn-sm"><i class="bi bi-arrow-right"></i></button>
531
+ <button class="btn btn-primary btn-sm" aria-label="Continue"><i class="bi bi-arrow-right" aria-hidden="true"></i></button>
532
532
  </div>
533
533
  </div>
534
534
  </div>
@@ -44,11 +44,12 @@
44
44
  </div>
45
45
 
46
46
  <!-- If a subtitle exists -->
47
- {%- if site.subtitle -%}
47
+ {%- assign _subtitle = site.subtitle | strip -%}
48
+ {%- if _subtitle != "" -%}
48
49
  <div class="navbar-brand site-subtitle d-none d-lg-inline">
49
- <a class="nav-link" href="{{ '/' | relative_url }}">
50
+ <a class="nav-link" href="{{ '/' | relative_url }}" aria-label="{{ _subtitle }} — {{ site.title }} home">
50
51
  <span class="site-subtitle-text">
51
- {{ site.subtitle }}
52
+ {{ _subtitle }}
52
53
  </span>
53
54
  </a>
54
55
  </div>
@@ -26,7 +26,7 @@
26
26
 
27
27
  {% if item.external and rendered_separator == false %}
28
28
  {% assign rendered_separator = true %}
29
- <li role="separator"><hr class="my-2"></li>
29
+ <li class="nav-item" aria-hidden="true"><hr class="my-2"></li>
30
30
  {% endif %}
31
31
 
32
32
  {% comment %} Active detection: exact URL match or admin_section match {% endcomment %}
@@ -19,9 +19,9 @@
19
19
  <div class="offcanvas-body">
20
20
  <!-- Desktop: width is the grid middle track; labels ellipsis or icon-only via container query -->
21
21
  <div class="bd-navbar-nav-viewport">
22
- <ul class="navbar-nav justify-content-lg-center text-start flex-grow-1" role="menubar">
23
- <li class="nav-item d-lg-none" role="none">
24
- <a class="nav-link" href="{{ '/' | relative_url }}" role="menuitem" {% if page.url == '/' %}aria-current="page"{% endif %}>
22
+ <ul class="navbar-nav justify-content-lg-center text-start flex-grow-1">
23
+ <li class="nav-item d-lg-none">
24
+ <a class="nav-link" href="{{ '/' | relative_url }}" {% if page.url == '/' %}aria-current="page"{% endif %}>
25
25
  <i class="{{site.default_icon}} bi-house" aria-hidden="true"></i>
26
26
  Home
27
27
  </a>
@@ -41,12 +41,12 @@
41
41
  {%- assign items_remaining = nav_main.size | minus: forloop.index -%}
42
42
 
43
43
  {%- if has_children -%}
44
- <li class="nav-item dropdown d-flex align-items-center nav-hover-dropdown" role="none">
44
+ <li class="nav-item dropdown d-flex align-items-center nav-hover-dropdown">
45
45
  <!-- Parent link navigates directly -->
46
46
  <a
47
47
  class="nav-link"
48
48
  href="{{ link.url | relative_url }}"
49
- role="menuitem"
49
+ aria-label="{{ link.title }}"
50
50
  {%- if link.url == page.url -%} aria-current="page"{%- endif -%}
51
51
  title="{{ link.title }}"
52
52
  >
@@ -70,13 +70,13 @@
70
70
  <span class="visually-hidden">Toggle {{ link.title }} submenu</span>
71
71
  </button>
72
72
 
73
- <ul class="dropdown-menu {% if items_remaining < 2 %}dropdown-menu-end{% else %}dropdown-menu-start{% endif %}" aria-labelledby="dropdown-{{ link.title | slugify }}" role="menu">
73
+ <ul class="dropdown-menu {% if items_remaining < 2 %}dropdown-menu-end{% else %}dropdown-menu-start{% endif %}" aria-labelledby="dropdown-{{ link.title | slugify }}">
74
74
  {%- for child in link.children -%}
75
- <li role="none">
75
+ <li>
76
76
  <a
77
77
  class="dropdown-item"
78
78
  href="{{ child.url | relative_url }}"
79
- role="menuitem"
79
+
80
80
  {%- if child.url == page.url -%} aria-current="page"{%- endif -%}
81
81
  >
82
82
  {%- if child.icon -%}
@@ -89,11 +89,11 @@
89
89
  </ul>
90
90
  </li>
91
91
  {%- else -%}
92
- <li class="nav-item" role="none">
92
+ <li class="nav-item">
93
93
  <a
94
94
  class="nav-link"
95
95
  href="{{ link.url | relative_url }}"
96
- role="menuitem"
96
+ aria-label="{{ link.title }}"
97
97
  {%- if link.url == page.url -%} aria-current="page"{%- endif -%}
98
98
  title="{{ link.title }}"
99
99
  >
@@ -117,11 +117,11 @@
117
117
  {%- if collection.docs.size > 0 -%}
118
118
  {%- assign col_title = collection.label | replace: "-", " " | replace: "_", " " | capitalize -%}
119
119
  {%- assign col_url = "/" | append: collection.label | append: "/" -%}
120
- <li class="nav-item" role="none">
120
+ <li class="nav-item">
121
121
  <a
122
122
  class="nav-link"
123
123
  href="{{ col_url | relative_url }}"
124
- role="menuitem"
124
+ aria-label="{{ col_title }}"
125
125
  title="{{ col_title }}"
126
126
  {%- if page.collection == collection.label -%} aria-current="page"{%- endif -%}
127
127
  >
@@ -133,7 +133,7 @@
133
133
  {%- endfor -%}
134
134
 
135
135
  {%- endif -%}
136
- <li class="nav-item d-lg-none" role="none">
136
+ <li class="nav-item d-lg-none">
137
137
  <button
138
138
  class="nav-link btn btn-link text-start w-100"
139
139
  type="button"
@@ -145,7 +145,7 @@
145
145
  Search
146
146
  </button>
147
147
  </li>
148
- <li class="nav-item d-lg-none" role="none">
148
+ <li class="nav-item d-lg-none">
149
149
  <button
150
150
  class="nav-link btn btn-link text-start w-100"
151
151
  type="button"
@@ -641,6 +641,17 @@ a.bd-intro-badge--tag:focus {
641
641
  min-width: 1px
642
642
  }
643
643
 
644
+ /* a11y (WCAG 1.4.1 link-in-text-block): prose links must be distinguishable
645
+ without relying on color. Underline inline content links; exclude links
646
+ that already carry their own non-color affordance (buttons, badges, nav
647
+ pills/tabs, card and icon links). */
648
+ .bd-content :is(p, li, blockquote, td, dd) a:not(.btn):not(.badge):not(.nav-link):not(.dropdown-item):not(.page-link):not(.card-link):not([class*="text-decoration"]),
649
+ .post-content :is(p, li, blockquote, td, dd) a:not(.btn):not(.badge):not(.nav-link):not(.dropdown-item):not(.page-link):not(.card-link):not([class*="text-decoration"]),
650
+ .landing-content-body :is(p, li, blockquote, td, dd) a:not(.btn):not(.badge):not(.nav-link):not(.dropdown-item):not(.page-link):not(.card-link):not([class*="text-decoration"]),
651
+ #admin-content :is(p, li, blockquote, td, dd, .alert) a:not(.btn):not(.badge):not(.nav-link):not(.dropdown-item):not(.page-link):not(.card-link):not(.alert-link):not([class*="text-decoration"]) {
652
+ text-decoration: underline;
653
+ }
654
+
644
655
  @media (min-width: 992px) {
645
656
  .bd-toc {
646
657
  position:-webkit-sticky;
@@ -89,7 +89,7 @@
89
89
  display: block;
90
90
  padding: 0;
91
91
  border: 0;
92
- overflow-x: auto;
92
+ // overflow on the parent <pre> only (single focusable scroll region, WCAG 2.1.1)
93
93
  white-space: pre;
94
94
  font-family: var(--zer0-font-mono);
95
95
  font-size: 0.8125rem;
@@ -254,7 +254,7 @@ pre.highlight,
254
254
  border: 0;
255
255
  padding: 0;
256
256
  padding-right: calc(var(--zer0-space-3) + var(--zer0-code-copy-width));
257
- overflow-x: auto;
257
+ // overflow on the parent <pre> only (single focusable scroll region, WCAG 2.1.1)
258
258
  white-space: pre;
259
259
  background-color: transparent;
260
260
  font-family: var(--zer0-font-mono);
@@ -108,6 +108,17 @@ document.addEventListener('DOMContentLoaded', function () {
108
108
 
109
109
  ensureLineNumbers(preElement, codeElement);
110
110
 
111
+ // a11y (WCAG 2.1.1 scrollable-region-focusable): a code block that can
112
+ // scroll horizontally must be reachable by keyboard. Make every <pre> a
113
+ // focusable region with a discernible name.
114
+ if (!preElement.hasAttribute('tabindex')) {
115
+ preElement.setAttribute('tabindex', '0');
116
+ if (!preElement.hasAttribute('role')) preElement.setAttribute('role', 'region');
117
+ if (!preElement.hasAttribute('aria-label')) {
118
+ preElement.setAttribute('aria-label', (getLanguageLabel(preElement) || 'Code') + ' code block');
119
+ }
120
+ }
121
+
111
122
  if (preElement.querySelector('.copy')) return;
112
123
 
113
124
  var rougeWrapper = preElement.closest('.highlighter-rouge > .highlight');
data/scripts/bin/validate CHANGED
@@ -404,6 +404,23 @@ classified_files = %w[
404
404
  pages/_about/settings/_config.yml
405
405
  .github/ISSUE_TEMPLATE/config.yml
406
406
  ]
407
+
408
+ # T-018: the admin config page renders pages/_about/settings/_config.yml via
409
+ # include_relative (Liquid cannot reach the repo root). Fail on drift so the
410
+ # page never shows a stale copy; refresh with:
411
+ # cp _config.yml pages/_about/settings/_config.yml
412
+ settings_copy = 'pages/_about/settings/_config.yml'
413
+ if File.file?(settings_copy)
414
+ live = File.read('_config.yml', encoding: 'UTF-8')
415
+ copy_lines = File.read(settings_copy, encoding: 'UTF-8').lines
416
+ # The copy is wrapped in {% raw %}/{% endraw %} so include_relative renders
417
+ # the config text literally (the live file mentions Liquid tags in comments).
418
+ refresh = "{ echo '{% raw %}'; cat _config.yml; echo '{% endraw %}'; } > #{settings_copy}"
419
+ assert(copy_lines.first.to_s.strip == '{% raw %}' && copy_lines.last.to_s.strip == '{% endraw %}',
420
+ "#{settings_copy} must be wrapped in raw/endraw; refresh it: #{refresh}")
421
+ assert(copy_lines[1..-2].join == live,
422
+ "#{settings_copy} has drifted from _config.yml; refresh it: #{refresh}")
423
+ end
407
424
  classified_files.concat(Dir.glob('_data/**/*config*.{yml,yaml}'))
408
425
 
409
426
  unclassified_files = config_like_files.sort - classified_files.select { |file| File.file?(file) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-theme-zer0
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.17.0
4
+ version: 1.17.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amr Abdel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-12 00:00:00.000000000 Z
11
+ date: 2026-06-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll