feat: tune search weights and heavy page loads

Status badges and hash-listing blocks leave the search index, card titles
weigh as page titles, and Pagefind ranking is set inside a range where
the ten gate queries all answer (search_gate.py: 10/10). Tables are set
up and measured when they near the screen, which relaid out a 3,800-row
page after its first paint, and console icons hold their box.
This commit is contained in:
Abdessamad Derraz committed 2026-10-22 16:49:27 +02:00
1 parent 294942afd0
commit 5f3534bdf2
9 files changed
+179 -26

No files matched your search

+13 -4
View File
@@ -1,7 +1,11 @@
#!/usr/bin/env python3
"""Lighthouse mobile (default form factor) on a list of pages, one summary line each.
python lighthouse.py <out dir> <name>=<base url> ... -- <page> ...
python lighthouse.py [--performance-only] <out dir> <name>=<base url> ... -- <page> ...
With all four categories, Lighthouse runs its accessibility checks inside the
page during the same load, and those tasks count toward Total Blocking Time
on a page of 15,000 elements. --performance-only measures the page alone.
Needs `npx lighthouse` and Chrome at /usr/bin/google-chrome.
"""
@@ -18,6 +22,10 @@ CATEGORIES = ("performance", "accessibility", "best-practices", "seo")
def main() -> int:
args = sys.argv[1:]
categories = CATEGORIES
if args[0] == "--performance-only":
args.pop(0)
categories = ("performance",)
out = Path(args.pop(0))
split = args.index("--")
engines = dict(item.split("=", 1) for item in args[:split])
@@ -34,7 +42,7 @@ def main() -> int:
["npx", "--prefix", str(Path(__file__).resolve().parent.parent), "lighthouse", base + page,
"--quiet", "--output=json", f"--output-path={report.resolve()}",
"--chrome-flags=--headless=new --no-sandbox",
f"--only-categories={','.join(CATEGORIES)}"],
f"--only-categories={','.join(categories)}"],
env=env, cwd=out, check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
data = json.loads(report.read_text())
cats, audits = data["categories"], data["audits"]
@@ -42,12 +50,13 @@ def main() -> int:
if a.get("score") is not None and a["score"] < 1 and a.get("scoreDisplayMode") == "binary"]
print(json.dumps({
"engine": name, "page": page or "/",
**{c: round(cats[c]["score"] * 100) for c in CATEGORIES},
**{c: round(cats[c]["score"] * 100) for c in categories},
"lcp": audits["largest-contentful-paint"]["displayValue"],
"tbt": audits["total-blocking-time"]["displayValue"],
"cls": audits["cumulative-layout-shift"]["displayValue"],
"bytes": audits["total-byte-weight"]["displayValue"],
"dom": audits["dom-size"]["displayValue"],
# Lighthouse 13 moved the DOM size audit into an insight.
"dom": (audits.get("dom-size") or audits.get("dom-size-insight") or {}).get("displayValue", ""),
"failed": failed,
}))
return 0
+60
View File
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
"""The search gate: ten queries a visitor types, and the pages that answer them.
Runs measure.mjs against a served build and counts a query as answered when
one of its expected pages is in the first three results of the site's own
search box.
python website/tools/search_gate.py http://127.0.0.1:4321/retrobios/
"""
from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
TOOLS = Path(__file__).resolve().parent
# Query, then the pages that answer it (path prefixes under the site).
QUERIES: dict[str, tuple[str, ...]] = {
"playstation bios": ("systems/sony/",),
# The file itself: its card on the system page, or a core that reads it.
"scph5501": ("systems/sony/", "emulators/beetle_psx/", "emulators/duckstation/",
"emulators/duckstation-legacy/", "emulators/pcsx1/", "emulators/swanstation/",
"emulators/ares/", "emulators/pcsx_rearmed/", "files/"),
"batocera folder": ("platforms/batocera/",),
"neogeo.zip": ("emulators/fbneo", "emulators/mame", "systems/snk/", "systems/arcade"),
"duckstation": ("emulators/duckstation/", "emulators/duckstation-legacy/"),
"steam deck": ("platforms/emudeck/", "platforms/retrodeck/", "which-pack/"),
"missing bios": ("wiki/troubleshooting/", "wiki/what-are-bios-files/", "wiki/faq/"),
"md5": ("wiki/verification-modes/",),
"retroarch system folder": ("platforms/retroarch/",),
"knulli": ("platforms/knulli/",),
}
def main() -> int:
base = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:4321/retrobios/"
root = "/" + base.split("/", 3)[3]
run = subprocess.run(
["node", str(TOOLS / "measure.mjs"), "search", base, "starlight-ui", *QUERIES],
capture_output=True, text=True, check=True,
)
answered = 0
for line in run.stdout.splitlines():
result = json.loads(line)
if "query" not in result:
print(f"first search: {result['first_search_kb']} KB in {result['first_search_requests']} requests")
continue
top = [entry.split(" | ")[1][len(root):] for entry in result["top"][:3]]
hit = any(page.startswith(QUERIES[result["query"]]) for page in top)
answered += hit
print(f"{'ok ' if hit else 'MISS'} {result['query']!r}: {', '.join(top)}")
print(f"{answered}/{len(QUERIES)} queries answered in the top 3")
return 0 if answered >= 9 else 1
if __name__ == "__main__":
sys.exit(main())
+23
View File
@@ -0,0 +1,23 @@
// Raw Pagefind results with their scores, to see why a page ranks where it does.
// node search_scores.mjs <base url> <query> [ranking json]
import { chromium } from 'playwright-core';
const [base, query, ranking] = process.argv.slice(2);
const browser = await chromium.launch({ executablePath: '/usr/bin/google-chrome' });
const page = await browser.newPage();
await page.goto(base, { waitUntil: 'networkidle' });
const rows = await page.evaluate(async ({ root, query, ranking }) => {
const pagefind = await import(`${root}pagefind/pagefind.js`);
if (ranking) await pagefind.options({ ranking: JSON.parse(ranking) });
const search = await pagefind.search(query);
const top = search.results.slice(0, 8);
const data = await Promise.all(top.map((result) => result.data()));
return top.map((result, index) => ({
score: result.score.toFixed(3),
url: data[index].url,
words: data[index].word_count,
hits: result.words.length,
}));
}, { root: base, query, ranking });
for (const row of rows) console.log(row.score, row.words, row.hits, row.url);
await browser.close();