From f0e7d87defac6c258f350b3efe2070e1943958fb Mon Sep 17 00:00:00 2001
From: Abdessamad Derraz <3028866+Abdess@users.noreply.github.com>
Date: Fri, 23 Oct 2026 11:12:12 +0200
Subject: [PATCH] fix: settle the round 3 site packages together
---
scripts/generate_site.py | 2 +-
scripts/sitestarlight.py | 6 +++---
tests/test_site_r2_components.py | 3 +++
tests/test_site_r2_gate.py | 15 ++++++++++++++-
tests/test_site_r2_nav.py | 4 ++++
tests/test_site_r3_entry.py | 6 +++---
tests/test_site_r3_nav.py | 7 ++++++-
website/src/plugins/rehype-stack-tables.mjs | 6 ++++++
website/src/scripts/site.ts | 4 +++-
website/src/styles/retrobios.css | 2 +-
10 files changed, 44 insertions(+), 11 deletions(-)
diff --git a/scripts/generate_site.py b/scripts/generate_site.py
index 69978167..a2220819 100644
--- a/scripts/generate_site.py
+++ b/scripts/generate_site.py
@@ -3207,7 +3207,7 @@ SAFE_LINK = "[Is it safe?](../index.md#is-it-safe)"
# Under "From your computer": a device with no network installs from the ZIP
# (`--from-pack`). The id is the one wiki/installer.md gives its section.
OFFLINE_LINE = ("No internet on the device? "
- "[Install from the ZIP](../wiki/installer.md#without-a-network).")
+ "[Install from the ZIP](../wiki/installer.md#without-a-network-offline-install).")
# Shown in the "On the device" tab of a guide that has no `terminal:` of its own.
DEVICE_TERMINAL_FALLBACK = "Or fill the folder from your computer, with the ZIP."
diff --git a/scripts/sitestarlight.py b/scripts/sitestarlight.py
index 5623c57b..c6c3b6f3 100644
--- a/scripts/sitestarlight.py
+++ b/scripts/sitestarlight.py
@@ -319,9 +319,9 @@ OG_DIR = Path(__file__).resolve().parent.parent / "docs_assets" / "og"
def og_image(rel: str, level: str) -> str:
"""The sharing card's address: the page's own when it has one, else
its section's."""
- own = re.fullmatch(r"platforms/([a-z0-9_-]+)\.md", rel)
- if own and own[1] != "index" and (OG_DIR / "platforms" / f"{own[1]}.png").is_file():
- return f"{SITE_URL}assets/og/platforms/{own[1]}.png"
+ own = re.fullmatch(r"(platforms|systems)/([a-z0-9_-]+)\.mdx?", rel)
+ if own and own[2] != "index" and (OG_DIR / own[1] / f"{own[2]}.png").is_file():
+ return f"{SITE_URL}assets/og/{own[1]}/{own[2]}.png"
return f"{SITE_URL}assets/og/{og_section(rel, level)}.png"
diff --git a/tests/test_site_r2_components.py b/tests/test_site_r2_components.py
index 449e2142..0d037a22 100644
--- a/tests/test_site_r2_components.py
+++ b/tests/test_site_r2_components.py
@@ -88,6 +88,8 @@ class TablesStackWhenBuilt(unittest.TestCase):
"reference": {"labels": ["A", "B", "C"], "rows": 5, "action": False, "level": "reference"},
# Four columns do not fit a phone column, on any page (round 3).
"wide": {"labels": ["File", "Region", "Required", "Hash"], "rows": 105, "action": False, "level": "reference"},
+ # A hash never breaks: three columns with one overflow a phone (NetherSX2).
+ "hashes": {"labels": ["File", "Required", "Hash"], "rows": 40, "action": False, "level": "reference"},
}
script = (
f"import {{ stackKind }} from {json.dumps(PLUGIN.as_uri())};"
@@ -101,6 +103,7 @@ class TablesStackWhenBuilt(unittest.TestCase):
self.assertEqual(json.loads(result.stdout), {
"zip-header": "true", "action": "true", "pair": "kv",
"short-start": "true", "long-setup": None, "reference": None, "wide": "true",
+ "hashes": "true",
})
diff --git a/tests/test_site_r2_gate.py b/tests/test_site_r2_gate.py
index a478877e..2ee703a4 100644
--- a/tests/test_site_r2_gate.py
+++ b/tests/test_site_r2_gate.py
@@ -45,6 +45,16 @@ _ELEMENT = re.compile(r"<[a-zA-Z][a-zA-Z0-9-]*[\s/>]")
_TAGS = re.compile(r"<[^>]+>")
_TABLE = re.compile(r"
", re.S)
_CURRENT = re.compile(r']*aria-current="page"[^>]*>(?P', re.S)
+_CURRENT_HREF = re.compile(r']*aria-current="page")[^>]*href="(?P[^"]*)"', re.S)
+
+
+def _marked_parent(html: str, url: str) -> str | None:
+ """The page a part page names as its parent. The sidebar marks that page
+ instead of the part, and the last crumb leads to it."""
+ found = _CURRENT_HREF.search(html.split('id="starlight__sidebar"', 1)[-1])
+ if found and found["href"].rstrip("/") != url.rstrip("/"):
+ return found["href"]
+ return None
_H1 = re.compile(r"]*>(?P.*?)
", re.S)
_HEADING = re.compile(r"[2-6])\b[^>]*>(?P.*?)", re.S)
_CRUMBS = re.compile(r'', re.S)
@@ -128,6 +138,8 @@ class EveryBuiltPage(unittest.TestCase):
# A tab, or a group of the sidebar, opens on its overview.
if url in self.tab_roots or url in self.group_pages or not current or not title:
continue
+ if _marked_parent(html, url):
+ continue
with self.subTest(page=url):
self.assertEqual(_text(current["label"]), _text(title["title"]))
@@ -144,10 +156,11 @@ class EveryBuiltPage(unittest.TestCase):
crumbs = _CRUMBS.search(html)
if not crumbs:
continue
+ parent = _marked_parent(html, _url(path))
for href in _HREF.findall(crumbs["inner"]):
with self.subTest(page=_url(path), href=href):
self.assertTrue(href in self.tab_roots or href in self.group_pages
- or "#" in href, href)
+ or "#" in href or href == parent, href)
def test_phone_tables_on_reader_pages_are_stacked(self):
"""A table of three columns or more, under RECORD_ROWS rows, on a
diff --git a/tests/test_site_r2_nav.py b/tests/test_site_r2_nav.py
index 07a89c55..50c1a40f 100644
--- a/tests/test_site_r2_nav.py
+++ b/tests/test_site_r2_nav.py
@@ -291,7 +291,11 @@ class PlatformShareCards(unittest.TestCase):
def test_other_pages_keep_their_section_card(self):
self.assertEqual(sitestarlight.og_image("platforms/index.md", "setup"),
f"{sitestarlight.SITE_URL}assets/og/setup.png")
+ # A manufacturer page has its own card since round 3 (6.6); one
+ # without a rendered card keeps its section's.
self.assertEqual(sitestarlight.og_image("systems/sony.md", "explore"),
+ f"{sitestarlight.SITE_URL}assets/og/systems/sony.png")
+ self.assertEqual(sitestarlight.og_image("systems/no-such-maker.md", "explore"),
f"{sitestarlight.SITE_URL}assets/og/explore.png")
diff --git a/tests/test_site_r3_entry.py b/tests/test_site_r3_entry.py
index 08aefa5d..0a81f5c5 100644
--- a/tests/test_site_r3_entry.py
+++ b/tests/test_site_r3_entry.py
@@ -181,9 +181,9 @@ class OfflineLine(unittest.TestCase):
line = text.index("No internet on the device?")
self.assertLess(start, line)
self.assertLess(line, text.index("## On the device"))
- self.assertIn("/retrobios/wiki/installer/#without-a-network", text)
- self.assertIn("{ #without-a-network }", (REPO_ROOT / "wiki" / "installer.md").read_text(
- encoding="utf-8"))
+ self.assertIn("/retrobios/wiki/installer/#without-a-network-offline-install", text)
+ self.assertIn("{ #without-a-network-offline-install }",
+ (REPO_ROOT / "wiki" / "installer.md").read_text(encoding="utf-8"))
class GetYourFilesListsEachSetupOnce(unittest.TestCase):
diff --git a/tests/test_site_r3_nav.py b/tests/test_site_r3_nav.py
index 9b9890c3..800a37e3 100644
--- a/tests/test_site_r3_nav.py
+++ b/tests/test_site_r3_nav.py
@@ -51,8 +51,13 @@ def _moved_platform_pages() -> list[Path]:
folder = DOCS / "platforms"
if not folder.is_dir():
return []
+ # A guide page sits beside the setups (platforms/linux-handhelds) without
+ # being a list moved off one of them.
+ from siteguides import HANDHELD_GUIDE # noqa: PLC0415
+
+ guides = {Path(HANDHELD_GUIDE.path).stem} if HANDHELD_GUIDE else set()
return [path for path in sorted(folder.glob("*.md*"))
- if path.stem not in platforms and path.stem != "index"]
+ if path.stem not in platforms and path.stem != "index" and path.stem not in guides]
class MetaFields(unittest.TestCase):
diff --git a/website/src/plugins/rehype-stack-tables.mjs b/website/src/plugins/rehype-stack-tables.mjs
index 061d6bfa..11737db6 100644
--- a/website/src/plugins/rehype-stack-tables.mjs
+++ b/website/src/plugins/rehype-stack-tables.mjs
@@ -12,6 +12,9 @@
// - on a level 0 or 1 page (`level: start` or `setup`), a three-column table
// short enough to read as records (under RECORD_ROWS rows): one block per
// row. A longer one is a list scanned by column and keeps its scroll.
+// - three columns or more where a column holds hashes: a hash cannot break,
+// so the row is wider than a phone at any length (NetherSX2's File,
+// Required, Hash summary measured 604 px in a 358 px column).
//
// A table of RECORD_ROWS rows or more gets its filter box here, above it.
// Written by site.ts once the page had painted, the box pushed the table
@@ -52,10 +55,13 @@ const holdsAction = (node) =>
(child.type === 'raw' && ACTION_CLASS.test(child.value ?? '')),
);
+const HASH_HEADER = /^(hash|sha-?1|sha-?256|md5|crc-?32)$/i;
+
export const stackKind = ({ labels, rows, action, level }) => {
if (labels.length === 2) return 'kv';
if (labels.length >= WIDE_COLUMNS) return 'true';
if (action || DOWNLOAD_HEADER.test(labels.at(-1) ?? '')) return 'true';
+ if (labels.length > 2 && labels.some((label) => HASH_HEADER.test(label))) return 'true';
if (READER_LEVELS.has(level) && labels.length > 2 && rows < RECORD_ROWS) return 'true';
return null;
};
diff --git a/website/src/scripts/site.ts b/website/src/scripts/site.ts
index 07066177..c4a49b50 100644
--- a/website/src/scripts/site.ts
+++ b/website/src/scripts/site.ts
@@ -317,7 +317,9 @@ const CATEGORY_WORDS: Record = { bios: 'BIOS', game_data: 'game
// own page (systems/sony-playstation/) is part of its manufacturer's file
// (systems/sony.json), found by dropping words from the end of the slug.
const heldFile = async (item: Item): Promise => {
- const slug = /^systems\/([^/]+)\//.exec(item.url)?.[1];
+ // Lookup urls are site-absolute (/retrobios/systems/...) since the API
+ // names every address from the site root.
+ const slug = /(?:^|\/)systems\/([^/]+)\//.exec(item.url)?.[1];
if (!slug || !item.sha1) return undefined;
const words = slug.split('-');
for (let size = words.length; size > 0; size -= 1) {
diff --git a/website/src/styles/retrobios.css b/website/src/styles/retrobios.css
index 0a47981d..45c6637e 100644
--- a/website/src/styles/retrobios.css
+++ b/website/src/styles/retrobios.css
@@ -343,7 +343,7 @@
}
@media (min-width: 50rem) {
/* The reason a file is listed reads as a sentence, not a word per line. */
- .sl-markdown-content :is(td.rb-reason, td[data-label='Reason']) { min-width: 12rem; }
+ .sl-markdown-content :is(td.rb-reason, td[data-label='Reason'], td:has(> .rb-reason)) { min-width: 12rem; }
/* Get your files: every group table shares one set of columns. */
.sl-markdown-content :is(table.rb-packs, table:has(td[data-label='Runs on']):has(td[data-label='ZIP'])) {