@oar-sh/oar 0.9.9 → 0.9.11

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 (154) hide show
  1. package/CHANGELOG.md +561 -1
  2. package/README.md +162 -909
  3. package/bin/bat/oar.bat +46 -46
  4. package/package.json +14 -14
  5. package/server/README.md +369 -13
  6. package/server/claude-worker/claude-remote-relay-tool.mjs +10 -0
  7. package/server/claude-worker/claude-session-process.mjs +121 -9
  8. package/server/claude-worker/claude-session-worker.mjs +9 -1
  9. package/server/claude-worker/claude-turn-publisher.mjs +3 -0
  10. package/server/claude-worker/sdk-message-normalizer.mjs +31 -4
  11. package/server/claude-worker/subagent-timeline-recorder.mjs +572 -0
  12. package/server/copilot-worker/copilot-sdk-session-process.mjs +79 -12
  13. package/server/copilot-worker/copilot-sdk-session-worker.mjs +6 -0
  14. package/server/cursor-worker/cursor-sdk-adapter.mjs +56 -2
  15. package/server/db-schema.mjs +95 -2
  16. package/server/grok-worker/acp-client.mjs +2 -1
  17. package/server/grok-worker/acp-host-services.mjs +4 -3
  18. package/server/migrations/0009-subagent-timeline.mjs +135 -0
  19. package/server/migrations/0010-subagent-timeline-pruning.mjs +89 -0
  20. package/server/migrations/0011-relay-event-subscriptions.mjs +125 -0
  21. package/server/migrations/0012-cross-relay-event-subscriptions.mjs +116 -0
  22. package/server/migrations/0013-previews.mjs +130 -0
  23. package/server/migrations/0014-conversation-groups.mjs +89 -0
  24. package/server/migrations/0015-conversation-group-position.mjs +76 -0
  25. package/server/migrations/0016-share-access-log.mjs +110 -0
  26. package/server/public/app/about-ui.js +73 -0
  27. package/server/public/app/about-view.mjs +234 -0
  28. package/server/public/app/action-confirmations.js +31 -14
  29. package/server/public/app/agent-exchange-fold-view.mjs +187 -0
  30. package/server/public/app/api-client.js +312 -16
  31. package/server/public/app/archive-confirm.mjs +28 -0
  32. package/server/public/app/ask-user-view.js +17 -12
  33. package/server/public/app/attachments-view.js +516 -6
  34. package/server/public/app/background-tasks-view.mjs +18 -26
  35. package/server/public/app/bootstrap.js +336 -59
  36. package/server/public/app/chat-actions-menu-state.mjs +126 -0
  37. package/server/public/app/claude-cloud-conversation-ui.js +10 -22
  38. package/server/public/app/composer-control-state.mjs +16 -0
  39. package/server/public/app/composer-keyboard.mjs +54 -0
  40. package/server/public/app/context-menu.mjs +90 -6
  41. package/server/public/app/conversation-group-drag.mjs +191 -0
  42. package/server/public/app/conversation-group-order.mjs +265 -0
  43. package/server/public/app/conversation-group-state.mjs +100 -0
  44. package/server/public/app/conversation-list-drag.mjs +449 -0
  45. package/server/public/app/conversation-turn-state.mjs +81 -0
  46. package/server/public/app/conversation-view.js +436 -117
  47. package/server/public/app/cwd-picker.js +1 -1
  48. package/server/public/app/explorer-edit-state.mjs +68 -0
  49. package/server/public/app/hidden-messages-view.mjs +312 -0
  50. package/server/public/app/host-suspend-ui.js +13 -4
  51. package/server/public/app/journal-view.js +930 -45
  52. package/server/public/app/no-conversation-state.mjs +58 -0
  53. package/server/public/app/preview-cards.mjs +117 -31
  54. package/server/public/app/preview-command.mjs +10 -1
  55. package/server/public/app/provider-readiness.mjs +120 -0
  56. package/server/public/app/relay-board-view.js +19 -3
  57. package/server/public/app/relay-event-subscriptions-ui.js +184 -0
  58. package/server/public/app/relay-event-view.mjs +234 -0
  59. package/server/public/app/relay-restart-error.mjs +16 -0
  60. package/server/public/app/settings-modal.js +93 -9
  61. package/server/public/app/settings-tabs.js +1 -1
  62. package/server/public/app/share-format.mjs +49 -0
  63. package/server/public/app/share-preview-marks.mjs +167 -0
  64. package/server/public/app/share-state.js +109 -0
  65. package/server/public/app/shared-conversations-settings.js +480 -0
  66. package/server/public/app/socket-handlers.js +137 -40
  67. package/server/public/app/store.js +115 -32
  68. package/server/public/app/subagent-row.mjs +138 -0
  69. package/server/public/app/subagent-timeline-view.mjs +186 -0
  70. package/server/public/app/subagent-viewer-watch.mjs +50 -0
  71. package/server/public/app/subagent-viewer.mjs +558 -0
  72. package/server/public/app/task-row-format.mjs +29 -0
  73. package/server/public/app/transcript-jump-bottom-view.mjs +464 -0
  74. package/server/public/app/transcript-jump-bottom.mjs +210 -0
  75. package/server/public/app/transcript-reload-freshness.mjs +25 -0
  76. package/server/public/app/transcript-viewport-anchor.mjs +140 -0
  77. package/server/public/app/update-ui.js +1 -1
  78. package/server/public/app/viewer-history.mjs +5 -3
  79. package/server/public/index.html +774 -12
  80. package/server/relay-tools.md +2 -1
  81. package/server/relay.mjs +2 -1
  82. package/server/repositories/conversation-group-repository.mjs +444 -0
  83. package/server/repositories/message-repository.mjs +30 -3
  84. package/server/repositories/question-repository.mjs +9 -3
  85. package/server/repositories/remote-relay-repository.mjs +6 -2
  86. package/server/repositories/session-repository.mjs +12 -37
  87. package/server/repositories/share-repository.mjs +322 -0
  88. package/server/repositories/share-visibility-statements.mjs +10 -0
  89. package/server/repositories/subagent-timeline-statements.mjs +170 -0
  90. package/server/routes/about-routes.mjs +15 -0
  91. package/server/routes/ask-user-routes.mjs +3 -0
  92. package/server/routes/cache-routes.mjs +4 -0
  93. package/server/routes/conversation-group-routes.mjs +171 -0
  94. package/server/routes/messages-routes.mjs +698 -114
  95. package/server/routes/preview-routes.mjs +9 -1
  96. package/server/routes/relay-event-routes.mjs +157 -0
  97. package/server/routes/remote-relay-routes.mjs +15 -0
  98. package/server/routes/sessions-routes.mjs +843 -193
  99. package/server/server-runtime.mjs +559 -160
  100. package/server/services/about-service.mjs +461 -0
  101. package/server/services/background-task-store.mjs +128 -0
  102. package/server/services/background-work-view.mjs +228 -0
  103. package/server/services/cache-rebuild-service.mjs +30 -0
  104. package/server/services/delete-archive-service.mjs +4 -1
  105. package/server/services/detached-worker-watch-service.mjs +150 -0
  106. package/server/services/explorer-entry-name.mjs +26 -0
  107. package/server/services/explorer-mutations.mjs +122 -0
  108. package/server/services/hidden-message-list.mjs +21 -0
  109. package/server/services/host-suspend-command.mjs +2 -1
  110. package/server/services/message-pin-list.mjs +1 -1
  111. package/server/services/preview-health-probe.mjs +8 -1
  112. package/server/services/preview-proxy-server.mjs +49 -27
  113. package/server/services/preview-registry-service.mjs +676 -26
  114. package/server/services/preview-static-handler.mjs +40 -7
  115. package/server/services/preview-store.mjs +274 -0
  116. package/server/services/push-dispatch-service.mjs +18 -0
  117. package/server/services/rate-limit-service.mjs +12 -1
  118. package/server/services/relay-auth.mjs +183 -0
  119. package/server/services/relay-cli-launcher-service.mjs +3 -1
  120. package/server/services/relay-event-link-service.mjs +289 -0
  121. package/server/services/relay-event-service.mjs +1952 -0
  122. package/server/services/relay-shutdown-gate.mjs +31 -0
  123. package/server/services/remote-relay-caller-context.mjs +52 -7
  124. package/server/services/remote-relay-dispatcher.mjs +551 -7
  125. package/server/services/remote-relay-registry-service.mjs +11 -1
  126. package/server/services/request-boundary.mjs +183 -0
  127. package/server/services/session-activity-tracker.mjs +695 -0
  128. package/server/services/session-history-refresh-service.mjs +7 -0
  129. package/server/services/session-worker-launch-service.mjs +190 -6
  130. package/server/services/session-worker-process-service.mjs +10 -6
  131. package/server/services/share-access-log-service.mjs +123 -0
  132. package/server/services/share-path-redaction.mjs +424 -0
  133. package/server/services/stale-attempt-log-limiter.mjs +96 -0
  134. package/server/services/stale-recovery-clock-guard.mjs +57 -0
  135. package/server/services/stale-subagent-run-sweep.mjs +109 -0
  136. package/server/services/status-event-service.mjs +7 -3
  137. package/server/services/subagent-record-retention.mjs +164 -0
  138. package/server/services/subagent-timeline-service.mjs +328 -0
  139. package/server/services/subagent-viewer-rooms.mjs +119 -0
  140. package/server/services/tunnel-worker-path-guard.mjs +24 -41
  141. package/server/services/upgrade-socket-guard.mjs +16 -0
  142. package/server/services/windows-autostart-service.mjs +6 -1
  143. package/server/services/windows-boot-autostart-service.mjs +207 -23
  144. package/shared/preview-tool-core.mjs +15 -5
  145. package/shared/relay-events.mjs +371 -0
  146. package/shared/remote-relay-contract.mjs +160 -6
  147. package/shared/share-path-privacy.mjs +30 -0
  148. package/shared/subagent-record-retention.mjs +31 -0
  149. package/shared/subagent-timeline.mjs +79 -0
  150. package/shared/windows-system-tools.mjs +106 -0
  151. package/shared/worker-runtime/background-task-digest.mjs +30 -0
  152. package/shared/worker-runtime/heartbeat.mjs +24 -0
  153. package/shared/worker-runtime/kept-posts.mjs +83 -0
  154. package/shared/worker-runtime/worker-websocket-link.mjs +47 -15
