pandorga 1.6.5 → 1.6.7

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: 0b772584dba676ef335aa1f56a912948ce50d9a142334711a24e9ebf6629f35e
4
- data.tar.gz: 046a693ae7e9cc93e4c300e998aeea7f72c0b31cfd9fa98847ed4a05c6761964
3
+ metadata.gz: 28483b04968177f92f86ee26292b1bca873db8515dcb0d1735b72be27a564fb8
4
+ data.tar.gz: d2d20eb68479f49655c122a3948398e8e2719a27be458e27bc6f60aa002729da
5
5
  SHA512:
6
- metadata.gz: 705f9a05d328407ea488e5493642debaaad9ee4775a7ac1fc0587adf494065960fb5f2f6b202c3ff14c04d21e7812cb02abef1f39b8a2c85da7bfe2a96e941c3
7
- data.tar.gz: ebd39b4db3858646577c254a590b6a5925d6b90e62b04d41bd29c17d13d9b3dfeeb2a8ced0189c227520d532ddaa3dc1419888dfd02f37c53802d99c7565c1a2
6
+ metadata.gz: 89dd79f11d075413daac4ebcda740dc4f36a426a8b11aa608570551a446c79b09fc2223a74d703a36c2cae2fdaf0a14f41b6e3cf0048261c336a3a2a082e0100
7
+ data.tar.gz: efeaa1c9b49c5884e19b0e778e1a7205eec1dd000dc3c8875fa62747cf3a5df813202310f2cd2092d4d71847d629e6e8d1fedbf63e6bff8c99245f568f1a73f2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.6.7] — 2026-10-10
4
+
5
+ ### Fixed
6
+
7
+ - Text faces are requested after the first paint and before the load
8
+ event. A blocking sheet held first paint on the font host. The load
9
+ event held the hero summary until images finished, so the live home
10
+ scored about 50 while the example, which has no summary, scored 98.
11
+ - The icon face names the icons it needs. The full variable file is about
12
+ a megabyte, and decoding it blocked the main thread on the live home.
13
+ - The project inventory region has a role to match its accessible name.
14
+ - Lighthouse measures `examples/full`, whose home includes a static hero
15
+ summary, the element a real site paints largest.
16
+
17
+ No `pandorga:` config keys were added or renamed.
18
+
19
+ ## [1.6.6] — 2026-10-10
20
+
21
+ ### Fixed
22
+
23
+ - The text faces and the flag stylesheet are requested after the load
24
+ event. A head link let a fast connection finish them before the largest
25
+ paint, and the simulator then counted those files as part of that paint.
26
+ The same templates scored 87 on the release run and 97 on the pull
27
+ request. `font-display: swap` stays.
28
+ - The markdown library loads with `defer`. The example home is already
29
+ HTML, so that script does not block the parser.
30
+
31
+ ### Changed
32
+
33
+ - A release below 90 does not publish. A bug-fix release whose notes
34
+ include `LIGHTHOUSE_BUGFIX=1` may still publish from 75 to 89.
35
+
36
+ No `pandorga:` config keys were added or renamed.
37
+
3
38
  ## [1.6.5] — 2026-10-10
4
39
 
5
40
  ### Fixed
data/README.md CHANGED
@@ -84,8 +84,9 @@ Lighthouse mobile thresholds apply to releases. A stable release scores
84
84
  and an urgent performance campaign must then bring it back to 90 or above.
85
85
  Below 75 is never acceptable for a release. A pull request or a merge may
86
86
  score lower when that is unavoidable to keep the work modular. The release
87
- build fails below 75. Pull requests and merges only warn. The checklist
88
- is in [CONTRIBUTING.md](CONTRIBUTING.md).
87
+ build publishes at 90 or above. Notes that include `LIGHTHOUSE_BUGFIX=1`
88
+ may publish a bug fix from 75 to 89. Pull requests and merges only warn.
89
+ The checklist is in [CONTRIBUTING.md](CONTRIBUTING.md).
89
90
 
90
91
  ## License
91
92
 
@@ -1,4 +1,4 @@
1
- <script src="https://cdn.jsdelivr.net/npm/marked@15.0.7/marked.min.js"></script>
1
+ <script defer src="https://cdn.jsdelivr.net/npm/marked@15.0.7/marked.min.js"></script>
2
2
  <script>
