@firstpick/pi-package-webui 0.2.4 → 0.2.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/package.json +1 -1
- package/public/app.js +120 -17
- package/public/index.html +23 -2
- package/public/service-worker.js +1 -1
- package/public/styles.css +51 -0
- package/tests/mobile-static.test.mjs +23 -3
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -43,6 +43,10 @@ const elements = {
|
|
|
43
43
|
publishMenu: $("#publishMenu"),
|
|
44
44
|
releaseNpmButton: $("#releaseNpmButton"),
|
|
45
45
|
releaseAurButton: $("#releaseAurButton"),
|
|
46
|
+
nativeCommandMenuButton: $("#nativeCommandMenuButton"),
|
|
47
|
+
nativeCommandMenu: $("#nativeCommandMenu"),
|
|
48
|
+
nativeSkillsButton: $("#nativeSkillsButton"),
|
|
49
|
+
nativeToolsButton: $("#nativeToolsButton"),
|
|
46
50
|
gitWorkflowPanel: $("#gitWorkflowPanel"),
|
|
47
51
|
gitWorkflowTitle: $("#gitWorkflowTitle"),
|
|
48
52
|
gitWorkflowHint: $("#gitWorkflowHint"),
|
|
@@ -143,6 +147,7 @@ let pathFastPicksReady = false;
|
|
|
143
147
|
let pathFastPicksLoadPromise = null;
|
|
144
148
|
let mobileTabsExpanded = false;
|
|
145
149
|
let openTerminalTabGroupKey = null;
|
|
150
|
+
let nativeCommandMenuOpen = false;
|
|
146
151
|
let availableCommands = [];
|
|
147
152
|
let rawAvailableCommands = [];
|
|
148
153
|
let commandSuggestions = [];
|
|
@@ -361,6 +366,8 @@ const OPTIONAL_COMMAND_FEATURES = new Map([
|
|
|
361
366
|
["git-staged-msg", "gitWorkflow"],
|
|
362
367
|
["release-npm", "releaseNpm"],
|
|
363
368
|
["release-aur", "releaseAur"],
|
|
369
|
+
["skills", "tuiSkillsCommand"],
|
|
370
|
+
["tools", "tuiToolsCommand"],
|
|
364
371
|
["stats", "statsCommand"],
|
|
365
372
|
["git-footer-refresh", "gitFooterStatus"],
|
|
366
373
|
["todo-progress-status", "todoProgressWidget"],
|
|
@@ -6943,6 +6950,13 @@ function setPublishMenuOpen(open) {
|
|
|
6943
6950
|
elements.publishButton.parentElement?.classList.toggle("open", publishMenuOpen);
|
|
6944
6951
|
}
|
|
6945
6952
|
|
|
6953
|
+
function setNativeCommandMenuOpen(open) {
|
|
6954
|
+
nativeCommandMenuOpen = !!open;
|
|
6955
|
+
elements.nativeCommandMenuButton.setAttribute("aria-expanded", nativeCommandMenuOpen ? "true" : "false");
|
|
6956
|
+
elements.nativeCommandMenuButton.classList.toggle("menu-open", nativeCommandMenuOpen);
|
|
6957
|
+
elements.nativeCommandMenuButton.parentElement?.classList.toggle("open", nativeCommandMenuOpen);
|
|
6958
|
+
}
|
|
6959
|
+
|
|
6946
6960
|
function optionalFeatureIdForCommand(name) {
|
|
6947
6961
|
if (OPTIONAL_COMMAND_FEATURES.has(name)) return OPTIONAL_COMMAND_FEATURES.get(name);
|
|
6948
6962
|
if (name === "release-toggle" || name === "release-abort" || name === "release-npm-logs") return "releaseNpm";
|
|
@@ -7096,6 +7110,18 @@ function renderOptionalFeatureControls() {
|
|
|
7096
7110
|
);
|
|
7097
7111
|
if (!hasPublishWorkflow && publishMenuOpen) setPublishMenuOpen(false);
|
|
7098
7112
|
|
|
7113
|
+
const hasNativeCommandMenu = isOptionalFeatureEnabled("tuiSkillsCommand") || isOptionalFeatureEnabled("tuiToolsCommand");
|
|
7114
|
+
elements.nativeSkillsButton.hidden = !isOptionalFeatureEnabled("tuiSkillsCommand");
|
|
7115
|
+
elements.nativeToolsButton.hidden = !isOptionalFeatureEnabled("tuiToolsCommand");
|
|
7116
|
+
const nativeCommandMenuContainer = elements.nativeCommandMenuButton.parentElement;
|
|
7117
|
+
if (nativeCommandMenuContainer) nativeCommandMenuContainer.hidden = !hasNativeCommandMenu;
|
|
7118
|
+
setOptionalControlState(
|
|
7119
|
+
elements.nativeCommandMenuButton,
|
|
7120
|
+
hasNativeCommandMenu,
|
|
7121
|
+
"Slash command menu unavailable: enable/install TUI Skills command and/or TUI Tools command in Optional features.",
|
|
7122
|
+
);
|
|
7123
|
+
if (!hasNativeCommandMenu && nativeCommandMenuOpen) setNativeCommandMenuOpen(false);
|
|
7124
|
+
|
|
7099
7125
|
renderOptionalFeaturePanel();
|
|
7100
7126
|
}
|
|
7101
7127
|
|
|
@@ -7159,6 +7185,23 @@ function runPublishWorkflow(command) {
|
|
|
7159
7185
|
sendPrompt("prompt", command);
|
|
7160
7186
|
}
|
|
7161
7187
|
|
|
7188
|
+
async function runNativeCommandMenu(command) {
|
|
7189
|
+
setComposerActionsOpen(false);
|
|
7190
|
+
setPublishMenuOpen(false);
|
|
7191
|
+
setNativeCommandMenuOpen(false);
|
|
7192
|
+
const commandName = String(command || "").replace(/^\//, "").split(/\s+/)[0].toLowerCase();
|
|
7193
|
+
const featureId = optionalFeatureIdForCommand(commandName);
|
|
7194
|
+
if ((featureId && !isOptionalFeatureEnabled(featureId)) || !hasAvailableCommand(commandName)) {
|
|
7195
|
+
const tabContext = activeTabContext();
|
|
7196
|
+
addEvent(commandUnavailableMessage(commandName), "warn");
|
|
7197
|
+
refreshCommands(tabContext).catch((error) => {
|
|
7198
|
+
if (isCurrentTabContext(tabContext)) addEvent(error.message || String(error), "error");
|
|
7199
|
+
});
|
|
7200
|
+
return;
|
|
7201
|
+
}
|
|
7202
|
+
await handleNativeSlashSelectorCommand(command);
|
|
7203
|
+
}
|
|
7204
|
+
|
|
7162
7205
|
function slashCommandName(message) {
|
|
7163
7206
|
const match = String(message || "").trim().match(/^\/([^\s]+)$/);
|
|
7164
7207
|
return match ? match[1].toLowerCase() : "";
|
|
@@ -7212,7 +7255,8 @@ function renderNativeLoading(label = "Loading…") {
|
|
|
7212
7255
|
function nativeSelectorMatches(item, query) {
|
|
7213
7256
|
if (!query) return true;
|
|
7214
7257
|
const needle = query.toLowerCase();
|
|
7215
|
-
|
|
7258
|
+
const tags = Array.isArray(item.tags) ? item.tags.map((tag) => tag?.label) : [];
|
|
7259
|
+
return [item.label, item.description, item.meta, item.badge, ...tags]
|
|
7216
7260
|
.filter(Boolean)
|
|
7217
7261
|
.some((value) => String(value).toLowerCase().includes(needle));
|
|
7218
7262
|
}
|
|
@@ -7245,6 +7289,10 @@ function renderNativeSelectorItems(items, { emptyText = "No choices.", onSelect,
|
|
|
7245
7289
|
}
|
|
7246
7290
|
title.append(badge);
|
|
7247
7291
|
}
|
|
7292
|
+
for (const tag of Array.isArray(item.tags) ? item.tags : []) {
|
|
7293
|
+
if (!tag?.label) continue;
|
|
7294
|
+
title.append(make("span", `native-selector-badge${tag.className ? ` ${tag.className}` : ""}`, tag.label));
|
|
7295
|
+
}
|
|
7248
7296
|
const detail = make("span", "native-selector-detail", item.description || "");
|
|
7249
7297
|
const meta = make("span", "native-selector-meta", item.meta || "");
|
|
7250
7298
|
button.append(title);
|
|
@@ -7583,6 +7631,12 @@ function nativeResourceSourceLabel(resource) {
|
|
|
7583
7631
|
return [info.source, info.scope, info.origin].filter(Boolean).join(" · ") || resource?.location || "loaded resource";
|
|
7584
7632
|
}
|
|
7585
7633
|
|
|
7634
|
+
function nativeToolOriginTag(resource) {
|
|
7635
|
+
return resource?.sourceInfo?.source === "builtin"
|
|
7636
|
+
? { label: "Pi Native", className: "native-selector-badge-pi-native" }
|
|
7637
|
+
: { label: "External", className: "native-selector-badge-external" };
|
|
7638
|
+
}
|
|
7639
|
+
|
|
7586
7640
|
function nativeResourceCounts(resources) {
|
|
7587
7641
|
const disabled = resources.filter((resource) => resource.enabled === false).length;
|
|
7588
7642
|
return { total: resources.length, disabled, enabled: resources.length - disabled };
|
|
@@ -7594,19 +7648,23 @@ function nativeResourceFilterMatches(resource, filter) {
|
|
|
7594
7648
|
return true;
|
|
7595
7649
|
}
|
|
7596
7650
|
|
|
7597
|
-
function renderNativeResourceToggles(resources, { savingName, filter = "all", onToggle } = {}) {
|
|
7651
|
+
function renderNativeResourceToggles(resources, { savingName, filter = "all", onToggle, getResourceTag } = {}) {
|
|
7598
7652
|
const filteredResources = resources.filter((resource) => nativeResourceFilterMatches(resource, filter));
|
|
7599
7653
|
const counts = nativeResourceCounts(resources);
|
|
7600
|
-
const items = filteredResources.map((resource) =>
|
|
7601
|
-
|
|
7602
|
-
|
|
7603
|
-
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
|
|
7654
|
+
const items = filteredResources.map((resource) => {
|
|
7655
|
+
const resourceTag = getResourceTag?.(resource);
|
|
7656
|
+
return {
|
|
7657
|
+
id: resource.name,
|
|
7658
|
+
label: resource.name,
|
|
7659
|
+
description: resource.description || "No description provided.",
|
|
7660
|
+
meta: nativeResourceSourceLabel(resource),
|
|
7661
|
+
badge: resource.enabled === false ? "disabled" : "enabled",
|
|
7662
|
+
badgeClass: resource.enabled === false ? "disabled native-selector-badge-disabled" : "enabled native-selector-badge-enabled",
|
|
7663
|
+
tags: resourceTag ? [resourceTag] : [],
|
|
7664
|
+
disabled: Boolean(savingName),
|
|
7665
|
+
resource,
|
|
7666
|
+
};
|
|
7667
|
+
});
|
|
7610
7668
|
const filterLabel = filter === "enabled" ? "enabled" : filter === "disabled" ? "disabled" : "all";
|
|
7611
7669
|
renderNativeSelectorItems(items, {
|
|
7612
7670
|
emptyText: `No ${filterLabel} entries match this filter.`,
|
|
@@ -7631,7 +7689,7 @@ function renderNativeResourceFilterActions(filter, setFilter, render) {
|
|
|
7631
7689
|
}
|
|
7632
7690
|
|
|
7633
7691
|
async function openNativeToolsSelector() {
|
|
7634
|
-
openNativeCommandDialog({ title: "
|
|
7692
|
+
openNativeCommandDialog({ title: "Tools Setup", message: "Enable or disable tools for the active Pi tab. Changes apply to the next model turn and persist on this session branch.", searchPlaceholder: "Filter tools…" });
|
|
7635
7693
|
renderNativeLoading("Loading tools…");
|
|
7636
7694
|
let tools = [];
|
|
7637
7695
|
let savingName = "";
|
|
@@ -7640,6 +7698,7 @@ async function openNativeToolsSelector() {
|
|
|
7640
7698
|
renderNativeResourceToggles(tools, {
|
|
7641
7699
|
savingName,
|
|
7642
7700
|
filter,
|
|
7701
|
+
getResourceTag: nativeToolOriginTag,
|
|
7643
7702
|
onToggle: async (tool) => {
|
|
7644
7703
|
if (!tool || savingName) return;
|
|
7645
7704
|
const enabledTools = new Set(tools.filter((item) => item.enabled !== false).map((item) => item.name));
|
|
@@ -7674,7 +7733,7 @@ async function openNativeToolsSelector() {
|
|
|
7674
7733
|
}
|
|
7675
7734
|
|
|
7676
7735
|
async function openNativeSkillsSelector() {
|
|
7677
|
-
openNativeCommandDialog({ title: "
|
|
7736
|
+
openNativeCommandDialog({ title: "Skills Setup", message: "Enable or disable skills for automatic model invocation in the active Pi tab. Disabled skills are removed from the system prompt and their /skill:name commands are blocked by Web UI.", searchPlaceholder: "Filter skills…" });
|
|
7678
7737
|
renderNativeLoading("Loading skills…");
|
|
7679
7738
|
let skills = [];
|
|
7680
7739
|
let savingName = "";
|
|
@@ -7730,6 +7789,15 @@ function openNativeAuthInfo(mode) {
|
|
|
7730
7789
|
async function handleNativeSlashSelectorCommand(message, { usesPromptInput = false } = {}) {
|
|
7731
7790
|
const name = slashCommandName(message);
|
|
7732
7791
|
if (!NATIVE_SELECTOR_COMMANDS.has(name)) return false;
|
|
7792
|
+
const featureId = optionalFeatureIdForCommand(name);
|
|
7793
|
+
if (featureId && !isOptionalFeatureEnabled(featureId)) {
|
|
7794
|
+
const tabContext = activeTabContext();
|
|
7795
|
+
addEvent(commandUnavailableMessage(name), "warn");
|
|
7796
|
+
refreshCommands(tabContext).catch((error) => {
|
|
7797
|
+
if (isCurrentTabContext(tabContext)) addEvent(error.message || String(error), "error");
|
|
7798
|
+
});
|
|
7799
|
+
return true;
|
|
7800
|
+
}
|
|
7733
7801
|
setComposerActionsOpen(false);
|
|
7734
7802
|
hideCommandSuggestions();
|
|
7735
7803
|
if (usesPromptInput) {
|
|
@@ -9213,7 +9281,7 @@ function showNextDialog() {
|
|
|
9213
9281
|
if (isGuardrailDialog && /^Block$/i.test(optionLabel)) button.classList.add("guardrail-safe-action");
|
|
9214
9282
|
if (isGuardrailDialog && /^Allow/i.test(optionLabel)) button.classList.add("guardrail-allow-action");
|
|
9215
9283
|
if (isReleaseDialog && /^(?:Yes|All eligible packages\b|Publish selected packages \([1-9]\d*\))/.test(optionLabel)) button.classList.add("primary", "release-publish-action");
|
|
9216
|
-
if (isReleaseDialog && /^Publish selected packages
|
|
9284
|
+
if (isReleaseDialog && /^Publish selected packages$/i.test(optionLabel)) button.classList.add("release-publish-disabled-action");
|
|
9217
9285
|
if (isReleaseDialog && /^\[x\]/.test(optionLabel)) button.classList.add("release-target-option", "release-target-selected");
|
|
9218
9286
|
if (isReleaseDialog && /^\[ \]/.test(optionLabel)) button.classList.add("release-target-option");
|
|
9219
9287
|
if (isReleaseDialog && /^(?:No|Cancel)$/i.test(optionLabel)) button.classList.add("release-cancel-action");
|
|
@@ -9515,18 +9583,46 @@ elements.gitWorkflowButton.addEventListener("click", () => {
|
|
|
9515
9583
|
});
|
|
9516
9584
|
const publishMenuContainer = elements.publishButton.parentElement;
|
|
9517
9585
|
elements.publishButton.addEventListener("click", () => {
|
|
9586
|
+
setNativeCommandMenuOpen(false);
|
|
9587
|
+
setPublishMenuOpen(true);
|
|
9588
|
+
});
|
|
9589
|
+
publishMenuContainer?.addEventListener("pointerenter", () => {
|
|
9590
|
+
setNativeCommandMenuOpen(false);
|
|
9518
9591
|
setPublishMenuOpen(true);
|
|
9519
9592
|
});
|
|
9520
|
-
publishMenuContainer?.addEventListener("pointerenter", () => setPublishMenuOpen(true));
|
|
9521
9593
|
publishMenuContainer?.addEventListener("pointerleave", () => setPublishMenuOpen(false));
|
|
9522
|
-
publishMenuContainer?.addEventListener("focusin", () =>
|
|
9594
|
+
publishMenuContainer?.addEventListener("focusin", () => {
|
|
9595
|
+
setNativeCommandMenuOpen(false);
|
|
9596
|
+
setPublishMenuOpen(true);
|
|
9597
|
+
});
|
|
9523
9598
|
publishMenuContainer?.addEventListener("focusout", () => {
|
|
9524
9599
|
setTimeout(() => {
|
|
9525
9600
|
if (!publishMenuContainer?.contains(document.activeElement)) setPublishMenuOpen(false);
|
|
9526
9601
|
}, 0);
|
|
9527
9602
|
});
|
|
9603
|
+
const nativeCommandMenuContainer = elements.nativeCommandMenuButton.parentElement;
|
|
9604
|
+
elements.nativeCommandMenuButton.addEventListener("click", () => {
|
|
9605
|
+
setPublishMenuOpen(false);
|
|
9606
|
+
setNativeCommandMenuOpen(true);
|
|
9607
|
+
});
|
|
9608
|
+
nativeCommandMenuContainer?.addEventListener("pointerenter", () => {
|
|
9609
|
+
setPublishMenuOpen(false);
|
|
9610
|
+
setNativeCommandMenuOpen(true);
|
|
9611
|
+
});
|
|
9612
|
+
nativeCommandMenuContainer?.addEventListener("pointerleave", () => setNativeCommandMenuOpen(false));
|
|
9613
|
+
nativeCommandMenuContainer?.addEventListener("focusin", () => {
|
|
9614
|
+
setPublishMenuOpen(false);
|
|
9615
|
+
setNativeCommandMenuOpen(true);
|
|
9616
|
+
});
|
|
9617
|
+
nativeCommandMenuContainer?.addEventListener("focusout", () => {
|
|
9618
|
+
setTimeout(() => {
|
|
9619
|
+
if (!nativeCommandMenuContainer?.contains(document.activeElement)) setNativeCommandMenuOpen(false);
|
|
9620
|
+
}, 0);
|
|
9621
|
+
});
|
|
9528
9622
|
elements.releaseNpmButton.addEventListener("click", () => runPublishWorkflow("/release-npm"));
|
|
9529
9623
|
elements.releaseAurButton.addEventListener("click", () => runPublishWorkflow("/release-aur"));
|
|
9624
|
+
elements.nativeSkillsButton.addEventListener("click", () => runNativeCommandMenu("/skills"));
|
|
9625
|
+
elements.nativeToolsButton.addEventListener("click", () => runNativeCommandMenu("/tools"));
|
|
9530
9626
|
elements.gitWorkflowCancelButton.addEventListener("click", () => cancelGitWorkflow());
|
|
9531
9627
|
elements.nativeCommandDialog.addEventListener("close", () => {
|
|
9532
9628
|
elements.nativeCommandSearch.oninput = null;
|
|
@@ -9727,6 +9823,9 @@ document.addEventListener("pointerdown", (event) => {
|
|
|
9727
9823
|
if (publishMenuOpen && !event.target?.closest?.(".composer-publish-menu")) {
|
|
9728
9824
|
setPublishMenuOpen(false);
|
|
9729
9825
|
}
|
|
9826
|
+
if (nativeCommandMenuOpen && !event.target?.closest?.(".composer-native-command-menu")) {
|
|
9827
|
+
setNativeCommandMenuOpen(false);
|
|
9828
|
+
}
|
|
9730
9829
|
if (document.body.classList.contains("mobile-tabs-expanded") && !elements.tabBar.contains(event.target) && !elements.terminalTabsToggleButton.contains(event.target)) {
|
|
9731
9830
|
setMobileTabsExpanded(false);
|
|
9732
9831
|
}
|
|
@@ -9813,6 +9912,10 @@ window.addEventListener("keydown", (event) => {
|
|
|
9813
9912
|
setPublishMenuOpen(false);
|
|
9814
9913
|
return;
|
|
9815
9914
|
}
|
|
9915
|
+
if (nativeCommandMenuOpen) {
|
|
9916
|
+
setNativeCommandMenuOpen(false);
|
|
9917
|
+
return;
|
|
9918
|
+
}
|
|
9816
9919
|
if (document.body.classList.contains("composer-actions-open")) {
|
|
9817
9920
|
setComposerActionsOpen(false);
|
|
9818
9921
|
return;
|
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=23" />
|
|
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">
|
|
@@ -133,6 +133,27 @@
|
|
|
133
133
|
</button>
|
|
134
134
|
</div>
|
|
135
135
|
</div>
|
|
136
|
+
<div class="composer-publish-menu composer-native-command-menu">
|
|
137
|
+
<button
|
|
138
|
+
id="nativeCommandMenuButton"
|
|
139
|
+
class="composer-icon-button composer-publish-button composer-native-command-button"
|
|
140
|
+
type="button"
|
|
141
|
+
title="Open skills and tools commands"
|
|
142
|
+
aria-label="Open /skills and /tools commands"
|
|
143
|
+
aria-haspopup="menu"
|
|
144
|
+
aria-expanded="false"
|
|
145
|
+
aria-controls="nativeCommandMenu"
|
|
146
|
+
data-tooltip="Skills/tools setup: open skill or tool setup."
|
|
147
|
+
><svg class="composer-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 5h16v14H4z" fill="none" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/><path d="m7 10 2.5 2L7 14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 15h5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></button>
|
|
148
|
+
<div id="nativeCommandMenu" class="composer-publish-menu-panel composer-native-command-menu-panel" role="menu" aria-label="Skills and tools setup">
|
|
149
|
+
<button id="nativeSkillsButton" class="composer-publish-menu-item composer-native-command-menu-item" type="button" role="menuitem" data-command="/skills">
|
|
150
|
+
<span>Skills Setup</span>
|
|
151
|
+
</button>
|
|
152
|
+
<button id="nativeToolsButton" class="composer-publish-menu-item composer-native-command-menu-item" type="button" role="menuitem" data-command="/tools">
|
|
153
|
+
<span>Tools Setup</span>
|
|
154
|
+
</button>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
136
157
|
</div>
|
|
137
158
|
<div class="spacer"></div>
|
|
138
159
|
<button
|
|
@@ -364,6 +385,6 @@
|
|
|
364
385
|
</form>
|
|
365
386
|
</dialog>
|
|
366
387
|
|
|
367
|
-
<script type="module" src="/app.js?v=
|
|
388
|
+
<script type="module" src="/app.js?v=23"></script>
|
|
368
389
|
</body>
|
|
369
390
|
</html>
|
package/public/service-worker.js
CHANGED
package/public/styles.css
CHANGED
|
@@ -3092,6 +3092,22 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3092
3092
|
.composer-publish-button.menu-open {
|
|
3093
3093
|
background: linear-gradient(120deg, var(--ctp-peach), var(--ctp-yellow), var(--ctp-mauve));
|
|
3094
3094
|
}
|
|
3095
|
+
.composer-native-command-button {
|
|
3096
|
+
color: var(--ctp-mauve);
|
|
3097
|
+
border-color: rgba(203, 166, 247, 0.40);
|
|
3098
|
+
background:
|
|
3099
|
+
linear-gradient(120deg, rgba(203, 166, 247, 0.15), rgba(137, 180, 250, 0.10)),
|
|
3100
|
+
linear-gradient(180deg, rgba(var(--ctp-surface-rgb), 0.88), rgba(var(--ctp-crust-rgb), 0.88));
|
|
3101
|
+
}
|
|
3102
|
+
.composer-native-command-button:hover,
|
|
3103
|
+
.composer-native-command-button.menu-open {
|
|
3104
|
+
color: #11111b;
|
|
3105
|
+
background: linear-gradient(120deg, var(--ctp-mauve), var(--ctp-blue), var(--ctp-teal));
|
|
3106
|
+
border-color: transparent;
|
|
3107
|
+
}
|
|
3108
|
+
.composer-native-command-menu.open .composer-native-command-button {
|
|
3109
|
+
border-color: rgba(203, 166, 247, 0.62);
|
|
3110
|
+
}
|
|
3095
3111
|
.composer-publish-menu-panel {
|
|
3096
3112
|
position: absolute;
|
|
3097
3113
|
z-index: 100;
|
|
@@ -3141,6 +3157,20 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3141
3157
|
background: linear-gradient(120deg, var(--ctp-peach), var(--ctp-yellow));
|
|
3142
3158
|
box-shadow: 0 0 1rem rgba(250, 179, 135, 0.20);
|
|
3143
3159
|
}
|
|
3160
|
+
.composer-native-command-menu-item {
|
|
3161
|
+
color: var(--ctp-mauve);
|
|
3162
|
+
border-color: rgba(203, 166, 247, 0.32);
|
|
3163
|
+
background:
|
|
3164
|
+
linear-gradient(120deg, rgba(203, 166, 247, 0.12), rgba(137, 180, 250, 0.08)),
|
|
3165
|
+
var(--ctp-crust);
|
|
3166
|
+
}
|
|
3167
|
+
.composer-native-command-menu-item:hover,
|
|
3168
|
+
.composer-native-command-menu-item:focus-visible {
|
|
3169
|
+
color: #11111b;
|
|
3170
|
+
border-color: transparent;
|
|
3171
|
+
background: linear-gradient(120deg, var(--ctp-mauve), var(--ctp-blue));
|
|
3172
|
+
box-shadow: 0 0 1rem rgba(203, 166, 247, 0.20);
|
|
3173
|
+
}
|
|
3144
3174
|
.composer button[data-tooltip] {
|
|
3145
3175
|
position: relative;
|
|
3146
3176
|
}
|
|
@@ -3213,6 +3243,15 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3213
3243
|
.composer-input-row button[data-tooltip].tooltip-open::before {
|
|
3214
3244
|
transform: translate(-1.2rem, 0) rotate(45deg);
|
|
3215
3245
|
}
|
|
3246
|
+
.composer-publish-menu:hover > .composer-publish-button[data-tooltip]::before,
|
|
3247
|
+
.composer-publish-menu:hover > .composer-publish-button[data-tooltip]::after,
|
|
3248
|
+
.composer-publish-menu:focus-within > .composer-publish-button[data-tooltip]::before,
|
|
3249
|
+
.composer-publish-menu:focus-within > .composer-publish-button[data-tooltip]::after,
|
|
3250
|
+
.composer-publish-menu.open > .composer-publish-button[data-tooltip]::before,
|
|
3251
|
+
.composer-publish-menu.open > .composer-publish-button[data-tooltip]::after {
|
|
3252
|
+
display: none !important;
|
|
3253
|
+
opacity: 0 !important;
|
|
3254
|
+
}
|
|
3216
3255
|
|
|
3217
3256
|
.details {
|
|
3218
3257
|
display: grid;
|
|
@@ -3489,6 +3528,18 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3489
3528
|
color: #ff9f43 !important;
|
|
3490
3529
|
background: rgba(255, 159, 67, 0.10);
|
|
3491
3530
|
}
|
|
3531
|
+
.native-selector-badge.native-selector-badge-pi-native,
|
|
3532
|
+
.native-command-body:has(.native-resource-summary) .native-selector-badge.native-selector-badge-pi-native {
|
|
3533
|
+
border-color: rgba(137, 180, 250, 0.48);
|
|
3534
|
+
color: var(--ctp-blue);
|
|
3535
|
+
background: rgba(137, 180, 250, 0.10);
|
|
3536
|
+
}
|
|
3537
|
+
.native-selector-badge.native-selector-badge-external,
|
|
3538
|
+
.native-command-body:has(.native-resource-summary) .native-selector-badge.native-selector-badge-external {
|
|
3539
|
+
border-color: rgba(203, 166, 247, 0.46);
|
|
3540
|
+
color: var(--ctp-mauve);
|
|
3541
|
+
background: rgba(203, 166, 247, 0.10);
|
|
3542
|
+
}
|
|
3492
3543
|
.native-selector-detail,
|
|
3493
3544
|
.native-selector-meta,
|
|
3494
3545
|
.native-settings-hint {
|
|
@@ -98,6 +98,10 @@ assert.doesNotMatch(html, /class="side-panel-controls"[\s\S]*id="abortButton"/,
|
|
|
98
98
|
assert.match(html, /id="publishButton"[\s\S]*?aria-controls="publishMenu"/, "composer should expose a Publish workflow menu button");
|
|
99
99
|
assert.match(html, /id="releaseNpmButton"[^>]*data-command="\/release-npm"[\s\S]*?<span>NPM Release<\/span>/, "Publish menu should include the npm release workflow by label");
|
|
100
100
|
assert.match(html, /id="releaseAurButton"[^>]*data-command="\/release-aur"[\s\S]*?<span>AUR Release<\/span>/, "Publish menu should include the AUR release workflow by label");
|
|
101
|
+
assert.match(html, /id="nativeCommandMenuButton"[\s\S]*?aria-controls="nativeCommandMenu"/, "composer should expose a /skills and /tools command menu button");
|
|
102
|
+
assert.ok(html.indexOf('id="publishButton"') < html.indexOf('id="nativeCommandMenuButton"'), "skills/tools command menu should render immediately after the Publish workflow button");
|
|
103
|
+
assert.match(html, /id="nativeSkillsButton"[^>]*data-command="\/skills"[\s\S]*?<span>Skills Setup<\/span>/, "skills/tools command menu should include Skills Setup");
|
|
104
|
+
assert.match(html, /id="nativeToolsButton"[^>]*data-command="\/tools"[\s\S]*?<span>Tools Setup<\/span>/, "skills/tools command menu should include Tools Setup");
|
|
101
105
|
assert.doesNotMatch(html, /<code>\/release-(?:npm|aur)<\/code>/, "Publish menu should not show slash command names as option labels");
|
|
102
106
|
assert.doesNotMatch(html, /data-tooltip="[^"]*\/release-(?:npm|aur)/, "Publish tooltip should not show slash command names");
|
|
103
107
|
assert.match(html, /id="steerButton"[\s\S]*?data-tooltip="Steer usage:/, "Steer should explain type-first usage in a tooltip");
|
|
@@ -180,9 +184,12 @@ assert.match(css, /\.action-feedback-controls:not\(:hover\):not\(:focus-within\)
|
|
|
180
184
|
assert.match(css, /\.action-feedback-button\.feedback-question\.active/, "question-mark reaction should have selected styling");
|
|
181
185
|
assert.match(css, /\.composer-row button\[data-tooltip\]::after/, "composer button tooltips should be shared across Git, Steer, and Follow-up buttons");
|
|
182
186
|
assert.match(css, /\.composer-row button\[data-tooltip\]\.tooltip-open::after/, "composer button tooltips should be triggerable from JS for empty mobile taps");
|
|
187
|
+
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");
|
|
183
188
|
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");
|
|
184
189
|
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");
|
|
185
|
-
assert.match(css, /\.composer-
|
|
190
|
+
assert.match(css, /\.composer-native-command-button \{[\s\S]*?color:\s*var\(--ctp-mauve\)/, "skills/tools command menu should have a distinct slash-command button style");
|
|
191
|
+
assert.match(css, /\.composer-native-command-menu-item \{[\s\S]*?color:\s*var\(--ctp-mauve\)/, "skills/tools command menu items should be styled separately from publish actions");
|
|
192
|
+
assert.match(css, /\.composer-actions-panel > \.composer-publish-menu[\s\S]*?grid-column: span 1/, "Publish and command menu buttons should fit beside Git workflow in mobile actions");
|
|
186
193
|
assert.match(css, /\.composer-actions-panel[\s\S]*?bottom:\s*calc\(100% \+ 0\.42rem\)/, "mobile composer actions should open as an above-composer sheet");
|
|
187
194
|
assert.match(css, /body\.composer-actions-open \.composer-actions-panel \{ display: grid; \}/, "composer actions panel should only open when toggled");
|
|
188
195
|
assert.match(css, /\.terminal-tabs-toggle-button \{ display: none; \}/, "terminal tab toggle should be hidden outside mobile CSS");
|
|
@@ -220,6 +227,8 @@ assert.match(css, /\.extension-dialog[\s\S]*?inset:\s*auto 0 0 0/, "mobile dialo
|
|
|
220
227
|
assert.match(css, /#dialogMessage \{[\s\S]*?white-space:\s*pre-wrap/, "extension dialog messages should preserve multiline prompts");
|
|
221
228
|
assert.match(css, /\.native-command-dialog \{[\s\S]*?width:\s*min\(56rem/, "native slash selector dialog should have roomy desktop layout");
|
|
222
229
|
assert.match(css, /\.native-selector-item \{[\s\S]*?--tree-depth/, "native slash selector choices should support tree indentation");
|
|
230
|
+
assert.match(css, /\.native-selector-badge\.native-selector-badge-pi-native[\s\S]*?color:\s*var\(--ctp-blue\)/, "Tools Setup should distinguish Pi native tools with a Pi Native tag");
|
|
231
|
+
assert.match(css, /\.native-selector-badge\.native-selector-badge-external[\s\S]*?color:\s*var\(--ctp-mauve\)/, "Tools Setup should distinguish external tools with an External tag");
|
|
223
232
|
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");
|
|
224
233
|
assert.match(css, /\.extension-dialog\.guardrail-dialog[\s\S]*?border-color:\s*rgba\(249, 226, 175/, "guardrail dialogs should have warning-specific styling");
|
|
225
234
|
assert.match(css, /\.extension-dialog\.release-dialog[\s\S]*?width:\s*min\(64rem/, "release confirmation dialogs should have more horizontal room");
|
|
@@ -339,6 +348,9 @@ assert.match(app, /OPTIONAL_FEATURES_STORAGE_KEY/, "optional feature disable tog
|
|
|
339
348
|
assert.match(app, /function renderOptionalFeatureDependentDisplays\(\)[\s\S]*renderOptionalFeatureControls\(\);[\s\S]*renderThemeSelect\(\);[\s\S]*renderWidgets\(\);[\s\S]*renderStatus\(\);[\s\S]*renderCommands\(\);[\s\S]*renderAllMessages\(\{ preserveScroll: true \}\);[\s\S]*if \(streamRawText\) renderStreamingAssistantText\(\);/, "optional feature toggles should immediately refresh visible controls, commands, transcript, and live stream displays");
|
|
340
349
|
assert.match(app, /function setOptionalFeatureDisabled\(featureId, disabled\)[\s\S]*renderOptionalFeatureDependentDisplays\(\);[\s\S]*const tabContext = activeTabContext\(\);[\s\S]*refreshCommands\(tabContext\)/, "optional feature enable/disable should re-render the GUI and then refresh command capabilities");
|
|
341
350
|
assert.match(app, /function setOptionalControlState\(button, available, unavailableTitle\)[\s\S]*setAttribute\("aria-label", nextAriaLabel\)[\s\S]*setAttribute\("data-tooltip", nextTooltip\)/, "optional feature button disabled state should update accessible labels and visible tooltips");
|
|
351
|
+
assert.match(app, /\["skills", "tuiSkillsCommand"\][\s\S]*\["tools", "tuiToolsCommand"\]/, "optional feature toggles should gate /skills and /tools command surfaces");
|
|
352
|
+
assert.match(app, /function setNativeCommandMenuOpen\(open\)/, "frontend should track the skills/tools command menu open state separately from Publish");
|
|
353
|
+
assert.match(app, /nativeSkillsButton\.hidden = !isOptionalFeatureEnabled\("tuiSkillsCommand"\)[\s\S]*nativeToolsButton\.hidden = !isOptionalFeatureEnabled\("tuiToolsCommand"\)/, "skills/tools menu items should be hidden by their optional feature toggles");
|
|
342
354
|
assert.match(app, /function renderCommands\(\)/, "side-panel commands should be re-renderable from current optional feature state");
|
|
343
355
|
assert.match(app, /function installOptionalFeature\(featureId\)/, "optional features should expose an install action");
|
|
344
356
|
assert.match(app, /api\("\/api\/optional-feature-install"/, "optional feature install action should call the backend installer endpoint");
|
|
@@ -486,10 +498,18 @@ assert.match(app, /function showComposerButtonTooltip\(button\)/, "empty mode-bu
|
|
|
486
498
|
assert.match(app, /sendPromptFromModeButton\("steer", elements\.steerButton\)/, "Steer should show tooltip instead of silently doing nothing when input is empty");
|
|
487
499
|
assert.match(app, /sendPromptFromModeButton\("follow-up", elements\.followUpButton\)/, "Follow-up should show tooltip instead of silently doing nothing when input is empty");
|
|
488
500
|
assert.match(app, /function runPublishWorkflow\(command\)[\s\S]*?sendPrompt\("prompt", command\)/, "Publish workflows should send slash commands directly without replacing the draft");
|
|
489
|
-
assert.match(app, /
|
|
501
|
+
assert.match(app, /async function runNativeCommandMenu\(command\)[\s\S]*?await handleNativeSlashSelectorCommand\(command\)/, "skills/tools command menu should open native selector dialogs directly");
|
|
502
|
+
assert.match(app, /function nativeToolOriginTag\(resource\)[\s\S]*?sourceInfo\?\.source === "builtin"[\s\S]*?label: "Pi Native"[\s\S]*?label: "External"/, "Tools Setup should classify built-in Pi tools separately from external tools");
|
|
503
|
+
assert.match(app, /renderNativeResourceToggles\(tools, \{[\s\S]*?getResourceTag: nativeToolOriginTag/, "Tools Setup should render Pi Native\/External tags");
|
|
504
|
+
assert.match(app, /const tags = Array\.isArray\(item\.tags\)[\s\S]*?item\.badge, \.\.\.tags/, "native selector filtering should include extra resource tags");
|
|
505
|
+
assert.match(app, /publishMenuContainer\?\.addEventListener\("pointerenter", \(\) => \{[\s\S]*?setPublishMenuOpen\(true\);[\s\S]*?\}\)/, "Publish menu should expand on hover");
|
|
490
506
|
assert.match(app, /publishMenuContainer\?\.addEventListener\("pointerleave", \(\) => setPublishMenuOpen\(false\)\)/, "Publish menu should collapse after hover leaves");
|
|
507
|
+
assert.match(app, /nativeCommandMenuContainer\?\.addEventListener\("pointerenter", \(\) => \{[\s\S]*?setNativeCommandMenuOpen\(true\);[\s\S]*?\}\)/, "skills/tools command menu should expand on hover");
|
|
508
|
+
assert.match(app, /nativeCommandMenuContainer\?\.addEventListener\("pointerleave", \(\) => setNativeCommandMenuOpen\(false\)\)/, "skills/tools command menu should collapse after hover leaves");
|
|
491
509
|
assert.match(app, /releaseNpmButton\.addEventListener\("click", \(\) => runPublishWorkflow\("\/release-npm"\)\)/, "Publish menu should launch /release-npm");
|
|
492
510
|
assert.match(app, /releaseAurButton\.addEventListener\("click", \(\) => runPublishWorkflow\("\/release-aur"\)\)/, "Publish menu should launch /release-aur");
|
|
511
|
+
assert.match(app, /nativeSkillsButton\.addEventListener\("click", \(\) => runNativeCommandMenu\("\/skills"\)\)/, "skills/tools command menu should launch /skills");
|
|
512
|
+
assert.match(app, /nativeToolsButton\.addEventListener\("click", \(\) => runNativeCommandMenu\("\/tools"\)\)/, "skills/tools command menu should launch /tools");
|
|
493
513
|
assert.match(app, /async function sendPrompt\(kind = "prompt", explicitMessage\)/, "prompt sending should accept direct messages that bypass the input field");
|
|
494
514
|
assert.match(app, /const rawMessage = usesPromptInput \? elements\.promptInput\.value : explicitMessage/, "direct prompt sends should not read the input textarea");
|
|
495
515
|
assert.match(app, /if \(usesPromptInput\) \{[\s\S]*?if \(targetStillActive\) \{[\s\S]*?elements\.promptInput\.value = "";/, "direct prompt sends should preserve the input textarea draft");
|
|
@@ -600,7 +620,7 @@ assert.equal(manifest.start_url, "/", "PWA manifest should start at the web UI r
|
|
|
600
620
|
assert.ok(manifest.icons?.some((icon) => icon.src === "/apple-touch-icon.png" && icon.sizes === "180x180"), "PWA manifest should include a conventional 180px apple touch icon");
|
|
601
621
|
assert.ok(manifest.icons?.some((icon) => icon.src === "/icon-192.png" && icon.sizes === "192x192"), "PWA manifest should include a 192px icon");
|
|
602
622
|
assert.ok(manifest.icons?.some((icon) => icon.src === "/icon-512.png" && icon.sizes === "512x512"), "PWA manifest should include a 512px icon");
|
|
603
|
-
assert.match(serviceWorker, /const CACHE_NAME = "pi-webui-pwa-
|
|
623
|
+
assert.match(serviceWorker, /const CACHE_NAME = "pi-webui-pwa-v23"/, "PWA service worker should define an app-shell cache");
|
|
604
624
|
assert.match(serviceWorker, /self\.addEventListener\("notificationclick"/, "PWA service worker should focus Web UI when blocked-tab notifications are clicked");
|
|
605
625
|
assert.match(serviceWorker, /event\.notification\.data\?\.url/, "blocked-tab notifications should carry a URL for service-worker click handling");
|
|
606
626
|
assert.match(serviceWorker, /"\/apple-touch-icon\.png"/, "PWA service worker should cache the apple touch icon");
|