@firstpick/pi-package-webui 0.2.7 → 0.2.9

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");
@@ -369,6 +382,8 @@ assert.doesNotMatch(app, /footerMeta\("runtime"/, "minimal Web UI footer should
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");
371
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");
372
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");
373
388
  assert.match(app, /requestGitFooterWebuiPayload\(tabContext, \{ force: shouldRefreshGitFooter \}\)/, "state refresh should force-refresh the git footer when model or thinking state changes");
374
389
  assert.match(app, /if \(response\.data\?\.level\) requestGitFooterWebuiPayload\(tabContext, \{ force: true \}\)/, "thinking shortcut should immediately force-refresh the git footer payload");
@@ -381,6 +396,8 @@ assert.doesNotMatch(workspaceInfoSource, /runCommand\("git"|branchStatus|isRepo/
381
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");
382
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");
383
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");
384
401
  assert.match(app, /\["skills", "tuiSkillsCommand"\][\s\S]*\["tools", "tuiToolsCommand"\]/, "optional feature toggles should gate /skills and /tools command surfaces");
385
402
  assert.match(app, /function setNativeCommandMenuOpen\(open\)/, "frontend should track the skills/tools command menu open state separately from Publish");
386
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");
@@ -532,6 +549,8 @@ assert.match(app, /sendPromptFromModeButton\("steer", elements\.steerButton\)/,
532
549
  assert.match(app, /sendPromptFromModeButton\("follow-up", elements\.followUpButton\)/, "Follow-up should show tooltip instead of silently doing nothing when input is empty");
533
550
  assert.match(app, /function runPublishWorkflow\(command\)[\s\S]*?sendPrompt\("prompt", command\)/, "Publish workflows should send slash commands directly without replacing the draft");
534
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");
535
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");
536
555
  assert.match(app, /renderNativeResourceToggles\(tools, \{[\s\S]*?getResourceTag: nativeToolOriginTag/, "Tools Setup should render Pi Native\/External tags");
537
556
  assert.match(app, /const tags = Array\.isArray\(item\.tags\)[\s\S]*?item\.badge, \.\.\.tags/, "native selector filtering should include extra resource tags");
@@ -539,19 +558,26 @@ assert.match(app, /publishMenuContainer\?\.addEventListener\("pointerenter", \(\
539
558
  assert.match(app, /publishMenuContainer\?\.addEventListener\("pointerleave", \(\) => setPublishMenuOpen\(false\)\)/, "Publish menu should collapse after hover leaves");
540
559
  assert.match(app, /nativeCommandMenuContainer\?\.addEventListener\("pointerenter", \(\) => \{[\s\S]*?setNativeCommandMenuOpen\(true\);[\s\S]*?\}\)/, "skills/tools command menu should expand on hover");
541
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");
542
563
  assert.match(app, /releaseNpmButton\.addEventListener\("click", \(\) => runPublishWorkflow\("\/release-npm"\)\)/, "Publish menu should launch /release-npm");
543
564
  assert.match(app, /releaseAurButton\.addEventListener\("click", \(\) => runPublishWorkflow\("\/release-aur"\)\)/, "Publish menu should launch /release-aur");
544
565
  assert.match(app, /nativeSkillsButton\.addEventListener\("click", \(\) => runNativeCommandMenu\("\/skills"\)\)/, "skills/tools command menu should launch /skills");
545
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
+ }
546
571
  assert.match(app, /async function sendPrompt\(kind = "prompt", explicitMessage\)/, "prompt sending should accept direct messages that bypass the input field");
547
572
  assert.match(app, /const rawMessage = usesPromptInput \? elements\.promptInput\.value : explicitMessage/, "direct prompt sends should not read the input textarea");
548
573
  assert.match(app, /if \(usesPromptInput\) \{[\s\S]*?if \(targetStillActive\) \{[\s\S]*?elements\.promptInput\.value = "";/, "direct prompt sends should preserve the input textarea draft");
549
574
  assert.match(app, /make\("button", "command-item"\)[\s\S]*?sendPrompt\("prompt", `\/\$\{command\.name\}`\)/, "side-panel command clicks should send the slash command directly");
550
- 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");
551
576
  assert.match(app, /async function handleNativeSlashSelectorCommand\(message/, "frontend should intercept exact native slash commands before prompt forwarding");
552
577
  assert.match(app, /kind === "prompt" && attachments\.length === 0 && await handleNativeSlashSelectorCommand/, "prompt sending should open native selector dialogs before marking a run active");
553
578
  assert.match(app, /function openNativeModelSelector\(\)[\s\S]*?nativeCommandApi\("\/api\/models"\)/, "native /model selector should load models through the active tab API");
554
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");
555
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");
556
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");
557
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");
@@ -629,7 +655,7 @@ assert.match(app, /case "webui_tab_renamed":/, "frontend should update tab label
629
655
  assert.match(app, /terminalTabsToggleButton\.addEventListener\("click"/, "terminal tabs trigger should be wired in JS");
630
656
  assert.match(app, /composerActionsButton\.addEventListener\("click"/, "composer actions trigger should be wired in JS");
631
657
  assert.match(app, /function setMobileFooterExpanded\(/, "mobile footer should preserve expansion state for compatibility");
632
- 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");
633
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");
634
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");
635
661
  assert.match(app, /footerTuiItem\(model, "footer-tui-model", \{[\s\S]*setFooterModelPickerOpen\(!footerModelPickerOpen\)/, "footer model item should be clickable");
@@ -637,6 +663,10 @@ assert.match(app, /function renderFooterModelPicker\(\)/, "footer should render
637
663
  assert.match(app, /api\("\/api\/scoped-models", \{ tabId: tabContext\.tabId \}\)/, "footer model picker should load scoped models instead of all available models");
638
664
  assert.match(app, /for \(const model of footerScopedModels\)/, "footer model picker should render only scoped models");
639
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");
640
670
  assert.doesNotMatch(app.match(/function renderMinimalFooter\(\)[\s\S]*?\n\}/)?.[0] || "", /footer-details-toggle/, "minimal default footer should not render a details toggle chip");
641
671
  assert.match(app, /bindMobileViewChanges\(/, "side panel state should react to mobile breakpoint changes");
642
672
  assert.match(app, /function restoreSidePanelState\(\) \{\n\s+if \(isMobileView\(\)\)/, "mobile should start with side panel collapsed even if desktop state was expanded");