Files
garden-astro/README.md
T
wesandClaude Opus 5 9da9a6cba2 Publish opted-in marimo notebooks at /notebooks
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>
2026-07-29 22:07:14 -04:00

5.0 KiB

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.