diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index d14f027..9701933 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -31,7 +31,7 @@ jobs:
path: site
- name: Apply SPA basepath patch
working-directory: site
- run: python ../publish/quartz-patches/apply.py .
+ run: python ../publish/quartz-patches/apply.py spa
- name: Apply publish overlay
run: cp publish/quartz.config.yaml site/quartz.config.yaml
- uses: actions/setup-node@v6
@@ -49,8 +49,6 @@ jobs:
with:
path: site/.quartz/plugins
key: ${{ runner.os }}-plugins-${{ hashFiles('site/package-lock.json') }}
- restore-keys: |
- ${{ runner.os }}-plugins-
- name: Install Dependencies
working-directory: site
run: npm ci
@@ -60,9 +58,8 @@ jobs:
- name: Build Quartz
working-directory: site
run: npx quartz build -d ..
- - name: Patch sidebar title link
- working-directory: site
- run: python ../publish/quartz-patches/patch-title-link.py
+ - name: Rewrite built HTML
+ run: python ../publish/quartz-patches/apply.py built
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
diff --git a/publish/quartz-patches/apply.py b/publish/quartz-patches/apply.py
index 4c8ab11..89678a5 100644
--- a/publish/quartz-patches/apply.py
+++ b/publish/quartz-patches/apply.py
@@ -1,35 +1,78 @@
-"""Patch quartz spa.inline.ts so relative URL clicks resolve from a directory base.
+"""Single source of truth for the Threadwell site base.
-Quartz's SPA click handler calls `new URL(href)`. When the URL bar is missing
-a trailing slash, the browser treats the last path segment as a file, so
-`../../../../tags/archive` from `/Threadwell/x/y/` (no slash) resolves to
-`/Threadwell/x/tags/archive` instead of `/Threadwell/tags/archive`. SPA routing
-loses the `/Threadwell/` base path on GitHub Pages project deployments.
+Two stages:
-Fix: compute `SPA_BASE_HREF` from `window.location`, always ending with `/`,
-and resolve relative URLs against it inside the SPA click handler.
+1. Apply the SPA basepath patch to quartz/components/scripts/spa.inline.ts so
+ relative URLs in the SPA click handler resolve from a directory base.
+
+2. After `npx quartz build`, rewrite every built HTML file:
+ - add `` so relative URL resolution works even when
+ the URL bar lacks a trailing slash
+ - rewrite the sidebar `` href to the absolute site
+ base, not the directory the page is in
+
+The base URL is read from `publish/quartz.config.yaml`'s `baseUrl`. Change
+that line; the next deploy uses the new value everywhere.
Idempotent. Safe to re-run.
-
-Path resolution is absolute: the script lives at
-`/publish/quartz-patches/apply.py` and writes to
-`/site/quartz/components/scripts/spa.inline.ts`.
"""
from __future__ import annotations
+import re
+import sys
from pathlib import Path
SCRIPT = Path(__file__).resolve()
VAULT = SCRIPT.parents[2]
-SPA_FILE = (
- VAULT
- / "site"
- / "quartz"
- / "components"
- / "scripts"
- / "spa.inline.ts"
+QUARTZ_REPO = VAULT / "site" / "quartz"
+SPA_FILE = QUARTZ_REPO / "components" / "scripts" / "spa.inline.ts"
+PUBLIC = VAULT / "site" / "public"
+OVERLAY = VAULT / "publish" / "quartz.config.yaml"
+
+
+def read_base_url() -> tuple[str, str]:
+ """Read `baseUrl` from the overlay (YAML) as the single source of truth.
+
+ Falls back to lenient line scan if YAML parsing isn't available.
+ """
+ text = OVERLAY.read_text(encoding="utf-8")
+ host = ""
+ for line in text.splitlines():
+ stripped = line.strip()
+ if stripped.startswith("#") or not stripped:
+ continue
+ if stripped.startswith("baseUrl:"):
+ host = stripped.split(":", 1)[1].strip()
+ break
+ if not host or "/" not in host:
+ raise SystemExit(f"missing baseUrl in {OVERLAY}")
+ if host.startswith("http://"):
+ scheme = "http://"
+ host = host[len("http://") :]
+ elif host.startswith("https://"):
+ scheme = "https://"
+ host = host[len("https://") :]
+ else:
+ scheme = "https://"
+ domain, _, path = host.partition("/")
+ abs_base = (
+ f"{scheme}{domain}/{path.rstrip('/')}/" if path else f"{scheme}{domain}/"
+ )
+ rel_base = "/" + path.rstrip("/") + "/" if path else "/"
+ return abs_base, rel_base
+
+
+ABS_BASE, REL_BASE = read_base_url()
+HEAD_RE = re.compile(r"(]*>)", re.IGNORECASE)
+BASE_TAG = f'\n'
+TITLE_LINK = re.compile(
+ r'(