From 11ecccbc2cd3ac4df8510f8bf7737b6938429aa8 Mon Sep 17 00:00:00 2001 From: Robbie Court Date: Fri, 14 Aug 2026 10:07:19 +0000 Subject: [PATCH 1/2] Reimplement the Docsy alert and pageinfo shortcodes Term pages generated at version 6 and earlier call Docsy's alert shortcode. vfb-nova does not ship it, and a missing shortcode template is a hard build error, so a single superseded file anywhere in ~763k pages aborts the run -- twice this week, at 93 and 38 minutes each. Same signature and rendering as Docsy's, plus .alert rules in compat.css using the theme's tokens so they are styled rather than bare. pageinfo is included pre-emptively: it is the other Docsy shortcode those pages are likely to use, and finding out the slow way costs another build. --- themes/vfb-nova/assets/css/compat.css | 32 +++++++++++++++++++ themes/vfb-nova/layouts/shortcodes/alert.html | 18 +++++++++++ .../vfb-nova/layouts/shortcodes/pageinfo.html | 9 ++++++ 3 files changed, 59 insertions(+) create mode 100644 themes/vfb-nova/layouts/shortcodes/alert.html create mode 100644 themes/vfb-nova/layouts/shortcodes/pageinfo.html diff --git a/themes/vfb-nova/assets/css/compat.css b/themes/vfb-nova/assets/css/compat.css index ca2049e..ddbb2ad 100644 --- a/themes/vfb-nova/assets/css/compat.css +++ b/themes/vfb-nova/assets/css/compat.css @@ -106,3 +106,35 @@ background: var(--surface); translate: 0 -.15em; } + +/* -------------------------------------------------------------------------- + Bootstrap `alert`, for the legacy Docsy shortcodes reimplemented in + layouts/shortcodes/{alert,pageinfo}.html. Superseded term pages call them, + and without a template the whole build fails; without these rules they + would render as unstyled text. Uses the theme's own tokens rather than + Bootstrap's palette so they sit with the rest of the page. + -------------------------------------------------------------------------- */ +.prose .alert { + display: block; + padding: var(--sp-4) var(--sp-5); + margin: var(--sp-5) 0; + border: 1px solid var(--line); + border-left-width: 3px; + border-radius: var(--r-md); + background: var(--elev-1); +} +.prose .alert > :first-child { margin-top: 0; } +.prose .alert > :last-child { margin-bottom: 0; } +.prose .alert-heading { + margin: 0 0 var(--sp-2); + font-size: 1rem; + font-weight: 600; +} +.prose .alert-primary, +.prose .alert-info { border-left-color: rgba(76, 141, 255, .7); } +.prose .alert-success { border-left-color: rgba(64, 192, 128, .7); } +.prose .alert-warning { border-left-color: rgba(230, 168, 64, .7); } +.prose .alert-danger { border-left-color: rgba(230, 96, 96, .7); } +.prose .alert-secondary, +.prose .alert-light, +.prose .alert-dark { border-left-color: var(--line); } diff --git a/themes/vfb-nova/layouts/shortcodes/alert.html b/themes/vfb-nova/layouts/shortcodes/alert.html new file mode 100644 index 0000000..e55d15c --- /dev/null +++ b/themes/vfb-nova/layouts/shortcodes/alert.html @@ -0,0 +1,18 @@ +{{/* + Docsy's `alert`, reimplemented so legacy generated pages still render. + + Term pages written by vfbterms.py version 6 and earlier used Docsy + shortcodes. Those files are superseded but survive in the corpus whenever a + term drops out of one run's ID list, and a single one aborts the entire + build — twice now, at 93 and 38 minutes a time. The cost of shipping this is + one small template; the cost of not shipping it is a lost build per orphan. + + Same signature as Docsy: {{< alert title="..." color="..." >}} ... {{< /alert >}} + `.Inner | markdownify` matches Docsy's own implementation, so pages written + against it render as they did. +*/}} +{{- $color := .Get "color" | default "primary" -}} + diff --git a/themes/vfb-nova/layouts/shortcodes/pageinfo.html b/themes/vfb-nova/layouts/shortcodes/pageinfo.html new file mode 100644 index 0000000..a8c91d1 --- /dev/null +++ b/themes/vfb-nova/layouts/shortcodes/pageinfo.html @@ -0,0 +1,9 @@ +{{/* + Docsy's `pageinfo`, for the same reason as alert.html: legacy generated pages + call it, and a missing shortcode template is a hard build error rather than a + warning. Shipped pre-emptively so the next orphan does not cost another build. +*/}} +{{- $color := .Get "color" | default "info" -}} +
+{{ .Inner | markdownify }} +
From ff816c0540a5cc0dad7730f22198a73ff3f1b10e Mon Sep 17 00:00:00 2001 From: Robbie Court Date: Fri, 14 Aug 2026 10:07:58 +0000 Subject: [PATCH 2/2] Prune every superseded term page, not just the previous version process_term() removed only version-1 after writing a page, so a term absent from a single run's ID list kept its older file forever. The corpus still held _v6 pages with version at 9, in blog/ontologies/* and blog/releases/datasets alike, including one named _v6.md from a term with an empty short_form. Orphans are live pages: they are walked and parsed on every build, they can collide on URL with the current version, and ones predating the move off Docsy call shortcodes the theme no longer ships -- which aborts the build outright. Prunes once per group with a single directory scan instead of a stat per term per version. The per-term form would cost millions of extra syscalls across ~763k terms, and this corpus lives on an NFS volume serving a few hundred metadata ops per second. Net effect is one fewer stat per term than before. --- vfbterms.py | 46 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/vfbterms.py b/vfbterms.py index 66951f1..83e047e 100644 --- a/vfbterms.py +++ b/vfbterms.py @@ -757,12 +757,51 @@ def fetch_ids(label, query): print(f" Retrieved {len(ids)} IDs for {label}") return ids +_VERSION_SUFFIX = re.compile(r"_v(\d+)\.md$") + + +def prune_old_versions(label): + """Delete superseded term pages in the current directory. + + process_term() used to remove only version-1 after writing a page. A term + absent from a single run's ID list therefore kept its older file forever: + the corpus still held _v6 pages long after version reached 9. Those orphans + are live pages -- they can collide on URL with the current version, they + are walked and parsed on every build, and pages old enough to predate the + move off Docsy call shortcodes the theme no longer ships, which aborts the + whole build. + + One directory scan rather than a stat per term per version. The per-term + form costs version-1 extra syscalls for every term, which at ~763k terms is + millions of round trips; on the NFS volume this corpus lives on, metadata + operations run at a few hundred per second, so that is hours. This also + removes one stat per term relative to the old code. + """ + removed = 0 + try: + with os.scandir(".") as entries: + for entry in entries: + match = _VERSION_SUFFIX.search(entry.name) + if match and int(match.group(1)) < version: + try: + os.remove(entry.name) + removed += 1 + except OSError: + pass + except OSError as e: + print(f"WARNING: could not prune {label}: {e}") + return + if removed: + print(f" pruned {removed} superseded pages from {label}") + + def process_group(base_path, relative_dir, label, query): """Change directory, fetch IDs, and generate pages for one ontology group.""" target_dir = os.path.normpath(join(base_path, relative_dir)) print(f"\n[{label}] {target_dir}") chdir(target_dir) save_terms(fetch_ids(label, query)) + prune_old_versions(label) def process_term(term_id): """Fetch, render and write one term page. Returns a status string. @@ -786,13 +825,6 @@ def process_term(term_id): f.write(page_content) os.replace(tmp, filename) - # Clean up previous version - old_filename = term_id + "_v" + str(version - 1) + ".md" - if os.path.isfile(old_filename): - try: - os.remove(old_filename) - except OSError: - pass return "ok" def save_terms(ids):