wes 51b4033541 Reading map: measure the marker contrast instead of asserting it
The ember-scheme override was sky blue against the fuchsia quoted marker,
which measures deltaE 0.5 under deuteranopia — the two rings would have been
the same colour to a deuteranope, and telling them apart is the one job these
hues actually have. White is the only candidate clearing all three of that
scheme's marks (worst case 30.4; cyan managed 7.8, lime 8.5).

Also records honestly that amber vs the trace green is 4.8 under protanopia,
accepted on the same grounds the ember scheme already documents, and drops the
border-top when the reading widget is the rail's only child — on a page with
fewer than two headings it was a separator with nothing above it.

Counts in the module docstring were still 262/206; they are 273/215.
2026-08-14 08:54:46 -04:00

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' + nginx try_files $uri $uri.html)
  • folder indexes: /Folder/ (from Folder/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 relative ![md](images) both rewrite to flat /assets/<name> URLs; scripts/copy-assets.mjs resolves 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 themes
  • draft: 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:

  1. 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-shot marimo:latest container — same image and .env as the live server, so it hits the real databases. Output lands in notebooks-export/<slug>.html plus an index.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.
  2. scripts/copy-notebooks.mjs stages those into public/nb/<slug>.html.
  3. Astro reads index.json (src/lib/notebooks.mjs) to build /notebooks and /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.

S
Description
Astro rebuild of the digital garden (Quartz replacement) with Quartz-compatible URLs
Readme
851 KiB
Languages
Astro 52.9%
JavaScript 26.7%
Python 9.6%
CSS 6.5%
TypeScript 2.5%
Other 1.8%