@songsid/agend 2.3.0-beta.25 → 2.3.0-beta.27

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 (440) hide show
  1. package/README.md +7 -5
  2. package/README.zh-TW.md +7 -5
  3. package/dist/adapter-world.js.map +1 -1
  4. package/dist/agend-home.d.ts +5 -0
  5. package/dist/agend-home.js +7 -0
  6. package/dist/agend-home.js.map +1 -0
  7. package/dist/agent-cli.js.map +1 -1
  8. package/dist/agent-endpoint.js +8 -0
  9. package/dist/agent-endpoint.js.map +1 -1
  10. package/dist/agent-file-read.js +1 -1
  11. package/dist/agent-file-read.js.map +1 -1
  12. package/dist/agent-whats-new.js +12 -2
  13. package/dist/agent-whats-new.js.map +1 -1
  14. package/dist/apply-job.js.map +1 -1
  15. package/dist/atomic-write.js.map +1 -1
  16. package/dist/auth-api.d.ts +5 -0
  17. package/dist/auth-api.js +21 -0
  18. package/dist/auth-api.js.map +1 -1
  19. package/dist/authority/authority-state.d.ts +4 -0
  20. package/dist/authority/authority-state.js +17 -8
  21. package/dist/authority/authority-state.js.map +1 -1
  22. package/dist/authority/session-authorizations.js.map +1 -1
  23. package/dist/authority/web-authority.d.ts +14 -6
  24. package/dist/authority/web-authority.js +19 -5
  25. package/dist/authority/web-authority.js.map +1 -1
  26. package/dist/backend/antigravity.d.ts +2 -2
  27. package/dist/backend/antigravity.js +2 -1
  28. package/dist/backend/antigravity.js.map +1 -1
  29. package/dist/backend/claude-code.js.map +1 -1
  30. package/dist/backend/codex-package.js.map +1 -1
  31. package/dist/backend/codex-session-lookup.js.map +1 -1
  32. package/dist/backend/codex.js +1 -1
  33. package/dist/backend/codex.js.map +1 -1
  34. package/dist/backend/effort-metadata.js.map +1 -1
  35. package/dist/backend/grok.js.map +1 -1
  36. package/dist/backend/kiro-engine-ledger.js.map +1 -1
  37. package/dist/backend/kiro-kas-store.js.map +1 -1
  38. package/dist/backend/kiro-v2-store.js.map +1 -1
  39. package/dist/backend/kiro-v3-identity.js.map +1 -1
  40. package/dist/backend/kiro.js.map +1 -1
  41. package/dist/backend/launch-effort.js.map +1 -1
  42. package/dist/backend/mock.js.map +1 -1
  43. package/dist/backend/muse.js.map +1 -1
  44. package/dist/backend/opencode-permission.js.map +1 -1
  45. package/dist/backend/opencode.js.map +1 -1
  46. package/dist/backend/session-signals.js.map +1 -1
  47. package/dist/backend-availability.js.map +1 -1
  48. package/dist/backend-command.js.map +1 -1
  49. package/dist/backend-outage.js.map +1 -1
  50. package/dist/binary-probe.js.map +1 -1
  51. package/dist/bug-report.js.map +1 -1
  52. package/dist/bundled-skills.js.map +1 -1
  53. package/dist/cache-service.js +1 -1
  54. package/dist/cache-service.js.map +1 -1
  55. package/dist/channel/adapters/discord.d.ts +2 -0
  56. package/dist/channel/adapters/discord.js +40 -5
  57. package/dist/channel/adapters/discord.js.map +1 -1
  58. package/dist/channel/adapters/telegram.d.ts +6 -0
  59. package/dist/channel/adapters/telegram.js +54 -14
  60. package/dist/channel/adapters/telegram.js.map +1 -1
  61. package/dist/channel/mcp-server.js +10 -1
  62. package/dist/channel/mcp-server.js.map +1 -1
  63. package/dist/channel/mcp-tools.js +2 -2
  64. package/dist/channel/mcp-tools.js.map +1 -1
  65. package/dist/channel/message-queue.js.map +1 -1
  66. package/dist/channel/sticker-download.js.map +1 -1
  67. package/dist/channel/tool-router.js.map +1 -1
  68. package/dist/classic-channel-manager.js +3 -2
  69. package/dist/classic-channel-manager.js.map +1 -1
  70. package/dist/cli-entry.js.map +1 -1
  71. package/dist/cli-flags.js.map +1 -1
  72. package/dist/cli.d.ts +1 -0
  73. package/dist/cli.js +44 -9
  74. package/dist/cli.js.map +1 -1
  75. package/dist/command-table.js.map +1 -1
  76. package/dist/compact-watch.js.map +1 -1
  77. package/dist/completion-install.js.map +1 -1
  78. package/dist/completion.js.map +1 -1
  79. package/dist/config-validator.js +22 -6
  80. package/dist/config-validator.js.map +1 -1
  81. package/dist/config.js +8 -2
  82. package/dist/config.js.map +1 -1
  83. package/dist/connection-secrets.js.map +1 -1
  84. package/dist/context-guardian.js.map +1 -1
  85. package/dist/coredump-filter.js.map +1 -1
  86. package/dist/cpu-profile.js.map +1 -1
  87. package/dist/cross-instance-envelope.js.map +1 -1
  88. package/dist/daemon.d.ts +3 -0
  89. package/dist/daemon.js +76 -4
  90. package/dist/daemon.js.map +1 -1
  91. package/dist/daily-summary.js.map +1 -1
  92. package/dist/darwin-memory.js.map +1 -1
  93. package/dist/dashboard-live.d.ts +8 -0
  94. package/dist/dashboard-live.js +35 -0
  95. package/dist/dashboard-live.js.map +1 -0
  96. package/dist/dashboard-permissions.d.ts +69 -0
  97. package/dist/dashboard-permissions.js +138 -0
  98. package/dist/dashboard-permissions.js.map +1 -0
  99. package/dist/decision-list.js.map +1 -1
  100. package/dist/delivery-consumed-watch.js.map +1 -1
  101. package/dist/delivery-outbox.js.map +1 -1
  102. package/dist/delivery-outbox.test.js.map +1 -1
  103. package/dist/delivery-reconciliation.test.js.map +1 -1
  104. package/dist/diagnostic-redaction.js.map +1 -1
  105. package/dist/doctor.d.ts +3 -0
  106. package/dist/doctor.js +10 -0
  107. package/dist/doctor.js.map +1 -1
  108. package/dist/e2e-isolation.js.map +1 -1
  109. package/dist/event-log.js.map +1 -1
  110. package/dist/export-import.js +3 -2
  111. package/dist/export-import.js.map +1 -1
  112. package/dist/fleet-control-audit.js.map +1 -1
  113. package/dist/fleet-label.js.map +1 -1
  114. package/dist/fleet-lock.js +3 -2
  115. package/dist/fleet-lock.js.map +1 -1
  116. package/dist/fleet-manager.d.ts +84 -7
  117. package/dist/fleet-manager.js +562 -158
  118. package/dist/fleet-manager.js.map +1 -1
  119. package/dist/fleet-process-handlers.js +3 -2
  120. package/dist/fleet-process-handlers.js.map +1 -1
  121. package/dist/fleet-startup.d.ts +14 -0
  122. package/dist/fleet-startup.js +30 -0
  123. package/dist/fleet-startup.js.map +1 -0
  124. package/dist/fleet-traffic.js.map +1 -1
  125. package/dist/forged-envelope-scan.d.ts +0 -20
  126. package/dist/forged-envelope-scan.js +3 -2
  127. package/dist/forged-envelope-scan.js.map +1 -1
  128. package/dist/full-restart.js.map +1 -1
  129. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +2 -0
  130. package/dist/general-knowledge/skills/web-appearance/SKILL.md +2 -0
  131. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +43 -21
  132. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +52 -23
  133. package/dist/host-memory.js.map +1 -1
  134. package/dist/idle-gate-log.d.ts +19 -0
  135. package/dist/idle-gate-log.js +15 -0
  136. package/dist/idle-gate-log.js.map +1 -0
  137. package/dist/instance-config-impact.js +6 -0
  138. package/dist/instance-config-impact.js.map +1 -1
  139. package/dist/instance-lifecycle.js +1 -1
  140. package/dist/instance-lifecycle.js.map +1 -1
  141. package/dist/instructions.d.ts +2 -0
  142. package/dist/instructions.js +6 -2
  143. package/dist/instructions.js.map +1 -1
  144. package/dist/kiro-compact-pane.js.map +1 -1
  145. package/dist/kiro-db-reader.js.map +1 -1
  146. package/dist/kiro-engine-status.js.map +1 -1
  147. package/dist/kiro-log-gate.js.map +1 -1
  148. package/dist/kiro-transcript-lane.js.map +1 -1
  149. package/dist/locale.js +40 -6
  150. package/dist/locale.js.map +1 -1
  151. package/dist/log-service.js.map +1 -1
  152. package/dist/log-steps.js.map +1 -1
  153. package/dist/logger.js +3 -2
  154. package/dist/logger.js.map +1 -1
  155. package/dist/login-controller.js.map +1 -1
  156. package/dist/login-manager.js.map +1 -1
  157. package/dist/login-shell.js.map +1 -1
  158. package/dist/memory-pressure.js.map +1 -1
  159. package/dist/muse-usage-relay.js.map +1 -1
  160. package/dist/needs-you-hub.js.map +1 -1
  161. package/dist/needs-you-live.js.map +1 -1
  162. package/dist/network-family.js.map +1 -1
  163. package/dist/operator-control-paths.js +3 -4
  164. package/dist/operator-control-paths.js.map +1 -1
  165. package/dist/outbound-handlers.js.map +1 -1
  166. package/dist/outbound-schemas.d.ts +68 -68
  167. package/dist/outbound-schemas.js +1 -1
  168. package/dist/outbound-schemas.js.map +1 -1
  169. package/dist/outbox-open-error.js.map +1 -1
  170. package/dist/pane-context-cache.js.map +1 -1
  171. package/dist/paths.js.map +1 -1
  172. package/dist/private-dir.js.map +1 -1
  173. package/dist/probe-worker-pool.js.map +1 -1
  174. package/dist/profile-control.js.map +1 -1
  175. package/dist/prompt-file-ref.js.map +1 -1
  176. package/dist/provider-probe.js.map +1 -1
  177. package/dist/provider-secret-registry.js.map +1 -1
  178. package/dist/public-link-auto-open.d.ts +32 -0
  179. package/dist/public-link-auto-open.js +86 -0
  180. package/dist/public-link-auto-open.js.map +1 -0
  181. package/dist/public-link-owner.d.ts +10 -0
  182. package/dist/public-link-owner.js +36 -0
  183. package/dist/public-link-owner.js.map +1 -0
  184. package/dist/public-link-progress.js.map +1 -1
  185. package/dist/public-preview.js.map +1 -1
  186. package/dist/public-web-gateway.js +7 -2
  187. package/dist/public-web-gateway.js.map +1 -1
  188. package/dist/public-web-link.d.ts +35 -1
  189. package/dist/public-web-link.js +142 -43
  190. package/dist/public-web-link.js.map +1 -1
  191. package/dist/quickstart-api.js.map +1 -1
  192. package/dist/quickstart.d.ts +5 -0
  193. package/dist/quickstart.js +25 -16
  194. package/dist/quickstart.js.map +1 -1
  195. package/dist/reply-buttons-controller.js.map +1 -1
  196. package/dist/reply-dedup.js.map +1 -1
  197. package/dist/resource-report.js.map +1 -1
  198. package/dist/restart-guard.js.map +1 -1
  199. package/dist/restart-progress.js.map +1 -1
  200. package/dist/rollout-index.js.map +1 -1
  201. package/dist/runtime-cpu-profile.js.map +1 -1
  202. package/dist/scheduler/db.js.map +1 -1
  203. package/dist/scheduler/scheduler.js.map +1 -1
  204. package/dist/scheduler/task-board-text.js +16 -11
  205. package/dist/scheduler/task-board-text.js.map +1 -1
  206. package/dist/scheduler/task-board.d.ts +39 -11
  207. package/dist/scheduler/task-board.js +123 -45
  208. package/dist/scheduler/task-board.js.map +1 -1
  209. package/dist/scheduler/types.d.ts +1 -1
  210. package/dist/sd-notify.js.map +1 -1
  211. package/dist/search/search-index.js.map +1 -1
  212. package/dist/secret-file.js.map +1 -1
  213. package/dist/secret-store.js.map +1 -1
  214. package/dist/service-installer.d.ts +7 -1
  215. package/dist/service-installer.js +13 -1
  216. package/dist/service-installer.js.map +1 -1
  217. package/dist/service-plan.d.ts +33 -4
  218. package/dist/service-plan.js +177 -8
  219. package/dist/service-plan.js.map +1 -1
  220. package/dist/session-oom.d.ts +21 -0
  221. package/dist/session-oom.js +139 -0
  222. package/dist/session-oom.js.map +1 -0
  223. package/dist/settings-api.js +3 -1
  224. package/dist/settings-api.js.map +1 -1
  225. package/dist/settings-baseline.js.map +1 -1
  226. package/dist/settings-change.d.ts +5 -0
  227. package/dist/settings-change.js +71 -1
  228. package/dist/settings-change.js.map +1 -1
  229. package/dist/settings-confirmation.d.ts +4 -0
  230. package/dist/settings-confirmation.js +2 -0
  231. package/dist/settings-confirmation.js.map +1 -1
  232. package/dist/settings-control.js.map +1 -1
  233. package/dist/settings-effect.js +4 -1
  234. package/dist/settings-effect.js.map +1 -1
  235. package/dist/settings-http-confirmation.js +1 -0
  236. package/dist/settings-http-confirmation.js.map +1 -1
  237. package/dist/settings-instance-patch.js +13 -1
  238. package/dist/settings-instance-patch.js.map +1 -1
  239. package/dist/settings-transaction.js.map +1 -1
  240. package/dist/setup-auth.js.map +1 -1
  241. package/dist/setup-host.d.ts +5 -1
  242. package/dist/setup-host.js +8 -1
  243. package/dist/setup-host.js.map +1 -1
  244. package/dist/setup-wizard.js +5 -2
  245. package/dist/setup-wizard.js.map +1 -1
  246. package/dist/spawn-gate.d.ts +4 -0
  247. package/dist/spawn-gate.js +15 -5
  248. package/dist/spawn-gate.js.map +1 -1
  249. package/dist/start-attempt.d.ts +1 -0
  250. package/dist/start-attempt.js +18 -0
  251. package/dist/start-attempt.js.map +1 -0
  252. package/dist/start-failure-notice.d.ts +4 -0
  253. package/dist/start-failure-notice.js +20 -0
  254. package/dist/start-failure-notice.js.map +1 -0
  255. package/dist/start-failure.d.ts +147 -0
  256. package/dist/start-failure.js +281 -0
  257. package/dist/start-failure.js.map +1 -0
  258. package/dist/startup-memory-deadline.d.ts +23 -0
  259. package/dist/startup-memory-deadline.js +55 -0
  260. package/dist/startup-memory-deadline.js.map +1 -0
  261. package/dist/statusline-watcher.js.map +1 -1
  262. package/dist/storm-window.js.map +1 -1
  263. package/dist/systemd-peer-bus.js.map +1 -1
  264. package/dist/target-queue-worker.js.map +1 -1
  265. package/dist/task-board-summary.js.map +1 -1
  266. package/dist/task-board-tool.js +3 -2
  267. package/dist/task-board-tool.js.map +1 -1
  268. package/dist/task-board-waker.js +8 -4
  269. package/dist/task-board-waker.js.map +1 -1
  270. package/dist/tips.js.map +1 -1
  271. package/dist/tmux-control.js.map +1 -1
  272. package/dist/tmux-exec-helper.js.map +1 -1
  273. package/dist/tmux-manager.d.ts +1 -0
  274. package/dist/tmux-manager.js +2 -1
  275. package/dist/tmux-manager.js.map +1 -1
  276. package/dist/tmux-read.js +1 -1
  277. package/dist/tmux-read.js.map +1 -1
  278. package/dist/tool-permissions.js.map +1 -1
  279. package/dist/tool-progress.js.map +1 -1
  280. package/dist/topic-archiver.js.map +1 -1
  281. package/dist/topic-commands.js.map +1 -1
  282. package/dist/topic-link.js.map +1 -1
  283. package/dist/transcript-delta-reader.js.map +1 -1
  284. package/dist/transcript-jsonl.js.map +1 -1
  285. package/dist/transcript-monitor.js.map +1 -1
  286. package/dist/transcript-sources.js.map +1 -1
  287. package/dist/tunnel/cloudflared-install.js.map +1 -1
  288. package/dist/tunnel/cloudflared.js.map +1 -1
  289. package/dist/tunnel/lease.js +3 -2
  290. package/dist/tunnel/lease.js.map +1 -1
  291. package/dist/tunnel/manager.js.map +1 -1
  292. package/dist/tunnel/public-fetch.js.map +1 -1
  293. package/dist/tunnel/purpose-lane.js.map +1 -1
  294. package/dist/tunnel/types.js.map +1 -1
  295. package/dist/types.d.ts +22 -1
  296. package/dist/ui/chat-render.js +7 -17
  297. package/dist/ui/chat-store.js +171 -61
  298. package/dist/ui/chat-strings.js +9 -4
  299. package/dist/ui/chat-thread.js +48 -1
  300. package/dist/ui/fleet-canvas-layout.js +3 -0
  301. package/dist/ui/fleet-canvas.js +23 -20
  302. package/dist/ui/fleet-office-editor.js +149 -0
  303. package/dist/ui/fleet-office-model.js +358 -0
  304. package/dist/ui/fleet-office-pack.js +199 -0
  305. package/dist/ui/fleet-office-skin.js +474 -0
  306. package/dist/ui/fleet-office-store.js +49 -0
  307. package/dist/ui/fleet-office-zones.js +177 -0
  308. package/dist/ui/fleet-office.js +434 -0
  309. package/dist/ui/fleet-org.js +18 -5
  310. package/dist/ui/guest-refused.html +41 -0
  311. package/dist/ui/image-lightbox.js +9 -3
  312. package/dist/ui/panel-chat.js +35 -22
  313. package/dist/ui/panel-settings.js +24 -7
  314. package/dist/ui/settings-dialogs.js +11 -2
  315. package/dist/ui/settings-model.js +20 -0
  316. package/dist/ui/settings-strings.js +36 -0
  317. package/dist/ui/settings-web-authority.js +7 -2
  318. package/dist/ui/shared/agend-auth.js +2 -0
  319. package/dist/ui/shared/app-i18n.js +2 -2
  320. package/dist/ui/shared/app-prefs.js +2 -0
  321. package/dist/ui/shared/app-shell.js +62 -9
  322. package/dist/ui/shared/app-store.js +1 -0
  323. package/dist/ui/shared/app-stream.js +2 -1
  324. package/dist/ui/shared/app.css +164 -15
  325. package/dist/ui/shared/guest-refused.js +35 -0
  326. package/dist/ui/shared/header-tools.js +2 -2
  327. package/dist/ui/shared/instance-nav.js +25 -8
  328. package/dist/ui/shared/tokens.css +4 -0
  329. package/dist/ui/shared/ui-dialog.js +2 -2
  330. package/dist/ui/shared/usage-dialog.js +152 -6
  331. package/dist/ui/shared/view-strings.js +20 -0
  332. package/dist/ui/shared/web-authorize.js +3 -1
  333. package/dist/update-check.js.map +1 -1
  334. package/dist/update-dispatch.js.map +1 -1
  335. package/dist/update-install.js.map +1 -1
  336. package/dist/update-launch.js.map +1 -1
  337. package/dist/update-marker.js.map +1 -1
  338. package/dist/update-preflight.js.map +1 -1
  339. package/dist/update-progress.d.ts +3 -1
  340. package/dist/update-progress.js +3 -1
  341. package/dist/update-progress.js.map +1 -1
  342. package/dist/usage/format-rich.js.map +1 -1
  343. package/dist/usage/providers.d.ts +8 -0
  344. package/dist/usage/providers.js +6 -2
  345. package/dist/usage/providers.js.map +1 -1
  346. package/dist/usage/statusline-usage.js.map +1 -1
  347. package/dist/usage/usage-api.js +2 -1
  348. package/dist/usage/usage-api.js.map +1 -1
  349. package/dist/usage-report.js.map +1 -1
  350. package/dist/usage-rollup.js +1 -1
  351. package/dist/usage-rollup.js.map +1 -1
  352. package/dist/user-systemd.js.map +1 -1
  353. package/dist/view-api.js +1 -1
  354. package/dist/view-api.js.map +1 -1
  355. package/dist/wake-coordinator.js.map +1 -1
  356. package/dist/web-ambient.js.map +1 -1
  357. package/dist/web-api.d.ts +19 -2
  358. package/dist/web-api.js +180 -28
  359. package/dist/web-api.js.map +1 -1
  360. package/dist/web-app-icon.js.map +1 -1
  361. package/dist/web-appearance-policy.js.map +1 -1
  362. package/dist/web-appearance-tools.js.map +1 -1
  363. package/dist/web-appearance-view.js.map +1 -1
  364. package/dist/web-appearance.js.map +1 -1
  365. package/dist/web-auth.js +5 -4
  366. package/dist/web-auth.js.map +1 -1
  367. package/dist/web-backend-login.js.map +1 -1
  368. package/dist/web-card.js.map +1 -1
  369. package/dist/web-channel-echo.js.map +1 -1
  370. package/dist/web-chat-history.d.ts +41 -11
  371. package/dist/web-chat-history.js +93 -22
  372. package/dist/web-chat-history.js.map +1 -1
  373. package/dist/web-commands.js.map +1 -1
  374. package/dist/web-effect-bundle.d.ts +2 -2
  375. package/dist/web-effect-library.js.map +1 -1
  376. package/dist/web-effect-playback.js.map +1 -1
  377. package/dist/web-effect-profiles.d.ts +16 -16
  378. package/dist/web-effect-profiles.js +3 -2
  379. package/dist/web-effect-profiles.js.map +1 -1
  380. package/dist/web-effect-service.js.map +1 -1
  381. package/dist/web-effect-tools.js.map +1 -1
  382. package/dist/web-effect-trials.js.map +1 -1
  383. package/dist/web-effect-upload.js.map +1 -1
  384. package/dist/web-effect.d.ts +7 -7
  385. package/dist/web-effect.js.map +1 -1
  386. package/dist/web-experimental.js.map +1 -1
  387. package/dist/web-guest-access.js +1 -1
  388. package/dist/web-guest-access.js.map +1 -1
  389. package/dist/web-guest-rooms.js.map +1 -1
  390. package/dist/web-host-guard.js.map +1 -1
  391. package/dist/web-icons.js.map +1 -1
  392. package/dist/web-identity-color.js.map +1 -1
  393. package/dist/web-images.js.map +1 -1
  394. package/dist/web-libavoid.js.map +1 -1
  395. package/dist/web-login.js.map +1 -1
  396. package/dist/web-mentions.js.map +1 -1
  397. package/dist/web-mod-agent.js.map +1 -1
  398. package/dist/web-mod-store.js.map +1 -1
  399. package/dist/web-office-store.d.ts +104 -0
  400. package/dist/web-office-store.js +236 -0
  401. package/dist/web-office-store.js.map +1 -0
  402. package/dist/web-owner-css-store.js.map +1 -1
  403. package/dist/web-owner-css.js.map +1 -1
  404. package/dist/web-png.js.map +1 -1
  405. package/dist/web-prefs.js +2 -0
  406. package/dist/web-prefs.js.map +1 -1
  407. package/dist/web-profile.js.map +1 -1
  408. package/dist/web-reactions.d.ts +2 -1
  409. package/dist/web-request-context.d.ts +5 -0
  410. package/dist/web-request-context.js.map +1 -1
  411. package/dist/web-returning-device.js +1 -1
  412. package/dist/web-returning-device.js.map +1 -1
  413. package/dist/web-schedules.js.map +1 -1
  414. package/dist/web-send-dedupe.d.ts +36 -0
  415. package/dist/web-send-dedupe.js +69 -0
  416. package/dist/web-send-dedupe.js.map +1 -0
  417. package/dist/web-session-authorization.js.map +1 -1
  418. package/dist/web-session.js +1 -1
  419. package/dist/web-session.js.map +1 -1
  420. package/dist/web-settings-patch.d.ts +1 -0
  421. package/dist/web-settings-patch.js +6 -4
  422. package/dist/web-settings-patch.js.map +1 -1
  423. package/dist/web-settings-tools.js.map +1 -1
  424. package/dist/web-terminal-http.js.map +1 -1
  425. package/dist/web-terminal.js.map +1 -1
  426. package/dist/web-theme-store.js.map +1 -1
  427. package/dist/web-upload.d.ts +5 -1
  428. package/dist/web-upload.js +6 -4
  429. package/dist/web-upload.js.map +1 -1
  430. package/dist/web-widget.js.map +1 -1
  431. package/dist/webhook-emitter.js.map +1 -1
  432. package/dist/windows-shortcuts.js +1 -1
  433. package/dist/windows-shortcuts.js.map +1 -1
  434. package/dist/workflow-templates/default.md +1 -1
  435. package/dist/ws-server.js.map +1 -1
  436. package/dist/yaml-shared-nodes.js.map +1 -1
  437. package/launcher/agend-install.cjs +281 -6
  438. package/launcher/private-home.cjs +43 -0
  439. package/package.json +3 -2
  440. package/templates/windows-shortcuts.ps1 +1 -1
