A third instance of the pattern notebooks-export/ and the reading payloads
already use: an artifact built somewhere with a toolchain this repo's
node:24-slim container does not have, dropped into a gitignored directory,
staged into public/ by a script, and picked up by auto-build.sh's signature on
the next tick. Nothing here needs a JDK, Go, or an opinion about the game --
cannons_reloaded's deploy/export-cannons.sh hands over a directory that is
already ready to serve.
Three things are load-bearing and easy to get wrong later:
The payload is at /cn/, not /cannons/. With build.format:'file' the page
builds to cannons.html, and try_files would then have a file and a directory
competing for the same URL. That is exactly why the notebook exports live at
/nb/ and not under /notebooks/.
nginx gets gzip_static for /cn/. nginx:alpine has `gzip on` commented out in
its base config, so without it the page ships 9MB instead of 3.3MB and nothing
anywhere reports that. It gets no-cache rather than an expiry, because
classes.js and cannons.wasm are stable filenames whose contents change on
every rebuild -- a long expiry would pair a stale client with a fresh server.
The page loads on click, not `loading="lazy"`. Lazy only defers until the
frame nears the viewport and this frame is above the fold, so a few MB would
download for anyone who opened the page to read the paragraph.
A missing export is a normal state throughout: copy-cannons.mjs stages
nothing, the page says so, and the sidebar link does not appear.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S4wdbGSVzvUDHBWRjSVxfr
An orbiting point cloud of all 8,424 indexed passages, with the 262 highlighted
ones lit and threaded in the order they were marked. Data comes from library-rag
(chunks + embeddings) projected to 3D with UMAP; the page never touches Postgres.
Projection is `airy` (n_neighbors=50, min_dist=0.8) and the palette is `frost` --
both chosen by eye against the alternatives. Note that airy shows the underlying
book-territory structure LESS than the tight projection the pre-flight measured,
so the 0.805 k-NN purity figure describes that one, not this.
Page weight: the wrapper adds 321 B gzip to the homepage's blocking load. `three`
(127 KB gz) and the payload (56 KB gz) are a dynamic chunk fetched after render.
It fails closed -- no WebGL, no payload, or a dead fetch leaves a plain page.
The payload is a static snapshot; the 5-minute auto-build rebuilds the site, not
the data. New reading reaches this map only when export.py is re-run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Notebooks from the marimo server can now appear in the garden. A notebook
opts in with an HTML comment in one of its markdown cells — invisible when
rendered, greppable in the .py source:
<!-- garden:publish
title: Dream of Spotification
order: 30
-->
Default-deny on purpose: garden.c0smere.net is public and the export bakes
each notebook's executed output into the page, not just its code.
export-notebooks.py also carries a NEVER_PUBLISH list (genome_*, coursework)
so a marker pasted into one of those refuses loudly instead of publishing.
Pipeline: export-notebooks.py runs each marked notebook in a one-shot
marimo container (same image/env/GPU as the live server, so it hits the real
databases) into notebooks-export/ + index.json; copy-notebooks.mjs stages
those to public/nb/; Astro reads index.json to build the pages and the
sidebar section.
Isolation choices worth keeping:
- Own timer and own lock, separate from the 5-min garden build — executing a
notebook takes minutes and must never hold up a build tick.
- Cached on notebook content; both index.json and .cache.json go through
write_if_changed, since auto-build.sh hashes mtimes under notebooks-export/
and an unconditional rewrite would force a full rebuild every 30 minutes.
- Per-notebook timeout; a failure keeps the previous export and continues.
- Runs against a throwaway copy of the notebook dir, so notebooks that write
scratch files don't dirty the notebooks repo.
- Notebooks are NOT injected into the garden collection — they aren't vault
notes, and doing so would move noteCount and the sitemap.
build.format:'file' makes the listing a file (notebooks.html) beside a
directory of detail pages. Verified against the running nginx: /notebooks and
/notebooks/<slug> resolve through the generic try_files but /notebooks/ does
not, so nginx.conf gets an explicit location for the trailing-slash form.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- footer: 'built <ts> · <hash> · <n> notes', hash linking to Gitea
(GARDEN_COMMIT passed in by build.sh; '+' marks dirty-tree builds)
- homepage header: live strip fed by api.c0smere.net/spotify/summary
(weekly aggregate + monthly top artist/genre), fail-closed like the
odometer and header quote
- /og/<slug>.png satori+resvg cards for every note + the homepage;
og:/twitter: meta in Base
- shiki github-dark-default (#0d1117 sits closer to the page ground)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>