@geml/geml 1.7.6 → 1.7.8

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
@@ -562,17 +562,27 @@ export class RenderCtx {
562
562
  return `<figure${idAttr}><p class="table-note">external data <code>${esc(src)}</code> — loaded at render time</p>${cap0}</figure>`;
563
563
  }
564
564
  }
565
+ // Overflow FOLDS; it is never dropped. `--to html` is a CONVERSION, and
566
+ // a conversion that loses data is not one — the old behaviour kept the
567
+ // first 500 lines and said "the complete data is in the document
568
+ // source", which is no help at all to someone holding only the HTML.
569
+ // The bound belongs to what is OPEN, not to what is present: the rest
570
+ // sits in a collapsed <details>, so the page is exactly as short as
571
+ // before and the block is complete. There is no ceiling above which
572
+ // lines vanish again, because a <pre> is a single text node — the cost
573
+ // of keeping them is bytes on disk, which is what not losing data
574
+ // costs.
565
575
  const tail = fmt === "jsonl" && lines.length > limit;
566
576
  const shown = tail ? lines.slice(-limit) : lines.slice(0, limit);
567
- const omitted = lines.length - shown.length;
568
- const note = omitted > 0
569
- ? `<p class="table-note">${tail
570
- ? `showing the last ${shown.length} of ${lines.length} lines — earlier lines are in the document source`
571
- : `showing the first ${shown.length} of ${lines.length} lines — the complete data is in the document source`}</p>`
572
- : "";
577
+ const rest = tail ? lines.slice(0, lines.length - shown.length) : lines.slice(shown.length);
573
578
  const cap = caption ? `<figcaption>${esc(caption)}</figcaption>` : "";
574
- const pre = `<pre class="data-src" data-format="${escAttr(fmt)}">${esc(shown.join("\n"))}</pre>`;
575
- return `<figure${idAttr}>${tail ? note + pre : pre + note}${cap}</figure>`;
579
+ const pre = (ls) => `<pre class="data-src" data-format="${escAttr(fmt)}">${esc(ls.join("\n"))}</pre>`;
580
+ if (rest.length === 0)
581
+ return `<figure${idAttr}>${pre(shown)}${cap}</figure>`;
582
+ // jsonl reads as an append-log, so its open end is the NEWEST lines and
583
+ // the fold holds the earlier ones, above; json reads from the top.
584
+ const more = `<details class="data-more"><summary>${rest.length} ${tail ? "earlier" : "more"} line${rest.length === 1 ? "" : "s"} of ${lines.length}</summary>${pre(rest)}</details>`;
585
+ return `<figure${idAttr}>${tail ? more + pre(shown) : pre(shown) + more}${cap}</figure>`;
576
586
  }
577
587
  case "table":
578
588
  return b.table ? this.table(b.table, b.id, caption) : `<p class="render-error">table failed to parse</p>`;
