Commit Graph

107 Commits

Author SHA1 Message Date
Levi Neuwirth 3e76833aac Validate tool inputs and surface tracebacks on errors
- import-photo.sh: validate \$SLUG against ^[a-z0-9-]+\$ before
  writing under content/photography/; rejects '../' and other
  surprises early. Also fail loudly with a clear message if either
  'magick' resize or 'magick mogrify -strip' returns non-zero
  (prevents shipping a file that still carries EXIF when the strip
  silently failed).
- compress-assets.sh: reject non-numeric MIN_SIZE up front (otherwise
  the comparison fails later with a cryptic arithmetic error).
- extract-dimensions.py / extract-exif.py / extract-palette.py:
  add traceback.print_exc() after the broad except so a corrupt
  image produces a stack trace alongside the one-line summary.
- extract-exif.py: switch from Pillow's deprecated _getexif() to
  the public getexif() API; pyproject allows Pillow up to 12 where
  _getexif may be removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:09:02 -04:00
Levi Neuwirth 0379dda908 Degrade gracefully on corrupt backlinks JSON
If data/backlinks.json fails to parse, every page that uses the
backlinks context aborts with 'fail'. The JSON is build-generated;
corruption is unlikely but not impossible (interrupted writer, disk
issue). Switch to noResult so the affected pages render without the
backlinks block instead of failing the whole build. The next clean
build regenerates the JSON.

Note: commonplace.yaml and now.yaml deliberately keep fail-fast —
they're hand-edited and silent fallbacks would mask author typos.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:08:54 -04:00
Levi Neuwirth 725fa17f6a Tighten partial patterns and switch to strict file reads in build/
- Stats.hs: median uses (!!) directly after the empty-case equation,
  dropping the unreachable empty-fallback arm.
- Stats.hs + BibExtras.hs: switch lazy readFile to strict readFile'
  (System.IO). Lazy IO leaves handles open until the value is forced;
  errors surface at unpredictable points and the em-dash fallback in
  Stats can hide real I/O failures. Strict reads fail at the read.
- Stability.hs: stabilityFromDates uses 'last dates' directly, since
  the (newest:_) pattern guarantees non-empty input.
  versionHistoryRangeField and versionHistoryRangeEndField bind the
  matched list as 'es' and call 'last es', dropping the
  reconstruction of (newest : more) just to call last on it.
- Tags.hs: parentOf is a 3-arm case (\[\], \[_\], segs) instead of a
  length-based guard around 'init segs'.
- Catalog.hs: renderGroup re-orders so the structurally-guaranteed
  (e:_) arm is matched first; the empty arm stays as a coverage stub
  with a comment noting it's unreachable per groupBy's contract.
- Utils.hs: trim uses dropWhileEnd instead of double-reverse.

All sites were runtime-safe before; the changes make the safety
structural and shorter to read.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:08:47 -04:00
Levi Neuwirth a818b7df9b Add robots.txt and sitemap.xml; tidy essay-route prefix-strip
- Emit a minimal robots.txt that points at the sitemap.
- Emit sitemap.xml covering every dated content page (essays, blog,
  fiction, poetry, music) with absolute <loc> and frontmatter-derived
  <lastmod>. Standalone pages (about, colophon, etc.) are
  intentionally omitted: they're reachable via the main nav, lack
  date: frontmatter, and would force a fallback lastmod that
  misrepresents staleness.
- Replace the magic 'drop 8' offset in essay routing with
  stripPrefix "content/". Same behavior, but reads structurally and
  fails closed if the prefix ever changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:08:33 -04:00
Levi Neuwirth 339433db20 Quote rsync target variables in Makefile deploy
A VPS_PATH containing whitespace or shell metacharacters would split
on the unquoted expansion and hand rsync extra arguments. The
existing VPS_PATH guard rejects obviously dangerous parents (/srv,
/var, etc.) but does not catch this. Quoting fails closed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:08:23 -04:00
Levi Neuwirth c7a588d769 Bump requires-python floor to 3.14
Matches .python-version and the actual development environment.
uv.lock already declared >=3.12 but the project has been on 3.14 for
a while; the floor was just stale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:08:20 -04:00
Levi Neuwirth eb7fef30df Pin Hugging Face model revisions for downloader and embed pipeline
- Add tools/model-checksums.sha256 with sha256 hashes for the five
  Xenova/all-MiniLM-L6-v2 files served from static/models/.
  download-model.sh was already plumbed to verify against this file
  when present; the file itself was missing, so downloads were
  unverified. Now every fetch checks against committed hashes and
  fails closed on mismatch.
