jekyll-theme-zer0 1.23.0 → 1.25.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 (149) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +110 -0
  3. data/README.md +3 -3
  4. data/_data/authors.yml +1 -0
  5. data/_data/features.yml +882 -34
  6. data/_data/navigation/README.md +15 -5
  7. data/_data/navigation/main.yml +0 -8
  8. data/_data/roadmap.yml +1 -0
  9. data/_includes/README.md +10 -7
  10. data/_includes/analytics/google-analytics.html +1 -0
  11. data/_includes/analytics/google-tag-manager-body.html +1 -0
  12. data/_includes/analytics/google-tag-manager-head.html +1 -0
  13. data/_includes/analytics/posthog.html +1 -0
  14. data/_includes/components/README.md +2 -1
  15. data/_includes/components/admin-links.html +63 -0
  16. data/_includes/components/admin-tabs.html +1 -0
  17. data/_includes/components/ai-chat.html +27 -5
  18. data/_includes/components/author-eeat.html +1 -0
  19. data/_includes/components/background-customizer.html +1 -0
  20. data/_includes/components/background-settings.html +36 -37
  21. data/_includes/components/component-showcase.html +1 -0
  22. data/_includes/components/cookie-consent.html +1 -0
  23. data/_includes/components/dev-shortcuts.html +1 -8
  24. data/_includes/components/env-switcher.html +90 -138
  25. data/_includes/components/feature-card.html +16 -1
  26. data/_includes/components/halfmoon.html +22 -41
  27. data/_includes/components/info-section.html +127 -130
  28. data/_includes/components/mermaid.html +1 -0
  29. data/_includes/components/nanobar.html +1 -0
  30. data/_includes/components/post-type-badge.html +1 -0
  31. data/_includes/components/preview-image.html +1 -0
  32. data/_includes/components/search-modal.html +1 -0
  33. data/_includes/components/searchbar.html +1 -0
  34. data/_includes/components/shortcuts-modal.html +1 -0
  35. data/_includes/components/theme-info.html +21 -30
  36. data/_includes/components/theme-preview-gallery.html +1 -0
  37. data/_includes/components/zer0-env-var.html +7 -1
  38. data/_includes/content/backlinks.html +1 -0
  39. data/_includes/content/giscus.html +1 -0
  40. data/_includes/content/jsonld-faq.html +1 -0
  41. data/_includes/content/seo.html +1 -0
  42. data/_includes/content/sitemap.html +1 -0
  43. data/_includes/content/toc.html +1 -0
  44. data/_includes/core/color-mode-init.html +1 -0
  45. data/_includes/core/footer.html +1 -0
  46. data/_includes/core/head.html +1 -0
  47. data/_includes/core/header.html +5 -15
  48. data/_includes/navigation/admin-nav.html +1 -0
  49. data/_includes/navigation/breadcrumbs.html +1 -0
  50. data/_includes/navigation/local-graph-fab.html +3 -9
  51. data/_includes/navigation/local-graph.html +4 -9
  52. data/_includes/navigation/navbar.html +1 -0
  53. data/_includes/navigation/sidebar-categories.html +56 -14
  54. data/_includes/navigation/sidebar-config.html +115 -0
  55. data/_includes/navigation/sidebar-folders.html +168 -62
  56. data/_includes/navigation/sidebar-left.html +29 -52
  57. data/_includes/navigation/sidebar-nav.html +30 -0
  58. data/_includes/navigation/sidebar-right.html +1 -0
  59. data/_includes/navigation/toc-fab.html +3 -9
  60. data/_includes/navigation/unified-drawer.html +7 -14
  61. data/_includes/obsidian/full-graph.html +1 -0
  62. data/_includes/setup/wizard.html +1 -0
  63. data/_includes/stats/stats-categories.html +1 -0
  64. data/_includes/stats/stats-header.html +1 -0
  65. data/_includes/stats/stats-metrics.html +1 -0
  66. data/_includes/stats/stats-overview.html +1 -0
  67. data/_includes/stats/stats-tags.html +1 -0
  68. data/_layouts/admin.html +1 -0
  69. data/_layouts/article.html +1 -0
  70. data/_layouts/author.html +1 -0
  71. data/_layouts/authors.html +1 -0
  72. data/_layouts/collection.html +1 -0
  73. data/_layouts/default.html +14 -37
  74. data/_layouts/home.html +1 -0
  75. data/_layouts/index.html +1 -0
  76. data/_layouts/landing.html +1 -0
  77. data/_layouts/news.html +1 -0
  78. data/_layouts/note.html +1 -0
  79. data/_layouts/notebook.html +1 -0
  80. data/_layouts/root.html +3 -2
  81. data/_layouts/search.html +1 -0
  82. data/_layouts/section.html +1 -0
  83. data/_layouts/setup.html +1 -0
  84. data/_layouts/sitemap-collection.html +1 -0
  85. data/_layouts/stats.html +1 -0
  86. data/_layouts/tag.html +1 -0
  87. data/_layouts/welcome.html +1 -0
  88. data/_plugins/author_pages_generator.rb +1 -0
  89. data/_plugins/obsidian_links.rb +1 -0
  90. data/_plugins/preview_image_generator.rb +1 -0
  91. data/_plugins/search_and_sitemap_generator.rb +1 -0
  92. data/_plugins/theme_version.rb +1 -0
  93. data/_sass/components/_author.scss +12 -1
  94. data/_sass/components/_footer.scss +22 -0
  95. data/_sass/components/_ui-enhancements.scss +9 -0
  96. data/_sass/core/_docs-layout.scss +1 -0
  97. data/_sass/core/_navbar.scss +10 -27
  98. data/_sass/core/_obsidian.scss +2 -2
  99. data/_sass/core/code-copy.scss +10 -0
  100. data/_sass/custom.scss +1 -0
  101. data/_sass/notebooks.scss +1 -0
  102. data/_sass/theme/_backgrounds.scss +7 -2
  103. data/_sass/utilities/_layout.scss +11 -0
  104. data/assets/css/main.scss +2 -1
  105. data/assets/js/ai-chat.js +1 -0
  106. data/assets/js/author-profile.js +1 -0
  107. data/assets/js/auto-hide-nav.js +1 -0
  108. data/assets/js/back-to-top.js +1 -0
  109. data/assets/js/background-customizer.js +1 -0
  110. data/assets/js/code-copy.js +1 -0
  111. data/assets/js/halfmoon.js +23 -22
  112. data/assets/js/modules/navigation/gestures.js +1 -0
  113. data/assets/js/modules/navigation/keyboard.js +1 -0
  114. data/assets/js/modules/navigation/scroll-spy.js +1 -0
  115. data/assets/js/modules/navigation/sidebar-state.js +1 -0
  116. data/assets/js/modules/navigation/sidebar-visibility.js +1 -0
  117. data/assets/js/modules/navigation/smooth-scroll.js +1 -0
  118. data/assets/js/modules/theme/appearance.js +52 -16
  119. data/assets/js/nanobar-init.js +1 -0
  120. data/assets/js/navigation.js +1 -0
  121. data/assets/js/obsidian-graph.js +1 -0
  122. data/assets/js/obsidian-wiki-links.js +1 -0
  123. data/assets/js/palette-generator.js +1 -0
  124. data/assets/js/particles-source.js +1 -0
  125. data/assets/js/search-modal.js +1 -0
  126. data/assets/js/setup-wizard.js +1 -0
  127. data/assets/js/share-actions.js +1 -0
  128. data/assets/js/skin-editor.js +1 -0
  129. data/assets/js/table-copy.js +1 -0
  130. data/assets/js/theme-customizer.js +1 -0
  131. data/scripts/analyze-commits.sh +1 -0
  132. data/scripts/bin/giscus-discussions +1 -0
  133. data/scripts/bin/install +1 -0
  134. data/scripts/bin/validate +18 -1
  135. data/scripts/build +1 -0
  136. data/scripts/content-review.rb +1 -0
  137. data/scripts/convert-notebooks.sh +4 -3
  138. data/scripts/fix-markdown-format.sh +1 -0
  139. data/scripts/generate-preview-images.sh +1 -0
  140. data/scripts/generate-roadmap.sh +1 -0
  141. data/scripts/install-preview-generator.sh +1 -0
  142. data/scripts/release +1 -0
  143. data/scripts/requirements.txt +11 -0
  144. data/scripts/setup.sh +1 -0
  145. data/scripts/tag-features +136 -0
  146. data/scripts/validate-features.rb +149 -0
  147. data/scripts/vendor-install.sh +1 -0
  148. metadata +9 -3
  149. data/_plugins/admin_page_urls.rb +0 -16
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # ---------------------------------------------------------------------------
5
+ # validate-features.rb — canonical integrity checker for the feature registry.
6
+ #
7
+ # Single source of truth shared by `scripts/bin/validate` (preflight) and
8
+ # `test/test_features.sh` (the `features` test suite). Governance:
9
+ # .github/instructions/features.instructions.md.
10
+ #
11
+ # HARD failures (exit 1):
12
+ # - features/features.yml and _data/features.yml are not byte-identical
13
+ # - the header is missing a `# Last Updated:` line
14
+ # - schema violations (missing required field, bad id/version, duplicate id)
15
+ # - implemented:false without `removed_in:`
16
+ # - a reference path on an ACTIVE feature does not exist in the repo
17
+ # - missing/malformed `provenance:` block on an active feature (since PR B)
18
+ # - missing/unresolvable `tests:` linkage on an active feature (since PR C):
19
+ # every entry must be a real test path or `{na: reason}`
20
+ #
21
+ # WARNINGS (non-fatal unless FEATURES_STRICT=1):
22
+ # - header `# Version:` not tracking the gem version
23
+ # - id gaps (IDs are never reused, but gaps are worth a human glance)
24
+ #
25
+ # Portable across Ruby 2.6 (macOS system) and 3.x (CI/Docker).
26
+ # ---------------------------------------------------------------------------
27
+
28
+ require 'date'
29
+ require 'yaml'
30
+
31
+ MASTER = 'features/features.yml'
32
+ MIRROR = '_data/features.yml'
33
+ REQUIRED = %w[id title description implemented version link docs tags date].freeze
34
+ STRICT = ENV['FEATURES_STRICT'] == '1'
35
+
36
+ def die(msg)
37
+ warn " \e[31m✗\e[0m #{msg}"
38
+ exit 1
39
+ end
40
+
41
+ # Ruby 3.1+ defaults YAML.load_file to safe loading (rejects Date); 2.6 does
42
+ # not accept the keywords. Try the strict form, fall back for old Ruby.
43
+ def load_yaml(path)
44
+ YAML.load_file(path, permitted_classes: [Date, Time], aliases: true)
45
+ rescue ArgumentError
46
+ YAML.load_file(path)
47
+ end
48
+
49
+ def ref_paths(refs)
50
+ return [] unless refs.is_a?(Hash)
51
+
52
+ refs.values.flat_map { |v| v.is_a?(Array) ? v : [v] }.select { |v| v.is_a?(String) }
53
+ end
54
+
55
+ [MASTER, MIRROR].each { |f| die "#{f} is missing" unless File.file?(f) }
56
+
57
+ # 1. Sync contract — the two registries must be byte-identical.
58
+ if File.binread(MASTER) != File.binread(MIRROR)
59
+ die "#{MASTER} and #{MIRROR} differ — run `cp #{MASTER} #{MIRROR}` (must be byte-identical)"
60
+ end
61
+
62
+ data = load_yaml(MIRROR)
63
+ feats = data && data['features']
64
+ die 'top-level `features:` list missing' unless feats.is_a?(Array)
65
+
66
+ warnings = []
67
+
68
+ # Header sanity.
69
+ header = File.foreach(MIRROR).first(8).join
70
+ gem_version = File.read('lib/jekyll-theme-zer0/version.rb')[/VERSION\s*=\s*"([^"]+)"/, 1]
71
+ warnings << "header `# Version:` should match gem version #{gem_version}" unless header.include?("# Version: #{gem_version}")
72
+ die 'header missing `# Last Updated: YYYY-MM-DD` line' unless header =~ /# Last Updated: \d{4}-\d{2}-\d{2}/
73
+
74
+ seen = {}
75
+ feats.each_with_index do |f, i|
76
+ die "entry ##{i} is not a mapping" unless f.is_a?(Hash)
77
+ id = f['id'] || "(index #{i})"
78
+
79
+ REQUIRED.each do |key|
80
+ val = f[key]
81
+ die "#{id}: required field `#{key}` is missing/empty" if val.nil? || (val.respond_to?(:empty?) && val.empty?)
82
+ end
83
+
84
+ # Title/description are rendered UNwrapped into HTML on /features/; a raw
85
+ # angle bracket (e.g. "/authors/<key>/") becomes a stray tag that swallows
86
+ # every following card. Keep them plain prose.
87
+ %w[title description].each do |key|
88
+ die "#{id}: `#{key}` must not contain raw `<`/`>` (breaks HTML on /features/ — use :key or &lt;)" if f[key].to_s =~ /[<>]/
89
+ end
90
+
91
+ die "#{id}: id must match ZER0-NNN" unless f['id'] =~ /\AZER0-\d{3}\z/
92
+ die "#{id}: duplicate id" if seen[f['id']]
93
+ seen[f['id']] = true
94
+ die "#{id}: version must be X.Y.Z" unless f['version'].to_s =~ /\A\d+\.\d+\.\d+/
95
+ die "#{id}: tags must be a non-empty list" unless f['tags'].is_a?(Array) && !f['tags'].empty?
96
+
97
+ if f['implemented'] == false
98
+ # Removed features keep their (now-absent) references for history but must
99
+ # record when they went away.
100
+ die "#{id}: implemented:false requires `removed_in:`" unless f['removed_in']
101
+ next
102
+ end
103
+
104
+ # 2. Every reference path on an active feature must exist.
105
+ ref_paths(f['references']).each do |p|
106
+ ok = p.end_with?('/') ? File.directory?(p) : File.exist?(p)
107
+ die "#{id}: reference path does not exist: #{p}" unless ok
108
+ end
109
+
110
+ # 3a. Provenance is REQUIRED on every active feature (backfilled in PR B).
111
+ prov = f['provenance']
112
+ die "#{id}: missing `provenance:` block" unless prov.is_a?(Hash)
113
+ die "#{id}: provenance.introduced_in must be a version string" unless prov['introduced_in'].is_a?(String) && prov['introduced_in'] =~ /\A\d+\.\d+\.\d+/
114
+ die "#{id}: provenance.commit must be a short git hash" unless prov['commit'].is_a?(String) && prov['commit'] =~ /\A[0-9a-f]{7,40}\z/
115
+ die "#{id}: provenance.pr must be an integer or null" unless prov['pr'].nil? || prov['pr'].is_a?(Integer)
116
+ die "#{id}: provenance.issue must be an integer or null" unless prov['issue'].nil? || prov['issue'].is_a?(Integer)
117
+
118
+ # 3b. Test linkage is REQUIRED on every active feature (backfilled in PR C).
119
+ tests = f['tests']
120
+ die "#{id}: missing/empty `tests:` (a real test path or `{na: reason}`)" unless tests.is_a?(Array) && !tests.empty?
121
+ tests.each do |t|
122
+ if t.is_a?(String)
123
+ die "#{id}: tests entry does not exist: #{t}" unless File.exist?(t)
124
+ elsif t.is_a?(Hash)
125
+ die "#{id}: tests `na:` must carry a non-empty reason" unless t['na'].is_a?(String) && !t['na'].strip.empty?
126
+ else
127
+ die "#{id}: tests entries must be a path string or `{na: reason}`"
128
+ end
129
+ end
130
+ end
131
+
132
+ # Sequential-id sanity (gaps allowed — never reuse an ID — but flagged).
133
+ nums = seen.keys.map { |k| k.split('-').last.to_i }.sort
134
+ gaps = (1..nums.last).to_a - nums
135
+ warnings << "id gaps (verify intentional): #{gaps.map { |n| format('ZER0-%03d', n) }.join(', ')}" unless gaps.empty?
136
+
137
+ unless warnings.empty?
138
+ verbose = STRICT || ENV['FEATURES_VERBOSE'] == '1'
139
+ prov = warnings.count { |w| w.include?('provenance') }
140
+ tests = warnings.count { |w| w.include?('`tests:`') }
141
+ other = warnings.length - prov - tests
142
+ warn " \e[33m⚠\e[0m feature registry: #{warnings.length} warning(s) — " \
143
+ "provenance:#{prov} tests:#{tests} other:#{other} (FEATURES_VERBOSE=1 for the full list)"
144
+ warnings.each { |w| warn " - #{w}" } if verbose
145
+ die "#{warnings.length} warning(s) treated as errors (FEATURES_STRICT=1)" if STRICT
146
+ end
147
+
148
+ active = feats.count { |f| f['implemented'] != false }
149
+ puts "Feature registry valid: #{feats.length} entries (#{active} active), refs resolved, master/_data in sync"
@@ -1,4 +1,5 @@
1
1
  #!/bin/bash
