@firstpick/pi-package-webui 0.2.6 → 0.2.8
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/WEBUI_TUI_NATIVE_PARITY.json +3 -3
- package/bin/pi-webui.mjs +219 -2
- package/package.json +1 -1
- package/public/app.js +639 -79
- package/public/index.html +43 -2
- package/public/styles.css +223 -7
- package/tests/mobile-static.test.mjs +38 -4
- package/tests/native-parity.test.mjs +3 -0
package/public/index.html
CHANGED
|
@@ -108,11 +108,12 @@
|
|
|
108
108
|
id="gitWorkflowButton"
|
|
109
109
|
class="composer-icon-button composer-git-button"
|
|
110
110
|
type="button"
|
|
111
|
+
hidden
|
|
111
112
|
title="Guided Git workflow"
|
|
112
113
|
aria-label="Start guided Git workflow: git add dot, run git-staged-msg, preview messages, commit short or long, then git push. Cancel is available at each step."
|
|
113
114
|
data-tooltip="GitHub workflow: 1. Run git add . 2. Run /git-staged-msg 3. Preview short + long messages 4. Commit with short or long message 5. Run git push Cancel is available at each step."
|
|
114
115
|
><svg class="composer-icon composer-icon-github" viewBox="0 0 16 16" aria-hidden="true" focusable="false"><path fill="currentColor" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></button>
|
|
115
|
-
<div class="composer-publish-menu">
|
|
116
|
+
<div class="composer-publish-menu" hidden>
|
|
116
117
|
<button
|
|
117
118
|
id="publishButton"
|
|
118
119
|
class="composer-icon-button composer-publish-button"
|
|
@@ -133,7 +134,7 @@
|
|
|
133
134
|
</button>
|
|
134
135
|
</div>
|
|
135
136
|
</div>
|
|
136
|
-
<div class="composer-publish-menu composer-native-command-menu">
|
|
137
|
+
<div class="composer-publish-menu composer-native-command-menu" hidden>
|
|
137
138
|
<button
|
|
138
139
|
id="nativeCommandMenuButton"
|
|
139
140
|
class="composer-icon-button composer-publish-button composer-native-command-button"
|
|
@@ -154,6 +155,45 @@
|
|
|
154
155
|
</button>
|
|
155
156
|
</div>
|
|
156
157
|
</div>
|
|
158
|
+
<div class="composer-publish-menu composer-options-menu">
|
|
159
|
+
<button
|
|
160
|
+
id="optionsMenuButton"
|
|
161
|
+
class="composer-icon-button composer-publish-button composer-options-button"
|
|
162
|
+
type="button"
|
|
163
|
+
title="Open common Pi options"
|
|
164
|
+
aria-label="Open common Pi options"
|
|
165
|
+
aria-haspopup="menu"
|
|
166
|
+
aria-expanded="false"
|
|
167
|
+
aria-controls="optionsMenu"
|
|
168
|
+
data-tooltip="Options: resume, reload, name, clone, settings, export, fork, or tree."
|
|
169
|
+
><svg class="composer-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 7h16M4 12h16M4 17h16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="8" cy="7" r="1.6" fill="currentColor"/><circle cx="16" cy="12" r="1.6" fill="currentColor"/><circle cx="11" cy="17" r="1.6" fill="currentColor"/></svg></button>
|
|
170
|
+
<div id="optionsMenu" class="composer-publish-menu-panel composer-options-menu-panel" role="menu" aria-label="Common Pi options">
|
|
171
|
+
<button id="optionsTreeButton" class="composer-publish-menu-item composer-options-menu-item" type="button" role="menuitem" data-command="/tree">
|
|
172
|
+
<span>Tree</span>
|
|
173
|
+
</button>
|
|
174
|
+
<button id="optionsForkButton" class="composer-publish-menu-item composer-options-menu-item" type="button" role="menuitem" data-command="/fork">
|
|
175
|
+
<span>Fork</span>
|
|
176
|
+
</button>
|
|
177
|
+
<button id="optionsExportButton" class="composer-publish-menu-item composer-options-menu-item" type="button" role="menuitem" data-command="/export">
|
|
178
|
+
<span>Export</span>
|
|
179
|
+
</button>
|
|
180
|
+
<button id="optionsSettingsButton" class="composer-publish-menu-item composer-options-menu-item" type="button" role="menuitem" data-command="/settings">
|
|
181
|
+
<span>Settings</span>
|
|
182
|
+
</button>
|
|
183
|
+
<button id="optionsCloneButton" class="composer-publish-menu-item composer-options-menu-item" type="button" role="menuitem" data-command="/clone">
|
|
184
|
+
<span>Clone Session</span>
|
|
185
|
+
</button>
|
|
186
|
+
<button id="optionsNameButton" class="composer-publish-menu-item composer-options-menu-item" type="button" role="menuitem" data-command="/name">
|
|
187
|
+
<span>Name Session</span>
|
|
188
|
+
</button>
|
|
189
|
+
<button id="optionsReloadButton" class="composer-publish-menu-item composer-options-menu-item" type="button" role="menuitem" data-command="/reload">
|
|
190
|
+
<span>Reload Pi</span>
|
|
191
|
+
</button>
|
|
192
|
+
<button id="optionsResumeButton" class="composer-publish-menu-item composer-options-menu-item" type="button" role="menuitem" data-command="/resume">
|
|
193
|
+
<span>Resume Session</span>
|
|
194
|
+
</button>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
157
197
|
</div>
|
|
158
198
|
<div class="spacer"></div>
|
|
159
199
|
<button
|
|
@@ -329,6 +369,7 @@
|
|
|
329
369
|
</button>
|
|
330
370
|
</h2>
|
|
331
371
|
<div id="sidePanelSectionCommands" class="side-panel-section-content" hidden>
|
|
372
|
+
<input id="commandSearchInput" class="commands-search" type="search" placeholder="Search commands…" autocomplete="off" spellcheck="false" aria-label="Search commands" />
|
|
332
373
|
<div id="commandsBox" class="commands-box muted">Loading…</div>
|
|
333
374
|
</div>
|
|
334
375
|
</section>
|
package/public/styles.css
CHANGED
|
@@ -636,6 +636,7 @@ body.side-panel-collapsed .terminal-tabs-shell {
|
|
|
636
636
|
.side-panel-section {
|
|
637
637
|
display: grid;
|
|
638
638
|
gap: 0.55rem;
|
|
639
|
+
min-width: 0;
|
|
639
640
|
}
|
|
640
641
|
.side-panel-section + .side-panel-section { margin-top: 0.78rem; }
|
|
641
642
|
.side-panel-section h2 {
|
|
@@ -684,6 +685,9 @@ body.side-panel-collapsed .terminal-tabs-shell {
|
|
|
684
685
|
.side-panel-section:not(.collapsed) .side-panel-section-chevron {
|
|
685
686
|
transform: rotate(90deg);
|
|
686
687
|
}
|
|
688
|
+
.side-panel-section-content {
|
|
689
|
+
min-width: 0;
|
|
690
|
+
}
|
|
687
691
|
.side-panel-section.collapsed .side-panel-section-content,
|
|
688
692
|
.side-panel-section-content[hidden] {
|
|
689
693
|
display: none;
|
|
@@ -1481,11 +1485,20 @@ body.side-panel-collapsed .terminal-tabs-shell {
|
|
|
1481
1485
|
gap: 0.5rem;
|
|
1482
1486
|
}
|
|
1483
1487
|
.footer-line-meta {
|
|
1484
|
-
display:
|
|
1485
|
-
|
|
1488
|
+
display: flex;
|
|
1489
|
+
flex-wrap: nowrap;
|
|
1486
1490
|
gap: 0.5rem;
|
|
1487
1491
|
color: rgba(var(--ctp-subtext-rgb), 0.76);
|
|
1488
1492
|
}
|
|
1493
|
+
.footer-line-meta .footer-meta {
|
|
1494
|
+
flex: 0 1 max-content;
|
|
1495
|
+
}
|
|
1496
|
+
.footer-line-meta .footer-details-toggle {
|
|
1497
|
+
flex: 0 0 auto;
|
|
1498
|
+
}
|
|
1499
|
+
.footer-workspace {
|
|
1500
|
+
flex: 1 1 8rem;
|
|
1501
|
+
}
|
|
1489
1502
|
.footer-line-tui {
|
|
1490
1503
|
align-items: center;
|
|
1491
1504
|
gap: 0.5rem;
|
|
@@ -1597,6 +1610,9 @@ button.footer-meta {
|
|
|
1597
1610
|
cursor: pointer;
|
|
1598
1611
|
}
|
|
1599
1612
|
.footer-meta-action {
|
|
1613
|
+
position: relative;
|
|
1614
|
+
border-color: rgba(148, 226, 213, 0.26);
|
|
1615
|
+
box-shadow: inset 0 1px 0 rgba(255,255,255,0.045), inset 0 0 0 1px rgba(148, 226, 213, 0.055), 0 0.45rem 1rem rgba(0, 0, 0, 0.10);
|
|
1600
1616
|
transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
|
|
1601
1617
|
}
|
|
1602
1618
|
.footer-meta-action:hover,
|
|
@@ -1648,12 +1664,20 @@ button.footer-meta {
|
|
|
1648
1664
|
color: var(--ctp-teal);
|
|
1649
1665
|
text-shadow: 0 0 0.6rem rgba(148, 226, 213, 0.22);
|
|
1650
1666
|
}
|
|
1667
|
+
.footer-thinking .footer-meta-value {
|
|
1668
|
+
color: var(--ctp-mauve);
|
|
1669
|
+
text-shadow: 0 0 0.6rem rgba(203, 166, 247, 0.22);
|
|
1670
|
+
}
|
|
1651
1671
|
.footer-model.footer-meta-action {
|
|
1652
1672
|
border-color: rgba(148, 226, 213, 0.24);
|
|
1653
1673
|
}
|
|
1674
|
+
.footer-thinking.footer-meta-action {
|
|
1675
|
+
border-color: rgba(203, 166, 247, 0.24);
|
|
1676
|
+
}
|
|
1654
1677
|
.footer-model-picker {
|
|
1655
1678
|
position: absolute;
|
|
1656
|
-
|
|
1679
|
+
left: var(--footer-model-picker-left, auto);
|
|
1680
|
+
right: var(--footer-model-picker-right, 0.95rem);
|
|
1657
1681
|
bottom: calc(100% + 0.5rem);
|
|
1658
1682
|
z-index: 40;
|
|
1659
1683
|
display: grid;
|
|
@@ -1728,6 +1752,42 @@ button.footer-meta {
|
|
|
1728
1752
|
color: var(--ctp-pink);
|
|
1729
1753
|
text-shadow: 0 0 0.55rem rgba(245, 194, 231, 0.18);
|
|
1730
1754
|
}
|
|
1755
|
+
.footer-changes,
|
|
1756
|
+
.footer-git-extra {
|
|
1757
|
+
flex: 0 0 auto;
|
|
1758
|
+
}
|
|
1759
|
+
.footer-changes {
|
|
1760
|
+
border-color: rgba(249, 226, 175, 0.36);
|
|
1761
|
+
background:
|
|
1762
|
+
linear-gradient(120deg, rgba(249, 226, 175, 0.14), rgba(250, 179, 135, 0.08)),
|
|
1763
|
+
linear-gradient(180deg, rgba(var(--ctp-surface-rgb), 0.54), rgba(var(--ctp-crust-rgb), 0.36));
|
|
1764
|
+
}
|
|
1765
|
+
.footer-changes .footer-meta-label {
|
|
1766
|
+
color: rgba(249, 226, 175, 0.88);
|
|
1767
|
+
text-shadow: 0 0 0.52rem rgba(249, 226, 175, 0.20);
|
|
1768
|
+
}
|
|
1769
|
+
.footer-changes .footer-meta-value {
|
|
1770
|
+
color: var(--ctp-yellow);
|
|
1771
|
+
font-weight: 950;
|
|
1772
|
+
letter-spacing: 0.01em;
|
|
1773
|
+
text-shadow: 0 0 0.72rem rgba(249, 226, 175, 0.28);
|
|
1774
|
+
}
|
|
1775
|
+
.footer-git-extra {
|
|
1776
|
+
border-color: rgba(137, 180, 250, 0.34);
|
|
1777
|
+
background:
|
|
1778
|
+
linear-gradient(120deg, rgba(137, 180, 250, 0.13), rgba(148, 226, 213, 0.08)),
|
|
1779
|
+
linear-gradient(180deg, rgba(var(--ctp-surface-rgb), 0.50), rgba(var(--ctp-crust-rgb), 0.36));
|
|
1780
|
+
}
|
|
1781
|
+
.footer-git-extra .footer-meta-label {
|
|
1782
|
+
color: rgba(137, 180, 250, 0.88);
|
|
1783
|
+
text-shadow: 0 0 0.52rem rgba(137, 180, 250, 0.20);
|
|
1784
|
+
}
|
|
1785
|
+
.footer-git-extra .footer-meta-value {
|
|
1786
|
+
color: var(--ctp-sky);
|
|
1787
|
+
font-weight: 900;
|
|
1788
|
+
letter-spacing: 0.01em;
|
|
1789
|
+
text-shadow: 0 0 0.68rem rgba(137, 180, 250, 0.26);
|
|
1790
|
+
}
|
|
1731
1791
|
.footer-runtime .footer-meta-value {
|
|
1732
1792
|
color: var(--ctp-yellow);
|
|
1733
1793
|
}
|
|
@@ -3141,6 +3201,9 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3141
3201
|
overflow: visible;
|
|
3142
3202
|
isolation: isolate;
|
|
3143
3203
|
}
|
|
3204
|
+
.composer-publish-menu[hidden] {
|
|
3205
|
+
display: none !important;
|
|
3206
|
+
}
|
|
3144
3207
|
.composer-publish-menu:hover,
|
|
3145
3208
|
.composer-publish-menu:focus-within,
|
|
3146
3209
|
.composer-publish-menu.open {
|
|
@@ -3207,6 +3270,22 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3207
3270
|
.composer-native-command-menu.open .composer-native-command-button {
|
|
3208
3271
|
border-color: rgba(203, 166, 247, 0.62);
|
|
3209
3272
|
}
|
|
3273
|
+
.composer-options-button {
|
|
3274
|
+
color: var(--ctp-sky);
|
|
3275
|
+
border-color: rgba(137, 220, 235, 0.40);
|
|
3276
|
+
background:
|
|
3277
|
+
linear-gradient(120deg, rgba(137, 220, 235, 0.15), rgba(166, 227, 161, 0.10)),
|
|
3278
|
+
linear-gradient(180deg, rgba(var(--ctp-surface-rgb), 0.88), rgba(var(--ctp-crust-rgb), 0.88));
|
|
3279
|
+
}
|
|
3280
|
+
.composer-options-button:hover,
|
|
3281
|
+
.composer-options-button.menu-open {
|
|
3282
|
+
color: #11111b;
|
|
3283
|
+
background: linear-gradient(120deg, var(--ctp-sky), var(--ctp-teal), var(--ctp-green));
|
|
3284
|
+
border-color: transparent;
|
|
3285
|
+
}
|
|
3286
|
+
.composer-options-menu.open .composer-options-button {
|
|
3287
|
+
border-color: rgba(137, 220, 235, 0.62);
|
|
3288
|
+
}
|
|
3210
3289
|
.composer-publish-menu-panel {
|
|
3211
3290
|
position: absolute;
|
|
3212
3291
|
z-index: 100;
|
|
@@ -3228,6 +3307,9 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3228
3307
|
.composer-publish-menu.open .composer-publish-menu-panel {
|
|
3229
3308
|
display: flex;
|
|
3230
3309
|
}
|
|
3310
|
+
.composer-options-menu-panel {
|
|
3311
|
+
max-height: min(88vh, 36rem);
|
|
3312
|
+
}
|
|
3231
3313
|
.composer-publish-menu.open .composer-publish-button {
|
|
3232
3314
|
border-color: rgba(250, 179, 135, 0.58);
|
|
3233
3315
|
}
|
|
@@ -3270,6 +3352,20 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3270
3352
|
background: linear-gradient(120deg, var(--ctp-mauve), var(--ctp-blue));
|
|
3271
3353
|
box-shadow: 0 0 1rem rgba(203, 166, 247, 0.20);
|
|
3272
3354
|
}
|
|
3355
|
+
.composer-options-menu-item {
|
|
3356
|
+
color: var(--ctp-sky);
|
|
3357
|
+
border-color: rgba(137, 220, 235, 0.32);
|
|
3358
|
+
background:
|
|
3359
|
+
linear-gradient(120deg, rgba(137, 220, 235, 0.12), rgba(166, 227, 161, 0.08)),
|
|
3360
|
+
var(--ctp-crust);
|
|
3361
|
+
}
|
|
3362
|
+
.composer-options-menu-item:hover,
|
|
3363
|
+
.composer-options-menu-item:focus-visible {
|
|
3364
|
+
color: #11111b;
|
|
3365
|
+
border-color: transparent;
|
|
3366
|
+
background: linear-gradient(120deg, var(--ctp-sky), var(--ctp-teal));
|
|
3367
|
+
box-shadow: 0 0 1rem rgba(137, 220, 235, 0.20);
|
|
3368
|
+
}
|
|
3273
3369
|
.composer button[data-tooltip] {
|
|
3274
3370
|
position: relative;
|
|
3275
3371
|
}
|
|
@@ -3368,18 +3464,40 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3368
3464
|
padding: 0.72rem;
|
|
3369
3465
|
box-shadow: inset 0 1px 0 rgba(255,255,255,0.035);
|
|
3370
3466
|
}
|
|
3467
|
+
.commands-search {
|
|
3468
|
+
width: 100%;
|
|
3469
|
+
min-width: 0;
|
|
3470
|
+
margin-bottom: 0.55rem;
|
|
3471
|
+
padding: 0.5rem 0.62rem;
|
|
3472
|
+
color: var(--ctp-text);
|
|
3473
|
+
border-color: rgba(148, 226, 213, 0.24);
|
|
3474
|
+
background:
|
|
3475
|
+
linear-gradient(120deg, rgba(148, 226, 213, 0.10), rgba(203, 166, 247, 0.08)),
|
|
3476
|
+
rgba(var(--ctp-crust-rgb), 0.58);
|
|
3477
|
+
font-size: 0.86rem;
|
|
3478
|
+
}
|
|
3479
|
+
.commands-search::placeholder {
|
|
3480
|
+
color: rgba(var(--ctp-subtext-rgb), 0.62);
|
|
3481
|
+
}
|
|
3371
3482
|
.commands-box {
|
|
3483
|
+
max-width: 100%;
|
|
3372
3484
|
max-height: min(32rem, 52vh);
|
|
3373
|
-
overflow: auto;
|
|
3485
|
+
overflow-y: auto;
|
|
3486
|
+
overflow-x: hidden;
|
|
3374
3487
|
font-size: 0.86rem;
|
|
3375
3488
|
}
|
|
3376
3489
|
.command-item {
|
|
3377
3490
|
display: block;
|
|
3378
3491
|
width: 100%;
|
|
3492
|
+
max-width: 100%;
|
|
3493
|
+
min-width: 0;
|
|
3379
3494
|
min-height: auto;
|
|
3380
3495
|
margin: 0 0 0.5rem;
|
|
3381
3496
|
padding: 0.48rem 0.56rem;
|
|
3382
3497
|
text-align: left;
|
|
3498
|
+
white-space: normal;
|
|
3499
|
+
overflow-wrap: anywhere;
|
|
3500
|
+
line-height: 1.35;
|
|
3383
3501
|
background: rgba(var(--ctp-surface-rgb), 0.18);
|
|
3384
3502
|
border-color: rgba(180, 190, 254, 0.12);
|
|
3385
3503
|
box-shadow: none;
|
|
@@ -3393,6 +3511,8 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3393
3511
|
}
|
|
3394
3512
|
.command-item code {
|
|
3395
3513
|
color: var(--ctp-green);
|
|
3514
|
+
overflow-wrap: anywhere;
|
|
3515
|
+
word-break: break-word;
|
|
3396
3516
|
text-shadow: 0 0 0.7rem rgba(166, 227, 161, 0.25);
|
|
3397
3517
|
}
|
|
3398
3518
|
.event-log {
|
|
@@ -3648,12 +3768,96 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3648
3768
|
.native-selector-meta {
|
|
3649
3769
|
font-size: 0.76rem;
|
|
3650
3770
|
}
|
|
3771
|
+
.native-settings-panel {
|
|
3772
|
+
display: grid;
|
|
3773
|
+
gap: 0.82rem;
|
|
3774
|
+
}
|
|
3775
|
+
.native-settings-note {
|
|
3776
|
+
display: grid;
|
|
3777
|
+
gap: 0.2rem;
|
|
3778
|
+
padding: 0.72rem 0.82rem;
|
|
3779
|
+
border: 1px solid rgba(148, 226, 213, 0.18);
|
|
3780
|
+
border-radius: 0.9rem;
|
|
3781
|
+
background: linear-gradient(120deg, rgba(148, 226, 213, 0.10), rgba(137, 180, 250, 0.08));
|
|
3782
|
+
color: rgba(var(--ctp-text-rgb), 0.86);
|
|
3783
|
+
}
|
|
3784
|
+
.native-settings-note span {
|
|
3785
|
+
color: rgba(var(--ctp-subtext-rgb), 0.78);
|
|
3786
|
+
font-size: 0.84rem;
|
|
3787
|
+
}
|
|
3788
|
+
.native-settings-section {
|
|
3789
|
+
border: 1px solid rgba(180, 190, 254, 0.15);
|
|
3790
|
+
border-radius: 1rem;
|
|
3791
|
+
background: rgba(var(--ctp-mantle-rgb), 0.42);
|
|
3792
|
+
overflow: clip;
|
|
3793
|
+
}
|
|
3794
|
+
.native-settings-section-summary {
|
|
3795
|
+
display: grid;
|
|
3796
|
+
gap: 0.18rem;
|
|
3797
|
+
padding: 0.78rem 0.9rem;
|
|
3798
|
+
cursor: pointer;
|
|
3799
|
+
list-style-position: inside;
|
|
3800
|
+
}
|
|
3801
|
+
.native-settings-section-summary:hover {
|
|
3802
|
+
background: rgba(var(--ctp-surface-rgb), 0.24);
|
|
3803
|
+
}
|
|
3804
|
+
.native-settings-section-title,
|
|
3805
|
+
.native-settings-label-row {
|
|
3806
|
+
display: flex;
|
|
3807
|
+
flex-wrap: wrap;
|
|
3808
|
+
align-items: center;
|
|
3809
|
+
gap: 0.38rem;
|
|
3810
|
+
}
|
|
3811
|
+
.native-settings-section-title strong {
|
|
3812
|
+
color: var(--text);
|
|
3813
|
+
}
|
|
3814
|
+
.native-settings-section-description {
|
|
3815
|
+
color: rgba(var(--ctp-subtext-rgb), 0.76);
|
|
3816
|
+
font-size: 0.82rem;
|
|
3817
|
+
}
|
|
3818
|
+
.native-settings-section .native-settings-grid {
|
|
3819
|
+
padding: 0 0.82rem 0.82rem;
|
|
3820
|
+
}
|
|
3651
3821
|
.native-settings-grid,
|
|
3652
3822
|
.native-tree-options {
|
|
3653
3823
|
display: grid;
|
|
3654
3824
|
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
|
|
3655
3825
|
gap: 0.72rem;
|
|
3656
3826
|
}
|
|
3827
|
+
.native-settings-badge {
|
|
3828
|
+
display: inline-flex;
|
|
3829
|
+
align-items: center;
|
|
3830
|
+
padding: 0.08rem 0.34rem;
|
|
3831
|
+
border: 1px solid rgba(180, 190, 254, 0.24);
|
|
3832
|
+
border-radius: 999px;
|
|
3833
|
+
color: rgba(var(--ctp-subtext-rgb), 0.86);
|
|
3834
|
+
font-size: 0.62rem;
|
|
3835
|
+
font-weight: 900;
|
|
3836
|
+
letter-spacing: 0.07em;
|
|
3837
|
+
text-transform: uppercase;
|
|
3838
|
+
}
|
|
3839
|
+
.native-settings-badge-now,
|
|
3840
|
+
.native-settings-badge-browser {
|
|
3841
|
+
border-color: rgba(166, 227, 161, 0.34);
|
|
3842
|
+
color: var(--ctp-green);
|
|
3843
|
+
background: rgba(166, 227, 161, 0.08);
|
|
3844
|
+
}
|
|
3845
|
+
.native-settings-badge-reload {
|
|
3846
|
+
border-color: rgba(249, 226, 175, 0.38);
|
|
3847
|
+
color: var(--ctp-yellow);
|
|
3848
|
+
background: rgba(249, 226, 175, 0.08);
|
|
3849
|
+
}
|
|
3850
|
+
.native-settings-badge-tui,
|
|
3851
|
+
.native-settings-badge-startup {
|
|
3852
|
+
border-color: rgba(137, 180, 250, 0.34);
|
|
3853
|
+
color: var(--ctp-blue);
|
|
3854
|
+
background: rgba(137, 180, 250, 0.08);
|
|
3855
|
+
}
|
|
3856
|
+
.native-settings-badge-safety {
|
|
3857
|
+
border-color: rgba(243, 139, 168, 0.42);
|
|
3858
|
+
color: var(--ctp-red);
|
|
3859
|
+
background: rgba(243, 139, 168, 0.08);
|
|
3860
|
+
}
|
|
3657
3861
|
.native-settings-field,
|
|
3658
3862
|
.native-settings-toggle {
|
|
3659
3863
|
display: grid;
|
|
@@ -3674,6 +3878,13 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3674
3878
|
letter-spacing: 0.08em;
|
|
3675
3879
|
text-transform: uppercase;
|
|
3676
3880
|
}
|
|
3881
|
+
.native-settings-field select,
|
|
3882
|
+
.native-settings-field input.dialog-input {
|
|
3883
|
+
width: 100%;
|
|
3884
|
+
}
|
|
3885
|
+
.native-settings-toggle input[type="checkbox"] {
|
|
3886
|
+
margin-top: 0.14rem;
|
|
3887
|
+
}
|
|
3677
3888
|
.native-settings-hint {
|
|
3678
3889
|
display: block;
|
|
3679
3890
|
margin-top: 0.18rem;
|
|
@@ -3857,9 +4068,13 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
3857
4068
|
body.side-panel-collapsed .layout { grid-template-columns: 1fr; height: auto; min-height: 100dvh; overflow: visible; }
|
|
3858
4069
|
.chat-panel { min-height: 70dvh; }
|
|
3859
4070
|
.side-panel { max-height: none; }
|
|
3860
|
-
.footer-line-meta {
|
|
3861
|
-
|
|
3862
|
-
|
|
4071
|
+
.footer-line-meta {
|
|
4072
|
+
display: grid;
|
|
4073
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
4074
|
+
}
|
|
4075
|
+
.footer-workspace { grid-column: 1 / -1; }
|
|
4076
|
+
.footer-model { grid-column: 1; }
|
|
4077
|
+
.footer-thinking { grid-column: 2; }
|
|
3863
4078
|
}
|
|
3864
4079
|
|
|
3865
4080
|
@media (max-width: 720px), (max-device-width: 720px), (pointer: coarse) and (hover: none) {
|
|
@@ -4190,6 +4405,7 @@ summary { cursor: pointer; color: var(--warning); }
|
|
|
4190
4405
|
.footer-changes { order: 5; }
|
|
4191
4406
|
.footer-runtime { order: 6; }
|
|
4192
4407
|
.footer-model { order: 7; }
|
|
4408
|
+
.footer-thinking { order: 8; }
|
|
4193
4409
|
body.footer-details-expanded .footer-line-meta {
|
|
4194
4410
|
grid-template-columns: 1fr;
|
|
4195
4411
|
}
|
|
@@ -190,6 +190,7 @@ assert.match(css, /\.composer-publish-menu:hover > \.composer-publish-button\[da
|
|
|
190
190
|
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
191
|
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");
|
|
192
192
|
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");
|
|
193
|
+
assert.match(css, /\.composer-options-menu-panel \{[\s\S]*?max-height:\s*min\(88vh, 36rem\)/, "Options menu should be tall enough for common commands without scrolling on normal viewports");
|
|
193
194
|
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");
|
|
194
195
|
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");
|
|
195
196
|
assert.match(css, /\.composer-actions-panel[\s\S]*?bottom:\s*calc\(100% \+ 0\.42rem\)/, "mobile composer actions should open as an above-composer sheet");
|
|
@@ -216,10 +217,21 @@ assert.match(css, /body:not\(\.pi-run-active\):not\(\.mobile-keyboard-open\) \.c
|
|
|
216
217
|
assert.match(css, /button\[hidden\] \{ display: none !important; \}/, "hidden bottom-row controls should not occupy layout space");
|
|
217
218
|
assert.match(css, /\.statusbar-tui-footer \{[\s\S]*?gap:\s*0/, "default TUI-like footer should reduce statusbar chrome around the compact line");
|
|
218
219
|
assert.match(css, /\.statusbar-git-footer \{[\s\S]*?gap:\s*0\.58rem/, "enabled git-footer extension should keep the styled Web UI footer spacing");
|
|
220
|
+
assert.match(css, /\.footer-line-meta \{[\s\S]*?display:\s*flex;[\s\S]*?flex-wrap:\s*nowrap/, "git-footer metadata should keep cwd, git chips, model, and effort on one row when space allows");
|
|
221
|
+
assert.match(css, /\.footer-line-meta \.footer-meta \{[\s\S]*?flex:\s*0 1 max-content/, "git-footer non-cwd boxes should shrink to content-sized chips while preferring complete text");
|
|
222
|
+
assert.match(css, /\.footer-workspace \{[\s\S]*?flex:\s*1 1 8rem/, "git-footer cwd chip should dynamically take remaining row space");
|
|
223
|
+
assert.match(css, /\.footer-thinking \.footer-meta-value \{[\s\S]*?color:\s*var\(--ctp-mauve\)/, "git-footer effort chip should have its own styling");
|
|
224
|
+
assert.match(css, /\.footer-changes \{[\s\S]*?border-color:\s*rgba\(249, 226, 175, 0\.36\)/, "git-footer changes chip should use a higher-contrast warning tint");
|
|
225
|
+
assert.match(css, /\.footer-changes \.footer-meta-value \{[\s\S]*?color:\s*var\(--ctp-yellow\)[\s\S]*?font-weight:\s*950/, "git-footer changes value should be bright and bold");
|
|
226
|
+
assert.match(css, /\.footer-git-extra \.footer-meta-value \{[\s\S]*?color:\s*var\(--ctp-sky\)[\s\S]*?font-weight:\s*900/, "git-footer extras value should be bright enough to read at footer size");
|
|
227
|
+
assert.match(css, /\.footer-changes,[\s\S]*?\.footer-git-extra \{[\s\S]*?flex:\s*0 0 auto/, "git-footer changes and extras chips should resist truncating short status values");
|
|
228
|
+
assert.match(css, /\.footer-meta-action \{[\s\S]*?position:\s*relative;[\s\S]*?border-color:\s*rgba\(148, 226, 213, 0\.26\)/, "clickable footer boxes should have a subtle always-visible highlight");
|
|
229
|
+
assert.doesNotMatch(css, /\.footer-meta-action::after/, "clickable footer boxes should not show a corner indicator dot");
|
|
230
|
+
assert.match(css, /@media \(max-width: 1050px\)[\s\S]*?\.footer-line-meta \{[\s\S]*?display:\s*grid;[\s\S]*?\.footer-model \{ grid-column: 1; \}[\s\S]*?\.footer-thinking \{ grid-column: 2; \}/, "narrow git-footer metadata should keep model and effort on the same row");
|
|
219
231
|
assert.match(css, /\.footer-line-tui \{[\s\S]*?white-space:\s*nowrap/, "default Web UI footer should use a minimal TUI-like line");
|
|
220
232
|
assert.match(css, /\.footer-tui-cwd[\s\S]*?max-width:\s*38%/, "TUI-like footer should keep cwd compact on desktop");
|
|
221
233
|
assert.match(css, /\.footer-tui-model[\s\S]*?text-align:\s*right/, "TUI-like footer should right-align model information on desktop");
|
|
222
|
-
assert.match(css, /\.footer-model-picker[\s\S]*?position:\s*absolute/, "footer model
|
|
234
|
+
assert.match(css, /\.footer-model-picker[\s\S]*?position:\s*absolute[\s\S]*?left:\s*var\(--footer-model-picker-left, auto\)[\s\S]*?right:\s*var\(--footer-model-picker-right, 0\.95rem\)/, "footer model and effort pickers should render as anchored dropdown popovers");
|
|
223
235
|
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");
|
|
224
236
|
assert.match(css, /bottom:\s*var\(--footer-model-picker-bottom/, "mobile footer model picker should be anchored by a JS-computed viewport offset");
|
|
225
237
|
assert.match(css, /\.footer-model-option\.active/, "footer model picker should style the selected scoped model");
|
|
@@ -355,7 +367,8 @@ assert.match(app, /restoreSidePanelSectionState\(\);\nbindSidePanelSectionToggle
|
|
|
355
367
|
assert.match(app, /OPTIONAL_FEATURES_STORAGE_KEY/, "optional feature disable toggles should persist in browser storage");
|
|
356
368
|
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");
|
|
357
369
|
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");
|
|
358
|
-
assert.match(app, /function renderFooter\(\)[\s\S]*parseGitFooterWebuiPayload\(\)[\s\S]*renderGitFooterPayload\(gitFooterPayload\)/, "detailed footer rendering should prefer the git-footer-status extension payload");
|
|
370
|
+
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
|
+
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");
|
|
359
372
|
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");
|
|
360
373
|
assert.match(app, /function renderGitFooterPayloadMetric\(chip\)[\s\S]*footerMetric\(chip\.icon/, "git footer main payload chips should render as styled metrics");
|
|
361
374
|
assert.match(app, /function renderGitFooterPayloadMeta\(chip, tab\)[\s\S]*footerMeta\(chip\.label, chip\.value, footerMetaClassForPayload\(chip\)/, "git footer meta payload chips should render as styled metadata");
|
|
@@ -368,6 +381,12 @@ assert.doesNotMatch(app, /Git footer status disabled/, "disabled git footer shou
|
|
|
368
381
|
assert.doesNotMatch(app, /footerMeta\("runtime"/, "minimal Web UI footer should not render runtime metadata");
|
|
369
382
|
assert.match(app, /statusEntries\.has\(GIT_FOOTER_WEBUI_STATUS_KEY\)/, "optional feature detection should recognize the git-footer-status Web UI payload");
|
|
370
383
|
assert.match(app, /\/git-footer-refresh --webui-silent/, "Web UI should quietly request the extension-owned footer payload when idle and missing");
|
|
384
|
+
assert.match(app, /function requestGitFooterWebuiPayload\(tabContext = activeTabContext\(\), \{ force = false \} = \{\}\)[\s\S]*?!force && statusEntries\.has\(GIT_FOOTER_WEBUI_STATUS_KEY\)/, "git footer payload refresh should support forced refresh even when a live payload already exists");
|
|
385
|
+
assert.doesNotMatch(app, /function requestGitFooterWebuiPayload\([\s\S]*?statusEntries\.delete\(GIT_FOOTER_WEBUI_STATUS_KEY\)/, "forced git footer refreshes should keep the existing payload visible while the refresh runs");
|
|
386
|
+
assert.match(app, /function applyOptimisticModelSelection\(model, tabContext = activeTabContext\(\)\)[\s\S]*?currentState = \{ \.\.\.currentState, model: nextModel \}[\s\S]*?renderStatus\(\)[\s\S]*?requestGitFooterWebuiPayload\(tabContext, \{ force: true \}\)/, "model changes should update current state and footer immediately before async refreshes complete");
|
|
387
|
+
assert.match(app, /function gitFooterRelevantStateChanged\(previousState, nextState\)[\s\S]*?previousState\.thinkingLevel !== nextState\.thinkingLevel[\s\S]*?modelStateKey\(previousState\.model\) !== modelStateKey\(nextState\.model\)/, "state refresh should detect model and thinking changes that make the git footer payload stale");
|
|
388
|
+
assert.match(app, /requestGitFooterWebuiPayload\(tabContext, \{ force: shouldRefreshGitFooter \}\)/, "state refresh should force-refresh the git footer when model or thinking state changes");
|
|
389
|
+
assert.match(app, /if \(response\.data\?\.level\) requestGitFooterWebuiPayload\(tabContext, \{ force: true \}\)/, "thinking shortcut should immediately force-refresh the git footer payload");
|
|
371
390
|
assert.match(app, /Loading git footer status…/, "missing git footer payload should show a loading state before declaring the extension unavailable");
|
|
372
391
|
assert.match(app, /GIT_FOOTER_WEBUI_PAYLOAD_CACHE_KEY/, "git footer payloads should be cached across Web UI reloads");
|
|
373
392
|
assert.match(app, /function setOptionalFeatureDisabled\(featureId, disabled\)[\s\S]*clearGitFooterWebuiPayloadCache\(\)/, "changing the git footer feature toggle should invalidate the cached footer payload");
|
|
@@ -377,6 +396,8 @@ assert.doesNotMatch(workspaceInfoSource, /runCommand\("git"|branchStatus|isRepo/
|
|
|
377
396
|
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");
|
|
378
397
|
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");
|
|
379
398
|
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");
|
|
399
|
+
assert.match(app, /const hasGitWorkflow = isOptionalFeatureEnabled\("gitWorkflow"\);\n\s+elements\.gitWorkflowButton\.hidden = !hasGitWorkflow/, "guided git workflow composer button should be hidden when unavailable or disabled");
|
|
400
|
+
assert.match(app, /elements\.publishButton\.hidden = !hasPublishWorkflow[\s\S]*elements\.nativeCommandMenuButton\.hidden = !hasNativeCommandMenu/, "optional publish and skills/tools menu buttons should be hidden when no enabled menu items are available");
|
|
380
401
|
assert.match(app, /\["skills", "tuiSkillsCommand"\][\s\S]*\["tools", "tuiToolsCommand"\]/, "optional feature toggles should gate /skills and /tools command surfaces");
|
|
381
402
|
assert.match(app, /function setNativeCommandMenuOpen\(open\)/, "frontend should track the skills/tools command menu open state separately from Publish");
|
|
382
403
|
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");
|
|
@@ -528,6 +549,8 @@ assert.match(app, /sendPromptFromModeButton\("steer", elements\.steerButton\)/,
|
|
|
528
549
|
assert.match(app, /sendPromptFromModeButton\("follow-up", elements\.followUpButton\)/, "Follow-up should show tooltip instead of silently doing nothing when input is empty");
|
|
529
550
|
assert.match(app, /function runPublishWorkflow\(command\)[\s\S]*?sendPrompt\("prompt", command\)/, "Publish workflows should send slash commands directly without replacing the draft");
|
|
530
551
|
assert.match(app, /async function runNativeCommandMenu\(command\)[\s\S]*?await handleNativeSlashSelectorCommand\(command\)/, "skills/tools command menu should open native selector dialogs directly");
|
|
552
|
+
assert.match(app, /async function runNativeCommandMenu\(command\)[\s\S]*?sendPrompt\("prompt", command\)/, "generic native command menu should fall back to slash-command prompt execution");
|
|
553
|
+
assert.match(app, /function setOptionsMenuOpen\(open\)/, "Options menu should have explicit open state");
|
|
531
554
|
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");
|
|
532
555
|
assert.match(app, /renderNativeResourceToggles\(tools, \{[\s\S]*?getResourceTag: nativeToolOriginTag/, "Tools Setup should render Pi Native\/External tags");
|
|
533
556
|
assert.match(app, /const tags = Array\.isArray\(item\.tags\)[\s\S]*?item\.badge, \.\.\.tags/, "native selector filtering should include extra resource tags");
|
|
@@ -535,19 +558,26 @@ assert.match(app, /publishMenuContainer\?\.addEventListener\("pointerenter", \(\
|
|
|
535
558
|
assert.match(app, /publishMenuContainer\?\.addEventListener\("pointerleave", \(\) => setPublishMenuOpen\(false\)\)/, "Publish menu should collapse after hover leaves");
|
|
536
559
|
assert.match(app, /nativeCommandMenuContainer\?\.addEventListener\("pointerenter", \(\) => \{[\s\S]*?setNativeCommandMenuOpen\(true\);[\s\S]*?\}\)/, "skills/tools command menu should expand on hover");
|
|
537
560
|
assert.match(app, /nativeCommandMenuContainer\?\.addEventListener\("pointerleave", \(\) => setNativeCommandMenuOpen\(false\)\)/, "skills/tools command menu should collapse after hover leaves");
|
|
561
|
+
assert.match(app, /optionsMenuContainer\?\.addEventListener\("pointerenter", \(\) => \{[\s\S]*?setOptionsMenuOpen\(true\);[\s\S]*?\}\)/, "Options menu should expand on hover");
|
|
562
|
+
assert.match(app, /optionsMenuContainer\?\.addEventListener\("pointerleave", \(\) => setOptionsMenuOpen\(false\)\)/, "Options menu should collapse after hover leaves");
|
|
538
563
|
assert.match(app, /releaseNpmButton\.addEventListener\("click", \(\) => runPublishWorkflow\("\/release-npm"\)\)/, "Publish menu should launch /release-npm");
|
|
539
564
|
assert.match(app, /releaseAurButton\.addEventListener\("click", \(\) => runPublishWorkflow\("\/release-aur"\)\)/, "Publish menu should launch /release-aur");
|
|
540
565
|
assert.match(app, /nativeSkillsButton\.addEventListener\("click", \(\) => runNativeCommandMenu\("\/skills"\)\)/, "skills/tools command menu should launch /skills");
|
|
541
566
|
assert.match(app, /nativeToolsButton\.addEventListener\("click", \(\) => runNativeCommandMenu\("\/tools"\)\)/, "skills/tools command menu should launch /tools");
|
|
567
|
+
for (const command of ["resume", "reload", "name", "clone", "settings", "export", "fork", "tree"]) {
|
|
568
|
+
const id = command.replace(/^./, (letter) => letter.toUpperCase());
|
|
569
|
+
assert.match(app, new RegExp(`options${id}Button\\.addEventListener\\("click", \\(\\) => runNativeCommandMenu\\("\\/${command}"\\)\\)`), `Options menu should launch /${command}`);
|
|
570
|
+
}
|
|
542
571
|
assert.match(app, /async function sendPrompt\(kind = "prompt", explicitMessage\)/, "prompt sending should accept direct messages that bypass the input field");
|
|
543
572
|
assert.match(app, /const rawMessage = usesPromptInput \? elements\.promptInput\.value : explicitMessage/, "direct prompt sends should not read the input textarea");
|
|
544
573
|
assert.match(app, /if \(usesPromptInput\) \{[\s\S]*?if \(targetStillActive\) \{[\s\S]*?elements\.promptInput\.value = "";/, "direct prompt sends should preserve the input textarea draft");
|
|
545
574
|
assert.match(app, /make\("button", "command-item"\)[\s\S]*?sendPrompt\("prompt", `\/\$\{command\.name\}`\)/, "side-panel command clicks should send the slash command directly");
|
|
546
|
-
assert.match(app, /const NATIVE_SELECTOR_COMMANDS = new Set\(\["model", "settings", "theme", "fork", "clone", "resume", "tree", "login", "logout", "scoped-models", "tools", "skills"\]\)/, "frontend should route native slash commands into selector UIs");
|
|
575
|
+
assert.match(app, /const NATIVE_SELECTOR_COMMANDS = new Set\(\["model", "settings", "theme", "fork", "clone", "name", "resume", "tree", "login", "logout", "scoped-models", "tools", "skills"\]\)/, "frontend should route native slash commands into selector UIs");
|
|
547
576
|
assert.match(app, /async function handleNativeSlashSelectorCommand\(message/, "frontend should intercept exact native slash commands before prompt forwarding");
|
|
548
577
|
assert.match(app, /kind === "prompt" && attachments\.length === 0 && await handleNativeSlashSelectorCommand/, "prompt sending should open native selector dialogs before marking a run active");
|
|
549
578
|
assert.match(app, /function openNativeModelSelector\(\)[\s\S]*?nativeCommandApi\("\/api\/models"\)/, "native /model selector should load models through the active tab API");
|
|
550
579
|
assert.match(app, /function openNativeSettingsDialog\(\)[\s\S]*?\/api\/steering-mode[\s\S]*?\/api\/follow-up-mode[\s\S]*?\/api\/auto-compaction/, "native /settings selector should expose queue and compaction controls");
|
|
580
|
+
assert.match(app, /function openNativeNameDialog\(\)[\s\S]*?sendPrompt\("prompt", `\/name \$\{name\}`\)/, "native /name selector should prompt before running the slash command");
|
|
551
581
|
assert.match(app, /function openNativeForkSelector\(\)[\s\S]*?\/api\/fork-messages[\s\S]*?\/api\/fork/, "native /fork selector should pair fork-point loading with the fork action");
|
|
552
582
|
assert.match(app, /function openNativeResumeSelector\(scope = "current"\)[\s\S]*?\/api\/sessions\?scope=\$\{encodeURIComponent\(selectedScope\)\}/, "native /resume selector should list current-cwd or all sessions");
|
|
553
583
|
assert.match(app, /function openNativeTreeSelector\(\)[\s\S]*?\/api\/session-tree[\s\S]*?\/api\/tree-navigate/, "native /tree selector should list tree entries and navigate through the backend helper");
|
|
@@ -625,7 +655,7 @@ assert.match(app, /case "webui_tab_renamed":/, "frontend should update tab label
|
|
|
625
655
|
assert.match(app, /terminalTabsToggleButton\.addEventListener\("click"/, "terminal tabs trigger should be wired in JS");
|
|
626
656
|
assert.match(app, /composerActionsButton\.addEventListener\("click"/, "composer actions trigger should be wired in JS");
|
|
627
657
|
assert.match(app, /function setMobileFooterExpanded\(/, "mobile footer should preserve expansion state for compatibility");
|
|
628
|
-
assert.match(app, /function updateFooterModelPickerPosition\(\)/, "
|
|
658
|
+
assert.match(app, /function updateFooterModelPickerPosition\(\)[\s\S]*?footerActivePickerTarget\(\)[\s\S]*?--footer-model-picker-left/, "footer picker should align desktop dropdowns above the active model or effort chip");
|
|
629
659
|
assert.match(app, /mobileFooterExpanded = false;[\s\S]*?document\.body\.classList\.remove\("footer-details-expanded"\)/, "opening mobile model picker should collapse legacy footer details so they cannot cover the dropdown");
|
|
630
660
|
assert.match(app, /function renderTuiFooterLine\([\s\S]*footer-line footer-line-tui/, "footer should render a minimal TUI-like line instead of metadata chips");
|
|
631
661
|
assert.match(app, /footerTuiItem\(model, "footer-tui-model", \{[\s\S]*setFooterModelPickerOpen\(!footerModelPickerOpen\)/, "footer model item should be clickable");
|
|
@@ -633,6 +663,10 @@ assert.match(app, /function renderFooterModelPicker\(\)/, "footer should render
|
|
|
633
663
|
assert.match(app, /api\("\/api\/scoped-models", \{ tabId: tabContext\.tabId \}\)/, "footer model picker should load scoped models instead of all available models");
|
|
634
664
|
assert.match(app, /for \(const model of footerScopedModels\)/, "footer model picker should render only scoped models");
|
|
635
665
|
assert.match(app, /api\("\/api\/model", \{ method: "POST"/, "footer model picker should apply selected model through the model API");
|
|
666
|
+
assert.match(app, /chip\.key === "thinking"[\s\S]*?setFooterThinkingPickerOpen\(!footerThinkingPickerOpen\)/, "git footer effort chip should open its own picker");
|
|
667
|
+
assert.match(app, /function renderFooterThinkingPicker\(\)[\s\S]*?Thinking effort[\s\S]*?for \(const level of footerThinkingLevels\(\)\)/, "footer should render a thinking effort picker dropdown");
|
|
668
|
+
assert.match(app, /api\("\/api\/thinking", \{ method: "POST", body: \{ level: nextLevel \}/, "footer thinking picker should apply selected effort through the thinking API");
|
|
669
|
+
assert.match(app, /function isFooterPickerOpen\(\)[\s\S]*?footerModelPickerOpen \|\| footerThinkingPickerOpen/, "footer picker overlay state should cover model and thinking pickers");
|
|
636
670
|
assert.doesNotMatch(app.match(/function renderMinimalFooter\(\)[\s\S]*?\n\}/)?.[0] || "", /footer-details-toggle/, "minimal default footer should not render a details toggle chip");
|
|
637
671
|
assert.match(app, /bindMobileViewChanges\(/, "side panel state should react to mobile breakpoint changes");
|
|
638
672
|
assert.match(app, /function restoreSidePanelState\(\) \{\n\s+if \(isMobileView\(\)\)/, "mobile should start with side panel collapsed even if desktop state was expanded");
|
|
@@ -147,9 +147,12 @@ assert.match(server, /async function cycleTabModel\(tab, direction = "forward"\)
|
|
|
147
147
|
assert.match(server, /url\.pathname === "\/api\/model-cycle" && req\.method === "POST"/, "server should expose model-cycle endpoint for shortcuts");
|
|
148
148
|
assert.match(server, /case "\/api\/thinking-cycle":[\s\S]*?type: "cycle_thinking_level"/, "server should expose thinking-cycle endpoint for shortcuts");
|
|
149
149
|
assert.match(server, /async function setThinkingLevelForTab\(tab, level, \{ allowPending = true \} = \{\}\)[\s\S]*?stateIsBusyForSettings\(stateResult\.data\)[\s\S]*?tab\.pendingThinkingLevel = level/, "server should queue side-panel thinking changes while a tab is running");
|
|
150
|
+
assert.match(server, /function eventForTabClients\(tab, event\)[\s\S]*?responseWithPendingThinking\(tab, event\)[\s\S]*?tabId: tab\.id/, "server should decorate SSE state responses with pending thinking before broadcasting to clients");
|
|
151
|
+
assert.match(server, /tab\.pendingThinkingLevel = level;\n\s+broadcastPendingThinkingState\(tab, stateResult\.data\)/, "server should broadcast queued thinking state after assigning the pending level");
|
|
150
152
|
assert.match(server, /const pendingThinkingResponse = await applyPendingThinkingBeforePrompt\(tab\)/, "server should apply queued thinking level before the next prompt");
|
|
151
153
|
assert.match(app, /pendingThinkingLevel[\s\S]*?next prompt/, "frontend should show queued thinking changes as applying on the next prompt");
|
|
152
154
|
assert.match(app, /response\.data\?\.pending[\s\S]*?will apply to the next prompt/, "frontend should announce queued side-panel thinking changes");
|
|
155
|
+
assert.match(app, /response\.data\?\.level[\s\S]*?Thinking level set to/, "frontend should announce effective side-panel thinking changes");
|
|
153
156
|
assert.match(app, /function handleNativeAppShortcut\(event\)/, "frontend should centralize native app shortcut handling");
|
|
154
157
|
assert.match(app, /openNativeModelSelector\(\)/, "Ctrl+L shortcut should open the native model selector");
|
|
155
158
|
assert.match(app, /cycleModelFromShortcut\(event\.shiftKey \? "backward" : "forward"\)/, "Ctrl+P shortcuts should cycle models forward and backward");
|