@@ -69,7 +69,7 @@ register("app", {
69
69
  needsYou: "needs you", approxNote: "Read from the terminal, so approximate", chat: "Chat", details: "Details", log: "Log", instanceViews: "Views of {0}",
70
70
  textSize: "Text size: {0}", textSize_s: "S", textSize_m: "M", textSize_l: "L", textSize_fit: "Fit", usage: "Usage", usageHide: "Hide usage",
71
71
  ownerCssKeep: "Keep this custom CSS?", ownerCssKeepLeft: "Reverts in {0} s", ownerCssKeepYes: "Keep", ownerCssKeepNo: "Revert",
72
- statusRunning: "running", statusStopped: "stopped", statusPaused: "paused", statusCrashed: "crashed", statusWorking: "working", statusStuck: "looks stuck", statusIdle: "idle",
72
+ statusQueued: "queued", statusStarting: "starting", fleetStarting: "Fleet starting {0}/{1}…", statusRunning: "running", statusStopped: "stopped", statusPaused: "paused", statusCrashed: "crashed", statusWorking: "working", statusStuck: "looks stuck", statusIdle: "idle",
73
73
  connReconnecting: "Can't reach AgEnD — it may be restarting. Reconnecting…", connReconnectingIn: "Can't reach AgEnD — it may be restarting. Trying again in {0} s.",
74
74
  hydrating: "Loading what is open now…", hydrateFailed: "Could not load what is open now (prompts, ticks).",
75
75
  skip: "Skip to the conversation", loadFailed: "This could not be loaded.", retry: "Try again", loading: "Loading…",
@@ -106,7 +106,7 @@ register("app", {
106
106
  needsYou: "等你回應", approxNote: "從終端畫面判讀,僅供參考", chat: "聊天", details: "詳細資訊", log: "紀錄", instanceViews: "{0} 的檢視",
107
107
  textSize: "字級:{0}", textSize_s: "小", textSize_m: "中", textSize_l: "大", textSize_fit: "填滿", usage: "用量", usageHide: "隱藏用量",
108
108
  ownerCssKeep: "保留這份自訂 CSS?", ownerCssKeepLeft: "{0} 秒後還原", ownerCssKeepYes: "保留", ownerCssKeepNo: "還原",
109
- statusRunning: "執行中", statusStopped: "已停止", statusPaused: "已暫停", statusCrashed: "已當掉", statusWorking: "工作中", statusStuck: "似乎卡住", statusIdle: "閒置",
109
+ statusQueued: "等待啟動", statusStarting: "啟動中", fleetStarting: "Fleet 啟動中 {0}/{1}…", statusRunning: "執行中", statusStopped: "已停止", statusPaused: "已暫停", statusCrashed: "已當掉", statusWorking: "工作中", statusStuck: "似乎卡住", statusIdle: "閒置",
110
110
  connReconnecting: "連不上 AgEnD——可能正在重新啟動。正在重新連線…", connReconnectingIn: "連不上 AgEnD——可能正在重新啟動。{0} 秒後再試。",
111
111
  hydrating: "正在載入目前的狀態…", hydrateFailed: "無法載入目前的狀態(提示、送達標記)。",
112
112
  skip: "跳到對話", loadFailed: "無法載入。", retry: "再試一次", loading: "載入中…",
@@ -45,6 +45,8 @@ export const SYNCED = {
45
45
  "color.show": { ls: "agend_color_show", ...json },
46
46
  "theme.pageByInstance": { ls: "agend_theme_page", ...json }, // #2196
47
47
  "color.off": { ls: "agend_color_off", toLocal: (v) => (v === true ? "on" : null), fromLocal: (s) => (s === "on" ? true : undefined) },
48
+ // The AI usage view's compact mode: one line per login (usage-dialog.js), in the dialog and the rail alike.
49
+ "usage.compact": { ls: "agend_usage_compact", toLocal: (v) => (v === true ? "on" : null), fromLocal: (s) => (s === "on" ? true : undefined) },
48
50
  };
49
51
  // #1629: fixed personal scope. A separate session-bound owner-effect store synchronizes these, never this host cache.
50
52
  export const OWNER_EFFECT_PREF_KEYS = Object.freeze(["fx.sidebar", "fx.header", "fx.page", "fx.box", "fx.cursor"]);
@@ -70,12 +70,14 @@ export const isMacLike = () => typeof navigator !== "undefined" && /Mac|iPhone|i
70
70
  /** What the row and tooltips show: ⌘K on a Mac, Ctrl K elsewhere. */
71
71
  export const searchShortcut = () => (isMacLike() ? "⌘K" : "Ctrl K");
72
72
  /**
73
- * Whether this page offers search: an admin's page reached directly. A guest's never, and never over the public link —
74
- * the server refuses it there (#2298 review), so no entry, no shortcut, and Ctrl+F stays the browser's.
73
+ * Whether this page offers search: an admin's page. A guest's never. #2478 (the user's request): over the public link
74
+ * too, unless the fleet keeps it off there (web.search.public_link: false — the page says data-search-link="0", the
75
+ * server refuses it), so then no entry, no shortcut, and Ctrl+F stays the browser's.
75
76
  */
76
77
  export function searchOffered(admin) {
77
- const onLink = typeof document !== "undefined" && !!document.body && document.body.dataset.publicLink === "1";
78
- return !!admin && !guestPage() && !onLink;
78
+ const body = typeof document !== "undefined" ? document.body : null;
79
+ const offLink = !!body && body.dataset.publicLink === "1" && body.dataset.searchLink === "0";
80
+ return !!admin && !guestPage() && !offLink;
79
81
  }
80
82
  let searchModule = null;
81
83
  /** Open search; `preset.instance`: narrowed to that agent (Ctrl+F in its chat). */
@@ -284,6 +286,8 @@ appStore.subscribe((s) => {
284
286
 
285
287
  function statusLabel(i, exec, awaiting) {
286
288
  if (awaiting != null) return t("app.needsYou");
289
+ if (i.startup_phase === "queued") return t("app.statusQueued");
290
+ if (i.startup_phase === "starting") return t("app.statusStarting");
287
291
  if (i.status !== "running") return i.status === "crashed" ? t("app.statusCrashed") : t("app.statusStopped");
288
292
  if (exec === "working") return t("app.statusWorking");
289
293
  if (exec === "stuck") return t("app.statusStuck");
@@ -291,6 +295,8 @@ function statusLabel(i, exec, awaiting) {
291
295
  }
292
296
  export function statusClass(i, exec, awaiting) {
293
297
  if (awaiting != null) return "warn";
298
+ if (i?.startup_phase === "starting") return "busy";
299
+ if (i?.startup_phase === "queued") return "off";
294
300
  if (!i || i.status !== "running") return i && i.status === "crashed" ? "bad" : "off";
295
301
  return exec === "working" ? "busy" : exec === "stuck" ? "warn" : "ok";
296
302
  }
@@ -500,6 +506,13 @@ function ConnectionLine() {
500
506
  // (agend-auth's banner says that) draw nothing either; only reconnecting and a failed catch-up are shown.
501
507
  return null;
502
508
  }
509
+
510
+ /** A connected fleet is still bringing up agents; this is distinct from the reconnect banner. */
511
+ export function StartupLine() {
512
+ const { startup } = useStore(appStore);
513
+ if (!startup) return null;
514
+ return html`<div data-trust="status" class="conn fleet-starting" role="status">${t("app.fleetStarting", startup.ready, startup.total)}</div>`;
515
+ }
503
516
  /** #1580: the fleet is not answering (a restart): said, with when the next try is — never a silent stale page. */
504
517
  function Reconnecting() {
505
518
  const { retryAt } = useStore(appStore);
@@ -633,6 +646,20 @@ function usePageTheme(ref, route) {
633
646
  }, [agent]);
634
647
  }
635
648
 
649
+ /** #2478: how far short of its tallest the view must be, with a text field focused, to be a keyboard (not a browser bar). */
650
+ export const KEYBOARD_MIN_PX = 150;
651
+ /** #2478: the on-screen keyboard is up — something is being typed into, and the view is well short of its tallest. */
652
+ export function keyboardUp(height, tallest, typing) { return !!typing && tallest - height > KEYBOARD_MIN_PX; }
653
+ const NOT_TYPED = /^(button|checkbox|radio|range|color|file|submit|reset|image|hidden)$/i;
654
+ /** #2478: an element the keyboard types into — a textarea, a text-like input, or an editable region. */
655
+ export function isTextEntry(el) {
656
+ if (!el || !el.tagName) return false;
657
+ const tag = String(el.tagName).toUpperCase();
658
+ if (tag === "TEXTAREA") return !el.readOnly && !el.disabled;
659
+ if (tag === "INPUT") return !NOT_TYPED.test(el.type || "text") && !el.readOnly && !el.disabled;
660
+ return el.isContentEditable === true;
661
+ }
662
+
636
663
  export function Shell({ panels, onNewInstance, viewOnly = false }) {
637
664
  const nav = useStore(navStore);
638
665
  const shell = useStore(shellStore);
@@ -642,13 +669,38 @@ export function Shell({ panels, onNewInstance, viewOnly = false }) {
642
669
  // A language switch re-renders everything and gives the panel a new lease (its key includes the language).
643
670
  useEffect(() => onLang(() => relang(n => n + 1)), []);
644
671
  useEffect(() => { closeDrawer(); }, [nav.seq]);
645
- // The bottom tabs give their room to the keyboard on a phone.
672
+ // The bottom tabs (and the line under the composer) give their room to the keyboard on a phone (#2478).
646
673
  useEffect(() => {
647
674
  const vv = typeof window !== "undefined" ? window.visualViewport : null;
648
- if (!vv) return undefined;
649
- const onResize = () => shellStore.set({ keyboard: window.innerHeight - vv.height > 150 });
650
- vv.addEventListener("resize", onResize);
651
- return () => vv.removeEventListener("resize", onResize);
675
+ if (!vv || typeof document === "undefined") return undefined;
676
+ // The tallest the view has been at this width: what it is with no keyboard. innerHeight is no measure on its own —
677
+ // it shrinks with the keyboard on iOS Safari and, under interactive-widget=resizes-content, on Android — so
678
+ // innerHeight − height stayed near 0 and the strips stayed between the composer and the keyboard.
679
+ // Only a touch screen (a coarse pointer) has an on-screen keyboard: a narrow desk window made shorter is not one.
680
+ // Turned with the keyboard up and the field still focused, the new width has no height without it yet: the keyboard
681
+ // stays up until the field is left, or the view grows from its lowest by more than a keyboard (put away).
682
+ let width = -1, tallest = 0, held = false, lowest = 0;
683
+ const coarse = () => typeof matchMedia === "function" && matchMedia("(pointer: coarse)").matches;
684
+ const check = () => {
685
+ const w = Math.round(vv.width), h = vv.height;
686
+ const typing = coarse() && isTextEntry(document.activeElement);
687
+ if (w !== width) { // turned, or a new width: measured again
688
+ held = width !== -1 && typing && shellStore.get().keyboard;
689
+ width = w; tallest = 0; lowest = h;
690
+ }
691
+ if (held) {
692
+ lowest = Math.min(lowest, h);
693
+ if (!typing || h - lowest > KEYBOARD_MIN_PX) held = false; // left the field, or the keyboard was put away
694
+ }
695
+ if (!held) tallest = Math.max(tallest, window.innerHeight || 0, typing ? 0 : h); // the view itself only when not typing
696
+ const up = held || keyboardUp(h, tallest, typing);
697
+ if (up !== shellStore.get().keyboard) shellStore.set({ keyboard: up });
698
+ };
699
+ check();
700
+ vv.addEventListener("resize", check);
701
+ document.addEventListener("focusin", check);
702
+ document.addEventListener("focusout", check);
703
+ return () => { vv.removeEventListener("resize", check); document.removeEventListener("focusin", check); document.removeEventListener("focusout", check); };
652
704
  }, []);
653
705
  const guest = guestPage(); // #1920: a guest's page — no operations, usage, pending changes or owner CSS
654
706
  const cls = ["shell", shell.collapsed ? "sb-collapsed" : "", shell.drawer ? "sb-open" : "", shell.keyboard ? "kb-open" : "", guest ? "guest" : ""].filter(Boolean).join(" ");
@@ -658,6 +710,7 @@ export function Shell({ panels, onNewInstance, viewOnly = false }) {
658
710
  <div class="scrim" onClick=${closeDrawer} aria-hidden="true"></div>
659
711
  <main id="main" class="main" tabindex="-1" inert=${shell.drawer && narrow() ? true : undefined}>
660
712
  <${ConnectionLine} />
713
+ <${StartupLine} />
661
714
  ${viewOnly || guest ? null : html`<${OperationLine} route=${nav.route} />`}
662
715
  ${viewOnly || guest || !shell.banner ? null : html`<${shell.banner} />`}
663
716
  <${Outlet} route=${nav.route} seq=${nav.seq} panels=${panels} />
@@ -50,6 +50,7 @@ export function applyStatus(d) {
50
50
  if (i.state === "awaiting_input") awaiting[i.name] = typeof i.interaction_summary === "string" ? i.interaction_summary : "";
51
51
  }
52
52
  appStore.set({ canvasWorker: d.canvas_worker === true, ready: true, instances: d.instances, uptime: d.uptime ?? 0, exec, awaiting,
53
+ startup: d.startup?.phase === "starting" ? d.startup : null,
53
54
  connections: typeof d.connections === "number" ? d.connections : null,
54
55
  // #1665: the host preferences' revision (app-prefs.js re-reads them when it moves).
55
56
  prefsRev: typeof d.prefs_rev === "number" ? d.prefs_rev : null,
@@ -34,7 +34,8 @@
34
34
  // org canvas reads /ui/traffic when it has no stream).
35
35
  // #2233: backend_login — a dashboard sign-in started, moved or ended (no details: the page reads its own view).
36
36
  // tasks — the task board changed: the card numbers a write touched (SSE only: the board reads again when it is shown).
37
- const EVENTS = ["status", "message", "agent_reply_live", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "reactions", "steps_tick", "clock", "traffic", "delegation", "backend_login", "tasks"];
37
+ // offices — the stored offices changed ({ rev }; SSE only: the Office view reads /ui/offices again).
38
+ const EVENTS = ["status", "message", "agent_reply_live", "delivery", "deliveries", "prompt", "prompts", "prompt_resolved", "activity", "needs", "reply_buttons", "reactions", "steps_tick", "clock", "traffic", "delegation", "backend_login", "tasks", "offices"];
38
39
  const MAX_HELD = 1000; // live events held during one catch-up read; more → read again afterwards
39
40
  const RETRY_DELAYS = [1000, 2000, 4000]; // after the first failed catch-up read; then it waits for Retry
40
41
  const POLL_LIMIT_MS = 10000; // a poll that has not answered by then counts as failed
@@ -130,9 +130,15 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
130
130
  A short box that clips to its corners (.msg-quote) moves the bar in (--lbar-left) and shortens it (--lbar-inset) so
131
131
  the corner never cuts its ends round.
132
132
  tests/web-left-bar-2330.test.ts keeps a coloured border-left off rounded boxes. */
133
- .lbar, .reply-bar, .msg-quote, .p-log .log-item.k-error, .p-log .log-item.s-failed, .org-node, .us-basis, .us-partial, .cmd-card.bad, .tb-tile { position: relative; }
133
+ .lbar, .reply-bar, .msg-quote, .p-log .log-item.k-error, .p-log .log-item.s-failed, .org-node, .us-basis, .us-partial, .cmd-card.bad, .tb-tile,
134
+ .search-row.on, .search-recent-item:hover, .search-recent-item:focus-visible, .search-ctx-line.here, .tw-line:hover, .tw-line:focus-visible,
135
+ .u-crow:focus-visible { position: relative; }
134
136
  .lbar::before, .reply-bar::before, .msg-quote::before, .p-log .log-item.k-error::before, .p-log .log-item.s-failed::before, .org-node::before,
135
- .us-basis::before, .us-partial::before, .cmd-card.bad::before, .tb-tile::before {
137
+ .us-basis::before, .us-partial::before, .cmd-card.bad::before, .tb-tile::before,
138
+ /* #2478 item 7 (the user's fixed template): a selected, hovered or keyboard-focused row is the straight bar and a flat
139
+ background at most — never a rounded fill, a curved inset edge or a rounded ring. */
140
+ .search-row.on::before, .search-recent-item:hover::before, .search-recent-item:focus-visible::before, .search-ctx-line.here::before,
141
+ .tw-line:hover::before, .tw-line:focus-visible::before, .u-crow:focus-visible::before {
136
142
  content: ""; position: absolute; left: var(--lbar-left, 0); top: var(--lbar-inset, var(--s-2)); bottom: var(--lbar-inset, var(--s-2)); width: 3px;
137
143
  background: var(--lbar-color, var(--accent)); pointer-events: none; }
138
144
  .idc-swatches, .idc-places { border: 0; padding: 0; margin: 0 0 var(--s-3); display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
@@ -470,6 +476,36 @@ html:has(.shell), html:has(.shell) body { overflow: hidden; }
470
476
  such as 「昨天 下午03:58」, ticks) — and a block body would stretch to it; fit-content keeps a short message short, on
471
477
  the right under its meta, and a long one still wraps at the message's max-width. */
472
478
  .msg.user .body { background: var(--user-bubble); border-radius: var(--r-xl); padding: 10px var(--s-4); width: fit-content; max-width: 100%; margin-left: auto; }
479
+ /* A message this page is sending (chat-thread.js outgoingHtml): shown at once, its text dimmed with "Sending…" until the
480
+ server takes it; failed, a straight red bar with Retry and Delete. Each file: a picture as itself (or a document mark),
481
+ its name, a square-ended bar and its percentage, moved in place. */
482
+ .msg.user.out .meta { align-items: center; gap: var(--s-2); }
483
+ .msg.out.out-queued .body, .msg.out.out-sending .body { color: var(--text-3); }
484
+ .msg.out.out-queued .body .md, .msg.out.out-sending .body .md { opacity: .72; }
485
+ .out-state { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-xs); color: var(--text-3); }
486
+ .out-spin { width: 10px; height: 10px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: out-spin .8s linear infinite; }
487
+ @keyframes out-spin { to { transform: rotate(360deg); } }
488
+ .out-files { display: flex; flex-direction: column; gap: var(--s-2); margin-top: var(--s-2); }
489
+ .msg.out .body .out-files:first-child { margin-top: 0; }
490
+ .out-file { display: flex; align-items: center; gap: var(--s-2); min-width: 0; }
491
+ .out-pic { width: 72px; height: 72px; object-fit: cover; border-radius: var(--r-md); flex: none; background: var(--surface-2); }
492
+ .msg.out.out-queued .out-pic, .msg.out.out-sending .out-pic { opacity: .7; }
493
+ .out-doc { width: 32px; height: 40px; flex: none; border: 1px solid var(--border); border-radius: 4px; background: var(--surface-2); }
494
+ .out-file-info { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 4px var(--s-2); min-width: 0; flex: 1; min-width: 140px; }
495
+ .out-name { font-size: var(--fs-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
496
+ .out-pct { font-size: var(--fs-xs); color: var(--text-3); font-variant-numeric: tabular-nums; }
497
+ .out-bar { grid-column: 1 / -1; height: 3px; background: var(--border); overflow: hidden; }
498
+ .out-fill { display: block; height: 100%; background: var(--accent); transform: scaleX(0); transform-origin: left; transition: transform .2s linear; }
499
+ .out-file[data-state="done"] .out-bar, .out-file[data-state="done"] .out-pct { visibility: hidden; }
500
+ .out-file[data-state="failed"] .out-fill { background: var(--danger); }
501
+ .out-file[data-state="failed"] .out-pct { color: var(--danger); }
502
+ .msg.out.out-sent .out-file .out-bar, .msg.out.out-sent .out-file .out-pct { visibility: hidden; }
503
+ /* Hugs its words under the bubble's right edge, as the bubble does: the bar stays beside them, never across the row. */
504
+ .out-note { --lbar-color: var(--danger); display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); width: fit-content; max-width: 100%;
505
+ margin: var(--s-2) 0 0 auto; padding: var(--s-1) 0 var(--s-1) calc(var(--s-2) + 3px); font-size: var(--fs-xs); color: var(--danger); }
506
+ .out-error { overflow-wrap: anywhere; }
507
+ .out-actions { display: inline-flex; gap: var(--s-1); }
508
+ @media (prefers-reduced-motion: reduce) { .out-spin { animation: none; border-right-color: currentColor; opacity: .6; } .out-fill { transition: none; } }
473
509
  /* A person's click on a reply button: the bubble shows 🔘 and the button's label (never the value the agent was given);
474
510
  live, the label is a button that shows the reply its buttons were on. */
475
511
  .click-chip { display: flex; }
@@ -615,8 +651,8 @@ html.lb-open .scroller { overflow: hidden; }
615
651
  .p-chat > .panel-head { --bg: var(--chrome-bg); --surface: var(--chrome-surface); --text: var(--chrome-text); --text-2: var(--chrome-text-2); --accent: var(--chrome-accent); background: var(--bg); color: var(--text); }
616
652
  .p-chat.th { background: var(--bg); color: var(--text); }
617
653
  .p-chat.th .msg.user .body { border-radius: var(--bubble-radius, var(--r-xl)); }
618
- .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); }
619
- .p-chat.th-font-mono .thread, .p-chat.th-font-mono .composer textarea { font-family: var(--font-mono); }
654
+ .p-chat.th-font-system .thread, .p-chat.th-font-system .composer textarea, .p-chat.th-font-system .composer-ph { font-family: system-ui, -apple-system, "Segoe UI", sans-serif, var(--font-emoji); }
655
+ .p-chat.th-font-mono .thread, .p-chat.th-font-mono .composer textarea, .p-chat.th-font-mono .composer-ph { font-family: var(--font-mono); }
620
656
  .p-chat.th-bubble-thin .msg.user .body { box-shadow: inset 0 0 0 1px var(--border); }
621
657
  .p-chat.th-bubble-accent .msg.user .body { box-shadow: inset 0 0 0 1px var(--accent); }
622
658
  .p-chat.th-agent-bubble .msg.agent .body { background: var(--agent-bubble); border-radius: var(--bubble-radius, var(--r-xl)); padding: 10px var(--s-4); }
@@ -750,6 +786,11 @@ img.amb-p { object-fit: contain; }
750
786
  font-size: var(--fs-md); line-height: var(--lh-md); resize: none; min-height: 24px; max-height: 240px; overflow-y: auto;
751
787
  }
752
788
  .composer textarea::placeholder { color: var(--text-3); }
789
+ /* #2478: the placeholder, drawn over the empty box at its text's place: one line ending in "…" (a textarea's own is
790
+ clipped, never ellipsized). The same size and line as the box's text; it never takes a tap. */
791
+ .composer-field { position: relative; flex: 1; min-width: 0; display: flex; }
792
+ .composer-ph { position: absolute; left: var(--s-1); right: var(--s-1); top: 7px; pointer-events: none; color: var(--text-3);
793
+ font-size: var(--fs-md); line-height: var(--lh-md); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
753
794
  .btn-send { width: 36px; padding: 0; }
754
795
  .btn-send .send-label { display: none; }
755
796
  /* A narrow phone: Stop reply beside Send leaves the message box no room — the label is for screen readers there. */
@@ -795,6 +836,8 @@ img.amb-p { object-fit: contain; }
795
836
  /* #1939 S2a: Settings → Web → Web authority. */
796
837
  .s-web-authority .s-va-sessions { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-2); }
797
838
  .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; }
839
+ /* #2453: "over your named link" stays one phrase (the list item may break anywhere, CJK included). */
840
+ .s-web-authority .s-va-named { white-space: nowrap; }
798
841
  .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); }
799
842
  .s-web-authority .save-row { flex-wrap: wrap; }
800
843
  /* #1920: a guest's note under the composer — on a phone too, where the key hints are hidden. */
@@ -920,6 +963,8 @@ img.amb-p { object-fit: contain; }
920
963
  padding: 0 env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
921
964
  }
922
965
  .kb-open .tabs { display: none; }
966
+ /* #2478: with the keyboard up the line under the composer goes too — the box sits on the keyboard; back on blur. */
967
+ .kb-open .composer-row { display: none; }
923
968
  .tab { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px; min-height: 56px; justify-content: center; font-size: 11px; color: var(--text-2); position: relative; }
924
969
  .tab.active { color: var(--text); font-weight: var(--fw-semibold); }
925
970
  .tab-badge { position: absolute; top: 6px; left: calc(50% + 6px); min-width: 16px; height: 16px; padding: 0 4px; border-radius: var(--r-full); background: var(--danger-fill); color: var(--on-danger); font-size: 10px; line-height: 16px; text-align: center; }
@@ -1032,7 +1077,21 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
1032
1077
  down, never the count; the count is as tall as a header, so the row does not grow. */
1033
1078
  .nav-facets { display: flex; align-items: flex-start; gap: var(--s-2); padding: var(--s-2) var(--s-1) 0; }
1034
1079
  .nav-facet-groups { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--s-1) var(--s-2); flex: 1 1 auto; min-width: 0; }
1035
- .nav-facets .nav-shown { flex: none; margin: 0 0 0 auto; padding: 2px 0; white-space: nowrap; }
1080
+ /* #2401: the count, "shown" under it and Clear under that. The word's width is held by ::before (its text from
1081
+ data-reserve) and idle Clear's by the button itself, both at no height, unseen and out of reach (visibility): the column —
1082
+ and the headers' box beside it — is as wide with a facet set as without. */
1083
+ .nav-facet-end { flex: none; margin: 0 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; }
1084
+ .nav-facets .nav-shown { margin: 0; padding: 2px 0; white-space: nowrap; text-align: right; font-variant-numeric: tabular-nums; }
1085
+ .nav-shown-word { display: block; }
1086
+ .nav-facet-end::before { content: attr(data-reserve); display: block; height: 0; overflow: hidden; visibility: hidden; font-size: var(--fs-xs); white-space: nowrap; }
1087
+ .nav-facet-end .is-idle { visibility: hidden; height: 0; min-height: 0; margin: 0; padding-top: 0; padding-bottom: 0; border-top-width: 0; border-bottom-width: 0; overflow: hidden; }
1088
+ /* No open list beside it (.compact): one cell of one line, as wide as the count or Clear; a facet set, Clear in the count's
1089
+ place (the count transparent, still read; the word for a screen reader only, so its width is not held here). */
1090
+ .nav-facets.compact .nav-facet-end { display: grid; justify-items: end; align-items: center; }
1091
+ .nav-facets.compact .nav-facet-end > * { grid-area: 1 / 1; margin: 0; }
1092
+ .nav-facets.compact .nav-facet-end::before { display: none; }
1093
+ .nav-facets.compact .nav-shown.is-set { opacity: 0; pointer-events: none; }
1094
+ .nav-facets.compact .nav-shown-word { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
1036
1095
  .nav-facet { font-size: var(--fs-xs); color: var(--text-2); min-width: 0; max-width: 100%; }
1037
1096
  /* #1700: a facet group's header — the whole row toggles its list; folded with a filter set, a short summary. */
1038
1097
  .nav-facet-head { display: inline-flex; align-items: center; gap: var(--s-1); max-width: 100%; padding: 2px var(--s-2); border: 0; border-radius: var(--r-sm);
@@ -1041,10 +1100,15 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
1041
1100
  .nav-facet-head:focus-visible { outline: 2px solid var(--accent); }
1042
1101
  .nav-facet-head .caret { color: var(--text-3); flex: none; }
1043
1102
  .nav-facet-sum { min-width: 0; max-width: 12em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--accent); }
1103
+ /* #2401: the summary takes only the room its line has left — never counted when the headers wrap (contain: inline-size,
1104
+ its gap taken back), so a facet set never sends a header to a line of its own; the header holding it grows into the rest. */
1105
+ .nav-facet.has-sum { flex-grow: 1; }
1106
+ .nav-facet.has-sum .nav-facet-head { width: 100%; }
1107
+ .nav-facet-sum { contain: inline-size; flex: 1 1 0; margin-left: calc(-1 * var(--s-1)); padding-left: var(--s-1); }
1044
1108
  .nav-facet-n { display: inline-block; min-width: 16px; padding: 0 4px; border-radius: 8px; background: var(--accent); color: var(--on-accent, #fff); text-align: center; font-variant-numeric: tabular-nums; }
1045
1109
  .nav-facet-list { display: flex; flex-direction: column; gap: 2px; padding: var(--s-1) var(--s-2); }
1046
1110
  .nav-chip { display: flex; align-items: center; gap: var(--s-2); cursor: pointer; color: var(--text); }
1047
- .nav-facet-reset { font-size: var(--fs-xs); }
1111
+ .nav-facet-reset { font-size: var(--fs-xs); flex: none; white-space: nowrap; margin-top: 2px; padding: 2px var(--s-1); }
1048
1112
 
1049
1113
  /* One gap on every page (View's, #1448): the header's controls are the same size on every tab. */
1050
1114
  .panel-actions .btn { gap: var(--s-1); }
@@ -1102,7 +1166,45 @@ body.side-resizing, body.side-resizing * { cursor: col-resize !important; user-s
1102
1166
  /* #1673: the usage rail beside the page on a wide screen (header-tools.js UsageRail; shown only at min-width 1200px). */
1103
1167
  .u-rail { width: 320px; flex-shrink: 0; display: flex; flex-direction: column; min-height: 0; border-left: 1px solid var(--border); background: var(--bg); }
1104
1168
  .u-rail-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-2); padding: var(--s-3) var(--s-3) var(--s-2) var(--s-4); }
1105
- .u-rail-head h2 { margin: 0; font-size: var(--fs-md); font-weight: var(--fw-semibold); }
1169
+ .u-rail-head h2 { margin: 0; flex: 1; min-width: 0; font-size: var(--fs-md); font-weight: var(--fw-semibold); }
1170
+ /* The usage view's compact mode (usage-dialog.js CompactGroup / CompactRow), the user's pick (2026-10-22): the logins
1171
+ grouped by provider (its name once, in small capitals), each in three short lines — its name and plan with the
1172
+ soonest reset on the right, then each window as a labelled full-width bar and the percent. */
1173
+ .dlg-tools { display: inline-flex; align-items: center; flex: none; }
1174
+ .u-compact-toggle { flex: none; }
1175
+ /* flex: none — a group never shrinks to fit the rail (it would clip its accounts): the rail scrolls instead. */
1176
+ .u-cgroup { flex: none; display: flex; flex-direction: column; border-radius: var(--r-md); background: var(--surface); overflow: hidden; }
1177
+ .u-cg-head { display: flex; align-items: baseline; gap: 4px; padding: 7px var(--s-3) 3px; font-size: 11px; font-weight: var(--fw-semibold);
1178
+ letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-3); }
1179
+ .u-cg-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1180
+ .u-cg-count { flex: none; }
1181
+ .u-crow { display: flex; flex-direction: column; gap: 3px; width: 100%; min-width: 0; padding: 6px var(--s-3); border: 0; border-top: 1px solid var(--border);
1182
+ background: transparent; color: inherit; font: inherit; line-height: 1.3; text-align: left; cursor: pointer; }
1183
+ .u-cg-head + .u-crow { border-top: 0; }
1184
+ .u-crow:hover { background: var(--surface-2); }
1185
+ /* #2478 (Prism r1): the compact rows sit in .u-cgroup, rounded and clipping (overflow: hidden) — the bar keeps clear of
1186
+ its 12 px corners (4 px in, 6 px off the row's ends) and there is no full-width underline to be rounded at the ends:
1187
+ the bar is the focus mark (hover has none). */
1188
+ .u-crow:focus-visible { outline: none; background: var(--surface-2); --lbar-left: var(--s-1); --lbar-inset: 6px; }
1189
+ /* The name never gives way to the plan (#2415); the reset keeps its room on the right. */
1190
+ .u-crow-head { display: flex; align-items: center; gap: 6px; min-width: 0; font-size: var(--fs-sm); }
1191
+ .u-crow-name { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--fw-semibold); color: var(--text); }
1192
+ .u-crow .u-plan { flex: 0 100 auto; min-width: 22px; max-width: max-content; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1193
+ padding-block: 0; line-height: 16px; font-size: 10.5px; }
1194
+ .u-crow-reset { margin-left: auto; flex: none; padding-left: 6px; font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; font-variant-numeric: tabular-nums; }
1195
+ .u-cm { display: grid; grid-template-columns: 2.6em minmax(0, 1fr) 3.4em; align-items: center; gap: 7px; min-width: 0; }
1196
+ .u-cm-tag { font-size: var(--fs-xs); color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1197
+ /* #2240: an account of several pools (agy) names each pool's line: a wider label, ending in an ellipsis if it must. */
1198
+ .u-crow.is-pooled .u-cm { grid-template-columns: minmax(0, 7.5em) minmax(0, 1fr) 3.4em; }
1199
+ .u-cm-bar { height: 6px; border-radius: var(--r-full); background: var(--surface-2); overflow: hidden; }
1200
+ .u-cm-fill { display: block; height: 100%; width: 0; min-width: 2px; border-radius: var(--r-full); background: var(--accent); }
1201
+ .u-cm-fill.warn { background: var(--warn); } .u-cm-fill.crit { background: var(--danger); }
1202
+ .u-cm-pct { text-align: right; font-size: 13px; font-weight: var(--fw-bold); font-variant-numeric: tabular-nums; color: var(--text); }
1203
+ .u-cm-pct.warn { color: var(--warn); } .u-cm-pct.crit { color: var(--danger); }
1204
+ .u-crow-note { font-size: var(--fs-xs); color: var(--text-3); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1205
+ .u-crow-note.is-alert::before { content: "●"; margin-right: 5px; color: var(--danger); }
1206
+ .u-crow.is-stale .u-crow-note.is-alert::before { color: var(--warn); }
1207
+ @media (prefers-reduced-motion: no-preference) { .u-crow { transition: background-color 0.15s var(--ease); } }
1106
1208
  .u-rail-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--s-3) var(--s-4); display: flex; flex-direction: column; gap: var(--s-3); }
1107
1209
  @media (max-width: 1199px) { .u-rail { display: none; } }
1108
1210
  @media (max-width: 899px) {
@@ -1460,6 +1562,49 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1460
1562
  .oc-tools { margin-left: 0; width: 100%; flex-wrap: wrap; row-gap: var(--s-2); }
1461
1563
  .oc-mini { display: none; }
1462
1564
  }
1565
+ /* ── Fleet → Org chart → Office (fleet-office.js): the fleet as a pixel office on one <canvas>. Its size is set through
1566
+ the CSSOM (never a style attribute, #1300); the stage scrolls, the page around it too. ── */
1567
+ .oo { display: flex; flex-direction: column; gap: var(--s-2); }
1568
+ .oo-bar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2); }
1569
+ .oo-pick { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-xs); color: var(--text-2); }
1570
+ .oo-pick .tb-select { max-width: 200px; }
1571
+ .oo-people { position: relative; }
1572
+ /* #2399: the offices' minimal editor (fleet-office-editor.js) — one row per office, its members unfolded under it. */
1573
+ .oe-form { border: 0; margin: 0; padding: 0; min-width: 0; }
1574
+ /* #2431: after a 409, the two ways on (keep my edits, load theirs), side by side; they wrap on a phone. */
1575
+ .oe-conflict { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
1576
+ .oe-list { list-style: none; margin: var(--s-3) 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-3); }
1577
+ .oe-row { padding: var(--s-3); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); display: flex; flex-direction: column; gap: var(--s-2); }
1578
+ .oe-head { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s-2); }
1579
+ .oe-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
1580
+ .oe-label { font-size: var(--fs-xs); color: var(--text-2); }
1581
+ .oe-name { min-width: 0; width: 16em; max-width: 100%; }
1582
+ .oe-remove { margin-left: auto; }
1583
+ .oe-members { display: flex; flex-wrap: wrap; gap: var(--s-3); }
1584
+ .oe-group { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; min-width: 12em; max-height: 240px; overflow-y: auto; }
1585
+ .oe-group legend { font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--text-2); padding: 0; margin-bottom: 4px; }
1586
+ .oo-people > .btn.on { border-color: var(--accent); }
1587
+ .oo-people-pop { position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; width: min(280px, calc(100vw - 32px)); max-height: 340px; overflow-y: auto;
1588
+ display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-2); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--shadow-2); }
1589
+ .oo-people-pop input[type="search"] { width: 100%; height: 32px; }
1590
+ .oo-people-list { list-style: none; margin: 0; padding: 0; }
1591
+ .oo-people-list label { display: flex; align-items: center; gap: 8px; padding: 3px 2px; font-size: var(--fs-xs); color: var(--text); cursor: pointer; overflow-wrap: anywhere; }
1592
+ .oo-note { font-size: var(--fs-xs); }
1593
+ .oo-stage { position: relative; height: clamp(420px, calc(100dvh - 280px), 960px); overflow: auto; border: 1px solid var(--border); border-radius: var(--r-lg); background: var(--bg); }
1594
+ .oo-canvas { display: block; margin: 0 auto; image-rendering: pixelated; }
1595
+ .oo-canvas.hot { cursor: pointer; }
1596
+ .oo-tip { position: absolute; left: 0; top: 0; z-index: 5; max-width: 240px; pointer-events: none; padding: 6px 10px; font-size: var(--fs-xs); line-height: 1.4;
1597
+ color: var(--text); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); box-shadow: var(--shadow-2); overflow-wrap: anywhere; }
1598
+ /* The desks as a list: for a screen reader, and shown (over the stage's corner) while the keyboard is in it. */
1599
+ .oo-desks { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; list-style: none; }
1600
+ .oo-desks:focus-within { position: sticky; left: var(--s-2); bottom: var(--s-2); width: auto; height: auto; max-height: 40%; margin: 0; padding: var(--s-1); clip: auto; overflow-y: auto;
1601
+ display: inline-flex; flex-wrap: wrap; gap: 4px; max-width: calc(100% - 2 * var(--s-2)); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-md); white-space: normal; }
1602
+ .oo-desk { font-size: var(--fs-xs); padding: 2px 6px; border-radius: var(--r-sm); background: var(--bg); color: var(--text); border: 1px solid var(--border); }
1603
+ .oo-desk:focus-visible { outline: none; border-color: var(--focus); box-shadow: inset 0 -2px 0 var(--focus); }
1604
+ @media (max-width: 640px) {
1605
+ .oo-stage { height: calc(100dvh - 300px); min-height: 340px; }
1606
+ .oo-pick .tb-select { max-width: 150px; }
1607
+ }
1463
1608
  /* A phone: Fleet's five tabs fit when only the open one shows its label (the others keep theirs for screen readers). */
1464
1609
  @media (max-width: 479px) {
1465
1610
  .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; }
@@ -1779,6 +1924,8 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1779
1924
  .chat-fx-notice > span { display: block; }
1780
1925
 
1781
1926
  /* ── #2293: global search — the sidebar row's shortcut, the header's button (collapsed sidebar, phone), the dialog ── */
1927
+ /* #2478: a touch screen has no Ctrl/⌘ K — the shortcut's hint is not shown there (the row is). */
1928
+ @media (pointer: coarse) { .side-search .kbd-hint { display: none; } }
1782
1929
  .kbd-hint { font: inherit; font-size: var(--fs-xs); color: var(--text-3); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 0 var(--s-1); line-height: 18px; white-space: nowrap; }
1783
1930
  .panel-head .sb-search { display: none; }
1784
1931
  .sb-collapsed .panel-head .sb-search { display: inline-flex; }
@@ -1804,15 +1951,17 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1804
1951
  .search-note { color: var(--warn); }
1805
1952
  .search-recent-head { display: flex; align-items: center; justify-content: space-between; padding: var(--s-2) var(--s-3) 0; color: var(--text-3); font-size: var(--fs-xs); }
1806
1953
  .search-recent ul { list-style: none; margin: 0; padding: 0; }
1807
- .search-recent-item { display: flex; align-items: center; gap: var(--s-2); width: 100%; border: 0; border-radius: var(--r-sm); background: transparent; color: var(--text); padding: var(--s-2) var(--s-3); font-size: var(--fs-sm); text-align: left; cursor: pointer; }
1808
- .search-recent-item:hover { background: var(--surface-2); }
1954
+ .search-recent-item { display: flex; align-items: center; gap: var(--s-2); width: 100%; border: 0; border-radius: 0; --lbar-inset: 4px; background: transparent; color: var(--text); padding: var(--s-2) var(--s-3); font-size: var(--fs-sm); text-align: left; cursor: pointer; }
1955
+ .search-recent-item:hover, .search-recent-item:focus-visible { background: var(--surface-2); }
1956
+ /* Keyboard focus: the bar and a straight underline (no rounded ring). */
1957
+ .search-recent-item:focus-visible { outline: none; box-shadow: inset 0 -2px 0 var(--focus); }
1809
1958
  .search-recent-item svg { color: var(--text-3); flex: none; }