- Pin embed.py's SentenceTransformer load to a specific HF commit
  (c9745ed1d9f207416be6d2e6f8de32d1f16199bf of
  sentence-transformers/all-MiniLM-L6-v2). A future model bump can no
  longer silently change embedding semantics across builds. Bump
  deliberately when validating; re-run a full embed pass to refresh
  the semantic + similar-links data.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:08:14 -04:00
Levi Neuwirth 87819501a5 nginx: ship security baseline, reference vhost, and tighter cache
- Add nginx/security-headers.conf — server_tokens off, HSTS (1y +
  preload), X-Content-Type-Options, X-Frame-Options DENY,
  Referrer-Policy, Permissions-Policy, and a usage-scoped CSP. CSP
  ships in Report-Only mode; promote to enforcing once the report
  stream is clean for a week. CSP allowlists are derived from actual
  usage (cdn.jsdelivr.net for KaTeX/Vega, *.basemaps.cartocdn.com for
  Leaflet tiles); 'unsafe-inline' and 'unsafe-eval' are documented
  inline.
- Add nginx/vhost.conf.example — reference vhost showing the canonical
  include order. The live vhost on the VPS remains the source of
  truth; this file documents the structure so the VPS config can be
  reproduced or audited from the repo.
- Shorten unfingerprinted CSS/JS cache from 24h to 1h. Bug fixes ship
  to warm clients within an hour; if assets are ever fingerprinted,
  this can move to immutable.
- Refresh README repo layout — add nginx/ entry, drop stale paper/
  and spec.md references that never existed in the working tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:08:03 -04:00
Levi Neuwirth fd84b7e6d2 Add AUDIT.md
Comprehensive audit of the repo flagging HIGH/MED/LOW/NIT issues across
the Haskell build, Python and shell tools, content + frontmatter,
templates, static assets, nginx snippets, README, .env, and gitignore.
Records what was fixed, what was deferred, and the recommended fix
order so future me has a paper trail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:07:48 -04:00
Levi Neuwirth 670d477cd6 auto: 2026-05-06T16:35:22Z [skip ci] 2026-05-06 12:35:22 -04:00
Levi Neuwirth be6aca9214 auto: 2026-05-06T16:29:30Z [skip ci] 2026-05-06 12:29:30 -04:00
Levi Neuwirth 9a343f16b0 Branch preprint 2026-05-06 12:21:59 -04:00
Levi Neuwirth 998e4a9b51 auto: 2026-05-06T16:09:51Z [skip ci] 2026-05-06 12:09:51 -04:00
Levi Neuwirth e4cf152066 auto: 2026-05-05T17:23:31Z [skip ci] 2026-05-05 13:23:31 -04:00
Levi Neuwirth 977c1cecbb yum cappuccino yay! 2026-05-03 21:16:58 -04:00
Levi Neuwirth 6286c82389 more prominent related pages 2026-05-03 14:01:14 -04:00
Levi Neuwirth 22b530c26d auto: 2026-05-03T16:57:50Z [skip ci] 2026-05-03 12:57:50 -04:00
Levi Neuwirth f41311a3eb Inline code reference previews 2026-05-02 10:40:43 -04:00
Levi Neuwirth b12f6cc387 auto: 2026-05-02T13:07:26Z [skip ci] 2026-05-02 09:07:26 -04:00
Levi Neuwirth 0fc9fec708 auto: 2026-05-02T12:45:20Z [skip ci] 2026-05-02 08:45:20 -04:00
Levi Neuwirth cd94227acb Spec dilemma 2026-05-01 21:22:01 -04:00
Levi Neuwirth 3a5326d92d auto: 2026-05-01T16:17:51Z [skip ci] 2026-05-01 12:17:51 -04:00
Levi Neuwirth 968b810394 auto: 2026-05-01T15:47:38Z [skip ci] 2026-05-01 11:47:38 -04:00
Levi Neuwirth 04e4aff641 auto: 2026-04-29T01:17:30Z [skip ci] 2026-04-28 21:17:30 -04:00
Levi Neuwirth 1c856cd6f8 auto: 2026-04-27T18:46:44Z [skip ci] 2026-04-27 14:46:44 -04:00
Levi Neuwirth 42ba2bf972 Current rework 2026-04-26 19:42:47 -04:00
Levi Neuwirth 53e053e9a7 auto: 2026-04-26T23:24:06Z [skip ci] 2026-04-26 19:24:06 -04:00
Levi Neuwirth 5cb6795a7a auto: 2026-04-26T20:47:12Z [skip ci] 2026-04-26 16:47:12 -04:00
Levi Neuwirth 370f81217c auto: 2026-04-26T15:31:49Z [skip ci] 2026-04-26 11:31:49 -04:00
Levi Neuwirth 6585573dae States/Context/Embeddings fixes 2026-04-26 11:22:57 -04:00
Levi Neuwirth 6d2f9d12ae PDF compression 2026-04-22 12:40:22 -04:00
Levi Neuwirth 3a95a05284 Fix broken PDF hyperlinks 2026-04-22 12:10:31 -04:00
Levi Neuwirth 40ba09209c Professional content refactor 2 2026-04-22 11:56:25 -04:00
Levi Neuwirth 913a374fb2 Professional content refactor 2026-04-22 11:46:57 -04:00
Levi Neuwirth e969461ca3 auto: 2026-04-21T02:24:58Z 2026-04-20 22:24:58 -04:00
Levi Neuwirth c2e8737c6e auto: 2026-04-21T01:22:39Z 2026-04-20 21:22:39 -04:00
Levi Neuwirth daa0ea4c3c library: fine-press typography + muted-warm palette
Scoped warm accent tokens (--library-accent and friends) defined at
:root inside library.css — page-scoped since the file only loads on
/library.html and /search.html. Section headings become Spectral
small-caps chapter markers at 1.2rem in the accent color, so the
ornament span inherits it via currentColor. Divider and "more on
this shelf" link pick up the muted variant. The leading blockquote
gets an epigraph treatment: narrower measure, italic, warm intro
ink, with the attribution line dropping to the accent-muted tone.
Card-level refinements (oldstyle figures on dates, small-caps on
item-kind) scoped via .library-section so /new.html and tag pages
retain their lining-figure UI treatment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:21:47 -04:00
Levi Neuwirth 0221603766 library: portal ornaments + inter-shelf divider
Each shelf gets a dingbat keyed by portal slug: laurel (research),
quill (nonfiction), open book (fiction), lyre (poetry), plus the
existing clef / ai / tech / trefoil glyphs for the remaining four.
Rendered via mask-image with currentColor so a single SVG per
portal inherits whatever color its heading carries. Between rendered
shelves, a centered fleuron flanked by thin rules (library-divider.svg)
sits via CSS adjacent-sibling so hidden sections leave no orphan
dividers. The template swaps its Unicode placeholder for a
data-ornament span, wires a '\$library-intro\$' slot above the shelves,
and renders a "More on this shelf →" link when has-more gates fire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:20:35 -04:00
Levi Neuwirth c877d8c9c6 library: sidecar-driven curation plumbing
Adds the library infrastructure without visible change to the rendered
page: a 'featured:' list in each portal's tag-meta sidecar drives shelf
curation (up to 5, default cap 4, recency fills the rest), a content/
library.md snapshot feeds a '\$library-intro\$' slot for a leading
blockquote, and '\$<slug>-has-more\$' gates expose whether the unfiltered
portal overflows the shelf. Items are now loaded once and partitioned
by primary portal rather than scanned per-section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 21:19:36 -04:00
Levi Neuwirth 908136b646 Navigation refactor 2026-04-19 14:35:41 -04:00
Levi Neuwirth 237380c4be date data 2026-04-17 15:15:04 -04:00
Levi Neuwirth 1a532f881b major visual changes - dingbats, footer, etc 2026-04-17 12:48:22 -04:00
Levi Neuwirth 7fbc4f8935 auto: 2026-04-16T02:43:50Z 2026-04-15 22:43:50 -04:00
Levi Neuwirth acb3ae7066 visual enhancements 2026-04-15 22:25:38 -04:00
Levi Neuwirth ec19367eaf auto: 2026-04-16T02:18:20Z 2026-04-15 22:18:20 -04:00
Levi Neuwirth 03babfb02a new mobile fix 2026-04-13 11:05:01 -04:00
Levi Neuwirth e005380362 subdomain svg correction 2026-04-13 10:26:25 -04:00
Levi Neuwirth 93d909893d auto: 2026-04-12T20:18:32Z 2026-04-12 16:18:32 -04:00
Levi Neuwirth 4186c6285b auto: 2026-04-12T20:06:09Z 2026-04-12 16:06:09 -04:00
Levi Neuwirth 4752f9e799 auto: 2026-04-12T20:05:04Z 2026-04-12 16:05:04 -04:00