@luckydraw/cumulus 0.28.4 → 0.28.5

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.
@@ -3,6 +3,11 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
6
+ <meta name="apple-mobile-web-app-capable" content="yes">
7
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
8
+ <meta name="apple-mobile-web-app-title" content="Cumulus">
9
+ <link rel="manifest" href="/manifest.json">
10
+ <link rel="apple-touch-icon" href="/icon-192.png">
6
11
  <title>Cumulus Chat</title>
7
12
  <style>
8
13
  *, *::before, *::after { box-sizing: border-box; }
Binary file
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 192 192">
2
+ <rect width="192" height="192" rx="32" fill="#2d2d2d"/>
3
+ <text x="96" y="120" font-family="system-ui, -apple-system, sans-serif" font-size="100" font-weight="700" fill="#e0e0e0" text-anchor="middle">C</text>
4
+ </svg>
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "Cumulus Chat",
3
+ "short_name": "Cumulus",
4
+ "start_url": "/chat",
5
+ "display": "standalone",
6
+ "background_color": "#1e1e1e",
7
+ "theme_color": "#1e1e1e",
8
+ "icons": [
9
+ {
10
+ "src": "/icon-192.png",
11
+ "sizes": "192x192",
12
+ "type": "image/png"
13
+ }
14
+ ]
15
+ }
@@ -234,10 +234,13 @@
234
234
  ' display: inline-flex; align-items: center; gap: 0.3em;',
235
235
  ' background: #2a3a4a; color: #d4e8f8; border: 1px solid #3a5060;',
236
236
  ' border-radius: 1em; padding: 0.15em 0.6em; font-size: 0.82em;',
237
- ' cursor: default; max-width: 32ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;',
237
+ ' cursor: default; max-width: 32ch;',
238
+ '}',
239
+ '.cumulus-interaction-chip .chip-label {',
240
+ ' overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;',
238
241
  '}',
239
242
  '.cumulus-interaction-chip .chip-dismiss {',
240
- ' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em;',
243
+ ' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em; flex-shrink: 0;',
241
244
  '}',
242
245
  '.cumulus-interaction-chip .chip-dismiss:hover { opacity: 1; }',
243
246
 
@@ -246,10 +249,13 @@
246
249
  ' display: inline-flex; align-items: center; gap: 0.3em;',
247
250
  ' background: #2a3a2a; color: #b8e6b8; border: 1px solid #3a6040;',
248
251
  ' border-radius: 1em; padding: 0.15em 0.6em; font-size: 0.82em;',
249
- ' cursor: default; max-width: 40ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;',
252
+ ' cursor: default; max-width: 40ch;',
253
+ '}',
254
+ '.cumulus-annotation-chip .chip-label {',
255
+ ' overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;',
250
256
  '}',
251
257
  '.cumulus-annotation-chip .chip-dismiss {',
252
- ' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em;',
258
+ ' cursor: pointer; opacity: 0.6; font-size: 0.9em; margin-left: 0.2em; flex-shrink: 0;',
253
259
  '}',
254
260
  '.cumulus-annotation-chip .chip-dismiss:hover { opacity: 1; }',
255
261
 
@@ -1228,7 +1234,23 @@
1228
1234
 
1229
1235
  // Per-thread store for blex interaction values (poll answers, confirm clicks, etc.)
1230
1236
  // Key: "threadName" → Map<"msgTimestamp:blockIdx", interactionValue>
1237
+ // Backed by localStorage for persistence across page refreshes
1238
+ var BLEX_STORE_KEY = 'cumulus-blex-interactions';
1231
1239
  var blexInteractionStore = {};
1240
+ try {
1241
+ var saved = localStorage.getItem(BLEX_STORE_KEY);
1242
+ if (saved) blexInteractionStore = JSON.parse(saved);
1243
+ } catch (e) {
1244
+ /* ignore corrupt/missing localStorage */
1245
+ }
1246
+
1247
+ function _flushBlexStore() {
1248
+ try {
1249
+ localStorage.setItem(BLEX_STORE_KEY, JSON.stringify(blexInteractionStore));
1250
+ } catch (e) {
1251
+ /* quota exceeded or private browsing */
1252
+ }
1253
+ }
1232
1254
 
1233
1255
  // Extract ~~~blex:TYPE\n{json}\n~~~ fences from text
1234
1256
  // Returns { text: string (with placeholders), blocks: Array<{type, json, idx}> }
