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