demolab-cli 2.2.1__tar.gz → 2.4.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.

Files changed (126) hide show
  1. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/PKG-INFO +2 -2
  2. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/CHANGELOG.md +18 -0
  3. demolab_cli-2.4.0/demolab_cli/VERSION +1 -0
  4. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/guides/GLOSSARY.md +2 -2
  5. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/guides/RULES.md +22 -1
  6. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/runbooks/GETTING-STARTED.md +4 -2
  7. demolab_cli-2.4.0/demolab_cli/scaffold/demo/artifacts/pdfs/ar027.pdf +0 -0
  8. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/demolab.yaml +5 -1
  9. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar025.typ +9 -2
  10. demolab_cli-2.4.0/demolab_cli/scaffold/demo/writings/ar027.typ +77 -0
  11. demolab_cli-2.4.0/demolab_cli/scaffold/demo/writings/ar028.typ +148 -0
  12. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/skeleton/demolab.yaml +9 -1
  13. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/test_engine_build.py +42 -0
  14. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/typ/entry.typ +2 -1
  15. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/typ/lib.typ +24 -3
  16. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/typ/main.typ +3 -2
  17. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/typ/style.css +100 -0
  18. demolab_cli-2.2.1/demolab_cli/VERSION +0 -1
  19. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/.gitattributes +0 -0
  20. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/.github/workflows/landing-preview.yml +0 -0
  21. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/.github/workflows/landing.yml +0 -0
  22. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/.github/workflows/publish.yml +0 -0
  23. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/.github/workflows/tests.yml +0 -0
  24. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/.gitignore +0 -0
  25. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/AGENTS.md +0 -0
  26. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/CLAUDE.md +0 -0
  27. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/DEVELOPING.md +0 -0
  28. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/LICENSE +0 -0
  29. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/README.md +0 -0
  30. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/AGENT.md +0 -0
  31. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/__init__.py +0 -0
  32. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/_paths.py +0 -0
  33. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/build.py +0 -0
  34. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/cli.py +0 -0
  35. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/deploy/deploy.yml +0 -0
  36. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/deploy/preview.yml +0 -0
  37. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/devserver.py +0 -0
  38. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/guides/AUTORESEARCH-RULES.md +0 -0
  39. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/guides/HOUSESTYLE.md +0 -0
  40. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/guides/SLIDES.md +0 -0
  41. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/guides/STRUCTURE.md +0 -0
  42. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/guides/SUPPORT.md +0 -0
  43. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/overlay.py +0 -0
  44. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/runbooks/AUTORESEARCH.md +0 -0
  45. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/runbooks/DOCTOR.md +0 -0
  46. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/runbooks/EMBED-DOCS.md +0 -0
  47. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/runbooks/FROM-JUPYTER.md +0 -0
  48. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/runbooks/LINT.md +0 -0
  49. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/runbooks/LITERATURE-SEARCH.md +0 -0
  50. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/runbooks/MIGRATE-CODE.md +0 -0
  51. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/runbooks/MIGRATE-STACK.md +0 -0
  52. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/runbooks/TOUR.md +0 -0
  53. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/runbooks/UPDATE.md +0 -0
  54. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/data/ar018/swe-bench.svg +0 -0
  55. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar004.pdf +0 -0
  56. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar005.pdf +0 -0
  57. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar010.pdf +0 -0
  58. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar011.pdf +0 -0
  59. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar012.pdf +0 -0
  60. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar013.pdf +0 -0
  61. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar014.pdf +0 -0
  62. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar015.pdf +0 -0
  63. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar016.pdf +0 -0
  64. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar017.pdf +0 -0
  65. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar018.pdf +0 -0
  66. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar019.pdf +0 -0
  67. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar020.pdf +0 -0
  68. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar021.pdf +0 -0
  69. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar022.pdf +0 -0
  70. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar023.pdf +0 -0
  71. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar024.pdf +0 -0
  72. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar025.pdf +0 -0
  73. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/ar026.pdf +0 -0
  74. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/artifacts/pdfs/book.pdf +0 -0
  75. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/site/CNAME +0 -0
  76. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/site/landing.typ +0 -0
  77. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar004.slide.typ +0 -0
  78. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar005.slide.typ +0 -0
  79. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar010.typ +0 -0
  80. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar011.typ +0 -0
  81. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar012.typ +0 -0
  82. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar013.typ +0 -0
  83. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar014.typ +0 -0
  84. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar015.typ +0 -0
  85. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar016.typ +0 -0
  86. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar017.typ +0 -0
  87. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar018.typ +0 -0
  88. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar019.typ +0 -0
  89. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar020.typ +0 -0
  90. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar021.typ +0 -0
  91. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar022.typ +0 -0
  92. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar023.typ +0 -0
  93. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar024.typ +0 -0
  94. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/demo/writings/ar026.typ +0 -0
  95. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/root/AGENTS.md +0 -0
  96. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/root/CLAUDE.md +0 -0
  97. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/root/README.md +0 -0
  98. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/root/github/workflows/tests.yml +0 -0
  99. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/root/gitignore +0 -0
  100. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/root/pyproject.toml +0 -0
  101. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/skeleton/.gitattributes +0 -0
  102. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/skeleton/HOUSESTYLE.local.md +0 -0
  103. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/skeleton/artifacts/data/.gitkeep +0 -0
  104. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/skeleton/artifacts/pdfs/.gitkeep +0 -0
  105. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/skeleton/experiments/helpers/__init__.py +0 -0
  106. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/skeleton/experiments/helpers/provenance.py +0 -0
  107. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/skeleton/experiments/helpers/style.py +0 -0
  108. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/skeleton/experiments/helpers/test_provenance.py +0 -0
  109. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/skeleton/tools/.gitkeep +0 -0
  110. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/skeleton/writings/.gitkeep +0 -0
  111. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/starters/monte-carlo-pi/README.md +0 -0
  112. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/starters/monte-carlo-pi/exp000.py +0 -0
  113. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/scaffold/starters/monte-carlo-pi/exp000.typ +0 -0
  114. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/slides.py +0 -0
  115. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/test_build_resilience.py +0 -0
  116. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/test_cli.py +0 -0
  117. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/test_command_catalog.py +0 -0
  118. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/test_devserver.py +0 -0
  119. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/test_init_and_staging.py +0 -0
  120. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/test_overlay.py +0 -0
  121. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/test_slide_catalog.py +0 -0
  122. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/test_slides.py +0 -0
  123. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/typ/cite-popover.js +0 -0
  124. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/demolab_cli/typ/favicon.svg +0 -0
  125. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/pyproject.toml +0 -0
  126. {demolab_cli-2.2.1 → demolab_cli-2.4.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: demolab-cli
3
- Version: 2.2.1
3
+ Version: 2.4.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
@@ -13,6 +13,24 @@ the runbook shows the entries between your version and the latest.
13
13
 
14
14
  ## [Unreleased]
15
15
 
16
+ ## [2.4.0] — 2026-08-14
17
+
18
+ ### Added
19
+ - **Collection articles can opt into a developer-documentation web theme.** Add `theme: docs`
20
+ to a collection in `demolab.yaml`; its article pages receive a technical sans/mono type system,
21
+ linked table-of-contents support, code and API-reference surfaces, and documentation-oriented
22
+ tables. Collection listings, experiments, PDFs, and the combined book retain the standard lab
23
+ presentation. The shipped demo includes a fictional Python SDK guide and API reference as a
24
+ complete worked example.
25
+
26
+ ## [2.3.0] — 2026-08-07
27
+
28
+ ### Added
29
+ - **Optional collaborative inline annotations with Hypothesis.** Set `annotations: hypothesis`
30
+ lab-wide in `demolab.yaml` or `annotations: "hypothesis"` on one writing's `meta`; web readers
31
+ can highlight text and use Hypothesis threads and private groups, while PDFs remain unchanged.
32
+ The new `ar027` demo article documents the workflow and opts itself in as a live example.
33
+
16
34
  ## [2.2.1] — 2026-08-07
17
35
 
18
36
  ### Fixed
@@ -0,0 +1 @@
1
+ 2.4.0
@@ -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 — Brand config.** The optional root `demolab.yaml`: wordmark, PDF titles, and the collection registry. Absent ⇒ engine defaults (§3.3, §6.5).
13
+ **G4 — Brand config.** The optional root `demolab.yaml`: wordmark, PDF titles, and the collection registry (including optional web themes). Absent ⇒ engine defaults (§3.3, §6.5).
14
14
 
15
15
  **G5 — Bundle.** The Typst multi-document output from one compile: the site, per-entry PDFs, and the book, all emitted together 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
 
@@ -31,7 +31,7 @@ The concrete annotated file tree is in [`STRUCTURE.md`](STRUCTURE.md); this sect
31
31
 
32
32
  **3.2 — Machine-managed staging** (gitignored, owned by the CLI — never hand-edit): `.demolab/` at the lab root holds the few engine files Typst must read from inside the lab tree (`lib.typ`, the web assets, a `VERSION` stamp); every build refreshes it when the installed engine version changes. `temp/bundle/` holds the staged bundle root (`main.typ`) plus build scratch. Local edits to either are overwritten without warning — a customisation that seems to need them is a missing config knob (see 3.3, or propose it upstream).
33
33
 
34
- **3.3 — Your root overrides, optional** (root files the framework reads; never overwritten by updates): `demolab.yaml` (wordmark + PDF titles + collections — also the **lab marker**: the CLI finds the lab root by walking up to it, so don't delete it) and `HOUSESTYLE.local.md` (your house-style overrides, which extend or replace the default `HOUSESTYLE.md`; an agent reads it). Every key defaults ⇒ a minimal file is fine. The root stubs (`AGENTS.md`, `CLAUDE.md`, `README.md`, `pyproject.toml`, `.gitignore`, CI) are laid down once by `demolab init` and are yours from then on.
34
+ **3.3 — Your root overrides, optional** (root files the framework reads; never overwritten by updates): `demolab.yaml` (wordmark + PDF titles + collections + optional web annotations — also the **lab marker**: the CLI finds the lab root by walking up to it, so don't delete it) and `HOUSESTYLE.local.md` (your house-style overrides, which extend or replace the default `HOUSESTYLE.md`; an agent reads it). Every key defaults ⇒ a minimal file is fine. The root stubs (`AGENTS.md`, `CLAUDE.md`, `README.md`, `pyproject.toml`, `.gitignore`, CI) are laid down once by `demolab init` and are yours from then on.
35
35
 
36
36
  **3.4 — User content** (100% the user's — freely deletable and replaceable): `tools/*`, `experiments/*` (runners, plus `playground.py` — the Streamlit demo, exempt from the contract), `writings/*` (`.typ` writeups), `artifacts/*` (`data/` per-run figures + `numbers.json`, `pdfs/` compiled PDFs; `artifacts/site/` is a gitignored build), `temp/*` (regenerable scratch).
37
37
 
@@ -109,6 +109,19 @@ Numbers must come from the run (§5.4) — never hand-type a literal that could
109
109
 
110
110
  **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
111
 
112
+ **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:
113
+
114
+ ```yaml
115
+ collection-order: [python-sdk]
116
+ collections:
117
+ python-sdk:
118
+ label: Python SDK
119
+ description: Guides and API reference for the Python package.
120
+ theme: docs
121
+ ```
122
+
123
+ 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.
124
+
112
125
  **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
126
 
114
127
  - **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.
@@ -118,6 +131,14 @@ Numbers must come from the run (§5.4) — never hand-type a literal that could
118
131
 
119
132
  See `ar006` for a worked example — ten references with DOIs, inline cites throughout, and the reference list at the foot of the body.
120
133
 
134
+ **6.7 — Collaborative annotations are opt-in and web-only.** Set `annotations: hypothesis`
135
+ in root `demolab.yaml` to embed the hosted Hypothesis client on every article and experiment,
136
+ or put `annotations: "hypothesis"` in one writing's `meta` block to enable only that entry. An
137
+ entry-level `annotations: none` disables a lab-wide setting. The client is never emitted into PDFs.
138
+ Hypothesis owns annotation accounts, storage, private-group membership, anchoring, and threads;
139
+ collaborators must choose their private group before posting if the discussion is not public. See
140
+ demo article `ar027` for the complete setup and a live example.
141
+
121
142
  ## 7. Adding an experiment
122
143
 
123
144
  **7.1 — Tool subcommand.** Add a subcommand (or reuse one) in the relevant `tools/<tool>/tool.py`. Pass a `manifest` to `write_output` declaring the headline metrics (and a video, for a rendering tool).
@@ -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. Deeper
186
- theming (`style.css`, `favicon.svg`) lives inside the engine package — leave it as advanced.
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,77 @@
1
+ #let meta = (
2
+ title: "Discussing a writeup inline",
3
+ date: "2026-08-07",
4
+ description: "Optional Hypothesis annotations let collaborators highlight a passage in a web article or experiment and discuss it in a private, threaded margin conversation.",
5
+ collection: "documentation",
6
+ status: "final",
7
+ order: 17,
8
+ annotations: "hypothesis",
9
+ )
10
+
11
+ #let hypothesis = "https://web.hypothes.is"
12
+
13
+ #let body = [
14
+ A published result often needs a conversation before it needs another revision. Hypothesis adds
15
+ that conversation directly to a demolab web page: select a passage, attach a comment, and reply
16
+ in a shared thread. The annotation stays outside the scientific record, while the experiment,
17
+ figures, and PDF remain reproducible and unchanged.
18
+
19
+ This page is itself an example. Its `meta` block contains `annotations: "hypothesis"`, so the
20
+ Hypothesis control appears at the right edge of the web edition. Select this sentence and attach
21
+ a private note to test the complete path.
22
+
23
+ == Set up a private reading group
24
+
25
+ + Create free Hypothesis accounts for each collaborator.
26
+ + #link(hypothesis + "/help/how-to-create-a-private-group/")[Create a private group], then send
27
+ its invitation link to the people who should read and reply.
28
+ + Open an annotated demolab page, sign in through the sidebar, and select that private group
29
+ before posting. The public layer is a different destination; check the group name on every
30
+ new top-level comment when the discussion must remain private.
31
+
32
+ Replies form a thread beneath the selected passage. Hypothesis also supports page notes for
33
+ comments about the whole experiment and LaTeX between double dollar signs for mathematical
34
+ discussion.
35
+
36
+ == Enable annotations
37
+
38
+ To annotate every article and experiment, add one field to the root `demolab.yaml`:
39
+
40
+ ```yaml
41
+ annotations: hypothesis
42
+ ```
43
+
44
+ To enable only selected entries, omit the root field and add the provider to a writing's
45
+ metadata instead:
46
+
47
+ ```typ
48
+ #let meta = (
49
+ title: "A result to review",
50
+ date: "2026-08-07",
51
+ annotations: "hypothesis",
52
+ )
53
+ ```
54
+
55
+ Entry metadata wins over the lab setting. Set `annotations: none` on one entry to keep it
56
+ unannotated when the rest of the lab has Hypothesis enabled.
57
+
58
+ == What gets published
59
+
60
+ The setting adds the hosted Hypothesis client to web entry pages only. It does not add scripts
61
+ to the homepage, collection listings, standalone PDFs, or the combined book. Comments and group
62
+ membership live in Hypothesis rather than the git repository, so rebuilding the site does not
63
+ publish a private discussion or alter a recorded result.
64
+
65
+ An annotation targets the page URL and records several descriptions of the selected text. Small
66
+ layout changes and unrelated edits usually leave it attached. Moving the page to another URL,
67
+ deleting the selected passage, or substantially rewriting its context can orphan it. Keep entry
68
+ IDs stable and resolve important review comments before replacing the prose they target.
69
+
70
+ == Operational boundary
71
+
72
+ This option loads JavaScript from `hypothes.is` and sends annotation content to the Hypothesis
73
+ service. That is an intentional external dependency, not part of demolab's committed research
74
+ record. Do not enable it for material whose hosting or data policy forbids that service. Review
75
+ #link(hypothesis + "/privacy/")[Hypothesis privacy information] with your collaborators before
76
+ using it for sensitive or unpublished work.
77
+ ]
@@ -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
+ ]
@@ -14,6 +14,11 @@ description: A lab notebook for computational science — reproducible results,
14
14
  # author: Your Name
15
15
  # contact: you@example.com
16
16
 
17
+ # Optional collaborative inline comments on web articles and experiments. Readers select text
18
+ # and discuss it through Hypothesis accounts and groups; PDFs are unchanged. Set this lab-wide,
19
+ # or omit it here and add `annotations: "hypothesis"` to selected writings' `meta` blocks.
20
+ # annotations: hypothesis
21
+
17
22
  # Optional custom landing page — not a config key: create a `landing.typ` at the repo root
18
23
  # exporting `#let body = [...]` and the homepage renders it (web only) instead of the collection
19
24
  # directory, below the title/tagline/byline above. Delete the file to get the directory back.
@@ -21,12 +26,15 @@ description: A lab notebook for computational science — reproducible results,
21
26
 
22
27
  # Homepage collections. Entries are grouped by their `collection:` meta field; this
23
28
  # registry gives each collection a display label + description and sets the order. A
24
- # collection with no entry here still works — its slug is just title-cased.
29
+ # collection with no entry here still works — its slug is just title-cased. Add
30
+ # `theme: docs` to a collection whose articles should have a light developer-documentation
31
+ # web treatment. Collection listings, experiments, and PDFs retain the standard design.
25
32
  collection-order: [documentation, neuron-models, mujoco, streamlit, slides]
26
33
  collections:
27
34
  documentation:
28
35
  label: Documentation
29
36
  description: "How demolab works: getting started, the always-on guides that set the conventions, and the on-demand runbooks the agent runs."
37
+ # theme: docs
30
38
  neuron-models:
31
39
  label: Neuron models
32
40
  description: Integrate-and-fire neurons and the membrane biophysics behind them.
@@ -83,6 +83,11 @@ def test_demo_fixture_builds_full_site(tmp_path: Path) -> None:
83
83
  root = tmp_path / "repo"
84
84
  root.mkdir()
85
85
  _assemble(root, demo=True)
86
+ # The docs treatment is article-only even when an experiment shares the collection.
87
+ (root / "writings" / "exp099.typ").write_text(
88
+ '#let meta = (title: "Theme boundary", date: "2026-08-14", collection: "theme-demo")\n'
89
+ '#let body = [An experiment keeps the standard presentation.]\n'
90
+ )
86
91
  _build(root)
87
92
 
88
93
  site = root / "artifacts" / "site"
@@ -102,6 +107,43 @@ def test_demo_fixture_builds_full_site(tmp_path: Path) -> None:
102
107
  assert '<ul class="coll-list"' in index, "collection directory visible in a user lab"
103
108
  entry = (site / "ar018.html").read_text()
104
109
  assert "<img" in entry or "<svg" in entry, "a figure made it into the entry page"
110
+ assert 'class="theme-docs"' not in entry, "ordinary collections retain the journal theme"
111
+ collection = (site / "documentation.html").read_text()
112
+ assert 'class="listing theme-docs"' not in collection, "ordinary collection listing stays neutral"
113
+ themed_entry = (site / "ar028.html").read_text()
114
+ assert 'class="theme-docs"' in themed_entry, "collection theme follows articles onto the web"
115
+ assert 'class="docs-toc"' in themed_entry, "developer-docs demo includes an on-page TOC"
116
+ assert 'href="#api-reference"' in themed_entry, "TOC links to generated heading anchors"
117
+ assert "class latticecache.Cache" in themed_entry, "demo includes a representative API reference"
118
+ themed_experiment = (site / "exp099.html").read_text()
119
+ assert 'class="theme-docs"' not in themed_experiment, "experiments stay on the standard theme"
120
+ themed_collection = (site / "theme-demo.html").read_text()
121
+ assert 'class="listing theme-docs"' not in themed_collection, "collection listings stay neutral"
122
+ assert 'class="theme-docs"' not in index, "collection theme does not leak onto the homepage"
123
+ annotated = (site / "ar027.html").read_text()
124
+ assert 'src="https://hypothes.is/embed.js"' in annotated, "opted-in entry embeds Hypothesis"
125
+ assert 'src="https://hypothes.is/embed.js"' not in entry, "annotations remain opt-in"
126
+
127
+
128
+ def test_lab_wide_hypothesis_annotations_only_touch_entries(tmp_path: Path) -> None:
129
+ root = tmp_path / "repo"
130
+ root.mkdir()
131
+ _assemble(root, demo=True)
132
+ with (root / "demolab.yaml").open("a", encoding="utf-8") as config:
133
+ config.write("\nannotations: hypothesis\n")
134
+ opted_out = root / "writings" / "ar018.typ"
135
+ opted_out.write_text(
136
+ opted_out.read_text(encoding="utf-8").replace("order: 1,", "order: 1,\n annotations: none,"),
137
+ encoding="utf-8",
138
+ )
139
+ _build(root)
140
+
141
+ site = root / "artifacts" / "site"
142
+ embed = 'src="https://hypothes.is/embed.js"'
143
+ assert embed in (site / "ar019.html").read_text(), "root config enables normal entries"
144
+ assert embed not in (site / "ar018.html").read_text(), "entry metadata can opt out"
145
+ assert embed not in (site / "index.html").read_text(), "homepage remains annotation-free"
146
+ assert embed not in (site / "documentation.html").read_text(), "listings remain annotation-free"
105
147
 
106
148
 
107
149
  def test_curated_collection_lists_in_reading_order(tmp_path: Path) -> None:
@@ -5,7 +5,8 @@
5
5
  #let has-brand = sys.inputs.at("has-brand", default: "false") == "true"
6
6
  #let config = if has-brand { yaml("/demolab.yaml") } else { (:) }
7
7
  #let brand = default-brand + config
8
+ #let annotations = config.at("annotations", default: none)
8
9
  #import "/writings/" + entry-id + ".typ" as writing
9
10
 
10
11
  #set document(title: writing.meta.title)
11
- #numbered-pages(entry-page(writing.meta, writing.body, id: entry-id, brand: brand))
12
+ #numbered-pages(entry-page(writing.meta, writing.body, id: entry-id, brand: brand, annotations: annotations))
@@ -31,7 +31,7 @@
31
31
  }
