@firstpick/pi-package-webui 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/WEBUI_TUI_NATIVE_PARITY.json +26 -0
- package/bin/pi-webui.mjs +371 -4
- package/package.json +10 -3
- package/public/app.js +409 -35
- package/public/index.html +26 -6
- package/public/service-worker.js +1 -1
- package/public/styles.css +225 -7
- package/start-webui.sh +9 -48
- package/tests/mobile-static.test.mjs +47 -6
- package/tests/native-parity.test.mjs +14 -0
- package/webui-rpc-helper.mjs +231 -0
package/public/index.html
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<link rel="manifest" href="/manifest.webmanifest" />
|
|
13
13
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
14
14
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
|
15
|
-
<link rel="stylesheet" href="/styles.css" />
|
|
15
|
+
<link rel="stylesheet" href="/styles.css?v=20" />
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<button id="sidePanelExpandButton" class="side-panel-expand-button" type="button" aria-controls="sidePanel" aria-expanded="false" aria-label="Expand side panel" title="Expand side panel">
|
|
@@ -36,6 +36,15 @@
|
|
|
36
36
|
</div>
|
|
37
37
|
</section>
|
|
38
38
|
|
|
39
|
+
<section id="serverRestartPanel" class="server-restart-panel" aria-live="assertive" hidden>
|
|
40
|
+
<div class="server-restart-card" role="status">
|
|
41
|
+
<div class="server-restart-spinner" aria-hidden="true"></div>
|
|
42
|
+
<span class="server-restart-kicker">Restarting</span>
|
|
43
|
+
<h1>Restarting Pi Web UI server</h1>
|
|
44
|
+
<p id="serverRestartMessage">Waiting for the server to come back…</p>
|
|
45
|
+
</div>
|
|
46
|
+
</section>
|
|
47
|
+
|
|
39
48
|
<main class="layout">
|
|
40
49
|
<section class="chat-panel">
|
|
41
50
|
<header class="terminal-tabs-shell">
|
|
@@ -151,8 +160,11 @@
|
|
|
151
160
|
<div class="side-panel-header">
|
|
152
161
|
<div>
|
|
153
162
|
<span class="side-panel-kicker">Pi Web UI</span>
|
|
154
|
-
<strong
|
|
155
|
-
|
|
163
|
+
<strong class="side-panel-title">
|
|
164
|
+
<span>Control Deck</span>
|
|
165
|
+
<span id="webuiVersionBadge" class="webui-version-badge" aria-label="Pi Web UI version" hidden></span>
|
|
166
|
+
<span id="webuiDevBadge" class="webui-dev-badge" aria-label="Pi Web UI dev server" hidden>DEV</span>
|
|
167
|
+
</strong>
|
|
156
168
|
</div>
|
|
157
169
|
<button id="toggleSidePanelButton" class="side-panel-toggle-button" type="button" aria-controls="sidePanel" aria-expanded="true" aria-label="Collapse side panel" title="Collapse side panel">
|
|
158
170
|
<span class="side-panel-button-icon" aria-hidden="true">
|
|
@@ -217,8 +229,16 @@
|
|
|
217
229
|
<button id="openNetworkButton" type="button">Open to network</button>
|
|
218
230
|
</div>
|
|
219
231
|
<div class="control-field server-control-field">
|
|
220
|
-
<label>Server</label>
|
|
221
|
-
<
|
|
232
|
+
<label for="serverActionSelect">Server</label>
|
|
233
|
+
<div class="server-action-row">
|
|
234
|
+
<select id="serverActionSelect" title="Server action" aria-label="Server action">
|
|
235
|
+
<option value="" selected>Choose action…</option>
|
|
236
|
+
<option value="restart">Restart Server</option>
|
|
237
|
+
<option value="stop">Stop Server</option>
|
|
238
|
+
</select>
|
|
239
|
+
<button id="runServerActionButton" type="button" disabled>Run</button>
|
|
240
|
+
</div>
|
|
241
|
+
<div id="serverActionStatus" class="server-action-status muted" role="status" aria-live="polite"></div>
|
|
222
242
|
</div>
|
|
223
243
|
<div class="control-field notification-control-field">
|
|
224
244
|
<span class="control-label">Notifications</span>
|
|
@@ -344,6 +364,6 @@
|
|
|
344
364
|
</form>
|
|
345
365
|
</dialog>
|
|
346
366
|
|
|
347
|
-
<script type="module" src="/app.js"></script>
|
|
367
|
+
<script type="module" src="/app.js?v=20"></script>
|
|
348
368
|
</body>
|
|
349
369
|
</html>
|
package/public/service-worker.js
CHANGED
package/public/styles.css
CHANGED
|
@@ -375,6 +375,65 @@ body.server-offline .layout {
|
|
|
375
375
|
filter: blur(2px);
|
|
376
376
|
pointer-events: none;
|
|
377
377
|
}
|
|
378
|
+
.server-restart-panel[hidden] {
|
|
379
|
+
display: none !important;
|
|
380
|
+
}
|
|
381
|
+
.server-restart-panel {
|
|
382
|
+
position: fixed;
|
|
383
|
+
inset: 1rem;
|
|
384
|
+
z-index: 62;
|
|
385
|
+
display: grid;
|
|
386
|
+
place-items: center;
|
|
387
|
+
padding: 1rem;
|
|
388
|
+
pointer-events: none;
|
|
389
|
+
}
|
|
390
|
+
.server-restart-card {
|
|
391
|
+
position: relative;
|
|
392
|
+
display: grid;
|
|
393
|
+
justify-items: center;
|
|
394
|
+
width: min(34rem, 100%);
|
|
395
|
+
pointer-events: auto;
|
|
396
|
+
padding: clamp(1.35rem, 4vw, 2.2rem);
|
|
397
|
+
text-align: center;
|
|
398
|
+
border: 1px solid rgba(148, 226, 213, 0.34);
|
|
399
|
+
border-radius: 1.2rem;
|
|
400
|
+
background:
|
|
401
|
+
radial-gradient(circle at 50% 0, rgba(148, 226, 213, 0.18), transparent 18rem),
|
|
402
|
+
linear-gradient(145deg, rgba(var(--ctp-base-rgb), 0.96), rgba(var(--ctp-crust-rgb), 0.98));
|
|
403
|
+
box-shadow: 0 1.2rem 4rem rgba(var(--ctp-crust-rgb), 0.74), 0 0 2rem rgba(148, 226, 213, 0.14), inset 0 1px 0 rgba(255,255,255,0.07);
|
|
404
|
+
}
|
|
405
|
+
.server-restart-spinner {
|
|
406
|
+
width: 2.8rem;
|
|
407
|
+
height: 2.8rem;
|
|
408
|
+
margin-bottom: 0.9rem;
|
|
409
|
+
border: 0.22rem solid rgba(148, 226, 213, 0.18);
|
|
410
|
+
border-top-color: var(--ctp-teal);
|
|
411
|
+
border-radius: 999px;
|
|
412
|
+
animation: server-restart-spin 900ms linear infinite;
|
|
413
|
+
}
|
|
414
|
+
.server-restart-kicker {
|
|
415
|
+
color: var(--ctp-teal);
|
|
416
|
+
font-size: 0.76rem;
|
|
417
|
+
font-weight: 900;
|
|
418
|
+
letter-spacing: 0.12em;
|
|
419
|
+
text-transform: uppercase;
|
|
420
|
+
}
|
|
421
|
+
.server-restart-card h1 {
|
|
422
|
+
margin: 0.35rem 0 0.45rem;
|
|
423
|
+
font-size: clamp(1.35rem, 4vw, 2rem);
|
|
424
|
+
}
|
|
425
|
+
.server-restart-card p {
|
|
426
|
+
margin: 0;
|
|
427
|
+
color: rgba(var(--ctp-subtext-rgb), 0.9);
|
|
428
|
+
}
|
|
429
|
+
body.server-restarting .layout {
|
|
430
|
+
opacity: 0.56;
|
|
431
|
+
filter: blur(1.5px);
|
|
432
|
+
pointer-events: none;
|
|
433
|
+
}
|
|
434
|
+
@keyframes server-restart-spin {
|
|
435
|
+
to { transform: rotate(360deg); }
|
|
436
|
+
}
|
|
378
437
|
.side-panel-expand-button {
|
|
379
438
|
position: fixed;
|
|
380
439
|
top: 1rem;
|
|
@@ -504,13 +563,36 @@ body.side-panel-collapsed .terminal-tabs-shell {
|
|
|
504
563
|
color: var(--ctp-text);
|
|
505
564
|
letter-spacing: 0.03em;
|
|
506
565
|
}
|
|
507
|
-
.side-panel-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
566
|
+
.side-panel-header .side-panel-title {
|
|
567
|
+
display: flex;
|
|
568
|
+
align-items: center;
|
|
569
|
+
gap: 0.46rem;
|
|
570
|
+
flex-wrap: wrap;
|
|
571
|
+
}
|
|
572
|
+
.webui-version-badge,
|
|
573
|
+
.webui-dev-badge {
|
|
574
|
+
display: inline-flex;
|
|
575
|
+
align-items: center;
|
|
576
|
+
min-height: 1.18rem;
|
|
577
|
+
padding: 0.05rem 0.44rem;
|
|
578
|
+
border: 1px solid rgba(180, 190, 254, 0.24);
|
|
579
|
+
border-radius: 999px;
|
|
580
|
+
color: var(--ctp-subtext);
|
|
581
|
+
background: rgba(var(--ctp-surface-rgb), 0.74);
|
|
582
|
+
font-size: 0.68rem;
|
|
583
|
+
font-weight: 800;
|
|
584
|
+
letter-spacing: 0.04em;
|
|
585
|
+
line-height: 1;
|
|
586
|
+
}
|
|
587
|
+
.webui-dev-badge {
|
|
588
|
+
border-color: rgba(249, 226, 175, 0.38);
|
|
589
|
+
color: var(--ctp-yellow);
|
|
590
|
+
background: rgba(249, 226, 175, 0.12);
|
|
591
|
+
box-shadow: 0 0 0.7rem rgba(249, 226, 175, 0.1);
|
|
592
|
+
}
|
|
593
|
+
.webui-version-badge[hidden],
|
|
594
|
+
.webui-dev-badge[hidden] {
|
|
595
|
+
display: none;
|
|
514
596
|
}
|
|
515
597
|
.side-panel-kicker {
|
|
516
598
|
display: block;
|
|
@@ -647,6 +729,35 @@ body.side-panel-collapsed .terminal-tabs-shell {
|
|
|
647
729
|
gap: 0.42rem;
|
|
648
730
|
align-items: center;
|
|
649
731
|
}
|
|
732
|
+
.server-action-row {
|
|
733
|
+
display: grid;
|
|
734
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
735
|
+
gap: 0.42rem;
|
|
736
|
+
align-items: center;
|
|
737
|
+
}
|
|
738
|
+
.server-action-row button {
|
|
739
|
+
width: auto;
|
|
740
|
+
min-width: 4.4rem;
|
|
741
|
+
}
|
|
742
|
+
.server-action-status {
|
|
743
|
+
min-height: 1.05rem;
|
|
744
|
+
color: rgba(var(--ctp-subtext-rgb), 0.82);
|
|
745
|
+
font-size: 0.72rem;
|
|
746
|
+
font-weight: 750;
|
|
747
|
+
line-height: 1.35;
|
|
748
|
+
}
|
|
749
|
+
.server-action-status.warn {
|
|
750
|
+
color: var(--ctp-yellow);
|
|
751
|
+
}
|
|
752
|
+
.server-action-status.error {
|
|
753
|
+
color: var(--ctp-red);
|
|
754
|
+
}
|
|
755
|
+
.server-action-status.success {
|
|
756
|
+
color: var(--ctp-green);
|
|
757
|
+
}
|
|
758
|
+
.server-action-status[hidden] {
|
|
759
|
+
display: none;
|
|
760
|
+
}
|
|
650
761
|
.background-clear-button {
|
|
651
762
|
width: 44px !important;
|
|
652
763
|
min-width: 44px !important;
|
|
@@ -1311,6 +1422,15 @@ body.side-panel-collapsed .terminal-tabs-shell {
|
|
|
1311
1422
|
color: rgba(var(--ctp-text-rgb), 0.78);
|
|
1312
1423
|
background: linear-gradient(90deg, rgba(203, 166, 247, 0.10), rgba(137, 180, 250, 0.05), rgba(148, 226, 213, 0.08));
|
|
1313
1424
|
}
|
|
1425
|
+
.widget-area:has(.release-npm-live-widget .release-npm-output-details[open]),
|
|
1426
|
+
.widget-area:has(.release-aur-live-widget .release-npm-output-details[open]) {
|
|
1427
|
+
flex: 0 0 min(44rem, 68dvh);
|
|
1428
|
+
min-height: 0;
|
|
1429
|
+
overflow: auto;
|
|
1430
|
+
overscroll-behavior: contain;
|
|
1431
|
+
scrollbar-gutter: stable;
|
|
1432
|
+
overflow-anchor: none;
|
|
1433
|
+
}
|
|
1314
1434
|
.statusbar {
|
|
1315
1435
|
position: relative;
|
|
1316
1436
|
flex: 0 0 auto;
|
|
@@ -1806,6 +1926,52 @@ button.footer-meta {
|
|
|
1806
1926
|
text-transform: none;
|
|
1807
1927
|
white-space: nowrap;
|
|
1808
1928
|
}
|
|
1929
|
+
.release-npm-output-details {
|
|
1930
|
+
display: grid;
|
|
1931
|
+
gap: 0.58rem;
|
|
1932
|
+
min-width: 0;
|
|
1933
|
+
}
|
|
1934
|
+
.release-npm-output-summary {
|
|
1935
|
+
display: grid;
|
|
1936
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
1937
|
+
align-items: center;
|
|
1938
|
+
gap: 0.38rem;
|
|
1939
|
+
min-width: 0;
|
|
1940
|
+
color: rgba(var(--ctp-text-rgb), 0.92);
|
|
1941
|
+
cursor: pointer;
|
|
1942
|
+
list-style: none;
|
|
1943
|
+
}
|
|
1944
|
+
.release-npm-output-summary::-webkit-details-marker { display: none; }
|
|
1945
|
+
.release-npm-output-summary:focus-visible {
|
|
1946
|
+
outline: 2px solid rgba(137, 180, 250, 0.72);
|
|
1947
|
+
outline-offset: 0.18rem;
|
|
1948
|
+
border-radius: 0.72rem;
|
|
1949
|
+
}
|
|
1950
|
+
.release-npm-output-summary:hover .release-npm-stream-header,
|
|
1951
|
+
.release-npm-output-summary:focus-visible .release-npm-stream-header {
|
|
1952
|
+
border-color: rgba(137, 180, 250, 0.54);
|
|
1953
|
+
box-shadow: inset 0 0 0 1px rgba(137, 180, 250, 0.08), 0 0 0.8rem rgba(137, 180, 250, 0.08);
|
|
1954
|
+
}
|
|
1955
|
+
.release-npm-output-toggle {
|
|
1956
|
+
display: inline-grid;
|
|
1957
|
+
place-items: center;
|
|
1958
|
+
width: 1rem;
|
|
1959
|
+
height: 1rem;
|
|
1960
|
+
flex: 0 0 auto;
|
|
1961
|
+
color: rgba(var(--ctp-subtext-rgb), 0.78);
|
|
1962
|
+
font-size: 1rem;
|
|
1963
|
+
font-weight: 950;
|
|
1964
|
+
line-height: 1;
|
|
1965
|
+
transition: transform 0.16s ease, color 0.16s ease;
|
|
1966
|
+
}
|
|
1967
|
+
.release-npm-output-details[open] .release-npm-output-toggle {
|
|
1968
|
+
color: var(--ctp-blue);
|
|
1969
|
+
transform: rotate(90deg);
|
|
1970
|
+
}
|
|
1971
|
+
.release-npm-output-summary .release-npm-stream-header {
|
|
1972
|
+
min-width: 0;
|
|
1973
|
+
width: 100%;
|
|
1974
|
+
}
|
|
1809
1975
|
.release-npm-terminal {
|
|
1810
1976
|
max-height: min(34rem, 42vh);
|
|
1811
1977
|
min-height: 5.25rem;
|
|
@@ -1825,6 +1991,11 @@ button.footer-meta {
|
|
|
1825
1991
|
line-height: 1.5;
|
|
1826
1992
|
overscroll-behavior: contain;
|
|
1827
1993
|
}
|
|
1994
|
+
.release-npm-live-widget .release-npm-output-details[open] .release-npm-terminal,
|
|
1995
|
+
.release-aur-live-widget .release-npm-output-details[open] .release-npm-terminal {
|
|
1996
|
+
height: clamp(15rem, 42dvh, 31rem);
|
|
1997
|
+
min-height: 0;
|
|
1998
|
+
}
|
|
1828
1999
|
.release-npm-line {
|
|
1829
2000
|
display: block;
|
|
1830
2001
|
width: max-content;
|
|
@@ -3189,6 +3360,28 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3189
3360
|
text-align: center;
|
|
3190
3361
|
font-weight: 800;
|
|
3191
3362
|
}
|
|
3363
|
+
.extension-dialog.release-dialog .dialog-options button.release-publish-disabled-action {
|
|
3364
|
+
color: rgba(var(--ctp-subtext-rgb), 0.72);
|
|
3365
|
+
border-color: rgba(var(--ctp-overlay-rgb), 0.32);
|
|
3366
|
+
background: linear-gradient(180deg, rgba(var(--ctp-surface-rgb), 0.58), rgba(var(--ctp-crust-rgb), 0.72));
|
|
3367
|
+
}
|
|
3368
|
+
.extension-dialog.release-dialog .dialog-options button.release-target-option {
|
|
3369
|
+
text-align: left;
|
|
3370
|
+
border-color: rgba(137, 180, 250, 0.34);
|
|
3371
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
|
3372
|
+
font-size: 0.78rem;
|
|
3373
|
+
line-height: 1.35;
|
|
3374
|
+
overflow-wrap: anywhere;
|
|
3375
|
+
white-space: normal;
|
|
3376
|
+
}
|
|
3377
|
+
.extension-dialog.release-dialog .dialog-options button.release-target-selected {
|
|
3378
|
+
color: var(--ctp-green);
|
|
3379
|
+
border-color: rgba(166, 227, 161, 0.58);
|
|
3380
|
+
background:
|
|
3381
|
+
linear-gradient(120deg, rgba(166, 227, 161, 0.16), rgba(137, 180, 250, 0.08)),
|
|
3382
|
+
linear-gradient(180deg, rgba(var(--ctp-surface-rgb), 0.80), rgba(var(--ctp-crust-rgb), 0.78));
|
|
3383
|
+
box-shadow: 0 0 1rem rgba(166, 227, 161, 0.14);
|
|
3384
|
+
}
|
|
3192
3385
|
.extension-dialog.release-dialog .dialog-options button.release-cancel-action {
|
|
3193
3386
|
border-color: rgba(249, 226, 175, 0.38);
|
|
3194
3387
|
color: var(--ctp-yellow);
|
|
@@ -3271,6 +3464,31 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3271
3464
|
text-transform: uppercase;
|
|
3272
3465
|
letter-spacing: 0.07em;
|
|
3273
3466
|
}
|
|
3467
|
+
.native-command-body:has(.native-resource-summary) .native-selector-badge {
|
|
3468
|
+
border-color: rgba(255, 159, 67, 0.62);
|
|
3469
|
+
color: #ff9f43;
|
|
3470
|
+
background: rgba(255, 159, 67, 0.10);
|
|
3471
|
+
}
|
|
3472
|
+
.native-selector-badge.enabled,
|
|
3473
|
+
.native-selector-badge.native-selector-badge-enabled,
|
|
3474
|
+
.native-selector-badge[data-badge-state="enabled"],
|
|
3475
|
+
.native-command-body:has(.native-resource-summary) .native-selector-badge.enabled,
|
|
3476
|
+
.native-command-body:has(.native-resource-summary) .native-selector-badge.native-selector-badge-enabled,
|
|
3477
|
+
.native-command-body:has(.native-resource-summary) .native-selector-badge[data-badge-state="enabled"] {
|
|
3478
|
+
border-color: rgba(166, 227, 161, 0.32);
|
|
3479
|
+
color: var(--ctp-green);
|
|
3480
|
+
background: transparent;
|
|
3481
|
+
}
|
|
3482
|
+
.native-selector-badge.disabled,
|
|
3483
|
+
.native-selector-badge.native-selector-badge-disabled,
|
|
3484
|
+
.native-selector-badge[data-badge-state="disabled"],
|
|
3485
|
+
.native-command-body:has(.native-resource-summary) .native-selector-badge.disabled,
|
|
3486
|
+
.native-command-body:has(.native-resource-summary) .native-selector-badge.native-selector-badge-disabled,
|
|
3487
|
+
.native-command-body:has(.native-resource-summary) .native-selector-badge[data-badge-state="disabled"] {
|
|
3488
|
+
border-color: rgba(255, 159, 67, 0.62) !important;
|
|
3489
|
+
color: #ff9f43 !important;
|
|
3490
|
+
background: rgba(255, 159, 67, 0.10);
|
|
3491
|
+
}
|
|
3274
3492
|
.native-selector-detail,
|
|
3275
3493
|
.native-selector-meta,
|
|
3276
3494
|
.native-settings-hint {
|
package/start-webui.sh
CHANGED
|
@@ -183,51 +183,10 @@ connect_host_for_port() {
|
|
|
183
183
|
esac
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
print_manual_url() {
|
|
187
187
|
local url="$1"
|
|
188
|
-
local platform=""
|
|
189
|
-
platform="$(uname -s 2>/dev/null || true)"
|
|
190
188
|
|
|
191
|
-
|
|
192
|
-
MINGW*|MSYS*|CYGWIN*)
|
|
193
|
-
if command -v cmd.exe >/dev/null 2>&1; then
|
|
194
|
-
cmd.exe /c start "" "$url" </dev/null >/dev/null 2>&1 &
|
|
195
|
-
return 0
|
|
196
|
-
fi
|
|
197
|
-
if command -v powershell.exe >/dev/null 2>&1; then
|
|
198
|
-
powershell.exe -NoProfile -Command 'Start-Process -FilePath $args[0]' "$url" </dev/null >/dev/null 2>&1 &
|
|
199
|
-
return 0
|
|
200
|
-
fi
|
|
201
|
-
;;
|
|
202
|
-
Linux*)
|
|
203
|
-
if grep -qi microsoft /proc/version 2>/dev/null; then
|
|
204
|
-
if command -v wslview >/dev/null 2>&1; then
|
|
205
|
-
wslview "$url" </dev/null >/dev/null 2>&1 &
|
|
206
|
-
return 0
|
|
207
|
-
fi
|
|
208
|
-
if command -v cmd.exe >/dev/null 2>&1; then
|
|
209
|
-
cmd.exe /c start "" "$url" </dev/null >/dev/null 2>&1 &
|
|
210
|
-
return 0
|
|
211
|
-
fi
|
|
212
|
-
fi
|
|
213
|
-
;;
|
|
214
|
-
esac
|
|
215
|
-
|
|
216
|
-
if command -v xdg-open >/dev/null 2>&1; then
|
|
217
|
-
xdg-open "$url" </dev/null >/dev/null 2>&1 &
|
|
218
|
-
elif command -v open >/dev/null 2>&1; then
|
|
219
|
-
open "$url" </dev/null >/dev/null 2>&1 &
|
|
220
|
-
elif command -v wslview >/dev/null 2>&1; then
|
|
221
|
-
wslview "$url" </dev/null >/dev/null 2>&1 &
|
|
222
|
-
elif command -v cmd.exe >/dev/null 2>&1; then
|
|
223
|
-
cmd.exe /c start "" "$url" </dev/null >/dev/null 2>&1 &
|
|
224
|
-
elif command -v powershell.exe >/dev/null 2>&1; then
|
|
225
|
-
powershell.exe -NoProfile -Command 'Start-Process -FilePath $args[0]' "$url" </dev/null >/dev/null 2>&1 &
|
|
226
|
-
else
|
|
227
|
-
echo "Could not find a browser opener. Open manually:" >&2
|
|
228
|
-
echo " $url" >&2
|
|
229
|
-
return 1
|
|
230
|
-
fi
|
|
189
|
+
echo "Open manually: $url"
|
|
231
190
|
}
|
|
232
191
|
|
|
233
192
|
http_ok() {
|
|
@@ -456,8 +415,7 @@ main() {
|
|
|
456
415
|
if [[ "$dev_mode" -eq 1 ]]; then
|
|
457
416
|
echo "--dev only affects newly started servers; stop the existing server first to run this checkout."
|
|
458
417
|
fi
|
|
459
|
-
|
|
460
|
-
open_url "$target_url" || true
|
|
418
|
+
print_manual_url "$target_url"
|
|
461
419
|
exit 0
|
|
462
420
|
fi
|
|
463
421
|
|
|
@@ -474,10 +432,12 @@ main() {
|
|
|
474
432
|
if [[ "$dev_mode" -eq 1 ]]; then
|
|
475
433
|
local_webui_bin="$(local_pi_webui_bin)"
|
|
476
434
|
webui_cmd=(node "$local_webui_bin")
|
|
435
|
+
export PI_WEBUI_DEV=1
|
|
477
436
|
echo "Dev mode: using local Pi Web UI server: $local_webui_bin"
|
|
478
437
|
else
|
|
479
438
|
ensure_pi_webui
|
|
480
439
|
webui_cmd=("$PI_WEBUI_COMMAND")
|
|
440
|
+
unset PI_WEBUI_DEV
|
|
481
441
|
fi
|
|
482
442
|
|
|
483
443
|
echo "Starting Pi Web UI in: $cwd"
|
|
@@ -491,7 +451,8 @@ main() {
|
|
|
491
451
|
trap 'cleanup; exit 143' TERM
|
|
492
452
|
|
|
493
453
|
if wait_until_ready "$url" "$SERVER_PID"; then
|
|
494
|
-
|
|
454
|
+
echo "Pi Web UI is ready."
|
|
455
|
+
print_manual_url "$url"
|
|
495
456
|
else
|
|
496
457
|
ready_status="$?"
|
|
497
458
|
if [[ "$ready_status" -eq 2 ]]; then
|
|
@@ -500,8 +461,8 @@ main() {
|
|
|
500
461
|
exit $?
|
|
501
462
|
fi
|
|
502
463
|
|
|
503
|
-
echo "Server did not respond yet; opening
|
|
504
|
-
|
|
464
|
+
echo "Server did not respond yet; not opening a browser automatically." >&2
|
|
465
|
+
print_manual_url "$url"
|
|
505
466
|
fi
|
|
506
467
|
|
|
507
468
|
wait "$SERVER_PID"
|
|
@@ -27,8 +27,10 @@ const companionDependencies = {
|
|
|
27
27
|
"@firstpick/pi-extension-git-footer-status": "^0.2.1",
|
|
28
28
|
"@firstpick/pi-extension-release-aur": "^0.1.3",
|
|
29
29
|
"@firstpick/pi-extension-release-npm": "^0.3.3",
|
|
30
|
+
"@firstpick/pi-extension-setup-skills": "^0.1.5",
|
|
30
31
|
"@firstpick/pi-extension-stats": "^0.2.0",
|
|
31
32
|
"@firstpick/pi-extension-todo-progress": "^0.1.7",
|
|
33
|
+
"@firstpick/pi-extension-tools": "^0.1.4",
|
|
32
34
|
"@firstpick/pi-prompts-git-pr": "^0.1.0",
|
|
33
35
|
"@firstpick/pi-themes-bundle": "^0.1.1",
|
|
34
36
|
};
|
|
@@ -41,11 +43,15 @@ assert.match(html, /<link rel="apple-touch-icon" href="\/apple-touch-icon\.png"
|
|
|
41
43
|
assert.match(html, /id="terminalTabsToggleButton"/, "mobile should expose a compact terminal-tabs toggle");
|
|
42
44
|
assert.match(html, /id="closeAllTabsButton"[\s\S]*?>Close all Tabs<\/button>/, "tab header should expose a top-right close-all tabs action");
|
|
43
45
|
assert.match(html, /id="sidePanelBackdrop"/, "mobile side panel needs an overlay/backdrop close target");
|
|
46
|
+
assert.match(html, /<strong class="side-panel-title">[\s\S]*Control Deck[\s\S]*id="webuiVersionBadge"[\s\S]*id="webuiDevBadge"/, "Control Deck title should expose Web UI version and dev badges");
|
|
47
|
+
assert.doesNotMatch(html, /id="sessionLine"/, "Control Deck title should not show verbose session status metadata");
|
|
44
48
|
assert.match(html, /id="themeSelect"/, "side panel should expose a theme selector");
|
|
45
49
|
assert.match(html, /<label for="themeSelect">Theme<\/label>/, "theme selector should be labeled in side-panel controls");
|
|
46
50
|
assert.match(html, /id="backgroundInput"[^>]*type="file"[^>]*accept="image\/png,image\/jpeg,image\/webp,image\/gif"/, "side panel should expose an image picker for custom backgrounds");
|
|
47
51
|
assert.match(html, /id="backgroundClearButton"[\s\S]*?>×<\/button>/, "side-panel background control should expose an X remove button");
|
|
48
|
-
assert.match(html, /id="
|
|
52
|
+
assert.match(html, /id="serverActionSelect"[\s\S]*<option value="restart">Restart Server<\/option>[\s\S]*<option value="stop">Stop Server<\/option>/, "side panel should expose restart and stop server actions in a dropdown");
|
|
53
|
+
assert.match(html, /id="runServerActionButton"[^>]*disabled[^>]*>Run<\/button>/, "side panel should expose a guarded button for selected server actions");
|
|
54
|
+
assert.match(html, /id="serverActionStatus"[^>]*aria-live="polite"/, "server actions should expose visible restart feedback");
|
|
49
55
|
assert.match(html, /id="agentDoneNotificationsToggle"/, "side panel should expose an agent-done notifications toggle");
|
|
50
56
|
assert.match(html, /id="agentDoneNotificationsStatus"/, "agent-done notifications toggle should expose status text");
|
|
51
57
|
assert.match(html, /id="thinkingVisibilityToggle"/, "side panel should expose a thinking-output visibility toggle");
|
|
@@ -56,6 +62,7 @@ assert.match(html, /id="optionalFeaturesBox"/, "side panel should expose optiona
|
|
|
56
62
|
assert.match(html, /id="codexUsageBox"/, "side panel should expose Codex subscription usage status");
|
|
57
63
|
assert.match(html, /data-side-panel-section="codex-usage"/, "Codex usage should live in a collapsible side-panel section");
|
|
58
64
|
assert.match(html, /id="serverOfflinePanel"/, "PWA/offline shell should expose a backend-offline recovery panel");
|
|
65
|
+
assert.match(html, /id="serverRestartPanel"[\s\S]*id="serverRestartMessage"/, "server restart should expose a loading overlay instead of the generic offline shell");
|
|
59
66
|
assert.match(html, /id="copyServerCommandButton"/, "backend-offline recovery panel should expose a start-command copy button");
|
|
60
67
|
assert.match(html, /id="retryServerConnectionButton"/, "backend-offline recovery panel should expose a retry button");
|
|
61
68
|
assert.match(html, /data-side-panel-section="controls"/, "side panel controls should live in a collapsible section");
|
|
@@ -135,6 +142,10 @@ assert.match(css, /\.message-collapse\[open\] \+ \.tool-result-preview \{[\s\S]*
|
|
|
135
142
|
assert.match(css, /\.run-indicator-pulse \{[\s\S]*?animation:\s*run-indicator-pulse/, "active agent run indicator should have an animated pulse");
|
|
136
143
|
assert.match(css, /\.optional-features-box \{[\s\S]*?display:\s*grid/, "optional features should render as a side-panel feature list");
|
|
137
144
|
assert.match(css, /\.side-panel-section-toggle \{[\s\S]*?justify-content:\s*space-between/, "side panel section toggles should align labels and chevrons");
|
|
145
|
+
assert.match(css, /\.server-restart-panel \{[\s\S]*?z-index:\s*62/, "server restart overlay should render above the offline shell");
|
|
146
|
+
assert.match(css, /@keyframes server-restart-spin/, "server restart overlay should show a loading spinner");
|
|
147
|
+
assert.match(css, /\.webui-version-badge,\n\.webui-dev-badge \{[\s\S]*?border-radius:\s*999px/, "Web UI version and dev indicators should render as compact title badges");
|
|
148
|
+
assert.match(css, /\.webui-dev-badge \{[\s\S]*?color:\s*var\(--ctp-yellow\)/, "Web UI dev indicator should have distinct warning styling");
|
|
138
149
|
assert.match(css, /\.side-panel-section\.collapsed \.side-panel-section-content,\n\.side-panel-section-content\[hidden\] \{\n\s+display:\s*none;/, "collapsed side panel section content should be hidden");
|
|
139
150
|
assert.match(css, /\.side-panel-section:not\(\.collapsed\) \.side-panel-section-chevron/, "expanded side panel sections should rotate the chevron");
|
|
140
151
|
assert.match(css, /\.optional-feature-pill\.enabled/, "optional features should visually distinguish enabled state");
|
|
@@ -145,6 +156,10 @@ assert.match(css, /\.todo-widget-item\.partial \.todo-widget-marker/, "todo-prog
|
|
|
145
156
|
assert.match(css, /\.todo-widget-item\.done \.todo-widget-text[\s\S]*?text-decoration:\s*line-through/, "todo-progress completed items should be visually crossed out");
|
|
146
157
|
assert.match(css, /\.release-npm-widget \{[\s\S]*?border-left:\s*0\.28rem solid/, "release-npm output should stand apart from the page background");
|
|
147
158
|
assert.match(css, /\.release-npm-stream-header \{[\s\S]*?text-transform:\s*uppercase/, "release-npm output should label the output stream clearly");
|
|
159
|
+
assert.match(css, /\.release-npm-output-summary \{[\s\S]*?cursor:\s*pointer/, "release-npm output should expose a local expand/collapse summary");
|
|
160
|
+
assert.match(css, /\.release-npm-output-details\[open\] \.release-npm-output-toggle/, "release-npm expanded output should rotate the summary chevron");
|
|
161
|
+
assert.match(css, /\.widget-area:has\(\.release-npm-live-widget \.release-npm-output-details\[open\]\)[\s\S]*?flex:\s*0 0 min\(44rem, 68dvh\)/, "live release output should reserve a stable widget slot instead of resizing the transcript while streaming");
|
|
162
|
+
assert.match(css, /\.release-npm-live-widget \.release-npm-output-details\[open\] \.release-npm-terminal,[\s\S]*?height:\s*clamp\(15rem, 42dvh, 31rem\)/, "live release terminals should keep a fixed viewport height while output streams");
|
|
148
163
|
assert.match(css, /\.release-npm-terminal \{[\s\S]*?rgba\(3, 4, 10, 0\.98\)/, "release-npm terminal should use a high-contrast stream panel");
|
|
149
164
|
assert.match(css, /\.release-aur-widget \{[\s\S]*?border-color/, "release-aur output should render as a specialized Web UI widget variant");
|
|
150
165
|
assert.match(css, /\.widget-area \.widget:not\(\.todo-widget\):not\(\.release-npm-widget\)/, "mobile widget filtering should keep release workflow output visible");
|
|
@@ -239,8 +254,22 @@ assert.match(app, /Restart Web UI to load themes/, "frontend should explain when
|
|
|
239
254
|
assert.match(app, /themeSelect\.addEventListener\("change"/, "side-panel theme selector should switch themes immediately");
|
|
240
255
|
assert.match(app, /open \? "Close for network" : "Open to network"/, "network button should toggle from open to close action");
|
|
241
256
|
assert.match(app, /api\("\/api\/network\/close", \{ method: "POST"/, "network close action should call the close endpoint");
|
|
242
|
-
assert.match(app, /
|
|
257
|
+
assert.match(app, /webuiVersionBadge: \$\("#webuiVersionBadge"\)/, "frontend should bind the Control Deck version badge");
|
|
258
|
+
assert.match(app, /webuiDevBadge: \$\("#webuiDevBadge"\)/, "frontend should bind the Control Deck dev badge");
|
|
259
|
+
assert.match(app, /function refreshWebuiVersion\(\)[\s\S]*api\("\/api\/health", \{ scoped: false \}\)[\s\S]*setWebuiVersion\(health\.webuiVersion\)[\s\S]*setWebuiDevServer\(isWebuiDevMetadata\(health\)\)/, "frontend should load Web UI version and dev mode from health metadata");
|
|
260
|
+
assert.match(app, /case "webui_connected":[\s\S]*setWebuiVersion\(event\.version\)[\s\S]*setWebuiDevServer\(isWebuiDevMetadata\(event\)\)/, "frontend should refresh Web UI version and dev mode from reconnect events");
|
|
261
|
+
assert.match(server, /const webuiDevServer = isTruthyEnv\(process\.env\.PI_WEBUI_DEV\) \|\| isSourceCheckout\(packageRoot\)/, "server should derive dev mode from PI_WEBUI_DEV or a source checkout");
|
|
262
|
+
assert.match(server, /webuiDev: webuiDevServer,[\s\S]*webuiMode: webuiDevServer \? "dev" : "production"/, "server status should expose Web UI dev mode");
|
|
263
|
+
assert.match(server, /type: "webui_connected",[\s\S]*webuiDev: webuiDevServer,[\s\S]*webuiMode: webuiDevServer \? "dev" : "production"/, "SSE connect event should expose Web UI dev mode");
|
|
264
|
+
assert.match(app, /serverActionSelect\.addEventListener\("change", updateServerActionButton\)/, "Server action dropdown should control the guarded run button");
|
|
265
|
+
assert.match(app, /runServerActionButton\.addEventListener\("click"[\s\S]*runSelectedServerAction/, "Server action run button should execute the selected action");
|
|
266
|
+
assert.match(app, /api\("\/api\/restart", \{ method: "POST", scoped: false \}\)/, "Restart Server action should call the unscoped restart endpoint");
|
|
267
|
+
assert.match(app, /setServerActionStatus\(message, "warn"\);\n\s+setServerRestartOverlay\(true, message\)/, "Restart Server action should show reconnect progress in the side panel and loading overlay");
|
|
268
|
+
assert.match(app, /const showOfflinePanel = backendOffline && !serverRestartInProgress/, "intentional restart should suppress the generic offline shell while reconnecting");
|
|
243
269
|
assert.match(app, /api\("\/api\/shutdown", \{ method: "POST", scoped: false \}\)/, "Stop Server action should call the unscoped shutdown endpoint");
|
|
270
|
+
assert.match(server, /url\.pathname === "\/api\/restart" && req\.method === "POST"/, "server should expose restart endpoint");
|
|
271
|
+
assert.match(server, /PI_WEBUI_RESTORE_TABS: JSON\.stringify\(restorableTabs \|\| \[\]\)/, "server restart should preserve restorable tab metadata");
|
|
272
|
+
assert.match(server, /if \(webuiDevServer\) env\.PI_WEBUI_DEV = "1";/, "server restart should explicitly preserve dev mode");
|
|
244
273
|
assert.match(server, /async function closeNetworkAccess\(\)/, "server should expose a local-only rebind helper for closing network access");
|
|
245
274
|
assert.match(server, /url\.pathname === "\/api\/network\/close" && req\.method === "POST"/, "server should route network close requests");
|
|
246
275
|
assert.match(server, /server\.closeAllConnections\?\.\(\)/, "network rebind should force-close long-lived clients so close-to-localhost can complete");
|
|
@@ -313,7 +342,9 @@ assert.match(app, /function setOptionalControlState\(button, available, unavaila
|
|
|
313
342
|
assert.match(app, /function renderCommands\(\)/, "side-panel commands should be re-renderable from current optional feature state");
|
|
314
343
|
assert.match(app, /function installOptionalFeature\(featureId\)/, "optional features should expose an install action");
|
|
315
344
|
assert.match(app, /api\("\/api\/optional-feature-install"/, "optional feature install action should call the backend installer endpoint");
|
|
316
|
-
assert.match(app, /
|
|
345
|
+
assert.match(app, /id: "tuiSkillsCommand"[\s\S]*?@firstpick\/pi-extension-setup-skills/, "optional features should include the TUI skills command companion");
|
|
346
|
+
assert.match(app, /id: "tuiToolsCommand"[\s\S]*?@firstpick\/pi-extension-tools/, "optional features should include the TUI tools command companion");
|
|
347
|
+
assert.match(app, /function updateOptionalFeatureAvailability\(\)[\s\S]*hasAvailableCommand\("git-staged-msg"\)[\s\S]*hasAvailableCommand\("release-npm"\)[\s\S]*hasAvailableCommand\("release-aur"\)[\s\S]*hasLoadedRpcCommand\("skills"\)[\s\S]*hasAvailableCommand\("todo-progress-status"\)[\s\S]*hasLoadedRpcCommand\("tools"\)/, "optional feature detection should call RPC-visible commands directly and distinguish native resource selectors from TUI companions");
|
|
317
348
|
assert.match(app, /if \(!isOptionalFeatureEnabled\("todoProgressWidget"\)\) return String\(text \|\| ""\)/, "todo progress line stripping should only run when the todo feature is detected and enabled");
|
|
318
349
|
assert.match(app, /const releasePrompt = detectedReleasePrompt && isOptionalFeatureEnabled\(detectedReleasePrompt\.featureId\) \? detectedReleasePrompt : null/, "release confirmation dialogs should use specialized rendering only when their release optional feature is enabled");
|
|
319
350
|
assert.match(app, /case "webui_tab_reloaded":[\s\S]*resetOptionalFeatureAvailability\(\)/, "optional feature state should reset when the RPC tab reloads resources");
|
|
@@ -324,7 +355,10 @@ assert.match(app, /function bindGitWorkflowToActiveTab\(\) \{\n\s+gitWorkflow =
|
|
|
324
355
|
assert.match(app, /function setGitWorkflow\(patch, \{ tabId = activeTabId \} = \{\}\)[\s\S]*if \(tabId === activeTabId\) \{[\s\S]*renderGitWorkflow\(\);/, "guided git workflow should not render inactive terminal workflows globally");
|
|
325
356
|
assert.doesNotMatch(app, /gitWorkflowVisibleTabId|Workflow belongs to/, "guided git workflow should not pin or show workflows outside their owning terminal tab");
|
|
326
357
|
assert.match(app, /function renderReleaseNpmOutputWidget\(\)/, "release-npm live output should use a specialized Web UI renderer");
|
|
358
|
+
assert.match(app, /const releaseNpmOutputExpandedByTab = new Map\(\)/, "release-npm output collapse state should be tracked per browser tab");
|
|
359
|
+
assert.match(app, /function renderReleaseNpmOutputDetails\(key, streamHeader, terminal, controls = null\)[\s\S]*node\.open = releaseNpmOutputExpandedByTab\.get\(stateKey\) !== false[\s\S]*release-npm-output-toggle/, "release-npm output should render as a browser-side details expander");
|
|
327
360
|
assert.match(app, /releaseNpmStreamHeader\("Live output stream", outputLines\.length, \{ live: true \}\)/, "release-npm live output should expose a clear stream heading");
|
|
361
|
+
assert.match(app, /renderReleaseNpmOutputDetails\("release-npm:output", streamHeader, terminal, controls\)/, "release-npm live stream should be wrapped in the local expander");
|
|
328
362
|
assert.match(app, /function renderReleaseAurOutputWidget\(\)/, "release-aur live output should use a specialized Web UI renderer");
|
|
329
363
|
assert.match(app, /releaseNpmActionButton\("Abort", "\/release-abort", "danger"\)/, "release-npm Web UI output should expose an abort action");
|
|
330
364
|
assert.match(app, /releaseNpmActionButton\("Abort", "\/release-aur abort", "danger"\)/, "release-aur Web UI output should expose an abort action");
|
|
@@ -460,7 +494,7 @@ assert.match(app, /async function sendPrompt\(kind = "prompt", explicitMessage\)
|
|
|
460
494
|
assert.match(app, /const rawMessage = usesPromptInput \? elements\.promptInput\.value : explicitMessage/, "direct prompt sends should not read the input textarea");
|
|
461
495
|
assert.match(app, /if \(usesPromptInput\) \{[\s\S]*?if \(targetStillActive\) \{[\s\S]*?elements\.promptInput\.value = "";/, "direct prompt sends should preserve the input textarea draft");
|
|
462
496
|
assert.match(app, /make\("button", "command-item"\)[\s\S]*?sendPrompt\("prompt", `\/\$\{command\.name\}`\)/, "side-panel command clicks should send the slash command directly");
|
|
463
|
-
assert.match(app, /const NATIVE_SELECTOR_COMMANDS = new Set\(\["model", "settings", "theme", "fork", "clone", "resume", "tree", "login", "logout", "scoped-models"\]\)/, "frontend should route native slash commands into selector UIs");
|
|
497
|
+
assert.match(app, /const NATIVE_SELECTOR_COMMANDS = new Set\(\["model", "settings", "theme", "fork", "clone", "resume", "tree", "login", "logout", "scoped-models", "tools", "skills"\]\)/, "frontend should route native slash commands into selector UIs");
|
|
464
498
|
assert.match(app, /async function handleNativeSlashSelectorCommand\(message/, "frontend should intercept exact native slash commands before prompt forwarding");
|
|
465
499
|
assert.match(app, /kind === "prompt" && attachments\.length === 0 && await handleNativeSlashSelectorCommand/, "prompt sending should open native selector dialogs before marking a run active");
|
|
466
500
|
assert.match(app, /function openNativeModelSelector\(\)[\s\S]*?nativeCommandApi\("\/api\/models"\)/, "native /model selector should load models through the active tab API");
|
|
@@ -469,7 +503,7 @@ assert.match(app, /function openNativeForkSelector\(\)[\s\S]*?\/api\/fork-messag
|
|
|
469
503
|
assert.match(app, /function openNativeResumeSelector\(scope = "current"\)[\s\S]*?\/api\/sessions\?scope=\$\{encodeURIComponent\(selectedScope\)\}/, "native /resume selector should list current-cwd or all sessions");
|
|
470
504
|
assert.match(app, /function openNativeTreeSelector\(\)[\s\S]*?\/api\/session-tree[\s\S]*?\/api\/tree-navigate/, "native /tree selector should list tree entries and navigate through the backend helper");
|
|
471
505
|
assert.match(app, /Provider credential entry is intentionally not implemented in the browser yet/, "native /login should remain a safe non-secret guidance dialog");
|
|
472
|
-
assert.match(app, /const HIDDEN_COMMAND_NAMES = new Set\(\["webui-tree-navigate"\]\)/, "internal Web UI helper commands should stay out of command pickers");
|
|
506
|
+
assert.match(app, /const HIDDEN_COMMAND_NAMES = new Set\(\["webui-tree-navigate", "webui-helper"\]\)/, "internal Web UI helper commands should stay out of command pickers");
|
|
473
507
|
assert.match(app, /function shouldSendPromptFromEnter\(event\)/, "prompt keyboard handling should be centralized");
|
|
474
508
|
assert.match(app, /const PROMPT_HISTORY_STORAGE_KEY = "pi-webui-prompt-history"/, "prompt history should be persisted per browser for keyboard recall");
|
|
475
509
|
assert.match(app, /function recallPreviousPromptFromHistory\(\)/, "prompt history should support recalling older prompts from the textarea");
|
|
@@ -566,7 +600,7 @@ assert.equal(manifest.start_url, "/", "PWA manifest should start at the web UI r
|
|
|
566
600
|
assert.ok(manifest.icons?.some((icon) => icon.src === "/apple-touch-icon.png" && icon.sizes === "180x180"), "PWA manifest should include a conventional 180px apple touch icon");
|
|
567
601
|
assert.ok(manifest.icons?.some((icon) => icon.src === "/icon-192.png" && icon.sizes === "192x192"), "PWA manifest should include a 192px icon");
|
|
568
602
|
assert.ok(manifest.icons?.some((icon) => icon.src === "/icon-512.png" && icon.sizes === "512x512"), "PWA manifest should include a 512px icon");
|
|
569
|
-
assert.match(serviceWorker, /const CACHE_NAME = "pi-webui-pwa-
|
|
603
|
+
assert.match(serviceWorker, /const CACHE_NAME = "pi-webui-pwa-v20"/, "PWA service worker should define an app-shell cache");
|
|
570
604
|
assert.match(serviceWorker, /self\.addEventListener\("notificationclick"/, "PWA service worker should focus Web UI when blocked-tab notifications are clicked");
|
|
571
605
|
assert.match(serviceWorker, /event\.notification\.data\?\.url/, "blocked-tab notifications should carry a URL for service-worker click handling");
|
|
572
606
|
assert.match(serviceWorker, /"\/apple-touch-icon\.png"/, "PWA service worker should cache the apple touch icon");
|
|
@@ -683,6 +717,8 @@ assert.match(server, /type: "set_follow_up_mode"/, "server should expose follow-
|
|
|
683
717
|
assert.match(server, /type: "set_auto_compaction"/, "server should expose auto-compaction changes for native /settings");
|
|
684
718
|
assert.match(server, /@firstpick\/pi-themes-bundle/, "server should discover themes from the optional theme package");
|
|
685
719
|
assert.match(server, /const OPTIONAL_FEATURE_PACKAGES = new Map/, "server should whitelist optional feature packages for install actions");
|
|
720
|
+
assert.match(server, /\["tuiSkillsCommand", "@firstpick\/pi-extension-setup-skills"\]/, "server should allow installing the TUI skills optional feature");
|
|
721
|
+
assert.match(server, /\["tuiToolsCommand", "@firstpick\/pi-extension-tools"\]/, "server should allow installing the TUI tools optional feature");
|
|
686
722
|
assert.match(server, /function installOptionalFeaturePackage\(featureId\)/, "server should provide optional feature package installation helper");
|
|
687
723
|
assert.match(server, /url\.pathname === "\/api\/optional-feature-install" && req\.method === "POST"/, "server should expose optional feature install endpoint");
|
|
688
724
|
assert.match(server, /Installing optional Web UI features is only allowed from localhost/, "optional feature install endpoint should be localhost-only");
|
|
@@ -715,6 +751,7 @@ assert.match(readme, /\.\/start-webui\.sh --dev --cwd \/path\/to\/project/, "REA
|
|
|
715
751
|
assert.match(startScript, /--dev\)/, "start-webui.sh should accept a --dev flag");
|
|
716
752
|
assert.match(startScript, /local_pi_webui_bin\(\)/, "start-webui.sh should resolve this checkout's local server entrypoint");
|
|
717
753
|
assert.match(startScript, /webui_cmd=\(node "\$local_webui_bin"\)/, "start-webui.sh --dev should run the local bin with node");
|
|
754
|
+
assert.match(startScript, /export PI_WEBUI_DEV=1/, "start-webui.sh --dev should mark the Web UI server as dev mode");
|
|
718
755
|
assert.match(startScript, /"\$\{webui_cmd\[@\]\}" --cwd "\$cwd" --host "\$host" --port "\$port" "\$\{pass_args\[@\]\}"/, "start-webui.sh should launch through the selected server command without forwarding --dev");
|
|
719
756
|
|
|
720
757
|
assert.match(pkg.scripts?.test || "", /node tests\/mobile-static\.test\.mjs/, "package test script should run the mobile static harness");
|
|
@@ -730,13 +767,17 @@ for (const extensionPath of [
|
|
|
730
767
|
"../pi-extension-git-footer-status/index.ts",
|
|
731
768
|
"../pi-extension-release-aur/index.ts",
|
|
732
769
|
"../pi-extension-release-npm/index.ts",
|
|
770
|
+
"../pi-extension-setup-skills/index.ts",
|
|
733
771
|
"../pi-extension-stats/index.ts",
|
|
734
772
|
"../pi-extension-todo-progress/index.ts",
|
|
773
|
+
"../pi-extension-tools/index.ts",
|
|
735
774
|
"node_modules/@firstpick/pi-extension-git-footer-status/index.ts",
|
|
736
775
|
"node_modules/@firstpick/pi-extension-release-aur/index.ts",
|
|
737
776
|
"node_modules/@firstpick/pi-extension-release-npm/index.ts",
|
|
777
|
+
"node_modules/@firstpick/pi-extension-setup-skills/index.ts",
|
|
738
778
|
"node_modules/@firstpick/pi-extension-stats/index.ts",
|
|
739
779
|
"node_modules/@firstpick/pi-extension-todo-progress/index.ts",
|
|
780
|
+
"node_modules/@firstpick/pi-extension-tools/index.ts",
|
|
740
781
|
]) {
|
|
741
782
|
assert.ok(pkg.pi?.extensions?.includes(extensionPath), `webui Pi manifest should load ${extensionPath} when present`);
|
|
742
783
|
}
|