openclacky 1.5.19 → 1.5.21

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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +95 -0
  3. data/README.md +19 -1
  4. data/README_CN.md +1 -1
  5. data/README_JA.md +1 -1
  6. data/lib/clacky/agent/chunk_index.rb +8 -1
  7. data/lib/clacky/agent/feedback.rb +30 -0
  8. data/lib/clacky/agent/history_navigation.rb +17 -3
  9. data/lib/clacky/agent/hook_manager.rb +6 -1
  10. data/lib/clacky/agent/llm_caller.rb +17 -3
  11. data/lib/clacky/agent/message_compressor_helper.rb +36 -2
  12. data/lib/clacky/agent/session_serializer.rb +45 -4
  13. data/lib/clacky/agent.rb +135 -55
  14. data/lib/clacky/agent_config.rb +82 -4
  15. data/lib/clacky/billing/platform_billing.rb +10 -5
  16. data/lib/clacky/brand_config.rb +1 -1
  17. data/lib/clacky/cli.rb +5 -4
  18. data/lib/clacky/client.rb +12 -5
  19. data/lib/clacky/deepseek_web_tools.rb +156 -0
  20. data/lib/clacky/default_extensions/ext-studio/api/handler.rb +8 -0
  21. data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +97 -20
  22. data/lib/clacky/default_extensions/time_machine/panels/time_machine/view.js +1 -0
  23. data/lib/clacky/default_skills/onboard/SKILL.md +1 -1
  24. data/lib/clacky/extension/loader.rb +5 -3
  25. data/lib/clacky/locales/en.rb +5 -5
  26. data/lib/clacky/locales/zh.rb +5 -5
  27. data/lib/clacky/message_format/anthropic.rb +20 -0
  28. data/lib/clacky/message_format/open_ai.rb +31 -14
  29. data/lib/clacky/message_history.rb +45 -4
  30. data/lib/clacky/platform_http_client.rb +6 -2
  31. data/lib/clacky/providers.rb +38 -138
  32. data/lib/clacky/search_config.rb +47 -13
  33. data/lib/clacky/server/browser_manager.rb +35 -5
  34. data/lib/clacky/server/channel/channel_manager.rb +6 -6
  35. data/lib/clacky/server/discover.rb +5 -4
  36. data/lib/clacky/server/http_server.rb +285 -53
  37. data/lib/clacky/server/model_prices.rb +10 -5
  38. data/lib/clacky/server/pid_files.rb +25 -0
  39. data/lib/clacky/server/server_master.rb +9 -3
  40. data/lib/clacky/server/session_registry.rb +37 -7
  41. data/lib/clacky/server/web_ui_controller.rb +72 -0
  42. data/lib/clacky/session_manager.rb +28 -6
  43. data/lib/clacky/skill.rb +1 -1
  44. data/lib/clacky/tools/ask_user.rb +4 -1
  45. data/lib/clacky/tools/base.rb +3 -2
  46. data/lib/clacky/tools/browser.rb +18 -2
  47. data/lib/clacky/tools/file_reader.rb +1 -1
  48. data/lib/clacky/tools/terminal/session_manager.rb +19 -1
  49. data/lib/clacky/tools/terminal.rb +65 -23
  50. data/lib/clacky/tools/trash_manager.rb +5 -3
  51. data/lib/clacky/tools/web_fetch.rb +81 -22
  52. data/lib/clacky/tools/web_search.rb +45 -4
  53. data/lib/clacky/ui2/ui_controller.rb +1 -1
  54. data/lib/clacky/ui_interface.rb +4 -3
  55. data/lib/clacky/utils/file_processor.rb +51 -0
  56. data/lib/clacky/utils/model_pricing.rb +242 -39
  57. data/lib/clacky/version.rb +1 -1
  58. data/lib/clacky/web/app.css +1158 -249
  59. data/lib/clacky/web/app.js +82 -13
  60. data/lib/clacky/web/components/chat-navigator.js +8 -1
  61. data/lib/clacky/web/components/code-editor.js +6 -2
  62. data/lib/clacky/web/components/mentions.js +2 -13
  63. data/lib/clacky/web/components/model-picker.js +92 -97
  64. data/lib/clacky/web/components/onboard.js +13 -10
  65. data/lib/clacky/web/components/sidebar.js +6 -2
  66. data/lib/clacky/web/features/archive/store.js +196 -0
  67. data/lib/clacky/web/features/archive/view.js +409 -0
  68. data/lib/clacky/web/features/backup/view.js +17 -7
  69. data/lib/clacky/web/features/billing/view.js +84 -10
  70. data/lib/clacky/web/features/channels/view.js +75 -29
  71. data/lib/clacky/web/features/new-session/store.js +3 -3
  72. data/lib/clacky/web/features/new-session/view.js +1 -0
  73. data/lib/clacky/web/features/trash/store.js +14 -11
  74. data/lib/clacky/web/features/trash/view.js +66 -13
  75. data/lib/clacky/web/features/workspace/view.js +421 -16
  76. data/lib/clacky/web/i18n.js +242 -70
  77. data/lib/clacky/web/index.html +270 -117
  78. data/lib/clacky/web/projects.js +56 -1
  79. data/lib/clacky/web/sessions.js +609 -267
  80. data/lib/clacky/web/settings.js +186 -32
  81. data/lib/clacky/web/ws-dispatcher.js +33 -21
  82. data/lib/clacky.rb +4 -2
  83. metadata +6 -1
@@ -526,16 +526,61 @@ body {
526
526
  display: flex;
527
527
  align-items: center;
528
528
  justify-content: space-between;
529
- padding: 0 1rem;
529
+ padding: 0; /* the three zones own their own padding */
530
530
  transition: background-color var(--transition-base), border-color var(--transition-base);
531
531
  flex-shrink: 0;
532
532
  position: relative;
533
533
  z-index: 10;
534
534
  }
535
+ /* Left zone sits exactly above the sidebar so the divider in front of the
536
+ session title lines up with the sidebar's right edge. The 1px margin
537
+ mirrors the #content-row border. */
535
538
  #header-left {
536
539
  display: flex;
537
540
  align-items: center;
538
541
  gap: 0.625rem;
542
+ align-self: stretch;
543
+ box-sizing: border-box;
544
+ width: var(--sidebar-width);
545
+ margin-left: 1px;
546
+ padding: 0 0.75rem 0 0.5rem;
547
+ min-width: 0;
548
+ overflow: hidden;
549
+ position: relative;
550
+ }
551
+ /* Short hairline, vertically centered — a border would touch the top and
552
+ bottom of the header, which reads as a hard seam instead of a divider. */
553
+ #header-left::after {
554
+ content: "";
555
+ position: absolute;
556
+ right: 0;
557
+ top: 50%;
558
+ transform: translateY(-50%);
559
+ width: 1px;
560
+ height: 1.25rem;
561
+ background: var(--color-border-primary);
562
+ transition: opacity var(--transition-base);
563
+ }
564
+ /* Collapsed sidebar → nothing to divide from, so the cluster shrinks back to
565
+ its natural width and the divider disappears. */
566
+ #app:has(#sidebar.hidden) #header-left {
567
+ width: auto;
568
+ }
569
+ #app:has(#sidebar.hidden) #header-left::after {
570
+ opacity: 0;
571
+ }
572
+ /* Active session title — aligned to the content column, right of the divider.
573
+ Empty (and hidden) on every non-session view. */
574
+ #header-session-title {
575
+ flex: 1;
576
+ min-width: 0;
577
+ padding: 0 1rem;
578
+ font-size: 0.875rem;
579
+ font-weight: 500;
580
+ color: var(--color-text-primary);
581
+ white-space: nowrap;
582
+ overflow: hidden;
583
+ text-overflow: ellipsis;
539
584
  }