32
32
 
33
33
  // --- web-styles: inject the stylesheet + head meta into HTML pages (ignored in the PDF pass) ---
34
- #let web-styles(brand: default-brand) = context {
34
+ #let web-styles(brand: default-brand, annotations: none) = context {
35
35
  if target() == "html" {
36
36
  html.elem("link", attrs: (rel: "icon", type: "image/svg+xml", href: "favicon.svg"))
37
37
  html.elem("style", read("/.demolab/style.css"))
@@ -42,6 +42,11 @@
42
42
  }
43
43
  // hover popovers for inline citations (no-op on pages without cites)
44
44
  html.elem("script", attrs: (src: "cite-popover.js", defer: ""))[]
45
+ // Optional collaborative web annotations. The hosted Hypothesis client owns accounts,
46
+ // private groups, storage, anchoring, and threads; demolab only opts this page into it.
47
+ if annotations == "hypothesis" {
48
+ html.elem("script", attrs: (src: "https://hypothes.is/embed.js", async: ""))[]
49
+ }
45
50
  }
46
51
  }
47
52
 
@@ -185,6 +190,11 @@
185
190
  #let title-case(slug) = slug.split("-").map(w => if w.len() > 0 { upper(w.slice(0, 1)) + w.slice(1) } else { w }).join(" ")
