Six follow-ups from the link-archive audit, all the items with a
code-shaped fix. Behaviour verified per feature (stubbed + live where a
network or browser was needed); ARCHIVE.md status notes updated alongside.
- suggest: `archive.py suggest` + `make archive-suggest` print works
cited in data/*.bib (url wins, DOI-only resolves to doi.org/...) but
absent from the manifest, as manifest-ready lines. Read-only, offline.
- aliases: optional `aliases:` manifest field for equivalent URLs no
normalisation can derive (DOI vs. landing URL). Enforced like canonical
URLs on both sides (archive.py pre-scan + Archive.hs validator);
ArchiveIndex drops alias keys matching a takedown. FIPS 203 now carries
its DOI form, so the simd paper's DOI citation resolves.
- check scheduling: systemd user timer (systemd/archive-check.{service,
timer}, symlink-installed) runs the rot scan daily. cmd_check gains an
offline canary guard so an unattended scan on an offline machine leaves
state untouched instead of mass-flipping entries to rotted.
- Wayback fallback: an original already dead at first fetch falls back to
its most recent existing Wayback capture (raw id_ bytes through the
normal pipeline), honouring a preserved X-Archive-Orig noarchive and
recording `fetched-from`. Dead-only, never during refresh.
- bibliography annotation: Filters.Archive exports annotateBlock;
Citations.hs applies it to each CSL-rendered entry, so bibliography
links get the same affordance / rotted-flip as body links.
- search-UI filter: archive "exclude/only" + "link status" filters on the
search page, backed by a new data/archive-meta.json. Scoped apart from
the epistemic `status` filter (own state, classes, labels) rather than
renamed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| archive | ||
| build | ||
| content | ||
| data | ||
| nginx | ||
| static | ||
| systemd | ||
| templates | ||
| tools | ||
| .env.example | ||
| .gitignore | ||
| .python-version | ||
| ARCHIVE.md | ||
| AUDIT-2026-06-09.md | ||
| AUDIT.md | ||
| HOMEPAGE.md | ||
| LICENSE | ||
| MARKS.md | ||
| Makefile | ||
| PHOTOGRAPHY.md | ||
| README.md | ||
| WRITING.md | ||
| cabal.project | ||
| cabal.project.freeze | ||
| levineuwirth.cabal | ||
| pyproject.toml | ||
| uv.lock | ||
README.md
levineuwirth.org
Personal site of Levi Neuwirth — essays, blog posts, poetry, fiction, and music.
Built with Hakyll and Pandoc,
with a custom build system in build/ and a Haskell + JS + Python toolchain.
Quickstart
make build # one-shot production build into _site/
make dev # dev build (drafts visible) + local server on :8000
make watch # Hakyll live-reload dev server (drafts visible)
make clean # cabal run site -- clean
make deploy # clean → build → sign → push → rsync to VPS
make build always runs make clean implicitly when invoked from make deploy.
For day-to-day work, prefer make dev (which serves the site on
http://localhost:8000) or make watch (Hakyll's live-reload preview server,
which rebuilds on save and serves the site locally).
Run make build any time you add or replace binary assets (JPEG/PNG
figures, PDFs, music assets). make dev and make watch skip the
convert-images.sh / pdf-thumbs preprocessing steps, so a fresh JPEG
will have no .webp companion and a fresh PDF will have no thumbnail
until a full make build regenerates them. Once the companions exist
they survive subsequent make dev runs.
Optional features
-
Similar-links and embeddings.
tools/embed.pyprecomputes page-level embeddings for the "Related" block. To enable:uv sync # creates .venv with sentence-transformers, faiss-cpuThe build silently skips embedding when
.venvis absent. -
Client-side semantic search. Downloads a quantized ONNX model used by
static/js/semantic-search.js(run once; files are gitignored):make download-model -
Image conversion.
make buildcallstools/convert-images.shto produce.webpcompanions next to every JPEG/PNG. Requirescwebp(libwebpon Arch,webpon Debian/Ubuntu). -
PDF thumbnails.
make pdf-thumbsgenerates first-page thumbnails for PDFs instatic/papers/usingpdftoppm(poppleron Arch,poppler-utilson Debian/Ubuntu). Skipped silently when missing.
Configuration
.env (gitignored, copy from .env.example) holds the GitHub PAT and
the VPS rsync target consumed by make deploy. Never commit it.
Repository layout
build/— Haskell build system (Hakyll rules, Pandoc filters, contexts). Seebuild/Filters/for the Pandoc AST transforms (sidenotes, wikilinks, transclusion, score embedding, viz, …).content/— authored Markdown (essays, blog, poetry, fiction, music).templates/— Hakyll/Pandoc HTML templates.static/— CSS, JS, fonts, images, vendored PDF.js.tools/— Python tooling (embeddings, importers) and shell scripts.data/— generated and source data (commonplace.yaml, annotations.json, bibliographies, similar-links.json).nginx/— vhost snippets shipped to the VPS (security-headers.conf,static-assets.conf,popup-proxy.conf). The live vhost on the VPS is the source of truth; seenginx/vhost.conf.examplefor the canonical structure and the include order these snippets expect.
Architecture pointers
build/Site.hsis the Hakyll rules entry point.build/Patterns.hsdefines canonical content patterns shared by Backlinks, Authors, Tags, and Site.build/Compilers.hswires the Pandoc filter chain into Hakyll.build/Filters/Images.hsdoes WebP<picture>wrapping; requires the.webpcompanions produced bytools/convert-images.sh.
License
See LICENSE.