@north-light/crouter 0.3.192 → 0.3.194

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.
Files changed (190) hide show
  1. package/dist/api/client.d.ts +31 -10
  2. package/dist/api/client.js +83 -24
  3. package/dist/api/dto/bash-jobs.d.ts +24 -0
  4. package/dist/api/dto/bash-jobs.js +9 -0
  5. package/dist/api/dto/human.d.ts +3 -4
  6. package/dist/api/dto/inbox.d.ts +106 -65
  7. package/dist/api/dto/inbox.js +2 -8
  8. package/dist/api/index.d.ts +1 -0
  9. package/dist/api/index.js +1 -0
  10. package/dist/api/routes.d.ts +7 -0
  11. package/dist/api/routes.js +7 -0
  12. package/dist/builtin-memory/02-lifecycle/01-resident.md +1 -1
  13. package/dist/builtin-memory/internal/examples/imessage-assistant.md +1 -1
  14. package/dist/builtin-memory/internal/nodes-and-canvas.md +2 -2
  15. package/dist/builtin-memory/internal/storage-tiers.md +1 -1
  16. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/serial/provider-rotation.test.ts +101 -3
  17. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.d.ts +6 -2
  18. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +68 -14
  19. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +87 -13
  20. package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +2 -2
  21. package/dist/clients/attach/__tests__/group-activity.test.js +2 -2
  22. package/dist/clients/attach/chrome/canvas-panels.d.ts +1 -1
  23. package/dist/clients/attach/chrome/canvas-panels.js +1 -1
  24. package/dist/clients/attach/input/overlay-owner.js +1 -1
  25. package/dist/clients/attach/overlays/auth.js +34 -8
  26. package/dist/clients/attach/overlays/pickers.d.ts +5 -8
  27. package/dist/clients/attach/overlays/pickers.js +37 -43
  28. package/dist/clients/attach/viewer.js +643 -620
  29. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +38 -128
  30. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +72 -148
  31. package/dist/clients/inbox/controller.d.ts +2 -3
  32. package/dist/clients/inbox/controller.js +41 -42
  33. package/dist/clients/inbox/page-adapter.d.ts +33 -0
  34. package/dist/clients/inbox/page-adapter.js +54 -0
  35. package/dist/clients/inbox/resolve.d.ts +2 -2
  36. package/dist/clients/inbox/resolve.js +1 -1
  37. package/dist/clients/inbox/review-visibility.d.ts +1 -1
  38. package/dist/clients/inbox/review-visibility.js +1 -1
  39. package/dist/clients/inbox/tui/ansi.d.ts +1 -1
  40. package/dist/clients/inbox/tui/ansi.js +2 -2
  41. package/dist/clients/inbox/tui/input.d.ts +1 -3
  42. package/dist/clients/inbox/tui/input.js +242 -553
  43. package/dist/clients/inbox/tui/panel.d.ts +3 -1
  44. package/dist/clients/inbox/tui/panel.js +59 -187
  45. package/dist/clients/inbox/tui/render.d.ts +0 -25
  46. package/dist/clients/inbox/tui/render.js +165 -416
  47. package/dist/clients/inbox/tui/slots.d.ts +32 -0
  48. package/dist/clients/inbox/tui/slots.js +156 -0
  49. package/dist/clients/inbox/tui/types.d.ts +25 -13
  50. package/dist/clients/inbox/tui.js +5 -3
  51. package/dist/commands/attention.js +2 -2
  52. package/dist/commands/human/doc.js +66 -20
  53. package/dist/commands/human/prompts.d.ts +3 -3
  54. package/dist/commands/human/prompts.js +158 -112
  55. package/dist/commands/human/queue.d.ts +0 -1
  56. package/dist/commands/human/queue.js +34 -94
  57. package/dist/commands/human/shared.d.ts +10 -4
  58. package/dist/commands/human/shared.js +62 -13
  59. package/dist/commands/human.js +8 -4
  60. package/dist/commands/memory/read.js +13 -5
  61. package/dist/commands/node/bash.js +18 -30
  62. package/dist/commands/node/lifecycle.js +2 -2
  63. package/dist/commands/surface/node/focus.js +1 -1
  64. package/dist/core/__tests__/human-deliver.test.js +40 -46
  65. package/dist/core/__tests__/seam/broker-attach-stream.test.js +7 -1
  66. package/dist/core/__tests__/seam/dormancy-release.test.js +64 -10
  67. package/dist/core/__tests__/serial/human-deliver-e2e.test.js +40 -31
  68. package/dist/core/__tests__/serial/spawn-root.test.js +3 -1
  69. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +3 -1
  70. package/dist/core/auth-interaction.d.ts +2 -2
  71. package/dist/core/bash-jobs.d.ts +43 -0
  72. package/dist/core/bash-jobs.js +96 -2
  73. package/dist/core/canvas/__tests__/attention.test.js +16 -10
  74. package/dist/core/canvas/__tests__/render-remote.test.js +9 -3
  75. package/dist/core/canvas/attention.js +1 -1
  76. package/dist/core/canvas/browse/app.js +1 -1
  77. package/dist/core/canvas/nav-model.d.ts +1 -1
  78. package/dist/core/canvas/nav-model.js +1 -1
  79. package/dist/core/canvas/remote-canvas-source.js +1 -1
  80. package/dist/core/canvas/types.d.ts +2 -1
  81. package/dist/core/config.js +4 -1
  82. package/dist/core/human/__tests__/page-render.test.d.ts +1 -0
  83. package/dist/core/human/__tests__/page-render.test.js +49 -0
  84. package/dist/core/human/__tests__/page-tickets.test.d.ts +1 -0
  85. package/dist/core/human/__tests__/page-tickets.test.js +112 -0
  86. package/dist/core/human/__tests__/page.test.d.ts +1 -0
  87. package/dist/core/human/__tests__/page.test.js +139 -0
  88. package/dist/core/human/__tests__/serial/inbox-core.test.js +42 -32
  89. package/dist/core/human/claim.js +2 -2
  90. package/dist/core/human/convention.d.ts +4 -4
  91. package/dist/core/human/convention.js +6 -13
  92. package/dist/core/human/markdown-html.d.ts +8 -0
  93. package/dist/core/human/markdown-html.js +581 -0
  94. package/dist/core/human/page-catalog.d.ts +6 -0
  95. package/dist/core/human/page-catalog.js +43 -0
  96. package/dist/core/human/page-render.d.ts +3 -0
  97. package/dist/core/human/page-render.js +131 -0
  98. package/dist/core/human/page-schema.d.ts +312 -0
  99. package/dist/core/human/page-schema.js +291 -0
  100. package/dist/core/human/page-synth.d.ts +9 -0
  101. package/dist/core/human/page-synth.js +17 -0
  102. package/dist/core/human/page.d.ts +22 -0
  103. package/dist/core/human/page.js +427 -0
  104. package/dist/core/human/review-schema.d.ts +19 -0
  105. package/dist/core/human/review-schema.js +34 -0
  106. package/dist/core/human/scan.d.ts +12 -3
  107. package/dist/core/human/scan.js +69 -26
  108. package/dist/core/human/summary.d.ts +5 -6
  109. package/dist/core/human/summary.js +54 -37
  110. package/dist/core/human/tickets.d.ts +61 -11
  111. package/dist/core/human/tickets.js +100 -57
  112. package/dist/core/human/types.d.ts +21 -66
  113. package/dist/core/human/types.js +1 -5
  114. package/dist/core/inspector/model.d.ts +5 -0
  115. package/dist/core/inspector/model.js +5 -0
  116. package/dist/core/inspector/text.js +2 -2
  117. package/dist/core/inspector/tui.js +10 -3
  118. package/dist/core/keybindings/catalog.d.ts +1 -1
  119. package/dist/core/keybindings/catalog.js +4 -2
  120. package/dist/core/preview-registry.js +0 -1
  121. package/dist/core/review/ticket-filter.d.ts +1 -1
  122. package/dist/core/review/ticket-filter.js +2 -8
  123. package/dist/core/runtime/broker/read-ops.js +3 -0
  124. package/dist/core/runtime/broker.js +19 -10
  125. package/dist/core/runtime/package-health.js +1 -1
  126. package/dist/core/runtime/persona.js +2 -1
  127. package/dist/core/runtime/tmux-bindings.js +8 -1
  128. package/dist/core/termrender/termrender.d.ts +1 -1
  129. package/dist/core/termrender/termrender.js +2 -2
  130. package/dist/core/termrender/version.d.ts +1 -1
  131. package/dist/core/termrender/version.js +1 -1
  132. package/dist/core/tui/page-host.d.ts +1 -1
  133. package/dist/core/tui/page-host.js +2 -2
  134. package/dist/core/user-settings.d.ts +19 -0
  135. package/dist/core/user-settings.js +22 -0
  136. package/dist/daemon/api/handlers/bash-jobs.d.ts +2 -0
  137. package/dist/daemon/api/handlers/bash-jobs.js +90 -0
  138. package/dist/daemon/api/handlers/human.js +16 -14
  139. package/dist/daemon/api/handlers/inbox.js +311 -227
  140. package/dist/daemon/api/router.d.ts +5 -1
  141. package/dist/daemon/api/router.js +5 -0
  142. package/dist/daemon/api/server.js +2 -0
  143. package/dist/daemon/cron-run.js +23 -10
  144. package/dist/daemon/human/finish.d.ts +8 -3
  145. package/dist/daemon/human/finish.js +35 -15
  146. package/dist/daemon/reconcilers/broker-supervision.js +7 -1
  147. package/dist/daemon/reconcilers/dormant-inbox.js +8 -0
  148. package/dist/daemon/reconcilers/live-obligation.d.ts +2 -0
  149. package/dist/daemon/reconcilers/live-obligation.js +11 -0
  150. package/dist/pages/bundle.css +1 -0
  151. package/dist/pages/bundle.js +1347 -0
  152. package/dist/pages/comments.d.ts +34 -0
  153. package/dist/pages/comments.js +72 -0
  154. package/dist/pages/elements/cards.d.ts +17 -0
  155. package/dist/pages/elements/cards.js +785 -0
  156. package/dist/pages/elements/chart.d.ts +1 -0
  157. package/dist/pages/elements/chart.js +648 -0
  158. package/dist/pages/elements/options.d.ts +21 -0
  159. package/dist/pages/elements/options.js +604 -0
  160. package/dist/pages/elements/pages.d.ts +24 -0
  161. package/dist/pages/elements/pages.js +355 -0
  162. package/dist/pages/elements/slot.d.ts +21 -0
  163. package/dist/pages/elements/slot.js +105 -0
  164. package/dist/pages/elements/table.d.ts +19 -0
  165. package/dist/pages/elements/table.js +906 -0
  166. package/dist/pages/elements/text.d.ts +25 -0
  167. package/dist/pages/elements/text.js +951 -0
  168. package/dist/pages/entry.d.ts +8 -0
  169. package/dist/pages/entry.js +8 -0
  170. package/dist/pages/host.d.ts +132 -0
  171. package/dist/pages/host.js +149 -0
  172. package/dist/pages/register.d.ts +2 -0
  173. package/dist/pages/register.js +3 -0
  174. package/dist/pages/slot-config.d.ts +36 -0
  175. package/dist/pages/slot-config.js +50 -0
  176. package/dist/pages/types.d.ts +98 -0
  177. package/dist/pages/types.js +23 -0
  178. package/dist/pi-extensions/canvas-bash-valve.d.ts +4 -2
  179. package/dist/pi-extensions/canvas-bash-valve.js +25 -7
  180. package/dist/types.d.ts +4 -0
  181. package/dist/types.js +2 -0
  182. package/package.json +8 -8
  183. package/runtime.lock.json +156 -101
  184. package/scripts/install-runtime.mjs +172 -5
  185. package/dist/clients/inbox/deck-adapter.d.ts +0 -29
  186. package/dist/clients/inbox/deck-adapter.js +0 -63
  187. package/dist/core/human/deck-factories.d.ts +0 -8
  188. package/dist/core/human/deck-factories.js +0 -15
  189. package/dist/core/human/deck-schema.d.ts +0 -70
  190. package/dist/core/human/deck-schema.js +0 -92