186
191
  #let collection-label(slug, meta) = meta.at(slug, default: (:)).at("label", default: title-case(slug))
187
192
  #let collection-description(slug, meta) = meta.at(slug, default: (:)).at("description", default: none)
193
+ #let collection-theme(slug, meta) = meta.at(slug, default: (:)).at("theme", default: none)
194
+ #let theme-class(theme, base: none) = {
195
+ let classes = if base == none { () } else { (base,) }
196
+ (classes + if theme == none { () } else { ("theme-" + str(theme),) }).join(" ")
197
+ }
188
198
  #let collection-rank(slug, order) = {
189
199
  let i = order.position(s => s == slug)
190
200
  if i == none { order.len() } else { i }
@@ -393,8 +403,19 @@
393
403
  html.elem("p", attrs: (class: "page-foot"), html.elem("a", attrs: (href: "index.html"), [← back to all entries]))
394
404
  }
395
405
 
396
- #let entry-page(meta, body, id: none, brand: default-brand) = {
397
- web-styles(brand: brand)
406
+ #let entry-page(meta, body, id: none, kind: "article", brand: default-brand, annotations: none, collection-meta: (:)) = {
407
+ // Entry metadata can override the lab-wide provider. `none` disables a global provider for
408
+ // one entry; absent metadata inherits the root demolab.yaml setting.
409
+ let annotation-provider = meta.at("annotations", default: annotations)
410
+ web-styles(brand: brand, annotations: annotation-provider)
411
+ // Collection themes are a light, web-only treatment for articles. Experiments, collection
412
+ // listings, PDFs, and the combined book deliberately retain the standard lab presentation.
413
+ let theme = collection-theme(meta.at("collection", default: "uncategorized"), collection-meta)
414
+ context {
415
+ if target() == "html" and kind == "article" and theme != none {
416
+ html.elem("div", attrs: (class: theme-class(theme), "aria-hidden": "true"))[]
417
+ }
418
+ }
398
419
  set text(font: "New Computer Modern", size: 11pt)
