diff --git a/packages/overview/INDEX.md b/packages/overview/INDEX.md index d35059f..31ddb01 100644 --- a/packages/overview/INDEX.md +++ b/packages/overview/INDEX.md @@ -1,8 +1,8 @@ # Documentation index -Read **high-level first**, then **architecture**, then the rest. Live catalog: https://zapier.georgelambert.org/ +Read **high-level first**, then **architecture**, then the rest. Live catalog (PDF by default): https://zapier.georgelambert.org/ — [Markdown indexes](https://zapier.georgelambert.org/index-md.html) -Operator console (loopback): http://127.0.0.1:3850/ +Operator console (loopback): http://127.0.0.1:3850/ · [CONSOLE.pdf](https://zapier.georgelambert.org/packages/verae-fleet/docs/CONSOLE.pdf) ## 1. High-level (non-technical) @@ -24,25 +24,25 @@ Operator console (loopback): http://127.0.0.1:3850/ | Address routing | [06-address-routing.md](06-address-routing.md) | | External resources | [07-external-resources.md](07-external-resources.md) | | Diagrams | [08-diagrams.md](08-diagrams.md) | -| Catalog home | https://zapier.georgelambert.org/ | -| Composition | https://zapier.georgelambert.org/docs/02-architecture/composition.html | -| NATS gateway | https://zapier.georgelambert.org/docs/02-architecture/nats-gateway.html | -| NATS subjects | https://zapier.georgelambert.org/docs/02-architecture/nats-subjects.html | -| Archive / bloom | https://zapier.georgelambert.org/docs/02-architecture/archive-nats.html | -| Module NATS map | https://zapier.georgelambert.org/docs/02-architecture/modules-and-nats.html | -| Tree nodes | https://zapier.georgelambert.org/docs/02-architecture/tree-nodes.html | -| Fleet | https://zapier.georgelambert.org/docs/02-architecture/fleet.html | +| Catalog home | https://zapier.georgelambert.org/ (PDF) · [Markdown indexes](https://zapier.georgelambert.org/index-md.html) | +| Composition | https://zapier.georgelambert.org/docs/02-architecture/composition.pdf | +| NATS gateway | https://zapier.georgelambert.org/docs/02-architecture/nats-gateway.pdf | +| NATS subjects | https://zapier.georgelambert.org/docs/02-architecture/nats-subjects.pdf | +| Archive / bloom | https://zapier.georgelambert.org/docs/02-architecture/archive-nats.pdf | +| Module NATS map | https://zapier.georgelambert.org/docs/02-architecture/modules-and-nats.pdf | +| Tree nodes | https://zapier.georgelambert.org/docs/02-architecture/tree-nodes.pdf | +| Fleet | https://zapier.georgelambert.org/docs/02-architecture/fleet.pdf | ## 3. Remaining documentation | Document | URL | |----------|-----| | Expansion template | [09-expansion-template.md](09-expansion-template.md) | -| Operator console | https://git.georgelambert.org/marchon/verae-fleet/src/branch/main/docs/CONSOLE.md | -| User guide | https://zapier.georgelambert.org/user-docs/README.md | -| Tree-node lookup (users) | https://zapier.georgelambert.org/packages/zapier-user-docs/09-lookup-tree-nodes.html | -| Zapier developer setup | https://zapier.georgelambert.org/docs/04-activate/SETUP-ZAPIER-DEVELOPER.html | -| docs-master | https://zapier.georgelambert.org/docs-master/README.md | +| Operator console | https://zapier.georgelambert.org/packages/verae-fleet/docs/CONSOLE.pdf | +| User guide | https://zapier.georgelambert.org/user-docs/README.pdf | +| Tree-node lookup (users) | https://zapier.georgelambert.org/packages/zapier-user-docs/09-lookup-tree-nodes.pdf | +| Zapier developer setup | https://zapier.georgelambert.org/docs/04-activate/SETUP-ZAPIER-DEVELOPER.pdf | +| docs-master | https://zapier.georgelambert.org/docs-master/README.pdf | ## Independent git repositories diff --git a/packages/overview/README.md b/packages/overview/README.md index 5ccd0c4..538167a 100644 --- a/packages/overview/README.md +++ b/packages/overview/README.md @@ -3,7 +3,7 @@ High-level description of the whole system: what it is, which **independent git repositories** implement it, how it stays up, how it behaves when the local network fails, and how it talks to the **central Verae NATS.IO 3-server cluster**. New work is added by **new address routing**, not by teaching Zapier about NATS. **This repo:** https://git.georgelambert.org/marchon/overview -**Live catalog:** https://zapier.georgelambert.org/ +**Live catalog (PDF by default):** https://zapier.georgelambert.org/ · [Markdown indexes](https://zapier.georgelambert.org/index-md.html) **Clone:** `ssh://git@git.georgelambert.org:2223/marchon/overview.git` ## Table of contents @@ -49,7 +49,7 @@ Clone any of them: `git clone ssh://git@git.georgelambert.org:2223/marchon/ + + + + + + Operator console + + + + +
Verae Time × Zapier