@@ -636,17 +646,20 @@ export class RenderCtx {
636
646
  table(t, id, caption) {
637
647
  const idAttr = id ? ` id="${escAttr(id)}"` : "";
638
648
  const alignStyle = (a) => (a ? ` style="text-align:${a}"` : "");
639
- // Parsing + laying out tens of thousands of <table> rows freezes the
640
- // page for seconds, so the HTML view renders a bounded preview (the
641
- // model keeps every row: charts, computed summaries and the code-graph
642
- // never read the HTML). Codemap edge tables additionally fold shut
643
- // they are machine data; elsewhere the table is content and stays open.
644
- // The codemap index's #modules table IS the page's content — the module
645
- // inventory people scan and filter so it renders in full. Edge tables
646
- // (#calls / #called-by) stay previewed+folded: machine data at scale.
647
- const maxRows = this.isCodemapDoc && id === "modules" ? Infinity : (this.opts.tableRows ?? 500);
649
+ // Laying out tens of thousands of <table> rows OPEN freezes the page for
650
+ // seconds, so a long table renders folded shut the codemap's edge tables
651
+ // (#calls / #called-by) are the ones that get there. Its #modules table is
652
+ // the page's content, the inventory people came to scan and filter, so that
653
+ // one stays open at any size.
654
+ //
655
+ // EVERY row is rendered. `tableRows` bounds what is OPEN, never what is
656
+ // present: past it the whole table goes inside a collapsed <details>, which
657
+ // costs the reader one click and costs the document nothing. It used to
658
+ // slice the rows away and say the complete table was in the source, which
659
+ // made `--to html` a lossy conversion of a table the model holds in full.
660
+ const foldAbove = this.isCodemapDoc && id === "modules" ? Infinity : (this.opts.tableRows ?? 500);
648
661
  const allRows = t.rows;
649
- const rows = allRows.length > maxRows ? allRows.slice(0, maxRows) : allRows;
662
+ const rows = allRows;
650
663
  const thead = t.header
651
664
  ? `<thead><tr>${t.columns.map((col, c) => `<th${alignStyle(t.align[c])}>${esc(col)}</th>`).join("")}</tr></thead>`
652
665
  : "";
@@ -666,15 +679,10 @@ export class RenderCtx {
666
679
  : "";
667
680
  const cap = caption ? `<figcaption>${esc(caption)}</figcaption>` : "";
668
681
  const tools = `<div class="table-tools"><input class="table-filter" type="search" placeholder="Filter rows…" aria-label="Filter table rows"></div>`;
669
- if (allRows.length > maxRows) {
670
- const note = `<p class="table-note">showing the first ${maxRows} of ${allRows.length} rows — the complete table is in the document source</p>`;
671
- if (this.isCodemapDoc) {
672
- const summary = `${esc(id ? "#" + id : "table")} · ${allRows.length} rows (preview: first ${maxRows})`;
673
- return `<figure class="table-figure"${idAttr}><details><summary>${summary}</summary>${tools}` +
674
- `<div class="table-scroll"><table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table></div>${note}</details>${cap}</figure>`;
675
- }
676
- return `<figure class="table-figure"${idAttr}>${tools}` +
677
- `<div class="table-scroll"><table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table></div>${note}${cap}</figure>`;
682
+ if (allRows.length > foldAbove) {
683
+ const summary = `${esc(id ? "#" + id : "table")} · ${allRows.length} rows`;
684
+ return `<figure class="table-figure"${idAttr}><details><summary>${summary}</summary>${tools}` +
685
+ `<div class="table-scroll"><table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table></div></details>${cap}</figure>`;
678
686
  }
679
687
  return `<figure class="table-figure"${idAttr}>${tools}` +
680
688
  `<div class="table-scroll"><table class="geml-table">${thead}<tbody>\n${bodyRows}\n</tbody>${tfoot}</table></div>${cap}</figure>`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geml/geml",
3
- "version": "1.7.6",
3
+ "version": "1.7.8",
4
4
  "mcpName": "io.github.geml-spec/geml",
5
5
  "publishConfig": {
6
6
  "access": "public"
package/skill/SKILL.md CHANGED
@@ -53,6 +53,23 @@ file stay what it is.
53
53
  If `geml --version` does not answer, none of this is available. Read and edit
54
54
  the ordinary way, and do not tell anyone to install anything.
55
55
 
56
+ ## A project moving TO GEML
57
+
58
+ "This project's documents are GEML now" means new documents are authored as
59
+ `.geml` — notes, plans, findings, reports — in one directory (`docs/geml/`
60
+ unless the project says otherwise), one file per topic, with an `index.geml`
61
+ saying what is there and why. It does not mean converting what is already
62
+ written, and nobody has to say "leave the existing files alone" for that to
63
+ hold.
64
+
65
+ **Add, never replace.** Writing a `.geml` version of a document is not licence
66
+ to delete the Markdown it was drawn from — however completely the content was
67
+ carried across, and whatever a "one home per topic" convention seems to imply.
68
+ Deleting a file is a request a person makes, never an inference from a
69
+ convention. When both exist, say in each what it is for and name one of them as
70
+ the place a given fact is maintained: two documents describing a project is
71
+ fine, two documents maintaining the same fact is what drifts.
72
+
56
73
  ## A GEML document — get the syntax right
57
74
 
58
75
  GEML expresses **every** kind of structured content — code, tables, diagrams,
@@ -75,9 +92,12 @@ GEML file is correct only when `geml check` reports **no error diagnostics**
75
92
  `---` breaks, no YAML frontmatter — metadata is a `=== meta` block, and the
76
93
  document TITLE lives there (`title = "…"`), not in an H1. A heading may
77
94
  carry a stable explicit id: `## Title {#sec}`.
78
- 4. **Every `#id` is unique per document**, and **every reference must
79
- resolve** `[t](#id)`, `[[#id]]`, `[^id]`, `src=`, `data=`,
80
- `other.geml#id`. An unresolved reference is a build **error**.
95
+ 4. **Give every section a stable `{#id}`** `## Findings {#findings}` — then
96
+ keep ids unique per document, with **every reference resolving**:
97
+ `[t](#id)`, `[[#id]]`, `[^id]`, `src=`, `data=`, `other.geml#id`. An
98
+ unresolved reference is a build **error**. Naming them is the part that pays
99
+ later: a document with no ids costs what Markdown costs, because there is
100
+ nothing for `geml get` to read or `geml set` to replace short of the file.
81
101
  5. **No raw HTML.** Notes → `=== note`, comments → `%%` lines, hidden content
82
102
  → `{hidden}`, addressable prose → `=== text`, verified data → `=== data`
83
103
  (json/jsonl; `code` shows text, `data` IS data).
@@ -108,38 +128,21 @@ geml find "text" file|dir # search block CONTENT -> file<TAB>address
108
128
  # a directory walks *.geml only
109
129
  geml get file.geml '#id' # read ONE block (a heading id = its whole section)
110
130
  geml set file.geml '#id' --in f # replace ONE block (re-parsed; never writes a broken doc)
111
- geml replace file.geml OLD NEW # EXPERIMENTAL literal swap; --within '#id' to narrow
112
131
  geml history save file.geml -m "…" # snapshot to .gemlhistory after each meaningful edit
113
132
  geml revert file.geml '#id' # roll ONE block back (--rev -2 | changed | <rev-id>)
114
133
  ```
115
134
 
116
135
  Address a block, never a line range: `#id` · `'## Heading'` (its whole section)
117
- · `'=== type'` · `@<hex>` (no id) · `L27` or `L27-58` (the smallest block holding
118
- those lines — how a line number from an editor, a linter or a diff hunk becomes
119
- an address). `list` and `find` print addresses that paste straight into the
120
- others, so neither `grep` nor a line count is needed to locate anything.
121
-
122
- Any section can be cut three ways, on `get` and `set` alike: `--head` (the
123
- heading line), `--intro` (what it says before its first subheading empty when
124
- one follows immediately, the whole body when none does), `--body` (everything
125
- under it, so it always contains the intro). `--intro` is how you edit a
126
- section's opening without pulling its subsections into context, and setting an
127
- empty one writes an opening where the section had none.
128
-
129
- When the exact old text is already known and nothing needs reading — a version
130
- string in six places, a renamed term — `geml replace` is the cheap path, and the
131
- one to prefer over dropping to `sed`: same two short strings, but the result is
132
- re-parsed before it lands, the blocks it touched are named back to you, and it
133
- is in `.gemlhistory` to revert. It swaps a LITERAL, never a pattern, and refuses
134
- a swap that would rename an id (use `geml rename`, which fixes the references
135
- too). **It is EXPERIMENTAL and may be withdrawn** — reach for it, but do not
136
- build anything on it that cannot change.
137
-
138
- A write is refused when it would break the document, never merely because it
139
- removes something: a replacement that drops blocks is carried out and NAMED on
140
- stderr — unnamed blocks included — with `geml revert` as the way back. Read,
141
- edit, write back, and nothing is dropped, because `get` handed those blocks to
142
- you. Send content that omits them only when removing them is the point.
136
+ · `L27-58` (the smallest block holding those lines how a line number from an
137
+ editor, a linter or a diff hunk becomes an address). `list` and `find` print
138
+ addresses that paste straight into the others, so neither `grep` nor a line
139
+ count is needed to locate anything.
140
+
141
+ The rest is one `geml get` away in the reference below, and stays there because
142
+ it is needed rarely and this page is read every time: the remaining address
143
+ forms in `#cli`, and in `#editing` the three ways to cut a section
144
+ (`--head`/`--intro`/`--body`), the experimental `replace`, and what a write that
145
+ drops blocks does.
143
146
 
144
147
  ## Full reference — pull ONE section, not the whole file
145
148
 
@@ -293,6 +293,28 @@ geml revert file.geml '#intro' --rev changed # …the block's last ACTUAL chang
293
293
  block) and `history`/`revert` (version and rewind it) let an agent revise a
294
294
  document incrementally and undo any single section.
295
295
 
296
+ **A section can be cut three ways**, on `get` and `set` alike: `--head` (the
297
+ heading line), `--intro` (what it says before its first subheading — empty when
298
+ one follows immediately, the whole body when none does), `--body` (everything
299
+ under it, so it always contains the intro). `--intro` is how you edit a
300
+ section's opening without pulling its subsections into context, and setting an
301
+ empty one writes an opening where the section had none.
302
+
303
+ **When the exact old text is already known** and nothing needs reading — a
304
+ version string in six places, a renamed term — `geml replace` is the cheap path,
305
+ and the one to prefer over dropping to `sed`: the same two short strings, but
306
+ the result is re-parsed before it lands, the blocks it touched are named back to
307
+ you, and it is in `.gemlhistory` to revert. It swaps a LITERAL, never a pattern,
308
+ and refuses a swap that would rename an id (use `geml rename`, which fixes the
309
+ references too). **It is EXPERIMENTAL and may be withdrawn** — reach for it, but
310
+ do not build anything on it that cannot change.
311
+
312
+ **A write is refused when it would BREAK the document**, never merely because it
313
+ removes something: a replacement that drops blocks is carried out and NAMED on
314
+ stderr — unnamed blocks included — with `geml revert` as the way back. Read,
315
+ edit, write back, and nothing is dropped, because `get` handed those blocks to
316
+ you. Send content that omits them only when removing them is the point.
317
+
296
318
  **Where sidecars do NOT belong:** a doc that git already versions — config
297
319
  docs especially — usually needs no `.gemlhistory`; do not create one there
298
320
  unless the user asks for finer-than-commit history.