@geml/geml 1.7.8 → 1.8.2

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/dist/mcp.js CHANGED
@@ -121,7 +121,43 @@ function resolveGraphDir(graphDir) {
121
121
  // Driving the CLI
122
122
  // ---------------------------------------------------------------------------
123
123
  const CLI = resolve(dirname(fileURLToPath(import.meta.url)), "geml.js");
124
+ // Verbs whose cross-document reference resolution takes a root. The write ones
125
+ // matter most: a write is refused when the result would not parse, so without a
126
+ // root the guard reads its own blind spot as breakage and a document whose
127
+ // `../sibling.md` links only resolve from the server root cannot be edited at
128
+ // all. The server has always known that root and simply never handed it over.
129
+ // NOT `find`: it searches block CONTENT and resolves no references, while its
130
+ // positionals are the places to look — a stray `--root` reads as one more of
131
+ // them, widening the very search the caller narrowed. A test caught exactly
132
+ // that when this list was written without the exception.
133
+ const ROOT_VERBS = new Set([
134
+ "get", "list", "check", "set", "replace", "add", "delete", "rename", "revert",
135
+ ]);
136
+ // The server root as the filesystem really spells it. Falls back to the stored
137
+ // value when it cannot be canonicalized: an unusable root is the caller's
138
+ // problem to hear about from the verb, not something to throw from here.
139
+ function rootReal() {
140
+ try {
141
+ return realpathSync(OPTS.root);
142
+ }
143
+ catch {
144
+ return OPTS.root;
145
+ }
146
+ }
124
147
  function runCli(args, input) {
148
+ // The server root IS the resolution root: every `file` already lives under it,
149
+ // so a reference reaching a sibling directory is in scope by definition.
150
+ if (ROOT_VERBS.has(args[0] ?? "") && !args.includes("--root")) {
151
+ // CANONICALIZED, because `resolveInRoot` already canonicalizes every `file`
152
+ // it hands over. Passing the root raw mixes the two: the CLI's cheap lexical
153
+ // gate compares the reference's absolute path against the root with
154
+ // `relative()`, and a root reached through a symlink is lexically outside a
155
+ // canonical target, so every cross-document reference in the workspace
156
+ // resolves to nothing. On macOS that is the default state of affairs —
157
+ // `os.tmpdir()` is `/var/folders/…`, a symlink to `/private/var/folders/…`
158
+ // — which is why this passed on Windows and failed in CI.
159
+ args = [...args, "--root", rootReal()];
160
+ }
125
161
  const r = spawnSync(process.execPath, [CLI, ...args], {
126
162
  input: input ?? "",
127
163
  encoding: "utf8",
@@ -298,15 +334,21 @@ export const TOOLS = [
298
334
  // stderr is the real error case.
299
335
  if (!run.ok && run.stderr)
300
336
  throw new Error(run.stderr);
301
- // The CLI prints the path it was given, which here is absolute. Every
302
- // other tool in this server speaks paths relative to the root, and a
303
- // model is meant to paste a row's file straight into geml_get — so put
304
- // the rows in those coordinates, and keep the server's own layout out of
305
- // the client's view while we are at it.
306
- const base = realpathSync(OPTS.root) + sep;
337
+ // The CLI prints the path it was given. Every other tool in this server
338
+ // speaks paths relative to the root, and a model is meant to paste a
339
+ // row's file straight into geml_get — so put the rows in those
340
+ // coordinates, and keep the server's own layout out of the client's
341
+ // view while we are at it. The given path carries the RAW root spelling
342
+ // when `path` was omitted but resolveInRoot's realpath-anchored one when
343
+ // it was not, and on a symlinked root (macOS /var -> /private/var) those
344
+ // two differ — so strip whichever spelling a row actually carries.
345
+ const bases = [...new Set([resolve(OPTS.root) + sep, realpathSync(OPTS.root) + sep])];
307
346
  return run.stdout
308
347
  .split("\n")
309
- .map((line) => (line.startsWith(base) ? line.slice(base.length).replace(/\\/g, "/") : line))
348
+ .map((line) => {
349
+ const b = bases.find((x) => line.startsWith(x));
350
+ return b ? line.slice(b.length).replace(/\\/g, "/") : line;
351
+ })
310
352
  .join("\n")
311
353
  .trim();
312
354
  },
@@ -727,25 +769,25 @@ export function handleLine(line, write = (s) => process.stdout.write(s)) {
727
769
  // ---------------------------------------------------------------------------
728
770
  // Entry
729
771
  // ---------------------------------------------------------------------------
730
- export const MCP_USAGE = `usage: geml mcp --root <dir> [--graph <dir>] [--no-history]
731
-
732
- Serve GEML document CRUD over the MCP stdio transport (JSON-RPC 2.0), plus the
733
- read-only code-graph tools when the root holds a code graph.
734
-
735
- --root <dir> REQUIRED. Root directory holding the .geml documents.
736
- Relative paths resolve against the server process's CWD,
737
- which the CLIENT chooses — pass an absolute path.
738
- Every path a client names is confined to this directory;
739
- a client cannot widen or override it.
740
- --graph <dir> Code-graph directory, inside --root. Defaults to
741
- <root>/.geml-code-graph when that holds an index.geml.
742
- With no graph, the code-graph tools are not served
743
- at all (a client sees only the document tools).
744
- --no-history Do not save a .gemlhistory revision before each
745
- write. Default is to save one, so geml_revert always
746
- has a revision to undo to.
747
-
748
- Register with a client:
772
+ export const MCP_USAGE = `usage: geml mcp --root <dir> [--graph <dir>] [--no-history]
773
+
774
+ Serve GEML document CRUD over the MCP stdio transport (JSON-RPC 2.0), plus the
775
+ read-only code-graph tools when the root holds a code graph.
776
+
777
+ --root <dir> REQUIRED. Root directory holding the .geml documents.
778
+ Relative paths resolve against the server process's CWD,
779
+ which the CLIENT chooses — pass an absolute path.
780
+ Every path a client names is confined to this directory;
781
+ a client cannot widen or override it.
782
+ --graph <dir> Code-graph directory, inside --root. Defaults to
783
+ <root>/.geml-code-graph when that holds an index.geml.
784
+ With no graph, the code-graph tools are not served
785
+ at all (a client sees only the document tools).
786
+ --no-history Do not save a .gemlhistory revision before each
787
+ write. Default is to save one, so geml_revert always
788
+ has a revision to undo to.
789
+
790
+ Register with a client:
749
791
  claude mcp add geml -- geml mcp --root /abs/path/to/repo`;
750
792
  export function parseArgs(args) {
751
793
  let root;
@@ -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
@@ -1273,164 +1273,164 @@ function trunc(s, n) {
1273
1273
  // ---------------------------------------------------------------------------
1274
1274
  // Page shell, inline CSS, inline interactivity JS
1275
1275
  // ---------------------------------------------------------------------------
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; }
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; }
1397
1397
  `;
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
- })();
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
+ })();
1434
1434
  `;
1435
1435
  // geml-code-graph runtime: layered layout AT DRAW TIME (GEP-0003 / v2-D8) so
1436
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.8",
3
+ "version": "1.8.2",
4
4
  "mcpName": "io.github.geml-spec/geml",
5
5
  "publishConfig": {
6
6
  "access": "public"