@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,545 +0,0 @@
1
- // Shared card action menu data, mutations, and desktop context-menu presentation.
2
- // Mobile can reuse buildCardActionMenu() + createCardActionHandlers() and render
3
- // the same model as a bottom sheet without duplicating mutation logic.
4
- //
5
- // Submenus are reparented to document.body on open so they escape the menu's
6
- // backdrop-filter containing block (otherwise position: fixed coords would be
7
- // interpreted relative to the menu instead of the viewport).
8
-
9
- import { api, withBoardQuery } from "./api.js";
10
- import { state, visibleStatesForProject } from "./state.js";
11
- import { canonicalTicketType, escapeHtml, priorityLabel, typeLabel } from "./format.js";
12
- import { toast } from "./toast.js";
13
-
14
- const CARD_TYPES = ["epic", "feature", "task", "bug"];
15
- const PRIORITIES = [0, 1, 2, 3, 4];
16
- const MENU_MARGIN = 8;
17
- const SUBMENU_GAP = 6;
18
- const HOVER_CLOSE_DELAY = 180;
19
-
20
- let openMenu = null;
21
- let dragInFlight = false;
22
- let dismissController = null;
23
- let lastFocusedElement = null;
24
- const submenuByItem = new WeakMap();
25
- const itemBySubmenu = new WeakMap();
26
- const openSubmenus = new Set();
27
- let pendingCloseTimer = null;
28
-
29
- export function buildCardActionMenu(ticket, lanes = visibleStatesForProject()) {
30
- if (!ticket) return [];
31
- return [
32
- {
33
- id: "move",
34
- label: "Move to",
35
- children: lanes.map((lane) => ({
36
- id: `move:${lane.id}`,
37
- label: lane.name,
38
- action: "move",
39
- value: lane.id,
40
- checked: lane.id === ticket.state_id,
41
- disabled: lane.id === ticket.state_id
42
- }))
43
- },
44
- {
45
- id: "priority",
46
- label: "Priority",
47
- children: PRIORITIES.map((priority) => ({
48
- id: `priority:${priority}`,
49
- label: priorityLabel(priority),
50
- action: "priority",
51
- value: priority,
52
- checked: Number(ticket.priority) === priority,
53
- disabled: Number(ticket.priority) === priority
54
- }))
55
- },
56
- {
57
- id: "type",
58
- label: "Type",
59
- children: CARD_TYPES.map((type) => ({
60
- id: `type:${type}`,
61
- label: typeLabel(type),
62
- action: "type",
63
- value: type,
64
- checked: canonicalTicketType(ticket.type) === type,
65
- disabled: canonicalTicketType(ticket.type) === type
66
- }))
67
- },
68
- { id: "open", label: "Open", action: "open" },
69
- { id: "archive", label: "Archive", action: "archive", danger: true }
70
- ];
71
- }
72
-
73
- export function calculateMenuPosition({ x, y, menuWidth, menuHeight, viewportWidth, viewportHeight, margin = MENU_MARGIN }) {
74
- const maxLeft = Math.max(margin, viewportWidth - menuWidth - margin);
75
- const maxTop = Math.max(margin, viewportHeight - menuHeight - margin);
76
- const preferredLeft = x + menuWidth + margin > viewportWidth ? x - menuWidth : x;
77
- const preferredTop = y + menuHeight + margin > viewportHeight ? y - menuHeight : y;
78
- return {
79
- left: Math.min(Math.max(margin, preferredLeft), maxLeft),
80
- top: Math.min(Math.max(margin, preferredTop), maxTop)
81
- };
82
- }
83
-
84
- export function calculateSubmenuPosition({
85
- triggerRect,
86
- submenuWidth,
87
- submenuHeight,
88
- viewportWidth,
89
- viewportHeight,
90
- margin = MENU_MARGIN,
91
- gap = SUBMENU_GAP
92
- }) {
93
- const maxLeft = Math.max(margin, viewportWidth - submenuWidth - margin);
94
- const rightLeft = triggerRect.right + gap;
95
- const leftLeft = triggerRect.left - submenuWidth - gap;
96
- const fitsRight = rightLeft + submenuWidth + margin <= viewportWidth;
97
- const fitsLeft = leftLeft >= margin;
98
- const preferredLeft = fitsRight || !fitsLeft ? rightLeft : leftLeft;
99
- const maxHeight = Math.max(0, viewportHeight - margin * 2);
100
- const boundedHeight = Math.min(submenuHeight, maxHeight);
101
- const maxTop = Math.max(margin, viewportHeight - boundedHeight - margin);
102
- const preferredTop = triggerRect.top - gap;
103
-
104
- return {
105
- left: Math.min(Math.max(margin, preferredLeft), maxLeft),
106
- top: Math.min(Math.max(margin, preferredTop), maxTop),
107
- maxHeight
108
- };
109
- }
110
-
111
- export function createCardActionHandlers({ apiClient = api, navigator = null, confirmer = globalThis.confirm } = {}) {
112
- async function patchTicket(ticket, body) {
113
- await apiClient(withBoardQuery(`/api/tickets/${encodeURIComponent(ticket.id)}`), {
114
- method: "PATCH",
115
- body
116
- });
117
- }
118
-
119
- return {
120
- async open(ticket) {
121
- if (navigator) return navigator({ boardId: state.boardId, view: "ticket", ticketId: ticket.id });
122
- return null;
123
- },
124
- async move(ticket, stateId) {
125
- await patchTicket(ticket, { state_id: stateId });
126
- },
127
- async priority(ticket, priority) {
128
- await patchTicket(ticket, { priority });
129
- },
130
- async type(ticket, type) {
131
- await patchTicket(ticket, { type });
132
- },
133
- async archive(ticket) {
134
- if (typeof confirmer === "function" && !confirmer(`Archive "${ticket.title || "card"}"? You can restore it from Settings → Card Archive.`)) return false;
135
- await apiClient(withBoardQuery(`/api/tickets/${encodeURIComponent(ticket.id)}/archive`), { method: "POST" });
136
- toast.removed(`Archived: ${ticket.title || "card"}`);
137
- return true;
138
- }
139
- };
140
- }
141
-
142
- export function setCardActionDragInFlight(value) {
143
- dragInFlight = Boolean(value);
144
- }
145
-
146
- export function closeCardActionMenu({ restoreFocus = false } = {}) {
147
- cancelPendingClose();
148
- if (dismissController) {
149
- dismissController.abort();
150
- dismissController = null;
151
- }
152
- for (const submenu of openSubmenus) {
153
- if (submenu.isConnected) submenu.remove();
154
- }
155
- openSubmenus.clear();
156
- if (openMenu) {
157
- openMenu.querySelectorAll(".card-action-menu-item.has-submenu").forEach((item) => {
158
- const submenu = submenuByItem.get(item);
159
- if (submenu?.isConnected) submenu.remove();
160
- submenuByItem.delete(item);
161
- });
162
- openMenu.remove();
163
- openMenu = null;
164
- }
165
- if (restoreFocus && lastFocusedElement?.isConnected) {
166
- lastFocusedElement.focus({ preventScroll: true });
167
- }
168
- lastFocusedElement = null;
169
- }
170
-
171
- export function openCardActionMenu({ ticket, anchor, x, y, handlers = createCardActionHandlers(), onBeforeOpen = null }) {
172
- closeCardActionMenu();
173
- if (!ticket || dragInFlight) return null;
174
-
175
- if (typeof onBeforeOpen === "function") onBeforeOpen();
176
- lastFocusedElement = anchor || document.activeElement;
177
-
178
- const menu = document.createElement("div");
179
- menu.className = "card-action-menu";
180
- menu.setAttribute("role", "menu");
181
- menu.setAttribute("aria-label", `Actions for #${ticket.number || ""} ${ticket.title || "card"}`.trim());
182
- menu.dataset.ticketId = ticket.id;
183
- menu.innerHTML = renderMenuItems(buildCardActionMenu(ticket));
184
- document.body.append(menu);
185
- openMenu = menu;
186
-
187
- // Detach each submenu from its <li> and reparent to document.body. The menu's
188
- // backdrop-filter creates a containing block, so a nested position:fixed
189
- // submenu would be positioned relative to the menu, not the viewport.
190
- menu.querySelectorAll(".card-action-menu-item.has-submenu").forEach((item) => {
191
- const submenu = item.querySelector(":scope > .card-action-submenu");
192
- if (!submenu) return;
193
- submenu.remove();
194
- document.body.append(submenu);
195
- submenuByItem.set(item, submenu);
196
- itemBySubmenu.set(submenu, item);
197
- wireSubmenu(submenu, ticket, handlers);
198
- });
199
-
200
- const rect = menu.getBoundingClientRect();
201
- const position = calculateMenuPosition({
202
- x,
203
- y,
204
- menuWidth: rect.width,
205
- menuHeight: rect.height,
206
- viewportWidth: window.innerWidth,
207
- viewportHeight: window.innerHeight
208
- });
209
- menu.style.left = `${position.left}px`;
210
- menu.style.top = `${position.top}px`;
211
-
212
- wireMenu(menu, ticket, handlers);
213
- const first = firstEnabledItem(menu);
214
- first?.focus({ preventScroll: true });
215
-
216
- dismissController = new AbortController();
217
- const signal = dismissController.signal;
218
- requestAnimationFrame(() => {
219
- document.addEventListener("pointerdown", dismissOnOutsidePointer, { signal, capture: true });
220
- });
221
- document.addEventListener("keydown", dismissOnEscape, { signal, capture: true });
222
- document.addEventListener("scroll", dismissOnScroll, { signal, capture: true, passive: true });
223
- window.addEventListener("resize", () => closeCardActionMenu(), { signal, passive: true });
224
- window.addEventListener("blur", () => closeCardActionMenu(), { signal });
225
-
226
- return menu;
227
- }
228
-
229
- export function bindCardContextMenu(card, ticket, { handlers = createCardActionHandlers(), onBeforeOpen = null } = {}) {
230
- card.addEventListener("contextmenu", (event) => {
231
- event.preventDefault();
232
- event.stopPropagation();
233
- if (dragInFlight) {
234
- closeCardActionMenu();
235
- return;
236
- }
237
- openCardActionMenu({
238
- ticket,
239
- anchor: card,
240
- x: event.clientX,
241
- y: event.clientY,
242
- handlers,
243
- onBeforeOpen
244
- });
245
- });
246
- }
247
-
248
- // Delegated variant: a single listener on the board container resolves the card
249
- // and its ticket per event. Lets renderBoard() swap innerHTML freely without
250
- // re-binding a contextmenu listener onto every card on every render.
251
- export function bindCardContextMenuDelegated(container, resolveTicket, { handlers = createCardActionHandlers(), onBeforeOpen = null } = {}) {
252
- container.addEventListener("contextmenu", (event) => {
253
- const card = event.target instanceof Element ? event.target.closest(".card") : null;
254
- if (!card) return;
255
- const ticket = resolveTicket(card.dataset.ticketId);
256
- if (!ticket) return;
257
- event.preventDefault();
258
- event.stopPropagation();
259
- if (dragInFlight) {
260
- closeCardActionMenu();
261
- return;
262
- }
263
- openCardActionMenu({ ticket, anchor: card, x: event.clientX, y: event.clientY, handlers, onBeforeOpen });
264
- });
265
- }
266
-
267
- function renderMenuItems(items) {
268
- return `<ul class="card-action-menu-list" role="none">${items.map(renderMenuItem).join("")}</ul>`;
269
- }
270
-
271
- function renderMenuItem(item) {
272
- const hasChildren = Boolean(item.children?.length);
273
- const classes = ["card-action-menu-item"];
274
- if (hasChildren) classes.push("has-submenu");
275
- if (item.danger) classes.push("is-danger");
276
- const check = item.checked ? `<span class="card-action-check" aria-hidden="true">✓</span>` : `<span class="card-action-check" aria-hidden="true"></span>`;
277
- const suffix = hasChildren ? `<span class="card-action-arrow" aria-hidden="true">›</span>` : "";
278
- return `
279
- <li class="${classes.join(" ")}" role="none">
280
- <button type="button"
281
- class="card-action-button"
282
- role="menuitem${item.checked ? "checkbox" : ""}"
283
- data-action="${escapeHtml(item.action || "")}" data-value="${escapeHtml(item.value ?? "")}" data-menu-id="${escapeHtml(item.id)}"
284
- ${item.disabled ? "disabled" : ""}
285
- ${item.checked ? 'aria-checked="true"' : ""}
286
- ${hasChildren ? 'aria-haspopup="menu" aria-expanded="false"' : ""}>
287
- ${hasChildren ? "" : check}
288
- <span class="card-action-label">${escapeHtml(item.label)}</span>
289
- ${suffix}
290
- </button>
291
- ${hasChildren ? `<div class="card-action-submenu" role="menu">${renderMenuItems(item.children)}</div>` : ""}
292
- </li>
293
- `;
294
- }
295
-
296
- function wireMenu(menu, ticket, handlers) {
297
- menu.addEventListener("pointerover", (event) => {
298
- const item = event.target instanceof Element ? event.target.closest(".card-action-menu-item") : null;
299
- if (!item || !menu.contains(item)) return;
300
- if (item.classList.contains("has-submenu")) {
301
- cancelPendingClose();
302
- openSubmenu(item);
303
- } else {
304
- scheduleCloseOtherSubmenus(menu);
305
- }
306
- }, true);
307
-
308
- menu.addEventListener("focusin", (event) => {
309
- const item = event.target instanceof Element ? event.target.closest(".card-action-menu-item") : null;
310
- if (!item || !menu.contains(item)) return;
311
- if (item.classList.contains("has-submenu")) {
312
- cancelPendingClose();
313
- openSubmenu(item);
314
- } else {
315
- cancelPendingClose();
316
- closeOpenSubmenus(menu);
317
- }
318
- });
319
-
320
- menu.addEventListener("click", (event) => handleItemClick(event, menu, ticket, handlers));
321
- menu.addEventListener("keydown", (event) => handleMenuKeydown(event, menu));
322
- }
323
-
324
- function wireSubmenu(submenu, ticket, handlers) {
325
- // Keep the submenu open while the pointer is inside it.
326
- submenu.addEventListener("pointerenter", cancelPendingClose);
327
- submenu.addEventListener("pointerover", (event) => {
328
- if (event.target instanceof Element && submenu.contains(event.target)) {
329
- cancelPendingClose();
330
- }
331
- }, true);
332
-
333
- submenu.addEventListener("click", (event) => {
334
- if (!openMenu) return;
335
- handleItemClick(event, openMenu, ticket, handlers, submenu);
336
- });
337
- submenu.addEventListener("keydown", (event) => {
338
- if (!openMenu) return;
339
- handleMenuKeydown(event, openMenu);
340
- });
341
- }
342
-
343
- function handleItemClick(event, rootMenu, ticket, handlers, container = rootMenu) {
344
- const button = event.target instanceof Element ? event.target.closest("button.card-action-button") : null;
345
- if (!button || !container.contains(button) || button.disabled) return;
346
- event.preventDefault();
347
- event.stopPropagation();
348
-
349
- const parent = button.closest(".card-action-menu-item");
350
- if (parent?.classList.contains("has-submenu")) {
351
- cancelPendingClose();
352
- openSubmenu(parent);
353
- firstEnabledItem(submenuByItem.get(parent))?.focus({ preventScroll: true });
354
- return;
355
- }
356
-
357
- const action = button.dataset.action;
358
- const value = button.dataset.value;
359
- closeCardActionMenu();
360
- if (action === "open") return handlers.open(ticket);
361
- if (action === "move") return handlers.move(ticket, value);
362
- if (action === "priority") return handlers.priority(ticket, Number(value));
363
- if (action === "type") return handlers.type(ticket, value);
364
- if (action === "archive") return handlers.archive(ticket);
365
- }
366
-
367
- function handleMenuKeydown(event, rootMenu) {
368
- const button = event.target instanceof Element ? event.target.closest("button.card-action-button") : null;
369
- if (!button) return;
370
- const submenu = button.closest(".card-action-submenu");
371
- const inRoot = rootMenu.contains(button);
372
- if (!inRoot && !submenu) return;
373
-
374
- const currentMenu = button.closest(".card-action-menu-list");
375
- const item = button.closest(".card-action-menu-item");
376
-
377
- if (event.key === "Escape") {
378
- event.preventDefault();
379
- if (submenu) {
380
- const parentItem = itemBySubmenu.get(submenu);
381
- const parentButton = parentItem?.querySelector(":scope > .card-action-button");
382
- closeSubmenu(parentItem);
383
- parentButton?.focus({ preventScroll: true });
384
- } else {
385
- closeCardActionMenu({ restoreFocus: true });
386
- }
387
- return;
388
- }
389
-
390
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
391
- event.preventDefault();
392
- moveFocus(currentMenu, button, event.key === "ArrowDown" ? 1 : -1);
393
- return;
394
- }
395
-
396
- if (event.key === "Home" || event.key === "End") {
397
- event.preventDefault();
398
- const enabled = enabledItems(currentMenu);
399
- enabled[event.key === "Home" ? 0 : enabled.length - 1]?.focus({ preventScroll: true });
400
- return;
401
- }
402
-
403
- if (event.key === "ArrowRight" && item?.classList.contains("has-submenu")) {
404
- event.preventDefault();
405
- cancelPendingClose();
406
- openSubmenu(item);
407
- firstEnabledItem(submenuByItem.get(item))?.focus({ preventScroll: true });
408
- return;
409
- }
410
-
411
- if (event.key === "ArrowLeft" && submenu) {
412
- const parentItem = itemBySubmenu.get(submenu);
413
- const parentButton = parentItem?.querySelector(":scope > .card-action-button");
414
- if (parentButton) {
415
- event.preventDefault();
416
- closeSubmenu(parentItem);
417
- parentButton.focus({ preventScroll: true });
418
- }
419
- }
420
- }
421
-
422
- function dismissOnOutsidePointer(event) {
423
- if (!openMenu) return;
424
- if (isEventInsideAnyMenu(event)) return;
425
- closeCardActionMenu();
426
- }
427
-
428
- function dismissOnEscape(event) {
429
- if (event.key === "Escape" && openMenu) {
430
- event.preventDefault();
431
- closeCardActionMenu({ restoreFocus: true });
432
- }
433
- }
434
-
435
- function dismissOnScroll(event) {
436
- if (!openMenu) return;
437
- if (isEventInsideAnyMenu(event)) return;
438
- closeCardActionMenu();
439
- }
440
-
441
- function isEventInsideAnyMenu(event) {
442
- const target = event.target;
443
- if (!(target instanceof Node)) return false;
444
- if (openMenu?.contains(target)) return true;
445
- for (const submenu of openSubmenus) {
446
- if (submenu.contains(target)) return true;
447
- }
448
- return false;
449
- }
450
-
451
- function openSubmenu(item) {
452
- const siblings = item.parentElement?.children || [];
453
- for (const sibling of siblings) {
454
- if (sibling !== item && sibling.classList?.contains("has-submenu") && sibling.classList.contains("is-open")) {
455
- closeSubmenu(sibling);
456
- }
457
- }
458
- const submenu = submenuByItem.get(item);
459
- if (!submenu) return;
460
- if (item.classList.contains("is-open")) {
461
- positionSubmenu(item, submenu);
462
- return;
463
- }
464
- item.classList.add("is-open");
465
- item.querySelector(":scope > .card-action-button")?.setAttribute("aria-expanded", "true");
466
- submenu.classList.add("is-open");
467
- openSubmenus.add(submenu);
468
- positionSubmenu(item, submenu);
469
- }
470
-
471
- function closeSubmenu(item) {
472
- if (!item) return;
473
- item.classList.remove("is-open");
474
- item.querySelector(":scope > .card-action-button")?.setAttribute("aria-expanded", "false");
475
- const submenu = submenuByItem.get(item);
476
- if (!submenu) return;
477
- submenu.classList.remove("is-open");
478
- submenu.style.removeProperty("left");
479
- submenu.style.removeProperty("top");
480
- submenu.style.removeProperty("max-height");
481
- openSubmenus.delete(submenu);
482
- }
483
-
484
- function closeOpenSubmenus(scope) {
485
- scope.querySelectorAll(".card-action-menu-item.has-submenu.is-open").forEach(closeSubmenu);
486
- }
487
-
488
- function scheduleCloseOtherSubmenus(rootMenu) {
489
- cancelPendingClose();
490
- pendingCloseTimer = setTimeout(() => {
491
- pendingCloseTimer = null;
492
- if (openMenu === rootMenu) closeOpenSubmenus(rootMenu);
493
- }, HOVER_CLOSE_DELAY);
494
- }
495
-
496
- function cancelPendingClose() {
497
- if (pendingCloseTimer != null) {
498
- clearTimeout(pendingCloseTimer);
499
- pendingCloseTimer = null;
500
- }
501
- }
502
-
503
- function positionSubmenu(item, submenu) {
504
- const trigger = item.querySelector(":scope > .card-action-button");
505
- if (!submenu || !trigger) return;
506
-
507
- const availableHeight = Math.max(0, window.innerHeight - MENU_MARGIN * 2);
508
- submenu.style.maxHeight = `${availableHeight}px`;
509
- submenu.style.left = "0px";
510
- submenu.style.top = "0px";
511
-
512
- const submenuRect = submenu.getBoundingClientRect();
513
- const position = calculateSubmenuPosition({
514
- triggerRect: trigger.getBoundingClientRect(),
515
- submenuWidth: submenuRect.width,
516
- submenuHeight: submenuRect.height,
517
- viewportWidth: window.innerWidth,
518
- viewportHeight: window.innerHeight
519
- });
520
-
521
- submenu.style.left = `${position.left}px`;
522
- submenu.style.top = `${position.top}px`;
523
- submenu.style.maxHeight = `${position.maxHeight}px`;
524
- }
525
-
526
- function enabledItems(scope) {
527
- if (!scope) return [];
528
- const list = scope.classList?.contains("card-action-menu-list")
529
- ? scope
530
- : scope.querySelector(":scope > .card-action-menu-list");
531
- if (!list) return [];
532
- return [...list.querySelectorAll(":scope > .card-action-menu-item > .card-action-button")].filter((button) => !button.disabled);
533
- }
534
-
535
- function firstEnabledItem(scope) {
536
- return enabledItems(scope)[0] || null;
537
- }
538
-
539
- function moveFocus(scope, current, step) {
540
- const items = enabledItems(scope);
541
- if (!items.length) return;
542
- const index = items.indexOf(current);
543
- const nextIndex = index === -1 ? 0 : (index + step + items.length) % items.length;
544
- items[nextIndex].focus({ preventScroll: true });
545
- }
@@ -1,20 +0,0 @@
1
- // Edition + feature flags. The build script replaces `__ORBIT_EDITION__`
2
- // via esbuild --define when producing dist/preview; in dev (raw modules
3
- // served from public/) the identifier is undefined and we fall back to
4
- // "full". `typeof` is the safe probe — bare reads of an undefined global
5
- // would throw.
6
-
7
- let resolvedEdition = "full";
8
- if (typeof __ORBIT_EDITION__ !== "undefined") {
9
- resolvedEdition = __ORBIT_EDITION__;
10
- }
11
-
12
- export const edition = resolvedEdition; // "full" | "preview"
13
-
14
- export const features = {
15
- sse: edition === "full",
16
- ai: edition === "full",
17
- multiBoard: edition === "full",
18
- tokenAuth: edition === "full",
19
- attachments: edition === "full"
20
- };