@geml/geml 1.5.1 → 1.6.0

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/render.js CHANGED
@@ -1229,143 +1229,143 @@ function trunc(s, n) {
1229
1229
  // ---------------------------------------------------------------------------
1230
1230
  // Page shell, inline CSS, inline interactivity JS
1231
1231
  // ---------------------------------------------------------------------------
1232
- export const CSS = `
1233
- :root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }
1234
- * { box-sizing: border-box; }
1235
- 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; }
1236
- main { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }
1237
- h1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }
1238
- h1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
1239
- h2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
1240
- h3 { font-size:1.25em; } h4 { font-size:1em; }
1241
- p { margin:.7em 0; }
1242
- a { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }
1243
- code { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
1244
- pre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }
1245
- pre code { background:none; padding:0; font-size:.85em; }
1246
- pre.output { background:#0d1117; color:#e6edf3; }
1247
- pre.output code { color:inherit; }
1248
- ul,ol { padding-left:1.6em; } li { margin:.2em 0; }
1249
- ul.task-list { list-style:none; padding-left:.2em; }
1250
- 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; }
1251
- li.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }
1252
- 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; }
1253
- aside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }
1254
- aside.aside { border-left-color:#8b949e; background:#f6f8fa; }
1255
- aside.warning { border-left-color:#d97706; background:#fff8f0; }
1256
- aside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }
1257
- figure { margin:1.2em 0; }
1258
- figcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }
1259
- table.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }
1260
- table.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }
1261
- table.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }
1262
- table.geml-table thead th::after { content:" \\2195"; color:var(--muted); font-size:.8em; }
1263
- table.geml-table thead th.asc::after { content:" \\2191"; color:var(--accent); }
1264
- table.geml-table thead th.desc::after { content:" \\2193"; color:var(--accent); }
1265
- table.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }
1266
- table.geml-table td.computed { color:#0a7c52; }
1267
- table.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }
1268
- .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; }
1269
- .table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }
1270
- .table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }
1271
- .geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }
1272
- .c-title { font-size:15px; font-weight:600; fill:var(--fg); }
1273
- .c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }
1274
- .media { max-width:100%; border-radius:8px; }
1275
- .diagram-src { color:var(--muted); } .render-error { color:#cf222e; }
1276
- .math-block { overflow-x:auto; padding:.4em 0; }
1277
- sup.fn a { font-size:.75em; }
1278
- .geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }
1279
- .geml-footer code { font-size:.95em; }
1280
- .code-graph { margin:1.4em 0; }
1281
- .cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }
1282
- .cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }
1283
- .cg-svg { display:block; }
1284
- .cg-search-wrap { position:relative; display:inline-block; }
1285
- .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; }
1286
- .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); }
1287
- .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; }
1288
- .cg-search-row:hover { background:var(--bd); }
1289
- .cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }
1290
- .cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }
1291
- .cg-search-grp:first-of-type { border-top:0; }
1292
- .cg-stage { display:flex; gap:10px; align-items:flex-start; }
1293
- .cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }
1294
- .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); }
1295
- .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; }
1296
- .cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }
1297
- .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; }
1298
- .cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }
1299
- .cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }
1300
- .cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }
1301
- .cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }
1302
- .cg-crumb .cg-seg:hover { text-decoration:underline; }
1303
- .cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }
1304
- .cg-flash { color:#b42318; }
1305
- .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; }
1306
- .cg-upbtn { cursor:pointer; }
1307
- .cg-upbtn circle { fill:#fff; stroke:#94a3b8; }
1308
- .cg-upbtn text { font-size:11px; fill:#57606a; }
1309
- .cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }
1310
- .cg-upbtn:hover text { fill:var(--accent); }
1311
- .cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }
1312
- .cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }
1313
- .cg-chip { display:inline-flex; align-items:center; gap:4px; }
1314
- .cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }
1315
- .cg-note { font-size:.8em; color:#9a6700; }
1316
- .cg-n rect { fill:#eef2f7; stroke:#94a3b8; }
1317
- .cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }
1318
- .cg-n { cursor:pointer; }
1319
- .cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }
1320
- .cg-n.leaf { opacity:.45; }
1321
- .cg-n.test rect { stroke-dasharray:3 2; }
1322
- .cg-n.grp rect { stroke-width:1.8; }
1323
- .cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }
1324
- .cg-e.cand { stroke-dasharray:2 3; }
1325
- .cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }
1326
- .cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */
1327
- .cg-e.soft { opacity:.55; }
1328
- .cg-svg.hl .cg-n { opacity:.22; }
1329
- .cg-svg.hl .cg-e { opacity:.1; }
1330
- .cg-svg.hl .cg-n.hl { opacity:1; }
1331
- .cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }
1232
+ export const CSS = `
1233
+ :root { --fg:#1f2328; --muted:#656d76; --bd:#d0d7de; --bg:#fff; --accent:#2563eb; --code-bg:#f6f8fa; }
1234
+ * { box-sizing: border-box; }
1235
+ 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; }
1236
+ main { max-width: 860px; margin: 0 auto; padding: 48px 24px 96px; background:var(--bg); }
1237
+ h1,h2,h3,h4,h5,h6 { line-height:1.25; margin:1.6em 0 .6em; scroll-margin-top:16px; }
1238
+ h1 { font-size:2em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
1239
+ h2 { font-size:1.5em; border-bottom:1px solid var(--bd); padding-bottom:.3em; }
1240
+ h3 { font-size:1.25em; } h4 { font-size:1em; }
1241
+ p { margin:.7em 0; }
1242
+ a { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }
1243
+ code { background:var(--code-bg); padding:.15em .35em; border-radius:6px; font:.88em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; }
1244
+ pre { background:var(--code-bg); padding:14px 16px; border-radius:8px; overflow:auto; }
1245
+ pre code { background:none; padding:0; font-size:.85em; }
1246
+ pre.output { background:#0d1117; color:#e6edf3; }
1247
+ pre.output code { color:inherit; }
1248
+ ul,ol { padding-left:1.6em; } li { margin:.2em 0; }
1249
+ ul.task-list { list-style:none; padding-left:.2em; }
1250
+ 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; }
1251
+ li.task input[type=checkbox]:checked { background-color:#1f883d; border-color:#1f883d; }
1252
+ 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; }
1253
+ aside.callout { border-left:4px solid var(--accent); background:#f0f6ff; padding:.4em 16px; border-radius:0 8px 8px 0; margin:1em 0; }
1254
+ aside.aside { border-left-color:#8b949e; background:#f6f8fa; }
1255
+ aside.warning { border-left-color:#d97706; background:#fff8f0; }
1256
+ aside.callout > :first-child { margin-top:0; } aside.callout > :last-child { margin-bottom:0; }
1257
+ figure { margin:1.2em 0; }
1258
+ figcaption { color:var(--muted); font-size:.86em; text-align:center; margin-top:.5em; }
1259
+ table.geml-table { border-collapse:collapse; width:100%; font-size:.92em; }
1260
+ table.geml-table th, table.geml-table td { border:1px solid var(--bd); padding:6px 12px; }
1261
+ table.geml-table thead th { background:var(--code-bg); cursor:pointer; user-select:none; white-space:nowrap; }
1262
+ table.geml-table thead th::after { content:" \\2195"; color:var(--muted); font-size:.8em; }
1263
+ table.geml-table thead th.asc::after { content:" \\2191"; color:var(--accent); }
1264
+ table.geml-table thead th.desc::after { content:" \\2193"; color:var(--accent); }
1265
+ table.geml-table tbody tr:nth-child(2n) { background:#fafbfc; }
1266
+ table.geml-table td.computed { color:#0a7c52; }
1267
+ table.geml-table tfoot td { background:var(--code-bg); font-weight:600; border-top:2px solid var(--bd); }
1268
+ .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; }
1269
+ .table-figure details > summary { cursor:pointer; color:var(--muted); font-size:.86em; padding:4px 0; }
1270
+ .table-note { color:var(--muted); font-size:.82em; margin:6px 0 0; }
1271
+ .geml-chart { width:100%; height:auto; background:var(--bg); border:1px solid var(--bd); border-radius:8px; }
1272
+ .c-title { font-size:15px; font-weight:600; fill:var(--fg); }
1273
+ .c-grid { stroke:#eaecef; } .c-axis { stroke:#aab1b8; } .c-tick { font-size:11px; fill:var(--muted); } .c-legend { font-size:12px; fill:var(--fg); }
1274
+ .media { max-width:100%; border-radius:8px; }
1275
+ .diagram-src { color:var(--muted); } .render-error { color:#cf222e; }
1276
+ .math-block { overflow-x:auto; padding:.4em 0; }
1277
+ sup.fn a { font-size:.75em; }
1278
+ .geml-footer { max-width:860px; margin:0 auto; padding:16px 24px 40px; color:var(--muted); font-size:.82em; }
1279
+ .geml-footer code { font-size:.95em; }
1280
+ .code-graph { margin:1.4em 0; }
1281
+ .cg-mount { border:1px solid var(--bd); border-radius:8px; padding:10px 12px; background:var(--bg); }
1282
+ .cg-scroll { overflow:auto; min-height:52vh; max-height:72vh; }
1283
+ .cg-svg { display:block; }
1284
+ .cg-search-wrap { position:relative; display:inline-block; }
1285
+ .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; }
1286
+ .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); }
1287
+ .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; }
1288
+ .cg-search-row:hover { background:var(--bd); }
1289
+ .cg-search-count { position:sticky; top:0; padding:4px 9px; font-size:11px; opacity:.65; background:var(--bg); border-bottom:1px solid var(--bd); }
1290
+ .cg-search-grp { padding:6px 9px 2px; font-size:11px; font-weight:600; opacity:.7; border-top:1px solid var(--bd); }
1291
+ .cg-search-grp:first-of-type { border-top:0; }
1292
+ .cg-stage { display:flex; gap:10px; align-items:flex-start; }
1293
+ .cg-stage .cg-scroll { flex:1 1 auto; min-width:0; }
1294
+ .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); }
1295
+ .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; }
1296
+ .cg-src-hd button { font:inherit; border:1px solid var(--bd); border-radius:5px; background:transparent; color:var(--muted); cursor:pointer; padding:0 6px; }
1297
+ .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; }
1298
+ .cg-src-note { color:var(--muted); font-style:italic; white-space:pre-wrap; }
1299
+ .cg-bar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; font-size:.82em; color:var(--muted); margin-bottom:6px; }
1300
+ .cg-bar button { font:inherit; padding:1px 8px; border:1px solid var(--bd); border-radius:5px; background:transparent; cursor:pointer; }
1301
+ .cg-crumb .cg-seg { border:0; border-radius:0; padding:0; background:none; color:var(--accent); cursor:pointer; font:inherit; }
1302
+ .cg-crumb .cg-seg:hover { text-decoration:underline; }
1303
+ .cg-frame { display:block; width:100%; height:72vh; border:0; background:var(--bg); }
1304
+ .cg-flash { color:#b42318; }
1305
+ .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; }
1306
+ .cg-upbtn { cursor:pointer; }
1307
+ .cg-upbtn circle { fill:#fff; stroke:#94a3b8; }
1308
+ .cg-upbtn text { font-size:11px; fill:#57606a; }
1309
+ .cg-upbtn:hover circle { stroke:var(--accent); stroke-width:1.6; }
1310
+ .cg-upbtn:hover text { fill:var(--accent); }
1311
+ .cg-uplink { fill:none; stroke:#94a3b8; stroke-dasharray:3 2.5; pointer-events:none; }
1312
+ .cg-groups { display:flex; flex-wrap:wrap; gap:4px 12px; margin-top:6px; font-size:.75em; color:var(--muted); }
1313
+ .cg-chip { display:inline-flex; align-items:center; gap:4px; }
1314
+ .cg-chip i { width:10px; height:10px; border-radius:2px; border:1px solid #94a3b8; display:inline-block; }
1315
+ .cg-note { font-size:.8em; color:#9a6700; }
1316
+ .cg-n rect { fill:#eef2f7; stroke:#94a3b8; }
1317
+ .cg-n text { font-size:12px; fill:var(--fg); font-family:ui-monospace,Consolas,monospace; }
1318
+ .cg-n { cursor:pointer; }
1319
+ .cg-n.root rect { fill:#dbeafe; stroke:#2563eb; stroke-width:2; }
1320
+ .cg-n.leaf { opacity:.45; }
1321
+ .cg-n.test rect { stroke-dasharray:3 2; }
1322
+ .cg-n.grp rect { stroke-width:1.8; }
1323
+ .cg-e { fill:none; stroke:#94a3b8; stroke-width:.9; }
1324
+ .cg-e.cand { stroke-dasharray:2 3; }
1325
+ .cg-e.back { stroke:#dc2626; stroke-dasharray:5 3; }
1326
+ .cg-e.http { stroke:#0891b2; stroke-width:1.5; stroke-dasharray:5 2; } /* cross-stack API link */
1327
+ .cg-e.soft { opacity:.55; }
1328
+ .cg-svg.hl .cg-n { opacity:.22; }
1329
+ .cg-svg.hl .cg-e { opacity:.1; }
1330
+ .cg-svg.hl .cg-n.hl { opacity:1; }
1331
+ .cg-svg.hl .cg-e.hl { opacity:1; stroke-width:1.6; }
1332
1332
  `;
1333
- export const JS = `
1334
- (function () {
1335
- function cmp(a, b) {
1336
- var na = a.dataset.sort, nb = b.dataset.sort;
1337
- if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);
1338
- return (a.textContent || "").localeCompare(b.textContent || "");
1339
- }
1340
- document.querySelectorAll("table.geml-table").forEach(function (table) {
1341
- var tbody = table.tBodies[0];
1342
- if (!tbody) return;
1343
- // Sort on header click.
1344
- var ths = table.tHead ? table.tHead.rows[0].cells : [];
1345
- Array.prototype.forEach.call(ths, function (th, col) {
1346
- th.addEventListener("click", function () {
1347
- var dir = th.classList.contains("asc") ? "desc" : "asc";
1348
- Array.prototype.forEach.call(ths, function (h) { h.classList.remove("asc", "desc"); });
1349
- th.classList.add(dir);
1350
- var rows = Array.prototype.slice.call(tbody.rows);
1351
- rows.sort(function (r1, r2) {
1352
- var c = cmp(r1.cells[col], r2.cells[col]);
1353
- return dir === "asc" ? c : -c;
1354
- });
1355
- rows.forEach(function (r) { tbody.appendChild(r); });
1356
- });
1357
- });
1358
- // Filter rows.
1359
- var fig = table.closest(".table-figure");
1360
- var input = fig ? fig.querySelector(".table-filter") : null;
1361
- if (input) input.addEventListener("input", function () {
1362
- var q = input.value.toLowerCase();
1363
- Array.prototype.forEach.call(tbody.rows, function (r) {
1364
- r.style.display = (r.textContent || "").toLowerCase().indexOf(q) >= 0 ? "" : "none";
1365
- });
1366
- });
1367
- });
1368
- })();
1333
+ export const JS = `
1334
+ (function () {
1335
+ function cmp(a, b) {
1336
+ var na = a.dataset.sort, nb = b.dataset.sort;
1337
+ if (na !== undefined && nb !== undefined) return parseFloat(na) - parseFloat(nb);
1338
+ return (a.textContent || "").localeCompare(b.textContent || "");
1339
+ }
1340
+ document.querySelectorAll("table.geml-table").forEach(function (table) {
1341
+ var tbody = table.tBodies[0];
1342
+ if (!tbody) return;
1343
+ // Sort on header click.
1344
+ var ths = table.tHead ? table.tHead.rows[0].cells : [];
1345
+ Array.prototype.forEach.call(ths, function (th, col) {
1346
+ th.addEventListener("click", function () {
1347
+ var dir = th.classList.contains("asc") ? "desc" : "asc";
1348
+ Array.prototype.forEach.call(ths, function (h) { h.classList.remove("asc", "desc"); });
1349
+ th.classList.add(dir);
1350
+ var rows = Array.prototype.slice.call(tbody.rows);
1351
+ rows.sort(function (r1, r2) {
1352
+ var c = cmp(r1.cells[col], r2.cells[col]);
1353
+ return dir === "asc" ? c : -c;
1354
+ });
1355
+ rows.forEach(function (r) { tbody.appendChild(r); });
1356
+ });
1357
+ });
1358
+ // Filter rows.
1359
+ var fig = table.closest(".table-figure");
1360
+ var input = fig ? fig.querySelector(".table-filter") : null;
1361
+ if (input) input.addEventListener("input", function () {
1362
+ var q = input.value.toLowerCase();
1363
+ Array.prototype.forEach.call(tbody.rows, function (r) {
1364
+ r.style.display = (r.textContent || "").toLowerCase().indexOf(q) >= 0 ? "" : "none";
1365
+ });
1366
+ });
1367
+ });
1368
+ })();
1369
1369
  `;
1370
1370
  // geml-code-graph runtime: layered layout AT DRAW TIME (GEP-0003 / v2-D8) so
1371
1371
  // clicking a node re-roots the view inside the embedded slice. Algorithm as
@@ -0,0 +1,55 @@
1
+ export interface Span {
2
+ start: number;
3
+ end: number;
4
+ }
5
+ export interface Unit {
6
+ span: Span;
7
+ kind: "block" | "heading" | "footnote";
8
+ type?: string;
9
+ id?: string;
10
+ level?: number;
11
+ text?: string;
12
+ }
13
+ export type Selector = {
14
+ form: "list";
15
+ } | {
16
+ form: "id";
17
+ raw: string;
18
+ } | {
19
+ form: "type";
20
+ type: string;
21
+ } | {
22
+ form: "content";
23
+ type?: string;
24
+ hex: string;
25
+ nth: number;
26
+ } | {
27
+ form: "attr";
28
+ type: string;
29
+ key: string;
30
+ };
31
+ export declare function sha8(text: string): string;
32
+ export declare function parseSelector(raw: string | undefined, attrsIdOf: (braces: string) => string | undefined): Selector;
33
+ export interface Addressed {
34
+ unit: Unit;
35
+ hex: string;
36
+ nth: number;
37
+ }
38
+ export declare function addressUnits(units: Unit[], textOf: (u: Unit) => string): Addressed[];
39
+ export declare function shortestAddress(a: Addressed, all: Addressed[]): string;
40
+ export type ContentHit = {
41
+ ok: true;
42
+ unit: Unit;
43
+ } | {
44
+ ok: false;
45
+ why: "no-match";
46
+ } | {
47
+ ok: false;
48
+ why: "wrong-type";
49
+ found: string;
50
+ };
51
+ export declare function matchContent(sel: Extract<Selector, {
52
+ form: "content";
53
+ }>, all: Addressed[]): ContentHit;
54
+ export declare function discoveryHint(where: string): string;
55
+ export declare function matchType(type: string, all: Addressed[]): Unit[];
@@ -0,0 +1,112 @@
1
+ // Block selectors — the one syntax `get`, `set` and `history get` all address
2
+ // blocks with (design: docs/design/specs/2026-08-04-geml-get-set-selector-design-change.md).
3
+ //
4
+ // §2's rule: a selector is a FILTER over blocks, `{…}` holds keys, and the same
5
+ // abbreviation rule applies twice — `#id` is `{#id}` short, `@<hex>` is
6
+ // `{@<hex>}` short. Both are keys; they differ only in selectivity.
7
+ //
8
+ // This module is PURE: it parses selector text and matches it against a unit
9
+ // index the caller supplies. It deliberately imports nothing from geml.ts —
10
+ // that module runs the CLI dispatch on import, so depending on it here would
11
+ // turn `import { parseSelector }` into "run the CLI". The scan that produces
12
+ // `Unit[]` therefore stays in geml.ts (one walk, several sinks) and the
13
+ // selector logic stays here, where it can be unit-tested on plain data.
14
+ import { createHash } from "node:crypto";
15
+ // The content address's hash. Same spelling as the `.gemlhistory` unit key
16
+ // (history.ts:112) because both answer the same question — how to address a
17
+ // unit that carries no id. The VALUES are deliberately not promised to match:
18
+ // history hashes a tile (trailing blank lines included), this hashes a block's
19
+ // span (§3.3). Do not port an address from one layer to the other.
20
+ export function sha8(text) {
21
+ return createHash("sha256").update(Buffer.from(text, "utf8")).digest("hex").slice(0, 8);
22
+ }
23
+ const FENCE_SEL = /^={3,}[ \t]*([A-Za-z][A-Za-z0-9_-]*)[ \t]*(@[0-9a-fA-F]{1,}(?:~\d+)?)?[ \t]*(\{.*\})?[ \t]*$/;
24
+ const BARE_AT = /^@([0-9a-fA-F]+)(?:~(\d+))?$/;
25
+ // Parse selector TEXT. Never touches a document: every form is decided by
26
+ // lexis alone, which is also what keeps the two selector namespaces on
27
+ // `history get <file> <rev> <selector>` from overlapping (history design §10.2).
28
+ // `attrsIdOf` lets the caller reuse its own `{…}` parser (parseAttrs) rather
29
+ // than this module growing a second one.
30
+ export function parseSelector(raw, attrsIdOf) {
31
+ if (raw === undefined || raw.trim() === "")
32
+ return { form: "list" };
33
+ const s = raw.trim();
34
+ const bare = BARE_AT.exec(s);
35
+ if (bare)
36
+ return { form: "content", hex: bare[1].toLowerCase(), nth: bare[2] ? Number(bare[2]) : 0 };
37
+ const fence = FENCE_SEL.exec(s);
38
+ if (fence) {
39
+ const type = fence[1];
40
+ const at = fence[2];
41
+ const braces = fence[3];
42
+ if (braces !== undefined) {
43
+ // `=== type {#id}` is the id key written out in full — redundant but
44
+ // legal (§2). Any OTHER key is the declared-not-implemented form.
45
+ const id = attrsIdOf(braces);
46
+ if (id !== undefined)
47
+ return { form: "id", raw: `#${id}` };
48
+ return { form: "attr", type, key: firstKey(braces) };
49
+ }
50
+ if (at !== undefined) {
51
+ const m = BARE_AT.exec(at);
52
+ return { form: "content", type, hex: m[1].toLowerCase(), nth: m[2] ? Number(m[2]) : 0 };
53
+ }
54
+ return { form: "type", type };
55
+ }
56
+ // Anything else is an id or a pasted heading line; the caller resolves it.
57
+ return { form: "id", raw: s };
58
+ }
59
+ // The first key inside `{…}`, for the §7 error message. Best-effort: it only
60
+ // has to name what the caller typed, and a class (`.warn`) is reported as
61
+ // written so the message does not claim a key that is not there.
62
+ function firstKey(braces) {
63
+ const inner = braces.replace(/^\{/, "").replace(/\}$/, "").trim();
64
+ const m = /^([.#]?[A-Za-z_][A-Za-z0-9_-]*)/.exec(inner);
65
+ return m ? m[1] : inner.split(/[\s=]/)[0] ?? "";
66
+ }
67
+ export function addressUnits(units, textOf) {
68
+ const seen = new Map();
69
+ return units.map((unit) => {
70
+ // LF-normalized so a CRLF checkout and an LF one address the same block.
71
+ const hex = sha8(textOf(unit).replace(/\r\n?/g, "\n"));
72
+ const nth = seen.get(hex) ?? 0;
73
+ seen.set(hex, nth + 1);
74
+ return { unit, hex, nth };
75
+ });
76
+ }
77
+ // §6.1 — the SHORTEST address that identifies this unit uniquely, which is what
78
+ // the listing prints. `#id` when it has one; else the bare type when the
79
+ // document holds exactly one block of it; else the content address. The three
80
+ // cases are one rule ("shortest unique"), not three rules.
81
+ export function shortestAddress(a, all) {
82
+ const u = a.unit;
83
+ if (u.id !== undefined)
84
+ return `#${u.id}`;
85
+ if (u.type === undefined)
86
+ return `@${a.hex}${a.nth ? `~${a.nth}` : ""}`;
87
+ const sameType = all.filter((x) => x.unit.type === u.type).length;
88
+ if (sameType === 1)
89
+ return `=== ${u.type}`;
90
+ return `=== ${u.type}@${a.hex}${a.nth ? `~${a.nth}` : ""}`;
91
+ }
92
+ export function matchContent(sel, all) {
93
+ const hit = all.find((a) => a.hex === sel.hex && a.nth === sel.nth);
94
+ if (!hit)
95
+ return { ok: false, why: "no-match" };
96
+ if (sel.type !== undefined && hit.unit.type !== sel.type) {
97
+ return { ok: false, why: "wrong-type", found: hit.unit.type ?? hit.unit.kind };
98
+ }
99
+ return { ok: true, unit: hit.unit };
100
+ }
101
+ // Where to send a caller whose selector found nothing: the listing IS the
102
+ // discovery command, and every address it prints pastes straight back (§6.2).
103
+ // One place, so `get`, `set` and `history get` all point at the same next step.
104
+ export function discoveryHint(where) {
105
+ return ` — run \`geml get ${where}\` to list every addressable block`;
106
+ }
107
+ // Match a type filter: every block of that type in document order. Blocks that
108
+ // carry an id are INCLUDED — the selector says nothing about ids, so filtering
109
+ // by whether one is present would be a rule nobody wrote down (§2).
110
+ export function matchType(type, all) {
111
+ return all.filter((a) => a.unit.type === type).map((a) => a.unit);
112
+ }
package/package.json CHANGED
@@ -1,66 +1,66 @@
1
- {
2
- "name": "@geml/geml",
3
- "version": "1.5.1",
4
- "mcpName": "io.github.geml-spec/geml",
5
- "publishConfig": {
6
- "access": "public"
7
- },
8
- "description": "CLI and parser for GEML, a plain-text document format where every block has an id — so an AI patches one block instead of rewriting the file; edits, reference checks and rollbacks are all per block. Ships an MCP server.",
9
- "type": "module",
10
- "bin": {
11
- "geml": "dist/geml.js"
12
- },
13
- "main": "dist/geml.js",
14
- "types": "dist/geml.d.ts",
15
- "files": [
16
- "dist",
17
- "codemap",
18
- "README.md",
19
- "LICENSE"
20
- ],
21
- "engines": {
22
- "node": ">=22"
23
- },
24
- "keywords": [
25
- "geml",
26
- "general expressive markup language",
27
- "mcp",
28
- "mcp-server",
29
- "markup",
30
- "markdown",
31
- "parser",
32
- "cli",
33
- "document",
34
- "typed-block",
35
- "ai",
36
- "agent",
37
- "llm",
38
- "addressable",
39
- "versioning",
40
- "docs",
41
- "code-graph"
42
- ],
43
- "repository": {
44
- "type": "git",
45
- "url": "git+https://github.com/geml-spec/geml.git",
46
- "directory": "geml-parser"
47
- },
48
- "homepage": "https://github.com/geml-spec/geml#readme",
49
- "bugs": {
50
- "url": "https://github.com/geml-spec/geml/issues"
51
- },
52
- "scripts": {
53
- "build": "tsc",
54
- "test": "tsc && node test/all.mjs",
55
- "parse": "node dist/geml.js",
56
- "coverage": "tsc && c8 --all --include=dist/**/*.js --include=codemap/**/*.mjs --reporter=text --reporter=text-summary node test/all.mjs",
57
- "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",
58
- "prepublishOnly": "npm run build"
59
- },
60
- "license": "MIT",
61
- "devDependencies": {
62
- "@types/node": "^22.19.21",
63
- "c8": "^10.1.3",
64
- "typescript": "^5.9.3"
65
- }
66
- }
1
+ {
2
+ "name": "@geml/geml",
3
+ "version": "1.6.0",
4
+ "mcpName": "io.github.geml-spec/geml",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "description": "CLI and parser for GEML, a plain-text document format where every block has an id — so an AI patches one block instead of rewriting the file; edits, reference checks and rollbacks are all per block. Ships an MCP server.",
9
+ "type": "module",
10
+ "bin": {
11
+ "geml": "dist/geml.js"
12
+ },
13
+ "main": "dist/geml.js",
14
+ "types": "dist/geml.d.ts",
15
+ "files": [
16
+ "dist",
17
+ "codemap",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "engines": {
22
+ "node": ">=22"
23
+ },
24
+ "keywords": [
25
+ "geml",
26
+ "general expressive markup language",
27
+ "mcp",
28
+ "mcp-server",
29
+ "markup",
30
+ "markdown",
31
+ "parser",
32
+ "cli",
33
+ "document",
34
+ "typed-block",
35
+ "ai",
36
+ "agent",
37
+ "llm",
38
+ "addressable",
39
+ "versioning",
40
+ "docs",
41
+ "code-graph"
42
+ ],
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/geml-spec/geml.git",
46
+ "directory": "geml-parser"
47
+ },
48
+ "homepage": "https://github.com/geml-spec/geml#readme",
49
+ "bugs": {
50
+ "url": "https://github.com/geml-spec/geml/issues"
51
+ },
52
+ "scripts": {
53
+ "build": "tsc",
54
+ "test": "tsc && node test/all.mjs",
55
+ "parse": "node dist/geml.js",
56
+ "coverage": "tsc && c8 --all --include=dist/**/*.js --include=codemap/**/*.mjs --reporter=text --reporter=text-summary node test/all.mjs",
57
+ "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",
58
+ "prepublishOnly": "npm run build"
59
+ },
60
+ "license": "MIT",
61
+ "devDependencies": {
62
+ "@types/node": "^22.19.21",
63
+ "c8": "^10.1.3",
64
+ "typescript": "^5.9.3"
65
+ }
66
+ }