2
+ # Feature: ZER0-053
2
3
 
3
4
  # Vendor asset installer for zer0-mistakes Jekyll theme
4
5
  # Downloads third-party JS/CSS libraries defined in vendor-manifest.json
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.23.0
4
+ version: 1.25.0
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-30 00:00:00.000000000 Z
11
+ date: 2026-07-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -114,6 +114,7 @@ files:
114
114
  - _includes/analytics/google-tag-manager-head.html
115
115
  - _includes/analytics/posthog.html
116
116
  - _includes/components/README.md
117
+ - _includes/components/admin-links.html
117
118
  - _includes/components/admin-tabs.html
118
119
  - _includes/components/ai-chat.html
119
120
  - _includes/components/analytics-dashboard.html
@@ -191,8 +192,10 @@ files:
191
192
  - _includes/navigation/navbar.html
192
193
  - _includes/navigation/section-sidebar.html
193
194
  - _includes/navigation/sidebar-categories.html
195
+ - _includes/navigation/sidebar-config.html
194
196
  - _includes/navigation/sidebar-folders.html
195
197
  - _includes/navigation/sidebar-left.html
198
+ - _includes/navigation/sidebar-nav.html
196
199
  - _includes/navigation/sidebar-right.html
197
200
  - _includes/navigation/toc-fab.html