1810
1959
  .search-results.stale { opacity: 0.6; }
1811
1960
  .search-group + .search-group { margin-top: var(--s-2); }
1812
1961
  .search-group-head { display: flex; justify-content: space-between; gap: var(--s-2); padding: var(--s-2) var(--s-3) var(--s-1); font-size: var(--fs-xs); font-weight: var(--fw-semibold); color: var(--text-2); }
1813
1962
  .search-count { color: var(--text-3); font-weight: var(--fw-regular); }
1814
- .search-row { padding: var(--s-2) var(--s-3); border-radius: var(--r-sm); cursor: pointer; }
1815
- .search-row.on { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--accent); }
1963
+ .search-row { padding: var(--s-2) var(--s-3); border-radius: 0; cursor: pointer; }
1964
+ .search-row.on { background: var(--surface-2); }
1816
1965
  .search-row-top { display: flex; align-items: center; gap: var(--s-2); font-size: var(--fs-xs); color: var(--text-2); }
1817
1966
  .search-who { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1818
1967
  .search-kind { color: var(--text-3); }
@@ -1826,8 +1975,8 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
1826
1975
  .search-detail-head { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-1) var(--s-1) var(--s-2); font-size: var(--fs-sm); }
1827
1976
  .search-detail-head .chip-btn { display: inline-flex; align-items: center; gap: 4px; }