Operator console

packages/verae-fleet/docs/CONSOLE.md
+
+

Operator console

+
+

Operator console

+

Loopback UI at http://127.0.0.1:3850/ — Fleet, Trace +(simulator), and Docs in one shell. Not a public site. NATS stays +private; SSH identity files stay on disk.

+
cd packages/verae-fleet
+node src/cli.js serve
+

Tabs

+ ++++ + + + + + + + + + + + + + + + + + + + + +
TabPurpose
FleetMachines (SSH/local), replica floors, green/yellow/red, RTT +min/avg/p50/p90 spark, pause/restart/off with confirm, ssh-check +toast
TraceIn-process Zapier simulator: run a step, hop log, +monitors/suggestions
DocsReading order: overview → module summaries → architecture → +remaining docs
+

Screenshots

+
+Fleet + +
+
+Trace + +
+
+Docs + +
+

Design tokens match the zappier portal/admin (indigo shell). Customer +portal is unchanged.

+

See overview.

+ + diff --git a/packages/verae-fleet/docs/CONSOLE.pdf b/packages/verae-fleet/docs/CONSOLE.pdf new file mode 100644 index 0000000..f1d1b27 Binary files /dev/null and b/packages/verae-fleet/docs/CONSOLE.pdf differ diff --git a/packages/verae-fleet/public/index.html b/packages/verae-fleet/public/index.html index 7f578e3..f193e6c 100644 --- a/packages/verae-fleet/public/index.html +++ b/packages/verae-fleet/public/index.html @@ -44,8 +44,8 @@ .pill.good { background:#c8efd4; color:var(--ok); } .pill.warn { background:#ffe08a; color:var(--warn); } .pill.bad { background:#f3c0c0; color:var(--err); } - button.act { margin:.15rem .2rem 0 0; border:1px solid var(--line); background:#fff; border-radius:8px; padding:.28rem .5rem; font:650 12px system-ui; cursor:pointer; } - button.act.primary { background:var(--accent); color:#fff; border:0; } + button.act, a.act { margin:.15rem .2rem 0 0; border:1px solid var(--line); background:#fff; border-radius:8px; padding:.28rem .5rem; font:650 12px system-ui; cursor:pointer; text-decoration:none; color:var(--ink); display:inline-block; } + button.act.primary, a.act.primary { background:var(--accent); color:#fff; border:0; } button.act.danger { color:var(--err); } table { width:100%; border-collapse:collapse; background:var(--panel); border-radius:var(--radius); overflow:hidden; box-shadow:var(--shadow); } th,td { text-align:left; padding:.45rem .6rem; border-bottom:1px solid var(--line); font-size:13px; vertical-align:top; } @@ -150,33 +150,36 @@ invoice-1003
-

Reading order: high-level first, then architecture, then the rest. Operator console is loopback only.

+

Reading order: high-level first, then architecture, then the rest. Catalog defaults to colored PDFs. Operator console is loopback only.

+

Open CONSOLE.pdf + · PDF catalog + · Markdown indexes

High-level (start here)

Architecture

Remaining documentation

diff --git a/packages/verae-fleet/src/server.js b/packages/verae-fleet/src/server.js index 8d89e19..311be8b 100644 --- a/packages/verae-fleet/src/server.js +++ b/packages/verae-fleet/src/server.js @@ -108,11 +108,27 @@ export function startControlServer(sup, mon) { fs.createReadStream(dest).pipe(res); return; } - if (req.method === 'GET' && url.pathname.startsWith('/docs/screenshots/')) { + if (req.method === 'GET' && url.pathname.startsWith('/docs/')) { const dest = path.normalize(path.join(PUBLIC, '..', url.pathname)); - const root = path.normalize(path.join(PUBLIC, '..')); - if (!dest.startsWith(root) || !fs.existsSync(dest)) return json(res, 404, { error: 'not found' }); - res.writeHead(200, { 'content-type': 'image/png' }); + const root = path.normalize(path.join(PUBLIC, '..', 'docs')); + if ( + !(dest === root || dest.startsWith(root + path.sep)) || + !fs.existsSync(dest) || + !fs.statSync(dest).isFile() + ) { + return json(res, 404, { error: 'not found' }); + } + const ext = path.extname(dest).toLowerCase(); + const types = { + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.svg': 'image/svg+xml', + '.pdf': 'application/pdf', + '.md': 'text/markdown; charset=utf-8', + '.html': 'text/html; charset=utf-8', + }; + res.writeHead(200, { 'content-type': types[ext] || 'application/octet-stream' }); fs.createReadStream(dest).pipe(res); return; } diff --git a/scripts/build-docs-site.py b/scripts/build-docs-site.py index cd5e90c..1756632 100755 --- a/scripts/build-docs-site.py +++ b/scripts/build-docs-site.py @@ -1,22 +1,30 @@ #!/usr/bin/env python3 -"""Build static documentation site into ./site for zapier.georgelambert.org.""" +"""Build static documentation site into ./site for zapier.georgelambert.org. + +Markdown is rendered to colored HTML *and* PDF. The home page (index.html) +defaults to PDF links; index-md.html is the Markdown/HTML index. +""" from __future__ import annotations import shutil import subprocess +from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime, timezone from pathlib import Path ROOT = Path(__file__).resolve().parents[1] SITE = ROOT / "site" +CSS = ROOT / "scripts" / "docs-print.css" SECTIONS = [ ( "Start here", [ + ("packages/overview/README.md", "System overview (plain language)"), + ("packages/overview/INDEX.md", "Documentation index"), + ("packages/verae-fleet/docs/CONSOLE.md", "Operator console (Fleet · Trace · Docs)"), ("README.md", "Workspace README"), - ("docs/04-activate/SETUP-ZAPIER-DEVELOPER.html", "Zapier developer setup (HTML)"), ("docs/04-activate/SETUP-ZAPIER-DEVELOPER.pdf", "Zapier developer setup (PDF)"), ("docs/OPEN.md", "Still open"), ("TODO.md", "Implementation TODO / gates"), @@ -33,8 +41,11 @@ SECTIONS = [ ("docs/02-architecture/modules-and-nats.md", "Module catalog and NATS addresses"), ("docs/02-architecture/tree-nodes.md", "Tree nodes and bulk Merkle summaries"), ("docs/02-architecture/fleet.md", "Fleet replica floors and monitor"), - ("packages/overview/README.md", "System overview (TOC)"), - ("packages/overview/INDEX.md", "Documentation index"), + ("packages/overview/03-nats-cluster.md", "NATS.IO 3-server cluster"), + ("packages/overview/04-uptime.md", "Uptime / replica floors"), + ("packages/overview/05-network-failures.md", "Local network failures"), + ("packages/overview/06-address-routing.md", "Address routing"), + ("packages/overview/08-diagrams.md", "Architectural diagrams"), ], ), ( @@ -82,44 +93,162 @@ def copy_tree(src: Path, dest: Path, ignore=None) -> None: shutil.copytree(src, dest, dirs_exist_ok=True, ignore=ignore) -def pandoc_md(src: Path, dest: Path, title: str) -> None: - dest.parent.mkdir(parents=True, exist_ok=True) - css = """ - body{font:16px/1.5 Georgia,serif;max-width:48rem;margin:2rem auto;padding:0 1rem;color:#122} - a{color:#0b4f8a} code,pre{font-family:ui-monospace,Menlo,monospace;font-size:0.88rem} - pre{background:#1b2833;color:#eef;padding:0.8rem;overflow:auto} - table{border-collapse:collapse} td,th{border:1px solid #ccc;padding:0.35rem 0.5rem} - nav{font:14px system-ui;margin-bottom:1.5rem} - """ - header = dest.with_suffix(".hdr.html") - header.write_text( - f"\n\n", +def site_href_for(path: str, src: Path) -> tuple[str, str]: + """Return (pdf_or_native_href, md_or_html_href) relative to SITE.""" + suffix = src.suffix.lower() + if suffix == ".md": + pdf = Path(path).with_suffix(".pdf").as_posix() + html = Path(path).with_suffix(".html").as_posix() + return pdf, html + return path, path + + +def render_md(src: Path, html_dest: Path, pdf_dest: Path, title: str) -> str: + """Pandoc markdown → colored HTML + WeasyPrint PDF. Returns '' on success.""" + html_dest.parent.mkdir(parents=True, exist_ok=True) + pdf_dest.parent.mkdir(parents=True, exist_ok=True) + header = html_dest.with_suffix(".hdr.html") + banner = html_dest.with_suffix(".ban.html") + rel_src = src.relative_to(ROOT).as_posix() if src.is_relative_to(ROOT) else src.name + css_text = CSS.read_text(encoding="utf-8") + header.write_text(f"\n", encoding="utf-8") + banner.write_text( + f'
' + f'' + f'
Verae Time × Zapier
' + f"

{title}

" + f'
{rel_src}
' + f"
\n", encoding="utf-8", ) - subprocess.run( + resource = str(src.parent) + r = subprocess.run( [ "pandoc", str(src), "-o", - str(dest), + str(html_dest), "--standalone", + f"--resource-path={resource}", + "--highlight-style=breezedark", f"--metadata=title={title}", f"--include-in-header={header}", + f"--include-before-body={banner}", ], - check=False, capture_output=True, + text=True, ) header.unlink(missing_ok=True) + banner.unlink(missing_ok=True) + if r.returncode != 0: + return f"pandoc {src}: {r.stderr[-400:]}" + w = subprocess.run( + ["weasyprint", str(html_dest), str(pdf_dest)], + capture_output=True, + text=True, + ) + if w.returncode != 0: + return f"weasyprint {src}: {w.stderr[-400:]}" + return "" -def walk_md_pdf(prefix: Path) -> list[tuple[str, str]]: - items = [] - if not prefix.exists(): - return items - for p in sorted(prefix.rglob("*")): - if p.suffix.lower() in {".md", ".pdf", ".html", ".yaml", ".yml", ".svg"} and p.is_file(): - items.append((rel(p), p.name)) - return items +def convert_all_markdown(copied: list[tuple[Path, Path, str]]) -> list[str]: + errors = [] + n = len(copied) + print(f"rendering {n} markdown files to HTML + PDF…") + with ThreadPoolExecutor(max_workers=6) as pool: + futs = { + pool.submit(render_md, src, html, pdf, title): src + for src, html, pdf, title in ( + ( + src, + dest.with_suffix(".html"), + dest.with_suffix(".pdf"), + title, + ) + for src, dest, title in copied + ) + } + done = 0 + for fut in as_completed(futs): + err = fut.result() + done += 1 + if err: + errors.append(err) + print(f" [{done}/{n}] FAIL {futs[fut].name}") + elif done % 25 == 0 or done == n: + print(f" [{done}/{n}]") + return errors + + +def format_switch(to_md: bool) -> str: + if to_md: + href, label = "/index-md.html", "Markdown indexes" + else: + href, label = "/", "PDF catalog" + return ( + f'{label}' + ) + + +def page_shell(now: str, body: str, switch_to_md: bool, title: str) -> str: + switch = format_switch(switch_to_md) + return f""" + + + + + {title} + + + +
+ {switch} +
zapier.georgelambert.org
+

Verae Time × Zapier documentation

+

Project reference: setup, architecture, NATS, module APIs, user guide, and operator console. + Catalog defaults to colored PDFs. Generated {now}.

+
+
+

+ {body} +
+ + + +""" def main() -> None: @@ -147,12 +276,37 @@ def main() -> None: "verae-nats-process", "docs-master", ): - readme = ROOT / "packages" / pkg / "README.md" - if readme.exists(): - copy_tree(readme, SITE / "packages" / pkg / "README.md") + pkg_root = ROOT / "packages" / pkg + if pkg in {"zapier-user-docs", "overview", "docs-master"}: + copy_tree( + pkg_root, + SITE / "packages" / pkg, + ignore=shutil.ignore_patterns("node_modules", ".git", "src", "test", "data"), + ) + elif pkg == "verae-fleet": + for name in ("README.md", "SUMMARY.md", "NATS.md", "SERVICES.md"): + p = pkg_root / name + if p.exists(): + copy_tree(p, SITE / "packages" / pkg / name) + copy_tree( + pkg_root / "docs", + SITE / "packages" / pkg / "docs", + ignore=shutil.ignore_patterns("node_modules"), + ) + elif pkg == "verae-nats-process": + for p in pkg_root.glob("*.md"): + copy_tree(p, SITE / "packages" / pkg / p.name) + else: + readme = pkg_root / "README.md" + if readme.exists(): + copy_tree(readme, SITE / "packages" / pkg / "README.md") zdocs = ROOT / "packages" / "zappier" / "docs" if zdocs.exists(): - copy_tree(zdocs, SITE / "packages" / "zappier" / "docs", ignore=shutil.ignore_patterns("screenshots", "walkthrough", "superpowers")) + copy_tree( + zdocs, + SITE / "packages" / "zappier" / "docs", + ignore=shutil.ignore_patterns("screenshots", "walkthrough", "superpowers"), + ) for name in ("README.md", "TODO.md", "OPEN.md"): src = ROOT / name if not src.exists() and name == "OPEN.md": @@ -163,11 +317,15 @@ def main() -> None: copy_tree(ROOT / "packages" / "docs-master", SITE / "docs-master") copy_tree(ROOT / "packages" / "overview", SITE / "overview") copy_tree(ROOT / "packages" / "zapier-user-docs", SITE / "user-docs", ignore=shutil.ignore_patterns("node_modules")) + copy_tree( + ROOT / "packages" / "verae-fleet" / "docs", + SITE / "packages" / "verae-fleet" / "docs", + ignore=shutil.ignore_patterns("node_modules"), + ) sim_pub = ROOT / "packages" / "verae-zapier-simulator" / "public" / "index.html" if sim_pub.exists(): copy_tree(sim_pub, SITE / "simulator" / "index.html") - # research markdown + diagrams only r = ROOT / "research" / "zapier" if r.exists(): for fname in ( @@ -186,45 +344,73 @@ def main() -> None: if diagrams.exists(): copy_tree(diagrams, SITE / "research" / "docs" / "diagrams") - # HTML versions of markdown for the catalog entries - html_pairs = [] - for _title, links in SECTIONS: + # Render every published .md from the site copy so relative images resolve. + to_render: list[tuple[Path, Path, str]] = [] + seen: set[Path] = set() + for md in SITE.rglob("*.md"): + if not md.is_file(): + continue + key = md.resolve() + if key in seen: + continue + seen.add(key) + to_render.append((md, md, md.stem.replace("-", " "))) + + # Prefer curated titles for catalog entries + title_map = {} + for _sec, links in SECTIONS: for path, label in links: - src = ROOT / path - if src.suffix == ".md" and src.exists(): - dest = SITE / Path(path).with_suffix(".html") - pandoc_md(src, dest, label) - html_pairs.append((path, dest.relative_to(SITE).as_posix(), label)) + title_map[path] = label + labeled = [] + for src, dest, title in to_render: + try: + key = dest.relative_to(SITE).as_posix() + except ValueError: + key = dest.name + # packages/overview/... also listed as packages/overview + labeled.append((src, dest, title_map.get(key, title_map.get(src.relative_to(ROOT).as_posix() if src.is_relative_to(ROOT) else key, title)))) + + errors = convert_all_markdown(labeled) + + # Also write CONSOLE.pdf next to the markdown in the fleet package (local console) + console_md = ROOT / "packages" / "verae-fleet" / "docs" / "CONSOLE.md" + if console_md.exists(): + err = render_md( + console_md, + ROOT / "packages" / "verae-fleet" / "docs" / "CONSOLE.html", + ROOT / "packages" / "verae-fleet" / "docs" / "CONSOLE.pdf", + "Operator console", + ) + if err: + errors.append(err) now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC") - cards = [] - for title, links in SECTIONS: - lis = [] - for path, label in links: - src = ROOT / path - if not src.exists() and path == "OPEN.md": - src = ROOT / "docs" / "OPEN.md" - path = "docs/OPEN.md" - if not src.exists(): - continue - href = path - html = Path(path).with_suffix(".html").as_posix() - if src.suffix == ".md" and (SITE / html).exists(): - href = html - lis.append(f'
  • {label} {path}
  • ') - cards.append(f"

    {title}

    ") - extra = [ - ("docs/modules/README.md", "Module API sheets"), - ("docs/models/README.md", "Data models"), - ("sphinx/index.html", "Sphinx HTML"), - ("docs/04-activate/SETUP-ZAPIER-DEVELOPER.html", "Setup HTML"), - ("docs-master/README.md", "Master summaries (docs-master)"), - ("docs-master/MESSAGE-FLOWS.md", "Numbered message flows"), - ("docs-master/modules-and-nats.md", "NATS address table"), - ("user-docs/README.md", "User guide index"), - ("packages/verae-zapier-simulator/README.md", "Simulator README"), - ] + def cards(mode: str) -> str: + out = [] + for title, links in SECTIONS: + lis = [] + for path, label in links: + src = ROOT / path + if not src.exists() and path == "OPEN.md": + src = ROOT / "docs" / "OPEN.md" + path = "docs/OPEN.md" + if not src.exists(): + continue + pdf_href, html_href = site_href_for(path, src) + if mode == "pdf": + href = pdf_href if (SITE / pdf_href).exists() or src.suffix.lower() in {".pdf", ".yaml", ".yml"} else html_href + badge = "PDF" if href.endswith(".pdf") else src.suffix.lstrip(".").upper() or "FILE" + else: + href = html_href if (SITE / html_href).exists() else path + badge = "MD" if src.suffix.lower() == ".md" else src.suffix.lstrip(".").upper() or "FILE" + lis.append( + f'
  • {label}' + f'{badge}' + f'{path}
  • ' + ) + out.append(f"

    {title}

    ") + return "".join(out) git_repos = [ ("master-zapier-plan-draft", "Monorepo (this workspace)"), @@ -248,41 +434,44 @@ def main() -> None: f'{desc}' for name, desc in git_repos ) - extra_lis = "".join( - f'
  • {lab}
  • ' for h, lab in extra if (SITE / h).exists() or h.startswith("sphinx") - ) - index = f""" - - - - - Verae Time × Zapier documentation - - - -
    -
    zapier.georgelambert.org
    -

    Verae Time × Zapier documentation

    -

    Project reference: setup checklist, architecture (including NATS job wait and WORM archive fan-out), module API sheets, models, OpenAPI, and research notes. Generated {now}.

    -
    -
    -

    - {''.join(cards)} + extra_pdf = [ + ("docs-master/README.pdf", "Master summaries (docs-master)"), + ("docs-master/MESSAGE-FLOWS.pdf", "Numbered message flows"), + ("docs-master/modules-and-nats.pdf", "NATS address table"), + ("user-docs/README.pdf", "User guide index"), + ("packages/verae-zapier-simulator/README.pdf", "Simulator README"), + ("packages/verae-fleet/docs/CONSOLE.pdf", "Operator console"), + ("overview/README.pdf", "System overview"), + ("overview/INDEX.pdf", "Documentation index"), + ("docs/modules-pdf/", "Module PDFs (book)"), + ("docs/models-pdf/", "Model PDFs (book)"), + ("sphinx/index.html", "Sphinx HTML"), + ] + extra_md = [ + ("docs-master/README.html", "Master summaries (docs-master)"), + ("docs-master/MESSAGE-FLOWS.html", "Numbered message flows"), + ("docs-master/modules-and-nats.html", "NATS address table"), + ("user-docs/README.html", "User guide index"), + ("packages/verae-zapier-simulator/README.html", "Simulator README"), + ("packages/verae-fleet/docs/CONSOLE.html", "Operator console"), + ("overview/README.html", "System overview"), + ("overview/INDEX.html", "Documentation index"), + ("docs/modules/", "All module markdown"), + ("docs/models/", "All model markdown"), + ("sphinx/index.html", "Sphinx HTML"), + ] + + def extra_section(items: list[tuple[str, str]]) -> str: + lis = "".join( + f'
  • {lab}
  • ' + for h, lab in items + if (SITE / h).exists() or h.endswith("/") or h.startswith("sphinx") + ) + return f"

    Catalogs

      {lis}
    " + + rest_pdf = f""" + {cards("pdf")}

    Git repositories (Forgejo)

    Clone: ssh://git@git.georgelambert.org:2223/marchon/<name>.git (SSH port 2223). Branches main and master.

    @@ -291,43 +480,62 @@ def main() -> None:

    Master module docs

    + {extra_section(extra_pdf)} +""" + rest_md = f""" + {cards("md")}
    -

    Catalogs

    -
    +
    +

    Master module docs (HTML / Markdown)

    +
    -
    - - - + {extra_section(extra_md)} """ - (SITE / "index.html").write_text(index, encoding="utf-8") - print(f"site built at {SITE} ({sum(1 for _ in SITE.rglob('*') if _.is_file())} files)") + + (SITE / "index.html").write_text( + page_shell(now, rest_pdf, switch_to_md=True, title="Verae Time × Zapier documentation"), + encoding="utf-8", + ) + (SITE / "index-md.html").write_text( + page_shell(now, rest_md, switch_to_md=False, title="Verae Time × Zapier — Markdown indexes"), + encoding="utf-8", + ) + nfiles = sum(1 for _ in SITE.rglob("*") if _.is_file()) + npdf = sum(1 for _ in SITE.rglob("*.pdf") if _.is_file()) + print(f"site built at {SITE} ({nfiles} files, {npdf} PDFs)") + if errors: + print(f"{len(errors)} render errors:") + for e in errors[:20]: + print(" ", e[:300]) if __name__ == "__main__": diff --git a/scripts/docs-print.css b/scripts/docs-print.css new file mode 100644 index 0000000..99256e9 --- /dev/null +++ b/scripts/docs-print.css @@ -0,0 +1,122 @@ +/* Colored print + screen stylesheet for zapier.georgelambert.org */ +:root { + --ink: #171a26; + --muted: #5b6178; + --line: #d9dce8; + --bg: #f4f5fb; + --paper: #ffffff; + --accent: #4f46e5; + --accent-deep: #312e81; + --accent-soft: #eef0fe; + --ok: #047857; + --warn: #8a5a00; + --code-bg: #1b1f33; + --code-fg: #e8ecff; +} +html { background: var(--bg); } +body { + margin: 0 auto; + padding: 1.5rem 1.25rem 3rem; + max-width: 48rem; + font: 15px/1.55 -apple-system, "Segoe UI", Georgia, serif; + color: var(--ink); + background: var(--paper); +} +.doc-banner { + background: linear-gradient(160deg, #312e81 0%, #4f46e5 60%, #7c74f0 100%); + color: #eef0fe; + margin: -1.5rem -1.25rem 1.5rem; + padding: 1.1rem 1.25rem 1rem; +} +.doc-banner a { color: #fff; } +.doc-banner .kicker { + letter-spacing: 0.12em; + text-transform: uppercase; + font: 700 10px system-ui, sans-serif; + opacity: 0.8; +} +.doc-banner h1 { margin: 0.25rem 0 0; font-size: 1.45rem; color: #fff; } +h1, h2, h3, h4 { color: var(--accent-deep); page-break-after: avoid; } +h1 { font-size: 1.7rem; } +h2 { + font-size: 1.2rem; + border-bottom: 2px solid var(--accent); + padding-bottom: 0.2rem; + margin-top: 1.6rem; +} +h3 { font-size: 1.05rem; color: var(--accent); } +a { color: var(--accent); } +p, li { orphans: 3; widows: 3; } +code { + font-family: ui-monospace, Menlo, Consolas, monospace; + font-size: 0.86em; + background: var(--accent-soft); + color: var(--accent-deep); + padding: 0.08em 0.28em; + border-radius: 4px; +} +pre, div.sourceCode, div.sourceCode pre { + background: var(--code-bg) !important; + color: var(--code-fg) !important; + padding: 0.85rem 1rem; + border-radius: 10px; + overflow: auto; + font-size: 0.78rem; + line-height: 1.4; + page-break-inside: avoid; +} +pre code { background: transparent; color: inherit; padding: 0; } +#title-block-header, header#title-block-header, h1.title { display: none; } +.doc-banner + h1 { display: none; } +table { + border-collapse: collapse; + width: 100%; + margin: 0.8rem 0 1.2rem; + font-size: 0.9rem; + page-break-inside: avoid; +} +th, td { border: 1px solid var(--line); padding: 0.38rem 0.55rem; text-align: left; vertical-align: top; } +th { + background: var(--accent); + color: #fff; + font: 650 12px system-ui, sans-serif; +} +tr:nth-child(even) td { background: var(--accent-soft); } +blockquote { + margin: 1rem 0; + padding: 0.4rem 0.9rem; + border-left: 4px solid var(--accent); + background: var(--accent-soft); + color: var(--accent-deep); +} +img { max-width: 100%; height: auto; border-radius: 8px; page-break-inside: avoid; } +hr { border: 0; border-top: 1px solid var(--line); } +ul, ol { padding-left: 1.25rem; } +nav.site { font: 13px system-ui, sans-serif; margin-bottom: 0.4rem; } +.source-path { font: 11px ui-monospace, Menlo, monospace; color: var(--muted); } + +@page { + size: letter; + margin: 0.65in 0.7in 0.8in 0.7in; + @top-left { + content: "Verae Time × Zapier"; + font: 700 8pt system-ui, sans-serif; + color: #4f46e5; + } + @top-right { + content: "zapier.georgelambert.org"; + font: 8pt system-ui, sans-serif; + color: #6b7186; + } + @bottom-center { + content: counter(page) " / " counter(pages); + font: 8pt system-ui, sans-serif; + color: #6b7186; + } +} +@media print { + html, body { background: #fff; max-width: none; padding: 0; } + .doc-banner { margin: 0 0 1rem; border-radius: 8px; -webkit-print-color-adjust: exact; print-color-adjust: exact; } + a { text-decoration: none; } + th, tr:nth-child(even) td, pre, blockquote, code { -webkit-print-color-adjust: exact; print-color-adjust: exact; } +}