uv-suite 0.26.1 → 0.26.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.
@@ -279,6 +279,42 @@
279
279
  .event.user-prompt { background: var(--user-prompt-bg); }
280
280
  .event.user-prompt .detail { color: var(--user-prompt-text); font-style: italic; }
281
281
 
282
+ /* Session priority: low rows are dimmed, high rows get an accent strip */
283
+ .event.priority-low { opacity: 0.45; }
284
+ .event.priority-low:hover { opacity: 0.85; }
285
+ .event.priority-high { border-left: 3px solid var(--accent); padding-left: 25px; }
286
+
287
+ /* Pills shown next to a session label */
288
+ .pill {
289
+ display: inline-block;
290
+ padding: 1px 7px;
291
+ margin-left: 6px;
292
+ font-size: 10.5px;
293
+ font-weight: 600;
294
+ letter-spacing: 0.04em;
295
+ text-transform: uppercase;
296
+ border-radius: 4px;
297
+ vertical-align: 1px;
298
+ }
299
+ .pill.persona-spike { background: rgba(191, 90, 242, 0.18); color: var(--purple); }
300
+ .pill.persona-sport { background: rgba(48, 209, 88, 0.18); color: var(--success); }
301
+ .pill.persona-professional { background: rgba(10, 132, 255, 0.18); color: var(--accent); }
302
+ .pill.persona-auto { background: rgba(255, 159, 10, 0.18); color: var(--warning); }
303
+ .pill.priority-low { background: rgba(154, 154, 163, 0.18); color: var(--text-muted); }
304
+ .pill.priority-med { background: rgba(255, 214, 10, 0.18); color: var(--yellow); }
305
+ .pill.priority-high { background: rgba(255, 69, 58, 0.18); color: var(--danger); }
306
+ .pill.kind-long-running { background: rgba(100, 210, 255, 0.18); color: var(--info); }
307
+ .pill.kind-outcome { background: rgba(255, 105, 97, 0.18); color: var(--peach); }
308
+
309
+ .session-tag.priority-low { opacity: 0.6; }
310
+ .session-tag .meta-line {
311
+ display: block;
312
+ font-size: 11px;
313
+ font-weight: 400;
314
+ color: var(--text-muted);
315
+ margin-top: 2px;
316
+ }
317
+
282
318
  .timeline-end { padding: 20px 24px; text-align: center; border-bottom: 1px solid var(--border-subtle); }
283
319
  .loader { display: inline-block; width: 48px; height: 4px; position: relative; }
