@delt/claude-alarm 0.3.5 → 0.3.7

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.
@@ -98,6 +98,10 @@
98
98
  letter-spacing: 0.5px;
99
99
  color: var(--text-dim);
100
100
  padding: 8px 8px 12px;
101
+ position: sticky;
102
+ top: 0;
103
+ background: var(--bg);
104
+ z-index: 1;
101
105
  }
102
106
  .session-card {
103
107
  background: var(--surface);
@@ -207,6 +211,28 @@
207
211
  margin-top: 4px;
208
212
  }
209
213
 
214
+ /* Scroll to bottom button */
215
+ .scroll-bottom {
216
+ position: absolute;
217
+ bottom: 80px;
218
+ right: 30px;
219
+ width: 36px;
220
+ height: 36px;
221
+ border-radius: 50%;
222
+ background: var(--surface);
223
+ border: 1px solid var(--border);
224
+ color: var(--text);
225
+ cursor: pointer;
226
+ font-size: 16px;
227
+ display: none;
228
+ align-items: center;
229
+ justify-content: center;
230
+ z-index: 5;
231
+ transition: border-color 0.15s;
232
+ }
233
+ .scroll-bottom:hover { border-color: var(--accent); }
234
+ .scroll-bottom.visible { display: flex; }
235
+
210
236
  /* Image preview */
211
237
  .image-preview {
212
238
  border-top: 1px solid var(--border);
@@ -308,13 +334,7 @@
308
334
  overflow-y: auto;
309
335
  padding: 12px;
310
336
  }
311
- .notifications-panel h2 {
312
- font-size: 13px;
313
- text-transform: uppercase;
314
- letter-spacing: 0.5px;
315
- color: var(--text-dim);
316
- padding: 8px 8px 12px;
317
- }
337
+ .notifications-panel > .notif-header + #notifList { /* spacer */ }
318
338
  .notif-item {
319
339
  background: var(--surface);
320
340
  border: 1px solid var(--border);
@@ -325,9 +345,43 @@
325
345
  cursor: pointer;
326
346
  }
327
347
  .notif-item:hover { border-color: var(--accent); }
328
- .notif-item .notif-title { font-weight: 500; margin-bottom: 2px; }
348
+ .notif-item:hover .notif-dismiss { opacity: 1; }
349
+ .notif-item .notif-title { font-weight: 500; margin-bottom: 2px; display: flex; align-items: center; }
350
+ .notif-item .notif-title-text { flex: 1; }
351
+ .notif-dismiss {
352
+ opacity: 0;
353
+ background: none;
354
+ border: none;
355
+ color: var(--text-dim);
356
+ cursor: pointer;
357
+ font-size: 14px;
358
+ padding: 0 2px;
359
+ transition: opacity 0.15s;
360
+ }
361
+ .notif-dismiss:hover { color: var(--red); }
329
362
  .notif-item .notif-message { color: var(--text-dim); }
330
363
  .notif-item .notif-time { font-size: 11px; color: var(--text-dim); margin-top: 4px; }
