@starlove/orbit 1.0.1
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/.dockerignore +13 -0
- package/Dockerfile +34 -0
- package/README.md +216 -0
- package/SKILL-ORBIT.md +94 -0
- package/build.mjs +204 -0
- package/dist/full/apple-touch-icon.png +0 -0
- package/dist/full/cursors/live_arrow.cur +0 -0
- package/dist/full/cursors/live_link.cur +0 -0
- package/dist/full/cursors/live_move.cur +0 -0
- package/dist/full/cursors/live_select.cur +0 -0
- package/dist/full/cursors/live_unavail.cur +0 -0
- package/dist/full/favicon-32x32.png +0 -0
- package/dist/full/index.html +314 -0
- package/dist/full/js/app.70be7d06.js +554 -0
- package/dist/full/js/app.70be7d06.js.map +7 -0
- package/dist/full/orbits-favicon.png +0 -0
- package/dist/full/orbits-logo-bg.png +0 -0
- package/dist/full/orbits-logo.png +0 -0
- package/dist/full/styles.c2e3d20a.css +4892 -0
- package/dist/full/version.json +6 -0
- package/docs/AGENT_PROTOCOL.md +379 -0
- package/docs/MCP_SETUP.md +49 -0
- package/docs/mcp-client.example.json +13 -0
- package/package.json +62 -0
- package/pnpm-workspace.yaml +2 -0
- 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/index.html +314 -0
- package/public/js/api.js +35 -0
- package/public/js/app.js +35 -0
- package/public/js/board-menu.js +145 -0
- package/public/js/config.js +19 -0
- package/public/js/create-card.js +153 -0
- package/public/js/dom.js +19 -0
- package/public/js/drawer.js +137 -0
- package/public/js/format.js +108 -0
- package/public/js/kanban.js +475 -0
- package/public/js/local-backend.js +934 -0
- package/public/js/main.js +142 -0
- package/public/js/modal.js +31 -0
- package/public/js/router.js +114 -0
- package/public/js/search.js +53 -0
- package/public/js/settings-tabs.js +7 -0
- package/public/js/settings.js +931 -0
- package/public/js/sse.js +156 -0
- package/public/js/starscape.js +315 -0
- package/public/js/state.js +54 -0
- package/public/js/ticket-detail.js +766 -0
- package/public/js/toast.js +74 -0
- package/public/js/transport.js +22 -0
- package/public/js/unread.js +121 -0
- package/public/orbits-favicon.png +0 -0
- package/public/orbits-logo-bg.png +0 -0
- package/public/orbits-logo.png +0 -0
- package/public/styles.css +4892 -0
- package/src/cli/orbit.js +438 -0
- package/src/core/agent.js +262 -0
- package/src/core/auth.js +49 -0
- package/src/core/backups.js +136 -0
- package/src/core/board.js +112 -0
- package/src/core/boards.js +310 -0
- package/src/core/bootstrap.js +162 -0
- package/src/core/db.js +237 -0
- package/src/core/delete-board.js +53 -0
- package/src/core/events.js +314 -0
- package/src/core/http.js +81 -0
- package/src/core/paths.js +25 -0
- package/src/core/provision-repo-board.js +113 -0
- package/src/core/queries.js +246 -0
- package/src/core/registry.js +207 -0
- package/src/core/relations.js +163 -0
- package/src/core/router.js +414 -0
- package/src/core/search.js +70 -0
- package/src/core/seed.js +226 -0
- package/src/core/snapshots.js +255 -0
- package/src/core/states.js +167 -0
- package/src/core/system-picker.js +93 -0
- package/src/core/tickets.js +344 -0
- package/src/core/util.js +105 -0
- package/src/mcp-server.js +773 -0
- package/src/server.js +48 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Orbit</title>
|
|
7
|
+
<link
|
|
8
|
+
rel="icon"
|
|
9
|
+
type="image/png"
|
|
10
|
+
sizes="32x32"
|
|
11
|
+
href="./favicon-32x32.png"
|
|
12
|
+
/>
|
|
13
|
+
<link
|
|
14
|
+
rel="apple-touch-icon"
|
|
15
|
+
sizes="180x180"
|
|
16
|
+
href="./apple-touch-icon.png"
|
|
17
|
+
/>
|
|
18
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
<div class="grain">
|
|
22
|
+
<div class="milky-way" aria-hidden="true"></div>
|
|
23
|
+
</div>
|
|
24
|
+
<header class="topbar">
|
|
25
|
+
<div class="brand" aria-label="Orbit">
|
|
26
|
+
<img class="mark" src="./orbits-logo.png" alt="Orbits logo" />
|
|
27
|
+
</div>
|
|
28
|
+
<div class="controls">
|
|
29
|
+
<span class="topbar-controls-gap" aria-hidden="true"></span>
|
|
30
|
+
<div class="topbar-search" id="topbarSearch">
|
|
31
|
+
<input
|
|
32
|
+
id="searchInput"
|
|
33
|
+
placeholder="Search tickets, comments, decisions..."
|
|
34
|
+
autocomplete="off"
|
|
35
|
+
/>
|
|
36
|
+
<div id="searchResults" class="search-results" hidden></div>
|
|
37
|
+
</div>
|
|
38
|
+
<button
|
|
39
|
+
id="newTicketBtn"
|
|
40
|
+
type="button"
|
|
41
|
+
class="topbar-btn btn-sun topbar-ctl-shrink"
|
|
42
|
+
>
|
|
43
|
+
<span class="btn-plus" aria-hidden="true">+</span>
|
|
44
|
+
<span class="btn-sun-label">New Card</span>
|
|
45
|
+
</button>
|
|
46
|
+
<div class="topbar-menu">
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
id="boardMenuBtn"
|
|
50
|
+
class="topbar-chip topbar-ctl-shrink"
|
|
51
|
+
aria-expanded="false"
|
|
52
|
+
aria-haspopup="true"
|
|
53
|
+
aria-controls="boardFlyout"
|
|
54
|
+
>
|
|
55
|
+
<svg
|
|
56
|
+
class="topbar-icon"
|
|
57
|
+
width="16"
|
|
58
|
+
height="16"
|
|
59
|
+
viewBox="0 0 24 24"
|
|
60
|
+
fill="none"
|
|
61
|
+
aria-hidden="true"
|
|
62
|
+
>
|
|
63
|
+
<rect
|
|
64
|
+
x="4"
|
|
65
|
+
y="4"
|
|
66
|
+
width="6"
|
|
67
|
+
height="16"
|
|
68
|
+
rx="1.5"
|
|
69
|
+
stroke="currentColor"
|
|
70
|
+
stroke-width="2"
|
|
71
|
+
/>
|
|
72
|
+
<rect
|
|
73
|
+
x="14"
|
|
74
|
+
y="4"
|
|
75
|
+
width="6"
|
|
76
|
+
height="16"
|
|
77
|
+
rx="1.5"
|
|
78
|
+
stroke="currentColor"
|
|
79
|
+
stroke-width="2"
|
|
80
|
+
/>
|
|
81
|
+
</svg>
|
|
82
|
+
Board
|
|
83
|
+
</button>
|
|
84
|
+
<div
|
|
85
|
+
id="boardFlyout"
|
|
86
|
+
class="menu-flyout menu-flyout--align-end"
|
|
87
|
+
hidden
|
|
88
|
+
role="menu"
|
|
89
|
+
></div>
|
|
90
|
+
</div>
|
|
91
|
+
<button
|
|
92
|
+
id="themeToggle"
|
|
93
|
+
type="button"
|
|
94
|
+
class="topbar-btn ghost topbar-ctl-shrink"
|
|
95
|
+
aria-label="Toggle theme"
|
|
96
|
+
>
|
|
97
|
+
<svg
|
|
98
|
+
class="topbar-icon"
|
|
99
|
+
id="themeIcon"
|
|
100
|
+
width="16"
|
|
101
|
+
height="16"
|
|
102
|
+
viewBox="0 0 24 24"
|
|
103
|
+
fill="none"
|
|
104
|
+
stroke="currentColor"
|
|
105
|
+
stroke-width="2"
|
|
106
|
+
stroke-linecap="round"
|
|
107
|
+
aria-hidden="true"
|
|
108
|
+
>
|
|
109
|
+
<circle cx="12" cy="12" r="5" />
|
|
110
|
+
<path
|
|
111
|
+
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"
|
|
112
|
+
/>
|
|
113
|
+
</svg>
|
|
114
|
+
</button>
|
|
115
|
+
<button
|
|
116
|
+
id="settingsBtn"
|
|
117
|
+
type="button"
|
|
118
|
+
class="topbar-btn ghost topbar-ctl-shrink"
|
|
119
|
+
>
|
|
120
|
+
<svg
|
|
121
|
+
class="topbar-icon"
|
|
122
|
+
width="16"
|
|
123
|
+
height="16"
|
|
124
|
+
viewBox="0 0 24 24"
|
|
125
|
+
fill="none"
|
|
126
|
+
stroke="currentColor"
|
|
127
|
+
stroke-width="2"
|
|
128
|
+
stroke-linecap="round"
|
|
129
|
+
stroke-linejoin="round"
|
|
130
|
+
aria-hidden="true"
|
|
131
|
+
>
|
|
132
|
+
<path
|
|
133
|
+
d="M12.22 2h-.44a2 2 0 00-2 2v.18a2 2 0 01-1 1.73l-.43.25a2 2 0 01-2 0l-.15-.08a2 2 0 00-2.73.73l-.22.38a2 2 0 00.73 2.73l.15.1a2 2 0 011 1.72v.51a2 2 0 01-1 1.74l-.15.09a2 2 0 00-.73 2.73l.22.38a2 2 0 002.73.73l.15-.08a2 2 0 012 0l.43.25a2 2 0 011 1.73V20a2 2 0 002 2h.44a2 2 0 002-2v-.18a2 2 0 011-1.73l.43-.25a2 2 0 012 0l.15.08a2 2 0 002.73-.73l.22-.39a2 2 0 00-.73-2.73l-.15-.08a2 2 0 01-1-1.74v-.5a2 2 0 011-1.74l.15-.09a2 2 0 00.73-2.73l-.22-.38a2 2 0 00-2.73-.73l-.15.08a2 2 0 01-2 0l-.43-.25a2 2 0 01-1-1.73V4a2 2 0 00-2-2z"
|
|
134
|
+
/>
|
|
135
|
+
<circle cx="12" cy="12" r="3" />
|
|
136
|
+
</svg>
|
|
137
|
+
Settings
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
</header>
|
|
141
|
+
|
|
142
|
+
<div
|
|
143
|
+
id="createFlyoutBackdrop"
|
|
144
|
+
class="create-flyout-backdrop"
|
|
145
|
+
hidden
|
|
146
|
+
aria-hidden="true"
|
|
147
|
+
></div>
|
|
148
|
+
<div
|
|
149
|
+
id="createFlyout"
|
|
150
|
+
class="create-flyout"
|
|
151
|
+
hidden
|
|
152
|
+
aria-label="New card"
|
|
153
|
+
>
|
|
154
|
+
<div id="createFlyoutInner" class="create-flyout-inner"></div>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<main class="board-main">
|
|
158
|
+
<div id="kanban" class="kanban" aria-live="polite"></div>
|
|
159
|
+
</main>
|
|
160
|
+
<footer class="frame-footer" aria-label="Application signature">
|
|
161
|
+
<span class="frame-footer-title">Orbit</span>
|
|
162
|
+
<span class="frame-footer-subtitle"><em>a Starscape app</em></span>
|
|
163
|
+
</footer>
|
|
164
|
+
|
|
165
|
+
<div
|
|
166
|
+
id="drawerBackdrop"
|
|
167
|
+
class="drawer-backdrop"
|
|
168
|
+
hidden
|
|
169
|
+
aria-hidden="true"
|
|
170
|
+
></div>
|
|
171
|
+
<aside id="drawer" class="drawer" hidden aria-label="Panel">
|
|
172
|
+
<header class="drawer-header">
|
|
173
|
+
<div class="drawer-title-block">
|
|
174
|
+
<span
|
|
175
|
+
id="drawerEyebrow"
|
|
176
|
+
class="drawer-eyebrow"
|
|
177
|
+
hidden
|
|
178
|
+
></span>
|
|
179
|
+
<h2 id="drawerTitle" class="drawer-title-text"></h2>
|
|
180
|
+
<div
|
|
181
|
+
id="drawerSubtitle"
|
|
182
|
+
class="drawer-subtitle"
|
|
183
|
+
hidden
|
|
184
|
+
></div>
|
|
185
|
+
</div>
|
|
186
|
+
<nav
|
|
187
|
+
id="drawerTabs"
|
|
188
|
+
class="drawer-tabs"
|
|
189
|
+
role="tablist"
|
|
190
|
+
hidden
|
|
191
|
+
></nav>
|
|
192
|
+
<button
|
|
193
|
+
type="button"
|
|
194
|
+
id="drawerArchiveBtn"
|
|
195
|
+
class="drawer-archive"
|
|
196
|
+
aria-label="Archive card"
|
|
197
|
+
title="Archive card"
|
|
198
|
+
hidden
|
|
199
|
+
>
|
|
200
|
+
<svg
|
|
201
|
+
viewBox="0 0 24 24"
|
|
202
|
+
width="18"
|
|
203
|
+
height="18"
|
|
204
|
+
fill="none"
|
|
205
|
+
stroke="currentColor"
|
|
206
|
+
stroke-width="2"
|
|
207
|
+
stroke-linecap="round"
|
|
208
|
+
stroke-linejoin="round"
|
|
209
|
+
aria-hidden="true"
|
|
210
|
+
>
|
|
211
|
+
<path d="M3 6h18" />
|
|
212
|
+
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
213
|
+
<path
|
|
214
|
+
d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"
|
|
215
|
+
/>
|
|
216
|
+
<path d="M10 11v6" />
|
|
217
|
+
<path d="M14 11v6" />
|
|
218
|
+
</svg>
|
|
219
|
+
</button>
|
|
220
|
+
<button
|
|
221
|
+
type="button"
|
|
222
|
+
id="drawerCloseBtn"
|
|
223
|
+
class="drawer-close"
|
|
224
|
+
aria-label="Close panel"
|
|
225
|
+
>
|
|
226
|
+
×
|
|
227
|
+
</button>
|
|
228
|
+
</header>
|
|
229
|
+
<div id="drawerInner" class="drawer-inner"></div>
|
|
230
|
+
</aside>
|
|
231
|
+
|
|
232
|
+
<template id="commentFormTemplate">
|
|
233
|
+
<form class="comment-form">
|
|
234
|
+
<textarea
|
|
235
|
+
name="body"
|
|
236
|
+
placeholder="Add a comment, decision, or instruction..."
|
|
237
|
+
required
|
|
238
|
+
></textarea>
|
|
239
|
+
<button type="submit">Comment</button>
|
|
240
|
+
</form>
|
|
241
|
+
</template>
|
|
242
|
+
|
|
243
|
+
<!-- Generic modal dialog — content varies by use-case.
|
|
244
|
+
The welcome dialog auto-shows on the preview edition via main.js.
|
|
245
|
+
Other uses: call openModal(el) from any module that imports modal.js. -->
|
|
246
|
+
<dialog
|
|
247
|
+
id="welcomeDialog"
|
|
248
|
+
class="orbit-dialog"
|
|
249
|
+
aria-labelledby="welcomeDialogTitle"
|
|
250
|
+
>
|
|
251
|
+
<div class="welcome-dialog-inner">
|
|
252
|
+
<div class="welcome-dialog-head">
|
|
253
|
+
<img
|
|
254
|
+
class="welcome-dialog-logo"
|
|
255
|
+
src="./orbits-logo.png"
|
|
256
|
+
alt=""
|
|
257
|
+
aria-hidden="true"
|
|
258
|
+
/>
|
|
259
|
+
<span class="welcome-dialog-edition-pill"
|
|
260
|
+
>Preview Edition</span
|
|
261
|
+
>
|
|
262
|
+
</div>
|
|
263
|
+
<div class="welcome-dialog-body">
|
|
264
|
+
<h2 id="welcomeDialogTitle" class="welcome-dialog-title">
|
|
265
|
+
No-AI Browser Preview
|
|
266
|
+
</h2>
|
|
267
|
+
<p class="welcome-dialog-lede">
|
|
268
|
+
You're using the local, browser-only preview of
|
|
269
|
+
<strong>Orbit</strong> — a developer-first kanban board
|
|
270
|
+
built for teams working alongside AI agents.
|
|
271
|
+
</p>
|
|
272
|
+
<ul class="welcome-dialog-notices">
|
|
273
|
+
<li>
|
|
274
|
+
<span class="welcome-dialog-notice-label"
|
|
275
|
+
>No AI</span
|
|
276
|
+
>
|
|
277
|
+
Boards are stored in your browser only. AI features,
|
|
278
|
+
MCP server, and server-side sync require the full
|
|
279
|
+
install.
|
|
280
|
+
</li>
|
|
281
|
+
<li>
|
|
282
|
+
<span class="welcome-dialog-notice-label"
|
|
283
|
+
>Download</span
|
|
284
|
+
>
|
|
285
|
+
Get the full app at
|
|
286
|
+
<a
|
|
287
|
+
class="welcome-dialog-link"
|
|
288
|
+
href="https://orbit.starscape.app/install"
|
|
289
|
+
>orbit.starscape.app/install</a
|
|
290
|
+
>
|
|
291
|
+
</li>
|
|
292
|
+
<li>
|
|
293
|
+
<span class="welcome-dialog-notice-label"
|
|
294
|
+
>Coming soon</span
|
|
295
|
+
>
|
|
296
|
+
Paid cloud hosting for teams — no install required.
|
|
297
|
+
</li>
|
|
298
|
+
</ul>
|
|
299
|
+
</div>
|
|
300
|
+
<footer class="welcome-dialog-footer">
|
|
301
|
+
<button
|
|
302
|
+
type="button"
|
|
303
|
+
class="welcome-dialog-cta"
|
|
304
|
+
data-modal-close
|
|
305
|
+
>
|
|
306
|
+
Get started
|
|
307
|
+
</button>
|
|
308
|
+
</footer>
|
|
309
|
+
</div>
|
|
310
|
+
</dialog>
|
|
311
|
+
|
|
312
|
+
<script src="/js/main.js" type="module"></script>
|
|
313
|
+
</body>
|
|
314
|
+
</html>
|
package/public/js/api.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Network layer. `api()` is the single fetch wrapper used everywhere; it
|
|
2
|
+
// delegates raw I/O to transport.js (swappable per edition) and surfaces
|
|
3
|
+
// errors as toasts. Path helpers live here because they're shared by every
|
|
4
|
+
// caller regardless of transport.
|
|
5
|
+
|
|
6
|
+
import { state } from "./state.js";
|
|
7
|
+
import { toast } from "./toast.js";
|
|
8
|
+
import { request } from "./transport.js";
|
|
9
|
+
|
|
10
|
+
/** Bootstrap for the currently selected board (server opens the right DB). */
|
|
11
|
+
export function bootstrapPath() {
|
|
12
|
+
const q = state.boardId ? `?board_id=${encodeURIComponent(state.boardId)}` : "";
|
|
13
|
+
return `/api/bootstrap${q}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Append board_id for ticket/state/entry routes when the path has no board prefix. */
|
|
17
|
+
export function withBoardQuery(path) {
|
|
18
|
+
if (!state.boardId || path.includes("board_id=")) return path;
|
|
19
|
+
const sep = path.includes("?") ? "&" : "?";
|
|
20
|
+
return `${path}${sep}board_id=${encodeURIComponent(state.boardId)}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function api(path, options = {}) {
|
|
24
|
+
try {
|
|
25
|
+
return await request({
|
|
26
|
+
method: options.method || "GET",
|
|
27
|
+
path,
|
|
28
|
+
body: options.body,
|
|
29
|
+
headers: options.headers
|
|
30
|
+
});
|
|
31
|
+
} catch (err) {
|
|
32
|
+
toast.error(err.payload?.error || err.message || "Request failed");
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
}
|
package/public/js/app.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Bootstrap reload + top-level render. Extracted from main.js so feature
|
|
2
|
+
// modules can import `load` / `render` without pulling in the entry-point
|
|
3
|
+
// init wiring (which would create import cycles).
|
|
4
|
+
|
|
5
|
+
import { state, syncBoardSelection } from "./state.js";
|
|
6
|
+
import { api, bootstrapPath } from "./api.js";
|
|
7
|
+
import { renderBoard } from "./kanban.js";
|
|
8
|
+
import { renderDetail } from "./ticket-detail.js";
|
|
9
|
+
import { closeMenuFlyouts, updateTopbarChips } from "./board-menu.js";
|
|
10
|
+
import { connectEventStream } from "./sse.js";
|
|
11
|
+
import { seedReadIfNeeded } from "./unread.js";
|
|
12
|
+
import { features } from "./config.js";
|
|
13
|
+
|
|
14
|
+
let sseBoardId = null;
|
|
15
|
+
|
|
16
|
+
export async function load() {
|
|
17
|
+
closeMenuFlyouts();
|
|
18
|
+
state.data = await api(bootstrapPath());
|
|
19
|
+
syncBoardSelection();
|
|
20
|
+
// First-time-per-board seed so existing cards don't all flash unread.
|
|
21
|
+
// No-op once the seed flag is set for this board.
|
|
22
|
+
seedReadIfNeeded(state.boardId, state.data?.tickets || []);
|
|
23
|
+
await render();
|
|
24
|
+
if (features.sse && state.boardId !== sseBoardId) {
|
|
25
|
+
sseBoardId = state.boardId;
|
|
26
|
+
connectEventStream();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function render() {
|
|
31
|
+
syncBoardSelection();
|
|
32
|
+
updateTopbarChips();
|
|
33
|
+
renderBoard();
|
|
34
|
+
await renderDetail();
|
|
35
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Board picker flyout (the "Board" chip in the topbar). Lists every board
|
|
2
|
+
// the actor can see, switches between them, and — for owners — exposes the
|
|
3
|
+
// new-board form. Also owns the topbar chip label/title sync.
|
|
4
|
+
|
|
5
|
+
import { boardFlyout, boardMenuBtn } from "./dom.js";
|
|
6
|
+
import { state } from "./state.js";
|
|
7
|
+
import { escapeHtml } from "./format.js";
|
|
8
|
+
import { api } from "./api.js";
|
|
9
|
+
import { navigate, syncUrlFromState } from "./router.js";
|
|
10
|
+
import { closeCreateFlyout } from "./create-card.js";
|
|
11
|
+
import { toast } from "./toast.js";
|
|
12
|
+
import { load } from "./app.js";
|
|
13
|
+
import { features } from "./config.js";
|
|
14
|
+
|
|
15
|
+
export function closeBoardFlyout() {
|
|
16
|
+
if (!boardFlyout) return;
|
|
17
|
+
boardFlyout.hidden = true;
|
|
18
|
+
boardFlyout.innerHTML = "";
|
|
19
|
+
boardMenuBtn?.setAttribute("aria-expanded", "false");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function closeMenuFlyouts() {
|
|
23
|
+
closeBoardFlyout();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function updateTopbarChips() {
|
|
27
|
+
if (!state.data) return;
|
|
28
|
+
const board = (state.data.boards || []).find((b) => b.id === state.boardId);
|
|
29
|
+
if (boardMenuBtn) {
|
|
30
|
+
boardMenuBtn.textContent = board?.name || "Board";
|
|
31
|
+
boardMenuBtn.title = board ? `${board.name} — slug: ${board.slug}` : "Choose board";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function renderBoardFlyoutInner() {
|
|
36
|
+
const boards = state.data.boards || [];
|
|
37
|
+
const rows = boards
|
|
38
|
+
.map(
|
|
39
|
+
(b) => `
|
|
40
|
+
<button type="button" class="menu-flyout-item ${b.id === state.boardId ? "is-current" : ""}" data-pick-board="${escapeHtml(b.id)}" role="menuitem">
|
|
41
|
+
<span class="menu-flyout-item-title">${escapeHtml(b.name)}</span>
|
|
42
|
+
<span class="menu-flyout-item-meta">${escapeHtml(b.slug)}</span>
|
|
43
|
+
</button>`
|
|
44
|
+
)
|
|
45
|
+
.join("");
|
|
46
|
+
const listBody =
|
|
47
|
+
rows ||
|
|
48
|
+
`<p class="menu-flyout-hint menu-flyout-hint--empty">No boards yet. Create one below if you can.</p>`;
|
|
49
|
+
const createBlock = features.multiBoard
|
|
50
|
+
? `
|
|
51
|
+
<div class="menu-flyout-section">
|
|
52
|
+
<h3 class="menu-flyout-heading">New board</h3>
|
|
53
|
+
<form id="createBoardForm" class="menu-flyout-form">
|
|
54
|
+
<div class="folder-picker-field">
|
|
55
|
+
<input id="repoPathInput" name="repo_path" type="text" required readonly placeholder="Choose a folder" autocomplete="off" />
|
|
56
|
+
<button type="button" id="pickRepoFolderBtn">Browse</button>
|
|
57
|
+
</div>
|
|
58
|
+
<p class="menu-flyout-hint menu-flyout-hint--intro">Board data is stored in <code>.orbit/</code> under this folder.</p>
|
|
59
|
+
<input name="name" type="text" required placeholder="Board name" autocomplete="off" />
|
|
60
|
+
<input name="slug" type="text" placeholder="Slug (optional)" autocomplete="off" />
|
|
61
|
+
<input name="default_branch" type="text" placeholder="Default branch (optional)" autocomplete="off" />
|
|
62
|
+
<input name="repo_url" type="text" placeholder="Repo URL (optional)" autocomplete="off" />
|
|
63
|
+
<button type="submit">Create board</button>
|
|
64
|
+
</form>
|
|
65
|
+
</div>`
|
|
66
|
+
: "";
|
|
67
|
+
return `
|
|
68
|
+
<div class="menu-flyout-head">Board</div>
|
|
69
|
+
<div class="menu-flyout-list">${listBody}</div>
|
|
70
|
+
${createBlock}
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function wireBoardFlyout() {
|
|
75
|
+
boardFlyout.querySelectorAll("[data-pick-board]").forEach((btn) => {
|
|
76
|
+
btn.addEventListener("click", async (event) => {
|
|
77
|
+
event.stopPropagation();
|
|
78
|
+
const id = btn.getAttribute("data-pick-board");
|
|
79
|
+
closeBoardFlyout();
|
|
80
|
+
if (id === state.boardId) return;
|
|
81
|
+
closeCreateFlyout();
|
|
82
|
+
await navigate({
|
|
83
|
+
boardId: id,
|
|
84
|
+
view: "board"
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
const form = boardFlyout.querySelector("#createBoardForm");
|
|
89
|
+
if (form) {
|
|
90
|
+
const pickRepoFolderBtn = form.querySelector("#pickRepoFolderBtn");
|
|
91
|
+
const repoPathInput = form.querySelector("#repoPathInput");
|
|
92
|
+
pickRepoFolderBtn?.addEventListener("click", async (event) => {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
pickRepoFolderBtn.disabled = true;
|
|
95
|
+
try {
|
|
96
|
+
const result = await api("/api/system/pick-folder", { method: "POST" });
|
|
97
|
+
if (result.path) {
|
|
98
|
+
repoPathInput.value = result.path;
|
|
99
|
+
repoPathInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
toast.error(error?.message || "Folder picker unavailable");
|
|
103
|
+
} finally {
|
|
104
|
+
pickRepoFolderBtn.disabled = false;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
form.addEventListener("submit", async (event) => {
|
|
108
|
+
event.preventDefault();
|
|
109
|
+
const fd = new FormData(form);
|
|
110
|
+
const slug = String(fd.get("slug") || "").trim();
|
|
111
|
+
const branch = String(fd.get("default_branch") || "").trim();
|
|
112
|
+
const repo = String(fd.get("repo_url") || "").trim();
|
|
113
|
+
const repoPath = String(fd.get("repo_path") || "").trim();
|
|
114
|
+
if (!repoPath) {
|
|
115
|
+
repoPathInput?.reportValidity();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const created = await api("/api/boards", {
|
|
119
|
+
method: "POST",
|
|
120
|
+
body: {
|
|
121
|
+
name: fd.get("name"),
|
|
122
|
+
repo_path: repoPath,
|
|
123
|
+
...(slug ? { slug } : {}),
|
|
124
|
+
...(branch ? { default_branch: branch } : {}),
|
|
125
|
+
...(repo ? { repo_url: repo } : {})
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
closeBoardFlyout();
|
|
129
|
+
state.boardId = created.id;
|
|
130
|
+
state.selectedTicketId = "";
|
|
131
|
+
state.detailMode = "settings";
|
|
132
|
+
await load();
|
|
133
|
+
syncUrlFromState();
|
|
134
|
+
toast.success("Board created");
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function openBoardFlyout() {
|
|
140
|
+
closeCreateFlyout();
|
|
141
|
+
boardFlyout.innerHTML = renderBoardFlyoutInner();
|
|
142
|
+
boardFlyout.hidden = false;
|
|
143
|
+
boardMenuBtn?.setAttribute("aria-expanded", "true");
|
|
144
|
+
wireBoardFlyout();
|
|
145
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
};
|