pi-studio 0.9.49 → 0.9.50

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/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.50] — 2026-08-25
8
+
9
+ ### Added
10
+ - Allow the global Studio header to be hidden independently and restored from a fixed top-edge control without shifting the workspace layout.
11
+
12
+ ### Changed
13
+ - Hide the global Studio header by default in Zen mode, while keeping **F9** as an immediate way to exit Zen and restore the full header.
14
+
7
15
  ## [0.9.49] — 2026-08-24
8
16
 
9
17
  ### Added
package/README.md CHANGED
@@ -30,7 +30,7 @@ _The video shows an earlier version of the Studio interface. The basic workflow
30
30
 
31
31
  - Opens a two-pane browser workspace: **Editor** (left) + **Response/Working/Editor Preview/Quarto Preview** (right)
32
32
  - Supports one canonical full Studio view per Pi session, plus additional editor-only companion views when you want extra editing/preview surfaces; editor-only views can also browse files and use the Studio REPL send controls without taking over the full Studio session view
33
- - Includes a global **Zen** mode for hiding secondary Studio chrome without changing the current left/right pane layout
33
+ - Includes a global **Zen** mode that hides the Studio header and secondary chrome without changing the current left/right pane layout; the header can also be hidden independently and restored from the top-right edge
34
34
  - Runs editor text directly, asks for structured critique (auto/writing/code focus), offers explicit **Show me** actions for a one-turn compact visual or structural explanation of the editor selection/document, displayed response, or current conversation topic, provides a manual **Suggest completion** action for short cursor-aware continuations (`Option/Alt+Tab` where available or `Cmd/Ctrl+Shift+Space` from the editor, `Tab` to insert a visible suggestion) with an optional editor-plus-latest-response context mode, or opens **Quiz me** for a Studio-native active-recall loop over the current editor text, selection, current file, folder, or repo, with optional focus guidance for shaping question selection
35
35
  - Includes a live **Working** view for following current model/tool activity, with `All` / `Thinking` / `Tools` filters, image previews for image-producing tool outputs, plus **Load visible into editor** and **Copy visible** actions; when cycling response history, Working follows saved working details for the selected response when available, and `Cmd/Ctrl+Alt+1–7` switches directly between right-pane views while `Cmd/Ctrl+Alt+P` / `Cmd/Ctrl+Alt+E` / `Cmd/Ctrl+Alt+W` keep quick mnemonic shortcuts for Response Preview, Editor Preview, and Working
36
36
  - Includes a right-pane **Changes** view for browsing the current git diff by file, previewing per-file diffs, opening changed files, loading the full diff into the editor, and copying the diff
@@ -116,6 +116,10 @@
116
116
  const sendEditorBtn = document.getElementById("sendEditorBtn");
117
117
  const openCompanionBtn = document.getElementById("openCompanionBtn");
118
118
  const getEditorBtn = document.getElementById("getEditorBtn");
119
+ const studioHeaderEl = document.getElementById("studioHeader");
120
+ const hideStudioHeaderBtn = document.getElementById("hideStudioHeaderBtn");
121
+ const studioHeaderRevealEl = document.getElementById("studioHeaderReveal");
122
+ const studioHeaderRevealBtn = document.getElementById("studioHeaderRevealBtn");
119
123
  const zenModeBtn = document.getElementById("zenModeBtn");
120
124
  const sendRunBtn = document.getElementById("sendRunBtn");
121
125
  const queueSteerBtn = document.getElementById("queueSteerBtn");
@@ -2187,6 +2191,7 @@
2187
2191
  let lineNumbersRenderRaf = null;
2188
2192
  let annotationsEnabled = true;
2189
2193
  const STUDIO_ZEN_MODE_STORAGE_KEY = "piStudio.zenMode";
2194
+ const STUDIO_HEADER_HIDDEN_STORAGE_KEY = "piStudio.headerHidden";
2190
2195
  const studioUiRefreshEnabled = readStudioUiRefreshEnabled();
2191
2196
  const EDITOR_FONT_SIZE_OPTIONS = [10, 11, 12, 13, 14, 15, 16, 18];
2192
2197
  const RESPONSE_FONT_SIZE_OPTIONS = [11, 12, 12.5, 13, 13.5, 14, 14.5, 15, 15.5, 16, 18, 20];
@@ -2239,12 +2244,17 @@
2239
2244
  let fileBrowserLoadNonce = 0;
2240
2245
  let studioUiRefreshUi = null;
2241
2246
  let studioZenModeEnabled = readStudioZenModeEnabled();
