@starlove/orbit 1.4.0 → 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.62905fcb.js +0 -952
  204. package/dist/full/js/app.62905fcb.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 -229
  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
@@ -1,374 +0,0 @@
1
- // Board picker flyout (the "Board" chip in the topbar). Lists every board
2
- // the actor can see, switches between them, and — for owners — exposes the
3
- // new-board form. Also owns the topbar chip label/title sync.
4
-
5
- import { boardFlyout, boardMenuBtn } from "./dom.js";
6
- import { state } from "./state.js";
7
- import { escapeHtml } from "./format.js";
8
- import { api } from "./api.js";
9
- import { navigate, syncUrlFromState } from "./router.js";
10
- import { closeCreateFlyout } from "./create-card.js";
11
- import { toast } from "./toast.js";
12
- import { load } from "./app.js";
13
- import { features } from "./config.js";
14
- import { exportAllBoards } from "./board-export.js";
15
-
16
- let boardFlyoutView = "list";
17
-
18
- export function closeBoardFlyout() {
19
- if (!boardFlyout) return;
20
- boardFlyout.hidden = true;
21
- boardFlyout.innerHTML = "";
22
- boardFlyoutView = "list";
23
- boardMenuBtn?.setAttribute("aria-expanded", "false");
24
- }
25
-
26
- export function closeMenuFlyouts() {
27
- closeBoardFlyout();
28
- }
29
-
30
- const boardChipIcon = `
31
- <svg
32
- class="topbar-icon"
33
- width="16"
34
- height="16"
35
- viewBox="0 0 24 24"
36
- fill="none"
37
- aria-hidden="true"
38
- >
39
- <rect x="4" y="2" width="6" height="20" rx="0.75" stroke="currentColor" stroke-width="2" />
40
- <rect x="14" y="2" width="6" height="20" rx="0.75" stroke="currentColor" stroke-width="2" />
41
- </svg>`;
42
-
43
- export function updateTopbarChips() {
44
- if (!state.data) return;
45
- const board = (state.data.boards || []).find((b) => b.id === state.boardId);
46
- if (boardMenuBtn) {
47
- const label = board?.name || "Board";
48
- boardMenuBtn.innerHTML = `${boardChipIcon}<span class="topbar-chip-label">${escapeHtml(label)}</span>`;
49
- boardMenuBtn.title = board ? `${board.name} — slug: ${board.slug}` : "Choose board";
50
- }
51
- }
52
-
53
- async function inflateZipEntry(bytes) {
54
- if (typeof DecompressionStream === "undefined") {
55
- throw new Error("Compressed ZIP entries are not supported in this browser.");
56
- }
57
- const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("deflate-raw"));
58
- return new Uint8Array(await new Response(stream).arrayBuffer());
59
- }
60
-
61
- function parseImportSnapshotText(text, filename = "snapshot") {
62
- const trimmed = String(text || "").trimStart();
63
- if (trimmed.startsWith("<")) throw new Error(`${filename} looks like HTML instead of JSON.`);
64
- try {
65
- const snapshot = JSON.parse(text);
66
- if (!snapshot?.board) throw new Error("snapshot_missing_board");
67
- return snapshot;
68
- } catch (error) {
69
- throw new Error(`${filename}: ${error?.message || "invalid JSON"}`);
70
- }
71
- }
72
-
73
- async function readZipSnapshots(file) {
74
- const buffer = await file.arrayBuffer();
75
- const view = new DataView(buffer);
76
- const bytes = new Uint8Array(buffer);
77
- const decoder = new TextDecoder();
78
- const snapshots = [];
79
- let offset = 0;
80
-
81
- while (offset + 30 <= bytes.length) {
82
- const signature = view.getUint32(offset, true);
83
- if (signature === 0x02014b50 || signature === 0x06054b50) break;
84
- if (signature !== 0x04034b50) throw new Error("Invalid ZIP archive.");
85
-
86
- const flags = view.getUint16(offset + 6, true);
87
- const method = view.getUint16(offset + 8, true);
88
- const compressedSize = view.getUint32(offset + 18, true);
89
- const filenameLength = view.getUint16(offset + 26, true);
90
- const extraLength = view.getUint16(offset + 28, true);
91
- const nameStart = offset + 30;
92
- const dataStart = nameStart + filenameLength + extraLength;
93
- const dataEnd = dataStart + compressedSize;
94
- if (flags & 0x0008) throw new Error("ZIP entries with data descriptors are not supported.");
95
- if (dataEnd > bytes.length) throw new Error("Invalid ZIP archive.");
96
-
97
- const name = decoder.decode(bytes.slice(nameStart, nameStart + filenameLength));
98
- if (!name.endsWith("/")) {
99
- if (!name.toLowerCase().endsWith(".orbit.json")) {
100
- throw new Error("ZIP imports may contain only .orbit.json files.");
101
- }
102
- const compressed = bytes.slice(dataStart, dataEnd);
103
- const data = method === 0 ? compressed : method === 8 ? await inflateZipEntry(compressed) : null;
104
- if (!data) throw new Error("Unsupported ZIP compression method.");
105
- snapshots.push(parseImportSnapshotText(decoder.decode(data), name));
106
- }
107
-
108
- offset = dataEnd;
109
- }
110
-
111
- if (!snapshots.length) throw new Error("ZIP archive did not contain any .orbit.json snapshots.");
112
- return snapshots;
113
- }
114
-
115
- async function readImportSnapshots(file) {
116
- const name = String(file?.name || "").toLowerCase();
117
- if (name.endsWith(".zip")) return readZipSnapshots(file);
118
- if (name.endsWith(".orbit.json")) return [parseImportSnapshotText(await file.text(), file.name)];
119
- throw new Error("Choose a .zip archive or a single .orbit.json snapshot.");
120
- }
121
-
122
- function renderBoardFlyoutHeader() {
123
- const showListButton = boardFlyoutView !== "list";
124
- const newLabel = showListButton ? "List" : "New";
125
- return `
126
- <div class="menu-flyout-head menu-flyout-head--actions">
127
- <span>Board</span>
128
- <span class="menu-flyout-actions">
129
- ${features.multiBoard ? `<button type="button" data-variant="ghost" data-board-view="${showListButton ? "list" : "new"}">${newLabel}</button>` : ""}
130
- <button type="button" data-variant="ghost" data-board-view="export">Export</button>
131
- </span>
132
- </div>`;
133
- }
134
-
135
- function renderBoardFlyoutInner() {
136
- const boards = state.data.boards || [];
137
- const rows = boards
138
- .map(
139
- (b) => `
140
- <button type="button" class="menu-flyout-item ${b.id === state.boardId ? "is-current" : ""}" data-variant="menu-item" data-pick-board="${escapeHtml(b.id)}" role="menuitem">
141
- <span class="menu-flyout-item-title">${escapeHtml(b.name)}</span>
142
- <span class="menu-flyout-item-meta">${escapeHtml(b.slug)}</span>
143
- </button>`
144
- )
145
- .join("");
146
- const listBody =
147
- rows ||
148
- `<p class="menu-flyout-hint menu-flyout-hint--empty">No boards yet. Create one below if you can.</p>`;
149
- const listBlock = `<div class="menu-flyout-list">${listBody}</div>`;
150
- const createBlock = `
151
- <div class="menu-flyout-section">
152
- <h3 class="menu-flyout-heading">New Board</h3>
153
- <form id="createBoardForm" class="menu-flyout-form">
154
- <input name="name" type="text" required placeholder="Board name" autocomplete="off" />
155
- <div class="folder-picker-field">
156
- <input id="repoPathInput" name="repo_path" type="text" readonly placeholder="Coding project folder (optional)" autocomplete="off" />
157
- <button type="button" id="pickRepoFolderBtn">Browse</button>
158
- </div>
159
- <div id="helperFilesReveal" hidden>
160
- <p class="menu-flyout-hint menu-flyout-hint--intro">Orbit writes <code>SKILL-ORBIT.md</code> and <code>AGENTS.md</code> into this folder for your AI coding agent.</p>
161
- <label class="menu-flyout-check">
162
- <input id="helperFilesCheckbox" name="manage_helper_files" type="checkbox" checked />
163
- <span>Generate AI helper files (<code>SKILL-ORBIT.md</code>, <code>AGENTS.md</code>)</span>
164
- </label>
165
- </div>
166
- <input name="slug" type="text" placeholder="Slug (optional)" autocomplete="off" />
167
- <button type="submit">Create board</button>
168
- </form>
169
- </div>`;
170
- const exportBlock = `
171
- <div class="menu-flyout-section menu-flyout-section--export">
172
- <p class="menu-flyout-copy">Export every board as Orbit snapshots in a ZIP archive.</p>
173
- ${features.attachments ? `
174
- <label class="orbit-check menu-flyout-toggle">
175
- <input type="checkbox" id="exportAllBoardImages" />
176
- <span class="orbit-check-box" aria-hidden="true">
177
- <svg viewBox="0 0 16 16" class="orbit-check-tick"><path d="M3.5 8.5l3 3 6-7" /></svg>
178
- </span>
179
- <span class="menu-flyout-toggle-text">Include attached images</span>
180
- </label>
181
- ` : ""}
182
- <div class="menu-flyout-actions-row">
183
- <button type="button" id="exportAllBoards">Export All</button>
184
- </div>
185
- <div class="menu-flyout-divider" aria-hidden="true"></div>
186
- <section class="menu-flyout-danger-section" aria-label="Import All">
187
- <h3 class="menu-flyout-danger-heading">Import All</h3>
188
- <p class="menu-flyout-danger-copy">Imports fully overwrite each matching board. Snapshots without a matching board create new boards.</p>
189
- <div class="menu-flyout-actions-row">
190
- <input type="file" id="importAllBoardsFile" class="import-file-input" accept=".zip,.orbit.json,application/zip" />
191
- <button type="button" data-arc="danger" id="importAllBoards">Import All</button>
192
- </div>
193
- </section>
194
- </div>`;
195
- const body = boardFlyoutView === "export" ? exportBlock : boardFlyoutView === "new" && features.multiBoard ? createBlock : listBlock;
196
- return `
197
- ${renderBoardFlyoutHeader()}
198
- ${body}
199
- `;
200
- }
201
-
202
- function rerenderBoardFlyout(view) {
203
- boardFlyoutView = view;
204
- boardFlyout.innerHTML = renderBoardFlyoutInner();
205
- wireBoardFlyout();
206
- if (view === "new") boardFlyout.querySelector('input[name="name"]')?.focus();
207
- }
208
-
209
- function wireBoardFlyout() {
210
- boardFlyout.querySelectorAll("[data-board-view]").forEach((button) => {
211
- button.addEventListener("click", (event) => {
212
- event.stopPropagation();
213
- const view = button.getAttribute("data-board-view") || "list";
214
- rerenderBoardFlyout(view);
215
- });
216
- });
217
-
218
- boardFlyout.querySelectorAll("[data-pick-board]").forEach((btn) => {
219
- btn.addEventListener("click", async (event) => {
220
- event.stopPropagation();
221
- const id = btn.getAttribute("data-pick-board");
222
- closeBoardFlyout();
223
- if (id === state.boardId) return;
224
- closeCreateFlyout();
225
- await navigate({
226
- boardId: id,
227
- view: "board"
228
- });
229
- });
230
- });
231
- const form = boardFlyout.querySelector("#createBoardForm");
232
- if (form) {
233
- const pickRepoFolderBtn = form.querySelector("#pickRepoFolderBtn");
234
- const repoPathInput = form.querySelector("#repoPathInput");
235
- const helperFilesReveal = form.querySelector("#helperFilesReveal");
236
- // The folder explanation and "Generate AI helper files" option only make
237
- // sense once a folder is chosen — there's nowhere to write them otherwise.
238
- // Reveal both with the folder value rather than disabling.
239
- const syncHelperFilesVisibility = () => {
240
- if (helperFilesReveal) helperFilesReveal.hidden = !repoPathInput.value.trim();
241
- };
242
- syncHelperFilesVisibility();
243
- pickRepoFolderBtn?.addEventListener("click", async (event) => {
244
- event.preventDefault();
245
- pickRepoFolderBtn.disabled = true;
246
- try {
247
- const result = await api("/api/system/pick-folder", { method: "POST" });
248
- if (result.path) {
249
- repoPathInput.value = result.path;
250
- repoPathInput.dispatchEvent(new Event("input", { bubbles: true }));
251
- syncHelperFilesVisibility();
252
- }
253
- } catch (error) {
254
- toast.error(error?.message || "Folder picker unavailable");
255
- } finally {
256
- pickRepoFolderBtn.disabled = false;
257
- }
258
- });
259
- form.addEventListener("submit", async (event) => {
260
- event.preventDefault();
261
- const fd = new FormData(form);
262
- const slug = String(fd.get("slug") || "").trim();
263
- const repoPath = String(fd.get("repo_path") || "").trim();
264
- // Folder is optional. Only forward the helper-files choice when a folder
265
- // is set; the server ignores it otherwise.
266
- const created = await api("/api/boards", {
267
- method: "POST",
268
- body: {
269
- name: fd.get("name"),
270
- ...(repoPath ? { repo_path: repoPath, manage_helper_files: fd.get("manage_helper_files") === "on" } : {}),
271
- ...(slug ? { slug } : {})
272
- }
273
- });
274
- closeBoardFlyout();
275
- state.boardId = created.id;
276
- state.selectedTicketId = "";
277
- state.detailMode = "settings";
278
- await load();
279
- syncUrlFromState();
280
- toast.success("Board created");
281
- });
282
- }
283
-
284
- boardFlyout.querySelector("#exportAllBoards")?.addEventListener("click", async (event) => {
285
- const button = event.currentTarget;
286
- const includeAttachments = Boolean(boardFlyout.querySelector("#exportAllBoardImages")?.checked);
287
- button.disabled = true;
288
- const originalText = button.textContent;
289
- button.textContent = "Exporting...";
290
- try {
291
- await exportAllBoards({ includeAttachments });
292
- } catch (error) {
293
- toast.error(`Export failed: ${error?.message || "request failed"}`);
294
- } finally {
295
- button.disabled = false;
296
- button.textContent = originalText;
297
- }
298
- });
299
-
300
- const importAllBoardsFile = boardFlyout.querySelector("#importAllBoardsFile");
301
- boardFlyout.querySelector("#importAllBoards")?.addEventListener("click", () => {
302
- importAllBoardsFile?.click();
303
- });
304
-
305
- importAllBoardsFile?.addEventListener("change", async (event) => {
306
- const input = event.currentTarget;
307
- const file = input.files?.[0];
308
- if (!file) return;
309
- const importButton = boardFlyout.querySelector("#importAllBoards");
310
- const originalText = importButton?.textContent;
311
- if (importButton) {
312
- importButton.disabled = true;
313
- importButton.textContent = "Importing...";
314
- }
315
- try {
316
- const snapshots = await readImportSnapshots(file);
317
- const boardsById = new Map((state.data.boards || []).map((board) => [board.id, board]));
318
- const boardsBySlug = new Map((state.data.boards || []).map((board) => [board.slug, board]));
319
- let replaced = 0;
320
- let created = 0;
321
-
322
- for (const snapshot of snapshots) {
323
- const incomingBoard = snapshot.board || {};
324
- const match = boardsBySlug.get(incomingBoard.slug) || boardsById.get(incomingBoard.id);
325
- if (match) {
326
- await api("/api/admin/import", {
327
- method: "POST",
328
- body: {
329
- board_id: match.id,
330
- replace_existing: true,
331
- snapshot
332
- }
333
- });
334
- replaced += 1;
335
- continue;
336
- }
337
-
338
- const result = await api("/api/admin/import", {
339
- method: "POST",
340
- body: {
341
- ...(state.boardId ? { board_id: state.boardId } : {}),
342
- create_new: true,
343
- snapshot
344
- }
345
- });
346
- created += 1;
347
- if (result.imported_board_id) boardsById.set(result.imported_board_id, { id: result.imported_board_id, slug: result.imported_board_slug });
348
- if (result.imported_board_slug) boardsBySlug.set(result.imported_board_slug, { id: result.imported_board_id, slug: result.imported_board_slug });
349
- }
350
-
351
- await load();
352
- toast.success(`Imported ${snapshots.length} board${snapshots.length === 1 ? "" : "s"} (${replaced} replaced, ${created} created)`);
353
- closeBoardFlyout();
354
- await navigate({ boardId: state.boardId, view: "board" }, { replace: true });
355
- } catch (error) {
356
- toast.error(`Import failed: ${error?.message || "invalid import file"}`);
357
- } finally {
358
- input.value = "";
359
- if (importButton) {
360
- importButton.disabled = false;
361
- importButton.textContent = originalText;
362
- }
363
- }
364
- });
365
- }
366
-
367
- export function openBoardFlyout() {
368
- closeCreateFlyout();
369
- if (boardFlyoutView === "new" && !features.multiBoard) boardFlyoutView = "list";
370
- boardFlyout.innerHTML = renderBoardFlyoutInner();
371
- boardFlyout.hidden = false;
372
- boardMenuBtn?.setAttribute("aria-expanded", "true");
373
- wireBoardFlyout();
374
- }