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>
72 lines
1.7 KiB
Plaintext
72 lines
1.7 KiB
Plaintext
---
|
|
import Base from '../../layouts/Base.astro';
|
|
import { loadNotebooks, notebookUrl } from '../../lib/notebooks.mjs';
|
|
|
|
const notebooks = loadNotebooks();
|
|
const fmt = (iso) =>
|
|
iso
|
|
? new Date(iso).toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric',
|
|
timeZone: 'America/New_York',
|
|
})
|
|
: null;
|
|
---
|
|
|
|
<Base
|
|
title="notebooks"
|
|
description="Interactive marimo notebooks — data science work from the homelab, executed against live data and snapshotted."
|
|
>
|
|
<h1>notebooks</h1>
|
|
<p>
|
|
marimo notebooks from the homelab, re-executed against their real data
|
|
sources on a schedule and snapshotted here. Code is shown alongside its
|
|
output — these are the working documents, not writeups.
|
|
</p>
|
|
|
|
{
|
|
notebooks.length === 0 ? (
|
|
<p class="nb-empty">No notebooks are published right now.</p>
|
|
) : (
|
|
<ul class="nb-list">
|
|
{notebooks.map((nb) => (
|
|
<li>
|
|
<a href={notebookUrl(nb.slug)}>{nb.title}</a>
|
|
{nb.description && <p class="nb-desc">{nb.description}</p>}
|
|
{fmt(nb.exportedAt) && (
|
|
<p class="nb-meta">last run {fmt(nb.exportedAt)}</p>
|
|
)}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)
|
|
}
|
|
|
|
<style>
|
|
.nb-list {
|
|
list-style: none;
|
|
margin: 2rem 0 0;
|
|
padding: 0;
|
|
}
|
|
.nb-list li {
|
|
margin: 0 0 1.6rem;
|
|
padding-left: 0.9rem;
|
|
border-left: 2px solid var(--hair);
|
|
}
|
|
.nb-list > li > a {
|
|
font-size: 1.05rem;
|
|
}
|
|
.nb-desc {
|
|
margin: 0.3rem 0 0;
|
|
color: var(--muted);
|
|
}
|
|
.nb-meta,
|
|
.nb-empty {
|
|
margin: 0.3rem 0 0;
|
|
font-size: 0.75rem;
|
|
color: var(--muted);
|
|
}
|
|
</style>
|
|
</Base>
|