demolab-cli 2.3.0__tar.gz → 2.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of demolab-cli might be problematic. Click here for more details.
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/PKG-INFO +4 -4
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/README.md +2 -2
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/AGENT.md +3 -3
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/CHANGELOG.md +17 -0
- demolab_cli-2.5.0/demolab_cli/VERSION +1 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/build.py +41 -12
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/cli.py +1 -1
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/guides/GLOSSARY.md +3 -3
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/guides/RULES.md +21 -4
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/guides/STRUCTURE.md +2 -2
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/runbooks/GETTING-STARTED.md +4 -2
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/demolab.yaml +5 -1
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar025.typ +9 -2
- demolab_cli-2.5.0/demolab_cli/scaffold/demo/writings/ar028.typ +148 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/root/README.md +2 -2
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/skeleton/demolab.yaml +8 -1
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/slides.py +2 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/test_engine_build.py +66 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/test_slides.py +9 -1
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/typ/lib.typ +31 -14
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/typ/main.typ +9 -6
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/typ/style.css +100 -0
- demolab_cli-2.3.0/demolab_cli/VERSION +0 -1
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/.gitattributes +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/.github/workflows/landing-preview.yml +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/.github/workflows/landing.yml +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/.github/workflows/publish.yml +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/.github/workflows/tests.yml +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/.gitignore +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/AGENTS.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/CLAUDE.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/DEVELOPING.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/LICENSE +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/__init__.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/_paths.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/deploy/deploy.yml +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/deploy/preview.yml +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/devserver.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/guides/AUTORESEARCH-RULES.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/guides/HOUSESTYLE.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/guides/SLIDES.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/guides/SUPPORT.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/overlay.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/runbooks/AUTORESEARCH.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/runbooks/DOCTOR.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/runbooks/EMBED-DOCS.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/runbooks/FROM-JUPYTER.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/runbooks/LINT.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/runbooks/LITERATURE-SEARCH.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/runbooks/MIGRATE-CODE.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/runbooks/MIGRATE-STACK.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/runbooks/TOUR.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/runbooks/UPDATE.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/data/ar018/swe-bench.svg +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar004.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar005.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar010.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar011.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar012.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar013.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar014.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar015.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar016.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar017.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar018.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar019.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar020.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar021.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar022.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar023.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar024.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar025.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar026.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar027.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/artifacts/pdfs/book.pdf +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/site/CNAME +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/site/landing.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar004.slide.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar005.slide.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar010.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar011.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar012.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar013.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar014.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar015.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar016.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar017.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar018.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar019.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar020.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar021.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar022.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar023.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar024.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar026.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/demo/writings/ar027.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/root/AGENTS.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/root/CLAUDE.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/root/github/workflows/tests.yml +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/root/gitignore +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/root/pyproject.toml +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/skeleton/.gitattributes +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/skeleton/HOUSESTYLE.local.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/skeleton/artifacts/data/.gitkeep +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/skeleton/artifacts/pdfs/.gitkeep +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/skeleton/experiments/helpers/__init__.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/skeleton/experiments/helpers/provenance.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/skeleton/experiments/helpers/style.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/skeleton/experiments/helpers/test_provenance.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/skeleton/tools/.gitkeep +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/skeleton/writings/.gitkeep +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/starters/monte-carlo-pi/README.md +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/starters/monte-carlo-pi/exp000.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/scaffold/starters/monte-carlo-pi/exp000.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/test_build_resilience.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/test_cli.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/test_command_catalog.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/test_devserver.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/test_init_and_staging.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/test_overlay.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/test_slide_catalog.py +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/typ/cite-popover.js +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/typ/entry.typ +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/demolab_cli/typ/favicon.svg +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/pyproject.toml +0 -0
- {demolab_cli-2.3.0 → demolab_cli-2.5.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: demolab-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Lab notebook for computational science: Python tools produce per-run artifacts; Typst publishes them as a web bundle, per-entry PDFs, and a book.
|
|
5
5
|
Project-URL: Homepage, https://github.com/eoinmurray/demolab
|
|
6
6
|
Project-URL: Changelog, https://github.com/eoinmurray/demolab/blob/main/demolab_cli/CHANGELOG.md
|
|
@@ -35,7 +35,7 @@ Description-Content-Type: text/markdown
|
|
|
35
35
|
|
|
36
36
|
> **Coding agent setting someone up?** Run `uvx demolab-cli docs GETTING-STARTED`, read the file it prints, and follow it strictly, step by step. *(Human, setting up by hand? See [Quickstart](#quickstart).)*
|
|
37
37
|
|
|
38
|
-
You write a model or experiment once as a small program. Demolab runs it, captures everything it produced, stamps it with the exact code version, and publishes a clean page — figures, numbers, and real typeset maths — to
|
|
38
|
+
You write a model or experiment once as a small program. Demolab runs it, captures everything it produced, stamps it with the exact code version, and publishes a clean page — figures, numbers, and real typeset maths — to the web and, by default, PDF. The numbers on the page are read from the run, so they can't drift. You drive it all by talking to a coding agent; there's no web development or build config to touch.
|
|
39
39
|
|
|
40
40
|
## Quickstart
|
|
41
41
|
|
|
@@ -78,7 +78,7 @@ Open your lab in your agent and say a runbook's name — it follows that runbook
|
|
|
78
78
|
|
|
79
79
|
## How it works
|
|
80
80
|
|
|
81
|
-
One decoupled loop: **a tool computes → drops data → an experiment writes it up → the site publishes it.** Every run records its exact parameters and the git commit it came from (stamped on the page), and tables read their numbers straight from the run — so prose and results can't disagree. A single Typst pass emits a website, a PDF per entry
|
|
81
|
+
One decoupled loop: **a tool computes → drops data → an experiment writes it up → the site publishes it.** Every run records its exact parameters and the git commit it came from (stamped on the page), and tables read their numbers straight from the run — so prose and results can't disagree. A single Typst pass emits a website and, unless `pdfs: false` is set in `demolab.yaml`, a PDF per entry and a book, all sharing the same live numbers.
|
|
82
82
|
|
|
83
83
|
The detail lives in the guides (in a lab: `demolab docs <NAME>`; in this repo, the files under `demolab_cli/guides/`):
|
|
84
84
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
> **Coding agent setting someone up?** Run `uvx demolab-cli docs GETTING-STARTED`, read the file it prints, and follow it strictly, step by step. *(Human, setting up by hand? See [Quickstart](#quickstart).)*
|
|
6
6
|
|
|
7
|
-
You write a model or experiment once as a small program. Demolab runs it, captures everything it produced, stamps it with the exact code version, and publishes a clean page — figures, numbers, and real typeset maths — to
|
|
7
|
+
You write a model or experiment once as a small program. Demolab runs it, captures everything it produced, stamps it with the exact code version, and publishes a clean page — figures, numbers, and real typeset maths — to the web and, by default, PDF. The numbers on the page are read from the run, so they can't drift. You drive it all by talking to a coding agent; there's no web development or build config to touch.
|
|
8
8
|
|
|
9
9
|
## Quickstart
|
|
10
10
|
|
|
@@ -47,7 +47,7 @@ Open your lab in your agent and say a runbook's name — it follows that runbook
|
|
|
47
47
|
|
|
48
48
|
## How it works
|
|
49
49
|
|
|
50
|
-
One decoupled loop: **a tool computes → drops data → an experiment writes it up → the site publishes it.** Every run records its exact parameters and the git commit it came from (stamped on the page), and tables read their numbers straight from the run — so prose and results can't disagree. A single Typst pass emits a website, a PDF per entry
|
|
50
|
+
One decoupled loop: **a tool computes → drops data → an experiment writes it up → the site publishes it.** Every run records its exact parameters and the git commit it came from (stamped on the page), and tables read their numbers straight from the run — so prose and results can't disagree. A single Typst pass emits a website and, unless `pdfs: false` is set in `demolab.yaml`, a PDF per entry and a book, all sharing the same live numbers.
|
|
51
51
|
|
|
52
52
|
The detail lives in the guides (in a lab: `demolab docs <NAME>`; in this repo, the files under `demolab_cli/guides/`):
|
|
53
53
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
You are operating a **demolab lab**: an agent-operated lab notebook for computational
|
|
4
4
|
science. The loop: a tool computes → drops data under `artifacts/data/<id>/` → an
|
|
5
5
|
experiment writes it up in `writings/<id>.typ` → `demolab build` publishes it all as a
|
|
6
|
-
website, per-entry PDFs
|
|
6
|
+
website, plus per-entry PDFs and a book unless the lab opts out. Every number on a page is read from the run that
|
|
7
7
|
produced it — nothing is hand-typed, so prose and results can't drift.
|
|
8
8
|
|
|
9
9
|
This manual ships inside the `demolab-cli` package and is printed by `demolab docs`, so it
|
|
@@ -13,7 +13,7 @@ short.
|
|
|
13
13
|
## The layout, in one breath
|
|
14
14
|
|
|
15
15
|
`tools/` (reusable science), `experiments/` (runners), `writings/` (the .typ writeups),
|
|
16
|
-
`artifacts/` (the committed record: `data/` + `pdfs/`; `site/` is a gitignored build),
|
|
16
|
+
`artifacts/` (the committed record: `data/` + optional `pdfs/`; `site/` is a gitignored build),
|
|
17
17
|
`demolab.yaml` (branding — and the lab-root marker every `demolab` command walks up to).
|
|
18
18
|
The engine lives in the installed package, not the lab; the gitignored `.demolab/` dir is
|
|
19
19
|
machine-managed staging. Full tree: `demolab docs STRUCTURE`.
|
|
@@ -71,7 +71,7 @@ guide covers it, ground your answer in it — cite it or offer to start it.
|
|
|
71
71
|
`demolab` lists them all. The loop: `uv run python experiments/expNNN.py` (run an experiment
|
|
72
72
|
end-to-end — run the runner directly, there's no `demolab run` wrapper) · `demolab dev`
|
|
73
73
|
(live-preview server — have the *user* run it in their own terminal) · `demolab build`
|
|
74
|
-
(
|
|
74
|
+
(site + optional PDFs) · `demolab build <id>` (one entry PDF, when enabled) · `demolab test`. Preview
|
|
75
75
|
PDFs stay in the ignored site tree; run the complete build before publishing. Reference data ships in the package:
|
|
76
76
|
`demolab docs STARTERS` (canonical first experiments — model file shapes on them, never copy
|
|
77
77
|
blindly; `monte-carlo-pi` is the default) and `demolab docs DEMO` (the published docs site's
|
|
@@ -13,6 +13,23 @@ the runbook shows the entries between your version and the latest.
|
|
|
13
13
|
|
|
14
14
|
## [Unreleased]
|
|
15
15
|
|
|
16
|
+
## [2.5.0] — 2026-08-23
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **PDF publishing can be disabled for web-only labs.** Set `pdfs: false` in
|
|
20
|
+
`demolab.yaml` to omit per-entry PDFs, the combined book, PDF links, and PDF-only slide
|
|
21
|
+
decks. Existing files under `artifacts/pdfs/` are preserved; the default remains enabled.
|
|
22
|
+
|
|
23
|
+
## [2.4.0] — 2026-08-14
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- **Collection articles can opt into a developer-documentation web theme.** Add `theme: docs`
|
|
27
|
+
to a collection in `demolab.yaml`; its article pages receive a technical sans/mono type system,
|
|
28
|
+
linked table-of-contents support, code and API-reference surfaces, and documentation-oriented
|
|
29
|
+
tables. Collection listings, experiments, PDFs, and the combined book retain the standard lab
|
|
30
|
+
presentation. The shipped demo includes a fictional Python SDK guide and API reference as a
|
|
31
|
+
complete worked example.
|
|
32
|
+
|
|
16
33
|
## [2.3.0] — 2026-08-07
|
|
17
34
|
|
|
18
35
|
### Added
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2.5.0
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Discover writings/*.typ, write a JSON manifest, and compile
|
|
1
|
+
"""Discover writings/*.typ, write a JSON manifest, and compile the configured targets.
|
|
2
2
|
|
|
3
3
|
build.py does only what Typst can't: it globs the filesystem (Typst has no directory
|
|
4
4
|
listing) and orchestrates the compiler. It stages the engine's Typst surface into the lab
|
|
@@ -16,8 +16,8 @@ into artifacts/site/:
|
|
|
16
16
|
pdfs/book.pdf — every entry concatenated into one PDF (book mode)
|
|
17
17
|
|
|
18
18
|
The site (artifacts/site/) is a self-contained build output (gitignored, deployed to
|
|
19
|
-
Pages).
|
|
20
|
-
deliverables.
|
|
19
|
+
Pages). Unless `pdfs: false` is set in demolab.yaml, PDFs are also mirrored to the
|
|
20
|
+
committed artifacts/pdfs/ as shareable deliverables.
|
|
21
21
|
|
|
22
22
|
Each writings/<id>.typ exposes `#let meta = (...)` and `#let body = [...]`.
|
|
23
23
|
Entries not yet in that convention are skipped (incremental migration).
|
|
@@ -49,6 +49,24 @@ SITE = ROOT / "artifacts" / "site" # bundle output (HTML + mp4 + pdfs/),
|
|
|
49
49
|
PDFS = ROOT / "artifacts" / "pdfs" # committed copy of the PDFs (shareable)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
def pdfs_enabled() -> bool:
|
|
53
|
+
"""Whether this lab publishes PDFs (default: yes).
|
|
54
|
+
|
|
55
|
+
demolab.yaml intentionally has no Python YAML dependency. This reads the one supported
|
|
56
|
+
top-level boolean directly; Typst remains the authority for the rest of the config.
|
|
57
|
+
"""
|
|
58
|
+
config = ROOT / "demolab.yaml"
|
|
59
|
+
if not config.is_file():
|
|
60
|
+
return True
|
|
61
|
+
text = config.read_text(encoding="utf-8")
|
|
62
|
+
match = re.search(r"(?m)^pdfs:\s*(true|false)\s*(?:#.*)?$", text)
|
|
63
|
+
if match:
|
|
64
|
+
return match.group(1) == "true"
|
|
65
|
+
if re.search(r"(?m)^pdfs\s*:", text):
|
|
66
|
+
raise SystemExit("error: demolab.yaml 'pdfs' must be true or false")
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
|
|
52
70
|
def _find_typst() -> str:
|
|
53
71
|
"""The typst CLI — needs --features bundle,html (experimental). A lab-local install wins
|
|
54
72
|
(.tools/bin — the no-package-manager fallback for locked-down machines), then PATH;
|
|
@@ -108,7 +126,8 @@ def discover_decks():
|
|
|
108
126
|
return [p.name.removesuffix(".slide.typ") for p in sorted(WRITINGS.glob("*.slide.typ"))]
|
|
109
127
|
|
|
110
128
|
|
|
111
|
-
def write_manifest(ids: list[str], deck_ids: list[str], broken: dict | None = None
|
|
129
|
+
def write_manifest(ids: list[str], deck_ids: list[str], broken: dict | None = None,
|
|
130
|
+
*, publish_pdfs: bool = True) -> None:
|
|
112
131
|
"""Write temp/bundle/index.json — the id/asset lists the staged main.typ reads.
|
|
113
132
|
|
|
114
133
|
This is the only place per-entry knowledge is assembled, and it's pure data (no Typst
|
|
@@ -132,6 +151,7 @@ def write_manifest(ids: list[str], deck_ids: list[str], broken: dict | None = No
|
|
|
132
151
|
manifest = {
|
|
133
152
|
"entries": entries,
|
|
134
153
|
"decks": [{"id": d} for d in deck_ids],
|
|
154
|
+
"pdfs_enabled": publish_pdfs,
|
|
135
155
|
"has_brand_config": (ROOT / "demolab.yaml").exists(),
|
|
136
156
|
"has_landing": (ROOT / "landing.typ").exists(),
|
|
137
157
|
}
|
|
@@ -180,13 +200,13 @@ def _error_excerpt(err: str, lines: int = 8) -> str:
|
|
|
180
200
|
return err.strip() or "build failed"
|
|
181
201
|
|
|
182
202
|
|
|
183
|
-
def compile_bundle(ids: list[str], deck_ids: list[str]) -> dict:
|
|
203
|
+
def compile_bundle(ids: list[str], deck_ids: list[str], *, publish_pdfs: bool = True) -> dict:
|
|
184
204
|
"""Compile the whole bundle. If an entry fails (a missing figure, a Typst error), flag it and
|
|
185
205
|
retry, so it renders as a stub page instead of taking the rest of the site down with it. Returns
|
|
186
206
|
the {id: error} map of entries that were stubbed."""
|
|
187
207
|
broken: dict = {}
|
|
188
208
|
while True:
|
|
189
|
-
write_manifest(ids, deck_ids, broken=broken)
|
|
209
|
+
write_manifest(ids, deck_ids, broken=broken, publish_pdfs=publish_pdfs)
|
|
190
210
|
proc = subprocess.run(
|
|
191
211
|
typst_compile("--format", "bundle", "--features", "bundle,html",
|
|
192
212
|
"--root", str(ROOT), str(MAIN), str(SITE) + "/"),
|
|
@@ -275,11 +295,14 @@ def main() -> None:
|
|
|
275
295
|
raise SystemExit("error: build accepts at most one entry id")
|
|
276
296
|
stage()
|
|
277
297
|
ids = discover()
|
|
298
|
+
publish_pdfs = pdfs_enabled()
|
|
278
299
|
_warn_if_content_misplaced(ids)
|
|
279
300
|
if entry_args:
|
|
301
|
+
if not publish_pdfs:
|
|
302
|
+
raise SystemExit("error: PDF publishing is disabled by demolab.yaml (pdfs: false)")
|
|
280
303
|
compile_entry(entry_args[0], ids)
|
|
281
304
|
return
|
|
282
|
-
deck_ids = discover_decks()
|
|
305
|
+
deck_ids = discover_decks() if publish_pdfs else []
|
|
283
306
|
# Zero writings is a valid state (a freshly `demolab scaffold`-ed repo): main.typ renders
|
|
284
307
|
# a friendly empty-state homepage, so we build rather than error.
|
|
285
308
|
# Compile decks first so their PDFs exist for the asset embeds in main.typ (skip reuses the
|
|
@@ -288,18 +311,23 @@ def main() -> None:
|
|
|
288
311
|
good_decks = [d for d in deck_ids if (DECKS / f"{d}.pdf").exists()]
|
|
289
312
|
else:
|
|
290
313
|
good_decks = compile_decks(deck_ids)
|
|
291
|
-
write_manifest(ids, good_decks)
|
|
314
|
+
write_manifest(ids, good_decks, publish_pdfs=publish_pdfs)
|
|
292
315
|
SITE.mkdir(parents=True, exist_ok=True)
|
|
316
|
+
if not publish_pdfs:
|
|
317
|
+
# Bundle compilation updates named outputs but does not prune outputs from an earlier
|
|
318
|
+
# build. The site tree is regenerable, so remove stale PDFs before a web-only compile;
|
|
319
|
+
# the committed artifacts/pdfs/ record remains deliberately untouched.
|
|
320
|
+
shutil.rmtree(SITE / "pdfs", ignore_errors=True)
|
|
293
321
|
if generate_only:
|
|
294
322
|
print(f"wrote manifest for {len(ids)} entries: {', '.join(ids)}"
|
|
295
323
|
+ (f" + {len(good_decks)} decks: {', '.join(good_decks)}" if good_decks else ""))
|
|
296
324
|
return
|
|
297
325
|
# One bad entry (a missing figure, a Typst error) becomes a stub page instead of failing the
|
|
298
326
|
# whole site — compile_bundle flags it and retries.
|
|
299
|
-
broken = compile_bundle(ids, good_decks)
|
|
327
|
+
broken = compile_bundle(ids, good_decks, publish_pdfs=publish_pdfs)
|
|
300
328
|
good = [i for i in ids if i not in broken]
|
|
301
329
|
# mirror the compiled PDFs (entries, book, and decks) to the committed artifacts/pdfs/
|
|
302
|
-
if not no_mirror:
|
|
330
|
+
if publish_pdfs and not no_mirror:
|
|
303
331
|
PDFS.mkdir(parents=True, exist_ok=True)
|
|
304
332
|
for pdf in sorted((SITE / "pdfs").glob("*.pdf")):
|
|
305
333
|
shutil.copy(pdf, PDFS / pdf.name)
|
|
@@ -310,8 +338,9 @@ def main() -> None:
|
|
|
310
338
|
print(f" entries: {', '.join(good)}"
|
|
311
339
|
+ (f" · decks: {', '.join(good_decks)}" if good_decks else "")
|
|
312
340
|
+ (f" · ⚠ stubbed: {', '.join(sorted(broken))}" if broken else "")
|
|
313
|
-
+ (f" · pdfs -> {PDFS.relative_to(ROOT)}/" if not no_mirror
|
|
314
|
-
else " · preview PDFs left untracked"
|
|
341
|
+
+ (f" · pdfs -> {PDFS.relative_to(ROOT)}/" if publish_pdfs and not no_mirror
|
|
342
|
+
else " · preview PDFs left untracked" if publish_pdfs
|
|
343
|
+
else " · PDF publishing disabled"))
|
|
315
344
|
summary = f"built {len(good)} entries" + (f" + {len(good_decks)} decks" if good_decks else "")
|
|
316
345
|
if broken:
|
|
317
346
|
summary += f", {len(broken)} stubbed"
|
|
@@ -45,7 +45,7 @@ GROUPS: list[tuple[str, list[tuple[str, str]]]] = [
|
|
|
45
45
|
]),
|
|
46
46
|
("publishing", [
|
|
47
47
|
("dev", "🔥 Serve the site with hot-reload + in-browser build errors (--demo serves the shipped demo, add --landing for its landing page; PORT overrides the auto-picked 3000)"),
|
|
48
|
-
("build", "📦 Build the
|
|
48
|
+
("build", "📦 Build the publication → artifacts/site/ (web) + optional artifacts/pdfs/"),
|
|
49
49
|
("slides", "🎞 Compile standalone Typst decks (writings/*.typ with no meta+body) to artifacts/pdfs/"),
|
|
50
50
|
("playground", "🎛 Launch the interactive Streamlit demo (http://localhost:8501)"),
|
|
51
51
|
]),
|
|
@@ -10,13 +10,13 @@ RULES' `§N.M`) and listed alphabetically. Section references like `§4.3` point
|
|
|
10
10
|
|
|
11
11
|
**G3 — Black box.** The installed `demolab-cli` package (the build engine, runbooks, guides, scaffold) plus the machine-managed `.demolab/` staging dir it materialises at the lab root. Pure upstream, never hand-edited; *"update demolab"* is a dependency bump (§3.1). Your customisation lives *outside* it, in `demolab.yaml` and your content.
|
|
12
12
|
|
|
13
|
-
**G4 —
|
|
13
|
+
**G4 — Lab config.** The optional root `demolab.yaml`: branding, collection registry, web themes, and the `pdfs` publishing switch. Absent keys use engine defaults (§3.3, §6.5).
|
|
14
14
|
|
|
15
|
-
**G5 — Bundle.** The Typst multi-document output from one compile: the site, per-entry PDFs
|
|
15
|
+
**G5 — Bundle.** The Typst multi-document output from one compile: always the site and, unless disabled, per-entry PDFs and the book, all emitted by `main.typ` (§5.2).
|
|
16
16
|
|
|
17
17
|
**G6 — Coding agent.** The AI assistant (Claude Code, Cursor, aider, …) that reads this repo and operates it: runs the toolchain, wires files, follows runbooks.
|
|
18
18
|
|
|
19
|
-
**G7 — Collection.** A group of entries sharing a `collection:` slug in their `meta`. Entries are grouped by collection on the homepage; a slug title-cases by default, and `demolab.yaml` can give it a label/description/order (§6.5).
|
|
19
|
+
**G7 — Collection.** A group of entries sharing a `collection:` slug in their `meta`. Entries are grouped by collection on the homepage; a slug title-cases by default, and `demolab.yaml` can give it a label/description/order plus an optional article web theme (§6.5).
|
|
20
20
|
|
|
21
21
|
**G8 — Contract.** The file-based interface between a tool and an experiment: a tool writes a fixed set of files (§4.3), a runner reads them by running the tool's CLI (§4.5). It's language-neutral (§1.4).
|
|
22
22
|
|
|
@@ -13,7 +13,7 @@ defined in [`GLOSSARY.md`](GLOSSARY.md).
|
|
|
13
13
|
|
|
14
14
|
**1.1 — Python via `uv`.** Never call `python` / `python3` directly. Deps are pinned in the root `pyproject.toml` / `uv.lock`; run scripts with `uv run python <script>` (e.g. `uv run python tools/neuron/tool.py lif`); `uv sync` after pulling.
|
|
15
15
|
|
|
16
|
-
**1.2 — Publishing via `typst`.** Use the `typst` CLI (an installed prerequisite, alongside `uv`). It compiles the site + PDFs (`demolab build` / `demolab dev`); the bundle build passes `--features bundle,html` (experimental, deliberately used here). Typst receives a deterministic creation timestamp (`SOURCE_DATE_EPOCH` overrides the engine default), so unchanged PDFs stay byte-identical within a stable toolchain. No Node/`bun` — demolab publishes entirely with Typst.
|
|
16
|
+
**1.2 — Publishing via `typst`.** Use the `typst` CLI (an installed prerequisite, alongside `uv`). It compiles the site + optional PDFs (`demolab build` / `demolab dev`); the bundle build passes `--features bundle,html` (experimental, deliberately used here). Typst receives a deterministic creation timestamp (`SOURCE_DATE_EPOCH` overrides the engine default), so unchanged PDFs stay byte-identical within a stable toolchain. No Node/`bun` — demolab publishes entirely with Typst.
|
|
17
17
|
|
|
18
18
|
**1.3 — Prefer `demolab`.** Common commands are exposed through the `demolab` CLI — prefer `demolab <name>` (run `demolab` to list them). To check the toolchain *and* that the repo obeys these conventions, run the *Doctor the repo* runbook (*"doctor the repo"*).
|
|
19
19
|
|
|
@@ -72,17 +72,21 @@ The concrete annotated file tree is in [`STRUCTURE.md`](STRUCTURE.md); this sect
|
|
|
72
72
|
|
|
73
73
|
**5.1 — Scratch vs record.** `temp/<tool>/<cmd>/` is scratch — gitignored, overwritten every run. The runner writes the rendered figure(s) + aggregated `numbers.json` (and any video) + a `run.sh` reproducer into **`artifacts/data/<id>/`**, which *is* committed. That folder is the publisher-neutral record: the single place the publisher reads from. (The tool's own `run.sh` stays in scratch `temp/`; the runner's is committed here.)
|
|
74
74
|
|
|
75
|
-
**5.2 — Typst is the publisher.** `demolab build` globs `writings/*.typ` (and each entry's mp4s) into a JSON manifest (`temp/bundle/index.json`), stages the engine's static `main.typ` beside it, then compiles it (it reads the manifest) to
|
|
75
|
+
**5.2 — Typst is the publisher.** `demolab build` globs `writings/*.typ` (and each entry's mp4s) into a JSON manifest (`temp/bundle/index.json`), stages the engine's static `main.typ` beside it, then compiles it (it reads the manifest) to the web and, by default, two PDF targets in one pass — no generated Typst source:
|
|
76
76
|
- **Web** — `artifacts/site/`: `index.html` (entries grouped by collection, §6.5), `all.html` (every entry, newest first), and an HTML page per entry (figures inline, videos play, math as MathML, styled by the engine's stylesheet).
|
|
77
77
|
- **Per-entry PDFs** — `artifacts/site/pdfs/<id>.pdf`.
|
|
78
78
|
- **Book** — `artifacts/site/pdfs/book.pdf`: every entry, with a table of contents.
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Set the top-level `pdfs: false` in `demolab.yaml` for a web-only lab. The build then omits
|
|
81
|
+
both PDF targets, every PDF link, and PDF-only slide decks. It does not delete existing
|
|
82
|
+
files under `artifacts/pdfs/`; omitting the key or setting it to `true` preserves the default.
|
|
83
|
+
|
|
84
|
+
When PDFs are enabled, `demolab build <id>` updates just
|
|
81
85
|
`artifacts/pdfs/<id>.pdf`; it deliberately leaves the site and `book.pdf` untouched.
|
|
82
86
|
`demolab dev` refreshes the complete ignored preview site without mirroring preview PDFs into
|
|
83
87
|
the tracked publication directory. Run bare `demolab build` before publishing.
|
|
84
88
|
|
|
85
|
-
**5.3 — What's committed.** PDFs are mirrored to the committed, shareable `artifacts/pdfs/`. `artifacts/site/` is a gitignored build output (CI regenerates + deploys it to Pages). CI does **not** run the experiments, so `artifacts/data/` **must** be committed — that record, not the ephemeral `temp/`, is what reaches the site.
|
|
89
|
+
**5.3 — What's committed.** When enabled, PDFs are mirrored to the committed, shareable `artifacts/pdfs/`. `artifacts/site/` is a gitignored build output (CI regenerates + deploys it to Pages). CI does **not** run the experiments, so `artifacts/data/` **must** be committed — that record, not the ephemeral `temp/`, is what reaches the site.
|
|
86
90
|
|
|
87
91
|
**5.4 — Numbers can't drift.** Each `writings/<id>.typ` reads its own bundle natively — `json("/artifacts/data/<id>/numbers.json")`, `#image("/artifacts/data/<id>/fig.png")` (compiled with `--root` at the repo root) — so the numbers and figures come straight from the run.
|
|
88
92
|
|
|
@@ -109,6 +113,19 @@ Numbers must come from the run (§5.4) — never hand-type a literal that could
|
|
|
109
113
|
|
|
110
114
|
**6.5a — Curated reading order.** An optional integer `order:` in a writing's `meta` marks its collection as *curated*: that collection's page lists entries by `order` ascending (unranked entries trail, in id order) instead of the default status-then-newest sort. Use it when a collection should read as a sequence — a documentation arc, a course, a serial — and leave it off for a chronological log of results. If you rank one entry in a collection, rank them all; a half-ranked collection reads as an accident.
|
|
111
115
|
|
|
116
|
+
**6.5b — Developer-documentation articles.** A registered collection may set `theme: docs` to give its **article web pages only** demolab's built-in developer-documentation treatment: technical sans/mono typography, cool slate surfaces, documentation tables, code panels, and API-reference accents. The collection listing, experiment pages in the same collection, per-entry PDFs, and combined book retain the standard lab design. `docs` is currently the only built-in theme:
|
|
117
|
+
|
|
118
|
+
```yaml
|
|
119
|
+
collection-order: [python-sdk]
|
|
120
|
+
collections:
|
|
121
|
+
python-sdk:
|
|
122
|
+
label: Python SDK
|
|
123
|
+
description: Guides and API reference for the Python package.
|
|
124
|
+
theme: docs
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The theme styles ordinary Typst headings, prose, code, and tables automatically. A documentation article may add HTML-only `docs-toc`, `docs-note`, and `api-signature` elements for the richer components demonstrated by the shipped `ar028` example; keep a meaningful plain Typst structure around them so the article remains useful in PDF form.
|
|
128
|
+
|
|
112
129
|
**6.6 — Citations & references.** Cite prior work with two `lib.typ` helpers, so numbering, linking, and the web popover all come for free — never hand-type a bracket or a manual list (HOUSESTYLE H24). Import both: `#import "/.demolab/lib.typ": cite, reference-list`.
|
|
113
130
|
|
|
114
131
|
- **Inline** — `#cite(1, 2)` renders `[1, 2]`. The numbers are **author-managed** (you pass them), so there's no `.bib` file to keep in sync. On the web each number links to its entry.
|
|
@@ -30,7 +30,7 @@ my-lab/
|
|
|
30
30
|
│ └── arNNN.slide.typ a deck — Touying slides → standalone PDF [G9]
|
|
31
31
|
├── artifacts/ the committed record of every run [§5]
|
|
32
32
|
│ ├── data/<id>/ figures + numbers.json + run.sh (+ any mp4) — the publisher-neutral record [§5.1, G18]
|
|
33
|
-
│ ├── pdfs/ compiled PDFs (per entry + book.pdf) — shareable
|
|
33
|
+
│ ├── pdfs/ optional compiled PDFs (per entry + book.pdf) — shareable [§5.3]
|
|
34
34
|
│ └── site/ the built web site — GITIGNORED (CI rebuilds + deploys it) [§5.3]
|
|
35
35
|
├── demolab.yaml branding + collections config — and the LAB MARKER the CLI walks up to [§3.3, §6.5, G4]
|
|
36
36
|
├── HOUSESTYLE.local.md optional — your house-style overrides (extend/replace) [§3.3]
|
|
@@ -67,7 +67,7 @@ lives in the installed `demolab-cli` package (site-packages), reached via `demol
|
|
|
67
67
|
|
|
68
68
|
**S5 — The package vs your stuff.** The engine is the installed `demolab-cli` package — you never hand-edit it, and updating it is a dependency bump (§3.1). The gitignored `.demolab/` staging dir is the CLI's, not yours (§3.2). Everything else in the tree is yours: `tools/`, `experiments/`, `writings/`, `artifacts/`, `demolab.yaml`, and the root stubs `demolab init` laid down (§3.3).
|
|
69
69
|
|
|
70
|
-
**S6 — Where the build goes.** `demolab build` globs `writings/*.typ` into `temp/bundle/index.json`, stages the engine's `main.typ` beside it, and compiles
|
|
70
|
+
**S6 — Where the build goes.** `demolab build` globs `writings/*.typ` into `temp/bundle/index.json`, stages the engine's `main.typ` beside it, and compiles the web site → `artifacts/site/`. By default it also emits per-entry PDFs + `book.pdf` under `artifacts/site/pdfs/` and mirrors them to `artifacts/pdfs/`; `pdfs: false` in `demolab.yaml` makes the build web-only and omits PDF-only decks. `demolab build <id>` updates only `artifacts/pdfs/<id>.pdf` when PDFs are enabled; `demolab dev` never mirrors preview PDFs. On an empty tree the build emits a friendly `index.html` and skips the other documents. CI deploys `artifacts/site/` to GitHub Pages (§5.2, §5.3).
|
|
71
71
|
|
|
72
72
|
## Scaffolding
|
|
73
73
|
|
|
@@ -182,8 +182,10 @@ freestyling, not following this runbook.
|
|
|
182
182
|
tagline, book/PDF title (defaults from the name), and **author + contact** (offer to pull
|
|
183
183
|
from git config — they render as a byline under the homepage title and an
|
|
184
184
|
`<meta name="author">`; contact, if given, links the byline). The engine defaults any key
|
|
185
|
-
you omit and updates never touch it; `demolab dev` hot-reloads, so they watch it change.
|
|
186
|
-
|
|
185
|
+
you omit and updates never touch it; `demolab dev` hot-reloads, so they watch it change. If an
|
|
186
|
+
article collection is developer documentation, offer its built-in `theme: docs` treatment
|
|
187
|
+
(RULES §6.5b); it affects only that collection's article web pages. Deeper theming
|
|
188
|
+
(`style.css`, `favicon.svg`) lives inside the engine package — leave it as advanced.
|
|
187
189
|
|
|
188
190
|
5. **Publish to GitHub Pages?** *"Free, unless the repo is private."* Default yes; if no, skip —
|
|
189
191
|
it all works locally. If yes: create + push a GitHub repo (`gh`), run `demolab deploy-setup`
|
|
@@ -7,8 +7,12 @@ book-title: Demolab — the book
|
|
|
7
7
|
contents-title: Demolab — contents
|
|
8
8
|
description: A lab notebook for computational science — reproducible results, published and citable.
|
|
9
9
|
|
|
10
|
-
collection-order: [documentation]
|
|
10
|
+
collection-order: [documentation, theme-demo]
|
|
11
11
|
collections:
|
|
12
12
|
documentation:
|
|
13
13
|
label: Documentation
|
|
14
14
|
description: "How demolab works, in reading order: what it is and why, setting up, the contract, the mechanics of experiments and writeups, and running the lab day to day."
|
|
15
|
+
theme-demo:
|
|
16
|
+
label: Developer docs theme
|
|
17
|
+
description: "A single-page showroom for the optional developer-documentation collection theme."
|
|
18
|
+
theme: docs
|
|
@@ -51,7 +51,10 @@
|
|
|
51
51
|
work: an unregistered slug is simply title-cased for display (`neuron-models` becomes
|
|
52
52
|
"Neuron Models"). The `collections:` map in `demolab.yaml` upgrades that: per slug, a
|
|
53
53
|
`label` (the display name) and a `description` (shown under the label on the homepage
|
|
54
|
-
and at the top of the collection's own page).
|
|
54
|
+
and at the top of the collection's own page). A collection can also set `theme: docs`
|
|
55
|
+
to give only its article web pages a light developer-documentation treatment. Collection
|
|
56
|
+
listings, experiment pages, standalone PDFs, and the combined book keep the standard lab
|
|
57
|
+
design. `docs` is currently the only built-in theme.
|
|
55
58
|
|
|
56
59
|
`collection-order:` is a list of slugs setting the homepage order. Collections you leave
|
|
57
60
|
out of the list still appear; they trail after the listed ones. Two special cases:
|
|
@@ -90,11 +93,15 @@
|
|
|
90
93
|
contents-title: Demolab — contents
|
|
91
94
|
description: A lab notebook for computational science — reproducible results, published and citable.
|
|
92
95
|
|
|
93
|
-
collection-order: [documentation, slides]
|
|
96
|
+
collection-order: [documentation, theme-demo, slides]
|
|
94
97
|
collections:
|
|
95
98
|
documentation:
|
|
96
99
|
label: Documentation
|
|
97
100
|
description: "How demolab works, in reading order: what it is and why, setting up, the contract, the mechanics of experiments and writeups, and running the lab day to day."
|
|
101
|
+
theme-demo:
|
|
102
|
+
label: Developer docs theme
|
|
103
|
+
description: "A single-page showroom for the optional developer-documentation collection theme."
|
|
104
|
+
theme: docs
|
|
98
105
|
slides:
|
|
99
106
|
label: Talks & slides
|
|
100
107
|
description: Deck PDFs from talks — paged-only, linked as PDF.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#let meta = (
|
|
2
|
+
title: "LatticeCache Python SDK",
|
|
3
|
+
date: "2026-08-14",
|
|
4
|
+
description: "Fake developer documentation and API reference for a fictional Python caching library.",
|
|
5
|
+
collection: "theme-demo",
|
|
6
|
+
status: "final",
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
#let body = [
|
|
10
|
+
LatticeCache is a fictional Python library for caching deterministic function calls on disk.
|
|
11
|
+
It is intentionally small, typed, and boring: the same inputs produce the same cache key, and
|
|
12
|
+
expired values disappear without ceremony.
|
|
13
|
+
|
|
14
|
+
#context if target() == "html" {
|
|
15
|
+
html.elem("nav", attrs: (class: "docs-toc", "aria-label": "On this page"), {
|
|
16
|
+
html.elem("p", attrs: (class: "docs-toc-title"), [On this page])
|
|
17
|
+
html.elem("ul", {
|
|
18
|
+
html.elem("li", html.elem("a", attrs: (href: "#installation"), [Installation]))
|
|
19
|
+
html.elem("li", html.elem("a", attrs: (href: "#quick-start"), [Quick start]))
|
|
20
|
+
html.elem("li", html.elem("a", attrs: (href: "#configuration"), [Configuration]))
|
|
21
|
+
html.elem("li", html.elem("a", attrs: (href: "#api-reference"), [API reference]))
|
|
22
|
+
html.elem("li", html.elem("a", attrs: (href: "#cache"), [`Cache`]))
|
|
23
|
+
html.elem("li", html.elem("a", attrs: (href: "#cached"), [`cached`]))
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
== Installation
|
|
29
|
+
|
|
30
|
+
LatticeCache requires Python 3.11 or later. Install it from the imaginary package index:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
pip install latticecache
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#context if target() == "html" {
|
|
37
|
+
html.elem("aside", attrs: (class: "docs-note"), [
|
|
38
|
+
*Demo package.* `latticecache` does not exist. This page is sample content for demolab's
|
|
39
|
+
optional article treatment, not advice to paste mysterious packages into your terminal.
|
|
40
|
+
])
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
== Quick start
|
|
44
|
+
|
|
45
|
+
Create one cache and call `get_or_set` with a stable key. The factory runs only when no live
|
|
46
|
+
value exists.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from datetime import timedelta
|
|
50
|
+
from latticecache import Cache
|
|
51
|
+
|
|
52
|
+
cache = Cache(".cache/weather")
|
|
53
|
+
|
|
54
|
+
forecast = cache.get_or_set(
|
|
55
|
+
"madrid:2026-08-14",
|
|
56
|
+
factory=lambda: fetch_forecast("Madrid"),
|
|
57
|
+
ttl=timedelta(minutes=15),
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Values are encoded as JSON by default. Supply a codec when storing dataclasses, NumPy arrays,
|
|
62
|
+
or other objects that JSON cannot represent.
|
|
63
|
+
|
|
64
|
+
== Configuration
|
|
65
|
+
|
|
66
|
+
#table(
|
|
67
|
+
columns: (1.1fr, 1fr, 2.3fr),
|
|
68
|
+
[*Option*], [*Default*], [*Meaning*],
|
|
69
|
+
[`namespace`], [`"default"`], [Prefix used to isolate cache keys],
|
|
70
|
+
[`max_bytes`], [`256 MiB`], [Soft size limit checked after each write],
|
|
71
|
+
[`serializer`], [`"json"`], [Built-in `json`, `text`, or a custom codec],
|
|
72
|
+
[`read_only`], [`False`], [Allow reads but never create or refresh entries],
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
Configuration may be passed to `Cache` directly or loaded from `pyproject.toml` under
|
|
76
|
+
`[tool.latticecache]`. Constructor arguments win when both are present.
|
|
77
|
+
|
|
78
|
+
== API reference
|
|
79
|
+
|
|
80
|
+
=== `Cache`
|
|
81
|
+
|
|
82
|
+
#context if target() == "html" {
|
|
83
|
+
html.elem("div", attrs: (class: "api-signature"), [
|
|
84
|
+
`class latticecache.Cache(path, *, namespace="default", max_bytes=268435456, read_only=False)`
|
|
85
|
+
])
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
A filesystem-backed cache. Creating an instance makes its directory lazily on the first write;
|
|
89
|
+
constructing a read-only cache never changes the filesystem.
|
|
90
|
+
|
|
91
|
+
==== Parameters
|
|
92
|
+
|
|
93
|
+
#table(
|
|
94
|
+
columns: (1fr, 1fr, 2.4fr),
|
|
95
|
+
[*Name*], [*Type*], [*Description*],
|
|
96
|
+
[`path`], [`str | Path`], [Directory containing cached values and metadata],
|
|
97
|
+
[`namespace`], [`str`], [Logical partition included in every derived key],
|
|
98
|
+
[`max_bytes`], [`int`], [Approximate limit before least-recently-used eviction],
|
|
99
|
+
[`read_only`], [`bool`], [Disable writes, refreshes, and eviction],
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
==== `Cache.get_or_set`
|
|
103
|
+
|
|
104
|
+
#context if target() == "html" {
|
|
105
|
+
html.elem("div", attrs: (class: "api-signature"), [
|
|
106
|
+
`get_or_set(key, factory, *, ttl=None) -> Any`
|
|
107
|
+
])
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Return the live value stored under `key`. If the key is absent or expired, call `factory`,
|
|
111
|
+
store its result atomically, and return the new value.
|
|
112
|
+
|
|
113
|
+
- *Raises `ReadOnlyMiss`* when a read-only cache has no live value.
|
|
114
|
+
- *Raises `EncodeError`* when the configured serializer rejects the factory result.
|
|
115
|
+
- The factory's own exceptions pass through unchanged and are never cached.
|
|
116
|
+
|
|
117
|
+
==== `Cache.invalidate`
|
|
118
|
+
|
|
119
|
+
#context if target() == "html" {
|
|
120
|
+
html.elem("div", attrs: (class: "api-signature"), [`invalidate(key) -> bool`])
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
Remove one key. Returns `true` when an entry existed and `false` when there was nothing to do.
|
|
124
|
+
|
|
125
|
+
=== `cached`
|
|
126
|
+
|
|
127
|
+
#context if target() == "html" {
|
|
128
|
+
html.elem("div", attrs: (class: "api-signature"), [
|
|
129
|
+
`latticecache.cached(cache, *, ttl=None, key=None) -> Callable`
|
|
130
|
+
])
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Decorate a deterministic function. By default, the key combines the function's qualified name,
|
|
134
|
+
its source fingerprint, and a canonical encoding of positional and keyword arguments.
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from latticecache import Cache, cached
|
|
138
|
+
|
|
139
|
+
cache = Cache(".cache/models", namespace="v2")
|
|
140
|
+
|
|
141
|
+
@cached(cache, ttl=3600)
|
|
142
|
+
def load_model(model_id: str, *, quantized: bool = False):
|
|
143
|
+
return download_model(model_id, quantized=quantized)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Mutable global state is not part of the derived key. Pass every input explicitly or provide a
|
|
147
|
+
custom `key` function; invisible dependencies and caches are natural enemies.
|
|
148
|
+
]
|
|
@@ -9,7 +9,7 @@ per-run artifacts; Typst publishes them as a website, per-entry PDFs, and a book
|
|
|
9
9
|
uv sync # install dependencies + the `demolab` command
|
|
10
10
|
demolab dev # live-preview the site at http://localhost:3000
|
|
11
11
|
uv run python experiments/exp000.py # run an experiment end-to-end
|
|
12
|
-
demolab build # build the site + PDFs into artifacts/
|
|
12
|
+
demolab build # build the site + optional PDFs into artifacts/
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Run `demolab` for the full command list. If you work with a coding agent, it reads
|
|
@@ -21,7 +21,7 @@ chat shows the menu.
|
|
|
21
21
|
- `experiments/` — runners that stage `artifacts/data/<id>/` (figures + numbers)
|
|
22
22
|
- `writings/` — the write-ups (`<id>.typ`), published by `demolab build`
|
|
23
23
|
- `tools/` — reusable science shared by experiments
|
|
24
|
-
- `artifacts/` — the committed record: `data/` per run, `pdfs/`
|
|
24
|
+
- `artifacts/` — the committed record: `data/` per run, optional `pdfs/` deliverables
|
|
25
25
|
- `demolab.yaml` — branding + collections (also marks this directory as a lab)
|
|
26
26
|
|
|
27
27
|
The engine ships in the `demolab-cli` package — nothing to vendor or maintain here.
|
|
@@ -14,6 +14,10 @@ description: A lab notebook for computational science — reproducible results,
|
|
|
14
14
|
# author: Your Name
|
|
15
15
|
# contact: you@example.com
|
|
16
16
|
|
|
17
|
+
# PDF publishing is enabled by default. Disable it for a web-only lab; existing PDFs are
|
|
18
|
+
# left untouched, and PDF-only slide decks are omitted from the site.
|
|
19
|
+
# pdfs: false
|
|
20
|
+
|
|
17
21
|
# Optional collaborative inline comments on web articles and experiments. Readers select text
|
|
18
22
|
# and discuss it through Hypothesis accounts and groups; PDFs are unchanged. Set this lab-wide,
|
|
19
23
|
# or omit it here and add `annotations: "hypothesis"` to selected writings' `meta` blocks.
|
|
@@ -26,12 +30,15 @@ description: A lab notebook for computational science — reproducible results,
|
|
|
26
30
|
|
|
27
31
|
# Homepage collections. Entries are grouped by their `collection:` meta field; this
|
|
28
32
|
# registry gives each collection a display label + description and sets the order. A
|
|
29
|
-
# collection with no entry here still works — its slug is just title-cased.
|
|
33
|
+
# collection with no entry here still works — its slug is just title-cased. Add
|
|
34
|
+
# `theme: docs` to a collection whose articles should have a light developer-documentation
|
|
35
|
+
# web treatment. Collection listings, experiments, and PDFs retain the standard design.
|
|
30
36
|
collection-order: [documentation, neuron-models, mujoco, streamlit, slides]
|
|
31
37
|
collections:
|
|
32
38
|
documentation:
|
|
33
39
|
label: Documentation
|
|
34
40
|
description: "How demolab works: getting started, the always-on guides that set the conventions, and the on-demand runbooks the agent runs."
|
|
41
|
+
# theme: docs
|
|
35
42
|
neuron-models:
|
|
36
43
|
label: Neuron models
|
|
37
44
|
description: Integrate-and-fire neurons and the membrane biophysics behind them.
|
|
@@ -19,6 +19,8 @@ def is_bundle_entry(source: str) -> bool:
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
def main() -> None:
|
|
22
|
+
if not build.pdfs_enabled():
|
|
23
|
+
raise SystemExit("error: PDF publishing is disabled by demolab.yaml (pdfs: false)")
|
|
22
24
|
build.stage() # a deck may import /.demolab/lib.typ (video, data-file, …)
|
|
23
25
|
build.PDFS.mkdir(parents=True, exist_ok=True)
|
|
24
26
|
found = failed = False
|