let-them-talk 5.4.3 → 5.5.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.
Files changed (37) hide show
  1. package/README.md +2 -1
  2. package/USAGE.md +1 -1
  3. package/cli.js +12 -3
  4. package/conversation-templates/autonomous-feature.json +4 -4
  5. package/conversation-templates/code-review.json +3 -3
  6. package/conversation-templates/debug-squad.json +3 -3
  7. package/conversation-templates/feature-build.json +3 -3
  8. package/conversation-templates/research-write.json +3 -3
  9. package/dashboard.html +329 -158
  10. package/dashboard.js +3459 -3429
  11. package/package.json +114 -113
  12. package/server.js +26 -85
  13. package/templates/debate.json +2 -2
  14. package/templates/managed.json +4 -4
  15. package/templates/pair.json +2 -2
  16. package/templates/review.json +2 -2
  17. package/templates/team.json +3 -3
  18. package/vendor/highlight-github-dark.min.css +10 -0
  19. package/vendor/highlight.min.js +1232 -0
  20. package/vendor/katex-fonts/KaTeX_AMS-Regular.woff2 +0 -0
  21. package/vendor/katex-fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  22. package/vendor/katex-fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  23. package/vendor/katex-fonts/KaTeX_Main-Bold.woff2 +0 -0
  24. package/vendor/katex-fonts/KaTeX_Main-Italic.woff2 +0 -0
  25. package/vendor/katex-fonts/KaTeX_Main-Regular.woff2 +0 -0
  26. package/vendor/katex-fonts/KaTeX_Math-Italic.woff2 +0 -0
  27. package/vendor/katex-fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  28. package/vendor/katex-fonts/KaTeX_Script-Regular.woff2 +0 -0
  29. package/vendor/katex-fonts/KaTeX_Size1-Regular.woff2 +0 -0
  30. package/vendor/katex-fonts/KaTeX_Size2-Regular.woff2 +0 -0
  31. package/vendor/katex-fonts/KaTeX_Size3-Regular.woff2 +0 -0
  32. package/vendor/katex-fonts/KaTeX_Size4-Regular.woff2 +0 -0
  33. package/vendor/katex-fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  34. package/vendor/katex.min.css +1 -0
  35. package/vendor/katex.min.js +1 -0
  36. package/vendor/marked.min.js +6 -0
  37. package/vendor/mermaid.min.js +2314 -0
package/dashboard.html CHANGED
@@ -4252,7 +4252,145 @@
4252
4252
  @media (max-width: 520px) {
4253
4253
  .char-designer { width: 100vw; }
4254
4254
  }
