@starlove/orbit 1.4.1 → 2.0.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 (329) hide show
  1. package/Dockerfile +10 -8
  2. package/README.md +146 -164
  3. package/dist/full/csp.txt +1 -0
  4. package/dist/full/fonts/JetBrains_Mono/JetBrainsMono-subset.woff2 +0 -0
  5. package/dist/full/fonts/JetBrains_Mono/README.txt +5 -0
  6. package/dist/full/fonts/Mona_Sans/MonaSans-subset.woff2 +0 -0
  7. package/{public/fonts/Open_Sans → dist/full/fonts/Mona_Sans}/OFL.txt +93 -93
  8. package/dist/full/fonts/Mona_Sans/README.txt +10 -0
  9. package/{public/fonts/JetBrains_Mono → dist/full/fonts/Newsreader}/OFL.txt +93 -93
  10. package/dist/full/fonts/Newsreader/README.txt +8 -0
  11. package/dist/full/fonts/Newsreader/newsreader.woff2 +0 -0
  12. package/dist/full/index.html +36 -653
  13. package/dist/full/js/app.BmuponPJ.js +32 -0
  14. package/dist/full/js/app.BmuponPJ.js.map +1 -0
  15. package/dist/full/js/chunk.1fJGQGbW.js +2 -0
  16. package/dist/full/js/chunk.1fJGQGbW.js.map +1 -0
  17. package/dist/full/js/chunk.5D7JO2zp.js +2 -0
  18. package/dist/full/js/chunk.5D7JO2zp.js.map +1 -0
  19. package/dist/full/js/chunk.BrNn_D79.js +2 -0
  20. package/dist/full/js/chunk.BrNn_D79.js.map +1 -0
  21. package/dist/full/js/chunk.CokJXIqM.js +2 -0
  22. package/dist/full/js/chunk.CokJXIqM.js.map +1 -0
  23. package/dist/full/js/chunk.D_Haqy6R.js +2 -0
  24. package/dist/full/js/chunk.D_Haqy6R.js.map +1 -0
  25. package/dist/full/js/chunk.RO0jsH6I.js +15 -0
  26. package/dist/full/js/chunk.RO0jsH6I.js.map +1 -0
  27. package/dist/full/js/chunk.dqWOEtBM.js +2 -0
  28. package/dist/full/js/chunk.dqWOEtBM.js.map +1 -0
  29. package/dist/full/js/warm.f260bbbc.js +15 -0
  30. package/dist/full/orbits-favicon.png +0 -0
  31. package/dist/full/orbits-logo-bg-wide.png +0 -0
  32. package/dist/full/orbits-logo-bg.png +0 -0
  33. package/dist/full/orbits-logo.png +0 -0
  34. package/dist/full/styles.2b154ffe.css +1 -0
  35. package/dist/full/vendor/starlove-ui/theme-init.js +66 -0
  36. package/dist/full/version.json +2 -2
  37. package/docs/AGENT_GUIDE.md +114 -0
  38. package/docs/UPDATING.md +249 -64
  39. package/lib/adapters/indexeddb/blobs.d.ts +3 -0
  40. package/lib/adapters/indexeddb/blobs.js +54 -0
  41. package/lib/adapters/indexeddb/database.d.ts +60 -0
  42. package/lib/adapters/indexeddb/database.js +126 -0
  43. package/lib/adapters/indexeddb/index.d.ts +5 -0
  44. package/lib/adapters/indexeddb/index.js +3 -0
  45. package/lib/adapters/indexeddb/locks.d.ts +14 -0
  46. package/lib/adapters/indexeddb/locks.js +40 -0
  47. package/lib/adapters/indexeddb/store.d.ts +40 -0
  48. package/lib/adapters/indexeddb/store.js +424 -0
  49. package/lib/adapters/indexeddb/tables.d.ts +43 -0
  50. package/lib/adapters/indexeddb/tables.js +127 -0
  51. package/lib/adapters/memory/blobs.d.ts +3 -0
  52. package/lib/adapters/memory/blobs.js +44 -0
  53. package/lib/adapters/memory/board.d.ts +41 -0
  54. package/lib/adapters/memory/board.js +521 -0
  55. package/lib/adapters/memory/index.d.ts +2 -0
  56. package/lib/adapters/memory/index.js +2 -0
  57. package/lib/adapters/memory/search.d.ts +6 -0
  58. package/lib/adapters/memory/search.js +30 -0
  59. package/lib/adapters/memory/store.d.ts +34 -0
  60. package/lib/adapters/memory/store.js +266 -0
  61. package/lib/adapters/sqlite/backups.d.ts +53 -0
  62. package/lib/adapters/sqlite/backups.js +232 -0
  63. package/lib/adapters/sqlite/blobs.d.ts +14 -0
  64. package/lib/adapters/sqlite/blobs.js +109 -0
  65. package/lib/adapters/sqlite/board.d.ts +77 -0
  66. package/lib/adapters/sqlite/board.js +328 -0
  67. package/lib/adapters/sqlite/database.d.ts +67 -0
  68. package/lib/adapters/sqlite/database.js +285 -0
  69. package/lib/adapters/sqlite/index.d.ts +3 -0
  70. package/lib/adapters/sqlite/index.js +2 -0
  71. package/lib/adapters/sqlite/lock.d.ts +11 -0
  72. package/lib/adapters/sqlite/lock.js +34 -0
  73. package/lib/adapters/sqlite/records.d.ts +54 -0
  74. package/lib/adapters/sqlite/records.js +546 -0
  75. package/lib/adapters/sqlite/schema.d.ts +54 -0
  76. package/lib/adapters/sqlite/schema.js +276 -0
  77. package/lib/adapters/sqlite/store.d.ts +39 -0
  78. package/lib/adapters/sqlite/store.js +385 -0
  79. package/lib/browser/in-page.d.ts +31 -0
  80. package/lib/browser/in-page.js +46 -0
  81. package/lib/browser/old-preview.d.ts +13 -0
  82. package/lib/browser/old-preview.js +80 -0
  83. package/lib/browser/preview-board.d.ts +8 -0
  84. package/lib/browser/preview-board.js +69 -0
  85. package/lib/cli/main.d.ts +7 -0
  86. package/lib/cli/main.js +343 -0
  87. package/lib/cli/orbit.d.ts +8 -0
  88. package/lib/cli/orbit.js +25 -0
  89. package/lib/client/http.d.ts +38 -0
  90. package/lib/client/http.js +334 -0
  91. package/lib/client/local.d.ts +26 -0
  92. package/lib/client/local.js +190 -0
  93. package/lib/client/object-url.d.ts +8 -0
  94. package/lib/client/object-url.js +21 -0
  95. package/lib/client/types.d.ts +268 -0
  96. package/lib/client/types.js +8 -0
  97. package/lib/commands/agent-tools.d.ts +31 -0
  98. package/lib/commands/agent-tools.js +100 -0
  99. package/lib/commands/index.d.ts +56 -0
  100. package/lib/commands/index.js +298 -0
  101. package/lib/commands/parse.d.ts +73 -0
  102. package/lib/commands/parse.js +231 -0
  103. package/lib/commands/present.d.ts +134 -0
  104. package/lib/commands/present.js +198 -0
  105. package/lib/commands/session.d.ts +85 -0
  106. package/lib/commands/session.js +138 -0
  107. package/lib/commands/verbs.d.ts +35 -0
  108. package/lib/commands/verbs.js +842 -0
  109. package/lib/core/actors.d.ts +18 -0
  110. package/lib/core/actors.js +45 -0
  111. package/lib/core/agent.d.ts +128 -0
  112. package/lib/core/agent.js +323 -0
  113. package/lib/core/boards.d.ts +113 -0
  114. package/lib/core/boards.js +288 -0
  115. package/lib/core/bootstrap.d.ts +47 -0
  116. package/lib/core/bootstrap.js +100 -0
  117. package/lib/core/bytes.d.ts +10 -0
  118. package/lib/core/bytes.js +52 -0
  119. package/lib/core/card-paths.d.ts +25 -0
  120. package/lib/core/card-paths.js +44 -0
  121. package/lib/core/context.d.ts +60 -0
  122. package/lib/core/context.js +42 -0
  123. package/lib/core/cross-board.d.ts +81 -0
  124. package/lib/core/cross-board.js +228 -0
  125. package/lib/core/describe.d.ts +21 -0
  126. package/lib/core/describe.js +81 -0
  127. package/lib/core/errors.d.ts +159 -0
  128. package/lib/core/errors.js +160 -0
  129. package/lib/core/files.d.ts +79 -0
  130. package/lib/core/files.js +262 -0
  131. package/lib/core/history.d.ts +20 -0
  132. package/lib/core/history.js +53 -0
  133. package/lib/core/image-types.d.ts +16 -0
  134. package/lib/core/image-types.js +36 -0
  135. package/lib/core/model.d.ts +263 -0
  136. package/lib/core/model.js +34 -0
  137. package/lib/core/nebula-link.d.ts +14 -0
  138. package/lib/core/nebula-link.js +39 -0
  139. package/lib/core/relations.d.ts +58 -0
  140. package/lib/core/relations.js +112 -0
  141. package/lib/core/search.d.ts +38 -0
  142. package/lib/core/search.js +92 -0
  143. package/lib/core/sessions.d.ts +44 -0
  144. package/lib/core/sessions.js +158 -0
  145. package/lib/core/snapshot-schema.d.ts +295 -0
  146. package/lib/core/snapshot-schema.js +82 -0
  147. package/lib/core/snapshots.d.ts +75 -0
  148. package/lib/core/snapshots.js +409 -0
  149. package/lib/core/states.d.ts +25 -0
  150. package/lib/core/states.js +126 -0
  151. package/lib/core/text.d.ts +40 -0
  152. package/lib/core/text.js +113 -0
  153. package/lib/core/tickets.d.ts +69 -0
  154. package/lib/core/tickets.js +311 -0
  155. package/lib/core/trello.d.ts +17 -0
  156. package/lib/core/trello.js +269 -0
  157. package/lib/core/verdicts.d.ts +28 -0
  158. package/lib/core/verdicts.js +92 -0
  159. package/lib/core/views.d.ts +95 -0
  160. package/lib/core/views.js +186 -0
  161. package/lib/http/app.d.ts +46 -0
  162. package/lib/http/app.js +491 -0
  163. package/lib/mcp-server.d.ts +10 -0
  164. package/lib/mcp-server.js +120 -0
  165. package/lib/node/assets.d.ts +7 -0
  166. package/lib/node/assets.js +68 -0
  167. package/lib/node/data-folder.d.ts +21 -0
  168. package/lib/node/data-folder.js +34 -0
  169. package/lib/node/env.d.ts +38 -0
  170. package/lib/node/env.js +49 -0
  171. package/lib/node/host-check.d.ts +24 -0
  172. package/lib/node/host-check.js +53 -0
  173. package/lib/node/host-services.d.ts +9 -0
  174. package/lib/node/host-services.js +150 -0
  175. package/lib/node/local.d.ts +22 -0
  176. package/lib/node/local.js +61 -0
  177. package/lib/node/migrate.d.ts +53 -0
  178. package/lib/node/migrate.js +635 -0
  179. package/lib/node/package.d.ts +7 -0
  180. package/lib/node/package.js +14 -0
  181. package/lib/node/repo-board.d.ts +49 -0
  182. package/lib/node/repo-board.js +245 -0
  183. package/lib/node/serve.d.ts +8 -0
  184. package/lib/node/serve.js +106 -0
  185. package/lib/orbit.d.ts +47 -0
  186. package/lib/orbit.js +44 -0
  187. package/lib/quiet-sqlite-warning.d.ts +1 -0
  188. package/lib/quiet-sqlite-warning.js +18 -0
  189. package/lib/store/blob-path.d.ts +6 -0
  190. package/lib/store/blob-path.js +12 -0
  191. package/lib/store/conformance.d.ts +52 -0
  192. package/lib/store/conformance.js +1449 -0
  193. package/lib/store/search-words.d.ts +10 -0
  194. package/lib/store/search-words.js +19 -0
  195. package/lib/store/types.d.ts +452 -0
  196. package/lib/store/types.js +34 -0
  197. package/llms.txt +63 -0
  198. package/package.json +109 -25
  199. package/src/cli/orbit.js +11 -658
  200. package/src/mcp-server.js +16 -876
  201. package/SKILL-ORBIT.md +0 -151
  202. package/build.mjs +0 -210
  203. package/dist/full/js/app.4e6fbe20.js +0 -952
  204. package/dist/full/js/app.4e6fbe20.js.map +0 -7
  205. package/dist/full/styles.4e60d824.css +0 -1
  206. package/docs/AGENT_PROTOCOL.md +0 -424
  207. package/docs/MCP_SETUP.md +0 -54
  208. package/docs/ORBIT_DISPATCH.md +0 -280
  209. package/docs/SS-Orbit-Dark.png +0 -0
  210. package/docs/SS-Orbit-Light.png +0 -0
  211. package/docs/mcp-client.example.json +0 -13
  212. package/docs/setup/orbit-init-wizard.md +0 -174
  213. package/public/apple-touch-icon.png +0 -0
  214. package/public/cursors/live_arrow.cur +0 -0
  215. package/public/cursors/live_link.cur +0 -0
  216. package/public/cursors/live_move.cur +0 -0
  217. package/public/cursors/live_select.cur +0 -0
  218. package/public/cursors/live_unavail.cur +0 -0
  219. package/public/favicon-32x32.png +0 -0
  220. package/public/fonts/Chakra_Petch/ChakraPetch-Bold.ttf +0 -0
  221. package/public/fonts/Chakra_Petch/ChakraPetch-BoldItalic.ttf +0 -0
  222. package/public/fonts/Chakra_Petch/ChakraPetch-Italic.ttf +0 -0
  223. package/public/fonts/Chakra_Petch/ChakraPetch-Light.ttf +0 -0
  224. package/public/fonts/Chakra_Petch/ChakraPetch-LightItalic.ttf +0 -0
  225. package/public/fonts/Chakra_Petch/ChakraPetch-Medium.ttf +0 -0
  226. package/public/fonts/Chakra_Petch/ChakraPetch-MediumItalic.ttf +0 -0
  227. package/public/fonts/Chakra_Petch/ChakraPetch-Regular.ttf +0 -0
  228. package/public/fonts/Chakra_Petch/ChakraPetch-SemiBold.ttf +0 -0
  229. package/public/fonts/Chakra_Petch/ChakraPetch-SemiBoldItalic.ttf +0 -0
  230. package/public/fonts/Chakra_Petch/OFL.txt +0 -93
  231. package/public/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf +0 -0
  232. package/public/fonts/Inter/Inter-VariableFont_opsz,wght.ttf +0 -0
  233. package/public/fonts/Inter/OFL.txt +0 -93
  234. package/public/fonts/Inter/README.txt +0 -118
  235. package/public/fonts/JetBrains_Mono/JetBrainsMono-Italic-VariableFont_wght.ttf +0 -0
  236. package/public/fonts/JetBrains_Mono/JetBrainsMono-VariableFont_wght.ttf +0 -0
  237. package/public/fonts/JetBrains_Mono/README.txt +0 -79
  238. package/public/fonts/OpenRing-Regular.ttf +0 -0
  239. package/public/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf +0 -0
  240. package/public/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf +0 -0
  241. package/public/fonts/Open_Sans/README.txt +0 -100
  242. package/public/fonts/Orbitron/OFL.txt +0 -93
  243. package/public/fonts/Orbitron/Orbitron-VariableFont_wght.ttf +0 -0
  244. package/public/fonts/Orbitron/README.txt +0 -68
  245. package/public/fonts/Rajdhani/OFL.txt +0 -93
  246. package/public/fonts/Rajdhani/Rajdhani-Bold.ttf +0 -0
  247. package/public/fonts/Rajdhani/Rajdhani-Light.ttf +0 -0
  248. package/public/fonts/Rajdhani/Rajdhani-Medium.ttf +0 -0
  249. package/public/fonts/Rajdhani/Rajdhani-Regular.ttf +0 -0
  250. package/public/fonts/Rajdhani/Rajdhani-SemiBold.ttf +0 -0
  251. package/public/fonts/runescape-uf.ttf +0 -0
  252. package/public/index.html +0 -769
  253. package/public/js/actor-labels.js +0 -65
  254. package/public/js/api.js +0 -39
  255. package/public/js/app.js +0 -45
  256. package/public/js/artifact-drawer.js +0 -79
  257. package/public/js/board-export.js +0 -161
  258. package/public/js/board-menu.js +0 -374
  259. package/public/js/card-actions.js +0 -545
  260. package/public/js/config.js +0 -20
  261. package/public/js/create-card.js +0 -190
  262. package/public/js/dom.js +0 -19
  263. package/public/js/drawer.js +0 -273
  264. package/public/js/font-preference.js +0 -230
  265. package/public/js/format.js +0 -433
  266. package/public/js/help-menu.js +0 -31
  267. package/public/js/icons.js +0 -16
  268. package/public/js/kanban.js +0 -694
  269. package/public/js/local-backend.js +0 -1015
  270. package/public/js/main.js +0 -161
  271. package/public/js/modal.js +0 -33
  272. package/public/js/motion-preference.js +0 -64
  273. package/public/js/panel.js +0 -58
  274. package/public/js/router.js +0 -134
  275. package/public/js/search.js +0 -56
  276. package/public/js/settings-tabs.js +0 -7
  277. package/public/js/settings.js +0 -1634
  278. package/public/js/sse.js +0 -159
  279. package/public/js/starscape.js +0 -332
  280. package/public/js/state.js +0 -129
  281. package/public/js/text-editing.js +0 -128
  282. package/public/js/theme-preference.js +0 -95
  283. package/public/js/ticket-detail.js +0 -1516
  284. package/public/js/ticket-typeahead.js +0 -49
  285. package/public/js/toast.js +0 -89
  286. package/public/js/topbar-notes.js +0 -155
  287. package/public/js/transport.js +0 -22
  288. package/public/js/typeahead.js +0 -206
  289. package/public/js/unread.js +0 -121
  290. package/public/js/update-check.js +0 -58
  291. package/public/js/url-routes.js +0 -94
  292. package/public/orbits-favicon.png +0 -0
  293. package/public/orbits-logo-bg-wide.png +0 -0
  294. package/public/orbits-logo-bg.png +0 -0
  295. package/public/orbits-logo.png +0 -0
  296. package/public/styles.css +0 -7709
  297. package/src/core/agent.js +0 -387
  298. package/src/core/agents-md.js +0 -109
  299. package/src/core/artifacts.js +0 -149
  300. package/src/core/attachments.js +0 -238
  301. package/src/core/auth.js +0 -85
  302. package/src/core/backups.js +0 -142
  303. package/src/core/board.js +0 -148
  304. package/src/core/boards.js +0 -409
  305. package/src/core/bootstrap.js +0 -170
  306. package/src/core/css-bundle.js +0 -30
  307. package/src/core/db.js +0 -315
  308. package/src/core/delete-board.js +0 -163
  309. package/src/core/dispatch.js +0 -1007
  310. package/src/core/events.js +0 -314
  311. package/src/core/http.js +0 -115
  312. package/src/core/migrate.js +0 -198
  313. package/src/core/paths.js +0 -55
  314. package/src/core/provision-repo-board.js +0 -119
  315. package/src/core/queries.js +0 -246
  316. package/src/core/registry.js +0 -211
  317. package/src/core/relations.js +0 -163
  318. package/src/core/review-verdicts.js +0 -138
  319. package/src/core/router.js +0 -556
  320. package/src/core/search.js +0 -183
  321. package/src/core/seed.js +0 -234
  322. package/src/core/snapshots.js +0 -692
  323. package/src/core/states.js +0 -167
  324. package/src/core/system-picker.js +0 -109
  325. package/src/core/tickets.js +0 -400
  326. package/src/core/util.js +0 -106
  327. package/src/core/version-check.js +0 -154
  328. package/src/mcp/orbit-client.js +0 -287
  329. package/src/server.js +0 -115
