@firstpick/pi-package-webui 0.1.7 → 0.1.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
@@ -240,6 +240,20 @@
240
240
  <div id="optionalFeaturesBox" class="optional-features-box muted">Checking optional features…</div>
241
241
  </div>
242
242
  </section>
243
+ <section class="side-panel-section" data-side-panel-section="codex-usage">
244
+ <h2>
245
+ <button id="sidePanelSectionToggleCodexUsage" class="side-panel-section-toggle" type="button" aria-expanded="true" aria-controls="sidePanelSectionCodexUsage" aria-label="Collapse Codex Usage section" title="Collapse Codex Usage section" data-side-panel-section-toggle="codex-usage">
246
+ <span class="side-panel-section-label">Codex Usage</span>
247
+ <span class="side-panel-section-chevron" aria-hidden="true">›</span>
248
+ </button>
249
+ </h2>
250
+ <div id="sidePanelSectionCodexUsage" class="side-panel-section-content">
251
+ <div class="codex-usage-actions">
252
+ <button id="refreshCodexUsageButton" type="button">Refresh usage</button>
253
+ </div>
254
+ <div id="codexUsageBox" class="codex-usage-box muted">Checking Codex usage…</div>
255
+ </div>
256
+ </section>
243
257
  <section class="side-panel-section" data-side-panel-section="session">
244
258
  <h2>
245
259
  <button id="sidePanelSectionToggleSession" class="side-panel-section-toggle" type="button" aria-expanded="true" aria-controls="sidePanelSectionSession" aria-label="Collapse Session section" title="Collapse Session section" data-side-panel-section-toggle="session">
package/public/styles.css CHANGED
@@ -808,6 +808,83 @@ body.side-panel-collapsed .terminal-tabs-shell {
808
808
  background: rgba(var(--ctp-crust-rgb), 0.46);
809
809
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.035), 0 0 1rem rgba(148, 226, 213, 0.05);
810
810
  }
