@firstpick/pi-package-webui 0.4.3 → 0.4.5
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/bin/pi-webui.mjs +28 -8
- package/package.json +6 -4
- package/public/app.js +528 -4
- package/public/index.html +11 -2
- package/public/styles.css +146 -1
- package/tests/fixtures/fake-pi.mjs +0 -0
- package/tests/mobile-static.test.mjs +34 -9
- package/tests/native-parity-harness.test.mjs +1 -1
- package/tests/native-parity.test.mjs +2 -2
- package/start-webui.ps1 +0 -368
- package/start-webui.sh +0 -472
- /package/{WEBUI_TUI_NATIVE_PARITY.json → dev/docs/WEBUI_TUI_NATIVE_PARITY.json} +0 -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?v=
|
|
15
|
+
<link rel="stylesheet" href="/styles.css?v=54" />
|
|
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">
|
|
@@ -289,6 +289,15 @@
|
|
|
289
289
|
data-tooltip="Follow-up usage: 1. Type your message in the textarea. 2. Tap Follow-up to send it as a follow-up. Use for extra context or the next request."
|
|
290
290
|
>Follow-up</button>
|
|
291
291
|
<button id="abortButton" class="composer-abort-button danger" type="button" title="Hold Esc or the Abort button for 3 seconds to abort the active Pi run" aria-label="Hold Esc or the Abort button for 3 seconds to abort the active Pi run" hidden disabled>Abort</button>
|
|
292
|
+
<button
|
|
293
|
+
id="btwButton"
|
|
294
|
+
class="composer-icon-button composer-btw-button"
|
|
295
|
+
type="button"
|
|
296
|
+
title="Ask as a /btw side question"
|
|
297
|
+
aria-label="Ask the current composer text as a /btw side question"
|
|
298
|
+
data-tooltip="/btw side question: Type a question, then tap /btw to ask without adding it to the main conversation."
|
|
299
|
+
hidden
|
|
300
|
+
><svg class="composer-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M5.5 6.5h10.75a2.25 2.25 0 0 1 2.25 2.25v4.5a2.25 2.25 0 0 1-2.25 2.25h-4.4L8 18.25V15.5H5.5a2.25 2.25 0 0 1-2.25-2.25v-4.5A2.25 2.25 0 0 1 5.5 6.5Z" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"/><path d="m15.75 5.25 1.1-2.1 1.1 2.1 2.3 1.1-2.3 1.1-1.1 2.1-1.1-2.1-2.3-1.1 2.3-1.1Z" fill="currentColor"/><path d="M7.25 10.25h5.8M7.25 12.75h4.1" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg></button>
|
|
292
301
|
<button id="sendButton" type="submit" class="primary" title="Send prompt">Send</button>
|
|
293
302
|
</div>
|
|
294
303
|
</form>
|
|
@@ -703,6 +712,6 @@
|
|
|
703
712
|
</form>
|
|
704
713
|
</dialog>
|
|
705
714
|
|
|
706
|
-
<script type="module" src="/app.js?v=
|
|
715
|
+
<script type="module" src="/app.js?v=52"></script>
|
|
707
716
|
</body>
|
|
708
717
|
</html>
|
package/public/styles.css
CHANGED
|
@@ -1701,6 +1701,7 @@ body.side-panel-collapsed .terminal-tabs-shell {
|
|
|
1701
1701
|
}
|
|
1702
1702
|
.widget-area:has(.release-npm-live-widget .release-npm-output-details[open]),
|
|
1703
1703
|
.widget-area:has(.release-aur-live-widget .release-npm-output-details[open]),
|
|
1704
|
+
.widget-area:has(.workflow-live-widget .release-npm-output-details[open]),
|
|
1704
1705
|
.widget-area:has(.app-runner-live-widget .release-npm-output-details[open]) {
|
|
1705
1706
|
flex: 0 0 min(44rem, 68dvh);
|
|
1706
1707
|
min-height: 0;
|
|
@@ -2745,6 +2746,120 @@ button.git-workflow-step:hover:not(:disabled) {
|
|
|
2745
2746
|
linear-gradient(145deg, rgba(var(--ctp-crust-rgb), 0.98), rgba(var(--ctp-mantle-rgb), 0.92));
|
|
2746
2747
|
}
|
|
2747
2748
|
.app-runner-widget .release-npm-kicker { color: var(--ctp-green); }
|
|
2749
|
+
.workflow-widget {
|
|
2750
|
+
border-color: rgba(137, 180, 250, 0.62);
|
|
2751
|
+
border-left-color: rgba(137, 180, 250, 0.88);
|
|
2752
|
+
background:
|
|
2753
|
+
radial-gradient(circle at 4% 0%, rgba(137, 180, 250, 0.20), transparent 16rem),
|
|
2754
|
+
radial-gradient(circle at 96% 12%, rgba(203, 166, 247, 0.14), transparent 18rem),
|
|
2755
|
+
linear-gradient(145deg, rgba(var(--ctp-crust-rgb), 0.98), rgba(var(--ctp-mantle-rgb), 0.92));
|
|
2756
|
+
}
|
|
2757
|
+
.workflow-widget .release-npm-kicker { color: var(--ctp-blue); }
|
|
2758
|
+
.workflow-widget .release-npm-pill.workflow-status.completed { color: var(--ctp-green); }
|
|
2759
|
+
.workflow-widget .release-npm-pill.workflow-status.failed { color: var(--ctp-red); }
|
|
2760
|
+
.workflow-widget .release-npm-pill.workflow-status.cancelled { color: var(--ctp-yellow); }
|
|
2761
|
+
.workflow-widget .release-npm-pill.workflow-status.running,
|
|
2762
|
+
.workflow-widget .release-npm-pill.workflow-status.queued { color: var(--ctp-blue); }
|
|
2763
|
+
.workflow-widget .release-npm-pill.workflow-truncated { color: var(--ctp-peach); }
|
|
2764
|
+
.btw-widget {
|
|
2765
|
+
border-color: rgba(148, 226, 213, 0.62);
|
|
2766
|
+
border-left-color: rgba(148, 226, 213, 0.88);
|
|
2767
|
+
background:
|
|
2768
|
+
radial-gradient(circle at 4% 0%, rgba(148, 226, 213, 0.20), transparent 16rem),
|
|
2769
|
+
radial-gradient(circle at 96% 12%, rgba(137, 180, 250, 0.14), transparent 18rem),
|
|
2770
|
+
linear-gradient(145deg, rgba(var(--ctp-crust-rgb), 0.98), rgba(var(--ctp-mantle-rgb), 0.92));
|
|
2771
|
+
}
|
|
2772
|
+
.btw-widget .release-npm-kicker { color: var(--ctp-teal); }
|
|
2773
|
+
.btw-widget-question {
|
|
2774
|
+
display: grid;
|
|
2775
|
+
gap: 0.28rem;
|
|
2776
|
+
min-width: 0;
|
|
2777
|
+
padding: 0.56rem 0.64rem;
|
|
2778
|
+
border: 1px solid rgba(148, 226, 213, 0.20);
|
|
2779
|
+
border-radius: 0.68rem;
|
|
2780
|
+
background: rgba(var(--ctp-crust-rgb), 0.54);
|
|
2781
|
+
}
|
|
2782
|
+
.btw-widget-question-label {
|
|
2783
|
+
color: var(--ctp-teal);
|
|
2784
|
+
font-size: 0.66rem;
|
|
2785
|
+
font-weight: 950;
|
|
2786
|
+
letter-spacing: 0.12em;
|
|
2787
|
+
text-transform: uppercase;
|
|
2788
|
+
}
|
|
2789
|
+
.btw-widget-question-text {
|
|
2790
|
+
min-width: 0;
|
|
2791
|
+
color: rgba(var(--ctp-text-rgb), 0.94);
|
|
2792
|
+
line-height: 1.4;
|
|
2793
|
+
overflow-wrap: anywhere;
|
|
2794
|
+
}
|
|
2795
|
+
.btw-widget .release-npm-line {
|
|
2796
|
+
width: auto;
|
|
2797
|
+
white-space: pre-wrap;
|
|
2798
|
+
overflow-wrap: anywhere;
|
|
2799
|
+
}
|
|
2800
|
+
.btw-widget-composer {
|
|
2801
|
+
display: grid;
|
|
2802
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
2803
|
+
gap: 0.5rem;
|
|
2804
|
+
align-items: stretch;
|
|
2805
|
+
min-width: 0;
|
|
2806
|
+
}
|
|
2807
|
+
.btw-widget-input {
|
|
2808
|
+
min-height: 2.5rem;
|
|
2809
|
+
max-height: min(16dvh, 7rem);
|
|
2810
|
+
padding: 0.64rem 0.72rem;
|
|
2811
|
+
resize: vertical;
|
|
2812
|
+
border: 1px solid rgba(148, 226, 213, 0.28);
|
|
2813
|
+
border-radius: 0.72rem;
|
|
2814
|
+
color: rgba(var(--ctp-text-rgb), 0.96);
|
|
2815
|
+
background: rgba(var(--ctp-crust-rgb), 0.72);
|
|
2816
|
+
box-shadow: inset 0 1px 0 rgba(var(--ctp-text-rgb), 0.04);
|
|
2817
|
+
font: inherit;
|
|
2818
|
+
line-height: 1.35;
|
|
2819
|
+
}
|
|
2820
|
+
.btw-widget-input:focus {
|
|
2821
|
+
outline: 2px solid rgba(148, 226, 213, 0.62);
|
|
2822
|
+
outline-offset: 0.12rem;
|
|
2823
|
+
border-color: rgba(148, 226, 213, 0.54);
|
|
2824
|
+
}
|
|
2825
|
+
.btw-widget-send {
|
|
2826
|
+
min-width: 5.8rem;
|
|
2827
|
+
color: var(--ctp-teal);
|
|
2828
|
+
border-color: rgba(148, 226, 213, 0.34);
|
|
2829
|
+
}
|
|
2830
|
+
.btw-widget-send:not(:disabled):hover,
|
|
2831
|
+
.btw-widget-send:not(:disabled):focus-visible {
|
|
2832
|
+
color: #11111b;
|
|
2833
|
+
border-color: transparent;
|
|
2834
|
+
background: linear-gradient(120deg, var(--ctp-teal), var(--ctp-blue));
|
|
2835
|
+
}
|
|
2836
|
+
.btw-transfer-action {
|
|
2837
|
+
display: inline-flex;
|
|
2838
|
+
align-items: center;
|
|
2839
|
+
gap: 0.38rem;
|
|
2840
|
+
color: var(--ctp-teal);
|
|
2841
|
+
border-color: rgba(148, 226, 213, 0.34);
|
|
2842
|
+
}
|
|
2843
|
+
.btw-transfer-action:not(:disabled):hover,
|
|
2844
|
+
.btw-transfer-action:not(:disabled):focus-visible {
|
|
2845
|
+
color: #11111b;
|
|
2846
|
+
border-color: transparent;
|
|
2847
|
+
background: linear-gradient(120deg, var(--ctp-teal), var(--ctp-blue));
|
|
2848
|
+
}
|
|
2849
|
+
.btw-transfer-icon {
|
|
2850
|
+
display: block;
|
|
2851
|
+
width: 1.05rem;
|
|
2852
|
+
height: 1.05rem;
|
|
2853
|
+
flex: 0 0 auto;
|
|
2854
|
+
}
|
|
2855
|
+
.btw-widget .release-npm-pill.btw-status.ready { color: var(--ctp-teal); }
|
|
2856
|
+
.btw-widget .release-npm-pill.btw-status.done { color: var(--ctp-green); }
|
|
2857
|
+
.btw-widget .release-npm-pill.btw-status.error { color: var(--ctp-red); }
|
|
2858
|
+
.btw-widget .release-npm-pill.btw-status.aborted { color: var(--ctp-yellow); }
|
|
2859
|
+
.btw-live-widget .release-npm-output-details[open] .release-npm-terminal {
|
|
2860
|
+
height: clamp(9rem, 28dvh, 22rem);
|
|
2861
|
+
min-height: 0;
|
|
2862
|
+
}
|
|
2748
2863
|
.app-runner-status.done { color: var(--ctp-green); }
|
|
2749
2864
|
.app-runner-status.failed,
|
|
2750
2865
|
.app-runner-status.error { color: var(--ctp-red); }
|
|
@@ -2951,6 +3066,7 @@ button.git-workflow-step:hover:not(:disabled) {
|
|
|
2951
3066
|
}
|
|
2952
3067
|
.release-npm-live-widget .release-npm-output-details[open] .release-npm-terminal,
|
|
2953
3068
|
.release-aur-live-widget .release-npm-output-details[open] .release-npm-terminal,
|
|
3069
|
+
.workflow-live-widget .release-npm-output-details[open] .release-npm-terminal,
|
|
2954
3070
|
.app-runner-live-widget .release-npm-output-details[open] .release-npm-terminal {
|
|
2955
3071
|
height: clamp(15rem, 42dvh, 31rem);
|
|
2956
3072
|
min-height: 0;
|
|
@@ -4223,6 +4339,23 @@ button.composer-skill-tag:focus-visible {
|
|
|
4223
4339
|
}
|
|
4224
4340
|
.composer-actions-button { display: none; }
|
|
4225
4341
|
.composer-actions-panel { display: contents; }
|
|
4342
|
+
.composer-btw-button {
|
|
4343
|
+
color: var(--ctp-teal);
|
|
4344
|
+
border-color: rgba(148, 226, 213, 0.34);
|
|
4345
|
+
background:
|
|
4346
|
+
linear-gradient(120deg, rgba(148, 226, 213, 0.14), rgba(137, 180, 250, 0.10)),
|
|
4347
|
+
linear-gradient(180deg, rgba(var(--ctp-surface-rgb), 0.82), rgba(var(--ctp-crust-rgb), 0.86));
|
|
4348
|
+
}
|
|
4349
|
+
.composer-btw-button .composer-icon {
|
|
4350
|
+
width: 1.34rem;
|
|
4351
|
+
height: 1.34rem;
|
|
4352
|
+
}
|
|
4353
|
+
.composer-btw-button:not(:disabled):hover,
|
|
4354
|
+
.composer-btw-button:not(:disabled):focus-visible {
|
|
4355
|
+
color: #11111b;
|
|
4356
|
+
border-color: transparent;
|
|
4357
|
+
background: linear-gradient(120deg, var(--ctp-teal), var(--ctp-blue));
|
|
4358
|
+
}
|
|
4226
4359
|
.composer-abort-button,
|
|
4227
4360
|
.composer-row button.primary {
|
|
4228
4361
|
min-width: 5.8rem;
|
|
@@ -6434,6 +6567,8 @@ button.composer-skill-tag:focus-visible {
|
|
|
6434
6567
|
}
|
|
6435
6568
|
.release-npm-meta,
|
|
6436
6569
|
.release-npm-actions { justify-content: flex-start; }
|
|
6570
|
+
.btw-widget-composer { grid-template-columns: minmax(0, 1fr); }
|
|
6571
|
+
.btw-widget-send { width: 100%; }
|
|
6437
6572
|
.release-npm-stream-header {
|
|
6438
6573
|
padding: 0.3rem 0.42rem;
|
|
6439
6574
|
font-size: 0.66rem;
|
|
@@ -6770,7 +6905,8 @@ button.composer-skill-tag:focus-visible {
|
|
|
6770
6905
|
min-height: 40px;
|
|
6771
6906
|
font-size: 0.86rem;
|
|
6772
6907
|
}
|
|
6773
|
-
body:not(.pi-run-active):not(.mobile-keyboard-open) .composer-row button.primary { grid-column: span
|
|
6908
|
+
body:not(.pi-run-active):not(.mobile-keyboard-open) .composer-row button.primary { grid-column: span 2; }
|
|
6909
|
+
body:not(.pi-run-active):not(.mobile-keyboard-open) .composer-btw-button[hidden] + button.primary { grid-column: span 4; }
|
|
6774
6910
|
body.pi-run-active:not(.mobile-keyboard-open) .composer-abort-button:not([hidden]) {
|
|
6775
6911
|
order: 1;
|
|
6776
6912
|
grid-column: span 2;
|
|
@@ -6778,12 +6914,21 @@ button.composer-skill-tag:focus-visible {
|
|
|
6778
6914
|
body.pi-run-active:not(.mobile-keyboard-open) .composer-row > #steerButton { order: 2; }
|
|
6779
6915
|
body.pi-run-active:not(.mobile-keyboard-open) .composer-row > #followUpButton { order: 3; }
|
|
6780
6916
|
body.pi-run-active:not(.mobile-keyboard-open) .composer-actions-button { order: 4; }
|
|
6917
|
+
body.pi-run-active:not(.mobile-keyboard-open) .composer-btw-button:not([hidden]) {
|
|
6918
|
+
order: 5;
|
|
6919
|
+
grid-column: span 2;
|
|
6920
|
+
}
|
|
6781
6921
|
body.pi-run-active:not(.mobile-keyboard-open) .composer-row button.primary {
|
|
6922
|
+
order: 6;
|
|
6923
|
+
grid-column: span 2;
|
|
6924
|
+
}
|
|
6925
|
+
body.pi-run-active:not(.mobile-keyboard-open) .composer-btw-button[hidden] + button.primary {
|
|
6782
6926
|
order: 5;
|
|
6783
6927
|
grid-column: span 4;
|
|
6784
6928
|
}
|
|
6785
6929
|
.composer-row > #followUpButton,
|
|
6786
6930
|
.composer-row > #steerButton,
|
|
6931
|
+
.composer-row > #btwButton,
|
|
6787
6932
|
.composer-actions-button { grid-column: span 2; }
|
|
6788
6933
|
.composer-actions-panel > #followUpButton,
|
|
6789
6934
|
.composer-actions-panel > #steerButton,
|
|
File without changes
|
|
@@ -12,7 +12,7 @@ const [pkgRaw, html, css, app, server, extension, readme, startScript, manifestR
|
|
|
12
12
|
readFile(join(root, "bin", "pi-webui.mjs"), "utf8"),
|
|
13
13
|
readFile(join(root, "index.ts"), "utf8"),
|
|
14
14
|
readFile(join(root, "README.md"), "utf8"),
|
|
15
|
-
readFile(join(root, "start-webui.sh"), "utf8"),
|
|
15
|
+
readFile(join(root, "dev", "scripts", "start-webui.sh"), "utf8"),
|
|
16
16
|
readFile(join(root, "public", "manifest.webmanifest"), "utf8"),
|
|
17
17
|
readFile(join(root, "public", "service-worker.js"), "utf8"),
|
|
18
18
|
readFile(join(root, "public", "apple-touch-icon.png")),
|
|
@@ -24,6 +24,7 @@ const [pkgRaw, html, css, app, server, extension, readme, startScript, manifestR
|
|
|
24
24
|
const pkg = JSON.parse(pkgRaw);
|
|
25
25
|
const manifest = JSON.parse(manifestRaw);
|
|
26
26
|
const companionDependencies = {
|
|
27
|
+
"@firstpick/pi-extension-btw": "^0.1.0",
|
|
27
28
|
"@firstpick/pi-extension-git-footer-status": "^0.3.3",
|
|
28
29
|
"@firstpick/pi-extension-release-aur": "^0.1.6",
|
|
29
30
|
"@firstpick/pi-extension-release-npm": "^0.4.0",
|
|
@@ -72,6 +73,7 @@ assert.match(html, /id="pathPickerCreateButton"[^>]*>Create directory<\/button>/
|
|
|
72
73
|
assert.match(html, /id="pathPickerSearchInput"[^>]*type="search"[^>]*placeholder="Search current directory…"/, "cwd picker should expose a current-directory search box");
|
|
73
74
|
assert.match(html, /id="pathPickerClearSearchButton"[^>]*hidden[^>]*>Clear<\/button>/, "cwd picker should expose a clear-search action");
|
|
74
75
|
assert.match(html, /id="optionalFeaturesBox"/, "side panel should expose optional feature status and controls");
|
|
76
|
+
assert.doesNotMatch(html, /id="btwOverlayDialog"/, "/btw should not use a blocking modal overlay");
|
|
75
77
|
assert.match(html, /id="codexUsageBox"/, "side panel should expose Codex subscription usage status");
|
|
76
78
|
assert.match(html, /data-side-panel-section="codex-usage"/, "Codex usage should live in a collapsible side-panel section");
|
|
77
79
|
assert.match(html, /data-side-panel-section="queue"[\s\S]*id="createPromptListButton"[\s\S]*>Create prompt list<\/button>/, "Queue section should expose prompt-list creation");
|
|
@@ -125,7 +127,9 @@ assert.match(app, /attachmentTextDialog\?\.addEventListener\("keydown"[\s\S]*eve
|
|
|
125
127
|
assert.match(css, /\.attachment-text-dialog[\s\S]*\.attachment-text-editor/, "text attachment editor should have dedicated dialog styling");
|
|
126
128
|
assert.match(html, /id="composerActionsButton"/, "mobile composer should expose a compact actions trigger");
|
|
127
129
|
assert.match(html, /id="composerActionsPanel"/, "secondary composer controls should live in a mobile actions panel");
|
|
128
|
-
assert.match(html, /<div class="composer-row">[\s\S]*id="abortButton"[\s\S]*id="sendButton"/, "Abort should live in the bottom composer row beside Send");
|
|
130
|
+
assert.match(html, /<div class="composer-row">[\s\S]*id="abortButton"[\s\S]*id="btwButton"[\s\S]*id="sendButton"/, "Abort and /btw should live in the bottom composer row beside Send");
|
|
131
|
+
assert.match(html, /id="btwButton"[\s\S]*class="composer-icon-button composer-btw-button"[\s\S]*?<svg class="composer-icon"/, "composer should expose an icon-only /btw side-question button");
|
|
132
|
+
assert.doesNotMatch(html, /id="btwButton"[\s\S]*?<span>\/btw<\/span>[\s\S]*?id="sendButton"/, "/btw composer button should not show a text label");
|
|
129
133
|
assert.match(html, /id="abortButton"[^>]*Hold Esc or the Abort button for 3 seconds/, "Abort should advertise guarded Esc and long-press affordances");
|
|
130
134
|
assert.doesNotMatch(html, /class="side-panel-controls"[\s\S]*id="abortButton"/, "Abort should not be buried in the side-panel controls");
|
|
131
135
|
assert.match(html, /id="publishButton"[\s\S]*?aria-controls="publishMenu"/, "composer should expose a Publish workflow menu button");
|
|
@@ -188,7 +192,7 @@ assert.match(css, /\.composer-row button \{\n\s+width:\s*100%;\n\s+min-height:\s
|
|
|
188
192
|
assert.match(css, /\.composer-abort-button,\n\.composer-row button\.primary \{[\s\S]*?min-width:/, "Abort and Send should share stable bottom-row sizing");
|
|
189
193
|
assert.match(css, /\.composer-abort-button\.long-pressing::after[\s\S]*?animation:\s*abort-long-press-fill var\(--abort-long-press-duration, 3000ms\) linear forwards/, "Abort should expose a visible 3-second long-press progress affordance");
|
|
190
194
|
assert.match(css, /body\.pi-run-active:not\(\.mobile-keyboard-open\) \.composer-abort-button:not\(\[hidden\]\) \{\n\s+order:\s*1;\n\s+grid-column:\s*span 2;/, "active mobile runs should move Abort to the top row");
|
|
191
|
-
assert.match(css, /body\.pi-run-active:not\(\.mobile-keyboard-open\) \.composer-actions-button \{ order:\s*4; \}[\s\S]*?body\.pi-run-active:not\(\.mobile-keyboard-open\) \.composer-row button\.primary \{\n\s+order:\s*
|
|
195
|
+
assert.match(css, /body\.pi-run-active:not\(\.mobile-keyboard-open\) \.composer-actions-button \{ order:\s*4; \}[\s\S]*?body\.pi-run-active:not\(\.mobile-keyboard-open\) \.composer-btw-button:not\(\[hidden\]\) \{\n\s+order:\s*5;\n\s+grid-column:\s*span 2;[\s\S]*?body\.pi-run-active:not\(\.mobile-keyboard-open\) \.composer-row button\.primary \{\n\s+order:\s*6;\n\s+grid-column:\s*span 2;/, "active mobile runs should keep Actions, /btw, and Send on the bottom row");
|
|
192
196
|
assert.match(css, /#promptInput \{[\s\S]*?min-height:\s*calc\(1\.5em \+ 1\.8rem\)/, "prompt input should default to a compact single-line height");
|
|
193
197
|
assert.match(css, /#promptInput \{[\s\S]*?overflow-y:\s*hidden/, "prompt input should be JS-resized instead of showing a scrollbar by default");
|
|
194
198
|
assert.match(css, /\.composer-context-tags \{[\s\S]*?top:\s*-0\.48rem;[\s\S]*?left:\s*0\.75rem;/, "busy prompt behavior and skill tags should sit at the top-left of the input frame");
|
|
@@ -224,6 +228,7 @@ assert.match(css, /\.message\.toolResult \.message-collapse\[open\] > \.message-
|
|
|
224
228
|
assert.match(css, /\.tool-output-details\[open\] > \.tool-output-code \{[\s\S]*?max-height:\s*min\(34rem, 52dvh\);[\s\S]*?overflow:\s*auto/, "expanded live tool output should get an internal scrollbar");
|
|
225
229
|
assert.match(css, /\.run-indicator-pulse \{[\s\S]*?animation:\s*run-indicator-pulse/, "active agent run indicator should have an animated pulse");
|
|
226
230
|
assert.match(css, /\.optional-features-box \{[\s\S]*?display:\s*grid/, "optional features should render as a side-panel feature list");
|
|
231
|
+
assert.match(css, /\.btw-widget \{[\s\S]*?\.btw-widget-composer \{[\s\S]*?\.btw-transfer-action \{[\s\S]*?\.btw-live-widget \.release-npm-output-details\[open\] \.release-npm-terminal \{[\s\S]*?height:\s*clamp/, "/btw should render as a non-blocking release-style output widget with its own input and transfer action");
|
|
227
232
|
assert.match(css, /\.prompt-list-controls \{[\s\S]*?display:\s*grid/, "Queue prompt-list controls should render as a side-panel control group");
|
|
228
233
|
assert.match(css, /\.prompt-list-dialog \{[\s\S]*?width:\s*min\(58rem/, "prompt-list editor dialog should have a wider prompt-friendly layout");
|
|
229
234
|
assert.match(css, /\.prompt-list-editor-rows \{[\s\S]*?max-height:/, "prompt-list dialog should scroll long follow-up lists inside the editor");
|
|
@@ -322,7 +327,7 @@ assert.match(css, /\.terminal-tabs[\s\S]*?position:\s*absolute/, "expanded mobil
|
|
|
322
327
|
assert.match(css, /body\.mobile-keyboard-open \.terminal-tabs-shell,[\s\S]*?body\.mobile-keyboard-open \.widget-area,[\s\S]*?body\.mobile-keyboard-open \.statusbar/, "mobile keyboard mode should hide header, widgets, and footer");
|
|
323
328
|
assert.match(css, /body\.mobile-keyboard-open \.composer-actions-button,[\s\S]*?body\.mobile-keyboard-open \.composer-actions-panel/, "mobile keyboard mode should hide the secondary actions sheet while keeping active-run controls available");
|
|
324
329
|
assert.match(css, /\.server-offline-panel/, "PWA/offline shell should style a backend-offline recovery panel");
|
|
325
|
-
assert.match(css, /body:not\(\.pi-run-active\):not\(\.mobile-keyboard-open\) \.composer-row button\.primary \{ grid-column: span 4; \}/, "idle mobile composer should keep Actions and Send on one compact row");
|
|
330
|
+
assert.match(css, /body:not\(\.pi-run-active\):not\(\.mobile-keyboard-open\) \.composer-row button\.primary \{ grid-column: span 2; \}[\s\S]*?body:not\(\.pi-run-active\):not\(\.mobile-keyboard-open\) \.composer-btw-button\[hidden\] \+ button\.primary \{ grid-column: span 4; \}/, "idle mobile composer should keep Actions, /btw, and Send on one compact row with a hidden-button fallback");
|
|
326
331
|
assert.match(css, /button\[hidden\] \{ display: none !important; \}/, "hidden bottom-row controls should not occupy layout space");
|
|
327
332
|
assert.match(css, /\.statusbar-tui-footer \{[\s\S]*?gap:\s*0/, "default TUI-like footer should reduce statusbar chrome around the compact line");
|
|
328
333
|
assert.match(css, /\.statusbar-git-footer \{[\s\S]*?--footer-chip-min-width:\s*7\.6rem;[\s\S]*?gap:\s*0\.58rem/, "enabled git-footer extension should keep styled spacing and one shared minimal chip width token");
|
|
@@ -363,6 +368,8 @@ assert.match(css, /\.footer-model-option\.active/, "footer model picker should s
|
|
|
363
368
|
assert.match(app, /async function createPathPickerDirectory\(\)/, "cwd picker should implement create-directory behavior in the browser");
|
|
364
369
|
assert.match(app, /function renderPathPickerDirectoryList\(\)[\s\S]*pathPickerDirectoryMatchesSearch/, "cwd picker should filter current-directory entries in the browser");
|
|
365
370
|
assert.match(app, /elements\.pathPickerSearchInput\.addEventListener\("input", renderPathPickerDirectoryList\)/, "cwd picker should update directory matches as the user types");
|
|
371
|
+
assert.match(app, /function shouldOpenCwdChangeInNewTab\(tab\) \{[\s\S]*!!tab\?\.conversationStarted[\s\S]*activeTabHasConversationMessages\(tab\)[\s\S]*stateHasVisibleWork\(currentState\)[\s\S]*tabHasActiveAgent\(tab\)/, "cwd changes for started conversations should be routed to a new tab");
|
|
372
|
+
assert.match(app, /if \(shouldOpenCwdChangeInNewTab\(tab\)\) \{[\s\S]*await createTerminalTab\(cwd, \{ triggerButton: null \}\);[\s\S]*return;[\s\S]*window\.confirm\(`Restart/, "footer cwd changes should open a new tab before destructive cwd restarts once a session is active");
|
|
366
373
|
assert.match(server, /async function createDirectoryPickerDirectory\(parentPath, nameValue, activeCwd\)/, "server should implement cwd picker directory creation");
|
|
367
374
|
assert.match(server, /function directoryPickerActiveCwd\(req, url, body = \{\}\)/, "server should let the cwd picker run before any Pi tabs exist");
|
|
368
375
|
assert.match(server, /url\.pathname === "\/api\/directories" && req\.method === "POST"/, "server should expose POST /api/directories for cwd picker directory creation");
|
|
@@ -443,6 +450,9 @@ assert.match(server, /--cwd does not exist:/, "server should report nonexistent
|
|
|
443
450
|
assert.match(server, /options\.cwd = await validateStartupCwd\(options\.cwd\)/, "server should fail fast for invalid startup cwd paths");
|
|
444
451
|
assert.match(server, /cwdExplicit: false/, "server should track whether startup cwd was explicitly requested");
|
|
445
452
|
assert.match(server, /return options\.cwdExplicit \? \[await createTab\(\)\] : \[\]/, "server should wait for UI cwd selection when no --cwd is supplied");
|
|
453
|
+
assert.match(server, /async function resolvedPiCliScript\(\)[\s\S]*require\.resolve\.paths\(PI_CODING_AGENT_PACKAGE\)[\s\S]*nodeModulesRoot[\s\S]*dist[\s\S]*cli\.js/, "server should resolve the bundled Pi CLI through Node resolution roots so hoisted global installs can spawn RPC tabs");
|
|
454
|
+
assert.match(server, /const bundledCli = await resolvedPiCliScript\(\)/, "standalone server should prefer the resolved Pi CLI script before falling back to PATH pi");
|
|
455
|
+
assert.match(server, /if \(options\.piBinExplicit\) \{\n\s+const command = await resolvePiCommand\(\["update"\]\)/, "explicit --pi JavaScript launchers should also work for update commands");
|
|
446
456
|
assert.match(app, /serverActionSelect\.addEventListener\("change", updateServerActionButton\)/, "Server action dropdown should control the guarded run button");
|
|
447
457
|
assert.match(app, /runServerActionButton\.addEventListener\("click"[\s\S]*runSelectedServerAction/, "Server action run button should execute the selected action");
|
|
448
458
|
assert.match(app, /api\("\/api\/restart", \{ method: "POST", scoped: false \}\)/, "Restart Server action should call the unscoped restart endpoint");
|
|
@@ -538,6 +548,8 @@ assert.match(app, /GIT_FOOTER_WEBUI_STATUS_KEY = "git-footer-webui"/, "git foote
|
|
|
538
548
|
assert.match(app, /function parseGitFooterWebuiPayloadRaw\(raw\)[\s\S]*GIT_FOOTER_WEBUI_PAYLOAD_TYPE[\s\S]*GIT_FOOTER_WEBUI_PAYLOAD_VERSION/, "Web UI footer should parse the structured payload emitted by git-footer-status");
|
|
539
549
|
assert.match(app, /function normalizeFooterPayloadChangedFile\(value\)[\s\S]*FOOTER_CHANGED_FILE_KINDS\.has\(value\.kind\)[\s\S]*oldPath/, "git footer payload parsing should preserve changed-file details for changes popovers");
|
|
540
550
|
assert.match(app, /const files = value\.files\.map\(normalizeFooterPayloadChangedFile\)\.filter\(Boolean\)\.slice\(0, 80\);[\s\S]*chip\.files = files;/, "git footer payload chips should retain bounded changed-file lists");
|
|
551
|
+
assert.match(app, /FOOTER_PAYLOAD_ACTIONS = new Set\(\["calibrate-current", "calibrate-probe"\]\)[\s\S]*chip\.action = value\.action;/, "git footer payload chips should preserve allowlisted actions such as PI calibration");
|
|
552
|
+
assert.match(app, /async function runGitFooterPiCalibration\(mode = "current", tabContext = activeTabContext\(\)\)[\s\S]*resolveAvailableCommandName\("calibrate", \{ rpcOnly: true \}\)[\s\S]*mode === "probe" \? `\/\$\{commandName\}` : `\/\$\{commandName\} current`[\s\S]*scheduleGitFooterPiCalibrationRefresh\(tabContext, mode === "probe" \? \[5000, 14000\] : \[600, 1600\]\)/, "clicking an uncalibrated PI footer chip should run current/probe calibration and refresh the git footer value");
|
|
541
553
|
assert.match(app, /title: cleanFooterPayloadText\(value\.title, "", 4000\)/, "git footer tooltip titles should preserve long cwd paths instead of truncating at chip display length");
|
|
542
554
|
assert.match(app, /const sourceTitle = cleanFooterPayloadText\(chip\?\.title, "", 4000\)/, "git footer tooltip rendering should keep full source titles for long cwd paths");
|
|
543
555
|
assert.match(app, /function renderFooter\(\)[\s\S]*parseGitFooterWebuiPayload\(\)[\s\S]*renderGitFooterPayload\(footerPayloadWithLiveModel\(gitFooterPayload\)\)/, "detailed footer rendering should prefer the git-footer-status extension payload");
|
|
@@ -602,6 +614,9 @@ assert.match(app, /api\("\/api\/optional-features"/, "optional feature panel sho
|
|
|
602
614
|
assert.match(app, /packageStatus\?\.updateAvailable[\s\S]*action\.textContent = "Update…"/, "optional feature package drift should turn the install action into an update action");
|
|
603
615
|
assert.match(app, /optionalFeatureInstallMessages\.set\(featureId[\s\S]*waiting for package-manager output/, "optional feature installs should show running feedback while npm is active");
|
|
604
616
|
assert.match(app, /api\("\/api\/optional-feature-install"/, "optional feature install action should call the backend installer endpoint");
|
|
617
|
+
assert.match(app, /id: "btwCommand"[\s\S]*?@firstpick\/pi-extension-btw/, "optional features should include the /btw companion");
|
|
618
|
+
assert.match(app, /BTW_OUTPUT_WIDGET_KEY = "btw:output"[\s\S]*function renderBtwOutputWidget/, "Web UI should render structured /btw output widgets");
|
|
619
|
+
assert.match(app, /if \(key\.startsWith\("btw:"\)\) return "btwCommand"/, "extension widget routing should associate /btw widgets with the optional feature");
|
|
605
620
|
assert.match(app, /id: "safetyGuard"[\s\S]*?@firstpick\/pi-extension-safety-guard/, "optional features should include the safety guard companion");
|
|
606
621
|
assert.match(app, /id: "tuiSkillsCommand"[\s\S]*?@firstpick\/pi-extension-setup-skills/, "optional features should include the TUI skills command companion");
|
|
607
622
|
assert.match(app, /id: "tuiToolsCommand"[\s\S]*?@firstpick\/pi-extension-tools/, "optional features should include the TUI tools command companion");
|
|
@@ -827,6 +842,11 @@ assert.match(app, /busyPromptBehaviorMenu\?\.addEventListener\("click"[\s\S]*cho
|
|
|
827
842
|
assert.match(app, /setBusyPromptBehavior\(controls\.busyBehavior\.select\.value\)/, "native settings should update the busy prompt behavior tag immediately");
|
|
828
843
|
assert.match(app, /sendPromptFromModeButton\("steer", elements\.steerButton\)/, "Steer should show tooltip instead of silently doing nothing when input is empty");
|
|
829
844
|
assert.match(app, /sendPromptFromModeButton\("follow-up", elements\.followUpButton\)/, "Follow-up should show tooltip instead of silently doing nothing when input is empty");
|
|
845
|
+
assert.match(app, /async function sendBtwQuestion\(question,[\s\S]*?`\/btw \$\{cleanQuestion\}`[\s\S]*?await sendPrompt\("prompt", message, \{ targetTabId, throwOnError: true \}\)/, "/btw helper should send text as an ephemeral slash command");
|
|
846
|
+
assert.match(app, /async function sendBtwPromptFromButton\(\)[\s\S]*?if \(!question\) \{\n\s+openBtwComposerWidget\(\);/, "empty /btw button should open the side-question widget input");
|
|
847
|
+
assert.match(app, /function renderBtwComposerForm\(\)[\s\S]*?form\.requestSubmit\(\)[\s\S]*?sendBtwQuestion\(question\)/, "/btw widget input should submit each message as a /btw trigger");
|
|
848
|
+
assert.match(app, /function makeBtwTransferIcon\(\)[\s\S]*?class", "btw-transfer-icon"[\s\S]*?function transferBtwContextToMain\(button\)[\s\S]*?`\/btw-transfer \$\{encoded\}`[\s\S]*?streamingBehavior: liveSteer \? "steer" : undefined/, "/btw widget should expose an iconified transfer-context action that steers during active runs");
|
|
849
|
+
assert.match(app, /async function sendPrompt\(kind = "prompt", explicitMessage, \{ targetTabId = activeTabId, throwOnError = false, streamingBehavior \} = \{\}\)[\s\S]*?if \(targetWasStreaming\) body\.streamingBehavior = streamingBehavior \|\| busyBehavior/, "prompt sending should support a per-call streaming behavior override");
|
|
830
850
|
assert.match(app, /function runPublishWorkflow\(command\)[\s\S]*?sendPrompt\("prompt", `\/\$\{resolvedCommandName\}\$\{commandRest\}`\)/, "Publish workflows should send resolved slash commands directly without replacing the draft");
|
|
831
851
|
assert.match(app, /async function runNativeCommandMenu\(command\)[\s\S]*?await handleNativeSlashSelectorCommand\(command\)/, "skills/tools command menu should open native selector dialogs directly");
|
|
832
852
|
assert.match(app, /async function runNativeCommandMenu\(command\)[\s\S]*?sendPrompt\("prompt", command\)/, "generic native command menu should fall back to slash-command prompt execution");
|
|
@@ -848,7 +868,7 @@ for (const command of ["resume", "reload", "remote", "name", "clone", "settings"
|
|
|
848
868
|
const id = command.replace(/^./, (letter) => letter.toUpperCase());
|
|
849
869
|
assert.match(app, new RegExp(`options${id}Button\\.addEventListener\\("click", \\(\\) => runNativeCommandMenu\\("\\/${command}"\\)\\)`), `Options menu should launch /${command}`);
|
|
850
870
|
}
|
|
851
|
-
assert.match(app, /async function sendPrompt\(kind = "prompt", explicitMessage, \{ targetTabId = activeTabId, throwOnError = false \} = \{\}\)/, "prompt sending should accept direct messages that bypass the input field and optional target tab");
|
|
871
|
+
assert.match(app, /async function sendPrompt\(kind = "prompt", explicitMessage, \{ targetTabId = activeTabId, throwOnError = false, streamingBehavior \} = \{\}\)/, "prompt sending should accept direct messages that bypass the input field and optional target tab");
|
|
852
872
|
assert.match(app, /const rawMessage = usesPromptInput \? elements\.promptInput\.value : explicitMessage/, "direct prompt sends should not read the input textarea");
|
|
853
873
|
assert.match(app, /if \(usesPromptInput\) \{[\s\S]*?if \(targetStillActive\) \{[\s\S]*?elements\.promptInput\.value = "";/, "direct prompt sends should preserve the input textarea draft");
|
|
854
874
|
assert.match(app, /make\("button", "command-item"\)[\s\S]*?sendPrompt\("prompt", `\/\$\{command\.name\}`\)/, "side-panel command clicks should send the slash command directly");
|
|
@@ -866,6 +886,7 @@ assert.match(app, /function openNativeTreeSelector\(\)[\s\S]*?\/api\/session-tre
|
|
|
866
886
|
assert.match(app, /async function openNativeAuthSelector\(mode\)[\s\S]*?\/api\/auth-providers[\s\S]*?Browser login is not implemented yet/, "native /login should list provider status without browser credential entry");
|
|
867
887
|
assert.match(app, /\/api\/auth-logout[\s\S]*?confirmed: true/, "native /logout should remove stored credentials through a confirmed localhost-only endpoint");
|
|
868
888
|
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");
|
|
889
|
+
assert.match(app, /HIDDEN_COMMAND_NAMES\.add\("btw-transfer"\)/, "/btw transfer helper command should stay out of command pickers");
|
|
869
890
|
assert.match(app, /function shouldSendPromptFromEnter\(event\)/, "prompt keyboard handling should be centralized");
|
|
870
891
|
assert.match(app, /const PROMPT_HISTORY_STORAGE_KEY = "pi-webui-prompt-history"/, "prompt history should be persisted per browser for keyboard recall");
|
|
871
892
|
assert.match(app, /function recallPreviousPromptFromHistory\(\)/, "prompt history should support recalling older prompts from the textarea");
|
|
@@ -1019,6 +1040,7 @@ assert.match(server, /WEBUI_TUI_NATIVE_PARITY\.json/, "native command descriptio
|
|
|
1019
1040
|
assert.match(server, /function parseSlashCommand\(message\)/, "server should parse native slash commands before prompt forwarding");
|
|
1020
1041
|
assert.match(server, /function generatedTabTitleFromPrompt\(message\)/, "server should derive concise automatic tab titles from first prompts");
|
|
1021
1042
|
assert.match(server, /function maybeNameTabForConversation\(tab, command\)/, "server should auto-name default tabs when a conversation starts");
|
|
1043
|
+
assert.match(server, /function maybeNameTabForConversation\(tab, command\) \{[\s\S]*const shouldRename = !tab\.conversationStarted && tab\.titleSource !== "explicit";[\s\S]*tab\.conversationStarted = true;[\s\S]*if \(!shouldRename\) return false;/, "server should mark conversations as started even when an explicit title prevents auto-renaming");
|
|
1022
1044
|
assert.match(server, /function createTabActivity\(/, "server should track per-tab activity for idle, working, and completed work");
|
|
1023
1045
|
assert.match(server, /function reconcileTabActivityFromState\(tab, state/, "server should recover stale working tab activity from get_state snapshots");
|
|
1024
1046
|
assert.match(server, /pendingExtensionUiRequests\(tab\)\.length > 0[\s\S]*?markTabWorking\(tab, timestamp\)/, "server should keep tabs with pending blockers in working activity until the blocker resolves");
|
|
@@ -1123,6 +1145,7 @@ assert.match(server, /type: "set_follow_up_mode"/, "server should expose follow-
|
|
|
1123
1145
|
assert.match(server, /type: "set_auto_compaction"/, "server should expose auto-compaction changes for native /settings");
|
|
1124
1146
|
assert.match(server, /@firstpick\/pi-themes-bundle/, "server should discover themes from the optional theme package");
|
|
1125
1147
|
assert.match(server, /const OPTIONAL_FEATURE_PACKAGES = new Map/, "server should whitelist optional feature packages for install actions");
|
|
1148
|
+
assert.match(server, /\["btwCommand", "@firstpick\/pi-extension-btw"\]/, "server should allow installing the /btw optional feature");
|
|
1126
1149
|
assert.match(server, /\["safetyGuard", "@firstpick\/pi-extension-safety-guard"\]/, "server should allow installing the safety guard optional feature");
|
|
1127
1150
|
assert.match(server, /\["tuiSkillsCommand", "@firstpick\/pi-extension-setup-skills"\]/, "server should allow installing the TUI skills optional feature");
|
|
1128
1151
|
assert.match(server, /\["tuiToolsCommand", "@firstpick\/pi-extension-tools"\]/, "server should allow installing the TUI tools optional feature");
|
|
@@ -1163,7 +1186,7 @@ assert.match(readme, /GET \/api\/path-suggestions\?tab=<tabId>&query=<path>/, "R
|
|
|
1163
1186
|
assert.match(readme, /GET \/api\/optional-features/, "README should document optional feature status endpoint");
|
|
1164
1187
|
assert.match(readme, /POST \/api\/optional-feature-install/, "README should document optional feature install endpoint");
|
|
1165
1188
|
assert.match(readme, /server-persisted fast picks/, "README should describe server-persisted fast picks");
|
|
1166
|
-
assert.match(readme,
|
|
1189
|
+
assert.match(readme, /`\/btw` side-question output widgets with optional context transfer\/live steering, browser notifications when a tab needs an extension UI response, and an optional side-panel toggle for agent-done notifications/, "README should describe /btw, blocked-tab, and agent-done notifications");
|
|
1167
1190
|
assert.match(readme, /blocked-tab browser notifications, and optional agent-done notifications require browser service-worker\/notification support/, "README should document notification requirements");
|
|
1168
1191
|
assert.match(readme, /Side-panel theme picker backed by optional `@firstpick\/pi-themes-bundle` themes when loaded/, "README should describe optional theme selection");
|
|
1169
1192
|
assert.match(readme, /## Optional companion packages/, "README should document optional Web UI companion packages");
|
|
@@ -1173,17 +1196,19 @@ assert.match(readme, /avoiding duplicate loads while keeping global `pi-webui` l
|
|
|
1173
1196
|
assert.match(readme, /checks loaded Pi capabilities directly through RPC-visible commands and live widget events/, "README should document capability-based startup checks");
|
|
1174
1197
|
assert.match(readme, /side panel shows each optional feature as enabled, disabled, installed-but-not-loaded, update-available, or install-needed/, "README should document optional feature side-panel controls");
|
|
1175
1198
|
assert.match(readme, /Installing or updating a feature is an explicit, warned action with running\/failure feedback/, "README should document optional feature install and update warning behavior");
|
|
1176
|
-
assert.match(readme, /\.\/start-webui\.sh --dev --cwd \/path\/to\/project/, "README should document the dev helper launcher");
|
|
1199
|
+
assert.match(readme, /\.\/dev\/scripts\/start-webui\.sh --dev --cwd \/path\/to\/project/, "README should document the dev helper launcher");
|
|
1177
1200
|
assert.match(readme, /sync-pi-package-symlinks\.sh[\s\S]*only one copy is loaded/, "README should document dev companion symlink setup");
|
|
1178
1201
|
assert.match(startScript, /--dev\)/, "start-webui.sh should accept a --dev flag");
|
|
1179
1202
|
assert.match(startScript, /local_pi_webui_bin\(\)/, "start-webui.sh should resolve this checkout's local server entrypoint");
|
|
1203
|
+
assert.match(startScript, /candidate="\$\(package_root\)\/bin\/pi-webui\.mjs"/, "start-webui.sh should resolve the package-root bin from dev/scripts");
|
|
1180
1204
|
assert.match(startScript, /webui_cmd=\(node "\$local_webui_bin"\)/, "start-webui.sh --dev should run the local bin with node");
|
|
1181
1205
|
assert.match(startScript, /export PI_WEBUI_DEV=1/, "start-webui.sh --dev should mark the Web UI server as dev mode");
|
|
1182
1206
|
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");
|
|
1183
1207
|
|
|
1184
1208
|
assert.match(pkg.scripts?.test || "", /node tests\/run-all\.mjs/, "package test script should run every tests/*.test.mjs through the shared runner");
|
|
1185
|
-
assert.ok(pkg.files?.includes("start-webui.sh"), "npm package should
|
|
1186
|
-
assert.ok(pkg.files?.includes("start-webui.ps1"), "npm package should
|
|
1209
|
+
assert.ok(!pkg.files?.includes("start-webui.sh"), "npm package should not list the moved Bash dev helper at the package root");
|
|
1210
|
+
assert.ok(!pkg.files?.includes("start-webui.ps1"), "npm package should not list the moved PowerShell dev helper at the package root");
|
|
1211
|
+
assert.ok(!pkg.files?.some((entry) => entry === "dev/scripts" || entry.startsWith("dev/scripts/")), "npm package should not publish development helper scripts");
|
|
1187
1212
|
for (const [name, range] of Object.entries(companionDependencies)) {
|
|
1188
1213
|
assert.equal(pkg.optionalDependencies?.[name], range, `webui package should optionally depend on ${name}`);
|
|
1189
1214
|
assert.equal(pkg.dependencies?.[name], undefined, `webui package should not require optional companion ${name}`);
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
} from "../lib/native-command-adapter.mjs";
|
|
29
29
|
|
|
30
30
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
31
|
-
const parity = JSON.parse(await readFile(join(root, "WEBUI_TUI_NATIVE_PARITY.json"), "utf8"));
|
|
31
|
+
const parity = JSON.parse(await readFile(join(root, "dev", "docs", "WEBUI_TUI_NATIVE_PARITY.json"), "utf8"));
|
|
32
32
|
|
|
33
33
|
const localReq = { socket: { remoteAddress: "127.0.0.1" } };
|
|
34
34
|
const remoteReq = { socket: { remoteAddress: "192.168.1.50" } };
|
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
|
|
6
6
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
7
|
const [parityRaw, server, app, pkgRaw] = await Promise.all([
|
|
8
|
-
readFile(join(root, "WEBUI_TUI_NATIVE_PARITY.json"), "utf8"),
|
|
8
|
+
readFile(join(root, "dev", "docs", "WEBUI_TUI_NATIVE_PARITY.json"), "utf8"),
|
|
9
9
|
readFile(join(root, "bin", "pi-webui.mjs"), "utf8"),
|
|
10
10
|
readFile(join(root, "public", "app.js"), "utf8"),
|
|
11
11
|
readFile(join(root, "package.json"), "utf8"),
|
|
@@ -181,7 +181,7 @@ assert.match(app, /let userBashQueuesByTab = new Map\(\)/, "frontend should trac
|
|
|
181
181
|
assert.match(app, /enqueueUserBashCommand\(parsed, \{ usesPromptInput, targetTabId \}\)/, "user bash should enqueue while an active or queued bash command exists");
|
|
182
182
|
assert.match(server, /function sendQueuedBashCommand\(tab, command\)/, "server should serialize user bash commands per tab");
|
|
183
183
|
assert.match(server, /command\.type === "bash"[\s\S]*?await sendQueuedBashCommand\(tab, command\)[\s\S]*?: await tab\.rpc\.send\(command\)/, "generic POST handling should route bash through the FIFO queue");
|
|
184
|
-
assert.ok(pkg.files.includes("WEBUI_TUI_NATIVE_PARITY.json"), "published package should include the native parity matrix");
|
|
184
|
+
assert.ok(pkg.files.includes("dev/docs/WEBUI_TUI_NATIVE_PARITY.json"), "published package should include the native parity matrix");
|
|
185
185
|
assert.ok(pkg.files.includes("lib"), "published package should include shared Web UI foundation modules");
|
|
186
186
|
assert.ok(pkg.files.includes("webui-rpc-helper.mjs"), "published package should include the Web UI RPC helper extension");
|
|
187
187
|
|