@songsid/agend 2.3.0-beta.9 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (723) hide show
  1. package/README.md +16 -8
  2. package/README.zh-TW.md +16 -8
  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-instructions.md +7 -1
  8. package/dist/agent-cli.js +41 -0
  9. package/dist/agent-cli.js.map +1 -1
  10. package/dist/agent-endpoint.d.ts +9 -0
  11. package/dist/agent-endpoint.js +23 -0
  12. package/dist/agent-endpoint.js.map +1 -1
  13. package/dist/agent-file-read.js +1 -1
  14. package/dist/agent-file-read.js.map +1 -1
  15. package/dist/agent-whats-new.d.ts +16 -0
  16. package/dist/agent-whats-new.js +53 -12
  17. package/dist/agent-whats-new.js.map +1 -1
  18. package/dist/apply-job.js.map +1 -1
  19. package/dist/atomic-write.js.map +1 -1
  20. package/dist/auth-api.d.ts +12 -0
  21. package/dist/auth-api.js +77 -22
  22. package/dist/auth-api.js.map +1 -1
  23. package/dist/authority/authority-state.d.ts +4 -0
  24. package/dist/authority/authority-state.js +17 -8
  25. package/dist/authority/authority-state.js.map +1 -1
  26. package/dist/authority/session-authorizations.js.map +1 -1
  27. package/dist/authority/web-authority.d.ts +16 -8
  28. package/dist/authority/web-authority.js +19 -5
  29. package/dist/authority/web-authority.js.map +1 -1
  30. package/dist/backend/antigravity.d.ts +2 -2
  31. package/dist/backend/antigravity.js +2 -1
  32. package/dist/backend/antigravity.js.map +1 -1
  33. package/dist/backend/claude-code.js +13 -4
  34. package/dist/backend/claude-code.js.map +1 -1
  35. package/dist/backend/codex-metadata.d.ts +8 -0
  36. package/dist/backend/codex-metadata.js +74 -17
  37. package/dist/backend/codex-metadata.js.map +1 -1
  38. package/dist/backend/codex-package.d.ts +30 -0
  39. package/dist/backend/codex-package.js +133 -0
  40. package/dist/backend/codex-package.js.map +1 -0
  41. package/dist/backend/codex-session-lookup.js.map +1 -1
  42. package/dist/backend/codex.d.ts +2 -0
  43. package/dist/backend/codex.js +25 -5
  44. package/dist/backend/codex.js.map +1 -1
  45. package/dist/backend/credential-profile.d.ts +2 -0
  46. package/dist/backend/credential-profile.js +2 -0
  47. package/dist/backend/credential-profile.js.map +1 -1
  48. package/dist/backend/effort-metadata.js.map +1 -1
  49. package/dist/backend/grok.js +58 -3
  50. package/dist/backend/grok.js.map +1 -1
  51. package/dist/backend/kiro-engine-ledger.js.map +1 -1
  52. package/dist/backend/kiro-kas-store.js.map +1 -1
  53. package/dist/backend/kiro-v2-store.js.map +1 -1
  54. package/dist/backend/kiro-v3-identity.js.map +1 -1
  55. package/dist/backend/kiro.d.ts +9 -1
  56. package/dist/backend/kiro.js +102 -1
  57. package/dist/backend/kiro.js.map +1 -1
  58. package/dist/backend/launch-effort.js.map +1 -1
  59. package/dist/backend/mock.js.map +1 -1
  60. package/dist/backend/muse.js.map +1 -1
  61. package/dist/backend/opencode-permission.js +1 -1
  62. package/dist/backend/opencode-permission.js.map +1 -1
  63. package/dist/backend/opencode.js +2 -1
  64. package/dist/backend/opencode.js.map +1 -1
  65. package/dist/backend/session-signals.js.map +1 -1
  66. package/dist/backend-availability.js.map +1 -1
  67. package/dist/backend-command.d.ts +2 -0
  68. package/dist/backend-command.js +3 -1
  69. package/dist/backend-command.js.map +1 -1
  70. package/dist/backend-outage.js.map +1 -1
  71. package/dist/binary-probe.js +1 -1
  72. package/dist/binary-probe.js.map +1 -1
  73. package/dist/bug-report-cli.d.ts +30 -0
  74. package/dist/bug-report-cli.js +139 -0
  75. package/dist/bug-report-cli.js.map +1 -0
  76. package/dist/bug-report.d.ts +47 -0
  77. package/dist/bug-report.js +137 -0
  78. package/dist/bug-report.js.map +1 -0
  79. package/dist/bundled-skills.d.ts +18 -0
  80. package/dist/bundled-skills.js +38 -0
  81. package/dist/bundled-skills.js.map +1 -0
  82. package/dist/cache-service.js +1 -1
  83. package/dist/cache-service.js.map +1 -1
  84. package/dist/channel/adapters/discord-client-retirement.d.ts +5 -0
  85. package/dist/channel/adapters/discord-client-retirement.js +57 -0
  86. package/dist/channel/adapters/discord-client-retirement.js.map +1 -0
  87. package/dist/channel/adapters/discord.d.ts +4 -0
  88. package/dist/channel/adapters/discord.js +57 -12
  89. package/dist/channel/adapters/discord.js.map +1 -1
  90. package/dist/channel/adapters/telegram.d.ts +6 -0
  91. package/dist/channel/adapters/telegram.js +54 -14
  92. package/dist/channel/adapters/telegram.js.map +1 -1
  93. package/dist/channel/mcp-server.js +21 -4
  94. package/dist/channel/mcp-server.js.map +1 -1
  95. package/dist/channel/mcp-tools.d.ts +3 -1
  96. package/dist/channel/mcp-tools.js +41 -4
  97. package/dist/channel/mcp-tools.js.map +1 -1
  98. package/dist/channel/message-queue.js.map +1 -1
  99. package/dist/channel/sticker-download.js.map +1 -1
  100. package/dist/channel/tool-router.js.map +1 -1
  101. package/dist/child-process.d.ts +21 -0
  102. package/dist/child-process.js +54 -0
  103. package/dist/child-process.js.map +1 -0
  104. package/dist/classic-channel-manager.js +3 -2
  105. package/dist/classic-channel-manager.js.map +1 -1
  106. package/dist/cli-entry.js.map +1 -1
  107. package/dist/cli-flags.js.map +1 -1
  108. package/dist/cli.d.ts +1 -0
  109. package/dist/cli.js +443 -311
  110. package/dist/cli.js.map +1 -1
  111. package/dist/command-table.d.ts +1 -1
  112. package/dist/command-table.js +1 -1
  113. package/dist/command-table.js.map +1 -1
  114. package/dist/compact-watch.js.map +1 -1
  115. package/dist/completion-install.js.map +1 -1
  116. package/dist/completion.js.map +1 -1
  117. package/dist/config-validator.d.ts +6 -0
  118. package/dist/config-validator.js +81 -8
  119. package/dist/config-validator.js.map +1 -1
  120. package/dist/config.d.ts +15 -3
  121. package/dist/config.js +41 -10
  122. package/dist/config.js.map +1 -1
  123. package/dist/connection-secrets.js.map +1 -1
  124. package/dist/context-guardian.js.map +1 -1
  125. package/dist/coredump-filter.js.map +1 -1
  126. package/dist/cpu-profile.js.map +1 -1
  127. package/dist/cross-instance-envelope.js.map +1 -1
  128. package/dist/cross-instance-notice.d.ts +7 -0
  129. package/dist/cross-instance-notice.js +10 -0
  130. package/dist/cross-instance-notice.js.map +1 -1
  131. package/dist/daemon.d.ts +39 -1
  132. package/dist/daemon.js +234 -15
  133. package/dist/daemon.js.map +1 -1
  134. package/dist/daily-summary.js.map +1 -1
  135. package/dist/darwin-memory.js +1 -1
  136. package/dist/darwin-memory.js.map +1 -1
  137. package/dist/dashboard-live.d.ts +8 -0
  138. package/dist/dashboard-live.js +35 -0
  139. package/dist/dashboard-live.js.map +1 -0
  140. package/dist/dashboard-permissions.d.ts +69 -0
  141. package/dist/dashboard-permissions.js +138 -0
  142. package/dist/dashboard-permissions.js.map +1 -0
  143. package/dist/decision-list.js.map +1 -1
  144. package/dist/delivery-consumed-watch.js.map +1 -1
  145. package/dist/delivery-outbox.js +5 -0
  146. package/dist/delivery-outbox.js.map +1 -1
  147. package/dist/delivery-outbox.test.js.map +1 -1
  148. package/dist/delivery-reconciliation.test.js.map +1 -1
  149. package/dist/diagnostic-redaction.d.ts +10 -0
  150. package/dist/diagnostic-redaction.js +167 -0
  151. package/dist/diagnostic-redaction.js.map +1 -0
  152. package/dist/doctor.d.ts +8 -0
  153. package/dist/doctor.js +52 -0
  154. package/dist/doctor.js.map +1 -1
  155. package/dist/e2e-isolation.js.map +1 -1
  156. package/dist/event-log.js.map +1 -1
  157. package/dist/export-import.js +7 -5
  158. package/dist/export-import.js.map +1 -1
  159. package/dist/fleet-control-audit.js.map +1 -1
  160. package/dist/fleet-label.js.map +1 -1
  161. package/dist/fleet-level-config.d.ts +1 -1
  162. package/dist/fleet-level-config.js +2 -0
  163. package/dist/fleet-level-config.js.map +1 -1
  164. package/dist/fleet-lock.js +3 -2
  165. package/dist/fleet-lock.js.map +1 -1
  166. package/dist/fleet-manager.d.ts +249 -13
  167. package/dist/fleet-manager.js +1561 -292
  168. package/dist/fleet-manager.js.map +1 -1
  169. package/dist/fleet-process-handlers.d.ts +18 -0
  170. package/dist/fleet-process-handlers.js +70 -0
  171. package/dist/fleet-process-handlers.js.map +1 -0
  172. package/dist/fleet-startup.d.ts +14 -0
  173. package/dist/fleet-startup.js +30 -0
  174. package/dist/fleet-startup.js.map +1 -0
  175. package/dist/fleet-traffic.js.map +1 -1
  176. package/dist/forged-envelope-scan.d.ts +0 -20
  177. package/dist/forged-envelope-scan.js +3 -2
  178. package/dist/forged-envelope-scan.js.map +1 -1
  179. package/dist/full-restart.d.ts +5 -5
  180. package/dist/full-restart.js +5 -5
  181. package/dist/full-restart.js.map +1 -1
  182. package/dist/general-knowledge/skills/bug-report/SKILL.md +31 -0
  183. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +52 -11
  184. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +5 -1
  185. package/dist/general-knowledge/skills/fleet-config/SKILL.md +3 -1
  186. package/dist/general-knowledge/skills/fleet-restart/SKILL.md +8 -9
  187. package/dist/general-knowledge/skills/multi-channel/SKILL.md +1 -1
  188. package/dist/general-knowledge/skills/persona-emoji/SKILL.md +1 -1
  189. package/dist/general-knowledge/skills/scheduling/SKILL.md +4 -0
  190. package/dist/general-knowledge/skills/web-appearance/SKILL.canvas-worker-off.md +321 -0
  191. package/dist/general-knowledge/skills/web-appearance/SKILL.md +24 -4
  192. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +553 -0
  193. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +154 -31
  194. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +1 -1
  195. package/dist/general-knowledge/steering/core-rules.md +1 -1
  196. package/dist/host-memory.js.map +1 -1
  197. package/dist/idle-gate-log.d.ts +19 -0
  198. package/dist/idle-gate-log.js +15 -0
  199. package/dist/idle-gate-log.js.map +1 -0
  200. package/dist/instance-config-impact.js +25 -4
  201. package/dist/instance-config-impact.js.map +1 -1
  202. package/dist/instance-lifecycle.d.ts +10 -1
  203. package/dist/instance-lifecycle.js +86 -25
  204. package/dist/instance-lifecycle.js.map +1 -1
  205. package/dist/instructions.d.ts +12 -6
  206. package/dist/instructions.js +19 -10
  207. package/dist/instructions.js.map +1 -1
  208. package/dist/kiro-compact-pane.js.map +1 -1
  209. package/dist/kiro-db-reader.js.map +1 -1
  210. package/dist/kiro-engine-status.js.map +1 -1
  211. package/dist/kiro-log-gate.js.map +1 -1
  212. package/dist/kiro-transcript-lane.js.map +1 -1
  213. package/dist/locale.js +428 -20
  214. package/dist/locale.js.map +1 -1
  215. package/dist/log-service.js.map +1 -1
  216. package/dist/log-steps.d.ts +1 -0
  217. package/dist/log-steps.js +15 -0
  218. package/dist/log-steps.js.map +1 -1
  219. package/dist/logger.js +3 -2
  220. package/dist/logger.js.map +1 -1
  221. package/dist/login-controller.d.ts +45 -0
  222. package/dist/login-controller.js +101 -5
  223. package/dist/login-controller.js.map +1 -1
  224. package/dist/login-flows.js +1 -1
  225. package/dist/login-manager.js.map +1 -1
  226. package/dist/login-shell.js +3 -1
  227. package/dist/login-shell.js.map +1 -1
  228. package/dist/ls-attach-resolve.d.ts +96 -0
  229. package/dist/ls-attach-resolve.js +180 -0
  230. package/dist/ls-attach-resolve.js.map +1 -0
  231. package/dist/ls-rows.d.ts +12 -0
  232. package/dist/ls-rows.js +83 -4
  233. package/dist/ls-rows.js.map +1 -1
  234. package/dist/memory-pressure.js.map +1 -1
  235. package/dist/muse-usage-relay.js.map +1 -1
  236. package/dist/needs-you-hub.d.ts +98 -1
  237. package/dist/needs-you-hub.js +313 -44
  238. package/dist/needs-you-hub.js.map +1 -1
  239. package/dist/needs-you-live.d.ts +5 -2
  240. package/dist/needs-you-live.js +6 -3
  241. package/dist/needs-you-live.js.map +1 -1
  242. package/dist/needs-you.d.ts +68 -3
  243. package/dist/needs-you.js +38 -4
  244. package/dist/needs-you.js.map +1 -1
  245. package/dist/network-family.js.map +1 -1
  246. package/dist/operator-control-paths.js +3 -4
  247. package/dist/operator-control-paths.js.map +1 -1
  248. package/dist/outbound-handlers.d.ts +3 -0
  249. package/dist/outbound-handlers.js +76 -19
  250. package/dist/outbound-handlers.js.map +1 -1
  251. package/dist/outbound-schemas.d.ts +106 -60
  252. package/dist/outbound-schemas.js +33 -10
  253. package/dist/outbound-schemas.js.map +1 -1
  254. package/dist/outbox-open-error.js.map +1 -1
  255. package/dist/pane-context-cache.js.map +1 -1
  256. package/dist/paths.js.map +1 -1
  257. package/dist/private-dir.js.map +1 -1
  258. package/dist/probe-worker-pool.js.map +1 -1
  259. package/dist/profile-control.js.map +1 -1
  260. package/dist/prompt-file-ref.js.map +1 -1
  261. package/dist/provider-probe.js.map +1 -1
  262. package/dist/provider-secret-registry.d.ts +5 -3
  263. package/dist/provider-secret-registry.js +8 -2
  264. package/dist/provider-secret-registry.js.map +1 -1
  265. package/dist/public-link-auto-open.d.ts +32 -0
  266. package/dist/public-link-auto-open.js +86 -0
  267. package/dist/public-link-auto-open.js.map +1 -0
  268. package/dist/public-link-owner.d.ts +10 -0
  269. package/dist/public-link-owner.js +36 -0
  270. package/dist/public-link-owner.js.map +1 -0
  271. package/dist/public-link-progress.d.ts +12 -1
  272. package/dist/public-link-progress.js +5 -10
  273. package/dist/public-link-progress.js.map +1 -1
  274. package/dist/public-preview.js.map +1 -1
  275. package/dist/public-tunnel-config.d.ts +11 -0
  276. package/dist/public-tunnel-config.js +68 -0
  277. package/dist/public-tunnel-config.js.map +1 -0
  278. package/dist/public-web-gateway.d.ts +2 -0
  279. package/dist/public-web-gateway.js +19 -6
  280. package/dist/public-web-gateway.js.map +1 -1
  281. package/dist/public-web-link.d.ts +48 -3
  282. package/dist/public-web-link.js +177 -51
  283. package/dist/public-web-link.js.map +1 -1
  284. package/dist/quickstart-api.d.ts +9 -0
  285. package/dist/quickstart-api.js +82 -0
  286. package/dist/quickstart-api.js.map +1 -1
  287. package/dist/quickstart.d.ts +5 -0
  288. package/dist/quickstart.js +40 -25
  289. package/dist/quickstart.js.map +1 -1
  290. package/dist/reply-buttons-controller.js.map +1 -1
  291. package/dist/reply-dedup.js.map +1 -1
  292. package/dist/resource-report.js +1 -1
  293. package/dist/resource-report.js.map +1 -1
  294. package/dist/restart-guard.js.map +1 -1
  295. package/dist/restart-progress.d.ts +80 -1
  296. package/dist/restart-progress.js +306 -21
  297. package/dist/restart-progress.js.map +1 -1
  298. package/dist/rollout-index.js.map +1 -1
  299. package/dist/runtime-cpu-profile.js.map +1 -1
  300. package/dist/scheduler/db.d.ts +40 -13
  301. package/dist/scheduler/db.js +123 -89
  302. package/dist/scheduler/db.js.map +1 -1
  303. package/dist/scheduler/scheduler.js.map +1 -1
  304. package/dist/scheduler/task-board-text.d.ts +26 -0
  305. package/dist/scheduler/task-board-text.js +140 -0
  306. package/dist/scheduler/task-board-text.js.map +1 -0
  307. package/dist/scheduler/task-board.d.ts +730 -0
  308. package/dist/scheduler/task-board.js +2179 -0
  309. package/dist/scheduler/task-board.js.map +1 -0
  310. package/dist/scheduler/types.d.ts +6 -1
  311. package/dist/sd-notify.js +2 -1
  312. package/dist/sd-notify.js.map +1 -1
  313. package/dist/search/search-docs.d.ts +58 -0
  314. package/dist/search/search-docs.js +63 -0
  315. package/dist/search/search-docs.js.map +1 -0
  316. package/dist/search/search-index.d.ts +141 -0
  317. package/dist/search/search-index.js +440 -0
  318. package/dist/search/search-index.js.map +1 -0
  319. package/dist/search/search-store.d.ts +122 -0
  320. package/dist/search/search-store.js +236 -0
  321. package/dist/search/search-store.js.map +1 -0
  322. package/dist/search/search-worker.d.ts +1 -0
  323. package/dist/search/search-worker.js +163 -0
  324. package/dist/search/search-worker.js.map +1 -0
  325. package/dist/secret-file.js.map +1 -1
  326. package/dist/secret-store.d.ts +2 -0
  327. package/dist/secret-store.js +21 -0
  328. package/dist/secret-store.js.map +1 -1
  329. package/dist/service-installer.d.ts +75 -0
  330. package/dist/service-installer.js +206 -0
  331. package/dist/service-installer.js.map +1 -1
  332. package/dist/service-owner.d.ts +44 -0
  333. package/dist/service-owner.js +48 -0
  334. package/dist/service-owner.js.map +1 -0
  335. package/dist/service-plan.d.ts +39 -2
  336. package/dist/service-plan.js +188 -6
  337. package/dist/service-plan.js.map +1 -1
  338. package/dist/session-oom.d.ts +21 -0
  339. package/dist/session-oom.js +139 -0
  340. package/dist/session-oom.js.map +1 -0
  341. package/dist/settings-api.d.ts +13 -0
  342. package/dist/settings-api.js +73 -12
  343. package/dist/settings-api.js.map +1 -1
  344. package/dist/settings-baseline.js.map +1 -1
  345. package/dist/settings-change.d.ts +5 -0
  346. package/dist/settings-change.js +167 -6
  347. package/dist/settings-change.js.map +1 -1
  348. package/dist/settings-confirmation.d.ts +9 -1
  349. package/dist/settings-confirmation.js +18 -8
  350. package/dist/settings-confirmation.js.map +1 -1
  351. package/dist/settings-control.js +2 -1
  352. package/dist/settings-control.js.map +1 -1
  353. package/dist/settings-effect.js +29 -4
  354. package/dist/settings-effect.js.map +1 -1
  355. package/dist/settings-http-confirmation.d.ts +13 -0
  356. package/dist/settings-http-confirmation.js +15 -3
  357. package/dist/settings-http-confirmation.js.map +1 -1
  358. package/dist/settings-instance-patch.js +13 -1
  359. package/dist/settings-instance-patch.js.map +1 -1
  360. package/dist/settings-request-capability.d.ts +4 -1
  361. package/dist/settings-request-capability.js +11 -3
  362. package/dist/settings-request-capability.js.map +1 -1
  363. package/dist/settings-transaction.js.map +1 -1
  364. package/dist/setup-auth.d.ts +1 -0
  365. package/dist/setup-auth.js +2 -1
  366. package/dist/setup-auth.js.map +1 -1
  367. package/dist/setup-form.d.ts +1 -1
  368. package/dist/setup-form.js +23 -7
  369. package/dist/setup-form.js.map +1 -1
  370. package/dist/setup-host.d.ts +6 -1
  371. package/dist/setup-host.js +15 -4
  372. package/dist/setup-host.js.map +1 -1
  373. package/dist/setup-wizard.js +6 -3
  374. package/dist/setup-wizard.js.map +1 -1
  375. package/dist/spawn-gate.d.ts +5 -0
  376. package/dist/spawn-gate.js +28 -8
  377. package/dist/spawn-gate.js.map +1 -1
  378. package/dist/start-attempt.d.ts +1 -0
  379. package/dist/start-attempt.js +18 -0
  380. package/dist/start-attempt.js.map +1 -0
  381. package/dist/start-failure-notice.d.ts +4 -0
  382. package/dist/start-failure-notice.js +20 -0
  383. package/dist/start-failure-notice.js.map +1 -0
  384. package/dist/start-failure.d.ts +147 -0
  385. package/dist/start-failure.js +281 -0
  386. package/dist/start-failure.js.map +1 -0
  387. package/dist/startup-memory-deadline.d.ts +23 -0
  388. package/dist/startup-memory-deadline.js +55 -0
  389. package/dist/startup-memory-deadline.js.map +1 -0
  390. package/dist/statusline-watcher.js.map +1 -1
  391. package/dist/storm-window.js.map +1 -1
  392. package/dist/systemd-peer-bus.js.map +1 -1
  393. package/dist/target-queue-worker.js.map +1 -1
  394. package/dist/task-board-summary.d.ts +101 -0
  395. package/dist/task-board-summary.js +177 -0
  396. package/dist/task-board-summary.js.map +1 -0
  397. package/dist/task-board-tool.d.ts +25 -0
  398. package/dist/task-board-tool.js +155 -0
  399. package/dist/task-board-tool.js.map +1 -0
  400. package/dist/task-board-waker.d.ts +51 -0
  401. package/dist/task-board-waker.js +141 -0
  402. package/dist/task-board-waker.js.map +1 -0
  403. package/dist/tips.js.map +1 -1
  404. package/dist/tmux-control.js +1 -1
  405. package/dist/tmux-control.js.map +1 -1
  406. package/dist/tmux-exec-helper.d.ts +104 -0
  407. package/dist/tmux-exec-helper.js +412 -0
  408. package/dist/tmux-exec-helper.js.map +1 -0
  409. package/dist/tmux-manager.d.ts +1 -0
  410. package/dist/tmux-manager.js +29 -13
  411. package/dist/tmux-manager.js.map +1 -1
  412. package/dist/tmux-read.js +2 -2
  413. package/dist/tmux-read.js.map +1 -1
  414. package/dist/tool-permissions.js.map +1 -1
  415. package/dist/tool-progress.js.map +1 -1
  416. package/dist/topic-archiver.js.map +1 -1
  417. package/dist/topic-commands.d.ts +13 -0
  418. package/dist/topic-commands.js +56 -33
  419. package/dist/topic-commands.js.map +1 -1
  420. package/dist/topic-link.js.map +1 -1
  421. package/dist/transcript-delta-reader.js.map +1 -1
  422. package/dist/transcript-jsonl.js.map +1 -1
  423. package/dist/transcript-monitor.js +5 -0
  424. package/dist/transcript-monitor.js.map +1 -1
  425. package/dist/transcript-sources.d.ts +2 -0
  426. package/dist/transcript-sources.js +4 -0
  427. package/dist/transcript-sources.js.map +1 -1
  428. package/dist/tunnel/cloudflared-install.js +3 -1
  429. package/dist/tunnel/cloudflared-install.js.map +1 -1
  430. package/dist/tunnel/cloudflared.d.ts +7 -16
  431. package/dist/tunnel/cloudflared.js +100 -13
  432. package/dist/tunnel/cloudflared.js.map +1 -1
  433. package/dist/tunnel/lease.js +3 -2
  434. package/dist/tunnel/lease.js.map +1 -1
  435. package/dist/tunnel/manager.js.map +1 -1
  436. package/dist/tunnel/named-diagnostics.d.ts +13 -0
  437. package/dist/tunnel/named-diagnostics.js +52 -0
  438. package/dist/tunnel/named-diagnostics.js.map +1 -0
  439. package/dist/tunnel/public-fetch.js.map +1 -1
  440. package/dist/tunnel/purpose-lane.js.map +1 -1
  441. package/dist/tunnel/types.d.ts +4 -1
  442. package/dist/tunnel/types.js.map +1 -1
  443. package/dist/types.d.ts +55 -2
  444. package/dist/ui/app-effects.js +23 -5
  445. package/dist/ui/app.html +2 -0
  446. package/dist/ui/backend-signin.js +297 -0
  447. package/dist/ui/chat-effects.js +10 -3
  448. package/dist/ui/chat-msg-actions.js +176 -0
  449. package/dist/ui/chat-render.js +62 -18
  450. package/dist/ui/chat-store.js +218 -48
  451. package/dist/ui/chat-strings.js +40 -8
  452. package/dist/ui/chat-thread.js +119 -10
  453. package/dist/ui/effect-trial-offer.js +2 -31
  454. package/dist/ui/effects-waker.js +2 -0
  455. package/dist/ui/fleet-canvas-layout.js +13 -4
  456. package/dist/ui/fleet-canvas.js +69 -28
  457. package/dist/ui/fleet-office-editor.js +149 -0
  458. package/dist/ui/fleet-office-model.js +358 -0
  459. package/dist/ui/fleet-office-pack.js +199 -0
  460. package/dist/ui/fleet-office-skin.js +474 -0
  461. package/dist/ui/fleet-office-store.js +49 -0
  462. package/dist/ui/fleet-office-zones.js +177 -0
  463. package/dist/ui/fleet-office.js +434 -0
  464. package/dist/ui/fleet-org.js +18 -5
  465. package/dist/ui/fleet-route-scene.js +272 -0
  466. package/dist/ui/fleet-route-worker.js +21 -0
  467. package/dist/ui/fleet-route.js +88 -0
  468. package/dist/ui/fleet-task-card.js +436 -0
  469. package/dist/ui/fleet-task-common.js +482 -0
  470. package/dist/ui/fleet-task-work.js +107 -0
  471. package/dist/ui/fleet-tasks.js +569 -0
  472. package/dist/ui/fleet-traffic.js +81 -18
  473. package/dist/ui/fleet-usage.js +36 -2
  474. package/dist/ui/guest-refused.html +41 -0
  475. package/dist/ui/icons/icon-192.png +0 -0
  476. package/dist/ui/icons/icon-512.png +0 -0
  477. package/dist/ui/image-lightbox.js +45 -1
  478. package/dist/ui/image-zoom.js +242 -0
  479. package/dist/ui/panel-chat.js +200 -56
  480. package/dist/ui/panel-details.js +2 -0
  481. package/dist/ui/panel-fleet.js +32 -71
  482. package/dist/ui/panel-needs.js +23 -6
  483. package/dist/ui/panel-settings.js +158 -32
  484. package/dist/ui/preview-panel.js +2 -1
  485. package/dist/ui/search-dialog.js +350 -0
  486. package/dist/ui/settings-appearance.js +170 -10
  487. package/dist/ui/settings-apply.js +3 -3
  488. package/dist/ui/settings-confirm.js +7 -2
  489. package/dist/ui/settings-dialogs.js +46 -21
  490. package/dist/ui/settings-discord.js +6 -2
  491. package/dist/ui/settings-guests.js +2 -1
  492. package/dist/ui/settings-learn.js +1 -0
  493. package/dist/ui/settings-model.js +51 -18
  494. package/dist/ui/settings-mods.js +8 -3
  495. package/dist/ui/settings-owner-effects.js +6 -4
  496. package/dist/ui/settings-strings.js +180 -6
  497. package/dist/ui/settings-token.js +3 -1
  498. package/dist/ui/settings-web-authority.js +13 -5
  499. package/dist/ui/settings-wizard.js +13 -7
  500. package/dist/ui/shared/agend-auth.js +2 -0
  501. package/dist/ui/shared/app-i18n.js +12 -5
  502. package/dist/ui/shared/app-needs.js +2 -2
  503. package/dist/ui/shared/app-prefs.js +15 -0
  504. package/dist/ui/shared/app-route.js +18 -0
  505. package/dist/ui/shared/app-shell.js +172 -28
  506. package/dist/ui/shared/app-store.js +3 -2
  507. package/dist/ui/shared/app-stream.js +5 -1
  508. package/dist/ui/shared/app-themes.js +116 -1
  509. package/dist/ui/shared/app.css +662 -48
  510. package/dist/ui/shared/app.js +9 -1
  511. package/dist/ui/shared/card-strings.js +4 -0
  512. package/dist/ui/shared/effect-app-runtime.js +7 -5
  513. package/dist/ui/shared/effect-chat-runtime.js +2 -7
  514. package/dist/ui/shared/effect-cursor.js +3 -2
  515. package/dist/ui/shared/effect-display-metadata.js +39 -0
  516. package/dist/ui/shared/effect-owner-source.js +8 -1
  517. package/dist/ui/shared/effect-registry.js +14 -4
  518. package/dist/ui/shared/effect-runtime-loader.js +3 -1
  519. package/dist/ui/shared/effect-stock-trust.js +5 -5
  520. package/dist/ui/shared/feature-flags.js +127 -0
  521. package/dist/ui/shared/guest-refused.js +35 -0
  522. package/dist/ui/shared/header-tools.js +2 -2
  523. package/dist/ui/shared/identity-card.js +26 -8
  524. package/dist/ui/shared/identity-color.js +17 -10
  525. package/dist/ui/shared/instance-nav.js +183 -28
  526. package/dist/ui/shared/owner-css.js +30 -1
  527. package/dist/ui/shared/panel-view.js +14 -29
  528. package/dist/ui/shared/text-undo.js +210 -0
  529. package/dist/ui/shared/tokens.css +4 -0
  530. package/dist/ui/shared/ui-dialog.js +5 -4
  531. package/dist/ui/shared/ui-icons.js +4 -0
  532. package/dist/ui/shared/ui-menu.js +20 -7
  533. package/dist/ui/shared/ui-toast.js +16 -3
  534. package/dist/ui/shared/usage-dialog.js +206 -16
  535. package/dist/ui/shared/view-strings.js +68 -4
  536. package/dist/ui/shared/web-authorize.js +50 -1
  537. package/dist/ui/signin.html +2 -0
  538. package/dist/ui/web-terminal/terminal.html +10 -10
  539. package/dist/ui/web-terminal/terminal.js +58 -12
  540. package/dist/update-check.js.map +1 -1
  541. package/dist/update-dispatch.js +1 -1
  542. package/dist/update-dispatch.js.map +1 -1
  543. package/dist/update-install.js.map +1 -1
  544. package/dist/update-launch.d.ts +10 -1
  545. package/dist/update-launch.js +39 -14
  546. package/dist/update-launch.js.map +1 -1
  547. package/dist/update-marker.js.map +1 -1
  548. package/dist/update-preflight.js +1 -1
  549. package/dist/update-preflight.js.map +1 -1
  550. package/dist/update-progress.d.ts +3 -1
  551. package/dist/update-progress.js +3 -1
  552. package/dist/update-progress.js.map +1 -1
  553. package/dist/usage/format-rich.js +6 -3
  554. package/dist/usage/format-rich.js.map +1 -1
  555. package/dist/usage/i18n-keys.d.ts +1 -1
  556. package/dist/usage/i18n-keys.js +2 -1
  557. package/dist/usage/i18n-keys.js.map +1 -1
  558. package/dist/usage/i18n.d.ts +13 -1
  559. package/dist/usage/i18n.js +29 -0
  560. package/dist/usage/i18n.js.map +1 -1
  561. package/dist/usage/providers.d.ts +89 -2
  562. package/dist/usage/providers.js +247 -41
  563. package/dist/usage/providers.js.map +1 -1
  564. package/dist/usage/statusline-usage.js.map +1 -1
  565. package/dist/usage/usage-api.d.ts +5 -1
  566. package/dist/usage/usage-api.js +185 -58
  567. package/dist/usage/usage-api.js.map +1 -1
  568. package/dist/usage-report.js.map +1 -1
  569. package/dist/usage-rollup.js +1 -1
  570. package/dist/usage-rollup.js.map +1 -1
  571. package/dist/user-systemd.d.ts +24 -0
  572. package/dist/user-systemd.js +75 -0
  573. package/dist/user-systemd.js.map +1 -0
  574. package/dist/view-api.d.ts +7 -0
  575. package/dist/view-api.js +16 -4
  576. package/dist/view-api.js.map +1 -1
  577. package/dist/wake-coordinator.js.map +1 -1
  578. package/dist/web-ambient.js.map +1 -1
  579. package/dist/web-api.d.ts +57 -10
  580. package/dist/web-api.js +1103 -70
  581. package/dist/web-api.js.map +1 -1
  582. package/dist/web-app-icon-worker.d.ts +1 -0
  583. package/dist/web-app-icon-worker.js +16 -0
  584. package/dist/web-app-icon-worker.js.map +1 -0
  585. package/dist/web-app-icon.d.ts +109 -0
  586. package/dist/web-app-icon.js +257 -0
  587. package/dist/web-app-icon.js.map +1 -0
  588. package/dist/web-appearance-policy.js.map +1 -1
  589. package/dist/web-appearance-tools.d.ts +1 -0
  590. package/dist/web-appearance-tools.js +7 -3
  591. package/dist/web-appearance-tools.js.map +1 -1
  592. package/dist/web-appearance-view.js.map +1 -1
  593. package/dist/web-appearance.d.ts +4 -10
  594. package/dist/web-appearance.js +7 -4
  595. package/dist/web-appearance.js.map +1 -1
  596. package/dist/web-auth.d.ts +8 -4
  597. package/dist/web-auth.js +33 -21
  598. package/dist/web-auth.js.map +1 -1
  599. package/dist/web-backend-login.d.ts +130 -0
  600. package/dist/web-backend-login.js +225 -0
  601. package/dist/web-backend-login.js.map +1 -0
  602. package/dist/web-card.js.map +1 -1
  603. package/dist/web-channel-echo.js.map +1 -1
  604. package/dist/web-chat-history.d.ts +118 -8
  605. package/dist/web-chat-history.js +220 -25
  606. package/dist/web-chat-history.js.map +1 -1
  607. package/dist/web-commands.d.ts +5 -1
  608. package/dist/web-commands.js +1 -1
  609. package/dist/web-commands.js.map +1 -1
  610. package/dist/web-cookie-names.d.ts +5 -0
  611. package/dist/web-cookie-names.js +12 -0
  612. package/dist/web-cookie-names.js.map +1 -0
  613. package/dist/web-effect-bundle.d.ts +2 -2
  614. package/dist/web-effect-library.js.map +1 -1
  615. package/dist/web-effect-playback.js.map +1 -1
  616. package/dist/web-effect-profiles.d.ts +16 -16
  617. package/dist/web-effect-profiles.js +3 -2
  618. package/dist/web-effect-profiles.js.map +1 -1
  619. package/dist/web-effect-service.js.map +1 -1
  620. package/dist/web-effect-tools.js.map +1 -1
  621. package/dist/web-effect-trials.js.map +1 -1
  622. package/dist/web-effect-upload.js.map +1 -1
  623. package/dist/web-effect.d.ts +6 -6
  624. package/dist/web-effect.js.map +1 -1
  625. package/dist/web-experimental-http.d.ts +2 -0
  626. package/dist/web-experimental-http.js +12 -0
  627. package/dist/web-experimental-http.js.map +1 -0
  628. package/dist/web-experimental.d.ts +11 -0
  629. package/dist/web-experimental.js +49 -0
  630. package/dist/web-experimental.js.map +1 -0
  631. package/dist/web-guest-access.d.ts +4 -2
  632. package/dist/web-guest-access.js +22 -10
  633. package/dist/web-guest-access.js.map +1 -1
  634. package/dist/web-guest-rooms.js.map +1 -1
  635. package/dist/web-host-guard.d.ts +11 -0
  636. package/dist/web-host-guard.js +11 -0
  637. package/dist/web-host-guard.js.map +1 -1
  638. package/dist/web-icons.d.ts +33 -1
  639. package/dist/web-icons.js +83 -11
  640. package/dist/web-icons.js.map +1 -1
  641. package/dist/web-identity-color.js.map +1 -1
  642. package/dist/web-image-meta.d.ts +13 -0
  643. package/dist/web-image-meta.js +62 -0
  644. package/dist/web-image-meta.js.map +1 -1
  645. package/dist/web-images.js.map +1 -1
  646. package/dist/web-libavoid.d.ts +14 -0
  647. package/dist/web-libavoid.js +46 -0
  648. package/dist/web-libavoid.js.map +1 -0
  649. package/dist/web-login.js.map +1 -1
  650. package/dist/web-mentions.js.map +1 -1
  651. package/dist/web-mod-agent.js.map +1 -1
  652. package/dist/web-mod-store.d.ts +2 -0
  653. package/dist/web-mod-store.js +10 -0
  654. package/dist/web-mod-store.js.map +1 -1
  655. package/dist/web-office-store.d.ts +104 -0
  656. package/dist/web-office-store.js +236 -0
  657. package/dist/web-office-store.js.map +1 -0
  658. package/dist/web-owner-css-store.d.ts +44 -1
  659. package/dist/web-owner-css-store.js +70 -9
  660. package/dist/web-owner-css-store.js.map +1 -1
  661. package/dist/web-owner-css.js.map +1 -1
  662. package/dist/web-png.d.ts +33 -0
  663. package/dist/web-png.js +329 -0
  664. package/dist/web-png.js.map +1 -0
  665. package/dist/web-prefs.d.ts +6 -0
  666. package/dist/web-prefs.js +29 -3
  667. package/dist/web-prefs.js.map +1 -1
  668. package/dist/web-preview.d.ts +2 -0
  669. package/dist/web-preview.js +2 -0
  670. package/dist/web-preview.js.map +1 -1
  671. package/dist/web-profile.js.map +1 -1
  672. package/dist/web-reactions.d.ts +35 -0
  673. package/dist/web-reactions.js +47 -0
  674. package/dist/web-reactions.js.map +1 -0
  675. package/dist/web-request-context.d.ts +5 -0
  676. package/dist/web-request-context.js.map +1 -1
  677. package/dist/web-returning-device.d.ts +4 -2
  678. package/dist/web-returning-device.js +13 -6
  679. package/dist/web-returning-device.js.map +1 -1
  680. package/dist/web-schedules.js.map +1 -1
  681. package/dist/web-send-dedupe.d.ts +36 -0
  682. package/dist/web-send-dedupe.js +69 -0
  683. package/dist/web-send-dedupe.js.map +1 -0
  684. package/dist/web-session-authorization.js.map +1 -1
  685. package/dist/web-session.js +1 -1
  686. package/dist/web-session.js.map +1 -1
  687. package/dist/web-settings-patch.d.ts +3 -1
  688. package/dist/web-settings-patch.js +11 -5
  689. package/dist/web-settings-patch.js.map +1 -1
  690. package/dist/web-settings-tools.d.ts +6 -0
  691. package/dist/web-settings-tools.js +52 -17
  692. package/dist/web-settings-tools.js.map +1 -1
  693. package/dist/web-terminal-http.js +15 -2
  694. package/dist/web-terminal-http.js.map +1 -1
  695. package/dist/web-terminal.js +1 -1
  696. package/dist/web-terminal.js.map +1 -1
  697. package/dist/web-theme-store.d.ts +2 -2
  698. package/dist/web-theme-store.js +6 -3
  699. package/dist/web-theme-store.js.map +1 -1
  700. package/dist/web-theme.d.ts +29 -0
  701. package/dist/web-theme.js +89 -1
  702. package/dist/web-theme.js.map +1 -1
  703. package/dist/web-upload.d.ts +18 -1
  704. package/dist/web-upload.js +45 -7
  705. package/dist/web-upload.js.map +1 -1
  706. package/dist/web-widget.js.map +1 -1
  707. package/dist/webhook-emitter.js.map +1 -1
  708. package/dist/windows-shortcuts.d.ts +78 -0
  709. package/dist/windows-shortcuts.js +203 -0
  710. package/dist/windows-shortcuts.js.map +1 -0
  711. package/dist/workflow-templates/default.md +3 -0
  712. package/dist/ws-server.js.map +1 -1
  713. package/dist/yaml-shared-nodes.d.ts +25 -0
  714. package/dist/yaml-shared-nodes.js +185 -0
  715. package/dist/yaml-shared-nodes.js.map +1 -0
  716. package/launcher/agend-install.cjs +828 -0
  717. package/launcher/install-admission.cjs +4 -39
  718. package/launcher/postinstall.cjs +14 -12
  719. package/launcher/private-home.cjs +43 -0
  720. package/package.json +16 -13
  721. package/scripts/preinstall-guard.cjs +2 -5
  722. package/templates/windows-shortcuts.ps1 +181 -0
  723. package/launcher/old-updater-note.cjs +0 -48
