@geml/geml 1.3.2 → 1.4.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/LICENSE +21 -21
- package/README.md +155 -109
- 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 +29 -29
- package/codemap/build.mjs +609 -579
- package/codemap/cross-stack.mjs +303 -0
- package/codemap/detect.mjs +399 -399
- package/codemap/emit.mjs +480 -432
- package/codemap/entries.mjs +129 -129
- package/codemap/exclude.mjs +52 -52
- package/codemap/find.mjs +63 -63
- package/codemap/foldings.mjs +110 -110
- package/codemap/joern-export.sc +83 -83
- package/codemap/mcp-server.mjs +172 -172
- package/codemap/normalize.mjs +275 -272
- package/codemap/recipe-trust.mjs +103 -103
- package/codemap/refresh.mjs +310 -310
- package/codemap/render-all.mjs +64 -64
- package/codemap/serve.mjs +578 -578
- package/codemap/sfc-virtualize.mjs +367 -367
- package/codemap/verify.mjs +148 -143
- package/dist/block-edit.d.ts +1 -0
- package/dist/block-edit.js +112 -0
- package/dist/geml.js +759 -161
- package/dist/render.d.ts +2 -2
- package/dist/render.js +261 -178
- package/package.json +1 -2
package/dist/render.d.ts
CHANGED
|
@@ -52,7 +52,7 @@ interface CGData {
|
|
|
52
52
|
depth: number;
|
|
53
53
|
roots: string[];
|
|
54
54
|
nodes: Record<string, CGNode>;
|
|
55
|
-
edges: [string, string, string, string][];
|
|
55
|
+
edges: [string, string, string, string, string?][];
|
|
56
56
|
mode?: "modules";
|
|
57
57
|
module?: string;
|
|
58
58
|
dir?: "up";
|
|
@@ -75,7 +75,7 @@ declare function buildCodeGraph(startRel: string, opts: RenderOptions, view?: {
|
|
|
75
75
|
error?: string;
|
|
76
76
|
truncated?: boolean;
|
|
77
77
|
};
|
|
78
|
-
export declare const CSS = "\n:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }\n* { box-sizing: border-box; }\nbody { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,\"PingFang SC\",\"Microsoft Yahei\",sans-serif; }\nmain { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }\nh1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }\nh1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh3 { font-size:1.25em; } h4 { font-size:1em; }\np { margin:.7em 0; }\na { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }\ncode { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }\npre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }\npre code { background:none; padding:0; font-size:.85em; }\npre.output { background:#0d1117; color:#e6edf3; }\npre.output code { color:inherit; }\nul,ol { padding-left:1.6em; } li { margin:.2em 0; }\nul.task-list { list-style:none; padding-left:.2em; }\nli.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; }\nli.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }\nli.task input[type=checkbox]:checked::after { content:\"\u2713\"; 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; }\naside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }\naside.aside { border-left-color:#8b949e; background:#f6f8fa; }\naside.warning { border-left-color:#d97706; background:#fff8f0; }\naside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }\nfigure { margin:1.2em 0; }\nfigcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }\ntable.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }\ntable.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }\ntable.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }\ntable.geml-table thead th::after { content:\" \\2195\"; color:var(--muted); font-size:.8em; }\ntable.geml-table thead th.asc::after { content:\" \\2191\"; color:var(--accent); }\ntable.geml-table thead th.desc::after { content:\" \\2193\"; color:var(--accent); }\ntable.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }\ntable.geml-table td.computed { color:#0a7c52; }\ntable.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }\n.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; }\n.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }\n.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }\n.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }\n.c-title { font-size:15px; font-weight:600; fill:var(--fg); }\n.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }\n.media { max-width:100%; border-radius:8px; }\n.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }\n.math-block { overflow-x:auto; padding:.4em 0; }\nsup.fn a { font-size:.75em; }\n.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }\n.geml-footer code { font-size:.95em; }\n.code-graph { margin:1.4em 0; }\n.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }\n.cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }\n.cg-svg { display:block; }\n.cg-search-wrap { position:relative; display:inline-block; }\n.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; }\n.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); }\n.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; }\n.cg-search-row:hover { background:var(--bd); }\n.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }\n.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }\n.cg-search-grp:first-of-type { border-top:0; }\n.cg-stage { display:flex; gap:10px; align-items:flex-start; }\n.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }\n.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); }\n.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; }\n.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }\n.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:72vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }\n.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }\n.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }\n.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }\n.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }\n.cg-crumb .cg-seg:hover { text-decoration:underline; }\n.cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }\n.cg-flash { color:#b42318; }\n.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; }\n.cg-upbtn { cursor:pointer; }\n.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }\n.cg-upbtn text { font-size:11px; fill:#57606a; }\n.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }\n.cg-upbtn:hover text { fill:var(--accent); }\n.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }\n.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }\n.cg-chip { display:inline-flex; align-items:center; gap:4px; }\n.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }\n.cg-note { font-size:.8em; color:#9a6700; }\n.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }\n.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }\n.cg-n { cursor:pointer; }\n.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }\n.cg-n.leaf { opacity:.45; }\n.cg-n.test rect { stroke-dasharray:3 2; }\n.cg-n.grp rect { stroke-width:1.8; }\n.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }\n.cg-e.cand { stroke-dasharray:2 3; }\n.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }\n.cg-e.soft { opacity:.55; }\n.cg-svg.hl .cg-n { opacity:.22; }\n.cg-svg.hl .cg-e { opacity:.1; }\n.cg-svg.hl .cg-n.hl { opacity:1; }\n.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }\n";
|
|
78
|
+
export declare const CSS = "\n:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }\n* { box-sizing: border-box; }\nbody { margin:0; color:var(--fg); background:#fafbfc; font:16px/1.6 -apple-system,BlinkMacSystemFont,\"Segoe UI\",Helvetica,Arial,\"PingFang SC\",\"Microsoft Yahei\",sans-serif; }\nmain { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }\nh1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }\nh1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }\nh3 { font-size:1.25em; } h4 { font-size:1em; }\np { margin:.7em 0; }\na { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }\ncode { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }\npre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }\npre code { background:none; padding:0; font-size:.85em; }\npre.output { background:#0d1117; color:#e6edf3; }\npre.output code { color:inherit; }\nul,ol { padding-left:1.6em; } li { margin:.2em 0; }\nul.task-list { list-style:none; padding-left:.2em; }\nli.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; }\nli.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }\nli.task input[type=checkbox]:checked::after { content:\"\u2713\"; 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; }\naside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }\naside.aside { border-left-color:#8b949e; background:#f6f8fa; }\naside.warning { border-left-color:#d97706; background:#fff8f0; }\naside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }\nfigure { margin:1.2em 0; }\nfigcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }\ntable.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }\ntable.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }\ntable.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }\ntable.geml-table thead th::after { content:\" \\2195\"; color:var(--muted); font-size:.8em; }\ntable.geml-table thead th.asc::after { content:\" \\2191\"; color:var(--accent); }\ntable.geml-table thead th.desc::after { content:\" \\2193\"; color:var(--accent); }\ntable.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }\ntable.geml-table td.computed { color:#0a7c52; }\ntable.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }\n.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; }\n.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }\n.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }\n.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }\n.c-title { font-size:15px; font-weight:600; fill:var(--fg); }\n.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }\n.media { max-width:100%; border-radius:8px; }\n.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }\n.math-block { overflow-x:auto; padding:.4em 0; }\nsup.fn a { font-size:.75em; }\n.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }\n.geml-footer code { font-size:.95em; }\n.code-graph { margin:1.4em 0; }\n.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }\n.cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }\n.cg-svg { display:block; }\n.cg-search-wrap { position:relative; display:inline-block; }\n.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; }\n.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); }\n.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; }\n.cg-search-row:hover { background:var(--bd); }\n.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }\n.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }\n.cg-search-grp:first-of-type { border-top:0; }\n.cg-stage { display:flex; gap:10px; align-items:flex-start; }\n.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }\n.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); }\n.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; }\n.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }\n.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:72vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }\n.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }\n.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }\n.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }\n.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }\n.cg-crumb .cg-seg:hover { text-decoration:underline; }\n.cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }\n.cg-flash { color:#b42318; }\n.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; }\n.cg-upbtn { cursor:pointer; }\n.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }\n.cg-upbtn text { font-size:11px; fill:#57606a; }\n.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }\n.cg-upbtn:hover text { fill:var(--accent); }\n.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }\n.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }\n.cg-chip { display:inline-flex; align-items:center; gap:4px; }\n.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }\n.cg-note { font-size:.8em; color:#9a6700; }\n.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }\n.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }\n.cg-n { cursor:pointer; }\n.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }\n.cg-n.leaf { opacity:.45; }\n.cg-n.test rect { stroke-dasharray:3 2; }\n.cg-n.grp rect { stroke-width:1.8; }\n.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }\n.cg-e.cand { stroke-dasharray:2 3; }\n.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }\n.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */\n.cg-e.soft { opacity:.55; }\n.cg-svg.hl .cg-n { opacity:.22; }\n.cg-svg.hl .cg-e { opacity:.1; }\n.cg-svg.hl .cg-n.hl { opacity:1; }\n.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }\n";
|
|
79
79
|
export declare const JS = "\n(function () {\n function cmp(a, b) {\n var na = a.dataset.sort, nb = b.dataset.sort;\n if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);\n return (a.textContent || \"\").localeCompare(b.textContent || \"\");\n }\n document.querySelectorAll(\"table.geml-table\").forEach(function (table) {\n var tbody = table.tBodies[0];\n if (!tbody) return;\n // Sort on header click.\n var ths = table.tHead ? table.tHead.rows[0].cells : [];\n Array.prototype.forEach.call(ths, function (th, col) {\n th.addEventListener(\"click\", function () {\n var dir = th.classList.contains(\"asc\") ? \"desc\" : \"asc\";\n Array.prototype.forEach.call(ths, function (h) { h.classList.remove(\"asc\", \"desc\"); });\n th.classList.add(dir);\n var rows = Array.prototype.slice.call(tbody.rows);\n rows.sort(function (r1, r2) {\n var c = cmp(r1.cells[col], r2.cells[col]);\n return dir === \"asc\" ? c : -c;\n });\n rows.forEach(function (r) { tbody.appendChild(r); });\n });\n });\n // Filter rows.\n var fig = table.closest(\".table-figure\");\n var input = fig ? fig.querySelector(\".table-filter\") : null;\n if (input) input.addEventListener(\"input\", function () {\n var q = input.value.toLowerCase();\n Array.prototype.forEach.call(tbody.rows, function (r) {\n r.style.display = (r.textContent || \"\").toLowerCase().indexOf(q) >= 0 ? \"\" : \"none\";\n });\n });\n });\n})();\n";
|
|
80
80
|
export declare function codeGraphRuntime(root: {
|
|
81
81
|
querySelectorAll(sel: string): ArrayLike<Element>;
|
package/dist/render.js
CHANGED
|
@@ -561,26 +561,42 @@ function buildCodeGraph(startRel, opts, view) {
|
|
|
561
561
|
if (idx)
|
|
562
562
|
return idx;
|
|
563
563
|
idx = new Map();
|
|
564
|
+
const add = (from, rec) => {
|
|
565
|
+
if (!from.startsWith("#"))
|
|
566
|
+
return;
|
|
567
|
+
let list = idx.get(from.slice(1));
|
|
568
|
+
if (!list) {
|
|
569
|
+
list = [];
|
|
570
|
+
idx.set(from.slice(1), list);
|
|
571
|
+
}
|
|
572
|
+
list.push(rec);
|
|
573
|
+
};
|
|
574
|
+
// Honor only the FIRST table of each id — a crafted second #calls/#api-calls
|
|
575
|
+
// must not inject edges (pinned by render-html tests).
|
|
576
|
+
let sawCalls = false, sawApi = false;
|
|
564
577
|
const d = loadParsed(docRel);
|
|
565
578
|
if (d)
|
|
566
579
|
for (const b of d.children) {
|
|
567
|
-
if (b.kind
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
580
|
+
if (b.kind !== "block" || b.type !== "table" || !b.table)
|
|
581
|
+
continue;
|
|
582
|
+
const cols = b.table.columns;
|
|
583
|
+
const fi = cols.indexOf("from"), ti = cols.indexOf("to");
|
|
584
|
+
if (fi < 0 || ti < 0)
|
|
585
|
+
continue;
|
|
586
|
+
if (b.id === "calls" && !sawCalls) {
|
|
587
|
+
sawCalls = true;
|
|
588
|
+
const ki = cols.indexOf("kind"), ci = cols.indexOf("confidence");
|
|
589
|
+
for (const r of b.table.rows)
|
|
590
|
+
add(r[fi]?.text ?? "", { to: r[ti]?.text ?? "", kind: r[ki]?.text || "call", conf: ci >= 0 ? (r[ci]?.text ?? "") : "" });
|
|
591
|
+
}
|
|
592
|
+
else if (b.id === "api-calls" && !sawApi) {
|
|
593
|
+
sawApi = true;
|
|
594
|
+
// cross-stack link: a frontend function → its backend handler,
|
|
595
|
+
// labelled with the endpoint (METHOD path). Rendered as a distinct
|
|
596
|
+
// `http` edge; the handler is a boundary node (not auto-expanded).
|
|
597
|
+
const ei = cols.indexOf("endpoint");
|
|
598
|
+
for (const r of b.table.rows)
|
|
599
|
+
add(r[fi]?.text ?? "", { to: r[ti]?.text ?? "", kind: "http", conf: "", endpoint: ei >= 0 ? (r[ei]?.text ?? "") : "" });
|
|
584
600
|
}
|
|
585
601
|
}
|
|
586
602
|
cache.set(docRel, idx);
|
|
@@ -596,8 +612,6 @@ function buildCodeGraph(startRel, opts, view) {
|
|
|
596
612
|
const hi = view.node.lastIndexOf("#");
|
|
597
613
|
if (hi <= 0)
|
|
598
614
|
return { error: `bad view node \`${view.node}\`` };
|
|
599
|
-
// Same once-per-document indexing as callRows — the upward BFS crosses
|
|
600
|
-
// documents through their #called-by tables just as hot.
|
|
601
615
|
const calledByIdxOf = (() => {
|
|
602
616
|
const cache = new Map();
|
|
603
617
|
return (docRel) => {
|
|
@@ -605,26 +619,38 @@ function buildCodeGraph(startRel, opts, view) {
|
|
|
605
619
|
if (idx)
|
|
606
620
|
return idx;
|
|
607
621
|
idx = new Map();
|
|
622
|
+
const add = (to, rec) => {
|
|
623
|
+
if (!to.startsWith("#"))
|
|
624
|
+
return;
|
|
625
|
+
let list = idx.get(to.slice(1));
|
|
626
|
+
if (!list) {
|
|
627
|
+
list = [];
|
|
628
|
+
idx.set(to.slice(1), list);
|
|
629
|
+
}
|
|
630
|
+
list.push(rec);
|
|
631
|
+
};
|
|
632
|
+
let sawCb = false, sawApi = false; // first-of-each-id only (see callIdxOf)
|
|
608
633
|
const d = loadParsed(docRel);
|
|
609
634
|
if (d)
|
|
610
635
|
for (const b of d.children) {
|
|
611
|
-
if (b.kind
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
636
|
+
if (b.kind !== "block" || b.type !== "table" || !b.table)
|
|
637
|
+
continue;
|
|
638
|
+
const cols = b.table.columns;
|
|
639
|
+
const fi = cols.indexOf("from"), ti = cols.indexOf("to");
|
|
640
|
+
if (fi < 0 || ti < 0)
|
|
641
|
+
continue;
|
|
642
|
+
if (b.id === "called-by" && !sawCb) {
|
|
643
|
+
sawCb = true;
|
|
644
|
+
const ki = cols.indexOf("kind");
|
|
645
|
+
for (const r of b.table.rows)
|
|
646
|
+
add(r[ti]?.text ?? "", { from: r[fi]?.text ?? "", kind: r[ki]?.text || "call" });
|
|
647
|
+
}
|
|
648
|
+
else if (b.id === "api-served-by" && !sawApi) {
|
|
649
|
+
sawApi = true;
|
|
650
|
+
// cross-stack: a backend handler ← its frontend caller.
|
|
651
|
+
const ei = cols.indexOf("endpoint");
|
|
652
|
+
for (const r of b.table.rows)
|
|
653
|
+
add(r[ti]?.text ?? "", { from: r[fi]?.text ?? "", kind: "http", endpoint: ei >= 0 ? (r[ei]?.text ?? "") : "" });
|
|
628
654
|
}
|
|
629
655
|
}
|
|
630
656
|
cache.set(docRel, idx);
|
|
@@ -656,8 +682,13 @@ function buildCodeGraph(startRel, opts, view) {
|
|
|
656
682
|
}
|
|
657
683
|
nodes[callerKey] = blockInfo(c.doc, c.id);
|
|
658
684
|
}
|
|
659
|
-
|
|
660
|
-
|
|
685
|
+
if (row.endpoint)
|
|
686
|
+
edges.push([toKey, callerKey, row.kind, "", row.endpoint]);
|
|
687
|
+
else
|
|
688
|
+
edges.push([toKey, callerKey, row.kind, ""]);
|
|
689
|
+
// Don't expand the frontend caller's subtree into a backend-rooted
|
|
690
|
+
// callers view across the http boundary; it's a boundary node.
|
|
691
|
+
if (row.kind !== "http" && !seenUp.has(callerKey)) {
|
|
661
692
|
seenUp.add(callerKey);
|
|
662
693
|
next.push(c);
|
|
663
694
|
}
|
|
@@ -712,8 +743,14 @@ function buildCodeGraph(startRel, opts, view) {
|
|
|
712
743
|
}
|
|
713
744
|
nodes[toKey] = blockInfo(t.doc, t.id);
|
|
714
745
|
}
|
|
715
|
-
|
|
716
|
-
|
|
746
|
+
if (row.endpoint)
|
|
747
|
+
edges.push([fromKey, toKey, row.kind, row.conf, row.endpoint]);
|
|
748
|
+
else
|
|
749
|
+
edges.push([fromKey, toKey, row.kind, row.conf]);
|
|
750
|
+
// Cross-stack `http` links reach into the OTHER tree — pull the handler
|
|
751
|
+
// in as a boundary node but don't expand its (backend) subtree into a
|
|
752
|
+
// frontend view; the user clicks through to open it in its own flow.
|
|
753
|
+
if (row.kind !== "http" && !seen.has(toKey)) {
|
|
717
754
|
seen.add(toKey);
|
|
718
755
|
next.push(t);
|
|
719
756
|
}
|
|
@@ -888,142 +925,143 @@ function trunc(s, n) {
|
|
|
888
925
|
// ---------------------------------------------------------------------------
|
|
889
926
|
// Page shell, inline CSS, inline interactivity JS
|
|
890
927
|
// ---------------------------------------------------------------------------
|
|
891
|
-
export const CSS = `
|
|
892
|
-
:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }
|
|
893
|
-
* { box-sizing: border-box; }
|
|
894
|
-
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; }
|
|
895
|
-
main { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }
|
|
896
|
-
h1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }
|
|
897
|
-
h1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
898
|
-
h2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
899
|
-
h3 { font-size:1.25em; } h4 { font-size:1em; }
|
|
900
|
-
p { margin:.7em 0; }
|
|
901
|
-
a { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }
|
|
902
|
-
code { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
|
|
903
|
-
pre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }
|
|
904
|
-
pre code { background:none; padding:0; font-size:.85em; }
|
|
905
|
-
pre.output { background:#0d1117; color:#e6edf3; }
|
|
906
|
-
pre.output code { color:inherit; }
|
|
907
|
-
ul,ol { padding-left:1.6em; } li { margin:.2em 0; }
|
|
908
|
-
ul.task-list { list-style:none; padding-left:.2em; }
|
|
909
|
-
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; }
|
|
910
|
-
li.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }
|
|
911
|
-
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; }
|
|
912
|
-
aside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }
|
|
913
|
-
aside.aside { border-left-color:#8b949e; background:#f6f8fa; }
|
|
914
|
-
aside.warning { border-left-color:#d97706; background:#fff8f0; }
|
|
915
|
-
aside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }
|
|
916
|
-
figure { margin:1.2em 0; }
|
|
917
|
-
figcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }
|
|
918
|
-
table.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }
|
|
919
|
-
table.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }
|
|
920
|
-
table.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }
|
|
921
|
-
table.geml-table thead th::after { content:" \\2195"; color:var(--muted); font-size:.8em; }
|
|
922
|
-
table.geml-table thead th.asc::after { content:" \\2191"; color:var(--accent); }
|
|
923
|
-
table.geml-table thead th.desc::after { content:" \\2193"; color:var(--accent); }
|
|
924
|
-
table.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }
|
|
925
|
-
table.geml-table td.computed { color:#0a7c52; }
|
|
926
|
-
table.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }
|
|
927
|
-
.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; }
|
|
928
|
-
.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }
|
|
929
|
-
.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }
|
|
930
|
-
.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }
|
|
931
|
-
.c-title { font-size:15px; font-weight:600; fill:var(--fg); }
|
|
932
|
-
.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }
|
|
933
|
-
.media { max-width:100%; border-radius:8px; }
|
|
934
|
-
.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }
|
|
935
|
-
.math-block { overflow-x:auto; padding:.4em 0; }
|
|
936
|
-
sup.fn a { font-size:.75em; }
|
|
937
|
-
.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }
|
|
938
|
-
.geml-footer code { font-size:.95em; }
|
|
939
|
-
.code-graph { margin:1.4em 0; }
|
|
940
|
-
.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }
|
|
941
|
-
.cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }
|
|
942
|
-
.cg-svg { display:block; }
|
|
943
|
-
.cg-search-wrap { position:relative; display:inline-block; }
|
|
944
|
-
.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; }
|
|
945
|
-
.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); }
|
|
946
|
-
.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; }
|
|
947
|
-
.cg-search-row:hover { background:var(--bd); }
|
|
948
|
-
.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }
|
|
949
|
-
.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }
|
|
950
|
-
.cg-search-grp:first-of-type { border-top:0; }
|
|
951
|
-
.cg-stage { display:flex; gap:10px; align-items:flex-start; }
|
|
952
|
-
.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }
|
|
953
|
-
.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); }
|
|
954
|
-
.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; }
|
|
955
|
-
.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }
|
|
956
|
-
.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:72vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }
|
|
957
|
-
.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }
|
|
958
|
-
.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }
|
|
959
|
-
.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }
|
|
960
|
-
.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }
|
|
961
|
-
.cg-crumb .cg-seg:hover { text-decoration:underline; }
|
|
962
|
-
.cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }
|
|
963
|
-
.cg-flash { color:#b42318; }
|
|
964
|
-
.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; }
|
|
965
|
-
.cg-upbtn { cursor:pointer; }
|
|
966
|
-
.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }
|
|
967
|
-
.cg-upbtn text { font-size:11px; fill:#57606a; }
|
|
968
|
-
.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }
|
|
969
|
-
.cg-upbtn:hover text { fill:var(--accent); }
|
|
970
|
-
.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }
|
|
971
|
-
.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }
|
|
972
|
-
.cg-chip { display:inline-flex; align-items:center; gap:4px; }
|
|
973
|
-
.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }
|
|
974
|
-
.cg-note { font-size:.8em; color:#9a6700; }
|
|
975
|
-
.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }
|
|
976
|
-
.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }
|
|
977
|
-
.cg-n { cursor:pointer; }
|
|
978
|
-
.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }
|
|
979
|
-
.cg-n.leaf { opacity:.45; }
|
|
980
|
-
.cg-n.test rect { stroke-dasharray:3 2; }
|
|
981
|
-
.cg-n.grp rect { stroke-width:1.8; }
|
|
982
|
-
.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }
|
|
983
|
-
.cg-e.cand { stroke-dasharray:2 3; }
|
|
984
|
-
.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }
|
|
985
|
-
.cg-e.
|
|
986
|
-
.cg-
|
|
987
|
-
.cg-svg.hl .cg-
|
|
988
|
-
.cg-svg.hl .cg-
|
|
989
|
-
.cg-svg.hl .cg-
|
|
928
|
+
export const CSS = `
|
|
929
|
+
:root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }
|
|
930
|
+
* { box-sizing: border-box; }
|
|
931
|
+
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; }
|
|
932
|
+
main { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }
|
|
933
|
+
h1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }
|
|
934
|
+
h1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
935
|
+
h2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
|
|
936
|
+
h3 { font-size:1.25em; } h4 { font-size:1em; }
|
|
937
|
+
p { margin:.7em 0; }
|
|
938
|
+
a { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }
|
|
939
|
+
code { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
|
|
940
|
+
pre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }
|
|
941
|
+
pre code { background:none; padding:0; font-size:.85em; }
|
|
942
|
+
pre.output { background:#0d1117; color:#e6edf3; }
|
|
943
|
+
pre.output code { color:inherit; }
|
|
944
|
+
ul,ol { padding-left:1.6em; } li { margin:.2em 0; }
|
|
945
|
+
ul.task-list { list-style:none; padding-left:.2em; }
|
|
946
|
+
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; }
|
|
947
|
+
li.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }
|
|
948
|
+
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; }
|
|
949
|
+
aside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }
|
|
950
|
+
aside.aside { border-left-color:#8b949e; background:#f6f8fa; }
|
|
951
|
+
aside.warning { border-left-color:#d97706; background:#fff8f0; }
|
|
952
|
+
aside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }
|
|
953
|
+
figure { margin:1.2em 0; }
|
|
954
|
+
figcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }
|
|
955
|
+
table.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }
|
|
956
|
+
table.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }
|
|
957
|
+
table.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }
|
|
958
|
+
table.geml-table thead th::after { content:" \\2195"; color:var(--muted); font-size:.8em; }
|
|
959
|
+
table.geml-table thead th.asc::after { content:" \\2191"; color:var(--accent); }
|
|
960
|
+
table.geml-table thead th.desc::after { content:" \\2193"; color:var(--accent); }
|
|
961
|
+
table.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }
|
|
962
|
+
table.geml-table td.computed { color:#0a7c52; }
|
|
963
|
+
table.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }
|
|
964
|
+
.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; }
|
|
965
|
+
.table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }
|
|
966
|
+
.table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }
|
|
967
|
+
.geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }
|
|
968
|
+
.c-title { font-size:15px; font-weight:600; fill:var(--fg); }
|
|
969
|
+
.c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }
|
|
970
|
+
.media { max-width:100%; border-radius:8px; }
|
|
971
|
+
.diagram-src { color:var(--muted); } .render-error { color:#cf222e; }
|
|
972
|
+
.math-block { overflow-x:auto; padding:.4em 0; }
|
|
973
|
+
sup.fn a { font-size:.75em; }
|
|
974
|
+
.geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }
|
|
975
|
+
.geml-footer code { font-size:.95em; }
|
|
976
|
+
.code-graph { margin:1.4em 0; }
|
|
977
|
+
.cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }
|
|
978
|
+
.cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }
|
|
979
|
+
.cg-svg { display:block; }
|
|
980
|
+
.cg-search-wrap { position:relative; display:inline-block; }
|
|
981
|
+
.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; }
|
|
982
|
+
.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); }
|
|
983
|
+
.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; }
|
|
984
|
+
.cg-search-row:hover { background:var(--bd); }
|
|
985
|
+
.cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }
|
|
986
|
+
.cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }
|
|
987
|
+
.cg-search-grp:first-of-type { border-top:0; }
|
|
988
|
+
.cg-stage { display:flex; gap:10px; align-items:flex-start; }
|
|
989
|
+
.cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }
|
|
990
|
+
.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); }
|
|
991
|
+
.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; }
|
|
992
|
+
.cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }
|
|
993
|
+
.cg-src-body { margin:0; padding:8px 10px; overflow:auto; max-height:72vh; color:var(--fg); font:12px/1.5 ui-monospace,Consolas,monospace; white-space:pre; }
|
|
994
|
+
.cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }
|
|
995
|
+
.cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }
|
|
996
|
+
.cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }
|
|
997
|
+
.cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }
|
|
998
|
+
.cg-crumb .cg-seg:hover { text-decoration:underline; }
|
|
999
|
+
.cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }
|
|
1000
|
+
.cg-flash { color:#b42318; }
|
|
1001
|
+
.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; }
|
|
1002
|
+
.cg-upbtn { cursor:pointer; }
|
|
1003
|
+
.cg-upbtn circle { fill:#fff; stroke:#94a3b8; }
|
|
1004
|
+
.cg-upbtn text { font-size:11px; fill:#57606a; }
|
|
1005
|
+
.cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }
|
|
1006
|
+
.cg-upbtn:hover text { fill:var(--accent); }
|
|
1007
|
+
.cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }
|
|
1008
|
+
.cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }
|
|
1009
|
+
.cg-chip { display:inline-flex; align-items:center; gap:4px; }
|
|
1010
|
+
.cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }
|
|
1011
|
+
.cg-note { font-size:.8em; color:#9a6700; }
|
|
1012
|
+
.cg-n rect { fill:#eef2f7; stroke:#94a3b8; }
|
|
1013
|
+
.cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }
|
|
1014
|
+
.cg-n { cursor:pointer; }
|
|
1015
|
+
.cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }
|
|
1016
|
+
.cg-n.leaf { opacity:.45; }
|
|
1017
|
+
.cg-n.test rect { stroke-dasharray:3 2; }
|
|
1018
|
+
.cg-n.grp rect { stroke-width:1.8; }
|
|
1019
|
+
.cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }
|
|
1020
|
+
.cg-e.cand { stroke-dasharray:2 3; }
|
|
1021
|
+
.cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }
|
|
1022
|
+
.cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */
|
|
1023
|
+
.cg-e.soft { opacity:.55; }
|
|
1024
|
+
.cg-svg.hl .cg-n { opacity:.22; }
|
|
1025
|
+
.cg-svg.hl .cg-e { opacity:.1; }
|
|
1026
|
+
.cg-svg.hl .cg-n.hl { opacity:1; }
|
|
1027
|
+
.cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }
|
|
990
1028
|
`;
|
|
991
|
-
export const JS = `
|
|
992
|
-
(function () {
|
|
993
|
-
function cmp(a, b) {
|
|
994
|
-
var na = a.dataset.sort, nb = b.dataset.sort;
|
|
995
|
-
if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);
|
|
996
|
-
return (a.textContent || "").localeCompare(b.textContent || "");
|
|
997
|
-
}
|
|
998
|
-
document.querySelectorAll("table.geml-table").forEach(function (table) {
|
|
999
|
-
var tbody = table.tBodies[0];
|
|
1000
|
-
if (!tbody) return;
|
|
1001
|
-
// Sort on header click.
|
|
1002
|
-
var ths = table.tHead ? table.tHead.rows[0].cells : [];
|
|
1003
|
-
Array.prototype.forEach.call(ths, function (th, col) {
|
|
1004
|
-
th.addEventListener("click", function () {
|
|
1005
|
-
var dir = th.classList.contains("asc") ? "desc" : "asc";
|
|
1006
|
-
Array.prototype.forEach.call(ths, function (h) { h.classList.remove("asc", "desc"); });
|
|
1007
|
-
th.classList.add(dir);
|
|
1008
|
-
var rows = Array.prototype.slice.call(tbody.rows);
|
|
1009
|
-
rows.sort(function (r1, r2) {
|
|
1010
|
-
var c = cmp(r1.cells[col], r2.cells[col]);
|
|
1011
|
-
return dir === "asc" ? c : -c;
|
|
1012
|
-
});
|
|
1013
|
-
rows.forEach(function (r) { tbody.appendChild(r); });
|
|
1014
|
-
});
|
|
1015
|
-
});
|
|
1016
|
-
// Filter rows.
|
|
1017
|
-
var fig = table.closest(".table-figure");
|
|
1018
|
-
var input = fig ? fig.querySelector(".table-filter") : null;
|
|
1019
|
-
if (input) input.addEventListener("input", function () {
|
|
1020
|
-
var q = input.value.toLowerCase();
|
|
1021
|
-
Array.prototype.forEach.call(tbody.rows, function (r) {
|
|
1022
|
-
r.style.display = (r.textContent || "").toLowerCase().indexOf(q) >= 0 ? "" : "none";
|
|
1023
|
-
});
|
|
1024
|
-
});
|
|
1025
|
-
});
|
|
1026
|
-
})();
|
|
1029
|
+
export const JS = `
|
|
1030
|
+
(function () {
|
|
1031
|
+
function cmp(a, b) {
|
|
1032
|
+
var na = a.dataset.sort, nb = b.dataset.sort;
|
|
1033
|
+
if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);
|
|
1034
|
+
return (a.textContent || "").localeCompare(b.textContent || "");
|
|
1035
|
+
}
|
|
1036
|
+
document.querySelectorAll("table.geml-table").forEach(function (table) {
|
|
1037
|
+
var tbody = table.tBodies[0];
|
|
1038
|
+
if (!tbody) return;
|
|
1039
|
+
// Sort on header click.
|
|
1040
|
+
var ths = table.tHead ? table.tHead.rows[0].cells : [];
|
|
1041
|
+
Array.prototype.forEach.call(ths, function (th, col) {
|
|
1042
|
+
th.addEventListener("click", function () {
|
|
1043
|
+
var dir = th.classList.contains("asc") ? "desc" : "asc";
|
|
1044
|
+
Array.prototype.forEach.call(ths, function (h) { h.classList.remove("asc", "desc"); });
|
|
1045
|
+
th.classList.add(dir);
|
|
1046
|
+
var rows = Array.prototype.slice.call(tbody.rows);
|
|
1047
|
+
rows.sort(function (r1, r2) {
|
|
1048
|
+
var c = cmp(r1.cells[col], r2.cells[col]);
|
|
1049
|
+
return dir === "asc" ? c : -c;
|
|
1050
|
+
});
|
|
1051
|
+
rows.forEach(function (r) { tbody.appendChild(r); });
|
|
1052
|
+
});
|
|
1053
|
+
});
|
|
1054
|
+
// Filter rows.
|
|
1055
|
+
var fig = table.closest(".table-figure");
|
|
1056
|
+
var input = fig ? fig.querySelector(".table-filter") : null;
|
|
1057
|
+
if (input) input.addEventListener("input", function () {
|
|
1058
|
+
var q = input.value.toLowerCase();
|
|
1059
|
+
Array.prototype.forEach.call(tbody.rows, function (r) {
|
|
1060
|
+
r.style.display = (r.textContent || "").toLowerCase().indexOf(q) >= 0 ? "" : "none";
|
|
1061
|
+
});
|
|
1062
|
+
});
|
|
1063
|
+
});
|
|
1064
|
+
})();
|
|
1027
1065
|
`;
|
|
1028
1066
|
// geml-code-graph runtime: layered layout AT DRAW TIME (GEP-0003 / v2-D8) so
|
|
1029
1067
|
// clicking a node re-roots the view inside the embedded slice. Algorithm as
|
|
@@ -1042,6 +1080,30 @@ export function codeGraphRuntime(root) {
|
|
|
1042
1080
|
el.setAttribute(k, String(attrs[k]));
|
|
1043
1081
|
return el;
|
|
1044
1082
|
}
|
|
1083
|
+
// Same-origin confinement for every URL the runtime fetches or loads.
|
|
1084
|
+
// navBase is derived from the mount's document-controlled `data-src`, so a
|
|
1085
|
+
// crafted codemap doc could otherwise aim a fetch / HEAD probe / <script src>
|
|
1086
|
+
// at a third-party host (silent beacon, SSRF, or remote-code load) or read an
|
|
1087
|
+
// out-of-directory local file. Resolve the candidate against the page and
|
|
1088
|
+
// require the SAME origin (and, on file://, the same directory). With no
|
|
1089
|
+
// location context (unit tests / non-browser) there is nothing to confine to,
|
|
1090
|
+
// so allow — the browser/CLI callers always have one.
|
|
1091
|
+
function cgSameOrigin(u) {
|
|
1092
|
+
try {
|
|
1093
|
+
var here = (typeof location !== "undefined" && location.href) ? location.href : "";
|
|
1094
|
+
if (!here)
|
|
1095
|
+
return true;
|
|
1096
|
+
var abs = new URL(String(u), here), cur = new URL(here);
|
|
1097
|
+
if (abs.protocol !== cur.protocol)
|
|
1098
|
+
return false;
|
|
1099
|
+
if (cur.protocol === "file:")
|
|
1100
|
+
return abs.pathname.indexOf(cur.pathname.replace(/[^\/]*$/, "")) === 0;
|
|
1101
|
+
return abs.origin === cur.origin;
|
|
1102
|
+
}
|
|
1103
|
+
catch (e) {
|
|
1104
|
+
return false;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1045
1107
|
// Arrow-marker ids must be unique per drawn svg — several mounts share one
|
|
1046
1108
|
// document, and duplicate ids would make every graph point at the first.
|
|
1047
1109
|
var arrowSeq = 0;
|
|
@@ -1446,7 +1508,7 @@ export function codeGraphRuntime(root) {
|
|
|
1446
1508
|
if (!a || !b)
|
|
1447
1509
|
return;
|
|
1448
1510
|
var isBack = s.back[e[0] + ">" + e[1]] || (e[0] === e[1]);
|
|
1449
|
-
var cls = "cg-e" + (e[2] === "candidate" ? " cand" : "") + (isBack ? " back" : "") + (e[3] === "medium" || e[3] === "low" ? " soft" : "");
|
|
1511
|
+
var cls = "cg-e" + (e[2] === "candidate" ? " cand" : "") + (e[2] === "http" ? " http" : "") + (isBack ? " back" : "") + (e[3] === "medium" || e[3] === "low" ? " soft" : "");
|
|
1450
1512
|
var p;
|
|
1451
1513
|
if (e[0] === e[1]) {
|
|
1452
1514
|
p = LR
|
|
@@ -1472,6 +1534,11 @@ export function codeGraphRuntime(root) {
|
|
|
1472
1534
|
p = "M" + x1 + " " + y1 + " C " + x1 + " " + (y1 + GY / 2) + " " + x2 + " " + (y2 - GY / 2) + " " + x2 + " " + y2;
|
|
1473
1535
|
}
|
|
1474
1536
|
var pathEl = h("path", { d: p, class: cls, "marker-end": "url(#" + arrId + (isBack ? "-b" : "") + ")" });
|
|
1537
|
+
if (e[2] === "http" && e[4]) {
|
|
1538
|
+
var tt = h("title", {});
|
|
1539
|
+
tt.textContent = e[4];
|
|
1540
|
+
pathEl.appendChild(tt);
|
|
1541
|
+
} // endpoint on hover
|
|
1475
1542
|
var ek = e[0] + ">" + e[1];
|
|
1476
1543
|
edgeEls[ek] = pathEl;
|
|
1477
1544
|
edgeBase[ek] = cls;
|
|
@@ -1658,7 +1725,11 @@ export function codeGraphRuntime(root) {
|
|
|
1658
1725
|
// blocked) — embed directly; the frame contains any error itself.
|
|
1659
1726
|
try {
|
|
1660
1727
|
if (/^https?:$/.test(window.location.protocol)) {
|
|
1661
|
-
|
|
1728
|
+
if (!cgSameOrigin(html)) {
|
|
1729
|
+
flash("cannot reach " + html + " (cross-origin blocked)");
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
fetch(html, { method: "HEAD", credentials: "omit" }).then(function (r) {
|
|
1662
1733
|
if (r.ok)
|
|
1663
1734
|
embed();
|
|
1664
1735
|
else
|
|
@@ -1872,6 +1943,10 @@ export function codeGraphRuntime(root) {
|
|
|
1872
1943
|
function withIndex(cb) {
|
|
1873
1944
|
if (window.__gemlSearch)
|
|
1874
1945
|
return cb(window.__gemlSearch);
|
|
1946
|
+
if (!cgSameOrigin(navBase + "_index/search-index.js")) {
|
|
1947
|
+
cb([]);
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1875
1950
|
var s = document.createElement("script");
|
|
1876
1951
|
s.src = navBase + "_index/search-index.js";
|
|
1877
1952
|
s.onload = function () { cb(window.__gemlSearch || []); };
|
|
@@ -2191,8 +2266,16 @@ export function codeGraphRuntime(root) {
|
|
|
2191
2266
|
degrade();
|
|
2192
2267
|
return;
|
|
2193
2268
|
}
|
|
2269
|
+
// `path` is the node's document-controlled `src=` route and `base` may
|
|
2270
|
+
// be empty (@self / bare-filename mounts), so an absolute or //-relative
|
|
2271
|
+
// src would fetch a third-party host (beacon / SSRF). Confine to the
|
|
2272
|
+
// page's origin and never send credentials.
|
|
2273
|
+
if (!cgSameOrigin(base + path)) {
|
|
2274
|
+
degrade();
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2194
2277
|
try {
|
|
2195
|
-
Promise.resolve(fetchFn(base + path)).then(function (r) {
|
|
2278
|
+
Promise.resolve(fetchFn(base + path, { credentials: "omit" })).then(function (r) {
|
|
2196
2279
|
if (!r || r.ok === false) {
|
|
2197
2280
|
degrade();
|
|
2198
2281
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geml/geml",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -48,7 +48,6 @@
|
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsc",
|
|
50
50
|
"test": "tsc && node test/all.mjs",
|
|
51
|
-
"convert": "node dist/geml.js convert",
|
|
52
51
|
"parse": "node dist/geml.js",
|
|
53
52
|
"coverage": "tsc && c8 --all --include=dist/**/*.js --include=codemap/**/*.mjs --reporter=text --reporter=text-summary node test/all.mjs",
|
|
54
53
|
"coverage:check": "tsc && c8 --all --include=dist/**/*.js --include=codemap/**/*.mjs --check-coverage --lines 95 --statements 95 --functions 95 --branches 95 node test/all.mjs",
|