0.9% (2 of 230), not the ~7% estimated before the endpoint was restricted to
indexed books — that filter, added for the byline fix, removed nearly every
orphan as a side effect.
Two standing markers, drawn as rings rather than brighter dots. The animated
trace head is already a bright point and comes to rest on exactly the newest
node at the end of every draw cycle — under prefers-reduced-motion it parks
there permanently — so a second filled dot at that coordinate would read as a
rendering fault. Outlines say "different kind of thing" without competing.
The newest highlight is the LAST trace entry, never the max of `t`: the
payload is pre-sorted, `t` is date-only, and many highlights share a day.
The quoted one is looked up by text_hash, which /highlights/random now
returns beside the quote. The two fetches race on every page view, so the
hash is published both as a global and as a `garden:quote` event — one covers
each order. About one highlight in fifteen has no node, because its source
book was never indexed; markQuoted returns false and nothing is drawn.
Rings are flat geometry parented to the rotating world, so they are
billboarded each frame or they would vanish edge-on twice a revolution.
Also adds the "Currently reading" rail widget. It says how long ago on
purpose: six of the ten books on that shelf have not been opened in months,
and without a relative date the thing looks broken rather than honest. Past
45 days it mutes itself. No cover — kobo.books.isbn is a sideloader's UUID
for most of the shelf and storygraph_links has no cover URL.
The TOC rail is now always rendered, since it carries the widget too; it was
conditional on having two headings, which would have hidden the widget on the
homepage. Hidden explicitly on wide notebook pages, where a third grid child
would wrap under the sidebar.
library-rag-export.service now regenerates reading_umap.json on cyrion
whenever the corpus or its located highlights move, writing straight into this
checkout. Keeping it tracked would defeat that: a generated file that changes
under git dirties the tree, which both flips auto-build.sh's change signature
and breaks the `git pull` at the top of every build tick — the reason
notebooks-export/ is already ignored, in that file's own words.
So it joins them: ignored, plus a find line in the signature block so the
5-minute tick still notices a new projection.
The cost is that a fresh checkout has no map until the service runs — on
cyrion that is within the hour, anywhere else never. ReadingMap.astro says so
where someone would hit it.
Two follow-ups on yesterday's hero.
The `prefers-reduced-motion` branch only skipped the trace *animation* — the
scene kept orbiting continuously, which is precisely what a reader who asked for
reduced motion is asking not to get, on the homepage no less. The cloud now
holds still; it still renders in full.
/knoebels was reachable only from the homepage crowd strip, and that strip is
hidden whenever the park API is unreachable — so an API outage made the page
unreachable from anywhere on the site. It gets a sidebar entry, the same way
/notebooks does, since neither is a vault note that buildNavTree could carry.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The homepage had two competing visuals once the reading map landed, and the
park heat map was in the header — above the hero. Moved to /knoebels; the
homepage keeps the one-line crowd verdict as a teaser that links there.
Extracted verbatim into KnoebelsMap.astro. Its styles stay `is:global`: every
element inside the <svg> is built with createElementNS and so never carries
Astro's scoping attribute, which would make a scoped `.knb-map text` selector
silently match nothing.
Homepage HTML drops ~4 KB and no longer fetches /knoebels/rides or the Esri
tiles; the strip's own /knoebels/now call is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Wayfinding anchors across the park regions instead of the busiest rides;
name-only, since the glow and hover already carry the waits.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Web-Mercator slippy projection for both tiles and rides (exact
alignment), dark tint to keep the garden tone, label halos for
legibility over imagery, required Esri attribution. Google tiles were
ruled out (ToS requires the Maps JS API + key/billing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One glow per ride from api.c0smere.net/knoebels/rides — live waits while
the park is open, typical-weekend-peak means otherwise. Color = wait
class (validated dark palette), glow radius = magnitude, top-5 direct
labels + native tooltips. Fail-closed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second row in the live strip fed by api.c0smere.net/knoebels/{now,outlook}
(knb-oracle pipeline): while the park is open, live avg wait + verdict with
a crowd-colored dot; while closed, the best upcoming day. Fail-closed.
Co-Authored-By: Claude Fable 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>
- deploy/rebuild-hook.py: tiny stdlib HTTP service (:18101) — button
page for the Homepage dashboard iframe, POST /rebuild runs
auto-build.sh --force, /status + /healthz for feedback/monitoring
- auto-build.sh: --force flag + flock so timer and manual builds
serialize
- [...slug].astro: skip the synthetic h1 when the note body already
opens with an identical h1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root-owned node_modules/dist/public from the container blocked git pull
(public/fonts could not be created). Chown once, then keep artifacts
user-owned via --user + HOME/npm cache in /tmp.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixed-dark theme matching blog.c0smere.net (Source theme + injections):
bg #0b0d12, white/64%-white text, accent green #218107 (links lifted to
#2fa50f for contrast). Self-hosted JetBrains Mono + Libre Baskerville —
the same woff2 files the blog serves. Site header with logo + nav
mirroring gh-head; serif-italic quotes; blog-style borderless tables;
single dark shiki/mermaid theme; homepage styled as a typographic feed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- deploy/auto-build.sh: git pull + rebuild only when the repo HEAD or any
vault file mtime changed (sha256 signature in .build_state); replaces
the Quartz update_quartz_docker.sh loop
- systemd user units (5-min cadence, matching the old Quartz timer)
- compose service/container renamed garden-next -> garden
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pulled client-side from /highlights/random (max_len=280); attribution
from the API's parsed title/author; hidden when the API is unreachable.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- two static quotes above the name line in every footer
- Toc component renders h1-h3 from Astro's render() headings as a sticky
right column on >=88rem screens (hidden below); scroll-position
highlighting via a small passive listener; shown only when a page has
2+ headings, so listings and short notes stay clean
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Widget + script + styles live in Base.astro now, so every page ticks;
index.astro is back to just rendering the vault homepage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- buildNavTree: every non-draft page nested by vault folder; folders link
to their (real or synthetic) index page and take its title when set
- NavTree component: recursive details/summary tree, no JS — folders on
the current path render open, current page gets aria-current styling
- layout: sticky left sidebar >=72rem, flows after the footer as a site
index on narrow screens
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>