811
+ .codex-usage-actions {
812
+ display: flex;
813
+ justify-content: flex-end;
814
+ margin-bottom: 0.48rem;
815
+ }
816
+ .codex-usage-actions button {
817
+ min-height: 34px;
818
+ padding: 0.32rem 0.58rem;
819
+ font-size: 0.72rem;
820
+ }
821
+ .codex-usage-box {
822
+ display: grid;
823
+ gap: 0.58rem;
824
+ padding: 0.72rem;
825
+ border: 1px solid rgba(180, 190, 254, 0.16);
826
+ border-radius: 0.85rem;
827
+ background: rgba(var(--ctp-crust-rgb), 0.50);
828
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.035), 0 0 1rem rgba(203, 166, 247, 0.05);
829
+ }
830
+ .codex-usage-summary {
831
+ display: flex;
832
+ flex-wrap: wrap;
833
+ justify-content: space-between;
834
+ gap: 0.35rem 0.55rem;
835
+ align-items: baseline;
836
+ }
837
+ .codex-usage-plan {
838
+ color: rgba(var(--ctp-text-rgb), 0.94);
839
+ font-weight: 900;
840
+ }
841
+ .codex-usage-fetched,
842
+ .codex-usage-detail,
843
+ .codex-usage-reset {
844
+ color: rgba(var(--ctp-subtext-rgb), 0.70);
845
+ font-size: 0.72rem;
846
+ line-height: 1.35;
847
+ }
848
+ .codex-usage-unavailable,
849
+ .codex-usage-warning {
850
+ color: var(--ctp-yellow);
851
+ font-weight: 800;
852
+ }
853
+ .codex-usage-bucket {
854
+ display: grid;
855
+ gap: 0.32rem;
856
+ }
857
+ .codex-usage-row {
858
+ display: flex;
859
+ justify-content: space-between;
860
+ gap: 0.6rem;
861
+ align-items: center;
862
+ }
863
+ .codex-usage-label {
864
+ min-width: 0;
865
+ color: rgba(var(--ctp-text-rgb), 0.86);
866
+ font-size: 0.78rem;
867
+ font-weight: 800;
868
+ overflow-wrap: anywhere;
869
+ }
870
+ .codex-usage-percent {
871
+ color: var(--ctp-teal);
872
+ font-size: 0.82rem;
873
+ }
874
+ .codex-usage-meter {
875
+ overflow: hidden;
876
+ height: 0.5rem;
877
+ border: 1px solid rgba(180, 190, 254, 0.14);
878
+ border-radius: 999px;
879
+ background: rgba(var(--ctp-surface-rgb), 0.26);
880
+ }
881
+ .codex-usage-meter-fill {
882
+ display: block;
883
+ height: 100%;
884
+ border-radius: inherit;
885
+ background: linear-gradient(90deg, var(--ctp-green), var(--ctp-yellow), var(--ctp-peach));
886
+ box-shadow: 0 0 0.85rem rgba(148, 226, 213, 0.20);
887
+ }
811
888
  .optional-feature-row {
812
889
  display: grid;
813
890
  grid-template-columns: minmax(0, 1fr) auto;
@@ -51,6 +51,8 @@ assert.match(html, /id="thinkingVisibilityStatus"/, "thinking-output visibility
51
51
  assert.match(html, /id="nativeCommandDialog"/, "native slash selector UI should have a dedicated dialog");
52
52
  assert.match(html, /id="nativeCommandSearch"[^>]*type="search"/, "native slash selector dialog should expose a filter box");
53
53
  assert.match(html, /id="optionalFeaturesBox"/, "side panel should expose optional feature status and controls");
54
+ assert.match(html, /id="codexUsageBox"/, "side panel should expose Codex subscription usage status");
55
+ assert.match(html, /data-side-panel-section="codex-usage"/, "Codex usage should live in a collapsible side-panel section");
54
56
  assert.match(html, /id="serverOfflinePanel"/, "PWA/offline shell should expose a backend-offline recovery panel");
55
57
  assert.match(html, /id="copyServerCommandButton"/, "backend-offline recovery panel should expose a start-command copy button");
56
58
  assert.match(html, /id="retryServerConnectionButton"/, "backend-offline recovery panel should expose a retry button");
@@ -258,6 +260,8 @@ assert.match(app, /Optional feature detection intentionally checks loaded Pi cap
258
260
  assert.match(app, /function resetOptionalFeatureAvailability\(\)/, "optional feature state should reset across active-tab and reload boundaries");
259
261
  assert.match(app, /function renderOptionalFeaturePanel\(\)/, "side panel should render optional feature installed/enabled state");
260
262
  assert.match(app, /function setSidePanelSectionCollapsed\(record, collapsed/, "side panel sections should have explicit collapse/expand behavior");
263
+ assert.match(app, /function renderCodexUsage\(\)/, "frontend should render Codex usage buckets in the side panel");
264
+ assert.match(app, /api\(`\/api\/codex-usage\$\{suffix\}`, \{ scoped: false \}\)/, "Codex usage should load through a server endpoint without browser credentials");
261
265
  assert.match(app, /restoreSidePanelSectionState\(\);\nbindSidePanelSectionToggles\(\);/, "side panel section state should restore before toggles are bound");
262
266
  assert.match(app, /OPTIONAL_FEATURES_STORAGE_KEY/, "optional feature disable toggles should persist in browser storage");
263
267
  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");
@@ -291,6 +295,10 @@ assert.match(app, /const TOOL_LIVE_UPDATE_THROTTLE_MS = 80/, "live tool cards sh
291
295
  assert.match(app, /function updateLiveToolCard\(bubble, message\)[\s\S]*?body\.replaceChildren\(\);[\s\S]*?renderToolExecution\(body, message\);/, "live tool card updates should re-render the existing card body in place");
292
296
  assert.match(app, /function scheduleLiveToolRunRender\(run[\s\S]*?liveToolRenderQueue\.set[\s\S]*?TOOL_LIVE_UPDATE_THROTTLE_MS/, "live tool update events should be queued and throttled for smoother browser output");
293
297
  assert.match(app, /function handleToolExecutionUpdate\(event\)[\s\S]*?event\.partialResult[\s\S]*?scheduleLiveToolRunRender\(run, \{ scroll: false \}\)/, "live tool_execution_update events should update transcript-visible tool cards without replacing them per event");
298
+ assert.match(app, /function captureReusableToolCards\(\)[\s\S]*?\.message\.toolExecution\[data-tool-call-id\]/, "full transcript re-renders should capture existing tool cards before clearing the chat");
299
+ assert.match(app, /function appendMessage\(message,[\s\S]*?reusableToolCards = null[\s\S]*?reuseToolExecutionBubble\(reusableToolCards, message/, "message rendering should reuse matching tool cards instead of replacing them during refreshes");
300
+ assert.match(app, /function renderAllMessages\(\{ preserveScroll = false \} = \{\}\)[\s\S]*?const reusableToolCards = captureReusableToolCards\(\);[\s\S]*?appendTranscriptMessage\(item\.message,[\s\S]*?reusableToolCards,/, "transcript refreshes should pass reusable tool cards through to item rendering");
301
+ assert.match(app, /const keyedToolExecution = message\.role === "toolExecution" && message\.toolCallId[\s\S]*?keyedToolExecution \? "toolExecution"[\s\S]*?keyedToolExecution \? "" : message\.title[\s\S]*?keyedToolExecution \? "" : message\.timestamp/, "tool action entry identity should stay stable when live transient cards become persisted transcript cards");
294
302
  assert.match(app, /appendText\(preview, toolResultPreviewText\(message, 10\), "code-block tool-result-preview-text"\)/, "collapsed tool results should render the first ten preview lines by default");
295
303
  assert.match(app, /function assistantDisplayMessages\(message\)/, "assistant history should split thinking and tool-call parts out of the final Assistant output card");
296
304
  assert.match(app, /function assistantHasToolCallAfter\(content, index\)/, "assistant text that precedes a tool call should be detectable and suppressible");
@@ -486,6 +494,10 @@ assert.ok(icon512.length > icon192.length, "PWA 512px icon should be present and
486
494
  assert.ok(matrixBackground.length > 100000, "Matrix background image should be present as an optimized WebP asset");
487
495
  assert.ok(mochaBackground.length > 8000, "Catppuccin Mocha background image should be present as a compact PNG asset");
488
496
 
497
+ assert.match(server, /AuthStorage, SessionManager/, "server should import AuthStorage for safe OAuth token refresh");
498
+ assert.match(server, /const CODEX_TOKEN_REFRESH_SKEW_MS = 5 \* 60 \* 1000/, "server should refresh Codex OAuth tokens before they expire");
499
+ assert.match(server, /url\.pathname === "\/api\/codex-usage" && req\.method === "GET"/, "server should expose a sanitized Codex usage endpoint");
500
+ assert.match(server, /OPENAI_CODEX_USAGE_ENDPOINT/, "server should query Codex usage from the backend, not the browser");
489
501
  assert.match(server, /const NATIVE_SLASH_COMMANDS = \[/, "server should define Pi native slash commands for autocomplete");
490
502
  assert.match(server, /\{ name: "reload", description: "Reload keybindings, extensions, skills, prompts, and themes" \}/, "native /reload should be advertised for autocomplete");
491
503
  assert.match(server, /function parseSlashCommand\(message\)/, "server should parse native slash commands before prompt forwarding");