@@ -29,6 +29,10 @@
29
29
  if (sharedIdx >= 0) {
30
30
  return path.slice(0, sharedIdx).replace(/\/+$/, '');
31
31
  }
32
+ const previewIdx = path.indexOf('/share-preview/');
33
+ if (previewIdx >= 0) {
34
+ return path.slice(0, previewIdx).replace(/\/+$/, '');
35
+ }
32
36
  return path.replace(/\/+$/, '');
33
37
  };
34
38
  const __APP_BASE = typeof __APP_CONFIG?.basePath === 'string' && __APP_CONFIG.basePath.trim()
@@ -526,6 +530,31 @@
526
530
  opacity: 0;
527
531
  pointer-events: none;
528
532
  }
533
+ /* "Preview as shared": what a viewer gets instead of a path, and what still
534
+ looks like one (share-preview-marks.mjs). */
535
+ mark.share-preview-redacted,
536
+ mark.share-preview-suspect {
537
+ color: inherit;
538
+ border-radius: 3px;
539
+ padding: 0 2px;
540
+ }
541
+ mark.share-preview-redacted {
542
+ background: rgba(63, 185, 80, 0.18);
543
+ box-shadow: inset 0 -1px 0 rgba(63, 185, 80, 0.65);
544
+ }
545
+ mark.share-preview-suspect {
546
+ background: rgba(210, 153, 34, 0.2);
547
+ box-shadow: inset 0 -2px 0 rgba(210, 153, 34, 0.9);
548
+ }
549
+ #share-preview-banner {
550
+ display: none;
551
+ padding: 6px 12px;
552
+ font-size: var(--fs-meta-sm);
553
+ color: var(--muted);
554
+ border-bottom: 1px solid var(--border);
555
+ background: var(--bg2);
556
+ }
557
+ body.share-preview-mode #share-preview-banner { display: block; }
529
558
  body.shared-reader-mode #sidebar,
530
559
  body.shared-reader-mode #sidebar-resizer,
531
560
  body.shared-reader-mode #sidebar-overlay,
@@ -586,7 +615,10 @@
586
615
  .header-icon-count:empty { display: none; }
587
616
  /* The display rule above outranks the UA [hidden] rule, same trap as the
588
617
  other [hidden] overrides in this file. */
589
- #pinned-messages-btn[hidden] { display: none; }
618
+ #pinned-messages-btn[hidden],
619
+ #hidden-messages-btn[hidden] { display: none; }
620
+ /* Same trap: without this the ⋯ trigger stays drawn as a dead button. */
621
+ #chat-actions-menu-btn[hidden] { display: none; }
590
622
  #cli-dot {
591
623
  width: 12px;
592
624
  height: 12px;
@@ -658,7 +690,8 @@
658
690
  /* The display rule above outranks the UA [hidden] rule, same trap as the
659
691
  other [hidden] overrides in this file. */
660
692
  #conv-filter-clear[hidden] { display: none; }
661
- #conv-filter-status {
693
+ #conv-filter-status,
694
+ #conv-list-announce {
662
695
  position: absolute;
663
696
  width: 1px;
664
697
  height: 1px;
@@ -868,7 +901,8 @@
868
901
  /* The conversation context menu (context-menu.mjs): right-click / long press. */
869
902
  .context-menu {
870
903
  position: fixed;
871
- z-index: 1200;
904
+ /* Above every modal: the explorer (10001, 12400 as a CWD picker) opens one too. */
905
+ z-index: 13000;
872
906
  min-width: 200px;
873
907
  max-width: min(320px, calc(100vw - 8px));
874
908
  padding: 4px;
@@ -903,6 +937,118 @@
903
937
  font-weight: 600;
904
938
  vertical-align: middle;
905
939
  }
940
+ /* ── Session groups in the sidebar ──
941
+ A head row (the session that created the others, or a line of its own for
942
+ a hand-made or headless group) with a fold button and a pill that sums up
943
+ the members; the members indented under it while the group is open. */
944
+ .conv-group-fold {
945
+ display: inline-flex; align-items: center; justify-content: center;
946
+ width: 20px; height: 20px; margin: 0 2px 0 -4px; padding: 0;
947
+ border: none; border-radius: 6px; background: transparent; color: var(--muted);
948
+ font: inherit; font-size: var(--fs-meta-md); line-height: 1; cursor: pointer; vertical-align: middle;
949
+ }
950
+ .conv-group-fold:hover { background: var(--bg3); color: var(--text); }
951
+ .conv-item.conv-group-synthetic { cursor: default; }
952
+ .conv-item.conv-group-synthetic .conv-title { color: var(--text); font-weight: 600; }
953
+ .conv-item.conv-group-synthetic.worker-ui-question .conv-title { color: var(--conversation-title-question); }
954
+ .conv-item.conv-group-member {
955
+ margin-left: 14px;
956
+ border-left: 2px solid color-mix(in srgb, var(--accent) 45%, transparent);
957
+ border-top-left-radius: 0; border-bottom-left-radius: 0;
958
+ }
959
+ /* The head row keeps its title and meta line clear of the archive and
960
+ delete icons (absolute, at the row's right), so nothing sits under them
961
+ on hover; a synthetic head has no icons. The margin is outside the
962
+ rectangles, so a measurement sees the clearance. */
963
+ .conv-item.conv-group-head:not(.conv-group-synthetic) .conv-title,
964
+ .conv-item.conv-group-head:not(.conv-group-synthetic) .conv-meta {
965
+ padding-right: 0;
966
+ margin-right: 48px;
967
+ }
968
+ /* In a head row's meta line the date gives way first and completely, then
969
+ the pill shortens with an ellipsis (its full text is its title), and the
970
+ provider chip always stays inside. Both shrink factors are 1 or more:
971
+ a factor below 1 that is left alone shrinks only that share of the
972
+ overflow, which is how the chip once left the row. */
973
+ .conv-item.conv-group-head .conv-meta-primary { flex: 0 1000 auto; }
974
+ .conv-group-summary {
975
+ display: inline-block; flex: 0 1 auto; min-width: 2.5em;
976
+ padding: 0 6px; border-radius: 999px;
977
+ border: 1px solid color-mix(in srgb, currentColor 40%, transparent);
978
+ background: color-mix(in srgb, currentColor 12%, transparent);
979
+ color: var(--muted); font-size: var(--fs-ui-chip); font-weight: 600; line-height: 1.3;
980
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: middle;
981
+ }
982
+ .conv-group-summary-running { color: var(--conversation-title-ready); }
983
+ .conv-group-summary-waiting { color: var(--conversation-title-question); cursor: pointer; }
984
+ .conv-group-summary-error { color: var(--conversation-title-error); }
985
+ .conv-group-summary[data-open-waiting]:hover { border-color: currentColor; }
986
+ /* Drag and drop (conversation-list-drag.mjs): a long press lifts a row, the
987
+ dragged row stays dimmed in place while a ghost follows the pointer, a
988
+ line shows where it lands, the target group is tinted, and a loose row
989
+ that would make a new group with it is highlighted. */
990
+ .conv-item.conv-drag-lifted {
991
+ background: var(--bg3);
992
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
993
+ transform: scale(1.02);
994
+ transition: transform 0.12s ease-out, box-shadow 0.12s ease-out;
995
+ z-index: 1;
996
+ }
997
+ #conv-list.conv-list-dragging { cursor: grabbing; }
998
+ #conv-list.conv-list-dragging .conv-item { cursor: grabbing; }
999
+ #conv-list.conv-list-dragging .conv-item:hover:not(.conv-drop-group):not(.conv-drop-new-group) { background: transparent; }
1000
+ .conv-item.conv-drag-source { opacity: 0.35; }
1001
+ .conv-item.conv-drag-ghost {
1002
+ position: fixed; z-index: 12900; margin: 0; pointer-events: none;
1003
+ background: var(--bg2); border-color: var(--accent);
1004
+ box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45); opacity: 0.95;
1005
+ cursor: grabbing; will-change: transform;
1006
+ }
1007
+ .conv-item.conv-drop-group {
1008
+ background: color-mix(in srgb, var(--accent) 10%, transparent);
1009
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent);
1010
+ }
1011
+ .conv-item.conv-drop-new-group,
1012
+ .conv-item.conv-drop-inside {
1013
+ background: color-mix(in srgb, var(--accent) 16%, transparent);
1014
+ border-color: var(--accent);
1015
+ }
1016
+ .conv-item.conv-drop-before::before,
1017
+ .conv-item.conv-drop-after::after {
1018
+ content: ''; position: absolute; left: 4px; right: 4px; height: 3px;
1019
+ border-radius: 2px; background: var(--accent); pointer-events: none;
1020
+ }
1021
+ .conv-item.conv-drop-before::before { top: -3px; }
1022
+ .conv-item.conv-drop-after::after { bottom: -3px; }
1023
+ .conv-item.conv-drop-flash { animation: conv-drop-flash 1.1s ease-out; }
1024
+ @keyframes conv-drop-flash {
1025
+ 0%, 30% { background: color-mix(in srgb, var(--accent) 28%, transparent); }
1026
+ 100% { background: transparent; }
1027
+ }
1028
+ @media (prefers-reduced-motion: reduce) {
1029
+ .conv-item.conv-drag-lifted { transform: none; transition: none; }
1030
+ .conv-item.conv-drop-flash { animation: none; outline: 2px solid var(--accent); outline-offset: -2px; }
1031
+ }
1032
+ /* The group picker and the rename form (inside the summary modal). */
1033
+ .conv-group-choices { display: flex; flex-direction: column; gap: 6px; max-height: 40vh; overflow-y: auto; }
1034
+ .conv-group-choice {
1035
+ display: flex; align-items: baseline; justify-content: space-between; gap: 10px; width: 100%;
1036
+ padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg3); color: var(--text);
1037
+ font: inherit; font-size: var(--fs-meta-md); text-align: left; cursor: pointer;
1038
+ }
1039
+ .conv-group-choice:hover:not([disabled]), .conv-group-choice:focus-visible { border-color: var(--accent); outline: none; }
1040
+ .conv-group-choice[disabled] { opacity: 0.6; cursor: default; }
1041
+ .conv-group-choice-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1042
+ .conv-group-choice-count { flex: 0 0 auto; color: var(--muted); font-size: var(--fs-meta-xs); }
1043
+ .conv-group-picker-empty { color: var(--muted); font-size: var(--fs-meta-md); margin: 0 0 6px; }
1044
+ .conv-group-form { margin-top: 14px; }
1045
+ .conv-group-form-label { display: block; color: var(--muted); font-size: var(--fs-meta-xs); margin-bottom: 4px; }
1046
+ .conv-group-form-row { display: flex; gap: 8px; align-items: center; }
1047
+ .conv-group-input {
1048
+ flex: 1 1 auto; min-width: 0; width: 100%; padding: 7px 10px;
1049
+ border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text); font: inherit;
1050
+ }
1051
+ .conv-group-input:focus { outline: none; border-color: var(--accent); }
906
1052
  /* ── Main Chat ── */