399
420
  // Restart figure numbering per entry: the whole bundle is one compile, so Typst's global
400
421
  // figure counter would otherwise carry across every document. Each entry (its page + its
@@ -24,6 +24,7 @@
24
24
  // read from it too. Absent ⇒ engine defaults + derivation-only collections.
25
25
  #let config = if manifest.has_brand_config { yaml("/demolab.yaml") } else { (:) }
26
26
  #let brand = default-brand + config
27
+ #let annotations = config.at("annotations", default: none)
27
28
  #let collection-order = config.at("collection-order", default: ())
28
29
  #let collection-meta = config.at("collections", default: (:))
29
30
 
@@ -79,8 +80,8 @@
79
80
  }
80
81
  }
81
82
  #for e in entries {
82
- [#document(e.id + ".html", title: [#e.meta.title])[#entry-page(e.meta, e.body, id: e.id, brand: brand)]]
83
- [#document("pdfs/" + e.id + ".pdf", title: [#e.meta.title])[#numbered-pages(entry-page(e.meta, e.body, id: e.id, brand: brand))]]
83
+ [#document(e.id + ".html", title: [#e.meta.title])[#entry-page(e.meta, e.body, id: e.id, kind: e.kind, brand: brand, annotations: annotations, collection-meta: collection-meta)]]
84
+ [#document("pdfs/" + e.id + ".pdf", title: [#e.meta.title])[#numbered-pages(entry-page(e.meta, e.body, id: e.id, kind: e.kind, brand: brand, annotations: annotations, collection-meta: collection-meta))]]
84
85
  }
85
86
  // stub pages for entries that failed to build — a visible "this page failed" placeholder at the
86
87
  // entry's own URL (web only; excluded from listings + the book), so the rest of the site is fine.
@@ -356,6 +356,105 @@ pre {
356
356
  }
357
357
  pre code { font-size: 1em; }
358
358
 
359
+ /* Built-in article treatment: developer documentation. A collection opts its articles in
360
+ with `theme: docs` in demolab.yaml. This stays close to demolab's default spacing and
361
+ structure while using the neutral sans/mono pairing, cool slate surfaces, and blue accent
362
+ readers expect from contemporary technical references.
363
+ Collection listings, experiments, PDFs, and the combined book remain unchanged. */
364
+ body:has(.theme-docs) {
365
+ max-width: 46em;
366
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Helvetica, Arial, sans-serif;
367
+ font-size: 17px;
368
+ line-height: 1.65;
369
+ color: #172033;
370
+ --ink: #172033;
371
+ --muted: #64748b;
372
+ --rule: #e2e8f0;
373
+ --rule-strong: #cbd5e1;
374
+ --paper-tint: #f8fafc;
375
+ --selection: #dbeafe;
376
+ --docs-accent: #315faa;
377
+ --mono: ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", monospace;
378
+ }
379
+ body:has(.theme-docs) h1,
380
+ body:has(.theme-docs) h2,
381
+ body:has(.theme-docs) h3,
382
+ body:has(.theme-docs) h4,
383
+ body:has(.theme-docs) h5,
384
+ body:has(.theme-docs) h6 {
385
+ color: #111827;
386
+ font-family: inherit;
387
+ letter-spacing: -.018em;
388
+ }
389
+ body:has(.theme-docs) h5 {
390
+ margin: 1.6rem 0 .4rem;
391
+ font-size: .95rem;
392
+ line-height: 1.35;
393
+ }
394
+ body:has(.theme-docs) h6 {
395
+ margin: 1.35rem 0 .35rem;
396
+ font-size: .9rem;
397
+ line-height: 1.35;
398
+ }
399
+ body:has(.theme-docs) a { color: var(--docs-accent); text-decoration-color: #9db4da; }
400
+ body:has(.theme-docs) .home-link,
401
+ body:has(.theme-docs) .entry-meta,
402
+ body:has(.theme-docs) .page-foot { color: var(--muted); }
403
+ body:has(.theme-docs) code {
404
+ padding: .08em .28em;
405
+ border-radius: 3px;
406
+ color: #334155;
407
+ background: #f1f5f9;
408
+ font-family: var(--mono);
409
+ font-size: .86em;
410
+ }
411
+ body:has(.theme-docs) pre {
412
+ border-color: #d0d7de;
413
+ border-radius: 6px;
414
+ color: #e2e8f0;
415
+ background: #172033;
416
+ box-shadow: 0 1px 2px rgba(15, 23, 42, .06);
417
+ }
418
+ body:has(.theme-docs) pre code { padding: 0; color: inherit; background: transparent; font-size: 1em; }
419
+ body:has(.theme-docs) table { width: 100%; font-size: .88rem; }
420
+ body:has(.theme-docs) th,
421
+ body:has(.theme-docs) td { border-color: var(--rule); padding: .55rem .7rem; vertical-align: top; }
422
+ body:has(.theme-docs) th { color: #334155; background: #f1f5f9; }
423
+ body:has(.theme-docs) td { color: #334155; }
424
+ body:has(.theme-docs) td:last-child,
425
+ body:has(.theme-docs) th:last-child { text-align: left; font-variant-numeric: normal; }
426
+ .docs-toc {
427
+ margin: 1.5rem 0 2rem;
428
+ padding: .85rem 1rem;
429
+ border: 1px solid var(--rule);
430
+ border-radius: 6px;
431
+ background: var(--paper-tint);
432
+ }
433
+ .docs-toc-title { margin: 0 0 .35rem; color: #334155; font-size: .88rem; font-weight: 650; }
434
+ .docs-toc ul { columns: 2; margin: 0; padding-left: 1.15rem; }
435
+ .docs-toc li { margin: .15rem 0; color: var(--muted); font-size: .9rem; }
436
+ .docs-note {
437
+ margin: 1.25rem 0;
438
+ padding: .75rem 1rem;
439
+ border-left: 3px solid var(--docs-accent);
440
+ color: #334155;
441
+ background: #eff6ff;
442
+ }
443
+ .api-signature {
444
+ margin: .8rem 0 1rem;
445
+ padding: .75rem .9rem;
446
+ overflow-x: auto;
447
+ border: 1px solid #dbe3ee;
448
+ border-left: 3px solid #93add3;
449
+ border-radius: 4px;
450
+ color: #1e3a5f;
451
+ background: #f8fafc;
452
+ font-family: var(--mono);
453
+ font-size: .82rem;
454
+ line-height: 1.5;
455
+ }
456
+ .api-signature code { padding: 0; color: inherit; background: transparent; font-size: 1em; }
457
+
359
458
  @media (max-width: 42rem) {
360
459
  body { margin-top: 2rem; margin-bottom: 2.5rem; }
361
460
  .welcome { margin-bottom: 2rem; }
@@ -364,6 +463,7 @@ pre code { font-size: 1em; }
364
463
  .welcome-runbook { display: block; padding: .6rem 0; }
365
464
  .welcome-runbook dt { margin-bottom: .2rem; }
366
465
  .entry-row { align-items: flex-start; gap: .5rem; }
466
+ .docs-toc ul { columns: 1; }
367
467
  }
368
468
 
369
469
  @media (max-width: 22rem) {
@@ -1 +0,0 @@
1
- 2.2.1
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes