@songsid/agend 2.3.0-beta.1 → 2.3.0-beta.10

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 (351) hide show
  1. package/dist/agent-cli-instructions.md +5 -2
  2. package/dist/agent-cli.js +43 -7
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/agent-endpoint.js +4 -0
  5. package/dist/agent-endpoint.js.map +1 -1
  6. package/dist/agent-file-read.d.ts +14 -6
  7. package/dist/agent-file-read.js +207 -19
  8. package/dist/agent-file-read.js.map +1 -1
  9. package/dist/agent-whats-new.d.ts +11 -0
  10. package/dist/agent-whats-new.js +29 -15
  11. package/dist/agent-whats-new.js.map +1 -1
  12. package/dist/auth-api.d.ts +30 -1
  13. package/dist/auth-api.js +45 -13
  14. package/dist/auth-api.js.map +1 -1
  15. package/dist/authority/authority-state.d.ts +38 -0
  16. package/dist/authority/authority-state.js +114 -0
  17. package/dist/authority/authority-state.js.map +1 -0
  18. package/dist/authority/session-authorizations.d.ts +43 -0
  19. package/dist/authority/session-authorizations.js +102 -0
  20. package/dist/authority/session-authorizations.js.map +1 -0
  21. package/dist/authority/web-authority.d.ts +120 -0
  22. package/dist/authority/web-authority.js +157 -0
  23. package/dist/authority/web-authority.js.map +1 -0
  24. package/dist/backend/claude-code.d.ts +2 -2
  25. package/dist/backend/claude-code.js +10 -5
  26. package/dist/backend/claude-code.js.map +1 -1
  27. package/dist/backend/types.d.ts +14 -0
  28. package/dist/backend/types.js +25 -0
  29. package/dist/backend/types.js.map +1 -1
  30. package/dist/backend-availability.d.ts +120 -0
  31. package/dist/backend-availability.js +396 -0
  32. package/dist/backend-availability.js.map +1 -0
  33. package/dist/backend-command.d.ts +21 -3
  34. package/dist/backend-command.js +23 -4
  35. package/dist/backend-command.js.map +1 -1
  36. package/dist/bundled-skills.d.ts +14 -0
  37. package/dist/bundled-skills.js +29 -0
  38. package/dist/bundled-skills.js.map +1 -0
  39. package/dist/cache-analysis.d.ts +47 -1
  40. package/dist/cache-analysis.js +68 -8
  41. package/dist/cache-analysis.js.map +1 -1
  42. package/dist/cache-ledger.d.ts +16 -3
  43. package/dist/cache-ledger.js +33 -13
  44. package/dist/cache-ledger.js.map +1 -1
  45. package/dist/cache-report-tool.d.ts +17 -0
  46. package/dist/cache-report-tool.js +40 -0
  47. package/dist/cache-report-tool.js.map +1 -0
  48. package/dist/cache-service.d.ts +70 -3
  49. package/dist/cache-service.js +178 -17
  50. package/dist/cache-service.js.map +1 -1
  51. package/dist/channel/adapters/discord.d.ts +7 -0
  52. package/dist/channel/adapters/discord.js +35 -0
  53. package/dist/channel/adapters/discord.js.map +1 -1
  54. package/dist/channel/mcp-tools.js +22 -10
  55. package/dist/channel/mcp-tools.js.map +1 -1
  56. package/dist/channel/types.d.ts +6 -0
  57. package/dist/classic-channel-manager.d.ts +10 -5
  58. package/dist/classic-channel-manager.js +12 -9
  59. package/dist/classic-channel-manager.js.map +1 -1
  60. package/dist/cli.js +130 -31
  61. package/dist/cli.js.map +1 -1
  62. package/dist/config-validator.js +103 -4
  63. package/dist/config-validator.js.map +1 -1
  64. package/dist/daemon.d.ts +7 -0
  65. package/dist/daemon.js +63 -16
  66. package/dist/daemon.js.map +1 -1
  67. package/dist/decision-list.d.ts +25 -0
  68. package/dist/decision-list.js +30 -0
  69. package/dist/decision-list.js.map +1 -0
  70. package/dist/delivery-outbox.d.ts +10 -0
  71. package/dist/delivery-outbox.js +12 -1
  72. package/dist/delivery-outbox.js.map +1 -1
  73. package/dist/fleet-context.d.ts +10 -0
  74. package/dist/fleet-manager.d.ts +417 -11
  75. package/dist/fleet-manager.js +1387 -109
  76. package/dist/fleet-manager.js.map +1 -1
  77. package/dist/fleet-traffic.d.ts +74 -0
  78. package/dist/fleet-traffic.js +196 -0
  79. package/dist/fleet-traffic.js.map +1 -0
  80. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +20 -5
  81. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +6 -1
  82. package/dist/general-knowledge/skills/multi-channel/SKILL.md +3 -2
  83. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  84. package/dist/general-knowledge/skills/web-appearance/SKILL.md +257 -23
  85. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +476 -0
  86. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  87. package/dist/human-delivery-receipts.d.ts +24 -1
  88. package/dist/human-delivery-receipts.js +64 -11
  89. package/dist/human-delivery-receipts.js.map +1 -1
  90. package/dist/instance-config-impact.js +3 -1
  91. package/dist/instance-config-impact.js.map +1 -1
  92. package/dist/instance-names.d.ts +4 -0
  93. package/dist/instance-names.js +12 -0
  94. package/dist/instance-names.js.map +1 -0
  95. package/dist/instructions.d.ts +12 -1
  96. package/dist/instructions.js +13 -2
  97. package/dist/instructions.js.map +1 -1
  98. package/dist/locale.js +100 -8
  99. package/dist/locale.js.map +1 -1
  100. package/dist/ls-attach-resolve.d.ts +95 -0
  101. package/dist/ls-attach-resolve.js +174 -0
  102. package/dist/ls-attach-resolve.js.map +1 -0
  103. package/dist/memory-hogs.d.ts +27 -0
  104. package/dist/memory-hogs.js +124 -0
  105. package/dist/memory-hogs.js.map +1 -0
  106. package/dist/outbound-handlers.d.ts +10 -0
  107. package/dist/outbound-handlers.js +122 -2
  108. package/dist/outbound-handlers.js.map +1 -1
  109. package/dist/outbound-schemas.d.ts +164 -4
  110. package/dist/outbound-schemas.js +99 -3
  111. package/dist/outbound-schemas.js.map +1 -1
  112. package/dist/public-web-gateway.js +8 -2
  113. package/dist/public-web-gateway.js.map +1 -1
  114. package/dist/public-web-link.d.ts +9 -0
  115. package/dist/public-web-link.js +8 -0
  116. package/dist/public-web-link.js.map +1 -1
  117. package/dist/quickstart-api.d.ts +10 -1
  118. package/dist/quickstart-api.js +12 -4
  119. package/dist/quickstart-api.js.map +1 -1
  120. package/dist/reply-buttons-controller.d.ts +4 -0
  121. package/dist/reply-buttons-controller.js +2 -2
  122. package/dist/reply-buttons-controller.js.map +1 -1
  123. package/dist/restart-progress.d.ts +65 -1
  124. package/dist/restart-progress.js +236 -10
  125. package/dist/restart-progress.js.map +1 -1
  126. package/dist/rollout-index.d.ts +5 -0
  127. package/dist/rollout-index.js +26 -5
  128. package/dist/rollout-index.js.map +1 -1
  129. package/dist/scheduler/scheduler.d.ts +12 -0
  130. package/dist/scheduler/scheduler.js +83 -10
  131. package/dist/scheduler/scheduler.js.map +1 -1
  132. package/dist/service-installer.d.ts +2 -1
  133. package/dist/service-installer.js +18 -3
  134. package/dist/service-installer.js.map +1 -1
  135. package/dist/settings-api.d.ts +19 -0
  136. package/dist/settings-api.js +137 -2
  137. package/dist/settings-api.js.map +1 -1
  138. package/dist/settings-change.js +104 -0
  139. package/dist/settings-change.js.map +1 -1
  140. package/dist/settings-effect.js +17 -1
  141. package/dist/settings-effect.js.map +1 -1
  142. package/dist/tmux-manager.d.ts +5 -0
  143. package/dist/tmux-manager.js +16 -0
  144. package/dist/tmux-manager.js.map +1 -1
  145. package/dist/tool-permissions.d.ts +4 -6
  146. package/dist/tool-permissions.js +53 -5
  147. package/dist/tool-permissions.js.map +1 -1
  148. package/dist/topic-commands.d.ts +7 -1
  149. package/dist/topic-commands.js +18 -4
  150. package/dist/topic-commands.js.map +1 -1
  151. package/dist/types.d.ts +48 -1
  152. package/dist/ui/app-effects.js +77 -31
  153. package/dist/ui/chat-ambient.js +307 -0
  154. package/dist/ui/chat-effect-posters.js +8 -8
  155. package/dist/ui/chat-effects.js +101 -13
  156. package/dist/ui/chat-render.js +5 -2
  157. package/dist/ui/chat-strings.js +59 -6
  158. package/dist/ui/chat-thread.js +149 -21
  159. package/dist/ui/effect-trial-controller.js +13 -8
  160. package/dist/ui/effect-trial-panel.js +19 -16
  161. package/dist/ui/fleet-cache.js +23 -1
  162. package/dist/ui/fleet-canvas-layout.js +111 -0
  163. package/dist/ui/fleet-canvas.js +525 -0
  164. package/dist/ui/fleet-org.js +27 -12
  165. package/dist/ui/fleet-traffic.js +343 -0
  166. package/dist/ui/fleet-usage.js +401 -0
  167. package/dist/ui/panel-chat.js +127 -49
  168. package/dist/ui/panel-details.js +2 -1
  169. package/dist/ui/panel-fleet.js +41 -16
  170. package/dist/ui/panel-settings.js +26 -8
  171. package/dist/ui/preview.js +3 -2
  172. package/dist/ui/settings-appearance.js +42 -18
  173. package/dist/ui/settings-catalog.js +92 -0
  174. package/dist/ui/settings-dialogs.js +40 -12
  175. package/dist/ui/settings-effects.js +4 -3
  176. package/dist/ui/settings-guests.js +117 -0
  177. package/dist/ui/settings-learn.js +25 -0
  178. package/dist/ui/settings-model.js +15 -6
  179. package/dist/ui/settings-mods.js +23 -9
  180. package/dist/ui/settings-owner-effects.js +9 -9
  181. package/dist/ui/settings-strings.js +120 -42
  182. package/dist/ui/settings-web-authority.js +91 -0
  183. package/dist/ui/shared/agend-auth.js +1 -1
  184. package/dist/ui/shared/app-i18n.js +4 -2
  185. package/dist/ui/shared/app-prefs.js +5 -4
  186. package/dist/ui/shared/app-route.js +1 -1
  187. package/dist/ui/shared/app-session.js +2 -0
  188. package/dist/ui/shared/app-shell.js +75 -9
  189. package/dist/ui/shared/app-store.js +26 -1
  190. package/dist/ui/shared/app-stream.js +3 -1
  191. package/dist/ui/shared/app-themes.js +24 -6
  192. package/dist/ui/shared/app.css +364 -16
  193. package/dist/ui/shared/app.js +21 -14
  194. package/dist/ui/shared/card-strings.js +4 -0
  195. package/dist/ui/shared/effect-app-runtime.js +68 -19
  196. package/dist/ui/shared/effect-chat-runtime.js +16 -3
  197. package/dist/ui/shared/effect-frame.js +26 -7
  198. package/dist/ui/shared/effect-geometry.js +24 -9
  199. package/dist/ui/shared/effect-policy.js +21 -7
  200. package/dist/ui/shared/effect-profiler.js +31 -9
  201. package/dist/ui/shared/effect-registry.js +87 -48
  202. package/dist/ui/shared/effect-stock-snapshot.js +9 -0
  203. package/dist/ui/shared/effect-stock-trust.js +62 -10
  204. package/dist/ui/shared/effect-trial-shadow.js +62 -0
  205. package/dist/ui/shared/effect-trial-source.js +1 -1
  206. package/dist/ui/shared/effect-trial-stage.js +54 -31
  207. package/dist/ui/shared/effect-trial.css +40 -0
  208. package/dist/ui/shared/fx-motion.js +67 -0
  209. package/dist/ui/shared/identity-card.js +75 -4
  210. package/dist/ui/shared/identity-color.js +3 -3
  211. package/dist/ui/shared/instance-nav.js +194 -28
  212. package/dist/ui/shared/panel-view.js +24 -4
  213. package/dist/ui/shared/tokens.css +9 -3
  214. package/dist/ui/shared/ui-icons.js +1 -0
  215. package/dist/ui/shared/usage-dialog.js +22 -2
  216. package/dist/ui/shared/view-strings.js +68 -2
  217. package/dist/ui/shared/web-authorize.js +151 -0
  218. package/dist/ui/signin.html +1 -1
  219. package/dist/ui/web-terminal/terminal.css +4 -4
  220. package/dist/usage/providers.d.ts +28 -0
  221. package/dist/usage/providers.js +126 -36
  222. package/dist/usage/providers.js.map +1 -1
  223. package/dist/usage-report.d.ts +158 -0
  224. package/dist/usage-report.js +226 -0
  225. package/dist/usage-report.js.map +1 -0
  226. package/dist/usage-rollup.d.ts +51 -0
  227. package/dist/usage-rollup.js +168 -0
  228. package/dist/usage-rollup.js.map +1 -0
  229. package/dist/view-api.d.ts +39 -0
  230. package/dist/view-api.js +65 -4
  231. package/dist/view-api.js.map +1 -1
  232. package/dist/web-ambient.d.ts +72 -0
  233. package/dist/web-ambient.js +161 -0
  234. package/dist/web-ambient.js.map +1 -0
  235. package/dist/web-api.d.ts +69 -5
  236. package/dist/web-api.js +362 -45
  237. package/dist/web-api.js.map +1 -1
  238. package/dist/web-appearance-policy.d.ts +4 -6
  239. package/dist/web-appearance-policy.js +5 -12
  240. package/dist/web-appearance-policy.js.map +1 -1
  241. package/dist/web-appearance-tools.d.ts +3 -0
  242. package/dist/web-appearance-tools.js +22 -3
  243. package/dist/web-appearance-tools.js.map +1 -1
  244. package/dist/web-appearance-view.d.ts +27 -2
  245. package/dist/web-appearance-view.js +129 -14
  246. package/dist/web-appearance-view.js.map +1 -1
  247. package/dist/web-appearance.d.ts +17 -3
  248. package/dist/web-appearance.js +82 -7
  249. package/dist/web-appearance.js.map +1 -1
  250. package/dist/web-asset-parts.d.ts +5 -10
  251. package/dist/web-asset-parts.js +3 -3
  252. package/dist/web-asset-parts.js.map +1 -1
  253. package/dist/web-auth.js +3 -1
  254. package/dist/web-auth.js.map +1 -1
  255. package/dist/web-card.d.ts +14 -0
  256. package/dist/web-card.js +40 -6
  257. package/dist/web-card.js.map +1 -1
  258. package/dist/web-channel-echo.d.ts +7 -1
  259. package/dist/web-channel-echo.js +6 -2
  260. package/dist/web-channel-echo.js.map +1 -1
  261. package/dist/web-chat-history.d.ts +46 -0
  262. package/dist/web-chat-history.js +80 -3
  263. package/dist/web-chat-history.js.map +1 -1
  264. package/dist/web-commands.d.ts +1 -0
  265. package/dist/web-effect-bootstrap.d.ts +12 -0
  266. package/dist/web-effect-bootstrap.js +49 -0
  267. package/dist/web-effect-bootstrap.js.map +1 -0
  268. package/dist/web-effect-bundle.js +46 -1
  269. package/dist/web-effect-bundle.js.map +1 -1
  270. package/dist/web-effect-playback.d.ts +30 -0
  271. package/dist/web-effect-playback.js +71 -0
  272. package/dist/web-effect-playback.js.map +1 -0
  273. package/dist/web-effect-profiles.d.ts +4 -4
  274. package/dist/web-effect-service.d.ts +11 -4
  275. package/dist/web-effect-service.js +28 -5
  276. package/dist/web-effect-service.js.map +1 -1
  277. package/dist/web-effect-tools.d.ts +1 -0
  278. package/dist/web-effect-tools.js +8 -5
  279. package/dist/web-effect-tools.js.map +1 -1
  280. package/dist/web-effect-trials.d.ts +4 -4
  281. package/dist/web-effect-trials.js +6 -6
  282. package/dist/web-effect-trials.js.map +1 -1
  283. package/dist/web-effect-upload.d.ts +2 -4
  284. package/dist/web-effect-upload.js +3 -8
  285. package/dist/web-effect-upload.js.map +1 -1
  286. package/dist/web-effect-worker.d.ts +1 -1
  287. package/dist/web-effect-worker.js +2 -2
  288. package/dist/web-effect-worker.js.map +1 -1
  289. package/dist/web-effect.d.ts +14 -0
  290. package/dist/web-effect.js +21 -0
  291. package/dist/web-effect.js.map +1 -1
  292. package/dist/web-guest-access.d.ts +125 -0
  293. package/dist/web-guest-access.js +234 -0
  294. package/dist/web-guest-access.js.map +1 -0
  295. package/dist/web-guest-rooms.d.ts +82 -0
  296. package/dist/web-guest-rooms.js +135 -0
  297. package/dist/web-guest-rooms.js.map +1 -0
  298. package/dist/web-identity-color.d.ts +1 -1
  299. package/dist/web-identity-color.js +2 -2
  300. package/dist/web-identity-color.js.map +1 -1
  301. package/dist/web-identity-palette.d.ts +1 -1
  302. package/dist/web-identity-palette.js +2 -2
  303. package/dist/web-identity-palette.js.map +1 -1
  304. package/dist/web-login.d.ts +47 -0
  305. package/dist/web-login.js +115 -0
  306. package/dist/web-login.js.map +1 -1
  307. package/dist/web-mod-agent.d.ts +1 -1
  308. package/dist/web-mod-agent.js +12 -8
  309. package/dist/web-mod-agent.js.map +1 -1
  310. package/dist/web-mod-bundle.d.ts +14 -1
  311. package/dist/web-mod-bundle.js +68 -3
  312. package/dist/web-mod-bundle.js.map +1 -1
  313. package/dist/web-mod-export.d.ts +9 -0
  314. package/dist/web-mod-export.js +21 -0
  315. package/dist/web-mod-export.js.map +1 -1
  316. package/dist/web-mod-store.d.ts +7 -1
  317. package/dist/web-mod-store.js +21 -7
  318. package/dist/web-mod-store.js.map +1 -1
  319. package/dist/web-prefs.d.ts +14 -0
  320. package/dist/web-prefs.js +24 -4
  321. package/dist/web-prefs.js.map +1 -1
  322. package/dist/web-preview.d.ts +6 -2
  323. package/dist/web-preview.js +4 -2
  324. package/dist/web-preview.js.map +1 -1
  325. package/dist/web-returning-device.d.ts +41 -0
  326. package/dist/web-returning-device.js +57 -5
  327. package/dist/web-returning-device.js.map +1 -1
  328. package/dist/web-session-authorization.d.ts +132 -0
  329. package/dist/web-session-authorization.js +193 -0
  330. package/dist/web-session-authorization.js.map +1 -0
  331. package/dist/web-session.d.ts +70 -10
  332. package/dist/web-session.js +137 -22
  333. package/dist/web-session.js.map +1 -1
  334. package/dist/web-settings-patch.d.ts +49 -2
  335. package/dist/web-settings-patch.js +145 -1
  336. package/dist/web-settings-patch.js.map +1 -1
  337. package/dist/web-settings-tools.d.ts +89 -0
  338. package/dist/web-settings-tools.js +270 -0
  339. package/dist/web-settings-tools.js.map +1 -0
  340. package/dist/web-shell-routes.d.ts +1 -1
  341. package/dist/web-shell-routes.js +1 -1
  342. package/dist/web-shell-routes.js.map +1 -1
  343. package/dist/web-theme-store.d.ts +39 -0
  344. package/dist/web-theme-store.js +122 -11
  345. package/dist/web-theme-store.js.map +1 -1
  346. package/dist/web-theme.d.ts +2 -0
  347. package/dist/web-theme.js +10 -1
  348. package/dist/web-theme.js.map +1 -1
  349. package/dist/workflow-templates/default.md +1 -1
  350. package/package.json +1 -1
  351. package/templates/systemd.service.ejs +8 -0
@@ -119,7 +119,10 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
119
119
  @media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) .idc { --id: var(--id-light); --id-name: var(--id-name-light); } }
120
120
  :root[data-theme="light"] .idc { --id: var(--id-light); --id-name: var(--id-name-light); }
121
121
  .idc.id-ring .av { box-shadow: 0 0 0 2px var(--id); }
122
- .idc.id-bar { box-shadow: inset 3px 0 0 var(--id); }
122
+ /* #2056: the bar is a plain straight line inside the row: no caps following the row's rounded corners (an inset shadow
123
+ did), and short of the corners, so the hover and selected highlight's radius is never crossed. */
124
+ .idc.id-bar { position: relative; }
125
+ .idc.id-bar::before { content: ""; position: absolute; left: 0; top: var(--s-2); bottom: var(--s-2); width: 3px; background: var(--id); pointer-events: none; }
123
126
  .idc.id-name .inst-name { color: var(--id-name); }
124
127
  .idc-swatches, .idc-places { border: 0; padding: 0; margin: 0 0 var(--s-3); display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
125
128
  .idc-swatches legend, .idc-places legend { font-weight: var(--fw-semibold); margin-bottom: 6px; width: 100%; }
@@ -132,6 +135,20 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
132
135
  .idc-hex { width: 9ch; font-family: var(--font-mono, monospace); }
133
136
  .dlg-actions .dlg-spacer { flex: 1 1 auto; }
134
137
  .idc-preview { padding: 6px; border-radius: 10px; background: var(--bg-sidebar); max-width: 260px; }
138
+ /* #2056 B: Settings → Web appearance — plain group headings, a "?" per card to its docs section, and the colour card's sample row. */
139
+ .s-ap-group { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--text); margin: var(--s-4) 0 var(--s-2); }
140
+ .s-ap-group:first-child { margin-top: 0; }
141
+ .s-ap-title { display: flex; align-items: center; gap: var(--s-2); }
142
+ /* The fields' placeholders show the defaults: readable, not the browser's faint grey (dark mode). */
143
+ .s-appearance input::placeholder { color: var(--text-3); opacity: 1; }
144
+ /* A card title in Chinese is never upper-cased ("AGENT 的主題"): the small caps are a Latin convention. */
145
+ :lang(zh-Hant) .card h3 { text-transform: none; letter-spacing: 0; }
146
+ .s-learn { display: inline-grid; place-items: center; width: 18px; height: 18px; border-radius: var(--r-full); border: 1px solid var(--border);
147
+ color: var(--text-3); font-size: 11px; text-decoration: none; text-transform: none; letter-spacing: 0; line-height: 1; }
148
+ .s-learn:hover, .s-learn:focus-visible { color: var(--text); border-color: var(--text-3); }
149
+ .s-colors-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); margin-bottom: var(--s-2); }
150
+ .s-colors-row .idc-places { margin-bottom: 0; flex: 1 1 220px; }
151
+ .s-colors-sample { flex: 0 1 220px; min-width: 0; }
135
152
  .av-text { overflow: hidden; }
136
153
  .av .av-dot { position: absolute; right: -2px; bottom: -2px; margin: 0; box-shadow: 0 0 0 2px var(--bg-sidebar); }
137
154
  /* #1722: the CLI as a letter on the avatar's top-left corner — monochrome (the #1630 ring keeps the colour), cut out like the dot */
@@ -157,11 +174,20 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
157
174
  }
158
175
  /* #1604: one row — Settings and Session (the anonymous View reader: Sign in and the theme/language popover). Their
159
176
  pop-ups open over the whole footer's width. */
160
- .side-foot { display: flex; align-items: center; gap: var(--s-1); padding: var(--s-2); position: relative; }
177
+ /* #1922: a divider above the footer, with the filter block's bottom padding and the footer's top padding trimmed to
178
+ --s-1: the gap reads as a section break, not an empty ~20 px band (the two --s-2 paddings used to stack). */
179
+ .side-foot { display: flex; align-items: center; gap: var(--s-1); padding: var(--s-1) var(--s-2) var(--s-2); border-top: 1px solid var(--border); position: relative; }
161
180
  .side-foot > .side-row, .side-foot > .session { flex: 1; min-width: 0; }
162
181
  .side-foot > .session { position: static; }
163
182
  .side-foot .side-row span:not(.dot):not(.meta) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
164
183
  .side-foot .session .meta { display: none; }
184
+ /* #1920 phase 3: a guest's page says who it is signed in as, beside its session. */
185
+ .guest-chip { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--s-1); margin: 0; padding: 6px var(--s-2); border-radius: var(--r-sm);
186
+ background: var(--surface-2); color: var(--text-2); font-size: var(--fs-sm); }
187
+ .guest-chip .grow { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
188
+ /* Its own row: a name and a platform do not fit beside the session at sidebar widths. */
189
+ .guest .side-foot { flex-wrap: wrap; }
190
+ .guest .side-foot > .guest-chip { flex-basis: 100%; }
165
191
  .prefs-pop > summary { list-style: none; cursor: pointer; }
166
192
  .prefs-pop > summary::-webkit-details-marker { display: none; }
167
193
  .prefs.labelled { flex-direction: column; gap: var(--s-2); padding: 0; }
@@ -220,11 +246,15 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
220
246
  }
221
247
  .panel-head .sb-open { display: none; }
222
248
  .sb-collapsed .panel-head .sb-open { display: inline-flex; }
249
+ /* #2074: a collapsed sidebar's "New agent", beside its ☰ (a phone opens the drawer, which has the row). */
250
+ .panel-head .sb-new { margin-left: calc(-1 * var(--s-1)); }
223
251
  /* One row of --header-h on every page (a phone's instance tabs wrap below it): the title, its status and badges on one centre line. */
224
252
  .panel-title { flex: 1; min-width: 0; display: flex; align-items: center; gap: var(--s-3); min-height: calc(var(--header-h) - 2 * var(--s-1)); }
225
253
  .panel-title h1 { font-size: var(--fs-lg); line-height: var(--lh-lg); font-weight: var(--fw-semibold); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; outline: none; }
226
254
  .panel-sub { color: var(--text-2); font-size: var(--fs-sm); white-space: nowrap; }
227
255
  .status { display: inline-flex; align-items: center; gap: 6px; }
256
+ /* #2015: the largest processes, after the host memory chip's text. */
257
+ .host-mem-top { margin-left: var(--s-2); opacity: 0.8; overflow-wrap: anywhere; }
228
258
  .panel-actions { display: flex; align-items: center; gap: var(--s-1); }
229
259
  /* #1523 N2: the instance view switch — beside the title on a wide screen; on a phone, a row of tabs under the header. */
230
260
  .inst-switch.seg { padding: 0; margin: 0; width: auto; max-width: none; flex-shrink: 0; }
@@ -343,6 +373,9 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
343
373
  .field input:focus, .field select:focus, .field textarea:focus, .field-row input:focus { border-color: var(--focus); box-shadow: 0 0 0 3px var(--accent-dim); }
344
374
  .field input:disabled { opacity: 0.6; }
345
375
  .field-row { display: flex; gap: var(--s-1); align-items: center; }
376
+ /* #2072: a model field — the list, and the text box "Other…" opens under it. */
377
+ .model-pick { display: flex; flex-direction: column; gap: var(--s-1); min-width: 0; }
378
+ .model-pick > select, .model-pick > input { width: 100%; }
346
379
  .note { font-size: var(--fs-xs); color: var(--text-3); }
347
380
  .checks { display: flex; flex-direction: column; gap: var(--s-1); max-height: 180px; overflow-y: auto; }
348
381
  .check { display: flex; gap: var(--s-2); align-items: center; font-size: var(--fs-sm); }
@@ -420,6 +453,9 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
420
453
  .msg.status-line .st-more { border: 0; background: none; color: var(--text-2); cursor: pointer; font-size: var(--fs-xs); padding: var(--s-1) 0 0 var(--s-3); }
421
454
  .msg.status-line .st-more:hover { color: var(--text); }
422
455
  .msg-tools { display: flex; gap: var(--s-1); margin-top: var(--s-1); opacity: 0; transition: opacity var(--t-fast); }
456
+ /* #2147: bounded stock toolbar paint, including fixed descendants. Padding preserves focus outlines. */
457
+ .shell .thread .msg-tools { contain: paint; overflow: clip; overflow-clip-margin: 0px; padding: 4px; margin: 0 -4px -4px; }
458
+ .shell .thread .codeblock > .cb-head { contain: paint; overflow: clip; overflow-clip-margin: 0px; }
423
459
  .msg:hover .msg-tools, .msg:focus-within .msg-tools { opacity: 1; }
424
460
  @media (hover: none) { .msg-tools { opacity: 1; } }
425
461
  /* Delivery ticks: always visible, in colour, with their label on hover and for screen readers (rough edge 10). */
@@ -526,7 +562,7 @@ html.lb-open .scroller { overflow: hidden; }
526
562
  these tokens. The surface paints its own background (the page's body is not its). */
527
563
  .p-chat.th { background: var(--bg); color: var(--text); }
528
564
  .p-chat.th .msg.user .body { border-radius: var(--bubble-radius, var(--r-xl)); }
529
- .p-chat.th-font-system .thread, .p-chat.th-font-system .composer textarea { font-family: system-ui, -apple-system, "Segoe UI", sans-serif; }
565
+ .p-chat.th-font-system .thread, .p-chat.th-font-system .composer textarea { font-family: system-ui, -apple-system, "Segoe UI", sans-serif, var(--font-emoji); }
530
566
  .p-chat.th-font-mono .thread, .p-chat.th-font-mono .composer textarea { font-family: var(--font-mono); }
531
567
  .p-chat.th-bubble-thin .msg.user .body { box-shadow: inset 0 0 0 1px var(--border); }
532
568
  .p-chat.th-bubble-accent .msg.user .body { box-shadow: inset 0 0 0 1px var(--accent); }
@@ -536,6 +572,20 @@ html.lb-open .scroller { overflow: hidden; }
536
572
  .th-bg { position: absolute; inset: 0; width: 100%; height: 100%; opacity: var(--th-bg-opacity, 1); pointer-events: none; z-index: 0; user-select: none; }
537
573
  .th-bg-cover { object-fit: cover; }
538
574
  .th-bg-contain { object-fit: contain; }
575
+ /* #2117: an agent's ambient effect (chat-ambient.js). The loop sits behind the thread (z 0, under the scroller and the
576
+ dock at z 1); a burst follows the dock in the tree at z 1, so it shows over the messages and under the header (z 2).
577
+ Neither ever takes a pointer, focus or selection; positions and sizes are set through the CSSOM. */
578
+ .chat-main.has-amb { position: relative; }
579
+ .chat-main.has-amb > .amb-layer, .chat-main.has-amb > .amb-burst { position: absolute; inset: 0; overflow: hidden; pointer-events: none; user-select: none; contain: strict; }
580
+ .chat-main.has-amb > .amb-layer { z-index: 0; }
581
+ .chat-main.has-amb > .amb-burst { z-index: 1; }
582
+ .amb-p { position: absolute; left: 0; top: 0; line-height: 1; color: var(--accent); pointer-events: none; will-change: transform, opacity; }
583
+ img.amb-p { object-fit: contain; }
584
+ .s-mod-glyph { width: 24px; height: 24px; object-fit: contain; }
585
+ /* #2117: the one-time "reduced motion; effects are on" note, a strip above the thread. */
586
+ .chat-main > .amb-note { position: relative; z-index: 1; display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; margin: var(--s-2) var(--s-3) 0; padding: var(--s-1) var(--s-2); border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); color: var(--text-2); font-size: 13px; }
587
+ .chat-main > .amb-note .amb-note-off { border: 0; background: none; padding: 0; color: var(--accent); text-decoration: underline; cursor: pointer; font: inherit; min-height: 32px; }
588
+ .chat-main > .amb-note .amb-note-x { margin-left: auto; min-width: 32px; min-height: 32px; border: 0; background: none; color: var(--text-2); cursor: pointer; }
539
589
  .pv-divider { flex: 0 0 8px; cursor: col-resize; background: var(--bg); border-left: 1px solid var(--border); touch-action: none; position: relative; }
540
590
  .pv-divider::after { content: ""; position: absolute; top: 50%; left: 2px; width: 3px; height: 32px; margin-top: -16px; border-radius: 2px; background: var(--border-strong); }
541
591
  .pv-divider:hover::after, .pv-divider:focus-visible::after, .chat-split.resizing .pv-divider::after { background: var(--accent); }
@@ -591,8 +641,12 @@ html.lb-open .scroller { overflow: hidden; }
591
641
  .pv-menu[open] { z-index: 6; background: var(--surface); border-radius: var(--r-sm); padding: var(--s-1); box-shadow: var(--shadow-2); }
592
642
  .pv-menu[open] > .chip-btn { display: block; text-align: left; width: max-content; max-width: 260px; white-space: normal; }
593
643
 
644
+ /* #2087: the pill takes no room in the thread — it was a sticky box in the flow, 38 px at the end of the scroll, so
645
+ hiding it 48 px from the bottom took that room away under a reader still scrolling — the wheel's last step was cut
646
+ short, or the view clamped back. Its dock is sticky and 0 px tall; the pill floats above it. */
647
+ .jump-dock { position: sticky; bottom: 0; height: 0; z-index: 5; }
594
648
  .jump-latest {
595
- position: sticky; bottom: var(--s-3); left: 50%; transform: translateX(-50%); z-index: 5; display: inline-flex; align-items: center; gap: var(--s-1);
649
+ position: absolute; bottom: var(--s-3); left: 50%; transform: translateX(-50%); white-space: nowrap; display: inline-flex; align-items: center; gap: var(--s-1);
596
650
  background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: var(--r-full); box-shadow: var(--shadow-2);
597
651
  padding: 6px var(--s-4); font-size: var(--fs-xs); font-weight: var(--fw-semibold); cursor: pointer;
598
652
  }
@@ -631,6 +685,26 @@ html.lb-open .scroller { overflow: hidden; }
631
685
  @media (max-width: 479px) { .btn-stop .stop-label { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; } }
632
686
  .btn-stop { background: var(--bg); }
633
687
  .hint { font-size: var(--fs-xs); color: var(--text-3); text-align: center; }
688
+ /* #1920 phase 3: Settings → Web → Guests. */
689
+ .s-guests .guest-names { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 2px var(--s-3); max-height: 220px; overflow: auto;
690
+ padding: var(--s-1) var(--s-2); border: 1px solid var(--border); border-radius: var(--r-sm); }
691
+ .s-guests .guest-name span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
692
+ .s-guests .guest-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-2); }
693
+ .s-guests .guest-row { padding: var(--s-2) var(--s-3); border: 1px solid var(--border); border-radius: var(--r-sm); }
694
+ .s-guests .guest-actions { display: flex; flex-wrap: wrap; gap: var(--s-1); margin-top: var(--s-1); }
695
+ .s-guests .guest-narrow-edit { margin-top: var(--s-2); }
696
+ .s-guests .guest-away { color: var(--text-3); }
697
+ /* #1939 S2b: this browser's authorization, under the composer and in the session menu (one compact line). */
698
+ .webauth-chip { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1) var(--s-2); margin: var(--s-1) 0 0; font-size: var(--fs-xs); }
699
+ .webauth-chip .webauth-ok { color: var(--ok); }
700
+ .webauth-chip .btn-sm { min-height: 24px; }
701
+ /* #1939 S2a: Settings → Web → Web authority. */
702
+ .s-web-authority .s-va-sessions { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-2); }
703
+ .s-web-authority .s-va-sessions li { padding: var(--s-2) var(--s-3); border: 1px solid var(--border); border-radius: var(--r-sm); overflow-wrap: anywhere; }
704
+ .s-web-authority .s-va-warn { background: var(--warn-bg); color: var(--warn); border: 1px solid var(--warn-line); border-radius: var(--r-sm); padding: var(--s-1) var(--s-2); }
705
+ .s-web-authority .save-row { flex-wrap: wrap; }
706
+ /* #1920: a guest's note under the composer — on a phone too, where the key hints are hidden. */
707
+ .guest-note { margin: 2px 0 0; font-size: var(--fs-xs); color: var(--text-3); text-align: center; }
634
708
  .pending-files { display: flex; flex-wrap: wrap; gap: var(--s-2); padding: var(--s-2) var(--s-2) 0; }
635
709
  .file-chip { display: flex; align-items: center; gap: 6px; font-size: var(--fs-xs); padding: 4px var(--s-2); border-radius: var(--r-md); background: var(--bg); max-width: 320px; }
636
710
  .file-chip img { width: 28px; height: 28px; object-fit: cover; border-radius: 4px; }
@@ -723,6 +797,7 @@ html.lb-open .scroller { overflow: hidden; }
723
797
  .sb-open .sidebar { transform: none; visibility: visible; }
724
798
  .sb-open .scrim { display: block; position: fixed; inset: 0; background: var(--scrim); z-index: 39; }
725
799
  .panel-head .sb-open { display: inline-flex; }
800
+ .panel-head .sb-new { display: none; }
726
801
  .only-narrow { display: inline-flex; }
727
802
  .thread { padding: var(--s-3) var(--s-3) var(--s-2); gap: var(--s-5); }
728
803
  .col { padding: var(--s-3) var(--s-3) var(--s-8); }
@@ -765,6 +840,9 @@ html.lb-open .scroller { overflow: hidden; }
765
840
  color: var(--text-3); font-size: var(--fs-xs); font-weight: var(--fw-semibold); text-transform: uppercase; letter-spacing: 0.04em; cursor: pointer; text-align: left;
766
841
  }
767
842
  .v-group-head:hover { color: var(--text); }
843
+ /* #2172: what a group is — a team's icon, or "#" before a tag — muted, before its name (sidebar and Settings). */
844
+ .v-group-kind { display: inline-flex; align-items: center; flex: none; color: var(--text-3); font-weight: var(--fw-medium); }
845
+ .v-group-kind.tag { min-width: 0.7em; justify-content: center; }
768
846
  .v-group-head .grow { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
769
847
  .v-group-head .count { font-weight: var(--fw-medium); letter-spacing: 0; text-transform: none; padding: 0 var(--s-2); border-radius: var(--r-full); background: var(--surface-2); }
770
848
  .caret { transition: transform var(--t-fast) var(--ease); }
@@ -781,6 +859,9 @@ html.lb-open .scroller { overflow: hidden; }
781
859
  .inst-star.on { color: var(--warn); }
782
860
  .inst-star.on svg, .fav-mark { fill: currentColor; }
783
861
  .fav-head { cursor: default; }
862
+ /* #1936: the open instance, pinned on top while a filter is on — its header reads "Current", a line under it. */
863
+ .pinned-head { cursor: default; }
864
+ .v-pinned { border-bottom: 1px solid var(--border); padding-bottom: var(--s-2); margin-bottom: var(--s-1); }
784
865
  .fav-mark { color: var(--warn); }
785
866
  .inst-nav:not(.fav-editing) .inst-star:not(.on) { visibility: hidden; }
786
867
  /* As specific as the rule that hides it (and later), so hover and focus win. */
@@ -791,6 +872,35 @@ html.lb-open .scroller { overflow: hidden; }
791
872
  @media (max-width: 899px) { .fav-editing .inst-star, .fav-editing .inst-move { width: 36px; height: 36px; } }
792
873
  .v-inst .dot { margin-top: 0; }
793
874
  .v-meta { color: var(--text-3); font-size: var(--fs-xs); font-variant-numeric: tabular-nums; }
875
+ .v-meta { flex: none; }
876
+ /* #2179 (B5, the user's pick): the name and its % on the first line; the other name and the last reply's time on the
877
+ second, the time at its end — one column of times down the list. The ★ beside a two-line row sits on the first line. */
878
+ .inst-line { display: flex; align-items: baseline; gap: var(--s-2); min-width: 0; }
879
+ .inst-line > .inst-name, .inst-line2 > .inst-alt { flex: 1; min-width: 0; }
880
+ .inst-when { flex: none; font-size: var(--fs-xs); line-height: var(--lh-xs); color: var(--text-3); font-variant-numeric: tabular-nums; white-space: nowrap; }
881
+ .inst-when.fresh { color: var(--accent); font-weight: var(--fw-semibold); }
882
+ .v-li.two-line > .inst-star, .v-li.two-line > .inst-move { align-self: flex-start; margin-top: 6px; }
883
+ /* A fresh reply: an accent tint that fades with --reply-a (1 → 0 over ten minutes, set on the row), never over the open
884
+ or a hovered row; one under a minute old pulses once — not under reduced motion. Nothing to click or clear. */
885
+ .inst.replied:not(.active):not(:hover) { background: color-mix(in srgb, var(--accent) calc(var(--reply-a, 0) * 22%), transparent); }
886
+ .inst.just:not(.active) { animation: inst-just 2.4s ease-out 1; }
887
+ @keyframes inst-just { from { box-shadow: inset 0 0 0 1.5px var(--accent); } to { box-shadow: inset 0 0 0 1.5px transparent; } }
888
+ @media (prefers-reduced-motion: reduce) { .inst.just:not(.active) { animation: none; } } /* as specific as the rule it stops */
889
+ /* #2179: how the list sorts — the button says it (the accent while "Recent replies" is on); its menu under it. */
890
+ .inst-head { position: relative; }
891
+ .nav-sort { position: static; flex: none; } /* the menu spans the header's width, under it — never past the sidebar's edge */
892
+ .nav-sort-btn { display: inline-flex; align-items: center; gap: 4px; }
893
+ .nav-sort-btn.on { color: var(--accent); }
894
+ .nav-sort-static { cursor: default; }
895
+ .nav-sort-menu { position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 30; padding: var(--s-3);
896
+ border-radius: var(--r-md); background: var(--surface); border: 1px solid var(--border); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
897
+ display: flex; flex-direction: column; gap: var(--s-3); }
898
+ .nav-sort-menu fieldset { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
899
+ .nav-sort-menu legend { padding: 0 0 4px; font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--text-2); }
900
+ .nav-sort-opt { display: flex; align-items: flex-start; gap: var(--s-2); min-height: 36px; padding: 4px 2px; font-size: var(--fs-sm); cursor: pointer; }
901
+ .nav-sort-opt input { margin-top: 3px; flex: none; }
902
+ .nav-sort-hint { display: block; font-size: var(--fs-xs); color: var(--text-3); }
903
+ .recent-head { cursor: default; }
794
904
  /* #1722: the sidebar's width — a separator on its right edge (drag, arrows, Home/End, double-click resets); not on a phone */
795
905
  .side-resize { position: absolute; top: 0; right: -3px; width: 6px; height: 100%; z-index: 5; cursor: col-resize; touch-action: none; }
796
906
  .side-resize:hover, .side-resize:focus-visible, .side-resize.dragging { background: var(--accent-dim, var(--border-strong)); }
@@ -800,7 +910,7 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
800
910
  .dragging { opacity: 0.4; }
801
911
  .drop-before { box-shadow: inset 0 2px 0 var(--accent); }
802
912
  .drop-after { box-shadow: inset 0 -2px 0 var(--accent); }
803
- .v-filter { flex: none; padding: var(--s-2) var(--s-3) var(--s-2); border-top: 1px solid var(--border); }
913
+ .v-filter { flex: none; padding: var(--s-2) var(--s-3) var(--s-1); border-top: 1px solid var(--border); }
804
914
  .v-filter-box { display: flex; align-items: center; gap: var(--s-2); padding: 0 var(--s-1) 0 var(--s-3); border-radius: var(--r-sm); background: var(--surface); color: var(--text-3); }
805
915
  .v-filter-box:focus-within { box-shadow: 0 0 0 2px var(--focus); }
806
916
  .v-filter-box input { flex: 1; min-width: 0; height: 34px; border: 0; background: transparent; color: var(--text); font-size: var(--fs-sm); outline: none; }
@@ -863,6 +973,10 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
863
973
  /* #1749: one line beside ↑↓ — the name gives way first, then the plan badge (each with an ellipsis); ↑↓ never wrap. */
864
974
  .u-head strong { min-width: 0; flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
865
975
  .u-plan { min-width: 0; flex: none; max-width: 55%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
976
+ /* #2014: a credential profile's row — the provider name beside the plan badge, and the profile on its own full-width
977
+ muted line under the head (one line in the rail, whole in the dialog). */
978
+ .u-profile { margin-top: calc(-1 * var(--s-2) + 2px); color: var(--text-3); font-size: var(--fs-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
979
+ .dlg .u-profile { white-space: normal; overflow-wrap: anywhere; }
866
980
  .u-order { margin-left: auto; display: flex; gap: 2px; flex: none; }
867
981
  .u-order .icon-btn { width: 28px; height: 28px; }
868
982
  .u-order .icon-btn:disabled { opacity: 0.3; }
@@ -1113,6 +1227,123 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1113
1227
  content: ""; position: absolute; top: var(--s-5); left: calc(-1 * var(--s-4) - 2px); width: var(--s-4); height: 2px; background: var(--border-strong); }
1114
1228
  .org-link { min-height: var(--tap); }
1115
1229
  }
1230
+ /* ── #1918: the org chart as a canvas (fleet-canvas.js). SVG with classes only: per-card values are SVG attributes, the
1231
+ view's transform one attribute on .oc-world (#1300). The stage fills the panel's height; touch gestures are the
1232
+ canvas's own (touch-action: none on it only), the page around it still scrolls. ── */
1233
+ .oc-bar { flex-wrap: wrap; row-gap: var(--s-2); }
1234
+ .oc-tools { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
1235
+ .oc-find { flex: 0 1 260px; min-width: 150px; position: relative; }
1236
+ .oc-find input { height: 32px; padding-right: 72px; }
1237
+ .oc-found { position: absolute; right: var(--s-3); top: 50%; transform: translateY(-50%); font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; pointer-events: none; }
1238
+ .oc-stage { position: relative; height: clamp(420px, calc(100dvh - 240px), 960px); border: 1px solid var(--border); border-radius: var(--r-lg); overflow: hidden; background: var(--bg); }
1239
+ .oc-svg { display: block; width: 100%; height: 100%; touch-action: none; cursor: grab; user-select: none; -webkit-user-select: none; }
1240
+ .oc-svg:active { cursor: grabbing; }
1241
+ .oc-svg:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
1242
+ .oc-tree path { fill: none; stroke: var(--border-strong); stroke-width: 1.25; }
1243
+ .oc-box { fill: var(--surface); stroke: var(--border); stroke-width: 1; }
1244
+ .oc-team.loose .oc-box { stroke-dasharray: 5 4; }
1245
+ .oc-box.match { stroke: var(--focus); stroke-width: 2.5; }
1246
+ .oc-head { fill: var(--surface-2); }
1247
+ .oc-hit { fill: transparent; }
1248
+ .oc-toggle:not(.empty) { cursor: pointer; }
1249
+ .oc-toggle:focus { outline: none; }
1250
+ .oc-toggle:focus-visible .oc-hit { stroke: var(--focus); stroke-width: 2; }
1251
+ .oc-caret { fill: var(--text-2); font-size: 12px; }
1252
+ .oc-team-name { fill: var(--text); font-size: 13px; font-weight: 600; }
1253
+ .oc-team-meta { fill: var(--text-3); font-size: 11px; }
1254
+ .oc-team-sum { fill: var(--text-2); font-size: 11.5px; }
1255
+ .oc-team.dim, .oc-team-head.dim { opacity: 0.55; }
1256
+ .oc-node.dim { opacity: 0.4; }
1257
+ .oc-node { cursor: pointer; }
1258
+ .oc-node[role="img"] { cursor: default; }
1259
+ .oc-node:focus { outline: none; }
1260
+ .oc-card { fill: var(--bg); stroke: var(--border); stroke-width: 1; }
1261
+ .oc-node.lead .oc-card { fill: var(--surface); stroke: var(--accent); stroke-width: 1.5; }
1262
+ .oc-node:hover .oc-card { stroke: var(--border-strong); }
1263
+ .oc-node:focus-visible .oc-card, .oc-node.match .oc-card, .oc-node.match:hover .oc-card { stroke: var(--focus); stroke-width: 2.5; }
1264
+ .oc-node.st-missing { opacity: 0.7; }
1265
+ .oc-av { fill: var(--surface-2); stroke: var(--surface); stroke-width: 2; }
1266
+ .oc-node.idc.id-ring .oc-av { stroke: var(--id); stroke-width: 2.5; }
1267
+ .oc-idbar { display: none; }
1268
+ .oc-node.idc.id-bar .oc-idbar { display: inline; fill: var(--id); }
1269
+ .oc-face.emoji .oc-av { fill: transparent; }
1270
+ .oc-av-t { fill: var(--text-2); font-size: 12px; font-weight: 600; }
1271
+ .oc-face.emoji .oc-av-t { font-size: 18px; font-weight: normal; }
1272
+ .oc-name { fill: var(--text); font-size: 13.5px; font-weight: 600; }
1273
+ .oc-node.lead .oc-name { font-size: 15px; }
1274
+ .oc-node.idc.id-name .oc-name { fill: var(--id-name); }
1275
+ .oc-sub { font-size: 12px; }
1276
+ .oc-st { fill: var(--text-2); }
1277
+ .oc-st.st-working { fill: var(--ok); } .oc-st.st-needs, .oc-st.st-stuck { fill: var(--warn); } .oc-st.st-crashed { fill: var(--danger); }
1278
+ .oc-dot { fill: var(--text-3); stroke: var(--bg); stroke-width: 2; }
1279
+ .oc-dot.st-working, .oc-dot.st-idle { fill: var(--ok); } .oc-dot.st-needs, .oc-dot.st-stuck { fill: var(--warn); } .oc-dot.st-crashed { fill: var(--danger); }
1280
+ .oc-dot.st-paused { fill: var(--bg); stroke: var(--text-3); stroke-width: 1.5; }
1281
+ .oc-zoom { position: absolute; right: var(--s-3); top: var(--s-3); display: flex; flex-direction: column; border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; background: var(--surface); }
1282
+ .oc-zoom .icon-btn { width: 36px; height: 34px; border-radius: 0; font-size: 18px; }
1283
+ .oc-zoom .icon-btn + .icon-btn { border-top: 1px solid var(--border); }
1284
+ .oc-mini { position: absolute; right: var(--s-3); bottom: var(--s-3); width: 180px; height: 120px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); cursor: pointer; }
1285
+ .oc-mini-team { fill: var(--surface-2); stroke: var(--border-strong); stroke-width: 6; }
1286
+ .oc-mini-team.needs { fill: var(--warn-bg); stroke: var(--warn); }
1287
+ .oc-mini-top { fill: var(--accent-dim); stroke: var(--accent); stroke-width: 6; }
1288
+ .oc-mini-team.match { stroke: var(--focus); stroke-width: 14; }
1289
+ .oc-mini-hit { fill: var(--focus); }
1290
+ .oc-mini-view { fill: none; stroke: var(--focus); stroke-width: 10; }
1291
+ /* #1918 phase 3: live traffic. Kind hues kept apart from the state colours (ok = working/idle, warn = needs you, danger). */
1292
+ .oc { --fx-task: #b794f6; --fx-send: #2dd4bf; --fx-report: #f472b6; --fx-broadcast: #fb923c; --fx-team: #cbd5e1; }
1293
+ @media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) .oc { --fx-task: #7c3aed; --fx-send: #0f766e; --fx-report: #be185d; --fx-broadcast: #c2410c; --fx-team: #475569; } }
1294
+ :root[data-theme="light"] .oc { --fx-task: #7c3aed; --fx-send: #0f766e; --fx-report: #be185d; --fx-broadcast: #c2410c; --fx-team: #475569; }
1295
+ .oc-flow { fill: none; stroke-width: 2.5; stroke-linecap: round; }
1296
+ .oc-flow-halo { fill: none; stroke: var(--bg); stroke-width: 6; stroke-linecap: round; opacity: 0.85; }
1297
+ .oc-flow.h-task { stroke: var(--fx-task); } .oc-flow.h-send { stroke: var(--fx-send); } .oc-flow.h-report { stroke: var(--fx-report); }
1298
+ .oc-flow.h-broadcast { stroke: var(--fx-broadcast); } .oc-flow.h-team { stroke: var(--fx-team); stroke-width: 3.5; }
1299
+ .oc-flow.ph-live { stroke-dasharray: 7 6; animation: oc-dash 0.9s linear infinite; }
1300
+ .oc-flow.ph-queued { stroke-dasharray: 2 5; opacity: 0.8; }
1301
+ .oc-flow.ph-fading { stroke: var(--text-3); animation: oc-fade 12s ease-out forwards; }
1302
+ /* #1918 review: the work — a delegation in progress (a slow dash, kept until its report), its report coming back, none. */
1303
+ .oc-flow.ph-work { stroke-dasharray: 10 7; stroke-width: 3; animation: oc-dash 2.4s linear infinite; }
1304
+ .oc-flow.ph-return { stroke-width: 3; animation: oc-fade 2.4s ease-out forwards; }
1305
+ .oc-flow.ph-stale { stroke: var(--text-3); stroke-dasharray: 3 6; animation: oc-fade 12s ease-out forwards; }
1306
+ .oc-ah.h-task { fill: var(--fx-task); } .oc-ah.h-send { fill: var(--fx-send); } .oc-ah.h-report { fill: var(--fx-report); }
1307
+ .oc-ah.h-broadcast { fill: var(--fx-broadcast); } .oc-ah.h-team { fill: var(--fx-team); } .oc-ah.h-fading { fill: var(--text-3); }
1308
+ .oc-pulse.h-task { fill: var(--fx-task); } .oc-pulse.h-send { fill: var(--fx-send); } .oc-pulse.h-report { fill: var(--fx-report); }
1309
+ .oc-pulse.h-broadcast { fill: var(--fx-broadcast); } .oc-pulse.h-team { fill: var(--fx-team); }
1310
+ .oc-fl.dim { opacity: 0.18; }
1311
+ .oc-badge rect { fill: var(--surface-2); stroke: var(--border); } .oc-badge text { fill: var(--text-2); font-size: 10.5px; }
1312
+ @keyframes oc-dash { to { stroke-dashoffset: -26; } }
1313
+ @keyframes oc-fade { from { opacity: 0.8; } to { opacity: 0.15; } }
1314
+ /* #2052: the key is a strip under the toolbar (its toggle is in the toolbar), never over the canvas. */
1315
+ .oc-legend { margin: 0 0 var(--s-2); font-size: var(--fs-xs); color: var(--text-2); background: var(--surface); border: 1px solid var(--border);
1316
+ border-radius: var(--r-md); padding: 6px 10px; }
1317
+ .oc-key.on { border-color: var(--accent); }
1318
+ .oc-lg-rows { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-3); }
1319
+ .oc-lg { display: inline-flex; align-items: center; gap: 6px; }
1320
+ .oc-sw { width: 18px; height: 3px; border-radius: 2px; display: inline-block; background: var(--text-3); }
1321
+ .oc-sw.h-task { background: var(--fx-task); } .oc-sw.h-send { background: var(--fx-send); } .oc-sw.h-report { background: var(--fx-report); } .oc-sw.h-broadcast { background: var(--fx-broadcast); }
1322
+ .oc-sw.ph-queued { background: repeating-linear-gradient(90deg, var(--text-2) 0 3px, transparent 3px 7px); }
1323
+ .oc-sw.ph-fading { opacity: 0.5; }
1324
+ .oc-sw.ph-work { background: repeating-linear-gradient(90deg, var(--fx-task) 0 6px, transparent 6px 9px); height: 4px; }
1325
+ .oc-sw.ph-return { height: 4px; }
1326
+ .oc-sw.ph-stale { background: repeating-linear-gradient(90deg, var(--text-3) 0 2px, transparent 2px 6px); }
1327
+ .oc-live { gap: 6px; }
1328
+ .oc-live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-3); }
1329
+ .oc-live.on .oc-live-dot { background: var(--ok); }
1330
+ .org-sending { font-size: var(--fs-xs); color: var(--text-2); overflow-wrap: anywhere; }
1331
+ .org-send.k-task { color: var(--fx-task); } .org-send.k-report { color: var(--fx-report); } .org-send.k-broadcast { color: var(--fx-broadcast); }
1332
+ .org-send.k-send, .org-send.k-query, .org-send.k-update { color: var(--fx-send); }
1333
+ @media (prefers-reduced-motion: reduce) {
1334
+ .oc-flow.ph-live { animation: none; stroke-dasharray: none; }
1335
+ .oc-flow.ph-fading { animation: none; opacity: 0.4; }
1336
+ .oc-flow.ph-work { animation: none; }
1337
+ .oc-flow.ph-return, .oc-flow.ph-stale { animation: none; opacity: 0.5; }
1338
+ .oc-pulse { display: none; }
1339
+ }
1340
+ @media (max-width: 640px) {
1341
+ .oc-stage { height: calc(100dvh - 248px); min-height: 340px; }
1342
+ .oc-find { flex: 1 1 150px; }
1343
+ /* #2052: Find, Key, Live and Canvas/List are wider than a phone together: the row wraps (nothing is cut off). */
1344
+ .oc-tools { margin-left: 0; width: 100%; flex-wrap: wrap; row-gap: var(--s-2); }
1345
+ .oc-mini { display: none; }
1346
+ }
1116
1347
  /* A phone: Fleet's five tabs fit when only the open one shows its label (the others keep theirs for screen readers). */
1117
1348
  @media (max-width: 479px) {
1118
1349
  .p-fleet .seg-item:not(.active) > span, .p-settings .seg-item:not(.active) > span { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
@@ -1154,6 +1385,73 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1154
1385
  .ch-bar .btn { min-height: var(--tap); }
1155
1386
  }
1156
1387
 
1388
+ /* ── #2122: Fleet → Usage. A categorical palette kept apart from the state colours (Okabe–Ito hues, tuned per mode for
1389
+ contrast); every series also has a text label, so colour is never the only cue. ── */
1390
+ .us { --us-0: #56b4e9; --us-1: #e69f00; --us-2: #2bc48a; --us-3: #f0e442; --us-4: #e78ac3; --us-5: #94a3b8; --us-heat: #56b4e9;
1391
+ display: flex; flex-direction: column; gap: var(--s-3); min-width: 0; }
1392
+ @media (prefers-color-scheme: light) { :root:not([data-theme="dark"]) .us { --us-0: #0072b2; --us-1: #c25400; --us-2: #007a5a; --us-3: #8a6d00; --us-4: #b0457a; --us-5: #64748b; --us-heat: #0072b2; } }
1393
+ :root[data-theme="light"] .us { --us-0: #0072b2; --us-1: #c25400; --us-2: #007a5a; --us-3: #8a6d00; --us-4: #b0457a; --us-5: #64748b; --us-heat: #0072b2; }
1394
+ .us-bar-top { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2) var(--s-3); margin-top: var(--s-2); }
1395
+ .us-group { display: inline-flex; align-items: center; gap: var(--s-2); font-size: var(--fs-sm); color: var(--text-2); }
1396
+ .us-group select, .us-custom input { min-height: 32px; padding: 0 var(--s-2); border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--bg); color: var(--text); }
1397
+ .us-group select:focus, .us-custom input:focus { border-color: var(--focus); box-shadow: 0 0 0 3px var(--accent-dim); outline: none; }
1398
+ .us-csv { margin-left: auto; }
1399
+ .us-custom { display: flex; flex-wrap: wrap; align-items: end; gap: var(--s-2) var(--s-3); font-size: var(--fs-sm); }
1400
+ .us-custom label { display: inline-flex; flex-direction: column; gap: 2px; color: var(--text-2); }
1401
+ .us-basis { font-size: var(--fs-sm); color: var(--text); padding: var(--s-2) var(--s-3); border-left: 3px solid var(--accent); background: var(--surface-2); border-radius: var(--r-sm); }
1402
+ .us-fresh { font-size: var(--fs-xs); }
1403
+ .us-partial { font-size: var(--fs-sm); padding: var(--s-2) var(--s-3); border-left: 3px solid var(--warn); background: var(--warn-bg); border-radius: var(--r-sm); }
1404
+ .us-unpriced { font-size: var(--fs-xs); }
1405
+ .us-scan { display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
1406
+ .us-totals { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-3); margin: 0; }
1407
+ .us-totals .ch-metric dd { font-size: var(--fs-lg); }
1408
+ .us-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); font-variant-numeric: tabular-nums; }
1409
+ .us-table th, .us-table td { padding: var(--s-2); border-bottom: 1px solid var(--border); text-align: left; vertical-align: top; }
1410
+ .us-table thead th { font-size: 11px; font-weight: var(--fw-regular); color: var(--text-3); }
1411
+ .us-table .num { text-align: right; white-space: nowrap; }
1412
+ .us-name { min-width: 0; max-width: 360px; font-weight: var(--fw-regular); }
1413
+ .us-name .mono { display: block; font-weight: var(--fw-semibold); overflow-wrap: anywhere; }
1414
+ .us-bar { display: block; width: 100%; height: 6px; margin: 4px 0; }
1415
+ .us-bar-bg { fill: var(--surface-2); } .us-bar-fg { fill: var(--us-0); }
1416
+ .us-meta { display: block; font-size: var(--fs-xs); color: var(--text-3); overflow-wrap: anywhere; }
1417
+ .us-flags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
1418
+ .us-trend, .us-heat { display: flex; flex-direction: column; gap: var(--s-2); min-width: 0; }
1419
+ .us-trend h3, .us-heat h3, .us-rest h3 { font-size: var(--fs-sm); font-weight: var(--fw-semibold); }
1420
+ .us-scroll { overflow-x: auto; max-width: 100%; }
1421
+ .us-chart, .us-heatmap { display: block; }
1422
+ .us-axis { font-size: 9px; fill: var(--text-3); }
1423
+ .us-s0 { fill: var(--us-0); background: var(--us-0); } .us-s1 { fill: var(--us-1); background: var(--us-1); } .us-s2 { fill: var(--us-2); background: var(--us-2); }
1424
+ .us-s3 { fill: var(--us-3); background: var(--us-3); } .us-s4 { fill: var(--us-4); background: var(--us-4); } .us-s5 { fill: var(--us-5); background: var(--us-5); }
1425
+ .us-legend { display: flex; flex-wrap: wrap; gap: var(--s-1) var(--s-4); list-style: none; padding: 0; margin: 0; font-size: var(--fs-xs); }
1426
+ .us-legend li { display: inline-flex; align-items: center; gap: var(--s-1); min-width: 0; overflow-wrap: anywhere; }
1427
+ .us-sw, .us-h-sw { display: inline-block; width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; }
1428
+ .us-h { fill: var(--us-heat); }
1429
+ .us-h.l1, .us-h-sw.l1 { opacity: 0.45; } .us-h.l2 { opacity: 0.65; } .us-h.l3 { opacity: 0.85; } .us-h.l4, .us-h-sw.l4 { opacity: 1; }
1430
+ .us-h-sw.l4 { background: var(--us-heat); }
1431
+ /* Read with nothing used: a solid grey cell. Not collected: an empty dashed outline. */
1432
+ .us-h.idle { fill: var(--text-3); opacity: 0.35; }
1433
+ .us-h-sw.idle { background: var(--text-3); opacity: 0.35; }
1434
+ .us-h.gap { fill: none; stroke: var(--text-3); stroke-width: 1; stroke-dasharray: 2 2; }
1435
+ .us-h-sw.gap { border: 1px dashed var(--text-3); }
1436
+ .us-grid, .us-base { stroke: var(--border); stroke-width: 1; }
1437
+ .us-grid { stroke-dasharray: 3 3; }
1438
+ .us-scale, .us-earlier { font-size: var(--fs-xs); }
1439
+ .us-heat-key { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1); font-size: var(--fs-xs); }
1440
+ .us-rest .row-item { font-size: var(--fs-xs); }
1441
+ .u-by-agent { margin-left: auto; }
1442
+ @media (max-width: 699px) {
1443
+ .us-totals { grid-template-columns: repeat(2, minmax(0, 1fr)); }
1444
+ .us-csv { margin-left: 0; }
1445
+ .us-bar-top .btn { min-height: var(--tap); }
1446
+ /* Rows become cards: each value with its label. */
1447
+ .us-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
1448
+ .us-table, .us-table tbody, .us-table tr, .us-table th, .us-table td { display: block; width: 100%; }
1449
+ .us-table tr { margin-bottom: var(--s-3); padding: var(--s-3); border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--bg); }
1450
+ .us-table th, .us-table td { border: 0; padding: 2px 0; max-width: none; }
1451
+ .us-table td { display: flex; justify-content: space-between; gap: var(--s-3); }
1452
+ .us-table td::before { content: attr(data-label); color: var(--text-3); text-align: left; }
1453
+ }
1454
+
1157
1455
  /* ── Chat commands and quick actions (#1269) ── */
1158
1456
  .hd-chip { margin-left: var(--s-2); padding: 0 var(--s-2); border: 1px solid var(--border); border-radius: var(--r-full); background: transparent; color: var(--text-2);
1159
1457
  font-size: var(--fs-xs); line-height: 20px; cursor: pointer; max-width: 16em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; }
@@ -1186,17 +1484,49 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1186
1484
  }
1187
1485
  /* #1630: a Discord server emoji in a chat message, the size of the text around it. */
1188
1486
  .md img.emoji { display: inline-block; width: 1.375em; height: 1.375em; object-fit: contain; vertical-align: -0.3em; margin: 0 0.05em; }
1487
+ .md .emoji-text { color: var(--text-2); }
1189
1488
  /* #1682: a mention, as Discord draws one — accent text on a tint of it; the id (or the Telegram handle) on hover. */
1190
1489
  .md .mention { display: inline; padding: 0 0.2em; border-radius: var(--r-sm); color: var(--accent); background: var(--accent-dim);
1191
1490
  font-weight: var(--fw-medium); white-space: nowrap; }
1192
1491
 
1193
1492
  /* #1629 r4 §1 / r4.1 Q3 (a): the identity card (identity-card.js). The size is a class token; its heights are here
1194
- (S 160 / M 240 / L 360 px, on a phone 120 / 180 / 240); the banner scales with the portrait. Never a style attribute. */
1195
- .id-card { --id-h: 240px; margin: 0 0 var(--s-4); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); overflow: hidden; }
1196
- .id-card.card-s { --id-h: 160px; }
1197
- .id-card.card-l { --id-h: 360px; }
1198
- .id-card.card-c { --id-h: 96px; margin: 0; border: 0; border-radius: 0; background: none; }
1199
- .id-banner { display: block; width: 100%; height: calc(var(--id-h) / 2); object-fit: cover; }
1493
+ (S 160 / M 240 / L 360 px, on a phone 120 / 180 / 240). Never a style attribute.
1494
+ #2038: the banner's height follows the card's width — S 4:1, M 3:1, L 2.25:1 — between a floor and a ceiling per size,
1495
+ so it is never a strip across a wide chat and never a wall on a phone; the picture fills it, cropped about its middle.
1496
+ The chat header's card (card-c) follows the same size choice (card-c bs-<size>). */
1497
+ .id-card { --id-feather: linear-gradient(to right, transparent 0, #000 7%, #000 93%, transparent 100%); --id-h: 240px; --id-ar: 3 / 1; --id-bmin: 140px; --id-bmax: 240px; margin: 0 0 var(--s-4); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); overflow: hidden; }
1498
+ .id-card.card-s { --id-h: 160px; --id-ar: 4 / 1; --id-bmin: 100px; --id-bmax: 160px; }
1499
+ .id-card.card-l { --id-h: 360px; --id-ar: 9 / 4; --id-bmin: 200px; --id-bmax: 360px; }
1500
+ .id-card.card-c { --id-h: 96px; --id-bmin: 120px; --id-bmax: 200px; margin: 0; border: 0; border-radius: 0; background: none; }
1501
+ .id-card.card-c.bs-s { --id-ar: 4 / 1; --id-bmin: 96px; --id-bmax: 150px; }
1502
+ .id-card.card-c.bs-l { --id-ar: 9 / 4; --id-bmin: 150px; --id-bmax: 220px; }
1503
+ /* #2086: the banner at its focus (--id-focus, set per card), never wider than 5 : 1 — its width is capped at five times its
1504
+ size's tallest height, so the band shown everywhere is (picture aspect ÷ 5) of its height. Beyond that it is centred
1505
+ over a blurred copy of itself. */
1506
+ .id-banner-box { position: relative; overflow: hidden; container: idbanner / inline-size; }
1507
+ /* #2089 review: and never narrower than 9 : 4 — its height never more than its box's width ÷ 2.25 (a size's floor yields
1508
+ to it on a narrow column: a wide sidebar, the usage rail), so the promised safe area holds on every layout. */
1509
+ .id-banner { position: relative; display: block; width: 100%; max-width: calc(var(--id-bmax) * 5); margin-inline: auto; height: auto; aspect-ratio: var(--id-ar);
1510
+ min-height: min(var(--id-bmin), calc(100cqw / 2.25)); max-height: min(var(--id-bmax), calc(100cqw / 2.25)); object-fit: cover; object-position: 50% var(--id-focus, 50%); }
1511
+ /* #2086: Edit profile's banner focus — the picture whole, the band every screen shows (dashed, outside it shaded) and a phone's. */
1512
+ .bf-wrap { position: relative; max-width: 440px; overflow: hidden; border-radius: var(--r-sm); }
1513
+ .bf-pic { display: block; width: 100%; height: auto; }
1514
+ .bf-band { position: absolute; pointer-events: none; box-sizing: border-box; }
1515
+ .bf-band span { position: absolute; right: 4px; top: 2px; font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: #fff; text-shadow: 0 1px 2px #000, 0 0 4px #000; }
1516
+ .bf-safe { border: 2px dashed #fff; box-shadow: 0 0 0 999px rgba(0, 0, 0, .45); }
1517
+ /* The phone's band over the shade, in its own colour (its label at its own top-left, never in the shaded part). */
1518
+ .bf-phone { z-index: 1; border: 2px solid #ffd54a; }
1519
+ .bf-phone span { right: auto; left: 8px; top: 5px; color: #ffd54a; }
1520
+ .bf-field input[type=range] { width: 100%; max-width: 440px; }
1521
+ .id-banner-fill { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% var(--id-focus, 50%); filter: blur(24px) saturate(0.75) brightness(0.7); transform: scale(1.4); opacity: .9; pointer-events: none; }
1522
+ /* Wider than the banner's cap (5 × the size's tallest height): its sides feather into the fill, so the sharp centre
1523
+ leads without a seam. One threshold per size (a container query cannot read a custom property). */
1524
+ @container idbanner (min-width: 750px) { .id-card.card-c.bs-s .id-banner { -webkit-mask-image: var(--id-feather); mask-image: var(--id-feather); } }
1525
+ @container idbanner (min-width: 800px) { .id-card.card-s .id-banner { -webkit-mask-image: var(--id-feather); mask-image: var(--id-feather); } }
1526
+ @container idbanner (min-width: 1000px) { .id-card.card-c:not(.bs-s):not(.bs-l) .id-banner { -webkit-mask-image: var(--id-feather); mask-image: var(--id-feather); } }
1527
+ @container idbanner (min-width: 1100px) { .id-card.card-c.bs-l .id-banner { -webkit-mask-image: var(--id-feather); mask-image: var(--id-feather); } }
1528
+ @container idbanner (min-width: 1200px) { .id-card:not(.card-s):not(.card-l):not(.card-c) .id-banner { -webkit-mask-image: var(--id-feather); mask-image: var(--id-feather); } }
1529
+ @container idbanner (min-width: 1800px) { .id-card.card-l .id-banner { -webkit-mask-image: var(--id-feather); mask-image: var(--id-feather); } }
1200
1530
  .id-row { display: flex; align-items: flex-start; gap: var(--s-3); padding: var(--s-3) var(--s-4); }
1201
1531
  .id-portrait { display: block; flex-shrink: 0; height: var(--id-h); width: auto; max-width: min(calc(var(--id-h) * 0.75), 100%); object-fit: cover; border-radius: var(--r-sm); }
1202
1532
  .id-text { flex: 1; min-width: 0; }
@@ -1253,12 +1583,21 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1253
1583
 
1254
1584
  .chat-card-toggle { display: flex; align-items: center; gap: var(--s-2); width: 100%; min-height: 32px; padding: 0 var(--s-4); border: 0; background: none; color: var(--text-2); font: inherit; font-size: var(--fs-xs); text-align: left; cursor: pointer; }
1255
1585
  .chat-card-peek { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-3); }
1586
+ /* A phone: the portrait stays on the text's row, smaller (the user's ask: never a portrait alone on a line with the text
1587
+ under it); the S / M / L control takes the line below. The chat header's card is trimmed further: a smaller portrait
1588
+ and banner, and its tagline and bio cut to a few lines (Details and View show them whole). */
1256
1589
  @media (max-width: 479px) {
1257
- .id-card.card-s { --id-h: 120px; }
1258
- .id-card.card-m { --id-h: 180px; }
1259
- .id-card.card-l { --id-h: 240px; }
1260
- .id-row { flex-wrap: wrap; }
1261
- .id-text { flex-basis: 100%; }
1590
+ .id-card.card-s { --id-h: 96px; --id-bmin: 80px; --id-bmax: 120px; }
1591
+ .id-card.card-m { --id-h: 128px; --id-bmin: 100px; --id-bmax: 150px; }
1592
+ .id-card.card-l { --id-h: 168px; --id-bmin: 120px; --id-bmax: 200px; }
1593
+ .id-card.card-c { --id-h: 64px; --id-bmin: 80px; --id-bmax: 110px; }
1594
+ .id-card.card-c.bs-s { --id-bmin: 80px; --id-bmax: 100px; }
1595
+ .id-card.card-c.bs-l { --id-bmin: 96px; --id-bmax: 130px; }
1596
+ .id-row { flex-wrap: wrap; gap: var(--s-2) var(--s-3); padding: var(--s-2) var(--s-3); }
1597
+ .card-size { flex-basis: 100%; justify-content: flex-end; }
1598
+ .id-card.card-c .id-tagline, .id-card.card-c .id-bio { display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
1599
+ .id-card.card-c .id-tagline { -webkit-line-clamp: 2; line-clamp: 2; }
1600
+ .id-card.card-c .id-bio { -webkit-line-clamp: 3; line-clamp: 3; }
1262
1601
  }
1263
1602
  /* #1629 r4 §1: the card in View (above the profile row) and its part of Edit profile. */
1264
1603
  .v-id { flex-shrink: 0; padding: var(--s-2) var(--s-4) 0; background: var(--bg); }
@@ -1295,6 +1634,10 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1295
1634
  .s-fx-owner-row{display:flex;align-items:end;flex-wrap:wrap;gap:var(--s-2);margin-top:var(--s-2)}
1296
1635
  .s-fx-owner-row>label{display:flex;flex-direction:column;gap:var(--s-1);min-width:0;flex:1}
1297
1636
 
1637
+ /* #2044: the effect frame's document declares no colour scheme (light). An iframe whose own colour scheme differs
1638
+ from its document's gets an opaque backdrop, so under the dark theme the frame hid the whole chat: keep them equal. */
1639
+ .agend-effect-frame { color-scheme: normal !important; }
1640
+
1298
1641
  /* #1629: host-created static poster IMG; geometry and visibility belong to the trusted CSSOM binding. */
1299
1642
  .agend-effect-poster { object-fit: cover !important; object-position: center !important; user-select: none; }
1300
1643
 
@@ -1312,3 +1655,8 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1312
1655
  .effect-trial-head > span { flex: 1; min-width: 0; overflow-wrap: anywhere; }
1313
1656
  .effect-trial-body { flex: 1; min-height: 0; display: flex; padding: var(--s-3); }
1314
1657
  .effect-trial-stage { position: relative; z-index: 0; flex: 1; min-width: 0; min-height: 0; contain: paint; overflow: clip; padding: 0; border: 0; outline: none; box-shadow: none; }
1658
+
1659
+ /* #2115: diagnostics remain inside the stock protected panel header, never in an effect plane. */
1660
+ .panel-head:has(> .chat-fx-notice) { flex-wrap: wrap; }
1661
+ .chat-fx-notice { order: 10; flex: 1 1 100%; min-width: 0; max-width: 100%; font-size: var(--fs-sm); overflow-wrap: anywhere; }
1662
+ .chat-fx-notice > span { display: block; }
@@ -12,7 +12,7 @@ import { Shell, handleKey, showDialog, closeDrawer, openDrawer, shellStore, retr
12
12
  import { installSwipe } from "./app-swipe.js";
13
13
  import { startRouter, navStore, navigate } from "./app-nav.js";
14
14
  import { createStream } from "./app-stream.js";
15
- import { appStore, applyStatus, applyActivity } from "./app-store.js";
15
+ import { appStore, applyStatus, applyActivity, guestPage } from "./app-store.js";
16
16
  import { chatPath } from "./app-route.js";
17
17
  import { startNeedsNotifier } from "./app-needs.js";
18
18
  import { toast } from "./ui-toast.js";
@@ -29,6 +29,9 @@ initUsageRail();
29
29
  // session-only panel, nothing under /ui/ is ever imported or read (#1408 §3).
30
30
  const mode = boot.mode === "view-only" ? "view-only" : "full";
31
31
  appStore.set({ viewOnly: mode === "view-only" });
32
+ // #1920 phase 3: a guest's page has its chats and nothing else — no panel, notifier, theme list or owner CSS a guest
33
+ // would be refused.
34
+ const guest = guestPage();
32
35
 
33
36
  // A failed load can be retried (the panel's Retry): each retry asks for a fresh URL, since a browser may keep a failed
34
37
  // module import cached under the first one. boot() runs once, whichever attempt succeeds.
@@ -80,19 +83,22 @@ if (mode === "full") {
80
83
  // a boot import that failed before the Outlet first asked would be retried at once, depending on timing.)
81
84
  let chatLoads = 0;
82
85
  panels.set("chat", { load: () => (chatLoads++ === 0 ? chatBoot : loadChat()).then((m) => m.ChatPanel) });
83
- panels.set("fleet", { load: () => loadFleet().then((m) => m.FleetPanel) });
84
- panels.set("settings", { load: () => loadSettings().then((m) => m.SettingsPanel) });
85
- panels.set("needs", { load: () => loadNeeds().then((m) => m.NeedsPanel) });
86
- // #1523 N2: one instance's Details (/ui/fleet/agent/<name>).
87
- panels.set("details", { load: () => loadDetails().then((m) => m.DetailsPanel) });
88
- // #1218: one instance's Log (/ui/log/<name>).
89
- panels.set("log", { load: () => loadLog().then((m) => m.LogPanel) });
90
- // #1386 §6.3: desktop notifications for new "Needs you" items, in any panel, while this signed-in page is open.
91
- startNeedsNotifier({ open: (instance) => navigate(chatPath(instance)) });
86
+ if (!guest) {
87
+ panels.set("fleet", { load: () => loadFleet().then((m) => m.FleetPanel) });
88
+ panels.set("settings", { load: () => loadSettings().then((m) => m.SettingsPanel) });
89
+ panels.set("needs", { load: () => loadNeeds().then((m) => m.NeedsPanel) });
90
+ // #1523 N2: one instance's Details (/ui/fleet/agent/<name>).
91
+ panels.set("details", { load: () => loadDetails().then((m) => m.DetailsPanel) });
92
+ // #1218: one instance's Log (/ui/log/<name>).
93
+ panels.set("log", { load: () => loadLog().then((m) => m.LogPanel) });
94
+ // #1386 §6.3: desktop notifications for new "Needs you" items, in any panel, while this signed-in page is open.
95
+ startNeedsNotifier({ open: (instance) => navigate(chatPath(instance)) });
96
+ }
92
97
  // #1665: this device's first upload of its own choices (once), then another device's changes at each status frame.
98
+ // A guest's page only reads them (prefsWritable is false there).
93
99
  bootPrefs();
94
- // #1629: agents' themes, read when the status frame's themes_rev moves.
95
- startThemes();
100
+ // #1629: agents' themes, read when the status frame's themes_rev moves (a guest's frames never carry it).
101
+ if (!guest) startThemes();
96
102
  onNewInstance = async () => {
97
103
  closeDrawer(); // on a phone the dialog opens from the drawer: the drawer goes first
98
104
  let m;
@@ -103,8 +109,9 @@ if (mode === "full") {
103
109
  } else appStore.set({ connection: "none" });
104
110
 
105
111
  startRouter(window);
106
- // #1629 r4 §3: the person's owner CSS (full app only; never on Settings, in safe mode or where this device turned it off).
107
- installOwnerCss();
112
+ // #1629 r4 §3: the person's owner CSS (full app only; never on Settings, in safe mode or where this device turned it off;
113
+ // never a guest's — owner CSS is an admin's).
114
+ if (!guest) installOwnerCss();
108
115
  // /ui on its own opens the chat this browser had open last (an unknown name shows the panel's "not found").
109
116
  {
110
117
  const r = navStore.get().route;