package/public/js/sse.js DELETED
@@ -1,159 +0,0 @@
1
- // Server-sent events client. Subscribes to /api/events/stream and turns
2
- // server events into a debounced bootstrap reload + toast notifications.
3
- // Owns its own reconnect backoff for the rare case where the browser's
4
- // auto-reconnect gives up (e.g., HTTP error → CLOSED state).
5
-
6
- import { state, syncBoardSelection } from "./state.js";
7
- import { api, bootstrapPath } from "./api.js";
8
- import { toast, debounce } from "./toast.js";
9
- import { render } from "./app.js";
10
- import { markRead, bumpUnread, clearUnread } from "./unread.js";
11
-
12
- // Event types that meaningfully change a card's state from the human's POV
13
- // — these are the ones that should bump the per-card unread count.
14
- const CARD_TOUCHING_EVENTS = new Set([
15
- "ticket_created",
16
- "state_changed",
17
- "ticket_updated",
18
- "comment_created",
19
- "comment_ai_omission_changed",
20
- "agent_claimed",
21
- "checkpoint_requested",
22
- "agent_completed"
23
- ]);
24
-
25
- // Manual reconnect backoff for the rare case the browser's auto-reconnect
26
- // gives up (e.g., the server returned an HTTP error and EventSource entered
27
- // the CLOSED state). Reset on every successful open.
28
- let sseReconnectDelayMs = 1000;
29
- let sseReconnectTimer = null;
30
- let sseDisconnectToastShown = false;
31
-
32
- export function connectEventStream() {
33
- if (sseReconnectTimer) {
34
- clearTimeout(sseReconnectTimer);
35
- sseReconnectTimer = null;
36
- }
37
- if (state.eventSource) {
38
- state.eventSource.close();
39
- state.eventSource = null;
40
- }
41
-
42
- const params = new URLSearchParams();
43
- if (state.boardId) params.set("board_id", state.boardId);
44
- const url = `/api/events/stream${params.toString() ? `?${params}` : ""}`;
45
-
46
- const es = new EventSource(url);
47
- state.eventSource = es;
48
-
49
- es.addEventListener("open", () => {
50
- sseReconnectDelayMs = 1000;
51
- if (sseDisconnectToastShown) {
52
- toast.success("Live updates reconnected");
53
- sseDisconnectToastShown = false;
54
- }
55
- });
56
-
57
- es.addEventListener("board", async (msg) => {
58
- let event;
59
- try {
60
- event = JSON.parse(msg.data);
61
- } catch {
62
- return;
63
- }
64
-
65
- const eventType = event.type;
66
- const findTicket = () => state.data?.tickets?.find((t) => t.id === event.ticket_id);
67
-
68
- // Per-card unread counter:
69
- // - my own events → mark read (no badge for my own changes)
70
- // - foreign card-touching events → bump count by 1
71
- // - delete → clear the entry entirely
72
- const me = state.data?.actor?.name;
73
- const isMine = event.actor && me && event.actor === me;
74
- if (event.ticket_id) {
75
- if (isMine) {
76
- markRead(event.ticket_id, event.timestamp);
77
- } else if (eventType === "ticket_deleted") {
78
- clearUnread(event.ticket_id);
79
- } else if (CARD_TOUCHING_EVENTS.has(eventType)) {
80
- bumpUnread(event.ticket_id);
81
- }
82
- }
83
-
84
- if (eventType === "ticket_created") {
85
- toast.success(`Card created: ${event.data.title || "New card"}`);
86
- scheduleSSEReload();
87
- } else if (eventType === "state_changed") {
88
- const ticket = findTicket();
89
- const label = ticket ? `#${ticket.number} ${ticket.title}` : "Card";
90
- toast.info(`${label} → ${event.data.to}`);
91
- scheduleSSEReload();
92
- } else if (eventType === "ticket_updated") {
93
- const ticket = findTicket();
94
- const label = ticket ? `#${ticket.number}` : "Card";
95
- const fields = event.data.fields ? Object.keys(event.data.fields).join(", ") : "";
96
- toast.info(`${label} updated${fields ? `: ${fields}` : ""}`);
97
- scheduleSSEReload();
98
- } else if (eventType === "comment_created") {
99
- const ticket = findTicket();
100
- const label = ticket ? `#${ticket.number}` : "ticket";
101
- toast.info(`New comment on ${label}`);
102
- scheduleSSEReload();
103
- } else if (eventType === "comment_ai_omission_changed") {
104
- scheduleSSEReload();
105
- } else if (eventType === "agent_claimed") {
106
- const ticket = findTicket();
107
- toast.info(`Agent claimed ${ticket ? `#${ticket.number} ${ticket.title}` : "a ticket"}`);
108
- scheduleSSEReload();
109
- } else if (eventType === "checkpoint_requested") {
110
- const ticket = findTicket();
111
- toast.warning(`Checkpoint on ${ticket ? `#${ticket.number}` : "ticket"} — needs human input`);
112
- scheduleSSEReload();
113
- } else if (eventType === "agent_completed") {
114
- const ticket = findTicket();
115
- toast.success(`Agent completed ${ticket ? `#${ticket.number} ${ticket.title}` : "a ticket"}`);
116
- scheduleSSEReload();
117
- } else if (
118
- eventType === "ticket_deleted" ||
119
- eventType === "states_reordered" ||
120
- eventType === "state_created" ||
121
- eventType === "state_deleted" ||
122
- eventType === "board_updated" ||
123
- eventType === "board_imported"
124
- ) {
125
- scheduleSSEReload();
126
- }
127
- });
128
-
129
- es.onerror = () => {
130
- // EventSource has three readyStates:
131
- // 0 (CONNECTING) — browser will auto-reconnect, no action needed.
132
- // 1 (OPEN) — transient blip, browser handles it.
133
- // 2 (CLOSED) — browser gave up (e.g., HTTP error). Surface and retry.
134
- if (es.readyState !== EventSource.CLOSED) return;
135
-
136
- if (!sseDisconnectToastShown) {
137
- toast.warning("Live updates disconnected — retrying…");
138
- sseDisconnectToastShown = true;
139
- }
140
-
141
- if (sseReconnectTimer) return;
142
- const delay = sseReconnectDelayMs;
143
- sseReconnectDelayMs = Math.min(sseReconnectDelayMs * 2, 30000);
144
- sseReconnectTimer = setTimeout(() => {
145
- sseReconnectTimer = null;
146
- connectEventStream();
147
- }, delay);
148
- };
149
- }
150
-
151
- export const scheduleSSEReload = debounce(async () => {
152
- const selectedId = state.selectedTicketId;
153
- const mode = state.detailMode;
154
- state.data = await api(bootstrapPath());
155
- syncBoardSelection();
156
- state.selectedTicketId = selectedId;
157
- state.detailMode = mode;
158
- await render();
159
- }, 300);
@@ -1,332 +0,0 @@
1
- // Animated starfield background, signature stars, meteors, and the theme
2
- // toggle (which drives the starscape's dark/light state). Self-initializing:
3
- // importing this module starts the canvas, schedules the first meteor, and
4
- // wires the theme toggle button.
5
- //
6
- // Background stars are drawn on a 2D canvas so each star has its own twinkle
7
- // period, phase, and warmth — scintillation that reads as per-star rather
8
- // than whole-layer. Layers parallax to the cursor by depth. The Milky Way,
9
- // nebula ghosts, signature stars with spikes, and meteors stay in CSS.
10
-
11
- import { themeToggle, themeIcon } from "./dom.js";
12
- import { applyReducedMotionPreference, effectiveReducedMotionPreference } from "./motion-preference.js";
13
- import {
14
- THEME_CHANGE_EVENT,
15
- applyStoredThemePreference,
16
- currentTheme,
17
- storedThemePreference,
18
- setThemePreference,
19
- toggleThemePreference
20
- } from "./theme-preference.js";
21
-
22
- // Stored theme must be applied before startStarscape() so its first sync()
23
- // sees the real theme; otherwise the canvas stays stopped until next resize.
24
- {
25
- applyStoredThemePreference();
26
- applyReducedMotionPreference();
27
- }
28
-
29
- const starscape = startStarscape();
30
- scheduleMeteor();
31
-
32
- // Sticky top bar gets a tinted glass panel once the page scrolls, so its
33
- // controls don't blend into whatever content is slipping behind them.
34
- {
35
- const updateScrolled = () => {
36
- document.body.classList.toggle("is-scrolled", window.scrollY > 4);
37
- };
38
- window.addEventListener("scroll", updateScrolled, { passive: true });
39
- updateScrolled();
40
- }
41
-
42
- syncThemeIcon();
43
- themeToggle?.addEventListener("click", () => {
44
- toggleThemePreference();
45
- });
46
-
47
- // When there's no explicit user preference stored, track OS changes live so
48
- // data-theme stays in sync and CSS selectors ([data-theme="dark"]) keep working.
49
- window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => {
50
- if (!storedThemePreference()) {
51
- setThemePreference(e.matches ? "dark" : "light", { notify: true });
52
- }
53
- });
54
-
55
- window.addEventListener(THEME_CHANGE_EVENT, () => {
56
- syncThemeIcon();
57
- starscape.sync();
58
- });
59
-
60
- function syncThemeIcon() {
61
- if (!themeIcon) return;
62
- const dark = currentTheme() === "dark";
63
- themeIcon.innerHTML = dark
64
- ? '<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />'
65
- : '<circle cx="12" cy="12" r="5" /><path d="M12 1v2m0 18v2M4.2 4.2l1.4 1.4m12.8 12.8l1.4 1.4M1 12h2m18 0h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4" />';
66
- }
67
-
68
- function startStarscape() {
69
- const grain = document.querySelector(".grain");
70
- if (!grain) return { sync: () => {} };
71
-
72
- buildSignatureStars(grain);
73
-
74
- const canvas = document.createElement("canvas");
75
- canvas.className = "starfield-canvas";
76
- canvas.setAttribute("aria-hidden", "true");
77
- const milkyWay = grain.querySelector(".milky-way");
78
- if (milkyWay && milkyWay.nextSibling) {
79
- grain.insertBefore(canvas, milkyWay.nextSibling);
80
- } else if (milkyWay) {
81
- grain.appendChild(canvas);
82
- } else {
83
- grain.insertBefore(canvas, grain.firstChild);
84
- }
85
-
86
- const ctx = canvas.getContext("2d");
87
- const DPR = Math.min(window.devicePixelRatio || 1, 2);
88
- let width = 0;
89
- let height = 0;
90
- let stars = [];
91
- let rafId = 0;
92
- let running = false;
93
-
94
- // Eased mouse position, normalized to [-0.5, 0.5]. Lerp gives inertia.
95
- const mouse = { x: 0, y: 0, tx: 0, ty: 0 };
96
-
97
- function resize() {
98
- width = window.innerWidth;
99
- height = window.innerHeight;
100
- canvas.width = Math.round(width * DPR);
101
- canvas.height = Math.round(height * DPR);
102
- canvas.style.width = width + "px";
103
- canvas.style.height = height + "px";
104
- ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
105
- buildStars();
106
- }
107
-
108
- function buildStars() {
109
- // Star count scales with viewport area so wide screens don't read empty.
110
- const density = (width * height) / (1440 * 900);
111
- const layers = [
112
- { count: Math.round(320 * density), depth: 0.12, sizes: [[1, 0.9], [1.4, 0.1]], glow: 0 },
113
- { count: Math.round(160 * density), depth: 0.30, sizes: [[1, 0.5], [1.6, 0.4], [2.4, 0.1]], glow: 1.8 },
114
- { count: Math.round(44 * density), depth: 0.58, sizes: [[1.8, 0.5], [2.6, 0.38], [3.6, 0.12]], glow: 2.4 }
115
- ];
116
- const rng = mulberry32(0xc0ffee);
117
- stars = [];
118
- for (const layer of layers) {
119
- for (let i = 0; i < layer.count; i++) {
120
- const size = pickWeighted(rng(), layer.sizes);
121
- stars.push({
122
- x: rng() * width,
123
- y: rng() * height,
124
- size,
125
- depth: layer.depth,
126
- glow: size >= 2 ? layer.glow : 0,
127
- rgb: pickStarColor(rng()),
128
- period: 2.2 + rng() * 5.4,
129
- phase: rng() * Math.PI * 2,
130
- amp: 0.25 + rng() * 0.5,
131
- baseAlpha: size < 1.5 ? 0.45 + rng() * 0.28 : 0.55 + rng() * 0.22
132
- });
133
- }
134
- }
135
- }
136
-
137
- function pickStarColor(r) {
138
- if (r < 0.80) return [255, 255, 255];
139
- if (r < 0.93) return [255, 222, 186];
140
- return [195, 218, 255];
141
- }
142
-
143
- function onPointerMove(e) {
144
- if (!width || !height) return;
145
- mouse.tx = e.clientX / width - 0.5;
146
- mouse.ty = e.clientY / height - 0.5;
147
- }
148
-
149
- function frame(t) {
150
- mouse.x += (mouse.tx - mouse.x) * 0.06;
151
- mouse.y += (mouse.ty - mouse.y) * 0.06;
152
-
153
- ctx.clearRect(0, 0, width, height);
154
- ctx.globalCompositeOperation = "lighter";
155
-
156
- const time = t / 1000;
157
- const parallaxPx = 70;
158
-
159
- for (let i = 0; i < stars.length; i++) {
160
- const s = stars[i];
161
- const pulse = 0.5 + 0.5 * Math.sin(time * ((2 * Math.PI) / s.period) + s.phase);
162
- const alpha = s.baseAlpha * (1 - s.amp + s.amp * pulse);
163
- const px = s.x - mouse.x * s.depth * parallaxPx;
164
- const py = s.y - mouse.y * s.depth * parallaxPx;
165
- const [r, g, b] = s.rgb;
166
-
167
- if (s.glow > 0) {
168
- // Glowed stars: soft halo + circular core (arc, not rect) so the
169
- // bright center doesn't read as a square at larger zoom levels.
170
- const radius = s.size * s.glow;
171
- const grad = ctx.createRadialGradient(px, py, 0, px, py, radius);
172
- grad.addColorStop(0, `rgba(${r},${g},${b},${alpha * 0.85})`);
173
- grad.addColorStop(0.35, `rgba(${r},${g},${b},${alpha * 0.3})`);
174
- grad.addColorStop(1, `rgba(${r},${g},${b},0)`);
175
- ctx.fillStyle = grad;
176
- ctx.beginPath();
177
- ctx.arc(px, py, radius, 0, Math.PI * 2);
178
- ctx.fill();
179
-
180
- ctx.fillStyle = `rgba(${r},${g},${b},${alpha})`;
181
- ctx.beginPath();
182
- ctx.arc(px, py, s.size / 2, 0, Math.PI * 2);
183
- ctx.fill();
184
- } else {
185
- // Sub-pixel dim stars: fillRect is fine — they're one device pixel.
186
- const half = s.size / 2;
187
- ctx.fillStyle = `rgba(${r},${g},${b},${alpha})`;
188
- ctx.fillRect(px - half, py - half, s.size, s.size);
189
- }
190
- }
191
-
192
- ctx.globalCompositeOperation = "source-over";
193
- if (running) rafId = requestAnimationFrame(frame);
194
- }
195
-
196
- function drawStaticFrame() {
197
- ctx.clearRect(0, 0, width, height);
198
- ctx.globalCompositeOperation = "lighter";
199
- for (const s of stars) {
200
- const [r, g, b] = s.rgb;
201
- ctx.fillStyle = `rgba(${r},${g},${b},${s.baseAlpha})`;
202
- if (s.glow > 0) {
203
- ctx.beginPath();
204
- ctx.arc(s.x, s.y, s.size / 2, 0, Math.PI * 2);
205
- ctx.fill();
206
- } else {
207
- const half = s.size / 2;
208
- ctx.fillRect(s.x - half, s.y - half, s.size, s.size);
209
- }
210
- }
211
- ctx.globalCompositeOperation = "source-over";
212
- }
213
-
214
- function start() {
215
- if (running) return;
216
- running = true;
217
- rafId = requestAnimationFrame(frame);
218
- }
219
-
220
- function stop() {
221
- running = false;
222
- cancelAnimationFrame(rafId);
223
- }
224
-
225
- function sync() {
226
- const isDark = currentTheme() === "dark";
227
- const reduced = effectiveReducedMotionPreference();
228
- if (!isDark) {
229
- stop();
230
- ctx.clearRect(0, 0, width, height);
231
- return;
232
- }
233
- if (!stars.length) resize();
234
- if (reduced || document.hidden) {
235
- stop();
236
- drawStaticFrame();
237
- } else {
238
- start();
239
- }
240
- }
241
-
242
- window.addEventListener("resize", () => {
243
- resize();
244
- sync();
245
- }, { passive: true });
246
- window.addEventListener("pointermove", onPointerMove, { passive: true });
247
- document.addEventListener("visibilitychange", sync);
248
- window.matchMedia("(prefers-reduced-motion: reduce)").addEventListener("change", () => {
249
- applyReducedMotionPreference();
250
- sync();
251
- });
252
- window.addEventListener("orbit:motion-preference-change", sync);
253
-
254
- resize();
255
- sync();
256
- return { sync };
257
- }
258
-
259
- function buildSignatureStars(grain) {
260
- grain.querySelectorAll(".signature-star").forEach((el) => el.remove());
261
- const rng = mulberry32(0x5ca1e5);
262
- const SIG_COUNT = 7;
263
- for (let i = 0; i < SIG_COUNT; i++) {
264
- const star = document.createElement("div");
265
- star.className = "signature-star";
266
- star.setAttribute("aria-hidden", "true");
267
- star.style.left = (6 + rng() * 88).toFixed(2) + "%";
268
- star.style.top = (8 + rng() * 78).toFixed(2) + "%";
269
- star.style.animationDelay = (-rng() * 4.8).toFixed(2) + "s";
270
- grain.appendChild(star);
271
- }
272
- }
273
-
274
- /* Shooting stars — rare streaks that cross the sky. CSS handles the trail
275
- and fade; JS picks the trajectory and reschedules the next one. */
276
- function scheduleMeteor() {
277
- const delay = 14000 + Math.random() * 28000;
278
- setTimeout(spawnMeteor, delay);
279
- }
280
-
281
- function spawnMeteor() {
282
- const isDark = currentTheme() === "dark";
283
- const reduced = effectiveReducedMotionPreference();
284
- const grain = document.querySelector(".grain");
285
- if (!isDark || reduced || document.hidden || !grain) {
286
- scheduleMeteor();
287
- return;
288
- }
289
-
290
- const vw = window.innerWidth;
291
- const vh = window.innerHeight;
292
- const startX = Math.random() * vw * 0.6;
293
- const startY = Math.random() * vh * 0.55;
294
- const distance = 380 + Math.random() * 520;
295
- const angleDeg = 18 + Math.random() * 26;
296
- const rad = (angleDeg * Math.PI) / 180;
297
- const endX = startX + Math.cos(rad) * distance;
298
- const endY = startY + Math.sin(rad) * distance;
299
-
300
- const m = document.createElement("div");
301
- m.className = "meteor";
302
- m.setAttribute("aria-hidden", "true");
303
- m.style.setProperty("--from-x", startX + "px");
304
- m.style.setProperty("--from-y", startY + "px");
305
- m.style.setProperty("--to-x", endX + "px");
306
- m.style.setProperty("--to-y", endY + "px");
307
- m.style.setProperty("--angle", angleDeg + "deg");
308
- grain.appendChild(m);
309
- m.addEventListener("animationend", () => m.remove(), { once: true });
310
-
311
- scheduleMeteor();
312
- }
313
-
314
- function pickWeighted(r, table) {
315
- let acc = 0;
316
- for (const [value, weight] of table) {
317
- acc += weight;
318
- if (r < acc) return value;
319
- }
320
- return table[table.length - 1][0];
321
- }
322
-
323
- function mulberry32(seed) {
324
- let a = seed >>> 0;
325
- return function () {
326
- a = (a + 0x6d2b79f5) >>> 0;
327
- let t = a;
328
- t = Math.imul(t ^ (t >>> 15), t | 1);
329
- t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
330
- return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
331
- };
332
- }
@@ -1,129 +0,0 @@
1
- import { normalizeSettingsTab } from "./settings-tabs.js";
2
-
3
- // Mutable application state plus board/ticket query helpers. The `state`
4
- // object is the single source of truth for "what view is showing" and
5
- // "what data did the bootstrap give us"; modules read it freely and mutate
6
- // it in-place. The query helpers wrap the most common slices.
7
-
8
- export const state = {
9
- data: null,
10
- /** Currently selected board id. Single-board UI; this points at the active
11
- * board in `state.data.boards`. */
12
- boardId: "",
13
- /** URL-facing board identifier. Routes use slugs; API calls keep using ids. */
14
- boardSlug: "",
15
- selectedTicketId: "",
16
- expandedCardIds: new Set(),
17
- /** "empty" | "ticket" | "settings" — drives the main right overlay drawer (not the New flyout). */
18
- detailMode: "empty",
19
- /** "lanes" | "ai" | "data" | "notes" | "journal" — which Settings tab is active. */
20
- settingsTab: normalizeSettingsTab(localStorage.getItem("mab_settings_tab")),
21
- /** Whether to render the priority pill on kanban / related cards. The detail
22
- * drawer header always shows it. Default on; user can toggle it from the
23
- * Lanes settings tab. */
24
- showPriority: localStorage.getItem("mab_show_priority") !== "false",
25
- eventSource: null
26
- };
27
-
28
- export function getSettingsTab() {
29
- return normalizeSettingsTab(state.settingsTab || localStorage.getItem("mab_settings_tab"));
30
- }
31
-
32
- export function syncBoardSelection() {
33
- if (!state.data) return;
34
- const boards = state.data.boards || [];
35
- let board = state.boardId ? boards.find((b) => b.id === state.boardId) : null;
36
- if (!board && state.boardSlug) {
37
- board = boards.find((b) => b.slug === state.boardSlug) || boards.find((b) => b.id === state.boardSlug);
38
- }
39
- if (!board) {
40
- const activeBoardId = state.data.active_board_id;
41
- board = boards.find((b) => b.id === activeBoardId) || boards[0] || null;
42
- }
43
- state.boardId = board?.id || "";
44
- state.boardSlug = board?.slug || "";
45
- }
46
-
47
- export function currentBoard() {
48
- return (state.data?.boards || []).find((b) => b.id === state.boardId);
49
- }
50
-
51
- /** Labels defined for the current board (from bootstrap), for autocomplete. */
52
- export function boardLabelCatalog() {
53
- if (!state.boardId) return [];
54
- return (state.data?.labels || []).filter((label) => label.board_id === state.boardId);
55
- }
56
-
57
- export function statesForProject() {
58
- return state.data.states.filter((item) => item.board_id === state.boardId);
59
- }
60
-
61
- export function isAiReadyState(item) {
62
- return item?.role === "ai_ready" || item?.name === "AI Ready";
63
- }
64
-
65
- export function visibleStatesForProject() {
66
- const states = statesForProject();
67
- if (currentBoard()?.ai_enabled !== 0) return states;
68
- return states.filter((item) => !isAiReadyState(item));
69
- }
70
-
71
- export function selectableStatesForProject(currentStateId = "") {
72
- const states = statesForProject();
73
- if (currentBoard()?.ai_enabled !== 0) return states;
74
- return states.filter((item) => !isAiReadyState(item) || item.id === currentStateId);
75
- }
76
-
77
- export function ticketsForProject() {
78
- return state.data.tickets.filter((item) => item.board_id === state.boardId);
79
- }
80
-
81
- function compactTicketForBoard(ticket) {
82
- if (!ticket) return null;
83
- return {
84
- id: ticket.id,
85
- board_id: ticket.board_id,
86
- board_slug: ticket.board_slug,
87
- number: ticket.number,
88
- title: ticket.title,
89
- type: ticket.type,
90
- state_name: ticket.state_name,
91
- state_role: ticket.state_role,
92
- priority: ticket.priority
93
- };
94
- }
95
-
96
- function refreshBoardTicketDerivatives(boardId) {
97
- if (!state.data?.tickets) return;
98
- const boardTickets = state.data.tickets.filter((ticket) => ticket.board_id === boardId && !ticket.archived_at);
99
- const ticketById = new Map(boardTickets.map((ticket) => [ticket.id, ticket]));
100
- const childCountByParent = new Map();
101
- for (const ticket of boardTickets) {
102
- if (!ticket.parent_ticket_id) continue;
103
- childCountByParent.set(ticket.parent_ticket_id, (childCountByParent.get(ticket.parent_ticket_id) || 0) + 1);
104
- }
105
- for (const ticket of boardTickets) {
106
- ticket.parent_ticket = ticket.parent_ticket_id ? compactTicketForBoard(ticketById.get(ticket.parent_ticket_id)) : null;
107
- ticket.child_count = childCountByParent.get(ticket.id) || 0;
108
- }
109
- }
110
-
111
- /** Merge a ticket returned by a mutation/context endpoint into the bootstrap cache.
112
- * Keeps drawer edits responsive without paying for a full /api/bootstrap reload. */
113
- export function upsertTicket(updatedTicket) {
114
- if (!state.data?.tickets || !updatedTicket?.id) return;
115
- const index = state.data.tickets.findIndex((ticket) => ticket.id === updatedTicket.id);
116
- const previous = index >= 0 ? state.data.tickets[index] : {};
117
- const merged = {
118
- ...previous,
119
- ...updatedTicket,
120
- labels: updatedTicket.labels || previous.labels || [],
121
- comment_count: updatedTicket.comment_count ?? previous.comment_count ?? 0,
122
- child_count: updatedTicket.child_count ?? previous.child_count ?? 0,
123
- parent_ticket: updatedTicket.parent_ticket || previous.parent_ticket || null
124
- };
125
- if (index >= 0) state.data.tickets.splice(index, 1, merged);
126
- else state.data.tickets.push(merged);
127
- state.data.tickets.sort((a, b) => String(b.updated_at || "").localeCompare(String(a.updated_at || "")) || String(a.id).localeCompare(String(b.id)));
128
- refreshBoardTicketDerivatives(merged.board_id || state.boardId);
129
- }