A quote landing in the philosophy knot was effectively invisible. Two causes, and only the second one was obvious. Size. `PointsMaterial` has one size for the whole object, so the vertex shader is patched to take a per-vertex `aScale`. Quoted draws 2.1x, newest 1.5x — quoted is larger because it is the one a reader goes hunting for, having just read the passage in the header; the newest is ambient and nobody looks for it. Draw order, which was the real problem and which no hue or size would have fixed. Every highlight is one transparent Points object, so vertices draw in buffer order: a node marked early in the trace sits under the two dozen white sprites and the green trace lines that come after it, and gets blended away. Measured on the worst case in the picture — the most crowded Meditations node, 25 neighbours within 0.05 — the mark rendered at 20 saturated pixels, peaking at (209,114,225) against a target of (232,121,249). So the two marked nodes are drawn a second time into their own two-point buffer above the trace. Same position, same sprite, same colour as the tint underneath: the same mark rendered where nothing can cover it, not a badge stuck on top. That is 54 pixels peaking at (229,120,245) — essentially the pure colour, and visible at a glance in the zoom. An unused slot hides by holding scale 0, which collapses gl_PointSize, rather than by juggling draw ranges for two points. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
garden-astro
Astro rebuild of the digital garden at garden.c0smere.net, replacing
Quartz v4. Content stays in the Obsidian vault (Digital_Garden/ folder);
this repo is only the rendering pipeline.
URL compatibility
Built to be a drop-in replacement — every URL on the live Quartz site resolves identically here (verified against the live sitemap, 102/102):
- case preserved, spaces become
-, hyphens/em-dashes kept - leaf pages:
/Folder/Note-Name(build.format: 'file'+ nginxtry_files $uri $uri.html) - folder indexes:
/Folder/(fromFolder/index.md, or a synthetic listing page when no index exists)
Obsidian compatibility (src/lib/remark-obsidian.mjs)
[[wikilinks]]with aliases/anchors, resolved vault-path-first then by basename; unresolved links degrade to plain text![[image embeds]]and relativeboth rewrite to flat/assets/<name>URLs;scripts/copy-assets.mjsresolves the files (Digital_Garden first, then vault-wide) and copies them in at build time> [!type]callouts, KaTeX math, mermaid (lazy-loaded client-side only on pages that use it), shiki dual light/dark themesdraft:frontmatter excluded, including Quartz-style quoted"true"
Build
GARDEN_CONTENT=/path/to/vault/Digital_Garden npm run build
Defaults to the kotov vault copy. On cyrion use deploy/build.sh
(containerized node:24, vault mounted read-only), then
docker compose -f deploy/docker-compose.yml up -d serves dist/ on
port 18100, proxied as garden.c0smere.net (NPM public + Caddy internal).
marimo notebooks (/notebooks)
Notebooks from the marimo server (/home/nox/docker/marimo/notebooks/) can
be published into the garden. A notebook publishes only if it opts in, by
carrying this HTML comment in one of its markdown cells — invisible in the
rendered page, greppable in the .py source:
<!-- garden:publish
title: Dream of Spotification
description: A decade of Spotify history, worked two ways.
order: 30
-->
Only the garden:publish line is required. Optional keys: title,
description, order (sort weight, default 100), slug (URL override,
default derived from the filename), timeout (export seconds, default 600).
Delete the marker to unpublish — the next export run removes the page.
This is deliberately default-deny: garden.c0smere.net is public and the
export bakes each notebook's executed output into the page, not just its
code. deploy/export-notebooks.py also carries a NEVER_PUBLISH list
(genome_*, coursework) as a second latch, so a marker pasted into one of
those refuses loudly instead of publishing.
Pipeline:
deploy/export-notebooks.py(own timer,garden-notebooks-export.timer, every 30 min; own lock) scans for markers and runs each opted-in notebook in a one-shotmarimo:latestcontainer — same image and.envas the live server, so it hits the real databases. Output lands innotebooks-export/<slug>.htmlplus anindex.json.- Cached on notebook content: a run after no edits does no work.
- Per-notebook timeout, and a failure keeps the previous export and continues. A broken notebook can't take the garden down.
- It runs against a throwaway copy of the notebook dir, so notebooks that write scratch files don't dirty the notebooks git repo.
scripts/copy-notebooks.mjsstages those intopublic/nb/<slug>.html.- Astro reads
index.json(src/lib/notebooks.mjs) to build/notebooksand/notebooks/<slug>, which wrap the raw export in an iframe with the garden chrome, plus the sidebar section.
The raw export is at /nb/<slug>.html, deliberately not under
/notebooks — with build.format: 'file' the page and the raw file would
collide on the same URL.
URL shapes here are worth knowing, since build.format: 'file' makes the
listing a file (dist/notebooks.html) that sits beside a directory of
detail pages (dist/notebooks/<slug>.html). Verified against the running
nginx: /notebooks and /notebooks/<slug> resolve through the generic
try_files, but /notebooks/ does not — a bare directory has no
index.html. deploy/nginx.conf carries an explicit location = /notebooks/
so the trailing-slash form works like every other folder URL on the site.
Both notebooks-export/ and public/nb/ are gitignored: tracking them would
make every export dirty the tree, which flips auto-build.sh's change
signature and breaks the git pull at the top of each build tick. The
signature hashes notebooks-export/ separately instead.
Force a full re-export with deploy/export-notebooks.py --force.
Layout extras
Every page carries a left sidebar tree of the whole garden (folders
collapsible, current page highlighted; flows after the footer on narrow
screens) and the bandwidth odometer in the footer, fed by
https://api.c0smere.net/bandwidth/odometer. The widget animates at the
lifetime average rate from a fetched baseline — it never reflects live
throughput, and hides itself if the API is down.