@kitkat-vn/kitcode 0.2.0-staging.3
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/LICENSE +21 -0
- package/README.md +328 -0
- package/bin/kitcode.mjs +1097 -0
- package/package.json +41 -0
- package/src/api-access.mjs +119 -0
- package/src/api.mjs +165 -0
- package/src/atomic-file.mjs +85 -0
- package/src/break-progress.mjs +87 -0
- package/src/claim-bundle.mjs +37 -0
- package/src/companion-controls.mjs +40 -0
- package/src/companion-electron.mjs +87 -0
- package/src/companion-preload.cjs +10 -0
- package/src/companion-window.mjs +57 -0
- package/src/cors.mjs +51 -0
- package/src/equals-ledger.mjs +166 -0
- package/src/focus-clock.mjs +86 -0
- package/src/fonts/fira-code/OFL.txt +93 -0
- package/src/fonts/fira-code/fira-code-latin-400-normal.woff2 +0 -0
- package/src/fonts/fira-code/fira-code-latin-500-normal.woff2 +0 -0
- package/src/fonts/fira-code/fira-code-latin-700-normal.woff2 +0 -0
- package/src/fonts/vt323/OFL.txt +93 -0
- package/src/fonts/vt323/vt323-latin-400-normal.woff2 +0 -0
- package/src/git.mjs +448 -0
- package/src/hook-installers.mjs +397 -0
- package/src/hook-prompt.mjs +201 -0
- package/src/install-id.mjs +94 -0
- package/src/integration-installers.mjs +28 -0
- package/src/integration-spec.mjs +398 -0
- package/src/log-file.mjs +60 -0
- package/src/milestones-reached.mjs +130 -0
- package/src/next-break-reminder.mjs +78 -0
- package/src/onboarding-electron.mjs +240 -0
- package/src/onboarding-platform.mjs +50 -0
- package/src/onboarding-preload.cjs +19 -0
- package/src/onboarding-window.mjs +874 -0
- package/src/open-dashboard.mjs +30 -0
- package/src/reward.mjs +311 -0
- package/src/runner-installer.mjs +153 -0
- package/src/runtime.mjs +1323 -0
- package/src/scan-worker.mjs +23 -0
- package/src/scanner.mjs +84 -0
- package/src/seen-lines.mjs +148 -0
- package/src/skill-installer.mjs +76 -0
- package/src/source-snapshot.mjs +725 -0
- package/src/store.mjs +595 -0
- package/src/tracker-instance.mjs +166 -0
- package/src/tracker-problems.mjs +30 -0
- package/src/tracker-stop.mjs +73 -0
- package/src/uninstall.mjs +80 -0
- package/src/vibe.mjs +4 -0
- package/src/window-guards.mjs +7 -0
- package/src/window-page.mjs +49 -0
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
import {
|
|
2
|
+
renderPlatformChrome,
|
|
3
|
+
resolveSetupPlatform,
|
|
4
|
+
setupPlatformTheme,
|
|
5
|
+
} from './onboarding-platform.mjs';
|
|
6
|
+
import {contentSecurityPolicy, fontFaces} from './window-page.mjs';
|
|
7
|
+
|
|
8
|
+
export function renderOnboardingWindow(platform = resolveSetupPlatform()) {
|
|
9
|
+
const theme = setupPlatformTheme(platform);
|
|
10
|
+
// The page's one stylesheet and one script, kept apart so that its policy allows exactly them.
|
|
11
|
+
const style = `
|
|
12
|
+
${fontFaces(['fira-code'])}
|
|
13
|
+
|
|
14
|
+
:root {
|
|
15
|
+
color-scheme: dark;
|
|
16
|
+
--bg-color: #111113;
|
|
17
|
+
--red-color: #e51324;
|
|
18
|
+
--grey-border: #555558;
|
|
19
|
+
--grey-text: #88888e;
|
|
20
|
+
--white-text: #e1e1e6;
|
|
21
|
+
--error: #ff9d9d;
|
|
22
|
+
--font-mono: 'Fira Code', 'Courier New', Courier, ui-monospace, monospace;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
26
|
+
|
|
27
|
+
html, body {
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: 100%;
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
background: transparent;
|
|
32
|
+
color: var(--white-text);
|
|
33
|
+
font: 11px/1.4 var(--font-mono);
|
|
34
|
+
-webkit-font-smoothing: antialiased;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
button, input { font: inherit; }
|
|
38
|
+
button, .option { cursor: pointer; }
|
|
39
|
+
button:disabled { cursor: not-allowed; }
|
|
40
|
+
|
|
41
|
+
.tech-box-red {
|
|
42
|
+
border-top: 3px solid var(--red-color);
|
|
43
|
+
border-bottom: 3px solid var(--red-color);
|
|
44
|
+
border-left: 2px dashed var(--red-color);
|
|
45
|
+
border-right: 2px dashed var(--red-color);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.tech-box-grey {
|
|
49
|
+
border-top: 3px solid var(--grey-border);
|
|
50
|
+
border-bottom: 3px solid var(--grey-border);
|
|
51
|
+
border-left: 2px dashed var(--grey-border);
|
|
52
|
+
border-right: 2px dashed var(--grey-border);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.tech-box-white {
|
|
56
|
+
border-top: 3px solid var(--white-text);
|
|
57
|
+
border-bottom: 3px solid var(--white-text);
|
|
58
|
+
border-left: 2px dashed var(--white-text);
|
|
59
|
+
border-right: 2px dashed var(--white-text);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.dashed-divider, .section-divider {
|
|
63
|
+
color: var(--red-color);
|
|
64
|
+
font-size: 11px;
|
|
65
|
+
letter-spacing: 1px;
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
white-space: nowrap;
|
|
68
|
+
margin: 14px 0;
|
|
69
|
+
width: 100%;
|
|
70
|
+
user-select: none;
|
|
71
|
+
opacity: 0.85;
|
|
72
|
+
line-height: 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.shell.window {
|
|
76
|
+
width: 100%;
|
|
77
|
+
height: 100%;
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-direction: column;
|
|
80
|
+
background: var(--bg-color);
|
|
81
|
+
border: 1px solid var(--red-color);
|
|
82
|
+
border-radius: 8px;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.title-bar {
|
|
87
|
+
height: 38px;
|
|
88
|
+
padding: 0 16px;
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 8px;
|
|
92
|
+
border-bottom: 1px solid var(--red-color);
|
|
93
|
+
-webkit-app-region: drag;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.dot {
|
|
97
|
+
width: 13px;
|
|
98
|
+
height: 13px;
|
|
99
|
+
border-radius: 50%;
|
|
100
|
+
border: 1px solid var(--red-color);
|
|
101
|
+
padding: 0;
|
|
102
|
+
background: transparent;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.dot.filled { background-color: var(--red-color); }
|
|
106
|
+
|
|
107
|
+
.dot.dot-close {
|
|
108
|
+
position: relative;
|
|
109
|
+
z-index: 1;
|
|
110
|
+
box-sizing: content-box;
|
|
111
|
+
border: 0;
|
|
112
|
+
padding: 7px;
|
|
113
|
+
margin: -7px;
|
|
114
|
+
display: grid;
|
|
115
|
+
place-items: center;
|
|
116
|
+
background-clip: content-box;
|
|
117
|
+
color: var(--bg-color);
|
|
118
|
+
font: 700 10px/1 var(--font-mono);
|
|
119
|
+
cursor: pointer;
|
|
120
|
+
-webkit-app-region: no-drag;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.dot.dot-close::before {
|
|
124
|
+
content: "×";
|
|
125
|
+
opacity: 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.dot.dot-close:hover::before,
|
|
129
|
+
.dot.dot-close:focus-visible::before { opacity: 1; }
|
|
130
|
+
|
|
131
|
+
.dot.dot-close:focus-visible { outline: none; }
|
|
132
|
+
|
|
133
|
+
.container {
|
|
134
|
+
flex: 1 1 auto;
|
|
135
|
+
min-height: 0;
|
|
136
|
+
display: flex;
|
|
137
|
+
flex-direction: column;
|
|
138
|
+
padding: 16px 28px 20px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.top-tag {
|
|
142
|
+
text-align: right;
|
|
143
|
+
color: var(--white-text);
|
|
144
|
+
font-size: 11px;
|
|
145
|
+
margin-bottom: 4px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.workspace.layout {
|
|
149
|
+
flex: 1 1 auto;
|
|
150
|
+
min-height: 0;
|
|
151
|
+
display: flex;
|
|
152
|
+
gap: 60px;
|
|
153
|
+
padding: 24px 0 30px;
|
|
154
|
+
overflow: auto;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.steps-panel { min-height: 0; }
|
|
158
|
+
|
|
159
|
+
.sidebar {
|
|
160
|
+
width: 150px;
|
|
161
|
+
flex: 0 0 150px;
|
|
162
|
+
display: flex;
|
|
163
|
+
flex-direction: column;
|
|
164
|
+
align-items: flex-start;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.rail-item {
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
gap: 12px;
|
|
171
|
+
color: var(--grey-text);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.rail-item.is-active { color: var(--red-color); }
|
|
175
|
+
|
|
176
|
+
.rail-item:not(.is-active) .rail-badge {
|
|
177
|
+
border-color: var(--grey-border);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.rail-badge {
|
|
181
|
+
padding: 3px 6px;
|
|
182
|
+
font-weight: 700;
|
|
183
|
+
font-size: 11px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.rail-label {
|
|
187
|
+
font-size: 10px;
|
|
188
|
+
line-height: 1.3;
|
|
189
|
+
font-weight: 700;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.step-connector {
|
|
193
|
+
width: 1px;
|
|
194
|
+
height: 32px;
|
|
195
|
+
border-left: 2px dashed var(--red-color);
|
|
196
|
+
margin: 6px 0 6px 13px;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.content {
|
|
200
|
+
flex: 1 1 auto;
|
|
201
|
+
min-width: 0;
|
|
202
|
+
display: flex;
|
|
203
|
+
flex-direction: column;
|
|
204
|
+
gap: 32px;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.section-title {
|
|
208
|
+
font-size: 11px;
|
|
209
|
+
margin-bottom: 12px;
|
|
210
|
+
letter-spacing: 0.5px;
|
|
211
|
+
color: var(--white-text);
|
|
212
|
+
font-weight: 500;
|
|
213
|
+
text-transform: uppercase;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.step { border: 0; margin: 0; padding: 0; min-width: 0; }
|
|
217
|
+
|
|
218
|
+
.option-group {
|
|
219
|
+
display: flex;
|
|
220
|
+
gap: 15px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.option {
|
|
224
|
+
position: relative;
|
|
225
|
+
width: 72px;
|
|
226
|
+
height: 38px;
|
|
227
|
+
flex: 0 0 72px;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.option input {
|
|
231
|
+
position: absolute;
|
|
232
|
+
width: 1px;
|
|
233
|
+
height: 1px;
|
|
234
|
+
margin: -1px;
|
|
235
|
+
overflow: hidden;
|
|
236
|
+
clip: rect(0 0 0 0);
|
|
237
|
+
clip-path: inset(50%);
|
|
238
|
+
white-space: nowrap;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.option-card {
|
|
242
|
+
display: inline-flex;
|
|
243
|
+
align-items: center;
|
|
244
|
+
justify-content: center;
|
|
245
|
+
width: 72px;
|
|
246
|
+
height: 38px;
|
|
247
|
+
background: transparent;
|
|
248
|
+
padding: 0;
|
|
249
|
+
font-size: 11px;
|
|
250
|
+
font-weight: 700;
|
|
251
|
+
letter-spacing: 0.5px;
|
|
252
|
+
cursor: pointer;
|
|
253
|
+
user-select: none;
|
|
254
|
+
color: var(--grey-text);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.option-card.btn-red { color: var(--red-color); }
|
|
258
|
+
.option-card.btn-grey { color: var(--grey-text); }
|
|
259
|
+
|
|
260
|
+
.option input + .option-card {
|
|
261
|
+
color: var(--grey-text);
|
|
262
|
+
border-top-color: var(--grey-border);
|
|
263
|
+
border-bottom-color: var(--grey-border);
|
|
264
|
+
border-left-color: var(--grey-border);
|
|
265
|
+
border-right-color: var(--grey-border);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.option input:checked + .option-card {
|
|
269
|
+
color: var(--red-color);
|
|
270
|
+
border-top-color: var(--red-color);
|
|
271
|
+
border-bottom-color: var(--red-color);
|
|
272
|
+
border-left-color: var(--red-color);
|
|
273
|
+
border-right-color: var(--red-color);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.option input:focus-visible + .option-card {
|
|
277
|
+
outline: none;
|
|
278
|
+
box-shadow: inset 0 0 0 1px var(--red-color);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.step-head {
|
|
282
|
+
display: flex;
|
|
283
|
+
align-items: center;
|
|
284
|
+
gap: 8px;
|
|
285
|
+
margin-bottom: 12px;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.step-head .section-title { margin-bottom: 0; }
|
|
289
|
+
|
|
290
|
+
.project-count {
|
|
291
|
+
margin-left: auto;
|
|
292
|
+
color: var(--red-color);
|
|
293
|
+
font-size: 10px;
|
|
294
|
+
font-weight: 700;
|
|
295
|
+
letter-spacing: 0.08em;
|
|
296
|
+
display: none;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.projects-step {
|
|
300
|
+
display: flex;
|
|
301
|
+
flex-direction: column;
|
|
302
|
+
min-height: 0;
|
|
303
|
+
flex: 1 1 auto;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.project-list {
|
|
307
|
+
min-height: 120px;
|
|
308
|
+
max-height: 180px;
|
|
309
|
+
overflow: auto;
|
|
310
|
+
margin-bottom: 16px;
|
|
311
|
+
scrollbar-color: var(--red-color) var(--bg-color);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.project-list.is-empty {
|
|
315
|
+
padding: 40px;
|
|
316
|
+
display: flex;
|
|
317
|
+
flex-direction: column;
|
|
318
|
+
align-items: center;
|
|
319
|
+
justify-content: center;
|
|
320
|
+
gap: 8px;
|
|
321
|
+
color: var(--grey-text);
|
|
322
|
+
overflow: hidden;
|
|
323
|
+
scrollbar-width: none;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.project-list.is-empty::-webkit-scrollbar { display: none; }
|
|
327
|
+
|
|
328
|
+
.dropzone-icon {
|
|
329
|
+
width: 22px;
|
|
330
|
+
height: 18px;
|
|
331
|
+
stroke: var(--grey-text);
|
|
332
|
+
fill: none;
|
|
333
|
+
stroke-width: 1.5;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.empty-state {
|
|
337
|
+
display: flex;
|
|
338
|
+
flex-direction: column;
|
|
339
|
+
align-items: center;
|
|
340
|
+
gap: 8px;
|
|
341
|
+
font-size: 11px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.project-row {
|
|
345
|
+
display: grid;
|
|
346
|
+
grid-template-columns: minmax(0, 1fr) auto auto;
|
|
347
|
+
align-items: center;
|
|
348
|
+
gap: 9px;
|
|
349
|
+
min-height: 38px;
|
|
350
|
+
padding: 5px 8px;
|
|
351
|
+
border-bottom: 1px dashed rgba(240, 48, 48, 0.25);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.project-row:last-child { border-bottom: 0; }
|
|
355
|
+
.project-row[data-kind="pending"] { background: rgba(240, 48, 48, 0.06); }
|
|
356
|
+
|
|
357
|
+
.project-copy { min-width: 0; }
|
|
358
|
+
|
|
359
|
+
.project-name,
|
|
360
|
+
.project-path {
|
|
361
|
+
display: block;
|
|
362
|
+
overflow: hidden;
|
|
363
|
+
text-overflow: ellipsis;
|
|
364
|
+
white-space: nowrap;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.project-name { color: var(--white-text); font-weight: 700; }
|
|
368
|
+
.project-path { color: var(--grey-text); font-size: 10px; }
|
|
369
|
+
.project-meta { color: var(--grey-text); font-size: 9px; font-weight: 700; }
|
|
370
|
+
.project-row[data-kind="pending"] .project-meta { color: var(--red-color); }
|
|
371
|
+
|
|
372
|
+
.project-actions {
|
|
373
|
+
display: flex;
|
|
374
|
+
align-items: center;
|
|
375
|
+
gap: 20px;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.hint {
|
|
379
|
+
color: var(--white-text);
|
|
380
|
+
font-size: 10px;
|
|
381
|
+
line-height: 1.4;
|
|
382
|
+
text-transform: uppercase;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.terminal-button {
|
|
386
|
+
background: transparent;
|
|
387
|
+
color: var(--red-color);
|
|
388
|
+
padding: 8px 18px;
|
|
389
|
+
font-size: 11px;
|
|
390
|
+
font-weight: 700;
|
|
391
|
+
letter-spacing: 0.5px;
|
|
392
|
+
cursor: pointer;
|
|
393
|
+
-webkit-app-region: no-drag;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.terminal-button:hover,
|
|
397
|
+
.terminal-button:focus-visible,
|
|
398
|
+
.remove-project:hover,
|
|
399
|
+
.remove-project:focus-visible {
|
|
400
|
+
background: rgba(240, 48, 48, 0.12);
|
|
401
|
+
outline: none;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.remove-project {
|
|
405
|
+
width: 25px;
|
|
406
|
+
height: 25px;
|
|
407
|
+
border: 1px dashed var(--grey-border);
|
|
408
|
+
background: transparent;
|
|
409
|
+
color: var(--grey-text);
|
|
410
|
+
cursor: pointer;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.content-footer.footer-action {
|
|
414
|
+
display: flex;
|
|
415
|
+
justify-content: space-between;
|
|
416
|
+
align-items: center;
|
|
417
|
+
gap: 16px;
|
|
418
|
+
margin: 12px 0;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
.message.comment-text {
|
|
422
|
+
min-width: 0;
|
|
423
|
+
color: var(--white-text);
|
|
424
|
+
font-size: 11px;
|
|
425
|
+
overflow: hidden;
|
|
426
|
+
text-overflow: ellipsis;
|
|
427
|
+
white-space: nowrap;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.message[data-state="error"] { color: var(--error); }
|
|
431
|
+
.message[data-state="success"] { color: var(--red-color); }
|
|
432
|
+
|
|
433
|
+
.primary-button { white-space: nowrap; }
|
|
434
|
+
|
|
435
|
+
button:disabled { cursor: not-allowed; opacity: 0.48; }
|
|
436
|
+
|
|
437
|
+
.statusline.status-bar {
|
|
438
|
+
display: flex;
|
|
439
|
+
justify-content: space-between;
|
|
440
|
+
align-items: center;
|
|
441
|
+
font-size: 10px;
|
|
442
|
+
color: var(--white-text);
|
|
443
|
+
margin-top: 4px;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.normal-mode.status-mode {
|
|
447
|
+
padding: 2px 6px;
|
|
448
|
+
font-weight: 700;
|
|
449
|
+
color: var(--red-color);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.status-text {
|
|
453
|
+
min-width: 0;
|
|
454
|
+
overflow: hidden;
|
|
455
|
+
text-overflow: ellipsis;
|
|
456
|
+
white-space: nowrap;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.status-text[data-state="error"] { color: var(--error); }
|
|
460
|
+
.status-text[data-state="success"] { color: var(--red-color); }
|
|
461
|
+
|
|
462
|
+
.keyboard-hint.status-shortcuts { color: var(--white-text); }
|
|
463
|
+
`;
|
|
464
|
+
const script = `
|
|
465
|
+
const bridge = window.kitcodeOnboarding;
|
|
466
|
+
const projectList = document.getElementById('projectList');
|
|
467
|
+
const projectCount = document.getElementById('projectCount');
|
|
468
|
+
const message = document.getElementById('message');
|
|
469
|
+
const statusText = document.getElementById('statusText');
|
|
470
|
+
const pickFolders = document.getElementById('pickFolders');
|
|
471
|
+
const saveButton = document.getElementById('saveButton');
|
|
472
|
+
const setupContent = document.getElementById('setupContent');
|
|
473
|
+
const railItems = [...document.querySelectorAll('.rail-item')];
|
|
474
|
+
const stepSections = [...document.querySelectorAll('[data-setup-step]')];
|
|
475
|
+
const SAVE_LABEL = '+ SAVE AND OPEN KITCODE ->';
|
|
476
|
+
const FOLDER_ICON = '<svg class="dropzone-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>';
|
|
477
|
+
let persistedProjects = [];
|
|
478
|
+
let pendingProjects = [];
|
|
479
|
+
let saving = false;
|
|
480
|
+
|
|
481
|
+
function projectName(project) {
|
|
482
|
+
const parts = String(project.repoRoot || '').split(/[\\\\/]/).filter(Boolean);
|
|
483
|
+
return parts[parts.length - 1] || project.repoRoot || 'project';
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function knownProjectIds() {
|
|
487
|
+
return new Set(persistedProjects.concat(pendingProjects).map((project) => project.id));
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function formatStatus(status) {
|
|
491
|
+
const value = status || 'setup:ready';
|
|
492
|
+
return value.startsWith('+++') ? value : '+++ ' + value + ' +++';
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function setFeedback(text, state, status) {
|
|
496
|
+
message.textContent = text.startsWith('/*') ? text : '/* ' + text;
|
|
497
|
+
message.dataset.state = state || 'normal';
|
|
498
|
+
statusText.textContent = formatStatus(status);
|
|
499
|
+
statusText.dataset.state = state || 'normal';
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function setActiveRailStep(stepId) {
|
|
503
|
+
if (!stepId) return;
|
|
504
|
+
railItems.forEach((item) => {
|
|
505
|
+
item.classList.toggle('is-active', item.dataset.step === stepId);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function bindStepRailSpy() {
|
|
510
|
+
const observer = new IntersectionObserver((entries) => {
|
|
511
|
+
const visible = entries
|
|
512
|
+
.filter((entry) => entry.isIntersecting)
|
|
513
|
+
.sort((left, right) => right.intersectionRatio - left.intersectionRatio);
|
|
514
|
+
if (visible[0]) {
|
|
515
|
+
setActiveRailStep(visible[0].target.dataset.setupStep);
|
|
516
|
+
}
|
|
517
|
+
}, {
|
|
518
|
+
root: setupContent,
|
|
519
|
+
threshold: [0.35, 0.55, 0.75],
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
stepSections.forEach((section) => observer.observe(section));
|
|
523
|
+
setupContent.addEventListener('focusin', (event) => {
|
|
524
|
+
const section = event.target.closest('[data-setup-step]');
|
|
525
|
+
if (section) setActiveRailStep(section.dataset.setupStep);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function createProjectRow(project, kind) {
|
|
530
|
+
const row = document.createElement('div');
|
|
531
|
+
const copy = document.createElement('div');
|
|
532
|
+
const name = document.createElement('span');
|
|
533
|
+
const path = document.createElement('span');
|
|
534
|
+
const meta = document.createElement('span');
|
|
535
|
+
const remove = document.createElement('button');
|
|
536
|
+
|
|
537
|
+
row.className = 'project-row';
|
|
538
|
+
row.dataset.kind = kind;
|
|
539
|
+
row.dataset.testid = kind === 'pending' ? 'pending-project-row' : 'persisted-project-row';
|
|
540
|
+
row.setAttribute('role', 'listitem');
|
|
541
|
+
row.title = project.repoRoot;
|
|
542
|
+
copy.className = 'project-copy';
|
|
543
|
+
name.className = 'project-name';
|
|
544
|
+
name.textContent = projectName(project);
|
|
545
|
+
path.className = 'project-path';
|
|
546
|
+
path.textContent = project.repoRoot;
|
|
547
|
+
meta.className = 'project-meta';
|
|
548
|
+
meta.textContent = (project.sourceType || 'vibe').toUpperCase() + ' · ' + (kind === 'pending' ? 'PENDING' : 'TRACKED');
|
|
549
|
+
copy.append(name, path);
|
|
550
|
+
row.append(copy, meta);
|
|
551
|
+
|
|
552
|
+
remove.className = 'remove-project';
|
|
553
|
+
remove.type = 'button';
|
|
554
|
+
remove.dataset.testid = 'remove-project';
|
|
555
|
+
remove.textContent = 'x';
|
|
556
|
+
remove.title = 'Remove project';
|
|
557
|
+
remove.setAttribute('aria-label', 'Remove ' + projectName(project));
|
|
558
|
+
remove.addEventListener('click', async () => {
|
|
559
|
+
if (kind === 'pending') {
|
|
560
|
+
pendingProjects = pendingProjects.filter((candidate) => candidate.id !== project.id);
|
|
561
|
+
renderProjects();
|
|
562
|
+
setFeedback('Project removed.', 'normal', 'projects:removed');
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (!bridge?.removeProject) {
|
|
567
|
+
persistedProjects = persistedProjects.filter((candidate) => candidate.id !== project.id);
|
|
568
|
+
renderProjects();
|
|
569
|
+
setFeedback('Project removed from this setup.', 'normal', 'projects:removed');
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
remove.disabled = true;
|
|
574
|
+
try {
|
|
575
|
+
const result = await bridge.removeProject(project.id);
|
|
576
|
+
if (!result.ok) {
|
|
577
|
+
remove.disabled = false;
|
|
578
|
+
setFeedback(result.error || 'Project could not be removed.', 'error', 'error:remove-project');
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
persistedProjects = Array.isArray(result.projects)
|
|
582
|
+
? result.projects
|
|
583
|
+
: persistedProjects.filter((candidate) => candidate.id !== project.id);
|
|
584
|
+
renderProjects();
|
|
585
|
+
setFeedback('Project removed.', 'normal', 'projects:removed');
|
|
586
|
+
} catch {
|
|
587
|
+
remove.disabled = false;
|
|
588
|
+
setFeedback('Project could not be removed. Try again.', 'error', 'error:remove-project');
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
row.append(remove);
|
|
592
|
+
|
|
593
|
+
return row;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function renderProjects() {
|
|
597
|
+
projectList.textContent = '';
|
|
598
|
+
const projects = persistedProjects.concat(pendingProjects);
|
|
599
|
+
projectCount.textContent = projects.length + ' ADDED';
|
|
600
|
+
projectList.classList.toggle('is-empty', !projects.length);
|
|
601
|
+
projectList.classList.toggle('tech-box-white', !projects.length);
|
|
602
|
+
|
|
603
|
+
if (!projects.length) {
|
|
604
|
+
const empty = document.createElement('div');
|
|
605
|
+
empty.className = 'empty-state';
|
|
606
|
+
empty.innerHTML = FOLDER_ICON + '<span>No projects added</span>';
|
|
607
|
+
projectList.append(empty);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
persistedProjects.forEach((project) => projectList.append(createProjectRow(project, 'persisted')));
|
|
612
|
+
pendingProjects.forEach((project) => projectList.append(createProjectRow(project, 'pending')));
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function syncOptionGroup(group) {
|
|
616
|
+
const radios = [...group.querySelectorAll('input[type="radio"]')];
|
|
617
|
+
radios.forEach((radio) => {
|
|
618
|
+
if (document.activeElement !== radio) radio.tabIndex = radio.checked ? 0 : -1;
|
|
619
|
+
radio.setAttribute('aria-checked', String(radio.checked));
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function focusNextSection(group) {
|
|
624
|
+
const next = document.getElementById(group.dataset.nextFocus);
|
|
625
|
+
if (next) next.focus();
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function bindOptionGroup(group) {
|
|
629
|
+
const radios = [...group.querySelectorAll('input[type="radio"]')];
|
|
630
|
+
radios.forEach((radio) => radio.addEventListener('change', () => syncOptionGroup(group)));
|
|
631
|
+
group.addEventListener('keydown', (event) => {
|
|
632
|
+
if (!radios.includes(event.target)) return;
|
|
633
|
+
const index = radios.indexOf(event.target);
|
|
634
|
+
|
|
635
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowRight' || event.key === 'ArrowUp' || event.key === 'ArrowLeft') {
|
|
636
|
+
event.preventDefault();
|
|
637
|
+
const delta = event.key === 'ArrowDown' || event.key === 'ArrowRight' ? 1 : -1;
|
|
638
|
+
const next = radios[(index + delta + radios.length) % radios.length];
|
|
639
|
+
radios.forEach((radio) => { radio.tabIndex = -1; });
|
|
640
|
+
next.tabIndex = 0;
|
|
641
|
+
next.focus();
|
|
642
|
+
statusText.textContent = formatStatus('select:space-or-enter');
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (event.key === ' ' || event.key === 'Enter') {
|
|
647
|
+
event.preventDefault();
|
|
648
|
+
event.target.checked = true;
|
|
649
|
+
event.target.dispatchEvent(new Event('change', {bubbles: true}));
|
|
650
|
+
if (event.key === 'Enter') focusNextSection(group);
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
syncOptionGroup(group);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function setBusy(busy) {
|
|
657
|
+
saving = busy;
|
|
658
|
+
pickFolders.disabled = busy;
|
|
659
|
+
saveButton.disabled = busy;
|
|
660
|
+
document.querySelectorAll('.option input, .remove-project').forEach((control) => { control.disabled = busy; });
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
bindOptionGroup(document.getElementById('autoTrackGroup'));
|
|
664
|
+
bindStepRailSpy();
|
|
665
|
+
renderProjects();
|
|
666
|
+
|
|
667
|
+
pickFolders.addEventListener('click', async () => {
|
|
668
|
+
setFeedback('Opening the native multi-folder picker...', 'normal', 'projects:select');
|
|
669
|
+
try {
|
|
670
|
+
const result = await bridge.selectFolders();
|
|
671
|
+
if (result.canceled) {
|
|
672
|
+
setFeedback('Project selection canceled.', 'normal', 'projects:unchanged');
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const known = knownProjectIds();
|
|
677
|
+
let duplicates = 0;
|
|
678
|
+
for (const project of result.projects || []) {
|
|
679
|
+
if (known.has(project.id)) {
|
|
680
|
+
duplicates += 1;
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
known.add(project.id);
|
|
684
|
+
pendingProjects.push(project);
|
|
685
|
+
}
|
|
686
|
+
renderProjects();
|
|
687
|
+
const added = (result.projects || []).length - duplicates;
|
|
688
|
+
setFeedback(
|
|
689
|
+
added ? added + ' project' + (added === 1 ? '' : 's') + ' ready to add.' : 'Those projects are already added.',
|
|
690
|
+
'normal',
|
|
691
|
+
duplicates ? 'projects:deduplicated' : 'projects:pending'
|
|
692
|
+
);
|
|
693
|
+
} catch {
|
|
694
|
+
setFeedback('Folder picker could not open. Restart setup and try again.', 'error', 'error:folder-picker');
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
saveButton.addEventListener('click', async () => {
|
|
699
|
+
if (saving) return;
|
|
700
|
+
if (!persistedProjects.length && !pendingProjects.length) {
|
|
701
|
+
setFeedback('Add at least one readable project folder.', 'error', 'error:no-projects');
|
|
702
|
+
pickFolders.focus();
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
setBusy(true);
|
|
707
|
+
saveButton.textContent = 'Saving...';
|
|
708
|
+
setFeedback('Saving projects and preferences...', 'normal', 'setup:save');
|
|
709
|
+
const autoTrack = document.querySelector('input[name="autoTrack"]:checked').value === 'yes';
|
|
710
|
+
const companionView = 'mini';
|
|
711
|
+
|
|
712
|
+
try {
|
|
713
|
+
const result = await bridge.submit({
|
|
714
|
+
folders: pendingProjects.map((project) => project.repoRoot),
|
|
715
|
+
autoTrack,
|
|
716
|
+
companionView,
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
if (Array.isArray(result.projects)) {
|
|
720
|
+
persistedProjects = result.projects;
|
|
721
|
+
const persistedIds = new Set(persistedProjects.map((project) => project.id));
|
|
722
|
+
pendingProjects = pendingProjects.filter((project) => !persistedIds.has(project.id));
|
|
723
|
+
renderProjects();
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (!result.ok) {
|
|
727
|
+
setBusy(false);
|
|
728
|
+
saveButton.textContent = 'Retry Save';
|
|
729
|
+
setFeedback(result.error || 'Setup could not be saved.', 'error', 'error:retry-available');
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
saveButton.textContent = 'Setup Complete';
|
|
734
|
+
setFeedback('Setup complete. Opening KitCode companion...', 'success', 'setup:complete');
|
|
735
|
+
} catch {
|
|
736
|
+
setBusy(false);
|
|
737
|
+
saveButton.textContent = 'Retry Save';
|
|
738
|
+
setFeedback('Setup request failed. Your selections are still here.', 'error', 'error:retry-available');
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
function closeSetup() {
|
|
743
|
+
try {
|
|
744
|
+
const result = bridge?.close();
|
|
745
|
+
if (result && typeof result.catch === 'function') result.catch(() => window.close());
|
|
746
|
+
if (!result) window.close();
|
|
747
|
+
} catch {
|
|
748
|
+
window.close();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const closeButton = document.getElementById('closeButton');
|
|
753
|
+
closeButton.addEventListener('pointerdown', (event) => event.stopPropagation());
|
|
754
|
+
closeButton.addEventListener('click', (event) => {
|
|
755
|
+
event.stopPropagation();
|
|
756
|
+
closeSetup();
|
|
757
|
+
});
|
|
758
|
+
document.addEventListener('keydown', (event) => {
|
|
759
|
+
if (event.key === 'Escape' && !saving) closeSetup();
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
if (!bridge) {
|
|
763
|
+
setBusy(true);
|
|
764
|
+
setFeedback('KitCode setup bridge is unavailable. Close this window and run kitcode setup again.', 'error', 'error:preload');
|
|
765
|
+
} else {
|
|
766
|
+
bridge.initialState().then((state) => {
|
|
767
|
+
persistedProjects = Array.isArray(state.projects) ? state.projects : [];
|
|
768
|
+
const known = knownProjectIds();
|
|
769
|
+
let suggestedCount = 0;
|
|
770
|
+
|
|
771
|
+
for (const project of state.suggestedProjects || []) {
|
|
772
|
+
if (known.has(project.id)) {
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
known.add(project.id);
|
|
776
|
+
pendingProjects.push(project);
|
|
777
|
+
suggestedCount += 1;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
document.querySelector('input[name="autoTrack"][value="' + (state.autoTrack === false ? 'no' : 'yes') + '"]').checked = true;
|
|
781
|
+
document.querySelectorAll('.option-group').forEach(syncOptionGroup);
|
|
782
|
+
renderProjects();
|
|
783
|
+
|
|
784
|
+
if (suggestedCount) {
|
|
785
|
+
setFeedback(
|
|
786
|
+
suggestedCount + ' project folder' + (suggestedCount === 1 ? '' : 's') + ' added from your chat. Review and save to continue.',
|
|
787
|
+
'normal',
|
|
788
|
+
'projects:chat-suggested'
|
|
789
|
+
);
|
|
790
|
+
} else {
|
|
791
|
+
setFeedback(
|
|
792
|
+
persistedProjects.length ? persistedProjects.length + ' tracked project' + (persistedProjects.length === 1 ? '' : 's') + ' loaded.' : 'Add one or more projects to continue.',
|
|
793
|
+
'normal',
|
|
794
|
+
persistedProjects.length ? 'projects:loaded' : 'setup:ready'
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}).catch(() => {
|
|
798
|
+
renderProjects();
|
|
799
|
+
setFeedback('Local setup state could not be loaded. Restart setup and try again.', 'error', 'error:initial-state');
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
`;
|
|
803
|
+
|
|
804
|
+
return `<!doctype html>
|
|
805
|
+
<html lang="en">
|
|
806
|
+
<head>
|
|
807
|
+
<meta charset="utf-8">
|
|
808
|
+
<meta http-equiv="Content-Security-Policy" content="${contentSecurityPolicy({script, style})}">
|
|
809
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
810
|
+
<title>KitCode Setup</title>
|
|
811
|
+
<style>${style}</style>
|
|
812
|
+
</head>
|
|
813
|
+
<body data-platform="${theme.id}" data-theme-marker="${theme.marker}">
|
|
814
|
+
<main class="shell window" data-testid="setup-shell">
|
|
815
|
+
${renderPlatformChrome(theme)}
|
|
816
|
+
<div class="container">
|
|
817
|
+
<div class="top-tag">local-first setup</div>
|
|
818
|
+
<div class="dashed-divider section-divider" aria-hidden="true">==================================================================================================================================================================================================</div>
|
|
819
|
+
<div class="workspace layout steps-panel" id="setupContent" data-testid="setup-step-rail">
|
|
820
|
+
<aside class="sidebar">
|
|
821
|
+
<div class="rail-item is-active" data-step="tracking" data-testid="setup-rail-tracking">
|
|
822
|
+
<span class="rail-badge tech-box-red">01</span>
|
|
823
|
+
<span class="rail-label">START TRACKING<br>AFTER SETUP</span>
|
|
824
|
+
</div>
|
|
825
|
+
<div class="step-connector" aria-hidden="true"></div>
|
|
826
|
+
<div class="rail-item" data-step="projects" data-testid="setup-rail-projects">
|
|
827
|
+
<span class="rail-badge tech-box-red">02</span>
|
|
828
|
+
<span class="rail-label">PROJECTS</span>
|
|
829
|
+
</div>
|
|
830
|
+
</aside>
|
|
831
|
+
<div class="content">
|
|
832
|
+
<fieldset class="step" id="trackingStep" data-setup-step="tracking">
|
|
833
|
+
<legend class="section-title">Start tracking after setup?</legend>
|
|
834
|
+
<div class="option-group" id="autoTrackGroup" role="radiogroup" aria-label="Start tracking after setup" data-next-focus="pickFolders">
|
|
835
|
+
<label class="option">
|
|
836
|
+
<input type="radio" name="autoTrack" value="yes" checked data-testid="auto-track-yes">
|
|
837
|
+
<span class="option-card btn btn-red tech-box-red option-mark">[ YES ]</span>
|
|
838
|
+
</label>
|
|
839
|
+
<label class="option">
|
|
840
|
+
<input type="radio" name="autoTrack" value="no" data-testid="auto-track-no">
|
|
841
|
+
<span class="option-card btn btn-grey tech-box-grey option-mark">[ NO ]</span>
|
|
842
|
+
</label>
|
|
843
|
+
</div>
|
|
844
|
+
</fieldset>
|
|
845
|
+
<section class="step projects-step" aria-labelledby="projectsTitle" data-setup-step="projects">
|
|
846
|
+
<div class="step-head">
|
|
847
|
+
<h2 class="section-title" id="projectsTitle">Select one or more local folders</h2>
|
|
848
|
+
<span class="project-count" id="projectCount">0 ADDED</span>
|
|
849
|
+
</div>
|
|
850
|
+
<div class="project-list is-empty tech-box-white" id="projectList" role="list" aria-label="KitCode projects" data-testid="project-list"></div>
|
|
851
|
+
<div class="project-actions">
|
|
852
|
+
<button class="terminal-button tech-box-red" id="pickFolders" type="button" data-testid="add-projects">+ ADD PROJECTS</button>
|
|
853
|
+
<p class="hint">Existing files become the baseline.<br>Source and diffs stay local.</p>
|
|
854
|
+
</div>
|
|
855
|
+
</section>
|
|
856
|
+
</div>
|
|
857
|
+
</div>
|
|
858
|
+
<div class="dashed-divider section-divider" aria-hidden="true">==================================================================================================================================================================================================</div>
|
|
859
|
+
<footer class="content-footer footer-action">
|
|
860
|
+
<div class="message comment-text" id="message" aria-live="polite">/* Add one or more projects to continue.</div>
|
|
861
|
+
<button class="terminal-button primary-button tech-box-red" id="saveButton" type="button" data-testid="save-setup">+ SAVE AND OPEN KITCODE -></button>
|
|
862
|
+
</footer>
|
|
863
|
+
<div class="dashed-divider section-divider" aria-hidden="true">==================================================================================================================================================================================================</div>
|
|
864
|
+
<footer class="statusline status-bar">
|
|
865
|
+
<span class="normal-mode status-mode tech-box-red">NORMAL</span>
|
|
866
|
+
<span class="status-text" id="statusText" aria-live="polite">+++ setup: ready +++</span>
|
|
867
|
+
<span class="keyboard-hint status-shortcuts">↑↓ move # space select # enter continue</span>
|
|
868
|
+
</footer>
|
|
869
|
+
</div>
|
|
870
|
+
</main>
|
|
871
|
+
<script>${script}</script>
|
|
872
|
+
</body>
|
|
873
|
+
</html>`;
|
|
874
|
+
}
|