1828
1977
  .search-ctx { list-style: none; margin: 0; padding: 0 var(--s-1); display: flex; flex-direction: column; gap: var(--s-1); }
1829
- .search-ctx-line { padding: var(--s-2) var(--s-3); border-radius: var(--r-sm); }
1830
- .search-ctx-line.here { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--accent); }
1978
+ .search-ctx-line { padding: var(--s-2) var(--s-3); border-radius: 0; }
1979
+ .search-ctx-line.here { background: var(--surface-2); }
1831
1980
  .search-ctx-text { margin-top: 2px; font-size: var(--fs-sm); white-space: pre-wrap; overflow-wrap: anywhere; }
1832
1981
  .search-detail-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--s-2); padding: var(--s-3) var(--s-1) var(--s-1); }
1833
1982
  @media (max-width: 899px) {
@@ -2003,9 +2152,9 @@ strong.org-name { font-size: var(--fs-sm); color: var(--text); }
2003
2152
  .tw-desc { margin: 0; font-size: var(--fs-xs); overflow-wrap: anywhere; }
2004
2153
  .tw-none { margin: 0; font-size: var(--fs-xs); }
2005
2154
  .tw-lines { display: flex; flex-direction: column; gap: 2px; margin: 0; padding: 0; list-style: none; }
2006
- .tw-line { display: flex; align-items: baseline; gap: var(--s-2); width: 100%; padding: 2px var(--s-1); background: none; border: 0; border-radius: var(--r-sm); color: var(--text); text-align: left; cursor: pointer; font-size: var(--fs-sm); min-width: 0; }
2155
+ .tw-line { display: flex; align-items: baseline; gap: var(--s-2); width: 100%; padding: 2px var(--s-1); background: none; border: 0; border-radius: 0; --lbar-inset: 2px; color: var(--text); text-align: left; cursor: pointer; font-size: var(--fs-sm); min-width: 0; }
2007
2156
  .tw-line:hover { background: var(--surface-2); }
2008
- .tw-line:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
2157
+ .tw-line:focus-visible { outline: none; background: var(--surface-2); box-shadow: inset 0 -2px 0 var(--focus); }
2009
2158
  .tw-kind { flex-shrink: 0; min-width: 4.5em; font-size: 11px; font-weight: var(--fw-semibold); color: var(--text-2); }
2010
2159
  .tw-kind.k-doing { color: var(--accent); }
2011
2160
  .tw-kind.k-blocked, .tw-kind.k-back { color: var(--danger); }
@@ -0,0 +1,35 @@
1
+ // What a guest's browser is shown for a page it cannot open. Loaded from /assets/guest-refused.js.
2
+ //
3
+ // The gate answers a guest's navigation with this page instead of its JSON (#2399 smoke): "members" for a page that is
4
+ // not a guest's (Fleet, Settings…), "ended" when no chat is shared with the guest any more. The page's words are in
5
+ // the browser's language, as the sign-in page's are; the HTML carries the English for a browser without scripts.
6
+ (() => {
7
+ "use strict";
8
+ const T = {
9
+ en: {
10
+ members_title: "This page is not open to guests",
11
+ members_lead: "This page is for the fleet's members. As a guest you can use the chats shared with you.",
12
+ members_go: "Open your chats",
13
+ ended_title: "No chats are shared with you",
14
+ ended_lead: "Your guest access on this fleet has no chat now — you may have left the room it came from. Ask whoever invited you to share a chat with you again.",
15
+ },
16
+ "zh-TW": {
17
+ members_title: "訪客無法開啟這個頁面",
18
+ members_lead: "這個頁面只給 fleet 的成員使用。身為訪客,你可以使用分享給你的聊天。",
19
+ members_go: "打開你的聊天",
20
+ ended_title: "目前沒有分享給你的聊天",
21
+ ended_lead: "你在這個 fleet 的訪客權限目前沒有任何聊天——你可能已經離開當初的頻道。請聯絡邀請你的人,再分享聊天給你。",
22
+ },
23
+ };
24
+ const lang = /^zh/i.test(navigator.language || "") ? "zh-TW" : "en";
25
+ const t = (k) => T[lang][k] || T.en[k] || k;
26
+ document.documentElement.lang = lang === "zh-TW" ? "zh-Hant" : "en";
27
+ const main = document.getElementById("refused");
28
+ const why = main && main.dataset.why === "ended" ? "ended" : "members";
29
+ document.getElementById("title").textContent = t(`${why}_title`);
30
+ document.getElementById("lead").textContent = t(`${why}_lead`);
31
+ // Nothing to open when no chat is shared (the chats would answer this page again): no button then.
32
+ const go = document.getElementById("go");
33
+ if (go) { if (why === "ended") go.remove(); else go.textContent = t("members_go"); }
34
+ document.title = `AgEnD — ${t(`${why}_title`)}`;
35
+ })();
@@ -9,7 +9,7 @@ import { html, useEffect, useState } from "./app-html.js";
9
9
  import { t } from "./app-i18n.js";
10
10
  import { createStore, useStore } from "./app-store.js";
11
11
  import { Icon } from "./ui-icons.js";
12
- import { UsageBody, UsageDialog } from "./usage-dialog.js";
12
+ import { UsageBody, UsageCompactToggle, UsageDialog } from "./usage-dialog.js";
13
13
 
14
14
  export const TEXT_SIZE_KEY = "agend_text_size";
15
15
  export const VIEW_FIT_KEY = "agend_view_fit";
@@ -126,7 +126,7 @@ export function UsageRail({ route }) {
126
126
  const { open } = useStore(usageRailStore);
127
127
  if (!wide || !open || !route || !RAIL_PANELS.includes(route.panel) || !usageOffered()) return null;
128
128
  return html`<aside data-trust="usage" class="u-rail" aria-label=${t("view.usage.title_plain")}>
129
- <div class="u-rail-head"><h2>${t("view.usage.title")}</h2>
129
+ <div class="u-rail-head"><h2>${t("view.usage.title")}</h2><${UsageCompactToggle} />
130
130
  <button type="button" class="icon-btn" onClick=${() => setUsageRail(false)} aria-label=${t("app.usageHide")} title=${t("app.usageHide")}><${Icon} name="close" size=${16} /></button></div>
131
131
  <div class="u-rail-body"><${UsageBody} owner="usage-rail" /></div>
132
132
  </aside>`;