@bakapiano/ccsm 0.5.0 → 0.6.0

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.
package/public/styles.css CHANGED
@@ -61,6 +61,7 @@
61
61
  }
62
62
 
63
63
  * { box-sizing: border-box; margin: 0; padding: 0; }
64
+ [hidden] { display: none !important; }
64
65
 
65
66
  html, body {
66
67
  background: var(--bg);
@@ -124,10 +125,9 @@ html, body {
124
125
  width: 32px;
125
126
  height: 32px;
126
127
  flex: 0 0 32px;
127
- background: var(--accent);
128
- color: var(--bg-elev);
129
- border-radius: var(--r-sm);
128
+ background: transparent; /* SVG draws its own terminal window */
130
129
  }
130
+ .brand-mark svg { display: block; }
131
131
  .brand-name {
132
132
  font-size: 19px;
133
133
  font-weight: 600;
@@ -155,7 +155,8 @@ html, body {
155
155
  align-items: center;
156
156
  gap: var(--s-3);
157
157
  width: 100%;
158
- padding: 8px 10px;
158
+ padding: 0 10px;
159
+ min-height: 36px; /* uniform across nav + util items */
159
160
  border-radius: var(--r-sm);
160
161
  cursor: pointer;
161
162
  color: var(--ink-mid);
@@ -185,6 +186,30 @@ html, body {
185
186
  background: var(--accent);
186
187
  border-radius: 2px;
187
188
  }
189
+
190
+ /* "Has unsaved changes" indicator — small accent dot next to the label */
191
+ .nav-item.has-changes::after {
192
+ content: "";
193
+ position: absolute;
194
+ right: 10px;
195
+ top: 50%;
196
+ transform: translateY(-50%);
197
+ width: 7px;
198
+ height: 7px;
199
+ border-radius: 50%;
200
+ background: var(--accent);
201
+ box-shadow: 0 0 0 0 rgba(196, 95, 63, 0.45);
202
+ animation: dirty-pulse 2s ease-in-out infinite;
203
+ }
204
+ @keyframes dirty-pulse {
205
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(196, 95, 63, 0.45); }
206
+ 50% { box-shadow: 0 0 0 5px rgba(196, 95, 63, 0); }
207
+ }
208
+ .sidebar[data-collapsed="true"] .nav-item.has-changes::after {
209
+ right: auto;
210
+ top: 6px;
211
+ left: 28px;
212
+ }
188
213
  .nav-icon {
189
214
  display: inline-flex;
190
215
  width: 18px;
@@ -324,6 +349,61 @@ html, body {
324
349
  .ph-val { color: var(--ink-mid); }
325
350
  .ph-divider { color: var(--ink-faint); }
326
351
 
352
+ /* Server status pill — "● online v0.5.0" / "● offline" */
353
+ .server-status {
354
+ display: inline-flex;
355
+ align-items: center;
356
+ gap: 6px;
357
+ padding: 2px 9px 2px 7px;
358
+ border-radius: 999px;
359
+ background: var(--bg);
360
+ border: 1px solid var(--border);
361
+ font-family: var(--mono);
362
+ font-size: 10.5px;
363
+ letter-spacing: 0.02em;
364
+ color: var(--ink-mid);
365
+ transition: background .15s ease, border-color .15s ease, color .15s ease;
366
+ cursor: default;
367
+ }
368
+ .server-status .status-pulse {
369
+ width: 6px;
370
+ height: 6px;
371
+ border-radius: 50%;
372
+ background: var(--ink-faint);
373
+ flex: 0 0 6px;
374
+ position: relative;
375
+ }
376
+ .server-status[data-state="online"] {
377
+ border-color: rgba(74, 138, 74, 0.35);
378
+ background: rgba(74, 138, 74, 0.06);
379
+ color: var(--green);
380
+ }
381
+ .server-status[data-state="online"] .status-pulse {
382
+ background: var(--green);
383
+ animation: server-pulse 2.2s ease-in-out infinite;
384
+ }
385
+ .server-status[data-state="offline"] {
386
+ border-color: rgba(183, 63, 63, 0.4);
387
+ background: rgba(183, 63, 63, 0.06);
388
+ color: var(--red);
389
+ }
390
+ .server-status[data-state="offline"] .status-pulse {
391
+ background: var(--red);
392
+ }
393
+ .server-status[data-state="connecting"] {
394
+ border-color: rgba(196, 137, 43, 0.4);
395
+ background: rgba(196, 137, 43, 0.06);
396
+ color: var(--yellow);
397
+ }
398
+ .server-status[data-state="connecting"] .status-pulse {
399
+ background: var(--yellow);
400
+ animation: server-pulse 1s ease-in-out infinite;
401
+ }
402
+ @keyframes server-pulse {
403
+ 0%, 100% { box-shadow: 0 0 0 0 currentColor; }
404
+ 50% { box-shadow: 0 0 0 4px transparent; }
405
+ }
406
+
327
407
  .content {
328
408
  flex: 1;
329
409
  display: flex;
@@ -360,9 +440,48 @@ html, body {
360
440
  padding: var(--s-4) var(--s-6) var(--s-3);
361
441
  border-bottom: 1px solid var(--border-soft);
362
442
  display: flex;
363
- justify-content: space-between;
364
- align-items: baseline;
365
- gap: var(--s-4);
443
+ justify-content: flex-start;
444
+ align-items: center;
445
+ gap: var(--s-3);
446
+ }
447
+ /* Make the whole header clickable to fold (only when card is foldable) */
448
+ .card[data-fold-key] .card-head {
449
+ cursor: pointer;
450
+ user-select: none;
451
+ transition: background .12s ease;
452
+ }
453
+ .card[data-fold-key] .card-head:hover {
454
+ background: var(--bg);
455
+ }
456
+ .card[data-collapsed] .card-head { border-bottom-color: transparent; }
457
+ .card-titles { flex: 1; min-width: 0; }
458
+
459
+ /* Fold toggle button in card head */
460
+ .card-fold {
461
+ appearance: none;
462
+ background: transparent;
463
+ border: 0;
464
+ padding: 4px;
465
+ margin: 0;
466
+ cursor: pointer;
467
+ color: var(--ink-muted);
468
+ display: inline-flex;
469
+ align-items: center;
470
+ justify-content: center;
471
+ border-radius: 4px;
472
+ transition: color .12s ease, background .12s ease, transform .25s cubic-bezier(.4, 0, .2, 1);
473
+ line-height: 0;
474
+ flex: 0 0 auto;
475
+ }
476
+ .card-fold:hover {
477
+ color: var(--ink);
478
+ background: var(--bg);
479
+ }
480
+ .card[data-collapsed] .card-fold {
481
+ transform: rotate(-90deg);
482
+ }
483
+ .card[data-collapsed] .card-body {
484
+ display: none;
366
485
  }
367
486
  .card-titles { min-width: 0; }
368
487
  .card-title {
@@ -394,20 +513,34 @@ html, body {
394
513
  Page-level inline actions (above the cards on a tab)
395
514
  ───────────────────────────────────────────────────────────── */
396
515
 
516
+ /* CTA banner — visually distinct from the data cards: subtle accent tint,
517
+ no card shadow, dashed-ish bottom rule, no rounded corners as heavy.
518
+ Reads as "tip / shortcut" rather than another data section. */
397
519
  .page-actions {
398
520
  display: flex;
399
521
  align-items: center;
400
522
  justify-content: space-between;
401
523
  gap: var(--s-4);
402
524
  padding: var(--s-3) var(--s-5);
403
- background: var(--bg-elev);
404
- border: 1px solid var(--border);
405
- border-radius: var(--r-md);
406
- box-shadow: var(--shadow-sm);
525
+ background: linear-gradient(180deg, rgba(196,95,63,0.06), rgba(196,95,63,0.02));
526
+ border: 1px solid rgba(196,95,63,0.18);
527
+ border-radius: var(--r-sm);
528
+ box-shadow: none;
529
+ position: relative;
530
+ }
531
+ .page-actions::before {
532
+ content: "";
533
+ position: absolute;
534
+ left: 0; top: 8px; bottom: 8px;
535
+ width: 2px;
536
+ background: var(--accent);
537
+ border-radius: 0 2px 2px 0;
407
538
  }
408
539
  .page-actions-hint {
409
540
  font-size: 13px;
410
- color: var(--ink-mid);
541
+ color: var(--accent-deep);
542
+ font-weight: 500;
543
+ padding-left: var(--s-2);
411
544
  }
412
545
  .page-actions .action svg { stroke-width: 2; }
413
546
 
@@ -417,10 +550,15 @@ html, body {
417
550
 
418
551
  .table-scroll {
419
552
  overflow-x: auto;
420
- /* fade out the right edge slightly when scrollable, like a hint */
553
+ /* tactile: scroll-padding so the actions column edges aren't flush against
554
+ the viewport when you scroll right to reach the buttons */
555
+ scroll-padding-right: var(--s-6);
421
556
  }
422
- .table-scroll .data { min-width: 760px; }
423
557
  .table-scroll::-webkit-scrollbar { height: 8px; }
558
+ .table-scroll .data {
559
+ min-width: 960px;
560
+ table-layout: auto;
561
+ }
424
562
 
425
563
  .data {
426
564
  width: 100%;
@@ -443,11 +581,39 @@ html, body {
443
581
  white-space: nowrap;
444
582
  }
445
583
  .data thead th.num { text-align: right; }
446
- .data thead th.col-mark { width: 28px; padding-left: 0; padding-right: 0; }
447
- .data thead th.col-star { width: 28px; padding-left: 0; padding-right: 0; }
448
- .data thead th.col-actions { width: 1px; }
584
+ /* shrink-to-fit pattern: width:1% with nowrap the cell only takes the
585
+ space its content needs, the title / path cells get the remainder */
586
+ .data thead th.col-mark { width: 28px; padding-left: 0; padding-right: 0; }
587
+ .data thead th.col-star { width: 28px; padding-left: 0; padding-right: 0; }
588
+ .data thead th.col-actions { width: 1%; white-space: nowrap; }
589
+ .data thead th.num { width: 1%; white-space: nowrap; }
590
+ .data tbody td.num { white-space: nowrap; }
591
+ .data tbody td:last-child { white-space: nowrap; }
449
592
  .data thead th:first-child { padding-left: var(--s-6); }
450
- .data thead th:last-child { padding-right: var(--s-6); }
593
+ .data thead th:last-child { padding-right: var(--s-6); }
594
+
595
+ /* ── Sticky actions column ──
596
+ The last column (Focus/Resume/Continue buttons) stays pinned to the
597
+ right edge of the .table-scroll container while horizontal scroll happens
598
+ underneath. Backgrounds must be solid because sticky cells overlay the
599
+ content scrolling beneath them. */
600
+ .data thead th:last-child,
601
+ .data tbody td:last-child {
602
+ position: sticky;
603
+ right: 0;
604
+ z-index: 1;
605
+ /* fade the seam into the cell so it doesn't look like a hard cut */
606
+ box-shadow: -10px 0 10px -8px rgba(26, 24, 21, 0.08);
607
+ }
608
+ .data thead th:last-child {
609
+ background: var(--bg); /* matches thead bg */
610
+ }
611
+ .data tbody td:last-child {
612
+ background: var(--bg-elev); /* matches card bg */
613
+ }
614
+ .data tbody tr:hover td:last-child {
615
+ background: var(--bg); /* match row hover */
616
+ }
451
617
 
452
618
  .data tbody tr {
453
619
  border-bottom: 1px solid var(--border-soft);
@@ -519,13 +685,21 @@ html, body {
519
685
  }
520
686
  }
521
687
 
522
- /* Composite cells */
523
- .title-cell { max-width: 420px; min-width: 0; }
688
+ /* Composite cells — title and path are fixed-width with overflow ellipsis
689
+ on the inner spans, so the actions column is always visible without
690
+ horizontal scroll at typical viewport widths. Long titles / paths
691
+ truncate with ellipsis (hover shows full value via title attribute). */
692
+ .title-cell {
693
+ width: 300px;
694
+ max-width: 300px;
695
+ min-width: 0;
696
+ }
524
697
  .title-cell .title-row {
525
698
  display: flex;
526
699
  align-items: center;
527
700
  gap: 6px;
528
701
  min-width: 0;
702
+ width: 100%;
529
703
  }
530
704
  .title-cell .primary {
531
705
  color: var(--ink);
@@ -543,13 +717,18 @@ html, body {
543
717
  color: var(--ink-muted);
544
718
  letter-spacing: 0.02em;
545
719
  margin-top: 2px;
720
+ white-space: nowrap;
721
+ overflow: hidden;
722
+ text-overflow: ellipsis;
546
723
  }
547
724
 
548
725
  .path-cell {
549
726
  font-family: var(--mono);
550
727
  font-size: 11.5px;
551
728
  color: var(--ink-mid);
552
- max-width: 380px;
729
+ width: 260px;
730
+ max-width: 260px;
731
+ min-width: 0;
553
732
  white-space: nowrap;
554
733
  overflow: hidden;
555
734
  text-overflow: ellipsis;
@@ -573,8 +752,8 @@ html, body {
573
752
  }
574
753
 
575
754
  /* Star button (favorite toggle) — sits inline next to the title text.
576
- Outline by default (faint), fades in to ink on row hover, fills with
577
- the accent color when starred. */
755
+ Outline by default, brightens on hover, fills with the accent color
756
+ when starred. */
578
757
  .star-btn {
579
758
  appearance: none;
580
759
  background: transparent;
@@ -582,7 +761,7 @@ html, body {
582
761
  padding: 2px;
583
762
  margin: 0;
584
763
  cursor: pointer;
585
- color: var(--border-strong);
764
+ color: var(--ink-muted);
586
765
  display: inline-flex;
587
766
  align-items: center;
588
767
  justify-content: center;
@@ -590,30 +769,67 @@ html, body {
590
769
  transition: color .12s ease, background .12s ease, transform .15s ease;
591
770
  line-height: 0;
592
771
  flex: 0 0 auto;
593
- opacity: 0.55;
594
772
  }
595
773
  .data tbody tr:hover .star-btn {
596
- opacity: 1;
597
- color: var(--ink-faint);
774
+ color: var(--ink-mid);
598
775
  }
599
776
  .star-btn:hover {
600
777
  color: var(--accent) !important;
601
778
  background: var(--accent-softer);
602
- opacity: 1 !important;
603
779
  }
604
780
  .star-btn:active { transform: scale(0.88); }
605
781
  .star-btn.is-fav {
606
782
  color: var(--accent);
607
- opacity: 1;
608
783
  }
609
784
  .star-btn svg { display: block; }
610
785
 
786
+ /* Rename (pencil) button — inline next to title, only visible on row hover
787
+ so it doesn't compete with the star or distract from the title. When a
788
+ label exists (`has-label`), it stays subtly visible so the user knows
789
+ the title is overridden. */
790
+ .rename-btn {
791
+ appearance: none;
792
+ background: transparent;
793
+ border: 0;
794
+ padding: 2px;
795
+ margin: 0;
796
+ cursor: pointer;
797
+ color: var(--ink-faint);
798
+ opacity: 0;
799
+ display: inline-flex;
800
+ align-items: center;
801
+ justify-content: center;
802
+ border-radius: 4px;
803
+ transition: opacity .12s ease, color .12s ease, background .12s ease, transform .15s ease;
804
+ line-height: 0;
805
+ flex: 0 0 auto;
806
+ }
807
+ .data tbody tr:hover .rename-btn {
808
+ opacity: 1;
809
+ color: var(--ink-muted);
810
+ }
811
+ .rename-btn:hover {
812
+ color: var(--accent) !important;
813
+ background: var(--accent-softer);
814
+ }
815
+ .rename-btn:active { transform: scale(0.88); }
816
+ .rename-btn.has-label {
817
+ opacity: 0.8;
818
+ color: var(--accent);
819
+ }
820
+ .rename-btn svg { display: block; }
821
+
611
822
  /* Title with icon glyph */
612
823
  .card-title .title-icon {
613
824
  color: var(--accent);
614
825
  margin-right: 6px;
615
826
  vertical-align: -2px;
616
827
  }
828
+ .card-title .title-icon-after {
829
+ margin-right: 0;
830
+ margin-left: 6px;
831
+ vertical-align: -1px;
832
+ }
617
833
 
618
834
  /* Favorites empty state — sits inside the card body (not generic table empty) */
619
835
  #favoritesEmpty {
@@ -1173,6 +1389,225 @@ input[type="checkbox"]:checked::after {
1173
1389
  .toast.error { border-left-color: var(--red); }
1174
1390
  .toast.ok { border-left-color: var(--green); }
1175
1391
 
1392
+ /* ─────────────────────────────────────────────────────────────
1393
+ FAB · floating action button (bottom-right "+ new session")
1394
+ ───────────────────────────────────────────────────────────── */
1395
+
1396
+ .fab {
1397
+ position: fixed;
1398
+ bottom: var(--s-6);
1399
+ right: var(--s-6);
1400
+ z-index: 90;
1401
+ width: 52px;
1402
+ height: 52px;
1403
+ border-radius: 50%;
1404
+ background: var(--accent);
1405
+ color: var(--bg-elev);
1406
+ border: 0;
1407
+ cursor: pointer;
1408
+ display: inline-flex;
1409
+ align-items: center;
1410
+ justify-content: center;
1411
+ box-shadow:
1412
+ 0 8px 24px -6px rgba(196, 95, 63, 0.5),
1413
+ 0 2px 6px -1px rgba(26, 24, 21, 0.15);
1414
+ transition: background .12s ease, transform .15s ease, box-shadow .15s ease;
1415
+ }
1416
+ .fab:hover {
1417
+ background: var(--accent-deep);
1418
+ transform: translateY(-1px) scale(1.04);
1419
+ box-shadow:
1420
+ 0 12px 28px -6px rgba(196, 95, 63, 0.6),
1421
+ 0 4px 10px -2px rgba(26, 24, 21, 0.2);
1422
+ }
1423
+ .fab:active { transform: scale(0.96); }
1424
+
1425
+ /* ─────────────────────────────────────────────────────────────
1426
+ Modal dialog
1427
+ ───────────────────────────────────────────────────────────── */
1428
+
1429
+ .modal-backdrop {
1430
+ position: fixed;
1431
+ inset: 0;
1432
+ z-index: 200;
1433
+ background: rgba(26, 24, 21, 0.42);
1434
+ backdrop-filter: blur(4px);
1435
+ -webkit-backdrop-filter: blur(4px);
1436
+ display: flex;
1437
+ align-items: center;
1438
+ justify-content: center;
1439
+ padding: var(--s-6);
1440
+ animation: backdrop-in .18s ease;
1441
+ }
1442
+ @keyframes backdrop-in { from { opacity: 0; } to { opacity: 1; } }
1443
+
1444
+ .modal {
1445
+ background: var(--bg-elev);
1446
+ border: 1px solid var(--border);
1447
+ border-radius: var(--r-md);
1448
+ width: min(560px, 100%);
1449
+ max-height: 90vh;
1450
+ display: flex;
1451
+ flex-direction: column;
1452
+ overflow: hidden;
1453
+ box-shadow:
1454
+ 0 24px 64px -16px rgba(26, 24, 21, 0.35),
1455
+ 0 4px 12px -2px rgba(26, 24, 21, 0.15);
1456
+ animation: modal-in .22s cubic-bezier(.4, 0, .2, 1);
1457
+ }
1458
+ @keyframes modal-in {
1459
+ from { opacity: 0; transform: translateY(12px) scale(0.98); }
1460
+ to { opacity: 1; transform: translateY(0) scale(1); }
1461
+ }
1462
+
1463
+ .modal-head {
1464
+ display: flex;
1465
+ align-items: center;
1466
+ justify-content: space-between;
1467
+ padding: var(--s-4) var(--s-6);
1468
+ border-bottom: 1px solid var(--border-soft);
1469
+ }
1470
+ .modal-head h2 {
1471
+ font-size: 16px;
1472
+ font-weight: 600;
1473
+ color: var(--ink);
1474
+ }
1475
+ .modal-close {
1476
+ appearance: none;
1477
+ background: transparent;
1478
+ border: 0;
1479
+ padding: 4px;
1480
+ margin: -4px;
1481
+ cursor: pointer;
1482
+ color: var(--ink-muted);
1483
+ border-radius: 4px;
1484
+ display: inline-flex;
1485
+ transition: color .12s ease, background .12s ease;
1486
+ }
1487
+ .modal-close:hover { color: var(--ink); background: var(--bg); }
1488
+
1489
+ .modal-body {
1490
+ padding: var(--s-5) var(--s-6);
1491
+ overflow-y: auto;
1492
+ flex: 1;
1493
+ }
1494
+ .modal-hint {
1495
+ font-size: 13px;
1496
+ color: var(--ink-muted);
1497
+ margin-bottom: var(--s-4);
1498
+ }
1499
+ .modal-hint code {
1500
+ font-family: var(--mono);
1501
+ font-size: 11.5px;
1502
+ background: var(--bg);
1503
+ padding: 1px 5px;
1504
+ border-radius: 4px;
1505
+ border: 1px solid var(--border-soft);
1506
+ }
1507
+
1508
+ .modal-foot {
1509
+ display: flex;
1510
+ justify-content: flex-end;
1511
+ gap: var(--s-3);
1512
+ padding: var(--s-3) var(--s-6);
1513
+ border-top: 1px solid var(--border-soft);
1514
+ background: var(--bg);
1515
+ }
1516
+
1517
+ .repos-inline-config {
1518
+ margin: var(--s-3) 0;
1519
+ border: 1px solid var(--border);
1520
+ border-radius: var(--r-sm);
1521
+ background: var(--bg);
1522
+ }
1523
+ .repos-inline-config summary {
1524
+ cursor: pointer;
1525
+ padding: 8px var(--s-3);
1526
+ font-size: 12.5px;
1527
+ color: var(--ink-mid);
1528
+ font-weight: 500;
1529
+ user-select: none;
1530
+ }
1531
+ .repos-inline-config summary::marker { color: var(--accent); }
1532
+ .repos-inline-config summary:hover { color: var(--ink); }
1533
+ .repos-inline-config[open] summary { border-bottom: 1px solid var(--border); }
1534
+ .repos-inline-body {
1535
+ padding: var(--s-3);
1536
+ background: var(--bg-elev);
1537
+ border-radius: 0 0 var(--r-sm) var(--r-sm);
1538
+ }
1539
+ .repos-inline-actions {
1540
+ display: flex;
1541
+ gap: var(--s-3);
1542
+ align-items: center;
1543
+ margin-top: var(--s-3);
1544
+ }
1545
+
1546
+ /* Unsaved-changes banner in Configure tab — sticky-ish callout above the
1547
+ settings card. Mirrors the accent CTA banner pattern but louder. */
1548
+ .dirty-banner {
1549
+ display: flex;
1550
+ align-items: center;
1551
+ gap: var(--s-3);
1552
+ padding: var(--s-3) var(--s-5);
1553
+ background: linear-gradient(180deg, rgba(196,95,63,0.12), rgba(196,95,63,0.06));
1554
+ border: 1px solid var(--accent);
1555
+ border-radius: var(--r-sm);
1556
+ color: var(--accent-deep);
1557
+ font-size: 13px;
1558
+ font-weight: 500;
1559
+ position: sticky;
1560
+ top: var(--s-3);
1561
+ z-index: 10;
1562
+ box-shadow: 0 4px 16px -8px rgba(196, 95, 63, 0.4);
1563
+ animation: banner-in .25s cubic-bezier(.4, 0, .2, 1);
1564
+ }
1565
+ @keyframes banner-in {
1566
+ from { opacity: 0; transform: translateY(-6px); }
1567
+ to { opacity: 1; transform: translateY(0); }
1568
+ }
1569
+ .dirty-banner .dirty-dot {
1570
+ width: 8px;
1571
+ height: 8px;
1572
+ border-radius: 50%;
1573
+ background: var(--accent);
1574
+ flex: 0 0 8px;
1575
+ box-shadow: 0 0 0 0 rgba(196, 95, 63, 0.5);
1576
+ animation: dirty-pulse 2s ease-in-out infinite;
1577
+ }
1578
+ .dirty-banner .dirty-text { flex: 1; }
1579
+
1580
+ /* Save button "pulse" when there are unsaved changes */
1581
+ .action.primary.is-dirty {
1582
+ animation: save-pulse 1.6s ease-in-out infinite;
1583
+ }
1584
+ @keyframes save-pulse {
1585
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(196, 95, 63, 0.4); }
1586
+ 50% { box-shadow: 0 0 0 6px rgba(196, 95, 63, 0); }
1587
+ }
1588
+
1589
+ /* Ad-hoc dialog variant (confirm / prompt) — narrower than the main modal */
1590
+ .modal-dialog { width: min(440px, 100%); }
1591
+ .modal-dialog .modal-head { padding: var(--s-4) var(--s-5) var(--s-3); border-bottom: 0; }
1592
+ .modal-dialog .modal-head h2 {
1593
+ font-size: 14.5px;
1594
+ font-weight: 600;
1595
+ color: var(--ink);
1596
+ line-height: 1.4;
1597
+ }
1598
+ .modal-dialog .modal-body { padding: 0 var(--s-5) var(--s-4); }
1599
+ .dialog-msg {
1600
+ font-size: 13.5px;
1601
+ color: var(--ink-mid);
1602
+ line-height: 1.55;
1603
+ }
1604
+ .modal-dialog .modal-foot { padding: var(--s-3) var(--s-5); }
1605
+ .modal-dialog input[type="text"] {
1606
+ width: 100%;
1607
+ max-width: none;
1608
+ margin-top: var(--s-2);
1609
+ }
1610
+
1176
1611
  /* ─────────────────────────────────────────────────────────────
1177
1612
  Small utilities
1178
1613
  ───────────────────────────────────────────────────────────── */
package/server.js CHANGED
@@ -6,6 +6,7 @@ const express = require('express');
6
6
 
7
7
  const { listSessions, listRecentSessions, findSessionMetadata } = require('./lib/sessions');
8
8
  const { listFavorites, addFavorite, removeFavorite, loadFavorites } = require('./lib/favorites');
9
+ const { loadLabels, setLabel, removeLabel } = require('./lib/labels');
9
10
  const { loadConfig, saveConfig, DATA_DIR } = require('./lib/config');
10
11
  const {
11
12
  saveSnapshot,
@@ -105,6 +106,29 @@ app.delete('/api/favorites/:sessionId', asyncH(async (req, res) => {
105
106
  res.json({ removed });
106
107
  }));
107
108
 
109
+ // ---- labels (rename overrides) ----
110
+ // Custom display titles keyed by sessionId. Empty body / empty label is
111
+ // treated as a delete.
112
+ app.get('/api/labels', asyncH(async (_req, res) => {
113
+ const labels = await loadLabels();
114
+ res.json({ labels });
115
+ }));
116
+
117
+ app.put('/api/labels/:sessionId', asyncH(async (req, res) => {
118
+ const label = req.body && req.body.label;
119
+ if (!label || !String(label).trim()) {
120
+ const removed = await removeLabel(req.params.sessionId);
121
+ return res.json({ removed });
122
+ }
123
+ const saved = await setLabel(req.params.sessionId, label);
124
+ res.json({ label: saved });
125
+ }));
126
+
127
+ app.delete('/api/labels/:sessionId', asyncH(async (req, res) => {
128
+ const removed = await removeLabel(req.params.sessionId);
129
+ res.json({ removed });
130
+ }));
131
+
108
132
  // ---- config ----
109
133
 
110
134
  app.get('/api/config', asyncH(async (_req, res) => {
@@ -340,11 +364,13 @@ app.post('/api/sessions/:sessionId/focus', asyncH(async (req, res) => {
340
364
  const sessions = await listSessions();
341
365
  const s = sessions.find((x) => x.sessionId === sessionId);
342
366
  if (!s) return res.status(404).json({ error: `session ${sessionId} not live` });
367
+ const cfg = await loadConfig();
343
368
  const result = await focusBySession({
344
369
  pid: s.pid,
345
370
  sessionId: s.sessionId,
346
371
  title: s.title,
347
372
  cwd: s.cwd,
373
+ moveToCenter: !!cfg.focusMovesToCenter,
348
374
  });
349
375
  res.json({ session: { pid: s.pid, sessionId: s.sessionId, cwd: s.cwd, title: s.title }, ...result });
350
376
  }));
@@ -353,7 +379,8 @@ app.post('/api/sessions/:sessionId/focus', asyncH(async (req, res) => {
353
379
  app.get('/api/terminals', (_req, res) => res.json({ terminals: listTerminalKinds() }));
354
380
 
355
381
  // ---- health ----
356
- app.get('/api/health', (_req, res) => res.json({ ok: true, pid: process.pid }));
382
+ const pkg = require('./package.json');
383
+ app.get('/api/health', (_req, res) => res.json({ ok: true, pid: process.pid, version: pkg.version, name: pkg.name }));
357
384
 
358
385
  // ---- auto-snapshot scheduler ----
359
386
  let snapshotTimer = null;