@starlove/orbit 1.2.0 → 1.3.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 +2 -2
- package/README.md +16 -17
- package/SKILL-ORBIT.md +1 -1
- package/dist/full/index.html +225 -29
- package/dist/full/js/app.b8296a78.js +742 -0
- package/dist/full/js/app.b8296a78.js.map +7 -0
- package/dist/full/styles.e2284569.css +1 -0
- package/dist/full/version.json +2 -2
- package/docs/MCP_SETUP.md +6 -5
- package/docs/setup/orbit-init-wizard.md +3 -3
- package/package.json +3 -3
- package/public/index.html +223 -27
- package/public/js/app.js +4 -1
- package/public/js/board-menu.js +21 -8
- package/public/js/help-menu.js +31 -0
- package/public/js/main.js +4 -0
- package/public/js/settings.js +95 -3
- package/public/js/ticket-detail.js +240 -93
- package/public/js/topbar-notes.js +155 -0
- package/public/styles.css +220 -487
- package/src/cli/orbit.js +48 -10
- package/src/core/agents-md.js +15 -0
- package/src/core/board.js +6 -3
- package/src/core/boards.js +54 -11
- package/src/core/db.js +13 -0
- package/src/core/migrate.js +1 -1
- package/src/core/paths.js +1 -1
- package/src/core/registry.js +4 -3
- package/src/server.js +24 -2
- package/dist/full/js/app.ccc442f7.js +0 -706
- package/dist/full/js/app.ccc442f7.js.map +0 -7
- package/dist/full/styles.baccc8e3.css +0 -1
package/public/index.html
CHANGED
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
<!-- Boot splash — the canonical ss-ui loading screen (see the package's
|
|
38
38
|
components/loader.css). Inline critical CSS so it paints before
|
|
39
39
|
/js/main.js loads. The logo grows in then breathes; no spinner.
|
|
40
|
-
Keyframes namespaced `
|
|
40
|
+
Keyframes namespaced `starscape-loader-*`. Under reduced motion the grow-in
|
|
41
41
|
is dropped and the dot grid stops; the gentle breathe stays. -->
|
|
42
42
|
<style>
|
|
43
|
-
.
|
|
43
|
+
.loader {
|
|
44
44
|
position: fixed;
|
|
45
45
|
inset: 0;
|
|
46
46
|
z-index: 99999;
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
61
61
|
Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
62
62
|
}
|
|
63
|
-
.
|
|
63
|
+
.loader::before {
|
|
64
64
|
content: "";
|
|
65
65
|
position: absolute;
|
|
66
66
|
width: 200%;
|
|
@@ -72,24 +72,28 @@
|
|
|
72
72
|
);
|
|
73
73
|
background-size: 50px 50px;
|
|
74
74
|
will-change: transform;
|
|
75
|
-
animation:
|
|
75
|
+
animation: starscape-loader-drift 20s linear infinite;
|
|
76
76
|
}
|
|
77
|
-
.
|
|
77
|
+
.loader.is-hiding {
|
|
78
78
|
opacity: 0;
|
|
79
79
|
pointer-events: none;
|
|
80
80
|
}
|
|
81
|
-
.
|
|
81
|
+
.loader__logo {
|
|
82
82
|
width: 120px;
|
|
83
83
|
height: 120px;
|
|
84
84
|
object-fit: contain;
|
|
85
85
|
filter: drop-shadow(0 0 20px rgba(255, 255, 255, 0.3));
|
|
86
86
|
transform-origin: center;
|
|
87
|
+
/* translateZ in the keyframes + backface-visibility keep the star
|
|
88
|
+
on a stable GPU layer, so it doesn't snap ~1px as the grow
|
|
89
|
+
starts. Keep in sync with the package components/loader.css. */
|
|
90
|
+
backface-visibility: hidden;
|
|
87
91
|
will-change: transform;
|
|
88
92
|
animation:
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
starscape-loader-grow 0.6s cubic-bezier(0.22, 1, 0.36, 1) both,
|
|
94
|
+
starscape-loader-breathe 3s ease-in-out 0.6s infinite;
|
|
91
95
|
}
|
|
92
|
-
.
|
|
96
|
+
.loader__label {
|
|
93
97
|
margin: 0;
|
|
94
98
|
color: #fff;
|
|
95
99
|
font-size: 1.25rem;
|
|
@@ -97,45 +101,45 @@
|
|
|
97
101
|
letter-spacing: 0.5px;
|
|
98
102
|
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
|
99
103
|
}
|
|
100
|
-
@keyframes
|
|
104
|
+
@keyframes starscape-loader-drift {
|
|
101
105
|
from { transform: translate(0, 0); }
|
|
102
106
|
to { transform: translate(-50px, -50px); }
|
|
103
107
|
}
|
|
104
|
-
@keyframes
|
|
105
|
-
from { transform: scale(0); opacity: 0; }
|
|
106
|
-
to { transform: scale(1); opacity: 1; }
|
|
108
|
+
@keyframes starscape-loader-grow {
|
|
109
|
+
from { transform: scale(0) translateZ(0); opacity: 0; }
|
|
110
|
+
to { transform: scale(1) translateZ(0); opacity: 1; }
|
|
107
111
|
}
|
|
108
|
-
@keyframes
|
|
109
|
-
0%, 100% { transform: scale(1); }
|
|
110
|
-
50% { transform: scale(1.06); }
|
|
112
|
+
@keyframes starscape-loader-breathe {
|
|
113
|
+
0%, 100% { transform: scale(1) translateZ(0); }
|
|
114
|
+
50% { transform: scale(1.06) translateZ(0); }
|
|
111
115
|
}
|
|
112
116
|
@media (prefers-reduced-motion: reduce) {
|
|
113
|
-
:root:not([data-motion="full"]) .
|
|
117
|
+
:root:not([data-motion="full"]) .loader::before {
|
|
114
118
|
animation: none;
|
|
115
119
|
}
|
|
116
|
-
:root:not([data-motion="full"]) .
|
|
117
|
-
animation:
|
|
120
|
+
:root:not([data-motion="full"]) .loader__logo {
|
|
121
|
+
animation: starscape-loader-breathe 3s ease-in-out infinite;
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
|
-
[data-motion="reduce"] .
|
|
124
|
+
[data-motion="reduce"] .loader::before {
|
|
121
125
|
animation: none;
|
|
122
126
|
}
|
|
123
|
-
[data-motion="reduce"] .
|
|
124
|
-
animation:
|
|
127
|
+
[data-motion="reduce"] .loader__logo {
|
|
128
|
+
animation: starscape-loader-breathe 3s ease-in-out infinite;
|
|
125
129
|
}
|
|
126
130
|
</style>
|
|
127
131
|
</head>
|
|
128
132
|
<body>
|
|
129
133
|
<!-- Boot splash - shown immediately, removed once the app is ready. -->
|
|
130
|
-
<div id="
|
|
134
|
+
<div id="loader" class="loader" role="status" aria-live="polite">
|
|
131
135
|
<img
|
|
132
|
-
class="
|
|
136
|
+
class="loader__logo"
|
|
133
137
|
src="./orbits-logo.png"
|
|
134
138
|
alt="Orbit"
|
|
135
139
|
width="120"
|
|
136
140
|
height="120"
|
|
137
141
|
/>
|
|
138
|
-
<p class="
|
|
142
|
+
<p class="loader__label">Loading Orbit…</p>
|
|
139
143
|
</div>
|
|
140
144
|
|
|
141
145
|
<div class="grain">
|
|
@@ -143,9 +147,40 @@
|
|
|
143
147
|
</div>
|
|
144
148
|
<header class="topbar">
|
|
145
149
|
<div class="brand" aria-label="Orbit">
|
|
146
|
-
<
|
|
150
|
+
<button
|
|
151
|
+
id="brandFocusBtn"
|
|
152
|
+
type="button"
|
|
153
|
+
class="brand-focus-btn"
|
|
154
|
+
data-variant="brand-focus"
|
|
155
|
+
aria-label="Show board notes"
|
|
156
|
+
aria-expanded="false"
|
|
157
|
+
aria-controls="brandFocusPopover"
|
|
158
|
+
>
|
|
159
|
+
<img class="mark" src="./orbits-logo.png" alt="Orbits logo" />
|
|
160
|
+
</button>
|
|
161
|
+
<div
|
|
162
|
+
id="brandFocusPopover"
|
|
163
|
+
class="brand-focus-popover"
|
|
164
|
+
role="dialog"
|
|
165
|
+
aria-label="Board notes"
|
|
166
|
+
hidden
|
|
167
|
+
>
|
|
168
|
+
<div class="brand-focus-popover__eyebrow">Board notes</div>
|
|
169
|
+
<div id="brandFocusNotes" class="brand-focus-notes-body">No board notes yet</div>
|
|
170
|
+
</div>
|
|
147
171
|
</div>
|
|
148
172
|
<div class="controls">
|
|
173
|
+
<button
|
|
174
|
+
id="brandNotesBtn"
|
|
175
|
+
type="button"
|
|
176
|
+
class="topbar-notes brand-notes-strip"
|
|
177
|
+
data-variant="topbar-notes"
|
|
178
|
+
aria-label="Show board notes"
|
|
179
|
+
aria-expanded="false"
|
|
180
|
+
aria-controls="brandFocusPopover"
|
|
181
|
+
>
|
|
182
|
+
<span id="brandNotesText" class="topbar-notes__text"></span>
|
|
183
|
+
</button>
|
|
149
184
|
<span class="topbar-controls-gap" aria-hidden="true"></span>
|
|
150
185
|
<div class="topbar-search" id="topbarSearch">
|
|
151
186
|
<input
|
|
@@ -235,6 +270,32 @@
|
|
|
235
270
|
/>
|
|
236
271
|
</svg>
|
|
237
272
|
</button>
|
|
273
|
+
<button
|
|
274
|
+
id="helpBtn"
|
|
275
|
+
type="button"
|
|
276
|
+
class="topbar-btn topbar-ctl-shrink"
|
|
277
|
+
data-variant="translucent"
|
|
278
|
+
aria-haspopup="dialog"
|
|
279
|
+
aria-controls="helpDialog"
|
|
280
|
+
>
|
|
281
|
+
<svg
|
|
282
|
+
class="topbar-icon"
|
|
283
|
+
width="16"
|
|
284
|
+
height="16"
|
|
285
|
+
viewBox="0 0 24 24"
|
|
286
|
+
fill="none"
|
|
287
|
+
stroke="currentColor"
|
|
288
|
+
stroke-width="2"
|
|
289
|
+
stroke-linecap="round"
|
|
290
|
+
stroke-linejoin="round"
|
|
291
|
+
aria-hidden="true"
|
|
292
|
+
>
|
|
293
|
+
<circle cx="12" cy="12" r="10" />
|
|
294
|
+
<path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3" />
|
|
295
|
+
<path d="M12 17h.01" />
|
|
296
|
+
</svg>
|
|
297
|
+
Help
|
|
298
|
+
</button>
|
|
238
299
|
<button
|
|
239
300
|
id="settingsBtn"
|
|
240
301
|
type="button"
|
|
@@ -424,6 +485,141 @@
|
|
|
424
485
|
</div>
|
|
425
486
|
</dialog>
|
|
426
487
|
|
|
488
|
+
<!-- Help & About. Reuses the .orbit-dialog shell + welcome-dialog-*
|
|
489
|
+
styling so it inherits the modal chrome; opened from the topbar
|
|
490
|
+
Help button and wired through modal.js (Escape/backdrop close). -->
|
|
491
|
+
<dialog
|
|
492
|
+
id="helpDialog"
|
|
493
|
+
class="orbit-dialog"
|
|
494
|
+
aria-labelledby="helpDialogTitle"
|
|
495
|
+
>
|
|
496
|
+
<div class="welcome-dialog-inner help-dialog-inner">
|
|
497
|
+
<div class="welcome-dialog-head">
|
|
498
|
+
<img
|
|
499
|
+
class="welcome-dialog-logo"
|
|
500
|
+
src="./orbits-logo.png"
|
|
501
|
+
alt=""
|
|
502
|
+
aria-hidden="true"
|
|
503
|
+
/>
|
|
504
|
+
<span
|
|
505
|
+
class="welcome-dialog-edition-pill"
|
|
506
|
+
id="helpDialogVersion"
|
|
507
|
+
>Orbit</span
|
|
508
|
+
>
|
|
509
|
+
<button
|
|
510
|
+
type="button"
|
|
511
|
+
class="help-dialog-close"
|
|
512
|
+
aria-label="Close"
|
|
513
|
+
data-modal-close
|
|
514
|
+
>
|
|
515
|
+
×
|
|
516
|
+
</button>
|
|
517
|
+
</div>
|
|
518
|
+
<div class="welcome-dialog-body">
|
|
519
|
+
<h2 id="helpDialogTitle" class="welcome-dialog-title">
|
|
520
|
+
Starscape Orbit
|
|
521
|
+
</h2>
|
|
522
|
+
<p class="welcome-dialog-lede">
|
|
523
|
+
A developer-first kanban board built for teams working
|
|
524
|
+
alongside AI agents. Track the motion of your project —
|
|
525
|
+
and hand work to your coding agent through the Orbit MCP
|
|
526
|
+
tools.
|
|
527
|
+
</p>
|
|
528
|
+
|
|
529
|
+
<section
|
|
530
|
+
class="help-dialog-section"
|
|
531
|
+
aria-label="Getting started"
|
|
532
|
+
>
|
|
533
|
+
<h3 class="help-dialog-heading">Getting started</h3>
|
|
534
|
+
<ol class="help-dialog-steps">
|
|
535
|
+
<li class="help-dialog-step">
|
|
536
|
+
<span class="help-dialog-step__index">01</span>
|
|
537
|
+
<div>
|
|
538
|
+
<h4>Create a board</h4>
|
|
539
|
+
<p>
|
|
540
|
+
Use the <strong>Board</strong> menu →
|
|
541
|
+
New board. Optionally point it at a
|
|
542
|
+
coding project folder so Orbit can drop
|
|
543
|
+
<code>SKILL-ORBIT.md</code> and
|
|
544
|
+
<code>AGENTS.md</code> for your agent.
|
|
545
|
+
</p>
|
|
546
|
+
</div>
|
|
547
|
+
</li>
|
|
548
|
+
<li class="help-dialog-step">
|
|
549
|
+
<span class="help-dialog-step__index">02</span>
|
|
550
|
+
<div>
|
|
551
|
+
<h4>Add cards & the AI Ready lane</h4>
|
|
552
|
+
<p>
|
|
553
|
+
Capture work with
|
|
554
|
+
<strong>New Card</strong>. Move cards
|
|
555
|
+
into <strong>AI Ready</strong> to stage
|
|
556
|
+
them for an agent to claim.
|
|
557
|
+
</p>
|
|
558
|
+
</div>
|
|
559
|
+
</li>
|
|
560
|
+
<li class="help-dialog-step">
|
|
561
|
+
<span class="help-dialog-step__index">03</span>
|
|
562
|
+
<div>
|
|
563
|
+
<h4>Connect your coding agent</h4>
|
|
564
|
+
<p>
|
|
565
|
+
Add the Orbit MCP server from
|
|
566
|
+
<strong>Settings</strong>, then ask your
|
|
567
|
+
agent to read <code>AGENTS.md</code> and
|
|
568
|
+
claim the next ticket.
|
|
569
|
+
</p>
|
|
570
|
+
</div>
|
|
571
|
+
</li>
|
|
572
|
+
</ol>
|
|
573
|
+
</section>
|
|
574
|
+
|
|
575
|
+
<section
|
|
576
|
+
class="help-dialog-section"
|
|
577
|
+
aria-label="Links"
|
|
578
|
+
>
|
|
579
|
+
<h3 class="help-dialog-heading">Links</h3>
|
|
580
|
+
<ul class="help-dialog-links">
|
|
581
|
+
<li>
|
|
582
|
+
<a
|
|
583
|
+
class="welcome-dialog-link"
|
|
584
|
+
href="https://orbit.starscape.app"
|
|
585
|
+
target="_blank"
|
|
586
|
+
rel="noopener"
|
|
587
|
+
>Documentation</a
|
|
588
|
+
>
|
|
589
|
+
</li>
|
|
590
|
+
<li>
|
|
591
|
+
<a
|
|
592
|
+
class="welcome-dialog-link"
|
|
593
|
+
href="https://github.com/brucestarlove/ss-orbit/blob/master/docs/MCP_SETUP.md"
|
|
594
|
+
target="_blank"
|
|
595
|
+
rel="noopener"
|
|
596
|
+
>MCP setup</a
|
|
597
|
+
>
|
|
598
|
+
</li>
|
|
599
|
+
<li>
|
|
600
|
+
<a
|
|
601
|
+
class="welcome-dialog-link"
|
|
602
|
+
href="https://orbit.starscape.app/support"
|
|
603
|
+
target="_blank"
|
|
604
|
+
rel="noopener"
|
|
605
|
+
>Support / Tip</a
|
|
606
|
+
>
|
|
607
|
+
</li>
|
|
608
|
+
</ul>
|
|
609
|
+
</section>
|
|
610
|
+
</div>
|
|
611
|
+
<footer class="welcome-dialog-footer">
|
|
612
|
+
<button
|
|
613
|
+
type="button"
|
|
614
|
+
class="welcome-dialog-cta"
|
|
615
|
+
data-modal-close
|
|
616
|
+
>
|
|
617
|
+
Got it
|
|
618
|
+
</button>
|
|
619
|
+
</footer>
|
|
620
|
+
</div>
|
|
621
|
+
</dialog>
|
|
622
|
+
|
|
427
623
|
<script src="/js/main.js" type="module"></script>
|
|
428
624
|
<!-- Fade out the boot splash once the DOM is ready. The /js/main.js
|
|
429
625
|
module runs before DOMContentLoaded, so theme/motion + wiring are
|
|
@@ -431,7 +627,7 @@
|
|
|
431
627
|
<script>
|
|
432
628
|
(function () {
|
|
433
629
|
function hideSplash() {
|
|
434
|
-
var splash = document.getElementById("
|
|
630
|
+
var splash = document.getElementById("loader");
|
|
435
631
|
if (!splash) return;
|
|
436
632
|
// Keep the splash up for a minimum of 786ms (measured from
|
|
437
633
|
// navigation start) so a fast load doesn't flash it in and out.
|
package/public/js/app.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// modules can import `load` / `render` without pulling in the entry-point
|
|
3
3
|
// init wiring (which would create import cycles).
|
|
4
4
|
|
|
5
|
-
import { state, syncBoardSelection } from "./state.js";
|
|
5
|
+
import { state, syncBoardSelection, currentBoard } from "./state.js";
|
|
6
6
|
import { api, bootstrapPath } from "./api.js";
|
|
7
7
|
import { renderBoard, renderBoardSelection } from "./kanban.js";
|
|
8
8
|
import { renderDetail } from "./ticket-detail.js";
|
|
@@ -10,6 +10,7 @@ import { closeMenuFlyouts, updateTopbarChips } from "./board-menu.js";
|
|
|
10
10
|
import { connectEventStream } from "./sse.js";
|
|
11
11
|
import { seedReadIfNeeded } from "./unread.js";
|
|
12
12
|
import { features } from "./config.js";
|
|
13
|
+
import { renderTopbarFocus } from "./topbar-notes.js";
|
|
13
14
|
|
|
14
15
|
let sseBoardId = null;
|
|
15
16
|
|
|
@@ -30,6 +31,7 @@ export async function load() {
|
|
|
30
31
|
export async function render() {
|
|
31
32
|
syncBoardSelection();
|
|
32
33
|
updateTopbarChips();
|
|
34
|
+
renderTopbarFocus(currentBoard());
|
|
33
35
|
renderBoard();
|
|
34
36
|
await renderDetail();
|
|
35
37
|
}
|
|
@@ -37,6 +39,7 @@ export async function render() {
|
|
|
37
39
|
export async function renderDetailOnly() {
|
|
38
40
|
syncBoardSelection();
|
|
39
41
|
updateTopbarChips();
|
|
42
|
+
renderTopbarFocus(currentBoard());
|
|
40
43
|
renderBoardSelection();
|
|
41
44
|
await renderDetail();
|
|
42
45
|
}
|
package/public/js/board-menu.js
CHANGED
|
@@ -65,12 +65,18 @@ function renderBoardFlyoutInner() {
|
|
|
65
65
|
<div class="menu-flyout-section">
|
|
66
66
|
<h3 class="menu-flyout-heading">New board</h3>
|
|
67
67
|
<form id="createBoardForm" class="menu-flyout-form">
|
|
68
|
+
<input name="name" type="text" required placeholder="Board name" autocomplete="off" />
|
|
68
69
|
<div class="folder-picker-field">
|
|
69
|
-
<input id="repoPathInput" name="repo_path" type="text"
|
|
70
|
+
<input id="repoPathInput" name="repo_path" type="text" readonly placeholder="Coding project folder (optional)" autocomplete="off" />
|
|
70
71
|
<button type="button" id="pickRepoFolderBtn">Browse</button>
|
|
71
72
|
</div>
|
|
72
|
-
<
|
|
73
|
-
|
|
73
|
+
<div id="helperFilesReveal" hidden>
|
|
74
|
+
<p class="menu-flyout-hint menu-flyout-hint--intro">Orbit writes <code>SKILL-ORBIT.md</code> and <code>AGENTS.md</code> into this folder for your AI coding agent.</p>
|
|
75
|
+
<label class="menu-flyout-check">
|
|
76
|
+
<input id="helperFilesCheckbox" name="manage_helper_files" type="checkbox" checked />
|
|
77
|
+
<span>Generate AI helper files (<code>SKILL-ORBIT.md</code>, <code>AGENTS.md</code>)</span>
|
|
78
|
+
</label>
|
|
79
|
+
</div>
|
|
74
80
|
<input name="slug" type="text" placeholder="Slug (optional)" autocomplete="off" />
|
|
75
81
|
<input name="default_branch" type="text" placeholder="Default branch (optional)" autocomplete="off" />
|
|
76
82
|
<input name="repo_url" type="text" placeholder="Repo URL (optional)" autocomplete="off" />
|
|
@@ -103,6 +109,14 @@ function wireBoardFlyout() {
|
|
|
103
109
|
if (form) {
|
|
104
110
|
const pickRepoFolderBtn = form.querySelector("#pickRepoFolderBtn");
|
|
105
111
|
const repoPathInput = form.querySelector("#repoPathInput");
|
|
112
|
+
const helperFilesReveal = form.querySelector("#helperFilesReveal");
|
|
113
|
+
// The folder explanation and "Generate AI helper files" option only make
|
|
114
|
+
// sense once a folder is chosen — there's nowhere to write them otherwise.
|
|
115
|
+
// Reveal both with the folder value rather than disabling.
|
|
116
|
+
const syncHelperFilesVisibility = () => {
|
|
117
|
+
if (helperFilesReveal) helperFilesReveal.hidden = !repoPathInput.value.trim();
|
|
118
|
+
};
|
|
119
|
+
syncHelperFilesVisibility();
|
|
106
120
|
pickRepoFolderBtn?.addEventListener("click", async (event) => {
|
|
107
121
|
event.preventDefault();
|
|
108
122
|
pickRepoFolderBtn.disabled = true;
|
|
@@ -111,6 +125,7 @@ function wireBoardFlyout() {
|
|
|
111
125
|
if (result.path) {
|
|
112
126
|
repoPathInput.value = result.path;
|
|
113
127
|
repoPathInput.dispatchEvent(new Event("input", { bubbles: true }));
|
|
128
|
+
syncHelperFilesVisibility();
|
|
114
129
|
}
|
|
115
130
|
} catch (error) {
|
|
116
131
|
toast.error(error?.message || "Folder picker unavailable");
|
|
@@ -125,15 +140,13 @@ function wireBoardFlyout() {
|
|
|
125
140
|
const branch = String(fd.get("default_branch") || "").trim();
|
|
126
141
|
const repo = String(fd.get("repo_url") || "").trim();
|
|
127
142
|
const repoPath = String(fd.get("repo_path") || "").trim();
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
143
|
+
// Folder is optional. Only forward the helper-files choice when a folder
|
|
144
|
+
// is set; the server ignores it otherwise.
|
|
132
145
|
const created = await api("/api/boards", {
|
|
133
146
|
method: "POST",
|
|
134
147
|
body: {
|
|
135
148
|
name: fd.get("name"),
|
|
136
|
-
repo_path: repoPath,
|
|
149
|
+
...(repoPath ? { repo_path: repoPath, manage_helper_files: fd.get("manage_helper_files") === "on" } : {}),
|
|
137
150
|
...(slug ? { slug } : {}),
|
|
138
151
|
...(branch ? { default_branch: branch } : {}),
|
|
139
152
|
...(repo ? { repo_url: repo } : {})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Help & About dialog. The topbar "Help" button opens a centered
|
|
2
|
+
// .orbit-dialog (same shell as the welcome dialog) describing what Orbit is,
|
|
3
|
+
// how to get started, and where the docs live. modal.js owns the
|
|
4
|
+
// Escape/backdrop/close-button behavior; this module only wires the open
|
|
5
|
+
// trigger and best-effort fills the version pill from the build's version.json.
|
|
6
|
+
|
|
7
|
+
import { openModal, wireModal } from "./modal.js";
|
|
8
|
+
|
|
9
|
+
async function fillVersion(pill) {
|
|
10
|
+
if (!pill) return;
|
|
11
|
+
try {
|
|
12
|
+
const res = await fetch("./version.json", { cache: "no-store" });
|
|
13
|
+
if (!res.ok) return;
|
|
14
|
+
const info = await res.json();
|
|
15
|
+
if (info?.version) pill.textContent = `v${info.version}`;
|
|
16
|
+
} catch {
|
|
17
|
+
// Dev edition serves raw modules without version.json — leave the
|
|
18
|
+
// static "Orbit" label in place.
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function wireHelpMenu() {
|
|
23
|
+
const dialog = document.getElementById("helpDialog");
|
|
24
|
+
const helpBtn = document.getElementById("helpBtn");
|
|
25
|
+
if (!dialog || !helpBtn) return;
|
|
26
|
+
|
|
27
|
+
wireModal(dialog);
|
|
28
|
+
fillVersion(document.getElementById("helpDialogVersion"));
|
|
29
|
+
|
|
30
|
+
helpBtn.addEventListener("click", () => openModal(dialog));
|
|
31
|
+
}
|
package/public/js/main.js
CHANGED
|
@@ -41,10 +41,14 @@ import { runSearch, hideSearch } from "./search.js";
|
|
|
41
41
|
import { load } from "./app.js";
|
|
42
42
|
import { edition } from "./config.js";
|
|
43
43
|
import { openModal, wireModal } from "./modal.js";
|
|
44
|
+
import { wireTopbarFocus } from "./topbar-notes.js";
|
|
45
|
+
import { wireHelpMenu } from "./help-menu.js";
|
|
44
46
|
|
|
45
47
|
async function init() {
|
|
46
48
|
const welcomeDialog = document.getElementById("welcomeDialog");
|
|
47
49
|
wireModal(welcomeDialog);
|
|
50
|
+
wireTopbarFocus();
|
|
51
|
+
wireHelpMenu();
|
|
48
52
|
if (edition === "preview") {
|
|
49
53
|
openModal(welcomeDialog);
|
|
50
54
|
}
|
package/public/js/settings.js
CHANGED
|
@@ -118,6 +118,11 @@ const MCP_CLIENTS = {
|
|
|
118
118
|
format: "MCP JSON",
|
|
119
119
|
helper: "Paste this JSON into Cursor MCP settings. Cursor starts Orbit when an agent needs the tools."
|
|
120
120
|
},
|
|
121
|
+
vscode: {
|
|
122
|
+
label: "VS Code",
|
|
123
|
+
format: "mcp.json",
|
|
124
|
+
helper: "Paste this JSON into VS Code's user or workspace MCP configuration."
|
|
125
|
+
},
|
|
121
126
|
opencode: {
|
|
122
127
|
label: "OpenCode",
|
|
123
128
|
format: "opencode.json",
|
|
@@ -138,8 +143,9 @@ const MCP_CLIENTS = {
|
|
|
138
143
|
function renderProjectAiTab(context) {
|
|
139
144
|
const project = context.board;
|
|
140
145
|
const aiEnabled = project.ai_enabled !== 0;
|
|
141
|
-
const aiDivider = aiEnabled ? `<div class="
|
|
146
|
+
const aiDivider = aiEnabled ? `<div class="separator" aria-hidden="true"></div>` : "";
|
|
142
147
|
const agentContextSetup = aiEnabled ? renderAgentContextSection(project) : "";
|
|
148
|
+
const helperFilesSetup = aiEnabled && features.multiBoard ? renderHelperFilesSection(project) : "";
|
|
143
149
|
const mcpSetup = aiEnabled ? renderMcpSetupSection(context) : "";
|
|
144
150
|
|
|
145
151
|
return `
|
|
@@ -159,6 +165,7 @@ function renderProjectAiTab(context) {
|
|
|
159
165
|
</div>
|
|
160
166
|
${aiDivider}
|
|
161
167
|
${agentContextSetup}
|
|
168
|
+
${helperFilesSetup ? `${aiDivider}${helperFilesSetup}` : ""}
|
|
162
169
|
${aiDivider}
|
|
163
170
|
${mcpSetup}
|
|
164
171
|
`;
|
|
@@ -174,7 +181,7 @@ function renderAgentContextSection(project) {
|
|
|
174
181
|
return `
|
|
175
182
|
<div class="section ai-subsection ai-context-setup">
|
|
176
183
|
<div class="ai-context-title-row">
|
|
177
|
-
<span class="
|
|
184
|
+
<span class="section-mark" aria-hidden="true">Context</span>
|
|
178
185
|
<h3>Agent Instructions</h3>
|
|
179
186
|
</div>
|
|
180
187
|
<p class="description">This project-level context is provided to agents when they work on tickets.</p>
|
|
@@ -203,7 +210,7 @@ function renderMcpSetupSection(context) {
|
|
|
203
210
|
<details class="section ai-subsection mcp-setup" data-mcp-setup>
|
|
204
211
|
<summary>
|
|
205
212
|
<div class="mcp-title-row">
|
|
206
|
-
<span class="
|
|
213
|
+
<span class="section-mark" aria-hidden="true">MCP</span>
|
|
207
214
|
<h3>Connect Your Agent to MCP</h3>
|
|
208
215
|
</div>
|
|
209
216
|
</summary>
|
|
@@ -302,6 +309,7 @@ function mcpConfigText(clientId, paths) {
|
|
|
302
309
|
if (clientId === "claude-code") return claudeCodeMcpCommand(paths);
|
|
303
310
|
if (clientId === "codex") return codexMcpToml(paths);
|
|
304
311
|
if (clientId === "opencode") return opencodeMcpJson(paths);
|
|
312
|
+
if (clientId === "vscode") return vscodeMcpJson(paths);
|
|
305
313
|
|
|
306
314
|
const config = {
|
|
307
315
|
mcpServers: {
|
|
@@ -331,6 +339,22 @@ function opencodeMcpJson(paths) {
|
|
|
331
339
|
);
|
|
332
340
|
}
|
|
333
341
|
|
|
342
|
+
function vscodeMcpJson(paths) {
|
|
343
|
+
return JSON.stringify(
|
|
344
|
+
{
|
|
345
|
+
servers: {
|
|
346
|
+
starscapeOrbit: {
|
|
347
|
+
type: "stdio",
|
|
348
|
+
command: "node",
|
|
349
|
+
args: [paths.serverPath]
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
null,
|
|
354
|
+
2
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
334
358
|
function codexMcpToml(paths) {
|
|
335
359
|
return `[mcp_servers.starscape-orbit]
|
|
336
360
|
command = "node"
|
|
@@ -426,6 +450,31 @@ function renderProjectRepositoryTab(context) {
|
|
|
426
450
|
`;
|
|
427
451
|
}
|
|
428
452
|
|
|
453
|
+
function renderHelperFilesSection(project) {
|
|
454
|
+
const hasFolder = Boolean(project.system_path);
|
|
455
|
+
const managed = Boolean(project.manage_helper_files);
|
|
456
|
+
return `
|
|
457
|
+
<div class="section ai-subsection helper-files-section">
|
|
458
|
+
<div class="ai-context-title-row">
|
|
459
|
+
<span class="section-mark" aria-hidden="true">Files</span>
|
|
460
|
+
<h3>AI Helper Files</h3>
|
|
461
|
+
</div>
|
|
462
|
+
<p class="description">Orbit can keep <code>SKILL-ORBIT.md</code> and <code>AGENTS.md</code> in this board's coding project folder so your AI agent knows how to use Orbit.${hasFolder ? "" : " Link a folder to enable this."}</p>
|
|
463
|
+
<div class="folder-picker-field helper-files-folder">
|
|
464
|
+
<input id="helperFolderInput" type="text" readonly placeholder="No folder linked" value="${escapeHtml(project.system_path || "")}" />
|
|
465
|
+
<button type="button" id="helperPickFolderBtn">${hasFolder ? "Change" : "Browse"}</button>
|
|
466
|
+
</div>
|
|
467
|
+
<label class="orbit-check">
|
|
468
|
+
<input type="checkbox" id="manageHelperFilesToggle" ${managed ? "checked" : ""} ${hasFolder ? "" : "disabled"} />
|
|
469
|
+
<span class="orbit-check-box" aria-hidden="true">
|
|
470
|
+
<svg viewBox="0 0 16 16" class="orbit-check-tick"><path d="M3.5 8.5l3 3 6-7" /></svg>
|
|
471
|
+
</span>
|
|
472
|
+
<span>Generate & maintain helper files in this folder</span>
|
|
473
|
+
</label>
|
|
474
|
+
</div>
|
|
475
|
+
`;
|
|
476
|
+
}
|
|
477
|
+
|
|
429
478
|
function renderBoardRenameSection(project) {
|
|
430
479
|
return `
|
|
431
480
|
<div class="section board-rename-section">
|
|
@@ -953,6 +1002,49 @@ function bindProjectTabHandlers(context, tab) {
|
|
|
953
1002
|
toast.success(enabled ? "AI features enabled — agent lanes provisioned" : "AI features disabled — AI Ready hidden");
|
|
954
1003
|
});
|
|
955
1004
|
|
|
1005
|
+
const helperPickFolderBtn = $("#helperPickFolderBtn");
|
|
1006
|
+
helperPickFolderBtn?.addEventListener("click", async () => {
|
|
1007
|
+
helperPickFolderBtn.disabled = true;
|
|
1008
|
+
try {
|
|
1009
|
+
const result = await api("/api/system/pick-folder", { method: "POST" });
|
|
1010
|
+
if (!result.path) return;
|
|
1011
|
+
await api(`/api/boards/${encodeURIComponent(project.id)}`, {
|
|
1012
|
+
method: "PATCH",
|
|
1013
|
+
body: { system_path: result.path }
|
|
1014
|
+
});
|
|
1015
|
+
await load();
|
|
1016
|
+
state.detailMode = "settings";
|
|
1017
|
+
toast.success("Linked coding project folder updated");
|
|
1018
|
+
} catch (error) {
|
|
1019
|
+
toast.error(error?.message || "Folder picker unavailable");
|
|
1020
|
+
} finally {
|
|
1021
|
+
helperPickFolderBtn.disabled = false;
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
const manageHelperFilesToggle = $("#manageHelperFilesToggle");
|
|
1026
|
+
manageHelperFilesToggle?.addEventListener("change", async () => {
|
|
1027
|
+
const enabled = manageHelperFilesToggle.checked;
|
|
1028
|
+
manageHelperFilesToggle.disabled = true;
|
|
1029
|
+
try {
|
|
1030
|
+
await api(`/api/boards/${encodeURIComponent(project.id)}`, {
|
|
1031
|
+
method: "PATCH",
|
|
1032
|
+
body: { manage_helper_files: enabled }
|
|
1033
|
+
});
|
|
1034
|
+
await load();
|
|
1035
|
+
state.detailMode = "settings";
|
|
1036
|
+
toast.success(
|
|
1037
|
+
enabled
|
|
1038
|
+
? "Generated SKILL-ORBIT.md and AGENTS.md — Orbit will keep them in sync"
|
|
1039
|
+
: "Stopped maintaining helper files (existing files left in place)"
|
|
1040
|
+
);
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
manageHelperFilesToggle.checked = !enabled;
|
|
1043
|
+
manageHelperFilesToggle.disabled = false;
|
|
1044
|
+
toast.error(error?.message || "Could not update helper files setting");
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
|
|
956
1048
|
}
|
|
957
1049
|
|
|
958
1050
|
if (tab === "archive") {
|