198
201
  - _includes/navigation/unified-drawer.html
@@ -227,7 +230,6 @@ files:
227
230
  - _layouts/stats.html
228
231
  - _layouts/tag.html
229
232
  - _layouts/welcome.html
230
- - _plugins/admin_page_urls.rb
231
233
  - _plugins/author_pages_generator.rb
232
234
  - _plugins/content_statistics_generator.rb
233
235
  - _plugins/obsidian_links.rb
@@ -282,6 +284,7 @@ files:
282
284
  - _sass/tokens/_spacing.scss
283
285
  - _sass/tokens/_typography.scss
284
286
  - _sass/utilities/_focus.scss
287
+ - _sass/utilities/_layout.scss
285
288
  - _sass/utilities/_motion.scss
286
289
  - assets/css/custom.css
287
290
  - assets/css/main.scss
@@ -498,11 +501,13 @@ files:
498
501
  - scripts/platform/setup-wsl.sh
499
502
  - scripts/post-template-setup.sh
500
503
  - scripts/release
504
+ - scripts/requirements.txt
501
505
  - scripts/setup.sh
502
506
  - scripts/sync-backlog.rb
503
507
  - scripts/sync-backlog.sh
504
508
  - scripts/sync-plan.rb
505
509
  - scripts/sync-plan.sh
