@viberaven/cli 1.1.9 → 1.1.10

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/dist/cli.js CHANGED
@@ -51,6 +51,10 @@ function renderLocalUiHtml() {
51
51
  --line-strong: #d0d5dd;
52
52
  --orange: #ff7a00;
53
53
  --orange-soft: #fff2e5;
54
+ --purple: #7c3aed;
55
+ --purple-soft: #f3edff;
56
+ --purple-line: #ddd0ff;
57
+ --mint-panel: #f6fffb;
54
58
  --green: #24b26b;
55
59
  --green-soft: #e9f8f1;
56
60
  --red: #e11d1d;
@@ -58,7 +62,8 @@ function renderLocalUiHtml() {
58
62
  --black-button: #071018;
59
63
  --radius: 8px;
60
64
  color-scheme: light;
61
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
65
+ --mono: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", monospace;
66
+ font-family: "Geist", "Satoshi", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
62
67
  }
63
68
  * { box-sizing: border-box; }
64
69
  html, body { min-height: 100%; }
@@ -66,8 +71,11 @@ function renderLocalUiHtml() {
66
71
  margin: 0;
67
72
  background: var(--canvas);
68
73
  color: var(--ink);
69
- min-height: 100dvh;
74
+ height: 100dvh;
70
75
  letter-spacing: 0;
76
+ display: grid;
77
+ grid-template-rows: auto minmax(0, 1fr) auto;
78
+ overflow: hidden;
71
79
  }
72
80
  button, input, textarea {
73
81
  font: inherit;
@@ -92,57 +100,54 @@ function renderLocalUiHtml() {
92
100
  }
93
101
  button.primary:hover { background: #111a22; }
94
102
  .topbar {
95
- height: 58px;
103
+ height: 68px;
96
104
  border-bottom: 1px solid var(--line);
97
105
  background: rgba(251, 251, 250, 0.96);
98
106
  display: grid;
99
- grid-template-columns: minmax(260px, 1fr) minmax(220px, 300px) auto;
107
+ grid-template-columns: minmax(340px, 1fr) minmax(240px, 310px) auto;
100
108
  gap: 18px;
101
109
  align-items: center;
102
- padding: 0 20px;
110
+ padding: 0 26px;
103
111
  position: sticky;
104
112
  top: 0;
113
+ z-index: 2;
114
+ box-shadow: 0 1px 0 rgba(17, 20, 23, 0.02);
105
115
  }
106
116
  .brand-lockup {
107
117
  display: flex;
108
118
  align-items: center;
109
- gap: 12px;
119
+ gap: 14px;
110
120
  min-width: 0;
111
121
  }
112
122
  .brand-lockup strong {
113
- font-size: 20px;
123
+ font-size: 28px;
114
124
  line-height: 1;
115
125
  white-space: nowrap;
116
126
  }
117
127
  .raven-mark {
118
- width: 36px;
119
- height: 30px;
120
- display: inline-block;
121
- border-radius: 65% 35% 50% 45%;
122
- background: var(--black-button);
123
- position: relative;
124
- transform: skewX(-18deg) rotate(-8deg);
128
+ width: 58px;
129
+ height: 46px;
130
+ display: inline-grid;
131
+ place-items: center;
132
+ flex: 0 0 auto;
133
+ overflow: visible;
125
134
  }
126
- .raven-mark::after {
127
- content: "";
128
- position: absolute;
129
- right: -7px;
130
- bottom: 4px;
131
- width: 18px;
132
- height: 5px;
133
- border-radius: 999px;
134
- background: var(--orange);
135
- transform: rotate(-28deg);
135
+ .raven-mark img {
136
+ width: 54px;
137
+ height: 54px;
138
+ object-fit: contain;
139
+ display: block;
140
+ transform: translateY(1px);
136
141
  }
137
142
  .tagline {
138
- border: 1px solid var(--line);
139
- border-radius: 7px;
140
- padding: 8px 12px;
141
- color: var(--ink);
142
- font-size: 13px;
143
- font-weight: 650;
143
+ border: 1px solid #eef0f4;
144
+ border-radius: 10px;
145
+ padding: 9px 18px;
146
+ color: var(--muted-strong);
147
+ font-size: 14px;
148
+ font-weight: 600;
144
149
  white-space: nowrap;
145
- background: var(--surface);
150
+ background: #f4f6f8;
146
151
  }
147
152
  .project-picker {
148
153
  display: flex;
@@ -156,16 +161,23 @@ function renderLocalUiHtml() {
156
161
  background: var(--surface);
157
162
  color: var(--ink);
158
163
  border-radius: var(--radius);
159
- padding: 9px 12px;
164
+ padding: 9px 14px;
165
+ cursor: pointer;
166
+ }
167
+ .project-picker:hover {
168
+ border-color: #cdd5df;
169
+ box-shadow: 0 10px 24px rgba(15, 23, 42, 0.06);
160
170
  }
161
171
  .project-picker::before {
162
172
  content: "";
163
- width: 15px;
164
- height: 12px;
173
+ width: 18px;
174
+ height: 13px;
165
175
  border: 1.6px solid currentColor;
166
- border-radius: 2px;
176
+ border-radius: 3px;
167
177
  display: inline-block;
168
- box-shadow: inset 0 4px 0 rgba(17, 20, 23, 0.06);
178
+ clip-path: polygon(0 28%, 36% 28%, 43% 8%, 100% 8%, 100% 100%, 0 100%);
179
+ background: linear-gradient(180deg, rgba(17, 20, 23, 0.03), rgba(17, 20, 23, 0.08));
180
+ flex: 0 0 auto;
169
181
  }
170
182
  .project-picker::after {
171
183
  content: "";
@@ -186,14 +198,35 @@ function renderLocalUiHtml() {
186
198
  .top-actions {
187
199
  display: flex;
188
200
  align-items: center;
189
- gap: 10px;
201
+ gap: 12px;
190
202
  justify-content: flex-end;
191
203
  }
204
+ .top-divider {
205
+ width: 1px;
206
+ height: 30px;
207
+ background: var(--line);
208
+ margin: 0 4px 0 6px;
209
+ flex: 0 0 auto;
210
+ }
192
211
  .status-button {
193
212
  display: inline-flex;
194
213
  align-items: center;
195
214
  gap: 9px;
196
215
  white-space: nowrap;
216
+ min-height: 44px;
217
+ padding-inline: 16px;
218
+ }
219
+ .live-status {
220
+ min-height: 44px;
221
+ display: inline-flex;
222
+ align-items: center;
223
+ gap: 9px;
224
+ white-space: nowrap;
225
+ border: 1px solid var(--line);
226
+ background: var(--surface);
227
+ border-radius: var(--radius);
228
+ padding: 9px 16px;
229
+ color: var(--ink);
197
230
  }
198
231
  .dot {
199
232
  width: 8px;
@@ -206,53 +239,96 @@ function renderLocalUiHtml() {
206
239
  .dot.warn { background: var(--orange); }
207
240
  .dot.bad { background: var(--red); }
208
241
  .icon-button {
209
- width: 38px;
210
- height: 38px;
242
+ width: 44px;
243
+ height: 44px;
211
244
  padding: 0;
212
245
  display: grid;
213
246
  place-items: center;
214
- color: var(--muted-strong);
247
+ color: #344054;
248
+ border: 1px solid var(--line);
249
+ background: var(--surface);
250
+ border-radius: 999px;
215
251
  }
252
+ .icon-button:hover { background: var(--surface-soft); }
216
253
  .icon-button::before {
217
- content: "";
218
- width: 17px;
219
- height: 17px;
220
- border: 2px solid currentColor;
221
- border-radius: 999px;
222
- box-shadow: 0 -8px 0 -6px currentColor, 0 8px 0 -6px currentColor, -8px 0 0 -6px currentColor, 8px 0 0 -6px currentColor;
254
+ content: none;
255
+ }
256
+ .icon-button svg {
257
+ width: 22px;
258
+ height: 22px;
259
+ display: block;
260
+ fill: none;
261
+ stroke: currentColor;
262
+ stroke-width: 1.9;
263
+ stroke-linecap: round;
264
+ stroke-linejoin: round;
223
265
  }
224
266
  .verify-button {
225
- min-width: 132px;
267
+ min-width: 128px;
268
+ height: 44px;
269
+ display: inline-flex;
270
+ align-items: center;
271
+ justify-content: center;
272
+ gap: 10px;
226
273
  font-size: 15px;
274
+ border-radius: 9px;
275
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 8px 18px rgba(7, 16, 24, 0.12);
276
+ }
277
+ .verify-button svg {
278
+ width: 19px;
279
+ height: 19px;
280
+ display: block;
281
+ fill: none;
282
+ stroke: currentColor;
283
+ stroke-width: 1.8;
284
+ stroke-linecap: round;
285
+ stroke-linejoin: round;
227
286
  }
228
287
  .shell {
229
288
  display: grid;
230
- grid-template-columns: 296px minmax(560px, 1fr) 360px;
231
- min-height: calc(100dvh - 96px);
289
+ grid-template-columns: 360px minmax(600px, 1fr) 520px;
290
+ min-height: 0;
291
+ height: 100%;
292
+ overflow: hidden;
232
293
  }
233
294
  .rail {
234
295
  border-right: 1px solid var(--line);
235
- padding: 18px 18px 26px;
296
+ padding: 36px 28px 28px;
236
297
  background: var(--canvas);
237
298
  overflow: auto;
299
+ min-height: 0;
238
300
  }
239
301
  .rail-title {
240
- margin: 0 0 10px;
241
- color: var(--muted-strong);
242
- font-size: 12px;
243
- font-weight: 700;
244
- text-transform: uppercase;
302
+ margin: 0 0 16px;
303
+ color: var(--ink);
304
+ font-size: 16px;
305
+ font-weight: 750;
306
+ text-transform: none;
307
+ display: flex;
308
+ align-items: center;
309
+ gap: 10px;
310
+ }
311
+ .rail-title::before {
312
+ content: "";
313
+ width: 17px;
314
+ height: 15px;
315
+ display: inline-block;
316
+ background:
317
+ linear-gradient(var(--muted) 0 0) 8px 2px / 9px 1.5px no-repeat,
318
+ linear-gradient(var(--muted) 0 0) 8px 7px / 9px 1.5px no-repeat,
319
+ linear-gradient(var(--muted) 0 0) 8px 12px / 9px 1.5px no-repeat,
320
+ radial-gradient(circle, var(--muted) 1.7px, transparent 2px) 0 0 / 7px 5px repeat-y;
245
321
  }
246
322
  .search-box {
247
- height: 38px;
323
+ height: 44px;
248
324
  border: 1px solid var(--line);
249
325
  background: var(--surface);
250
- border-radius: var(--radius);
326
+ border-radius: 9px;
251
327
  display: flex;
252
328
  align-items: center;
253
329
  gap: 9px;
254
330
  padding: 0 10px;
255
- margin-bottom: 12px;
331
+ margin-bottom: 18px;
256
332
  }
257
333
  .search-box:focus-within {
258
334
  border-color: var(--orange);
@@ -288,52 +364,47 @@ function renderLocalUiHtml() {
288
364
  }
289
365
  .provider-list {
290
366
  display: grid;
291
- gap: 8px;
367
+ gap: 10px;
292
368
  }
293
369
  .provider-button {
294
370
  width: 100%;
295
- min-height: 58px;
371
+ min-height: 64px;
296
372
  display: grid;
297
- grid-template-columns: 34px minmax(0, 1fr) 8px 12px;
298
- gap: 10px;
373
+ grid-template-columns: 42px minmax(0, 1fr) 8px;
374
+ gap: 14px;
299
375
  align-items: center;
300
376
  text-align: left;
301
- padding: 10px 10px;
377
+ padding: 11px 14px;
302
378
  background: var(--surface);
303
379
  border-color: var(--line);
380
+ border-radius: 10px;
381
+ box-shadow: 0 1px 2px rgba(16, 24, 40, 0.02);
304
382
  }
305
383
  .provider-button.is-selected {
306
384
  border-color: var(--orange);
307
- box-shadow: inset 0 0 0 1px rgba(255, 122, 0, 0.08);
308
- }
309
- .provider-button::after {
310
- content: "";
311
- width: 7px;
312
- height: 7px;
313
- border-right: 1.5px solid var(--muted-strong);
314
- border-bottom: 1.5px solid var(--muted-strong);
315
- transform: rotate(-45deg);
385
+ background: #fffaf5;
386
+ box-shadow: 0 8px 22px rgba(255, 122, 0, 0.08);
316
387
  }
317
388
  .provider-icon {
318
- width: 32px;
319
- height: 32px;
389
+ width: 42px;
390
+ height: 42px;
320
391
  display: grid;
321
392
  place-items: center;
322
393
  border: 1px solid var(--line);
323
- border-radius: 7px;
394
+ border-radius: 9px;
324
395
  background: var(--surface);
325
396
  color: var(--ink);
326
397
  overflow: hidden;
327
398
  }
328
399
  .provider-icon svg, .provider-icon img {
329
- width: 22px;
330
- height: 22px;
400
+ width: 30px;
401
+ height: 30px;
331
402
  display: block;
332
403
  }
333
404
  .provider-name { min-width: 0; }
334
405
  .provider-name strong {
335
406
  display: block;
336
- font-size: 13px;
407
+ font-size: 14px;
337
408
  line-height: 1.2;
338
409
  white-space: nowrap;
339
410
  overflow: hidden;
@@ -341,7 +412,7 @@ function renderLocalUiHtml() {
341
412
  }
342
413
  .state {
343
414
  display: inline-flex;
344
- margin-top: 5px;
415
+ margin-top: 6px;
345
416
  font-size: 12px;
346
417
  line-height: 1;
347
418
  color: var(--muted);
@@ -360,11 +431,12 @@ function renderLocalUiHtml() {
360
431
  .provider-dot[data-state="repo_evidence_found"], .provider-dot[data-state="live_verified"] { background: var(--green); }
361
432
  .provider-dot[data-state="blocked"], .provider-dot[data-state="error"] { background: var(--red); }
362
433
  .run-local-card {
363
- margin-top: 14px;
434
+ margin-top: 20px;
364
435
  border: 1px solid var(--line);
365
- border-radius: var(--radius);
436
+ border-radius: 10px;
366
437
  background: var(--surface);
367
- padding: 14px;
438
+ padding: 18px;
439
+ box-shadow: 0 1px 2px rgba(16, 24, 40, 0.02);
368
440
  }
369
441
  .run-local-card h2 {
370
442
  margin: 0 0 6px;
@@ -385,7 +457,7 @@ function renderLocalUiHtml() {
385
457
  color: #ffffff;
386
458
  border-radius: 7px;
387
459
  padding: 11px 12px;
388
- font: 13px/1.2 ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
460
+ font: 13px/1.2 var(--mono);
389
461
  overflow: hidden;
390
462
  }
391
463
  .command-pill span {
@@ -394,39 +466,40 @@ function renderLocalUiHtml() {
394
466
  white-space: nowrap;
395
467
  }
396
468
  .main {
397
- padding: 26px 28px 34px;
469
+ padding: 42px 48px 40px;
398
470
  overflow: auto;
399
471
  background: var(--canvas);
472
+ min-height: 0;
400
473
  }
401
474
  .provider-header {
402
475
  display: grid;
403
- grid-template-columns: minmax(0, 1fr) auto;
476
+ grid-template-columns: minmax(0, 1fr);
404
477
  gap: 18px;
405
478
  align-items: center;
406
- margin-bottom: 18px;
479
+ margin-bottom: 26px;
407
480
  }
408
481
  .provider-heading {
409
482
  display: flex;
410
- gap: 16px;
483
+ gap: 20px;
411
484
  align-items: center;
412
485
  min-width: 0;
413
486
  }
414
487
  .provider-heading .provider-icon {
415
- width: 44px;
416
- height: 44px;
488
+ width: 58px;
489
+ height: 58px;
417
490
  border: 0;
418
491
  background: transparent;
419
492
  }
420
493
  .provider-heading h1 {
421
494
  margin: 0;
422
- font-size: 22px;
495
+ font-size: 34px;
423
496
  line-height: 1.15;
424
- font-weight: 750;
497
+ font-weight: 780;
425
498
  }
426
499
  .provider-heading p {
427
- margin: 5px 0 0;
500
+ margin: 8px 0 0;
428
501
  color: var(--muted-strong);
429
- font-size: 13px;
502
+ font-size: 16px;
430
503
  line-height: 1.35;
431
504
  }
432
505
  .secondary-action {
@@ -441,178 +514,346 @@ function renderLocalUiHtml() {
441
514
  }
442
515
  .path-list {
443
516
  display: grid;
444
- gap: 0;
517
+ gap: 14px;
445
518
  position: relative;
446
- padding-left: 34px;
519
+ padding-left: 72px;
447
520
  }
448
521
  .path-list::before {
449
522
  content: "";
450
523
  position: absolute;
451
- left: 14px;
452
- top: 26px;
453
- bottom: 26px;
524
+ left: 27px;
525
+ top: -16px;
526
+ bottom: 30px;
454
527
  width: 1px;
455
- background: var(--line-strong);
528
+ background: repeating-linear-gradient(to bottom, #f5b16b 0 9px, transparent 9px 18px);
456
529
  }
457
530
  .path-row {
531
+ width: 100%;
458
532
  border: 1px solid var(--line);
459
- border-radius: 8px;
533
+ border-radius: 12px;
460
534
  background: var(--surface);
461
- padding: 13px 16px;
535
+ padding: 22px 24px;
462
536
  display: grid;
463
- grid-template-columns: minmax(0, 1fr) auto;
464
- gap: 14px;
537
+ grid-template-columns: 48px minmax(0, 1fr) auto 18px;
538
+ gap: 18px;
539
+ align-items: center;
540
+ text-align: left;
465
541
  position: relative;
466
- margin-bottom: 10px;
542
+ margin-bottom: 0;
543
+ min-height: 90px;
544
+ box-shadow: 0 12px 28px rgba(16, 24, 40, 0.035);
467
545
  }
468
546
  .path-row::before {
469
- content: "";
547
+ content: attr(data-step);
470
548
  position: absolute;
471
- left: -31px;
472
- top: 18px;
473
- width: 22px;
474
- height: 22px;
549
+ left: -72px;
550
+ top: 22px;
551
+ width: 40px;
552
+ height: 40px;
475
553
  border: 1px solid var(--line-strong);
476
554
  border-radius: 999px;
477
555
  background: var(--surface);
478
- }
479
- .path-row::after {
480
- content: "";
481
- position: absolute;
482
- left: -23px;
483
- top: 26px;
484
- width: 6px;
485
- height: 6px;
486
- border-radius: 999px;
487
- background: var(--muted);
556
+ display: grid;
557
+ place-items: center;
558
+ color: #344054;
559
+ font-size: 15px;
560
+ font-weight: 800;
488
561
  }
489
562
  .path-row.is-focused {
490
- border-color: var(--line-strong);
491
- box-shadow: 0 10px 22px rgba(17, 20, 23, 0.04);
563
+ border-color: #ffb36b;
564
+ box-shadow: 0 14px 30px rgba(255, 122, 0, 0.09);
492
565
  }
493
566
  .path-row.is-focused::before {
494
567
  border-color: var(--orange);
495
568
  background: var(--orange-soft);
569
+ color: var(--orange);
570
+ }
571
+ .path-icon {
572
+ width: 44px;
573
+ height: 44px;
574
+ border-radius: 10px;
575
+ background: var(--surface-soft);
576
+ color: #344054;
577
+ display: grid;
578
+ place-items: center;
579
+ }
580
+ .path-row.is-focused .path-icon {
581
+ color: var(--orange);
582
+ background: var(--orange-soft);
583
+ }
584
+ .path-icon svg {
585
+ width: 23px;
586
+ height: 23px;
587
+ fill: none;
588
+ stroke: currentColor;
589
+ stroke-width: 2;
590
+ stroke-linecap: round;
591
+ stroke-linejoin: round;
496
592
  }
497
- .path-row.is-focused::after { background: var(--orange); }
498
593
  .path-row strong {
499
- font-size: 14px;
594
+ font-size: 15px;
500
595
  line-height: 1.25;
501
596
  }
502
597
  .path-row p {
503
- margin: 5px 0 0;
598
+ margin: 6px 0 0;
504
599
  color: var(--muted-strong);
505
- font-size: 13px;
600
+ font-size: 14px;
506
601
  line-height: 1.4;
507
602
  }
508
603
  .path-state {
509
- align-self: start;
510
- border: 1px solid currentColor;
604
+ align-self: center;
605
+ border: 0;
511
606
  border-radius: 999px;
512
- padding: 5px 10px;
607
+ padding: 7px 13px;
513
608
  font-size: 12px;
514
609
  line-height: 1;
515
610
  color: var(--muted);
516
611
  white-space: nowrap;
517
- background: var(--surface);
612
+ background: var(--surface-soft);
613
+ }
614
+ .path-arrow {
615
+ color: var(--muted);
616
+ display: grid;
617
+ place-items: center;
618
+ }
619
+ .path-arrow svg {
620
+ width: 18px;
621
+ height: 18px;
622
+ stroke: currentColor;
623
+ stroke-width: 2;
624
+ fill: none;
625
+ stroke-linecap: round;
626
+ stroke-linejoin: round;
518
627
  }
519
628
  .path-state[data-state="ready"] { background: var(--green-soft); }
520
629
  .path-state[data-state="needs_fix"], .path-state[data-state="needs_connect"] { background: var(--orange-soft); }
521
630
  .path-state[data-state="blocked"] { background: var(--red-soft); }
522
631
  .next-fix {
523
- margin: 8px 0 0 34px;
632
+ margin: 36px 0 0 0;
524
633
  border: 1px solid var(--line);
525
- border-radius: var(--radius);
634
+ border-radius: 14px;
526
635
  background: var(--surface);
527
- padding: 16px;
636
+ padding: 28px;
637
+ box-shadow: 0 12px 30px rgba(16, 24, 40, 0.035);
528
638
  }
529
639
  .next-fix h2 {
530
- margin: 0 0 14px;
531
- font-size: 14px;
640
+ margin: 0 0 4px;
641
+ font-size: 18px;
532
642
  line-height: 1.2;
643
+ display: flex;
644
+ align-items: center;
645
+ gap: 12px;
533
646
  }
534
- .fix-grid {
535
- display: grid;
536
- gap: 0;
647
+ .next-fix h2::before {
648
+ content: "";
649
+ width: 18px;
650
+ height: 18px;
651
+ display: inline-block;
652
+ background: var(--purple);
653
+ clip-path: polygon(45% 0, 82% 0, 62% 38%, 94% 38%, 32% 100%, 45% 56%, 10% 56%);
537
654
  }
538
- .fix-section {
539
- display: grid;
540
- grid-template-columns: 34px minmax(0, 1fr);
541
- gap: 12px;
542
- padding: 13px 0;
543
- border-top: 1px solid var(--line);
655
+ .next-fix-subtitle {
656
+ margin: 0 0 22px;
657
+ color: var(--muted-strong);
658
+ font-size: 14px;
544
659
  }
545
- .fix-section:first-child { border-top: 0; padding-top: 0; }
546
- .fix-section:last-child { padding-bottom: 0; }
547
- .fix-symbol {
548
- width: 26px;
549
- height: 26px;
550
- border-radius: 999px;
551
- border: 1px solid var(--line);
660
+ .next-action-row {
661
+ display: grid;
662
+ grid-template-columns: 44px minmax(0, 1fr) auto;
663
+ gap: 16px;
664
+ align-items: center;
665
+ border: 1px solid var(--purple-line);
666
+ border-radius: 10px;
667
+ padding: 18px;
668
+ background: linear-gradient(100deg, #fbf8ff 0%, #ffffff 58%, #f9f7ff 100%);
669
+ }
670
+ .next-action-icon {
671
+ width: 40px;
672
+ height: 40px;
673
+ border-radius: 9px;
552
674
  display: grid;
553
675
  place-items: center;
554
- color: var(--muted-strong);
555
- background: var(--surface-soft);
556
- font-size: 12px;
557
- font-weight: 800;
676
+ color: var(--purple);
677
+ background: var(--purple-soft);
558
678
  }
559
- .fix-section h3 {
679
+ .next-action-icon svg {
680
+ width: 22px;
681
+ height: 22px;
682
+ fill: none;
683
+ stroke: currentColor;
684
+ stroke-width: 2;
685
+ stroke-linecap: round;
686
+ stroke-linejoin: round;
687
+ }
688
+ .next-action-row h3 {
560
689
  margin: 0 0 6px;
561
- font-size: 13px;
690
+ font-size: 14px;
562
691
  line-height: 1.2;
563
692
  }
564
- .fix-section p {
693
+ .next-action-row p {
565
694
  margin: 0;
566
695
  color: var(--muted-strong);
567
696
  font-size: 13px;
568
697
  line-height: 1.45;
569
698
  }
699
+ .open-guide-button {
700
+ display: inline-flex;
701
+ align-items: center;
702
+ gap: 8px;
703
+ border: 0;
704
+ background: var(--purple);
705
+ color: #ffffff;
706
+ border-radius: 8px;
707
+ padding: 10px 14px;
708
+ font-size: 13px;
709
+ font-weight: 750;
710
+ white-space: nowrap;
711
+ }
712
+ .open-guide-button:hover {
713
+ background: #6d28d9;
714
+ color: #ffffff;
715
+ }
570
716
  .drawer {
571
717
  border-left: 1px solid var(--line);
572
- padding: 24px 18px;
573
- background: var(--surface);
718
+ padding: 30px 38px 28px 30px;
719
+ background: var(--canvas);
574
720
  overflow: auto;
721
+ min-height: 0;
722
+ display: grid;
723
+ align-content: start;
724
+ gap: 24px;
725
+ }
726
+ .prompt-panel, .quick-panel {
727
+ border: 1px solid var(--line);
728
+ border-radius: 14px;
729
+ background: var(--surface);
730
+ padding: 20px 24px;
731
+ box-shadow: 0 12px 30px rgba(16, 24, 40, 0.04);
732
+ }
733
+ .panel-heading {
734
+ display: flex;
735
+ align-items: start;
736
+ justify-content: space-between;
737
+ gap: 16px;
738
+ margin-bottom: 20px;
739
+ }
740
+ .panel-title {
741
+ display: flex;
742
+ align-items: center;
743
+ gap: 12px;
744
+ }
745
+ .panel-glyph {
746
+ color: #344054;
747
+ display: grid;
748
+ place-items: center;
749
+ width: 26px;
750
+ height: 26px;
751
+ }
752
+ .panel-glyph svg {
753
+ width: 24px;
754
+ height: 24px;
755
+ fill: none;
756
+ stroke: currentColor;
757
+ stroke-width: 1.9;
758
+ stroke-linecap: round;
759
+ stroke-linejoin: round;
760
+ }
761
+ .copy-small {
762
+ min-height: 36px;
763
+ display: inline-flex;
764
+ align-items: center;
765
+ gap: 8px;
766
+ border-radius: 9px;
767
+ padding: 8px 12px;
768
+ color: var(--muted-strong);
769
+ font-weight: 650;
575
770
  }
576
771
  .drawer h2 {
577
772
  margin: 0;
578
- font-size: 17px;
773
+ font-size: 18px;
579
774
  line-height: 1.2;
580
775
  }
581
776
  .drawer p {
582
- margin: 8px 0 16px;
777
+ margin: 8px 0 0;
583
778
  color: var(--muted-strong);
584
- font-size: 13px;
779
+ font-size: 14px;
585
780
  line-height: 1.4;
586
781
  }
587
782
  .prompt-box {
783
+ position: absolute;
784
+ opacity: 0;
785
+ pointer-events: none;
786
+ width: 1px;
787
+ height: 1px;
788
+ }
789
+ .prompt-card {
588
790
  width: 100%;
589
- min-height: 330px;
590
- resize: vertical;
591
791
  border: 1px solid var(--line);
592
- border-radius: var(--radius);
593
- padding: 14px;
594
- background: var(--surface);
595
- color: var(--muted-strong);
596
- font: 13px/1.7 ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
792
+ border-radius: 10px;
793
+ padding: 22px;
794
+ background: linear-gradient(135deg, var(--mint-panel), #ffffff 74%);
795
+ color: #1f2937;
796
+ font: 13px/1.55 var(--mono);
797
+ min-height: 220px;
798
+ white-space: pre-wrap;
799
+ }
800
+ .prompt-line {
801
+ display: grid;
802
+ grid-template-columns: 20px minmax(0, 1fr);
803
+ gap: 10px;
804
+ margin: 7px 0;
805
+ }
806
+ .prompt-check {
807
+ color: var(--green);
808
+ display: grid;
809
+ place-items: start center;
810
+ padding-top: 3px;
811
+ }
812
+ .prompt-check svg {
813
+ width: 14px;
814
+ height: 14px;
597
815
  }
598
816
  .drawer-actions {
599
817
  display: grid;
600
- gap: 9px;
601
- margin-top: 12px;
818
+ gap: 10px;
819
+ margin-top: 18px;
602
820
  }
603
821
  .drawer-actions button {
604
822
  min-height: 42px;
605
823
  font-weight: 650;
824
+ position: relative;
825
+ display: grid;
826
+ grid-template-columns: 24px minmax(0, 1fr) 14px;
827
+ gap: 10px;
828
+ align-items: center;
829
+ text-align: left;
830
+ border-radius: 9px;
831
+ padding: 10px 12px;
606
832
  }
607
- .tip {
608
- margin-top: 16px;
609
- border: 1px solid var(--line);
610
- border-radius: var(--radius);
611
- padding: 12px;
833
+ .drawer-actions button::after {
834
+ content: "";
835
+ width: 8px;
836
+ height: 8px;
837
+ justify-self: end;
838
+ border-right: 1.5px solid var(--muted);
839
+ border-bottom: 1.5px solid var(--muted);
840
+ transform: rotate(-45deg);
841
+ }
842
+ .action-icon {
843
+ width: 24px;
844
+ height: 24px;
612
845
  color: var(--muted-strong);
613
- font-size: 13px;
614
- line-height: 1.4;
615
- background: var(--surface);
846
+ display: grid;
847
+ place-items: center;
848
+ }
849
+ .action-icon svg {
850
+ width: 19px;
851
+ height: 19px;
852
+ fill: none;
853
+ stroke: currentColor;
854
+ stroke-width: 1.8;
855
+ stroke-linecap: round;
856
+ stroke-linejoin: round;
616
857
  }
617
858
  .empty {
618
859
  color: var(--muted);
@@ -623,6 +864,16 @@ function renderLocalUiHtml() {
623
864
  font-size: 13px;
624
865
  line-height: 1.45;
625
866
  }
867
+ .tip {
868
+ margin-top: 14px;
869
+ border: 1px solid var(--line);
870
+ border-radius: 9px;
871
+ padding: 12px;
872
+ color: var(--muted-strong);
873
+ background: var(--surface-soft);
874
+ font-size: 13px;
875
+ line-height: 1.4;
876
+ }
626
877
  .status-footer {
627
878
  min-height: 38px;
628
879
  border-top: 1px solid var(--line);
@@ -647,7 +898,7 @@ function renderLocalUiHtml() {
647
898
  padding: 7px 12px;
648
899
  background: var(--surface);
649
900
  color: var(--ink);
650
- font: 13px/1 ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
901
+ font: 13px/1 var(--mono);
651
902
  overflow: hidden;
652
903
  text-overflow: ellipsis;
653
904
  white-space: nowrap;
@@ -662,6 +913,207 @@ function renderLocalUiHtml() {
662
913
  white-space: nowrap;
663
914
  }
664
915
  .gate-status[data-status="clear"] { color: var(--green); }
916
+ .action-panel-backdrop {
917
+ position: fixed;
918
+ inset: 0;
919
+ z-index: 9;
920
+ background: rgba(17, 20, 23, 0.28);
921
+ opacity: 0;
922
+ visibility: hidden;
923
+ pointer-events: none;
924
+ align-items: stretch;
925
+ justify-content: flex-end;
926
+ transition: opacity 180ms cubic-bezier(0.16, 1, 0.3, 1), visibility 180ms ease;
927
+ }
928
+ .action-panel-backdrop.is-open {
929
+ opacity: 1;
930
+ visibility: visible;
931
+ pointer-events: auto;
932
+ }
933
+ .action-panel {
934
+ width: min(620px, 100%);
935
+ height: 100%;
936
+ background: var(--canvas);
937
+ border-left: 1px solid var(--line);
938
+ box-shadow: -24px 0 60px rgba(16, 24, 40, 0.16);
939
+ display: grid;
940
+ grid-template-rows: auto minmax(0, 1fr);
941
+ transform: translateX(28px);
942
+ opacity: 0.98;
943
+ transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1), opacity 180ms ease;
944
+ }
945
+ .action-panel-backdrop.is-open .action-panel {
946
+ transform: translateX(0);
947
+ opacity: 1;
948
+ }
949
+ .action-panel-header {
950
+ padding: 24px 28px;
951
+ border-bottom: 1px solid var(--line);
952
+ background: var(--surface);
953
+ display: grid;
954
+ grid-template-columns: minmax(0, 1fr) auto;
955
+ gap: 16px;
956
+ align-items: start;
957
+ }
958
+ .action-panel-header h2 {
959
+ margin: 0;
960
+ font-size: 23px;
961
+ line-height: 1.15;
962
+ }
963
+ .action-panel-header p {
964
+ margin: 8px 0 0;
965
+ color: var(--muted-strong);
966
+ line-height: 1.45;
967
+ }
968
+ .panel-close {
969
+ width: 38px;
970
+ height: 38px;
971
+ border-radius: 999px;
972
+ display: grid;
973
+ place-items: center;
974
+ padding: 0;
975
+ }
976
+ .panel-close::before,
977
+ .panel-close::after {
978
+ content: "";
979
+ width: 15px;
980
+ height: 1.8px;
981
+ background: currentColor;
982
+ grid-area: 1 / 1;
983
+ border-radius: 999px;
984
+ }
985
+ .panel-close::before { transform: rotate(45deg); }
986
+ .panel-close::after { transform: rotate(-45deg); }
987
+ .action-panel-body {
988
+ overflow: auto;
989
+ padding: 24px 28px 30px;
990
+ display: grid;
991
+ gap: 14px;
992
+ align-content: start;
993
+ }
994
+ .guide-card {
995
+ border: 1px solid var(--line);
996
+ border-radius: 12px;
997
+ background: var(--surface);
998
+ padding: 18px;
999
+ box-shadow: 0 10px 22px rgba(16, 24, 40, 0.035);
1000
+ animation: panelCardIn 260ms cubic-bezier(0.16, 1, 0.3, 1) both;
1001
+ animation-delay: calc(var(--panel-index, 0) * 38ms);
1002
+ }
1003
+ .guide-card h3 {
1004
+ margin: 0 0 8px;
1005
+ font-size: 15px;
1006
+ }
1007
+ .guide-card p {
1008
+ margin: 0;
1009
+ color: var(--muted-strong);
1010
+ line-height: 1.5;
1011
+ }
1012
+ .guide-card code {
1013
+ display: inline-flex;
1014
+ max-width: 100%;
1015
+ margin-top: 10px;
1016
+ border: 1px solid var(--line);
1017
+ border-radius: 7px;
1018
+ padding: 8px 10px;
1019
+ background: var(--surface-soft);
1020
+ color: var(--ink);
1021
+ font: 12px/1.35 var(--mono);
1022
+ white-space: pre-wrap;
1023
+ }
1024
+ .guide-actions {
1025
+ display: flex;
1026
+ flex-wrap: wrap;
1027
+ gap: 10px;
1028
+ margin-top: 14px;
1029
+ }
1030
+ .guide-actions button {
1031
+ min-height: 38px;
1032
+ font-weight: 700;
1033
+ }
1034
+ .guide-actions .primary-action {
1035
+ border-color: var(--black-button);
1036
+ background: var(--black-button);
1037
+ color: #ffffff;
1038
+ }
1039
+ .guide-actions .primary-action:hover {
1040
+ background: #111a22;
1041
+ color: #ffffff;
1042
+ }
1043
+ .guide-actions .verify-action {
1044
+ border-color: var(--purple);
1045
+ background: var(--purple);
1046
+ color: #ffffff;
1047
+ }
1048
+ .guide-actions .verify-action:hover {
1049
+ background: #6d28d9;
1050
+ color: #ffffff;
1051
+ }
1052
+ .task-card {
1053
+ display: grid;
1054
+ grid-template-columns: 34px minmax(0, 1fr);
1055
+ gap: 14px;
1056
+ }
1057
+ .task-number {
1058
+ width: 34px;
1059
+ height: 34px;
1060
+ border-radius: 999px;
1061
+ display: grid;
1062
+ place-items: center;
1063
+ color: var(--orange);
1064
+ background: var(--orange-soft);
1065
+ font-weight: 800;
1066
+ font-size: 13px;
1067
+ }
1068
+ .provider-pill-row {
1069
+ display: flex;
1070
+ flex-wrap: wrap;
1071
+ gap: 8px;
1072
+ margin-top: 12px;
1073
+ }
1074
+ .provider-pill {
1075
+ border: 1px solid var(--line);
1076
+ border-radius: 999px;
1077
+ background: var(--surface-soft);
1078
+ padding: 7px 10px;
1079
+ color: var(--muted-strong);
1080
+ font-size: 12px;
1081
+ font-weight: 700;
1082
+ }
1083
+ .report-grid {
1084
+ display: grid;
1085
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1086
+ gap: 12px;
1087
+ }
1088
+ .report-metric {
1089
+ border: 1px solid var(--line);
1090
+ border-radius: 10px;
1091
+ background: var(--surface);
1092
+ padding: 14px;
1093
+ }
1094
+ .report-metric span {
1095
+ display: block;
1096
+ color: var(--muted);
1097
+ font-size: 12px;
1098
+ margin-bottom: 6px;
1099
+ }
1100
+ .report-metric strong {
1101
+ font-size: 18px;
1102
+ }
1103
+ .panel-pre {
1104
+ margin: 0;
1105
+ border: 1px solid var(--line);
1106
+ border-radius: 10px;
1107
+ background: #fff;
1108
+ padding: 16px;
1109
+ color: #1f2937;
1110
+ white-space: pre-wrap;
1111
+ font: 13px/1.55 var(--mono);
1112
+ }
1113
+ @keyframes panelCardIn {
1114
+ from { opacity: 0; transform: translateY(10px); }
1115
+ to { opacity: 1; transform: translateY(0); }
1116
+ }
665
1117
  @media (max-width: 1240px) {
666
1118
  .topbar { grid-template-columns: minmax(220px, 1fr) auto; }
667
1119
  .project-picker { display: none; }
@@ -669,6 +1121,12 @@ function renderLocalUiHtml() {
669
1121
  .drawer { grid-column: 1 / -1; border-left: 0; border-top: 1px solid var(--line); }
670
1122
  }
671
1123
  @media (max-width: 820px) {
1124
+ body {
1125
+ height: auto;
1126
+ min-height: 100dvh;
1127
+ overflow: auto;
1128
+ display: block;
1129
+ }
672
1130
  .topbar {
673
1131
  height: auto;
674
1132
  min-height: 58px;
@@ -680,9 +1138,10 @@ function renderLocalUiHtml() {
680
1138
  .top-actions {
681
1139
  width: 100%;
682
1140
  display: grid;
683
- grid-template-columns: 1fr 38px 1fr;
1141
+ grid-template-columns: minmax(118px, 1fr) auto 42px minmax(118px, 1fr);
684
1142
  }
685
- .shell { grid-template-columns: 1fr; min-height: auto; }
1143
+ .verify-button { min-width: 118px; }
1144
+ .shell { grid-template-columns: 1fr; min-height: auto; height: auto; overflow: visible; }
686
1145
  .rail { border-right: 0; border-bottom: 1px solid var(--line); max-height: 330px; }
687
1146
  .main { padding: 20px 14px 28px; }
688
1147
  .provider-header { grid-template-columns: 1fr; }
@@ -696,15 +1155,29 @@ function renderLocalUiHtml() {
696
1155
  <body>
697
1156
  <header class="topbar">
698
1157
  <div class="brand-lockup" aria-label="VibeRaven">
699
- <span class="raven-mark" aria-hidden="true"></span>
1158
+ <span class="raven-mark" aria-hidden="true">
1159
+ <img src="/assets/extension-icon.png" alt="" />
1160
+ </span>
700
1161
  <strong>VibeRaven</strong>
701
1162
  <span class="tagline">From AI demo to production</span>
702
1163
  </div>
703
- <div id="project-picker" class="project-picker" aria-label="Current project"><span>Loading project...</span></div>
1164
+ <button id="project-picker" class="project-picker" type="button" aria-label="Current project"><span>Loading project...</span></button>
704
1165
  <div class="top-actions">
705
- <button id="scan" class="status-button" type="button"><span class="dot ok" aria-hidden="true"></span>Local scan</button>
706
- <span id="settings" class="icon-button" title="Settings unavailable locally" aria-label="Settings unavailable locally" aria-disabled="true"></span>
707
- <button id="verify" class="primary verify-button" type="button">Verify</button>
1166
+ <span class="live-status"><span class="dot ok" aria-hidden="true"></span>Connected</span>
1167
+ <span class="top-divider" aria-hidden="true"></span>
1168
+ <button id="settings" class="icon-button" title="Settings" aria-label="Settings" type="button">
1169
+ <svg viewBox="0 0 24 24" aria-hidden="true">
1170
+ <path d="M12 8.25a3.75 3.75 0 1 0 0 7.5 3.75 3.75 0 0 0 0-7.5Z"/>
1171
+ <path d="M19.4 15a1.7 1.7 0 0 0 .34 1.87l.05.05a2 2 0 0 1-2.83 2.83l-.05-.05a1.7 1.7 0 0 0-1.87-.34 1.7 1.7 0 0 0-1.04 1.56V21a2 2 0 0 1-4 0v-.08a1.7 1.7 0 0 0-1.04-1.56 1.7 1.7 0 0 0-1.87.34l-.05.05a2 2 0 0 1-2.83-2.83l.05-.05A1.7 1.7 0 0 0 4.6 15a1.7 1.7 0 0 0-1.56-1.04H3a2 2 0 0 1 0-4h.08A1.7 1.7 0 0 0 4.6 8.9a1.7 1.7 0 0 0-.34-1.87l-.05-.05a2 2 0 0 1 2.83-2.83l.05.05a1.7 1.7 0 0 0 1.87.34A1.7 1.7 0 0 0 10 3.08V3a2 2 0 0 1 4 0v.08a1.7 1.7 0 0 0 1.04 1.56 1.7 1.7 0 0 0 1.87-.34l.05-.05a2 2 0 0 1 2.83 2.83l-.05.05A1.7 1.7 0 0 0 19.4 8.9a1.7 1.7 0 0 0 1.56 1.04H21a2 2 0 0 1 0 4h-.08A1.7 1.7 0 0 0 19.4 15Z"/>
1172
+ </svg>
1173
+ </button>
1174
+ <button id="verify" class="primary verify-button" type="button">
1175
+ <svg viewBox="0 0 24 24" aria-hidden="true">
1176
+ <path d="M12 3.2 19 6v5.3c0 4.4-2.7 7.4-7 9.5-4.3-2.1-7-5.1-7-9.5V6Z"/>
1177
+ <path d="m9.2 12.1 2 2 4-4.3"/>
1178
+ </svg>
1179
+ <span>Verify now</span>
1180
+ </button>
708
1181
  </div>
709
1182
  </header>
710
1183
  <div class="shell">
@@ -712,9 +1185,9 @@ function renderLocalUiHtml() {
712
1185
  <p class="rail-title">Providers</p>
713
1186
  <label class="search-box"><span aria-hidden="true"></span><input id="provider-search" placeholder="Search providers..." aria-label="Search providers" /></label>
714
1187
  <div id="providers" class="provider-list"></div>
715
- <section class="run-local-card" aria-label="Run VibeRaven locally">
716
- <h2>Run VibeRaven locally</h2>
717
- <p>Verify your project from the terminal.</p>
1188
+ <section class="run-local-card" aria-label="Run VibeRaven">
1189
+ <h2>Run VibeRaven</h2>
1190
+ <p>Check your project from the terminal.</p>
718
1191
  <div class="command-pill"><span>$ npx -y viberaven</span></div>
719
1192
  </section>
720
1193
  </aside>
@@ -722,34 +1195,66 @@ function renderLocalUiHtml() {
722
1195
  <section id="provider-header" class="provider-header"></section>
723
1196
  <section id="path"></section>
724
1197
  <section class="next-fix">
725
- <h2>Next fix</h2>
1198
+ <h2>Next action</h2>
726
1199
  <div id="next-fix"></div>
727
1200
  </section>
728
1201
  </main>
729
- <aside class="drawer" aria-label="Agent prompt">
730
- <h2>Agent prompt</h2>
731
- <p>Use this prompt with your coding agent.</p>
732
- <textarea id="prompt" class="prompt-box" readonly></textarea>
733
- <div class="drawer-actions">
734
- <button id="copy" class="primary" type="button">Copy prompt</button>
735
- <button id="tasklist" type="button">Open tasklist</button>
736
- <button id="drawer-verify" type="button">Run verify</button>
737
- </div>
738
- <div class="tip">Tip: re-run Verify after applying a fix to confirm the gate state.</div>
1202
+ <aside class="drawer" aria-label="Agent prompt and quick actions">
1203
+ <section class="prompt-panel" aria-label="Agent prompt">
1204
+ <div class="panel-heading">
1205
+ <div class="panel-title">
1206
+ <span class="panel-glyph" aria-hidden="true">
1207
+ <svg viewBox="0 0 24 24"><path d="M4 6h16"/><path d="M4 12h10"/><path d="M4 18h7"/><path d="M17 12l3 3-3 3"/><path d="M14 15h6"/></svg>
1208
+ </span>
1209
+ <div>
1210
+ <h2>Agent prompt</h2>
1211
+ <p>Run this with your coding agent.</p>
1212
+ </div>
1213
+ </div>
1214
+ <button id="copy" class="copy-small" type="button" aria-label="Copy prompt">
1215
+ <span class="action-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg></span>
1216
+ Copy
1217
+ </button>
1218
+ </div>
1219
+ <div id="prompt-preview" class="prompt-card"></div>
1220
+ <textarea id="prompt" class="prompt-box" readonly></textarea>
1221
+ </section>
1222
+ <section class="quick-panel" aria-label="Quick actions">
1223
+ <div class="panel-title">
1224
+ <span class="action-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="m13 2-8 12h7l-1 8 8-12h-7l1-8Z"/></svg></span>
1225
+ <h2>Quick actions</h2>
1226
+ </div>
1227
+ <div class="drawer-actions">
1228
+ <button id="tasklist" type="button"><span class="action-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M8 6h13"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M3 6h.01"/><path d="M3 12h.01"/><path d="M3 18h.01"/></svg></span><span>Open tasklist</span></button>
1229
+ <button id="drawer-verify" type="button" aria-label="Run verify"><span class="action-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="m10 8 6 4-6 4Z"/></svg></span><span>Run verify</span></button>
1230
+ </div>
1231
+ <div class="tip" hidden></div>
1232
+ </section>
739
1233
  </aside>
740
1234
  </div>
741
1235
  <footer class="status-footer">
742
1236
  <div class="footer-left">
743
1237
  <strong>VibeRaven CLI</strong>
744
- <span id="footer-project">Local project</span>
745
- <span id="footer-command" class="footer-command">npx -y viberaven</span>
746
1238
  </div>
747
1239
  <div class="footer-right">
1240
+ <span>Need help?</span>
748
1241
  <span id="footer-gate" class="gate-status" data-status="not_clear"><span class="dot bad" aria-hidden="true"></span>Gate not clear</span>
749
1242
  </div>
750
1243
  </footer>
1244
+ <div id="action-panel-backdrop" class="action-panel-backdrop" aria-hidden="true">
1245
+ <section class="action-panel" role="dialog" aria-modal="true" aria-labelledby="action-panel-title">
1246
+ <header class="action-panel-header">
1247
+ <div>
1248
+ <h2 id="action-panel-title">Guide</h2>
1249
+ <p id="action-panel-subtitle"></p>
1250
+ </div>
1251
+ <button id="action-panel-close" class="panel-close" type="button" aria-label="Close panel"></button>
1252
+ </header>
1253
+ <div id="action-panel-body" class="action-panel-body"></div>
1254
+ </section>
1255
+ </div>
751
1256
  <script>
752
- const state = { data: null, selectedProviderId: null, providerQuery: '' };
1257
+ const state = { data: null, selectedProviderId: null, selectedPathItemId: null, providerQuery: '', busyAction: '' };
753
1258
  const localToken = new URLSearchParams(window.location.search).get('vr_token') || '';
754
1259
  const labels = {
755
1260
  not_detected: 'Not detected',
@@ -769,6 +1274,183 @@ function renderLocalUiHtml() {
769
1274
  function selectedProvider() {
770
1275
  return state.data.providers.find((provider) => provider.id === state.selectedProviderId) || state.data.providers[0];
771
1276
  }
1277
+ function selectedPathItem(provider) {
1278
+ return provider.launchPath.find((item) => item.id === state.selectedPathItemId)
1279
+ || (provider.nextFix && provider.launchPath.find((item) => item.id === provider.nextFix.launchPathItemId))
1280
+ || provider.launchPath[0];
1281
+ }
1282
+ function providerPrompt(provider, item) {
1283
+ const nextFix = provider.nextFix;
1284
+ if (nextFix && item.id === nextFix.launchPathItemId) return nextFix.prompt;
1285
+ return [
1286
+ 'Work on the ' + provider.label + ' launch path for ' + (state.data.project.name || 'this project') + '.',
1287
+ '',
1288
+ 'Focused check: ' + item.title,
1289
+ '',
1290
+ 'Requirements:',
1291
+ '- Understand why it matters: ' + item.whyItMatters,
1292
+ '- Make the repo-owned change: ' + item.whatToChange,
1293
+ '- Keep changes local/repo-only and do not add secret values.',
1294
+ '- Re-run npx -y viberaven --verify when done.',
1295
+ '',
1296
+ 'Return the exact files changed and the verification result.'
1297
+ ].join('\\n');
1298
+ }
1299
+ function setTip(message, kind) {
1300
+ const tip = document.querySelector('.tip');
1301
+ tip.hidden = !message;
1302
+ tip.textContent = message || '';
1303
+ tip.dataset.kind = kind || 'info';
1304
+ }
1305
+ function escapeHtml(value) {
1306
+ return String(value == null ? '' : value)
1307
+ .replaceAll('&', '&amp;')
1308
+ .replaceAll('<', '&lt;')
1309
+ .replaceAll('>', '&gt;')
1310
+ .replaceAll('"', '&quot;')
1311
+ .replaceAll("'", '&#39;');
1312
+ }
1313
+ function openPanel(title, subtitle, html) {
1314
+ document.getElementById('action-panel-title').textContent = title;
1315
+ document.getElementById('action-panel-subtitle').textContent = subtitle || '';
1316
+ document.getElementById('action-panel-body').innerHTML = html;
1317
+ const backdrop = document.getElementById('action-panel-backdrop');
1318
+ backdrop.classList.add('is-open');
1319
+ backdrop.setAttribute('aria-hidden', 'false');
1320
+ }
1321
+ function closePanel() {
1322
+ const backdrop = document.getElementById('action-panel-backdrop');
1323
+ backdrop.classList.remove('is-open');
1324
+ backdrop.setAttribute('aria-hidden', 'true');
1325
+ }
1326
+ function guideSteps(provider, item) {
1327
+ if (provider.id === 'supabase' && item.id === 'rls-policies') {
1328
+ return [
1329
+ { title: 'Open Supabase policies', body: 'In Supabase, open Authentication and Database policies for the project tables that store user data.', code: 'https://supabase.com/dashboard/project/_/auth/policies', openUrl: 'https://supabase.com/dashboard/project/_/auth/policies' },
1330
+ { title: 'Enable RLS in a migration', body: 'Keep proof in the repo. Add SQL that enables row level security for each public table.', code: 'alter table public.your_table enable row level security;' },
1331
+ { title: 'Scope rows to the owner', body: 'Add policies that only allow authenticated users to read or write their own records. Avoid permissive USING (true) policies.', code: "create policy \\"Users manage own rows\\" on public.your_table\\nfor all to authenticated\\nusing (auth.uid() = user_id)\\nwith check (auth.uid() = user_id);" },
1332
+ { title: 'Run verify', body: 'Refresh the evidence map after the repo change. VibeRaven does not need secret values.', code: 'npx -y viberaven --verify', verify: true }
1333
+ ];
1334
+ }
1335
+ if (provider.id === 'supabase' && item.id === 'production-env') {
1336
+ return [
1337
+ { title: 'Open .env.example', body: 'Add names only. Never paste real Supabase keys into repo files.', code: 'NEXT_PUBLIC_SUPABASE_URL=\\nNEXT_PUBLIC_SUPABASE_ANON_KEY=\\nSERVER_ONLY_SUPABASE_ADMIN_KEY=<server-only, never expose to browser>', file: '.env.example' },
1338
+ { title: 'Document the boundary', body: 'Make it obvious which key can be public and which must stay server-only.', code: 'The server-only Supabase admin key must not be used in client components.' },
1339
+ { title: 'Run verify', body: 'Refresh evidence after the placeholders are committed.', code: 'npx -y viberaven --verify', verify: true }
1340
+ ];
1341
+ }
1342
+ if (provider.id === 'stripe') {
1343
+ return [
1344
+ { title: 'Open env example', body: 'Add payment env names only. Keep server keys out of client code.', code: 'STRIPE_SERVER_KEY=<server-only>\\nSTRIPE_WEBHOOK_SIGNING_SECRET=<server-only>\\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=', file: '.env.example' },
1345
+ { title: 'Copy webhook target', body: 'Use a stable production webhook route and verify signatures before changing subscription state.', code: '/api/stripe/webhook' },
1346
+ { title: 'Run verify', body: 'VibeRaven checks repo evidence and does not need live secret values.', code: 'npx -y viberaven --verify', verify: true }
1347
+ ];
1348
+ }
1349
+ if (provider.id === 'vercel') {
1350
+ return [
1351
+ { title: 'Open env example', body: 'Document the env names your app expects in production without values.', code: '.env.example', file: '.env.example' },
1352
+ { title: 'Confirm deploy command', body: 'Make package scripts and deployment config agree on build and test commands.', code: 'npm run build' },
1353
+ { title: 'Run verify', body: 'Refresh the launch map after repo evidence changes.', code: 'npx -y viberaven --verify', verify: true }
1354
+ ];
1355
+ }
1356
+ return [
1357
+ { title: item.title, body: item.whyItMatters, code: item.whatToChange },
1358
+ { title: 'Keep the proof in the repo', body: 'Provider setup can still require manual work, but VibeRaven needs safe local evidence for the coding agent.', code: item.verifyWith },
1359
+ { title: 'Run verify', body: 'Run verify after the repo-owned fix. No production secrets are required.', code: 'npx -y viberaven --verify', verify: true }
1360
+ ];
1361
+ }
1362
+ function renderGuideHtml(provider, item) {
1363
+ const cards = guideSteps(provider, item).map((step, index) => {
1364
+ const actions = [
1365
+ step.code ? '<button type="button" data-copy="' + escapeHtml(step.code) + '">Copy</button>' : '',
1366
+ step.file ? '<button type="button" data-open-file="' + escapeHtml(step.file) + '">Open file</button>' : '',
1367
+ step.openUrl ? '<button type="button" data-open-url="' + escapeHtml(step.openUrl) + '">Open</button>' : '',
1368
+ step.verify ? '<button class="verify-action" type="button" data-run-verify="true">Run verify</button>' : ''
1369
+ ].filter(Boolean).join('');
1370
+ return '<article class="guide-card task-card" style="--panel-index:' + index + '"><span class="task-number">' + (index + 1) + '</span><div><h3>' + escapeHtml(step.title) + '</h3><p>' + escapeHtml(step.body) + '</p>' + (step.code ? '<code>' + escapeHtml(step.code) + '</code>' : '') + '<div class="guide-actions">' + actions + '</div></div></article>';
1371
+ }).join('');
1372
+ return cards + '<article class="guide-card" style="--panel-index:8"><h3>Use with your coding agent</h3><p>The prompt on the right is scoped to this provider check. Copy it after selecting the row you want to fix.</p><div class="guide-actions"><button class="primary-action" type="button" data-copy-prompt="true">Copy focused prompt</button><button class="verify-action" type="button" data-run-verify="true">Run verify</button></div></article>';
1373
+ }
1374
+ function projectFiles() {
1375
+ return (state.data && state.data.project && state.data.project.files) || [];
1376
+ }
1377
+ function fileByPath(path) {
1378
+ return projectFiles().find((file) => file.path === path);
1379
+ }
1380
+ function bestEnvFile() {
1381
+ return fileByPath('.env.local')?.exists ? '.env.local'
1382
+ : fileByPath('.env')?.exists ? '.env'
1383
+ : '.env.example';
1384
+ }
1385
+ function projectFileCards() {
1386
+ return projectFiles().map((file, index) => {
1387
+ const stateLabel = file.exists ? 'Found' : 'Missing';
1388
+ const action = file.exists
1389
+ ? '<button type="button" data-open-file="' + escapeHtml(file.path) + '">Open</button>'
1390
+ : '<button type="button" data-copy="' + escapeHtml(file.path) + '">Copy path</button>';
1391
+ return '<article class="guide-card" style="--panel-index:' + index + '"><h3>' + escapeHtml(file.label) + '</h3><p>' + escapeHtml(file.path) + ' - ' + stateLabel + '</p><div class="guide-actions">' + action + '</div></article>';
1392
+ }).join('');
1393
+ }
1394
+ function taskSteps(provider, item) {
1395
+ if (provider.id === 'supabase' && item.id === 'production-env') {
1396
+ const envFile = bestEnvFile();
1397
+ return [
1398
+ { title: 'Open ' + envFile, body: 'Put the required variable names where your app already keeps environment configuration.', file: envFile },
1399
+ { title: 'Copy Supabase placeholders', body: 'Paste names only. Add real values in your normal local environment, not into committed source.', code: 'NEXT_PUBLIC_SUPABASE_URL=\\nNEXT_PUBLIC_SUPABASE_ANON_KEY=\\nSERVER_ONLY_SUPABASE_ADMIN_KEY=<server-only, never expose to browser>' },
1400
+ { title: 'Run verify', body: 'Refresh VibeRaven after the file is saved.', verify: true }
1401
+ ];
1402
+ }
1403
+ if (provider.id === 'supabase' && item.id === 'rls-policies') {
1404
+ return [
1405
+ { title: 'Open Supabase policies', body: 'Use Supabase to inspect the affected tables and policy names.', openUrl: 'https://supabase.com/dashboard/project/_/auth/policies' },
1406
+ { title: 'Open migrations folder', body: 'Keep the durable SQL proof in your repo so Codex or Claude Code can edit it.', file: 'supabase/migrations' },
1407
+ { title: 'Copy RLS policy template', body: 'Adapt table and owner column names before saving.', code: "alter table public.your_table enable row level security;\\n\\ncreate policy \\"Users manage own rows\\" on public.your_table\\nfor all to authenticated\\nusing (auth.uid() = user_id)\\nwith check (auth.uid() = user_id);" },
1408
+ { title: 'Run verify', body: 'Refresh VibeRaven after the migration is saved.', verify: true }
1409
+ ];
1410
+ }
1411
+ if (provider.id === 'stripe') {
1412
+ const envFile = bestEnvFile();
1413
+ return [
1414
+ { title: 'Open ' + envFile, body: 'Add payment variable names where your app already expects environment values.', file: envFile },
1415
+ { title: 'Copy payment placeholders', body: 'Keep server keys server-side. Use real values only in your local/provider environment.', code: 'STRIPE_SERVER_KEY=<server-only>\\nSTRIPE_WEBHOOK_SIGNING_SECRET=<server-only>\\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=' },
1416
+ { title: 'Run verify', body: 'Refresh VibeRaven after the repo evidence is saved.', verify: true }
1417
+ ];
1418
+ }
1419
+ return [
1420
+ { title: 'Open the likely file', body: 'Start where this project stores launch evidence.', file: bestEnvFile() },
1421
+ { title: 'Copy the focused change', body: item.whatToChange, code: item.whatToChange },
1422
+ { title: 'Run verify', body: 'Refresh VibeRaven after the change is saved.', verify: true }
1423
+ ];
1424
+ }
1425
+ function renderTasklistHtml() {
1426
+ const provider = selectedProvider();
1427
+ const item = selectedPathItem(provider);
1428
+ const cards = taskSteps(provider, item).map((step, index) => {
1429
+ const actions = [
1430
+ step.code ? '<button type="button" data-copy="' + escapeHtml(step.code) + '">Copy</button>' : '',
1431
+ step.file ? '<button type="button" data-open-file="' + escapeHtml(step.file) + '">Open file</button>' : '',
1432
+ step.openUrl ? '<button type="button" data-open-url="' + escapeHtml(step.openUrl) + '">Open</button>' : '',
1433
+ step.verify ? '<button class="verify-action" type="button" data-run-verify="true">Run verify</button>' : ''
1434
+ ].filter(Boolean).join('');
1435
+ return '<article class="guide-card task-card" style="--panel-index:' + index + '"><span class="task-number">' + (index + 1) + '</span><div><h3>' + escapeHtml(step.title) + '</h3><p>' + escapeHtml(step.body) + '</p>' + (step.code ? '<code>' + escapeHtml(step.code) + '</code>' : '') + '<div class="guide-actions">' + actions + '</div></div></article>';
1436
+ }).join('');
1437
+ const providerPills = state.data.providers.map((candidate) => '<span class="provider-pill">' + escapeHtml(candidate.label) + ': ' + escapeHtml(labels[candidate.state] || candidate.state) + '</span>').join('');
1438
+ return '<article class="guide-card" style="--panel-index:0"><h3>Current focus</h3><p>' + escapeHtml(provider.label + ' - ' + item.title) + '</p><div class="provider-pill-row">' + providerPills + '</div></article>' + cards;
1439
+ }
1440
+ async function openTasklistPanel() {
1441
+ setTip('Loading tasklist...');
1442
+ await fetch('/api/tasklist?vr_token=' + encodeURIComponent(localToken));
1443
+ openPanel('Tasklist', 'Focused steps from repo evidence. No secrets are needed.', renderTasklistHtml());
1444
+ setTip('Tasklist loaded inside VibeRaven.');
1445
+ }
1446
+ function openSettingsPanel() {
1447
+ const project = state.data.project;
1448
+ openPanel('Settings', 'Project files and editor shortcuts.', '<article class="guide-card"><h3>Project folder</h3><p>' + escapeHtml(project.workspacePath) + '</p><div class="guide-actions"><button type="button" data-open-file=".">Open folder</button></div></article>' + projectFileCards() + '<article class="guide-card"><h3>Commands</h3><code>npx -y viberaven\\nnpx -y viberaven --verify\\nnpx -y viberaven --agent-mode</code></article>');
1449
+ }
1450
+ function openProjectPanel() {
1451
+ const project = state.data.project;
1452
+ openPanel('Project', project.name, '<article class="guide-card"><h3>Working folder</h3><p>' + escapeHtml(project.workspacePath) + '</p><div class="guide-actions"><button class="primary-action" type="button" data-open-file=".">Open folder</button><button type="button" data-run-verify="true">Run verify</button></div></article>' + projectFileCards());
1453
+ }
772
1454
  function gateLabel(status) {
773
1455
  return status === 'clear' ? 'Gate clear' : 'Gate not clear';
774
1456
  }
@@ -778,8 +1460,46 @@ function renderLocalUiHtml() {
778
1460
  if (provider.state === 'needs_repo_fix' || provider.state === 'connect_live' || provider.state === 'requires_user_action') return 'needs_repo_fix';
779
1461
  return provider.state;
780
1462
  }
781
- function setPrompt(provider) {
782
- document.getElementById('prompt').value = provider.nextFix ? provider.nextFix.prompt : '';
1463
+ function pathIconHtml(index) {
1464
+ const icons = [
1465
+ '<svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="7" ry="3"/><path d="M5 5v6c0 1.7 3.1 3 7 3s7-1.3 7-3V5"/><path d="M5 11v6c0 1.7 3.1 3 7 3s7-1.3 7-3v-6"/></svg>',
1466
+ '<svg viewBox="0 0 24 24"><path d="m12 3 8 4.5v9L12 21l-8-4.5v-9Z"/><path d="m4 7.5 8 4.5 8-4.5"/><path d="M12 12v9"/></svg>',
1467
+ '<svg viewBox="0 0 24 24"><path d="m14 10-4 4"/><path d="M16.5 7.5a3.5 3.5 0 0 1 0 5l-1.5 1.5a3.5 3.5 0 0 1-5 0"/><path d="M7.5 16.5a3.5 3.5 0 0 1 0-5L9 10a3.5 3.5 0 0 1 5 0"/></svg>',
1468
+ '<svg viewBox="0 0 24 24"><path d="m12 3 8 4-8 4-8-4Z"/><path d="m4 12 8 4 8-4"/><path d="m4 17 8 4 8-4"/></svg>'
1469
+ ];
1470
+ return icons[index % icons.length];
1471
+ }
1472
+ function renderPromptPreview(prompt) {
1473
+ const preview = document.getElementById('prompt-preview');
1474
+ preview.textContent = '';
1475
+ const lines = prompt ? prompt.split('\\n') : [];
1476
+ for (const line of lines) {
1477
+ const trimmed = line.trim();
1478
+ if (!trimmed) {
1479
+ preview.append(document.createTextNode('\\n'));
1480
+ continue;
1481
+ }
1482
+ if (trimmed.startsWith('- ')) {
1483
+ const row = document.createElement('div');
1484
+ row.className = 'prompt-line';
1485
+ const check = document.createElement('span');
1486
+ check.className = 'prompt-check';
1487
+ check.innerHTML = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="m3.5 8.5 2.5 2.5 6-7" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
1488
+ const copy = document.createElement('span');
1489
+ copy.textContent = trimmed.slice(2);
1490
+ row.append(check, copy);
1491
+ preview.append(row);
1492
+ continue;
1493
+ }
1494
+ const block = document.createElement('div');
1495
+ block.textContent = line;
1496
+ preview.append(block);
1497
+ }
1498
+ }
1499
+ function setPrompt(provider, item) {
1500
+ const prompt = item ? providerPrompt(provider, item) : (provider.nextFix ? provider.nextFix.prompt : '');
1501
+ document.getElementById('prompt').value = prompt;
1502
+ renderPromptPreview(prompt);
783
1503
  }
784
1504
  function renderProviders() {
785
1505
  const container = document.getElementById('providers');
@@ -801,6 +1521,8 @@ function renderLocalUiHtml() {
801
1521
  button.type = 'button';
802
1522
  button.addEventListener('click', () => {
803
1523
  state.selectedProviderId = provider.id;
1524
+ state.selectedPathItemId = null;
1525
+ setTip('');
804
1526
  render();
805
1527
  });
806
1528
  const icon = document.createElement('span');
@@ -832,82 +1554,92 @@ function renderLocalUiHtml() {
832
1554
  icon.innerHTML = provider.iconHtml;
833
1555
  const copy = document.createElement('div');
834
1556
  const title = document.createElement('h1');
835
- title.textContent = provider.label + ' launch path';
1557
+ title.textContent = 'Your launch path';
836
1558
  const meta = document.createElement('p');
837
- meta.textContent = 'What must be true before this app can ship.';
1559
+ meta.textContent = "We'll guide you through what matters most.";
838
1560
  copy.append(title, meta);
839
1561
  heading.append(icon, copy);
840
- const guide = document.createElement('span');
841
- guide.className = 'secondary-action';
842
- guide.setAttribute('aria-disabled', 'true');
843
- guide.textContent = 'Provider guide unavailable locally';
844
- header.append(heading, guide);
1562
+ header.append(heading);
845
1563
  }
846
1564
  function renderPath(provider) {
847
1565
  const path = document.getElementById('path');
848
1566
  path.textContent = '';
849
1567
  const list = document.createElement('div');
850
1568
  list.className = 'path-list';
851
- for (const item of provider.launchPath) {
852
- const row = document.createElement('article');
853
- row.className = 'path-row' + (provider.nextFix && provider.nextFix.launchPathItemId === item.id ? ' is-focused' : '');
1569
+ provider.launchPath.forEach((item, index) => {
1570
+ const row = document.createElement('button');
1571
+ const isFocused = selectedPathItem(provider).id === item.id;
1572
+ row.className = 'path-row' + (isFocused ? ' is-focused' : '');
1573
+ row.type = 'button';
1574
+ row.dataset.step = String(index + 1);
1575
+ row.setAttribute('aria-label', 'Open ' + item.title + ' guidance');
1576
+ row.addEventListener('click', () => {
1577
+ state.selectedPathItemId = item.id;
1578
+ setTip('Opened guidance for ' + item.title + '. Copy the prompt or use Open guide for the focused steps.');
1579
+ render();
1580
+ });
1581
+ const icon = document.createElement('span');
1582
+ icon.className = 'path-icon';
1583
+ icon.innerHTML = pathIconHtml(index);
854
1584
  const body = document.createElement('div');
855
1585
  const title = document.createElement('strong');
856
1586
  title.textContent = item.title;
857
1587
  const copy = document.createElement('p');
858
- copy.textContent = item.whyItMatters;
1588
+ copy.textContent = item.whatToChange || item.whyItMatters;
859
1589
  body.append(title, copy);
860
1590
  const status = document.createElement('span');
861
1591
  status.className = 'path-state';
862
1592
  status.dataset.state = item.state;
863
- status.textContent = labels[item.state] || item.state;
864
- row.append(body, status);
1593
+ status.textContent = item.state === 'not_checked' ? 'Pending' : (labels[item.state] || item.state);
1594
+ const arrow = document.createElement('span');
1595
+ arrow.className = 'path-arrow';
1596
+ arrow.innerHTML = '<svg viewBox="0 0 24 24"><path d="m9 18 6-6-6-6"/></svg>';
1597
+ row.append(icon, body, status, arrow);
865
1598
  list.append(row);
866
- }
1599
+ });
867
1600
  path.append(list);
868
1601
  }
869
1602
  function renderNextFix(provider) {
870
1603
  const container = document.getElementById('next-fix');
871
1604
  container.textContent = '';
872
- if (!provider.nextFix) {
1605
+ const item = selectedPathItem(provider);
1606
+ if (!item) {
873
1607
  const empty = document.createElement('div');
874
1608
  empty.className = 'empty';
875
- empty.textContent = 'No repo-code gap is focused for this provider. Run scan or verify to refresh VibeRaven evidence.';
1609
+ empty.textContent = 'No launch-path guidance is available for this provider yet. Run scan or verify to refresh VibeRaven evidence.';
876
1610
  container.append(empty);
877
- setPrompt(provider);
1611
+ setPrompt(provider, item);
878
1612
  return;
879
1613
  }
880
- const grid = document.createElement('div');
881
- grid.className = 'fix-grid';
882
- const sections = [
883
- ['1', 'Why it matters', provider.nextFix.whyItMatters],
884
- ['2', 'What to change', provider.nextFix.whatToChange],
885
- ['3', 'Verify with VibeRaven', provider.nextFix.verifyWith]
886
- ];
887
- for (const [symbol, heading, copy] of sections) {
888
- const section = document.createElement('article');
889
- section.className = 'fix-section';
890
- const marker = document.createElement('span');
891
- marker.className = 'fix-symbol';
892
- marker.textContent = symbol;
893
- const body = document.createElement('div');
894
- const h = document.createElement('h3');
895
- h.textContent = heading;
896
- const p = document.createElement('p');
897
- p.textContent = copy;
898
- body.append(h, p);
899
- section.append(marker, body);
900
- grid.append(section);
901
- }
902
- container.append(grid);
903
- setPrompt(provider);
1614
+ const subtitle = document.createElement('p');
1615
+ subtitle.className = 'next-fix-subtitle';
1616
+ subtitle.textContent = provider.nextFix && item.id === provider.nextFix.launchPathItemId ? 'Start here to move forward.' : 'Manual guidance for the selected launch check.';
1617
+ const row = document.createElement('article');
1618
+ row.className = 'next-action-row';
1619
+ const icon = document.createElement('span');
1620
+ icon.className = 'next-action-icon';
1621
+ icon.innerHTML = pathIconHtml(Math.max(0, provider.launchPath.findIndex((candidate) => candidate.id === item.id)));
1622
+ const body = document.createElement('div');
1623
+ const h = document.createElement('h3');
1624
+ h.textContent = 'Fix ' + item.title.toLowerCase();
1625
+ const p = document.createElement('p');
1626
+ p.textContent = item.whatToChange;
1627
+ body.append(h, p);
1628
+ const guide = document.createElement('button');
1629
+ guide.className = 'open-guide-button';
1630
+ guide.type = 'button';
1631
+ guide.textContent = 'Open guide';
1632
+ guide.addEventListener('click', () => {
1633
+ openPanel(provider.label + ' guide', item.title + ' guidance for this project.', renderGuideHtml(provider, item));
1634
+ setTip('Opened in-app guide for ' + item.title + '.');
1635
+ });
1636
+ row.append(icon, body, guide);
1637
+ container.append(subtitle, row);
1638
+ setPrompt(provider, item);
904
1639
  }
905
1640
  function renderChrome() {
906
1641
  const projectName = state.data.project.name;
907
- const command = state.data.command || 'npx -y viberaven';
908
1642
  document.querySelector('#project-picker span').textContent = projectName;
909
- document.getElementById('footer-project').textContent = projectName;
910
- document.getElementById('footer-command').textContent = command;
911
1643
  const gate = document.getElementById('footer-gate');
912
1644
  gate.dataset.status = state.data.project.gateStatus;
913
1645
  gate.textContent = '';
@@ -944,20 +1676,62 @@ function renderLocalUiHtml() {
944
1676
  }
945
1677
  state.data = payload.state || payload;
946
1678
  state.selectedProviderId = state.data.selectedProviderId;
1679
+ state.selectedPathItemId = null;
1680
+ setTip(path === '/api/verify' ? 'Verify finished. The launch map has been refreshed.' : 'Scan finished. The launch map has been refreshed.');
947
1681
  render();
948
1682
  }
949
1683
  document.getElementById('provider-search').addEventListener('input', (event) => {
950
1684
  state.providerQuery = event.target.value.toLowerCase();
951
1685
  renderProviders();
952
1686
  });
953
- document.getElementById('scan').addEventListener('click', () => postAndRefresh('/api/scan'));
954
- document.getElementById('verify').addEventListener('click', () => postAndRefresh('/api/verify'));
955
- document.getElementById('drawer-verify').addEventListener('click', () => postAndRefresh('/api/verify'));
956
- document.getElementById('tasklist').addEventListener('click', () => window.open('/api/tasklist?vr_token=' + encodeURIComponent(localToken), '_blank', 'noopener,noreferrer'));
1687
+ document.getElementById('project-picker').addEventListener('click', openProjectPanel);
1688
+ document.getElementById('verify').addEventListener('click', () => {
1689
+ setTip('Running verify...');
1690
+ postAndRefresh('/api/verify');
1691
+ });
1692
+ document.getElementById('drawer-verify').addEventListener('click', () => {
1693
+ setTip('Running verify...');
1694
+ postAndRefresh('/api/verify');
1695
+ });
1696
+ document.getElementById('tasklist').addEventListener('click', () => {
1697
+ openTasklistPanel().catch((error) => setTip(error instanceof Error ? error.message : String(error), 'error'));
1698
+ });
1699
+ document.getElementById('settings').addEventListener('click', openSettingsPanel);
1700
+ document.getElementById('action-panel-close').addEventListener('click', closePanel);
1701
+ document.getElementById('action-panel-backdrop').addEventListener('click', (event) => {
1702
+ if (event.target.id === 'action-panel-backdrop') closePanel();
1703
+ });
1704
+ document.getElementById('action-panel-body').addEventListener('click', async (event) => {
1705
+ const target = event.target.closest('button');
1706
+ if (!target) return;
1707
+ if (target.dataset.copy) {
1708
+ await navigator.clipboard.writeText(target.dataset.copy);
1709
+ setTip('Copied guide snippet.');
1710
+ }
1711
+ if (target.dataset.openUrl) {
1712
+ window.open(target.dataset.openUrl, '_blank', 'noopener,noreferrer');
1713
+ setTip('Opened provider page.');
1714
+ }
1715
+ if (target.dataset.openFile) {
1716
+ const base = state.data.project.workspacePath.replaceAll('\\\\', '/').replace(/\\/$/, '');
1717
+ const rel = target.dataset.openFile.replace(/^[/\\\\]+/, '');
1718
+ window.open('vscode://file/' + encodeURI(base + '/' + rel), '_blank', 'noopener,noreferrer');
1719
+ setTip('Opened ' + rel + ' in your editor if the editor protocol is enabled.');
1720
+ }
1721
+ if (target.dataset.copyPrompt) {
1722
+ await navigator.clipboard.writeText(document.getElementById('prompt').value || '');
1723
+ setTip('Copied focused prompt.');
1724
+ }
1725
+ if (target.dataset.runVerify) {
1726
+ setTip('Running verify...');
1727
+ postAndRefresh('/api/verify');
1728
+ }
1729
+ });
957
1730
  document.getElementById('copy').addEventListener('click', async () => {
958
1731
  const prompt = document.getElementById('prompt').value;
959
1732
  if (!prompt) return;
960
1733
  await navigator.clipboard.writeText(prompt);
1734
+ setTip('Prompt copied. Paste it into your coding agent for the selected launch check.');
961
1735
  });
962
1736
  refresh().catch((error) => {
963
1737
  const tip = document.querySelector('.tip');
@@ -969,9 +1743,139 @@ function renderLocalUiHtml() {
969
1743
  }
970
1744
 
971
1745
  // src/version.ts
972
- var VERSION = "1.1.9";
1746
+ var VERSION = "1.1.10";
973
1747
 
974
1748
  // src/publicLocalCli.ts
1749
+ function brandSvg(title, fill, path) {
1750
+ return `<svg viewBox="0 0 24 24" role="img" aria-label="${title} logo" fill="${fill}"><title>${title}</title><path d="${path}"/></svg>`;
1751
+ }
1752
+ var PUBLIC_PROVIDER_CATALOG = [
1753
+ {
1754
+ id: "supabase",
1755
+ label: "Supabase",
1756
+ area: "database",
1757
+ stateWhenDetected: "needs_repo_fix",
1758
+ aliases: ["supabase", "rls", "row level security", "row-level security", "migration", "database"],
1759
+ iconHtml: brandSvg("Supabase", "#3FCF8E", "M11.9 1.036c-.015-.986-1.26-1.41-1.874-.637L.764 12.05C-.33 13.427.65 15.455 2.409 15.455h9.579l.113 7.51c.014.985 1.259 1.408 1.873.636l9.262-11.653c1.093-1.375.113-3.403-1.645-3.403h-9.642z"),
1760
+ rows: [
1761
+ { id: "schema-migrations", title: "Schema and migrations", whyItMatters: "Production data shape needs repo-owned evidence before real users write to it.", whatToChange: "Add or update migration files that prove the production schema and indexes.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["schema", "migration", "database"] },
1762
+ { id: "rls-policies", title: "RLS policies", whyItMatters: "User data must be protected by row ownership rules before launch.", whatToChange: "Add policy SQL that enables RLS and restricts reads and writes to the authenticated owner.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["rls", "policy", "row"] },
1763
+ { id: "auth-callbacks", title: "Auth callbacks", whyItMatters: "Authentication breaks when production callback URLs are missing or local-only.", whatToChange: "Document production site URL and redirect URL evidence without secrets.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["auth", "callback", "redirect"] },
1764
+ { id: "production-env", title: "Production env", whyItMatters: "Production database URLs and keys must be explicit and safely scoped.", whatToChange: "Add safe env placeholders for URL, anon key, and server-only service-role boundaries.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["env", "secret", "url"] }
1765
+ ]
1766
+ },
1767
+ {
1768
+ id: "vercel",
1769
+ label: "Vercel",
1770
+ area: "deployment",
1771
+ stateWhenDetected: "repo_evidence_found",
1772
+ aliases: ["vercel", "deployment", "deploy", "hosting", "preview"],
1773
+ iconHtml: brandSvg("Vercel", "#000000", "m12 1.608 12 20.784H0Z"),
1774
+ rows: [
1775
+ { id: "preview-gate", title: "Preview deployment gate", whyItMatters: "Every production change should have a reproducible preview path.", whatToChange: "Add repo evidence for preview deploys, build command, and required checks.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["preview", "deploy", "ci"] },
1776
+ { id: "production-env", title: "Production env", whyItMatters: "Production runtime variables must be named without leaking values.", whatToChange: "Document required production env names in repo evidence.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["env", "secret"] },
1777
+ { id: "domain-routing", title: "Domain and routing", whyItMatters: "The launch URL needs canonical HTTPS and predictable routes.", whatToChange: "Add evidence for the production domain, redirects, and framework routing assumptions.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["domain", "route"] }
1778
+ ]
1779
+ },
1780
+ {
1781
+ id: "stripe",
1782
+ label: "Stripe",
1783
+ area: "payments",
1784
+ stateWhenDetected: "connect_live",
1785
+ aliases: ["stripe", "payment", "checkout", "subscription", "webhook"],
1786
+ iconHtml: brandSvg("Stripe", "#635BFF", "M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.594-7.305h.003z"),
1787
+ rows: [
1788
+ { id: "checkout-route", title: "Checkout route", whyItMatters: "Money movement needs a trusted server-side path.", whatToChange: "Add checkout or subscription route evidence with stable price env names.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["checkout", "price"] },
1789
+ { id: "webhook-authenticity", title: "Webhook authenticity", whyItMatters: "Payment state must be reconciled from trusted server events.", whatToChange: "Add webhook route evidence with authenticity checks and idempotency.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["webhook", "authenticity"] },
1790
+ { id: "customer-portal", title: "Customer portal", whyItMatters: "Users need a way to manage subscription state without support.", whatToChange: "Add repo evidence for customer portal or account payment routes.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["portal", "customer"] }
1791
+ ]
1792
+ },
1793
+ {
1794
+ id: "github",
1795
+ label: "GitHub",
1796
+ area: "testing",
1797
+ stateWhenDetected: "repo_evidence_found",
1798
+ aliases: ["github", "actions", "workflow", "pull request", "ci"],
1799
+ iconHtml: brandSvg("GitHub", "#181717", "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"),
1800
+ rows: [
1801
+ { id: "required-checks", title: "Required checks", whyItMatters: "Unsafe changes should not merge without automated proof.", whatToChange: "Add workflow evidence for tests, typecheck, build, or public export verification.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["actions", "workflow", "ci"] },
1802
+ { id: "branch-protection", title: "Branch protection", whyItMatters: "CI is advisory unless merge rules require it.", whatToChange: "Document required checks or owner review expectations in repo evidence.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["branch", "review"] }
1803
+ ]
1804
+ },
1805
+ {
1806
+ id: "sentry",
1807
+ label: "Sentry",
1808
+ area: "monitoring",
1809
+ stateWhenDetected: "blocked",
1810
+ aliases: ["sentry", "error", "monitoring", "trace", "dsn"],
1811
+ iconHtml: brandSvg("Sentry", "#362D59", "M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z"),
1812
+ rows: [
1813
+ { id: "runtime-capture", title: "Runtime capture", whyItMatters: "Production errors need a destination before users report them.", whatToChange: "Add Sentry DSN env evidence and runtime initialization proof.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["dsn", "error"] },
1814
+ { id: "release-context", title: "Release context", whyItMatters: "Errors need commit and release metadata to be actionable.", whatToChange: "Add release, environment, or source-map evidence.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["release", "source map"] }
1815
+ ]
1816
+ },
1817
+ {
1818
+ id: "clerk",
1819
+ label: "Clerk",
1820
+ area: "auth",
1821
+ stateWhenDetected: "connect_live",
1822
+ aliases: ["clerk", "auth", "session", "middleware"],
1823
+ iconHtml: brandSvg("Clerk", "#6C47FF", "m21.47 20.829-2.881-2.881a.572.572 0 0 0-.7-.084 6.854 6.854 0 0 1-7.081 0 .576.576 0 0 0-.7.084l-2.881 2.881a.576.576 0 0 0-.103.69.57.57 0 0 0 .166.186 12 12 0 0 0 14.113 0 .58.58 0 0 0 .239-.423.576.576 0 0 0-.172-.453Zm.002-17.668-2.88 2.88a.569.569 0 0 1-.701.084A6.857 6.857 0 0 0 8.724 8.08a6.862 6.862 0 0 0-1.222 3.692 6.86 6.86 0 0 0 .978 3.764.573.573 0 0 1-.083.699l-2.881 2.88a.567.567 0 0 1-.864-.063A11.993 11.993 0 0 1 6.771 2.7a11.99 11.99 0 0 1 14.637-.405.566.566 0 0 1 .232.418.57.57 0 0 1-.168.448Zm-7.118 12.261a3.427 3.427 0 1 0 0-6.854 3.427 3.427 0 0 0 0 6.854Z"),
1824
+ rows: [
1825
+ { id: "session-boundary", title: "Session boundary", whyItMatters: "Protected routes need server-side identity checks.", whatToChange: "Add evidence for session middleware and protected API boundaries.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["session", "middleware"] },
1826
+ { id: "callback-urls", title: "Callback URLs", whyItMatters: "Auth flows fail when production redirects are missing.", whatToChange: "Document production callback and redirect env names.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["callback", "redirect"] }
1827
+ ]
1828
+ },
1829
+ {
1830
+ id: "posthog",
1831
+ label: "PostHog",
1832
+ area: "analytics",
1833
+ stateWhenDetected: "connect_live",
1834
+ aliases: ["posthog", "analytics", "event", "funnel"],
1835
+ iconHtml: brandSvg("PostHog", "#000000", "M9.854 14.5 5 9.647.854 5.5A.5.5 0 0 0 0 5.854V8.44a.5.5 0 0 0 .146.353L5 13.647l.147.146L9.854 18.5l.146.147v-.049c.065.03.134.049.207.049h2.586a.5.5 0 0 0 .353-.854L9.854 14.5zm0-5-4-4a.487.487 0 0 0-.409-.144.515.515 0 0 0-.356.21.493.493 0 0 0-.089.288V8.44a.5.5 0 0 0 .147.353l9 9a.5.5 0 0 0 .853-.354v-2.585a.5.5 0 0 0-.146-.354l-5-5zm1-4a.5.5 0 0 0-.854.354V8.44a.5.5 0 0 0 .147.353l4 4a.5.5 0 0 0 .853-.354V9.854a.5.5 0 0 0-.146-.354l-4-4zm12.647 11.515a3.863 3.863 0 0 1-2.232-1.1l-4.708-4.707a.5.5 0 0 0-.854.354v6.585a.5.5 0 0 0 .5.5H23.5a.5.5 0 0 0 .5-.5v-.6c0-.276-.225-.497-.499-.532zm-5.394.032a.8.8 0 1 1 0-1.6.8.8 0 0 1 0 1.6zM.854 15.5a.5.5 0 0 0-.854.354v2.293a.5.5 0 0 0 .5.5h2.293c.222 0 .39-.135.462-.309a.493.493 0 0 0-.109-.545L.854 15.5zM5 14.647.854 10.5a.5.5 0 0 0-.854.353v2.586a.5.5 0 0 0 .146.353L4.854 18.5l.146.147h2.793a.5.5 0 0 0 .353-.854L5 14.647z"),
1836
+ rows: [
1837
+ { id: "event-taxonomy", title: "Event taxonomy", whyItMatters: "Analytics should answer launch questions, not just count views.", whatToChange: "Add named events for account creation, activation, and conversion points.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["event", "taxonomy"] },
1838
+ { id: "privacy-boundary", title: "Capture boundary", whyItMatters: "Session capture can expose sensitive fields if unchecked.", whatToChange: "Add masking, opt-out, or capture-boundary evidence.", verifyWith: "Run npx -y viberaven --verify.", keywords: ["privacy", "capture"] }
1839
+ ]
1840
+ }
1841
+ ];
1842
+ function haystack(value) {
1843
+ return JSON.stringify(value ?? {}).toLowerCase();
1844
+ }
1845
+ function gapMatchesProvider(gap, provider) {
1846
+ const text = [gap.id, gap.title, gap.detail, gap.category, gap.primaryMapCategory].join(" ").toLowerCase();
1847
+ return provider.aliases.some((alias) => text.includes(alias));
1848
+ }
1849
+ function artifactHasEvidenceForProvider(artifact, provider) {
1850
+ if (!artifact) return false;
1851
+ const text = haystack({
1852
+ stackWiring: artifact.stackWiring,
1853
+ providerRegistry: artifact.providerRegistry,
1854
+ missionGraph: artifact.missionGraph
1855
+ });
1856
+ return provider.aliases.some((alias) => text.includes(alias));
1857
+ }
1858
+ function gapForProvider(artifact, provider) {
1859
+ return artifact?.gaps.find((gap) => gapMatchesProvider(gap, provider));
1860
+ }
1861
+ function pathState(providerState, rowId, focusedRowId) {
1862
+ if (focusedRowId === rowId) {
1863
+ if (providerState === "blocked") return "blocked";
1864
+ if (providerState === "connect_live") return "needs_connect";
1865
+ return "needs_fix";
1866
+ }
1867
+ if (providerState === "repo_evidence_found") return "ready";
1868
+ return "not_checked";
1869
+ }
1870
+ function rowForGap(provider, gap) {
1871
+ if (!gap) return provider.id === "supabase" ? provider.rows[1] ?? provider.rows[0] : provider.rows[0];
1872
+ const text = [gap.id, gap.title, gap.detail, gap.category, gap.primaryMapCategory].join(" ").toLowerCase();
1873
+ const scored = provider.rows.map((row) => ({
1874
+ row,
1875
+ score: row.keywords.reduce((total, keyword) => text.includes(keyword.toLowerCase()) ? total + keyword.length : total, 0)
1876
+ })).sort((left, right) => right.score - left.score);
1877
+ return scored[0]?.score ? scored[0].row : provider.rows[0];
1878
+ }
975
1879
  function send(res, status, body, contentType) {
976
1880
  res.writeHead(status, {
977
1881
  "content-type": contentType,
@@ -1107,6 +2011,62 @@ function tasklist(artifact) {
1107
2011
  return `${lines.join("\n").trim()}
1108
2012
  `;
1109
2013
  }
2014
+ function escapeHtml(value) {
2015
+ return String(value ?? "").replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2016
+ }
2017
+ function reportHtml(artifact) {
2018
+ const gaps = artifact.gaps.length ? artifact.gaps.map(
2019
+ (gap) => `<article class="gap">
2020
+ <div><strong>${escapeHtml(gap.title)}</strong><span>${escapeHtml(gap.severity)}</span></div>
2021
+ <p>${escapeHtml(gap.detail)}</p>
2022
+ <code>${escapeHtml(gap.id)}</code>
2023
+ </article>`
2024
+ ).join("") : '<p class="empty">No local repo-evidence gaps found.</p>';
2025
+ const areas = artifact.missionGraph.areas.map(
2026
+ (area) => `<li><span>${escapeHtml(area.label)}</span><strong>${escapeHtml(area.readinessPercent)}%</strong></li>`
2027
+ ).join("");
2028
+ return `<!doctype html>
2029
+ <html lang="en">
2030
+ <head>
2031
+ <meta charset="utf-8" />
2032
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
2033
+ <title>VibeRaven Local Report</title>
2034
+ <style>
2035
+ body { margin: 0; background: #fbfbfa; color: #111417; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
2036
+ main { max-width: 920px; margin: 0 auto; padding: 42px 24px; }
2037
+ h1 { margin: 0 0 8px; font-size: 32px; line-height: 1.1; }
2038
+ .meta { color: #475467; margin: 0 0 28px; }
2039
+ .summary, .gap, .areas { border: 1px solid #e4e7ec; background: #fff; border-radius: 12px; box-shadow: 0 12px 28px rgba(16, 24, 40, 0.035); }
2040
+ .summary { padding: 22px; margin-bottom: 20px; }
2041
+ .status { display: inline-flex; gap: 8px; align-items: center; padding: 7px 11px; border-radius: 999px; background: #fff2e5; color: #ff7a00; font-weight: 700; }
2042
+ .status[data-clear="true"] { background: #e9f8f1; color: #24b26b; }
2043
+ .areas { list-style: none; padding: 8px 18px; margin: 0 0 22px; }
2044
+ .areas li { display: flex; justify-content: space-between; gap: 18px; padding: 12px 0; border-bottom: 1px solid #eef0f4; }
2045
+ .areas li:last-child { border-bottom: 0; }
2046
+ .gap { padding: 18px; margin: 12px 0; }
2047
+ .gap div { display: flex; justify-content: space-between; gap: 16px; }
2048
+ .gap span { color: #ff7a00; font-size: 13px; font-weight: 700; }
2049
+ .gap p { color: #475467; line-height: 1.45; }
2050
+ code { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 13px; }
2051
+ .empty { border: 1px dashed #d0d5dd; border-radius: 10px; padding: 16px; background: #fff; color: #475467; }
2052
+ </style>
2053
+ </head>
2054
+ <body>
2055
+ <main>
2056
+ <h1>VibeRaven Local Report</h1>
2057
+ <p class="meta">${escapeHtml(artifact.workspacePath)} \xB7 ${escapeHtml(artifact.scannedAt)}</p>
2058
+ <section class="summary">
2059
+ <p class="status" data-clear="${artifact.verificationSummary.status === "clear"}">${escapeHtml(artifact.verificationSummary.status)}</p>
2060
+ <h2>${escapeHtml(artifact.scoreLabel)}</h2>
2061
+ <p>${escapeHtml(artifact.summary)}</p>
2062
+ </section>
2063
+ <ul class="areas">${areas}</ul>
2064
+ <h2>Open gaps</h2>
2065
+ ${gaps}
2066
+ </main>
2067
+ </body>
2068
+ </html>`;
2069
+ }
1110
2070
  async function writeArtifacts(workspacePath, artifact) {
1111
2071
  const out = (0, import_node_path.join)(workspacePath, ".viberaven");
1112
2072
  await (0, import_promises.mkdir)(out, { recursive: true });
@@ -1153,37 +2113,91 @@ async function loadArtifact(workspacePath) {
1153
2113
  return void 0;
1154
2114
  }
1155
2115
  }
2116
+ function projectFiles(workspacePath) {
2117
+ return [
2118
+ { label: "Environment", path: ".env", exists: (0, import_node_fs.existsSync)((0, import_node_path.join)(workspacePath, ".env")) },
2119
+ { label: "Local environment", path: ".env.local", exists: (0, import_node_fs.existsSync)((0, import_node_path.join)(workspacePath, ".env.local")) },
2120
+ { label: "Env example", path: ".env.example", exists: (0, import_node_fs.existsSync)((0, import_node_path.join)(workspacePath, ".env.example")) },
2121
+ { label: "Package manifest", path: "package.json", exists: (0, import_node_fs.existsSync)((0, import_node_path.join)(workspacePath, "package.json")) },
2122
+ { label: "Vercel config", path: "vercel.json", exists: (0, import_node_fs.existsSync)((0, import_node_path.join)(workspacePath, "vercel.json")) },
2123
+ { label: "Supabase migrations", path: "supabase/migrations", exists: (0, import_node_fs.existsSync)((0, import_node_path.join)(workspacePath, "supabase", "migrations")) }
2124
+ ];
2125
+ }
1156
2126
  function localState(cwd, artifact) {
1157
2127
  const firstGap = artifact?.gaps[0];
1158
- const provider = {
1159
- id: "local-readiness",
1160
- label: "Local readiness",
1161
- area: "appFlow",
1162
- state: firstGap ? "needs_repo_fix" : artifact ? "repo_evidence_found" : "not_detected",
1163
- iconHtml: '<span aria-hidden="true">VR</span>',
1164
- launchPath: [
1165
- {
1166
- id: "local-repo-evidence",
1167
- title: firstGap?.title ?? "Local repo evidence",
1168
- whyItMatters: firstGap?.detail ?? "The public CLI checks deterministic local repo evidence.",
1169
- whatToChange: firstGap ? "Fix the listed repo evidence gap, then run viberaven --verify." : "Keep local repo evidence current as the app changes.",
1170
- verifyWith: "Run viberaven --verify.",
1171
- keywords: ["local", "source", "verify"],
1172
- area: "appFlow",
1173
- state: firstGap ? "needs_fix" : artifact ? "ready" : "not_checked"
1174
- }
1175
- ],
1176
- nextFix: firstGap ? {
1177
- gapId: firstGap.id,
1178
- launchPathItemId: "local-repo-evidence",
1179
- launchPathTitle: firstGap.title,
1180
- currentIssue: firstGap.detail,
1181
- whyItMatters: firstGap.detail,
1182
- whatToChange: "Edit repo-controlled evidence for this local readiness gap.",
1183
- verifyWith: "Run viberaven --verify.",
1184
- prompt: `Fix VibeRaven local gap ${firstGap.id}: ${firstGap.detail}`
1185
- } : void 0
1186
- };
2128
+ const providers = PUBLIC_PROVIDER_CATALOG.map((seed) => {
2129
+ const assignedGap = gapForProvider(artifact, seed);
2130
+ const focusedGap = assignedGap ?? (seed.id === "supabase" ? firstGap : void 0);
2131
+ const focusedRow = rowForGap(seed, focusedGap);
2132
+ const hasEvidence = artifactHasEvidenceForProvider(artifact, seed);
2133
+ const providerState = focusedGap ? seed.id === "sentry" ? "blocked" : "needs_repo_fix" : hasEvidence ? "repo_evidence_found" : artifact ? seed.stateWhenDetected : seed.stateWhenDetected;
2134
+ const launchPath = seed.rows.map((row) => ({
2135
+ id: row.id,
2136
+ title: row.title,
2137
+ whyItMatters: row.whyItMatters,
2138
+ whatToChange: row.whatToChange,
2139
+ verifyWith: row.verifyWith,
2140
+ keywords: row.keywords,
2141
+ area: seed.area,
2142
+ state: pathState(providerState, row.id, focusedGap ? focusedRow.id : void 0)
2143
+ }));
2144
+ const defaultNextFix = !artifact && seed.id === "supabase" ? {
2145
+ gapId: "LOCAL-SCAN-001",
2146
+ launchPathItemId: focusedRow.id,
2147
+ launchPathTitle: focusedRow.title,
2148
+ currentIssue: "VibeRaven has not checked this project yet.",
2149
+ whyItMatters: "The launch console needs repo evidence before it can focus the correct provider risk.",
2150
+ whatToChange: "Click Verify so VibeRaven can map package, env, deployment, test, and provider evidence.",
2151
+ verifyWith: "Click Verify or run npx -y viberaven --verify.",
2152
+ prompt: [
2153
+ `Run VibeRaven evidence discovery for ${(0, import_node_path.basename)(cwd) || cwd}.`,
2154
+ "",
2155
+ "Requirements:",
2156
+ "- Inspect package, env example, deployment, test, auth, data, payment, and monitoring evidence.",
2157
+ "- Identify the first repo-owned launch gap.",
2158
+ "- Keep all findings local and do not add secret values.",
2159
+ "- Re-run npx -y viberaven --verify after the first repo-code fix.",
2160
+ "",
2161
+ "Return the first concrete fix to make this app safer to ship."
2162
+ ].join("\n")
2163
+ } : void 0;
2164
+ if (defaultNextFix) {
2165
+ const focused = launchPath.find((item) => item.id === defaultNextFix.launchPathItemId);
2166
+ if (focused) focused.state = "needs_fix";
2167
+ }
2168
+ const provider = {
2169
+ id: seed.id,
2170
+ label: seed.label,
2171
+ area: seed.area,
2172
+ state: providerState,
2173
+ iconHtml: seed.iconHtml,
2174
+ launchPath,
2175
+ nextFix: focusedGap ? {
2176
+ gapId: focusedGap.id,
2177
+ launchPathItemId: focusedRow.id,
2178
+ launchPathTitle: focusedRow.title,
2179
+ currentIssue: focusedGap.detail,
2180
+ whyItMatters: focusedRow.whyItMatters,
2181
+ whatToChange: focusedRow.whatToChange,
2182
+ verifyWith: focusedRow.verifyWith,
2183
+ prompt: [
2184
+ `Fix the ${seed.label} launch path for ${(0, import_node_path.basename)(cwd) || cwd}.`,
2185
+ "",
2186
+ `Current VibeRaven gap: ${focusedGap.id} - ${focusedGap.detail}`,
2187
+ "",
2188
+ "Requirements:",
2189
+ `- Address ${focusedRow.title}.`,
2190
+ `- ${focusedRow.whatToChange}`,
2191
+ "- Keep changes repo-only and do not add secret values.",
2192
+ "- Re-run npx -y viberaven --verify when done.",
2193
+ "",
2194
+ "Return a concise summary of what changed."
2195
+ ].join("\n")
2196
+ } : defaultNextFix
2197
+ };
2198
+ return provider;
2199
+ });
2200
+ const selectedProvider = providers.find((provider) => provider.nextFix) ?? providers.find((provider) => provider.id === "supabase") ?? providers[0];
1187
2201
  return {
1188
2202
  version: 1,
1189
2203
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1193,15 +2207,29 @@ function localState(cwd, artifact) {
1193
2207
  score: artifact?.score,
1194
2208
  scoreLabel: artifact?.scoreLabel,
1195
2209
  summary: artifact?.summary,
1196
- gateStatus: artifact?.verificationSummary.status ?? "not_checked"
2210
+ gateStatus: artifact?.verificationSummary.status ?? "not_checked",
2211
+ files: projectFiles(cwd)
1197
2212
  },
1198
- providers: [provider],
1199
- selectedProviderId: provider.id,
1200
- command: "viberaven --verify"
2213
+ providers,
2214
+ selectedProviderId: selectedProvider.id,
2215
+ command: "npx -y viberaven"
1201
2216
  };
1202
2217
  }
1203
2218
  async function route(req, res, options) {
1204
2219
  const url = new URL(req.url ?? "/", "http://127.0.0.1");
2220
+ if (req.method === "GET" && (url.pathname === "/assets/extension-icon.png" || url.pathname === "/favicon.ico")) {
2221
+ try {
2222
+ const icon = await (0, import_promises.readFile)((0, import_node_path.join)(__dirname, "extension-icon.png"));
2223
+ res.writeHead(200, {
2224
+ "content-type": "image/png",
2225
+ "cache-control": "public, max-age=31536000, immutable"
2226
+ });
2227
+ res.end(icon);
2228
+ } catch {
2229
+ sendJson(res, 404, { error: "Asset not found" });
2230
+ }
2231
+ return;
2232
+ }
1205
2233
  if (req.method === "GET" && url.pathname === "/") {
1206
2234
  send(res, 200, renderLocalUiHtml(), "text/html; charset=utf-8");
1207
2235
  return;
@@ -1227,6 +2255,14 @@ async function route(req, res, options) {
1227
2255
  send(res, 200, tasklist(artifact), "text/plain; charset=utf-8");
1228
2256
  return;
1229
2257
  }
2258
+ if (req.method === "GET" && url.pathname === "/api/report") {
2259
+ let artifact = await loadArtifact(options.cwd);
2260
+ if (!artifact) {
2261
+ artifact = await runLocalScan(options.cwd);
2262
+ }
2263
+ send(res, 200, reportHtml(artifact), "text/html; charset=utf-8");
2264
+ return;
2265
+ }
1230
2266
  if (req.method === "POST" && (url.pathname === "/api/scan" || url.pathname === "/api/verify")) {
1231
2267
  const artifact = await runLocalScan(options.cwd);
1232
2268
  sendJson(res, 200, { ...localState(options.cwd, artifact), exitCode: artifact.verificationSummary.status === "clear" ? 0 : 1 });