@@ -178,8 +178,17 @@ function endOf(L, name) {
178
178
 
179
179
  // ── Routing: lanes the layout keeps empty ──
180
180
 
181
- /** A side lane's offset from the middle of the box's padding: at least 6 px from the box's border and from its cards. */
182
- const laneOffset = (o) => Math.max(-OC.PAD / 2 + 6, Math.min(OC.PAD / 2 - 6, o * 0.5));
181
+ /**
182
+ * A side lane's offset from the middle of the box's padding: at least 6 px from the box's border and from its cards.
183
+ * Negative: toward the border, on either side (#2329) — the first lines (o < 0) leave the cards a long last run for a head.
184
+ */
185
+ const laneOffset = (o, PAD = OC.PAD) => Math.max(-PAD / 2 + 6, Math.min(PAD / 2 - 6, o * 0.5));
186
+ const leftLaneOf = (b, io, PAD) => b.x + PAD / 2 + io;
187
+ const rightLaneOf = (b, io, PAD) => b.x + b.w - PAD / 2 - io;
188
+ /** Where a line meets a card's side, from the side's middle (#2329): its full offset, a head's width from its neighbours. */
189
+ const sideOffset = (o) => Math.max(-OC.NH / 2 + 7, Math.min(OC.NH / 2 - 7, o));
190
+ /** The layout's own box padding and gutter (a phone's are narrower). */
191
+ const spacing = (L) => ({ PAD: L.pad ?? OC.PAD, GAP: L.gap ?? OC.GAP });
183
192
 
184
193
  const firstBusOf = (L) => Math.min(...L.boxes.map((b) => b.busY));
185
194
  /** From an end up to its row's bus lane: the points, and the lane's y. `o` spreads lines sharing a lane. */
@@ -194,17 +203,17 @@ function outward(L, end, o) {
194
203
  const x = b.x + b.w / 2 + o * 1.5;
195
204
  return { pts: [{ x, y: b.y }, { x, y: b.busY + o }], bus: b.busY + o };
196
205
  }
197
- const { PAD, GAP, NW, NH } = OC;
206
+ const { NW, NH } = OC, { PAD, GAP } = spacing(L);
198
207
  const card = end.c, col = end.i % b.cols, rowI = Math.floor(end.i / b.cols), lines = Math.ceil(b.cells.length / b.cols);
199
- const io = laneOffset(o);
200
- const leftLane = b.x + PAD / 2 + io, rightLane = b.x + b.w - PAD / 2 + io;
208
+ const io = laneOffset(o, PAD);
209
+ const leftLane = leftLaneOf(b, io, PAD), rightLane = rightLaneOf(b, io, PAD);
201
210
  let pts;
202
211
  if (col === 0 || col === b.cols - 1) {
203
- const left = col === 0, lane = left ? leftLane : rightLane, cy = card.y + NH / 2 + io;
212
+ const left = col === 0, lane = left ? leftLane : rightLane, cy = card.y + NH / 2 + sideOffset(o);
204
213
  pts = [{ x: left ? card.x : card.x + NW, y: cy }, { x: lane, y: cy }, { x: lane, y: b.y }];
205
214
  } else {
206
215
  // A middle column: down into the gutter below its row, across to the nearer side lane.
207
- const gy = card.y + NH + (rowI === lines - 1 ? PAD : GAP) / 2 + io / 2, cx = card.x + NW / 2 + o;
216
+ const gy = card.y + NH + (rowI === lines - 1 ? PAD : GAP) / 2 - io / 2, cx = card.x + NW / 2 + o;
208
217
  const lane = cx - b.x < b.w / 2 ? leftLane : rightLane;
209
218
  pts = [{ x: cx, y: card.y + NH }, { x: cx, y: gy }, { x: lane, y: gy }, { x: lane, y: b.y }];
210
219
  }
@@ -217,23 +226,23 @@ function outward(L, end, o) {
217
226
  * else down into the gutter under its row (no card is there) and across. The last point is on the lane.
218
227
  */
219
228
  function toLane(L, end, lane, o) {
220
- const { PAD, GAP, NW, NH } = OC;
229
+ const { NW, NH } = OC, { PAD, GAP } = spacing(L);
221
230
  const b = end.b, card = end.c, col = end.i % b.cols, rowI = Math.floor(end.i / b.cols), lines = Math.ceil(b.cells.length / b.cols);
222
- const io = laneOffset(o);
231
+ const io = laneOffset(o, PAD);
223
232
  const left = lane < b.x + b.w / 2;
224
233
  if ((left && col === 0) || (!left && col === b.cols - 1)) {
225
- const cy = card.y + NH / 2 + io;
234
+ const cy = card.y + NH / 2 + sideOffset(o);
226
235
  return [{ x: left ? card.x : card.x + NW, y: cy }, { x: lane, y: cy }];
227
236
  }
228
- const gy = card.y + NH + (rowI === lines - 1 ? PAD : GAP) / 2 + io / 2, cx = card.x + NW / 2 + o;
237
+ const gy = card.y + NH + (rowI === lines - 1 ? PAD : GAP) / 2 - io / 2, cx = card.x + NW / 2 + o;
229
238
  return [{ x: cx, y: card.y + NH }, { x: cx, y: gy }, { x: lane, y: gy }];
230
239
  }
231
240
  /** Two cards in one open box: joined inside it, through the side lane nearer to them both (never out to the bus). */
232
241
  function inBox(L, a, z, o) {
233
- const b = a.b, { PAD } = OC;
234
- const io = laneOffset(o);
242
+ const b = a.b, { PAD } = spacing(L);
243
+ const io = laneOffset(o, PAD);
235
244
  const mid = ((a.i % b.cols) + (z.i % b.cols)) / 2;
236
- const lane = mid <= (b.cols - 1) / 2 ? b.x + PAD / 2 + io : b.x + b.w - PAD / 2 + io;
245
+ const lane = mid <= (b.cols - 1) / 2 ? leftLaneOf(b, io, PAD) : rightLaneOf(b, io, PAD);
237
246
  return [...toLane(L, a, lane, o), ...toLane(L, z, lane, o).reverse()];
238
247
  }
239
248
  /** Two of General's cards: under them, through the gap above the first row (or the canvas's empty space below). */
@@ -278,6 +287,42 @@ export function badgeAt(pts) {
278
287
  return best;
279
288
  }
280
289
 
290
+ // ── Arrowheads (#2329) ──
291
+
292
+ /** A line's stroke width (px) by its kind and phase: the work's and its report's are heavier, a team pair's the most. */
293
+ export const strokeOf = (hue, phase) => (phase === "work" || phase === "return" ? 3 : hue === "team" ? 3.5 : 2.5);
294
+ /**
295
+ * The arrowhead and the ends. In stroke widths (the markers' units, fleet-canvas.js): a head LEN long and WIDE across,
296
+ * the line's own stroke ending BACK inside it (its round cap under the head, never out past the tip); the source dot
297
+ * DOT across. In px: the tip GAP short of the card, the dot's centre START off its card; a head only on a last straight
298
+ * run that holds it and ROOM of line before it — else the line ends GAP short with none (its dot and pulse show the way).
299
+ */
300
+ export const HEAD = { LEN: 2.8, WIDE: 2.3, BACK: 1.4, DOT: 2, GAP: 3.5, START: 5, ROOM: 1 };
301
+ /** Whether a line of stroke `w` px whose last straight run is `run` px long gets a head. */
302
+ export const headFits = (run, w) => run >= HEAD.GAP + HEAD.LEN * w + HEAD.ROOM;
303
+ /** An orthogonal polyline without repeated points or points in the middle of a straight run. */
304
+ function corners(pts) {
305
+ const q = pts.filter((pt, i) => !i || pt.x !== pts[i - 1].x || pt.y !== pts[i - 1].y).map((pt) => ({ x: pt.x, y: pt.y }));
306
+ return q.filter((pt, i) => i === 0 || i === q.length - 1 || !((q[i - 1].x === pt.x && pt.x === q[i + 1].x) || (q[i - 1].y === pt.y && pt.y === q[i + 1].y)));
307
+ }
308
+ /** Route `pts` as drawn: its start moved `a` px along its first run, its end `b` px back along its last (each keeps 1 px). */
309
+ export function trimEnds(pts, a, b) {
310
+ const q = corners(pts);
311
+ if (q.length < 2) return q;
312
+ const pull = (i, j, by) => {
313
+ const l = Math.hypot(q[j].x - q[i].x, q[j].y - q[i].y), t = Math.max(0, Math.min(by, l - 1)) / l;
314
+ q[i] = { x: q[i].x + (q[j].x - q[i].x) * t, y: q[i].y + (q[j].y - q[i].y) * t };
315
+ };
316
+ pull(0, 1, a);
317
+ pull(q.length - 1, q.length - 2, b);
318
+ return q;
319
+ }
320
+ /** The length of a route's last straight run. */
321
+ export function lastRun(pts) {
322
+ const q = corners(pts);
323
+ return q.length < 2 ? 0 : Math.hypot(q[q.length - 1].x - q[q.length - 2].x, q[q.length - 1].y - q[q.length - 2].y);
324
+ }
325
+
281
326
  // ── Edges ──
282
327
 
283
328
  /** The colour family of a kind: task, report, broadcast, and the rest (send, query, update). */
@@ -289,9 +334,14 @@ const settled = (phase) => (phase === "fading" || phase === "stale" ? "fading" :
289
334
 
290
335
  /**
291
336
  * The lines to draw from the store's items on layout `L`. `far`: zoomed out (one line per team pair). Each edge:
292
- * { key, hue, phase, n, from, to, d, pts, badge } — `from`/`to` the instance (or team) names it joins, for search.
337
+ * { key, hue, phase, n, from, to, d, pts, w, head, badge, a, z, routed } — `from`/`to` the instance (or team) names it
338
+ * joins, for search; `pts` its route, end to end (on the cards); `d` the path drawn, its ends trimmed off the cards for
339
+ * the dot and the head (#2329); `w` its stroke width; `head` whether it has a head (headFits);
340
+ * `a`/`z` its two ends (fleet-route-scene.js routes the ones on cards). `routed` (#2182): the router's points by
341
+ * line key (fleet-route.js); a line it has none for keeps its lane route — or, while a new answer is on its way, the
342
+ * points the same two ends and kind had in the last one (a line changing phase keeps its place).
293
343
  */
294
- export function trafficEdges(items, L, { far = false, max = MAX_EDGES } = {}) {
344
+ export function trafficEdges(items, L, { far = false, max = MAX_EDGES, routed = null } = {}) {
295
345
  if (!L.boxes.length && L.tops.length < 2) return [];
296
346
  // A top's way out is the first row's bus: with no box there is none, so only top ↔ top lines (betweenTops) are drawn.
297
347
  const groups = new Map();
@@ -321,10 +371,23 @@ export function trafficEdges(items, L, { far = false, max = MAX_EDGES } = {}) {
321
371
  add(`${a.key}>${z.key}|${hue(it.kind)}|${phase}`, { a, z, hue: hue(it.kind), phase, n: 1, names });
322
372
  }
323
373
  const list = [...groups.entries()].sort((x, y) => RANK[x[1].phase] - RANK[y[1].phase]).slice(0, max);
374
+ const lent = new Set();
375
+ const routedBy = (key) => {
376
+ if (!routed || far) return null;
377
+ const exact = routed.get(key);
378
+ if (exact) return exact;
379
+ const kin = key.slice(0, key.lastIndexOf("|") + 1);
380
+ for (const [k, pts] of routed) if (k.startsWith(kin) && !groups.has(k) && !lent.has(k)) { lent.add(k); return pts; }
381
+ return null;
382
+ };
324
383
  return list.map(([key, g], i) => {
325
384
  const o = ((i % 7) - 3) * 7; // neighbours 7 px apart (within the ±22 px bus lane)
326
- const pts = route(L, g.a, g.z, o);
327
- return { key, hue: g.hue, phase: g.phase, n: g.n, names: [...new Set(g.names)], d: pathOf(pts), pts, badge: g.n > 1 ? badgeAt(pts) : null };
385
+ const viaRouter = routedBy(key);
386
+ const pts = viaRouter ?? route(L, g.a, g.z, o);
387
+ const w = strokeOf(g.hue, g.phase), head = headFits(lastRun(pts), w);
388
+ const d = pathOf(trimEnds(pts, HEAD.START, HEAD.GAP + (head ? HEAD.BACK * w : 0)));
389
+ return { key, hue: g.hue, phase: g.phase, n: g.n, names: [...new Set(g.names)], d, pts, w, head, badge: g.n > 1 ? badgeAt(pts) : null,
390
+ a: g.a, z: g.z, routed: !!viaRouter };
328
391
  });
329
392
  }
330
393
 
@@ -183,6 +183,38 @@ export function zonesChanged(report) {
183
183
  return zones.length > 1 || (zones.length === 1 && zones[0] !== report.zone) ? zones : null;
184
184
  }
185
185
 
186
+ /** Estimated pixel width of one character in the 9 px `.us-axis` label. */
187
+ const LABEL_CHAR_W = 5.5;
188
+
189
+ /**
190
+ * The 0-based indices of dates that should receive an x-axis label.
191
+ * Keeps the first (0) and last (n-1) when they are far enough apart not to
192
+ * overlap. When first and last are too close (n-1 < step), only the last
193
+ * (most recent) is shown — overlap-free takes precedence over keeping both
194
+ * ends. Intermediate labels are thinned so that no two consecutive shown
195
+ * labels are closer than `labelWidthPx` pixels apart.
196
+ *
197
+ * @param {number} n Number of data points (dates).
198
+ * @param {number} slotPx Pixels per slot (bar + gap).
199
+ * @param {number} labelWidthPx Estimated rendered width of one label.
200
+ * @returns {Set<number>}
201
+ */
202
+ export function thinXLabels(n, slotPx, labelWidthPx) {
203
+ if (n <= 0) return new Set();
204
+ if (n === 1) return new Set([0]);
205
+ // Minimum slot steps between label centres (+ 2 px breathing room).
206
+ const step = Math.max(1, Math.ceil((labelWidthPx + 2) / slotPx));
207
+ // If first and last are too close to both be shown without overlap, keep
208
+ // only the last (most recent). The caller's "always first+last" contract
209
+ // is relaxed here: overlap-free takes precedence.
210
+ if (n - 1 < step) return new Set([n - 1]);
211
+ const idx = [0];
212
+ // Add intermediates that are at least `step` slots away from n-1.
213
+ for (let i = step; i < n - step; i += step) idx.push(i);
214
+ idx.push(n - 1); // always include last
215
+ return new Set(idx);
216
+ }
217
+
186
218
  /** The trend's series: the top `TOP` agents by the measure over the range, then "others"; one value per date. */
187
219
  export function trendSeries(report, dates) {
188
220
  const priced = report.totals && report.totals.amount !== null && report.totals.amount > 0;
@@ -230,6 +262,8 @@ function Trend({ report, dates }) {
230
262
  const max = Math.max(...sums, 0) || 1;
231
263
  const what = s.priced ? tu("amount") : tu("trendTokens"), others = s.names.length > TOP;
232
264
  const label = others ? tu("trendLabel", what, TOP, dates[0], dates[dates.length - 1]) : tu("trendLabelAll", what, s.names.length, dates[0], dates[dates.length - 1]);
265
+ // Thin x-axis labels: "MM-DD" (5 chars) × LABEL_CHAR_W ≈ 27.5 px at 9 px sans-serif.
266
+ const shownLabels = thinXLabels(dates.length, slot, 5 * LABEL_CHAR_W);
233
267
  return html`<section class="us-trend card" aria-labelledby="us-trend-h">
234
268
  <h3 id="us-trend-h">${tu("trend")} <span class="note">· ${s.priced ? tu("amount") : tu("trendTokens")}</span></h3>
235
269
  <p class="note us-scale">${tu("scale", fmtMeasure(s.priced, max))}</p>
@@ -243,7 +277,7 @@ function Trend({ report, dates }) {
243
277
  const h = v[i] > 0 ? Math.max(1, ((base - top) * v[i]) / max) : 0;
244
278
  y -= h;
245
279
  return h ? html`<rect key=${k} class=${`us-s${k}`} x=${left + i * slot + 2} y=${y} width=${slot - 4} height=${h}><title>${`${date} · ${s.names[k]}: ${fmtMeasure(s.priced, v[i])}`}</title></rect>` : null;
246
- })}${(dates.length - 1 - i) % 7 === 0 || dates.length <= 8 ? html`<text class="us-axis" x=${left + i * slot + slot / 2} y=${H - 6} text-anchor="middle">${date.slice(5)}</text>` : null}</g>`;
280
+ })}${shownLabels.has(i) ? html`<text class="us-axis us-xlabel" x=${left + i * slot + slot / 2} y=${H - 6} text-anchor="middle">${date.slice(5)}</text>` : null}</g>`;
247
281
  })}
248
282
  </svg></div>
249
283
  <${EarlierHint} wide=${wide} />
@@ -287,7 +321,7 @@ function Row({ r, group, max, measure }) {
287
321
  const meta = [r.backends.join(", "), r.models.join(", "), r.profiles.join(", ")].filter(Boolean).join(" · ");
288
322
  return html`<tr class="us-row">
289
323
  <th scope="row" class="us-name"><span class="mono">${keyText(group, r.key)}</span>
290
- <svg class="us-bar" viewBox="0 0 100 6" preserveAspectRatio="none" aria-hidden="true"><rect class="us-bar-bg" width="100" height="6" rx="3" /><rect class="us-bar-fg" width=${Math.round(share * 100)} height="6" rx="3" /></svg>
324
+ <svg class="us-bar" aria-hidden="true"><rect class="us-bar-bg" width="100%" height="6" rx="3" /><rect class="us-bar-fg" width=${`${Math.round(share * 100)}%`} height="6" rx="3" /></svg>
291
325
  ${group !== "instance" || meta ? html`<span class="us-meta">${meta}</span>` : null}
292
326
  ${r.flags.length ? html`<span class="us-flags">${r.flags.map((f) => html`<span key=${f} class="ch-tag">${flagText(f, r)}</span>`)}</span>` : null}</th>
293
327
  <td data-label=${tu("amount")} class=${`num${r.amount === null ? " muted" : ""}`}>${amountText(r)}</td>
@@ -0,0 +1,41 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="referrer" content="no-referrer">
7
+ <meta name="robots" content="noindex">
8
+ <title>AgEnD</title>
9
+ <!--app-icon-->
10
+ <link rel="icon" href="/favicon.ico" sizes="16x16 32x32 64x64">
11
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml" sizes="any">
12
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180">
13
+ <!--/app-icon-->
14
+ <script src="/assets/theme.js"></script>
15
+ <link rel="stylesheet" href="/assets/tokens.css">
16
+ <style>
17
+ /* What a guest's browser gets for a page it cannot open (#2399 smoke: it was the gate's bare JSON). The sign-in
18
+ page's look: the app's tokens, light and dark (theme.js before paint). */
19
+ * { box-sizing: border-box; }
20
+ body { margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 16px;
21
+ background: var(--bg); color: var(--text); font: var(--fw-regular) var(--fs-md)/var(--lh-md) var(--font-ui); -webkit-font-smoothing: antialiased; }
22
+ main { width: 100%; max-width: 380px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: 28px 24px; }
23
+ .brand { margin: 0 0 18px; font-size: var(--fs-lg); font-weight: var(--fw-bold); }
24
+ h1 { margin: 0 0 6px; font-size: var(--fs-xl); font-weight: var(--fw-semibold); }
25
+ p { margin: 0 0 18px; color: var(--text-2); }
26
+ a.go { display: block; width: 100%; padding: 12px; text-align: center; text-decoration: none; font: var(--fw-semibold) var(--fs-md) var(--font-ui);
27
+ color: var(--on-accent); background: var(--accent-fill); border-radius: var(--r-full); }
28
+ a.go:hover { opacity: .88; }
29
+ a.go:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-dim); }
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <main id="refused" data-why="__WHY__">
34
+ <div class="brand">AgEnD</div>
35
+ <h1 id="title">This page is not open to guests</h1>
36
+ <p id="lead">This page is for the fleet's members. As a guest you can use the chats shared with you.</p>
37
+ <a id="go" class="go" href="/ui">Open your chats</a>
38
+ </main>
39
+ <script src="/assets/guest-refused.js"></script>
40
+ </body>
41
+ </html>
Binary file
Binary file
@@ -5,10 +5,14 @@
5
5
  //
6
6
  // The native <dialog> opened with showModal(): the browser traps focus and makes the rest inert. No style attribute
7
7
  // (#1300): classes only. Only a file URL the chat itself rendered (/ui/file/<32 hex>) is ever shown.
8
+ //
9
+ // #2333: zoom and pan (image-zoom.js). The toolbar has −, the level (it toggles fit ↔ 100%), + and Fit width; the keys
10
+ // + / − / 0 (fit) / 1 (100%) / W (fit width). Each image opens fitted; stepping to another fits it again.
8
11
  import { html, useEffect, useLayoutEffect, useRef, useState } from "/assets/app-html.js";
9
12
  import { t } from "/assets/app-i18n.js";
10
13
  import { createStore, useStore } from "/assets/app-store.js";
11
14
  import { Icon } from "/assets/ui-icons.js";
15
+ import { createZoom, STEP } from "./image-zoom.js";
12
16
 
13
17
  const FILE_URL = /^\/ui\/file\/[0-9a-f]{32}$/;
14
18
  /** What is shown: { items: [{ src, name }], index, opener } or null. One per page; the chat that shows it owns it. */
@@ -54,6 +58,10 @@ function ImageLightbox({ items, index, opener, onClose }) {
54
58
  const [at, setAt] = useState(index);
55
59
  const ref = useRef(null);
56
60
  const close = useRef(null);
61
+ const stage = useRef(null);
62
+ const img = useRef(null);
63
+ const zoom = useRef(null);
64
+ const [level, setLevel] = useState(null); // { scale, fit, mode } once the picture has loaded
57
65
  const item = items[at] || items[0];
58
66
  const many = items.length > 1;
59
67
  const step = (d) => setAt((n) => (n + d + items.length) % items.length);
@@ -69,6 +77,25 @@ function ImageLightbox({ items, index, opener, onClose }) {
69
77
  if (opener && typeof opener.focus === "function" && document.contains?.(opener)) opener.focus();
70
78
  };
71
79
  }, []);
