This meta page powers the presentation of the Schrollar report. It holds:
```mermaid block in the space renders as a diagram (light or dark, matching the theme);sch.nav(): previous / contents / next links, worked out from each page's order frontmatter;sch.contents(section): a live contents table built from each page's section, order and summary frontmatter;sch.pages(): the report's pages in reading order;decision, scale, roadmap), navigation bar and diagram frames.Mermaid is loaded from jsDelivr inside a sandboxed widget, so it never touches the editor itself. Diagrams render with securityLevel: "strict".
-- priority: 20
sch = sch or {}
sch.mermaidUrl = "https://cdn.jsdelivr.net/npm/mermaid@11.4.1/dist/mermaid.min.js"
local function escapeHtml(s)
s = string.gsub(s, "&", "&")
s = string.gsub(s, "<", "<")
s = string.gsub(s, ">", ">")
return s
end
codeWidget.define {
language = "mermaid",
render = function(bodyText)
return widget.sandbox {
markdown = "```mermaid\n" .. bodyText .. "\n```",
html = [==[<style>
html, body { margin: 0; padding: 0; background: transparent; overflow: hidden; }
#out { display: flex; justify-content: center; padding: 8px 0; font: 13px system-ui, sans-serif; color: #888; }
#out svg { max-width: 100%; height: auto; }
</style>
<pre id="src" style="display:none">]==] .. escapeHtml(bodyText) .. [==[</pre>
<div id="out">Drawing diagram…</div>]==],
script = [==[
(async () => {
const out = document.getElementById("out");
try {
if (!globalThis.mermaid) await loadJsByUrl("]==] .. sch.mermaidUrl .. [==[");
const dark = document.documentElement.getAttribute("data-theme") === "dark";
mermaid.initialize({ startOnLoad: false, securityLevel: "strict", theme: dark ? "dark" : "neutral", fontFamily: "system-ui, sans-serif" });
const code = document.getElementById("src").textContent;
const { svg } = await mermaid.render("sch" + Math.random().toString(36).slice(2), code);
out.innerHTML = svg;
} catch (e) {
out.textContent = "This diagram could not be drawn: " + (e && e.message ? e.message : e);
}
// The sandbox sizes itself for a few seconds after load; diagrams can take longer.
const remeasure = () => { try { heightChecks = 0; updateHeight(); } catch (_) {} }; remeasure(); [200, 600, 1500].forEach((t) => setTimeout(remeasure, t));
})();
]==],
}
end
}
-- priority: 20
sch = sch or {}
-- The report's pages in reading order (pages tagged `schrollar` with an `order`).
function sch.pages()
local pages = query[
from p = index.pages()
where p.tags and table.includes(p.tags, "schrollar") and p.order
order by p.order
](<
from p = index.pages()
where p.tags and table.includes(p.tags, "schrollar") and p.order
order by p.order
>)
return pages
end
-- A page's short title: its displayName, or the last part of its path.
function sch.title(p)
if p.displayName then return p.displayName end
return string.match(p.name, "([^/]+)$") or p.name
end
-- Previous · Contents · Next, for the bottom of every report page.
function sch.nav()
local pages = sch.pages()
local current = editor.getCurrentPage()
local prev, nxt
for i, p in ipairs(pages) do
if p.name == current then
prev = pages[i - 1]
nxt = pages[i + 1]
end
end
local parts = {}
if prev then table.insert(parts, "← [" .. sch.title(prev) .. "](<" .. prev.name .. ">)") end
table.insert(parts, "[Contents](</Schrollar>)")
if nxt then table.insert(parts, "[" .. sch.title(nxt) .. "](<" .. nxt.name .. ">) →") end
return widget.new {
markdown = table.concat(parts, " · "),
display = "block",
cssClasses = { "sch-nav" },
}
end
-- A contents table for a section ("Architecture", "Features", …), or the whole report.
function sch.contents(section)
local rows = { "| | Page | What it covers |", "|---|---|---|" }
local n = 0
for _, p in ipairs(sch.pages()) do
if (section == nil or p.section == section) and not p.hub then
n = n + 1
local where = section and "" or (p.section or "")
table.insert(rows, "| " .. where .. " | [" .. sch.title(p) .. "](<" .. p.name .. ">) | " .. (p.summary or "") .. " |")
end
end
if n == 0 then return "" end
return widget.new {
markdown = table.concat(rows, "\n"),
display = "block",
cssClasses = { "sch-contents" },
}
end
/* priority: 10 */
/* Custom callouts used across the report: > **decision** …, > **scale** …, > **roadmap** … */
.sb-admonition[admonition="decision" i] {
.sb-admonition-type * { display: none; }
.sb-admonition-type::before { width: var(--admonition-width) !important; }
--admonition-color: #8b5cf6;
--admonition-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="18" r="3"/><circle cx="6" cy="6" r="3"/><path d="M6 21V9a9 9 0 0 0 9 9"/></svg>');
}
.sb-admonition[admonition="scale" i] {
.sb-admonition-type * { display: none; }
.sb-admonition-type::before { width: var(--admonition-width) !important; }
--admonition-color: #f59e0b;
--admonition-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/></svg>');
}
.sb-admonition[admonition="roadmap" i] {
.sb-admonition-type * { display: none; }
.sb-admonition-type::before { width: var(--admonition-width) !important; }
--admonition-color: #14b8a6;
--admonition-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/></svg>');
}
/* Previous / contents / next bar */
.sch-nav {
margin-top: 2em;
padding: 0.7em 1em;
border-top: 2px solid var(--ui-accent-color, #8b5cf6);
border-radius: 6px;
background: color-mix(in srgb, var(--ui-accent-color, #8b5cf6) 8%, transparent);
text-align: center;
font-weight: 600;
}
/* Contents tables */
.sch-contents table { width: 100%; }
.sch-contents td:first-child { white-space: nowrap; opacity: 0.7; font-size: 0.9em; }
/* Report pages: tags shown as quiet chips */
.sb-hashtag[data-tag-name="schrollar"] { background: #8b5cf6; color: white; }