@starlove/orbit 1.0.6 → 1.1.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 (58) hide show
  1. package/README.md +22 -7
  2. package/SKILL-ORBIT.md +28 -1
  3. package/build.mjs +2 -2
  4. package/dist/full/index.html +2 -2
  5. package/dist/full/js/app.a4f045fc.js +676 -0
  6. package/dist/full/js/app.a4f045fc.js.map +7 -0
  7. package/dist/full/{styles.c2e3d20a.css → styles.836872e7.css} +1117 -47
  8. package/dist/full/version.json +2 -2
  9. package/docs/AGENT_PROTOCOL.md +27 -5
  10. package/docs/MCP_SETUP.md +7 -3
  11. package/docs/ORBIT_DISPATCH.md +185 -0
  12. package/docs/setup/orbit-init-wizard.md +174 -0
  13. package/package.json +2 -2
  14. package/public/js/actor-labels.js +65 -0
  15. package/public/js/api.js +5 -1
  16. package/public/js/app.js +8 -1
  17. package/public/js/card-actions.js +526 -0
  18. package/public/js/config.js +2 -1
  19. package/public/js/drawer.js +2 -0
  20. package/public/js/format.js +336 -0
  21. package/public/js/kanban.js +25 -2
  22. package/public/js/local-backend.js +24 -8
  23. package/public/js/main.js +14 -6
  24. package/public/js/motion-preference.js +53 -0
  25. package/public/js/router.js +78 -58
  26. package/public/js/search.js +6 -3
  27. package/public/js/settings-tabs.js +1 -1
  28. package/public/js/settings.js +192 -29
  29. package/public/js/starscape.js +25 -18
  30. package/public/js/state.js +61 -2
  31. package/public/js/theme-preference.js +88 -0
  32. package/public/js/ticket-detail.js +432 -126
  33. package/public/js/url-routes.js +94 -0
  34. package/public/styles.css +1117 -47
  35. package/src/cli/orbit.js +217 -32
  36. package/src/core/agent.js +1 -18
  37. package/src/core/agents-md.js +16 -10
  38. package/src/core/attachments.js +238 -0
  39. package/src/core/auth.js +36 -0
  40. package/src/core/backups.js +9 -3
  41. package/src/core/board.js +32 -1
  42. package/src/core/boards.js +1 -1
  43. package/src/core/bootstrap.js +9 -1
  44. package/src/core/db.js +42 -2
  45. package/src/core/delete-board.js +87 -21
  46. package/src/core/dispatch.js +532 -0
  47. package/src/core/http.js +31 -11
  48. package/src/core/migrate.js +198 -0
  49. package/src/core/paths.js +6 -0
  50. package/src/core/registry.js +12 -9
  51. package/src/core/router.js +62 -6
  52. package/src/core/search.js +56 -22
  53. package/src/core/snapshots.js +14 -2
  54. package/src/mcp/orbit-client.js +223 -0
  55. package/src/mcp-server.js +89 -129
  56. package/src/server.js +20 -0
  57. package/dist/full/js/app.aedc1bfe.js +0 -554
  58. package/dist/full/js/app.aedc1bfe.js.map +0 -7
@@ -10,6 +10,8 @@ export const state = {
10
10
  /** Currently selected board id. Single-board UI; this points at the active
11
11
  * board in `state.data.boards`. */
12
12
  boardId: "",
13
+ /** URL-facing board identifier. Routes use slugs; API calls keep using ids. */
14
+ boardSlug: "",
13
15
  selectedTicketId: "",
14
16
  expandedCardIds: new Set(),
15
17
  /** "empty" | "ticket" | "settings" — drives the main right overlay drawer (not the New flyout). */
@@ -30,9 +32,16 @@ export function getSettingsTab() {
30
32
  export function syncBoardSelection() {
31
33
  if (!state.data) return;
32
34
  const boards = state.data.boards || [];
33
- if (!state.boardId || !boards.some((b) => b.id === state.boardId)) {
34
- state.boardId = boards[0]?.id || "";
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);
35
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 || "";
36
45
  }
37
46
 
38
47
  export function currentBoard() {
@@ -68,3 +77,53 @@ export function selectableStatesForProject(currentStateId = "") {
68
77
  export function ticketsForProject() {
69
78
  return state.data.tickets.filter((item) => item.board_id === state.boardId);
70
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
+ }
@@ -0,0 +1,88 @@
1
+ export const THEME_STORAGE_KEY = "mab_theme";
2
+ export const THEME_CHANGE_EVENT = "orbit:theme-change";
3
+
4
+ const THEMES = new Set(["light", "dark"]);
5
+
6
+ function safeStorage(storage) {
7
+ try {
8
+ return storage ?? globalThis.localStorage ?? null;
9
+ } catch {
10
+ return null;
11
+ }
12
+ }
13
+
14
+ function safeDocument(doc) {
15
+ try {
16
+ return doc ?? globalThis.document ?? null;
17
+ } catch {
18
+ return null;
19
+ }
20
+ }
21
+
22
+ function safeEventTarget(eventTarget) {
23
+ try {
24
+ return eventTarget ?? globalThis.window ?? null;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ export function normalizeTheme(theme) {
31
+ return THEMES.has(theme) ? theme : "light";
32
+ }
33
+
34
+ export function storedThemePreference(storage = safeStorage()) {
35
+ const store = safeStorage(storage);
36
+ const value = store?.getItem?.(THEME_STORAGE_KEY);
37
+ return THEMES.has(value) ? value : null;
38
+ }
39
+
40
+ export function currentTheme({ doc = safeDocument(), storage = safeStorage() } = {}) {
41
+ const rootTheme = safeDocument(doc)?.documentElement?.getAttribute?.("data-theme");
42
+ return THEMES.has(rootTheme) ? rootTheme : storedThemePreference(storage) ?? "light";
43
+ }
44
+
45
+ export function applyStoredThemePreference({
46
+ doc = safeDocument(),
47
+ storage = safeStorage()
48
+ } = {}) {
49
+ const storedTheme = storedThemePreference(storage);
50
+ if (storedTheme) {
51
+ safeDocument(doc)?.documentElement?.setAttribute?.("data-theme", storedTheme);
52
+ }
53
+ return currentTheme({ doc, storage });
54
+ }
55
+
56
+ export function setThemePreference(theme, {
57
+ doc = safeDocument(),
58
+ storage = safeStorage(),
59
+ eventTarget = safeEventTarget(),
60
+ notify = true
61
+ } = {}) {
62
+ const next = normalizeTheme(theme);
63
+ safeDocument(doc)?.documentElement?.setAttribute?.("data-theme", next);
64
+ safeStorage(storage)?.setItem?.(THEME_STORAGE_KEY, next);
65
+
66
+ if (notify) {
67
+ dispatchThemeChange(next, eventTarget);
68
+ }
69
+
70
+ return next;
71
+ }
72
+
73
+ export function toggleThemePreference(options = {}) {
74
+ const next = currentTheme(options) === "dark" ? "light" : "dark";
75
+ return setThemePreference(next, options);
76
+ }
77
+
78
+ function dispatchThemeChange(theme, eventTarget = safeEventTarget()) {
79
+ const target = safeEventTarget(eventTarget);
80
+ if (typeof target?.dispatchEvent !== "function") return;
81
+
82
+ if (typeof CustomEvent === "function") {
83
+ target.dispatchEvent(new CustomEvent(THEME_CHANGE_EVENT, { detail: { theme } }));
84
+ return;
85
+ }
86
+
87
+ target.dispatchEvent({ type: THEME_CHANGE_EVENT, detail: { theme } });
88
+ }