@luckydraw/cumulus 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/gateway/adapters/webchat.d.ts +20 -0
- package/dist/gateway/adapters/webchat.d.ts.map +1 -1
- package/dist/gateway/adapters/webchat.js +81 -3
- package/dist/gateway/adapters/webchat.js.map +1 -1
- package/dist/gateway/static/blex-render.js +399 -0
- package/dist/gateway/static/chat.html +7 -0
- package/dist/gateway/static/mermaid-esm.js +2143 -0
- package/dist/gateway/static/widget.js +112 -127
- package/docs/web-app-agent-guide.md +117 -21
- package/examples/web-app-agent/README.md +225 -19
- package/examples/web-app-agent/agent/apply-thread-configs.mjs +139 -0
- package/examples/web-app-agent/gateway.config.example.json +22 -4
- package/examples/web-app-agent/public/agent/blex-mount.js +136 -0
- package/examples/web-app-agent/public/agent/bridge-mount.js +19 -3
- package/examples/web-app-agent/public/agent/commands.js +10 -4
- package/examples/web-app-agent/public/agent/device-thread.js +5 -5
- package/examples/web-app-agent/public/agent/panel.css +6 -0
- package/examples/web-app-agent/public/agent/panel.js +27 -4
- package/examples/web-app-agent/public/index.html +28 -0
- package/examples/web-app-agent/server.js +172 -11
- package/examples/web-app-agent/thread-config.example.json +22 -0
- package/examples/web-app-agent/thread-config.visitor.example.json +41 -0
- package/package.json +3 -2
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cumulus shared blex renderer (task 131, item 3).
|
|
3
|
+
*
|
|
4
|
+
* ONE renderer, many surfaces. The standalone chat widget and any embedding app
|
|
5
|
+
* (the web-app-agent kit, kalendeer, …) drive this same file through an injected
|
|
6
|
+
* adapter, rather than each re-deriving a port of it. Copying the port is what
|
|
7
|
+
* guarantees drift in every consumer at once.
|
|
8
|
+
*
|
|
9
|
+
* Served by the gateway at /blex-render.js. It is plain ES5-compatible script —
|
|
10
|
+
* no modules, no build step — so a browser can load it directly, cross-origin.
|
|
11
|
+
*
|
|
12
|
+
* ── Adapter contract ────────────────────────────────────────────────────────
|
|
13
|
+
* Required:
|
|
14
|
+
* allowType(type) -> boolean. Which block types this surface renders.
|
|
15
|
+
* getInput() -> the text input element, or null.
|
|
16
|
+
* getSendButton() -> the send button element, or null.
|
|
17
|
+
* Optional (default to no-ops, which is what makes a surface render-only):
|
|
18
|
+
* addChip(interaction, handle, sourceId)
|
|
19
|
+
* readPersisted(storeKey) -> previously stored interaction value
|
|
20
|
+
* persist(storeKey, value)
|
|
21
|
+
* onRendered(container, storeKey)
|
|
22
|
+
*
|
|
23
|
+
* A surface that supplies only the three required members gets render-only
|
|
24
|
+
* behaviour for free: interactions that complete locally still work, and
|
|
25
|
+
* deferred ones land in a no-op instead of a dead button.
|
|
26
|
+
*/
|
|
27
|
+
(function (global) {
|
|
28
|
+
'use strict';
|
|
29
|
+
|
|
30
|
+
// ── Fenced-code spans ──────────────────────────────────────────────────────
|
|
31
|
+
//
|
|
32
|
+
// Held as a source string, not a shared RegExp object — a /g regex carries
|
|
33
|
+
// lastIndex state, and these call sites must not be able to affect each other.
|
|
34
|
+
var CODE_FENCE_SRC = '```([^\\n`]*)\\n([\\s\\S]*?)```';
|
|
35
|
+
var BLEX_FENCE_SRC = '~~~blex:(\\w[\\w-]*)\\n([\\s\\S]*?)\\n~~~';
|
|
36
|
+
|
|
37
|
+
function codeFenceRe() {
|
|
38
|
+
return new RegExp(CODE_FENCE_SRC, 'g');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function findCodeFenceSpans(text) {
|
|
42
|
+
var spans = [];
|
|
43
|
+
var re = codeFenceRe();
|
|
44
|
+
var m;
|
|
45
|
+
while ((m = re.exec(text)) !== null) {
|
|
46
|
+
spans.push([m.index, m.index + m[0].length]);
|
|
47
|
+
}
|
|
48
|
+
return spans;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Replace ~~~blex fences with \x00BLEX_n\x00 tokens.
|
|
53
|
+
*
|
|
54
|
+
* A fence INSIDE a code block is an example being displayed, not a block to
|
|
55
|
+
* render — extracting it would delete it from the code block that exists to
|
|
56
|
+
* show it. String.replace reports offsets into the original string, so one
|
|
57
|
+
* pass over precomputed spans is enough.
|
|
58
|
+
*/
|
|
59
|
+
function extractBlocks(text) {
|
|
60
|
+
var blocks = [];
|
|
61
|
+
var protectedSpans = findCodeFenceSpans(text);
|
|
62
|
+
var replaced = text.replace(
|
|
63
|
+
new RegExp(BLEX_FENCE_SRC, 'g'),
|
|
64
|
+
function (match, type, json, offset) {
|
|
65
|
+
for (var i = 0; i < protectedSpans.length; i++) {
|
|
66
|
+
if (offset >= protectedSpans[i][0] && offset < protectedSpans[i][1]) return match;
|
|
67
|
+
}
|
|
68
|
+
var idx = blocks.length;
|
|
69
|
+
blocks.push({ type: type, json: json.trim(), idx: idx });
|
|
70
|
+
return '\x00BLEX_' + idx + '\x00';
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
return { text: replaced, blocks: blocks };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function escapeHtml(text) {
|
|
77
|
+
var div = document.createElement('div');
|
|
78
|
+
div.textContent = text;
|
|
79
|
+
return div.innerHTML;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The original fence text, reconstructed for display. */
|
|
83
|
+
function fenceSource(block) {
|
|
84
|
+
return '~~~blex:' + block.type + '\n' + block.json + '\n~~~';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* HTML for one placeholder container.
|
|
89
|
+
*
|
|
90
|
+
* The container carries the raw fence as its body. `render()` clears it before
|
|
91
|
+
* rendering, so a CLAIMED container is unchanged — but an unclaimed one shows
|
|
92
|
+
* what the author actually wrote instead of a blank rectangle. Containers go
|
|
93
|
+
* unclaimed on several real paths: user messages and changelog entries (which
|
|
94
|
+
* render markdown but never call render()), any page where blex.min.js failed
|
|
95
|
+
* to fetch, and — deliberately — any block type the adapter denies.
|
|
96
|
+
*
|
|
97
|
+
* opts.streaming suppresses the fallback: a completed-but-not-yet-rendered
|
|
98
|
+
* fence would otherwise flash its JSON before flipping to the rendered block.
|
|
99
|
+
*/
|
|
100
|
+
function placeholderHtml(idx, block, opts) {
|
|
101
|
+
var streaming = !!(opts && opts.streaming);
|
|
102
|
+
var body =
|
|
103
|
+
streaming || !block
|
|
104
|
+
? ''
|
|
105
|
+
: '<pre class="blex-fallback">' + escapeHtml(fenceSource(block)) + '</pre>';
|
|
106
|
+
return '<div class="blex-block-container" data-blex-idx="' + idx + '">' + body + '</div>';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Replace every \x00BLEX_n\x00 token in rendered HTML with its placeholder. */
|
|
110
|
+
function insertPlaceholders(html, blocks, opts) {
|
|
111
|
+
// The NUL is a deliberate in-band sentinel: it cannot occur in model output,
|
|
112
|
+
// so a token can never be forged by message content.
|
|
113
|
+
// eslint-disable-next-line no-control-regex
|
|
114
|
+
return html.replace(/\x00BLEX_(\d+)\x00/g, function (_, idx) {
|
|
115
|
+
return placeholderHtml(idx, blocks[parseInt(idx, 10)], opts);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── The render-only allow set ──────────────────────────────────────────────
|
|
120
|
+
//
|
|
121
|
+
// Derived, not argued. A type may render on a non-interactive surface iff every
|
|
122
|
+
// affordance it draws either completes locally or is purely visual:
|
|
123
|
+
//
|
|
124
|
+
// table / code / file-tree / timeline / image — selection is a highlight.
|
|
125
|
+
// Nothing is labelled with a verb; an emit landing in a no-op is invisible.
|
|
126
|
+
// terminal / svg — labelled buttons, but clipboard write and the object-URL
|
|
127
|
+
// download both run BEFORE the emit. The emit is a receipt, not the mechanism.
|
|
128
|
+
// chart / status / metric / progress / gallery / kanban / calendar /
|
|
129
|
+
// branch / layout — no clickable affordances at all.
|
|
130
|
+
//
|
|
131
|
+
// Excluded, and the reason each is excluded rather than merely unwired:
|
|
132
|
+
// confirm / poll / form — interaction is the entire point.
|
|
133
|
+
// diff — Apply/Reject have no local half. The emit IS the mechanism, so on a
|
|
134
|
+
// render-only surface they are guaranteed dead buttons that read as
|
|
135
|
+
// "click to apply" — worse than not rendering, because a dead button looks
|
|
136
|
+
// alive in a way raw JSON does not.
|
|
137
|
+
// mermaid — passes the affordance test (Show source / Export SVG / Export PNG
|
|
138
|
+
// all complete locally, like terminal and svg). It used to be excluded on a
|
|
139
|
+
// DIFFERENT axis — it could not LOAD — which is now fixed by vendoring the
|
|
140
|
+
// library and declaring an import map (task 132). Loadability is therefore
|
|
141
|
+
// no longer an allow-set question: it is a property of the DOCUMENT, and it
|
|
142
|
+
// is enforced for every adapter by BARE_SPECIFIER_TYPES below.
|
|
143
|
+
var RENDER_ONLY_TYPES = [
|
|
144
|
+
'table',
|
|
145
|
+
'status',
|
|
146
|
+
'metric',
|
|
147
|
+
'progress',
|
|
148
|
+
'code',
|
|
149
|
+
'timeline',
|
|
150
|
+
'svg',
|
|
151
|
+
'image',
|
|
152
|
+
'gallery',
|
|
153
|
+
'file-tree',
|
|
154
|
+
'terminal',
|
|
155
|
+
'chart',
|
|
156
|
+
'kanban',
|
|
157
|
+
'calendar',
|
|
158
|
+
'branch',
|
|
159
|
+
'layout',
|
|
160
|
+
'mermaid',
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
var INTERACTIVE_TYPES = ['confirm', 'poll', 'form', 'diff'];
|
|
164
|
+
|
|
165
|
+
// ── Load capability: a document property, checked for EVERY adapter ─────────
|
|
166
|
+
//
|
|
167
|
+
// Some renderers can only run if the PAGE resolves a bare module specifier for
|
|
168
|
+
// them. blex's mermaid renderer is the only one today: it does
|
|
169
|
+
// `await import("mermaid")`, whose sole resolution mechanism in a browser is an
|
|
170
|
+
// import map. Upstream catches the failure and paints the literal string
|
|
171
|
+
// "Mermaid render error", so a page without the mapping would show an error box
|
|
172
|
+
// where the fence used to be — worse than the fence.
|
|
173
|
+
//
|
|
174
|
+
// This is deliberately NOT expressed in the allow set. The allow set describes a
|
|
175
|
+
// surface's affordances, and the widget's adapter is `allowType: () => true`, so
|
|
176
|
+
// an allow-set entry would not govern /chat at all. One rule, both surfaces: a
|
|
177
|
+
// type whose renderer needs a bare specifier is never claimed unless the
|
|
178
|
+
// document declares an import mapping for it. A page that opts in gets real
|
|
179
|
+
// diagrams; a page that does not keeps a readable raw fence.
|
|
180
|
+
var BARE_SPECIFIER_TYPES = { mermaid: 'mermaid' };
|
|
181
|
+
|
|
182
|
+
var importMapCache = null;
|
|
183
|
+
|
|
184
|
+
/** Does this document declare an import mapping for `specifier`? */
|
|
185
|
+
function pageResolvesSpecifier(specifier) {
|
|
186
|
+
if (importMapCache === null) {
|
|
187
|
+
importMapCache = {};
|
|
188
|
+
try {
|
|
189
|
+
var maps = global.document
|
|
190
|
+
? global.document.querySelectorAll('script[type="importmap"]')
|
|
191
|
+
: [];
|
|
192
|
+
for (var i = 0; i < maps.length; i++) {
|
|
193
|
+
var parsed = JSON.parse(maps[i].textContent || '{}');
|
|
194
|
+
var imports = parsed && parsed.imports;
|
|
195
|
+
if (!imports) continue;
|
|
196
|
+
for (var key in imports) {
|
|
197
|
+
if (Object.prototype.hasOwnProperty.call(imports, key)) importMapCache[key] = true;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
/* a malformed import map must not break every other block type */
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return importMapCache[specifier] === true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** False only for a type that needs a bare specifier this page cannot resolve. */
|
|
208
|
+
function pageCanLoadType(type) {
|
|
209
|
+
var specifier = BARE_SPECIFIER_TYPES[type];
|
|
210
|
+
return !specifier || pageResolvesSpecifier(specifier);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* An adapter for a surface that has an input but no chip tray.
|
|
215
|
+
*
|
|
216
|
+
* Denied types are NOT routed to a library fallback renderer: the global blex
|
|
217
|
+
* bundle exports BaseRenderer, getPluginInfo, getRegisteredTypes, hasBlockType,
|
|
218
|
+
* registerBlockType, registerLazyBlockType, registerPlugin, renderBlock,
|
|
219
|
+
* renderPlaceholder, unregisterBlockType, unregisterPlugin — and no fallback
|
|
220
|
+
* renderer among them. Leaving the container unclaimed reuses the raw-fence
|
|
221
|
+
* body above, which is honest and costs nothing.
|
|
222
|
+
*
|
|
223
|
+
* Deny is a per-render gate on purpose. Do NOT implement it with
|
|
224
|
+
* unregisterBlockType: the registry is module-global, so unregistering
|
|
225
|
+
* `confirm` removes it for every surface sharing that module instance.
|
|
226
|
+
*/
|
|
227
|
+
function renderOnlyAdapter(overrides) {
|
|
228
|
+
var base = {
|
|
229
|
+
allowType: function (type) {
|
|
230
|
+
return RENDER_ONLY_TYPES.indexOf(type) !== -1;
|
|
231
|
+
},
|
|
232
|
+
getInput: function () {
|
|
233
|
+
return null;
|
|
234
|
+
},
|
|
235
|
+
getSendButton: function () {
|
|
236
|
+
return null;
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
if (overrides) {
|
|
240
|
+
for (var k in overrides) {
|
|
241
|
+
if (Object.prototype.hasOwnProperty.call(overrides, k)) base[k] = overrides[k];
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return base;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ── Handle lifecycle ───────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
var handleMap = typeof WeakMap !== 'undefined' ? new WeakMap() : null;
|
|
250
|
+
|
|
251
|
+
function trackHandle(el, handle) {
|
|
252
|
+
if (!handleMap) return;
|
|
253
|
+
var existing = handleMap.get(el) || [];
|
|
254
|
+
existing.push(handle);
|
|
255
|
+
handleMap.set(el, existing);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function destroy(el) {
|
|
259
|
+
if (!handleMap) return;
|
|
260
|
+
var handles = handleMap.get(el);
|
|
261
|
+
if (!handles) return;
|
|
262
|
+
handles.forEach(function (h) {
|
|
263
|
+
try {
|
|
264
|
+
h.destroy();
|
|
265
|
+
} catch {
|
|
266
|
+
/* a renderer that cannot tear down must not block the rest */
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
handleMap.delete(el);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function errorBox(message) {
|
|
273
|
+
return (
|
|
274
|
+
'<pre style="color:#f66;font-size:0.85em;padding:0.5em;">' + escapeHtml(message) + '</pre>'
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function callOptional(adapter, name, args) {
|
|
279
|
+
if (adapter && typeof adapter[name] === 'function') {
|
|
280
|
+
return adapter[name].apply(adapter, args);
|
|
281
|
+
}
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Render extracted blocks into the placeholder containers inside `el`.
|
|
287
|
+
*
|
|
288
|
+
* opts: { msgKey, streaming, keyPrefix }
|
|
289
|
+
*/
|
|
290
|
+
function render(el, blocks, adapter, opts) {
|
|
291
|
+
if (!blocks || blocks.length === 0) return;
|
|
292
|
+
// Contract violation, checked before the library guard: an adapter without
|
|
293
|
+
// allowType is a programming error and must surface whether or not blex
|
|
294
|
+
// happened to load, otherwise it hides until the first page that has blex.
|
|
295
|
+
if (!adapter || typeof adapter.allowType !== 'function') {
|
|
296
|
+
throw new Error('[blex] render() requires an adapter with allowType()');
|
|
297
|
+
}
|
|
298
|
+
if (typeof global.Blex === 'undefined') {
|
|
299
|
+
// The raw-fence fallback is already in the DOM, so the reader sees the
|
|
300
|
+
// payload rather than a blank box. warn, not error: some embedders fail
|
|
301
|
+
// their build gate on any console error.
|
|
302
|
+
console.warn('[blex] library not loaded — leaving raw fences in place');
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
var options = opts || {};
|
|
306
|
+
var streaming = !!options.streaming;
|
|
307
|
+
var msgKey = options.msgKey;
|
|
308
|
+
|
|
309
|
+
var containers = el.querySelectorAll('.blex-block-container');
|
|
310
|
+
Array.prototype.forEach.call(containers, function (container) {
|
|
311
|
+
var idx = parseInt(container.getAttribute('data-blex-idx'), 10);
|
|
312
|
+
if (isNaN(idx) || idx >= blocks.length) return;
|
|
313
|
+
var block = blocks[idx];
|
|
314
|
+
// Checked before allowType: this one is about the DOCUMENT, not the
|
|
315
|
+
// surface, so it applies even to an allow-everything adapter.
|
|
316
|
+
if (!pageCanLoadType(block.type)) {
|
|
317
|
+
// Deliberately unclaimed — the raw fence stays visible, which beats the
|
|
318
|
+
// error box upstream would paint.
|
|
319
|
+
console.warn(
|
|
320
|
+
'[blex] ' +
|
|
321
|
+
block.type +
|
|
322
|
+
' needs an import map this page does not declare — leaving the fence in place'
|
|
323
|
+
);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (!adapter.allowType(block.type)) {
|
|
327
|
+
// Deliberately unclaimed — the raw fence stays visible.
|
|
328
|
+
console.warn('[blex] type not rendered on this surface: ' + block.type);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
var storeKey = msgKey ? msgKey + ':' + idx : null;
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
var data = JSON.parse(block.json);
|
|
335
|
+
var blockObj = { type: block.type, id: block.type + '-' + idx, data: data };
|
|
336
|
+
|
|
337
|
+
// Claim the container: drop the raw-fence fallback. Bad JSON throws
|
|
338
|
+
// before this line and is reported by the catch below, which names the
|
|
339
|
+
// problem — better than either the fallback or a blank box.
|
|
340
|
+
container.innerHTML = '';
|
|
341
|
+
|
|
342
|
+
if (streaming) {
|
|
343
|
+
global.Blex.renderPlaceholder(block.type, container);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
var renderOpts = {};
|
|
348
|
+
var previous = callOptional(adapter, 'readPersisted', [storeKey]);
|
|
349
|
+
if (previous !== undefined) renderOpts.previousValue = previous;
|
|
350
|
+
|
|
351
|
+
global.Blex.renderBlock(blockObj, container, renderOpts)
|
|
352
|
+
.then(function (handle) {
|
|
353
|
+
trackHandle(el, handle);
|
|
354
|
+
callOptional(adapter, 'onRendered', [container, storeKey]);
|
|
355
|
+
if (!handle || !handle.onInteraction) return;
|
|
356
|
+
handle.onInteraction(function (interaction) {
|
|
357
|
+
if (storeKey && interaction.value !== undefined) {
|
|
358
|
+
callOptional(adapter, 'persist', [storeKey, interaction.value]);
|
|
359
|
+
}
|
|
360
|
+
if (!interaction.serialized) return;
|
|
361
|
+
var inputEl = adapter.getInput();
|
|
362
|
+
if (!inputEl) return;
|
|
363
|
+
if (interaction.immediate) {
|
|
364
|
+
var existingText = (inputEl.value || '').trim();
|
|
365
|
+
inputEl.value = existingText
|
|
366
|
+
? existingText + '\n\n' + interaction.serialized
|
|
367
|
+
: interaction.serialized;
|
|
368
|
+
inputEl.dispatchEvent(new Event('input', { bubbles: true }));
|
|
369
|
+
var sendBtn = adapter.getSendButton();
|
|
370
|
+
if (sendBtn) sendBtn.click();
|
|
371
|
+
} else {
|
|
372
|
+
callOptional(adapter, 'addChip', [interaction, handle, block.type + '-' + idx]);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
})
|
|
376
|
+
.catch(function (err) {
|
|
377
|
+
container.innerHTML = errorBox('Blex error: ' + (err.message || err));
|
|
378
|
+
});
|
|
379
|
+
} catch (e) {
|
|
380
|
+
container.innerHTML = errorBox('Invalid blex JSON: ' + e.message);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
global.CumulusBlexRender = {
|
|
386
|
+
extractBlocks: extractBlocks,
|
|
387
|
+
placeholderHtml: placeholderHtml,
|
|
388
|
+
insertPlaceholders: insertPlaceholders,
|
|
389
|
+
fenceSource: fenceSource,
|
|
390
|
+
render: render,
|
|
391
|
+
destroy: destroy,
|
|
392
|
+
renderOnlyAdapter: renderOnlyAdapter,
|
|
393
|
+
RENDER_ONLY_TYPES: RENDER_ONLY_TYPES,
|
|
394
|
+
INTERACTIVE_TYPES: INTERACTIVE_TYPES,
|
|
395
|
+
BARE_SPECIFIER_TYPES: BARE_SPECIFIER_TYPES,
|
|
396
|
+
pageCanLoadType: pageCanLoadType,
|
|
397
|
+
CODE_FENCE_SRC: CODE_FENCE_SRC,
|
|
398
|
+
};
|
|
399
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
<link rel="manifest" href="/manifest.json">
|
|
10
10
|
<link rel="apple-touch-icon" href="/icon-192.png">
|
|
11
11
|
<title>Cumulus Chat</title>
|
|
12
|
+
<!-- blex's mermaid renderer resolves the bare specifier "mermaid" (task 132).
|
|
13
|
+
An import map is the only mechanism that can satisfy it; without this tag
|
|
14
|
+
blex-render.js leaves mermaid fences unclaimed rather than letting upstream
|
|
15
|
+
paint "Mermaid render error". The URL is stamped with a content hash at
|
|
16
|
+
serve time by stampAssetUrls(). -->
|
|
17
|
+
<script type="importmap">{"imports":{"mermaid":"/mermaid-esm.js"}}</script>
|
|
12
18
|
<style>
|
|
13
19
|
*, *::before, *::after { box-sizing: border-box; }
|
|
14
20
|
html, body {
|
|
@@ -24,6 +30,7 @@
|
|
|
24
30
|
</head>
|
|
25
31
|
<body>
|
|
26
32
|
<script src="/blex.min.js" onload="console.log('[blex] blex.min.js loaded, Blex:', typeof Blex)" onerror="console.error('[blex] FAILED to load blex.min.js')"></script>
|
|
33
|
+
<script src="/blex-render.js"></script>
|
|
27
34
|
<script src="/widget.js" data-standalone="true" data-api-key=""></script>
|
|
28
35
|
</body>
|
|
29
36
|
</html>
|