907
1053
  #main {
908
1054
  /* Containing block for #transcript-separator-rail. Safe to add: every
@@ -1442,7 +1588,27 @@
1442
1588
  }
1443
1589
  .empty-state .icon { font-size: 3rem; margin-bottom: 12px; }
1444
1590
  .empty-state h3 { font-size: var(--fs-core-chat-title); margin-bottom: 8px; color: var(--text); }
1445
- .empty-state p { font-size: var(--fs-meta-md); line-height: 1.5; }
1591
+ .empty-state p { font-size: var(--fs-meta-md); line-height: 1.5; max-width: 36em; }
1592
+ /* The actions of the "no conversation open" screen: Settings, New chat, the
1593
+ list on the phone. Same shapes as .summary-btn and #new-conv-btn. */
1594
+ .empty-state-actions {
1595
+ display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; align-items: center;
1596
+ margin-top: 18px;
1597
+ }
1598
+ .empty-state-btn {
1599
+ border: 1px solid var(--border); background: var(--bg2); color: var(--text);
1600
+ border-radius: 8px; height: 36px; padding: 0 14px; font-size: var(--fs-meta-md);
1601
+ cursor: pointer;
1602
+ }
1603
+ .empty-state-btn:hover { border-color: var(--accent); color: var(--accent); }
1604
+ .empty-state-btn.empty-state-btn-accent {
1605
+ background: var(--accent); color: #000; border-color: var(--accent); font-weight: 600;
1606
+ }
1607
+ .empty-state-btn.empty-state-btn-accent:hover { color: #000; opacity: 0.85; }
1608
+ .empty-state-btn.empty-state-link {
1609
+ border-color: transparent; background: transparent; color: var(--muted); text-decoration: underline;
1610
+ }
1611
+ .empty-state-btn.empty-state-link:hover { color: var(--accent); }
1446
1612
 
1447
1613
  .msg {
1448
1614
  display: flex;
@@ -2147,6 +2313,102 @@
2147
2313
  opacity: 1;
2148
2314
  transition: width 0.2s ease, background-size 0.2s ease, box-shadow 0.2s ease;
2149
2315
  }
2316
+ /* Jump to the end of the transcript (transcript-jump-bottom-view.mjs).
2317
+ Anchored to the composer's top edge, so it rides above the composer and
2318
+ the background-task panel at whatever height they have, and above the
2319
+ on-screen keyboard with them. Below the composer popups (z-index 30). */
2320
+ #jump-to-bottom-btn {
2321
+ position: absolute;
2322
+ right: calc(20px + env(safe-area-inset-right, 0px));
2323
+ bottom: calc(100% + 12px);
2324
+ z-index: 25;
2325
+ width: 40px;
2326
+ height: 40px;
2327
+ padding: 0;
2328
+ border-radius: 50%;
2329
+ border: 1px solid var(--border);
2330
+ background: var(--bg2);
2331
+ color: var(--text);
2332
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
2333
+ display: flex;
2334
+ align-items: center;
2335
+ justify-content: center;
2336
+ cursor: pointer;
2337
+ touch-action: manipulation;
2338
+ -webkit-tap-highlight-color: transparent;
2339
+ opacity: 0;
2340
+ visibility: hidden;
2341
+ transform: translateY(6px);
2342
+ transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s linear 0.18s, border-color 0.15s ease, color 0.15s ease;
2343
+ }
2344
+ #jump-to-bottom-btn.visible {
2345
+ opacity: 1;
2346
+ visibility: visible;
2347
+ transform: none;
2348
+ transition: opacity 0.18s ease, transform 0.18s ease, visibility 0s, border-color 0.15s ease, color 0.15s ease;
2349
+ }
2350
+ #jump-to-bottom-btn:hover,
2351
+ #jump-to-bottom-btn:focus-visible { border-color: var(--accent); color: var(--accent); }
2352
+ .jump-to-bottom-icon {
2353
+ font-size: 26px;
2354
+ line-height: 1;
2355
+ /* The arrowhead sits low in its em box. */
2356
+ transform: translateY(-4px);
2357
+ }
2358
+ .jump-to-bottom-badge {
2359
+ position: absolute;
2360
+ top: -6px;
2361
+ right: -6px;
2362
+ min-width: 18px;
2363
+ height: 18px;
2364
+ padding: 0 5px;
2365
+ box-sizing: border-box;
2366
+ border-radius: 9px;
2367
+ background: var(--accent);
2368
+ color: #000;
2369
+ font-size: 11px;
2370
+ font-weight: 700;
2371
+ line-height: 18px;
2372
+ text-align: center;
2373
+ }
2374
+ .jump-to-bottom-badge[hidden] { display: none; }
2375
+ /* Something new landed below a reader who is scrolled up: the accent
2376
+ colour, and a count or (news without a row to count) a dot. */
2377
+ #jump-to-bottom-btn[data-state="new"] { border-color: var(--accent); color: var(--accent); }
2378
+ .jump-to-bottom-badge.jump-to-bottom-badge-dot {
2379
+ top: -3px;
2380
+ right: -3px;
2381
+ min-width: 12px;
2382
+ width: 12px;
2383
+ height: 12px;
2384
+ padding: 0;
2385
+ border-radius: 50%;
2386
+ border: 2px solid var(--bg2);
2387
+ }
2388
+ /* A question, an approval or a board waits below: the button is filled. */
2389
+ #jump-to-bottom-btn[data-state="question"] {
2390
+ border-color: var(--warn);
2391
+ background: var(--warn);
2392
+ color: #000;
2393
+ animation: jump-to-bottom-attention 1.6s ease-out 2;
2394
+ }
2395
+ :root[data-theme="light"] #jump-to-bottom-btn[data-state="question"] { color: #fff; }
2396
+ #jump-to-bottom-btn[data-state="question"] .jump-to-bottom-badge {
2397
+ background: var(--bg2);
2398
+ color: var(--warn);
2399
+ border: 1px solid var(--warn);
2400
+ line-height: 16px;
2401
+ font-size: 12px;
2402
+ }
2403
+ @keyframes jump-to-bottom-attention {
2404
+ 0% { box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35), 0 0 0 0 var(--warn); }
2405
+ 100% { box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35), 0 0 0 12px transparent; }
2406
+ }
2407
+ @media (prefers-reduced-motion: reduce) {
2408
+ #jump-to-bottom-btn,
2409
+ #jump-to-bottom-btn.visible { transition: none; }
2410
+ #jump-to-bottom-btn[data-state="question"] { animation: none; }
2411
+ }
2150
2412
  /* ── Context usage modal ── */
2151
2413
  .ctx-usage-model {
2152
2414
  font-size: 0.95rem;
@@ -2479,6 +2741,25 @@
2479
2741
  }
2480
2742
  .plan-usage-link { color: var(--accent); text-decoration: none; }
2481
2743
  .plan-usage-link:hover { text-decoration: underline; }