3
3
  window.ContentRuntimeConfig = {
4
4
  contentBase: '{{ site.content_api_base_url | default: "" }}',
@@ -54,7 +54,7 @@
54
54
  data-tag-base="{{ _path }}"
55
55
  >
56
56
  <div class="home-project-specimen-slot" data-home-specimen></div>
57
- <div class="home-project-inventory" data-home-inventory aria-label="Project inventory"></div>
57
+ <div class="home-project-inventory" data-home-inventory role="region" aria-label="Project inventory"></div>
58
58
  </div>
59
59
  {%- else -%}
60
60
  <div
@@ -51,6 +51,13 @@
51
51
  {% endunless %}
52
52
  {% endif %}
53
53
  {% assign fonts_href = fonts_href | append: "&display=swap" %}
54
- <link rel="preload" as="style" href="{{ fonts_href }}" />
55
- <link rel="stylesheet" href="{{ fonts_href }}" media="print" onload="this.media='all'" />
54
+ {% comment %}
55
+ Not a render-blocking head link, and not the load event. A blocking
56
+ sheet holds first paint on the font host. The load event holds the hero
57
+ summary until images finish. The shell inserts this sheet after the
58
+ first paint. display=swap stays.
59
+ {% endcomment %}
60
+ <script>
61
+ window.__pandorgaFontsHref = {{ fonts_href | jsonify }};
62
+ </script>
56
63
  <noscript><link rel="stylesheet" href="{{ fonts_href }}" /></noscript>
data/_layouts/shell.html CHANGED
@@ -59,10 +59,10 @@
59
59
  <!-- Canonical URL -->
60
60
  <link rel="canonical" href="{{ canonical_path | absolute_url }}" />
61
61
  {%- comment -%}
62
- Preconnect only. font-loader's stylesheet is render-blocking, and the
63
- preload script below is parser-blocking. A stylesheet before that script
64
- holds the parser until fonts.googleapis.com answers, so the ledger sheets
65
- and the hero summary cannot paint. The face is linked with the other sheets.
62
+ Preconnect only. The preload script below is parser-blocking. A
63
+ stylesheet before that script holds the parser until the font host
64
+ answers, so the ledger sheets and the hero cannot paint. The text-face
65
+ loader sits with the local sheets.
66
66
  {%- endcomment -%}
67
67
  <link href="https://fonts.googleapis.com" rel="preconnect" />
68
68
  <link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect" />
@@ -117,22 +117,75 @@
117
117
  {%- endfor -%}
118
118
  {% include theme/font-loader.html %}
119
119
  {%- comment -%}
120
- Icons are not first paint. The face is about a megabyte. A link in the
121
- head, even with print media, is discovered immediately and the simulator
122
- then waits on that face for the largest paint. Add the sheet after load.
120
+ Named icons only. The full variable face is about a megabyte, and a
121
+ late request for it becomes the largest paint on a real hero. Nav icons
122
+ from the registry join the shell set.
123
123
  {%- endcomment -%}
124
- {%- assign _symbols_href = "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" -%}
124
+ {%- assign _icon_names = "arrow_left_alt,arrow_right_alt,architecture,article,chat,chevron_left,chevron_right,close,code,dark_mode,expand_more,filter_list,format_quote,format_size,info,light_mode,menu,picture_as_pdf,public,routine,search,share,shuffle,sync" -%}
125
+ {%- for _group in site.data.nav -%}
126
+ {%- for _item in _group.pages -%}
127
+ {%- if _item.icon -%}
128
+ {%- assign _icon_hay = "," | append: _icon_names | append: "," -%}
129
+ {%- assign _icon_needle = "," | append: _item.icon | append: "," -%}
130
+ {%- unless _icon_hay contains _icon_needle -%}
131
+ {%- assign _icon_names = _icon_names | append: "," | append: _item.icon -%}
132
+ {%- endunless -%}
133
+ {%- endif -%}
134
+ {%- endfor -%}
135
+ {%- endfor -%}
136
+ {%- assign _symbols_href = "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&icon_names=" | append: _icon_names | append: "&display=swap" -%}
137
+ <script>
138
+ window.__pandorgaIconsHref = {{ _symbols_href | jsonify }};
139
+ </script>
140
+ <noscript><link id="pandorga-icons" rel="stylesheet" href="{{ _symbols_href }}" /></noscript>
141
+ <script>
142
+ (function () {
143
+ var known = {{ _icon_names | split: "," | jsonify }};
144
+ var seen = {};
145
+ known.forEach(function (name) { seen[name] = true; });
146
+ var dirty = false;
147
+ var timer = 0;
148
+ window.__pandorgaFlushIcons = function () {
149
+ var link = document.getElementById('pandorga-icons');
150
+ if (!link || !dirty) return;
151
+ dirty = false;
152
+ link.href = 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&icon_names=' + known.join(',') + '&display=swap';
153
+ };
154
+ function consider(root) {
155
+ if (!root || !root.querySelectorAll) return;
156
+ var nodes = root.querySelectorAll('.material-symbols-outlined');
157
+ var extra = false;
158
+ for (var i = 0; i < nodes.length; i++) {
159
+ var name = (nodes[i].textContent || '').trim();
160
+ if (!name || seen[name] || !/^[a-z0-9_]+$/.test(name)) continue;
161
+ seen[name] = true;
162
+ known.push(name);
163
+ extra = true;
164
+ }
165
+ if (!extra) return;
166
+ dirty = true;
167
+ clearTimeout(timer);
168
+ timer = setTimeout(window.__pandorgaFlushIcons, 50);
169
+ }
170
+ function start() {
171
+ consider(document);
172
+ var obs = new MutationObserver(function () { consider(document); });
173
+ obs.observe(document.body, { childList: true, subtree: true, characterData: true });
174
+ }
175
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
176
+ else start();
177
+ })();
178
+ </script>
179
+ {%- assign _flags_href = "https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.2.3/css/flag-icons.min.css" -%}
125
180
  <script>
126
181
  window.addEventListener('load', function () {
127
182
  var link = document.createElement('link');
128
183
  link.rel = 'stylesheet';
129
- link.href = {{ _symbols_href | jsonify }};
184
+ link.href = {{ _flags_href | jsonify }};
130
185
  document.head.appendChild(link);
131
186
  });
132
187
  </script>
133
- <noscript><link rel="stylesheet" href="{{ _symbols_href }}" /></noscript>
134
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.2.3/css/flag-icons.min.css" media="print" onload="this.media='all'" />
135
- <noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.2.3/css/flag-icons.min.css" /></noscript>
188
+ <noscript><link rel="stylesheet" href="{{ _flags_href }}" /></noscript>
136
189
  <style>
137
190
  :root { --drop-cap-lines: {{ site.drop_cap_lines | default: 2 }}; }
138
191
  </style>
@@ -257,6 +310,28 @@
257
310
  {% include page/site-footer.html nav_active=nav_active %}
258
311
 
259
312
  {% include page/content-runtime.html %}
313
+ <script>
314
+ (function () {
315
+ var fonts = window.__pandorgaFontsHref;
316
+ var icons = window.__pandorgaIconsHref;
317
+ if (!fonts && !icons) return;
318
+ requestAnimationFrame(function () {
319
+ requestAnimationFrame(function () {
320
+ function sheet(href, id) {
321
+ if (!href) return;
322
+ var link = document.createElement('link');
323
+ link.rel = 'stylesheet';
324
+ link.href = href;
325
+ if (id) link.id = id;
326
+ document.head.appendChild(link);
327
+ }
328
+ sheet(fonts);
329
+ sheet(icons, 'pandorga-icons');
330
+ if (window.__pandorgaFlushIcons) window.__pandorgaFlushIcons();
331
+ });
332
+ });
333
+ })();
334
+ </script>
260
335
 
261
336
  </body>
262
337
 
@@ -1 +1 @@
1
- Ada Example designs publishing frames. This summary is fictional.
1
+ Ada Example designs publishing frames. This summary is fictional. The home hero includes it as HTML, so the largest paint is this paragraph, the same element a real site measures.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pandorga
4
- VERSION = "1.6.5"
4
+ VERSION = "1.6.7"
5
5
  end
@@ -1,16 +1,18 @@
1
1
  #!/usr/bin/env bash
2
2
  # One Lighthouse mobile run against the built example home.
3
- # Thresholds apply to releases. LIGHTHOUSE_MODE=enforce fails below 75
4
- # and warns below 90. LIGHTHOUSE_MODE=warn never fails the run. PLT-AC-24.
3
+ # Thresholds apply to releases. LIGHTHOUSE_MODE=enforce does not publish
4
+ # below 90 unless LIGHTHOUSE_BUGFIX=1, and that bug-fix release still
5
+ # fails below 75. LIGHTHOUSE_MODE=warn never fails the run. PLT-AC-24.
5
6
  set -euo pipefail
6
7
 
7
8
  ROOT="$(cd "$(dirname "$0")/.." && pwd)"
8
- SITE="${ROOT}/examples/minimal/_site"
9
+ SITE="${ROOT}/examples/full/_site"
9
10
  PORT="${LIGHTHOUSE_PORT:-4173}"
10
11
  OUT="${LIGHTHOUSE_REPORT:-/tmp/lighthouse-home.json}"
11
12
  FAIL_BELOW=75
12
13
  WARN_BELOW=90
13
14
  MODE="${LIGHTHOUSE_MODE:-enforce}"
15
+ BUGFIX="${LIGHTHOUSE_BUGFIX:-}"
14
16
 
15
17
  fail() {
16
18
  echo "ERROR: $1" >&2
@@ -24,6 +26,8 @@ if [[ "${MODE}" != "enforce" && "${MODE}" != "warn" ]]; then
24
26
  fi
25
27
 
26
28
  [[ -f "${SITE}/index.html" ]] || fail "missing ${SITE}/index.html (run scripts/validate.sh first)"
29
+ grep -q 'id="home-cv-summary"[^>]*data-content-static' "${SITE}/index.html" \
30
+ || fail "example home has no static hero summary (data-content-static)"
27
31
 
28
32
  if [[ -n "${CHROME_PATH:-}" && -x "${CHROME_PATH}" ]]; then
29
33
  CHROME="${CHROME_PATH}"
@@ -65,11 +69,11 @@ npx --yes lighthouse@13.5.0 "http://127.0.0.1:${PORT}/" \
65
69
  --chrome-path="${CHROME}" \
66
70
  --chrome-flags="--headless=new --no-sandbox --disable-gpu"
67
71
 
68
- python3 - "${OUT}" "${FAIL_BELOW}" "${WARN_BELOW}" "${MODE}" <<'PY'
72
+ python3 - "${OUT}" "${FAIL_BELOW}" "${WARN_BELOW}" "${MODE}" "${BUGFIX}" <<'PY'
69
73
  import json
70
74
  import sys
71
75
 
72
- path, fail_below, warn_below, mode = sys.argv[1:]
76
+ path, fail_below, warn_below, mode, bugfix = sys.argv[1:]
73
77
  fail_below = int(fail_below)
74
78
  warn_below = int(warn_below)
75
79
  report = json.load(open(path, encoding="utf-8"))
@@ -79,6 +83,16 @@ if raw is None:
79
83
  sys.exit(1)
80
84
  score = int(round(float(raw) * 100))
81
85
  print(f"Lighthouse mobile performance: {score} ({mode})")
86
+ audits = report.get("audits", {})
87
+ for key in (
88
+ "first-contentful-paint",
89
+ "largest-contentful-paint",
90
+ "speed-index",
91
+ "total-blocking-time",
92
+ "cumulative-layout-shift",
93
+ ):
94
+ display = audits.get(key, {}).get("displayValue", "?")
95
+ print(f" {key}: {display}")
82
96
  campaign = (
83
97
  "A score from 75 to 89 is acceptable only to ship a bug fix. "
84
98
  "An urgent performance campaign must bring it back to 90 or above."
@@ -86,6 +100,13 @@ campaign = (
86
100
  if score < fail_below and mode == "enforce":
87
101
  print(f"::error::Lighthouse mobile performance {score} is below {fail_below}")
88
102
  sys.exit(1)
103
+ if mode == "enforce" and score < warn_below and bugfix != "1":
104
+ print(
105
+ f"::error::Lighthouse mobile performance {score} is below {warn_below}. "
106
+ + campaign
107
+ + " This release is not marked LIGHTHOUSE_BUGFIX=1, so it will not publish."
108
+ )
109
+ sys.exit(1)
89
110
  if score < fail_below:
90
111
  print(
91
112
  f"::warning::Lighthouse mobile performance {score} is below {fail_below}. "
@@ -49,6 +49,11 @@ fail!("lighthouse script lost the fail floor") unless script.include?("FAIL_BELO
49
49
  fail!("lighthouse script lost the warn floor") unless script.include?("WARN_BELOW=90")
50
50
  fail!("lighthouse script lost enforce mode") unless script.include?('LIGHTHOUSE_MODE:-enforce')
51
51
  fail!("warn mode still fails the run") unless script.include?('mode == "enforce"')
52
+ fail!("a non-bug-fix release can publish below 90") unless script.include?('bugfix != "1"')
53
+ fail!("release workflow cannot mark a bug fix") unless release.include?("LIGHTHOUSE_BUGFIX")
54
+ fail!("spec still publishes a feature below 90") unless spec.include?("LIGHTHOUSE_BUGFIX=1")
55
+ fail!("contributing still publishes a feature below 90") unless contributing.include?("LIGHTHOUSE_BUGFIX=1")
56
+ fail!("readme still publishes a feature below 90") unless docs.fetch("readme").include?("LIGHTHOUSE_BUGFIX=1")
52
57
  fail!("lighthouse script is not a single mobile run") unless script.include?("--form-factor=mobile")
53
58
  fail!("lighthouse script repeats runs") if script.include?("--repeat")
54
59
  fail!("example server is still one-thread HTTP/1.0") if script.include?("python3 -m http.server")
@@ -59,8 +64,30 @@ fail!("icon face is preloaded") if shell.include?("rel=\"preload\" as=\"style\"
59
64
 
60
65
  screen = shell.sub(%r{<noscript>.*?</noscript>}m, "")
61
66
  fail!("Material Symbols sheet is missing") unless shell.include?("Material+Symbols+Outlined")
62
- fail!("icon sheet is not deferred until load") unless screen.include?("addEventListener('load'")
63
- fail!("screen still has a Material Symbols stylesheet link") if screen.match?(/<link\b[^>]*Material\+Symbols/)
67
+ fail!("screen still has a flag stylesheet link") if screen.match?(/<link\b[^>]*flag-icons/)
68
+ fail!("flag sheet is not deferred until load") unless screen.include?("flag-icons@7.2.3")
69
+ fail!("icon face is the full variable font") if shell.include?("0..1&display=swap")
70
+ fail!("icon face is not a named subset") unless shell.include?("icon_names=")
71
+ fail!("icon subset link has no id") unless screen.include?("pandorga-icons")
72
+
73
+ fonts = ROOT.join("_includes/theme/font-loader.html").read
74
+ fail!("text faces are preloaded") if fonts.include?('rel="preload"')
75
+ fail!("text face still waits for the load event") if fonts.include?("addEventListener('load'")
76
+ fail!("text face href is not handed to the shell") unless fonts.include?("__pandorgaFontsHref")
77
+ fail!("text face is not requested after first paint") unless screen.scan("requestAnimationFrame").length >= 2
78
+
79
+ marked = ROOT.join("_includes/content-runtime/00-config.html").read
80
+ fail!("markdown library still blocks parsing") unless marked.include?("<script defer ")
81
+ fail!("markdown library source changed") unless marked.include?("marked@15.0.7/marked.min.js")
82
+
83
+ fail!("spec still withholds the text face until load") unless spec.include?("after the first paint and before the load event")
84
+ fail!("lighthouse example is not the full site") unless script.include?("examples/full")
85
+ fail!("lighthouse example can ship without a static summary") unless script.include?("data-content-static")
86
+ summary = ROOT.join("examples/full/content/pages/cv/summary.md").read
87
+ fail!("full summary dropped the fixture sentence") unless summary.include?("Ada Example designs publishing frames")
88
+ fail!("full summary is too short to be the hero paint") unless summary.scan(".").length >= 3
89
+ fail!("spec still lets the markdown library block parsing") unless spec.include?("markdown library loads with `defer`")
90
+ fail!("lighthouse script hides the metric breakdown") unless script.include?("largest-contentful-paint")
64
91
 
65
92
  icon = base[/\.material-symbols-outlined\.material-symbols-outlined \{.*?\n\}/m]
66
93
  fail!("icon rule missing") unless icon
@@ -71,6 +98,7 @@ fail!("content runtime still precedes the page") unless shell.index("{{ content
71
98
  band = File.read(File.expand_path("../../_includes/home/band-listing.html", __dir__))
72
99
  fail!("band title is still painted by script") unless band.include?('data-content-section-title>{{ _title }}')
73
100
  fail!("band note is still painted by script") unless band.include?("page_headers")
101
+ fail!("project inventory has a name and no role") unless band.include?('role="region"') && band.include?('aria-label="Project inventory"')
74
102
  headers = File.read(File.expand_path("../../_plugins/content_headers.rb", __dir__))
75
103
  fail!("page headers are not loaded at build time") unless headers.include?('site.data["page_headers"]')
76
104
  home = File.read(File.expand_path("../../_layouts/home.html", __dir__))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pandorga
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.5
4
+ version: 1.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Pandorga contributors