510
+ - scripts/tag-features
506
511
  - scripts/test-auto-version.sh
507
512
  - scripts/test-mermaid.sh
508
513
  - scripts/test-notebook-conversion.sh
@@ -526,6 +531,7 @@ files:
526
531
  - scripts/utils/fix-markdown
527
532
  - scripts/utils/setup
528
533
  - scripts/validate
534
+ - scripts/validate-features.rb
529
535
  - scripts/vendor-install.sh
530
536
  homepage: https://github.com/bamr87/zer0-mistakes
531
537
  licenses:
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Populates site.data['admin_page_urls'] once per build with a pipe-delimited
4
- # string of /about/ page URLs (e.g. "|/about/config/|/about/settings/theme/|").
5
- # Templates use a `contains` check against this string instead of re-running
6
- # `site.html_pages | where_exp | map | join` on every page render.
7
- Jekyll::Hooks.register :site, :pre_render do |site|
8
- admin_urls = site.pages
9
- .select { |p| p.output_ext == '.html' && p.url.include?('/about/') }
10
- .map(&:url)
11
- .sort
12
- .join('|')
13
- admin_urls = "|#{admin_urls}|" unless admin_urls.empty?
14
- site.data ||= {}
15
- site.data['admin_page_urls'] = admin_urls
16
- end