2744
+ /* About OAR: the plan-usage cards and tables, stacked. */
2745
+ .about-modal { display: flex; flex-direction: column; gap: 12px; }
2746
+ .about-section .plan-usage-card-head { margin-bottom: 4px; }
2747
+ .about-section code { font-size: 0.82rem; }
2748
+ .about-usage-provider { font-size: 0.82rem; font-weight: 600; margin-top: 8px; }
2749
+ .about-usage-provider:first-child { margin-top: 0; }
2750
+ .about-state { font-weight: 600; }
2751
+ .about-state[data-state="online"] { color: var(--success); }
2752
+ .about-state[data-state="offline"] { color: var(--warn); }
2753
+ .about-state[data-state="idle"] { color: var(--muted); }
2754
+ .about-inline-button {
2755
+ background: none;
2756
+ border: none;
2757
+ padding: 0;
2758
+ font: inherit;
2759
+ cursor: pointer;
2760
+ text-align: left;
2761
+ }
2762
+ .about-links { display: flex; gap: 16px; flex-wrap: wrap; justify-content: center; font-size: 0.86rem; padding: 2px 0 4px; }
2482
2763
  @media (prefers-reduced-motion: reduce) {
2483
2764
  .plan-usage-bar-fill { transition: none; }
2484
2765
  }
@@ -2994,6 +3275,110 @@
2994
3275
  }
