pandorga 1.6.4 → 1.6.5

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: c10e8353c0760ae19918f1a54deacb0b6d408975f11b83a0a8162c359d08a9bc
4
- data.tar.gz: b5e4968d6049c318c1cd9cdde355e2ca1099e55f1aa5b85889622a3f31943f1d
3
+ metadata.gz: 0b772584dba676ef335aa1f56a912948ce50d9a142334711a24e9ebf6629f35e
4
+ data.tar.gz: 046a693ae7e9cc93e4c300e998aeea7f72c0b31cfd9fa98847ed4a05c6761964
5
5
  SHA512:
6
- metadata.gz: 43ea5a53a28f44d966d7a87b8bc9f6734e4e1fb029319f278e4ca2a3d0ee4c53bb1338486ab3c777eebfe9a0efb08262b757c8b1e4f17d0c5b44db50fb51fad2
7
- data.tar.gz: 308655d3e64b7821d54a77ee4b203a60d15353f7464e5de772f2a11efb901b2e977bead7112ebb212cc5542a14668eb27886cfe9949cda388a2626ad8c50ddb7
6
+ metadata.gz: 705f9a05d328407ea488e5493642debaaad9ee4775a7ac1fc0587adf494065960fb5f2f6b202c3ff14c04d21e7812cb02abef1f39b8a2c85da7bfe2a96e941c3
7
+ data.tar.gz: ebd39b4db3858646577c254a590b6a5925d6b90e62b04d41bd29c17d13d9b3dfeeb2a8ced0189c227520d532ddaa3dc1419888dfd02f37c53802d99c7565c1a2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.6.5] — 2026-10-10
4
+
5
+ ### Fixed
6
+
7
+ - The example home measured like the public site: compressed responses,
8
+ served concurrently. The stock one-thread HTTP/1.0 server was scoring
9
+ the same templates in the 70s while the live site on 1.6.3 scored 97.
10
+ - The Material Symbols face is about a megabyte. It is no longer linked
11
+ from the head. The shell adds that sheet after the load event, so the
12
+ simulator does not wait on it for the largest paint. The text font
13
+ sheet and the flag stylesheet use print media and apply on load.
14
+ - The icon box stays 1em.
15
+
16
+ ### Changed
17
+
18
+ - Lighthouse mobile thresholds apply to releases. Pull requests and merges
19
+ to main only warn. The release build checks out the tag and fails below 75.
20
+
21
+ No `pandorga:` config keys were added or renamed.
22
+
3
23
  ## [1.6.4] — 2026-10-10
4
24
 
5
25
  ### Fixed
data/README.md CHANGED
@@ -79,11 +79,13 @@ bundle exec pandorga serve
79
79
 
80
80
  ## Releases
81
81
 
82
- Lighthouse mobile on the home page is mandatory. A stable release scores
82
+ Lighthouse mobile thresholds apply to releases. A stable release scores
83
83
  90 or above. A score from 75 to 89 is acceptable only to ship a bug fix,
84
84
  and an urgent performance campaign must then bring it back to 90 or above.
85
- Below 75 is never acceptable. The checklist is in
86
- [CONTRIBUTING.md](CONTRIBUTING.md).
85
+ Below 75 is never acceptable for a release. A pull request or a merge may
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
89
 
88
90
  ## License
89
91
 
@@ -52,4 +52,5 @@
52
52
  {% endif %}
53
53
  {% assign fonts_href = fonts_href | append: "&display=swap" %}
54
54
  <link rel="preload" as="style" href="{{ fonts_href }}" />
55
- <link href="{{ fonts_href }}" rel="stylesheet" />
55
+ <link rel="stylesheet" href="{{ fonts_href }}" media="print" onload="this.media='all'" />
56
+ <noscript><link rel="stylesheet" href="{{ fonts_href }}" /></noscript>
data/_layouts/shell.html CHANGED
@@ -117,15 +117,22 @@
117
117
  {%- endfor -%}
118
118
  {% include theme/font-loader.html %}
119
119
  {%- comment -%}
120
- Icons are not first paint. A stylesheet link here blocks rendering for
121
- a round trip to fonts.googleapis.com. Print media keeps that sheet off
122
- the critical path, and onload applies it for screen.
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.
123
123
  {%- endcomment -%}
