Commit Graph

43 Commits

Author SHA1 Message Date
Levi Neuwirth 56afdb867a Feature modules: URL normalization, Maybe-trust, proper medians
- Empty/all-comments manifest.yaml is the empty archive, not a fatal
  parse error (AUDIT §3.11)
- Backlinks normaliseUrl strips index.html like SimilarLinks, so links
  to canonical directory URLs invert again; Stats normUrl updated in
  lockstep (§3.12)
- PDF viewer file= query value percent-encoded (hand-rolled RFC 3986
  encoder; network-uri is not a dependency) (§3.13)
- Photography feed thumbnails embed for flat singles and series
  children, not just directory entries (§3.14)
- Marks trust is Maybe Int: missing confidence/evidence collapses the
  figure to the bare frame as documented, instead of a literal
  "0 TRUST"; result-shape glyph centers when no score (§3.15)
- Unknown catalog categories fold into one Other bucket; medians take
  the mean of middle elements; protocol-relative URLs excluded from
  backlinks; @string/@comment/@preamble skipped in BibTeX parsing;
  watch-staleness of the once-per-process archive reads documented;
  stale comments fixed (§3.16, §3.9)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:13:34 -04:00
Levi Neuwirth f254ce866e Filters: fence/code-span awareness, host matching, nested-header skip
- SourceRefs trigger whitelist aligned to the /source/ serving
  whitelist (drops content/, yaml-source/, broad static//tools//data
  prefixes; adds .bib); existsCached no longer memoizes non-existence,
  so files created under make watch are picked up (§2.5, §2.16)
- fill/stroke hex replacement is boundary-aware: #000080 and 8-digit
  RGBA forms can no longer be corrupted into currentColor80 (§2.12)
- Wikilinks/Transclusion/EmbedPdf skip fenced code blocks (shared
  CommonMark fence tracker), and wikilinks additionally skip inline
  code spans — the syntax-documentation essay now renders its own
  examples literally while live wikilinks still convert (verified both
  ways in output) (§2.13)
- domainIcon matches the extracted host by label suffix instead of
  substring-of-URL; extractHost also strips userinfo (§2.14)
- webpSrc escaped in srcset; internal PDF links no longer double-
  classified; Smallcaps/Archive header-skip now holds at every nesting
  depth via protect/restore walks (§2.17)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:13:08 -04:00
Levi Neuwirth c8eeaaa9bc Core build cleanups: guards, pattern unification, noResult hygiene
- Library page no longer hard-depends on content/library.md; deleting
  it degrades to no intro block (AUDIT §2.8)
- primaryPortalOf accepts scalar comma-form tags via getTags, matching
  the tag system (§2.9)
- allContent gains me/ and memento-mori/ so their outgoing links join
  the backlinks graph; photography exclusion now documented (§2.10)
- Paginated tag pages partition AND sort by the same revision-aware
  display date — cross-page order is monotone again (§2.11)
- New stripPrefixRoute replaces gsubRoute at 17 call sites: prefix-only
  stripping, no mid-path mangling; route inventory verified identical
  (§2.15)
- random-pages uses canonical patterns (collection poems randomizable);
  pattern literals replaced with Patterns imports; duplicate local
  poetry patterns deleted; flat/collection poetry rules merged (§2.17)
- noResult instead of empty-list/fail for tagLinksField, dotsField,
  abstract/description/summary/bibliography/further-reading, plus the
  confidence-trend, overall-score, has-score, has-movements, and
  movement-audio fields — no more empty wrappers or [ERROR] log noise
  for legitimately-absent values (§2.17)
- tagItemCtx composes siteCtx, so monograms render on tag pages (§2.17)
- readingTime ceilings (399 words -> 2 min); authorSlugify comment
  fixed to match behavior, code untouched for URL stability; stale
  portal-count comments corrected (§2.17)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 11:13:08 -04:00
