@doxbrix/doxloop 0.1.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.
Files changed (152) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/LICENSE +93 -0
  3. package/README.md +576 -0
  4. package/SECURITY.md +15 -0
  5. package/assets/brand/README.md +8 -0
  6. package/assets/brand/doxloop-favicon.png +0 -0
  7. package/assets/brand/doxloop-logo-dark.png +0 -0
  8. package/assets/brand/doxloop-logo-light.png +0 -0
  9. package/assets/doxbrix-preview.css +6672 -0
  10. package/dist/agents.d.ts +26 -0
  11. package/dist/agents.d.ts.map +1 -0
  12. package/dist/agents.js +258 -0
  13. package/dist/agents.js.map +1 -0
  14. package/dist/args.d.ts +8 -0
  15. package/dist/args.d.ts.map +1 -0
  16. package/dist/args.js +95 -0
  17. package/dist/args.js.map +1 -0
  18. package/dist/artifact-deploy.d.ts +8 -0
  19. package/dist/artifact-deploy.d.ts.map +1 -0
  20. package/dist/artifact-deploy.js +240 -0
  21. package/dist/artifact-deploy.js.map +1 -0
  22. package/dist/auth.d.ts +17 -0
  23. package/dist/auth.d.ts.map +1 -0
  24. package/dist/auth.js +245 -0
  25. package/dist/auth.js.map +1 -0
  26. package/dist/author.d.ts +30 -0
  27. package/dist/author.d.ts.map +1 -0
  28. package/dist/author.js +195 -0
  29. package/dist/author.js.map +1 -0
  30. package/dist/capture.d.ts +10 -0
  31. package/dist/capture.d.ts.map +1 -0
  32. package/dist/capture.js +299 -0
  33. package/dist/capture.js.map +1 -0
  34. package/dist/cli.d.ts +3 -0
  35. package/dist/cli.d.ts.map +1 -0
  36. package/dist/cli.js +558 -0
  37. package/dist/cli.js.map +1 -0
  38. package/dist/deploy.d.ts +22 -0
  39. package/dist/deploy.d.ts.map +1 -0
  40. package/dist/deploy.js +136 -0
  41. package/dist/deploy.js.map +1 -0
  42. package/dist/doctor.d.ts +18 -0
  43. package/dist/doctor.d.ts.map +1 -0
  44. package/dist/doctor.js +222 -0
  45. package/dist/doctor.js.map +1 -0
  46. package/dist/doxbrix-markdown.d.ts +20 -0
  47. package/dist/doxbrix-markdown.d.ts.map +1 -0
  48. package/dist/doxbrix-markdown.js +873 -0
  49. package/dist/doxbrix-markdown.js.map +1 -0
  50. package/dist/errors.d.ts +8 -0
  51. package/dist/errors.d.ts.map +1 -0
  52. package/dist/errors.js +15 -0
  53. package/dist/errors.js.map +1 -0
  54. package/dist/fs.d.ts +6 -0
  55. package/dist/fs.d.ts.map +1 -0
  56. package/dist/fs.js +77 -0
  57. package/dist/fs.js.map +1 -0
  58. package/dist/generator-api.d.ts +69 -0
  59. package/dist/generator-api.d.ts.map +1 -0
  60. package/dist/generator-api.js +6 -0
  61. package/dist/generator-api.js.map +1 -0
  62. package/dist/generator-manager.d.ts +13 -0
  63. package/dist/generator-manager.d.ts.map +1 -0
  64. package/dist/generator-manager.js +155 -0
  65. package/dist/generator-manager.js.map +1 -0
  66. package/dist/generator-runtime.d.ts +24 -0
  67. package/dist/generator-runtime.d.ts.map +1 -0
  68. package/dist/generator-runtime.js +265 -0
  69. package/dist/generator-runtime.js.map +1 -0
  70. package/dist/generators.d.ts +21 -0
  71. package/dist/generators.d.ts.map +1 -0
  72. package/dist/generators.js +202 -0
  73. package/dist/generators.js.map +1 -0
  74. package/dist/preview.d.ts +21 -0
  75. package/dist/preview.d.ts.map +1 -0
  76. package/dist/preview.js +691 -0
  77. package/dist/preview.js.map +1 -0
  78. package/dist/progress.d.ts +19 -0
  79. package/dist/progress.d.ts.map +1 -0
  80. package/dist/progress.js +70 -0
  81. package/dist/progress.js.map +1 -0
  82. package/dist/project.d.ts +37 -0
  83. package/dist/project.d.ts.map +1 -0
  84. package/dist/project.js +499 -0
  85. package/dist/project.js.map +1 -0
  86. package/dist/sync.d.ts +7 -0
  87. package/dist/sync.d.ts.map +1 -0
  88. package/dist/sync.js +221 -0
  89. package/dist/sync.js.map +1 -0
  90. package/dist/types.d.ts +141 -0
  91. package/dist/types.d.ts.map +1 -0
  92. package/dist/types.js +2 -0
  93. package/dist/types.js.map +1 -0
  94. package/dist/validation.d.ts +4 -0
  95. package/dist/validation.d.ts.map +1 -0
  96. package/dist/validation.js +478 -0
  97. package/dist/validation.js.map +1 -0
  98. package/dist/version.d.ts +3 -0
  99. package/dist/version.d.ts.map +1 -0
  100. package/dist/version.js +5 -0
  101. package/dist/version.js.map +1 -0
  102. package/docs/agent-compatibility.md +41 -0
  103. package/docs/ci-and-automation.md +56 -0
  104. package/docs/doxbrix-http-api.md +182 -0
  105. package/docs/generator-authoring.md +59 -0
  106. package/docs/generator-selection.md +25 -0
  107. package/docs/project-format.md +97 -0
  108. package/docs/security-model.md +90 -0
  109. package/docs/troubleshooting.md +103 -0
  110. package/package.json +87 -0
  111. package/skills/doxloop-authoring/SKILL.md +381 -0
  112. package/skills/doxloop-authoring/agents/openai.yaml +4 -0
  113. package/skills/doxloop-authoring/assets/screenshot-highlight.js +83 -0
  114. package/skills/doxloop-authoring/references/accessibility.md +54 -0
  115. package/skills/doxloop-authoring/references/audience-flavors.md +93 -0
  116. package/skills/doxloop-authoring/references/branding.md +50 -0
  117. package/skills/doxloop-authoring/references/documentation-types.md +236 -0
  118. package/skills/doxloop-authoring/references/domain-ai-ml.md +67 -0
  119. package/skills/doxloop-authoring/references/domain-api-platform.md +68 -0
  120. package/skills/doxloop-authoring/references/domain-cli-tool.md +65 -0
  121. package/skills/doxloop-authoring/references/domain-data-platform.md +65 -0
  122. package/skills/doxloop-authoring/references/domain-developer-library.md +63 -0
  123. package/skills/doxloop-authoring/references/domain-ecommerce.md +63 -0
  124. package/skills/doxloop-authoring/references/domain-infrastructure-devops.md +67 -0
  125. package/skills/doxloop-authoring/references/domain-payments-fintech.md +69 -0
  126. package/skills/doxloop-authoring/references/domain-saas.md +71 -0
  127. package/skills/doxloop-authoring/references/domain-security-identity.md +65 -0
  128. package/skills/doxloop-authoring/references/editorial-style.md +105 -0
  129. package/skills/doxloop-authoring/references/examples-and-evidence.md +71 -0
  130. package/skills/doxloop-authoring/references/navigation-architecture.md +152 -0
  131. package/skills/doxloop-authoring/references/project-format.md +119 -0
  132. package/skills/doxloop-authoring/references/quality.md +122 -0
  133. package/skills/doxloop-authoring/references/reference-sites.md +207 -0
  134. package/skills/doxloop-authoring/references/screenshots.md +251 -0
  135. package/skills/doxloop-authoring/references/template-routing.md +94 -0
  136. package/skills/doxloop-authoring/references/type-administrator-guide.md +59 -0
  137. package/skills/doxloop-authoring/references/type-api-reference.md +66 -0
  138. package/skills/doxloop-authoring/references/type-architecture-concepts.md +61 -0
  139. package/skills/doxloop-authoring/references/type-cli-manual.md +61 -0
  140. package/skills/doxloop-authoring/references/type-deployment-operations.md +64 -0
  141. package/skills/doxloop-authoring/references/type-developer-portal.md +69 -0
  142. package/skills/doxloop-authoring/references/type-getting-started.md +59 -0
  143. package/skills/doxloop-authoring/references/type-integration-guide.md +59 -0
  144. package/skills/doxloop-authoring/references/type-migration-release.md +66 -0
  145. package/skills/doxloop-authoring/references/type-sdk-guide.md +63 -0
  146. package/skills/doxloop-authoring/references/type-troubleshooting-kb.md +63 -0
  147. package/skills/doxloop-authoring/references/type-user-guide.md +59 -0
  148. package/skills/doxloop-doxbrix/SKILL.md +83 -0
  149. package/skills/doxloop-doxbrix/agents/openai.yaml +4 -0
  150. package/skills/doxloop-doxbrix/references/api-endpoints.md +128 -0
  151. package/skills/doxloop-doxbrix/references/components.md +137 -0
  152. package/skills/doxloop-doxbrix/references/manifest.md +117 -0
