@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
@@ -1,190 +0,0 @@
1
- // "New card" flyout (top-right panel, separate from the main ticket
2
- // drawer). Handles open/close transitions, form rendering, and submission.
3
-
4
- import {
5
- $,
6
- createFlyout,
7
- createFlyoutInner,
8
- createFlyoutBackdrop
9
- } from "./dom.js";
10
- import { state, visibleStatesForProject, ticketsForProject } from "./state.js";
11
- import { escapeHtml } from "./format.js";
12
- import { api } from "./api.js";
13
- import { syncUrlFromState } from "./router.js";
14
- import { handleTextareaIndentationKeydown } from "./text-editing.js";
15
- import { closeDrawer } from "./drawer.js";
16
- import { closeMenuFlyouts } from "./board-menu.js";
17
- import { load } from "./app.js";
18
- import { closeIconSvg } from "./icons.js";
19
- import { attachTicketTypeahead, ticketCanonicalLabel } from "./ticket-typeahead.js";
20
-
21
- /** Clears any pending hide timeout when reopening the create flyout quickly. */
22
- let createFlyoutHideTimer = 0;
23
-
24
- export function isCreateFlyoutOpen() {
25
- return createFlyout?.classList.contains("is-open");
26
- }
27
-
28
- /** Compact top-right panel for creating a card (separate from the main ticket drawer). */
29
- export function openCreateFlyout(preselectedLaneId) {
30
- clearTimeout(createFlyoutHideTimer);
31
- closeMenuFlyouts();
32
- closeDrawer();
33
- createFlyoutInner.innerHTML = renderCreateForm(preselectedLaneId || "");
34
- wireCreateForm();
35
- $("#createFlyoutClose")?.addEventListener("click", () => closeCreateFlyout());
36
- createFlyout.hidden = false;
37
- createFlyoutBackdrop.hidden = false;
38
- requestAnimationFrame(() => {
39
- createFlyout.classList.add("is-open");
40
- createFlyoutBackdrop.classList.add("is-visible");
41
- });
42
- requestAnimationFrame(() => {
43
- $("#createTicketTitle")?.focus();
44
- });
45
- }
46
-
47
- export function closeCreateFlyout() {
48
- clearTimeout(createFlyoutHideTimer);
49
- createFlyout.classList.remove("is-open");
50
- createFlyoutBackdrop.classList.remove("is-visible");
51
- createFlyoutHideTimer = setTimeout(() => {
52
- createFlyout.hidden = true;
53
- createFlyoutBackdrop.hidden = true;
54
- createFlyoutInner.innerHTML = "";
55
- }, 220);
56
- }
57
-
58
- /** HTML for the “New” flyout: lane + type + priority on one row, then parent and labels. */
59
- function renderCreateForm(preselectedLaneId) {
60
- const states = visibleStatesForProject();
61
- const defaultState = states.find((item) => item.is_default) || states[0];
62
- const effectiveStateId =
63
- preselectedLaneId && states.some((s) => s.id === preselectedLaneId)
64
- ? preselectedLaneId
65
- : defaultState?.id || "";
66
- const stateOptions = states
67
- .map(
68
- (item) =>
69
- `<option value="${escapeHtml(item.id)}" ${item.id === effectiveStateId ? "selected" : ""}>${escapeHtml(item.name)}</option>`
70
- )
71
- .join("");
72
- return `
73
- <div class="create-flyout-head">
74
- <h2>New card</h2>
75
- <button type="button" class="create-flyout-close ghost" id="createFlyoutClose" aria-label="Close">${closeIconSvg}</button>
76
- </div>
77
- <form id="createTicketForm" class="create-flyout-form">
78
- <input id="createTicketTitle" name="title" type="text" placeholder="Title — what is this card?" autocomplete="off" required />
79
- <textarea id="createTicketDescription" name="description" placeholder="Description (optional)"></textarea>
80
- <div class="control-grid">
81
- <label class="control-field">
82
- <span class="control-field-label">LANE</span>
83
- <select id="createTicketState" name="state" class="select-chevron-field" aria-label="Lane">${stateOptions}</select>
84
- </label>
85
- <label class="control-field">
86
- <span class="control-field-label">TYPE</span>
87
- <select id="createTicketType" name="type" class="select-chevron-field" aria-label="Ticket type">
88
- <option value="epic">Epic</option>
89
- <option value="feature">Feature</option>
90
- <option value="task" selected>Task</option>
91
- <option value="bug">Bug</option>
92
- </select>
93
- </label>
94
- <label class="control-field">
95
- <span class="control-field-label">PRIORITY</span>
96
- <select id="createTicketPriority" name="priority" class="select-chevron-field" aria-label="Priority">
97
- <option value="4">URGENT</option>
98
- <option value="3">HIGH</option>
99
- <option value="2" selected>MED</option>
100
- <option value="1">LOW</option>
101
- <option value="0">MAYBE</option>
102
- </select>
103
- </label>
104
- </div>
105
- <label class="control-field" id="createTicketParentField">
106
- <span class="control-field-label">Parent epic</span>
107
- <input id="createTicketParent" name="parent_target" type="text" placeholder="Search epic… (optional)" autocomplete="off" aria-label="Parent epic" />
108
- <input type="hidden" name="parent_ticket_id" id="createTicketParentId" value="" />
109
- </label>
110
- <input id="createTicketLabels" name="labels" type="text" placeholder="Labels (comma-separated)" />
111
- <button type="submit">Create card</button>
112
- </form>
113
- `;
114
- }
115
-
116
- function wireCreateForm() {
117
- const form = $("#createTicketForm");
118
- if (!form) return;
119
- const description = form.querySelector("#createTicketDescription");
120
- description?.addEventListener("keydown", (event) => {
121
- handleTextareaIndentationKeydown(event, description);
122
- });
123
-
124
- // Parent epic is chosen via the shared styled typeahead. The visible field
125
- // holds the "#12 — Title" label; the resolved epic id lives in a hidden input
126
- // that the submit reads. Typing (or clearing) drops a stale id so only an
127
- // explicit pick is submitted.
128
- const parentInput = form.querySelector("#createTicketParent");
129
- const parentId = form.querySelector("#createTicketParentId");
130
- parentInput.addEventListener("input", () => {
131
- parentId.value = "";
132
- });
133
- attachTicketTypeahead(
134
- parentInput,
135
- () => ticketsForProject().filter((ticket) => ticket.type === "epic"),
136
- {
137
- onSelect: (ticket) => {
138
- parentId.value = ticket.id;
139
- parentInput.value = ticketCanonicalLabel(ticket);
140
- },
141
- },
142
- );
143
-
144
- // Epics cannot have a parent (server rejects with `epic_cannot_have_parent`),
145
- // so hide the Parent epic field whenever Type is Epic.
146
- const typeSelect = form.querySelector("#createTicketType");
147
- const parentField = form.querySelector("#createTicketParentField");
148
- const syncParentVisibility = () => {
149
- const isEpic = typeSelect.value === "epic";
150
- parentField.hidden = isEpic;
151
- if (isEpic) {
152
- parentInput.value = "";
153
- parentId.value = "";
154
- }
155
- };
156
- typeSelect.addEventListener("change", syncParentVisibility);
157
- syncParentVisibility();
158
-
159
- form.addEventListener("submit", createTicketFromDrawer);
160
- }
161
-
162
- async function createTicketFromDrawer(event) {
163
- event.preventDefault();
164
- const form = event.currentTarget;
165
- const data = new FormData(form);
166
- const labels = String(data.get("labels") || "")
167
- .split(",")
168
- .map((item) => item.trim())
169
- .filter(Boolean);
170
-
171
- const created = await api("/api/tickets", {
172
- method: "POST",
173
- body: {
174
- board_id: state.boardId,
175
- title: data.get("title"),
176
- description: data.get("description"),
177
- type: data.get("type"),
178
- parent_ticket_id: data.get("parent_ticket_id") || null,
179
- state_id: data.get("state"),
180
- priority: Number(data.get("priority")),
181
- labels
182
- }
183
- });
184
-
185
- closeCreateFlyout();
186
- state.selectedTicketId = created.id;
187
- state.detailMode = "ticket";
188
- await load();
189
- syncUrlFromState();
190
- }
package/public/js/dom.js DELETED
@@ -1,19 +0,0 @@
1
- // Cached DOM references queried once at module-load time. Importing this
2
- // module assumes the body has been parsed (the script tag is at the bottom
3
- // of <body> and uses type="module", so this holds).
4
-
5
- export const $ = (selector) => document.querySelector(selector);
6
-
7
- export const boardMenuBtn = $("#boardMenuBtn");
8
- export const boardFlyout = $("#boardFlyout");
9
- export const kanban = $("#kanban");
10
- export const drawer = $("#drawer");
11
- export const drawerInner = $("#drawerInner");
12
- export const drawerBackdrop = $("#drawerBackdrop");
13
- export const createFlyout = $("#createFlyout");
14
- export const createFlyoutInner = $("#createFlyoutInner");
15
- export const createFlyoutBackdrop = $("#createFlyoutBackdrop");
16
- export const searchInput = $("#searchInput");
17
- export const searchResults = $("#searchResults");
18
- export const themeToggle = $("#themeToggle");
19
- export const themeIcon = $("#themeIcon");
@@ -1,273 +0,0 @@
1
- // Right-side overlay drawer used for ticket detail and project settings.
2
- // Owns the open/close transition state and the shared header layout
3
- // (eyebrow, title, subtitle, tabs, archive/close buttons). Bodies are
4
- // rendered by the feature modules that call `renderDrawerShell`.
5
-
6
- import { drawer, drawerInner, drawerBackdrop } from "./dom.js";
7
- import { state } from "./state.js";
8
- import { escapeHtml } from "./format.js";
9
-
10
- /** Bumps on each open/close so a stale `transitionend` from an old close cannot clear a newly opened drawer. */
11
- let drawerToken = 0;
12
-
13
- /** Aborts scroll/resize observers when tabs are re-rendered or the drawer closes. */
14
- let drawerTabsScrollAbort = null;
15
-
16
- /** Toggle edge-fade classes on the tab shell when the strip can scroll further left/right. */
17
- function syncDrawerTabsScrollState(tabsEl) {
18
- const shellEl = document.getElementById("drawerTabsShell");
19
- if (!tabsEl || !shellEl) return;
20
-
21
- const maxScroll = tabsEl.scrollWidth - tabsEl.clientWidth;
22
- if (maxScroll <= 1) {
23
- shellEl.classList.remove("can-scroll-left", "can-scroll-right");
24
- return;
25
- }
26
-
27
- shellEl.classList.toggle("can-scroll-left", tabsEl.scrollLeft > 1);
28
- shellEl.classList.toggle("can-scroll-right", tabsEl.scrollLeft < maxScroll - 1);
29
- }
30
-
31
- /** Keep the active settings tab visible when the strip overflows horizontally. */
32
- function scrollActiveDrawerTabIntoView(tabsEl) {
33
- tabsEl
34
- ?.querySelector(".drawer-tab.is-active")
35
- ?.scrollIntoView({ block: "nearest", inline: "nearest" });
36
- }
37
-
38
- function teardownDrawerTabsScroll() {
39
- drawerTabsScrollAbort?.abort();
40
- drawerTabsScrollAbort = null;
41
- const shellEl = document.getElementById("drawerTabsShell");
42
- const tabsEl = document.getElementById("drawerTabs");
43
- shellEl?.classList.remove("can-scroll-left", "can-scroll-right");
44
- tabsEl?.classList.remove("is-grabbing");
45
- }
46
-
47
- function bindDrawerTabsScroll(tabsEl) {
48
- teardownDrawerTabsScroll();
49
- if (!tabsEl) return;
50
-
51
- const controller = new AbortController();
52
- drawerTabsScrollAbort = controller;
53
- const { signal } = controller;
54
-
55
- const onScrollChange = () => syncDrawerTabsScrollState(tabsEl);
56
- tabsEl.addEventListener("scroll", onScrollChange, { passive: true, signal });
57
-
58
- if (typeof ResizeObserver !== "undefined") {
59
- const observer = new ResizeObserver(onScrollChange);
60
- observer.observe(tabsEl);
61
- signal.addEventListener("abort", () => observer.disconnect(), { once: true });
62
- }
63
-
64
- // Vertical wheel over the tab strip pans horizontally (no Shift required).
65
- const LINE_SCROLL_PX = 16;
66
- const wheelPixels = (value, mode) => {
67
- if (mode === WheelEvent.DOM_DELTA_LINE) return value * LINE_SCROLL_PX;
68
- if (mode === WheelEvent.DOM_DELTA_PAGE) return value * Math.max(tabsEl.clientWidth, 1);
69
- return value;
70
- };
71
-
72
- tabsEl.addEventListener(
73
- "wheel",
74
- (event) => {
75
- if (event.defaultPrevented || event.ctrlKey) return;
76
- if (tabsEl.scrollWidth <= tabsEl.clientWidth) return;
77
-
78
- const horizontalDelta = event.deltaX || event.deltaY;
79
- if (!horizontalDelta) return;
80
-
81
- event.preventDefault();
82
- tabsEl.scrollLeft += wheelPixels(horizontalDelta, event.deltaMode);
83
- },
84
- { passive: false, signal }
85
- );
86
-
87
- // Click-and-drag horizontal panning; threshold keeps tab clicks intact.
88
- const DRAG_THRESHOLD = 4;
89
- let dragActive = false;
90
- let dragged = false;
91
- let startX = 0;
92
- let startScroll = 0;
93
- let pointerId = 0;
94
-
95
- tabsEl.addEventListener(
96
- "pointerdown",
97
- (event) => {
98
- if (event.button !== 0) return;
99
- if (tabsEl.scrollWidth <= tabsEl.clientWidth) return;
100
- dragged = false;
101
- dragActive = true;
102
- pointerId = event.pointerId;
103
- startX = event.clientX;
104
- startScroll = tabsEl.scrollLeft;
105
- },
106
- { signal }
107
- );
108
-
109
- tabsEl.addEventListener(
110
- "pointermove",
111
- (event) => {
112
- if (!dragActive || event.pointerId !== pointerId) return;
113
- const delta = event.clientX - startX;
114
- if (!dragged && Math.abs(delta) > DRAG_THRESHOLD) {
115
- dragged = true;
116
- try {
117
- tabsEl.setPointerCapture(pointerId);
118
- } catch {
119
- /* no-op: some browsers can't capture here */
120
- }
121
- tabsEl.classList.add("is-grabbing");
122
- }
123
- if (dragged) {
124
- event.preventDefault();
125
- tabsEl.scrollLeft = startScroll - delta;
126
- }
127
- },
128
- { signal }
129
- );
130
-
131
- const endDrag = (event) => {
132
- if (!dragActive || (event && event.pointerId !== pointerId)) return;
133
- dragActive = false;
134
- tabsEl.classList.remove("is-grabbing");
135
- try {
136
- tabsEl.releasePointerCapture(pointerId);
137
- } catch {
138
- /* no-op */
139
- }
140
- };
141
-
142
- tabsEl.addEventListener("pointerup", endDrag, { signal });
143
- tabsEl.addEventListener("pointercancel", endDrag, { signal });
144
- tabsEl.addEventListener("pointerleave", endDrag, { signal });
145
-
146
- tabsEl.addEventListener(
147
- "click",
148
- (event) => {
149
- if (dragged) {
150
- event.stopPropagation();
151
- event.preventDefault();
152
- dragged = false;
153
- }
154
- },
155
- { capture: true, signal }
156
- );
157
-
158
- requestAnimationFrame(() => {
159
- if (signal.aborted) return;
160
- onScrollChange();
161
- scrollActiveDrawerTabIntoView(tabsEl);
162
- });
163
- }
164
-
165
- /** Slide the right drawer in (ticket detail, project settings, or create form). */
166
- export function openDrawer() {
167
- drawerToken += 1;
168
- drawer.hidden = false;
169
- drawerBackdrop.hidden = false;
170
- requestAnimationFrame(() => {
171
- drawer.classList.add("is-open");
172
- drawerBackdrop.classList.add("is-visible");
173
- });
174
- }
175
-
176
- /**
177
- * Close the drawer overlay. Uses transitionend with a timeout fallback for `hidden`.
178
- * `drawerToken` invalidates pending finish callbacks if the user opens the drawer again mid-animation.
179
- */
180
- export function closeDrawer() {
181
- const finishToken = ++drawerToken;
182
- const wasOpen = drawer.classList.contains("is-open");
183
- drawer.classList.remove("is-open");
184
- drawerBackdrop.classList.remove("is-visible");
185
-
186
- const finish = () => {
187
- if (drawerToken !== finishToken) return;
188
- drawer.hidden = true;
189
- drawerBackdrop.hidden = true;
190
- drawerInner.innerHTML = "";
191
- const titleBlockEl = document.getElementById("drawerTitleBlock");
192
- const tabsEl = document.getElementById("drawerTabs");
193
- const tabsShellEl = document.getElementById("drawerTabsShell");
194
- if (titleBlockEl) titleBlockEl.innerHTML = "";
195
- if (tabsEl) tabsEl.innerHTML = "";
196
- if (tabsShellEl) tabsShellEl.hidden = true;
197
- teardownDrawerTabsScroll();
198
- drawer.classList.remove("is-wide");
199
- };
200
-
201
- if (!wasOpen) {
202
- finish();
203
- return;
204
- }
205
-
206
- let ended = false;
207
- const end = () => {
208
- if (ended) return;
209
- ended = true;
210
- finish();
211
- };
212
-
213
- drawer.addEventListener("transitionend", end, { once: true });
214
- setTimeout(end, 360);
215
- }
216
-
217
- /**
218
- * Fill the drawer's header + body slots. All drawer modes (ticket, project,
219
- * create) should route through this so the header/title/close/tabs stay
220
- * structurally consistent. `body` is an HTML string assigned to drawerInner.
221
- */
222
- export function renderDrawerShell({ eyebrow, title, titleAttrs, subtitleHtml, tabs, activeTab, onTabSelect, body, mode }) {
223
- drawer.classList.toggle("is-settings", mode === "settings");
224
- drawer.classList.toggle("is-wide", mode === "ticket" || mode === "settings");
225
- const titleBlockEl = document.getElementById("drawerTitleBlock");
226
- const tabsEl = document.getElementById("drawerTabs");
227
- const tabsShellEl = document.getElementById("drawerTabsShell");
228
-
229
- // Rebuild the eyebrow / title / subtitle wholesale each render. Like
230
- // drawerInner.innerHTML below, this means any listeners the caller attached
231
- // last render (e.g. the inline title editor's click handler) die with the
232
- // old nodes, instead of stacking on persistent shell elements.
233
- const extraTitleClass = titleAttrs?.class ? ` ${titleAttrs.class}` : "";
234
- let titleAttrHtml = ` class="drawer-title drawer-title-text${extraTitleClass}"`;
235
- for (const [key, value] of Object.entries(titleAttrs || {})) {
236
- if (key === "class") continue;
237
- titleAttrHtml += ` ${key}="${escapeHtml(String(value))}"`;
238
- }
239
- const segments = [];
240
- if (eyebrow) segments.push(`<span class="drawer-eyebrow">${escapeHtml(eyebrow)}</span>`);
241
- if (title != null && String(title).trim() !== "") {
242
- segments.push(`<h2${titleAttrHtml}>${escapeHtml(title)}</h2>`);
243
- }
244
- if (subtitleHtml) segments.push(`<div class="drawer-subtitle">${subtitleHtml}</div>`);
245
- titleBlockEl.innerHTML = segments.join("");
246
-
247
- if (tabs && tabs.length) {
248
- if (tabsShellEl) tabsShellEl.hidden = false;
249
- tabsEl.innerHTML = tabs
250
- .map(
251
- (t) =>
252
- `<button type="button" role="tab" class="drawer-tab ${
253
- t.id === activeTab ? "is-active" : ""
254
- }" data-drawer-tab="${escapeHtml(t.id)}" aria-selected="${t.id === activeTab}">${escapeHtml(t.label)}</button>`
255
- )
256
- .join("");
257
- if (typeof onTabSelect === "function") {
258
- tabsEl.querySelectorAll(".drawer-tab").forEach((btn) => {
259
- btn.addEventListener("click", () => onTabSelect(btn.dataset.drawerTab));
260
- });
261
- }
262
- bindDrawerTabsScroll(tabsEl);
263
- } else {
264
- if (tabsShellEl) tabsShellEl.hidden = true;
265
- tabsEl.innerHTML = "";
266
- teardownDrawerTabsScroll();
267
- }
268
-
269
- const archiveBtn = document.getElementById("drawerArchiveBtn");
270
- if (archiveBtn) archiveBtn.hidden = state.detailMode !== "ticket";
271
-
272
- drawerInner.innerHTML = body ?? "";
273
- }