364
+ .notif-header {
365
+ display: flex;
366
+ align-items: center;
367
+ justify-content: space-between;
368
+ padding: 8px 8px 12px;
369
+ position: sticky;
370
+ top: 0;
371
+ background: var(--bg);
372
+ z-index: 1;
373
+ }
374
+ .notif-header h2 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim); padding: 0; }
375
+ .notif-clear-all {
376
+ background: none;
377
+ border: 1px solid var(--border);
378
+ border-radius: 4px;
379
+ color: var(--text-dim);
380
+ cursor: pointer;
381
+ font-size: 11px;
382
+ padding: 2px 8px;
383
+ }
384
+ .notif-clear-all:hover { border-color: var(--red); color: var(--red); }
331
385
  .notif-level {
332
386
  display: inline-block;
333
387
  width: 6px; height: 6px;
@@ -455,6 +509,7 @@
455
509
  <div class="messages-list" id="messagesList">
456
510
  <div class="empty-state">Select a session to view messages</div>
457
511
  </div>
512
+ <button class="scroll-bottom" id="scrollBottom" title="Scroll to bottom">&#8595;</button>
458
513
  <div class="drag-overlay" id="dragOverlay">Drop image here</div>
459
514
  <div class="image-preview" id="imagePreview">
460
515
  <img id="previewImg" src="" alt="preview">
@@ -470,7 +525,10 @@
470
525
  </div>
471
526
 
472
527
  <div class="notifications-panel">
473
- <h2>Notifications</h2>
528
+ <div class="notif-header">
529
+ <h2>Notifications</h2>
530
+ <button class="notif-clear-all" id="notifClearAll">Clear all</button>
531
+ </div>
474
532
  <div id="notifList">
475
533
  <div class="empty-state">No notifications yet</div>
476
534
  </div>
@@ -587,6 +645,8 @@
587
645
  if (!state.messages[msg.sessionId]) state.messages[msg.sessionId] = [];
588
646
  state.messages[msg.sessionId].push({ from: 'session', content: msg.content, time: msg.timestamp });
589
647
  if (state.selectedSession === msg.sessionId) renderMessages();
648
+ state.notifications.unshift({ sessionId: msg.sessionId, title: 'Reply', message: msg.content.slice(0, 100), level: 'info', time: msg.timestamp });
649
+ renderNotifications();
590
650
  break;
591
651
  case 'notification':
592
652
  state.notifications.unshift({ sessionId: msg.sessionId, title: msg.title, message: msg.message, level: msg.level || 'info', time: msg.timestamp });
@@ -701,26 +761,38 @@
701
761
 
702
762
  function renderNotifications() {
703
763
  const el = $('#notifList');
764
+ const clearBtn = $('#notifClearAll');
704
765
  if (!state.notifications.length) {
705
766
  el.innerHTML = '<div class="empty-state">No notifications yet</div>';
767
+ clearBtn.style.display = 'none';
706
768
  return;
707
769
  }
708
- el.innerHTML = state.notifications.slice(0, 50).map(n => {
770
+ clearBtn.style.display = 'block';
771
+ el.innerHTML = state.notifications.slice(0, 50).map((n, i) => {
709
772
  const timeStr = new Date(n.time).toLocaleTimeString();
710
773
  const session = state.sessions[n.sessionId];
711
774
  const sName = session ? session.name : n.sessionId.slice(0, 8);
712
- return `<div class="notif-item" data-session="${n.sessionId}">
713
- <div class="notif-title"><span class="notif-level ${n.level}"></span>${esc(n.title)}</div>
775
+ return `<div class="notif-item" data-session="${n.sessionId}" data-index="${i}">
776
+ <div class="notif-title"><span class="notif-level ${n.level}"></span><span class="notif-title-text">${esc(n.title)}</span><button class="notif-dismiss" data-index="${i}">&times;</button></div>
714
777
  <div class="notif-message">${esc(n.message)}</div>
715
778
  <div class="notif-time">${sName} &middot; ${timeStr}</div>
716
779
  </div>`;
717
780
  }).join('');
718
781
  el.querySelectorAll('.notif-item').forEach(item => {
719
- item.addEventListener('click', () => {
782
+ item.addEventListener('click', (e) => {
783
+ if (e.target.classList.contains('notif-dismiss')) return;
720
784
  const sid = item.dataset.session;
721
785
  if (sid && state.sessions[sid]) selectSession(sid);
722
786
  });
723
787
  });
788
+ el.querySelectorAll('.notif-dismiss').forEach(btn => {
789
+ btn.addEventListener('click', (e) => {
790
+ e.stopPropagation();
791
+ const idx = parseInt(btn.dataset.index);
792
+ state.notifications.splice(idx, 1);
793
+ renderNotifications();
794
+ });
795
+ });
724
796
  }
725
797
 
726
798
  // --- Image handling ---
@@ -870,6 +942,23 @@
870
942
  return html;
871
943
  }
872
944
 
945
+ // Clear all notifications
946
+ $('#notifClearAll').addEventListener('click', () => {
947
+ state.notifications = [];
948
+ renderNotifications();
949
+ });
950
+
951
+ // Scroll to bottom button
952
+ const msgList = $('#messagesList');
953
+ const scrollBtn = $('#scrollBottom');
954
+ msgList.addEventListener('scroll', () => {
955
+ const gap = msgList.scrollHeight - msgList.scrollTop - msgList.clientHeight;
956
+ scrollBtn.classList.toggle('visible', gap > 100);
957
+ });
958
+ scrollBtn.addEventListener('click', () => {
959
+ msgList.scrollTo({ top: msgList.scrollHeight, behavior: 'smooth' });
960
+ });
961
+
873
962
  state.token = getToken();
874
963
  connect();
875
964
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delt/claude-alarm",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Monitor and get notifications from multiple Claude Code sessions via MCP Channels",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -98,6 +98,10 @@
98
98
  letter-spacing: 0.5px;
99
99
  color: var(--text-dim);
100
100
  padding: 8px 8px 12px;
101
+ position: sticky;
102
+ top: 0;
103
+ background: var(--bg);
104
+ z-index: 1;
101
105
  }
102
106
  .session-card {
103
107
  background: var(--surface);
@@ -207,6 +211,28 @@
207
211
  margin-top: 4px;
208
212
  }
209
213
 
214
+ /* Scroll to bottom button */
215
+ .scroll-bottom {
216
+ position: absolute;
217
+ bottom: 80px;
218
+ right: 30px;
219
+ width: 36px;
220
+ height: 36px;
221
+ border-radius: 50%;
222
+ background: var(--surface);
223
+ border: 1px solid var(--border);
224
+ color: var(--text);
225
+ cursor: pointer;
226
+ font-size: 16px;
227
+ display: none;
228
+ align-items: center;
229
+ justify-content: center;
230
+ z-index: 5;
231
+ transition: border-color 0.15s;
232
+ }
233
+ .scroll-bottom:hover { border-color: var(--accent); }
234
+ .scroll-bottom.visible { display: flex; }
235
+
210
236
  /* Image preview */
211
237
  .image-preview {
212
238
  border-top: 1px solid var(--border);
@@ -308,13 +334,7 @@
308
334
  overflow-y: auto;
309
335
  padding: 12px;
310
336
  }
311
- .notifications-panel h2 {
312
- font-size: 13px;
313
- text-transform: uppercase;
314
- letter-spacing: 0.5px;
315
- color: var(--text-dim);
316
- padding: 8px 8px 12px;
317
- }
337
+ .notifications-panel > .notif-header + #notifList { /* spacer */ }
318
338
  .notif-item {
319
339
  background: var(--surface);
320
340
  border: 1px solid var(--border);
@@ -325,9 +345,43 @@
325
345
  cursor: pointer;
326
346
  }
327
347
  .notif-item:hover { border-color: var(--accent); }
328
- .notif-item .notif-title { font-weight: 500; margin-bottom: 2px; }
348
+ .notif-item:hover .notif-dismiss { opacity: 1; }
349
+ .notif-item .notif-title { font-weight: 500; margin-bottom: 2px; display: flex; align-items: center; }
350
+ .notif-item .notif-title-text { flex: 1; }
351
+ .notif-dismiss {
352
+ opacity: 0;
353
+ background: none;
354
+ border: none;
355
+ color: var(--text-dim);
356
+ cursor: pointer;
357
+ font-size: 14px;
358
+ padding: 0 2px;
359
+ transition: opacity 0.15s;
360
+ }
361
+ .notif-dismiss:hover { color: var(--red); }
329
362
  .notif-item .notif-message { color: var(--text-dim); }
330
363
  .notif-item .notif-time { font-size: 11px; color: var(--text-dim); margin-top: 4px; }
364
+ .notif-header {
365
+ display: flex;
366
+ align-items: center;
367
+ justify-content: space-between;
368
+ padding: 8px 8px 12px;
369
+ position: sticky;
370
+ top: 0;
371
+ background: var(--bg);
372
+ z-index: 1;
373
+ }
374
+ .notif-header h2 { font-size: 13px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim); padding: 0; }
375
+ .notif-clear-all {
376
+ background: none;
377
+ border: 1px solid var(--border);
378
+ border-radius: 4px;
379
+ color: var(--text-dim);
380
+ cursor: pointer;
381
+ font-size: 11px;
382
+ padding: 2px 8px;
383
+ }
384
+ .notif-clear-all:hover { border-color: var(--red); color: var(--red); }
331
385
  .notif-level {
332
386
  display: inline-block;
333
387
  width: 6px; height: 6px;
@@ -455,6 +509,7 @@
455
509
  <div class="messages-list" id="messagesList">
456
510
  <div class="empty-state">Select a session to view messages</div>
457
511
  </div>
512
+ <button class="scroll-bottom" id="scrollBottom" title="Scroll to bottom">&#8595;</button>
458
513
  <div class="drag-overlay" id="dragOverlay">Drop image here</div>
459
514
  <div class="image-preview" id="imagePreview">
460
515
  <img id="previewImg" src="" alt="preview">
@@ -470,7 +525,10 @@
470
525
  </div>
471
526
 
472
527
  <div class="notifications-panel">
473
- <h2>Notifications</h2>
528
+ <div class="notif-header">
529
+ <h2>Notifications</h2>
530
+ <button class="notif-clear-all" id="notifClearAll">Clear all</button>
531
+ </div>
474
532
  <div id="notifList">
475
533
  <div class="empty-state">No notifications yet</div>
476
534
  </div>
@@ -587,6 +645,8 @@
587
645
  if (!state.messages[msg.sessionId]) state.messages[msg.sessionId] = [];
588
646
  state.messages[msg.sessionId].push({ from: 'session', content: msg.content, time: msg.timestamp });
589
647
  if (state.selectedSession === msg.sessionId) renderMessages();
648
+ state.notifications.unshift({ sessionId: msg.sessionId, title: 'Reply', message: msg.content.slice(0, 100), level: 'info', time: msg.timestamp });
649
+ renderNotifications();
590
650
  break;
591
651
  case 'notification':
592
652
  state.notifications.unshift({ sessionId: msg.sessionId, title: msg.title, message: msg.message, level: msg.level || 'info', time: msg.timestamp });
@@ -701,26 +761,38 @@
701
761
 
702
762
  function renderNotifications() {
703
763
  const el = $('#notifList');
764
+ const clearBtn = $('#notifClearAll');
704
765
  if (!state.notifications.length) {
705
766
  el.innerHTML = '<div class="empty-state">No notifications yet</div>';
767
+ clearBtn.style.display = 'none';
706
768
  return;
707
769
  }
708
- el.innerHTML = state.notifications.slice(0, 50).map(n => {
770
+ clearBtn.style.display = 'block';
771
+ el.innerHTML = state.notifications.slice(0, 50).map((n, i) => {
709
772
  const timeStr = new Date(n.time).toLocaleTimeString();
710
773
  const session = state.sessions[n.sessionId];
711
774
  const sName = session ? session.name : n.sessionId.slice(0, 8);
712
- return `<div class="notif-item" data-session="${n.sessionId}">
713
- <div class="notif-title"><span class="notif-level ${n.level}"></span>${esc(n.title)}</div>
775
+ return `<div class="notif-item" data-session="${n.sessionId}" data-index="${i}">
776
+ <div class="notif-title"><span class="notif-level ${n.level}"></span><span class="notif-title-text">${esc(n.title)}</span><button class="notif-dismiss" data-index="${i}">&times;</button></div>
714
777
  <div class="notif-message">${esc(n.message)}</div>
715
778
  <div class="notif-time">${sName} &middot; ${timeStr}</div>
716
779
  </div>`;
717
780
  }).join('');
718
781
  el.querySelectorAll('.notif-item').forEach(item => {
719
- item.addEventListener('click', () => {
782
+ item.addEventListener('click', (e) => {
783
+ if (e.target.classList.contains('notif-dismiss')) return;
720
784
  const sid = item.dataset.session;
721
785
  if (sid && state.sessions[sid]) selectSession(sid);
722
786
  });
723
787
  });
788
+ el.querySelectorAll('.notif-dismiss').forEach(btn => {
789
+ btn.addEventListener('click', (e) => {
790
+ e.stopPropagation();
791
+ const idx = parseInt(btn.dataset.index);
792
+ state.notifications.splice(idx, 1);
793
+ renderNotifications();
794
+ });
795
+ });
724
796
  }
725
797
 
726
798
  // --- Image handling ---
@@ -870,6 +942,23 @@
870
942
  return html;
871
943
  }
872
944
 
945
+ // Clear all notifications
946
+ $('#notifClearAll').addEventListener('click', () => {
947
+ state.notifications = [];
948
+ renderNotifications();
949
+ });
950
+
951
+ // Scroll to bottom button
952
+ const msgList = $('#messagesList');
953
+ const scrollBtn = $('#scrollBottom');
954
+ msgList.addEventListener('scroll', () => {
955
+ const gap = msgList.scrollHeight - msgList.scrollTop - msgList.clientHeight;
956
+ scrollBtn.classList.toggle('visible', gap > 100);
957
+ });
958
+ scrollBtn.addEventListener('click', () => {
959
+ msgList.scrollTo({ top: msgList.scrollHeight, behavior: 'smooth' });
960
+ });
961
+
873
962
  state.token = getToken();
874
963
  connect();
875
964
  })();