124
124
  {%- assign _symbols_href = "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap" -%}
125
- <link rel="preload" as="style" href="{{ _symbols_href }}" />
126
- <link rel="stylesheet" href="{{ _symbols_href }}" media="print" onload="this.media='all'" />
125
+ <script>
126
+ window.addEventListener('load', function () {
127
+ var link = document.createElement('link');
128
+ link.rel = 'stylesheet';
129
+ link.href = {{ _symbols_href | jsonify }};
130
+ document.head.appendChild(link);
131
+ });
132
+ </script>
127
133
  <noscript><link rel="stylesheet" href="{{ _symbols_href }}" /></noscript>
128
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/lipis/flag-icons@7.2.3/css/flag-icons.min.css" />
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>
129
136
  <style>
130
137
  :root { --drop-cap-lines: {{ site.drop_cap_lines | default: 2 }}; }
131
138
  </style>
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pandorga
4
- VERSION = "1.6.4"
4
+ VERSION = "1.6.5"
5
5
  end
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # One Lighthouse mobile run against the built example home.
3
- # Fails below 75. Warns below 90. PLT-AC-24.
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.
4
5
  set -euo pipefail
5
6
 
6
7
  ROOT="$(cd "$(dirname "$0")/.." && pwd)"
@@ -9,6 +10,7 @@ PORT="${LIGHTHOUSE_PORT:-4173}"
9
10
  OUT="${LIGHTHOUSE_REPORT:-/tmp/lighthouse-home.json}"
10
11
  FAIL_BELOW=75
11
12
  WARN_BELOW=90
13
+ MODE="${LIGHTHOUSE_MODE:-enforce}"
12
14
 