2995
3276
  .bg-task-stop:hover:not(:disabled) { border-color: #f85149; color: #f85149; }
2996
3277
  .bg-task-stop:disabled { opacity: 0.5; cursor: default; }
3278
+ /* Sub-agent rows (reply bubble, viewer): the task panel's row grammar. The
3279
+ title is a button that opens the agent's record in the viewer. */
3280
+ .subagent-rows { display: flex; flex-direction: column; margin-top: 4px; }
3281
+ .subagent-row-bubble { background: var(--bg3); border: 1px solid var(--border); }
3282
+ .subagent-row-timeline { margin-top: 0; background: var(--bg3); border: 1px solid var(--border); }
3283
+ .bg-task-desc.subagent-open {
3284
+ display: block; -webkit-line-clamp: unset; background: none; border: 0; padding: 0; margin: 0;
3285
+ font: inherit; color: var(--text); text-align: left; cursor: pointer;
3286
+ text-decoration: underline; text-decoration-color: transparent; text-underline-offset: 3px;
3287
+ }
3288
+ .bg-task-desc.subagent-open:hover, .bg-task-desc.subagent-open:focus-visible {
3289
+ color: var(--accent); text-decoration-color: currentColor; outline: none;
3290
+ }
3291
+ .subagent-row-status.subagent-status-failed { color: #f85149; }
3292
+ .subagent-row-status.subagent-status-completed { color: #3fb950; }
3293
+ /* The sub-agent viewer: one agent's thoughts, tool calls and reply in order.
3294
+ Below the file viewer, so a file opened from the record comes on top. */
3295
+ #subagent-viewer-modal {
3296
+ position: fixed; inset: 0; z-index: 10900; display: none;
3297
+ align-items: center; justify-content: center;
3298
+ background: rgba(0, 0, 0, 0.68); padding: 18px;
3299
+ }
3300
+ #subagent-viewer-modal.visible { display: flex; }
3301
+ .subagent-viewer-dialog {
3302
+ position: relative; width: min(980px, 100%); height: min(90vh, 1000px);
3303
+ display: flex; flex-direction: column; overflow: hidden;
3304
+ background: var(--bg2); border: 1px solid var(--border); border-radius: 14px;
3305
+ }
3306
+ .subagent-viewer-header {
3307
+ display: flex; flex-direction: column; gap: 4px; padding: 10px 14px;
3308
+ border-bottom: 1px solid var(--border); background: var(--bg3);
3309
+ }
3310
+ .subagent-viewer-title-row { display: flex; align-items: center; gap: 10px; min-width: 0; }
3311
+ #subagent-viewer-title { flex: 1; min-width: 0; margin: 0; font-size: 1.05rem; font-weight: 600; overflow-wrap: anywhere; }
3312
+ #subagent-viewer-meta { color: var(--muted); font-size: var(--fs-meta-sm); overflow-wrap: anywhere; }
3313
+ .subagent-viewer-btn {
3314
+ flex-shrink: 0; border: 1px solid var(--border); background: var(--bg2); color: var(--text);
3315
+ border-radius: 8px; height: 32px; padding: 0 10px; font-size: var(--fs-meta-sm); cursor: pointer;
3316
+ }
3317
+ .subagent-viewer-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
3318
+ #subagent-viewer-stop:hover:not(:disabled) { border-color: #f85149; color: #f85149; }
3319
+ .subagent-viewer-btn:disabled { opacity: 0.5; cursor: default; }
3320
+ .subagent-viewer-close { font-size: 1.2rem; line-height: 1; }
3321
+ #subagent-viewer-parent {
3322
+ align-self: flex-start; background: none; border: 0; padding: 0; color: var(--accent);
3323
+ cursor: pointer; font-size: var(--fs-meta-sm);
3324
+ }
3325
+ #subagent-viewer-body { flex: 1; overflow-y: auto; overscroll-behavior: contain; padding: 8px 14px 28px; }
3326
+ #subagent-viewer-status { color: var(--muted); padding: 14px 0; font-size: var(--fs-meta-sm); }
3327
+ #subagent-viewer-jump {
3328
+ position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
3329
+ border: 1px solid var(--accent); background: var(--bg3); color: var(--accent);
3330
+ border-radius: 999px; padding: 5px 14px; font-size: var(--fs-meta-sm); cursor: pointer;
3331
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.35);
3332
+ }
3333
+ .sav-entry { margin: 6px 0; min-width: 0; }
3334
+ .sav-entry details > summary {
3335
+ cursor: pointer; display: flex; align-items: baseline; gap: 6px; min-width: 0; list-style: none;
3336
+ }
3337
+ .sav-entry details > summary::-webkit-details-marker { display: none; }
3338
+ .sav-entry details > summary::before { content: '▸'; color: var(--muted); flex-shrink: 0; }
3339
+ .sav-entry details[open] > summary::before { content: '▾'; }
3340
+ .sav-call-line { display: flex; align-items: baseline; gap: 6px; min-width: 0; }
3341
+ .sav-kind { flex-shrink: 0; }
3342
+ .sav-tool-name { font-weight: 600; flex-shrink: 0; }
3343
+ .sav-summary-text, .sav-tool-summary {
3344
+ min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
3345
+ }
3346
+ .sav-tool-summary { color: var(--muted); font-family: var(--mono, monospace); font-size: var(--fs-meta-sm); }
3347
+ .sav-pre {
3348
+ margin: 6px 0 0; padding: 8px 10px; max-height: 50vh; overflow: auto;
3349
+ white-space: pre-wrap; overflow-wrap: anywhere; font-family: var(--mono, monospace);
3350
+ font-size: var(--fs-meta-sm); background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
3351
+ }
3352
+ .sav-entry-tool_result { margin-left: 22px; color: var(--muted); font-size: var(--fs-meta-sm); }
3353
+ .sav-entry-tool_result .sav-pre { color: var(--text); }
3354
+ .sav-entry-tool_result.is-error .sav-result-label { color: #f85149; }
3355
+ .sav-entry-thought { color: var(--muted); }
3356
+ .sav-thought-body { padding: 4px 0 0 18px; color: var(--text); opacity: 0.85; }
3357
+ .sav-text {
3358
+ padding: 8px 12px; border-radius: 10px; background: var(--bg3); border: 1px solid var(--border);
3359
+ overflow-wrap: anywhere;
3360
+ }
3361
+ .sav-entry.is-open .sav-text::after,
3362
+ .sav-entry-thought.is-open .sav-thought-body::after {
3363
+ content: '▍'; margin-left: 2px; animation: bg-task-pulse 1s ease-in-out infinite;
3364
+ }
3365
+ /* Markdown in the record reads like a reply bubble. */
3366
+ .sav-text p, .sav-thought-body p { margin: 0 0 0.5em; }
3367
+ .sav-text p:last-child, .sav-thought-body p:last-child { margin-bottom: 0; }
3368
+ .sav-text ul, .sav-text ol, .sav-thought-body ul, .sav-thought-body ol { padding-left: 1.4em; margin: 0 0 0.5em; }
3369
+ .sav-text h1, .sav-text h2, .sav-text h3 { margin: 0.5em 0 0.25em; font-size: 1em; }
3370
+ .sav-text pre, .sav-thought-body pre { margin: 0.5em 0; border-radius: 8px; max-width: 100%; overflow-x: auto; }
3371
+ .sav-text code:not(pre code), .sav-thought-body code:not(pre code) {
3372
+ background: var(--inline-code-bg); padding: 2px 6px; border-radius: 4px; font-size: 0.875em; overflow-wrap: anywhere;
3373
+ }
3374
+ .sav-note { color: var(--muted); font-size: var(--fs-meta-sm); font-style: italic; }
3375
+ .sav-note-failed { color: #f85149; }
3376
+ .sav-muted { color: var(--muted); }
3377
+ @media (max-width: 680px) {
3378
+ #subagent-viewer-modal { padding: 0; }
3379
+ .subagent-viewer-dialog { width: 100%; height: 100%; border: 0; border-radius: 0; }
3380
+ #subagent-viewer-body { padding: 8px 10px 28px; }
3381
+ }
2997
3382
  /* Preview cards: same row grammar as a background task, with a link-shaped
2998
3383
  primary action. Shared by the composer panel and the settings list. */
2999
3384
  .preview-row {
@@ -3023,6 +3408,32 @@
3023
3408
  .preview-close:hover:not(:disabled) { border-color: #f85149; color: #f85149; }
3024
3409
  .preview-close:disabled { opacity: 0.5; cursor: default; }
3025
3410
  #settings-previews-empty { color: var(--muted); font-size: var(--fs-meta-sm); }
3411
+ /* Settings → Sharing: one row per shared conversation, the preview row's
3412
+ grammar, with its access log folded underneath. */
3413
+ #settings-shares-list { width: 100%; }
3414
+ .share-row { margin-top: 8px; padding: 6px 8px; border-radius: 8px; background: var(--bg2); }
3415
+ .share-row-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
3416
+ .share-row-main { flex: 1 1 220px; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
3417
+ .share-row-title {
3418
+ background: none; border: 0; padding: 0; text-align: left; cursor: pointer;
3419
+ color: var(--text); font: inherit; overflow-wrap: anywhere;
3420
+ }
3421
+ .share-row-title:hover { color: var(--accent); text-decoration: underline; }
3422
+ .share-row-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
3423
+ .share-row-actions button {
3424
+ flex-shrink: 0; background: transparent; border: 1px solid var(--border);
3425
+ color: var(--text); border-radius: 8px; padding: 3px 10px; cursor: pointer;
3426
+ font-size: var(--fs-meta-sm); line-height: 1.5;
3427
+ }
3428
+ .share-row-actions button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
3429
+ .share-row-actions button.share-stop:hover:not(:disabled) { border-color: #f85149; color: #f85149; }
3430
+ .share-row-actions button:disabled { opacity: 0.5; cursor: default; }
3431
+ .share-opens { margin-top: 6px; border-top: 1px solid var(--border); padding-top: 4px; }
3432
+ .share-open { padding: 4px 0; font-size: var(--fs-meta-sm); border-bottom: 1px solid var(--border); }
3433
+ .share-open:last-child { border-bottom: 0; }
3434
+ .share-open-meta { color: var(--text); }
3435
+ .share-open-agent { color: var(--muted); overflow-wrap: anywhere; }
3436
+ #settings-shares-empty { color: var(--muted); font-size: var(--fs-meta-sm); }
3026
3437
  /* Remote relays (Settings → Relays, the @mention popup): a health dot —
3027
3438
  online green, unreachable/refused red, never checked grey. */
3028
3439
  .remote-relay-dot {
@@ -3115,6 +3526,100 @@
3115
3526
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
3116
3527
  }
3117
3528
  #chat-title-origin button.conv-origin-agent { padding: 3px 0; }
3529
+ /* Relay events: the event rows the relay writes into a watcher's
3530
+ conversation (never a bubble of the user's), the "watching" chip with its
3531
+ list, and the "watched" badge. */
3532
+ .msg.relay-event { align-self: stretch; max-width: 100%; align-items: stretch; }
3533
+ .relay-event-row {
3534
+ border: 1px dashed var(--border); border-left: 3px solid var(--accent);
3535
+ border-radius: 6px; background: var(--bg2); color: var(--muted);
3536
+ font-size: var(--fs-meta-xs); padding: 2px 8px;
3537
+ }
3538
+ .relay-event-row.relay-event-urgent { border-left-color: var(--warn); }
3539
+ .relay-event-summary {
3540
+ cursor: pointer; display: flex; gap: 8px; align-items: baseline; min-width: 0;
3541
+ padding: 3px 0; list-style-position: inside;
3542
+ }
3543
+ .relay-event-tag {
3544
+ flex: 0 0 auto; text-transform: uppercase; letter-spacing: 0.04em;
3545
+ font-size: var(--fs-ui-chip); color: var(--accent);
3546
+ }
3547
+ .relay-event-urgent .relay-event-tag { color: var(--warn); }
3548
+ .relay-event-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3549
+ .relay-event-time { flex: 0 0 auto; }
3550
+ .relay-event-lines { margin: 2px 0 4px; padding-left: 18px; }
3551
+ .relay-event-line { margin: 2px 0; overflow-wrap: anywhere; white-space: pre-wrap; }
3552
+ .relay-event-line.relay-event-question-asked,
3553
+ .relay-event-line.relay-event-error,
3554
+ .relay-event-line.relay-event-task-failed,
3555
+ .relay-event-line.relay-event-stalled { color: var(--text, inherit); }
3556
+ /* Agent exchanges: an agent's prompt folded into one row in the event
3557
+ rows' look (agent-exchange-fold-view.mjs). The row is the first child of
3558
+ the prompt's own node; closed, the prompt's bubble is hidden. The reply
3559
+ and the live bubble are never part of it. The prompt's node spans the
3560
+ pane for the row; its bubble keeps a user bubble's width. */
3561
+ .msg.agent-exchange-head { align-self: stretch; max-width: 100%; }
3562
+ .msg.agent-exchange-head > .msg-bubble { max-width: 82%; }
3563
+ .msg.agent-exchange-head.agent-exchange-closed > .msg-bubble,
3564
+ .msg.agent-exchange-head.agent-exchange-closed > .msg-label { display: none; }
3565
+ .agent-exchange-row {
3566
+ align-self: stretch; min-width: 0;
3567
+ border: 1px dashed var(--border); border-left: 3px solid var(--accent);
3568
+ border-radius: 6px; background: var(--bg2); color: var(--muted);
3569
+ font-size: var(--fs-meta-xs); padding: 2px 8px;
3570
+ }
3571
+ .msg.agent-exchange-head:not(.agent-exchange-closed) > .agent-exchange-row { margin-bottom: 6px; }
3572
+ .agent-exchange-summary {
3573
+ cursor: pointer; display: flex; gap: 8px; align-items: baseline; min-width: 0;
3574
+ padding: 3px 0; list-style: none;
3575
+ }
3576
+ .agent-exchange-summary::-webkit-details-marker { display: none; }
3577
+ .agent-exchange-summary::before { content: '▸'; flex: 0 0 auto; color: var(--accent); }
3578
+ .agent-exchange-row[open] > .agent-exchange-summary::before { content: '▾'; }
3579
+ .agent-exchange-text { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3580
+ .agent-exchange-status,
3581
+ .agent-exchange-time { flex: 0 0 auto; }
3582
+ .agent-exchange-status { color: var(--text, inherit); }
3583
+ .agent-exchange-row.agent-exchange-failed,
3584
+ .agent-exchange-row.agent-exchange-stopped { border-left-color: var(--warn); }
3585
+ .agent-exchange-failed .agent-exchange-status,
3586
+ .agent-exchange-stopped .agent-exchange-status { color: var(--warn); }
3587
+ @media (max-width: 680px) {
3588
+ .msg.agent-exchange-head > .msg-bubble { max-width: 95%; }
3589
+ }
3590
+ #chat-title-relay-events {
3591
+ display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-top: 2px;
3592
+ font-size: var(--fs-meta-xs); line-height: 1.2; color: var(--muted); position: relative;
3593
+ }
3594
+ #chat-title-relay-events[hidden] { display: none; }
3595
+ .relay-watch-panel[hidden] { display: none; }
3596
+ .relay-watch-chip {
3597
+ font: inherit; color: var(--accent); background: transparent; cursor: pointer;
3598
+ border: 1px solid var(--border); border-radius: 10px; padding: 2px 8px;
3599
+ }
3600
+ .relay-watch-panel {
3601
+ position: fixed; top: 0; left: 0; z-index: 60; overflow-y: auto; box-sizing: border-box;
3602
+ min-width: min(260px, calc(100vw - 16px)); max-width: min(calc(100vw - 16px), 480px);
3603
+ font-size: var(--fs-meta-xs); line-height: 1.2; color: var(--muted);
3604
+ background: var(--bg2); border: 1px solid var(--border); border-radius: 8px;
3605
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25); padding: 6px;
3606
+ }
3607
+ .relay-watch-list { list-style: none; margin: 0; padding: 0; }
3608
+ .relay-watch-item {
3609
+ display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2px 8px; align-items: center;
3610
+ padding: 6px 4px; border-bottom: 1px solid var(--border);
3611
+ }
3612
+ .relay-watch-item:last-child { border-bottom: none; }
3613
+ .relay-watch-what { color: var(--text, inherit); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3614
+ .relay-watch-events { grid-column: 1; font-size: var(--fs-ui-chip); }
3615
+ .relay-watch-unsubscribe {
3616
+ grid-column: 2; grid-row: 1 / span 2; font: inherit; cursor: pointer;
3617
+ color: var(--danger); background: transparent; border: 1px solid var(--border); border-radius: 6px; padding: 4px 8px;
3618
+ }
3619
+ .conv-watched-badge {
3620
+ flex: 0 0 auto; font-size: var(--fs-ui-chip); color: var(--accent);
3621
+ border: 1px solid var(--border); border-radius: 8px; padding: 0 5px;
3622
+ }
3118
3623
  /* Agent sessions (Settings → Relays). The slider spans the row so a thumb
3119
3624
  has room to travel at phone width, with a taller touch area than the
3120
3625
  track itself. */
@@ -3433,7 +3938,19 @@
3433
3938
  .file-preview-markdown p { margin-bottom: 0.55em; }
3434
3939
  .file-preview-markdown h1, .file-preview-markdown h2, .file-preview-markdown h3 { margin: 0.6em 0 0.35em; }
3435
3940
  .file-preview-markdown ul, .file-preview-markdown ol { margin-left: 1.3em; margin-bottom: 0.55em; }
3436
- .file-preview-markdown table { border-collapse: collapse; width: 100%; max-width: 100%; margin: 0.55em 0; font-size: 0.88em; }
3941
+ /* A table wider than the text scrolls sideways in its own box, as code
3942
+ does: the body itself never scrolls sideways, and the gallery swipe
3943
+ knows to leave a sideways scroller alone. */
3944
+ .file-preview-markdown table {
3945
+ display: block;
3946
+ width: max-content;
3947
+ max-width: 100%;
3948
+ overflow-x: auto;
3949
+ -webkit-overflow-scrolling: touch;
3950
+ border-collapse: collapse;
3951
+ margin: 0.55em 0;
3952
+ font-size: 0.88em;
3953
+ }
3437
3954
  .file-preview-markdown th, .file-preview-markdown td { border: 1px solid var(--border); padding: 6px 9px; }
3438
3955
  .file-preview-markdown th { background: var(--bg3); }
3439
3956
  .file-preview-markdown img { display: block; max-width: 100%; height: auto; }
@@ -4069,6 +4586,28 @@
4069
4586
  border-color: var(--accent);
4070
4587
  color: var(--accent);
4071
4588
  }
4589
+ /* The one main action of a dialog, in #new-conv-btn's colours. */
4590
+ .summary-btn.summary-btn-accent {
4591
+ background: var(--accent);
4592
+ color: #000;
4593
+ border-color: var(--accent);
4594
+ font-weight: 600;
4595
+ }
4596
+ .summary-btn.summary-btn-accent:hover { opacity: 0.85; }
4597
+ #new-conversation-open-settings[hidden] { display: none; }
4598
+ /* New Chat with no provider ready: the note above the buttons. */
4599
+ .new-conversation-setup-note {
4600
+ margin-top: 14px;
4601
+ padding: 10px 12px;
4602
+ border: 1px solid var(--border);
4603
+ border-left: 3px solid var(--accent);
4604
+ border-radius: 8px;
4605
+ background: var(--bg3);
4606
+ color: var(--text);
4607
+ font-size: var(--fs-meta-md);
4608
+ line-height: 1.5;
4609
+ }
4610
+ .new-conversation-setup-note[hidden] { display: none; }
4072
4611
  .summary-close {
4073
4612
  width: 32px;
4074
4613
  padding: 0;
@@ -4455,6 +4994,44 @@
4455
4994
  font-size: var(--fs-meta-sm);
4456
4995
  padding: 8px 6px;
4457
4996
  }
4997
+ /* Hidden messages list (kind 'hidden-messages'): the pin list's rows, with
4998
+ Unhide as a word where the pin list has 🗑, and Unhide all above. */
4999
+ .hidden-row-unhide {
5000
+ appearance: none;
5001
+ border: 0;
5002
+ border-left: 1px solid var(--border);
5003
+ background: transparent;
5004
+ color: var(--muted);
5005
+ cursor: pointer;
5006
+ flex: 0 0 auto;
5007
+ min-width: 44px;
5008
+ min-height: 44px;
5009
+ padding: 0 12px;
5010
+ font: inherit;
5011
+ font-size: var(--fs-meta-sm);
5012
+ }
5013
+ .hidden-row-unhide:hover:not(:disabled) {
5014
+ color: var(--accent);
5015
+ background: var(--bg2);
5016
+ }
5017
+ .hidden-row-unhide:disabled { cursor: wait; opacity: 0.55; }
5018
+ .hidden-list-actions,
5019
+ .hidden-unhide-all-confirm {
5020
+ margin-bottom: 10px;
5021
+ }
5022
+ .hidden-list-actions {
5023
+ display: flex;
5024
+ justify-content: flex-end;
5025
+ }
5026
+ .hidden-unhide-all-confirm {
5027
+ border: 1px solid var(--border);
5028
+ border-radius: 10px;
5029
+ padding: 10px 12px;
5030
+ }
5031
+ .hidden-unhide-all-confirm p {
5032
+ margin: 0 0 8px;
5033
+ font-size: var(--fs-meta-sm);
5034
+ }
4458
5035
 
4459
5036
  #git-changes-modal,
4460
5037
  #git-diff-modal {
@@ -5050,6 +5627,43 @@
5050
5627
  font-size: var(--fs-meta-md);
5051
5628
  padding: 10px 6px;
5052
5629
  }
5630
+ /* Explorer edits: the New-folder row at the top of the folder pane and the in-place rename. */
5631
+ .repo-tree-summary, .repo-entry-row, .repo-card { -webkit-touch-callout: none; user-select: none; -webkit-user-select: none; }
5632
+ .repo-inline-edit {
5633
+ display: grid;
5634
+ grid-template-columns: auto minmax(0, 1fr) auto auto;
5635
+ align-items: center;
5636
+ gap: 8px;
5637
+ margin: 0 0 10px;
5638
+ padding: 8px 10px;
5639
+ border: 1px solid var(--accent);
5640
+ border-radius: 10px;
5641
+ background: rgba(88, 166, 255, 0.08);
5642
+ }
5643
+ .repo-inline-edit input,
5644
+ .repo-rename-input,
5645
+ .repo-delete-confirm-input {
5646
+ min-width: 0;
5647
+ width: 100%;
5648
+ border: 1px solid var(--border);
5649
+ border-radius: 8px;
5650
+ background: var(--bg);
5651
+ color: var(--text);
5652
+ padding: 6px 8px;
5653
+ font: inherit;
5654
+ font-size: var(--fs-meta-md);
5655
+ }
5656
+ .repo-inline-edit input:focus, .repo-rename-input:focus, .repo-delete-confirm-input:focus { outline: none; border-color: var(--accent); }
5657
+ .repo-inline-edit-error { grid-column: 1 / -1; color: var(--danger); font-size: var(--fs-meta-xs); min-height: 0; }
5658
+ .repo-inline-edit-error:empty { display: none; }
5659
+ .repo-rename-input { display: inline-block; width: auto; max-width: 100%; padding: 2px 6px; }
5660
+ .repo-tree-summary .repo-rename-input { width: calc(100% - 2.2em); }
5661
+ .repo-delete-confirm-label { display: block; margin: 10px 0 0; font-size: var(--fs-meta-md); color: var(--muted); }
5662
+ .repo-delete-confirm-input { margin-top: 6px; }
5663
+ @media (max-width: 720px) {
5664
+ .repo-inline-edit { grid-template-columns: auto minmax(0, 1fr); }
5665
+ .repo-inline-edit .repo-browser-btn { grid-column: span 1; }
5666
+ }
5053
5667
 
5054
5668
  .msg-mode {
5055
5669
  display: inline-block; font-size: var(--fs-ui-chip); background: var(--bg3);
@@ -5362,6 +5976,10 @@
5362
5976
  pointer-events: auto;
5363
5977
  }
5364
5978
  #sidebar-resizer { display: none; }
5979
+ /* The "no conversation open" actions stack on the phone. */
5980
+ .empty-state { padding: 16px; }
5981
+ .empty-state-actions { flex-direction: column; align-items: stretch; width: min(100%, 320px); }
5982
+ .empty-state-btn { height: 40px; }
5365
5983
  #fullscreen-btn { display: inline-flex; align-items: center; justify-content: center; }
5366
5984
  #sidebar-overlay { display: block; opacity: 0; pointer-events: none; transition: opacity 0.25s; }
5367
5985
  #sidebar-overlay.visible { opacity: 1; pointer-events: all; }
@@ -5406,6 +6024,14 @@
5406
6024
  }
5407
6025
  .conv-meta { font-size: var(--fs-meta-sm); margin-top: 5px; }
5408
6026
  .conv-delete { opacity: 1; right: 4px; font-size: var(--fs-ui-icon); }
6027
+ /* Session groups on a phone: a shallower indent, and a fold target that
6028
+ is easy to hit without stealing the row's width. */
6029
+ .conv-item.conv-group-member { margin-left: 10px; }
6030
+ .conv-group-fold { width: 24px; height: 24px; margin-left: -6px; }
6031
+ /* The finger covers the row it lifts: the ghost sits a little higher. */
6032
+ .conv-item.conv-drag-ghost { margin: -18px 0 0; }
6033
+ .conv-item.conv-drop-before::before,
6034
+ .conv-item.conv-drop-after::after { height: 4px; }
5409
6035
  #chat-header {
5410
6036
  padding: var(--chat-header-top-pad-mobile) 10px 10px;
5411
6037
  gap: 6px 8px;
@@ -5542,6 +6168,7 @@
5542
6168
  .chat-actions-menu-item,
5543
6169
  .chat-actions-menu-meta { min-height: 30px; }
5544
6170
  #input-area { padding: 2px 10px calc(6px + env(safe-area-inset-bottom, 0px)); }
6171
+ #jump-to-bottom-btn { width: 44px; height: 44px; right: calc(12px + env(safe-area-inset-right, 0px)); bottom: calc(100% + 10px); }
5545
6172
  .input-row {
5546
6173
  display: flex;
5547
6174
  flex-wrap: wrap;
@@ -5775,10 +6402,31 @@
5775
6402
  }
5776
6403
  /* The pin list has one action, Close: the full-width action row above
5777
6404
  would leave its title no room. Title left, close button right. */
5778
- #summary-modal[data-kind="pinned-messages"] .summary-header-actions {
6405
+ #summary-modal[data-kind="pinned-messages"] .summary-header-actions,
6406
+ #summary-modal[data-kind="hidden-messages"] .summary-header-actions {
5779
6407
  width: auto;
5780
6408
  }
5781
- #summary-modal[data-kind="pinned-messages"] .summary-header-actions .summary-close {
6409
+ #summary-modal[data-kind="pinned-messages"] .summary-header-actions .summary-close,
6410
+ #summary-modal[data-kind="hidden-messages"] .summary-header-actions .summary-close {
6411
+ flex: 0 0 auto;
6412
+ min-width: 40px;
6413
+ }
6414
+ /* The session-group picker, rename and confirmations: the same layout,
6415
+ so their title ("Add to group", the chat's name) stays readable. */
6416
+ #summary-modal[data-kind^="group-"] .summary-header-actions {
6417
+ width: auto;
6418
+ }
6419
+ #summary-modal[data-kind^="group-"] .summary-header-actions .summary-close {
6420
+ flex: 0 0 auto;
6421
+ min-width: 40px;
6422
+ }
6423
+ /* About OAR: the same, with Refresh beside the close button. */
6424
+ #summary-modal[data-kind="about"] .summary-header-actions {
6425
+ width: auto;
6426
+ flex: 0 0 auto;
6427
+ }
6428
+ #summary-modal[data-kind="about"] .summary-header-actions .summary-btn,
6429
+ #summary-modal[data-kind="about"] .summary-header-actions .summary-close {
5782
6430
  flex: 0 0 auto;
