@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.
@@ -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 omitted = lines.length - shown.length;
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(shown.join("\n"))}</pre>`;
575
- return `<figure${idAttr}>${tail ? note + pre : pre + note}${cap}</figure>`;
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
- // Parsing + laying out tens of thousands of <table> rows freezes the
640
- // page for seconds, so the HTML view renders a bounded preview (the
641
- // model keeps every row: charts, computed summaries and the code-graph
642
- // never read the HTML). Codemap edge tables additionally fold shut
643
- // they are machine data; elsewhere the table is content and stays open.
644
- // The codemap index's #modules table IS the page's content — the module
645
- // inventory people scan and filter so it renders in full. Edge tables
646
- // (#calls / #called-by) stay previewed+folded: machine data at scale.
647
- const maxRows = this.isCodemapDoc && id === "modules" ? Infinity : (this.opts.tableRows ?? 500);
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.length > maxRows ? allRows.slice(0, maxRows) : 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 > maxRows) {
670
- const note = `<p class="table-note">showing the first ${maxRows} of ${allRows.length} rows — the complete table is in the document source</p>`;
671
- if (this.isCodemapDoc) {
672
- const summary = `${esc(id ? "#" + id : "table")} · ${allRows.length} rows (preview: first ${maxRows})`;
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geml/geml",
3
- "version": "1.7.7",
3
+ "version": "1.8.1",
4
4
  "mcpName": "io.github.geml-spec/geml",
5
5
  "publishConfig": {
6
6
  "access": "public"