pandorga 1.6.8 → 1.6.9
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 +11 -0
- data/_includes/content-runtime/40-listing.html +13 -5
- data/_includes/content-runtime/60-fragments.html +36 -10
- data/_layouts/home.html +44 -15
- data/lib/pandorga/version.rb +1 -1
- data/scripts/check_home_lists.mjs +39 -11
- data/scripts/test/test-home-lists.rb +10 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 110f9fdf3636db9ef496093f969e15ef4cd3322ee143c5efadb210fb5e4b98b9
|
|
4
|
+
data.tar.gz: 6c5625b1cbdac652c0120d2b25e49a722c59941862cf91da7f44eff6f1ada004
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 22f67174268b9edd80046d916e6d8394d4ce1df62e8802be7dc4eab8771479825c1ba8b3aa2f668ebef53bc95f116f840ddd300428362b938a19b9955d0914d8
|
|
7
|
+
data.tar.gz: 85ce7605aae925b73d384a9ddbc0269b5b31bb58652b05b60617c5b784f8cfc2c68dc92cc3824ed07bf016f305aee36c5fba17a51c891ecce06cd58a0c3f94d0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.9] — 2026-10-10
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Home list hydration no longer fills every band in one turn. The project
|
|
8
|
+
specimen measurement yields between projects, each band starts on its
|
|
9
|
+
own turn, and each home section introduction yields before it is
|
|
10
|
+
parsed. Dates reuse one formatter. The lists still render.
|
|
11
|
+
|
|
12
|
+
No `pandorga:` config keys were added or renamed.
|
|
13
|
+
|
|
3
14
|
## [1.6.8] — 2026-10-10
|
|
4
15
|
|
|
5
16
|
### Fixed
|
|
@@ -113,15 +113,23 @@
|
|
|
113
113
|
return textExcerpt((item && item.body_markdown) || '');
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
var dateFormatters = Object.create(null);
|
|
117
|
+
|
|
116
118
|
function formatDate(value, locale) {
|
|
117
119
|
if (!value) return '';
|
|
118
120
|
var parts = String(value).slice(0, 10).split('-').map(Number);
|
|
119
121
|
if (parts.length !== 3 || parts.some(isNaN)) return value;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
var key = locale || 'en';
|
|
123
|
+
var formatter = dateFormatters[key];
|
|
124
|
+
if (!formatter) {
|
|
125
|
+
formatter = new Intl.DateTimeFormat(key, {
|
|
126
|
+
day: '2-digit',
|
|
127
|
+
month: 'short',
|
|
128
|
+
year: 'numeric'
|
|
129
|
+
});
|
|
130
|
+
dateFormatters[key] = formatter;
|
|
131
|
+
}
|
|
132
|
+
return formatter.format(new Date(parts[0], parts[1] - 1, parts[2]));
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
function chronologySortKey(item) {
|
|
@@ -353,15 +353,31 @@
|
|
|
353
353
|
});
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
+
function yieldToMain() {
|
|
357
|
+
return new Promise(function (resolve) {
|
|
358
|
+
if (typeof scheduler !== 'undefined' && typeof scheduler.yield === 'function') {
|
|
359
|
+
scheduler.yield().then(resolve);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
window.setTimeout(resolve, 0);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
356
366
|
function hydrateHomeSections() {
|
|
357
367
|
var roots = Array.prototype.slice.call(
|
|
358
368
|
document.querySelectorAll('[data-content-home-section]')
|
|
359
369
|
);
|
|
360
370
|
if (!roots.length) return Promise.resolve();
|
|
361
371
|
return loadPageHeaders().then(function (sections) {
|
|
372
|
+
var chain = Promise.resolve();
|
|
362
373
|
roots.forEach(function (root) {
|
|
363
|
-
|
|
374
|
+
chain = chain.then(function () {
|
|
375
|
+
return yieldToMain().then(function () {
|
|
376
|
+
applyHomeSection(root, sections[root.getAttribute('data-content-home-section')]);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
364
379
|
});
|
|
380
|
+
return chain;
|
|
365
381
|
}).catch(function (error) {
|
|
366
382
|
console.error(error);
|
|
367
383
|
roots.forEach(function (root) { applyHomeSection(root, null); });
|
|
@@ -605,21 +621,31 @@
|
|
|
605
621
|
}
|
|
606
622
|
|
|
607
623
|
function hydratePageFragments() {
|
|
608
|
-
document.querySelectorAll('[data-content-page-header]')
|
|
609
|
-
|
|
624
|
+
var headers = Array.prototype.slice.call(document.querySelectorAll('[data-content-page-header]'));
|
|
625
|
+
var chain = Promise.resolve();
|
|
626
|
+
headers.forEach(function (el) {
|
|
627
|
+
chain = chain.then(function () {
|
|
628
|
+
return yieldToMain().then(function () { return hydratePageHeader(el); });
|
|
629
|
+
});
|
|
610
630
|
});
|
|
611
631
|
document.querySelectorAll('[data-content-fragment]').forEach(function (el) {
|
|
612
632
|
var path = el.getAttribute('data-content-fragment');
|
|
613
633
|
if (!path) return;
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
634
|
+
chain = chain.then(function () {
|
|
635
|
+
return yieldToMain().then(function () {
|
|
636
|
+
return mountMarkdownFragment(path, el, {
|
|
637
|
+
loadingText: el.getAttribute('data-content-loading') || 'Loading...',
|
|
638
|
+
errorText: el.getAttribute('data-content-error') || 'Content could not be loaded right now.'
|
|
639
|
+
});
|
|
640
|
+
});
|
|
617
641
|
});
|
|
618
642
|
});
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
643
|
+
chain.then(function () {
|
|
644
|
+
hydrateHomeSections();
|
|
645
|
+
hydrateBandArt();
|
|
646
|
+
hydrateContacts();
|
|
647
|
+
hydrateHeroPortrait();
|
|
648
|
+
});
|
|
623
649
|
}
|
|
624
650
|
|
|
625
651
|
function schedulePageFragmentHydration() {
|
data/_layouts/home.html
CHANGED
|
@@ -121,7 +121,21 @@ layout: shell
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function byDateDesc(a, b) {
|
|
124
|
-
|
|
124
|
+
var left = String(b.date || '');
|
|
125
|
+
var right = String(a.date || '');
|
|
126
|
+
if (left > right) return 1;
|
|
127
|
+
if (left < right) return -1;
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function afterPaint() {
|
|
132
|
+
return new Promise(function (resolve) {
|
|
133
|
+
if (typeof scheduler !== 'undefined' && typeof scheduler.yield === 'function') {
|
|
134
|
+
scheduler.yield().then(resolve);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
window.setTimeout(resolve, 0);
|
|
138
|
+
});
|
|
125
139
|
}
|
|
126
140
|
|
|
127
141
|
function tagHrefFrom(element) {
|
|
@@ -175,8 +189,13 @@ layout: shell
|
|
|
175
189
|
function draftFolios(posts) {
|
|
176
190
|
var folios = Object.create(null);
|
|
177
191
|
posts.slice().sort(function (a, b) {
|
|
178
|
-
var byDate = String(a.date || '').
|
|
179
|
-
|
|
192
|
+
var byDate = String(a.date || '') < String(b.date || '') ? -1 : (String(a.date || '') > String(b.date || '') ? 1 : 0);
|
|
193
|
+
if (byDate !== 0) return byDate;
|
|
194
|
+
var left = String(a.slug || '');
|
|
195
|
+
var right = String(b.slug || '');
|
|
196
|
+
if (left < right) return -1;
|
|
197
|
+
if (left > right) return 1;
|
|
198
|
+
return 0;
|
|
180
199
|
}).forEach(function (entry, index) {
|
|
181
200
|
folios[entry.slug] = index + 1;
|
|
182
201
|
});
|
|
@@ -221,7 +240,8 @@ layout: shell
|
|
|
221
240
|
element.appendChild(fragment);
|
|
222
241
|
}
|
|
223
242
|
|
|
224
|
-
function fillBand(section, index) {
|
|
243
|
+
async function fillBand(section, index) {
|
|
244
|
+
await afterPaint();
|
|
225
245
|
var kind = section.getAttribute('data-home-kind');
|
|
226
246
|
var collection = section.getAttribute('data-home-collection');
|
|
227
247
|
var limit = parseInt(section.getAttribute('data-home-limit') || '3', 10) || 3;
|
|
@@ -316,7 +336,7 @@ layout: shell
|
|
|
316
336
|
fitted(best);
|
|
317
337
|
}
|
|
318
338
|
|
|
319
|
-
function paintSpecimen(entry) {
|
|
339
|
+
async function paintSpecimen(entry) {
|
|
320
340
|
specimenSlot.innerHTML = '';
|
|
321
341
|
specimenSlot.appendChild(runtime.renderProjectSpecimen(asItem(entry), {
|
|
322
342
|
href: entry.url || runtime.projectUrl(entry.slug),
|
|
@@ -324,10 +344,11 @@ layout: shell
|
|
|
324
344
|
folio: positionOf(entry.slug) + 1,
|
|
325
345
|
openLabel: 'Open project'
|
|
326
346
|
}));
|
|
347
|
+
await afterPaint();
|
|
327
348
|
ellipsizeSpecimenCopy(specimenSlot);
|
|
328
349
|
}
|
|
329
350
|
|
|
330
|
-
function measureTallestSpecimen() {
|
|
351
|
+
async function measureTallestSpecimen() {
|
|
331
352
|
var bench = specimenSlot.closest('.home-projects-bench');
|
|
332
353
|
if (!bench) return 0;
|
|
333
354
|
var width = specimenSlot.getBoundingClientRect().width;
|
|
@@ -348,7 +369,8 @@ layout: shell
|
|
|
348
369
|
bench.appendChild(probe);
|
|
349
370
|
|
|
350
371
|
var max = 0;
|
|
351
|
-
portfolio.
|
|
372
|
+
for (var i = 0; i < portfolio.length; i += 1) {
|
|
373
|
+
var entry = portfolio[i];
|
|
352
374
|
probe.innerHTML = '';
|
|
353
375
|
var card = runtime.renderProjectSpecimen(asItem(entry), {
|
|
354
376
|
href: entry.url || runtime.projectUrl(entry.slug),
|
|
@@ -360,15 +382,16 @@ layout: shell
|
|
|
360
382
|
card.style.minHeight = '0';
|
|
361
383
|
probe.appendChild(card);
|
|
362
384
|
max = Math.max(max, Math.ceil(card.getBoundingClientRect().height));
|
|
363
|
-
|
|
385
|
+
await afterPaint();
|
|
386
|
+
}
|
|
364
387
|
probe.remove();
|
|
365
388
|
return max;
|
|
366
389
|
}
|
|
367
390
|
|
|
368
|
-
function lockSpecimenHeight() {
|
|
391
|
+
async function lockSpecimenHeight() {
|
|
369
392
|
var bench = specimenSlot.closest('.home-projects-bench');
|
|
370
393
|
if (!bench) return;
|
|
371
|
-
var tallest = measureTallestSpecimen();
|
|
394
|
+
var tallest = await measureTallestSpecimen();
|
|
372
395
|
/* Floor at the CSS inventory minimum so six rows always clear
|
|
373
396
|
three stacked resource icons (see --home-inventory-min-height). */
|
|
374
397
|
var inventoryMin = 0;
|
|
@@ -425,8 +448,9 @@ layout: shell
|
|
|
425
448
|
});
|
|
426
449
|
}
|
|
427
450
|
|
|
428
|
-
lockSpecimenHeight();
|
|
429
|
-
paintSpecimen(entryBySlug(selectedSlug));
|
|
451
|
+
await lockSpecimenHeight();
|
|
452
|
+
await paintSpecimen(entryBySlug(selectedSlug));
|
|
453
|
+
await afterPaint();
|
|
430
454
|
paintInventory();
|
|
431
455
|
|
|
432
456
|
var resizeTimer = null;
|
|
@@ -437,8 +461,9 @@ layout: shell
|
|
|
437
461
|
var nextWidth = Math.round(specimenSlot.getBoundingClientRect().width);
|
|
438
462
|
if (nextWidth === lastMeasureWidth) return;
|
|
439
463
|
lastMeasureWidth = nextWidth;
|
|
440
|
-
lockSpecimenHeight()
|
|
441
|
-
|
|
464
|
+
lockSpecimenHeight().then(function () {
|
|
465
|
+
return paintSpecimen(entryBySlug(selectedSlug));
|
|
466
|
+
});
|
|
442
467
|
}, 120);
|
|
443
468
|
});
|
|
444
469
|
return;
|
|
@@ -484,7 +509,11 @@ layout: shell
|
|
|
484
509
|
|
|
485
510
|
runtime.loadJson('data/writing_index.json').then(function (entries) {
|
|
486
511
|
var index = Array.isArray(entries) ? entries : [];
|
|
487
|
-
|
|
512
|
+
var chain = Promise.resolve();
|
|
513
|
+
sections.forEach(function (section) {
|
|
514
|
+
chain = chain.then(function () { return fillBand(section, index); });
|
|
515
|
+
});
|
|
516
|
+
return chain;
|
|
488
517
|
}).catch(function (error) {
|
|
489
518
|
console.error(error);
|
|
490
519
|
sections.forEach(function (section) {
|
data/lib/pandorga/version.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Lighthouse run treats an empty home as a fast page.
|
|
5
5
|
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
|
-
import { mkdtemp, rm } from 'node:fs/promises';
|
|
7
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
8
8
|
import { createServer } from 'node:net';
|
|
9
9
|
import { tmpdir } from 'node:os';
|
|
10
10
|
import { join } from 'node:path';
|
|
@@ -90,17 +90,44 @@ class Cdp {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
async function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
93
|
+
async function activePort(profile) {
|
|
94
|
+
try {
|
|
95
|
+
const text = await readFile(join(profile, 'DevToolsActivePort'), 'utf8');
|
|
96
|
+
const value = Number(text.split('\n')[0]);
|
|
97
|
+
return Number.isInteger(value) && value > 0 ? value : null;
|
|
98
|
+
} catch (_error) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function waitForDevtools(port, chrome, profile) {
|
|
104
|
+
let stderr = '';
|
|
105
|
+
if (chrome.stderr) {
|
|
106
|
+
chrome.stderr.on('data', (chunk) => {
|
|
107
|
+
stderr += chunk;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
for (let attempt = 0; attempt < 200; attempt += 1) {
|
|
111
|
+
if (chrome.exitCode !== null) {
|
|
112
|
+
throw new Error(`Chrome exited ${chrome.exitCode}: ${stderr.slice(-500)}`);
|
|
113
|
+
}
|
|
114
|
+
const opened = await activePort(profile);
|
|
115
|
+
const candidates = opened && opened !== port ? [port, opened] : [port];
|
|
116
|
+
for (const candidate of candidates) {
|
|
117
|
+
try {
|
|
118
|
+
const response = await fetch(`http://127.0.0.1:${candidate}/json/version`);
|
|
119
|
+
if (response.ok) return response.json();
|
|
120
|
+
} catch (_error) {
|
|
121
|
+
// Chrome is still starting.
|
|
122
|
+
}
|
|
100
123
|
}
|
|
101
124
|
await delay(100);
|
|
102
125
|
}
|
|
103
|
-
|
|
126
|
+
const opened = await activePort(profile);
|
|
127
|
+
throw new Error(
|
|
128
|
+
`Chrome DevTools did not answer on ${port}` +
|
|
129
|
+
`${opened ? ` (DevToolsActivePort ${opened})` : ''}. ${stderr.slice(-500)}`
|
|
130
|
+
);
|
|
104
131
|
}
|
|
105
132
|
|
|
106
133
|
async function openSocket(url) {
|
|
@@ -155,14 +182,15 @@ async function main() {
|
|
|
155
182
|
'--disable-dev-shm-usage',
|
|
156
183
|
'--no-first-run',
|
|
157
184
|
'--remote-allow-origins=*',
|
|
185
|
+
'--remote-debugging-address=127.0.0.1',
|
|
158
186
|
`--remote-debugging-port=${port}`,
|
|
159
187
|
`--user-data-dir=${profile}`,
|
|
160
188
|
'about:blank'
|
|
161
|
-
], { stdio: 'ignore' });
|
|
189
|
+
], { stdio: ['ignore', 'ignore', 'pipe'] });
|
|
162
190
|
|
|
163
191
|
let failed = false;
|
|
164
192
|
try {
|
|
165
|
-
const version = await waitForDevtools(port);
|
|
193
|
+
const version = await waitForDevtools(port, chrome, profile);
|
|
166
194
|
const ws = await openSocket(version.webSocketDebuggerUrl);
|
|
167
195
|
const browser = new Cdp(ws);
|
|
168
196
|
const created = await browser.send('Target.createTarget', { url: 'about:blank' });
|
|
@@ -43,7 +43,14 @@ end
|
|
|
43
43
|
|
|
44
44
|
home = ROOT.join("_layouts/home.html").read
|
|
45
45
|
fail!("home paint still runs before the runtime") unless home.scan("pandorga:runtime").length >= 2
|
|
46
|
-
fail!("home bands still
|
|
46
|
+
fail!("home bands still fill in one turn") unless home.include?("scheduler.yield") && home.include?("await afterPaint()")
|
|
47
|
+
fail!("specimen measurement does not yield") unless home.include?("await measureTallestSpecimen()")
|
|
48
|
+
fail!("home still fills every band inside one callback") if home.include?("sections.forEach(function (section) { fillBand(section, index); })")
|
|
49
|
+
fragments = ROOT.join("_includes/content-runtime/60-fragments.html").read
|
|
50
|
+
fail!("home section markdown still runs in one turn") unless fragments.include?("function yieldToMain()") &&
|
|
51
|
+
fragments.include?("return yieldToMain().then(function () {")
|
|
52
|
+
listing = ROOT.join("_includes/content-runtime/40-listing.html").read
|
|
53
|
+
fail!("date formatting builds a new formatter per card") unless listing.include?("dateFormatters")
|
|
47
54
|
|
|
48
55
|
marked = ROOT.join("_includes/content-runtime/00-config.html").read
|
|
49
56
|
fail!("markdown library no longer defers") unless marked.include?("<script defer ")
|
|
@@ -67,6 +74,8 @@ fail!("probe ignores the project inventory") unless probe.include?("#home-projec
|
|
|
67
74
|
fail!("probe ignores article cards") unless probe.include?("#home-articles .home-featured, #home-articles .ledger-card")
|
|
68
75
|
fail!("probe ignores the phone viewport") unless probe.include?("name: 'mobile'")
|
|
69
76
|
fail!("probe ignores the desktop viewport") unless probe.include?("name: 'desktop'")
|
|
77
|
+
fail!("probe does not pin DevTools to IPv4") unless probe.include?("--remote-debugging-address=127.0.0.1")
|
|
78
|
+
fail!("probe gives Chrome only a few seconds to open DevTools") unless probe.include?("attempt < 200")
|
|
70
79
|
|
|
71
80
|
_out, err, status = Open3.capture3("node", "--check", ROOT.join("scripts/check_home_lists.mjs").to_s)
|
|
72
81
|
fail!("home list probe is not valid JavaScript\n#{err}") unless status.success?
|