The project index was the last hand-maintained duplicate of CV data, and it had already drifted: a superseded Weenix line count and an end date the CV disagreed with. It now renders from yaml-source/data/projects.yml, which grows from three entries to nine and gains `group` (drives the index's sections, first-appearance order, like now.yaml) and `links`. Pmacs, Levshell, and Epiphany join under "Personal" — self-directed tools rather than coursework or employment — written from the repos rather than from memory. They are web-only: personal infrastructure is not academic output, and the résumé is a full page. LeVCS keeps its essay; project writeups stay essays, since a writeup is an informal presentation of a project and not a record of it. web_visible is a third visibility axis alongside cv_visible and resume_visible, defaulting to cv_visible so nothing changes until it is set. The file already knew how to let the CV and résumé disagree about an entry; generating a third surface from the same data would otherwise have collapsed that, and a document handed to a chosen reader is not a page that gets crawled. This is what makes web-only projects expressible — and what would let the xAI entry sit quietly on the CV while staying off the site, if that is wanted later. It stays off both for now. Two LaTeX conversions added while moving the markdown prose into the YAML: $\rightarrow$ and $\sim$. The latter fixes a bug I introduced doing it — "~10 crates" meant "about ten", but bare ~ is LaTeX's non-breaking space, so it rendered "( 10 crates" and dropped the approximation. Weenix's description now carries the site's fuller tail (the CS 169 origin and the later pipes/preemption work) rather than the CV's terser "Brown CS 169." Single-sourcing means picking one, and this is the better one; the CV grows by a clause and the résumé still fits on one page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SUGesXiMmACsLBTGG1xuEU |
||
|---|---|---|
| archive | ||
| build | ||
| content | ||
| data | ||
| nginx | ||
| paper | ||
| static | ||
| systemd | ||
| templates | ||
| tests | ||
| tools | ||
| yaml-source | ||
| .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.