2247
+ let studioHeaderHidden = readStudioHeaderHiddenEnabled();
2248
+ let studioHeaderRevealTimer = null;
2242
2249
  if (studioUiRefreshEnabled && document.body) {
2243
2250
  document.body.classList.add("studio-ui-refresh");
2244
2251
  }
2245
2252
  if (studioZenModeEnabled && document.body) {
2246
2253
  document.body.classList.add("studio-zen-mode");
2247
2254
  }
2255
+ if ((studioZenModeEnabled || studioHeaderHidden) && document.body) {
2256
+ document.body.classList.add("studio-header-hidden");
2257
+ }
2248
2258
  let scratchpadText = "";
2249
2259
  let scratchpadReturnFocusEl = null;
2250
2260
  let scratchpadPersistTimer = null;
@@ -2308,22 +2318,129 @@
2308
2318
  }
2309
2319
  }
2310
2320
 
2311
- function syncStudioZenModeUi() {
2312
- if (document.body) document.body.classList.toggle("studio-zen-mode", studioZenModeEnabled);
2313
- if (!zenModeBtn) return;
2314
- zenModeBtn.textContent = studioZenModeEnabled ? "Exit Zen" : "Zen";
2315
- zenModeBtn.title = studioZenModeEnabled ? "Show full Studio controls. Shortcut: F9." : "Hide secondary Studio controls. Shortcut: F9.";
2316
- zenModeBtn.setAttribute("aria-pressed", studioZenModeEnabled ? "true" : "false");
2321
+ function readStudioHeaderHiddenEnabled() {
2322
+ const normalize = (value) => String(value == null ? "" : value).trim().toLowerCase();
2323
+ const isTruthy = (value) => ["1", "true", "yes", "on", "hidden"].indexOf(normalize(value)) !== -1;
2324
+ try {
2325
+ const stored = window.localStorage ? window.localStorage.getItem(STUDIO_HEADER_HIDDEN_STORAGE_KEY) : null;
2326
+ if (stored === null) return false;
2327
+ return isTruthy(stored);
2328
+ } catch {
2329
+ return false;
2330
+ }
2317
2331
  }
2318
2332
 
2319
- function setStudioZenMode(enabled) {
2320
- studioZenModeEnabled = Boolean(enabled);
2333
+ function persistStudioHeaderHiddenEnabled() {
2334
+ try {
2335
+ window.localStorage && window.localStorage.setItem(STUDIO_HEADER_HIDDEN_STORAGE_KEY, studioHeaderHidden ? "1" : "0");
2336
+ } catch {}
2337
+ }
2338
+
2339
+ function focusStudioChromeControl(control) {
2340
+ if (!control || typeof control.focus !== "function") return;
2341
+ window.setTimeout(() => {
2342
+ try {
2343
+ control.focus({ preventScroll: true });
2344
+ } catch {
2345
+ try { control.focus(); } catch {}
2346
+ }
2347
+ }, 0);
2348
+ }
2349
+
2350
+ function showStudioHeaderRevealControl() {
2351
+ if (!studioHeaderRevealEl || !studioHeaderRevealBtn) return;
2352
+ studioHeaderRevealEl.classList.add("is-open");
2353
+ if (studioHeaderRevealTimer) window.clearTimeout(studioHeaderRevealTimer);
2354
+ studioHeaderRevealTimer = window.setTimeout(() => {
2355
+ studioHeaderRevealTimer = null;
2356
+ studioHeaderRevealEl.classList.remove("is-open");
2357
+ }, 1800);
2358
+ focusStudioChromeControl(studioHeaderRevealBtn);
2359
+ }
2360
+
2361
+ function syncStudioHeaderVisibilityUi() {
2362
+ const hidden = Boolean(studioZenModeEnabled || studioHeaderHidden);
2363
+ if (document.body) document.body.classList.toggle("studio-header-hidden", hidden);
2364
+ if (studioHeaderRevealEl) {
2365
+ studioHeaderRevealEl.hidden = !hidden;
2366
+ studioHeaderRevealEl.dataset.mode = studioZenModeEnabled ? "zen" : "header";
2367
+ if (!hidden) studioHeaderRevealEl.classList.remove("is-open");
2368
+ }
2369
+ if (!hidden && studioHeaderRevealTimer) {
2370
+ window.clearTimeout(studioHeaderRevealTimer);
2371
+ studioHeaderRevealTimer = null;
2372
+ }
2373
+ if (studioHeaderRevealBtn) {
2374
+ const label = studioZenModeEnabled ? "Exit Zen" : "Show header";
2375
+ studioHeaderRevealBtn.textContent = label;
2376
+ studioHeaderRevealBtn.setAttribute("aria-label", label);
2377
+ studioHeaderRevealBtn.title = studioZenModeEnabled
2378
+ ? "Exit Zen and restore the Studio header. Shortcut: F9."
2379
+ : "Restore the Studio header.";
2380
+ }
2381
+ }
2382
+
2383
+ function syncStudioZenModeUi() {
2384
+ if (document.body) document.body.classList.toggle("studio-zen-mode", studioZenModeEnabled);
2385
+ if (zenModeBtn) {
2386
+ zenModeBtn.textContent = studioZenModeEnabled ? "Exit Zen" : "Zen";
2387
+ zenModeBtn.title = studioZenModeEnabled
2388
+ ? "Exit Zen and restore the Studio header. Shortcut: F9."
2389
+ : "Hide the Studio header and secondary controls. Shortcut: F9.";
2390
+ zenModeBtn.setAttribute("aria-pressed", studioZenModeEnabled ? "true" : "false");
2391
+ }
2392
+ syncStudioHeaderVisibilityUi();
2393
+ }
2394
+
2395
+ function setStudioHeaderHidden(enabled, options) {
2396
+ const nextHidden = Boolean(enabled);
2397
+ const moveFocusToReveal = Boolean(
2398
+ nextHidden
2399
+ && (
2400
+ (options && options.focusReveal)
2401
+ || (studioHeaderEl && document.activeElement && studioHeaderEl.contains(document.activeElement))
2402
+ )
2403
+ );
2404
+ studioHeaderHidden = nextHidden;
2405
+ persistStudioHeaderHiddenEnabled();
2406
+ closeStudioUiRefreshMenus();
2407
+ closeExportPreviewMenu();
2408
+ syncStudioHeaderVisibilityUi();
2409
+ if (moveFocusToReveal) showStudioHeaderRevealControl();
2410
+ }
2411
+
2412
+ function setStudioZenMode(enabled, options) {
2413
+ const nextEnabled = Boolean(enabled);
2414
+ const moveFocusToReveal = Boolean(
2415
+ nextEnabled
2416
+ && (
2417
+ (options && options.focusReveal)
2418
+ || (studioHeaderEl && document.activeElement && studioHeaderEl.contains(document.activeElement))
2419
+ )
2420
+ );
2421
+ studioZenModeEnabled = nextEnabled;
2422
+ if (!studioZenModeEnabled) {
2423
+ studioHeaderHidden = false;
2424
+ persistStudioHeaderHiddenEnabled();
2425
+ }
2321
2426
  try {
2322
2427
  window.localStorage && window.localStorage.setItem(STUDIO_ZEN_MODE_STORAGE_KEY, studioZenModeEnabled ? "1" : "0");
2323
2428
  } catch {}
2324
2429
  closeStudioUiRefreshMenus();
2325
2430
  closeExportPreviewMenu();
2326
2431
  syncStudioZenModeUi();
2432
+ if (moveFocusToReveal) showStudioHeaderRevealControl();
2433
+ }
2434
+
2435
+ function restoreStudioHeaderFromReveal() {
2436
+ const wasZenMode = studioZenModeEnabled;
2437
+ if (wasZenMode) {
2438
+ setStudioZenMode(false);
2439
+ } else {
2440
+ setStudioHeaderHidden(false);
2441
+ }
2442
+ focusStudioChromeControl(wasZenMode ? zenModeBtn : hideStudioHeaderBtn);
2443
+ setStatus(wasZenMode ? "Zen mode off. Studio header restored." : "Studio header shown.");
2327
2444
  }
2328
2445
 
2329
2446
  function makeStudioUiRefreshElement(tagName, className, text) {
@@ -4658,8 +4775,17 @@
4658
4775
  const isZenModeShortcut = key === "F9" && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
4659
4776
  if (isZenModeShortcut) {
4660
4777
  event.preventDefault();
4661
- setStudioZenMode(!studioZenModeEnabled);
4662
- setStatus(studioZenModeEnabled ? "Zen mode on." : "Zen mode off.");
4778
+ const restoringHeaderOnly = Boolean(!studioZenModeEnabled && studioHeaderHidden);
4779
+ if (studioZenModeEnabled) {
4780
+ setStudioZenMode(false);
4781
+ } else if (studioHeaderHidden) {
4782
+ setStudioHeaderHidden(false);
4783
+ } else {
4784
+ setStudioZenMode(true);
4785
+ }
4786
+ setStatus(studioZenModeEnabled
4787
+ ? "Zen mode on. Studio header hidden; press F9 to exit."
4788
+ : (restoringHeaderOnly ? "Studio header restored." : "Zen mode off. Studio header restored."));
4663
4789
  return;
4664
4790
  }
4665
4791
 
@@ -22254,9 +22380,29 @@
22254
22380
  });
22255
22381
  }