5783
6431
  min-width: 40px;
5784
6432
  }
@@ -6039,6 +6687,7 @@
6039
6687
  </div>
6040
6688
  <div id="conv-archived-banner" hidden>Archived conversations <button type="button" class="conv-archived-back" onclick="toggleArchivedConversations()">Back to the list</button></div>
6041
6689
  <div id="conv-list"></div>
6690
+ <div id="conv-list-announce" role="status" aria-live="polite"></div>
6042
6691
  </div>
6043
6692
  <div id="sidebar-resizer" role="separator" aria-orientation="vertical" aria-label="Resize conversations sidebar"></div>
6044
6693
 
@@ -6055,19 +6704,23 @@
6055
6704
  </div>
6056
6705
  <div id="chat-header-actions">
6057
6706
  <button id="pinned-messages-btn" class="header-icon-btn has-count" type="button" title="Pinned messages" aria-label="Pinned messages" hidden>📍<span class="header-icon-count"></span></button>
6707
+ <button id="hidden-messages-btn" class="header-icon-btn has-count" type="button" title="Hidden from shared viewers" aria-label="Hidden from shared viewers" hidden>🙈<span class="header-icon-count"></span></button>
6058
6708
  <button id="message-search-btn" class="header-icon-btn" onclick="openMessageSearchModal()" title="Search messages" aria-label="Search messages">🔍</button>
