@firstpick/pi-package-webui 0.2.8 → 0.3.0
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/bin/pi-webui.mjs +38 -1
- package/package.json +6 -1
- package/public/app.js +778 -59
- package/public/index.html +51 -10
- package/public/styles.css +457 -17
- package/tests/mobile-static.test.mjs +60 -4
|
@@ -58,9 +58,18 @@ assert.match(html, /id="thinkingVisibilityToggle"/, "side panel should expose a
|
|
|
58
58
|
assert.match(html, /id="thinkingVisibilityStatus"/, "thinking-output visibility toggle should expose status text");
|
|
59
59
|
assert.match(html, /id="nativeCommandDialog"/, "native slash selector UI should have a dedicated dialog");
|
|
60
60
|
assert.match(html, /id="nativeCommandSearch"[^>]*type="search"/, "native slash selector dialog should expose a filter box");
|
|
61
|
+
assert.match(html, /id="pathPickerCreateNameInput"[^>]*placeholder="New directory name"/, "cwd picker should expose a new-directory name input");
|
|
62
|
+
assert.match(html, /id="pathPickerCreateButton"[^>]*>Create directory<\/button>/, "cwd picker should expose a create-directory action");
|
|
63
|
+
assert.match(html, /id="pathPickerSearchInput"[^>]*type="search"[^>]*placeholder="Search current directory…"/, "cwd picker should expose a current-directory search box");
|
|
64
|
+
assert.match(html, /id="pathPickerClearSearchButton"[^>]*hidden[^>]*>Clear<\/button>/, "cwd picker should expose a clear-search action");
|
|
61
65
|
assert.match(html, /id="optionalFeaturesBox"/, "side panel should expose optional feature status and controls");
|
|
62
66
|
assert.match(html, /id="codexUsageBox"/, "side panel should expose Codex subscription usage status");
|
|
63
67
|
assert.match(html, /data-side-panel-section="codex-usage"/, "Codex usage should live in a collapsible side-panel section");
|
|
68
|
+
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");
|
|
69
|
+
assert.match(html, /id="loadPromptListButton"[\s\S]*>Load List<\/button>[\s\S]*id="runLoadedPromptListButton"[^>]*disabled[^>]*>Run<\/button>/, "Queue section should expose load and run controls for saved prompt lists");
|
|
70
|
+
assert.match(html, /id="promptListDialog"[\s\S]*id="promptListAddPromptButton"[\s\S]*\+ Add follow-up prompt/, "prompt-list dialog should add follow-up prompt rows");
|
|
71
|
+
assert.match(html, /id="promptListLoadSelectedButton"[\s\S]*>Load selected<\/button>[\s\S]*id="promptListDeleteSelectedButton"[^>]*class="danger"[\s\S]*>Delete<\/button>/, "prompt-list dialog should delete saved lists from the load panel");
|
|
72
|
+
assert.match(html, /id="promptListSaveButton"[\s\S]*>Save<\/button>[\s\S]*id="promptListRunListButton"[\s\S]*>Run List<\/button>/, "prompt-list dialog should save and run the displayed list");
|
|
64
73
|
assert.match(html, /id="serverOfflinePanel"/, "PWA/offline shell should expose a backend-offline recovery panel");
|
|
65
74
|
assert.match(html, /id="serverRestartPanel"[\s\S]*id="serverRestartMessage"/, "server restart should expose a loading overlay instead of the generic offline shell");
|
|
66
75
|
assert.match(html, /id="copyServerCommandButton"/, "backend-offline recovery panel should expose a start-command copy button");
|
|
@@ -120,6 +129,10 @@ assert.match(css, /--theme-background-image:\s*none/, "CSS should expose a theme
|
|
|
120
129
|
assert.match(css, /var\(--theme-background-image\)/, "body background should include the selected theme background image layer");
|
|
121
130
|
assert.match(css, /\.background-control-row \{[\s\S]*?grid-template-columns:\s*minmax\(0, 1fr\) auto/, "side-panel background controls should keep the remove button beside the picker");
|
|
122
131
|
assert.match(css, /\.background-clear-button \{[\s\S]*?color:\s*var\(--ctp-red\)/, "background remove button should be visually destructive");
|
|
132
|
+
assert.match(css, /\.path-picker-create-row \{[\s\S]*?grid-template-columns:\s*minmax\(0, 1fr\) auto/, "cwd picker directory creation controls should sit in a responsive row");
|
|
133
|
+
assert.match(css, /\.path-picker-create-button:hover,[\s\S]*?var\(--ctp-blue\)/, "cwd picker create action should have a distinct hover style");
|
|
134
|
+
assert.match(css, /\.path-picker-search-row \{[\s\S]*?grid-template-columns:\s*minmax\(0, 1fr\) auto/, "cwd picker search controls should sit in a responsive row");
|
|
135
|
+
assert.match(css, /\.path-picker-clear-search-button:hover,[\s\S]*?var\(--ctp-mauve\)/, "cwd picker clear-search action should have a distinct hover style");
|
|
123
136
|
assert.match(css, /height:\s*var\(--visual-viewport-height, 100dvh\)/, "layout should consume visual viewport height");
|
|
124
137
|
assert.match(css, /button, select, input \{ min-height: 44px; \}/, "base controls should meet 44px touch-target height");
|
|
125
138
|
assert.match(css, /\.composer-row button[\s\S]*?min-height:\s*44px/, "mobile composer buttons should keep 44px touch targets");
|
|
@@ -147,6 +160,10 @@ assert.match(css, /\.tool-result-preview \{[\s\S]*?padding:/, "collapsed tool re
|
|
|
147
160
|
assert.match(css, /\.message-collapse\[open\] \+ \.tool-result-preview \{[\s\S]*?display:\s*none/, "tool result preview should hide when full output is expanded");
|
|
148
161
|
assert.match(css, /\.run-indicator-pulse \{[\s\S]*?animation:\s*run-indicator-pulse/, "active agent run indicator should have an animated pulse");
|
|
149
162
|
assert.match(css, /\.optional-features-box \{[\s\S]*?display:\s*grid/, "optional features should render as a side-panel feature list");
|
|
163
|
+
assert.match(css, /\.prompt-list-controls \{[\s\S]*?display:\s*grid/, "Queue prompt-list controls should render as a side-panel control group");
|
|
164
|
+
assert.match(css, /\.prompt-list-dialog \{[\s\S]*?width:\s*min\(58rem/, "prompt-list editor dialog should have a wider prompt-friendly layout");
|
|
165
|
+
assert.match(css, /\.prompt-list-editor-rows \{[\s\S]*?max-height:/, "prompt-list dialog should scroll long follow-up lists inside the editor");
|
|
166
|
+
assert.match(css, /\.prompt-list-load-row \{[\s\S]*?grid-template-columns:\s*minmax\(0, 1fr\) auto auto/, "prompt-list load row should fit load and delete actions beside saved-list selection");
|
|
150
167
|
assert.match(css, /\.side-panel-section-toggle \{[\s\S]*?justify-content:\s*space-between/, "side panel section toggles should align labels and chevrons");
|
|
151
168
|
assert.match(css, /\.server-restart-panel \{[\s\S]*?z-index:\s*62/, "server restart overlay should render above the offline shell");
|
|
152
169
|
assert.match(css, /@keyframes server-restart-spin/, "server restart overlay should show a loading spinner");
|
|
@@ -186,6 +203,9 @@ assert.match(css, /\.action-feedback-controls:not\(:hover\):not\(:focus-within\)
|
|
|
186
203
|
assert.match(css, /\.action-feedback-button\.feedback-question\.active/, "question-mark reaction should have selected styling");
|
|
187
204
|
assert.match(css, /\.composer-row button\[data-tooltip\]::after/, "composer button tooltips should be shared across Git, Steer, and Follow-up buttons");
|
|
188
205
|
assert.match(css, /\.composer-row button\[data-tooltip\]\.tooltip-open::after/, "composer button tooltips should be triggerable from JS for empty mobile taps");
|
|
206
|
+
assert.match(css, /\.footer-floating-tooltip \{[\s\S]*?position:\s*fixed;[\s\S]*?z-index:\s*1000/, "git footer extension boxes should use one viewport-positioned styled tooltip");
|
|
207
|
+
assert.match(css, /\.footer-floating-tooltip \{[\s\S]*?overflow-wrap:\s*anywhere;[\s\S]*?white-space:\s*pre-wrap;/, "git footer tooltips should wrap long paths instead of clipping them");
|
|
208
|
+
assert.doesNotMatch(css, /\.statusbar-git-footer \.footer-(?:metric|meta)\[data-tooltip\]::after/, "git footer chips should not also render a second pseudo-element tooltip");
|
|
189
209
|
assert.match(css, /\.composer-publish-menu:hover > \.composer-publish-button\[data-tooltip\]::before,[\s\S]*?\.composer-publish-menu\.open > \.composer-publish-button\[data-tooltip\]::after \{[\s\S]*?display:\s*none !important;[\s\S]*?opacity:\s*0 !important;/, "dropdown button tooltips should hide while publish or setup menus are open");
|
|
190
210
|
assert.match(css, /\.composer-publish-menu-panel \{[\s\S]*?display:\s*none;[\s\S]*?flex-direction:\s*column/, "Publish workflow menu should hide when closed and expand like grouped tabs");
|
|
191
211
|
assert.match(css, /\.composer-publish-menu:hover \.composer-publish-menu-panel,[\s\S]*?\.composer-publish-menu:focus-within \.composer-publish-menu-panel,[\s\S]*?\.composer-publish-menu\.open \.composer-publish-menu-panel \{\n\s+display:\s*flex;/, "Publish workflow menu should open on hover, focus, or explicit open state");
|
|
@@ -197,10 +217,12 @@ assert.match(css, /\.composer-actions-panel[\s\S]*?bottom:\s*calc\(100% \+ 0\.42
|
|
|
197
217
|
assert.match(css, /body\.composer-actions-open \.composer-actions-panel \{ display: grid; \}/, "composer actions panel should only open when toggled");
|
|
198
218
|
assert.match(css, /\.terminal-tabs-toggle-button \{ display: none; \}/, "terminal tab toggle should be hidden outside mobile CSS");
|
|
199
219
|
assert.match(css, /\.terminal-close-all-button \{[\s\S]*?color:\s*var\(--ctp-red\)/, "close-all tabs action should render as a top-right destructive tab action");
|
|
220
|
+
assert.match(css, /\.terminal-tabs\.terminal-tabs-dense \{[\s\S]*?flex-wrap:\s*wrap/, "large terminal tab sets should wrap into a readable dense tab strip");
|
|
200
221
|
assert.match(css, /\.terminal-tab-group-close \{[\s\S]*?border-left-color/, "terminal tab groups should style their close button distinctly");
|
|
201
222
|
assert.match(css, /body\.mobile-tabs-expanded \.terminal-tabs \{ display: flex; \}/, "mobile tabs should expand only when toggled");
|
|
202
223
|
assert.match(css, /\.terminal-tab-activity-indicator/, "terminal tabs should expose per-tab agent activity indicators");
|
|
203
|
-
assert.match(css, /\.terminal-tab-group-item \{[\s\S]*?background:\s*var\(--ctp-crust\)/, "grouped terminal tab items should use opaque backgrounds");
|
|
224
|
+
assert.match(css, /\.terminal-tab-group-item \{[\s\S]*?flex:\s*0 0 auto[\s\S]*?background:\s*var\(--ctp-crust\)/, "grouped terminal tab items should keep readable height and use opaque backgrounds");
|
|
225
|
+
assert.match(css, /\.terminal-tab-group-add \{[\s\S]*?flex:\s*0 0 auto/, "grouped terminal tab menus should scroll instead of shrinking the add-tab action");
|
|
204
226
|
assert.match(css, /\.terminal-tab-group\.active \{[\s\S]*?background:[\s\S]*?var\(--ctp-crust\)/, "active terminal tab groups should keep opaque backgrounds");
|
|
205
227
|
assert.match(css, /\.terminal-tab-group\.stopped \{[\s\S]*?opacity:\s*1/, "stopped terminal tab groups should not become transparent");
|
|
206
228
|
assert.match(css, /\.terminal-tabs:has\(\.terminal-tab-group\.menu-open\)/, "open terminal tab groups should keep the tab strip usable across rerenders");
|
|
@@ -235,6 +257,11 @@ assert.match(css, /\.footer-model-picker[\s\S]*?position:\s*absolute[\s\S]*?left
|
|
|
235
257
|
assert.match(css, /@media \(max-width: 720px\), \(max-device-width: 720px\), \(pointer: coarse\) and \(hover: none\)[\s\S]*?\.footer-model-picker \{[\s\S]*?position:\s*fixed/, "mobile footer model picker should escape footer-details stacking as a fixed overlay on narrow, device-width-narrow, or touch-only devices");
|
|
236
258
|
assert.match(css, /bottom:\s*var\(--footer-model-picker-bottom/, "mobile footer model picker should be anchored by a JS-computed viewport offset");
|
|
237
259
|
assert.match(css, /\.footer-model-option\.active/, "footer model picker should style the selected scoped model");
|
|
260
|
+
assert.match(app, /async function createPathPickerDirectory\(\)/, "cwd picker should implement create-directory behavior in the browser");
|
|
261
|
+
assert.match(app, /function renderPathPickerDirectoryList\(\)[\s\S]*pathPickerDirectoryMatchesSearch/, "cwd picker should filter current-directory entries in the browser");
|
|
262
|
+
assert.match(app, /elements\.pathPickerSearchInput\.addEventListener\("input", renderPathPickerDirectoryList\)/, "cwd picker should update directory matches as the user types");
|
|
263
|
+
assert.match(server, /async function createDirectoryPickerDirectory\(parentPath, nameValue, activeCwd\)/, "server should implement cwd picker directory creation");
|
|
264
|
+
assert.match(server, /url\.pathname === "\/api\/directories" && req\.method === "POST"/, "server should expose POST /api/directories for cwd picker directory creation");
|
|
238
265
|
assert.match(css, /@media \(max-width: 720px\), \(max-device-width: 720px\), \(pointer: coarse\) and \(hover: none\)[\s\S]*?\.footer-line-tui \{[\s\S]*?flex-wrap:\s*wrap/, "mobile footer should wrap the minimal TUI-like line instead of using expanded metadata chips");
|
|
239
266
|
assert.match(css, /(?:^|\n)\s*\.side-panel-backdrop\s*\{[\s\S]*?position:\s*fixed/, "mobile side panel backdrop should be fixed overlay UI");
|
|
240
267
|
assert.match(css, /(?:^|\n)\s*\.side-panel\s*\{[\s\S]*?position:\s*fixed/, "mobile side panel should be an overlay drawer instead of stacked content");
|
|
@@ -248,7 +275,10 @@ assert.match(css, /\.native-selector-badge\.native-selector-badge-external[\s\S]
|
|
|
248
275
|
assert.match(css, /\.native-settings-grid,[\s\S]*?\.native-tree-options \{[\s\S]*?grid-template-columns:/, "native settings and tree selector options should use responsive grids");
|
|
249
276
|
assert.match(css, /\.extension-dialog\.guardrail-dialog[\s\S]*?border-color:\s*rgba\(249, 226, 175/, "guardrail dialogs should have warning-specific styling");
|
|
250
277
|
assert.match(css, /\.extension-dialog\.release-dialog[\s\S]*?width:\s*min\(64rem/, "release confirmation dialogs should have more horizontal room");
|
|
278
|
+
assert.match(css, /\.extension-dialog\.release-dialog[\s\S]*?overflow:\s*hidden/, "release confirmation dialogs should clip overflowing internal panes instead of bleeding over the transcript");
|
|
279
|
+
assert.match(css, /\.extension-dialog\.release-dialog form[\s\S]*?min-height:\s*0/, "release confirmation layout should allow inner scroll panes to shrink inside the modal");
|
|
251
280
|
assert.match(css, /\.extension-dialog\.release-dialog #dialogMessage[\s\S]*?max-height:\s*min\(56vh, 34rem\)/, "release confirmation summaries should scroll in a roomy panel");
|
|
281
|
+
assert.match(css, /\.extension-dialog\.release-dialog #dialogBody[\s\S]*?max-height:\s*min\(23rem, 34vh\)[\s\S]*?overflow:\s*auto/, "release target option lists should scroll when many packages are eligible");
|
|
252
282
|
assert.match(css, /\.release-dialog-success \{ color: var\(--ctp-green\); \}/, "release confirmation should color publish/update lines as success");
|
|
253
283
|
assert.match(css, /\.release-dialog-danger \{ color: var\(--ctp-red\); \}/, "release confirmation should color blocked/error lines as danger");
|
|
254
284
|
|
|
@@ -367,11 +397,23 @@ assert.match(app, /restoreSidePanelSectionState\(\);\nbindSidePanelSectionToggle
|
|
|
367
397
|
assert.match(app, /OPTIONAL_FEATURES_STORAGE_KEY/, "optional feature disable toggles should persist in browser storage");
|
|
368
398
|
assert.match(app, /GIT_FOOTER_WEBUI_STATUS_KEY = "git-footer-webui"/, "git footer Web UI data should be received as an extension-owned status payload");
|
|
369
399
|
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");
|
|
400
|
+
assert.match(app, /title: cleanFooterPayloadText\(value\.title, "", 4000\)/, "git footer tooltip titles should preserve long cwd paths instead of truncating at chip display length");
|
|
401
|
+
assert.match(app, /const sourceTitle = cleanFooterPayloadText\(chip\?\.title, "", 4000\)/, "git footer tooltip rendering should keep full source titles for long cwd paths");
|
|
370
402
|
assert.match(app, /function renderFooter\(\)[\s\S]*parseGitFooterWebuiPayload\(\)[\s\S]*renderGitFooterPayload\(footerPayloadWithLiveModel\(gitFooterPayload\)\)/, "detailed footer rendering should prefer the git-footer-status extension payload");
|
|
371
403
|
assert.match(app, /function footerPayloadWithLiveModel\(payload\)[\s\S]*?shortModelLabel\(currentState\.model\)[\s\S]*?footerThinkingDisplay\(\)[\s\S]*?key: "thinking", label: "effort"/, "git footer payload rendering should split model and effort chips from live Web UI state");
|
|
404
|
+
assert.match(app, /function footerContextDisplayWithAuto\(value, state = currentState\)[\s\S]*autoCompactionEnabled !== false[\s\S]*`\$\{withoutAuto\} \(auto\)`/, "context displays should append the auto-compaction indicator when enabled");
|
|
405
|
+
assert.match(app, /function footerPayloadWithLiveModel\(payload\)[\s\S]*const contextChip = \(chip\)[\s\S]*footerContextDisplayWithAuto\(chip\?\.value\)[\s\S]*if \(chip\?\.key === "context"\) return \[contextChip\(chip\)\]/, "git footer context chips should use live Web UI auto-compaction state");
|
|
372
406
|
assert.match(app, /function renderGitFooterPayload\(payload\)[\s\S]*classList\.remove\("statusbar-tui-footer"\)[\s\S]*classList\.add\("statusbar-git-footer"\)[\s\S]*payload\.main\.map\(renderGitFooterPayloadMetric\)[\s\S]*payload\.meta\.map/, "enabled git footer payload should use the styled extension chip renderer, not the default TUI line");
|
|
373
|
-
assert.match(app, /function
|
|
374
|
-
|
|
407
|
+
assert.match(app, /function ensureFooterTooltipNode\(\)[\s\S]*footer-floating-tooltip[\s\S]*document\.body\.append\(footerTooltipNode\)/, "git footer tooltips should render into a single floating viewport-level node");
|
|
408
|
+
const footerTooltipSource = app.match(/function applyFooterTooltip\(node, tooltip, options = \{\}\)[\s\S]*?\n}\n\nfunction footerMetric/)?.[0] || "";
|
|
409
|
+
assert.ok(footerTooltipSource, "git footer tooltip application source should be inspectable");
|
|
410
|
+
assert.doesNotMatch(footerTooltipSource, /node\.title/, "git footer tooltips should not set native title tooltips in addition to the styled tooltip");
|
|
411
|
+
assert.match(app, /const GIT_FOOTER_TOOLTIP_COPY = \{[\s\S]*tokens:[\s\S]*cache:[\s\S]*pi:[\s\S]*speed:[\s\S]*cost:[\s\S]*context:[\s\S]*cwd:[\s\S]*git:[\s\S]*"git-state":[\s\S]*sync:[\s\S]*changes:[\s\S]*"git-extra":[\s\S]*model:[\s\S]*thinking:/, "git footer tooltips should explain each known extension footer box");
|
|
412
|
+
assert.match(app, /function gitFooterPayloadTooltip\(chip, options = \{\}\)[\s\S]*GIT_FOOTER_TOOLTIP_COPY\[key\][\s\S]*`Current: \$\{value\}`/, "git footer tooltips should combine explanations with the current chip value");
|
|
413
|
+
assert.match(app, /function isRedundantFooterTooltipTitle\(sourceTitle, chip, value\)[\s\S]*labels\.map\(\(label\) => `\$\{label\}: \$\{value\}`\)/, "git footer tooltips should suppress duplicate label/current title lines");
|
|
414
|
+
assert.match(app, /function gitFooterTooltipAlign\(chip\)[\s\S]*\["tokens", "cwd"\][\s\S]*return "start";[\s\S]*\["model", "thinking"\][\s\S]*return "end";/, "git footer tooltip alignment should keep edge boxes readable");
|
|
415
|
+
assert.match(app, /function renderGitFooterPayloadMetric\(chip\)[\s\S]*footerMetric\(chip\.icon[\s\S]*gitFooterPayloadTooltip\(chip\)[\s\S]*tooltipAlign: gitFooterTooltipAlign\(chip\)/, "git footer main payload chips should render as styled metrics with explanatory tooltips");
|
|
416
|
+
assert.match(app, /function renderGitFooterPayloadMeta\(chip, tab\)[\s\S]*options\.title = gitFooterPayloadTooltip\(chip, \{ action \}\)[\s\S]*options\.tooltipAlign = gitFooterTooltipAlign\(chip\)[\s\S]*footerMeta\(chip\.label, chip\.value, footerMetaClassForPayload\(chip\)/, "git footer meta payload chips should render as styled metadata with explanatory tooltips");
|
|
375
417
|
assert.match(app, /let latestStats = null/, "default footer should retain session stats for token and context display");
|
|
376
418
|
assert.match(app, /async function refreshStats\(tabContext = activeTabContext\(\)\)[\s\S]*api\("\/api\/stats"/, "default footer should fetch session stats");
|
|
377
419
|
assert.match(app, /function renderMinimalFooter\(\)[\s\S]*stats: fallbackFooterStats\(\)/, "minimal default footer should include token, cost, and context stats");
|
|
@@ -568,7 +610,7 @@ for (const command of ["resume", "reload", "name", "clone", "settings", "export"
|
|
|
568
610
|
const id = command.replace(/^./, (letter) => letter.toUpperCase());
|
|
569
611
|
assert.match(app, new RegExp(`options${id}Button\\.addEventListener\\("click", \\(\\) => runNativeCommandMenu\\("\\/${command}"\\)\\)`), `Options menu should launch /${command}`);
|
|
570
612
|
}
|
|
571
|
-
assert.match(app, /async function sendPrompt\(kind = "prompt", explicitMessage\)/, "prompt sending should accept direct messages that bypass the input field");
|
|
613
|
+
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");
|
|
572
614
|
assert.match(app, /const rawMessage = usesPromptInput \? elements\.promptInput\.value : explicitMessage/, "direct prompt sends should not read the input textarea");
|
|
573
615
|
assert.match(app, /if \(usesPromptInput\) \{[\s\S]*?if \(targetStillActive\) \{[\s\S]*?elements\.promptInput\.value = "";/, "direct prompt sends should preserve the input textarea draft");
|
|
574
616
|
assert.match(app, /make\("button", "command-item"\)[\s\S]*?sendPrompt\("prompt", `\/\$\{command\.name\}`\)/, "side-panel command clicks should send the slash command directly");
|
|
@@ -595,6 +637,12 @@ assert.match(app, /cycleModelFromShortcut\(event\.shiftKey \? "backward" : "forw
|
|
|
595
637
|
assert.match(app, /cycleThinkingFromShortcut\(\)/, "Shift+Tab should cycle thinking level");
|
|
596
638
|
assert.match(app, /setToolOutputGloballyExpanded\(!toolOutputGloballyExpanded, \{ announce: true \}\)/, "Ctrl+O should toggle global tool expansion");
|
|
597
639
|
assert.match(app, /function restoreQueuedMessagesToComposerFromShortcut\(\)/, "Alt+Up should restore queued steering\/follow-up text into the composer");
|
|
640
|
+
assert.match(app, /const PROMPT_LIST_STORAGE_KEY = "pi-webui-prompt-lists"/, "frontend should persist prompt lists in browser storage");
|
|
641
|
+
assert.match(app, /async function runPromptList\(prompts,[\s\S]*sendPrompt\("prompt", listPrompts\[0\]/, "prompt-list runner should send the first item as the start prompt");
|
|
642
|
+
assert.match(app, /for \(const prompt of listPrompts\.slice\(1\)\)[\s\S]*sendPrompt\("follow-up", prompt/, "prompt-list runner should send remaining items as follow-ups");
|
|
643
|
+
assert.match(app, /async function runDisplayedPromptList\(\)[\s\S]*const saved = saveDisplayedPromptList\(\)[\s\S]*runPromptList\(saved\.prompts/, "Run List should persist the displayed prompt list before running it");
|
|
644
|
+
assert.match(app, /function deleteSelectedPromptList\(\)[\s\S]*window\.confirm[\s\S]*deleteStoredPromptList/, "prompt-list deletion should confirm before deleting saved browser storage");
|
|
645
|
+
assert.match(app, /function loadSelectedPromptListIntoEditor\(\)[\s\S]*loadPromptListIntoEditor\(list, \{ updateLoaded: true \}\)[\s\S]*elements\.promptListDialog\?\.close\(\)/, "loading a saved prompt list from the popup should close the dialog");
|
|
598
646
|
assert.match(app, /event\.altKey && key === "ArrowUp"[\s\S]*?restoreQueuedMessagesToComposerFromShortcut\(\)/, "Alt+Up should be handled by native shortcut routing");
|
|
599
647
|
assert.match(app, /clearPromptFromShortcut\(\)/, "Ctrl+C should clear only through a guarded prompt helper");
|
|
600
648
|
assert.match(app, /if \(event\.defaultPrevented\) return;/, "textarea keydown handling should respect app-level shortcut interception");
|
|
@@ -606,6 +654,10 @@ assert.match(app, /function updateStickyUserPromptButton\(/, "last user prompt h
|
|
|
606
654
|
assert.match(app, /function jumpToStickyUserPrompt\(/, "last user prompt header should jump to its source message");
|
|
607
655
|
assert.match(app, /data-user-prompt/, "user prompt messages should be marked for sticky prompt navigation");
|
|
608
656
|
assert.match(app, /function resetChatOutput\(\)[\s\S]*?stickyUserPromptButton/, "chat rerenders should preserve the sticky user prompt control inside the transcript scroller");
|
|
657
|
+
assert.match(app, /function nextQueuedFollowUpPrompt\(tabId = activeTabId\)/, "sticky prompt header should derive the next queued follow-up prompt for display");
|
|
658
|
+
assert.match(app, /sticky-user-follow-up-prompt[\s\S]*Next follow-up/, "sticky prompt header should render the next follow-up under the last user prompt");
|
|
659
|
+
assert.match(app, /function renderQueue\(event\)[\s\S]*updateStickyUserPromptButton\(\)/, "queue updates should refresh the sticky last-user-prompt header");
|
|
660
|
+
assert.match(css, /\.sticky-user-follow-up-prompt \{[\s\S]*?grid-column:\s*1 \/ -1/, "next follow-up should render below the sticky last-user-prompt row");
|
|
609
661
|
assert.match(app, /LAST_USER_PROMPT_STORAGE_KEY/, "last user prompt should be cached so compaction cannot remove the sticky prompt preview");
|
|
610
662
|
assert.match(app, /function syncLastUserPromptFromMessages\(messages = latestMessages\)/, "message refresh should preserve the latest user prompt across compacted transcripts");
|
|
611
663
|
assert.match(app, /dataset\.compacted/, "sticky prompt should expose a compacted fallback state when its source message was summarized away");
|
|
@@ -614,6 +666,8 @@ assert.match(app, /function setComposerActionsOpen\(/, "mobile composer actions
|
|
|
614
666
|
assert.match(app, /function setMobileTabsExpanded\(/, "mobile tab strip should be JS-toggleable");
|
|
615
667
|
assert.match(app, /let openTerminalTabGroupKey = null/, "frontend should track the open terminal tab group across tab bar rerenders");
|
|
616
668
|
assert.match(app, /function updateTerminalTabGroupOpenState\(\)/, "frontend should be able to reapply open terminal tab group state after rerenders");
|
|
669
|
+
assert.match(app, /classList\.toggle\("terminal-tabs-dense", tabs\.length >= 10\)/, "frontend should enable dense tab layout before tab names become unreadable");
|
|
670
|
+
assert.match(app, /appendTerminalTabContent\(button, \{ title: activeTitle,[\s\S]*?count: groupTabs\.length \}\)/, "group buttons should show the active terminal name instead of only the cwd label");
|
|
617
671
|
assert.match(app, /wrapper\.addEventListener\("pointerenter", \(\) => setOpenTerminalTabGroup\(group\.key\)\)/, "terminal tab groups should mark themselves open while hovered");
|
|
618
672
|
assert.match(app, /if \(openTerminalTabGroupKey\) \{\n\s+scheduleRefreshTabs\(600\);/, "tab polling should defer full tab refreshes while a group menu is open");
|
|
619
673
|
assert.match(app, /function shouldRenderTerminalTabGroup\(group, groupCount\) \{\n\s+return groupCount > 1 && group\.tabs\.length > 1 && Boolean\(group\.cwd\);\n\}/, "terminal tabs should only collapse cwd groups when multiple groups are available");
|
|
@@ -735,6 +789,8 @@ assert.match(server, /if \(sessionFile && !options\.noSession\) piArgs\.push\("-
|
|
|
735
789
|
assert.doesNotMatch(server, /args\.push\("--name"/, "Web UI tab titles should not be forwarded as Pi CLI --name flags because older bundled Pi CLIs reject them");
|
|
736
790
|
assert.match(server, /const closedRestorableTabs = \[\]/, "server should track recently closed tabs separately from restart restore descriptors");
|
|
737
791
|
assert.match(server, /async function closeTab\(id\)[\s\S]*?rememberClosedRestorableTab\(tab, restorableState\)/, "closing a tab should capture its session before stopping RPC for detailed closed-tab status");
|
|
792
|
+
assert.match(server, /async function restorableTabsForRestart\(\)[\s\S]*?return mergeRestorableTabDescriptors\(liveDescriptors\)/, "server restart should restore only currently open tabs");
|
|
793
|
+
assert.doesNotMatch(server, /return mergeRestorableTabDescriptors\(liveDescriptors,\s*closedRestorableTabs\)/, "server restart should not restore closed tabs");
|
|
738
794
|
assert.match(server, /async function closeTabs\(ids\)[\s\S]*?if \(targetTabs\.length >= tabs\.size\) \{\n\s+await createTab/, "bulk tab close should create a replacement before closing every current tab");
|
|
739
795
|
assert.match(server, /url\.pathname === "\/api\/tabs\/close" && req\.method === "POST"[\s\S]*?closedIds: closed\.map\(\(tab\) => tab\.id\)/, "server should expose a bulk close-tabs endpoint");
|
|
740
796
|
assert.match(server, /function rememberTabState\(tab, state\)/, "server should cache last-known tab state for restart-safe session restoration");
|