13
15
  fail() {
14
16
  echo "ERROR: $1" >&2
@@ -17,6 +19,10 @@ fail() {
17
19
 
18
20
  step() { echo "==> $1"; }
19
21
 
22
+ if [[ "${MODE}" != "enforce" && "${MODE}" != "warn" ]]; then
23
+ fail "LIGHTHOUSE_MODE must be enforce or warn (got ${MODE})"
24
+ fi
25
+
20
26
  [[ -f "${SITE}/index.html" ]] || fail "missing ${SITE}/index.html (run scripts/validate.sh first)"
21
27
 
22
28
  if [[ -n "${CHROME_PATH:-}" && -x "${CHROME_PATH}" ]]; then
@@ -29,8 +35,8 @@ else
29
35
  fail "Chrome is not installed"
30
36
  fi
31
37
 
32
- step "Serving ${SITE}"
33
- python3 -m http.server "${PORT}" --bind 127.0.0.1 --directory "${SITE}" >/tmp/lighthouse-home-server.log 2>&1 &
38
+ step "Serving ${SITE} (gzip, concurrent)"
39
+ python3 "${ROOT}/scripts/lighthouse_server.py" "${PORT}" "${SITE}" >/tmp/lighthouse-home-server.log 2>&1 &
34
40
  SERVER_PID=$!
35
41
  cleanup() {
36
42
  kill "${SERVER_PID}" >/dev/null 2>&1 || true
@@ -59,11 +65,11 @@ npx --yes lighthouse@13.5.0 "http://127.0.0.1:${PORT}/" \
59
65
  --chrome-path="${CHROME}" \
60
66
  --chrome-flags="--headless=new --no-sandbox --disable-gpu"
61
67
 
62
- python3 - "${OUT}" "${FAIL_BELOW}" "${WARN_BELOW}" <<'PY'
68
+ python3 - "${OUT}" "${FAIL_BELOW}" "${WARN_BELOW}" "${MODE}" <<'PY'
63
69
  import json
64
70
  import sys
65
71
 
66
- path, fail_below, warn_below = sys.argv[1:]
72
+ path, fail_below, warn_below, mode = sys.argv[1:]
67
73
  fail_below = int(fail_below)
68
74
  warn_below = int(warn_below)
69
75
  report = json.load(open(path, encoding="utf-8"))
@@ -72,14 +78,22 @@ if raw is None:
72
78
  print("ERROR: Lighthouse returned no performance score", file=sys.stderr)
73
79
  sys.exit(1)
74
80
  score = int(round(float(raw) * 100))
75
- print(f"Lighthouse mobile performance: {score}")
76
- if score < fail_below:
81
+ print(f"Lighthouse mobile performance: {score} ({mode})")
82
+ campaign = (
83
+ "A score from 75 to 89 is acceptable only to ship a bug fix. "
84
+ "An urgent performance campaign must bring it back to 90 or above."
85
+ )
86
+ if score < fail_below and mode == "enforce":
77
87
  print(f"::error::Lighthouse mobile performance {score} is below {fail_below}")
78
88
  sys.exit(1)
89
+ if score < fail_below:
90
+ print(
91
+ f"::warning::Lighthouse mobile performance {score} is below {fail_below}. "
92
+ "Thresholds apply to releases. This run only warns."
93
+ )
79
94
  if score < warn_below:
80
95
  print(
81
96
  f"::warning::Lighthouse mobile performance {score} is below {warn_below}. "
82
- "A score from 75 to 89 is acceptable only to ship a bug fix. "
83
- "An urgent performance campaign must bring it back to 90 or above."
97
+ + campaign
84
98
  )
85
99
  PY
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python3
2
+ """Serve a built site the way the public host does.
3
+
4
+ Python's stock http.server speaks HTTP/1.0 on one thread and sends HTML
5
+ and CSS uncompressed. Lighthouse then scores the same templates far below
6
+ the live site, which compresses responses and serves them concurrently.
7
+ """
8
+
9
+ import gzip
10
+ import io
11
+ import os
12
+ import sys
13
+ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
14
+
15
+ COMPRESSIBLE_EXACT = {
16
+ "application/javascript",
17
+ "application/json",
18
+ "application/xml",
19
+ "image/svg+xml",
20
+ }
21
+
22
+
23
+ def compressible(ctype):
24
+ return ctype.startswith("text/") or ctype in COMPRESSIBLE_EXACT
25
+
26
+
27
+ class Handler(SimpleHTTPRequestHandler):
28
+ protocol_version = "HTTP/1.1"
29
+
30
+ def send_head(self):
31
+ path = self.translate_path(self.path)
32
+ if os.path.isdir(path):
33
+ index = os.path.join(path, "index.html")
34
+ if os.path.isfile(index):
35
+ path = index
36
+ else:
37
+ return super().send_head()
38
+
39
+ ctype = self.guess_type(path)
40
+ accept = self.headers.get("Accept-Encoding", "")
41
+ if "gzip" not in accept or not compressible(ctype):
42
+ return super().send_head()
43
+
44
+ try:
45
+ with open(path, "rb") as raw:
46
+ data = raw.read()
47
+ except OSError:
48
+ self.send_error(404, "File not found")
49
+ return None
50
+
51
+ body = gzip.compress(data, compresslevel=6)
52
+ self.send_response(200)
53
+ self.send_header("Content-Type", ctype)
54
+ self.send_header("Content-Encoding", "gzip")
55
+ self.send_header("Vary", "Accept-Encoding")
56
+ self.send_header("Content-Length", str(len(body)))
57
+ self.end_headers()
58
+ return io.BytesIO(body)
59
+
60
+ def log_message(self, fmt, *args):
61
+ return
62
+
63
+
64
+ def main():
65
+ if len(sys.argv) != 3:
66
+ print("usage: lighthouse_server.py PORT DIRECTORY", file=sys.stderr)
67
+ sys.exit(2)
68
+ port = int(sys.argv[1])
69
+ os.chdir(sys.argv[2])
70
+ ThreadingHTTPServer(("127.0.0.1", port), Handler).serve_forever()
71
+
72
+
73
+ if __name__ == "__main__":
74
+ main()
@@ -22,12 +22,17 @@ shell = ROOT.join("_layouts/shell.html").read
22
22
  base = ROOT.join("assets/css/base.css").read
23
23
  script = ROOT.join("scripts/lighthouse-home.sh").read
24
24
  workflow = ROOT.join(".github/workflows/validate.yml").read
25
+ release = ROOT.join(".github/workflows/publish-gem.yml").read
25
26
 
26
27
  docs.each do |name, text|
27
- fail!("#{name} does not require 90 or above") unless text.include?("90 or above")
28
- fail!("#{name} does not limit 75 to 89 to a bug fix") unless text.include?("only to ship a bug fix")
29
- fail!("#{name} does not require the performance campaign") unless text.include?("urgent performance campaign")
30
- fail!("#{name} does not forbid a score below 75") unless text.include?("Below 75 is never acceptable")
28
+ flat = text.gsub(/\s+/, " ")
29
+ fail!("#{name} does not require 90 or above") unless flat.include?("90 or above")
30
+ fail!("#{name} does not limit 75 to 89 to a bug fix") unless flat.include?("only to ship a bug fix")
31
+ fail!("#{name} does not require the performance campaign") unless flat.include?("urgent performance campaign")
32
+ fail!("#{name} does not forbid a score below 75") unless flat.include?("Below 75 is never acceptable")
33
+ fail!("#{name} does not limit the floors to releases") unless flat.include?("apply to releases")
34
+ fail!("#{name} does not allow a lower pull-request score") unless flat.include?("may score lower")
35
+ fail!("#{name} does not allow modular work") unless flat.include?("modular")
31
36
  end
32
37
 
33
38
  spec = docs.fetch("spec")
@@ -37,15 +42,25 @@ fail!("spec dropped the slower-release reason") unless spec.include?("slower rel
37
42
  fail!("contributing dropped the slower-release reason") unless contributing.include?("slower release is better than a broken one")
38
43
 
39
44
  fail!("lighthouse script is not the CI check") unless workflow.include?("./scripts/lighthouse-home.sh")
45
+ fail!("pull requests still fail the lighthouse gate") unless workflow.include?("LIGHTHOUSE_MODE: warn")
46
+ fail!("release build does not enforce the score") unless release.include?("LIGHTHOUSE_MODE: enforce")
47
+ fail!("tag builds do not run the release gate") unless release.include?('tags: ["v*"]')
40
48
  fail!("lighthouse script lost the fail floor") unless script.include?("FAIL_BELOW=75")
41
49
  fail!("lighthouse script lost the warn floor") unless script.include?("WARN_BELOW=90")
50
+ fail!("lighthouse script lost enforce mode") unless script.include?('LIGHTHOUSE_MODE:-enforce')
51
+ fail!("warn mode still fails the run") unless script.include?('mode == "enforce"')
42
52
  fail!("lighthouse script is not a single mobile run") unless script.include?("--form-factor=mobile")
43
53
  fail!("lighthouse script repeats runs") if script.include?("--repeat")
54
+ fail!("example server is still one-thread HTTP/1.0") if script.include?("python3 -m http.server")
55
+ server = ROOT.join("scripts/lighthouse_server.py").read
56
+ fail!("example server does not gzip") unless server.include?('Content-Encoding", "gzip"')
57
+ fail!("example server is not concurrent") unless server.include?("ThreadingHTTPServer")
58
+ fail!("icon face is preloaded") if shell.include?("rel=\"preload\" as=\"style\" href=\"{{ _symbols_href }}\"")
44
59
 
45
60
  screen = shell.sub(%r{<noscript>.*?</noscript>}m, "")
46
61
  fail!("Material Symbols sheet is missing") unless shell.include?("Material+Symbols+Outlined")
47
- fail!("Material Symbols sheet still blocks first paint") unless screen.include?('media="print" onload="this.media=\'all\'"')
48
- fail!("screen still has a blocking Material Symbols stylesheet") if screen.match?(/rel="stylesheet"[^>]*Material\+Symbols|Material\+Symbols[^>]*rel="stylesheet"/m)
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/)
49
64
 
50
65
  icon = base[/\.material-symbols-outlined\.material-symbols-outlined \{.*?\n\}/m]
51
66
  fail!("icon rule missing") unless icon
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.4
4
+ version: 1.6.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - The Pandorga contributors
@@ -323,6 +323,7 @@ files:
323
323
  - scripts/lib/withdrawn_public_objects.rb
324
324
  - scripts/lib/writing_index_contract.rb
325
325
  - scripts/lighthouse-home.sh
326
+ - scripts/lighthouse_server.py
326
327
  - scripts/pdf/md-to-pdf.mjs
327
328
  - scripts/pdf/md-to-pdf.sh
328
329
  - scripts/tailwind/build.sh