Levi Neuwirth 8ca22a45d2 Sidenotes: emit the section.footnotes fallback the CSS expects
The filter consumes every Pandoc Note, so the "standard Pandoc-
generated section.footnotes" its doc claimed as the no-JS fallback
never existed — below 1500px with JS disabled, footnote content was
simply invisible (AUDIT §2.3). The filter now collects consumed notes
and appends the section itself: letter labels, jump targets for the
in-text refs (which now point at the visible fallback item), and
doc-backlink returns. sidenotes.js pairs ref/note by element id and
preventDefaults clicks, so behavior with JS is unchanged.

Verified in output: per-page item count matches inline sidenote count;
refs target #fn-<label>; backlinks target #snref-<label>.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 10:37:28 -04:00
Levi Neuwirth c68d03af31 Fix audit MEDs in feature modules
- Backlinks: handle Plain blocks (tight list items) and DefinitionList
  in link extraction — links in ordinary bullet lists were invisible to
  the backlinks system (AUDIT §3.3)
- Sidenotes: render note bodies with a KaTeX writer so footnote math
  reaches the client-side KaTeX pass instead of degrading to italics
  (§2.4)
- Archive: join manifest to provenance on normalised URLs like every
  other comparison in the system — an equivalent-form URL edit silently
  unpublished the page while links kept pointing at it (§3.6)