@@ -1288,9 +1310,10 @@
1288
1310
  if (handle && handle.onInteraction) {
1289
1311
  // Wire interaction handler
1290
1312
  handle.onInteraction(function (interaction) {
1291
- // Store the interaction value for persistence across re-renders
1313
+ // Store the interaction value for persistence across re-renders + refreshes
1292
1314
  if (store && storeKey && interaction.value !== undefined) {
1293
1315
  store[storeKey] = interaction.value;
1316
+ _flushBlexStore();
1294
1317
  }
1295
1318
 
1296
1319
  // Find the panel — try el.closest first, fall back to document query
@@ -1319,8 +1342,8 @@
1319
1342
  sendBtn.click();
1320
1343
  }
1321
1344
  } else {
1322
- // Deferred: add to chip tray
1323
- addInteractionChip(panel, interaction, handle);
1345
+ // Deferred: add to chip tray (sourceId deduplicates rapid-fire from same block)
1346
+ addInteractionChip(panel, interaction, handle, block.type + '-' + idx);
1324
1347
  }
1325
1348
  }
1326
1349
  });
@@ -1360,16 +1383,27 @@
1360
1383
  }
1361
1384
 
1362
1385
  // Add a deferred interaction chip to the chip tray
1363
- function addInteractionChip(panel, interaction, handle) {
1386
+ // If a chip from the same source already exists, replace it (prevents spam from rapid interactions)
1387
+ function addInteractionChip(panel, interaction, handle, sourceId) {
1364
1388
  if (!panel) return;
1365
1389
  var tray = panel.querySelector('.cumulus-chip-tray');
1366
1390
  if (!tray) return;
1367
1391
 
1392
+ // Replace existing chip from same source instead of appending
1393
+ if (sourceId) {
1394
+ var existing = tray.querySelector(
1395
+ '.cumulus-interaction-chip[data-source="' + sourceId + '"]'
1396
+ );
1397
+ if (existing) existing.remove();
1398
+ }
1399
+
1368
1400
  var chip = document.createElement('span');
1369
1401
  chip.className = 'cumulus-interaction-chip';
1370
1402
  chip.setAttribute('data-serialized', interaction.serialized || '');
1403
+ if (sourceId) chip.setAttribute('data-source', sourceId);
1371
1404
 
1372
1405
  var label = document.createElement('span');
1406
+ label.className = 'chip-label';
1373
1407
  label.textContent =
1374
1408
  (interaction.icon || '\u2022') +
1375
1409
  ' ' +
@@ -1435,17 +1469,30 @@
1435
1469
  var chip = document.createElement('span');
1436
1470
  chip.className = 'cumulus-annotation-chip';
1437
1471
  chip.setAttribute('data-serialized', serialized);
1472
+ chip.setAttribute('data-quote', quote);
1473
+ chip.setAttribute('data-comment', comment);
1474
+ chip.setAttribute('data-is-code', isCodeBlock ? '1' : '');
1475
+ chip.setAttribute('data-code-lang', codeLang || '');
1438
1476
  chip.title = '"' + quote + '"\n\n' + comment;
1439
1477
 
1440
1478
  var label = document.createElement('span');
1479
+ label.className = 'chip-label';
1441
1480
  label.textContent =
1442
1481
  '\uD83D\uDCDD \u201C' + truncatedQuote + '\u201D \u2192 ' + truncatedComment;
1443
1482
  chip.appendChild(label);
1444
1483
 
1484
+ // Click label to edit
1485
+ label.style.cursor = 'pointer';
1486
+ label.addEventListener('click', function (e) {
1487
+ e.stopPropagation();
1488
+ showAnnotationEditPopover(panel, chip);
1489
+ });
1490
+
1445
1491
  var dismiss = document.createElement('span');
1446
1492
  dismiss.className = 'chip-dismiss';
1447
1493
  dismiss.textContent = '\u00d7';
1448
- dismiss.addEventListener('click', function () {
1494
+ dismiss.addEventListener('click', function (e) {
1495
+ e.stopPropagation();
1449
1496
  chip.remove();
1450
1497
  });
1451
1498
  chip.appendChild(dismiss);
@@ -1453,6 +1500,104 @@
1453
1500
  tray.appendChild(chip);
1454
1501
  }
1455
1502
 
1503
+ // Show the annotation popover near a chip for editing
1504
+ function showAnnotationEditPopover(panel, chip) {
1505
+ dismissAnnotationPopover(panel);
1506
+
1507
+ var quote = chip.getAttribute('data-quote') || '';
1508
+ var comment = chip.getAttribute('data-comment') || '';
1509
+ var isCodeBlock = chip.getAttribute('data-is-code') === '1';
1510
+ var codeLang = chip.getAttribute('data-code-lang') || '';
1511
+
1512
+ var popover = document.createElement('div');
1513
+ popover.className = 'cumulus-annotation-popover';
1514
+
1515
+ // Position near the chip
1516
+ var panelRect = panel.getBoundingClientRect();
1517
+ var chipRect = chip.getBoundingClientRect();
1518
+ var top = chipRect.top - panelRect.top + panel.scrollTop - 160;
1519
+ if (top < 4) top = chipRect.bottom - panelRect.top + panel.scrollTop + 4;
1520
+ var left = chipRect.left - panelRect.left;
1521
+ if (left + 260 > panelRect.width) left = panelRect.width - 270;
1522
+ if (left < 4) left = 4;
1523
+ popover.style.top = top + 'px';
1524
+ popover.style.left = left + 'px';
1525
+
1526
+ var quoteEl = document.createElement('div');
1527
+ quoteEl.className = 'annotation-quote';
1528
+ quoteEl.textContent = '\u201C' + quote + '\u201D';
1529
+ popover.appendChild(quoteEl);
1530
+
1531
+ var textarea = document.createElement('textarea');
1532
+ textarea.placeholder = 'Edit comment\u2026';
1533
+ textarea.rows = 4;
1534
+ textarea.value = comment;
1535
+ textarea.setAttribute('data-testid', 'annotation-edit-comment');
1536
+ popover.appendChild(textarea);
1537
+
1538
+ var actions = document.createElement('div');
1539
+ actions.className = 'annotation-actions';
1540
+
1541
+ var cancelBtn = document.createElement('button');
1542
+ cancelBtn.className = 'annotation-cancel';
1543
+ cancelBtn.textContent = 'Cancel';
1544
+ cancelBtn.addEventListener('click', function () {
1545
+ dismissAnnotationPopover(panel);
1546
+ });
1547
+
1548
+ var submitBtn = document.createElement('button');
1549
+ submitBtn.className = 'annotation-submit';
1550
+ submitBtn.textContent = 'Update';
1551
+ submitBtn.setAttribute('data-testid', 'annotation-edit-submit');
1552
+ submitBtn.addEventListener('click', function () {
1553
+ var newComment = textarea.value.trim();
1554
+ if (!newComment) {
1555
+ dismissAnnotationPopover(panel);
1556
+ return;
1557
+ }
1558
+ // Update chip data
1559
+ var newSerialized;
1560
+ if (isCodeBlock) {
1561
+ newSerialized =
1562
+ '```' +
1563
+ (codeLang && codeLang !== 'text' ? codeLang : '') +
1564
+ '\n' +
1565
+ quote +
1566
+ '\n```\n' +
1567
+ newComment;
1568
+ } else {
1569
+ newSerialized = '> ' + quote.replace(/\n/g, '\n> ') + '\n' + newComment;
1570
+ }
1571
+ chip.setAttribute('data-serialized', newSerialized);
1572
+ chip.setAttribute('data-comment', newComment);
1573
+ var truncQ = quote.length > 30 ? quote.substring(0, 30) + '\u2026' : quote;
1574
+ var truncC = newComment.length > 30 ? newComment.substring(0, 30) + '\u2026' : newComment;
1575
+ var labelEl = chip.querySelector('.chip-label');
1576
+ if (labelEl) {
1577
+ labelEl.textContent = '\uD83D\uDCDD \u201C' + truncQ + '\u201D \u2192 ' + truncC;
1578
+ }
1579
+ chip.title = '"' + quote + '"\n\n' + newComment;
1580
+ dismissAnnotationPopover(panel);
1581
+ });
1582
+
1583
+ actions.appendChild(cancelBtn);
1584
+ actions.appendChild(submitBtn);
1585
+ popover.appendChild(actions);
1586
+
1587
+ textarea.addEventListener('keydown', function (e) {
1588
+ if (e.key === 'Enter' && !e.shiftKey) {
1589
+ e.preventDefault();
1590
+ submitBtn.click();
1591
+ } else if (e.key === 'Escape') {
1592
+ dismissAnnotationPopover(panel);
1593
+ }
1594
+ });
1595
+
1596
+ panel.appendChild(popover);
1597
+ textarea.focus();
1598
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length);
1599
+ }
1600
+
1456
1601
  // Show the annotation popover near a text selection
1457
1602
  function showAnnotationPopover(panel, selectedText, anchorRect, isCodeBlock, codeLang) {
1458
1603
  // Remove any existing popover
@@ -4587,55 +4732,72 @@
4587
4732
  // ── Push notification subscription ──
4588
4733
  function registerPushNotifications() {
4589
4734
  if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
4590
- console.log('[Cumulus] Push notifications not supported');
4735
+ console.log('[Cumulus] Push notifications not supported in this browser');
4591
4736
  return;
4592
4737
  }
4593
4738
 
4594
- navigator.serviceWorker
4595
- .register('/sw.js')
4596
- .then(function (registration) {
4597
- console.log('[Cumulus] Service worker registered');
4598
-
4599
- // Check existing subscription
4600
- return registration.pushManager.getSubscription().then(function (existing) {
4601
- if (existing) {
4602
- // Already subscribed — send to server in case it's new/different
4603
- sendPushSubscription(existing);
4604
- return;
4605
- }
4739
+ if (!('Notification' in window)) {
4740
+ console.log('[Cumulus] Notification API not available');
4741
+ return;
4742
+ }
4606
4743
 
4607
- // Get VAPID key from server
4608
- var loc = window.location;
4609
- var apiUrl = loc.protocol + '//' + loc.host;
4610
- fetch(apiUrl + '/api/push/vapid-key')
4611
- .then(function (r) {
4612
- return r.json();
4613
- })
4614
- .then(function (data) {
4615
- if (!data.publicKey) {
4616
- console.log('[Cumulus] Push not configured on server');
4744
+ // Must request permission explicitly (required for iOS PWA)
4745
+ Notification.requestPermission().then(function (permission) {
4746
+ console.log('[Cumulus] Notification permission:', permission);
4747
+ if (permission !== 'granted') {
4748
+ console.log('[Cumulus] Push notifications denied by user');
4749
+ return;
4750
+ }
4751
+
4752
+ navigator.serviceWorker
4753
+ .register('/sw.js', { scope: '/' })
4754
+ .then(function (registration) {
4755
+ console.log('[Cumulus] Service worker registered, scope:', registration.scope);
4756
+
4757
+ // Wait for the service worker to be ready
4758
+ return navigator.serviceWorker.ready.then(function (reg) {
4759
+ // Check existing subscription
4760
+ return reg.pushManager.getSubscription().then(function (existing) {
4761
+ if (existing) {
4762
+ console.log('[Cumulus] Existing push subscription found');
4763
+ sendPushSubscription(existing);
4617
4764
  return;
4618
4765
  }
4619
4766
 
4620
- // Request permission and subscribe
4621
- return registration.pushManager
4622
- .subscribe({
4623
- userVisibleOnly: true,
4624
- applicationServerKey: urlBase64ToUint8Array(data.publicKey),
4767
+ // Get VAPID key from server
4768
+ var loc = window.location;
4769
+ var apiUrl = loc.protocol + '//' + loc.host;
4770
+ fetch(apiUrl + '/api/push/vapid-key')
4771
+ .then(function (r) {
4772
+ return r.json();
4625
4773
  })
4626
- .then(function (subscription) {
4627
- console.log('[Cumulus] Push subscription created');
4628
- sendPushSubscription(subscription);
4774
+ .then(function (data) {
4775
+ if (!data.publicKey) {
4776
+ console.log('[Cumulus] Push not configured on server (no VAPID key)');
4777
+ return;
4778
+ }
4779
+
4780
+ console.log('[Cumulus] Subscribing to push with VAPID key');
4781
+ return reg.pushManager
4782
+ .subscribe({
4783
+ userVisibleOnly: true,
4784
+ applicationServerKey: urlBase64ToUint8Array(data.publicKey),
4785
+ })
4786
+ .then(function (subscription) {
4787
+ console.log('[Cumulus] Push subscription created:', subscription.endpoint);
4788
+ sendPushSubscription(subscription);
4789
+ });
4790
+ })
4791
+ .catch(function (err) {
4792
+ console.warn('[Cumulus] Push subscription failed:', err.message || err);
4629
4793
  });
4630
- })
4631
- .catch(function (err) {
4632
- console.warn('[Cumulus] Push subscription failed:', err);
4633
4794
  });
4795
+ });
4796
+ })
4797
+ .catch(function (err) {
4798
+ console.warn('[Cumulus] Service worker registration failed:', err.message || err);
4634
4799
  });
4635
- })
4636
- .catch(function (err) {
4637
- console.warn('[Cumulus] Service worker registration failed:', err);
4638
- });
4800
+ });
4639
4801
  }
4640
4802
 
4641
4803
  function sendPushSubscription(subscription) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luckydraw/cumulus",
3
- "version": "0.28.4",
3
+ "version": "0.28.5",
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",