@@ -0,0 +1,873 @@
1
+ /**
2
+ * MDX -> HTML renderer for `dxb dev`. Emits the same `dp-`/`df-` class structure
3
+ * the published reader site uses (see apps/web BlockRenderer + DocumentationSite),
4
+ * so that β€” paired with the bundled reader stylesheet (preview.css) β€” the local
5
+ * preview renders the way the cloud site does. Plain Markdown and the Doxbrix
6
+ * component blocks (callouts, Steps, Tabs, Accordion, Card/CardGroup, Columns,
7
+ * CodeGroup, Terminal, Frame, Video, Embed, File, ApiEndpoint, Badge, Icon,
8
+ * Update, Mermaid, Math, ...) are all supported.
9
+ */
10
+ import hljs from 'highlight.js/lib/common';
11
+ // Sentinels (private-use code points) that shield inline-component HTML from the
12
+ // Markdown escaper. Built from char codes so the source stays pure ASCII.
13
+ const TOK_OPEN = String.fromCharCode(0xe000);
14
+ const TOK_CLOSE = String.fromCharCode(0xe001);
15
+ let uidCounter = 0;
16
+ const uid = () => `t${(uidCounter++).toString(36)}`;
17
+ // TOC collection state (only top-level headings are recorded).
18
+ let tocEntries = [];
19
+ const slugCounts = new Map();
20
+ let renderDepth = 0;
21
+ export function renderMarkdown(md) {
22
+ uidCounter = 0;
23
+ tocEntries = [];
24
+ slugCounts.clear();
25
+ renderDepth = 0;
26
+ // The page title is rendered in the article header, so drop a single leading
27
+ // H1 from the body (matching the reader, where the title is a separate field).
28
+ const body = stripLeadingH1(md.replace(/\r\n/g, '\n'));
29
+ const html = renderNodes(body, true);
30
+ return { html, toc: tocEntries };
31
+ }
32
+ function stripLeadingH1(md) {
33
+ const lines = md.split('\n');
34
+ let i = 0;
35
+ while (i < lines.length && !lines[i].trim())
36
+ i++;
37
+ if (i < lines.length && /^#\s+/.test(lines[i])) {
38
+ lines.splice(0, i + 1);
39
+ return lines.join('\n').replace(/^\n+/, '');
40
+ }
41
+ return md;
42
+ }
43
+ /** Split a source string into component/markdown nodes and render each. */
44
+ function renderNodes(src, top = false) {
45
+ renderDepth++;
46
+ if (renderDepth > 40) {
47
+ renderDepth--;
48
+ process.stderr.write('\x1b[33m[dxb] Parser: component nesting too deep β€” truncated\x1b[0m\n');
49
+ return renderParseError('Component nesting too deep β€” preview truncated here.');
50
+ }
51
+ try {
52
+ return extractNodes(src)
53
+ .map((n) => (n.type === 'md' ? renderMarkdownChunk(n.text, top) : safeRenderElement(n)))
54
+ .join('\n');
55
+ }
56
+ finally {
57
+ renderDepth--;
58
+ }
59
+ }
60
+ function safeRenderElement(node) {
61
+ try {
62
+ return renderElement(node);
63
+ }
64
+ catch (err) {
65
+ const msg = err instanceof Error ? err.message : String(err);
66
+ process.stderr.write(`\x1b[31m[dxb] Render error in <${node.name}>: ${msg}\x1b[0m\n`);
67
+ return renderParseError(`Error rendering <${node.name}>: ${msg}`);
68
+ }
69
+ }
70
+ function renderParseError(msg) {
71
+ return `<div class="dp-callout callout-warning" style="font-family:monospace"><span class="dp-callout-icon">⚠️</span><span class="dp-callout-body"><strong>Preview parse error</strong><br>${esc(msg)}</span></div>`;
72
+ }
73
+ // ---------------------------------------------------------------------------
74
+ // Tokenizer: split a source into top-level component elements and markdown runs.
75
+ // ---------------------------------------------------------------------------
76
+ function extractNodes(src) {
77
+ const lines = src.split('\n');
78
+ const nodes = [];
79
+ let md = [];
80
+ const flush = () => {
81
+ const text = md.join('\n');
82
+ if (text.trim())
83
+ nodes.push({ type: 'md', text });
84
+ md = [];
85
+ };
86
+ let i = 0;
87
+ let guard = 0;
88
+ while (i < lines.length) {
89
+ if (++guard > 100_000) {
90
+ process.stderr.write('\x1b[31m[dxb] Parser: extractNodes iteration limit β€” content may be malformed\x1b[0m\n');
91
+ break;
92
+ }
93
+ const line = lines[i];
94
+ // Fenced code block - keep verbatim and don't scan it for component tags.
95
+ if (/^\s*(```|~~~)/.test(line)) {
96
+ md.push(line);
97
+ i++;
98
+ let fenceGuard = 0;
99
+ while (i < lines.length && !/^\s*(```|~~~)/.test(lines[i])) {
100
+ if (++fenceGuard > 50_000)
101
+ break;
102
+ md.push(lines[i++]);
103
+ }
104
+ if (i < lines.length)
105
+ md.push(lines[i++]);
106
+ continue;
107
+ }
108
+ // Block-level component element: a capitalized tag at the start of a line.
109
+ if (/^\s*<[A-Z][A-Za-z0-9]*(\s|\/|>|$)/.test(line)) {
110
+ const el = parseElement(lines, i);
111
+ if (el) {
112
+ flush();
113
+ nodes.push(el.node);
114
+ i = el.next;
115
+ continue;
116
+ }
117
+ }
118
+ md.push(line);
119
+ i++;
120
+ }
121
+ flush();
122
+ return nodes;
123
+ }
124
+ function parseElement(lines, start) {
125
+ const rest = lines.slice(start).join('\n');
126
+ const open = readOpenTag(rest);
127
+ if (!open)
128
+ return null;
129
+ if (open.selfClosed) {
130
+ return {
131
+ node: { type: 'el', name: open.name, props: open.props, inner: '' },
132
+ next: start + countNewlines(rest.slice(0, open.tagEnd)) + 1,
133
+ };
134
+ }
135
+ const close = findClose(rest, open.name, open.tagEnd);
136
+ if (!close) {
137
+ return {
138
+ node: { type: 'el', name: open.name, props: open.props, inner: rest.slice(open.tagEnd) },
139
+ next: lines.length,
140
+ };
141
+ }
142
+ return {
143
+ node: { type: 'el', name: open.name, props: open.props, inner: dedent(rest.slice(open.tagEnd, close.innerEnd)) },
144
+ next: start + countNewlines(rest.slice(0, close.closeEnd)) + 1,
145
+ };
146
+ }
147
+ function readOpenTag(s) {
148
+ const m = /^\s*<([A-Z][A-Za-z0-9]*)/.exec(s);
149
+ if (!m)
150
+ return null;
151
+ const name = m[1];
152
+ let i = m[0].length;
153
+ let quote = null;
154
+ let brace = 0;
155
+ for (; i < s.length; i++) {
156
+ const c = s[i];
157
+ if (quote) {
158
+ if (c === quote)
159
+ quote = null;
160
+ continue;
161
+ }
162
+ if (c === '"' || c === "'")
163
+ quote = c;
164
+ else if (c === '{')
165
+ brace++;
166
+ else if (c === '}')
167
+ brace--;
168
+ else if (brace === 0 && c === '>')
169
+ break;
170
+ }
171
+ if (i >= s.length)
172
+ return null;
173
+ const inside = s.slice(m[0].length, i);
174
+ const selfClosed = /\/\s*$/.test(inside);
175
+ return { name, props: parseProps(inside.replace(/\/\s*$/, '')), selfClosed, tagEnd: i + 1 };
176
+ }
177
+ function parseProps(attrStr) {
178
+ const props = {};
179
+ const re = /([A-Za-z_][\w-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|\{([^}]*)\}))?/g;
180
+ let m;
181
+ while ((m = re.exec(attrStr))) {
182
+ const key = m[1];
183
+ if (m[2] !== undefined)
184
+ props[key] = m[2];
185
+ else if (m[3] !== undefined)
186
+ props[key] = m[3];
187
+ else if (m[4] !== undefined)
188
+ props[key] = m[4].trim().replace(/^['"]|['"]$/g, '');
189
+ else
190
+ props[key] = true;
191
+ }
192
+ return props;
193
+ }
194
+ function findClose(s, name, from) {
195
+ const openTag = '<' + name;
196
+ const closeTag = '</' + name;
197
+ let i = from;
198
+ let depth = 1;
199
+ let inFence = false;
200
+ let guard = 0;
201
+ while (i < s.length) {
202
+ if (++guard > 500_000) {
203
+ process.stderr.write(`\x1b[31m[dxb] Parser: findClose iteration limit for <${name}> β€” treating as unclosed tag\x1b[0m\n`);
204
+ return null;
205
+ }
206
+ if (i === from || s[i - 1] === '\n') {
207
+ let j = i;
208
+ while (s[j] === ' ' || s[j] === '\t')
209
+ j++;
210
+ if (s.startsWith('```', j) || s.startsWith('~~~', j))
211
+ inFence = !inFence;
212
+ }
213
+ if (!inFence && s[i] === '<') {
214
+ if (s.startsWith(closeTag, i) && isTagBoundary(s[i + closeTag.length])) {
215
+ const gt = s.indexOf('>', i);
216
+ if (gt === -1)
217
+ return null;
218
+ depth--;
219
+ if (depth === 0)
220
+ return { innerEnd: i, closeEnd: gt + 1 };
221
+ i = gt + 1;
222
+ continue;
223
+ }
224
+ if (s.startsWith(openTag, i) && isTagBoundary(s[i + openTag.length])) {
225
+ const gt = s.indexOf('>', i);
226
+ if (gt === -1)
227
+ return null;
228
+ if (s[gt - 1] !== '/')
229
+ depth++;
230
+ i = gt + 1;
231
+ continue;
232
+ }
233
+ }
234
+ i++;
235
+ }
236
+ return null;
237
+ }
238
+ function isTagBoundary(c) {
239
+ return c === undefined || c === ' ' || c === '\t' || c === '\n' || c === '>' || c === '/';
240
+ }
241
+ // ---------------------------------------------------------------------------
242
+ // Component renderers (emit reader `dp-` classes).
243
+ // ---------------------------------------------------------------------------
244
+ const CALLOUTS = {
245
+ Info: { icon: 'ℹ️', cls: 'callout-info' },
246
+ Note: { icon: 'πŸ“', cls: 'callout-note' },
247
+ Tip: { icon: 'πŸ’‘', cls: 'callout-tip' },
248
+ Check: { icon: 'βœ…', cls: 'callout-check' },
249
+ Warning: { icon: '⚠️', cls: 'callout-warning' },
250
+ Danger: { icon: '🚫', cls: 'callout-danger' },
251
+ };
252
+ function renderElement(node) {
253
+ const { name, props, inner } = node;
254
+ const callout = CALLOUTS[name];
255
+ if (callout) {
256
+ return `<div class="dp-callout ${callout.cls}"><span class="dp-callout-icon">${callout.icon}</span><span class="dp-callout-body">${stripBlockWrap(renderNodes(inner))}</span></div>`;
257
+ }
258
+ switch (name) {
259
+ case 'Steps':
260
+ return steps(inner);
261
+ case 'Tabs':
262
+ return tabset(childElements(inner, 'Tab').map((t) => ({
263
+ label: str(t.props.title) || str(t.props.label),
264
+ html: renderNodes(t.inner),
265
+ })));
266
+ case 'Accordion':
267
+ case 'AccordionGroup':
268
+ return accordion(inner);
269
+ case 'AccordionItem':
270
+ return accordionItem(props, inner);
271
+ case 'Expandable':
272
+ return `<div class="dp-expandable"><details><summary class="dp-expandable-trigger">${esc(str(props.title) || 'Details')}</summary><div class="dp-expandable-body">${renderNodes(inner)}</div></details></div>`;
273
+ case 'CardGroup':
274
+ return `<div class="dp-card-group cols-${clampCols(num(props.cols, 2), 3)}">${renderNodes(inner)}</div>`;
275
+ case 'Card':
276
+ return card(props, inner);
277
+ case 'Columns':
278
+ return `<div class="dp-columns" style="grid-template-columns:repeat(${clampCols(num(props.cols, 2), 4)},minmax(0,1fr))">${renderNodes(inner)}</div>`;
279
+ case 'Column':
280
+ return `<div class="dp-column">${renderNodes(inner)}</div>`;
281
+ case 'CodeGroup':
282
+ return codeGroup(inner);
283
+ case 'Terminal':
284
+ return `<div class="dp-terminal"><div class="dp-terminal-header"><span class="dp-terminal-dot" style="background:#ff5f57"></span><span class="dp-terminal-dot" style="background:#febc2e"></span><span class="dp-terminal-dot" style="background:#28c840"></span></div><pre class="dp-terminal-pre"><code>${esc(textOf(inner))}</code></pre></div>`;
285
+ case 'Frame':
286
+ return frame(props, inner);
287
+ case 'Image':
288
+ return image(props);
289
+ case 'Video':
290
+ return video(props);
291
+ case 'Embed':
292
+ return `<div class="dp-frame"><iframe src="${escAttr(str(props.src) || str(props.url))}" loading="lazy" allowfullscreen title="${escAttr(str(props.title) || 'Embedded content')}"></iframe></div>`;
293
+ case 'File':
294
+ return `<a class="dp-file" href="${escAttr(str(props.url) || str(props.src))}" download><span class="dp-file-name">${esc(str(props.name) || str(props.url) || str(props.src) || 'Download')}</span></a>`;
295
+ case 'ApiEndpoint':
296
+ return apiEndpoint(props, inner);
297
+ case 'Badge':
298
+ return badge(props, inner);
299
+ case 'Icon':
300
+ return `<span class="dp-inline-badge">${esc(str(props.emoji) || iconGlyph(str(props.name)))}${inner.trim() ? ` ${inline(textOf(inner))}` : ''}</span>`;
301
+ case 'Update':
302
+ return update(props, inner);
303
+ case 'ParameterTable':
304
+ return parameterTable(props, inner);
305
+ case 'ResponseExample':
306
+ return responseExample(props, inner);
307
+ case 'Mermaid':
308
+ return `<div class="dp-diagram-wrap"><pre class="mermaid">${esc(textOf(inner))}</pre></div>`;
309
+ case 'Math':
310
+ return `<div class="dp-math-wrap"><div class="dp-math-display">${esc(textOf(inner))}</div></div>`;
311
+ case 'Excalidraw':
312
+ return `<div class="dp-excalidraw-wrap dp-diagram-error">Excalidraw diagram (preview unavailable)</div>`;
313
+ default:
314
+ return inner.trim() ? renderNodes(inner) : '';
315
+ }
316
+ }
317
+ /** Drop a single wrapping <p class="dp-p">…</p> (callouts render inline bodies). */
318
+ function stripBlockWrap(html) {
319
+ const m = /^<p class="dp-p">([\s\S]*)<\/p>$/.exec(html.trim());
320
+ return m ? m[1] : html;
321
+ }
322
+ function steps(inner) {
323
+ const items = childElements(inner, 'Step');
324
+ const body = items
325
+ .map((s, i) => {
326
+ const connector = i < items.length - 1 ? '<div class="dp-step-connector"></div>' : '';
327
+ const stepBody = s.inner.trim() ? `<div class="dp-step-body">${renderNodes(s.inner)}</div>` : '';
328
+ return `<div class="dp-step"><div class="dp-step-left"><div class="dp-step-num">${i + 1}</div>${connector}</div><div class="dp-step-content"><div class="dp-step-title">${inline(str(s.props.title) || `Step ${i + 1}`)}</div>${stepBody}</div></div>`;
329
+ })
330
+ .join('');
331
+ return `<div class="dp-steps">${body}</div>`;
332
+ }
333
+ function tabset(tabs) {
334
+ if (!tabs.length)
335
+ return '';
336
+ const id = uid();
337
+ const bar = tabs
338
+ .map((t, k) => `<button class="dp-code-group-tab${k === 0 ? ' active' : ''}" data-tab="${id}-${k}">${esc(t.label || `Tab ${k + 1}`)}</button>`)
339
+ .join('');
340
+ const panels = tabs
341
+ .map((t, k) => `<div class="dp-tab-panel${k === 0 ? ' active' : ''}" id="${id}-${k}">${t.html}</div>`)
342
+ .join('');
343
+ return `<div class="dp-tabs"><div class="dp-code-group-tabs">${bar}</div>${panels}</div>`;
344
+ }
345
+ function accordion(inner) {
346
+ const items = childElements(inner, 'AccordionItem');
347
+ const body = items.length ? items.map((it) => accordionItem(it.props, it.inner)).join('') : renderNodes(inner);
348
+ return `<div class="dp-accordion">${body}</div>`;
349
+ }
350
+ function accordionItem(props, inner) {
351
+ return `<details class="dp-accordion-item"><summary class="dp-accordion-trigger">${esc(str(props.title) || 'Details')}</summary><div class="dp-accordion-body">${renderNodes(inner)}</div></details>`;
352
+ }
353
+ function card(props, inner) {
354
+ const icon = props.icon ? `<div class="dp-card-icon">${esc(iconGlyph(str(props.icon)))}</div>` : '';
355
+ const title = `<div class="dp-card-title">${inline(str(props.title) || 'Card')}</div>`;
356
+ const descText = inner.trim() ? stripBlockWrap(renderNodes(inner)) : str(props.description);
357
+ const desc = descText ? `<div class="dp-card-desc">${descText}</div>` : '';
358
+ const content = `${icon}${title}${desc}`;
359
+ return props.href
360
+ ? `<a class="dp-card" href="${escAttr(str(props.href))}">${content}</a>`
361
+ : `<div class="dp-card">${content}</div>`;
362
+ }
363
+ function codeGroup(inner) {
364
+ const blocks = parseFences(inner);
365
+ if (!blocks.length)
366
+ return `<div class="dp-code-group">${renderNodes(inner)}</div>`;
367
+ const id = uid();
368
+ const bar = blocks
369
+ .map((b, k) => `<button class="dp-code-group-tab${k === 0 ? ' active' : ''}" data-tab="${id}-${k}">${esc(b.label || b.lang || `Tab ${k + 1}`)}</button>`)
370
+ .join('');
371
+ const panels = blocks
372
+ .map((b, k) => `<div class="dp-tab-panel${k === 0 ? ' active' : ''}" id="${id}-${k}">${codeBlock(b.code, b.lang)}</div>`)
373
+ .join('');
374
+ return `<div class="dp-code-group"><div class="dp-code-group-tabs">${bar}</div>${panels}</div>`;
375
+ }
376
+ function codeBlock(code, lang) {
377
+ const requested = (lang || 'plaintext').toLowerCase();
378
+ const aliases = { ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript', shellscript: 'bash', shell: 'bash', sh: 'bash', py: 'python', yml: 'yaml', text: 'plaintext' };
379
+ const language = aliases[requested] ?? requested;
380
+ let highlighted;
381
+ try {
382
+ highlighted = hljs.getLanguage(language)
383
+ ? hljs.highlight(code, { language, ignoreIllegals: true }).value
384
+ : esc(code);
385
+ }
386
+ catch {
387
+ highlighted = esc(code);
388
+ }
389
+ return `<div class="dp-code-block dp-code-theme-auto dp-code-theme-light"><div class="dp-code-header"><span class="dp-code-lang">${esc(lang || 'plaintext')}</span><button class="dp-code-copy-btn" type="button" aria-label="Copy code">${copyIcon()}<span>Copy</span></button></div><pre class="dp-code-pre dp-hl-auto dp-hl-light"><code>${highlighted}</code></pre></div>`;
390
+ }
391
+ function copyIcon() {
392
+ return '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
393
+ }
394
+ function frame(props, inner) {
395
+ // A Frame wrapping an image renders as a captioned image (matches the reader).
396
+ const img = /!\[([^\]]*)\]\(([^)\s]+)[^)]*\)/.exec(inner);
397
+ if (img) {
398
+ const caption = str(props.caption);
399
+ const cap = caption ? `<div class="dp-image-caption">${inline(caption)}</div>` : '';
400
+ return `<div class="dp-image-wrap align-center"><img alt="${escAttr(img[1] ?? '')}" src="${escAttr(img[2] ?? '')}" />${cap}</div>`;
401
+ }
402
+ const url = str(props.url) || str(props.src);
403
+ if (url) {
404
+ return `<div class="dp-frame"><iframe src="${escAttr(url)}" loading="lazy" title="${escAttr(str(props.title) || 'Framed content')}"></iframe></div>`;
405
+ }
406
+ return `<div class="dp-frame">${renderNodes(inner)}</div>`;
407
+ }
408
+ function image(props) {
409
+ const src = str(props.src) || str(props.url);
410
+ const caption = str(props.caption);
411
+ const cap = caption ? `<div class="dp-image-caption">${inline(caption)}</div>` : '';
412
+ return `<div class="dp-image-wrap align-center"><img alt="${escAttr(str(props.alt))}" src="${escAttr(src)}" />${cap}</div>`;
413
+ }
414
+ function video(props) {
415
+ const src = str(props.url) || str(props.src);
416
+ const embed = /youtu\.?be|youtube|vimeo|loom|wistia/i.test(src) ? toEmbedUrl(src) : src;
417
+ return `<div class="dp-video"><iframe src="${escAttr(embed)}" loading="lazy" allowfullscreen title="Video"></iframe></div>`;
418
+ }
419
+ function apiEndpoint(props, inner) {
420
+ const method = (str(props.method) || 'GET').toUpperCase();
421
+ const path = str(props.path) || '/';
422
+ const baseUrl = str(props.baseUrl).replace(/\/+$/, '');
423
+ const description = str(props.description);
424
+ const params = childElements(inner, 'Param');
425
+ const responses = childElements(inner, 'Response');
426
+ const requestId = uid();
427
+ const responseGroupId = uid();
428
+ const curl = apiCurlExample(method, path, baseUrl, params);
429
+ const parameterSection = (title, location, color) => {
430
+ const matching = params.filter((param) => (str(param.props.in) || 'query') === location);
431
+ if (matching.length === 0)
432
+ return '';
433
+ const rows = matching
434
+ .map((param) => {
435
+ const type = str(param.props.type);
436
+ const where = str(param.props.in) || 'query';
437
+ const body = stripBlockWrap(renderNodes(param.inner));
438
+ return `<div class="dp-api-param-item"><div class="dp-api-param-meta"><span class="dp-api-param-name" style="color:${color}">${esc(str(param.props.name))}</span>${type ? `<span class="dp-api-badge">${esc(type)}</span>` : ''}<span class="dp-api-badge">${esc(where)}</span>${param.props.required ? '<span class="dp-api-param-required">required</span>' : ''}</div>${body ? `<div class="dp-api-param-desc">${body}</div>` : ''}</div>`;
439
+ })
440
+ .join('');
441
+ return `<div class="dp-api-section"><button class="dp-api-section-btn" type="button" aria-expanded="true"><span aria-hidden="true">βŒ„</span><span>${title}</span></button><div class="dp-api-section-body">${rows}</div></div>`;
442
+ };
443
+ const responseRows = responses
444
+ .map((response) => {
445
+ const status = str(response.props.status) || '200';
446
+ const tone = apiStatusTone(status);
447
+ const responseDescription = str(response.props.description);
448
+ return `<div class="dp-api-resp-row"><span class="dp-api-resp-status${tone ? ` ${tone}` : ''}">${esc(status)}</span>${responseDescription ? `<span class="dp-api-resp-desc">${esc(responseDescription)}</span>` : ''}</div>`;
449
+ })
450
+ .join('');
451
+ const responseSection = responseRows
452
+ ? `<div class="dp-api-section dp-api-section--response"><div class="dp-api-section-btn dp-api-section-btn--static"><span>Response</span></div><div class="dp-api-section-body">${responseRows}</div></div>`
453
+ : '';
454
+ const responseTabs = responses
455
+ .map((response, index) => {
456
+ const status = str(response.props.status) || '200';
457
+ const tone = apiStatusTone(status);
458
+ return `<button class="dp-api-resp-tab${tone ? ` ${tone}` : ''}${index === 0 ? ' active' : ''}" type="button" data-api-response-tab="${responseGroupId}-${index}">${esc(status)}</button>`;
459
+ })
460
+ .join('');
461
+ const responsePanels = responses
462
+ .map((response, index) => {
463
+ const example = apiExample(response.inner);
464
+ return `<pre class="dp-api-resp-body" id="${responseGroupId}-${index}" data-api-response-panel${index === 0 ? '' : ' hidden'}>${esc(example || '{}')}</pre>`;
465
+ })
466
+ .join('');
467
+ const responseCard = responses.length > 0
468
+ ? `<div class="dp-api-resp-card"><div class="dp-api-resp-header"><div class="dp-api-resp-tabs">${responseTabs}</div><button class="dp-api-light-copy" type="button">Copy</button></div>${responsePanels}</div>`
469
+ : '';
470
+ return `<div class="dp-api-ref">${description ? `<p class="dp-api-ref-desc">${inline(description)}</p>` : ''}<div class="dp-api-ref-body"><div class="dp-api-ref-left"><div class="dp-api-ref-url-row"><span class="dp-api-method-badge ${method}">${esc(method)}</span><code class="dp-api-ref-path">${esc(path)}</code><button class="dp-api-try-btn" type="button" disabled title="Interactive Try it is available in published Doxbrix">Try it β–Ά</button></div>${parameterSection('Authorizations', 'header', '#059669')}${parameterSection('Path Parameters', 'path', '#d97706')}${parameterSection('Query Parameters', 'query', '#2563eb')}${parameterSection('Body Parameters', 'body', '#7c3aed')}${responseSection}</div><div class="dp-api-ref-right"><div class="dp-api-code-card"><div class="dp-api-code-card-header"><span class="dp-api-code-card-lang">cURL</span><div class="dp-api-code-card-actions"><span class="dp-api-code-copy">cURLβŒ„</span><button class="dp-api-code-copy" type="button">Copy</button></div></div><pre class="dp-api-code-body" id="${requestId}">${esc(curl)}</pre></div>${responseCard}</div></div></div>`;
471
+ }
472
+ function apiCurlExample(method, path, baseUrl, params) {
473
+ let resolvedPath = path;
474
+ for (const param of params.filter((item) => str(item.props.in) === 'path')) {
475
+ const name = str(param.props.name);
476
+ const example = str(param.props.example) || `{${name}}`;
477
+ if (name)
478
+ resolvedPath = resolvedPath.replaceAll(`{${name}}`, example);
479
+ }
480
+ const query = params
481
+ .filter((param) => str(param.props.in) === 'query')
482
+ .map((param) => {
483
+ const name = str(param.props.name);
484
+ const example = str(param.props.example) || `{${name}}`;
485
+ return `${encodeURIComponent(name)}=${encodeURIComponent(example)}`;
486
+ });
487
+ const url = `${baseUrl}${resolvedPath}${query.length ? `?${query.join('&')}` : ''}`;
488
+ const lines = [`curl --request ${method} \\`, ` --url '${url || resolvedPath}'`];
489
+ for (const param of params.filter((item) => str(item.props.in) === 'header')) {
490
+ const name = str(param.props.name);
491
+ const example = str(param.props.example) ||
492
+ (name.toLowerCase() === 'authorization'
493
+ ? 'Bearer YOUR_API_KEY'
494
+ : `{${name}}`);
495
+ lines[lines.length - 1] += ' \\';
496
+ lines.push(` --header '${name}: ${example}'`);
497
+ }
498
+ const bodyParams = params.filter((item) => str(item.props.in) === 'body');
499
+ if (bodyParams.length > 0) {
500
+ const body = {};
501
+ for (const param of bodyParams) {
502
+ const name = str(param.props.name);
503
+ if (!name)
504
+ continue;
505
+ setApiBodyValue(body, name.split('.'), apiBodyExample(param));
506
+ }
507
+ lines[lines.length - 1] += ' \\';
508
+ lines.push(` --header 'Content-Type: application/json' \\`);
509
+ lines.push(` --data '${JSON.stringify(body, null, 2)}'`);
510
+ }
511
+ return lines.join('\n');
512
+ }
513
+ function apiBodyExample(param) {
514
+ const name = str(param.props.name);
515
+ const example = str(param.props.example);
516
+ if (!example)
517
+ return `{${name}}`;
518
+ switch (str(param.props.type).toLowerCase()) {
519
+ case 'integer': {
520
+ const value = Number(example);
521
+ return Number.isInteger(value) ? value : example;
522
+ }
523
+ case 'number': {
524
+ const value = Number(example);
525
+ return Number.isFinite(value) ? value : example;
526
+ }
527
+ case 'boolean':
528
+ if (example === 'true')
529
+ return true;
530
+ if (example === 'false')
531
+ return false;
532
+ return example;
533
+ case 'array':
534
+ case 'object':
535
+ try {
536
+ return JSON.parse(example);
537
+ }
538
+ catch {
539
+ return example;
540
+ }
541
+ default:
542
+ return example;
543
+ }
544
+ }
545
+ function setApiBodyValue(body, path, value) {
546
+ const key = path[0];
547
+ if (!key)
548
+ return;
549
+ if (path.length === 1) {
550
+ body[key] = value;
551
+ return;
552
+ }
553
+ const existing = body[key];
554
+ const child = existing !== null &&
555
+ typeof existing === 'object' &&
556
+ !Array.isArray(existing)
557
+ ? existing
558
+ : {};
559
+ body[key] = child;
560
+ setApiBodyValue(child, path.slice(1), value);
561
+ }
562
+ function apiStatusTone(status) {
563
+ if (/^2/.test(status))
564
+ return 'success';
565
+ if (/^[45]/.test(status))
566
+ return 'error';
567
+ return '';
568
+ }
569
+ function apiExample(inner) {
570
+ const body = textOf(inner).trim();
571
+ const fenced = /^```(?:json)?\s*\n([\s\S]*?)\n```\s*$/i.exec(body);
572
+ return fenced?.[1]?.trim() ?? body;
573
+ }
574
+ function badge(props, inner) {
575
+ const text = str(props.text) || textOf(inner);
576
+ const color = /^(blue|green|yellow|red|purple|gray|orange)$/.test(str(props.color))
577
+ ? ` ${str(props.color)}`
578
+ : '';
579
+ return `<span class="dp-inline-badge${color}">${esc(text)}</span>`;
580
+ }
581
+ function update(props, inner) {
582
+ const labelText = str(props.label) ||
583
+ [str(props.type), str(props.version)].filter(Boolean).join(' Β· ');
584
+ const descriptionText = str(props.description) || str(props.date);
585
+ const label = labelText ? `<div class="dp-update-label">${esc(labelText)}</div>` : '';
586
+ const desc = descriptionText ? `<div class="dp-update-desc">${esc(descriptionText)}</div>` : '';
587
+ return `<div class="dp-update"><div class="dp-update-head">${label}${desc}</div><div class="dp-update-body">${renderNodes(inner)}</div></div>`;
588
+ }
589
+ function parameterTable(props, inner) {
590
+ const params = childElements(inner, 'Param');
591
+ const title = str(props.title);
592
+ const rows = params
593
+ .map((param) => `<div class="dp-param-row"><span class="dp-param-name">${esc(str(param.props.name))}</span><span class="dp-param-type-tag">${esc(str(param.props.type))}</span>${param.props.required ? '<span class="dp-param-required-tag">required</span>' : ''}<span class="dp-param-desc-text">${stripBlockWrap(renderNodes(param.inner))}</span></div>`)
594
+ .join('');
595
+ return `<div class="dp-api-endpoint"><div class="dp-api-endpoint-body">${title ? `<div class="dp-param-section-title">${esc(title)}</div>` : ''}${rows || renderNodes(inner)}</div></div>`;
596
+ }
597
+ function responseExample(_props, inner) {
598
+ const responses = childElements(inner, 'Response');
599
+ if (responses.length === 0)
600
+ return `<div class="dp-api-resp-card">${renderNodes(inner)}</div>`;
601
+ return responses
602
+ .map((response) => `<div class="dp-api-resp-card"><div class="dp-api-resp-header"><span class="dp-api-resp-status">${esc(str(response.props.status))}</span>${response.props.description ? `<span class="dp-api-resp-desc">${esc(str(response.props.description))}</span>` : ''}</div><div class="dp-api-resp-body">${renderNodes(response.inner)}</div></div>`)
603
+ .join('');
604
+ }
605
+ // ---------------------------------------------------------------------------
606
+ // Markdown chunk renderer.
607
+ // ---------------------------------------------------------------------------
608
+ function renderMarkdownChunk(md, top) {
609
+ const lines = md.split('\n');
610
+ const out = [];
611
+ let i = 0;
612
+ let guard = 0;
613
+ while (i < lines.length) {
614
+ if (++guard > 100_000) {
615
+ process.stderr.write('\x1b[31m[dxb] Parser: renderMarkdownChunk iteration limit β€” content truncated\x1b[0m\n');
616
+ break;
617
+ }
618
+ const line = lines[i];
619
+ const fence = /^\s*(```|~~~)(\w*)\s*$/.exec(line);
620
+ if (fence) {
621
+ const lang = fence[2] ?? '';
622
+ const body = [];
623
+ i++;
624
+ while (i < lines.length && !/^\s*(```|~~~)\s*$/.test(lines[i]))
625
+ body.push(lines[i++]);
626
+ i++; // closing fence
627
+ out.push(codeBlock(body.join('\n'), lang));
628
+ continue;
629
+ }
630
+ if (!line.trim()) {
631
+ i++;
632
+ continue;
633
+ }
634
+ // HTML comment - skip (matches the reader, which hides it).
635
+ if (/^\s*<!--/.test(line)) {
636
+ while (i < lines.length && !/-->/.test(lines[i]))
637
+ i++;
638
+ i++;
639
+ continue;
640
+ }
641
+ // Stray closing tag - drop it rather than leak it.
642
+ if (/^\s*<\/[A-Za-z]/.test(line)) {
643
+ i++;
644
+ continue;
645
+ }
646
+ const h = /^(#{1,6})\s+(.*)$/.exec(line);
647
+ if (h) {
648
+ out.push(heading(h[1].length, h[2], top));
649
+ i++;
650
+ continue;
651
+ }
652
+ if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
653
+ out.push('<hr class="dp-hr" />');
654
+ i++;
655
+ continue;
656
+ }
657
+ if (/^\s*>\s?/.test(line)) {
658
+ const body = [];
659
+ while (i < lines.length && /^\s*>\s?/.test(lines[i]))
660
+ body.push(lines[i++].replace(/^\s*>\s?/, ''));
661
+ out.push(`<blockquote class="dp-blockquote">${renderMarkdownChunk(body.join('\n'), false)}</blockquote>`);
662
+ continue;
663
+ }
664
+ if (/\|/.test(line) && i + 1 < lines.length && /^\s*\|?[\s:|-]+\|?\s*$/.test(lines[i + 1])) {
665
+ const header = splitRow(line);
666
+ i += 2;
667
+ const rows = [];
668
+ while (i < lines.length && /\|/.test(lines[i]) && lines[i].trim())
669
+ rows.push(splitRow(lines[i++]));
670
+ out.push(renderTable(header, rows));
671
+ continue;
672
+ }
673
+ if (/^\s*([-*+]|\d+\.)\s+/.test(line)) {
674
+ const ordered = /^\s*\d+\.\s+/.test(line);
675
+ const items = [];
676
+ while (i < lines.length && /^\s*([-*+]|\d+\.)\s+/.test(lines[i])) {
677
+ items.push(lines[i++].replace(/^\s*([-*+]|\d+\.)\s+/, ''));
678
+ }
679
+ out.push(renderList(items, ordered));
680
+ continue;
681
+ }
682
+ // Lone-image paragraph -> block image (matches the reader's image block).
683
+ const loneImg = /^\s*!\[([^\]]*)\]\(([^)\s]+)[^)]*\)\s*$/.exec(line);
684
+ if (loneImg) {
685
+ out.push(`<div class="dp-image-wrap align-center"><img alt="${escAttr(loneImg[1] ?? '')}" src="${escAttr(loneImg[2] ?? '')}" /></div>`);
686
+ i++;
687
+ continue;
688
+ }
689
+ const para = [];
690
+ while (i < lines.length &&
691
+ lines[i].trim() &&
692
+ !/^\s*(#{1,6}\s|>\s?|```|~~~|<!--|<\/[A-Za-z]|([-*+]|\d+\.)\s+)/.test(lines[i])) {
693
+ para.push(lines[i++]);
694
+ }
695
+ if (para.length)
696
+ out.push(`<p class="dp-p">${inline(para.join(' '))}</p>`);
697
+ }
698
+ return out.join('\n');
699
+ }
700
+ function heading(level, text, top) {
701
+ const lvl = Math.min(6, Math.max(2, level === 1 ? 2 : level));
702
+ const id = slugify(stripInline(text));
703
+ if (top && (lvl === 2 || lvl === 3 || lvl === 4)) {
704
+ tocEntries.push({ id, level: lvl, title: stripInline(text) });
705
+ }
706
+ return `<h${lvl} id="${escAttr(id)}" class="dp-h${lvl}">${inline(text)}</h${lvl}>`;
707
+ }
708
+ function renderList(items, ordered) {
709
+ const isTask = items.some((it) => /^\[[ xX]\]\s+/.test(it));
710
+ if (isTask) {
711
+ const lis = items
712
+ .map((it) => {
713
+ const m = /^\[([ xX])\]\s+(.*)$/.exec(it);
714
+ const checked = m && m[1] !== ' ';
715
+ const icon = `<span class="dp-task-check-icon${checked ? ' checked' : ''}"></span>`;
716
+ const txt = m ? m[2] : it;
717
+ const style = checked ? ' style="text-decoration:line-through;color:#9ca3af"' : '';
718
+ return `<li class="dp-task-item">${icon}<span${style}>${inline(txt)}</span></li>`;
719
+ })
720
+ .join('');
721
+ return `<ul class="dp-task-list">${lis}</ul>`;
722
+ }
723
+ const tag = ordered ? 'ol' : 'ul';
724
+ const cls = ordered ? 'dp-ol' : 'dp-ul';
725
+ const lis = items.map((it) => `<li>${inline(it)}</li>`).join('');
726
+ return `<${tag} class="${cls}">${lis}</${tag}>`;
727
+ }
728
+ function renderTable(header, rows) {
729
+ const head = `<tr>${header.map((c) => `<th>${inline(c)}</th>`).join('')}</tr>`;
730
+ const body = rows.map((r) => `<tr>${r.map((c) => `<td>${inline(c)}</td>`).join('')}</tr>`).join('');
731
+ return `<div class="dp-table-wrap"><table class="dp-table"><thead>${head}</thead><tbody>${body}</tbody></table></div>`;
732
+ }
733
+ // ---------------------------------------------------------------------------
734
+ // Inline marks (+ inline components). Mirrors apps/web inline-format.tsx.
735
+ // ---------------------------------------------------------------------------
736
+ function inline(text) {
737
+ const tokens = [];
738
+ let s = text.replace(/<(Badge|Icon)\b([^>]*?)\/?>(?:([\s\S]*?)<\/\1>)?/g, (_m, tag, attrs, body) => {
739
+ const props = parseProps(attrs);
740
+ const html = tag === 'Badge'
741
+ ? `<span class="dp-inline-badge">${esc(str(props.text) || (body ?? '').trim())}</span>`
742
+ : `<span class="dp-inline-badge">${esc(iconGlyph(str(props.name)))}</span>`;
743
+ tokens.push(html);
744
+ return `${TOK_OPEN}${tokens.length - 1}${TOK_CLOSE}`;
745
+ });
746
+ s = esc(s);
747
+ s = s.replace(/`([^`\n]+)`/g, (_m, c) => `<code class="df-inline-code">${c}</code>`);
748
+ s = s.replace(/!\[([^\]]*)\]\(([^)\s]+)[^)]*\)/g, (_m, alt, src) => `<img alt="${escAttr(alt)}" src="${escAttr(src)}" />`);
749
+ s = s.replace(/\[([^\]]+)\]\(([^)\s]+)[^)]*\)/g, (_m, t, href) => `<a class="df-inline-link" href="${escAttr(href)}">${t}</a>`);
750
+ s = s.replace(/<u>([\s\S]+?)<\/u>/gi, '<u>$1</u>');
751
+ s = s.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
752
+ s = s.replace(/~~([^~]+)~~/g, '<s>$1</s>');
753
+ s = s.replace(/(^|[^*])\*([^*]+)\*/g, '$1<em>$2</em>');
754
+ s = s.replace(/(^|[^_\w])_([^_]+)_/g, '$1<em>$2</em>');
755
+ if (tokens.length) {
756
+ s = s.replace(new RegExp(`${TOK_OPEN}(\\d+)${TOK_CLOSE}`, 'g'), (_m, k) => tokens[Number(k)] ?? '');
757
+ }
758
+ return s;
759
+ }
760
+ // ---------------------------------------------------------------------------
761
+ // Small helpers.
762
+ // ---------------------------------------------------------------------------
763
+ function childElements(inner, name) {
764
+ return extractNodes(inner).filter((n) => n.type === 'el' && n.name === name);
765
+ }
766
+ function parseFences(src) {
767
+ const lines = src.split('\n');
768
+ const blocks = [];
769
+ let i = 0;
770
+ while (i < lines.length) {
771
+ const open = /^\s*(```|~~~)\s*(\w+)?\s*(.*)$/.exec(lines[i]);
772
+ if (!open) {
773
+ i++;
774
+ continue;
775
+ }
776
+ i++;
777
+ const body = [];
778
+ while (i < lines.length && !/^\s*(```|~~~)\s*$/.test(lines[i]))
779
+ body.push(lines[i++]);
780
+ i++;
781
+ blocks.push({ lang: open[2] ?? '', label: (open[3] ?? '').trim(), code: body.join('\n') });
782
+ }
783
+ return blocks;
784
+ }
785
+ function textOf(inner) {
786
+ return inner.replace(/^\n+/, '').replace(/\n+$/, '');
787
+ }
788
+ function toEmbedUrl(src) {
789
+ const yt = /(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/))([\w-]+)/.exec(src);
790
+ return yt ? `https://www.youtube.com/embed/${yt[1]}` : src;
791
+ }
792
+ const ICONS = {
793
+ rocket: 'πŸš€', sparkles: '✨', 'wand-sparkles': 'πŸͺ„', star: '⭐', globe: '🌐', terminal: '⌨️',
794
+ 'pen-line': '✏️', pencil: '✏️', users: 'πŸ‘₯', user: 'πŸ‘€', layers: '🧱', book: 'πŸ“˜', 'book-open': 'πŸ“–',
795
+ 'message-circle': 'πŸ’¬', chat: 'πŸ’¬', bell: 'πŸ””', lock: 'πŸ”’', key: 'πŸ”‘', shield: 'πŸ›‘οΈ', search: 'πŸ”',
796
+ zap: '⚑', bolt: '⚑', code: 'πŸ’»', file: 'πŸ“„', folder: 'πŸ“', settings: 'βš™οΈ', gear: 'βš™οΈ', check: 'βœ…',
797
+ info: 'ℹ️', warning: '⚠️', heart: '❀️', mail: 'βœ‰οΈ', link: 'πŸ”—', download: '⬇️', upload: '⬆️',
798
+ database: 'πŸ—„οΈ', cloud: '☁️', plug: 'πŸ”Œ', package: 'πŸ“¦', tag: '🏷️', calendar: 'πŸ“…', clock: 'πŸ•’',
799
+ eye: 'πŸ‘οΈ', git: '🌿', branch: '🌿', play: '▢️', flag: '🚩', map: 'πŸ—ΊοΈ', compass: '🧭',
800
+ };
801
+ function iconGlyph(name) {
802
+ if (/[^\p{L}\p{N}\s_-]/u.test(name))
803
+ return name;
804
+ return ICONS[name.toLowerCase()] ?? 'πŸ“„';
805
+ }
806
+ function slugify(text) {
807
+ const base = text
808
+ .toLowerCase()
809
+ .replace(/[^\w\s-]/g, '')
810
+ .trim()
811
+ .replace(/\s+/g, '-')
812
+ .replace(/-+/g, '-') || 'section';
813
+ // De-duplicate repeated slugs on the same page.
814
+ const seen = slugCounts.get(base);
815
+ if (seen === undefined) {
816
+ slugCounts.set(base, base);
817
+ return base;
818
+ }
819
+ let n = 2;
820
+ while (slugCounts.has(`${base}-${n}`))
821
+ n++;
822
+ const out = `${base}-${n}`;
823
+ slugCounts.set(out, out);
824
+ return out;
825
+ }
826
+ function stripInline(text) {
827
+ return text
828
+ .replace(/`([^`]+)`/g, '$1')
829
+ .replace(/\*\*([^*]+)\*\*/g, '$1')
830
+ .replace(/\*([^*]+)\*/g, '$1')
831
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
832
+ .trim();
833
+ }
834
+ function num(v, fallback) {
835
+ const n = typeof v === 'string' ? Number(v) : NaN;
836
+ return Number.isFinite(n) ? n : fallback;
837
+ }
838
+ function clampCols(n, max) {
839
+ return Math.min(max, Math.max(1, Math.round(n) || 1));
840
+ }
841
+ function str(v) {
842
+ return typeof v === 'string' ? v : '';
843
+ }
844
+ function countNewlines(s) {
845
+ return (s.match(/\n/g) ?? []).length;
846
+ }
847
+ function dedent(s) {
848
+ const body = s.replace(/^\n/, '');
849
+ const lines = body.split('\n');
850
+ let min = Infinity;
851
+ for (const l of lines) {
852
+ if (!l.trim())
853
+ continue;
854
+ min = Math.min(min, /^[ \t]*/.exec(l)[0].length);
855
+ }
856
+ if (!Number.isFinite(min) || min === 0)
857
+ return body;
858
+ return lines.map((l) => l.slice(min)).join('\n');
859
+ }
860
+ function splitRow(line) {
861
+ return line
862
+ .replace(/^\s*\|/, '')
863
+ .replace(/\|\s*$/, '')
864
+ .split('|')
865
+ .map((c) => c.trim());
866
+ }
867
+ function esc(s) {
868
+ return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
869
+ }
870
+ function escAttr(s) {
871
+ return esc(s).replace(/"/g, '&quot;');
872
+ }
873
+ //# sourceMappingURL=doxbrix-markdown.js.map