6059
6709
  <button id="context-btn" class="header-icon-btn" onclick="showContext()" title="Check current context" aria-label="Check current context">🧠</button>
6060
6710
  <div id="chat-actions-menu-wrap">
6061
- <button id="chat-actions-menu-btn" class="header-icon-btn" type="button" title="Conversation actions" aria-label="Conversation actions" aria-haspopup="menu" aria-expanded="false">⋯</button>
6711
+ <button id="chat-actions-menu-btn" class="header-icon-btn" type="button" title="Menu" aria-label="Menu" aria-haspopup="menu" aria-expanded="false">⋯</button>
6062
6712
  <div id="chat-actions-menu" role="menu" hidden>
6063
6713
  <div id="chat-menu-pwa-version" class="chat-actions-menu-meta chat-actions-menu-meta-stacked" aria-hidden="true"><span class="chat-actions-menu-meta-primary">PWA shell version</span><span class="chat-actions-menu-meta-secondary" id="chat-menu-pwa-version-value">v?</span></div>
6064
6714
  <div id="chat-menu-queue-status" class="chat-actions-menu-meta chat-actions-menu-meta-stacked" aria-hidden="true"><div class="chat-actions-menu-meta-top"><span class="chat-actions-menu-meta-primary">Queue</span><button id="chat-menu-empty-queue" class="chat-actions-menu-meta-label" type="button">🚮 Empty queue</button></div><span class="chat-actions-menu-meta-secondary" id="chat-menu-queue-status-value">pending=?, processing=?, parked=?</span></div>
6065
6715
  <button id="chat-menu-edit-title" class="chat-actions-menu-item" type="button" role="menuitem">✍️ Edit conversation title</button>
6066
- <button id="chat-menu-shared-theme-toggle" class="chat-actions-menu-item" type="button" role="menuitem" hidden>🌙/☀️ Day/Night mode</button>
6716
+ <button id="chat-menu-theme-toggle" class="chat-actions-menu-item" type="button" role="menuitem" hidden>🌙/☀️ Day/Night mode</button>
6067
6717
  <button id="chat-menu-usage" class="chat-actions-menu-item" type="button" role="menuitem">📊 Check Usage</button>
6068
6718
  <button id="chat-menu-compact" class="chat-actions-menu-item" type="button" role="menuitem" hidden disabled aria-hidden="true" tabindex="-1" style="display:none">🗜️ Compact</button>
6069
6719
  <button id="chat-menu-share-conversation" class="chat-actions-menu-item" type="button" role="menuitem">➡️ Share conversation</button>
6720
+ <button id="chat-menu-stop-sharing" class="chat-actions-menu-item" type="button" role="menuitem" hidden>⛔ Stop sharing</button>
6721
+ <button id="chat-menu-preview-shared" class="chat-actions-menu-item" type="button" role="menuitem">🔍 Preview as shared</button>
6070
6722
  <button id="chat-menu-archive" class="chat-actions-menu-item" type="button" role="menuitem">🗄 Archive conversation</button>
6723
+ <button id="chat-menu-group" class="chat-actions-menu-item" type="button" role="menuitem">🧩 Session group…</button>
6071
6724
  <button id="chat-menu-refresh-history" class="chat-actions-menu-item" type="button" role="menuitem" hidden disabled aria-hidden="true" tabindex="-1" style="display:none">🔄️ Refresh history</button>
6072
6725
  <button id="chat-menu-inspect-tmux" class="chat-actions-menu-item" type="button" role="menuitem">🖥️ Inspect tmux console</button>
6073
6726
  <button id="chat-menu-select-models" class="chat-actions-menu-item" type="button" role="menuitem">🤗 Select Models</button>
@@ -6075,6 +6728,7 @@
6075
6728
  <button id="chat-menu-git-changes" class="chat-actions-menu-item" type="button" role="menuitem">🌿 Git changes</button>
6076
6729
  <button id="chat-menu-open-cloud-session" class="chat-actions-menu-item" type="button" role="menuitem" hidden>☁️ Open on claude.ai</button>
6077
6730
  <button id="chat-menu-settings" class="chat-actions-menu-item" type="button" role="menuitem">⚙️ Settings</button>
6731
+ <button id="chat-menu-about" class="chat-actions-menu-item" type="button" role="menuitem">ℹ️ About OAR</button>
6078
6732
  <button id="chat-menu-restart-relay" class="chat-actions-menu-item" type="button" role="menuitem">🌄 Restart web relay</button>
6079
6733
  <button id="chat-menu-suspend-host" class="chat-actions-menu-item" type="button" role="menuitem" onclick="openSuspendHostConfirmation()">💤 Suspend host</button>
6080
6734
  <button id="chat-menu-kill-session" class="chat-actions-menu-item" type="button" role="menuitem">☠️ Kill session</button>
@@ -6085,14 +6739,17 @@
6085
6739
  <button id="pending-question-banner" onclick="openPendingQuestionFromBanner()" title="Open pending question"></button>
6086
6740
  </div>
6087
6741
  <div id="messages">
6742
+ <!-- Replaced on start by conversation-view.js (no-conversation-state.mjs),
6743
+ which knows whether a provider is ready and whether chats exist. -->
6088
6744
  <div class="empty-state">
6089
6745
  <div class="icon">💬</div>
6090
- <h3>No conversation selected</h3>
6091
- <p>Click <b>+ New Conversation</b> to start chatting with your Copilot CLI</p>
6746
+ <h3>No conversation open</h3>
6092
6747
  </div>
6093
6748
  </div>
6094
6749
  <div id="input-area">
6750
+ <button id="jump-to-bottom-btn" type="button" title="Jump to the latest message" aria-label="Jump to the latest message"><span class="jump-to-bottom-icon" aria-hidden="true">⌄</span><span class="jump-to-bottom-badge" hidden></span></button>
6095
6751
  <div id="offline-banner">⚠️ CLI is offline — messages will queue until it reconnects</div>
6752
+ <div id="share-preview-banner" role="note"></div>
6096
6753
  <div id="model-banner"></div>
6097
6754
  <div id="usage-limit-banner" role="status" aria-live="polite" hidden></div>
6098
6755
  <div id="model-lock-note" hidden></div>
@@ -6167,6 +6824,7 @@
6167
6824
  <button id="repo-toggle-hidden-btn" class="repo-browser-btn" onclick="toggleRepoBrowserHidden()">Hidden: Off</button>
6168
6825
  <button id="repo-toggle-heavy-btn" class="repo-browser-btn" onclick="toggleRepoBrowserHeavy()">Heavy: Off</button>
6169
6826
  <button class="repo-browser-btn" onclick="refreshRepoBrowser()">Refresh</button>
6827
+ <button id="repo-new-folder-btn" class="repo-browser-btn" onclick="startRepoNewFolder()" title="Create a folder inside the open folder">📁+ New folder</button>
6170
6828
  </div>
6171
6829
  <div class="repo-browser-layout">
6172
6830
  <section class="repo-pane">
@@ -6274,6 +6932,16 @@
6274
6932
  tabindex="-1"
6275
6933
  data-settings-tab="previews"
6276
6934
  >Previews</button>
6935
+ <button
6936
+ id="settings-tab-shares"
6937
+ class="settings-tab"
6938
+ type="button"
6939
+ role="tab"
6940
+ aria-selected="false"
6941
+ aria-controls="settings-panel-shares"
6942
+ tabindex="-1"
6943
+ data-settings-tab="shares"
6944
+ >Sharing</button>
6277
6945
  <button
6278
6946
  id="settings-tab-notifications"
6279
6947
  class="settings-tab"
@@ -6326,6 +6994,17 @@
6326
6994
  >