540
585
  .sidebar-toggle-btn {
541
586
  width: 2.25rem;
@@ -567,7 +612,7 @@ body {
567
612
  display: flex;
568
613
  align-items: center;
569
614
  gap: 0.625rem;
570
- margin-left: 0.5rem;
615
+ margin-left: 0.25rem;
571
616
  min-width: 0;
572
617
  }
573
618
  .header-logo {
@@ -653,6 +698,7 @@ body {
653
698
  display: flex;
654
699
  align-items: center;
655
700
  gap: 0.375rem;
701
+ padding-right: 1rem;
656
702
  }
657
703
  #header-cmdbar {
658
704
  display: flex;
@@ -733,8 +779,13 @@ body {
733
779
  /* ── Sidebar ─────────────────────────────────────────────────────────────── */
734
780
  /* The sidebar sits on the outer frame (bg-primary) so it's visibly a layer
735
781
  BEHIND the chat surface (bg-secondary). Subtle right border separates them. */
736
- #sidebar {
782
+ /* The width lives on #app, not on #sidebar: the header's session divider and
783
+ title align to the same edge, so they must read the same value. JS writes
784
+ the dragged width here (see app.js / _initSidebarResize). */
785
+ #app {
737
786
  --sidebar-width: 16rem;
787
+ }
788
+ #sidebar {
738
789
  width: var(--sidebar-width);
739
790
  min-width: var(--sidebar-width);
740
791
  background: var(--color-bg-primary);
@@ -1186,11 +1237,11 @@ body {
1186
1237
 
1187
1238
  .cmd-palette {
1188
1239
  width: 100%;
1189
- max-width: 34rem;
1240
+ max-width: 50rem;
1190
1241
  margin: 0 1rem;
1191
1242
  background: var(--color-bg-card, var(--color-bg-secondary));
1192
1243
  border: 1px solid var(--color-border-primary);
1193
- border-radius: 12px;
1244
+ border-radius: 14px;
1194
1245
  box-shadow: var(--shadow-lg, 0 12px 40px rgba(0,0,0,0.25));
1195
1246
  overflow: hidden;
1196
1247
  transform: translateY(-8px) scale(0.99);
@@ -1204,27 +1255,147 @@ body {
1204
1255
  border-radius: 0;
1205
1256
  border-bottom: 1px solid var(--color-border-secondary);
1206
1257
  background: transparent;
1258
+ padding: 0.75rem;
1259
+ }
1260
+
1261
+ /* Input row reads as its own surface: pill outline, focus ring on the border */
1262
+ .cmd-palette .search-input-wrap {
1263
+ display: flex;
1264
+ align-items: center;
1265
+ gap: 0.375rem;
1266
+ }
1267
+ .cmd-palette .search-input-row {
1268
+ flex: 1;
1269
+ min-width: 0;
1270
+ height: 2.25rem;
1271
+ padding: 0 0.5rem 0 0.9375rem;
1272
+ gap: 0.5rem;
1273
+ border: 1px solid var(--color-border-primary);
1274
+ border-radius: var(--radius-pill);
1275
+ background: var(--color-bg-card);
1276
+ transition: border-color 0.12s ease;
1207
1277
  }
1208
- .cmd-palette .search-input-row { height: 3rem; }
1278
+ .cmd-palette .search-input-row:focus-within {
1279
+ border-color: color-mix(in srgb, var(--color-accent-primary) 45%, var(--color-border-primary));
1280
+ }
1281
+ .cmd-palette .search-icon svg { width: 1rem; height: 1rem; }
1209
1282
  .cmd-palette .search-input { font-size: 0.9375rem; }
1210
1283
 
1211
- /* ESC chip in the input row */
1212
- .cmd-palette-esc {
1284
+ /* Clear stays a text affordance; the pill outline owns the emphasis */
1285
+ .cmd-palette .btn-search-q-clear {
1213
1286
  flex-shrink: 0;
1214
- font-family: var(--font-mono);
1215
- font-size: 0.625rem;
1216
- letter-spacing: 0.04em;
1287
+ width: auto;
1288
+ height: 1.375rem;
1289
+ padding: 0 0.375rem;
1290
+ border-radius: 0.375rem;
1291
+ background: transparent;
1217
1292
  color: var(--color-text-muted);
1218
- padding: 0.125rem 0.375rem;
1219
- border: 1px solid var(--color-border-secondary);
1220
- border-radius: var(--radius-sm);
1221
- background: var(--color-bg-secondary);
1222
- cursor: pointer;
1223
- transition: color 0.12s, border-color 0.12s;
1293
+ font-size: 0.8125rem;
1294
+ opacity: 1;
1224
1295
  }
1225
- .cmd-palette-esc:hover {
1296
+ .cmd-palette .btn-search-q-clear:hover {
1226
1297
  color: var(--color-text-secondary);
1227
- border-color: var(--color-border-primary);
1298
+ background: var(--color-bg-hover);
1299
+ }
1300
+
1301
+ /* Filter row: chip-shaped triggers, no hairline divider */
1302
+ .cmd-palette .search-filter-row {
1303
+ height: auto;
1304
+ border-top: none;
1305
+ padding: 0.625rem 0 0;
1306
+ gap: 0.5rem;
1307
+ }
1308
+ .cmd-palette .search-select {
1309
+ flex: 0 0 auto;
1310
+ width: fit-content;
1311
+ min-width: 6.5rem;
1312
+ height: 1.875rem;
1313
+ border: 1px solid var(--color-border-primary);
1314
+ border-radius: var(--radius-pill);
1315
+ background: var(--color-bg-card);
1316
+ transition: border-color 0.12s ease, background 0.12s ease;
1317
+ }
1318
+ .cmd-palette .search-select .custom-select-trigger--bare {
1319
+ width: 100%;
1320
+ padding: 0 0.5rem 0 0.625rem;
1321
+ font-size: 0.8125rem;
1322
+ }
1323
+ .cmd-palette .search-select .placeholder,
1324
+ .cmd-palette .search-select .custom-select-value:not(.placeholder),
1325
+ .cmd-palette .search-select .custom-select-arrow {
1326
+ color: var(--color-text-primary);
1327
+ font-weight: 400;
1328
+ }
1329
+ .cmd-palette .search-date-wrap {
1330
+ flex: 0 0 auto;
1331
+ min-width: 6.5rem;
1332
+ height: 1.875rem;
1333
+ padding: 0;
1334
+ border: 1px solid var(--color-border-primary);
1335
+ border-radius: var(--radius-pill);
1336
+ background: var(--color-bg-card);
1337
+ color: var(--color-text-primary);
1338
+ transition: border-color 0.12s ease, background 0.12s ease;
1339
+ }
1340
+ /* Button spans the whole pill so padding and icon areas stay clickable */
1341
+ .cmd-palette .search-date {
1342
+ flex: 1 1 auto;
1343
+ height: 100%;
1344
+ padding: 0 2.1875rem 0 0.625rem;
1345
+ border-radius: inherit;
1346
+ font-size: 0.8125rem;
1347
+ color: inherit;
1348
+ }
1349
+ .cmd-palette .search-date-icon {
1350
+ right: 0.5rem;
1351
+ width: 0.9375rem;
1352
+ height: 0.9375rem;
1353
+ stroke-width: 1.8;
1354
+ color: inherit;
1355
+ }
1356
+ .cmd-palette .search-date-wrap:has(.search-date[data-active="true"]) {
1357
+ color: var(--color-accent-primary);
1358
+ }
1359
+
1360
+ /* Hover / expanded state for the two filter pills */
1361
+ .cmd-palette .search-select:hover,
1362
+ .cmd-palette .search-date-wrap:hover,
1363
+ .cmd-palette .search-select:has(.custom-select-trigger.open) {
1364
+ background: color-mix(in srgb, var(--color-bg-hover) 45%, var(--color-bg-card));
1365
+ }
1366
+ .cmd-palette .search-select:has(.custom-select-trigger.open) {
1367
+ border-color: var(--color-border-strong);
1368
+ }
1369
+ .cmd-palette .search-select:hover .placeholder,
1370
+ .cmd-palette .search-select:has(.custom-select-trigger.open) .placeholder {
1371
+ color: var(--color-text-primary);
1372
+ }
1373
+ .cmd-palette .search-select:hover .custom-select-arrow,
1374
+ .cmd-palette .search-select:has(.custom-select-trigger.open) .custom-select-arrow {
1375
+ color: var(--color-text-primary);
1376
+ }
1377
+ .cmd-palette .btn-search-clear-all { margin-left: auto; }
1378
+
1379
+ /* Close sits outside the pill, matching its height so the row reads as one unit */
1380
+ .cmd-palette-close {
1381
+ flex-shrink: 0;
1382
+ display: inline-flex;
1383
+ align-items: center;
1384
+ justify-content: center;
1385
+ width: 2.25rem;
1386
+ height: 2.25rem;
1387
+ padding: 0;
1388
+ border: none;
1389
+ border-radius: 50%;
1390
+ background: transparent;
1391
+ color: color-mix(in srgb, var(--color-text-secondary) 65%, var(--color-text-muted));
1392
+ cursor: pointer;
1393
+ transition: color 0.12s ease, background 0.12s ease;
1394
+ }
1395
+ .cmd-palette-close svg { width: 1rem; height: 1rem; }
1396
+ .cmd-palette-close:hover {
1397
+ color: var(--color-text-primary);
1398
+ background: var(--color-bg-hover);
1228
1399
  }
1229
1400
 
1230
1401
  /* Results region */
@@ -1234,6 +1405,45 @@ body {
1234
1405
  padding: 0.375rem 0.5rem 0.5rem;
1235
1406
  }
1236
1407
  .cmd-palette-results .session-item { border-radius: 7px; }
1408
+ .cmd-palette-results .session-body {
1409
+ display: grid;
1410
+ grid-template-columns: minmax(0, 1fr) auto;
1411
+ column-gap: 0.75rem;
1412
+ align-items: center;
1413
+ }
1414
+ .cmd-palette-results .session-meta {
1415
+ grid-column: 2;
1416
+ grid-row: 1;
1417
+ margin-top: 0;
1418
+ font-family: inherit;
1419
+ font-size: 0.6875rem;
1420
+ opacity: 1;
1421
+ }
1422
+ .cmd-palette-results .session-item:has(.session-snippet) {
1423
+ min-height: 2.625rem;
1424
+ box-sizing: border-box;
1425
+ }
1426
+ .cmd-palette-results .session-name {
1427
+ color: var(--color-text-primary);
1428
+ font-weight: 500;
1429
+ }
1430
+ .cmd-palette-results .session-name__text mark,
1431
+ .cmd-palette-results .session-snippet mark {
1432
+ background: color-mix(in srgb, #fde047 55%, transparent);
1433
+ color: var(--color-text-primary);
1434
+ }
1435
+ [data-theme="dark"] .cmd-palette-results .session-name__text mark,
1436
+ [data-theme="dark"] .cmd-palette-results .session-snippet mark {
1437
+ background: color-mix(in srgb, #facc15 78%, transparent);
1438
+ color: #111;
1439
+ }
1440
+ .cmd-palette-results .session-snippet {
1441
+ grid-column: 1 / -1;
1442
+ margin-top: 0.25rem;
1443
+ font-size: 0.72rem;
1444
+ color: var(--color-text-muted);
1445
+ line-height: 1.35;
1446
+ }
1237
1447
  .cmd-palette-hint {
1238
1448
  padding: 1.75rem 1rem;
1239
1449
  text-align: center;
@@ -1527,30 +1737,26 @@ body {
1527
1737
  .session-search-group + .session-search-group {
1528
1738
  margin-top: 0.25rem;
1529
1739
  }
1530
-
1531
1740
  .session-snippet {
1532
1741
  white-space: nowrap;
1533
1742
  overflow: hidden;
1534
1743
  text-overflow: ellipsis;
1535
- font-size: 0.6875rem;
1536
- color: var(--color-text-muted);
1744
+ font-size: 0.75rem;
1745
+ color: var(--color-text-secondary);
1537
1746
  margin-top: 2px;
1538
- line-height: 1.35;
1539
- opacity: 0.85;
1540
- }
1541
- .session-snippet mark {
1542
- background: var(--color-warning-bg, #fef3c7);
1543
- color: var(--color-text-primary);
1544
- padding: 0 2px;
1545
- border-radius: 2px;
1546
- font-weight: 500;
1747
+ line-height: 1.4;
1547
1748
  }
1548
-
1749
+ .session-snippet mark,
1549
1750
  .session-name__text mark {
1550
- background: var(--color-warning-bg, #fef3c7);
1551
- color: var(--color-text-primary);
1751
+ background: #fde047;
1752
+ color: #1f2328;
1552
1753
  padding: 0 1px;
1553
- border-radius: 2px;
1754
+ border-radius: 3px;
1755
+ }
1756
+ [data-theme="dark"] .session-snippet mark,
1757
+ [data-theme="dark"] .session-name__text mark {
1758
+ background: #facc15;
1759
+ color: #111;
1554
1760
  }
1555
1761
 
1556
1762
  /* Legacy .btn-new-inline — kept for compat, can be removed after cleanup */
@@ -1578,12 +1784,8 @@ body {
1578
1784
  margin-bottom: 1px;
1579
1785
  transition: background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);
1580
1786
  }
1581
- .session-item[draggable="true"] {
1582
- cursor: grab;
1583
- }
1584
1787
  .session-item.is-dragging {
1585
1788
  opacity: 0.55;
1586
- cursor: grabbing;
1587
1789
  }
1588
1790
  /* Two-row text block next to dot */
1589
1791
  .session-body {
@@ -1658,7 +1860,7 @@ body {
1658
1860
  border-radius: 0 2px 2px 0;
1659
1861
  }
1660
1862
 
1661
- /* Actions button (⋯) on session list items — visible only on hover */
1863
+ /* Actions button (⋯) on session list items — visible on hover or keyboard focus */
1662
1864
  .session-actions-btn {
1663
1865
  flex-shrink: 0;
1664
1866
  display: flex;
@@ -1679,7 +1881,7 @@ body {
1679
1881
  align-self: center;
1680
1882
  }
1681
1883
  .session-item:hover .session-actions-btn,
1682
- .session-item.active .session-actions-btn {
1884
+ .session-actions-btn:focus-visible {
1683
1885
  width: 1.5rem;
1684
1886
  opacity: 1;
1685
1887
  pointer-events: auto;
@@ -1766,22 +1968,28 @@ body {
1766
1968
  .session-actions-menu-item:hover .session-actions-menu-icon {
1767
1969
  color: var(--color-text-primary);
1768
1970
  }
1971
+ /* Entries with nothing to act on (e.g. "Close to the Right" on the last tab)
1972
+ stay in place so the menu does not shift, but are inert. */
1973
+ .session-actions-menu-item--disabled {
1974
+ color: var(--color-text-muted);
1975
+ cursor: default;
1976
+ }
1977
+ .session-actions-menu-item--disabled:hover {
1978
+ background: transparent;
1979
+ }
1980
+ .session-actions-menu-item--disabled .session-actions-menu-icon,
1981
+ .session-actions-menu-item--disabled:hover .session-actions-menu-icon {
1982
+ color: var(--color-text-muted);
1983
+ }
1769
1984
  .session-actions-menu-item--danger .session-actions-menu-icon {
1770
1985
  color: var(--color-text-secondary);
1771
1986
  }
1772
- .session-actions-menu-item--danger {
1773
- margin-top: 0.3125rem;
1774
- position: relative;
1775
- }
1776
- .session-actions-menu-item--danger::before {
1777
- content: "";
1778
- position: absolute;
1779
- left: 0;
1780
- right: 0;
1781
- top: -0.1875rem;
1987
+ /* The last group (archive + delete) is set off by an explicit divider element,
1988
+ so the danger styling itself draws no line. */
1989
+ .session-actions-menu-divider {
1782
1990
  height: 1px;
1991
+ margin: 0.3125rem 0;
1783
1992
  background: var(--color-border-secondary);
1784
- pointer-events: none;
1785
1993
  }
1786
1994
  .session-actions-menu-item--danger:hover {
1787
1995
  background: var(--color-error-bg);
@@ -2048,6 +2256,28 @@ body {
2048
2256
  background: var(--color-accent-primary);
2049
2257
  }
2050
2258
 
2259
+ /* ── Data management page (archive / recycle bin / backup tabs) ──────────── */
2260
+ #data-management-panel {
2261
+ flex: 1;
2262
+ display: flex;
2263
+ flex-direction: column;
2264
+ overflow: hidden;
2265
+ }
2266
+ /* Title + description share the tab bodies' side padding so everything lines up. */
2267
+ #data-management-header {
2268
+ padding: 1.75rem 2.25rem 0;
2269
+ }
2270
+ /* Margin (not padding) keeps the tab bar's underline inside the content column —
2271
+ padding would stretch the border line all the way to the panel edges. */
2272
+ #data-management-tabs {
2273
+ margin: 1.25rem 2.25rem 0;
2274
+ }
2275
+ /* Each tab is a full-height body of its own; min-height:0 lets it scroll. */
2276
+ #data-management-panel > .dm-view {
2277
+ min-height: 0;
2278
+ }
2279
+
2280
+
2051
2281
  .task-row {
2052
2282
  display: flex;
2053
2283
  align-items: center;
@@ -3259,11 +3489,12 @@ body {
3259
3489
  .aside-panel { flex: 1; min-height: 0; overflow: auto; display: none; flex-direction: column; }
3260
3490
  .aside-panel.active { display: flex; }
3261
3491
 
3262
- /* Floating opener shown when the column is collapsed. */
3492
+ /* Floating opener shown when the column is collapsed. Sits exactly where
3493
+ #btn-aside-collapse sits in the 40px tab bar so the toggle never jumps. */
3263
3494
  .session-aside-opener {
3264
3495
  position: absolute;
3265
- top: 0.5rem;
3266
- right: 0.5rem;
3496
+ top: 3.5px;
3497
+ right: 12px;
3267
3498
  z-index: 100;
3268
3499
  display: inline-flex;
3269
3500
  align-items: center;
@@ -3272,7 +3503,7 @@ body {
3272
3503
  height: 2rem;
3273
3504
  border: 1px solid var(--color-border-primary);
3274
3505
  background: var(--color-bg-secondary);
3275
- color: var(--color-text-secondary);
3506
+ color: var(--color-text-tertiary);
3276
3507
  border-radius: var(--radius-sm);
3277
3508
  cursor: pointer;
3278
3509
  box-shadow: var(--shadow-sm, 0 2px 8px rgba(0,0,0,0.06));
@@ -3576,12 +3807,59 @@ body {
3576
3807
  color: var(--color-text-tertiary);
3577
3808
  }
3578
3809
  .wv-code .cm-activeLineGutter { background: var(--color-bg-hover); }
3810
+ /* The picture is laid out at its natural size and moved by transform, which
3811
+ keeps the zoom percentage in the bar a true pixel ratio. */
3579
3812
  .wv-pane--image {
3813
+ overflow: hidden;
3814
+ background: var(--color-bg-primary);
3815
+ }
3816
+ .wv-zoom {
3817
+ flex: 1;
3818
+ min-width: 0;
3819
+ display: flex;
3580
3820
  align-items: center;
3581
3821
  justify-content: center;
3582
- background: var(--color-bg-primary);
3822
+ overflow: hidden;
3823
+ touch-action: none;
3824
+ }
3825
+ .wv-zoom img {
3826
+ /* flex: none keeps the picture at its natural size — a flex item would
3827
+ otherwise be shrunk to the pane and quietly break the pixel ratio. */
3828
+ flex: none;
3829
+ max-width: none;
3830
+ max-height: none;
3831
+ transform-origin: center center;
3832
+ will-change: transform;
3833
+ cursor: grab;
3834
+ user-select: none;
3835
+ -webkit-user-drag: none;
3836
+ }
3837
+ .wv-zoom.is-panning img { cursor: grabbing; }
3838
+ .wv-zoom-bar {
3839
+ position: absolute;
3840
+ right: 0.75rem;
3841
+ bottom: 0.75rem;
3842
+ display: flex;
3843
+ align-items: center;
3844
+ gap: 0.125rem;
3845
+ padding: 0.1875rem;
3846
+ border: 1px solid var(--color-border-secondary);
3847
+ border-radius: 9px;
3848
+ background: color-mix(in srgb, var(--color-bg-secondary) 88%, transparent);
3849
+ backdrop-filter: blur(6px);
3850
+ opacity: 0.85;
3851
+ transition: opacity 0.2s ease;
3852
+ }
3853
+ .wv-pane--image:hover .wv-zoom-bar { opacity: 1; }
3854
+ .wv-zoom-btn { width: 1.375rem; height: 1.375rem; }
3855
+ .wv-zoom-btn:disabled { opacity: 0.35; cursor: default; background: none; }
3856
+ .wv-zoom-level {
3857
+ min-width: 2.5rem;
3858
+ text-align: center;
3859
+ font-size: 0.6875rem;
3860
+ font-variant-numeric: tabular-nums;
3861
+ color: var(--color-text-tertiary);
3583
3862
  }
3584
- .wv-pane--image img { max-width: 100%; max-height: 100%; object-fit: contain; }
3585
3863
 
3586
3864
  /* PDF: browser-native rendering inside an iframe pointing at /api/local-image */
3587
3865
  .wv-pane--pdf { background: var(--color-bg-primary); }
@@ -3592,6 +3870,33 @@ body {
3592
3870
  background: var(--color-bg-primary);
3593
3871
  }
3594
3872
 
3873
+ /* Audio/video: browser-native controls over the existing range-capable local
3874
+ media endpoint. Unsupported codecs fall back to the regular Open action. */
3875
+ .wv-pane--media {
3876
+ align-items: center;
3877
+ justify-content: center;
3878
+ padding: 1rem;
3879
+ background: var(--color-bg-primary);
3880
+ }
3881
+ .wv-media-player--video {
3882
+ width: auto;
3883
+ height: auto;
3884
+ max-width: 100%;
3885
+ max-height: 100%;
3886
+ background: transparent;
3887
+ }
3888
+ .wv-media-player--audio {
3889
+ width: min(32rem, 100%);
3890
+ }
3891
+ .wv-media-error {
3892
+ display: none;
3893
+ font-size: 0.8125rem;
3894
+ color: var(--color-text-secondary);
3895
+ text-align: center;
3896
+ }
3897
+ .wv-pane--media.is-error .wv-media-player { display: none; }
3898
+ .wv-pane--media.is-error .wv-media-error { display: block; }
3899
+
3595
3900
  /* CSV/TSV: read-only table view; edit the original via "Open ▾" */
3596
3901
  .wv-pane--table {
3597
3902
  flex-direction: column;
@@ -3715,8 +4020,9 @@ body {
3715
4020
  font-weight: 600;
3716
4021
  }
3717
4022
 
3718
- /* Separator inside the "open with" dropdown */
3719
- .wv-open-menu-separator {
4023
+ /* Separator inside the "open with" dropdown and the tab context menu */
4024
+ .wv-open-menu-separator,
4025
+ .wv-menu-separator {
3720
4026
  height: 1px;
3721
4027
  margin: 0.25rem 0.375rem;
3722
4028
  background: var(--color-border-secondary);
@@ -3888,20 +4194,22 @@ body {
3888
4194
  .chat-nav-bookmark.bookmarked svg { fill: currentColor; }
3889
4195
  .chat-nav-user,
3890
4196
  .chat-nav-answer {
3891
- display: -webkit-box;
3892
- -webkit-box-orient: vertical;
3893
4197
  overflow: hidden;
3894
4198
  overflow-wrap: anywhere;
3895
4199
  font-size: 0.8125rem;
3896
4200
  line-height: 1.5;
3897
4201
  }
3898
4202
  .chat-nav-user {
3899
- -webkit-line-clamp: 1;
4203
+ display: block;
3900
4204
  padding-right: 2rem;
4205
+ text-overflow: ellipsis;
4206
+ white-space: nowrap;
3901
4207
  color: var(--color-text-primary);
3902
4208
  font-weight: 500;
3903
4209
  }
3904
4210
  .chat-nav-answer {
4211
+ display: -webkit-box;
4212
+ -webkit-box-orient: vertical;
3905
4213
  -webkit-line-clamp: 3;
3906
4214
  color: var(--color-text-muted);
3907
4215
  }
@@ -3912,6 +4220,7 @@ body {
3912
4220
  font-size: 0.9em;
3913
4221
  white-space: normal;
3914
4222
  }
4223
+ .chat-nav-popup .chat-nav-user code { white-space: inherit; }
3915
4224
  .chat-nav-popup.loading span {
3916
4225
  width: 100%;
3917
4226
  border-radius: 0.25rem;
@@ -4050,13 +4359,21 @@ body {
4050
4359
  }
4051
4360
  /* Time color / alignment: anchor to the bubble's own side, let width be
4052
4361
  driven by content — prevents overflow on narrow bubbles. */
4053
- /* User timestamp: appended to .msg-user-wrap (not bubble), sits below action
4054
- buttons, right-aligned. */
4055
- .msg-user-wrap > .msg-time { color: var(--color-text-secondary); top: auto; bottom: 0.2rem; right: 0; left: auto; padding-right: 0.25rem; }
4056
- .msg-assistant .msg-time { color: var(--color-text-secondary); left: 0; right: auto; padding-left: 0.25rem; }
4057
-
4058
- .msg-user { background: var(--color-accent-soft); color: var(--color-text-primary); white-space: pre-wrap; }
4059
- [data-theme="dark"] .msg-user { background: var(--color-accent-soft); }
4362
+ /* Timestamps live inside the user hover action bar (see .msg-user-actions),
4363
+ left of the icons. */
4364
+ .msg-user-actions > .msg-time {
4365
+ order: -1;
4366
+ position: static;
4367
+ margin: 0;
4368
+ padding: 0;
4369
+ font-size: 0.75rem;
4370
+ opacity: 1;
4371
+ transform: none;
4372
+ color: var(--color-text-tertiary);
4373
+ }
4374
+
4375
+ .msg-user { background: var(--color-accent-soft); color: var(--color-text-primary); white-space: pre-wrap; }
4376
+ [data-theme="dark"] .msg-user { background: var(--color-accent-soft); }
4060
4377
  .msg-assistant { background: var(--color-bg-tertiary); border: 1px solid var(--color-border-primary); align-self: flex-start; }
4061
4378
 
4062
4379
  /* Highlighted slash command inside a user bubble (/skill-name …). Rendered by
@@ -4077,14 +4394,15 @@ body {
4077
4394
  position: relative;
4078
4395
  display: flex;
4079
4396
  justify-content: flex-end;
4080
- padding-bottom: 3rem;
4397
+ padding-bottom: 1.85rem;
4081
4398
  }
4082
4399
  .msg-user-actions {
4083
4400
  position: absolute;
4084
- bottom: 1.1rem;
4401
+ bottom: 0.15rem;
4085
4402
  right: 0;
4086
4403
  display: flex;
4087
- gap: 0.125rem;
4404
+ align-items: center;
4405
+ gap: 0.25rem;
4088
4406
  opacity: 0;
4089
4407
  transition: opacity 0.15s ease;
4090
4408
  pointer-events: none;
@@ -4099,25 +4417,25 @@ body {
4099
4417
  display: inline-flex;
4100
4418
  align-items: center;
4101
4419
  justify-content: center;
4102
- width: 1.625rem;
4103
- height: 1.625rem;
4420
+ width: 1.5rem;
4421
+ height: 1.5rem;
4104
4422
  padding: 0;
4105
- border: 1px solid var(--color-border-primary);
4106
- border-radius: 5px;
4107
- background: var(--color-bg-primary);
4108
- color: var(--color-text-secondary);
4423
+ border: none;
4424
+ border-radius: 4px;
4425
+ background: transparent;
4426
+ color: var(--color-text-tertiary);
4109
4427
  cursor: pointer;
4110
- transition: color 0.15s ease, background 0.15s ease, border-color 0.15s ease;
4428
+ transition: color 0.15s ease, background 0.15s ease;
4111
4429
  }
4112
4430
  .msg-user-action-btn:hover {
4113
4431
  color: var(--color-text-primary);
4114
- background: var(--color-bg-tertiary);
4432
+ background: var(--color-bg-hover);
4115
4433
  }
4116
- .msg-user-action-btn:active { transform: translateY(1px); }
4117
- .msg-user-action-btn.is-copied {
4118
- color: var(--color-success);
4119
- border-color: var(--color-success);
4434
+ .msg-user-action-btn:active {
4435
+ color: var(--color-text-primary);
4436
+ background: var(--color-bg-hover);
4120
4437
  }
4438
+ .msg-user-action-btn.is-copied { color: var(--color-success); }
4121
4439
  .msg-user-copy-icon-check { display: none; }
4122
4440
  .msg-user-action-btn.is-copied .msg-user-copy-icon { display: none; }
4123
4441
  .msg-user-action-btn.is-copied .msg-user-copy-icon-check { display: inline; }
@@ -4544,6 +4862,7 @@ body {
4544
4862
  }
4545
4863
  .feedback-actions {
4546
4864
  display: flex;
4865
+ flex-wrap: wrap;
4547
4866
  justify-content: flex-end;
4548
4867
  align-items: center;
4549
4868
  gap: 0.5rem;
@@ -4578,10 +4897,11 @@ body {
4578
4897
  .feedback-actions [hidden] {
4579
4898
  display: none;
4580
4899
  }
4581
- .feedback-hint {
4900
+ .feedback-status {
4901
+ flex: 1;
4902
+ min-width: 8rem;
4582
4903
  font-size: 0.6875rem;
4583
4904
  color: var(--color-text-secondary);
4584
- margin-top: 0.375rem;
4585
4905
  }
4586
4906
  .feedback-card--submitted {
4587
4907
  opacity: 0.5;
@@ -4624,6 +4944,11 @@ body {
4624
4944
  flex-direction: column;
4625
4945
  gap: 0.5rem;
4626
4946
  }
4947
+ .msg-error.msg-history-error {
4948
+ background: transparent;
4949
+ border-color: var(--color-border-primary);
4950
+ color: var(--color-text-secondary);
4951
+ }
4627
4952
  .msg-error-actions {
4628
4953
  display: flex;
4629
4954
  align-items: center;
@@ -4759,50 +5084,163 @@ body {
4759
5084
  .tool-name { color: var(--color-warning); font-weight: 500; }
4760
5085
  .progress-msg { color: var(--color-accent-primary); font-size: 0.75rem; align-self: center; }
4761
5086
 
4762
- /* ── Todo / task list panel ────────────────────────────────────────────────
4763
- Fixed panel above the composer, driven by `todo_update` WS events.
4764
- Mirrors the CLI TodoArea: shows progress count + each task with a
4765
- done/pending marker. Hidden automatically when no todos are active. */
5087
+ /* ── Todo / task list ──────────────────────────────────────────────────────
5088
+ Compact session-bar entry driven by `todo_update` WS events. The icon
5089
+ remains as a stable anchor while progress + caret expand only when a list
5090
+ exists; the list itself opens in a fixed popover above the bar. */
4766
5091
  .todo-panel {
4767
- max-height: 12rem;
4768
- overflow-y: auto;
4769
- padding: 0.625rem 24px 0.625rem;
5092
+ display: inline-flex;
5093
+ align-items: center;
5094
+ flex-shrink: 0;
5095
+ }
5096
+ .todo-trigger {
5097
+ display: inline-flex;
5098
+ align-items: center;
5099
+ justify-content: flex-start;
5100
+ width: 1.375rem;
5101
+ height: 1.375rem;
5102
+ padding: 0.1875rem 0.25rem;
5103
+ overflow: hidden;
4770
5104
  background: transparent;
4771
- font-size: 0.8125rem;
5105
+ border: 0;
5106
+ border-radius: 3px;
5107
+ font: inherit;
5108
+ /* <button> does not inherit color: without this the icon, count and caret
5109
+ fall back to the UA black, which is unreadable on dark themes. */
5110
+ color: var(--color-text-primary);
5111
+ white-space: nowrap;
5112
+ cursor: default;
5113
+ transition: width 0.18s ease, color 0.15s ease, background-color 0.15s ease;
4772
5114
  }
4773
- .todo-header {
4774
- display: flex;
5115
+ .todo-panel.has-todos .todo-trigger {
5116
+ cursor: pointer;
5117
+ opacity: 0.75;
5118
+ }
5119
+ .todo-panel.has-todos .todo-trigger:hover,
5120
+ .todo-panel.has-todos .todo-trigger:focus-visible,
5121
+ .todo-panel.is-open .todo-trigger {
5122
+ color: var(--color-accent-primary);
5123
+ background: var(--color-bg-hover);
5124
+ opacity: 1;
5125
+ outline: none;
5126
+ }
5127
+ .todo-trigger:disabled {
5128
+ color: var(--color-text-muted);
5129
+ opacity: 0.65;
5130
+ }
5131
+ .todo-status-icon {
5132
+ width: 0.875rem;
5133
+ height: 0.875rem;
5134
+ flex: 0 0 0.875rem;
5135
+ }
5136
+ .todo-trigger-detail {
5137
+ display: inline-flex;
4775
5138
  align-items: center;
4776
- gap: 0.375rem;
4777
- color: var(--color-text-tertiary);
5139
+ gap: 0.1875rem;
5140
+ max-width: 0;
5141
+ margin-left: 0;
5142
+ overflow: hidden;
5143
+ opacity: 0;
5144
+ transform: translateX(-0.1875rem);
5145
+ transition: max-width 0.18s ease, margin-left 0.18s ease,
5146
+ opacity 0.12s ease, transform 0.18s ease;
5147
+ }
5148
+ .todo-panel.has-todos .todo-trigger-detail {
5149
+ max-width: 5rem;
5150
+ margin-left: 0.3125rem;
5151
+ opacity: 1;
5152
+ transform: translateX(0);
5153
+ }
5154
+ .todo-count {
5155
+ color: inherit;
4778
5156
  font-weight: 500;
4779
- font-size: 0.75rem;
4780
- margin-bottom: 0.5rem;
4781
- cursor: pointer;
4782
- user-select: none;
5157
+ font-variant-numeric: tabular-nums;
4783
5158
  }
4784
- .todo-header:hover { color: var(--color-text-secondary); }
4785
- .todo-toggle {
4786
- width: 0.7rem;
4787
- height: 0.7rem;
5159
+ .todo-caret {
5160
+ width: 0.625rem;
5161
+ height: 0.625rem;
4788
5162
  flex-shrink: 0;
5163
+ /* Dimmed relative to the icon and count, matching #session-info-bar .sib-caret. */
5164
+ opacity: 0.75;
4789
5165
  transition: transform 0.15s ease;
4790
5166
  }
4791
- .todo-panel.collapsed .todo-list { display: none; }
4792
- .todo-panel.collapsed .todo-toggle { transform: rotate(-90deg); }
4793
- .todo-panel.collapsed .todo-header { margin-bottom: 0; }
4794
- .todo-count {
4795
- font-weight: 500;
4796
- color: var(--color-text-muted, var(--color-text-tertiary));
5167
+ .todo-panel:not(.is-open) .todo-caret { transform: rotate(180deg); }
5168
+
5169
+ .todo-popover {
5170
+ position: fixed;
5171
+ display: flex;
5172
+ flex-direction: column;
5173
+ z-index: 1000;
5174
+ width: min(22rem, calc(100vw - 1.5rem));
5175
+ max-height: min(24rem, calc(100vh - 7rem));
5176
+ overflow: hidden;
5177
+ transform: translate(-50%, -100%);
5178
+ font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
5179
+ white-space: normal;
5180
+ }
5181
+ .todo-popover[hidden] { display: none; }
5182
+ .todo-popover-header {
5183
+ display: flex;
5184
+ align-items: center;
5185
+ justify-content: space-between;
5186
+ gap: 0.75rem;
5187
+ padding: 0.4375rem 0.5rem 0.4375rem 0.75rem;
5188
+ border-bottom: 1px solid var(--color-border-primary);
5189
+ }
5190
+ .todo-popover-heading {
5191
+ display: flex;
5192
+ align-items: baseline;
5193
+ min-width: 0;
5194
+ gap: 0.5rem;
5195
+ white-space: nowrap;
5196
+ }
5197
+ .todo-popover-title {
5198
+ color: var(--color-text-primary);
5199
+ font-size: 0.8125rem;
5200
+ font-weight: 600;
5201
+ }
5202
+ .todo-summary {
5203
+ color: var(--color-text-tertiary);
5204
+ font-size: 0.6875rem;
5205
+ }
5206
+ .todo-close {
5207
+ display: inline-flex;
5208
+ align-items: center;
5209
+ justify-content: center;
5210
+ width: 1.75rem;
5211
+ height: 1.75rem;
5212
+ padding: 0;
5213
+ color: var(--color-text-tertiary);
5214
+ background: transparent;
5215
+ border: 0;
5216
+ border-radius: 4px;
5217
+ cursor: pointer;
5218
+ }
5219
+ .todo-close:hover,
5220
+ .todo-close:focus-visible {
5221
+ color: var(--color-text-primary);
5222
+ background: var(--color-bg-hover);
5223
+ outline: none;
5224
+ }
5225
+ .todo-close svg { width: 0.875rem; height: 0.875rem; }
5226
+ .todo-list {
5227
+ display: flex;
5228
+ flex-direction: column;
5229
+ flex: 1;
5230
+ gap: 1px;
5231
+ min-height: 0;
5232
+ max-height: 18rem;
5233
+ overflow-y: auto;
5234
+ padding: 0.375rem;
4797
5235
  }
4798
- .todo-list { display: flex; flex-direction: column; gap: 1px; }
4799
5236
  .todo-item {
4800
5237
  display: flex;
4801
5238
  align-items: center;
4802
5239
  gap: 0.5rem;
4803
- padding: 0.2rem 0.5rem;
5240
+ padding: 0.4375rem 0.5rem;
4804
5241
  border-radius: var(--radius-sm, 6px);
4805
5242
  color: var(--color-text-secondary);
5243
+ font-size: 0.75rem;
4806
5244
  line-height: 1.4;
4807
5245
  transition: background 0.12s ease;
4808
5246
  }
@@ -4817,7 +5255,33 @@ body {
4817
5255
  .todo-item:not(.done) .todo-icon {
4818
5256
  color: var(--color-border-strong, #d4d4ce);
4819
5257
  }
4820
- .todo-text { flex: 1; word-break: break-word; }
5258
+ .todo-text {
5259
+ flex: 1;
5260
+ min-width: 0;
5261
+ overflow: hidden;
5262
+ white-space: nowrap;
5263
+ -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 1rem), transparent);
5264
+ mask-image: linear-gradient(to right, #000 calc(100% - 1rem), transparent);
5265
+ }
5266
+ .todo-text__content {
5267
+ display: inline-block;
5268
+ min-width: max-content;
5269
+ transform: translateX(0);
5270
+ transition: transform 0.18s ease-out;
5271
+ }
5272
+ .todo-item.todo-text-scrolling .todo-text__content {
5273
+ transform: translateX(calc(-1 * var(--todo-text-scroll-distance)));
5274
+ transition-duration: var(--todo-text-scroll-duration);
5275
+ transition-delay: 0.3s;
5276
+ transition-timing-function: linear;
5277
+ }
5278
+
5279
+ @media (prefers-reduced-motion: reduce) {
5280
+ .todo-trigger,
5281
+ .todo-trigger-detail,
5282
+ .todo-caret,
5283
+ .todo-text__content { transition: none; }
5284
+ }
4821
5285
 
4822
5286
  /* ── Token usage line ────────────────────────────────────────────────────── */
4823
5287
  .token-usage-line {
@@ -5253,7 +5717,7 @@ body {
5253
5717
  /*
5254
5718
  * Visual hierarchy (3 tiers):
5255
5719
  * Tier 1 primary (opacity 1.00) — status, model → "what am I and is it running"
5256
- * Tier 2 secondary (opacity ~0.75) — dir, tasks → "contextual info"
5720
+ * Tier 2 secondary (opacity ~0.75) — dir, todo → "contextual info"
5257
5721
  * Tier 3 tertiary (opacity ~0.45) — id, mode, cost, │ → "can be glanced over"
5258
5722
  * Base bar opacity is kept high so primary tier stays fully readable; the
5259
5723
  * hover :hover rule lifts everything to 1.0 for detail inspection.
@@ -5305,11 +5769,6 @@ body {
5305
5769
  opacity: 0.85;
5306
5770
  }
5307
5771
 
5308
- /* Show all fields by default, not just on hover */
5309
- .sib-detail {
5310
- display: contents;
5311
- }
5312
-
5313
5772
  #sib-id {
5314
5773
  opacity: 1;
5315
5774
  font-size: 0.625rem;
@@ -5457,14 +5916,27 @@ body {
5457
5916
  opacity: 1;
5458
5917
  }
5459
5918
 
5460
- #sib-mode { opacity: 0.45; flex-shrink: 0; } /* tier 3 */
5919
+ #sib-mode {
5920
+ flex-shrink: 0;
5921
+ display: inline-flex;
5922
+ align-items: center;
5923
+ gap: 0.3125rem;
5924
+ font: inherit;
5925
+ cursor: pointer;
5926
+ transition: all 0.15s ease;
5927
+ }
5928
+ #sib-mode:disabled { opacity: 0.5; cursor: default; }
5929
+ #sib-mode-menu { position: fixed; width: 240px; box-sizing: border-box; white-space: normal; padding: 4px; z-index: 1000; }
5930
+ #sib-mode-menu[hidden] { display: none; }
5931
+ #sib-mode-menu button { display: block; width: 100%; text-align: left; padding: 9px; border: 0; border-radius: 6px; background: transparent; color: var(--color-text-primary); cursor: pointer; }
5932
+ #sib-mode-menu button:hover, #sib-mode-menu button[aria-checked="true"] { background: var(--color-bg-hover); }
5933
+ #sib-mode-menu small { display: block; margin-top: 4px; color: var(--color-text-secondary); line-height: 1.5; }
5461
5934
  #sib-model { opacity: 1; flex-shrink: 0; } /* tier 1 — the model is primary */
5462
- #sib-tasks { opacity: 0.75; flex-shrink: 0; } /* tier 2 */
5463
5935
  #sib-cost { opacity: 0.45; flex-shrink: 0; } /* tier 3 */
5464
5936
 
5465
- /* ── Latency signal (right after model name) ──────────────────────────────
5466
- A compact 4-bar signal + TTFT value. Placed adjacent to #sib-model so the
5467
- user's mental mapping "this model is fast/slow" is immediate. Variant
5937
+ /* ── Latency signal (right of the task list) ──────────────────────────────
5938
+ A compact 4-bar signal + TTFT value. A trailing metric, so it sits after
5939
+ the pickers and the task list, just before the cost. Variant
5468
5940
  classes (-ok/-warn/-bad) are applied by Sessions._renderSignal based on
5469
5941
  TTFT thresholds; colours intentionally use CSS vars so the same palette
5470
5942
  works in both light and dark themes. */
@@ -5574,10 +6046,11 @@ body {
5574
6046
  #sib-model.sib-model-no-subs .sib-caret {
5575
6047
  display: none;
5576
6048
  }
5577
- /* The dir/reasoning carets are hints only: clicking them must reach the row,
5578
- and the row hover already provides the feedback. */
6049
+ /* The dir/reasoning/mode carets are hints only: clicking them must reach the
6050
+ row, and the row hover already provides the feedback. */
5579
6051
  #sib-dir .sib-caret,
5580
- .sib-reasoning-clickable .sib-caret {
6052
+ .sib-reasoning-clickable .sib-caret,
6053
+ .sib-mode-clickable .sib-caret {
5581
6054
  pointer-events: none;
5582
6055
  }
5583
6056
  .sib-model-clickable:hover .sib-caret,
@@ -5585,7 +6058,9 @@ body {
5585
6058
  #sib-dir:hover .sib-caret,
5586
6059
  #sib-dir.is-open .sib-caret,
5587
6060
  .sib-reasoning-clickable:hover .sib-caret,
5588
- .sib-reasoning-clickable.is-open .sib-caret {
6061
+ .sib-reasoning-clickable.is-open .sib-caret,
6062
+ .sib-mode-clickable:hover .sib-caret,
6063
+ .sib-mode-clickable.is-open .sib-caret {
5589
6064
  opacity: 1;
5590
6065
  }
5591
6066
  /* Hovering the model caret needs feedback of its own: the row hover already
@@ -5597,7 +6072,8 @@ body {
5597
6072
  }
5598
6073
  .sib-model-clickable.is-open .sib-caret,
5599
6074
  #sib-dir.is-open .sib-caret,
5600
- .sib-reasoning-clickable.is-open .sib-caret {
6075
+ .sib-reasoning-clickable.is-open .sib-caret,
6076
+ .sib-mode-clickable.is-open .sib-caret {
5601
6077
  transform: rotate(180deg);
5602
6078
  }
5603
6079
  .sib-model-disabled .sib-caret {
@@ -5632,7 +6108,8 @@ body {
5632
6108
  /* Pickers render as outlined pills so they read as controls, not status text. */
5633
6109
  #session-info-bar #sib-dir,
5634
6110
  #session-info-bar .sib-model-clickable,
5635
- #session-info-bar .sib-reasoning-clickable {
6111
+ #session-info-bar .sib-reasoning-clickable,
6112
+ #session-info-bar .sib-mode-clickable {
5636
6113
  height: 1.375rem;
5637
6114
  padding: 0 0.4375rem 0 0.5rem;
5638
6115
  border: 1px solid var(--color-border-primary);
@@ -5644,9 +6121,11 @@ body {
5644
6121
  #session-info-bar #sib-dir:hover,
5645
6122
  #session-info-bar .sib-model-clickable:hover,
5646
6123
  #session-info-bar .sib-reasoning-clickable:hover,
6124
+ #session-info-bar .sib-mode-clickable:hover,
5647
6125
  #session-info-bar #sib-dir.is-open,
5648
6126
  #session-info-bar .sib-model-clickable.is-open,
5649
- #session-info-bar .sib-reasoning-clickable.is-open {
6127
+ #session-info-bar .sib-reasoning-clickable.is-open,
6128
+ #session-info-bar .sib-mode-clickable.is-open {
5650
6129
  background: var(--color-bg-secondary);
5651
6130
  border-color: var(--color-text-tertiary);
5652
6131
  color: var(--color-text-primary);
@@ -5663,7 +6142,8 @@ body {
5663
6142
  display: none;
5664
6143
  }
5665
6144
  #sib-model-wrap,
5666
- #sib-reasoning-wrap {
6145
+ #sib-reasoning-wrap,
6146
+ #sib-mode-wrap {
5667
6147
  margin-left: 0.1875rem;
5668
6148
  }
5669
6149
  .sib-reasoning-dropdown {
@@ -5753,9 +6233,11 @@ body {
5753
6233
  margin-left: 0.5rem;
5754
6234
  opacity: 0.8;
5755
6235
  }
6236
+ /* Same tokens as .sib-submodel-default-tag: the dropdown's "default" badge and
6237
+ the variant panel's tag say the same word, so they wear one color. */
5756
6238
  .sib-model-option .model-badge.default {
5757
- background: var(--color-success-bg);
5758
- color: var(--color-success);
6239
+ background: color-mix(in srgb, var(--color-text-secondary) 15%, transparent);
6240
+ color: var(--color-text-secondary);
5759
6241
  }
5760
6242
  .sib-model-option .model-badge.lite {
5761
6243
  background: color-mix(in srgb, var(--color-accent-primary) 10%, transparent);
@@ -5816,7 +6298,7 @@ body {
5816
6298
  display: inline-flex;
5817
6299
  align-items: center;
5818
6300
  justify-content: center;
5819
- transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease;
6301
+ transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
5820
6302
  }
5821
6303
  .sib-submodel-toggle:hover {
5822
6304
  background: color-mix(in srgb, var(--color-accent-primary) 15%, transparent);
@@ -5841,6 +6323,22 @@ body {
5841
6323
  transition: transform 0.18s ease;
5842
6324
  }
5843
6325
 
6326
+ /* The switcher is an affordance, not information: every row can offer one
6327
+ (each row knows its provider's models), so it stays out of the way until the
6328
+ row is reached. The selected row keeps it visible — it marks that the
6329
+ session already runs a variant. */
6330
+ .sib-model-option .sib-submodel-toggle {
6331
+ opacity: 0;
6332
+ pointer-events: none;
6333
+ }
6334
+ .sib-model-option:hover .sib-submodel-toggle,
6335
+ .sib-model-option.current .sib-submodel-toggle,
6336
+ .sib-model-option.submodel-open .sib-submodel-toggle,
6337
+ .sib-model-option .sib-submodel-toggle:focus-visible {
6338
+ opacity: 1;
6339
+ pointer-events: auto;
6340
+ }
6341
+
5844
6342
  .sib-model-option.submodel-open {
5845
6343
  background: var(--color-bg-hover);
5846
6344
  }
@@ -5913,36 +6411,6 @@ body {
5913
6411
  white-space: nowrap;
5914
6412
  }
5915
6413
 
5916
- /* ── Model picker header ───────────────────────────────────────────────────
5917
- The dropdown lists models from every provider, so the header states which
5918
- provider and model are live — otherwise the rows read as a provider chooser.
5919
- It scrolls away with the list; .sib-model-bench below is the sticky row. */
5920
- .sib-picker-head {
5921
- padding: 0.5rem 0.875rem 0.4375rem;
5922
- border-bottom: 1px solid var(--color-border-primary);
5923
- }
5924
- .sib-picker-title {
5925
- font-size: 0.6875rem;
5926
- font-weight: 600;
5927
- color: var(--color-text-primary);
5928
- }
5929
- .sib-picker-sub {
5930
- margin-top: 0.1875rem;
5931
- font-size: 0.625rem;
5932
- color: var(--color-text-secondary);
5933
- overflow: hidden;
5934
- text-overflow: ellipsis;
5935
- white-space: nowrap;
5936
- }
5937
- .sib-picker-sub-value {
5938
- font-family: var(--font-mono, monospace);
5939
- color: var(--color-text-primary);
5940
- }
5941
- .sib-picker-sub-sep {
5942
- padding: 0 0.3125rem;
5943
- color: var(--color-text-tertiary);
5944
- }
5945
-
5946
6414
  /* ── Model switcher benchmark banner & latency column ──────────────────────
5947
6415
  The banner sits at the top of the dropdown with a subtle border so it
5948
6416
  visually separates from the scrollable model list below. The ⚡ button is
@@ -6108,8 +6576,10 @@ body {
6108
6576
  .sib-model-option .sib-model-name.has-sub {
6109
6577
  flex-direction: column;
6110
6578
  align-items: stretch;
6111
- gap: 0.1875rem;
6579
+ gap: 0.375rem;
6112
6580
  }
6581
+ /* Lead line of a row: the provider the model runs through (or the model name
6582
+ on rows with no provider), so it carries the weight. */
6113
6583
  .sib-model-option .sib-model-name-main {
6114
6584
  display: inline-flex;
6115
6585
  align-items: center;
@@ -6117,6 +6587,7 @@ body {
6117
6587
  overflow: hidden;
6118
6588
  flex: 0 1 auto;
6119
6589
  min-width: 0;
6590
+ font-weight: 500;
6120
6591
  }
6121
6592
  .sib-model-option .sib-model-name-text {
6122
6593
  overflow: hidden;
@@ -6172,15 +6643,25 @@ body {
6172
6643
  fill: initial;
6173
6644
  }
6174
6645
  .sib-model-option .sib-model-name-sub {
6175
- /* Indent past the vendor badge so it lines up with the model name. */
6176
- padding-left: 1.5rem;
6646
+ /* Detail line under the provider: vendor mark + model name (+ remark), so it
6647
+ starts at the row's left edge instead of indenting past the badge. */
6648
+ display: inline-flex;
6649
+ align-items: center;
6650
+ gap: 0.375rem;
6177
6651
  font-size: 0.6875rem;
6178
6652
  color: var(--color-text-secondary);
6179
- opacity: 0.75;
6653
+ overflow: hidden;
6654
+ max-width: 100%;
6655
+ }
6656
+ /* The selected row accents its provider line only; the model stays a detail. */
6657
+ .sib-model-option.current .sib-model-name-sub .sib-model-name-text {
6658
+ color: var(--color-text-secondary);
6659
+ }
6660
+ .sib-model-option .sib-model-name-remark {
6661
+ color: var(--color-text-tertiary);
6180
6662
  overflow: hidden;
6181
6663
  text-overflow: ellipsis;
6182
6664
  white-space: nowrap;
6183
- max-width: 100%;
6184
6665
  }
6185
6666
  .sib-model-option .sib-model-name-override {
6186
6667
  font-size: 0.6875rem;
@@ -6197,6 +6678,36 @@ body {
6197
6678
  gap: 0.5rem;
6198
6679
  flex-shrink: 0;
6199
6680
  }
6681
+ /* Per-row shortcut to the provider's own console (top up / manage the key).
6682
+ Revealed on hover or keyboard focus so it never competes with the model name.
6683
+ Opacity rather than display, so revealing it cannot shift the rest of the row;
6684
+ pointer-events follow the same states so an invisible chip stays unclickable. */
6685
+ .sib-model-option .sib-topup-btn {
6686
+ display: inline-flex;
6687
+ align-items: center;
6688
+ flex: 0 0 auto;
6689
+ padding: 1px 0.4375rem;
6690
+ font-size: 0.5625rem;
6691
+ font-family: inherit;
6692
+ line-height: 1.6;
6693
+ border: 1px solid color-mix(in srgb, var(--color-accent-primary) 40%, transparent);
6694
+ border-radius: 8px;
6695
+ background: color-mix(in srgb, var(--color-accent-primary) 8%, transparent);
6696
+ color: var(--color-accent-primary);
6697
+ text-decoration: none;
6698
+ white-space: nowrap;
6699
+ opacity: 0;
6700
+ pointer-events: none;
6701
+ transition: opacity 0.12s ease;
6702
+ }
6703
+ .sib-model-option:hover .sib-topup-btn,
6704
+ .sib-model-option .sib-topup-btn:focus-visible {
6705
+ opacity: 1;
6706
+ pointer-events: auto;
6707
+ }
6708
+ .sib-model-option .sib-topup-btn:hover {
6709
+ background: color-mix(in srgb, var(--color-accent-primary) 16%, transparent);
6710
+ }
6200
6711
  .sib-model-price {
6201
6712
  font-size: 0.625rem;
6202
6713
  font-variant-numeric: tabular-nums;
@@ -6291,7 +6802,8 @@ body {
6291
6802
  }
6292
6803
 
6293
6804
  /* Attachment strip — top part of the composer card, above the input row */
6294
- #image-preview-strip {
6805
+ #image-preview-strip,
6806
+ #ns-image-preview-strip {
6295
6807
  display: flex;
6296
6808
  flex-wrap: wrap;
6297
6809
  gap: 0.5rem;
@@ -6299,12 +6811,6 @@ body {
6299
6811
  /* Divider to the input row — the strip is display:none when no chips exist. */
6300
6812
  border-bottom: 1px solid var(--color-border-primary);
6301
6813
  }
6302
- #ns-image-preview-strip {
6303
- display: flex;
6304
- flex-wrap: wrap;
6305
- gap: 0.5rem;
6306
- padding: 0.25rem 0 0.5rem;
6307
- }
6308
6814
  .img-preview-item {
6309
6815
  position: relative;
6310
6816
  width: 3.25rem;
@@ -6319,6 +6825,7 @@ body {
6319
6825
  height: 100%;
6320
6826
  object-fit: cover;
6321
6827
  }
6828
+ .img-preview-thumb { cursor: zoom-in; }
6322
6829
  .img-preview-remove {
6323
6830
  position: absolute;
6324
6831
  top: 2px;
@@ -7168,6 +7675,16 @@ body {
7168
7675
  line-height: 1.5;
7169
7676
  margin: 0;
7170
7677
  }
7678
+ .settings-network-note {
7679
+ font-size: 0.75rem;
7680
+ color: var(--color-text-tertiary);
7681
+ line-height: 1.5;
7682
+ margin: 0;
7683
+ }
7684
+ .settings-network-divider {
7685
+ border-top: 1px solid var(--color-border-secondary);
7686
+ padding-top: 0.75rem;
7687
+ }
7171
7688
 
7172
7689
  /* ── Backup section ── */
7173
7690
  .backup-auto-card {
@@ -7191,11 +7708,6 @@ body {
7191
7708
  .backup-option {
7192
7709
  margin: 0.75rem 0;
7193
7710
  }
7194
- .backup-option-nested {
7195
- margin: 0.5rem 0 0.25rem 2.75rem;
7196
- font-size: 0.78125rem;
7197
- color: var(--color-text-secondary);
7198
- }
7199
7711
  .backup-auto-row {
7200
7712
  display: flex;
7201
7713
  align-items: center;
@@ -7229,19 +7741,33 @@ body {
7229
7741
  text-overflow: ellipsis;
7230
7742
  white-space: nowrap;
7231
7743
  }
7232
- .btn-settings-link {
7744
+ /* One button shape for the whole backup page — "Open", "Download backup" and
7745
+ "Restore backup" are the same kind of control, so they share size, radius and
7746
+ hover instead of the two sizes the path row and the action row used to have. */
7747
+ .btn-backup-action {
7233
7748
  flex-shrink: 0;
7234
- background: transparent;
7749
+ display: inline-flex;
7750
+ align-items: center;
7751
+ gap: 0.375rem;
7752
+ height: 2rem;
7753
+ padding: 0 0.875rem;
7235
7754
  border: 1px solid var(--color-border-primary);
7236
- border-radius: 6px;
7237
- color: var(--color-accent-primary);
7238
- font-size: 0.75rem;
7239
- padding: 0.25rem 0.625rem;
7755
+ border-radius: 8px;
7756
+ background: transparent;
7757
+ color: var(--color-text-secondary);
7758
+ font-size: 0.8125rem;
7240
7759
  cursor: pointer;
7241
7760
  white-space: nowrap;
7761
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
7242
7762
  }
7243
- .btn-settings-link:hover {
7244
- border-color: var(--color-accent-primary);
7763
+ .btn-backup-action:hover:not(:disabled) {
7764
+ background: var(--color-bg-hover);
7765
+ border-color: var(--color-text-muted);
7766
+ color: var(--color-text-primary);
7767
+ }
7768
+ .btn-backup-action:disabled {
7769
+ opacity: 0.5;
7770
+ cursor: not-allowed;
7245
7771
  }
7246
7772
  .backup-actions {
7247
7773
  display: flex;
@@ -7255,9 +7781,6 @@ body {
7255
7781
  padding-top: 0.75rem;
7256
7782
  border-top: 1px solid var(--color-border);
7257
7783
  }
7258
- .btn-settings-action--file {
7259
- cursor: pointer;
7260
- }
7261
7784
  .backup-actions-sep {
7262
7785
  width: 1px;
7263
7786
  height: 1.25rem;
@@ -7295,6 +7818,77 @@ body {
7295
7818
  gap: 0.625rem;
7296
7819
  flex-shrink: 0;
7297
7820
  }
7821
+
7822
+ /* ── Browser screenshot settings ────────────────────────────────────────── */
7823
+ .settings-screenshot {
7824
+ display: flex;
7825
+ flex-direction: column;
7826
+ gap: 0.5rem;
7827
+ padding: 0.875rem 1rem;
7828
+ background: var(--color-bg-secondary);
7829
+ border: 1px solid var(--color-border-primary);
7830
+ border-radius: 10px;
7831
+ }
7832
+ .settings-screenshot-row {
7833
+ display: flex;
7834
+ align-items: center;
7835
+ gap: 0.625rem;
7836
+ }
7837
+ .settings-screenshot-head {
7838
+ display: flex;
7839
+ flex-direction: column;
7840
+ gap: 0.1875rem;
7841
+ padding-bottom: 0.125rem;
7842
+ }
7843
+ .settings-screenshot-title {
7844
+ font-size: 0.8125rem;
7845
+ font-weight: 600;
7846
+ color: var(--color-text-primary);
7847
+ }
7848
+ .settings-screenshot-desc {
7849
+ margin: 0.25rem 0 0.5rem 0;
7850
+ font-size: 0.75rem;
7851
+ color: var(--color-text-tertiary);
7852
+ line-height: 1.5;
7853
+ }
7854
+ .settings-screenshot-label {
7855
+ font-size: 0.8125rem;
7856
+ color: var(--color-text-secondary);
7857
+ flex-shrink: 0;
7858
+ }
7859
+ .settings-segmented {
7860
+ display: inline-flex;
7861
+ gap: 2px;
7862
+ padding: 2px;
7863
+ background: var(--color-bg-input);
7864
+ border: 1px solid var(--color-border-primary);
7865
+ border-radius: 7px;
7866
+ }
7867
+ .settings-segmented-btn {
7868
+ border: 0;
7869
+ background: transparent;
7870
+ color: var(--color-text-secondary);
7871
+ font-size: 0.75rem;
7872
+ line-height: 1.4;
7873
+ padding: 0.1875rem 0.625rem;
7874
+ border-radius: 5px;
7875
+ cursor: pointer;
7876
+ transition: background 0.15s, color 0.15s;
7877
+ }
7878
+ .settings-segmented-btn:hover:not(.active) {
7879
+ background: var(--color-bg-hover);
7880
+ color: var(--color-text-primary);
7881
+ }
7882
+ .settings-segmented-btn.active {
7883
+ background: var(--color-accent-primary);
7884
+ color: var(--color-text-inverse);
7885
+ }
7886
+ .settings-screenshot-hint {
7887
+ margin: 0 0 0.25rem 0;
7888
+ font-size: 0.75rem;
7889
+ color: var(--color-text-tertiary);
7890
+ line-height: 1.5;
7891
+ }
7298
7892
  .toggle-switch {
7299
7893
  position: relative;
7300
7894
  display: inline-flex;
@@ -7859,8 +8453,21 @@ body {
7859
8453
  white-space: nowrap;
7860
8454
  }
7861
8455
  .model-card-grid-remark {
8456
+ display: flex;
8457
+ align-items: baseline;
8458
+ gap: 0.375rem;
8459
+ min-width: 0;
7862
8460
  font-size: 0.75rem;
7863
8461
  color: var(--color-text-secondary);
8462
+ line-height: 1.4;
8463
+ }
8464
+ .model-card-grid-remark-label {
8465
+ flex-shrink: 0;
8466
+ color: var(--color-text-tertiary);
8467
+ font-size: 0.6875rem;
8468
+ letter-spacing: 0.3px;
8469
+ }
8470
+ .model-card-grid-remark-text {
7864
8471
  overflow: hidden;
7865
8472
  text-overflow: ellipsis;
7866
8473
  white-space: nowrap;
@@ -11882,7 +12489,20 @@ body.setup-mode {
11882
12489
  .badge-active { color: var(--color-success); }
11883
12490
  .badge-expired { color: var(--color-error); }
11884
12491
  .badge-expiring { color: var(--color-warning); }
11885
- .badge-unreachable { color: var(--color-text-secondary); }
12492
+ .badge-unreachable { color: var(--color-warning); }
12493
+ .brand-status-state {
12494
+ display: flex;
12495
+ align-items: baseline;
12496
+ gap: 0.375rem;
12497
+ flex-wrap: wrap;
12498
+ min-width: 0;
12499
+ }
12500
+ .brand-status-hint {
12501
+ font-size: 0.75rem;
12502
+ line-height: 1.5;
12503
+ font-weight: 400;
12504
+ color: var(--color-warning);
12505
+ }
11886
12506
  .badge-inactive { color: var(--color-text-secondary); }
11887
12507
 
11888
12508
  /* Support QR code */
@@ -12008,7 +12628,7 @@ body.setup-mode {
12008
12628
  background: var(--color-bg-primary);
12009
12629
  border: 1px solid var(--color-border-primary);
12010
12630
  border-radius: 16px;
12011
- padding: 1.75rem 2rem 1.5rem;
12631
+ padding: 3.25rem 2rem 1.5rem;
12012
12632
  display: flex;
12013
12633
  flex-direction: column;
12014
12634
  align-items: center;
@@ -12932,7 +13552,7 @@ body.setup-mode {
12932
13552
  * Assistant Memory is a 3-tab read-only window:
12933
13553
  * 🧬 Soul – SOUL.md + "Have the assistant curate this" → /onboard scope:soul
12934
13554
  * 👤 User – USER.md + "Have the assistant update this" → /onboard scope:user
12935
- * 🧠 Memories – cards with Curate + Delete (→ File Recall) → /onboard path:<abs>
13555
+ * 🧠 Memories – cards with Curate + Delete (→ Recycle Bin) → /onboard path:<abs>
12936
13556
  *
12937
13557
  * All mutations go through agent sessions; there are no inline editors.
12938
13558
  * All colours use design tokens from :root / [data-theme="dark"] above —
@@ -13357,19 +13977,28 @@ body.setup-mode {
13357
13977
  padding: 1.5rem 0;
13358
13978
  }
13359
13979
 
13360
- /* Tab switches (file trash / session trash) — mirrors skills-tabs pattern */
13980
+ /* Tab switches (file trash / session trash) — a segmented control; the pill
13981
+ styles live in the "Segmented Control" block near the top of this file.
13982
+ The wrapper is a flex row so the track hugs its buttons instead of
13983
+ stretching to the full content width. */
13361
13984
  #trash-tabs {
13362
- justify-content: space-between;
13363
- margin-bottom: 0.25rem;
13364
- }
13365
- .trash-tabs-left {
13366
13985
  display: flex;
13367
- gap: 0.25rem;
13986
+ margin-bottom: 0.25rem;
13368
13987
  }
13369
13988
 
13370
13989
  .trash-tab-content {
13371
13990
  flex: 1;
13372
13991
  overflow-y: auto;
13992
+ /* The list scrolls on its own so the toolbar above it stays put. Pulling the
13993
+ box out into #trash-body's right padding leaves the cards where they are and
13994
+ parks the scrollbar in that gutter instead of against the cards — the same
13995
+ trick the tasks and skills lists use. */
13996
+ margin-right: -2.25rem;
13997
+ padding-right: 2.25rem;
13998
+ /* #trash-body's section gap is meant for the stacked heading blocks above; the
13999
+ toolbar and the list are one block, so halve it and let the strip sit exactly
14000
+ one card-gap above the list instead of floating over a band of empty space. */
14001
+ margin-top: -0.625rem;
13373
14002
  }
13374
14003
 
13375
14004
  /* Session trash card — similar to file trash card but shows session name and timestamp */
@@ -13392,7 +14021,6 @@ body.setup-mode {
13392
14021
  }
13393
14022
  .trash-session-card-name {
13394
14023
  font-size: 0.875rem;
13395
- font-weight: 500;
13396
14024
  color: var(--color-text-primary);
13397
14025
  white-space: nowrap;
13398
14026
  overflow: hidden;
@@ -13525,7 +14153,7 @@ body.setup-mode {
13525
14153
  color: var(--color-text-secondary);
13526
14154
  }
13527
14155
  .trash-summary {
13528
- font-size: 0.6875rem;
14156
+ font-size: 0.8125rem;
13529
14157
  color: var(--color-text-muted);
13530
14158
  margin-right: auto;
13531
14159
  }
@@ -13534,36 +14162,29 @@ body.setup-mode {
13534
14162
  align-items: center;
13535
14163
  gap: 0.375rem;
13536
14164
  }
13537
- /* One shape for every control in the toolbar — filter chips, project picker and
14165
+ /* One shape for every control in the toolbar — the filter dropdowns and the
13538
14166
  bulk actions share height, radius and type scale so the row reads as a strip. */
13539
- .trash-filter-chip,
13540
14167
  .trash-filter-project-wrap,
13541
14168
  .btn-trash-action {
13542
- height: 1.75rem;
14169
+ height: 2.125rem;
13543
14170
  display: inline-flex;
13544
14171
  align-items: center;
13545
- gap: 0.3125rem;
13546
- padding: 0 0.625rem;
13547
- border-radius: 6px;
14172
+ gap: 0.375rem;
14173
+ padding: 0 0.75rem;
14174
+ border-radius: 8px;
13548
14175
  border: 1px solid var(--color-border-primary);
13549
14176
  background: transparent;
13550
14177
  color: var(--color-text-secondary);
13551
- font-size: 0.75rem;
14178
+ font-size: 0.8125rem;
13552
14179
  cursor: pointer;
13553
14180
  transition: background 0.15s, border-color 0.15s, color 0.15s;
13554
14181
  }
13555
- .trash-filter-chip:hover:not(.active),
13556
14182
  .trash-filter-project-wrap:hover,
13557
14183
  .btn-trash-action:hover:not(:disabled) {
13558
14184
  background: var(--color-bg-hover);
13559
14185
  border-color: var(--color-text-muted);
13560
14186
  color: var(--color-text-primary);
13561
14187
  }
13562
- .trash-filter-chip.active {
13563
- background: var(--color-accent-primary);
13564
- border-color: var(--color-accent-primary);
13565
- color: #fff;
13566
- }
13567
14188
  /* Project filter dropdown */
13568
14189
  .trash-filter-project-wrap {
13569
14190
  position: relative;
@@ -13584,7 +14205,15 @@ body.setup-mode {
13584
14205
  max-width: 130px;
13585
14206
  }
13586
14207
  .trash-filter-project-wrap .custom-select-trigger--bare {
13587
- font-size: 0.75rem; /* matches the neighbouring chips and action buttons */
14208
+ font-size: 0.8125rem; /* matches the neighbouring chips and action buttons */
14209
+ }
14210
+ /* The chip is wider and taller than the trigger inside it — the icon and the
14211
+ shared padding sit outside the trigger — so clicks on those edges did
14212
+ nothing. Cover the whole control with the trigger's (empty) hit area. */
14213
+ .trash-filter-project-wrap .custom-select-trigger::after {
14214
+ content: "";
14215
+ position: absolute;
14216
+ inset: 0;
13588
14217
  }
13589
14218
  .trash-actions {
13590
14219
  display: flex;
@@ -13675,6 +14304,194 @@ body.setup-mode {
13675
14304
  border: 1px solid rgba(217, 119, 6, .3);
13676
14305
  }
13677
14306
 
14307
+ /* ═════════════════════════════════════════════════════════════════════════
14308
+ * ARCHIVE PANEL — archived chats, grouped by project
14309
+ * ═════════════════════════════════════════════════════════════════════════ */
14310
+
14311
+ #archive-panel {
14312
+ flex: 1;
14313
+ display: flex;
14314
+ flex-direction: column;
14315
+ overflow: hidden;
14316
+ }
14317
+ #archive-body {
14318
+ flex: 1;
14319
+ overflow-y: auto;
14320
+ padding: 1.75rem 2.25rem;
14321
+ display: flex;
14322
+ flex-direction: column;
14323
+ gap: 1.25rem;
14324
+ }
14325
+ .archive-search-wrap {
14326
+ display: flex;
14327
+ align-items: center;
14328
+ gap: 0.4375rem;
14329
+ flex: 1;
14330
+ min-width: 0;
14331
+ /* Capped like the tasks search: the field should look like a search box, not a
14332
+ rule stretched across the toolbar. */
14333
+ max-width: 18rem;
14334
+ height: 2.125rem;
14335
+ padding: 0 0.75rem;
14336
+ border: 1px solid var(--color-border-primary);
14337
+ border-radius: 8px;
14338
+ color: var(--color-text-secondary);
14339
+ }
14340
+ .archive-search-wrap:focus-within {
14341
+ border-color: var(--color-accent-primary);
14342
+ }
14343
+ .archive-search-icon {
14344
+ flex-shrink: 0;
14345
+ }
14346
+ .archive-search-input {
14347
+ flex: 1;
14348
+ min-width: 0;
14349
+ padding: 0;
14350
+ border: none;
14351
+ outline: none;
14352
+ background: transparent;
14353
+ color: var(--color-text-primary);
14354
+ font-family: inherit;
14355
+ font-size: 0.8125rem;
14356
+ }
14357
+ .archive-search-input::placeholder {
14358
+ color: var(--color-text-muted);
14359
+ }
14360
+ /* The count anchors the right edge — without it the row would stop mid-card now
14361
+ that the search field no longer fills the space. */
14362
+ #archive-summary {
14363
+ margin-right: 0;
14364
+ margin-left: auto;
14365
+ }
14366
+ #archive-list {
14367
+ display: flex;
14368
+ flex-direction: column;
14369
+ gap: 1.25rem;
14370
+ }
14371
+ .archive-group-header {
14372
+ display: flex;
14373
+ align-items: center;
14374
+ gap: 0.375rem;
14375
+ margin-bottom: 0.5rem;
14376
+ font-size: 0.75rem;
14377
+ font-weight: 600;
14378
+ color: var(--color-text-secondary);
14379
+ }
14380
+ .archive-group-icon {
14381
+ width: 13px;
14382
+ height: 13px;
14383
+ color: var(--color-text-muted);
14384
+ }
14385
+ .archive-group-dot {
14386
+ width: 7px;
14387
+ height: 7px;
14388
+ border-radius: 50%;
14389
+ background: var(--color-text-muted);
14390
+ flex-shrink: 0;
14391
+ }
14392
+ .archive-group-count {
14393
+ font-weight: 400;
14394
+ color: var(--color-text-muted);
14395
+ }
14396
+ .archive-group-items {
14397
+ display: flex;
14398
+ flex-direction: column;
14399
+ gap: 0.5rem;
14400
+ }
14401
+ .btn-archive-unarchive {
14402
+ height: 1.75rem;
14403
+ padding: 0 0.625rem;
14404
+ border-radius: 6px;
14405
+ border: 1px solid var(--color-border-primary);
14406
+ background: transparent;
14407
+ color: var(--color-text-secondary);
14408
+ font-size: 0.75rem;
14409
+ white-space: nowrap;
14410
+ cursor: pointer;
14411
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
14412
+ }
14413
+ .btn-archive-unarchive:hover:not(:disabled) {
14414
+ background: var(--color-bg-hover);
14415
+ border-color: var(--color-text-muted);
14416
+ color: var(--color-text-primary);
14417
+ }
14418
+ .btn-archive-delete {
14419
+ width: 1.75rem;
14420
+ height: 1.75rem;
14421
+ padding: 0;
14422
+ border-radius: 6px;
14423
+ border: 1px solid var(--color-border-primary);
14424
+ background: transparent;
14425
+ color: var(--color-text-secondary);
14426
+ cursor: pointer;
14427
+ display: inline-flex;
14428
+ align-items: center;
14429
+ justify-content: center;
14430
+ flex-shrink: 0;
14431
+ transition: background 0.15s, border-color 0.15s, color 0.15s;
14432
+ }
14433
+ .btn-archive-delete:hover:not(:disabled) {
14434
+ background: var(--color-error-bg);
14435
+ border-color: var(--color-error, #ef4444);
14436
+ color: var(--color-error, #ef4444);
14437
+ }
14438
+ .btn-archive-unarchive:disabled,
14439
+ .btn-archive-delete:disabled {
14440
+ opacity: .4;
14441
+ cursor: not-allowed;
14442
+ }
14443
+ .archive-empty {
14444
+ padding: 3rem 0;
14445
+ text-align: center;
14446
+ font-size: 0.8125rem;
14447
+ color: var(--color-text-secondary);
14448
+ }
14449
+ /* Same shape as the session-list and extensions load-more rows: a full-width
14450
+ button under the list, not a toolbar chip squeezed into the middle. */
14451
+ #btn-archive-load-more {
14452
+ display: block;
14453
+ width: 100%;
14454
+ padding: 0.6rem;
14455
+ background: var(--color-bg-secondary);
14456
+ border: 1px solid var(--color-border-primary);
14457
+ border-radius: 8px;
14458
+ color: var(--color-text-secondary);
14459
+ font-family: inherit;
14460
+ font-size: 0.8125rem;
14461
+ cursor: pointer;
14462
+ transition: border-color 0.15s, color 0.15s;
14463
+ }
14464
+ #btn-archive-load-more:hover:not(:disabled) {
14465
+ border-color: var(--color-text-muted);
14466
+ color: var(--color-text-primary);
14467
+ }
14468
+ #btn-archive-load-more:disabled {
14469
+ opacity: 0.6;
14470
+ cursor: default;
14471
+ }
14472
+
14473
+ /* ═════════════════════════════════════════════════════════════════════════
14474
+ * BACKUP PANEL — backup controls, moved out of the Settings page
14475
+ * ═════════════════════════════════════════════════════════════════════════ */
14476
+
14477
+ #backup-panel {
14478
+ flex: 1;
14479
+ display: flex;
14480
+ flex-direction: column;
14481
+ overflow: hidden;
14482
+ }
14483
+ #backup-body {
14484
+ flex: 1;
14485
+ overflow-y: auto;
14486
+ padding: 1.75rem 2.25rem;
14487
+ }
14488
+ #backup-content {
14489
+ max-width: 920px;
14490
+ display: flex;
14491
+ flex-direction: column;
14492
+ gap: 1rem;
14493
+ }
14494
+
13678
14495
  /* Trash panel — matches Channels layout for visual consistency */
13679
14496
  #trash-panel {
13680
14497
  flex: 1;
@@ -13694,7 +14511,7 @@ body.setup-mode {
13694
14511
  display: flex;
13695
14512
  align-items: center;
13696
14513
  gap: 0.5rem;
13697
- padding: 0.625rem 0.875rem;
14514
+ padding: 0.75rem 0.875rem;
13698
14515
  border: 1px solid var(--color-border-primary);
13699
14516
  border-radius: 10px;
13700
14517
  background: var(--color-bg-secondary);
@@ -13713,6 +14530,7 @@ body.setup-mode {
13713
14530
  background: color-mix(in srgb, var(--color-bg-hover) 70%, var(--color-bg-secondary));
13714
14531
  border-radius: 8px;
13715
14532
  padding: 3px;
14533
+ transition: background 0.35s ease;
13716
14534
  }
13717
14535
  .segmented-control__btn {
13718
14536
  padding: 0.3125rem 0.75rem;
@@ -13722,7 +14540,7 @@ body.setup-mode {
13722
14540
  font-size: 0.8125rem;
13723
14541
  cursor: pointer;
13724
14542
  border-radius: 6px;
13725
- transition: all 0.15s ease;
14543
+ transition: all 0.35s ease;
13726
14544
  white-space: nowrap;
13727
14545
  }
13728
14546
  .segmented-control__btn:hover:not(.active) {
@@ -13745,6 +14563,12 @@ body.setup-mode {
13745
14563
  background: var(--color-bg-hover);
13746
14564
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
13747
14565
  }
14566
+ /* Hover deepens the track itself rather than the button under the cursor,
14567
+ so the active pill never competes with a second raised surface. Declared
14568
+ after the theme overrides above, which it has to outrank on dark tracks. */
14569
+ .segmented-control:hover {
14570
+ background: color-mix(in srgb, var(--color-text-primary) 2%, var(--color-bg-hover));
14571
+ }
13748
14572
 
13749
14573
  /* ═════════════════════════════════════════════════════════════════════════
13750
14574
  * BILLING PANEL
@@ -15687,9 +16511,21 @@ body.setup-mode {
15687
16511
  white-space: nowrap;
15688
16512
  }
15689
16513
 
15690
- /* Tighten header padding */
16514
+ /* Tighten header padding. The sidebar is an overlay drawer here, so the
16515
+ left zone is no longer pinned to it and carries no divider. */
15691
16516
  #top-header {
15692
- padding: 0 0.75rem;
16517
+ padding: 0;
16518
+ }
16519
+ #header-left {
16520
+ width: auto;
16521
+ margin-left: 0;
16522
+ padding: 0 0 0 0.75rem;
16523
+ }
16524
+ #header-left::after {
16525
+ display: none;
16526
+ }
16527
+ #header-right {
16528
+ padding-right: 0.75rem;
15693
16529
  }
15694
16530
 
15695
16531
  /* Hide brand name and divider on mobile, keep only the logo icon */
@@ -15717,8 +16553,10 @@ body.setup-mode {
15717
16553
 
15718
16554
  @media (max-width: 360px) {
15719
16555
  #top-header {
15720
- padding: 0 0.375rem;
16556
+ padding: 0;
15721
16557
  }
16558
+ #header-left { padding-left: 0.375rem; }
16559
+ #header-right { padding-right: 0.375rem; }
15722
16560
  }
15723
16561
 
15724
16562
  /* Session info bar: single-line, no hover-expand, font smaller */
@@ -15731,19 +16569,12 @@ body.setup-mode {
15731
16569
  white-space: nowrap;
15732
16570
  flex-wrap: nowrap;
15733
16571
  }
15734
- /* Always show detail fields on mobile (no hover) */
15735
- #session-info-bar .sib-detail {
15736
- display: contents;
15737
- }
15738
16572
  /* Hide less-important fields and their separators (element + sib-sep-after-element pattern) */
15739
16573
  #sib-id,
15740
16574
  .sib-sep-after-id,
15741
16575
  #sib-dir,
15742
16576
  .sib-sep-after-dir,
15743
- #sib-mode,
15744
- .sib-sep-after-mode,
15745
16577
  #sib-reasoning-wrap,
15746
- .sib-sep-after-reasoning,
15747
16578
  #sib-signal-wrap,
15748
16579
  .sib-sep-after-signal {
15749
16580
  display: none;
@@ -15987,9 +16818,74 @@ body.setup-mode {
15987
16818
  -webkit-overflow-scrolling: touch;
15988
16819
  overscroll-behavior: contain;
15989
16820
  }
15990
- /* ── Billing / Trash pages ── */
16821
+ /* ── Billing / Trash / Archive / Backup pages ── */
15991
16822
  #billing-body { padding: 1rem 1rem 1rem; }
15992
16823
  #trash-body { padding: 1rem 1rem 1rem; }
16824
+ .trash-tab-content { margin-right: -1rem; padding-right: 1rem; }
16825
+ #archive-body { padding: 1rem 1rem 1rem; }
16826
+ #backup-body { padding: 1rem 1rem 1rem; }
16827
+ #data-management-header { padding: 1rem 1rem 0; }
16828
+ #data-management-tabs { margin: 1rem 1rem 0; }
16829
+
16830
+ .billing-chart-card {
16831
+ padding: 0.875rem;
16832
+ }
16833
+ .billing-heatmap-year .billing-heat-year-wrap {
16834
+ gap: 0;
16835
+ overflow-x: auto;
16836
+ padding-bottom: 0.5rem;
16837
+ scrollbar-width: thin;
16838
+ -webkit-overflow-scrolling: touch;
16839
+ }
16840
+ .billing-heat-dow-year {
16841
+ position: sticky;
16842
+ left: 0;
16843
+ z-index: 1;
16844
+ width: 24px;
16845
+ padding-right: 6px;
16846
+ background: var(--color-bg-secondary);
16847
+ }
16848
+ .billing-heat-content {
16849
+ flex: 0 0 auto;
16850
+ width: max-content;
16851
+ }
16852
+ .billing-heat-grid-year {
16853
+ justify-content: flex-start;
16854
+ width: max-content;
16855
+ gap: 3px;
16856
+ }
16857
+ .billing-heat-week {
16858
+ flex: 0 0 13px;
16859
+ width: 13px;
16860
+ gap: 3px;
16861
+ }
16862
+ .billing-heat-week .billing-heat-cell {
16863
+ width: 13px;
16864
+ height: 13px;
16865
+ max-width: none;
16866
+ max-height: none;
16867
+ aspect-ratio: auto;
16868
+ }
16869
+ .billing-heat-week .billing-heat-cell.is-active {
16870
+ outline: 2px solid var(--color-accent-primary);
16871
+ outline-offset: 1px;
16872
+ }
16873
+ .billing-trend-card {
16874
+ min-height: 13.75rem;
16875
+ }
16876
+ .billing-trend-chart {
16877
+ min-height: 10rem;
16878
+ }
16879
+ .billing-trend-y-label,
16880
+ .billing-trend-x-label {
16881
+ font-size: 10px;
16882
+ }
16883
+ .billing-trend-line {
16884
+ stroke-width: 2;
16885
+ }
16886
+ .billing-trend-dot {
16887
+ opacity: 1;
16888
+ }
15993
16889
 
15994
16890
  .billing-controls .segmented-control {
15995
16891
  flex: 1 0 100%;
@@ -16039,11 +16935,17 @@ body.setup-mode {
16039
16935
  .memory-card-head { flex-wrap: wrap; }
16040
16936
  .memory-card-actions { margin-top: 0.375rem; flex-wrap: wrap; }
16041
16937
 
16042
- /* ── File Recall page ── */
16938
+ /* ── Recycle Bin page ── */
16043
16939
  .trash-toolbar {
16044
16940
  flex-wrap: wrap;
16045
16941
  gap: 0.5rem;
16046
16942
  }
16943
+ /* The capped search field would otherwise shrink to a sliver next to the
16944
+ filter chips once the row wraps — give it the full line instead. */
16945
+ .archive-search-wrap {
16946
+ flex-basis: 100%;
16947
+ max-width: none;
16948
+ }
16047
16949
  .trash-summary {
16048
16950
  width: 100%;
16049
16951
  }
@@ -16427,26 +17329,22 @@ body.setup-mode {
16427
17329
  .project-actions-menu-item:hover .project-actions-menu-icon {
16428
17330
  color: var(--color-text-primary);
16429
17331
  }
16430
- .project-actions-menu-item.danger {
16431
- color: var(--color-error);
16432
- margin-top: 0.25rem;
16433
- position: relative;
16434
- }
16435
- .project-actions-menu-item.danger .project-actions-menu-icon,
16436
- .project-actions-menu-item.danger:hover .project-actions-menu-icon {
16437
- color: var(--color-error);
17332
+ .project-actions-menu-item.danger .project-actions-menu-icon {
17333
+ color: var(--color-text-secondary);
16438
17334
  }
16439
- .project-actions-menu-item.danger::before {
16440
- content: "";
16441
- position: absolute;
16442
- left: 0;
16443
- right: 0;
16444
- top: -0.1875rem;
17335
+ /* The last group (archive + delete) is set off by an explicit divider element,
17336
+ so the danger styling itself draws no line. */
17337
+ .project-actions-menu-divider {
16445
17338
  height: 1px;
17339
+ margin: 0.3125rem 0;
16446
17340
  background: var(--color-border-secondary);
16447
17341
  }
16448
17342
  .project-actions-menu-item.danger:hover {
16449
17343
  background: var(--color-error-bg);
17344
+ color: var(--color-error);
17345
+ }
17346
+ .project-actions-menu-item.danger:hover .project-actions-menu-icon {
17347
+ color: var(--color-error);
16450
17348
  }
16451
17349
 
16452
17350
  /* ── Organize menu (view-mode toggle) ──────────────────────────────────────── */
@@ -16879,3 +17777,14 @@ body.setup-mode {
16879
17777
  .qq-qr-open-btn:hover { opacity: 0.92; }
16880
17778
  .qq-qr-copy-btn { padding: 8px 16px; border-radius: 8px; border: 1px solid var(--color-border); background: var(--color-bg-secondary); color: var(--color-text-primary); font-size: 14px; cursor: pointer; }
16881
17779
  .qq-qr-copy-btn:hover { border-color: var(--color-accent-primary); }
17780
+ .qq-qr-frame-wrap { position: relative; width: 320px; max-width: 100%; height: 460px; margin: 0 auto 12px; border-radius: 12px; overflow: hidden; }
17781
+ .qq-qr-frame { width: 100%; height: 100%; border: 0; display: block; }
17782
+ .qq-qr-frame-wrap.expired .qq-qr-frame { filter: blur(4px); opacity: 0.35; pointer-events: none; }
17783
+ .qq-qr-refresh { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; border: 0; background: transparent; color: #1f2329; font-size: 14px; font-weight: 600; cursor: pointer; }
17784
+ .qq-qr-refresh[hidden] { display: none; }
17785
+ .qq-qr-refresh:disabled { cursor: progress; opacity: 0.7; }
17786
+ .qq-qr-refresh-icon { width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: var(--color-accent-primary); color: #fff; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); }
17787
+ .qq-qr-refresh:hover .qq-qr-refresh-icon { opacity: 0.92; }
17788
+ .qq-qr-success { padding: 8px 0 4px; }
17789
+ .qq-qr-success-title { font-size: 18px; font-weight: 600; color: var(--color-accent-primary); margin-top: 8px; }
17790
+ .qq-qr-success .qq-qr-open-btn { border: 0; cursor: pointer; margin-top: 4px; }