22256
22382
 
22383
+ if (hideStudioHeaderBtn) {
22384
+ hideStudioHeaderBtn.addEventListener("click", () => {
22385
+ setStudioHeaderHidden(true, { focusReveal: true });
22386
+ setStatus("Studio header hidden. Move to the top-right edge to restore it.");
22387
+ });
22388
+ }
22389
+
22390
+ if (studioHeaderRevealEl) {
22391
+ studioHeaderRevealEl.addEventListener("click", (event) => {
22392
+ if (event.target === studioHeaderRevealEl) restoreStudioHeaderFromReveal();
22393
+ });
22394
+ }
22395
+
22396
+ if (studioHeaderRevealBtn) {
22397
+ studioHeaderRevealBtn.addEventListener("click", restoreStudioHeaderFromReveal);
22398
+ }
22399
+
22257
22400
  if (zenModeBtn) {
22258
22401
  zenModeBtn.addEventListener("click", () => {
22259
- setStudioZenMode(!studioZenModeEnabled);
22402
+ setStudioZenMode(!studioZenModeEnabled, { focusReveal: !studioZenModeEnabled });
22403
+ setStatus(studioZenModeEnabled
22404
+ ? "Zen mode on. Studio header hidden; press F9 to exit."
22405
+ : "Zen mode off. Studio header restored.");
22260
22406
  });
22261
22407
  }