6327
6995
  </div>
6328
6996
  <div class="settings-help" style="margin-top: 6px;">Hide the menu action when host suspend is unsupported or not needed on this device.</div>
6997
+ <div class="settings-row" style="margin-top: 12px; margin-bottom: 0;">
6998
+ <label for="scroll-on-send-toggle">Jump to the end when I send a message</label>
6999
+ <input
7000
+ id="scroll-on-send-toggle"
7001
+ type="checkbox"
7002
+ onchange="updateScrollOnSendSetting(this.checked)"
7003
+ aria-label="Jump to the end when I send a message"
7004
+ checked
7005
+ >
7006
+ </div>
7007
+ <div class="settings-help" style="margin-top: 6px;">A conversation only scrolls by itself while you are at its end. Anywhere else it stays where you are reading, and the round button above the composer shows that something new or a question is waiting below. Your own message is the exception: sending one takes you to the end. Switch this off to stay put then too. Saved in this browser.</div>
6329
7008
  <div class="settings-row settings-row-column" style="margin-top: 12px; margin-bottom: 0;">
6330
7009
  <label for="turn-ceiling-slider">Max turn duration <span id="turn-ceiling-value">No limit</span></label>
6331
7010
  <input
@@ -6356,6 +7035,17 @@
6356
7035
  >
6357
7036
  </div>
6358
7037
  <div class="settings-help" style="margin-top: 6px;">How long background tasks may keep a session alive after its turn ends: backgrounded commands, subagents and monitors on Claude; background agents and detached shells on Copilot's SDK engine. When the limit is reached the tasks are stopped. Set to 0 for no limit — tasks then run until they finish or you stop them from the composer panel.</div>
7038
+ <div class="settings-row" style="margin-top: 12px; margin-bottom: 0;">
7039
+ <label for="subagent-record-retention-select">Keep sub-agent tool output</label>
7040
+ <select id="subagent-record-retention-select" onchange="updateSubagentRecordRetentionSetting(this.value)" aria-label="Keep sub-agent tool output">
7041
+ <option value="7">7 days</option>
7042
+ <option value="14">14 days</option>
7043
+ <option value="30">30 days</option>
7044
+ <option value="90" selected>90 days</option>
7045
+ <option value="0">Always</option>
7046
+ </select>
7047
+ </div>
7048
+ <div class="settings-help" style="margin-top: 6px;">A sub-agent's record keeps every tool call's full input and the first 4 KB of each result. After this many days the relay removes them from finished agents, once a day; the thoughts, the reply, notes and the one-line summary of each call stay. Always keeps everything.</div>
6359
7049
  <div id="windows-autostart-setting" hidden>
6360
7050
  <div class="settings-row settings-row-column" style="margin-top: 12px; margin-bottom: 0;">
6361
7051
  <label>Autostart (Windows)</label>
@@ -7072,7 +7762,52 @@
7072
7762
  <label>Live previews</label>
7073
7763
  <div id="settings-previews-list"></div>
7074
7764
  <div id="settings-previews-empty" class="settings-help">Loading previews…</div>
7075
- <div class="settings-help">Local dev servers published on the preview host. Previews never expire on their own — they live until you close them here or the relay restarts. Every link is public: anyone with the URL reaches the app without logging in.</div>
7765
+ <div class="settings-help">Local dev servers and folders published on the preview host. Previews never expire and survive a relay restart — they live until you close them here or delete their conversation (a dev-server preview also ends when the machine reboots). Every link is public: anyone with the URL reaches the app without logging in.</div>
7766
+ </div>
7767
+ </div>
7768
+ <div
7769
+ id="settings-panel-shares"
7770
+ class="settings-panel"
7771
+ role="tabpanel"
7772
+ tabindex="0"
7773
+ aria-labelledby="settings-tab-shares"
7774
+ data-settings-panel="shares"
7775
+ hidden
7776
+ >
7777
+ <!-- One section per concern; further sharing settings (such as how
7778
+ paths show in a shared view) get their own section in here. -->
7779
+ <div id="shares-settings-sections" class="shares-settings-sections">
7780
+ <div id="shares-settings-section" class="settings-row settings-row-column" style="margin-top: 12px; margin-bottom: 0;">
7781
+ <label>Shared conversations</label>
7782
+ <div id="settings-shares-list"></div>
7783
+ <div id="settings-shares-empty" class="settings-help">Loading shared conversations…</div>
7784
+ <div class="settings-help">Every link is public: anyone with the URL can read the conversation without logging in, until you stop sharing it. Renew link gives the conversation a new link; the old one stops working at once and the access log carries over. Stop sharing ends the link and deletes its access log.</div>
7785
+ </div>
7786
+ <div id="shares-access-log-section" class="settings-row settings-row-column" style="margin-top: 16px; margin-bottom: 0;">
7787
+ <label>Access log</label>
7788
+ <div class="settings-row" style="width: 100%; margin-bottom: 0;">
7789
+ <label for="settings-shares-store-ip">Store viewer IP addresses</label>
7790
+ <input id="settings-shares-store-ip" type="checkbox" aria-label="Store viewer IP addresses" checked>
7791
+ </div>
7792
+ <div class="settings-help">Each open of a shared link is logged with its time, browser and country. Off: new entries keep no IP address (entries logged before keep theirs), and distinct viewers are still told apart by a keyed hash. The log is kept until you stop sharing the link or delete the conversation.</div>
7793
+ <div id="settings-shares-status" class="settings-help" role="status"></div>
7794
+ </div>
7795
+ <div id="shares-path-privacy-section" class="settings-row settings-row-column" style="margin-top: 16px; margin-bottom: 0;">
7796
+ <label id="settings-shares-path-privacy-label">Paths and file names</label>
7797
+ <div role="radiogroup" aria-labelledby="settings-shares-path-privacy-label" style="display: flex; flex-direction: column; gap: 4px;">
7798
+ <label style="font-weight: normal;">
7799
+ <input type="radio" name="settings-shares-path-privacy" value="show"> Show every path and file name
7800
+ </label>
7801
+ <label style="font-weight: normal;">
7802
+ <input type="radio" name="settings-shares-path-privacy" value="filename"> Hide the path, show the file name
7803
+ </label>
7804
+ <label style="font-weight: normal;">
7805
+ <input type="radio" name="settings-shares-path-privacy" value="hide"> Hide paths and file names
7806
+ </label>
7807
+ </div>
7808
+ <div class="settings-help">What every shared link shows of this relay's paths; your own view always shows everything. Free text (replies, thoughts, tool output) is filtered on a best-effort basis: check a conversation with ⋯ → Preview as shared.</div>
7809
+ <div id="settings-shares-path-privacy-status" class="settings-help" role="status"></div>
7810
+ </div>
7076
7811
  </div>
7077
7812
  </div>
7078
7813
  <div
@@ -7215,8 +7950,13 @@
7215
7950
  <select id="new-conversation-size-select"></select>
7216
7951
  <div id="new-conversation-size-status" style="margin-top:8px;font-size:0.78rem;color:var(--muted);">Image size used for generated outputs in this chat.</div>
7217
7952
  </div>
7953
+ <!-- No provider known to be ready (journal-view.js): the note says so and
7954
+ Settings is the main button; Start stays as "anyway", because the
7955
+ Copilot signal can be wrong. -->
7956
+ <div id="new-conversation-setup-note" class="new-conversation-setup-note" role="status" hidden></div>
7218
7957
  <div class="summary-modal-actions">
7219
7958
  <button type="button" class="summary-btn" onclick="closeNewConversationModelModal()">Cancel</button>
7959
+ <button id="new-conversation-open-settings" type="button" class="summary-btn summary-btn-accent" hidden>Open Settings → Providers</button>
7220
7960
  <button id="new-conversation-model-confirm" type="button" class="summary-btn" onclick="confirmNewConversationModel()">Start chat</button>
7221
7961
  </div>
7222
7962
  </div>
@@ -7267,6 +8007,28 @@
7267
8007
  </div>
7268
8008
  </div>
7269
8009
 
8010
+ <!-- Sub-agent viewer: one sub-agent's thoughts, tool calls with their results
8011
+ and reply, in order, live while it runs (subagent-viewer.mjs). -->
8012
+ <div id="subagent-viewer-modal" aria-hidden="true">
8013
+ <div class="subagent-viewer-dialog" role="dialog" aria-modal="true" aria-labelledby="subagent-viewer-title">
8014
+ <div class="subagent-viewer-header">
8015
+ <button id="subagent-viewer-parent" type="button" hidden></button>
8016
+ <div class="subagent-viewer-title-row">
8017
+ <span aria-hidden="true">🤖</span>
8018
+ <h2 id="subagent-viewer-title">Sub-agent</h2>
8019
+ <button id="subagent-viewer-stop" class="subagent-viewer-btn" type="button" hidden>Stop</button>
8020
+ <button class="subagent-viewer-btn subagent-viewer-close" type="button" aria-label="Close">×</button>
8021
+ </div>
8022
+ <div id="subagent-viewer-meta"></div>
8023
+ </div>
8024
+ <div id="subagent-viewer-body">
8025
+ <div id="subagent-viewer-timeline"></div>
8026
+ <div id="subagent-viewer-status"></div>
8027
+ </div>
8028
+ <button id="subagent-viewer-jump" type="button" hidden>New entries ↓</button>
8029
+ </div>
8030
+ </div>
8031
+
7270
8032
  <!-- Fullscreen file viewer. The content fills the screen; the chrome bars
7271
8033
  float over it and a tap on the content shows or hides them. -->
7272
8034
  <div id="file-preview-modal" aria-hidden="true">