80
+ useEffect(() => {
81
+ if (!stage.current || !img.current) return undefined;
82
+ const z = createZoom({ stage: stage.current, img: img.current, onChange: setLevel });
83
+ zoom.current = z;
84
+ return () => { z.destroy(); zoom.current = null; };
85
+ }, []);
86
+ // Another image: hidden until it has loaded and been fitted (its load resets the zoom). A layout effect — in the same
87
+ // commit that set the new src, before any load can arrive: a picture already in the cache (every thumbnail above it
88
+ // has loaded) fires its load before a passive effect would run, and such an effect then hid it for good with the
89
+ // level at "…" (the report on beta.24: Next "flashes once, then the next image is not visible"). A picture that is
90
+ // already complete here (the same file again: no load comes) is fitted at once.
91
+ const first = useRef(true);
92
+ useLayoutEffect(() => {
93
+ if (first.current) { first.current = false; return; }
94
+ const el = img.current;
95
+ if (el) el.classList.remove("lb-ready");
96
+ setLevel(null);
97
+ if (el && el.complete && el.naturalWidth > 0 && zoom.current) zoom.current.reset();
98
+ }, [at]);
72
99
  useEffect(() => {
73
100
  const d = ref.current;
74
101
  if (!d) return undefined;
@@ -80,6 +107,12 @@ function ImageLightbox({ items, index, opener, onClose }) {
80
107
  // The dark area (the dialog itself, or the stage around the image) closes it; the image and the controls do not.
81
108
  const onClick = (e) => { if (e.target === d || (e.target && e.target.classList && e.target.classList.contains("lb-stage"))) onClose(); };
82
109
  const onKey = (e) => {
110
+ const z = zoom.current;
111
+ if (z && !e.ctrlKey && !e.metaKey && !e.altKey) {
112
+ const act = { "+": () => z.zoomBy(STEP), "=": () => z.zoomBy(STEP), "-": () => z.zoomBy(1 / STEP), "_": () => z.zoomBy(1 / STEP),
113
+ "0": () => z.fit(), "1": () => z.actual(), w: () => z.fitWidth(), W: () => z.fitWidth() }[e.key];
114
+ if (act) { e.preventDefault(); act(); return; }
115
+ }
83
116
  if (!many) return;
84
117
  if (e.key === "ArrowRight") { e.preventDefault(); step(1); } else if (e.key === "ArrowLeft") { e.preventDefault(); step(-1); }
85
118
  };
@@ -90,14 +123,25 @@ function ImageLightbox({ items, index, opener, onClose }) {
90
123
  return () => { d.removeEventListener("cancel", onCancel); d.removeEventListener("close", onNativeClose); d.removeEventListener("click", onClick); d.removeEventListener("keydown", onKey); };
91
124
  }, [onClose, many, items.length]);
92
125
  const label = item.name ? t("chat.lbLabel", item.name) : t("chat.lbLabelNoName");
126
+ const pct = level ? Math.round(level.scale * 100) : null;
93
127
  return html`<dialog data-trust="lightbox" ref=${ref} class="lb" role="dialog" aria-modal="true" aria-label=${label}>
94
128
  <div class="lb-bar">
95
129
  <span class="lb-name" title=${item.name}>${item.name}</span>
96
130
  ${many ? html`<span class="lb-count">${t("chat.lbCount", at + 1, items.length)}</span>` : null}
131
+ <span class="lb-zoom" role="group">
132
+ <button type="button" class="icon-btn lb-zoom-out" disabled=${!level} aria-label=${t("chat.lbZoomOut")} title=${t("chat.lbZoomOut")}
133
+ onClick=${() => zoom.current && zoom.current.zoomBy(1 / STEP)}><${Icon} name="minus" /></button>
134
+ <button type="button" class="btn btn-sm lb-level" disabled=${!level} aria-label=${t("chat.lbZoomLevel", pct ?? "")} title=${t("chat.lbZoomLevel", pct ?? "")}
135
+ onClick=${() => zoom.current && zoom.current.toggle()}>${pct === null ? "…" : `${pct}%`}</button>
136
+ <button type="button" class="icon-btn lb-zoom-in" disabled=${!level} aria-label=${t("chat.lbZoomIn")} title=${t("chat.lbZoomIn")}
137
+ onClick=${() => zoom.current && zoom.current.zoomBy(STEP)}><${Icon} name="plus" /></button>
138
+ <button type="button" class=${`icon-btn lb-fit-width${level && level.mode === "width" ? " on" : ""}`} disabled=${!level} aria-pressed=${level && level.mode === "width" ? "true" : "false"}
139
+ aria-label=${t("chat.lbFitWidth")} title=${t("chat.lbFitWidth")} onClick=${() => zoom.current && zoom.current.fitWidth()}><${Icon} name="fitwidth" /></button>
140
+ </span>
97
141
  <a class="btn btn-sm lb-orig" href=${item.src} target="_blank" rel="noopener noreferrer"><${Icon} name="external" size=${14} /><span class="lbl">${t("chat.lbOpenOriginal")}</span></a>
98
142
  <button type="button" class="icon-btn lb-close" ref=${close} aria-label=${t("chat.lbClose")} title=${t("chat.lbClose")} onClick=${onClose}><${Icon} name="close" /></button>
99
143
  </div>
100
- <div class="lb-stage"><img class="lb-img" src=${item.src} alt=${item.name} /></div>
144
+ <div class="lb-stage" ref=${stage}><img class="lb-img" ref=${img} src=${item.src} alt=${item.name} draggable=${false} /></div>
101
145
  ${many ? html`<button type="button" class="icon-btn lb-nav lb-prev" aria-label=${t("chat.lbPrev")} title=${t("chat.lbPrev")} onClick=${() => step(-1)}><${Icon} name="back" /></button>
102
146
  <button type="button" class="icon-btn lb-nav lb-next" aria-label=${t("chat.lbNext")} title=${t("chat.lbNext")} onClick=${() => step(1)}><${Icon} name="forward" /></button>` : null}
103
147
  </dialog>`;
@@ -0,0 +1,242 @@
1
+ // #2333: zoom and pan for the chat image lightbox. The math is pure (a view box, the image's natural size, a scale and
2
+ // an offset) and unit-tested; `createZoom` applies it to one <img> in one stage as a CSSOM transform — translate then
3
+ // scale from the top-left corner, so the browser never re-decodes the picture, and no style attribute is written
4
+ // (#1300: the CSSOM is not governed by the page's style-src).
5
+ //
6
+ // Gestures: wheel and ctrl/⌘+wheel (a trackpad pinch arrives as ctrl+wheel) zoom around the cursor; once the image is
7
+ // larger than the view a plain wheel scrolls it instead, so a tall sheet reads like a page. A mouse click on the image
8
+ // toggles fit ↔ 100% at that point; a drag pans. Touch: two fingers pinch (and pan), one finger pans, a double tap
9
+ // toggles fit ↔ 100%. A gesture that moved is never also a click (the lightbox closes on a click on the dark area).
10
+
11
+ /** The largest zoom: 8× the picture's own pixels. */
12
+ export const MAX_SCALE = 8;
13
+ /** One step of +/−, the keys or the toolbar. */
14
+ export const STEP = 1.25;
15
+ /** A pointer that moved less than this (CSS px) clicked or tapped; more, it dragged. */
16
+ export const SLOP = 4;
17
+ /** Two taps within this long and this close are a double tap. */
18
+ export const DOUBLE_TAP_MS = 300;
19
+ export const DOUBLE_TAP_PX = 24;
20
+
21
+ /** The whole picture in the view, never enlarged. view = { x, y, w, h } (stage coordinates); img = { w, h } natural. */
22
+ export function fitScale(view, img) {
23
+ return Math.min(1, view.w / img.w, view.h / img.h);
24
+ }
25
+ /** The picture's width across the view, never enlarged: a tall sheet then scrolls. */
26
+ export function fitWidthScale(view, img) {
27
+ return Math.min(1, view.w / img.w);
28
+ }
29
+ /** The scales a gesture may reach: down to fit (or 100%, for a picture smaller than the view), up to MAX_SCALE. */
30
+ export function scaleLimits(view, img) {
31
+ return { min: fitScale(view, img), max: Math.max(MAX_SCALE, fitScale(view, img)) };
32
+ }
33
+
34
+ /**
35
+ * Keep the picture in view: an axis it does not fill is centred; an axis it overflows has no gap at either edge.
36
+ * z = { s, tx, ty } (tx, ty: where the picture's top-left corner is, in stage coordinates). Returns a new z.
37
+ */
38
+ export function clampPan(z, view, img) {
39
+ const axis = (t, size, start, len) => size <= len ? start + (len - size) / 2 : Math.min(start, Math.max(start + len - size, t));
40
+ return { s: z.s, tx: axis(z.tx, img.w * z.s, view.x, view.w), ty: axis(z.ty, img.h * z.s, view.y, view.h) };
41
+ }
42
+
43
+ /** Scale to `s` (within the limits) keeping the picture point under (px, py) where it is. */
44
+ export function zoomAt(z, s, px, py, view, img) {
45
+ const { min, max } = scaleLimits(view, img);
46
+ const to = Math.min(max, Math.max(min, s));
47
+ const k = to / z.s;
48
+ return clampPan({ s: to, tx: px - (px - z.tx) * k, ty: py - (py - z.ty) * k }, view, img);
49
+ }
50
+
51
+ /** Fit: the whole picture, centred. */
52
+ export function fitView(view, img) {
53
+ return clampPan({ s: fitScale(view, img), tx: 0, ty: 0 }, view, img);
54
+ }
55
+ /** Fit width: the picture across the view, from its top. */
56
+ export function fitWidthView(view, img) {
57
+ return clampPan({ s: fitWidthScale(view, img), tx: 0, ty: view.y }, view, img);
58
+ }
59
+ /** Whether the picture is larger than the view on either axis (then a plain wheel scrolls it, and a drag pans it). */
60
+ export function overflows(z, view, img) {
61
+ return img.w * z.s > view.w + 0.5 || img.h * z.s > view.h + 0.5;
62
+ }
63
+
64
+ /** Wheel distance in CSS px (a line is 16, a page the view's height). */
65
+ function wheelPx(e, view) {
66
+ const unit = e.deltaMode === 1 ? 16 : e.deltaMode === 2 ? view.h : 1;
67
+ return { dx: (e.deltaX || 0) * unit, dy: (e.deltaY || 0) * unit };
68
+ }
69
+
70
+ /**
71
+ * Drive one picture in one stage. `onChange({ scale, fit, mode })` after every change (mode: "fit" | "width" | "free").
72
+ * Returns { reset, fit, fitWidth, actual, zoomBy, toggle, destroy }. Nothing happens until the picture has loaded.
73
+ */
74
+ export function createZoom({ stage, img, onChange }) {
75
+ let nat = null; // { w, h } once loaded
76
+ let z = { s: 1, tx: 0, ty: 0 };
77
+ let mode = "fit";
78
+ const pointers = new Map(); // pointerId → { x, y }
79
+ let gesture = null; // { kind: "drag" | "pinch", ... }
80
+ let moved = false; // the gesture now ending moved: its click is not a click
81
+ let lastTap = null; // { t, x, y } of the last touch tap
82
+ let lastType = "mouse"; // the pointer type of the last press: a touch tap's click never toggles (double tap does);
83
+ // a mouse or a pen click does, and neither runs the double-tap logic
84
+
85
+ const view = () => {
86
+ const r = stage.getBoundingClientRect();
87
+ const cs = typeof getComputedStyle === "function" ? getComputedStyle(stage) : null;
88
+ const px = (k) => (cs ? parseFloat(cs[k]) || 0 : 0);
89
+ const x = px("paddingLeft"), y = px("paddingTop");
90
+ return { x, y, w: Math.max(1, r.width - x - px("paddingRight")), h: Math.max(1, r.height - y - px("paddingBottom")) };
91
+ };
92
+ const at = (e) => { const r = stage.getBoundingClientRect(); return { x: e.clientX - r.left, y: e.clientY - r.top }; };
93
+ const apply = () => {
94
+ img.style.transform = `translate(${z.tx}px, ${z.ty}px) scale(${z.s})`;
95
+ stage.classList.toggle("lb-zoomed", !!nat && overflows(z, view(), nat));
96
+ if (onChange) onChange({ scale: z.s, fit: nat ? fitScale(view(), nat) : 1, mode });
97
+ };
98
+ const set = (next, m = "free") => { if (!nat) return; z = next; mode = m; apply(); };
99
+
100
+ const api = {
101
+ /** A new picture (or the first): read its size and fit it. */
102
+ reset() {
103
+ nat = img.naturalWidth > 0 && img.naturalHeight > 0 ? { w: img.naturalWidth, h: img.naturalHeight } : null;
104
+ img.classList.toggle("lb-ready", !!nat);
105
+ img.classList.remove("lb-err");
106
+ if (nat) set(fitView(view(), nat), "fit");
107
+ },
108
+ fit() { if (nat) set(fitView(view(), nat), "fit"); },
109
+ fitWidth() { if (nat) set(fitWidthView(view(), nat), "width"); },
110
+ /** 100% at a point (default: the middle of the view). */
111
+ actual(px, py) {
112
+ if (!nat) return;
113
+ const v = view();
114
+ set(zoomAt(z, 1, px ?? v.x + v.w / 2, py ?? v.y + v.h / 2, v, nat));
115
+ },
116
+ zoomBy(factor, px, py) {
117
+ if (!nat) return;
118
+ const v = view();
119
+ set(zoomAt(z, z.s * factor, px ?? v.x + v.w / 2, py ?? v.y + v.h / 2, v, nat));
120
+ },
121
+ /** Fit ↔ 100%: anything but fit goes back to fit. */
122
+ toggle(px, py) {
123
+ if (!nat) return;
124
+ if (mode === "fit" && Math.abs(z.s - 1) > 1e-6) api.actual(px, py); else api.fit();
125
+ },
126
+ destroy() {
127
+ stage.removeEventListener("wheel", onWheel);
128
+ stage.removeEventListener("pointerdown", onDown);
129
+ stage.removeEventListener("pointermove", onMove);
130
+ stage.removeEventListener("pointerup", onUp);
131
+ stage.removeEventListener("pointercancel", onUp);
132
+ stage.removeEventListener("click", onClick, true);
133
+ img.removeEventListener("load", api.reset);
134
+ img.removeEventListener("error", onError);
135
+ if (typeof window !== "undefined") window.removeEventListener("resize", onResize);
136
+ },
137
+ };
138
+
139
+ function onWheel(e) {
140
+ if (!nat) return;
141
+ e.preventDefault();
142
+ const v = view();
143
+ const { dx, dy } = wheelPx(e, v);
144
+ if (e.ctrlKey || e.metaKey || !overflows(z, v, nat)) {
145
+ const p = at(e);
146
+ // A pinch on a trackpad sends small ctrl+wheel steps; a wheel notch is ~100 px.
147
+ set(zoomAt(z, z.s * Math.exp(-dy * (e.ctrlKey ? 0.01 : 0.002)), p.x, p.y, v, nat));
148
+ } else {
149
+ // Shift+wheel on a mouse scrolls sideways.
150
+ const sx = e.shiftKey && !dx ? dy : dx, sy = e.shiftKey && !dx ? 0 : dy;
151
+ set(clampPan({ s: z.s, tx: z.tx - sx, ty: z.ty - sy }, v, nat), mode === "width" ? "width" : "free");
152
+ }
153
+ }
154
+ function onDown(e) {
155
+ if (!nat || (e.pointerType === "mouse" && e.button !== 0)) return;
156
+ lastType = e.pointerType || "mouse";
157
+ pointers.set(e.pointerId, at(e));
158
+ // No pointer capture yet: it would retarget the click to the stage, and a click on the picture is not one on the
159
+ // dark area. Captured only once the pointer moves (capture()).
160
+ if (pointers.size === 1) { moved = false; gesture = { kind: "drag", from: at(e), z0: z }; }
161
+ else if (pointers.size === 2) {
162
+ const [a, b] = [...pointers.values()];
163
+ gesture = { kind: "pinch", d0: Math.hypot(b.x - a.x, b.y - a.y) || 1, m0: { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }, z0: z };
164
+ moved = true; // two fingers: never a tap
165
+ for (const id of pointers.keys()) capture(id);
166
+ }
167
+ }
168
+ function onMove(e) {
169
+ if (!gesture || !pointers.has(e.pointerId)) return;
170
+ pointers.set(e.pointerId, at(e));
171
+ const v = view();
172
+ if (gesture.kind === "pinch" && pointers.size >= 2) {
173
+ const [a, b] = [...pointers.values()];
174
+ const m = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
175
+ const { z0, m0, d0 } = gesture;
176
+ const s = z0.s * (Math.hypot(b.x - a.x, b.y - a.y) / d0);
177
+ // The picture point under the fingers' first midpoint follows the midpoint (zoom and pan in one).
178
+ const zoomed = zoomAt(z0, s, m0.x, m0.y, v, nat);
179
+ set(clampPan({ s: zoomed.s, tx: zoomed.tx + (m.x - m0.x), ty: zoomed.ty + (m.y - m0.y) }, v, nat));
180
+ } else if (gesture.kind === "drag") {
181
+ const p = pointers.get(e.pointerId);
182
+ const dx = p.x - gesture.from.x, dy = p.y - gesture.from.y;
183
+ if (!moved && Math.hypot(dx, dy) < SLOP) return;
184
+ if (!moved) capture(e.pointerId);
185
+ moved = true;
186
+ if (overflows(gesture.z0, v, nat)) set(clampPan({ s: gesture.z0.s, tx: gesture.z0.tx + dx, ty: gesture.z0.ty + dy }, v, nat), mode === "width" ? "width" : "free");
187
+ }
188
+ }
189
+ function onUp(e) {
190
+ if (!pointers.has(e.pointerId)) return;
191
+ const p = pointers.get(e.pointerId);
192
+ pointers.delete(e.pointerId);
193
+ if (pointers.size === 1 && gesture && gesture.kind === "pinch") {
194
+ // One finger left after a pinch: it pans on from here.
195
+ const [rest] = [...pointers.values()];
196
+ gesture = { kind: "drag", from: rest, z0: z };
197
+ return;
198
+ }
199
+ if (pointers.size > 0) return;
200
+ const tapped = !moved && gesture && gesture.kind === "drag";
201
+ gesture = null;
202
+ // Only a finger double-taps: a mouse or a pen clicks (onClick toggles), so a pen tap must not also count here.
203
+ if (!tapped || e.pointerType !== "touch") return;
204
+ // A touch tap: a second one soon and near is a double tap — fit ↔ 100% there.
205
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
206
+ if (lastTap && now - lastTap.t < DOUBLE_TAP_MS && Math.hypot(p.x - lastTap.x, p.y - lastTap.y) < DOUBLE_TAP_PX) {
207
+ lastTap = null;
208
+ moved = true; // its click neither closes nor toggles again
209
+ api.toggle(p.x, p.y);
210
+ } else lastTap = { t: now, x: p.x, y: p.y };
211
+ }
212
+ /** A drag or pinch keeps its pointers even off the stage. */
213
+ function capture(id) {
214
+ if (stage.setPointerCapture) { try { stage.setPointerCapture(id); } catch { /* already gone */ } }
215
+ }
216
+ // Capture phase: before the lightbox's own click (which closes on the dark area) can see it. A click that ended a
217
+ // drag or a double tap is swallowed; a mouse click on the picture toggles fit ↔ 100% there. A touch tap's click on
218
+ // the picture does nothing (a single tap waits for a double tap); on the dark area it still closes.
219
+ function onClick(e) {
220
+ if (moved) { moved = false; e.stopPropagation(); e.preventDefault(); return; }
221
+ if (e.target === img && nat && lastType !== "touch") {
222
+ const p = at(e);
223
+ e.stopPropagation();
224
+ api.toggle(p.x, p.y);
225
+ }
226
+ }
227
+ // A picture that does not load is shown as the browser shows a broken one, where the old lightbox put it.
228
+ const onError = () => { nat = null; img.classList.add("lb-err"); img.classList.remove("lb-ready"); if (onChange) onChange(null); };
229
+ const onResize = () => { if (!nat) return; if (mode === "fit") api.fit(); else if (mode === "width") api.fitWidth(); else set(clampPan(z, view(), nat)); };
230
+
231
+ stage.addEventListener("wheel", onWheel, { passive: false });
232
+ stage.addEventListener("pointerdown", onDown);
233
+ stage.addEventListener("pointermove", onMove);
234
+ stage.addEventListener("pointerup", onUp);
235
+ stage.addEventListener("pointercancel", onUp);
236
+ stage.addEventListener("click", onClick, true);
237
+ img.addEventListener("load", api.reset);
238
+ img.addEventListener("error", onError);
239
+ if (typeof window !== "undefined") window.addEventListener("resize", onResize);
240
+ if (img.complete) api.reset();
241
+ return api;
242
+ }