4255
+
4256
+ /* ==================== RICH MESSAGE RENDERING (v5.5) ==================== */
4257
+ .msg-content, .ws-entry-content {
4258
+ line-height: 1.55;
4259
+ font-size: 14px;
4260
+ word-wrap: break-word;
4261
+ }
4262
+ .msg-content p { margin: 0 0 8px; }
4263
+ .msg-content p:last-child { margin-bottom: 0; }
4264
+ .msg-content h1, .msg-content h2, .msg-content h3, .msg-content h4 {
4265
+ margin: 14px 0 6px; font-weight: 600; line-height: 1.3;
4266
+ }
4267
+ .msg-content h1 { font-size: 19px; border-bottom: 1px solid var(--border); padding-bottom: 4px; }
4268
+ .msg-content h2 { font-size: 17px; border-bottom: 1px solid var(--border); padding-bottom: 3px; }
4269
+ .msg-content h3 { font-size: 15px; }
4270
+ .msg-content h4 { font-size: 14px; color: var(--text-muted); }
4271
+ .msg-content ul, .msg-content ol { margin: 6px 0 8px; padding-left: 22px; }
4272
+ .msg-content li { margin: 2px 0; }
4273
+ .msg-content li > input[type="checkbox"] {
4274
+ margin-right: 6px; transform: translateY(1px); accent-color: var(--accent);
4275
+ }
4276
+ .msg-content hr { border: 0; border-top: 1px solid var(--border); margin: 12px 0; }
4277
+ .msg-content a { color: var(--accent); text-decoration: none; }
4278
+ .msg-content a:hover { text-decoration: underline; }
4279
+ .msg-content blockquote {
4280
+ margin: 8px 0; padding: 6px 12px; border-left: 3px solid var(--border);
4281
+ background: var(--surface-2); color: var(--text-muted);
4282
+ }
4283
+ .msg-content img { max-width: 100%; border-radius: 6px; cursor: zoom-in; margin: 4px 0; }
4284
+
4285
+ /* GitHub-quality tables */
4286
+ .msg-content table {
4287
+ width: 100%; border-collapse: collapse; margin: 10px 0;
4288
+ font-size: 13px; overflow: auto; display: block;
4289
+ }
4290
+ .msg-content thead { background: var(--surface-2); position: sticky; top: 0; z-index: 1; }
4291
+ .msg-content th, .msg-content td {
4292
+ border: 1px solid var(--border); padding: 6px 10px; text-align: left; vertical-align: top;
4293
+ }
4294
+ .msg-content th { font-weight: 600; color: var(--text); white-space: nowrap; }
4295
+ .msg-content tbody tr:nth-child(odd) { background: rgba(255,255,255,0.015); }
4296
+ .msg-content tbody tr:hover { background: rgba(88, 166, 255, 0.08); }
4297
+ .msg-content table code { font-size: 12px; }
4298
+
4299
+ /* Code blocks (with language pill + copy button) */
4300
+ .msg-content .code-block {
4301
+ position: relative; margin: 8px 0; border-radius: 6px;
4302
+ background: #0d1117; border: 1px solid #30363d; overflow: hidden;
4303
+ }
4304
+ .msg-content .code-block pre {
4305
+ margin: 0; padding: 12px 14px; overflow-x: auto;
4306
+ font-family: 'JetBrains Mono', 'Consolas', 'Courier New', monospace;
4307
+ font-size: 12.5px; line-height: 1.5;
4308
+ }
4309
+ .msg-content .code-block code.hljs { background: transparent; padding: 0; font-size: inherit; }
4310
+ .msg-content .code-lang-pill {
4311
+ position: absolute; top: 6px; left: 10px; font-size: 10px; font-weight: 600;
4312
+ color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px;
4313
+ pointer-events: none; user-select: none;
4314
+ }
4315
+ .msg-content .code-copy-btn {
4316
+ position: absolute; top: 4px; right: 6px; font-size: 10px;
4317
+ background: #21262d; color: #c9d1d9; border: 1px solid #30363d;
4318
+ border-radius: 4px; padding: 3px 8px; cursor: pointer; opacity: 0; transition: opacity 0.15s, background 0.15s;
4319
+ }
4320
+ .msg-content .code-block:hover .code-copy-btn { opacity: 1; }
4321
+ .msg-content .code-copy-btn:hover { background: #30363d; }
4322
+ .msg-content .code-copy-btn.copied { color: #3fb950; border-color: #3fb950; }
4323
+ .msg-content :not(.code-block) > code {
4324
+ background: var(--surface-2); border: 1px solid var(--border); border-radius: 4px;
4325
+ padding: 1px 5px; font-size: 12px;
4326
+ font-family: 'JetBrains Mono', 'Consolas', 'Courier New', monospace;
4327
+ }
4328
+
4329
+ /* Obsidian-style callouts */
4330
+ .msg-content .callout {
4331
+ display: block; margin: 10px 0; padding: 10px 14px;
4332
+ border-radius: 6px; border-left: 4px solid;
4333
+ background: rgba(255,255,255,0.02);
4334
+ }
4335
+ .msg-content .callout .callout-title {
4336
+ display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 13px;
4337
+ }
4338
+ .msg-content .callout .callout-icon { font-size: 14px; }
4339
+ .msg-content .callout .callout-body { margin-top: 6px; font-size: 13.5px; line-height: 1.55; }
4340
+ .msg-content .callout .callout-body > :first-child { margin-top: 0; }
4341
+ .msg-content .callout .callout-body > :last-child { margin-bottom: 0; }
4342
+ .msg-content details.callout-collapsible > summary { cursor: pointer; user-select: none; list-style: none; }
4343
+ .msg-content details.callout-collapsible > summary::-webkit-details-marker { display: none; }
4344
+ .msg-content details.callout-collapsible > summary::before {
4345
+ content: '▶'; display: inline-block; width: 14px; color: var(--text-muted); transition: transform 0.15s;
4346
+ }
4347
+ .msg-content details.callout-collapsible[open] > summary::before { transform: rotate(90deg); }
4348
+
4349
+ .msg-content .callout-note { border-left-color: #58a6ff; }
4350
+ .msg-content .callout-note .callout-title { color: #58a6ff; }
4351
+ .msg-content .callout-info { border-left-color: #58a6ff; }
4352
+ .msg-content .callout-info .callout-title { color: #58a6ff; }
4353
+ .msg-content .callout-tip { border-left-color: #3fb950; }
4354
+ .msg-content .callout-tip .callout-title { color: #3fb950; }
4355
+ .msg-content .callout-success { border-left-color: #3fb950; }
4356
+ .msg-content .callout-success .callout-title { color: #3fb950; }
4357
+ .msg-content .callout-warning { border-left-color: #e3b341; }
4358
+ .msg-content .callout-warning .callout-title { color: #e3b341; }
4359
+ .msg-content .callout-danger { border-left-color: #f85149; }
4360
+ .msg-content .callout-danger .callout-title { color: #f85149; }
4361
+ .msg-content .callout-question { border-left-color: #bc8cff; }
4362
+ .msg-content .callout-question .callout-title { color: #bc8cff; }
4363
+ .msg-content .callout-summary { border-left-color: #79c0ff; }
4364
+ .msg-content .callout-summary .callout-title { color: #79c0ff; }
4365
+ .msg-content .callout-example { border-left-color: #d2a8ff; }
4366
+ .msg-content .callout-example .callout-title { color: #d2a8ff; }
4367
+ .msg-content .callout-quote { border-left-color: #8b949e; }
4368
+ .msg-content .callout-quote .callout-title { color: #c9d1d9; }
4369
+
4370
+ /* Mermaid container */
4371
+ .msg-content .mermaid-block {
4372
+ margin: 10px 0; padding: 12px; background: #0d1117;
4373
+ border: 1px solid #30363d; border-radius: 6px; overflow-x: auto;
4374
+ display: flex; justify-content: center;
4375
+ }
4376
+ .msg-content .mermaid-block .mermaid { max-width: 100%; }
4377
+
4378
+ /* KaTeX block spacing */
4379
+ .msg-content .katex-display { margin: 10px 0; overflow-x: auto; overflow-y: hidden; }
4380
+
4381
+ /* Image lightbox (click an image in a message) */
4382
+ .image-lightbox {
4383
+ position: fixed; inset: 0; background: rgba(0,0,0,0.85); z-index: 10000;
4384
+ display: flex; align-items: center; justify-content: center;
4385
+ cursor: zoom-out; animation: lightbox-in 0.15s ease-out;
4386
+ }
4387
+ .image-lightbox img { max-width: 94vw; max-height: 94vh; border-radius: 8px; box-shadow: 0 20px 60px rgba(0,0,0,0.8); }
4388
+ @keyframes lightbox-in { from { opacity: 0; } to { opacity: 1; } }
4255
4389
  </style>
4390
+ <!-- Bundled rendering libs (local, offline-safe) -->
4391
+ <link rel="stylesheet" href="/vendor/highlight-github-dark.min.css">
4392
+ <script src="/vendor/marked.min.js"></script>
4393
+ <script src="/vendor/highlight.min.js"></script>
4256
4394
  </head>
4257
4395
  <body>
4258
4396
 
@@ -5449,182 +5587,215 @@ function escapeHtml(s) {
5449
5587
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/\\/g, '&#92;');
5450
5588
  }
5451
5589
 
5452
- // ==================== MARKDOWN RENDERER ====================
5453
-
5454
- function renderMarkdown(text) {
5455
- // Strip null bytes to prevent placeholder collision attacks
5456
- text = text.replace(/\x00/g, '');
5457
- // Extract fenced code blocks first to protect them
5458
- var codeBlocks = [];
5459
- text = text.replace(/```(\w*)\n([\s\S]*?)```/g, function(match, lang, code) {
5460
- var idx = codeBlocks.length;
5461
- codeBlocks.push({ lang: lang, code: code.replace(/\n$/, '') });
5462
- return '\x00CODEBLOCK' + idx + '\x00';
5463
- });
5464
- // Also handle ``` without newline after lang
5465
- text = text.replace(/```(\w*)([\s\S]*?)```/g, function(match, lang, code) {
5466
- var idx = codeBlocks.length;
5467
- codeBlocks.push({ lang: lang, code: code.replace(/^\n/, '').replace(/\n$/, '') });
5468
- return '\x00CODEBLOCK' + idx + '\x00';
5469
- });
5470
-
5471
- // Split into lines for block-level processing
5472
- var lines = text.split('\n');
5473
- var html = '';
5474
- var inList = false;
5475
- var listType = '';
5476
- var inTable = false;
5477
- var tableRows = [];
5478
- var inBlockquote = false;
5479
-
5480
- for (var i = 0; i < lines.length; i++) {
5481
- var line = lines[i];
5482
-
5483
- // Code block placeholder
5484
- var cbMatch = line.match(/^\x00CODEBLOCK(\d+)\x00$/);
5485
- if (cbMatch) {
5486
- if (inList) { html += '</' + listType + '>'; inList = false; }
5487
- if (inTable) { html += renderTable(tableRows); inTable = false; tableRows = []; }
5488
- if (inBlockquote) { html += '</blockquote>'; inBlockquote = false; }
5489
- var cb = codeBlocks[parseInt(cbMatch[1])];
5490
- if (!cb) { html += escapeHtml(line); continue; }
5491
- var langTag = cb.lang ? '<span class="lang-tag">' + escapeHtml(cb.lang) + '</span>' : '';
5492
- html += '<pre>' + langTag + '<code>' + escapeHtml(cb.code) + '</code></pre>';
5493
- continue;
5494
- }
5495
-
5496
- // Horizontal rule
5497
- if (/^(---|\*\*\*|___)$/.test(line.trim())) {
5498
- if (inList) { html += '</' + listType + '>'; inList = false; }
5499
- if (inTable) { html += renderTable(tableRows); inTable = false; tableRows = []; }
5500
- html += '<hr>';
5501
- continue;
5590
+ // ==================== MARKDOWN RENDERER (v5.5+) ====================
5591
+ // Uses marked (GFM) + highlight.js + Obsidian-style callout preprocessor.
5592
+ // Mermaid + KaTeX are lazy-loaded on demand from /vendor/.
5593
+
5594
+ var _markedConfigured = false;
5595
+ var _mermaidLoaded = false;
5596
+
5597
+ function configureMarked() {
5598
+ if (_markedConfigured || typeof window.marked === 'undefined') return;
5599
+ _markedConfigured = true;
5600
+ var renderer = new window.marked.Renderer();
5601
+ renderer.code = function(code, lang) {
5602
+ var codeText = (code && typeof code === 'object' && 'text' in code) ? code.text : code;
5603
+ var codeLang = (code && typeof code === 'object' && 'lang' in code) ? code.lang : lang;
5604
+ if ((codeLang || '').toLowerCase() === 'mermaid') {
5605
+ queueMermaidRender();
5606
+ return '<div class="mermaid-block"><pre class="mermaid">' + escapeHtml(codeText) + '</pre></div>';
5607
+ }
5608
+ var effectiveLang = codeLang && typeof window.hljs !== 'undefined' && window.hljs.getLanguage(codeLang) ? codeLang : '';
5609
+ var highlighted;
5610
+ if (typeof window.hljs !== 'undefined') {
5611
+ try {
5612
+ highlighted = effectiveLang
5613
+ ? window.hljs.highlight(codeText, { language: effectiveLang, ignoreIllegals: true }).value
5614
+ : window.hljs.highlightAuto(codeText).value;
5615
+ } catch (e) { highlighted = escapeHtml(codeText); }
5616
+ } else {
5617
+ highlighted = escapeHtml(codeText);
5502
5618
  }
5619
+ var langLabel = effectiveLang || (codeLang || '');
5620
+ var pill = langLabel ? '<span class="code-lang-pill">' + escapeHtml(langLabel) + '</span>' : '';
5621
+ var copyBtn = '<button class="code-copy-btn" onclick="copyCodeBlock(this)" title="Copy code">Copy</button>';
5622
+ return '<div class="code-block">' + pill + copyBtn + '<pre><code class="hljs' + (effectiveLang ? ' language-' + effectiveLang : '') + '">' + highlighted + '</code></pre></div>';
5623
+ };
5624
+ window.marked.use({ gfm: true, breaks: true, pedantic: false, renderer: renderer });
5625
+ }
5626
+
5627
+ var CALLOUT_MAP = {
5628
+ note: { icon: '\uD83D\uDCDD', cls: 'callout-note', label: 'Note' },
5629
+ info: { icon: '\u2139\uFE0F', cls: 'callout-info', label: 'Info' },
5630
+ tip: { icon: '\uD83D\uDCA1', cls: 'callout-tip', label: 'Tip' },
5631
+ success: { icon: '\u2705', cls: 'callout-success', label: 'Success' },
5632
+ warning: { icon: '\u26A0\uFE0F', cls: 'callout-warning', label: 'Warning' },
5633
+ danger: { icon: '\uD83D\uDEA8', cls: 'callout-danger', label: 'Danger' },
5634
+ question: { icon: '\u2753', cls: 'callout-question', label: 'Question' },
5635
+ summary: { icon: '\uD83D\uDCCB', cls: 'callout-summary', label: 'Summary' },
5636
+ example: { icon: '\uD83E\uDDEA', cls: 'callout-example', label: 'Example' },
5637
+ quote: { icon: '\uD83D\uDCAC', cls: 'callout-quote', label: 'Quote' },
5638
+ };
5639
+ var _calloutStore = {};
5503
5640
 
5504
- // Table detection
5505
- if (line.indexOf('|') !== -1 && line.trim().charAt(0) === '|') {
5506
- if (inList) { html += '</' + listType + '>'; inList = false; }
5507
- if (inBlockquote) { html += '</blockquote>'; inBlockquote = false; }
5508
- // Check if this is a separator row
5509
- if (/^\|[\s\-:|]+\|$/.test(line.trim())) {
5510
- // separator, skip but mark table as started
5511
- if (!inTable) inTable = true;
5512
- continue;
5641
+ function preprocessCallouts(text) {
5642
+ var lines = text.split('\n');
5643
+ var out = [];
5644
+ var i = 0;
5645
+ while (i < lines.length) {
5646
+ var m = lines[i].match(/^\s*>\s*\[!([a-z]+)\](-?)\s*(.*)$/i);
5647
+ if (m) {
5648
+ var typeKey = m[1].toLowerCase();
5649
+ var meta = CALLOUT_MAP[typeKey] || CALLOUT_MAP.note;
5650
+ var collapsible = m[2] === '-';
5651
+ var title = (m[3] || '').trim() || meta.label;
5652
+ var body = [];
5653
+ i++;
5654
+ while (i < lines.length && /^\s*>\s?/.test(lines[i])) {
5655
+ body.push(lines[i].replace(/^\s*>\s?/, ''));
5656
+ i++;
5513
5657
  }
5514
- inTable = true;
5515
- tableRows.push(line);
5516
- continue;
5517
- } else if (inTable) {
5518
- html += renderTable(tableRows);
5519
- inTable = false;
5520
- tableRows = [];
5521
- }
5522
-
5523
- // Headers
5524
- var hMatch = line.match(/^(#{1,4})\s+(.+)/);
5525
- if (hMatch) {
5526
- if (inList) { html += '</' + listType + '>'; inList = false; }
5527
- if (inBlockquote) { html += '</blockquote>'; inBlockquote = false; }
5528
- var level = hMatch[1].length;
5529
- html += '<h' + level + '>' + inlineMarkdown(hMatch[2]) + '</h' + level + '>';
5658
+ var bodyRaw = body.join('\n');
5659
+ var bodyHtml = bodyRaw ? window.marked.parse(bodyRaw) : '';
5660
+ var id = '__CALLOUT_' + Math.random().toString(36).slice(2, 10) + '__';
5661
+ var openTag = collapsible ? 'details' : 'div';
5662
+ var titleTag = collapsible ? 'summary' : 'div';
5663
+ var html = '<' + openTag + ' class="callout ' + meta.cls + (collapsible ? ' callout-collapsible' : '') + '"' + (collapsible ? ' open' : '') + '>' +
5664
+ '<' + titleTag + ' class="callout-title"><span class="callout-icon">' + meta.icon + '</span>' + escapeHtml(title) + '</' + titleTag + '>' +
5665
+ (bodyHtml ? '<div class="callout-body">' + bodyHtml + '</div>' : '') +
5666
+ '</' + openTag + '>';
5667
+ _calloutStore[id] = html;
5668
+ out.push(id);
5530
5669
  continue;
5531
5670
  }
5671
+ out.push(lines[i]);
5672
+ i++;
5673
+ }
5674
+ return out.join('\n');
5675
+ }
5676
+ function postprocessCallouts(html) {
5677
+ return html
5678
+ .replace(/<p>(__CALLOUT_[a-z0-9]+__)<\/p>/g, function(_, id) { return _calloutStore[id] || id; })
5679
+ .replace(/__CALLOUT_[a-z0-9]+__/g, function(id) { return _calloutStore[id] || id; });
5680
+ }
5532
5681
 
5533
- // Blockquote
5534
- if (line.match(/^>\s?(.*)$/)) {
5535
- if (inList) { html += '</' + listType + '>'; inList = false; }
5536
- var bqContent = line.replace(/^>\s?/, '');
5537
- if (!inBlockquote) { html += '<blockquote>'; inBlockquote = true; }
5538
- html += inlineMarkdown(bqContent) + '<br>';
5539
- continue;
5540
- } else if (inBlockquote) {
5541
- html += '</blockquote>';
5542
- inBlockquote = false;
5543
- }
5544
-
5545
- // Unordered list
5546
- if (line.match(/^[\s]*[-*+]\s+(.+)/)) {
5547
- if (!inList || listType !== 'ul') {
5548
- if (inList) html += '</' + listType + '>';
5549
- html += '<ul>';
5550
- inList = true;
5551
- listType = 'ul';
5552
- }
5553
- html += '<li>' + inlineMarkdown(line.replace(/^[\s]*[-*+]\s+/, '')) + '</li>';
5554
- continue;
5555
- }
5682
+ var _mathStore = {};
5683
+ function preprocessMath(text) {
5684
+ text = text.replace(/\$\$([\s\S]+?)\$\$/g, function(_, expr) {
5685
+ var id = '__MATHBLOCK_' + Math.random().toString(36).slice(2, 10) + '__';
5686
+ _mathStore[id] = { mode: 'block', expr: expr };
5687
+ queueKatexRender();
5688
+ return id;
5689
+ });
5690
+ text = text.replace(/(^|[^a-zA-Z0-9_$])\$([^\$\n]+?)\$(?=[^a-zA-Z0-9_$]|$)/g, function(match, pre, expr) {
5691
+ var id = '__MATHINLINE_' + Math.random().toString(36).slice(2, 10) + '__';
5692
+ _mathStore[id] = { mode: 'inline', expr: expr };
5693
+ queueKatexRender();
5694
+ return pre + id;
5695
+ });
5696
+ return text;
5697
+ }
5698
+ function renderMathPlaceholders(container) {
5699
+ if (typeof window.katex === 'undefined' || !container) return;
5700
+ var html = container.innerHTML;
5701
+ var next = html
5702
+ .replace(/__MATHBLOCK_[a-z0-9]+__/g, function(id) {
5703
+ var entry = _mathStore[id]; if (!entry) return id;
5704
+ try { return window.katex.renderToString(entry.expr, { displayMode: true, throwOnError: false }); } catch (e) { return id; }
5705
+ })
5706
+ .replace(/__MATHINLINE_[a-z0-9]+__/g, function(id) {
5707
+ var entry = _mathStore[id]; if (!entry) return id;
5708
+ try { return window.katex.renderToString(entry.expr, { displayMode: false, throwOnError: false }); } catch (e) { return id; }
5709
+ });
5710
+ if (next !== html) container.innerHTML = next;
5711
+ }
5556
5712
 
5557
- // Ordered list
5558
- if (line.match(/^[\s]*\d+\.\s+(.+)/)) {
5559
- if (!inList || listType !== 'ol') {
5560
- if (inList) html += '</' + listType + '>';
5561
- html += '<ol>';
5562
- inList = true;
5563
- listType = 'ol';
5713
+ function loadScriptOnce(src) {
5714
+ return new Promise(function(resolve, reject) {
5715
+ var existing = document.querySelector('script[data-vsrc="' + src + '"]');
5716
+ if (existing) { if (existing.dataset.loaded === '1') return resolve(); existing.addEventListener('load', function(){ resolve(); }); return; }
5717
+ var s = document.createElement('script'); s.src = src; s.async = true; s.dataset.vsrc = src;
5718
+ s.onload = function(){ s.dataset.loaded = '1'; resolve(); };
5719
+ s.onerror = function(){ reject(new Error('failed to load ' + src)); };
5720
+ document.head.appendChild(s);
5721
+ });
5722
+ }
5723
+ function loadStyleOnce(href) {
5724
+ if (document.querySelector('link[data-vhref="' + href + '"]')) return;
5725
+ var l = document.createElement('link'); l.rel = 'stylesheet'; l.href = href; l.dataset.vhref = href;
5726
+ document.head.appendChild(l);
5727
+ }
5728
+ var _mermaidRenderScheduled = false;
5729
+ function queueMermaidRender() {
5730
+ if (_mermaidRenderScheduled) return;
5731
+ _mermaidRenderScheduled = true;
5732
+ setTimeout(function() {
5733
+ _mermaidRenderScheduled = false;
5734
+ loadScriptOnce('/vendor/mermaid.min.js').then(function() {
5735
+ if (!_mermaidLoaded && typeof window.mermaid !== 'undefined') {
5736
+ window.mermaid.initialize({ startOnLoad: false, theme: 'dark', securityLevel: 'strict' });
5737
+ _mermaidLoaded = true;
5564
5738
  }
5565
- html += '<li>' + inlineMarkdown(line.replace(/^[\s]*\d+\.\s+/, '')) + '</li>';
5566
- continue;
5567
- }
5568
-
5569
- // Close list if line doesn't match
5570
- if (inList && line.trim() === '') {
5571
- html += '</' + listType + '>';
5572
- inList = false;
5573
- }
5574
-
5575
- // Empty line
5576
- if (line.trim() === '') {
5577
- continue;
5578
- }
5579
-
5580
- // Regular paragraph
5581
- if (inList) { html += '</' + listType + '>'; inList = false; }
5582
- html += '<p>' + inlineMarkdown(line) + '</p>';
5739
+ if (typeof window.mermaid !== 'undefined') {
5740
+ try { window.mermaid.run({ querySelector: '.mermaid:not([data-processed="true"])' }); } catch (e) {}
5741
+ }
5742
+ }).catch(function(){});
5743
+ }, 50);
5744
+ }
5745
+ var _katexRenderScheduled = false;
5746
+ function queueKatexRender() {
5747
+ if (_katexRenderScheduled) return;
5748
+ _katexRenderScheduled = true;
5749
+ setTimeout(function() {
5750
+ _katexRenderScheduled = false;
5751
+ loadStyleOnce('/vendor/katex.min.css');
5752
+ loadScriptOnce('/vendor/katex.min.js').then(function() {
5753
+ document.querySelectorAll('.msg-content, .ws-entry-content').forEach(function(el) { renderMathPlaceholders(el); });
5754
+ }).catch(function(){});
5755
+ }, 50);
5756
+ }
5757
+
5758
+ function copyCodeBlock(btn) {
5759
+ var codeEl = btn.parentElement.querySelector('pre code');
5760
+ if (!codeEl) return;
5761
+ var text = codeEl.innerText;
5762
+ if (navigator.clipboard && navigator.clipboard.writeText) {
5763
+ navigator.clipboard.writeText(text).then(function() {
5764
+ var old = btn.textContent; btn.textContent = 'Copied!'; btn.classList.add('copied');
5765
+ setTimeout(function(){ btn.textContent = old; btn.classList.remove('copied'); }, 1200);
5766
+ });
5583
5767
  }
5584
-
5585
- // Close any open elements
5586
- if (inList) html += '</' + listType + '>';
5587
- if (inTable) html += renderTable(tableRows);
5588
- if (inBlockquote) html += '</blockquote>';
5589
-
5590
- return html;
5591
5768
  }
5592
5769
 
5593
- function inlineMarkdown(text) {
5594
- var html = escapeHtml(text);
5595
- // Inline code (must be before bold/italic to avoid conflicts)
5596
- html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
5597
- // Bold + italic
5598
- html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
5599
- // Bold
5600
- html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
5601
- // Italic
5602
- html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
5603
- // Strikethrough
5604
- html = html.replace(/~~(.+?)~~/g, '<del>$1</del>');
5770
+ function renderMarkdown(text) {
5771
+ if (typeof text !== 'string' || !text) return '';
5772
+ text = text.replace(/\x00/g, '');
5773
+ if (typeof window.marked === 'undefined') return escapeHtml(text).replace(/\n/g, '<br>');
5774
+ configureMarked();
5775
+ text = preprocessMath(text);
5776
+ text = preprocessCallouts(text);
5777
+ var html;
5778
+ try { html = window.marked.parse(text); } catch (e) { html = escapeHtml(text).replace(/\n/g, '<br>'); }
5779
+ html = postprocessCallouts(html);
5780
+ if (html.indexOf('class="mermaid"') !== -1) queueMermaidRender();
5605
5781
  return html;
5606
5782
  }
5607
5783
 
5608
- function renderTable(rows) {
5609
- if (rows.length === 0) return '';
5610
- var html = '<table>';
5611
- for (var i = 0; i < rows.length; i++) {
5612
- var cells = rows[i].split('|').filter(function(c, idx, arr) {
5613
- return idx > 0 && idx < arr.length - 1;
5614
- });
5615
- var tag = (i === 0) ? 'th' : 'td';
5616
- html += '<tr>';
5617
- for (var j = 0; j < cells.length; j++) {
5618
- html += '<' + tag + '>' + inlineMarkdown(cells[j].trim()) + '</' + tag + '>';
5619
- }
5620
- html += '</tr>';
5784
+ // Click-to-expand image lightbox (delegated on document)
5785
+ document.addEventListener('click', function(e) {
5786
+ var t = e.target;
5787
+ if (t && t.tagName === 'IMG' && t.closest && t.closest('.msg-content')) {
5788
+ var overlay = document.createElement('div');
5789
+ overlay.className = 'image-lightbox';
5790
+ overlay.addEventListener('click', function(){ overlay.remove(); });
5791
+ var img = document.createElement('img'); img.src = t.src; overlay.appendChild(img);
5792
+ document.body.appendChild(overlay);
5621
5793
  }
5622
- html += '</table>';
5623
- return html;
5624
- }
5794
+ }, true);
5625
5795
 
5626
5796
  // ==================== AGENT MONITORING ====================
5627
5797
 
5798
+
5628
5799
  var PROVIDER_COLORS = { claude: '#d97706', anthropic: '#d97706', openai: '#10b981', gemini: '#3b82f6', google: '#3b82f6' };
5629
5800
 
5630
5801
  function getProviderBadge(provider) {