First real commit of site/. Until now outreach/.git tracked exactly one file
(README.md) and every content pack stamped git_sha=5619a40 regardless of what it
shipped — a signature with no provenance behind it. The site had no witness, which
is why a deploy could erase two sessions of work with nothing to diff against and
nothing to restore from.
Tracked here (authored): site/content, site/config, site/i18n, site/public/images,
justfile, scripts/, .just/, run*.sh, rustelo.manifest.toml.
Ignored (reproducible, or secret):
.k, .env real keys — outreach mirrors to a PUBLIC remote
site/r, site/public/r content indexes, rebuilt by `just content`
provisioning/.content-packs 228 MB of deploy tarballs
cache, data, logs_*.out runtime droppings
Three trees are deliberately NOT ignored despite looking generated, because their
declared source cannot currently reproduce them — ignoring them would delete the
only copy:
site/public/images 196 of 209 images exist nowhere else (assets/site is empty)
site/content/{adr,catalog} projections whose generators are unwired (gen-adr-pages)
site/content/domains or failing (`just graph`)
They become ignorable when an audit proves regeneration is a no-op, not before.
Fixes carried in this import
---------------------------
content: the mirror ran one way and reverted its own work. content_processor writes
site/r, but the recipe ended with `rsync -a site/public/r/ -> site/r/`, so each fresh
index was clobbered by the stale deploy-tree copy: the tool reported "index.json with
69 posts" while the disk kept 58. Meanwhile public/r is the tree the deploy pack ships,
so no new content could ever reach production. The two trees own different files, so
the mirror now runs both ways: content indexes r -> public/r (with --delete, or a
renamed slug survives as a live URL), and the four nu artifacts (about, adr-map,
taglines, content_graph) back public/r -> r, excluded from the outbound leg so stale
copies cannot overwrite the fresh originals.
templates: the assembler has two layers (framework defaults, source-project templates)
but the level chain has three (rustelo -> website-htmx-rustelo -> outreach/site). The
site level had no overlay slot, so its template work had nowhere legitimate to live and
ended up in htmx-templates/ — the one tree `just templates` rm -rf's. That is how the
nav submenus were lost. site/templates-overlay/ is the missing third slot; 214 lines
(nav submenus, subscribe CTA, post engagement block) now live in a declared source and
survive regeneration byte-identical.
adr: projected ADR-059..069 into the content tree. They were written months ago and
never reached the site because gen-adr-pages.nu is wired into no recipe — a generator
outside the dependency chain is a generator that does not run.
Gates added
-----------
templates-check reassembles into a temp tree and asserts an empty diff against the
live one: any drift means htmx-templates/ holds work that exists
live one: any drift means htmx-templates/ holds work that exists
nowhere else and the next run deletes it.
Both gates run before the destructive operation, not after. A check you can only run
afterwards is not a gate, it is an autopsy.
Refs: ADR-062 (projection-not-own-repo: site is a Projection of outreach, not its own
repo — different deploy target is not a different visibility boundary), ADR-048
(materialization), ADR-066 (the check decides, never the reporter — which the content
pipeline, the surface that publishes ontoref, was the last place not to honour).
139 lines
7.8 KiB
Text
139 lines
7.8 KiB
Text
#!/usr/bin/env nu
|
|
|
|
# Shared page chrome for projected static doc surfaces (ADRs, catalog, …).
|
|
# One source of truth for the brand palette, dark/light theme, nav, and the
|
|
# common structural CSS (.doc-* classes). Consumed by gen-adr-pages.nu and
|
|
# gen-catalog-pages.nu so both outreach/web (static) and the SSR site share an
|
|
# identical look and feel.
|
|
|
|
export def esc [s: string] {
|
|
$s | str replace --all '&' '&' | str replace --all '<' '<' | str replace --all '>' '>' | str replace --all '"' '"'
|
|
}
|
|
|
|
export def paras [text: string] {
|
|
$text | split row "\n\n" | where {|b| ($b | str trim | is-not-empty) }
|
|
| each {|b| $"<p>(esc ($b | str trim))</p>" } | str join "\n"
|
|
}
|
|
|
|
# A single <section> with an anchor + h2; empty body collapses to nothing.
|
|
export def sec [anchor: string, title: string, body: string] {
|
|
if ($body | is-empty) { "" } else { $"<section id=\"($anchor)\" class=\"doc-section\"><h2>(esc $title)</h2>($body)</section>" }
|
|
}
|
|
|
|
# Brand-aligned CSS: ontoref palette tokens, dark default + html.light overrides,
|
|
# content-graph SVG var bindings, and the .doc-* structural classes.
|
|
export def page-css [] { r#'<style>
|
|
:root{
|
|
--onto-amber:#E8A838;--onto-amber-dark:#C88A20;--onto-amber-light:#F0C265;
|
|
--onto-silver:#C0CCD8;--onto-dark:#0F1319;--onto-gray:#8090A4;--onto-gray2:#a8b4c8;
|
|
--onto-surface:#141c24;--onto-border:#1e2a38;
|
|
--onto-amber-rgb:232,168,56;--onto-silver-rgb:192,204,216;--onto-dark-rgb:15,19,25;--onto-amber-dark-rgb:200,138,32;
|
|
--cg-node:#4a5568;--cg-ego:#E8A838;--cg-onto:#9b59b6;
|
|
--cg-adr:#E8A838;--cg-edge:#1e2a38;--cg-label:#C0CCD8;
|
|
}
|
|
html.light{
|
|
--onto-dark:#F0F2F5;--onto-surface:#FFFFFF;--onto-border:#d1d9e0;
|
|
--onto-silver:#1a2030;--onto-gray:#5a6a7c;--onto-gray2:#374151;
|
|
--cg-node:#6b7280;--cg-ego:#C88A20;--cg-edge:#d1d5db;--cg-label:#374151;
|
|
}
|
|
*{box-sizing:border-box}
|
|
body{font-family:Inter,-apple-system,BlinkMacSystemFont,sans-serif;background:var(--onto-dark);color:var(--onto-silver);margin:0;transition:background .2s,color .2s}
|
|
a{color:var(--onto-amber);text-decoration:none}a:hover{text-decoration:underline}
|
|
h1{color:var(--onto-silver)}
|
|
.doc-nav{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1.5rem;border-bottom:1px solid var(--onto-border);background:var(--onto-dark);position:sticky;top:0;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);z-index:10}
|
|
.doc-nav-logo{font-weight:700;color:var(--onto-amber);text-decoration:none;font-family:monospace;font-size:1.1rem;letter-spacing:-.02em}
|
|
.doc-nav-ctl{display:flex;gap:.5rem;align-items:center}
|
|
.doc-nav-btn{padding:.25rem .75rem;border-radius:.4rem;text-decoration:none;border:1px solid var(--onto-border);color:var(--onto-silver);font-size:.85rem;background:none}
|
|
.doc-nav-btn:hover{border-color:var(--onto-amber);color:var(--onto-amber);text-decoration:none}
|
|
.theme-btn{background:none;border:1px solid var(--onto-border);border-radius:.4rem;padding:.25rem .5rem;cursor:pointer;color:var(--onto-silver);font-size:.9rem;line-height:1}
|
|
.theme-btn:hover{border-color:var(--onto-amber)}
|
|
.doc-wrap{max-width:52rem;margin:0 auto;padding:2rem 1rem}
|
|
.doc-head{border-bottom:1px solid var(--onto-border);padding-bottom:1rem;margin-bottom:1.5rem}
|
|
.doc-badge{display:inline-block;padding:.15rem .6rem;border-radius:.4rem;font:600 .75rem/1.4 monospace;margin-right:.35rem}
|
|
.doc-st-accepted{background:rgba(31,120,74,.2);color:#7fe0a8}
|
|
.doc-st-proposed{background:rgba(232,168,56,.15);color:#E8A838}
|
|
.doc-st-superseded,.doc-st-deprecated{background:rgba(224,128,128,.15);color:#e08080}
|
|
.doc-sev-hard{background:rgba(224,128,128,.15);color:#e08080}
|
|
.doc-sev-soft{background:var(--onto-surface);color:var(--onto-gray2)}
|
|
.doc-tag-structural{background:rgba(155,89,182,.15);color:#c39bd3}
|
|
.doc-tag-contextual{background:rgba(232,168,56,.15);color:#E8A838}
|
|
.doc-tag-operation{background:rgba(64,160,200,.15);color:#7fc8e0}
|
|
.doc-tag-validator{background:rgba(127,224,168,.12);color:#7fe0a8}
|
|
.doc-meta{font-family:monospace;opacity:.6;margin-left:.5rem;font-size:.9rem}
|
|
.doc-section{margin:1.5rem 0}
|
|
.doc-section h2{color:var(--onto-amber);font-size:1.1rem;margin-bottom:.5rem;font-weight:600}
|
|
.doc-section p{line-height:1.7;margin-bottom:.75rem}
|
|
.doc-list{list-style:none;padding:0}
|
|
.doc-list>li{display:flex;gap:.6rem;padding:.5rem 0;border-bottom:1px solid var(--onto-border);line-height:1.6}
|
|
.doc-alt-opt{font-weight:600;margin-bottom:.2rem}
|
|
.doc-alt-why{color:var(--onto-gray2);margin:0}
|
|
.doc-kv{width:100%;border-collapse:collapse;font-size:.9rem}
|
|
.doc-kv td{padding:.4rem .6rem;border-bottom:1px solid var(--onto-border);vertical-align:top}
|
|
.doc-kv td:first-child{font-family:monospace;color:var(--onto-gray2);white-space:nowrap;width:1%}
|
|
.doc-code{font-family:'JetBrains Mono',Consolas,monospace;font-size:.85em;color:var(--onto-amber-light)}
|
|
.doc-related a{font-family:monospace;color:var(--onto-amber)}
|
|
.doc-toc{border:1px solid var(--onto-border);border-radius:.5rem;padding:.75rem 1rem;margin:1rem 0;background:var(--onto-surface)}
|
|
.doc-toc p{font:600 .7rem/1 monospace;text-transform:uppercase;opacity:.5;margin:0 0 .5rem}
|
|
.doc-toc ul{list-style:none;padding:0;margin:0;display:flex;flex-wrap:wrap;gap:.5rem}
|
|
.doc-toc a{font-size:.8rem;color:var(--onto-silver);text-decoration:none}
|
|
.doc-toc a:hover{color:var(--onto-amber)}
|
|
.doc-search{width:100%;padding:.6rem .9rem;margin:0 0 1rem;border-radius:.5rem;border:1px solid var(--onto-border);background:var(--onto-surface);color:var(--onto-silver);font-size:.95rem}
|
|
.doc-search::placeholder{color:var(--onto-gray)}
|
|
.doc-search:focus{outline:none;border-color:var(--onto-amber)}
|
|
.doc-mini-graph{margin:1.5rem 0;border:1px solid var(--onto-border);border-radius:.5rem;overflow:hidden;background:var(--onto-surface);padding:.5rem}
|
|
.doc-mini-graph .content-graph-mini{width:100%;max-width:360px;display:block;margin:0 auto;height:auto}
|
|
.doc-idx-group{margin:1.5rem 0}
|
|
.doc-idx-group h2{color:var(--onto-amber);font-size:1rem;font-weight:600;margin-bottom:.5rem;font-family:monospace}
|
|
.doc-idx-list{list-style:none;padding:0}
|
|
.doc-idx-list>li{padding:.4rem 0;border-bottom:1px solid var(--onto-border)}
|
|
.doc-idx-list>li.hidden{display:none}
|
|
.doc-idx-list a{color:var(--onto-silver);display:block}
|
|
.doc-idx-list a:hover{color:var(--onto-amber)}
|
|
.doc-idx-list a code{font-size:.8rem;color:var(--onto-gray2);font-family:monospace;margin-right:.4rem}
|
|
.no-results{display:none;color:var(--onto-gray);font-style:italic;padding:1rem 0}
|
|
</style>'# }
|
|
|
|
# Persisted theme toggle, shared across all doc pages.
|
|
export def theme-js [] { r#'<script>
|
|
(function(){
|
|
var K="ontoref-theme";
|
|
function getT(){return localStorage.getItem(K)||"dark"}
|
|
function setT(t){
|
|
localStorage.setItem(K,t);
|
|
document.documentElement.classList.toggle("light",t==="light");
|
|
var b=document.getElementById("theme-btn");
|
|
if(b)b.textContent=t==="light"?"🌙":"☀️";
|
|
}
|
|
window.toggleTheme=function(){setT(getT()==="dark"?"light":"dark")};
|
|
setT(getT());
|
|
})();
|
|
</script>'# }
|
|
|
|
# Full HTML document: brand chrome + sibling-surface nav (ADRs · Catalog) + body.
|
|
export def page-shell [title: string, content: string] {
|
|
$"<!DOCTYPE html>
|
|
<html lang=\"en\">
|
|
<head>
|
|
<meta charset=\"utf-8\">
|
|
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
|
|
<title>(esc $title) · Ontoref</title>
|
|
<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">
|
|
<link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=JetBrains+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">
|
|
(page-css)
|
|
</head>
|
|
<body>
|
|
<nav class=\"doc-nav\">
|
|
<a class=\"doc-nav-logo\" href=\"/\">ontoref</a>
|
|
<div class=\"doc-nav-ctl\">
|
|
<a class=\"doc-nav-btn\" href=\"/adr/\">ADRs</a>
|
|
<a class=\"doc-nav-btn\" href=\"/catalog/\">Catalog</a>
|
|
<button id=\"theme-btn\" class=\"theme-btn\" onclick=\"toggleTheme\(\)\" title=\"Toggle theme\">☀️</button>
|
|
</div>
|
|
</nav>
|
|
<div class=\"doc-wrap\">
|
|
($content)
|
|
</div>
|
|
(theme-js)
|
|
</body>
|
|
</html>"
|
|
}
|