@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/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: grid;
1485
- grid-template-columns: minmax(10rem, 1.9fr) minmax(5rem, 0.55fr) minmax(6.5rem, 0.6fr) minmax(7rem, 0.65fr) minmax(12rem, 1.2fr);
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
- right: 0.95rem;
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 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
3861
- .footer-workspace,
3862
- .footer-model { grid-column: 1 / -1; }
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 picker should render as a dropdown/popover");
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\(\)/, "mobile model picker should compute a fixed overlay position above the footer");
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");