# 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/` 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 ```sh 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: ``` ``` 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/.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/.html`. 3. Astro reads `index.json` (`src/lib/notebooks.mjs`) to build `/notebooks` and `/notebooks/`, which wrap the raw export in an iframe with the garden chrome, plus the sidebar section. The raw export is at `/nb/.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/.html`). Verified against the running nginx: `/notebooks` and `/notebooks/` 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.