@luckydraw/cumulus 0.28.3 → 0.28.4

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.
@@ -1226,6 +1226,10 @@
1226
1226
  // WeakMap: DOM element -> BlockHandle[] for lifecycle management
1227
1227
  var blexHandles = typeof WeakMap !== 'undefined' ? new WeakMap() : null;
1228
1228
 
1229
+ // Per-thread store for blex interaction values (poll answers, confirm clicks, etc.)
1230
+ // Key: "threadName" → Map<"msgTimestamp:blockIdx", interactionValue>
1231
+ var blexInteractionStore = {};
1232
+
1229
1233
  // Extract ~~~blex:TYPE\n{json}\n~~~ fences from text
1230
1234
  // Returns { text: string (with placeholders), blocks: Array<{type, json, idx}> }
1231
1235
  function extractBlexBlocks(text) {
@@ -1240,18 +1244,23 @@
1240
1244
  }
1241
1245
 
1242
1246
  // Render blex blocks into placeholder divs within a message element
1243
- function renderBlexBlocks(el, blexBlocks, isStreaming) {
1247
+ function renderBlexBlocks(el, blexBlocks, isStreaming, msgKey, threadName) {
1244
1248
  if (!blexBlocks || blexBlocks.length === 0) return;
1245
1249
  if (typeof Blex === 'undefined') {
1246
1250
  console.warn('[blex] Blex library not loaded — blex.min.js may have failed to fetch');
1247
1251
  return;
1248
1252
  }
1249
1253
  var placeholders = el.querySelectorAll('.blex-block-container');
1254
+ // Get the interaction store for this thread
1255
+ var store = threadName
1256
+ ? blexInteractionStore[threadName] || (blexInteractionStore[threadName] = {})
1257
+ : null;
1250
1258
 
1251
1259
  placeholders.forEach(function (container) {
1252
1260
  var idx = parseInt(container.getAttribute('data-blex-idx'), 10);
1253
1261
  if (isNaN(idx) || idx >= blexBlocks.length) return;
1254
1262
  var block = blexBlocks[idx];
1263
+ var storeKey = msgKey ? msgKey + ':' + idx : null;
1255
1264
 
1256
1265
  try {
1257
1266
  var data = JSON.parse(block.json);
@@ -1261,8 +1270,14 @@
1261
1270
  // During streaming, show placeholder skeleton
1262
1271
  Blex.renderPlaceholder(block.type, container);
1263
1272
  } else {
1273
+ // Check for previously stored interaction value
1274
+ var renderOpts = {};
1275
+ if (store && storeKey && store[storeKey]) {
1276
+ renderOpts.previousValue = store[storeKey];
1277
+ }
1278
+
1264
1279
  // Final render — full interactive block
1265
- Blex.renderBlock(blockObj, container)
1280
+ Blex.renderBlock(blockObj, container, renderOpts)
1266
1281
  .then(function (handle) {
1267
1282
  // Store handle immediately (inside async callback, not after sync loop)
1268
1283
  if (blexHandles) {
@@ -1273,6 +1288,11 @@
1273
1288
  if (handle && handle.onInteraction) {
1274
1289
  // Wire interaction handler
1275
1290
  handle.onInteraction(function (interaction) {
1291
+ // Store the interaction value for persistence across re-renders
1292
+ if (store && storeKey && interaction.value !== undefined) {
1293
+ store[storeKey] = interaction.value;
1294
+ }
1295
+
1276
1296
  // Find the panel — try el.closest first, fall back to document query
1277
1297
  // (el may be detached from DOM if renderPanelMessages rebuilt the message list)
1278
1298
  var panel = el.closest('.cumulus-thread-panel') || el.closest('.cumulus-panel');
@@ -1287,8 +1307,11 @@
1287
1307
 
1288
1308
  if (inputEl && interaction.serialized) {
1289
1309
  if (interaction.immediate) {
1290
- // Immediate: inject into textarea and auto-send
1291
- inputEl.value = interaction.serialized;
1310
+ // Immediate: prepend any existing input text, then auto-send
1311
+ var existingText = inputEl.value.trim();
1312
+ inputEl.value = existingText
1313
+ ? existingText + '\n\n' + interaction.serialized
1314
+ : interaction.serialized;
1292
1315
  inputEl.dispatchEvent(new Event('input', { bubbles: true }));
1293
1316
  // Find and click the send button
1294
1317
  var sendBtn = panel ? panel.querySelector('[data-testid*="send"]') : null;
@@ -1389,14 +1412,25 @@
1389
1412
  // ── Inline Annotations (Google Docs-style highlight + comment) ──────────
1390
1413
 
1391
1414
  // Add an annotation chip to the chip tray
1392
- function addAnnotationChip(panel, quote, comment) {
1415
+ function addAnnotationChip(panel, quote, comment, isCodeBlock, codeLang) {
1393
1416
  if (!panel) return;
1394
1417
  var tray = panel.querySelector('.cumulus-chip-tray');
1395
1418
  if (!tray) return;
1396
1419
 
1397
1420
  var truncatedQuote = quote.length > 30 ? quote.substring(0, 30) + '\u2026' : quote;
1398
1421
  var truncatedComment = comment.length > 30 ? comment.substring(0, 30) + '\u2026' : comment;
1399
- var serialized = '> ' + quote.replace(/\n/g, '\n> ') + '\n' + comment;
1422
+ var serialized;
1423
+ if (isCodeBlock) {
1424
+ serialized =
1425
+ '```' +
1426
+ (codeLang && codeLang !== 'text' ? codeLang : '') +
1427
+ '\n' +
1428
+ quote +
1429
+ '\n```\n' +
1430
+ comment;
1431
+ } else {
1432
+ serialized = '> ' + quote.replace(/\n/g, '\n> ') + '\n' + comment;
1433
+ }
1400
1434
 
1401
1435
  var chip = document.createElement('span');
1402
1436
  chip.className = 'cumulus-annotation-chip';
@@ -1420,7 +1454,7 @@
1420
1454
  }
1421
1455
 
1422
1456
  // Show the annotation popover near a text selection
1423
- function showAnnotationPopover(panel, selectedText, anchorRect) {
1457
+ function showAnnotationPopover(panel, selectedText, anchorRect, isCodeBlock, codeLang) {
1424
1458
  // Remove any existing popover
1425
1459
  dismissAnnotationPopover(panel);
1426
1460
 
@@ -1469,7 +1503,7 @@
1469
1503
  submitBtn.addEventListener('click', function () {
1470
1504
  var comment = textarea.value.trim();
1471
1505
  if (comment) {
1472
- addAnnotationChip(panel, selectedText, comment);
1506
+ addAnnotationChip(panel, selectedText, comment, isCodeBlock, codeLang);
1473
1507
  }
1474
1508
  dismissAnnotationPopover(panel);
1475
1509
  window.getSelection().removeAllRanges();
@@ -1519,10 +1553,22 @@
1519
1553
  var msgEl = container.closest ? container.closest('.cumulus-msg.assistant') : null;
1520
1554
  if (!msgEl) return;
1521
1555
 
1556
+ // Detect if selection is inside a code block
1557
+ var codeAncestor = container.closest ? container.closest('pre, code') : null;
1558
+ var isCodeBlock = !!codeAncestor;
1559
+ var codeLang = '';
1560
+ if (isCodeBlock) {
1561
+ var wrapper = container.closest ? container.closest('.code-block-wrapper') : null;
1562
+ if (wrapper) {
1563
+ var langEl = wrapper.querySelector('.code-block-language');
1564
+ if (langEl) codeLang = langEl.textContent.trim();
1565
+ }
1566
+ }
1567
+
1522
1568
  // Get anchor position for the popover
1523
1569
  var rect = range.getBoundingClientRect();
1524
1570
 
1525
- showAnnotationPopover(panel, selectedText, rect);
1571
+ showAnnotationPopover(panel, selectedText, rect, isCodeBlock, codeLang);
1526
1572
  }, 10);
1527
1573
  });
1528
1574
 
@@ -2168,7 +2214,8 @@
2168
2214
  function buildUserMsgEl(msg) {
2169
2215
  var el = document.createElement('div');
2170
2216
  el.className = 'cumulus-msg user' + (isWideUserMessage(msg.content) ? ' wide' : '');
2171
- el.textContent = msg.content;
2217
+ var mdResult = renderMarkdown(msg.content);
2218
+ el.innerHTML = mdResult.html;
2172
2219
  if (msg.attachments && msg.attachments.length > 0) {
2173
2220
  var attRow = document.createElement('div');
2174
2221
  attRow.className = 'cumulus-msg-attachments';
@@ -2193,7 +2240,7 @@
2193
2240
  return el;
2194
2241
  }
2195
2242
 
2196
- function buildAssistantMsgEl(content, isStreaming) {
2243
+ function buildAssistantMsgEl(content, isStreaming, msgKey) {
2197
2244
  var el = document.createElement('div');
2198
2245
  el.className = 'cumulus-msg assistant';
2199
2246
  if (isStreaming) el.setAttribute('data-testid', 'webchat-streaming');
@@ -2205,7 +2252,7 @@
2205
2252
  }
2206
2253
  // Render blex blocks — skip during streaming to avoid destroy/recreate churn
2207
2254
  if (!isStreaming) {
2208
- renderBlexBlocks(el, mdResult.blexBlocks, false);
2255
+ renderBlexBlocks(el, mdResult.blexBlocks, false, msgKey, threadName);
2209
2256
  }
2210
2257
  el.querySelectorAll('.code-block-copy-btn').forEach(function (btn) {
2211
2258
  btn.addEventListener('click', function () {
@@ -2296,7 +2343,7 @@
2296
2343
  row.appendChild(buildUserMsgEl(msg));
2297
2344
  }
2298
2345
  } else {
2299
- row.appendChild(buildAssistantMsgEl(msg.content, false));
2346
+ row.appendChild(buildAssistantMsgEl(msg.content, false, String(msg.timestamp)));
2300
2347
  }
2301
2348
  appendTimestamp(row, msg.timestamp);
2302
2349
  messagesEl.appendChild(row);
@@ -3733,7 +3780,8 @@
3733
3780
  function buildUserMsgEl(msg) {
3734
3781
  var el = document.createElement('div');
3735
3782
  el.className = 'cumulus-msg user' + (isWideUserMessage(msg.content) ? ' wide' : '');
3736
- el.textContent = msg.content;
3783
+ var mdResult = renderMarkdown(msg.content);
3784
+ el.innerHTML = mdResult.html;
3737
3785
  if (msg.attachments && msg.attachments.length > 0) {
3738
3786
  var attRow = document.createElement('div');
3739
3787
  attRow.className = 'cumulus-msg-attachments';
@@ -3758,7 +3806,7 @@
3758
3806
  return el;
3759
3807
  }
3760
3808
 
3761
- function buildAssistantMsgEl(content, isStreaming) {
3809
+ function buildAssistantMsgEl(content, isStreaming, msgKey) {
3762
3810
  var el = document.createElement('div');
3763
3811
  el.className = 'cumulus-msg assistant';
3764
3812
  if (isStreaming) el.setAttribute('data-testid', 'webchat-streaming');
@@ -3770,7 +3818,7 @@
3770
3818
  }
3771
3819
  // Render blex blocks — skip during streaming to avoid destroy/recreate churn
3772
3820
  if (!isStreaming) {
3773
- renderBlexBlocks(el, mdResult.blexBlocks, false);
3821
+ renderBlexBlocks(el, mdResult.blexBlocks, false, msgKey, threadName);
3774
3822
  }
3775
3823
  el.querySelectorAll('.code-block-copy-btn').forEach(function (btn) {
3776
3824
  btn.addEventListener('click', function () {
@@ -3853,7 +3901,7 @@
3853
3901
  row.appendChild(buildUserMsgEl(msg));
3854
3902
  }
3855
3903
  } else {
3856
- row.appendChild(buildAssistantMsgEl(msg.content, false));
3904
+ row.appendChild(buildAssistantMsgEl(msg.content, false, String(msg.timestamp)));
3857
3905
  }
3858
3906
  appendTimestamp(row, msg.timestamp);
3859
3907
  messagesEl.appendChild(row);
@@ -4129,13 +4177,15 @@
4129
4177
  return;
4130
4178
  }
4131
4179
 
4132
- // Freeze the partial assistant response as a message
4180
+ // Freeze the partial assistant response as a message so the interjection
4181
+ // appears AFTER the (partial) LLM response, not next to the prior user message
4133
4182
  if (state.streamBuffer) {
4134
4183
  state.messages.push({ role: 'assistant', content: state.streamBuffer });
4135
4184
  }
4136
4185
  // Reset streaming state — the server will send 'interjected' for the old stream
4137
4186
  state.streamBuffer = '';
4138
4187
  state.interjecting = true;
4188
+ state.interjectionStreamId = (state.interjectionStreamId || 0) + 1;
4139
4189
  // Don't set streaming=false — the new message will keep streaming
4140
4190
  }
4141
4191
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luckydraw/cumulus",
3
- "version": "0.28.3",
3
+ "version": "0.28.4",
4
4
  "description": "RLM-based CLI chat wrapper for Claude with external history context management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",