@geml/geml 1.7.7 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +285 -285
- package/codemap/adapters/crg.mjs +120 -120
- package/codemap/adapters/joern.mjs +131 -131
- package/codemap/adapters/scip.mjs +658 -658
- package/codemap/browser-stub.mjs +34 -34
- package/codemap/build.mjs +629 -629
- package/codemap/cross-stack.mjs +303 -303
- package/codemap/detect.mjs +399 -399
- package/codemap/emit.mjs +510 -510
- package/codemap/entries.mjs +129 -129
- package/codemap/exclude.mjs +56 -56
- package/codemap/find.mjs +49 -49
- package/codemap/foldings.mjs +110 -110
- package/codemap/joern-export.sc +83 -83
- package/codemap/mcp-server.mjs +434 -431
- package/codemap/normalize.mjs +275 -275
- package/codemap/recipe-trust.mjs +103 -103
- package/codemap/refresh.mjs +313 -313
- package/codemap/render-all.mjs +90 -90
- package/codemap/serve.mjs +585 -585
- package/codemap/sfc-virtualize.mjs +367 -367
- package/codemap/verify.mjs +158 -158
- package/dist/cli.js +121 -121
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.js +2 -1
- package/dist/geml.js +45 -24
- package/dist/inline.d.ts +7 -1
- package/dist/inline.js +203 -137
- package/dist/mcp.js +32 -26
- package/dist/render-html.js +35 -35
- package/dist/render.js +192 -184
- package/package.json +1 -1
- package/skill/SKILL.md +167 -167
- package/skill/references/authoring.geml +365 -365
package/dist/render-html.js
CHANGED
|
@@ -36,43 +36,43 @@ function page(title, body, ctx, source) {
|
|
|
36
36
|
? `<script type="importmap">{"imports":{"node:fs":"${lg}_node-stub.js","node:path":"${lg}_node-stub.js","node:crypto":"${lg}_node-stub.js","node:url":"${lg}_node-stub.js","node:child_process":"${lg}_node-stub.js","node:os":"${lg}_node-stub.js"}}</script>\n`
|
|
37
37
|
: "";
|
|
38
38
|
const liveJs = wantLive
|
|
39
|
-
? `<script type="module">
|
|
40
|
-
globalThis.process ??= { argv: [], env: {} };
|
|
41
|
-
const { parse } = await import("${lg}geml.js");
|
|
42
|
-
const { codeGraphWaves } = await import("${lg}render.js");
|
|
43
|
-
const w = codeGraphWaves(async (rel) => {
|
|
44
|
-
try { const r = await fetch(rel, { cache: "no-cache" }); return r.ok ? await r.text() : null; } catch { return null; }
|
|
45
|
-
}, parse);
|
|
46
|
-
for (const m of document.querySelectorAll(".cg-mount[data-start]")) {
|
|
47
|
-
const start = m.getAttribute("data-start");
|
|
48
|
-
m._cgView = async (view) => {
|
|
49
|
-
// A directed view builds from the node's OWN document (its meta names the
|
|
50
|
-
// module and graph-depth); {doc} opens that document; else the mount's.
|
|
51
|
-
const src = view && view.doc ? view.doc
|
|
52
|
-
: view && view.node ? view.node.slice(0, view.node.lastIndexOf("#"))
|
|
53
|
-
: start;
|
|
54
|
-
const r = await w.build(src, view && view.doc ? undefined : view);
|
|
55
|
-
return r.error !== undefined ? null : r.data;
|
|
56
|
-
};
|
|
57
|
-
}
|
|
39
|
+
? `<script type="module">
|
|
40
|
+
globalThis.process ??= { argv: [], env: {} };
|
|
41
|
+
const { parse } = await import("${lg}geml.js");
|
|
42
|
+
const { codeGraphWaves } = await import("${lg}render.js");
|
|
43
|
+
const w = codeGraphWaves(async (rel) => {
|
|
44
|
+
try { const r = await fetch(rel, { cache: "no-cache" }); return r.ok ? await r.text() : null; } catch { return null; }
|
|
45
|
+
}, parse);
|
|
46
|
+
for (const m of document.querySelectorAll(".cg-mount[data-start]")) {
|
|
47
|
+
const start = m.getAttribute("data-start");
|
|
48
|
+
m._cgView = async (view) => {
|
|
49
|
+
// A directed view builds from the node's OWN document (its meta names the
|
|
50
|
+
// module and graph-depth); {doc} opens that document; else the mount's.
|
|
51
|
+
const src = view && view.doc ? view.doc
|
|
52
|
+
: view && view.node ? view.node.slice(0, view.node.lastIndexOf("#"))
|
|
53
|
+
: start;
|
|
54
|
+
const r = await w.build(src, view && view.doc ? undefined : view);
|
|
55
|
+
return r.error !== undefined ? null : r.data;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
58
|
</script>\n`
|
|
59
59
|
: "";
|
|
60
|
-
return `<!doctype html>
|
|
61
|
-
<html lang="en">
|
|
62
|
-
<head>
|
|
63
|
-
<meta charset="utf-8">
|
|
64
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
65
|
-
<title>${esc(title)}</title>
|
|
66
|
-
<style>${CSS}</style>
|
|
67
|
-
${importMap}${mathHead}${mermaidHead}</head>
|
|
68
|
-
<body>
|
|
69
|
-
<main>
|
|
70
|
-
${body}
|
|
71
|
-
</main>
|
|
72
|
-
${footer}
|
|
73
|
-
<script>${JS}</script>
|
|
74
|
-
${ctx.usedCodeGraph ? `<script>${CODE_GRAPH_JS}</script>\n` : ""}${liveJs}</body>
|
|
75
|
-
</html>
|
|
60
|
+
return `<!doctype html>
|
|
61
|
+
<html lang="en">
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="utf-8">
|
|
64
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
65
|
+
<title>${esc(title)}</title>
|
|
66
|
+
<style>${CSS}</style>
|
|
67
|
+
${importMap}${mathHead}${mermaidHead}</head>
|
|
68
|
+
<body>
|
|
69
|
+
<main>
|
|
70
|
+
${body}
|
|
71
|
+
</main>
|
|
72
|
+
${footer}
|
|
73
|
+
<script>${JS}</script>
|
|
74
|
+
${ctx.usedCodeGraph ? `<script>${CODE_GRAPH_JS}</script>\n` : ""}${liveJs}</body>
|
|
75
|
+
</html>
|
|
76
76
|
`;
|
|
77
77
|
}
|
|
78
78
|
export function renderHtml(doc, opts = {}) {
|
package/dist/render.js
CHANGED
|
@@ -562,17 +562,27 @@ export class RenderCtx {
|
|
|
562
562
|
return `<figure${idAttr}><p class="table-note">external data <code>${esc(src)}</code> — loaded at render time</p>${cap0}</figure>`;
|
|
563
563
|
}
|
|
564
564
|
}
|
|
565
|
+
// Overflow FOLDS; it is never dropped. `--to html` is a CONVERSION, and
|
|
566
|
+
// a conversion that loses data is not one — the old behaviour kept the
|
|
567
|
+
// first 500 lines and said "the complete data is in the document
|
|
568
|
+
// source", which is no help at all to someone holding only the HTML.
|
|
569
|
+
// The bound belongs to what is OPEN, not to what is present: the rest
|
|
570
|
+
// sits in a collapsed <details>, so the page is exactly as short as
|
|
571
|
+
// before and the block is complete. There is no ceiling above which
|
|
572
|
+
// lines vanish again, because a <pre> is a single text node — the cost
|
|
573
|
+
// of keeping them is bytes on disk, which is what not losing data
|
|
574
|
+
// costs.
|
|
565
575
|
const tail = fmt === "jsonl" && lines.length > limit;
|
|
566
576
|
const shown = tail ? lines.slice(-limit) : lines.slice(0, limit);
|
|
567
|
-
const
|
|
568
|
-
const note = omitted > 0
|
|
569
|
-
? `<p class="table-note">${tail
|
|
570
|
-
? `showing the last ${shown.length} of ${lines.length} lines — earlier lines are in the document source`
|
|
571
|
-
: `showing the first ${shown.length} of ${lines.length} lines — the complete data is in the document source`}</p>`
|
|
572
|
-
: "";
|
|
577
|
+
const rest = tail ? lines.slice(0, lines.length - shown.length) : lines.slice(shown.length);
|
|
573
578
|
const cap = caption ? `<figcaption>${esc(caption)}</figcaption>` : "";
|
|
574
|
-
const pre = `<pre class="data-src" data-format="${escAttr(fmt)}">${esc(
|
|
575
|
-
|
|
579
|
+
const pre = (ls) => `<pre class="data-src" data-format="${escAttr(fmt)}">${esc(ls.join("\n"))}</pre>`;
|
|
580
|
+
if (rest.length === 0)
|
|
581
|
+
return `<figure${idAttr}>${pre(shown)}${cap}</figure>`;
|
|
582
|
+
// jsonl reads as an append-log, so its open end is the NEWEST lines and
|
|
583
|
+
// the fold holds the earlier ones, above; json reads from the top.
|
|
584
|
+
const more = `<details class="data-more"><summary>${rest.length} ${tail ? "earlier" : "more"} line${rest.length === 1 ? "" : "s"} of ${lines.length}</summary>${pre(rest)}</details>`;
|
|
585
|
+
return `<figure${idAttr}>${tail ? more + pre(shown) : pre(shown) + more}${cap}</figure>`;
|
|
576
586
|
}
|
|
577
587
|
case "table":
|
|
578
588
|
return b.table ? this.table(b.table, b.id, caption) : `<p class="render-error">table failed to parse</p>`;
|
|
@@ -636,17 +646,20 @@ export class RenderCtx {
|
|
|
636
646
|
table(t, id, caption) {
|
|
637
647
|
const idAttr = id ? ` id="${escAttr(id)}"` : "";
|
|
638
648
|
const alignStyle = (a) => (a ? ` style="text-align:${a}"` : "");
|
|
639
|
-
//
|
|
640
|
-
//
|
|
641
|
-
//
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
//
|
|
646
|
-
//
|
|
647
|
-
|
|
649
|
+
// Laying out tens of thousands of <table> rows OPEN freezes the page for
|
|
650
|
+
// seconds, so a long table renders folded shut — the codemap's edge tables
|
|
651
|
+
// (#calls / #called-by) are the ones that get there. Its #modules table is
|
|
652
|
+
// the page's content, the inventory people came to scan and filter, so that
|
|
653
|
+
// one stays open at any size.
|
|
654
|
+
//
|
|
655
|
+
// EVERY row is rendered. `tableRows` bounds what is OPEN, never what is
|
|
656
|
+
// present: past it the whole table goes inside a collapsed <details>, which
|
|
657
|
+
// costs the reader one click and costs the document nothing. It used to
|
|
658
|
+
// slice the rows away and say the complete table was in the source, which
|
|
659
|
+
// made `--to html` a lossy conversion of a table the model holds in full.
|
|
660
|
+
const foldAbove = this.isCodemapDoc && id === "modules" ? Infinity : (this.opts.tableRows ?? 500);
|
|
648
661
|
const allRows = t.rows;
|
|
649
|
-
const rows = allRows
|
|
662
|
+
const rows = allRows;
|
|
650
663
|
const thead = t.header
|
|
651
664
|
? `<thead><tr>${t.columns.map((col, c) => `<th${alignStyle(t.align[c])}>${esc(col)}</th>`).join("")}</tr></thead>`
|
|
652
665
|
: "";
|
|
@@ -666,15 +679,10 @@ export class RenderCtx {
|
|
|
666
679
|
: "";
|
|
667
680
|
const cap = caption ? `<figcaption>${esc(caption)}</figcaption>` : "";
|
|
668
681
|
const tools = `<div class="table-tools"><input class="table-filter" type="search" placeholder="Filter rows…" aria-label="Filter table rows"></div>`;
|
|
669
|
-
if (allRows.length >
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
return `<figure class="table-figure"${idAttr}><details><summary>${summary}</summary>${tools}` +
|
|
674
|
-
`<div class="table-scroll"><table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table></div>${note}</details>${cap}</figure>`;
|
|
675
|
-
}
|
|
676
|
-
return `<figure class="table-figure"${idAttr}>${tools}` +
|
|
677
|
-
`<div class="table-scroll"><table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table></div>${note}${cap}</figure>`;
|
|
682
|
+
if (allRows.length > foldAbove) {
|
|
683
|
+
const summary = `${esc(id ? "#" + id : "table")} · ${allRows.length} rows`;
|
|
684
|
+
return `<figure class="table-figure"${idAttr}><details><summary>${summary}</summary>${tools}` +
|
|
685
|
+
`<div class="table-scroll"><table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table></div></details>${cap}</figure>`;
|
|
678
686
|
}
|
|
679
687
|
return `<figure class="table-figure"${idAttr}>${tools}` +
|
|
680
688
|
`<div class="table-scroll"><table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table></div>${cap}</figure>`;
|
|
@@ -1265,164 +1273,164 @@ function trunc(s, n) {
|
|
|
1265
1273
|
// ---------------------------------------------------------------------------
|
|
1266
1274
|
// Page shell, inline CSS, inline interactivity JS
|
|
1267
1275
|
// ---------------------------------------------------------------------------
|
|
1268
|
-
export const CSS = `
|
|
1269
|
-
:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }
|
|
1270
|
-
* { box-sizing: border-box; }
|
|
1271
|
-
body { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,"PingFang SC","Microsoft Yahei",sans-serif; }
|
|
1272
|
-
main { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }
|
|
1273
|
-
h1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }
|
|
1274
|
-
h1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
1275
|
-
h2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
1276
|
-
h3 { font-size:1.25em; } h4 { font-size:1em; }
|
|
1277
|
-
p { margin:.7em 0; }
|
|
1278
|
-
a { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }
|
|
1279
|
-
code { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
|
|
1280
|
-
pre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }
|
|
1281
|
-
pre code { background:none; padding:0; font-size:.85em; }
|
|
1282
|
-
pre.output { background:#0d1117; color:#e6edf3; }
|
|
1283
|
-
pre.output code { color:inherit; }
|
|
1284
|
-
ul,ol { padding-left:1.6em; } li { margin:.2em 0; }
|
|
1285
|
-
ul.task-list { list-style:none; padding-left:.2em; }
|
|
1286
|
-
li.task input[type=checkbox] { appearance:none; -webkit-appearance:none; width:1.1em; height:1.1em; margin:0 .5em 0 0; vertical-align:-.2em; border:1.5px solid #c8ccd0; border-radius:4px; background:#fff; position:relative; opacity:1; cursor:default; box-sizing:border-box; }
|
|
1287
|
-
li.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }
|
|
1288
|
-
li.task input[type=checkbox]:checked::after { content:"✓"; position:absolute; top:0; right:0; bottom:0; left:0; display:flex; align-items:center; justify-content:center; color:#fff; font-size:.8em; line-height:1; font-weight:700; }
|
|
1289
|
-
aside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }
|
|
1290
|
-
aside.aside { border-left-color:#8b949e; background:#f6f8fa; }
|
|
1291
|
-
aside.warning { border-left-color:#d97706; background:#fff8f0; }
|
|
1292
|
-
aside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }
|
|
1293
|
-
figure { margin:1.2em 0; }
|
|
1294
|
-
figcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }
|
|
1295
|
-
table.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }
|
|
1296
|
-
table.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }
|
|
1297
|
-
table.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }
|
|
1298
|
-
table.geml-table thead th::after { content:" \\2195"; color:var(--muted); font-size:.8em; }
|
|
1299
|
-
table.geml-table thead th.asc::after { content:" \\2191"; color:var(--accent); }
|
|
1300
|
-
table.geml-table thead th.desc::after { content:" \\2193"; color:var(--accent); }
|
|
1301
|
-
table.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }
|
|
1302
|
-
table.geml-table td.computed { color:#0a7c52; }
|
|
1303
|
-
table.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }
|
|
1304
|
-
.table-tools { margin-bottom:6px; } .table-filter { width:240px; max-width:100%; padding:5px 9px; border:1px solid var(--bd); border-radius:7px; font-size:.85em; }
|
|
1305
|
-
/* A table wider than the column scrolls INSIDE its own box — the page itself
|
|
1306
|
-
must never scroll sideways. Only the table is in here: the filter box above
|
|
1307
|
-
and the caption below stay put instead of sliding out of view with it. */
|
|
1308
|
-
.table-scroll { overflow-x:auto; max-width:100%; }
|
|
1309
|
-
.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }
|
|
1310
|
-
.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }
|
|
1311
|
-
.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }
|
|
1312
|
-
.c-title { font-size:15px; font-weight:600; fill:var(--fg); }
|
|
1313
|
-
.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }
|
|
1314
|
-
.media { max-width:100%; border-radius:8px; }
|
|
1315
|
-
.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }
|
|
1316
|
-
.math-block { overflow-x:auto; padding:.4em 0; }
|
|
1317
|
-
sup.fn a { font-size:.75em; }
|
|
1318
|
-
.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }
|
|
1319
|
-
.geml-footer code { font-size:.95em; }
|
|
1320
|
-
.code-graph { margin:1.4em 0; }
|
|
1321
|
-
/* The graph is the widest artifact on the page: let it break out of main's
|
|
1322
|
-
860px reading column and take the viewport, centred, leaving the prose
|
|
1323
|
-
around it untouched. Negative inline margins, NOT transform — a transform
|
|
1324
|
-
would become the containing block for the fullscreen overlay below. */
|
|
1325
|
-
@media (min-width:900px) { .code-graph { margin-inline: calc((100% - min(96vw, 1600px)) / 2); } }
|
|
1326
|
-
.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }
|
|
1327
|
-
.cg-scroll { overflow:auto; min-height:60vh; max-height:84vh; }
|
|
1328
|
-
.cg-svg { display:block; }
|
|
1329
|
-
/* Fullscreen: one class drives the layout, whether the native Fullscreen API
|
|
1330
|
-
took (mount is in the top layer) or we fell back to a fixed overlay. The
|
|
1331
|
-
:fullscreen pseudo-class is deliberately NOT in these selectors — a browser
|
|
1332
|
-
that doesn't know it would drop the whole rule; the runtime keeps the class
|
|
1333
|
-
in sync with fullscreenchange instead. Toolbar/legend keep their height,
|
|
1334
|
-
the stage eats the rest. */
|
|
1335
|
-
.cg-mount.cg-full { position:fixed; inset:0; z-index:9999; margin:0; border:0; border-radius:0; padding:10px 14px; background:var(--bg); display:flex; flex-direction:column; }
|
|
1336
|
-
.cg-mount.cg-full > .cg-bar, .cg-mount.cg-full > .cg-legend, .cg-mount.cg-full > .cg-groups { flex:0 0 auto; }
|
|
1337
|
-
.cg-mount.cg-full .cg-stage { flex:1 1 auto; min-height:0; }
|
|
1338
|
-
.cg-mount.cg-full .cg-scroll { min-height:0; max-height:none; height:100%; }
|
|
1339
|
-
.cg-mount.cg-full .cg-src-body { max-height:none; }
|
|
1340
|
-
.cg-mount.cg-full .cg-frame { flex:1 1 auto; height:auto; }
|
|
1341
|
-
.cg-search-wrap { position:relative; display:inline-block; }
|
|
1342
|
-
.cg-search { font:12px/1.4 inherit; padding:2px 7px; border:1px solid var(--bd); border-radius:4px; background:var(--bg); color:var(--fg); min-width:13ch; }
|
|
1343
|
-
.cg-search-menu { position:absolute; z-index:30; top:calc(100% + 2px); left:0; min-width:24ch; max-width:52ch; max-height:52vh; overflow:auto; background:var(--bg); border:1px solid var(--bd); border-radius:6px; box-shadow:0 6px 20px rgba(0,0,0,.18); }
|
|
1344
|
-
.cg-search-row { display:block; width:100%; text-align:left; padding:4px 9px 4px 18px; border:0; background:none; color:var(--fg); cursor:pointer; font:12px/1.4 inherit; }
|
|
1345
|
-
.cg-search-row:hover { background:var(--bd); }
|
|
1346
|
-
.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }
|
|
1347
|
-
.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }
|
|
1348
|
-
.cg-search-grp:first-of-type { border-top:0; }
|
|
1349
|
-
.cg-stage { display:flex; gap:10px; align-items:flex-start; }
|
|
1350
|
-
.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }
|
|
1351
|
-
.cg-src { flex:0 0 42%; max-width:46%; display:flex; flex-direction:column; border:1px solid var(--bd); border-radius:6px; overflow:hidden; background:var(--bg); }
|
|
1352
|
-
.cg-src-hd { display:flex; gap:8px; align-items:center; justify-content:space-between; padding:4px 8px; border-bottom:1px solid var(--bd); color:var(--muted); font:.76em ui-monospace,Consolas,monospace; word-break:break-all; }
|
|
1353
|
-
.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }
|
|
1354
|
-
.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:84vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }
|
|
1355
|
-
.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }
|
|
1356
|
-
.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }
|
|
1357
|
-
.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }
|
|
1358
|
-
.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }
|
|
1359
|
-
.cg-crumb .cg-seg:hover { text-decoration:underline; }
|
|
1360
|
-
.cg-frame { display:block; width:100%; height:84vh; border:0; background:var(--bg); }
|
|
1361
|
-
.cg-flash { color:#b42318; }
|
|
1362
|
-
.cg-legend { display:flex; gap:14px; align-items:center; justify-content:space-between; flex-wrap:wrap; font-size:.75em; color:var(--muted); margin-top:6px; }
|
|
1363
|
-
.cg-upbtn { cursor:pointer; }
|
|
1364
|
-
.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }
|
|
1365
|
-
.cg-upbtn text { font-size:11px; fill:#57606a; }
|
|
1366
|
-
.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }
|
|
1367
|
-
.cg-upbtn:hover text { fill:var(--accent); }
|
|
1368
|
-
.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }
|
|
1369
|
-
.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }
|
|
1370
|
-
.cg-chip { display:inline-flex; align-items:center; gap:4px; }
|
|
1371
|
-
.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }
|
|
1372
|
-
.cg-note { font-size:.8em; color:#9a6700; }
|
|
1373
|
-
.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }
|
|
1374
|
-
.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }
|
|
1375
|
-
.cg-n { cursor:pointer; }
|
|
1376
|
-
.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }
|
|
1377
|
-
.cg-n.leaf { opacity:.45; }
|
|
1378
|
-
.cg-n.test rect { stroke-dasharray:3 2; }
|
|
1379
|
-
.cg-n.grp rect { stroke-width:1.8; }
|
|
1380
|
-
.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }
|
|
1381
|
-
.cg-e.cand { stroke-dasharray:2 3; }
|
|
1382
|
-
.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }
|
|
1383
|
-
.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */
|
|
1384
|
-
.cg-e.soft { opacity:.55; }
|
|
1385
|
-
.cg-svg.hl .cg-n { opacity:.22; }
|
|
1386
|
-
.cg-svg.hl .cg-e { opacity:.1; }
|
|
1387
|
-
.cg-svg.hl .cg-n.hl { opacity:1; }
|
|
1388
|
-
.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }
|
|
1276
|
+
export const CSS = `
|
|
1277
|
+
:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }
|
|
1278
|
+
* { box-sizing: border-box; }
|
|
1279
|
+
body { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,"PingFang SC","Microsoft Yahei",sans-serif; }
|
|
1280
|
+
main { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }
|
|
1281
|
+
h1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }
|
|
1282
|
+
h1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
1283
|
+
h2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
1284
|
+
h3 { font-size:1.25em; } h4 { font-size:1em; }
|
|
1285
|
+
p { margin:.7em 0; }
|
|
1286
|
+
a { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }
|
|
1287
|
+
code { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
|
|
1288
|
+
pre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }
|
|
1289
|
+
pre code { background:none; padding:0; font-size:.85em; }
|
|
1290
|
+
pre.output { background:#0d1117; color:#e6edf3; }
|
|
1291
|
+
pre.output code { color:inherit; }
|
|
1292
|
+
ul,ol { padding-left:1.6em; } li { margin:.2em 0; }
|
|
1293
|
+
ul.task-list { list-style:none; padding-left:.2em; }
|
|
1294
|
+
li.task input[type=checkbox] { appearance:none; -webkit-appearance:none; width:1.1em; height:1.1em; margin:0 .5em 0 0; vertical-align:-.2em; border:1.5px solid #c8ccd0; border-radius:4px; background:#fff; position:relative; opacity:1; cursor:default; box-sizing:border-box; }
|
|
1295
|
+
li.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }
|
|
1296
|
+
li.task input[type=checkbox]:checked::after { content:"✓"; position:absolute; top:0; right:0; bottom:0; left:0; display:flex; align-items:center; justify-content:center; color:#fff; font-size:.8em; line-height:1; font-weight:700; }
|
|
1297
|
+
aside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }
|
|
1298
|
+
aside.aside { border-left-color:#8b949e; background:#f6f8fa; }
|
|
1299
|
+
aside.warning { border-left-color:#d97706; background:#fff8f0; }
|
|
1300
|
+
aside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }
|
|
1301
|
+
figure { margin:1.2em 0; }
|
|
1302
|
+
figcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }
|
|
1303
|
+
table.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }
|
|
1304
|
+
table.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }
|
|
1305
|
+
table.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }
|
|
1306
|
+
table.geml-table thead th::after { content:" \\2195"; color:var(--muted); font-size:.8em; }
|
|
1307
|
+
table.geml-table thead th.asc::after { content:" \\2191"; color:var(--accent); }
|
|
1308
|
+
table.geml-table thead th.desc::after { content:" \\2193"; color:var(--accent); }
|
|
1309
|
+
table.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }
|
|
1310
|
+
table.geml-table td.computed { color:#0a7c52; }
|
|
1311
|
+
table.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }
|
|
1312
|
+
.table-tools { margin-bottom:6px; } .table-filter { width:240px; max-width:100%; padding:5px 9px; border:1px solid var(--bd); border-radius:7px; font-size:.85em; }
|
|
1313
|
+
/* A table wider than the column scrolls INSIDE its own box — the page itself
|
|
1314
|
+
must never scroll sideways. Only the table is in here: the filter box above
|
|
1315
|
+
and the caption below stay put instead of sliding out of view with it. */
|
|
1316
|
+
.table-scroll { overflow-x:auto; max-width:100%; }
|
|
1317
|
+
.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }
|
|
1318
|
+
.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }
|
|
1319
|
+
.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }
|
|
1320
|
+
.c-title { font-size:15px; font-weight:600; fill:var(--fg); }
|
|
1321
|
+
.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }
|
|
1322
|
+
.media { max-width:100%; border-radius:8px; }
|
|
1323
|
+
.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }
|
|
1324
|
+
.math-block { overflow-x:auto; padding:.4em 0; }
|
|
1325
|
+
sup.fn a { font-size:.75em; }
|
|
1326
|
+
.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }
|
|
1327
|
+
.geml-footer code { font-size:.95em; }
|
|
1328
|
+
.code-graph { margin:1.4em 0; }
|
|
1329
|
+
/* The graph is the widest artifact on the page: let it break out of main's
|
|
1330
|
+
860px reading column and take the viewport, centred, leaving the prose
|
|
1331
|
+
around it untouched. Negative inline margins, NOT transform — a transform
|
|
1332
|
+
would become the containing block for the fullscreen overlay below. */
|
|
1333
|
+
@media (min-width:900px) { .code-graph { margin-inline: calc((100% - min(96vw, 1600px)) / 2); } }
|
|
1334
|
+
.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }
|
|
1335
|
+
.cg-scroll { overflow:auto; min-height:60vh; max-height:84vh; }
|
|
1336
|
+
.cg-svg { display:block; }
|
|
1337
|
+
/* Fullscreen: one class drives the layout, whether the native Fullscreen API
|
|
1338
|
+
took (mount is in the top layer) or we fell back to a fixed overlay. The
|
|
1339
|
+
:fullscreen pseudo-class is deliberately NOT in these selectors — a browser
|
|
1340
|
+
that doesn't know it would drop the whole rule; the runtime keeps the class
|
|
1341
|
+
in sync with fullscreenchange instead. Toolbar/legend keep their height,
|
|
1342
|
+
the stage eats the rest. */
|
|
1343
|
+
.cg-mount.cg-full { position:fixed; inset:0; z-index:9999; margin:0; border:0; border-radius:0; padding:10px 14px; background:var(--bg); display:flex; flex-direction:column; }
|
|
1344
|
+
.cg-mount.cg-full > .cg-bar, .cg-mount.cg-full > .cg-legend, .cg-mount.cg-full > .cg-groups { flex:0 0 auto; }
|
|
1345
|
+
.cg-mount.cg-full .cg-stage { flex:1 1 auto; min-height:0; }
|
|
1346
|
+
.cg-mount.cg-full .cg-scroll { min-height:0; max-height:none; height:100%; }
|
|
1347
|
+
.cg-mount.cg-full .cg-src-body { max-height:none; }
|
|
1348
|
+
.cg-mount.cg-full .cg-frame { flex:1 1 auto; height:auto; }
|
|
1349
|
+
.cg-search-wrap { position:relative; display:inline-block; }
|
|
1350
|
+
.cg-search { font:12px/1.4 inherit; padding:2px 7px; border:1px solid var(--bd); border-radius:4px; background:var(--bg); color:var(--fg); min-width:13ch; }
|
|
1351
|
+
.cg-search-menu { position:absolute; z-index:30; top:calc(100% + 2px); left:0; min-width:24ch; max-width:52ch; max-height:52vh; overflow:auto; background:var(--bg); border:1px solid var(--bd); border-radius:6px; box-shadow:0 6px 20px rgba(0,0,0,.18); }
|
|
1352
|
+
.cg-search-row { display:block; width:100%; text-align:left; padding:4px 9px 4px 18px; border:0; background:none; color:var(--fg); cursor:pointer; font:12px/1.4 inherit; }
|
|
1353
|
+
.cg-search-row:hover { background:var(--bd); }
|
|
1354
|
+
.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }
|
|
1355
|
+
.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }
|
|
1356
|
+
.cg-search-grp:first-of-type { border-top:0; }
|
|
1357
|
+
.cg-stage { display:flex; gap:10px; align-items:flex-start; }
|
|
1358
|
+
.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }
|
|
1359
|
+
.cg-src { flex:0 0 42%; max-width:46%; display:flex; flex-direction:column; border:1px solid var(--bd); border-radius:6px; overflow:hidden; background:var(--bg); }
|
|
1360
|
+
.cg-src-hd { display:flex; gap:8px; align-items:center; justify-content:space-between; padding:4px 8px; border-bottom:1px solid var(--bd); color:var(--muted); font:.76em ui-monospace,Consolas,monospace; word-break:break-all; }
|
|
1361
|
+
.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }
|
|
1362
|
+
.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:84vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }
|
|
1363
|
+
.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }
|
|
1364
|
+
.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }
|
|
1365
|
+
.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }
|
|
1366
|
+
.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }
|
|
1367
|
+
.cg-crumb .cg-seg:hover { text-decoration:underline; }
|
|
1368
|
+
.cg-frame { display:block; width:100%; height:84vh; border:0; background:var(--bg); }
|
|
1369
|
+
.cg-flash { color:#b42318; }
|
|
1370
|
+
.cg-legend { display:flex; gap:14px; align-items:center; justify-content:space-between; flex-wrap:wrap; font-size:.75em; color:var(--muted); margin-top:6px; }
|
|
1371
|
+
.cg-upbtn { cursor:pointer; }
|
|
1372
|
+
.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }
|
|
1373
|
+
.cg-upbtn text { font-size:11px; fill:#57606a; }
|
|
1374
|
+
.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }
|
|
1375
|
+
.cg-upbtn:hover text { fill:var(--accent); }
|
|
1376
|
+
.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }
|
|
1377
|
+
.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }
|
|
1378
|
+
.cg-chip { display:inline-flex; align-items:center; gap:4px; }
|
|
1379
|
+
.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }
|
|
1380
|
+
.cg-note { font-size:.8em; color:#9a6700; }
|
|
1381
|
+
.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }
|
|
1382
|
+
.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }
|
|
1383
|
+
.cg-n { cursor:pointer; }
|
|
1384
|
+
.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }
|
|
1385
|
+
.cg-n.leaf { opacity:.45; }
|
|
1386
|
+
.cg-n.test rect { stroke-dasharray:3 2; }
|
|
1387
|
+
.cg-n.grp rect { stroke-width:1.8; }
|
|
1388
|
+
.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }
|
|
1389
|
+
.cg-e.cand { stroke-dasharray:2 3; }
|
|
1390
|
+
.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }
|
|
1391
|
+
.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */
|
|
1392
|
+
.cg-e.soft { opacity:.55; }
|
|
1393
|
+
.cg-svg.hl .cg-n { opacity:.22; }
|
|
1394
|
+
.cg-svg.hl .cg-e { opacity:.1; }
|
|
1395
|
+
.cg-svg.hl .cg-n.hl { opacity:1; }
|
|
1396
|
+
.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }
|
|
1389
1397
|
`;
|
|
1390
|
-
export const JS = `
|
|
1391
|
-
(function () {
|
|
1392
|
-
function cmp(a, b) {
|
|
1393
|
-
var na = a.dataset.sort, nb = b.dataset.sort;
|
|
1394
|
-
if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);
|
|
1395
|
-
return (a.textContent || "").localeCompare(b.textContent || "");
|
|
1396
|
-
}
|
|
1397
|
-
document.querySelectorAll("table.geml-table").forEach(function (table) {
|
|
1398
|
-
var tbody = table.tBodies[0];
|
|
1399
|
-
if (!tbody) return;
|
|
1400
|
-
// Sort on header click.
|
|
1401
|
-
var ths = table.tHead ? table.tHead.rows[0].cells : [];
|
|
1402
|
-
Array.prototype.forEach.call(ths, function (th, col) {
|
|
1403
|
-
th.addEventListener("click", function () {
|
|
1404
|
-
var dir = th.classList.contains("asc") ? "desc" : "asc";
|
|
1405
|
-
Array.prototype.forEach.call(ths, function (h) { h.classList.remove("asc", "desc"); });
|
|
1406
|
-
th.classList.add(dir);
|
|
1407
|
-
var rows = Array.prototype.slice.call(tbody.rows);
|
|
1408
|
-
rows.sort(function (r1, r2) {
|
|
1409
|
-
var c = cmp(r1.cells[col], r2.cells[col]);
|
|
1410
|
-
return dir === "asc" ? c : -c;
|
|
1411
|
-
});
|
|
1412
|
-
rows.forEach(function (r) { tbody.appendChild(r); });
|
|
1413
|
-
});
|
|
1414
|
-
});
|
|
1415
|
-
// Filter rows.
|
|
1416
|
-
var fig = table.closest(".table-figure");
|
|
1417
|
-
var input = fig ? fig.querySelector(".table-filter") : null;
|
|
1418
|
-
if (input) input.addEventListener("input", function () {
|
|
1419
|
-
var q = input.value.toLowerCase();
|
|
1420
|
-
Array.prototype.forEach.call(tbody.rows, function (r) {
|
|
1421
|
-
r.style.display = (r.textContent || "").toLowerCase().indexOf(q) >= 0 ? "" : "none";
|
|
1422
|
-
});
|
|
1423
|
-
});
|
|
1424
|
-
});
|
|
1425
|
-
})();
|
|
1398
|
+
export const JS = `
|
|
1399
|
+
(function () {
|
|
1400
|
+
function cmp(a, b) {
|
|
1401
|
+
var na = a.dataset.sort, nb = b.dataset.sort;
|
|
1402
|
+
if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);
|
|
1403
|
+
return (a.textContent || "").localeCompare(b.textContent || "");
|
|
1404
|
+
}
|
|
1405
|
+
document.querySelectorAll("table.geml-table").forEach(function (table) {
|
|
1406
|
+
var tbody = table.tBodies[0];
|
|
1407
|
+
if (!tbody) return;
|
|
1408
|
+
// Sort on header click.
|
|
1409
|
+
var ths = table.tHead ? table.tHead.rows[0].cells : [];
|
|
1410
|
+
Array.prototype.forEach.call(ths, function (th, col) {
|
|
1411
|
+
th.addEventListener("click", function () {
|
|
1412
|
+
var dir = th.classList.contains("asc") ? "desc" : "asc";
|
|
1413
|
+
Array.prototype.forEach.call(ths, function (h) { h.classList.remove("asc", "desc"); });
|
|
1414
|
+
th.classList.add(dir);
|
|
1415
|
+
var rows = Array.prototype.slice.call(tbody.rows);
|
|
1416
|
+
rows.sort(function (r1, r2) {
|
|
1417
|
+
var c = cmp(r1.cells[col], r2.cells[col]);
|
|
1418
|
+
return dir === "asc" ? c : -c;
|
|
1419
|
+
});
|
|
1420
|
+
rows.forEach(function (r) { tbody.appendChild(r); });
|
|
1421
|
+
});
|
|
1422
|
+
});
|
|
1423
|
+
// Filter rows.
|
|
1424
|
+
var fig = table.closest(".table-figure");
|
|
1425
|
+
var input = fig ? fig.querySelector(".table-filter") : null;
|
|
1426
|
+
if (input) input.addEventListener("input", function () {
|
|
1427
|
+
var q = input.value.toLowerCase();
|
|
1428
|
+
Array.prototype.forEach.call(tbody.rows, function (r) {
|
|
1429
|
+
r.style.display = (r.textContent || "").toLowerCase().indexOf(q) >= 0 ? "" : "none";
|
|
1430
|
+
});
|
|
1431
|
+
});
|
|
1432
|
+
});
|
|
1433
|
+
})();
|
|
1426
1434
|
`;
|
|
1427
1435
|
// geml-code-graph runtime: layered layout AT DRAW TIME (GEP-0003 / v2-D8) so
|
|
1428
1436
|
// clicking a node re-roots the view inside the embedded slice. Algorithm as
|