From 73998020b3ea712d4a48fac633d6330339a20d13 Mon Sep 17 00:00:00 2001
From: buenosairesam
Date: Tue, 22 Sep 2026 05:45:52 -0300
Subject: [PATCH] docgen graphgen ui and converging update
---
soleprint/atlas2/docgen/Makefile | 10 +-
soleprint/atlas2/docgen/README.md | 4 +-
soleprint/atlas2/docgen/book/__init__.py | 16 +-
soleprint/atlas2/docgen/book/build.py | 20 +-
soleprint/atlas2/docgen/book/checks.py | 12 +-
soleprint/atlas2/docgen/book/config.py | 12 +-
soleprint/atlas2/docgen/book/document.py | 32 +-
soleprint/atlas2/docgen/cli/emit.py | 20 +-
.../atlas2/docgen/docgen.project.example.toml | 4 +-
.../atlas2/docgen/docs/img/architecture.svg | 798 ++++++++--------
soleprint/atlas2/docgen/docs/img/minimap.svg | 883 +++++++++---------
soleprint/atlas2/docgen/docs/index.html | 52 +-
soleprint/atlas2/docgen/docs/models.md | 18 +-
soleprint/atlas2/docgen/emitters/auto.py | 4 +-
soleprint/atlas2/docgen/emitters/dot.py | 4 +-
soleprint/atlas2/docgen/emitters/markdown.py | 4 +-
.../{docsite.py => minisite/__init__.py} | 32 +-
.../emitters/{site.py => minisite/onepage.py} | 16 +-
soleprint/atlas2/docgen/emitters/paged.py | 8 +-
soleprint/atlas2/docgen/ops/pages.py | 6 +-
soleprint/atlas2/docgen/selftest.py | 64 +-
soleprint/atlas2/docgen/viewer_test.js | 2 +-
22 files changed, 1019 insertions(+), 1002 deletions(-)
rename soleprint/atlas2/docgen/emitters/{docsite.py => minisite/__init__.py} (91%)
rename soleprint/atlas2/docgen/emitters/{site.py => minisite/onepage.py} (98%)
diff --git a/soleprint/atlas2/docgen/Makefile b/soleprint/atlas2/docgen/Makefile
index d963bac..24fa88c 100644
--- a/soleprint/atlas2/docgen/Makefile
+++ b/soleprint/atlas2/docgen/Makefile
@@ -55,7 +55,7 @@ SLUG_ARG := $(if $(SLUG),--slug $(SLUG))
OVER_ARG := $(if $(OVERLAY),--overlay $(OVERLAY))
ONLY_ARGS := $(foreach n,$(subst $(comma), ,$(ONLY)),--only $(n))
-.PHONY: help sync lock book run scaffold check ir db code view graph index site minimap explore docs self doctor clean
+.PHONY: help sync lock book run scaffold check ir db code view graph index minisite minimap explore docs self doctor clean
help: ## List every target
@echo "docgen — static analysis of a tree, and the artifacts that fall out of it"
@@ -130,9 +130,9 @@ index: ## OUT/ir.json -> OUT/index.md and OUT/sidebar.json
@$(CLI) emit index $(OUT)/ir.json -o $(OUT)/index.md
@$(CLI) emit index $(OUT)/ir.json -o $(OUT)/sidebar.json
-site: view ## OUT/view.json -> a self-contained docs site in OUT/site
- @$(CLI) emit site $(OUT)/view.json -o $(OUT)/site $(STYLE_ARGS)
- @echo " open $(OUT)/site/index.html"
+minisite: view ## OUT/view.json -> a self-contained page in OUT/minisite
+ @$(CLI) emit minisite $(OUT)/view.json -o $(OUT)/minisite $(STYLE_ARGS)
+ @echo " open $(OUT)/minisite/index.html"
minimap: ## OUT/ir.json -> OUT/minimap.svg — what is where, read from the colours
@$(CLI) emit minimap $(OUT)/ir.json -o $(OUT)/minimap.svg $(STYLE_ARGS) --scale $(SCALE)
@@ -149,7 +149,7 @@ docs: ## Regenerate the figures in docs/ — docgen documented by docgen
@$(CLI) emit minimap /tmp/$(PKG)-docs.json -o $(HERE)/docs/img/minimap.svg --scale 0.5 --width 860
@$(CLI) emit erd $(OUT)/ir.json -o $(HERE)/docs/img/erd.svg 2>/dev/null \
|| echo " (erd figure kept — needs a schema IR at $(OUT)/ir.json to refresh)"
- @PYTHONPATH=$(PARENT) $(PY) -c "from $(PKG).emitters.site import VIEWER, _slots, _fill; \
+ @PYTHONPATH=$(PARENT) $(PY) -c "from $(PKG).emitters.minisite import VIEWER, _slots, _fill; \
from $(PKG).style import Style; import pathlib; \
pathlib.Path('$(HERE)/docs/viewer.html').write_text( \
_fill(VIEWER.replace('__TITLE__', 'docgen docs'), _slots(Style.load('lucid'))))"
diff --git a/soleprint/atlas2/docgen/README.md b/soleprint/atlas2/docgen/README.md
index b02ddf8..f44859a 100644
--- a/soleprint/atlas2/docgen/README.md
+++ b/soleprint/atlas2/docgen/README.md
@@ -102,7 +102,7 @@ output made of all of them. Keys docgen deliberately doesn't do (`[extract.db] u
shell/YAML/SQL `ext`, unknown readers) are refused with what to do instead.
A book's diagram is split into **pages** by structure — overview first, one page per
-subsystem — measured until each fits its column. On the site the diagrams are inline,
+subsystem — measured until each fits its column. On the minisite the diagrams are inline,
so a box opens its page; in a notebook, a page index links every figure, and each
figure is an SVG cell attachment that renders without running anything.
@@ -282,7 +282,7 @@ about either.
## Testing
```bash
-make check # docgen's own suite: 346 after make sync, 331 with nothing
+make check # docgen's own suite: 348 after make sync, 333 with nothing
make check BOOK=out/book/x # one book's own level — or a project's .docgen/
make doctor # the machine; never fails
```
diff --git a/soleprint/atlas2/docgen/book/__init__.py b/soleprint/atlas2/docgen/book/__init__.py
index f5fea7e..856c4ab 100644
--- a/soleprint/atlas2/docgen/book/__init__.py
+++ b/soleprint/atlas2/docgen/book/__init__.py
@@ -30,7 +30,7 @@ artifacts useful, and that property is the whole reason the sequence is worth
having.
**Not a new pipeline.** Everything here composes functions that already existed
-— `ops.overview`, `emitters.dot`, `emitters.site`, `notebook.spec`. The spine
+— `ops.overview`, `emitters.dot`, `emitters.minisite`, `notebook.spec`. The spine
adds a ledger and two measurements. If it ever starts doing the work itself,
something has gone wrong.
@@ -48,7 +48,7 @@ from pathlib import Path
from .larder import Larder
-# The two spine steps. Named here because the notebook, the site and the checks
+# The two spine steps. Named here because the notebook, the minisite and the checks
# all have to agree on what they are called, and three string literals in three
# files is how they stop agreeing.
FIRST = "larder"
@@ -258,13 +258,13 @@ class Book:
# -- writing ----------------------------------------------------------
- def close(self, *, site=None) -> Step:
+ def close(self, *, minisite=None) -> Step:
"""Append the last step. Call once, after the web output is written.
Must be called BEFORE the notebook is built, because the notebook quotes
the book measure and the measure is not complete until this step exists.
- The site is this step's *artifact*. The notebook is not: it is the whole
+ The minisite is this step's *artifact*. The notebook is not: it is the whole
sequence's rendering rather than an item in it, and it is set on
`self.notebook` afterwards, by name only — a notebook cannot report its
own byte count without changing it.
@@ -273,10 +273,10 @@ class Book:
summary = " · ".join(f"{v} {k}" for k, v in
sorted(m["by_kind"].items(), key=lambda kv: -kv[1]))
artifact, size = None, 0
- if site is not None:
- site = Path(site)
- if site.exists():
- artifact, size = str(site.relative_to(self.out)), _tree_bytes(site)
+ if minisite is not None:
+ minisite = Path(minisite)
+ if minisite.exists():
+ artifact, size = str(minisite.relative_to(self.out)), _tree_bytes(minisite)
self.steps.append(Step(
id=LAST,
label="what came out",
diff --git a/soleprint/atlas2/docgen/book/build.py b/soleprint/atlas2/docgen/book/build.py
index 0122b96..96a92e7 100644
--- a/soleprint/atlas2/docgen/book/build.py
+++ b/soleprint/atlas2/docgen/book/build.py
@@ -4,7 +4,7 @@ Run a book: larder, the steps, the web output, the book measure.
python3 -m docgen book --root ../station -o out/book/station
Composes functions that already exist. Nothing here parses, lays out or styles
-anything — `ops.overview`, `emitters.auto.draw`, `emitters.site` and `notebook.spec`
+anything — `ops.overview`, `emitters.auto.draw`, `emitters.minisite` and `notebook.spec`
do all of it, and this decides the order and writes the ledger. If this file
ever starts doing the work, the seam has moved to the wrong place.
@@ -170,7 +170,7 @@ def spec_from(book: Book, ir: dict, *, prelude_dir: str | None = None) -> dict:
f"**{summary} · {m['edges']} edges · {m['external']} external**\n\n"
f"{len(m['artifacts'])} artifact(s), {m['bytes']:,} bytes.\n\n"
f"Reconciled against what came in:\n\n{reconciled}\n\n"
- "The web output is `site/index.html`."
+ "The web output is `minisite/index.html`."
),
))
@@ -229,7 +229,9 @@ def _load_cell(step_id: str, artifact: str) -> str:
# remove first. Named, never globbed: a book directory also holds the hand-written
# `checks.py` and `overlay.json`, and a misconfigured output path could point at
# somebody's source tree — so nothing outside this list is ever deleted.
-GENERATED = ("book.json", "notebook.ipynb", "steps", "site", "explore", "document.json")
+# `site` is the minisite's old name: a book built before the rename still has one.
+GENERATED = ("book.json", "notebook.ipynb", "steps", "minisite", "site", "explore",
+ "document.json")
# The steps a book runs, by id — fixed, so a note can be filed under one before
# the book has ever been built, and a skipped step does not make its note drift.
@@ -249,7 +251,7 @@ def clear(out) -> list[str]:
"""Remove a previous build's outputs from `out`. Returns what was removed.
Without this, re-running a book leaves the last run's artifacts beside the new
- ledger: a graph that is now `graph.md` keeps its old `graph.svg`, and the site
+ ledger: a graph that is now `graph.md` keeps its old `graph.svg`, and the minisite
goes on showing it. The ledger would not list the stale file, so nothing would
say it was stale — which is the failure the book measure exists to prevent.
@@ -275,7 +277,7 @@ def clear(out) -> list[str]:
def build_book(kind: str, source, out, *, slug: str | None = None, exclude=(),
ops: dict | None = None, suffixes=None, quiet: bool = False) -> Book:
- """One book's steps: larder, IR, view, pages, drawings. No site, no notebook.
+ """One book's steps: larder, IR, view, pages, drawings. No minisite, no notebook.
Those are a document's outputs and are assembled by `book/document.py`, which
may put several books into one of each. The book is returned open — its last
@@ -385,8 +387,8 @@ def run(kind: str, source, out, *, slug: str | None = None, style: str = "lucid"
quiet: bool = False) -> Book:
"""One book, as a one-book document whose outputs live in its own directory.
- `book.json`, `steps/`, `site/` and `notebook.ipynb` land where they always did;
- the site and notebook are now assembled the same way a many-book document's are.
+ `book.json`, `steps/`, `minisite/` and `notebook.ipynb` land where they always did;
+ the minisite and notebook are now assembled the same way a many-book document's are.
"""
from .document import Document, Output, Part, build
@@ -397,7 +399,7 @@ def run(kind: str, source, out, *, slug: str | None = None, style: str = "lucid"
notes=Path(notes) if notes else None,
parts=[Part(name=slug, kind=kind, source=Path(source), out=out,
exclude=tuple(exclude), overlay=overlay)],
- outputs=[Output("site", out / "site", style=style, theme=theme),
+ outputs=[Output("minisite", out / "minisite", style=style, theme=theme),
Output("notebook", out / "notebook.ipynb", style=style, theme=theme)],
)
build(doc, quiet=quiet)
@@ -412,6 +414,6 @@ def run(kind: str, source, out, *, slug: str | None = None, style: str = "lucid"
f"{m['external']} external · {len(m['artifacts'])} artifacts")
for r in book.compare():
print(f" {'ok ' if r['ok'] else 'LOST'} {r['claim']}")
- print(f" open {out / 'site' / 'index.html'}")
+ print(f" open {out / 'minisite' / 'index.html'}")
print(f" {out / 'book.json'}")
return book
diff --git a/soleprint/atlas2/docgen/book/checks.py b/soleprint/atlas2/docgen/book/checks.py
index 3c56c5c..118892c 100644
--- a/soleprint/atlas2/docgen/book/checks.py
+++ b/soleprint/atlas2/docgen/book/checks.py
@@ -191,12 +191,12 @@ def generated(book: Loaded, r: Report) -> None:
r.check(f"its {ran} code cells run, not merely compile", None, failure)
r.note("the web output exists and shows both ends")
- index = book.dir / "site" / "index.html"
+ index = book.dir / "minisite" / "index.html"
if not index.exists():
- r.skip("site", "no site/index.html in this book")
+ r.skip("minisite", "no minisite/index.html in this book")
else:
html = index.read_text()
- # The site is the last step precisely because it is the artifact somebody
+ # The minisite is the last step precisely because it is the artifact somebody
# definitely opens. A page that shows the result without showing what
# went in is the thing this whole change corrects.
r.check("the page says what came in", True, "what came in" in html)
@@ -205,7 +205,7 @@ def generated(book: Loaded, r: Report) -> None:
r.check("the page admits the book is incomplete", True,
"This book is incomplete" in html)
- # A book built inline — its own site and notebook in its own directory —
+ # A book built inline — its own minisite and notebook in its own directory —
# also carries the document ledger, and with it the notes it used.
doc_ledger = book.dir / "document.json"
if doc_ledger.is_file():
@@ -334,8 +334,8 @@ def document(build_dir, r: Report) -> None:
r.skip("figures carry their SVG", f"{len(figures) - drawn} not drawn — Graphviz absent?")
else:
r.check("every figure carries its SVG, so it renders without running", True, True)
- elif out["emitter"] == "site":
- r.note(f"the site — {path}")
+ elif out["emitter"] == "minisite":
+ r.note(f"the minisite — {path}")
index = path / "index.html"
r.check("index.html exists", True, index.is_file())
if not index.is_file():
diff --git a/soleprint/atlas2/docgen/book/config.py b/soleprint/atlas2/docgen/book/config.py
index bff8674..f7bb708 100644
--- a/soleprint/atlas2/docgen/book/config.py
+++ b/soleprint/atlas2/docgen/book/config.py
@@ -365,7 +365,7 @@ def run(runfile, names=None, *, check: bool = False, quiet: bool = True):
# [project] name, title, notes, build, reference
# [extract.] one book per table; [[extract.]] for several
# [ops] applied to every book's view, before paging
-# [emit.
@@ -131,7 +131,7 @@ make check BOOK=out/book/mine
larder /path/to/repo — 45 files read, 2 failed, 12 packages
book 312 nodes · 244 edges · 26 external · 8 artifacts
ok 45 file(s) read produced 45 module(s)
- open out/book/mine/site/index.html
+ open out/book/mine/minisite/index.html
Underneath it is three commands that compose, and each still works on its own.
@@ -151,7 +151,7 @@ python3 -m docgen emit auto view.json -o out/
make ir SRC=/path/to/repo OUT=out # extract
make explore OUT=out # the two-pane navigator
-make site OUT=out # a docs site with a sidebar
+make minisite OUT=out # a one-page site with a sidebar
make self # docgen's book of soleprint, then check it
@@ -163,7 +163,7 @@ make self # docgen's book of soleprint
Your first project
- From nothing to a notebook and a site for a project of yours, with your own text
+ From nothing to a notebook and a minisite for a project of yours, with your own text
in the right places. docgen lives in its own folder, and the project only gets one
config file and, later, a folder of notes.
Paths are relative to this file. Keep the outputs outside the source tree. See
@@ -231,7 +231,7 @@ python3 -m docgen scaffold docgen.toml # or: write an e
Pick a spot and write markdown into notes/<book>/<spot>.md,
for example notes/code/overview.md above the overview page. Rebuild
- and it's there, in the notebook and on the site. Stubs you leave empty add
+ and it's there, in the notebook and on the minisite. Stubs you leave empty add
nothing.
@@ -474,7 +474,7 @@ python3 -m docgen scaffold docgen.toml # or: write an e
├── notebook.ipynb the sequence; first and last cells are the measures
├── overlay.json hand-written, optional, re-applied every build
├── checks.py this book's own assertions — optional
-└── site/ the web output, both measures at the top
+└── minisite/ the web output, both measures at the top
make book SRC=../station BOOK=out/book/station
make check BOOK=out/book/station
@@ -726,7 +726,7 @@ python3 -m docgen view ir.json --shape # what will this
erd
SVG, written directly
a schema, as cards
minimap
SVG, written directly
what is where, at a glance
notebook
.ipynb
a runnable walkthrough
-
site
a static docs site
reading
+
minisite
a static docs site
reading
explore
a two-pane navigator
finding your way
auto
whichever of the above fits
not having to choose
@@ -821,18 +821,18 @@ python3 -m docgen view ir.json --shape # what will this
Measured on soleprint, whose module graph rendered at 14:1 as one picture: 69
- pages, and at the site's 800px column the widest flagged page is 988px.
+ pages, and at the minisite's 800px column the widest flagged page is 988px.
Schemas page differently: cards in columns capped to the width, grouped by
tables joined by foreign keys, so a schema grows downward instead of sideways.
Which pages exist doesn't depend on width, only how each is laid out
- does. A notebook column and a site column get the same pages, which is what lets
+ does. A notebook column and a minisite column get the same pages, which is what lets
a note filed under a page land in the same place in both.
-
Clicking through, on the site
+
Clicking through, on the minisite
Every page is its own HTML file with the diagram inline, not in an
@@ -978,7 +978,7 @@ python3 -m docgen emit notebook ir.json --overlay overlay.json -o walkthrough.ip
To put text above a page, create the file named after it. No schema, no JSON,
no ids to look up. That's something a person or a model can do without reading
anything first. Local images in a note are embedded: as a cell attachment in the
- notebook, as a data URI on the site.
+ notebook, as a data URI on the minisite.
make scaffold CONFIG=docgen.toml # an empty stub for every spot, never overwriting
@@ -1129,10 +1129,10 @@ make doctor
context
checks
skipped
-
in the repo, synced
346
—
-
copied out, bare python3
329
tree-sitter, OpenAPI, the in-place case
-
copied out, synced
338
OpenAPI, the in-place case
-
copied out, synced, DOCGEN_REFERENCE set
345
+
in the repo, synced
348
—
+
copied out, bare python3
331
tree-sitter, OpenAPI, the in-place case
+
copied out, synced
340
OpenAPI, the in-place case
+
copied out, synced, DOCGEN_REFERENCE set
347
the in-place case — correctly, there is no repo above
A rebuild replaces the previous build, and nothing else. Before writing,
a book removes exactly what a build writes — book.json,
- notebook.ipynb, steps/, site/,
+ notebook.ipynb, steps/, minisite/,
explore/ — and only from a directory that already holds a
book.json. Without it a graph that is now graph.md kept
its old graph.svg, unlisted in the ledger and still on the page.
@@ -1262,14 +1262,14 @@ drop_builtins = true
output = "../docs_out/orders_api_notebook"
theme = "lucid"
-[emit.site]
-output = "../docs_out/orders_api_site"
+[emit.minisite]
+output = "../docs_out/orders_api_minisite"
width = 800
Each [extract.*] table is a book, and a book is a part of
every output rather than an output of its own. Each [emit.*] table
- is an output made of all of them: one notebook and one site, each with a part
+ is an output made of all of them: one notebook and one minisite, each with a part
per book. Several sources of one kind use [[extract.code]] with a
name each. [ops] applies to every book's view before
paging.
@@ -1326,7 +1326,7 @@ width = 800
@@ -1407,7 +1407,7 @@ python3 -m docgen emit explore ir.json -o explore/
A command reports an expected failure by raising, and cli.main
turns it into the only exit path: one Error: line on stderr, exit 1,
never a traceback. Doing that split also surfaced a third copy of "which drawing
- does this graph want" — in the auto command, the site
+ does this graph want" — in the auto command, the minisite
command and the book — which is now emitters.auto.draw(), returning
content and leaving the file name to the caller.
@@ -1473,14 +1473,14 @@ make lock # after editing pyproject
command
asks about
fails?
make doctor
the machine — what is installed
never; it reports
-
make check
docgen — 346 checks
+
make check
docgen — 348 checks
exit 1
make check BOOK=<dir>
that one book, or a project's document
exit 1
- All 346 after make sync, 331 with nothing optional installed — the
+ All 348 after make sync, 333 with nothing optional installed — the
suite skips rather than fails when tree-sitter, lxml or the OpenAPI reader is
absent. See standalone for the counts outside the repo.
Among them, two that keep this page's structure true: nothing outside
diff --git a/soleprint/atlas2/docgen/docs/models.md b/soleprint/atlas2/docgen/docs/models.md
index d92e257..142df0e 100644
--- a/soleprint/atlas2/docgen/docs/models.md
+++ b/soleprint/atlas2/docgen/docs/models.md
@@ -68,8 +68,8 @@ drop_builtins = true
output = "../docs_out/orders_service_notebook"
theme = "lucid"
-[emit.site]
-output = "../docs_out/orders_service_site"
+[emit.minisite]
+output = "../docs_out/orders_service_minisite"
```
### `[project]`
@@ -112,8 +112,8 @@ name = "frontend"
root = "web/src"
ext = [".ts", ".tsx"]
-[emit.site]
-output = "../docs_out/storefront_site"
+[emit.minisite]
+output = "../docs_out/storefront_minisite"
```
### `[ops]`: applied to every book before drawing
@@ -131,14 +131,15 @@ Kinds: `module`, `class`, `interface`, `function`, `external`, `table`, `column`
### `[emit.]`: the outputs
-The outputs are `notebook` and `site`, nothing else.
+The outputs are `notebook` and `minisite`, nothing else. `site` is the old name of
+`minisite` and is refused.
| key | required | value |
|---|---|---|
| `output` | yes | a directory. A notebook output may also be a path ending in `.ipynb` |
| `theme` | no | `"dark"` (default) or `"lucid"` (light, for print) |
| `style` | no | `"lucid"` (default) |
-| `width` | no | pixels a diagram page must fit; default 960 for a notebook, 800 for a site |
+| `width` | no | pixels a diagram page must fit; default 960 for a notebook, 800 for a minisite |
---
@@ -174,7 +175,7 @@ A book's own value replaces the default for that key. Values are never merged.
## Notes
A note is a markdown file. docgen puts its content directly above the spot it is named
-after, in both the notebook and the site. Its path is:
+after, in both the notebook and the minisite. Its path is:
```
/_intro.md top of the whole document
@@ -250,7 +251,8 @@ The person running docgen pastes the errors back. Each one says what to change:
| `docgen reads a schema dump and never connects` | remove `url`; use `schema = "path/to/schema.json"` |
| `no grammar for .sh` | remove those extensions from `ext` |
| `no reader named 'x'` | use one of `python`, `code`, `db`, `openapi`, `usage` |
-| `no output named 'x'` | use `notebook` or `site` |
+| `no output named 'x'` | use `notebook` or `minisite` |
+| `is now [emit.…] — rename the table` | rename `[emit.site]` to `[emit.minisite]`; nothing else changes |
| `is inside the tree it reads` | move `output` or `build` outside the source directory |
| `needs a name when there are several` | give each `[[extract.x]]` table a different `name` |
| `must name exactly one source` | a `[[book]]` needs exactly one of `root`, `schema`, `openapi`, `har` |
diff --git a/soleprint/atlas2/docgen/emitters/auto.py b/soleprint/atlas2/docgen/emitters/auto.py
index b7e8aa2..8e5764e 100644
--- a/soleprint/atlas2/docgen/emitters/auto.py
+++ b/soleprint/atlas2/docgen/emitters/auto.py
@@ -11,7 +11,7 @@ because that *is* the right artifact for a flat list of peers.
## One function, three callers
This branch — erd, or index, or dot through Graphviz — used to be written out
-three times: in the `auto` command, in the `site` command choosing its picture,
+three times: in the `auto` command, in the `minisite` command choosing its picture,
and in the book choosing its graph step. Three copies of "which drawing does this
graph want" is three chances for them to disagree about the answer, so it lives
here and returns content rather than writing it. Where the file goes, and what
@@ -37,7 +37,7 @@ def draw(ir: dict, style, *, force: str | None = None) -> dict:
"why": why this emitter, or why nothing was drawn}
Never raises for a missing Graphviz. A layered graph on a machine without
- `dot` is a normal state — the book records the step as skipped and the site
+ `dot` is a normal state — the book records the step as skipped and the minisite
goes text-only — so it is reported in `why` with `content` None.
"""
verdict = classify(ir)
diff --git a/soleprint/atlas2/docgen/emitters/dot.py b/soleprint/atlas2/docgen/emitters/dot.py
index bc4915b..9bde39d 100644
--- a/soleprint/atlas2/docgen/emitters/dot.py
+++ b/soleprint/atlas2/docgen/emitters/dot.py
@@ -78,7 +78,7 @@ def _node_attrs(node, rule: dict, style, links: dict | None = None) -> dict:
attrs = node.get("attrs") or {}
if links and node["id"] in links:
# A link the caller supplied wins over the source anchor: on a paginated
- # site a box is a way into its own page, which is worth more than the
+ # minisite a box is a way into its own page, which is worth more than the
# file it came from.
a["href"] = links[node["id"]]
a["tooltip"] = attrs.get("doc") or f"open {node['id']}"
@@ -100,7 +100,7 @@ def emit(ir: dict, style, *, max_depth: int | None = None,
`links` maps node ids to URLs and becomes each box's `href` in the SVG.
Passed in rather than read from the IR, because where a box leads is a fact
- about the output — a site page, a notebook anchor — and the IR is the same
+ about the output — a minisite page, a notebook anchor — and the IR is the same
for every output. Same rule that keeps colour out of it.
"""
nodes = {n["id"]: n for n in ir["nodes"]}
diff --git a/soleprint/atlas2/docgen/emitters/markdown.py b/soleprint/atlas2/docgen/emitters/markdown.py
index 767cd78..c70691c 100644
--- a/soleprint/atlas2/docgen/emitters/markdown.py
+++ b/soleprint/atlas2/docgen/emitters/markdown.py
@@ -1,7 +1,7 @@
"""
-Markdown -> HTML, for notes on the site. Stdlib only, and deliberately small.
+Markdown -> HTML, for notes on the minisite. Stdlib only, and deliberately small.
-A notebook renders markdown itself, so notes go into it as written. The site has
+A notebook renders markdown itself, so notes go into it as written. The minisite has
no renderer, and adding a dependency to turn a few paragraphs into HTML would
break the rule that the structural path is the stdlib. So this covers what notes
are actually written with:
diff --git a/soleprint/atlas2/docgen/emitters/docsite.py b/soleprint/atlas2/docgen/emitters/minisite/__init__.py
similarity index 91%
rename from soleprint/atlas2/docgen/emitters/docsite.py
rename to soleprint/atlas2/docgen/emitters/minisite/__init__.py
index cc902dc..d0b7e59 100644
--- a/soleprint/atlas2/docgen/emitters/docsite.py
+++ b/soleprint/atlas2/docgen/emitters/minisite/__init__.py
@@ -1,9 +1,9 @@
"""
-A document's site: an index of books, and every book as pages you click through.
+The minisite: a document's index of books, and every book as pages you click through.
- site/
+ minisite/
├── index.html every book, with what came in and what came out
- ├── site.css viewer.html
+ ├── minisite.css viewer.html
└── /
├── overview.html the book's top page
├── .html one subsystem each; its boxes open the next level
@@ -12,7 +12,7 @@ A document's site: an index of books, and every book as pages you click through.
## Inline, so the boxes are links
-The single-page site put its diagram in an ``, and an image swallows every
+The single-page reference (`onepage.py`) put its diagram in an ``, and an image swallows every
click. Here the SVG is inlined, so a box that stands for a subsystem opens that
subsystem's page, a box for a module opens its section of the reference page,
and a context box — something outside this subsystem — opens its own branch.
@@ -33,10 +33,10 @@ never heard of a book's extractor, and this one has not either.
from html import escape
from pathlib import Path
-from .markdown import to_html
-from .paged import inline
-from .site import CONTENT_W, CSS, SIDEBAR_W, VIEWER, _fill, _ledger, _slots
-from .site import emit as reference_page
+from ..markdown import to_html
+from ..paged import inline
+from .onepage import CONTENT_W, CSS, SIDEBAR_W, VIEWER, _fill, _ledger, _slots
+from .onepage import emit as reference_page
CONTENT_WIDTH = CONTENT_W
@@ -75,7 +75,7 @@ def _note(note, cls="note") -> str:
def _missing(failed: dict | None) -> str:
"""Books that were meant to be here and are not. Above the ones that are,
- because a reader who does not see this has no way to know the site is short."""
+ because a reader who does not see this has no way to know the minisite is short."""
if not failed:
return ""
items = "".join(f"
{escape(name)} — {escape(why)}
"
@@ -116,7 +116,7 @@ def _page(title: str, book: dict, page: dict, svg: str | None, pages: list[dict]
else:
crumbs.append(f'{escape(p["title"])}')
- from ..book.document import caption # noqa: F401 - kept out of module import time
+ from ...book.document import caption # noqa: F401 - kept out of module import time
cap = caption(page)
warn = " warn" if page.get("too_wide") else ""
if svg is not None:
@@ -144,7 +144,7 @@ def _page(title: str, book: dict, page: dict, svg: str | None, pages: list[dict]
{escape(page["title"])} — {escape(b.slug)} — {escape(doc_title)}
-
+
@@ -174,7 +174,7 @@ def _page(title: str, book: dict, page: dict, svg: str | None, pages: list[dict]
def write(out_dir, style, *, title: str, books: list[dict], intro=None, end=None,
failed: dict | None = None) -> list[Path]:
- """Write the site. Returns every file written, for the output manifest.
+ """Write the minisite. Returns every file written, for the output manifest.
`books` is a list of::
@@ -193,7 +193,7 @@ def write(out_dir, style, *, title: str, books: list[dict], intro=None, end=None
path.write_text(text)
written.append(path)
- put(out_dir / "site.css",
+ put(out_dir / "minisite.css",
_fill(CSS.replace("__SIDEBAR__", str(SIDEBAR_W)).replace("__CONTENT__", str(CONTENT_W)),
values))
put(out_dir / "viewer.html", _fill(VIEWER.replace("__TITLE__", escape(title)), values))
@@ -209,10 +209,10 @@ def write(out_dir, style, *, title: str, books: list[dict], intro=None, end=None
if svg is not None:
put(book_dir / f"{page['slug']}.svg", svg)
- # The reference page: the single-page site as it was, one per book, where
+ # The reference page: the single-page `onepage` as it was, one per book, where
# a module box on any page opens its own section.
ref = reference_page(b.view, style, title=f"{b.slug} — reference")["index.html"]
- ref = ref.replace('href="site.css"', 'href="../site.css"')
+ ref = ref.replace('href="minisite.css"', 'href="../minisite.css"')
put(book_dir / "reference.html", ref)
explore = b.out / "explore"
@@ -246,7 +246,7 @@ def write(out_dir, style, *, title: str, books: list[dict], intro=None, end=None
{escape(title)}
-
+
diff --git a/soleprint/atlas2/docgen/emitters/site.py b/soleprint/atlas2/docgen/emitters/minisite/onepage.py
similarity index 98%
rename from soleprint/atlas2/docgen/emitters/site.py
rename to soleprint/atlas2/docgen/emitters/minisite/onepage.py
index e48c933..aedcccc 100644
--- a/soleprint/atlas2/docgen/emitters/site.py
+++ b/soleprint/atlas2/docgen/emitters/minisite/onepage.py
@@ -1,5 +1,7 @@
"""
-IR -> a self-contained documentation site: sidebar, content, graph viewer.
+IR -> one self-contained page: sidebar, content, graph viewer.
+
+The minisite's per-book reference page, and `emit minisite` on a bare IR.
Not invented here. Five demos under `semester/` already converged on the same
two files, and this generates that arrangement rather than a sixth variant:
@@ -29,7 +31,7 @@ are looking at. A click that moved the mouse is a drag and does not toggle.
## Colours
Baked from the same style slots as every diagram, so the page and the graph on
-it match by construction. `--theme lucid` produces a light site and a light
+it match by construction. `--theme lucid` produces a light page and a light
diagram together; nothing has to be kept in sync by hand.
Self-contained and offline: no CDN, no build step, opens over `file://`.
@@ -316,7 +318,7 @@ body {
def _ledger(book: dict) -> str:
"""The two measures, and the gap between them if there is one.
- This is the whole reason the site is the book's last step rather than just
+ This is the whole reason the minisite is the book's last step rather than just
another emitter: it is the one artifact somebody definitely opens, so it is
where "45 of 47 files" has to appear. A diagram cannot say it, and a log
nobody reads does not count as having said it.
@@ -435,9 +437,9 @@ def emit(ir: dict, style, *, graph: str | None = None, title: str = "",
`book` is a book ledger (`book/__init__.py`). When present the page opens
with both measures — what went in, what came out — because a page that
shows only the result is the thing the measure exists to correct.
- Optional, so the site emitter still works on a bare IR.
+ Optional, so the page still works on a bare IR.
"""
- from .index import to_sidebar
+ from ..index import to_sidebar
meta = ir.get("meta", {})
name = title or meta.get("root", "docs")
@@ -477,7 +479,7 @@ def emit(ir: dict, style, *, graph: str | None = None, title: str = "",
{escape(name)}
-
+
@@ -519,7 +521,7 @@ document.querySelectorAll('h2[id], h3[id]').forEach(function (h) {{ obs.observe(
return {
"index.html": index,
"viewer.html": _fill(VIEWER.replace("__TITLE__", escape(name)), values),
- "site.css": _fill(
+ "minisite.css": _fill(
CSS.replace("__SIDEBAR__", str(SIDEBAR_W)).replace("__CONTENT__", str(CONTENT_W)),
values,
),
diff --git a/soleprint/atlas2/docgen/emitters/paged.py b/soleprint/atlas2/docgen/emitters/paged.py
index 92ba6f1..b942541 100644
--- a/soleprint/atlas2/docgen/emitters/paged.py
+++ b/soleprint/atlas2/docgen/emitters/paged.py
@@ -17,7 +17,7 @@ page records `measured: False` so nobody mistakes one for the other.
Graphviz and the ERD emitter both write sizes in points, and a browser draws a
point as 4/3 of a CSS pixel. Budgets here are pixels, because the column they
-have to fit — a notebook cell, a site's content column — is sized in pixels.
+have to fit — a notebook cell, a minisite's content column — is sized in pixels.
"""
import re
@@ -25,8 +25,8 @@ import re
PT_TO_PX = 4 / 3
# Budgets for the outputs docgen writes. A notebook cell in JupyterLab or VS Code
-# is a little under a thousand pixels at a normal window size; the site's content
-# column is set by `site.CONTENT_W`.
+# is a little under a thousand pixels at a normal window size; the minisite's
+# content column is set by `minisite.CONTENT_W`.
NOTEBOOK_WIDTH = 960
@@ -72,7 +72,7 @@ def fits_for(style, width: int):
def links_for(pages: list[dict], page: dict, url) -> dict:
"""{box id on this page: url of the page that box opens}.
- `url(target_page)` builds the address, so the site and anything else that
+ `url(target_page)` builds the address, so the minisite and anything else that
can follow a link decide their own scheme. Context boxes link too, when their
branch has a page of its own — that is how a reader crosses to a sibling.
"""
diff --git a/soleprint/atlas2/docgen/ops/pages.py b/soleprint/atlas2/docgen/ops/pages.py
index 22dc851..469cba2 100644
--- a/soleprint/atlas2/docgen/ops/pages.py
+++ b/soleprint/atlas2/docgen/ops/pages.py
@@ -12,7 +12,7 @@ estimated from node counts is wrong the moment the labels are long.
**Which pages exist does not depend on width** — only how each one is laid out
does. So the two are separate calls: the page ids a note is filed under are the
-same for a notebook column and a site column, and listing them needs no
+same for a notebook column and a minisite column, and listing them needs no
renderer at all.
## By structure, never by slicing
@@ -24,7 +24,7 @@ nothing on its own. A page here is a subsystem:
└── station one box per child of `station`
└── station.tools one box per child of `station.tools`, and so on
-Clicking a box — on the site — or following the index — in a notebook — moves
+Clicking a box — on the minisite — or following the index — in a notebook — moves
down a level. Every page is a real graph: the boxes are the scope's children,
and anything outside the scope that they touch appears as a **context** box,
collapsed to the branch it sits on, so a page still says what its subsystem
@@ -203,7 +203,7 @@ def paginate(view: dict, *, max_depth: int = 6) -> list[dict]:
`options` and `too_wide` are placeholders until `layout()` fills them.
in reading order — a page is followed by its own children, depth first — so
- a notebook can emit them in sequence and a site can build its tree from
+ a notebook can emit them in sequence and a minisite can build its tree from
`parent` and `children`.
"""
pages: list[dict] = []
diff --git a/soleprint/atlas2/docgen/selftest.py b/soleprint/atlas2/docgen/selftest.py
index 9575d56..86611ae 100644
--- a/soleprint/atlas2/docgen/selftest.py
+++ b/soleprint/atlas2/docgen/selftest.py
@@ -46,7 +46,7 @@ index_mod = __import__(f"{PKG}.emitters.index", fromlist=["*"])
ops_mod = __import__(f"{PKG}.ops", fromlist=["*"])
erd_mod = __import__(f"{PKG}.emitters.erd", fromlist=["*"])
nb_mod = __import__(f"{PKG}.emitters.notebook", fromlist=["*"])
-site_mod = __import__(f"{PKG}.emitters.site", fromlist=["*"])
+site_mod = __import__(f"{PKG}.emitters.minisite.onepage", fromlist=["*"])
mm_mod = __import__(f"{PKG}.emitters.minimap", fromlist=["*"])
exp_mod = __import__(f"{PKG}.emitters.explore", fromlist=["*"])
spec_mod = __import__(f"{PKG}.notebook", fromlist=["*"])
@@ -1284,18 +1284,18 @@ check("every verdict carries options", "options" in ops_mod.classify(ir))
# --------------------------------------------------------------------------
-print("\n11. the docs site")
+print("\n11. the minisite's one page")
# Not invented here: five demos under semester/ carry the same viewer, differing
# only in comments and one background colour. This generates that arrangement
# rather than a sixth copy — and adds the 1:1 toggle none of them has.
-site_dir = ROOT.parent / "site"
+site_dir = ROOT.parent / "minisite"
files = site_mod.write(ops_mod.overview(ir), lucid, site_dir, graph="graph.svg", title="fx")
names = {f.name for f in files}
-check("it writes the three files", names == {"index.html", "viewer.html", "site.css"}, str(names))
+check("it writes the three files", names == {"index.html", "viewer.html", "minisite.css"}, str(names))
index_html = (site_dir / "index.html").read_text()
-css = (site_dir / "site.css").read_text()
+css = (site_dir / "minisite.css").read_text()
viewer = (site_dir / "viewer.html").read_text()
check(
@@ -1316,8 +1316,8 @@ check(
light = site_mod.emit(ops_mod.overview(ir), print_theme, graph="graph.svg")
check(
"a light theme gives a light page",
- print_theme.slot("surface-0") in light["site.css"]
- and lucid.slot("surface-0") not in light["site.css"],
+ print_theme.slot("surface-0") in light["minisite.css"]
+ and lucid.slot("surface-0") not in light["minisite.css"],
"the page and the diagram on it move together",
)
check(
@@ -1766,11 +1766,11 @@ def _books():
ran, failure = checks_mod._run_cells(nb["cells"], out)
check(f"its {ran} code cells run from the book directory", failure is None, failure or "")
- html = (out / "site" / "index.html").read_text()
+ html = (out / "minisite" / "index.html").read_text()
check(
"the web output shows both ends",
"what came in" in html and "what came out" in html,
- "the site is the last step because it is the artifact somebody opens",
+ "the minisite is the last step because it is the artifact somebody opens",
)
check(
"and admits when the book is incomplete",
@@ -1783,7 +1783,7 @@ def _books():
again = Path(tmp.name) / "book-out-2"
build_mod.run("python", fixture, again, slug="fixture", quiet=True)
differs = [
- name for name in ("book.json", "notebook.ipynb", "steps/ir.json", "site/index.html")
+ name for name in ("book.json", "notebook.ipynb", "steps/ir.json", "minisite/index.html")
if (out / name).read_bytes() != (again / name).read_bytes()
]
check("the same larder builds the same bytes", not differs, f"differ: {differs}")
@@ -2094,9 +2094,12 @@ def _layout():
and "Traceback" not in bad.stderr and bad.stderr.count("\n") == 1,
f"exit {bad.returncode}: {bad.stderr[:120]!r}",
)
+ old = run("emit", "site")
+ check("`emit site` names its new name, `emit minisite`",
+ old.returncode == 1 and "is now emit minisite" in old.stderr, old.stderr[:120])
# The structure-picks-the-drawing branch used to be written three times —
- # the `auto` command, the `site` command, the book. Now it is `draw()`, and
+ # the `auto` command, the `minisite` command, the book. Now it is `draw()`, and
# the book reaching past it to the concrete emitters would be the first copy.
# Parsed, not grepped, for the reason section 2 gives: the docstring naming
# `emitters.dot` is prose, and the first version of this check failed on it.
@@ -2266,7 +2269,7 @@ def _runfiles():
check(
"a rebuild removes the previous build's outputs",
again["app"]["ok"] and not (app / "steps" / "stale.svg").exists(),
- "a stale graph.svg would go on showing in the site, unlisted in the ledger",
+ "a stale graph.svg would go on showing in the minisite, unlisted in the ledger",
)
check("...and keeps the hand-written checks.py", (app / "checks.py").exists())
@@ -2381,7 +2384,7 @@ def _pages():
and p["children"]],
)
# Which pages exist must not depend on width — notes are filed by page id,
- # and a note has to land in the same place in a notebook and on a site.
+ # and a note has to land in the same place in a notebook and on a minisite.
check(
"which pages exist does not depend on width",
[p["id"] for p in pages_mod.layout(structure, lambda ir, o: False)]
@@ -2406,7 +2409,7 @@ def _pages():
if px > 800 and not p["too_wide"]:
over.append(f"{p['id']} {px:.0f}px")
check("no page is wider than its budget unless it says so", not over, "; ".join(over))
- # Links are the output's, never the IR's: a site passes them, and then a
+ # Links are the output's, never the IR's: a minisite passes them, and then a
# relative source path — a dead link on a web page — must not survive.
page = next(p for p in laid if p["children"])
links = paged.links_for(laid, page, lambda t: f"{t['slug']}.html")
@@ -2546,15 +2549,15 @@ def _documents():
build_mod.run("python", src, out, slug="app", notes=notes, quiet=True)
check("the same sources and notes build the same notebook", first == (out / "notebook.ipynb").read_bytes())
- # -- the site: drill-down --------------------------------------------------
- site = out / "site"
+ # -- the minisite: drill-down ----------------------------------------------
+ site = out / "minisite"
page_files = sorted((site / "app").glob("*.html"))
broken = []
for f in page_files:
for href in re.findall(r'href="([^"#?]+)', f.read_text()):
if not href.startswith(("http:", "https:")) and not (f.parent / href).exists():
broken.append(f"{f.name} -> {href}")
- check("every link on every site page opens something", not broken, broken[:5])
+ check("every link on every minisite page opens something", not broken, broken[:5])
overview_html = (site / "app" / "overview.html").read_text()
check(
"a page's diagram is inline, so its boxes can be clicked",
@@ -2569,15 +2572,15 @@ def _documents():
'href="overview.html"' in child_html.split('class="crumbs"')[1].split("
")[0],
)
check("a page has a fit / 1:1 control", 'data-size="actual"' in overview_html)
- check("the site renders the note above the diagram",
+ check("the minisite renders the note above the diagram",
overview_html.index("Written by hand") < overview_html.index('class="figbar"'))
# Manifests: a rebuild removes what the last one wrote, and nothing else.
- project_out = base / "project-site"
+ project_out = base / "project-minisite"
doc = doc_mod.Document(
name="p", title="P", build=base / "p-build", notes=None,
parts=[doc_mod.Part(name="app", kind="python", source=src, out=base / "p-build" / "app")],
- outputs=[doc_mod.Output("site", project_out)])
+ outputs=[doc_mod.Output("minisite", project_out)])
doc_mod.build(doc)
(project_out / "app" / "stale.html").write_text("left by a previous build")
manifest = json.loads((project_out / ".docgen-output.json").read_text())
@@ -2592,19 +2595,19 @@ def _documents():
# One book that cannot be built must not cost the document — and the document
# has to say it is short, where a reader will see it, not only in a terminal.
(base / "broken.har").write_text("not a recording")
- partial_site = base / "partial-site"
+ partial_site = base / "partial-minisite"
partial = doc_mod.Document(
name="partial", title="Partial", build=base / "partial-build", notes=None,
parts=[doc_mod.Part(name="bad", kind="usage", source=base / "broken.har",
out=base / "partial-build" / "bad"),
doc_mod.Part(name="app", kind="python", source=src,
out=base / "partial-build" / "app")],
- outputs=[doc_mod.Output("site", partial_site),
+ outputs=[doc_mod.Output("minisite", partial_site),
doc_mod.Output("notebook", base / "partial.ipynb")])
doc_mod.build(partial)
check("a book that fails does not stop the others",
[b.slug for b in partial.books] == ["app"] and "bad" in partial.failed)
- check("the site says the document is incomplete",
+ check("the minisite says the document is incomplete",
"This document is incomplete" in (partial_site / "index.html").read_text())
check("...and so does the notebook",
"This document is incomplete" in (base / "partial.ipynb").read_text())
@@ -2636,7 +2639,7 @@ def _projects():
project, problems = load(
'[project]\nname = "p"\n\n[extract.python]\nroot = "src"\n\n'
'[extract.db]\nschema = "shop.json"\n\n[ops]\ndrop_stdlib = true\n\n'
- '[emit.notebook]\noutput = "out/nb"\n\n[emit.site]\noutput = "out/site"\nwidth = 700\n')
+ '[emit.notebook]\noutput = "out/nb"\n\n[emit.minisite]\noutput = "out/minisite"\nwidth = 700\n')
check("a project file loads", isinstance(project, cfg.ProjectFile), problems)
if project is None:
return
@@ -2644,10 +2647,10 @@ def _projects():
check(
"each [extract.*] is a book, each [emit.*] an output of all of them",
[p.name for p in doc.parts] == ["python", "db"]
- and sorted(o.emitter for o in doc.outputs) == ["notebook", "site"],
+ and sorted(o.emitter for o in doc.outputs) == ["minisite", "notebook"],
)
check("[ops] applies to every book", all(p.ops.get("drop_stdlib") for p in doc.parts))
- check("an output's width is kept", next(o for o in doc.outputs if o.emitter == "site").width == 700)
+ check("an output's width is kept", next(o for o in doc.outputs if o.emitter == "minisite").width == 700)
books_file, _ = load('[[book]]\nname = "a"\nroot = "src"\n', "books.toml")
check("a [[book]] list still loads as it did", isinstance(books_file, cfg.RunFile))
@@ -2661,7 +2664,7 @@ def _projects():
'[project]\nname = "p"\n\n'
'[extract.db]\nurl = "postgresql://user:hunter2@localhost:5432/app"\n\n'
'[extract.code]\nroot = "src"\next = [".sh", ".yaml", ".sql"]\n\n'
- '[extract.seed]\nroot = "src"\n\n[emit.site]\noutput = "out"\n', "refused.toml")
+ '[extract.seed]\nroot = "src"\n\n[emit.minisite]\noutput = "out"\n', "refused.toml")
text = "\n".join(refused)
check("a database url is refused, pointing at modelgen from-db",
"never connects" in text and "modelgen from-db" in text, refused)
@@ -2670,8 +2673,13 @@ def _projects():
"no grammar for .sh, .yaml, .sql" in text and ".cs" in text)
check("an unknown reader is refused, naming the readers", "no reader named 'seed'" in text)
_, inside = load('[project]\nname = "p"\n[extract.python]\nroot = "src"\n'
- '[emit.site]\noutput = "src/site"\n', "inside.toml")
+ '[emit.minisite]\noutput = "src/minisite"\n', "inside.toml")
check("an output inside a tree it reads is refused", any("inside the tree" in p for p in inside), inside)
+ _, renamed = load('[project]\nname = "p"\n[extract.python]\nroot = "src"\n'
+ '[emit.site]\noutput = "out/site"\n', "renamed.toml")
+ check("the old [emit.site] is refused with its new name, not as unknown",
+ any("is now [emit.minisite]" in p for p in renamed)
+ and not any("no output named" in p for p in renamed), renamed)
example = cfg.load(HERE / "docgen.project.example.toml")
check("the shipped project example loads",
diff --git a/soleprint/atlas2/docgen/viewer_test.js b/soleprint/atlas2/docgen/viewer_test.js
index 4ed571c..4707a55 100644
--- a/soleprint/atlas2/docgen/viewer_test.js
+++ b/soleprint/atlas2/docgen/viewer_test.js
@@ -1,6 +1,6 @@
// Drive the generated viewer's logic under a stub DOM and assert the toggle.
const fs = require('fs');
-const html = fs.readFileSync(process.argv[2] || '/tmp/site/viewer.html', 'utf8');
+const html = fs.readFileSync(process.argv[2] || '/tmp/minisite/viewer.html', 'utf8');
const script = html.split('')[0];
const handlers = {};