- Photography: flat singles get their basename as slug and root-level
  asset paths in map.json (§3.7); geo-precision now fails closed — an
  unrecognised value (typo'd "hidden") suppresses the pin instead of
  publishing rounded coordinates (§3.8)
- Stability: age is measured first-commit -> today, not the commit
  span, so quiet time stabilises a piece as documented (§3.4);
  history: entries are sorted newest-first by date regardless of
  authored order (§3.5); pinned pages format last-reviewed like the
  git branch (§3.10)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 09:43:25 -04:00
Levi Neuwirth 902e43ea19 Add /poetry/ and /fiction/ indexes; widen tag-collision guard
Nav, the home portal grid, and the library have linked both URLs since
the portals were added, but no rule generated either index — confirmed
404s in production (AUDIT §2.1). Both rules mirror the essays index;
fiction renders an empty list until content exists.

sectionOwnedTopLevelTags now lists every namespace owning a
<name>/index.html route, not just photography — Hakyll silently
overwrites on duplicate routes, so an essay tagged e.g. "music" would
have clobbered a real section landing (AUDIT §2.2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 09:25:50 -04:00
Levi Neuwirth 7ca937d98c Fix audit HIGHs/MEDs in build code
- ArchiveIndex: guard rawIndex/rawState with doesFileExist so a fresh
  clone (gitignored data/ JSONs absent) degrades to empty instead of
  crashing — the behavior the module doc already promised (AUDIT §1.2)
- Commonplace: decode YAML via encodeUtf8, not Char8.pack, which
  truncates codepoints above 0x7F (AUDIT §3.2)
- Stats: DayOfWeek is ISO-numbered (Mon=1..Sun=7); dowOf and weekStart
  assumed Mon=0..Sun=6, clipping every Sunday cell outside the heatmap
  viewBox and starting weeks on Sunday (AUDIT §3.1)
- Site: epistemicEntry now honors the proved/proven confidence sentinel
  like Contexts.overallScoreField (AUDIT §2.6)
- Contexts: affiliationField returns noResult instead of an empty list,
  so essays without affiliation no longer render an empty meta row
  (AUDIT §2.7)

Verified: full site build passes; proved page gets score=100 in
epistemic-meta.json; empty .meta-affiliation gone; heatmap rows
y=22..94 all inside the 104-high viewBox.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 09:21:30 -04:00
Levi Neuwirth 154b47a4cb Marks II: broader monogram coverage + audit-marks tool
Extends the Phase-1 monogram mark system to every long-form content
type (essays, blog posts, poems, fiction, music) and introduces a
coverage audit so gaps are visible.

* build/Marks.hs gains hasMonogram (predicate), monogramSvgFieldFor +
  hasMonogramFieldFor (for explicit-path callers like the /build/ and
  /stats/ pages). Contexts.hs exports hasMonogramField as a siteCtx
  boolean so templates can conditionally render the slot without
  emitting an empty <div>.
* essay.html, blog-post.html, reading.html: hoist the frontmatter
  block out of <main id="markdownBody"> so the monogram + epistemic
  marks render as wrapper chrome rather than indexable prose; left
  + right mark slots are now unconditional (CSS handles the empty
  state) so the layout is grid-stable across pieces.
* templates/partials/item-card.html: optional monogram chip on cards
  (item-card--has-monogram modifier), gated on $has-monogram$ so
  monogram-less pieces stay flush.
* build/Stats.hs grows a "Marks coverage" telemetry section: per-type
  pieces / monogram / epistemic-figure counts + a coverage rollup,
  rendered between epistemic and output on /build/.
* tools/audit-marks.py: coverage report (ASCII table) walking
  content/**/*.md, plus a pre-commit hook at
  tools/hooks/pre-commit-marks.sh that runs the same scan against
  newly-staged .md files. New `make audit-marks` runs the report
  manually; the hook gates commits.
* static/css/marks.css: layout for the new frontmatter slots and the
  item-card monogram chip.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 12:05:08 -04:00
Levi Neuwirth 77e31efdae Add link archive system: snapshots, backlinks, link-rot
Preserve external works the site cites against link rot, host them at
permanent /archive/<slug>/ URLs in site chrome, and treat them as
first-class citizens of the backlinks and similar-pages indexes.
Curated, not crawled: the author adds one line to archive/manifest.yaml
and the build fetches, hashes, snapshots, and indexes the work.

* archive/manifest.yaml + tools/archive.py (fetch / refresh / wayback /
  check / gc) — PDFs downloaded directly, HTML pages snapshotted with a
  vendored monolith (tools/bin/monolith @ 2.10.1) into a single
  self-contained file with the archive CSP and a noarchive robots meta
  injected. Per-entry PROVENANCE.json committed; gitignored .txt
  sidecars regenerated from the artifact's SHA-256.
* build/Archive.hs + build/ArchiveIndex.hs + build/Filters/Archive.hs
  — Hakyll rules for /archive/ and /archive/<slug>/, a body Pandoc
  filter that appends an archive affordance to live citations and
  flips dead ones to the local copy on archive.py check's asymmetric
  hysteresis (rotted needs 3 fails over >= 14 days; one ok recovers).
* build/Backlinks.hs — keeps archived external URLs through pass 1 and
  canonicalises them to /archive/<slug>/ in pass 2, producing a
  "Referenced by" section grouped by the fragment each citation
  targets. build/Stats.hs gains a "Link archive" telemetry block on
  /build/ (count, total size, median age, by-status / by-quality /
  by-visibility, orphans).
* Integrity: archive.py fetch and build/Archive.hs (via sha256sum)
  both re-hash every committed artifact, so a tampered file halts the
  build even with cabal invoked directly or no .venv present. refresh
  refuses to replace an uncommitted prior snapshot and rolls back
  atomically on any exit path. removed.yaml is honoured by fetch,
  wayback, and check using canonical-form (tracking-stripped,
  arXiv-canonicalised) comparison.
* visibility: private keeps an entry in-repo but undeployed.
  nginx/archive.conf emits X-Robots-Tag: noindex, noarchive for raw
  artifacts that cannot carry meta directives.

The full design, phase plan (1-5), and three refinement passes live
in ARCHIVE.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 10:06:33 -04:00
Levi Neuwirth 7f7c029601 Marks I 2026-05-07 23:51:14 -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 6286c82389 more prominent related pages 2026-05-03 14:01:14 -04:00
Levi Neuwirth f41311a3eb Inline code reference previews 2026-05-02 10:40:43 -04:00
Levi Neuwirth cd94227acb Spec dilemma 2026-05-01 21:22:01 -04:00
Levi Neuwirth 42ba2bf972 Current rework 2026-04-26 19:42:47 -04:00
Levi Neuwirth 6585573dae States/Context/Embeddings fixes 2026-04-26 11:22:57 -04:00
Levi Neuwirth 913a374fb2 Professional content refactor 2026-04-22 11:46:57 -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 acb3ae7066 visual enhancements 2026-04-15 22:25:38 -04:00
Levi Neuwirth e005380362 subdomain svg correction 2026-04-13 10:26:25 -04:00
Levi Neuwirth 41bbbd799b favicon, logo, internal popups 2026-04-12 14:57:01 -04:00
Levi Neuwirth e25a311dd9 filters + epistemic popups 2026-04-12 10:40:58 -04:00
Levi Neuwirth d113671e96 epistemic redo 2026-04-11 19:40:58 -04:00
Levi Neuwirth 1be9c7f3d1 wikilinks fix 2026-04-11 15:45:46 -04:00
Levi Neuwirth e5ed6a3bb4 library sorting 2026-04-11 15:10:48 -04:00
Levi Neuwirth 256808d2b2 sync semantic embeddings 2026-04-11 14:35:01 -04:00
Levi Neuwirth a358c8b246 audit: Haskell build system correctness + Patterns.hs + Stats blaze rewrite 2026-04-10 17:41:13 -04:00
Levi Neuwirth c864e2f9cc makefile corrections + esoteric math rendering 2026-04-05 12:00:07 -04:00
Levi Neuwirth 02b7694bfe forgejo links 2026-04-05 11:32:50 -04:00
Levi Neuwirth 9a01c602bc auto: 2026-04-05T01:18:45Z 2026-04-04 21:18:45 -04:00
Levi Neuwirth 9a3a5b62b6 type annotations for stats integers 2026-04-03 20:33:56 -04:00
Levi Neuwirth aee326bfec New page 2026-03-30 20:45:03 -04:00
Levi Neuwirth b06b1e741c popup improvements, citation fix 2026-03-29 08:02:04 -04:00
Levi Neuwirth a5495035be epistemic v2 2026-03-26 09:10:35 -04:00
Levi Neuwirth 728afd4c68 affiliation, cabal helper script 2026-03-26 08:14:50 -04:00
Levi Neuwirth 5cfbfbc0ef GPG signing, embedding pipeline, visualization filter, search timing, sig popups
- GPG page signing: dedicated signing subkey in ~/.gnupg-signing, sign-site.sh
  walks _site/**/*.html producing .sig files, preset-signing-passphrase.sh caches
  passphrase via gpg-preset-passphrase; make sign target; make deploy chains it
- Footer sig link: $url$.sig with hover popup showing ASCII armor (popups.js
  sigContent provider; .footer-sig-link bound explicitly to bypass footer exclusion)
- Public key at static/gpg/pubkey.asc
- Embedding pipeline: tools/embed.py encodes _site pages with nomic-embed-text-v1.5
  + FAISS IndexFlatIP, writes data/similar-links.json; staleness check skips when
  JSON is newer than all HTML; make build invokes via uv, skips gracefully if .venv absent
- SimilarLinks.hs: similarLinksField loads similar-links.json with Hakyll dependency
  tracking; renders Related section in page-footer.html
- uv environment: pyproject.toml + uv.lock (CPU-only torch via pytorch-cpu index)
- Visualization filter: Filters/Viz.hs runs Python scripts for .figure (SVG) and
  .visualization (Vega-Lite JSON) fenced divs; viz.js renders with monochrome config
  and MutationObserver dark-mode re-render; viz.css layout
- Search timing: #search-timing element shows elapsed ms via MutationObserver
- Build telemetry timestamps removed from git tracking (now in .gitignore)
- spec.md updated to v9; WRITING.md updated with viz, related, signing, build docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 20:14:49 -04:00
Levi Neuwirth 26c067147a Build telemetry 2026-03-19 15:27:12 -04:00
Levi Neuwirth 714824a0b5 initial deploy! whoop 2026-03-17 21:56:14 -04:00