284
320
  .loader::before {
@@ -393,9 +429,21 @@ let lastEventDiv = null;
393
429
  // Session colors and naming
394
430
  const palette = ['#0a84ff','#30d158','#ff9f0a','#bf5af2','#ff375f','#64d2ff','#ffd60a','#ac8ee0','#ff6961','#5e5ce6'];
395
431
  let colorIdx = 0;
432
+
433
+ // Resolve the canonical session key for an event: prefer the UV Suite id (one
434
+ // per `uv` launch), fall back to Claude Code's session id, then source_app.
435
+ function eventSid(ev) {
436
+ return ev.uvs_session_id || ev.session_id || ev.source_app || 'unknown';
437
+ }
438
+
396
439
  function sessionColor(id) {
397
440
  if (!sessions[id]) {
398
- sessions[id] = { color: palette[colorIdx++ % palette.length], count: 0, lastEvent: null, label: null, app: null };
441
+ sessions[id] = {
442
+ color: palette[colorIdx++ % palette.length],
443
+ count: 0, lastEvent: null,
444
+ name: '', kind: '', purpose: '', priority: '', persona: '',
445
+ app: null, label: null,
446
+ };
399
447
  updateSessionBar();
400
448
  updateFilterSession();
401
449
  }
@@ -406,33 +454,54 @@ function sessionColor(id) {
406
454
 
407
455
  function updateSessionLabel(sid, ev) {
408
456
  if (!sessions[sid]) return;
409
- if (!sessions[sid].app && ev.source_app) {
410
- sessions[sid].app = ev.source_app;
457
+ const s = sessions[sid];
458
+ let changed = false;
459
+
460
+ if (!s.app && ev.source_app) { s.app = ev.source_app; changed = true; }
461
+
462
+ // Configured metadata wins over heuristics. Update on every event so a
463
+ // mid-session /session-init relabel is reflected without a refresh.
464
+ for (const [evKey, sKey] of [
465
+ ['session_name','name'], ['session_kind','kind'],
466
+ ['session_purpose','purpose'], ['session_priority','priority'],
467
+ ['persona','persona'],
468
+ ]) {
469
+ if (ev[evKey] !== undefined && ev[evKey] !== null && ev[evKey] !== s[sKey]) {
470
+ s[sKey] = ev[evKey];
471
+ changed = true;
472
+ }
411
473
  }
412
- // Use first UserPromptSubmit as session label
474
+
475
+ // Fall back to first UserPromptSubmit if no configured name yet
413
476
  const type = ev.event_type || ev.hook_event_name || '';
414
- if (!sessions[sid].label && type === 'UserPromptSubmit') {
477
+ if (!s.name && !s.label && type === 'UserPromptSubmit') {
415
478
  const prompt = ev.tool_input?.prompt || ev.tool_input?.content || ev.message || '';
416
479
  if (prompt.length > 0) {
417
480
  let label = prompt.slice(0, 45).replace(/\s+\S*$/, '');
418
481
  if (prompt.length > label.length) label += '...';
419
- sessions[sid].label = label;
420
- updateSessionBar();
421
- updateFilterSession();
482
+ s.label = label;
483
+ changed = true;
422
484
  }
423
485
  }
486
+
487
+ if (changed) {
488
+ updateSessionBar();
489
+ updateFilterSession();
490
+ }
424
491
  }
425
492
 
426
493
  function sessionDisplayName(id) {
427
494
  const s = sessions[id];
428
495
  if (!s) return shortId(id);
429
- if (s.label) {
430
- return s.app ? s.app + ': ' + s.label : s.label;
431
- }
496
+ if (s.name) return s.name;
497
+ if (s.label) return s.app ? s.app + ': ' + s.label : s.label;
432
498
  if (s.app) return s.app;
433
499
  return shortId(id);
434
500
  }
435
501
 
502
+ // Sort order: high → med → unset → low (low last so it groups at the bottom)
503
+ const PRIORITY_ORDER = { high: 0, med: 1, '': 2, low: 3 };
504
+
436
505
  function shortId(id) {
437
506
  if (!id) return '—';
438
507
  return id.length > 10 ? id.slice(0, 8) + '..' : id;
@@ -526,7 +595,7 @@ function eventDetail(ev) {
526
595
  }
527
596
 
528
597
  function renderEvent(ev) {
529
- const sid = ev.session_id || ev.source_app || 'unknown';
598
+ const sid = eventSid(ev);
530
599
  const color = sessionColor(sid);
531
600
  const type = ev.event_type || ev.hook_event_name || '?';
532
601
  const tool = ev.tool_name || '';
@@ -534,6 +603,7 @@ function renderEvent(ev) {
534
603
  const fail = isFailure(ev);
535
604
  const boundary = isSessionBoundary(ev);
536
605
  const prompt = isUserPrompt(ev);
606
+ const priority = sessions[sid]?.priority || '';
537
607
 
538
608
  const div = document.createElement('div');
539
609
  let cls = 'event';
@@ -541,6 +611,8 @@ function renderEvent(ev) {
541
611
  else if (fail) cls += ' failure';
542
612
  else if (prompt) cls += ' user-prompt';
543
613
  else if (boundary) cls += ' session-boundary';
614
+ if (priority === 'high') cls += ' priority-high';
615
+ if (priority === 'low') cls += ' priority-low';
544
616
  div.className = cls;
545
617
 
546
618
  const humanBadge = human ? '<span class="human-badge">NEEDS HUMAN</span>' : '';
@@ -567,7 +639,9 @@ function renderEvent(ev) {
567
639
  function addEvent(ev) {
568
640
  events.push(ev);
569
641
  if (emptyState.parentNode) emptyState.remove();
570
- const sid = ev.session_id || ev.source_app || 'unknown';
642
+ const sid = eventSid(ev);
643
+ // Make sure the session is registered before we try to update its metadata
644
+ sessionColor(sid);
571
645
  updateSessionLabel(sid, ev);
572
646
 
573
647
  // Remove "latest" class from previous latest
@@ -592,7 +666,7 @@ function addEvent(ev) {
592
666
 
593
667
  function updateWaitingText(ev) {
594
668
  if (ev) {
595
- const sid = ev.session_id || ev.source_app || 'unknown';
669
+ const sid = eventSid(ev);
596
670
  waitingText.textContent = `Last: ${sessionDisplayName(sid)} — ${timeSince(ev._ts)}`;
597
671
  }
598
672
  }
@@ -613,7 +687,7 @@ function updateStats() {
613
687
  // number drops back down once the session continues past the prompt.
614
688
  const latestBySession = {};
615
689
  for (const ev of events) {
616
- const sid = ev.session_id || ev.source_app || 'unknown';
690
+ const sid = eventSid(ev);
617
691
  latestBySession[sid] = ev;
618
692
  }
619
693
  const humans = Object.values(latestBySession).filter(needsHuman).length;
@@ -621,15 +695,46 @@ function updateStats() {
621
695
  document.getElementById('humanCount').className = 'n' + (humans > 0 ? ' alert' : '');
622
696
  }
623
697
 
698
+ function escapeHtml(s) {
699
+ return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
700
+ }
701
+
702
+ function pill(cls, label) {
703
+ return `<span class="pill ${cls}">${escapeHtml(label)}</span>`;
704
+ }
705
+
624
706
  function updateSessionBar() {
625
707
  sessionBar.innerHTML = '';
626
- for (const [id, s] of Object.entries(sessions)) {
708
+ // Sort: high priority first, then med/unset, then low; within a tier, most
709
+ // recent activity wins so the dashboard surfaces what's happening now.
710
+ const ids = Object.keys(sessions).sort((a, b) => {
711
+ const pa = PRIORITY_ORDER[sessions[a].priority] ?? PRIORITY_ORDER[''];
712
+ const pb = PRIORITY_ORDER[sessions[b].priority] ?? PRIORITY_ORDER[''];
713
+ if (pa !== pb) return pa - pb;
714
+ return (sessions[b].lastEvent || 0) - (sessions[a].lastEvent || 0);
715
+ });
716
+
717
+ for (const id of ids) {
718
+ const s = sessions[id];
627
719
  const tag = document.createElement('span');
628
- tag.className = 'session-tag' + (selectedSession === id ? ' active' : '');
720
+ let cls = 'session-tag';
721
+ if (selectedSession === id) cls += ' active';
722
+ if (s.priority === 'low') cls += ' priority-low';
723
+ tag.className = cls;
629
724
  tag.style.background = s.color + '22';
630
725
  tag.style.color = s.color;
631
- tag.textContent = sessionDisplayName(id) + ' (' + s.count + ')';
632
- tag.title = id;
726
+ tag.title = `${id}${s.purpose ? '\n' + s.purpose : ''}`;
727
+
728
+ const pills = [];
729
+ if (s.persona) pills.push(pill('persona-' + s.persona, s.persona));
730
+ if (s.priority) pills.push(pill('priority-' + s.priority, 'P:' + s.priority));
731
+ if (s.kind) pills.push(pill('kind-' + s.kind, s.kind));
732
+
733
+ const namePart = `<strong>${escapeHtml(sessionDisplayName(id))}</strong>` +
734
+ ` <span style="opacity:0.7">(${s.count})</span>`;
735
+ const meta = pills.length ? `<span class="meta-line">${pills.join('')}</span>` : '';
736
+ tag.innerHTML = namePart + meta;
737
+
633
738
  tag.onclick = () => { selectedSession = selectedSession === id ? '' : id; refilter(); updateSessionBar(); };
634
739
  sessionBar.appendChild(tag);
635
740
  }
@@ -662,7 +767,7 @@ function refilter() {
662
767
  rows.forEach((row) => {
663
768
  const ev = row._ev;
664
769
  if (!ev) return;
665
- const sid = ev.session_id || ev.source_app || 'unknown';
770
+ const sid = eventSid(ev);
666
771
  const type = ev.event_type || ev.hook_event_name || '';
667
772
  const show = (!selectedType || type === selectedType)
668
773
  && (!selectedSession || sid === selectedSession)
@@ -1,22 +1 @@
1
- [
2
- {
3
- "event_type": "PostToolUse",
4
- "session_id": "test-123",
5
- "source_app": "uv-suite",
6
- "tool_name": "Edit",
7
- "cwd": "/tmp",
8
- "_ts": 1776756371726,
9
- "_id": "3f130976-6226-47ea-a477-18a16e194415"
10
- },
11
- {
12
- "event_type": "PostToolUse",
13
- "session_id": "test-session",
14
- "source_app": "my-project",
15
- "tool_name": "Edit",
16
- "tool_input": {
17
- "file_path": "src/app.ts"
18
- },
19
- "_ts": 1776757586012,
20
- "_id": "efa9ae75-c0e0-48c2-a078-e7075ccef5a7"
21
- }
22
- ]
1
+ []