@firstpick/pi-package-webui 0.4.3 → 0.4.4
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 +6 -2
- 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/mobile-static.test.mjs +31 -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,
|
|
@@ -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");
|
|
@@ -538,6 +545,8 @@ assert.match(app, /GIT_FOOTER_WEBUI_STATUS_KEY = "git-footer-webui"/, "git foote
|
|
|
538
545
|
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
546
|
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
547
|
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");
|
|
548
|
+
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");
|
|
549
|
+
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
550
|
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
551
|
assert.match(app, /const sourceTitle = cleanFooterPayloadText\(chip\?\.title, "", 4000\)/, "git footer tooltip rendering should keep full source titles for long cwd paths");
|
|
543
552
|
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 +611,9 @@ assert.match(app, /api\("\/api\/optional-features"/, "optional feature panel sho
|
|
|
602
611
|
assert.match(app, /packageStatus\?\.updateAvailable[\s\S]*action\.textContent = "Update…"/, "optional feature package drift should turn the install action into an update action");
|
|
603
612
|
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
613
|
assert.match(app, /api\("\/api\/optional-feature-install"/, "optional feature install action should call the backend installer endpoint");
|
|
614
|
+
assert.match(app, /id: "btwCommand"[\s\S]*?@firstpick\/pi-extension-btw/, "optional features should include the /btw companion");
|
|
615
|
+
assert.match(app, /BTW_OUTPUT_WIDGET_KEY = "btw:output"[\s\S]*function renderBtwOutputWidget/, "Web UI should render structured /btw output widgets");
|
|
616
|
+
assert.match(app, /if \(key\.startsWith\("btw:"\)\) return "btwCommand"/, "extension widget routing should associate /btw widgets with the optional feature");
|
|
605
617
|
assert.match(app, /id: "safetyGuard"[\s\S]*?@firstpick\/pi-extension-safety-guard/, "optional features should include the safety guard companion");
|
|
606
618
|
assert.match(app, /id: "tuiSkillsCommand"[\s\S]*?@firstpick\/pi-extension-setup-skills/, "optional features should include the TUI skills command companion");
|
|
607
619
|
assert.match(app, /id: "tuiToolsCommand"[\s\S]*?@firstpick\/pi-extension-tools/, "optional features should include the TUI tools command companion");
|
|
@@ -827,6 +839,11 @@ assert.match(app, /busyPromptBehaviorMenu\?\.addEventListener\("click"[\s\S]*cho
|
|
|
827
839
|
assert.match(app, /setBusyPromptBehavior\(controls\.busyBehavior\.select\.value\)/, "native settings should update the busy prompt behavior tag immediately");
|
|
828
840
|
assert.match(app, /sendPromptFromModeButton\("steer", elements\.steerButton\)/, "Steer should show tooltip instead of silently doing nothing when input is empty");
|
|
829
841
|
assert.match(app, /sendPromptFromModeButton\("follow-up", elements\.followUpButton\)/, "Follow-up should show tooltip instead of silently doing nothing when input is empty");
|
|
842
|
+
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");
|
|
843
|
+
assert.match(app, /async function sendBtwPromptFromButton\(\)[\s\S]*?if \(!question\) \{\n\s+openBtwComposerWidget\(\);/, "empty /btw button should open the side-question widget input");
|
|
844
|
+
assert.match(app, /function renderBtwComposerForm\(\)[\s\S]*?form\.requestSubmit\(\)[\s\S]*?sendBtwQuestion\(question\)/, "/btw widget input should submit each message as a /btw trigger");
|
|
845
|
+
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");
|
|
846
|
+
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
847
|
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
848
|
assert.match(app, /async function runNativeCommandMenu\(command\)[\s\S]*?await handleNativeSlashSelectorCommand\(command\)/, "skills/tools command menu should open native selector dialogs directly");
|
|
832
849
|
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 +865,7 @@ for (const command of ["resume", "reload", "remote", "name", "clone", "settings"
|
|
|
848
865
|
const id = command.replace(/^./, (letter) => letter.toUpperCase());
|
|
849
866
|
assert.match(app, new RegExp(`options${id}Button\\.addEventListener\\("click", \\(\\) => runNativeCommandMenu\\("\\/${command}"\\)\\)`), `Options menu should launch /${command}`);
|
|
850
867
|
}
|
|
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");
|
|
868
|
+
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
869
|
assert.match(app, /const rawMessage = usesPromptInput \? elements\.promptInput\.value : explicitMessage/, "direct prompt sends should not read the input textarea");
|
|
853
870
|
assert.match(app, /if \(usesPromptInput\) \{[\s\S]*?if \(targetStillActive\) \{[\s\S]*?elements\.promptInput\.value = "";/, "direct prompt sends should preserve the input textarea draft");
|
|
854
871
|
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 +883,7 @@ assert.match(app, /function openNativeTreeSelector\(\)[\s\S]*?\/api\/session-tre
|
|
|
866
883
|
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
884
|
assert.match(app, /\/api\/auth-logout[\s\S]*?confirmed: true/, "native /logout should remove stored credentials through a confirmed localhost-only endpoint");
|
|
868
885
|
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");
|
|
886
|
+
assert.match(app, /HIDDEN_COMMAND_NAMES\.add\("btw-transfer"\)/, "/btw transfer helper command should stay out of command pickers");
|
|
869
887
|
assert.match(app, /function shouldSendPromptFromEnter\(event\)/, "prompt keyboard handling should be centralized");
|
|
870
888
|
assert.match(app, /const PROMPT_HISTORY_STORAGE_KEY = "pi-webui-prompt-history"/, "prompt history should be persisted per browser for keyboard recall");
|
|
871
889
|
assert.match(app, /function recallPreviousPromptFromHistory\(\)/, "prompt history should support recalling older prompts from the textarea");
|
|
@@ -1019,6 +1037,7 @@ assert.match(server, /WEBUI_TUI_NATIVE_PARITY\.json/, "native command descriptio
|
|
|
1019
1037
|
assert.match(server, /function parseSlashCommand\(message\)/, "server should parse native slash commands before prompt forwarding");
|
|
1020
1038
|
assert.match(server, /function generatedTabTitleFromPrompt\(message\)/, "server should derive concise automatic tab titles from first prompts");
|
|
1021
1039
|
assert.match(server, /function maybeNameTabForConversation\(tab, command\)/, "server should auto-name default tabs when a conversation starts");
|
|
1040
|
+
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
1041
|
assert.match(server, /function createTabActivity\(/, "server should track per-tab activity for idle, working, and completed work");
|
|
1023
1042
|
assert.match(server, /function reconcileTabActivityFromState\(tab, state/, "server should recover stale working tab activity from get_state snapshots");
|
|
1024
1043
|
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 +1142,7 @@ assert.match(server, /type: "set_follow_up_mode"/, "server should expose follow-
|
|
|
1123
1142
|
assert.match(server, /type: "set_auto_compaction"/, "server should expose auto-compaction changes for native /settings");
|
|
1124
1143
|
assert.match(server, /@firstpick\/pi-themes-bundle/, "server should discover themes from the optional theme package");
|
|
1125
1144
|
assert.match(server, /const OPTIONAL_FEATURE_PACKAGES = new Map/, "server should whitelist optional feature packages for install actions");
|
|
1145
|
+
assert.match(server, /\["btwCommand", "@firstpick\/pi-extension-btw"\]/, "server should allow installing the /btw optional feature");
|
|
1126
1146
|
assert.match(server, /\["safetyGuard", "@firstpick\/pi-extension-safety-guard"\]/, "server should allow installing the safety guard optional feature");
|
|
1127
1147
|
assert.match(server, /\["tuiSkillsCommand", "@firstpick\/pi-extension-setup-skills"\]/, "server should allow installing the TUI skills optional feature");
|
|
1128
1148
|
assert.match(server, /\["tuiToolsCommand", "@firstpick\/pi-extension-tools"\]/, "server should allow installing the TUI tools optional feature");
|
|
@@ -1163,7 +1183,7 @@ assert.match(readme, /GET \/api\/path-suggestions\?tab=<tabId>&query=<path>/, "R
|
|
|
1163
1183
|
assert.match(readme, /GET \/api\/optional-features/, "README should document optional feature status endpoint");
|
|
1164
1184
|
assert.match(readme, /POST \/api\/optional-feature-install/, "README should document optional feature install endpoint");
|
|
1165
1185
|
assert.match(readme, /server-persisted fast picks/, "README should describe server-persisted fast picks");
|
|
1166
|
-
assert.match(readme,
|
|
1186
|
+
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
1187
|
assert.match(readme, /blocked-tab browser notifications, and optional agent-done notifications require browser service-worker\/notification support/, "README should document notification requirements");
|
|
1168
1188
|
assert.match(readme, /Side-panel theme picker backed by optional `@firstpick\/pi-themes-bundle` themes when loaded/, "README should describe optional theme selection");
|
|
1169
1189
|
assert.match(readme, /## Optional companion packages/, "README should document optional Web UI companion packages");
|
|
@@ -1173,17 +1193,19 @@ assert.match(readme, /avoiding duplicate loads while keeping global `pi-webui` l
|
|
|
1173
1193
|
assert.match(readme, /checks loaded Pi capabilities directly through RPC-visible commands and live widget events/, "README should document capability-based startup checks");
|
|
1174
1194
|
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
1195
|
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");
|
|
1196
|
+
assert.match(readme, /\.\/dev\/scripts\/start-webui\.sh --dev --cwd \/path\/to\/project/, "README should document the dev helper launcher");
|
|
1177
1197
|
assert.match(readme, /sync-pi-package-symlinks\.sh[\s\S]*only one copy is loaded/, "README should document dev companion symlink setup");
|
|
1178
1198
|
assert.match(startScript, /--dev\)/, "start-webui.sh should accept a --dev flag");
|
|
1179
1199
|
assert.match(startScript, /local_pi_webui_bin\(\)/, "start-webui.sh should resolve this checkout's local server entrypoint");
|
|
1200
|
+
assert.match(startScript, /candidate="\$\(package_root\)\/bin\/pi-webui\.mjs"/, "start-webui.sh should resolve the package-root bin from dev/scripts");
|
|
1180
1201
|
assert.match(startScript, /webui_cmd=\(node "\$local_webui_bin"\)/, "start-webui.sh --dev should run the local bin with node");
|
|
1181
1202
|
assert.match(startScript, /export PI_WEBUI_DEV=1/, "start-webui.sh --dev should mark the Web UI server as dev mode");
|
|
1182
1203
|
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
1204
|
|
|
1184
1205
|
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
|
|
1206
|
+
assert.ok(!pkg.files?.includes("start-webui.sh"), "npm package should not list the moved Bash dev helper at the package root");
|
|
1207
|
+
assert.ok(!pkg.files?.includes("start-webui.ps1"), "npm package should not list the moved PowerShell dev helper at the package root");
|
|
1208
|
+
assert.ok(!pkg.files?.some((entry) => entry === "dev/scripts" || entry.startsWith("dev/scripts/")), "npm package should not publish development helper scripts");
|
|
1187
1209
|
for (const [name, range] of Object.entries(companionDependencies)) {
|
|
1188
1210
|
assert.equal(pkg.optionalDependencies?.[name], range, `webui package should optionally depend on ${name}`);
|
|
1189
1211
|
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
|
|