22262
22408
 
package/client/studio.css CHANGED
@@ -59,28 +59,77 @@
59
59
  flex-wrap: wrap;
60
60
  }
61
61
 
62
- .zen-mode-btn {
62
+ .zen-mode-btn,
63
+ .header-visibility-btn {
63
64
  font-weight: inherit;
64
65
  white-space: nowrap;
65
66
  }
66
67
 
67
- body.studio-zen-mode > header {
68
- padding: 8px 12px;
68
+ body.studio-header-hidden > #studioHeader,
69
+ body.studio-zen-mode > #studioHeader {
70
+ display: none !important;
69
71
  }
70
72
 
71
- body.studio-zen-mode .app-subtitle {
72
- display: none;
73
+ .studio-header-reveal {
74
+ position: fixed;
75
+ top: 0;
76
+ right: max(8px, env(safe-area-inset-right));
77
+ z-index: 60;
78
+ width: 118px;
79
+ height: 10px;
80
+ cursor: pointer;
73
81
  }
74
82
 
75
- body.studio-zen-mode > header .controls > :not(#zenModeBtn) {
83
+ .studio-header-reveal[hidden] {
76
84
  display: none !important;
77
85
  }
78
86
 
79
- body.studio-zen-mode #zenModeBtn {
87
+ .studio-header-reveal::after {
88
+ content: "";
89
+ position: absolute;
90
+ right: 10px;
91
+ bottom: 2px;
92
+ left: 10px;
93
+ height: 3px;
94
+ border-radius: 999px;
95
+ background: var(--accent);
96
+ opacity: 0.62;
97
+ pointer-events: none;
98
+ }
99
+
100
+ .studio-header-reveal button {
101
+ position: absolute;
102
+ top: 0;
103
+ right: 0;
104
+ min-width: 100%;
105
+ min-height: 32px;
106
+ padding: 5px 10px;
107
+ border-top: 0;
108
+ border-radius: 0 0 9px 9px;
80
109
  background: var(--panel);
81
- border-color: var(--control-border);
82
110
  color: var(--text);
83
- font-weight: inherit;
111
+ box-shadow: 0 8px 24px var(--shadow-color);
112
+ white-space: nowrap;
113
+ transform: translateY(calc(-100% + 5px));
114
+ transition: transform 140ms ease;
115
+ }
116
+
117
+ .studio-header-reveal.is-open button,
118
+ .studio-header-reveal:hover button,
119
+ .studio-header-reveal:focus-within button {
120
+ transform: translateY(0);
121
+ }
122
+
123
+ .studio-header-reveal button:not(:disabled):hover,
124
+ .studio-header-reveal button:focus-visible {
125
+ background: var(--panel-2);
126
+ color: var(--accent);
127
+ }
128
+
129
+ @media (prefers-reduced-motion: reduce) {
130
+ .studio-header-reveal button {
131
+ transition: none;
132
+ }
84
133
  }
85
134
 
86
135
  .export-preview-controls {
@@ -5388,10 +5437,6 @@
5388
5437
  gap: 5px;
5389
5438
  }
5390
5439
 
5391
- body.studio-ui-refresh.studio-zen-mode > header {
5392
- padding: 7px 12px;
5393
- }
5394
-
5395
5440
  body.studio-ui-refresh > header button,
5396
5441
  body.studio-ui-refresh #responseActions button,
5397
5442
  body.studio-ui-refresh #responseActions select {
package/index.ts CHANGED
@@ -10756,7 +10756,7 @@ ${cssVarsBlock}
10756
10756
  <link rel="stylesheet" href="${stylesheetHref}" />
10757
10757
  </head>
10758
10758
  <body data-initial-source="${initialSource}" data-initial-label="${initialLabel}" data-initial-path="${initialPath}" data-initial-draft-id="${initialDraftId}" data-initial-resource-dir="${initialResourceDir}" data-model-label="${initialModel}" data-terminal-label="${initialTerminal}" data-terminal-detail="${initialTerminalDetailAttr}" data-theme-name="${initialTheme}" data-context-tokens="${initialContextTokens}" data-context-window="${initialContextWindow}" data-context-percent="${initialContextPercent}" data-studio-mode="${studioMode}" data-ssh-session="${initialSshSession}">
10759
- <header>
10759
+ <header id="studioHeader">
10760
10760
  <h1><span class="app-logo" aria-hidden="true">π</span> Studio <span class="app-subtitle">${appSubtitle}</span></h1>
10761
10761
  <div class="controls">
10762
10762
  <button id="saveAsBtn" type="button" title="Save editor content to a new file path. Cmd/Ctrl+S falls back here when no direct save path is available.">Save editor as…</button>
@@ -10766,9 +10766,13 @@ ${cssVarsBlock}
10766
10766
  <button id="importFileBtn" type="button" title="Import a file as an editable copy.">Import file copy…</button>
10767
10767
  <input id="fileInput" class="file-input-hidden" type="file" tabindex="-1" aria-hidden="true" accept=".md,.markdown,.mdx,.qmd,.js,.mjs,.cjs,.jsx,.ts,.mts,.cts,.tsx,.py,.pyw,.sh,.bash,.zsh,.json,.jsonc,.json5,.rs,.c,.h,.cpp,.cxx,.cc,.hpp,.hxx,.jl,.f90,.f95,.f03,.f,.for,.r,.R,.m,.tex,.latex,.diff,.patch,.java,.go,.rb,.swift,.html,.htm,.css,.xml,.yaml,.yml,.toml,.lua,.txt,.rst,.adoc" />
10768
10768
  <button id="getEditorBtn" type="button" title="Load the current terminal editor draft into Studio.">Load from pi editor</button>
10769
- <button id="zenModeBtn" class="zen-mode-btn" type="button" title="Hide secondary Studio controls. Shortcut: F9.">Zen</button>
10769
+ <button id="hideStudioHeaderBtn" class="header-visibility-btn" type="button" aria-controls="studioHeader" title="Hide the global Studio header. Restore it from the top-right edge.">Hide header</button>
10770
+ <button id="zenModeBtn" class="zen-mode-btn" type="button" title="Hide the Studio header and secondary controls. Shortcut: F9.">Zen</button>
10770
10771
  </div>
10771
10772
  </header>
10773
+ <div id="studioHeaderReveal" class="studio-header-reveal" hidden>
10774
+ <button id="studioHeaderRevealBtn" type="button" aria-controls="studioHeader" title="Show the Studio header.">Show header</button>
10775
+ </div>
10772
10776
 
10773
10777
  <main>
10774
10778
  <section id="leftPane">
@@ -11081,7 +11085,7 @@ ${cssVarsBlock}
11081
11085
  <div><dt>Cmd/Ctrl+Alt+W</dt><dd>Switch the right pane directly to Working</dd></div>
11082
11086
  <div><dt>F8</dt><dd>Focus editor text</dd></div>
11083
11087
  <div><dt>Shift+F8</dt><dd>Focus right-pane content</dd></div>
11084
- <div><dt>F9</dt><dd>Toggle Zen mode</dd></div>
11088
+ <div><dt>F9</dt><dd>Toggle Zen mode and hide or restore the Studio header</dd></div>
11085
11089
  <div><dt>F10</dt><dd>Focus or unfocus the active pane</dd></div>
11086
11090
  <div><dt>Esc</dt><dd>Close overlays, exit pane focus, or stop an active request</dd></div>
11087
11091
  <div><dt>?</dt><dd>Show keyboard shortcuts when not editing text</dd></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.9.49",
3
+ "version": "0.9.50",
4
4
  "description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, active quiz, prompt/response history, live previews, and tmux-backed REPL/literate REPL workflows",
5
5
  "type": "module",
6
6
  "license": "MIT",