@@ -0,0 +1,581 @@
1
+ // Markdown → HTML for md-dialect page prose. Hand-rolled and dependency-free:
2
+ // crtr ships no markdown-to-HTML dependency, and the only markdown machinery it
3
+ // has (`core/termrender`) renders ANSI terminal lines through a managed Python
4
+ // venv — the wrong thing to invoke inside a daemon HTTP handler.
5
+ //
6
+ // The supported subset is a deliberate CommonMark slice, pinned here because
7
+ // page authors and page hosts both read it as the contract:
8
+ //
9
+ // Blocks — ATX headings (`#`…`######`), setext headings (`===`/`---`
10
+ // underlines), fenced code (``` and ~~~, info string → `class="language-x"`),
11
+ // blockquotes, thematic breaks, bullet lists (`-`, `*`, `+`), ordered lists
12
+ // (`1.`, `1)`), GFM pipe tables (with `:---:` alignment), raw HTML blocks,
13
+ // and paragraphs. Lists nest by indentation and render tight or loose.
14
+ // Inline — backslash escapes, code spans, `**strong**`, `*em*`, `***both***`
15
+ // (and the `_` forms, intraword-safe), links, images, autolinks
16
+ // (`<https://…>`, `<a@b.c>`), raw inline HTML, character entities, and
17
+ // two-space hard breaks.
18
+ //
19
+ // NOT supported (authors get the literal text): reference links and
20
+ // definitions, footnotes, indented (four-space) code blocks, task-list
21
+ // checkboxes, strikethrough, and HTML comments as block separators.
22
+ //
23
+ // Raw HTML passes through verbatim. A page document is authored by the agent
24
+ // that owns the ticket, and the html dialect is whole-document passthrough by
25
+ // design — md prose is not a less-trusted channel than the document carrying it.
26
+ const ENTITY = /^&(?:[a-zA-Z][a-zA-Z0-9]{1,31}|#\d{1,7}|#[xX][0-9a-fA-F]{1,6});/;
27
+ const ASCII_PUNCTUATION = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/;
28
+ const AUTOLINK_URI = /^<([a-zA-Z][a-zA-Z0-9+.-]{1,31}:[^<>\u0000-\u0020]*)>/;
29
+ const AUTOLINK_EMAIL = /^<([^\s@<>]+@[^\s@<>]+\.[^\s@<>]+)>/;
30
+ const RAW_INLINE_TAG = /^<\/?[A-Za-z][A-Za-z0-9-]*(?:\s[^<>]*?)?\/?>/;
31
+ const HTML_COMMENT = /^<!--[\s\S]*?-->/;
32
+ const LIST_ITEM = /^( {0,3})([-*+]|\d{1,9}[.)])( +|$)(.*)$/;
33
+ const THEMATIC_BREAK = /^ {0,3}([-*_])(?: *\1){2,} *$/;
34
+ /** Escape text for an HTML text node. */
35
+ export function escapeHtmlText(raw) {
36
+ return raw.replace(/[&<>]/g, (character) => (character === '&' ? '&amp;' : character === '<' ? '&lt;' : '&gt;'));
37
+ }
38
+ /** Escape a value for a double-quoted HTML attribute. */
39
+ export function escapeHtmlAttribute(raw) {
40
+ return escapeHtmlText(raw).replace(/"/g, '&quot;');
41
+ }
42
+ /** Render one markdown document fragment as HTML. */
43
+ export function renderMarkdownToHtml(markdown) {
44
+ return renderBlocks(markdown.replace(/\r\n?/g, '\n').split('\n'), false);
45
+ }
46
+ // ---------------------------------------------------------------------------
47
+ // Blocks
48
+ // ---------------------------------------------------------------------------
49
+ /** `unwrapParagraphs` renders a tight list item: its paragraphs emit inline
50
+ * content with no `<p>` wrapper, exactly as CommonMark specifies. */
51
+ function renderBlocks(lines, unwrapParagraphs) {
52
+ const blocks = [];
53
+ let index = 0;
54
+ while (index < lines.length) {
55
+ const line = lines[index];
56
+ if (line.trim() === '') {
57
+ index++;
58
+ continue;
59
+ }
60
+ const fence = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
61
+ if (fence !== null) {
62
+ index = pushFencedCode(blocks, lines, index, fence[1], fence[2]);
63
+ continue;
64
+ }
65
+ const heading = /^ {0,3}(#{1,6})(?:[ \t]+(.*?))?[ \t]*$/.exec(line);
66
+ if (heading !== null) {
67
+ const level = heading[1].length;
68
+ blocks.push(`<h${level}>${renderInline((heading[2] ?? '').replace(/[ \t]+#+[ \t]*$/, ''))}</h${level}>`);
69
+ index++;
70
+ continue;
71
+ }
72
+ if (THEMATIC_BREAK.test(line)) {
73
+ blocks.push('<hr />');
74
+ index++;
75
+ continue;
76
+ }
77
+ if (/^ {0,3}>/.test(line)) {
78
+ index = pushBlockquote(blocks, lines, index);
79
+ continue;
80
+ }
81
+ if (LIST_ITEM.test(line) && !THEMATIC_BREAK.test(line)) {
82
+ index = pushList(blocks, lines, index);
83
+ continue;
84
+ }
85
+ if (isHtmlBlockStart(line)) {
86
+ index = pushHtmlBlock(blocks, lines, index);
87
+ continue;
88
+ }
89
+ const tableEnd = pushTable(blocks, lines, index);
90
+ if (tableEnd !== null) {
91
+ index = tableEnd;
92
+ continue;
93
+ }
94
+ index = pushParagraph(blocks, lines, index, unwrapParagraphs);
95
+ }
96
+ return blocks.join('\n');
97
+ }
98
+ function closesFence(text, opener) {
99
+ const trimmed = text.trim();
100
+ return trimmed.length >= opener.length && trimmed.split('').every((character) => character === opener[0]);
101
+ }
102
+ function pushFencedCode(blocks, lines, start, opener, info) {
103
+ let end = start + 1;
104
+ while (end < lines.length && !closesFence(lines[end], opener))
105
+ end++;
106
+ const body = lines.slice(start + 1, end);
107
+ const language = info.trim().split(/\s+/)[0] ?? '';
108
+ const attribute = language === '' ? '' : ` class="language-${escapeHtmlAttribute(language)}"`;
109
+ blocks.push(`<pre><code${attribute}>${escapeHtmlText(body.join('\n'))}${body.length === 0 ? '' : '\n'}</code></pre>`);
110
+ return end < lines.length ? end + 1 : end;
111
+ }
112
+ function pushBlockquote(blocks, lines, start) {
113
+ const quoted = [];
114
+ let index = start;
115
+ while (index < lines.length && /^ {0,3}>/.test(lines[index])) {
116
+ quoted.push(lines[index].replace(/^ {0,3}> ?/, ''));
117
+ index++;
118
+ // A lazy continuation line belongs to the quoted paragraph.
119
+ while (index < lines.length && lines[index].trim() !== '' && !/^ {0,3}>/.test(lines[index]) && !startsNewBlock(lines[index])) {
120
+ quoted.push(lines[index]);
121
+ index++;
122
+ }
123
+ }
124
+ blocks.push(`<blockquote>\n${renderBlocks(quoted, false)}\n</blockquote>`);
125
+ return index;
126
+ }
127
+ function startsNewBlock(line) {
128
+ return /^ {0,3}(`{3,}|~{3,})/.test(line)
129
+ || /^ {0,3}#{1,6}(\s|$)/.test(line)
130
+ || THEMATIC_BREAK.test(line)
131
+ || /^ {0,3}>/.test(line)
132
+ || LIST_ITEM.test(line)
133
+ || isHtmlBlockStart(line);
134
+ }
135
+ function isHtmlBlockStart(line) {
136
+ return /^ {0,3}<(?:[A-Za-z][A-Za-z0-9-]*|\/[A-Za-z][A-Za-z0-9-]*|!--)/.test(line);
137
+ }
138
+ function pushHtmlBlock(blocks, lines, start) {
139
+ let end = start;
140
+ while (end < lines.length && lines[end].trim() !== '')
141
+ end++;
142
+ blocks.push(lines.slice(start, end).join('\n'));
143
+ return end;
144
+ }
145
+ // ---------------------------------------------------------------------------
146
+ // Lists
147
+ // ---------------------------------------------------------------------------
148
+ function markerType(marker) {
149
+ return /^\d/.test(marker) ? 'ordered' : 'bullet';
150
+ }
151
+ function pushList(blocks, lines, start) {
152
+ const first = LIST_ITEM.exec(lines[start]);
153
+ const type = markerType(first[2]);
154
+ const items = [];
155
+ let current = null;
156
+ let contentIndent = 0;
157
+ let loose = false;
158
+ let pendingBlank = false;
159
+ let index = start;
160
+ while (index < lines.length) {
161
+ const line = lines[index];
162
+ if (line.trim() === '') {
163
+ pendingBlank = true;
164
+ index++;
165
+ continue;
166
+ }
167
+ const item = LIST_ITEM.exec(line);
168
+ const itemIndent = item === null ? 0 : item[1].length;
169
+ if (item !== null && !THEMATIC_BREAK.test(line) && markerType(item[2]) === type && (current === null || itemIndent < contentIndent)) {
170
+ if (current !== null && pendingBlank)
171
+ loose = true;
172
+ contentIndent = itemIndent + item[2].length + (item[3] === '' ? 1 : item[3].length);
173
+ current = [item[4]];
174
+ items.push(current);
175
+ pendingBlank = false;
176
+ index++;
177
+ continue;
178
+ }
179
+ if (current === null)
180
+ break;
181
+ const indented = /^ +/.exec(line)?.[0]?.length ?? 0;
182
+ if (indented >= contentIndent) {
183
+ if (pendingBlank) {
184
+ current.push('');
185
+ loose = true;
186
+ }
187
+ current.push(line.slice(contentIndent));
188
+ pendingBlank = false;
189
+ index++;
190
+ continue;
191
+ }
192
+ // A lazy continuation of the item's paragraph — but only before a blank line.
193
+ if (!pendingBlank && !startsNewBlock(line)) {
194
+ current.push(line);
195
+ index++;
196
+ continue;
197
+ }
198
+ break;
199
+ }
200
+ const rendered = items.map((item) => `<li>${wrapListItem(renderBlocks(item, !loose))}</li>`);
201
+ const tag = type === 'ordered' ? 'ol' : 'ul';
202
+ const startNumber = type === 'ordered' ? Number.parseInt(first[2], 10) : 1;
203
+ const startAttribute = type === 'ordered' && startNumber !== 1 ? ` start="${startNumber}"` : '';
204
+ blocks.push(`<${tag}${startAttribute}>\n${rendered.join('\n')}\n</${tag}>`);
205
+ return index;
206
+ }
207
+ function wrapListItem(html) {
208
+ return html.includes('\n') ? `\n${html}\n` : html;
209
+ }
210
+ // ---------------------------------------------------------------------------
211
+ // GFM tables
212
+ // ---------------------------------------------------------------------------
213
+ function splitRow(line) {
214
+ const trimmed = line.trim().replace(/^\|/, '').replace(/\|$/, '');
215
+ const cells = [];
216
+ let cell = '';
217
+ for (let index = 0; index < trimmed.length; index++) {
218
+ const character = trimmed[index];
219
+ if (character === '\\' && trimmed[index + 1] === '|') {
220
+ cell += '|';
221
+ index++;
222
+ continue;
223
+ }
224
+ if (character === '|') {
225
+ cells.push(cell.trim());
226
+ cell = '';
227
+ continue;
228
+ }
229
+ cell += character;
230
+ }
231
+ cells.push(cell.trim());
232
+ return cells;
233
+ }
234
+ /** Render a GFM pipe table at `start`, or return null when there is none. */
235
+ function pushTable(blocks, lines, start) {
236
+ const header = lines[start];
237
+ const delimiter = lines[start + 1];
238
+ if (!header.includes('|') || delimiter === undefined)
239
+ return null;
240
+ if (!/^ {0,3}\|?(?: *:?-+:? *\|)+ *:?-*:? *\|? *$/.test(delimiter) && !/^ {0,3}:?-+:? *$/.test(delimiter))
241
+ return null;
242
+ const alignments = splitRow(delimiter).map((cell) => {
243
+ if (!/^:?-+:?$/.test(cell))
244
+ return null;
245
+ if (cell.startsWith(':') && cell.endsWith(':'))
246
+ return 'center';
247
+ if (cell.endsWith(':'))
248
+ return 'right';
249
+ if (cell.startsWith(':'))
250
+ return 'left';
251
+ return '';
252
+ });
253
+ if (alignments.some((alignment) => alignment === null))
254
+ return null;
255
+ const headerCells = splitRow(header);
256
+ if (headerCells.length !== alignments.length)
257
+ return null;
258
+ const align = (column) => {
259
+ const alignment = alignments[column];
260
+ return alignment === undefined || alignment === '' ? '' : ` style="text-align:${alignment}"`;
261
+ };
262
+ const rows = [];
263
+ let index = start + 2;
264
+ while (index < lines.length && lines[index].trim() !== '' && lines[index].includes('|') && !startsNewBlock(lines[index])) {
265
+ const cells = splitRow(lines[index]);
266
+ const padded = alignments.map((_, column) => cells[column] ?? '');
267
+ rows.push(`<tr>${padded.map((cell, column) => `<td${align(column)}>${renderInline(cell)}</td>`).join('')}</tr>`);
268
+ index++;
269
+ }
270
+ const head = `<thead>\n<tr>${headerCells.map((cell, column) => `<th${align(column)}>${renderInline(cell)}</th>`).join('')}</tr>\n</thead>`;
271
+ const body = rows.length === 0 ? '' : `\n<tbody>\n${rows.join('\n')}\n</tbody>`;
272
+ blocks.push(`<table>\n${head}${body}\n</table>`);
273
+ return index;
274
+ }
275
+ // ---------------------------------------------------------------------------
276
+ // Paragraphs
277
+ // ---------------------------------------------------------------------------
278
+ function pushParagraph(blocks, lines, start, unwrap) {
279
+ const collected = [lines[start].trim()];
280
+ let index = start + 1;
281
+ while (index < lines.length) {
282
+ const line = lines[index];
283
+ if (line.trim() === '')
284
+ break;
285
+ if (/^ {0,3}=+ *$/.test(line)) {
286
+ blocks.push(`<h1>${renderInline(collected.join('\n'))}</h1>`);
287
+ return index + 1;
288
+ }
289
+ if (/^ {0,3}-+ *$/.test(line)) {
290
+ blocks.push(`<h2>${renderInline(collected.join('\n'))}</h2>`);
291
+ return index + 1;
292
+ }
293
+ if (startsNewBlock(line))
294
+ break;
295
+ collected.push(line.replace(/^ {0,3}/, ''));
296
+ index++;
297
+ }
298
+ const inline = renderInline(collected.join('\n'));
299
+ blocks.push(unwrap ? inline : `<p>${inline}</p>`);
300
+ return index;
301
+ }
302
+ // ---------------------------------------------------------------------------
303
+ // Inline
304
+ // ---------------------------------------------------------------------------
305
+ /** Render inline markdown. Exported for callers rendering a single line. */
306
+ export function renderInline(source) {
307
+ let out = '';
308
+ let index = 0;
309
+ while (index < source.length) {
310
+ const character = source[index];
311
+ if (character === '\\') {
312
+ const next = source[index + 1];
313
+ if (next === '\n') {
314
+ out += '<br />\n';
315
+ index += 2;
316
+ continue;
317
+ }
318
+ if (next !== undefined && ASCII_PUNCTUATION.test(next)) {
319
+ out += escapeHtmlText(next);
320
+ index += 2;
321
+ continue;
322
+ }
323
+ out += '\\';
324
+ index++;
325
+ continue;
326
+ }
327
+ if (character === ' ') {
328
+ const run = /^( +)(\n|$)/.exec(source.slice(index));
329
+ if (run !== null) {
330
+ out += run[1].length >= 2 && run[2] === '\n' ? '<br />\n' : run[2] === '\n' ? '\n' : '';
331
+ index += run[1].length + run[2].length;
332
+ continue;
333
+ }
334
+ out += ' ';
335
+ index++;
336
+ continue;
337
+ }
338
+ if (character === '`') {
339
+ const span = codeSpanAt(source, index);
340
+ if (span !== null) {
341
+ out += span.html;
342
+ index = span.end;
343
+ continue;
344
+ }
345
+ out += '`';
346
+ index++;
347
+ continue;
348
+ }
349
+ if (character === '<') {
350
+ const raw = rawInlineAt(source, index);
351
+ if (raw !== null) {
352
+ out += raw.html;
353
+ index = raw.end;
354
+ continue;
355
+ }
356
+ out += '&lt;';
357
+ index++;
358
+ continue;
359
+ }
360
+ if (character === '&') {
361
+ const entity = ENTITY.exec(source.slice(index));
362
+ if (entity !== null) {
363
+ out += entity[0];
364
+ index += entity[0].length;
365
+ continue;
366
+ }
367
+ out += '&amp;';
368
+ index++;
369
+ continue;
370
+ }
371
+ if (character === '>') {
372
+ out += '&gt;';
373
+ index++;
374
+ continue;
375
+ }
376
+ if (character === '!' && source[index + 1] === '[') {
377
+ const image = linkAt(source, index + 1);
378
+ if (image !== null) {
379
+ out += `<img src="${escapeHtmlAttribute(image.destination)}" alt="${escapeHtmlAttribute(stripInlineMarkup(image.text))}"${image.title === undefined ? '' : ` title="${escapeHtmlAttribute(image.title)}"`} />`;
380
+ index = image.end;
381
+ continue;
382
+ }
383
+ }
384
+ if (character === '[') {
385
+ const link = linkAt(source, index);
386
+ if (link !== null) {
387
+ out += `<a href="${escapeHtmlAttribute(link.destination)}"${link.title === undefined ? '' : ` title="${escapeHtmlAttribute(link.title)}"`}>${renderInline(link.text)}</a>`;
388
+ index = link.end;
389
+ continue;
390
+ }
391
+ }
392
+ if (character === '*' || character === '_') {
393
+ const emphasis = emphasisAt(source, index);
394
+ if (emphasis !== null) {
395
+ out += emphasis.html;
396
+ index = emphasis.end;
397
+ continue;
398
+ }
399
+ out += character;
400
+ index++;
401
+ continue;
402
+ }
403
+ out += character;
404
+ index++;
405
+ }
406
+ return out;
407
+ }
408
+ /** Plain text for an image `alt`, which carries no markup. */
409
+ function stripInlineMarkup(source) {
410
+ return source.replace(/[*_`]/g, '');
411
+ }
412
+ function backtickRun(source, at) {
413
+ let run = 0;
414
+ while (source[at + run] === '`')
415
+ run++;
416
+ return run;
417
+ }
418
+ function codeSpanAt(source, start) {
419
+ const run = backtickRun(source, start);
420
+ let index = start + run;
421
+ while (index < source.length) {
422
+ if (source[index] !== '`') {
423
+ index++;
424
+ continue;
425
+ }
426
+ const closing = backtickRun(source, index);
427
+ if (closing === run) {
428
+ let content = source.slice(start + run, index).replace(/\n/g, ' ');
429
+ if (content.length > 2 && content.startsWith(' ') && content.endsWith(' ') && content.trim() !== '')
430
+ content = content.slice(1, -1);
431
+ return { html: `<code>${escapeHtmlText(content)}</code>`, end: index + closing };
432
+ }
433
+ index += closing;
434
+ }
435
+ return null;
436
+ }
437
+ function rawInlineAt(source, start) {
438
+ const rest = source.slice(start);
439
+ const uri = AUTOLINK_URI.exec(rest);
440
+ if (uri !== null)
441
+ return { html: `<a href="${escapeHtmlAttribute(uri[1])}">${escapeHtmlText(uri[1])}</a>`, end: start + uri[0].length };
442
+ const email = AUTOLINK_EMAIL.exec(rest);
443
+ if (email !== null)
444
+ return { html: `<a href="mailto:${escapeHtmlAttribute(email[1])}">${escapeHtmlText(email[1])}</a>`, end: start + email[0].length };
445
+ const comment = HTML_COMMENT.exec(rest);
446
+ if (comment !== null)
447
+ return { html: comment[0], end: start + comment[0].length };
448
+ const tag = RAW_INLINE_TAG.exec(rest);
449
+ if (tag !== null)
450
+ return { html: tag[0], end: start + tag[0].length };
451
+ return null;
452
+ }
453
+ function linkAt(source, start) {
454
+ let depth = 0;
455
+ let index = start;
456
+ let textEnd = -1;
457
+ while (index < source.length) {
458
+ const character = source[index];
459
+ if (character === '\\') {
460
+ index += 2;
461
+ continue;
462
+ }
463
+ if (character === '`') {
464
+ const span = codeSpanAt(source, index);
465
+ index = span === null ? index + 1 : span.end;
466
+ continue;
467
+ }
468
+ if (character === '[')
469
+ depth++;
470
+ else if (character === ']') {
471
+ depth--;
472
+ if (depth === 0) {
473
+ textEnd = index;
474
+ break;
475
+ }
476
+ }
477
+ index++;
478
+ }
479
+ if (textEnd === -1 || source[textEnd + 1] !== '(')
480
+ return null;
481
+ let cursor = textEnd + 2;
482
+ while (source[cursor] === ' ' || source[cursor] === '\n')
483
+ cursor++;
484
+ let destination = '';
485
+ if (source[cursor] === '<') {
486
+ const close = source.indexOf('>', cursor + 1);
487
+ if (close === -1)
488
+ return null;
489
+ destination = source.slice(cursor + 1, close);
490
+ cursor = close + 1;
491
+ }
492
+ else {
493
+ let parens = 0;
494
+ while (cursor < source.length) {
495
+ const character = source[cursor];
496
+ if (character === '\\' && source[cursor + 1] !== undefined) {
497
+ destination += source[cursor + 1];
498
+ cursor += 2;
499
+ continue;
500
+ }
501
+ if (/\s/.test(character))
502
+ break;
503
+ if (character === '(')
504
+ parens++;
505
+ if (character === ')') {
506
+ if (parens === 0)
507
+ break;
508
+ parens--;
509
+ }
510
+ destination += character;
511
+ cursor++;
512
+ }
513
+ }
514
+ while (source[cursor] === ' ' || source[cursor] === '\n')
515
+ cursor++;
516
+ let title;
517
+ const quote = source[cursor];
518
+ if (quote === '"' || quote === "'") {
519
+ const close = source.indexOf(quote, cursor + 1);
520
+ if (close === -1)
521
+ return null;
522
+ title = source.slice(cursor + 1, close);
523
+ cursor = close + 1;
524
+ while (source[cursor] === ' ' || source[cursor] === '\n')
525
+ cursor++;
526
+ }
527
+ if (source[cursor] !== ')')
528
+ return null;
529
+ return { text: source.slice(start + 1, textEnd), destination, ...(title === undefined ? {} : { title }), end: cursor + 1 };
530
+ }
531
+ function emphasisAt(source, start) {
532
+ const marker = source[start];
533
+ let run = 0;
534
+ while (source[start + run] === marker)
535
+ run++;
536
+ const need = Math.min(run, 3);
537
+ const opened = source[start + need];
538
+ if (opened === undefined || /\s/.test(opened))
539
+ return null;
540
+ if (marker === '_') {
541
+ const before = source[start - 1];
542
+ if (before !== undefined && /[A-Za-z0-9]/.test(before))
543
+ return null;
544
+ }
545
+ const closer = findEmphasisCloser(source, start + need, marker, need);
546
+ if (closer === null)
547
+ return null;
548
+ const inner = renderInline(source.slice(start + need, closer));
549
+ const html = need === 1 ? `<em>${inner}</em>` : need === 2 ? `<strong>${inner}</strong>` : `<strong><em>${inner}</em></strong>`;
550
+ return { html, end: closer + need };
551
+ }
552
+ function findEmphasisCloser(source, from, marker, need) {
553
+ let index = from;
554
+ while (index < source.length) {
555
+ const character = source[index];
556
+ if (character === '\\') {
557
+ index += 2;
558
+ continue;
559
+ }
560
+ if (character === '`') {
561
+ const span = codeSpanAt(source, index);
562
+ index = span === null ? index + 1 : span.end;
563
+ continue;
564
+ }
565
+ if (character !== marker) {
566
+ index++;
567
+ continue;
568
+ }
569
+ let run = 0;
570
+ while (source[index + run] === marker)
571
+ run++;
572
+ const before = source[index - 1];
573
+ const after = source[index + run];
574
+ const closes = run >= need && before !== undefined && !/\s/.test(before)
575
+ && !(marker === '_' && after !== undefined && /[A-Za-z0-9]/.test(after));
576
+ if (closes && index > from)
577
+ return index;
578
+ index += run;
579
+ }
580
+ return null;
581
+ }
@@ -0,0 +1,6 @@
1
+ /** The page slot kinds crtr validates and renders itself. */
2
+ export declare const BUILTIN_PAGE_KINDS: readonly ["options", "text", "table", "cards", "chart"];
3
+ /** Normalize the product-registered page slot catalog from raw config. */
4
+ export declare function normalizePageComponents(raw: unknown): string[];
5
+ /** Whether crtr or the product catalog registers a page slot kind. */
6
+ export declare function isRegisteredPageKind(kind: string, productKinds: string[]): boolean;
@@ -0,0 +1,43 @@
1
+ /** The page slot kinds crtr validates and renders itself. */
2
+ export const BUILTIN_PAGE_KINDS = ['options', 'text', 'table', 'cards', 'chart'];
3
+ const PAGE_KIND_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
4
+ const BUILTIN_PAGE_KIND_SET = new Set(BUILTIN_PAGE_KINDS);
5
+ function describeEntry(entry) {
6
+ try {
7
+ const json = JSON.stringify(entry);
8
+ return json === undefined ? String(entry) : json;
9
+ }
10
+ catch {
11
+ return String(entry);
12
+ }
13
+ }
14
+ /** Normalize the product-registered page slot catalog from raw config. */
15
+ export function normalizePageComponents(raw) {
16
+ if (raw === undefined)
17
+ return [];
18
+ if (!Array.isArray(raw)) {
19
+ throw new Error(`page_components must be an array; received ${describeEntry(raw)}`);
20
+ }
21
+ const seen = new Set();
22
+ return raw.map((entry, index) => {
23
+ if (typeof entry !== 'string') {
24
+ throw new Error(`page_components[${index}] must be a string; offending entry: ${describeEntry(entry)}`);
25
+ }
26
+ const kind = entry.trim();
27
+ if (!PAGE_KIND_PATTERN.test(kind)) {
28
+ throw new Error(`page_components[${index}] has invalid page kind ${describeEntry(entry)} after trimming; expected /^[a-z][a-z0-9-]{0,63}$/`);
29
+ }
30
+ if (BUILTIN_PAGE_KIND_SET.has(kind)) {
31
+ throw new Error(`page_components[${index}] ${describeEntry(entry)} collides with built-in page kind "${kind}"`);
32
+ }
33
+ if (seen.has(kind)) {
34
+ throw new Error(`page_components[${index}] ${describeEntry(entry)} duplicates page kind "${kind}"`);
35
+ }
36
+ seen.add(kind);
37
+ return kind;
38
+ });
39
+ }
40
+ /** Whether crtr or the product catalog registers a page slot kind. */
41
+ export function isRegisteredPageKind(kind, productKinds) {
42
+ return BUILTIN_PAGE_KIND_SET.has(kind) || productKinds.includes(kind);
43
+ }
@@ -0,0 +1,3 @@
1
+ import { type PageManifest } from './page-schema.js';
2
+ /** Render one page ticket's authored document as the runtime's HTML payload. */
3
+ export declare function renderPageHtml(manifest: PageManifest, document: string, productKinds?: string[]): string;