pi-studio 0.9.60-rc.0 → 0.9.60

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,22 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.60] — 2026-09-07
8
+
9
+ ### Added
10
+ - Promote the opt-in `--listen-all` networking support from `0.9.60-rc.0` to stable: default binding remains localhost, wildcard binding requires explicit intent and token authentication, and status reports the actual bind address. See the RC security notes below for the exposure and Origin-check boundaries.
11
+ - Add `Cmd/Ctrl+Option/Alt+A` to toggle the browser-stored **Follow activity** preference from the main editable Studio workspace.
12
+ - Add `Cmd/Ctrl+Option/Alt+Enter` for **Annotate response** from either main pane, retaining the existing action's availability and modal guards without changing Run, REPL, or Side question shortcuts. Add Shift (`Cmd/Ctrl+Option/Alt+Shift+Enter`) for **Load response into editor** without switching views, through the existing button and the same replacement safeguards. All three shortcuts ignore held-key repeats and text composition. View-preserving Load refuses to detach the file required by Quarto Preview; choose another view first or use Annotate response.
13
+
14
+ ### Changed
15
+ - Use **Option/Alt** consistently in current shortcut labels, tooltips, and documentation. Keyboard behavior and standard accessibility key names are unchanged.
16
+
17
+ ### Fixed
18
+ - Protect unsubmitted editor changes and unsaved file edits before **Annotate response** or **Load response into editor** replaces them. Keep unchanged accepted prompts confirmation-free using bounded, page-memory-only request-correlated snapshots; failed submissions and late acknowledgements cannot mark newer edits as submitted, and submission never marks file text saved. Cancel preserves text, source identity, and view; confirmation is revalidated against the editor and selected response before replacement. Pi-terminal draft-cleanup provenance remains separate.
19
+
20
+ ### Validation note
21
+ - Production network lifecycle and TCP-forwarding checks pass, but exact `sbx` confirmation from the issue #4 reporter is still outstanding. The issue remains open for confirmation or a follow-up fix; this release does not claim that environment has been tested directly.
22
+
7
23
  ## [0.9.60-rc.0] — 2026-09-03
8
24
 
9
25
  ### Added
package/README.md CHANGED
@@ -37,7 +37,7 @@ _The video shows an earlier version of the Studio interface. The basic workflow
37
37
  - 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
38
38
  - Treats text copied from or staged in Pi's terminal input editor as a linked draft: ordinary Studio edits retain the link, and an accepted **Run editor text** clears the terminal draft only when it has not changed in the meantime; a confirmed **Clear Pi editor text…** action remains available explicitly
39
39
  - Adds a right-pane **Side questions** thread for contextual questions that stay outside the main Pi conversation unless explicitly promoted. It can start with the editor selection, the Markdown/LaTeX heading block at the cursor, nearby unstructured text, the whole editor, the displayed response, or no starting text; independently, it can use read-only tools to map, search, and read the document folder, repository, or another chosen folder—including extracted PDF, DOCX, ODT, and EPUB text—and can optionally search the web through Brave Search.
40
- - 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; optional browser-stored **Follow activity** (off by default under **View → Workspace**) switches to Working when the main Pi agent starts generating and to Response Preview when it completes, unless you manually choose another view during that request. When cycling response history, Working follows saved working details for the selected response when available, and `Cmd/Ctrl+Alt+1–8` switches directly between right-pane views while `Cmd/Ctrl+Alt+P` / `Cmd/Ctrl+Alt+E` / `Cmd/Ctrl+Alt+W` / `Cmd/Ctrl+Alt+F` / `Cmd/Ctrl+Alt+R` / `Cmd/Ctrl+Alt+Q` keep quick mnemonic shortcuts for Response Preview, Editor Preview, Working, Files, REPL, and Side questions
40
+ - 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; optional browser-stored **Follow activity** (`Cmd/Ctrl+Option/Alt+A`; off by default under **View → Workspace**) switches to Working when the main Pi agent starts generating and to Response Preview when it completes, unless you manually choose another view during that request. When cycling response history, Working follows saved working details for the selected response when available, and `Cmd/Ctrl+Option/Alt+1–8` switches directly between right-pane views while `Cmd/Ctrl+Option/Alt+P` / `Cmd/Ctrl+Option/Alt+E` / `Cmd/Ctrl+Option/Alt+W` / `Cmd/Ctrl+Option/Alt+F` / `Cmd/Ctrl+Option/Alt+R` / `Cmd/Ctrl+Option/Alt+Q` keep quick mnemonic shortcuts for Response Preview, Editor Preview, Working, Files, REPL, and Side questions
41
41
  - 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
42
42
  - Includes a right-pane **Files** view rooted in folders allowed for the current Studio session, with an **Allow folder…** action and location selector; exact-file grants can still be opened independently without exposing their parent folders. Files supports sorting by name/modified time/size, opening folders or the current root in Finder/the system file manager, loading text/code/CSV/TSV documents into the editor, opening a read-only **Preview (follow)** tab whose disk content is authoritative, previewing PDFs/images, opening previews in new Studio tabs, converting DOCX/ODT documents to editable Markdown when Pandoc is available after confirmation, copying paths, setting the current folder as the Studio working directory, and revealing files in the file manager
43
43
  - Imports detached text-file copies either through the browser's file picker or from a path on the computer running Pi, keeping the path option available in embedded and remote browser views; imported copies remain detached until saved or opened as file-backed documents
@@ -50,9 +50,10 @@ _The video shows an earlier version of the Studio interface. The basic workflow
50
50
  - Turns local preview links, including links inside sandboxed HTML previews, into Studio actions: PDFs open in the embedded viewer, images open in a zoomable focus viewer, PDF/image links can open in a new Studio preview tab, text/code/CSV/TSV document links can open in a new editor tab, DOCX/ODT links can be converted to editable Markdown, and right-click menus provide **Open here**, **Reveal in file manager**, and **Copy path** for local resources
51
51
  - Includes local comments anchored to selections/lines, shown in a docked **Comments** rail, with transient **Comment** / **Jump** actions from raw-editor selections plus editor-preview selections for Markdown, LaTeX, code/text/diff previews, and an opt-in comment mode for editor HTML previews; source-anchored comments can be toggled into inline `[an: ...]` annotations when you want comments reflected in the document text
52
52
  - Browses response history (`Prev/Next/Last`) and loads either:
53
- - response text, with a one-click **Annotate response** action that also opens **Editor (Preview)**
53
+ - response text, with **Annotate response** (`Cmd/Ctrl+Option/Alt+Enter` from either main pane) also opening **Editor (Preview)**; add Shift (`Cmd/Ctrl+Option/Alt+Shift+Enter`) for **Load response into editor** without switching views (choose another view first if file-backed Quarto Preview is active)
54
54
  - critique notes/full critique
55
55
  - the prompt that generated a selected response
56
+ - Keeps the prompt → accepted Run → **Annotate response** loop confirmation-free when the submitted editor text is unchanged. **Annotate response** and **Load response into editor** ask before replacing unsubmitted edits or unsaved file changes; Cancel preserves the editor and its view so you can save or copy first. A failed submission or late acknowledgement cannot mark later typing as submitted, and sending file text does not save it. This bounded accepted-prompt baseline stays in page memory, is not restored after a page reload, and is separate from Pi-terminal draft cleanup.
56
57
  - Supports an annotation workflow for `[an: ...]` markers:
57
58
  - inserts/removes the annotated-reply header
58
59
  - shows/hides annotation markers in preview
@@ -159,7 +160,7 @@ See [`shared/REPL_SESSION_RECORD_PROTOCOL.md`](./shared/REPL_SESSION_RECORD_PROT
159
160
 
160
161
  ## Side questions and research context
161
162
 
162
- Open **Review → Side question**, select **Side questions** in the right pane, or press **Cmd/Ctrl+Alt+Q**. A new side thread captures its starting text while keeping related-file access separate:
163
+ Open **Review → Side question**, select **Side questions** in the right pane, or press **Cmd/Ctrl+Option/Alt+Q**. A new side thread captures its starting text while keeping related-file access separate:
163
164
 
164
165
  - **Starting text** controls what unsaved text begins the thread. The compact **Automatic: selection → heading block at cursor → nearby text** disclosure explains the fallback order; when expanded, it defines the heading boundary. The summary below reports the resolved starting text, editor line range when applicable, and character count separately from the related-file scope.
165
166
  - Explicit **Editor selection only** no longer falls back to the whole document when no text is selected; the summary instead reports that no editor text is selected.
@@ -199,7 +200,7 @@ caption: Optional caption
199
200
 
200
201
  `path` must point to a local `.pdf`. Relative paths resolve from the opened document's directory, or from Studio's working directory for non-file-backed content; the resolved file must be inside a location allowed for the current Studio session before it loads. `page` is an initial page hint for the browser PDF viewer, `height` controls the embedded frame height in pixels, and `watch: true` enables auto-refresh after Studio observes the changed file in a stable state. Use normal Markdown links for PDFs when embedding is not useful.
201
202
 
202
- To view an existing PDF directly, run `/studio report.pdf` (or `/studio "path with spaces/report.pdf"`). The PDF opens read-only in a focused companion preview, even when the full Studio workspace is already open. A `#page=N` suffix selects the initial page. Auto-refresh is off by default; use `/studio --watch report.pdf` to start with it on, or toggle **Auto-refresh** in the PDF card or focused viewer. Studio checks only while the tab is visible and waits for two matching file-version observations before reloading, which avoids reading a PDF while LaTeX is still writing it. **Cmd/Ctrl+Alt+Shift+R** manually refreshes the focused or visible PDF. This is distinct from `/studio-pdf`, which exports Markdown, LaTeX, code, or the last response to a new PDF.
203
+ To view an existing PDF directly, run `/studio report.pdf` (or `/studio "path with spaces/report.pdf"`). The PDF opens read-only in a focused companion preview, even when the full Studio workspace is already open. A `#page=N` suffix selects the initial page. Auto-refresh is off by default; use `/studio --watch report.pdf` to start with it on, or toggle **Auto-refresh** in the PDF card or focused viewer. Studio checks only while the tab is visible and waits for two matching file-version observations before reloading, which avoids reading a PDF while LaTeX is still writing it. **Cmd/Ctrl+Option/Alt+Shift+R** manually refreshes the focused or visible PDF. This is distinct from `/studio-pdf`, which exports Markdown, LaTeX, code, or the last response to a new PDF.
203
204
 
204
205
  For a disk-authoritative text or code preview, run `/studio --watch notes.md` or choose **Preview file (follow changes)** from a local-link menu / **Preview (follow)** in Files. This opens a transient read-only companion tab with the preview pane focused; leaving pane focus reveals the same disk-following text as **Source** with a visible **Read-only · follows disk** badge rather than as an editable buffer. Studio polls the canonical file, debounces change bursts, compares SHA-256 content revisions, accepts ordinary and atomic-replacement saves, and never writes back. Each watcher socket is bound to the exact path in a bounded, expiring server-created tab capability; if that identity expires before a reload or reconnect, the tab stays read-only and asks you to open a fresh watched preview. A missing, unreadable, oversized, binary, or path-replaced source leaves the last good preview visible and automatically recovers when the same canonical file becomes readable again. Render failures likewise retain the last successfully staged document rather than replacing it with partial output. On successful updates, Studio restores a nearby stable content anchor where possible and falls back to the previous scroll ratio.
205
206
 
package/ROADMAP.md CHANGED
@@ -6,7 +6,7 @@ This is the current planning source of truth for Studio work. `NEXT-STAGE.md` is
6
6
 
7
7
  Studio development follows two tracks:
8
8
 
9
- - **Stable `0.9.x` releases** contain one coherent family of fixes or incremental features and keep `main` releasable.
9
+ - **Stable `0.9.x` releases** contain a bounded, explicitly scoped batch of fixes or incremental features and keep `main` releasable.
10
10
  - **`0.10.0`** is reserved for the buffer-first document architecture. Risky prereleases should use the npm `next` tag before replacing `latest`.
11
11
 
12
12
  Changes should be based on capabilities rather than browser, terminal host, or user-agent detection. Muxy, ordinary Chromium browsers, embedded WebKit views, and SSH/headless use are validation environments, not separate product variants.
@@ -19,7 +19,19 @@ Each release should finish with:
19
19
  4. exact package-content and byte-level artifact audit;
20
20
  5. a fresh npm installation and naturally rendered Studio smoke test.
21
21
 
22
- Publishing, pushing, and tagging remain explicit release actions rather than automatic consequences of merging work.
22
+ Committing, publishing, pushing, and tagging remain explicit actions rather than automatic consequences of implementation or planning approval.
23
+
24
+ ## Agreed next sequence (2026-09-07)
25
+
26
+ 1. Ship **`0.9.60`** as the bounded networking/shortcut release. Do not start buffer architecture in this batch.
27
+ 2. After that release, implement **`0.10.0`** around an explicit active Prompt, independently editable documents, context transfer, and parked prompt drafts. The design is in [`BUFFER-DESIGN.md`](https://github.com/omaclaren/pi-studio/blob/main/BUFFER-DESIGN.md).
28
+ 3. Keep Derived REPL Transcript, editor-engine migration, and arbitrary pane arrangements outside both batches.
29
+
30
+ Current status:
31
+
32
+ - `0.9.60` combines the networking fix with Follow activity, Annotate response, and view-preserving Load response shortcuts, plus accurate draft-replacement protection. The published networking RC remains unchanged.
33
+ - The reporter has not replied to the RC invitation on [issue #4](https://github.com/omaclaren/pi-studio/issues/4) as of 2026-09-07. Exact `sbx` validation remains outstanding; the release proceeds with that limitation documented and the issue open for confirmation or a follow-up fix.
34
+ - `0.10.0` remains design-only. Approval to release `0.9.60` does not authorize buffer implementation.
23
35
 
24
36
  ## 0.9.52 — Rendering and media consistency (shipped 2026-08-28)
25
37
 
@@ -116,9 +128,13 @@ A small workflow refinement for moving Neovim-provided context through Pi and St
116
128
  - provide `Cmd/Ctrl+Shift+L` for loading and a confirmed **Clear Pi editor text…** fallback without changing Studio text or conversation history;
117
129
  - keep `pi-nvim-context`, protocol v1, networking, and the buffer-first architecture unchanged.
118
130
 
119
- ## 0.9.60 — Opt-in container/network binding (`0.9.60-rc.0` prerelease, 2026-09-03)
131
+ ## 0.9.60 — Networking and prompt-workflow shortcuts (2026-09-07)
120
132
 
121
- A bounded implementation of GitHub issue #4, separate from REPL transcript and buffer work:
133
+ This release combines the networking portion shipped as `0.9.60-rc.0` on 2026-09-03 with the small shortcut batch below. The published RC remains a reproducible networking test target.
134
+
135
+ ### Networking
136
+
137
+ A bounded implementation of GitHub issue #4:
122
138
 
123
139
  - retain `127.0.0.1` as the default and require explicit `--listen-all` intent before binding to `0.0.0.0`;
124
140
  - keep the server-lifetime bearer token mandatory and the generated URL on loopback for same-port container publishing;
@@ -127,18 +143,47 @@ A bounded implementation of GitHub issue #4, separate from REPL transcript and b
127
143
  - document loopback-only host publishing, private-network limits, and SSH forwarding as the safer option on untrusted networks;
128
144
  - validate both default loopback and opt-in wildcard sockets without changing the buffer model, protocol v1, or `pi-repl`.
129
145
 
130
- ## 0.10.0 Buffer-first editing
146
+ ### Shortcut and draft-replacement batch
147
+
148
+ The shortcuts and replacement safeguards below are implemented, with focused regression tests and browser workflow checks using controlled model replies. The buffer architecture remains deferred.
149
+
150
+ - Include `Cmd/Ctrl+Option/Alt+A` **Follow activity** toggle: retain opt-in persistence, full-workspace scope, modal protections, and key-repeat suppression.
151
+ - Add `Cmd/Ctrl+Option/Alt+Enter` for **Annotate response** from either main pane when the existing action is available. Invoke the same action as the button and retain its busy, response-type, and mode guards. Add Shift (`Cmd/Ctrl+Option/Alt+Shift+Enter`) to invoke **Load response into editor** without switching views, with the same replacement safeguards. Do not reinterpret `Cmd/Ctrl+Enter` as the right pane's primary action.
152
+ - Keep Run's existing active-pane behaviour and REPL/Side question submission shortcuts unchanged in this release.
153
+ - Protect **unsubmitted or unsaved work**, not every non-empty editor. The normal write prompt → accepted Run → response → Annotate response loop must remain confirmation-free when the submitted prompt is still unchanged. Ask before replacing post-submission edits, a new unsent draft, edited response annotations, or unsaved file changes; Cancel preserves text, source identity, and view state. Keep the decision/save-or-copy workflow bounded rather than adding a hidden second editor or a new recovery schema to `0.9.60`.
154
+ - Correlate an editor-text/source snapshot with the accepted submission and compare the current editor against that baseline; do not mark later typing as submitted when an acknowledgement or response arrives. Failed submissions do not establish the baseline, and submitting file text does not mark it saved.
155
+ - Capture the intended response and revalidate the editor after an asynchronous replacement decision; later typing or a changed target must not be overwritten by stale consent.
156
+ - Test both modifier variants, option-modified key values, key repeat, modal ownership, unavailable actions, and unchanged Run/REPL/Side question behaviour. Update the shortcut overlay, tooltips, accessibility metadata, and changelog only as behaviour is implemented.
157
+
158
+ ### Release gate
159
+
160
+ Run the normal full validation and fresh-package browser smoke over the combined batch, including a confirmation-free submitted-prompt → Annotate response loop and protected replacement/cancellation for genuinely new work. The previous RC verification does not substitute for checking the final artifact.
161
+
162
+ The networking review and production lifecycle tests support the reported fix; a separate TCP-forward smoke also verified HTML, client JavaScript, and WebSocket state delivery through a non-loopback destination. Neither is an exact `sbx` test. On 2026-09-07, Oliver approved shipping after final validation with this limitation documented and issue #4 left open. Reporter feedback can confirm the fix or lead to a follow-up patch. Any updated prerelease must use a new version, never replace `rc.0`.
163
+
164
+ Do not include buffer tabs, context-transfer messages, recovery schema v2, or REPL transcript work in this release.
165
+
166
+ ## 0.10.0 — Active Prompt and editable document buffers (planned)
167
+
168
+ Studio's primary loop is prompt construction and response reading. Documents remain useful editable sources, not implicit submission targets or a replacement for Neovim. The design is **one clearly active Prompt, other prompt drafts that can be parked, and independently editable documents**.
169
+
170
+ The first architectural release should add:
171
+
172
+ - a `StudioBufferStore` with stable buffer identity, explicit prompt/document roles, and separate selected-buffer and active-prompt identities;
173
+ - a prominent **Prompt** view with response/Working alongside it, plus editable document tabs with their own previews;
174
+ - recovery schema v2 with non-destructive migration from the single-document schema;
175
+ - buffer-specific text/baseline, dirty state, cursor, selection, scroll, preview context, annotations, and resource directory, preserving existing disk-revision checks;
176
+ - **Add selection to prompt** and **Copy document to prompt**, including an acknowledged handoff from editable companion views to the full workspace's explicit prompt destination;
177
+ - editable context snapshots of the visible text, including unsaved edits and source labels/ranges where known; no automatic submission, replacement, file write, or later live update of the copied text;
178
+ - a lightweight parked-prompt picker, with one active submission draft at a time and no per-draft conversation forks;
179
+ - tabs at ordinary widths, a compact searchable picker at narrow widths, and MRU/next/previous/close/reopen navigation;
180
+ - originating buffer ID, revision, and request ID targeting for asynchronous work, so switching tabs cannot redirect results or destructive actions.
131
181
 
132
- The first architectural `0.10` release should add:
182
+ Opening a document never changes the active prompt. Initially, document views should offer a clear return to Prompt rather than implicitly submit the document or send a hidden prompt. Keep Run's precise shortcut behaviour separate from the buffer model; settle it in the interaction tests before shipping.
133
183
 
134
- - a `StudioBufferStore` with stable document identity;
135
- - recovery schema v2 with migration from the single-document schema;
136
- - buffer-specific text/baseline, dirty state, cursor, selection, scroll, preview context, annotations, and resource directory;
137
- - tabs at ordinary widths and a compact searchable buffer picker at narrow widths;
138
- - MRU cycling, next/previous, close, and reopen commands;
139
- - originating buffer ID and revision targeting for asynchronous Pi work.
184
+ **Acceptance workflow:** start a prompt → inspect and edit a document with preview → add context to the prompt → annotate that detached copy → inspect another document → return and submit, without losing edits, changing the original through prompt annotations, or changing the submission destination accidentally.
140
185
 
141
- PDFs remain preview documents, and conversation history remains session-global. Arbitrary multi-pane layouts, project trees, and simultaneous per-buffer Pi conversations are outside the initial `0.10.0` scope.
186
+ PDFs remain preview documents; watched previews remain disk-authoritative and read-only. Conversation history, resource grants, and the exact-session Shared REPL Record remain outside individual buffers. Arbitrary multi-pane layouts, project trees, simultaneous per-buffer Pi conversations, editor-engine migration, and Derived REPL Transcript are outside the initial scope. See [`BUFFER-DESIGN.md`](https://github.com/omaclaren/pi-studio/blob/main/BUFFER-DESIGN.md) for the state, recovery, handoff, and implementation sequence.
142
187
 
143
188
  ## Validation matrix
144
189
 
package/WORKFLOW.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # pi-studio workflow/spec note
2
2
 
3
+ This note describes the original single-editor workflow; it is not the current release backlog. See [ROADMAP.md](ROADMAP.md) for the active `0.9.60` networking/shortcut batch and planned `0.10.0` work. The future active-Prompt/editable-document model is recorded in [BUFFER-DESIGN.md](https://github.com/omaclaren/pi-studio/blob/main/BUFFER-DESIGN.md); buffer tabs and context transfer are not implemented yet.
4
+
3
5
  ## Goal
4
6
 
5
7
  Keep Studio simple while supporting both loops:
@@ -117,8 +119,8 @@ Rules of thumb:
117
119
 
118
120
  ---
119
121
 
120
- ## Non-goals (for now)
122
+ ## Non-goals for the single-editor `0.9.x` workflow
121
123
 
122
- - Multi-document tabs
124
+ - Multi-document tabs (planned separately for `0.10.0`)
123
125
  - Multi-user collaboration
124
126
  - Heavy schema validation
@@ -255,6 +255,14 @@
255
255
  ) {
256
256
  throw new Error("Studio preview resource helpers failed to load.");
257
257
  }
258
+ const editorDraftHelpers = globalThis.PiStudioEditorDraftHelpers;
259
+ if (!editorDraftHelpers || typeof editorDraftHelpers.createSubmittedEditorDraftTracker !== "function"
260
+ || typeof editorDraftHelpers.needsDraftReplacementConfirmation !== "function") {
261
+ throw new Error("Studio editor draft helpers failed to load.");
262
+ }
263
+ const submittedEditorDrafts = editorDraftHelpers.createSubmittedEditorDraftTracker();
264
+ let editorSourceGeneration = 0;
265
+ let responseReplacementPending = false;
258
266
  const showMeHelpers = globalThis.PiStudioShowMeHelpers;
259
267
  if (!showMeHelpers || typeof showMeHelpers.chooseStudioShowMeFocus !== "function") {
260
268
  throw new Error("Studio Show me helpers failed to load.");
@@ -433,8 +441,8 @@
433
441
  rightViewSelect.title = isWatchedFilePreview
434
442
  ? "Read-only watched preview follows this file on disk."
435
443
  : (isEditorOnlyMode
436
- ? "Editor-only views: Editor Preview, contextual Quarto Preview for .qmd/.md/.markdown files, Changes, Files, REPL, or Side questions. F7 cycles; Cmd/Ctrl+Alt+3/5/6/7/8 switch directly to numbered right-pane views, and Cmd/Ctrl+Alt+F/R/Q open Files/REPL/Side questions."
437
- : "Right pane view mode. F7 cycles, including contextual Quarto Preview for file-backed .qmd, .md, and .markdown documents; Cmd/Ctrl+Alt+1–8 switches directly between the numbered views. Cmd/Ctrl+Alt+P/E/W/F/R/Q keep mnemonic shortcuts for Preview, Editor Preview, Working, Files, REPL, and Side questions.");
444
+ ? "Editor-only views: Editor Preview, contextual Quarto Preview for .qmd/.md/.markdown files, Changes, Files, REPL, or Side questions. F7 cycles; Cmd/Ctrl+Option/Alt+3/5/6/7/8 switch directly to numbered right-pane views, and Cmd/Ctrl+Option/Alt+F/R/Q open Files/REPL/Side questions."
445
+ : "Right pane view mode. F7 cycles, including contextual Quarto Preview for file-backed .qmd, .md, and .markdown documents; Cmd/Ctrl+Option/Alt+1–8 switches directly between the numbered views. Cmd/Ctrl+Option/Alt+P/E/W/F/R/Q keep mnemonic shortcuts for Preview, Editor Preview, Working, Files, REPL, and Side questions.");
438
446
  }
439
447
 
440
448
  function getInitialRightView(source) {
@@ -4524,6 +4532,11 @@
4524
4532
  return true;
4525
4533
  }
4526
4534
 
4535
+ function getEditorDraftSourceKey() {
4536
+ return JSON.stringify([editorSourceGeneration, sourceState.source, sourceState.path,
4537
+ sourceState.draftId, getCurrentResourceDirValue()]);
4538
+ }
4539
+
4527
4540
  function markFileBackedBaseline(text, diskRevision) {
4528
4541
  fileBackedBaselineText = String(text || "");
4529
4542
  fileBackedDiskRevision = normalizeStudioDiskRevision(diskRevision);
@@ -5096,6 +5109,35 @@
5096
5109
  switchRightPaneToView("editor-preview");
5097
5110
  }
5098
5111
 
5112
+ function studioModalBlocksDraftAction() {
5113
+ return Boolean(studioDecisionState || isScratchpadOpen() || isShortcutsOpen() || isQuizOpen()
5114
+ || isStudioPdfFocusOpen() || isStudioHtmlFocusOpen() || isStudioImageFocusOpen());
5115
+ }
5116
+
5117
+ function triggerLoadResponseShortcut() {
5118
+ if (isEditorOnlyMode || isWatchedFilePreview || studioModalBlocksDraftAction()) return false;
5119
+ if (!loadResponseBtn || loadResponseBtn.hidden || loadResponseBtn.disabled) return false;
5120
+ loadResponseBtn.click();
5121
+ return true;
5122
+ }
5123
+
5124
+ function triggerAnnotateResponseShortcut() {
5125
+ if (isEditorOnlyMode || isWatchedFilePreview || studioModalBlocksDraftAction()) return false;
5126
+ if (!annotateResponseBtn || annotateResponseBtn.hidden || annotateResponseBtn.disabled) return false;
5127
+ annotateResponseBtn.click();
5128
+ return true;
5129
+ }
5130
+
5131
+ function triggerActivityTrackingShortcut() {
5132
+ if (studioModalBlocksDraftAction()) return false;
5133
+ if (isEditorOnlyMode || isWatchedFilePreview) {
5134
+ setStatus("Follow activity is available only in the main editable Studio workspace.", "warning");
5135
+ return false;
5136
+ }
5137
+ setActivityTrackingEnabled(!activityTrackingEnabled);
5138
+ return true;
5139
+ }
5140
+
5099
5141
  function cycleActivePaneView(direction) {
5100
5142
  if (activePane === "right") {
5101
5143
  if (!rightViewSelect || rightViewSelect.disabled) {
@@ -5495,6 +5537,36 @@
5495
5537
  return;
5496
5538
  }
5497
5539
 
5540
+ const isLoadResponseShortcut = key === "Enter"
5541
+ && (event.metaKey || event.ctrlKey)
5542
+ && event.altKey
5543
+ && event.shiftKey;
5544
+ if (isLoadResponseShortcut) {
5545
+ event.preventDefault();
5546
+ if (!event.repeat && !event.isComposing) triggerLoadResponseShortcut();
5547
+ return;
5548
+ }
5549
+
5550
+ const isAnnotateResponseShortcut = key === "Enter"
5551
+ && (event.metaKey || event.ctrlKey)
5552
+ && event.altKey
5553
+ && !event.shiftKey;
5554
+ if (isAnnotateResponseShortcut) {
5555
+ event.preventDefault();
5556
+ if (!event.repeat && !event.isComposing) triggerAnnotateResponseShortcut();
5557
+ return;
5558
+ }
5559
+
5560
+ const isActivityTrackingShortcut = (key.toLowerCase() === "a" || code === "KeyA")
5561
+ && (event.metaKey || event.ctrlKey)
5562
+ && event.altKey
5563
+ && !event.shiftKey;
5564
+ if (isActivityTrackingShortcut) {
5565
+ event.preventDefault();
5566
+ if (!event.repeat && !event.isComposing) triggerActivityTrackingShortcut();
5567
+ return;
5568
+ }
5569
+
5498
5570
  const isContentFocusShortcut = key === "F8" && !event.metaKey && !event.ctrlKey && !event.altKey;
5499
5571
  if (isContentFocusShortcut) {
5500
5572
  event.preventDefault();
@@ -7993,7 +8065,7 @@
7993
8065
  refreshBtn.type = "button";
7994
8066
  refreshBtn.className = "studio-pdf-focus-btn studio-pdf-focus-refresh";
7995
8067
  refreshBtn.textContent = "Refresh";
7996
- refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+Shift+R.";
8068
+ refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Option/Alt+Shift+R.";
7997
8069
  refreshBtn.setAttribute("aria-label", "Refresh PDF preview from disk");
7998
8070
  refreshBtn.addEventListener("click", () => refreshStudioPdfFocusViewer());
7999
8071
  actions.appendChild(refreshBtn);
@@ -9312,7 +9384,7 @@
9312
9384
  refreshBtn.type = "button";
9313
9385
  refreshBtn.className = "studio-pdf-card-action studio-pdf-card-refresh";
9314
9386
  refreshBtn.textContent = "Refresh";
9315
- refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+Shift+R.";
9387
+ refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Option/Alt+Shift+R.";
9316
9388
  refreshBtn.addEventListener("click", (event) => {
9317
9389
  event.preventDefault();
9318
9390
  event.stopPropagation();
@@ -14250,7 +14322,7 @@
14250
14322
  annotateResponseBtn.textContent = annotationWorkspaceReady ? "Response ready to annotate" : "Annotate response";
14251
14323
  annotateResponseBtn.title = annotationWorkspaceReady
14252
14324
  ? "The selected response is exactly in the raw editor with Editor Preview open."
14253
- : "Load the selected response into the raw editor and show Editor Preview. This replaces the current editor text.";
14325
+ : "Load the selected response into the raw editor and show Editor Preview. Ask before replacing unsubmitted or unsaved work. Shortcut: Cmd/Ctrl+Option/Alt+Enter.";
14254
14326
 
14255
14327
  loadCritiqueNotesBtn.disabled = uiBusy || !isCritiqueResponse || !critiqueNotes || critiqueNotesLoaded;
14256
14328
  loadCritiqueNotesBtn.textContent = critiqueNotesLoaded ? "Critique notes already in editor" : "Load critique notes into editor";
@@ -14649,6 +14721,7 @@
14649
14721
  setStatus("This read-only preview remains bound to its watched file.", "warning");
14650
14722
  return false;
14651
14723
  }
14724
+ editorSourceGeneration += 1;
14652
14725
  sourceState = {
14653
14726
  source: next && next.source ? next.source : "blank",
14654
14727
  label: next && next.label ? next.label : "blank",
@@ -23701,6 +23774,11 @@
23701
23774
  return;
23702
23775
  }
23703
23776
 
23777
+ if (message.type === "run_accepted") {
23778
+ submittedEditorDrafts.accept(message.requestId);
23779
+ return;
23780
+ }
23781
+
23704
23782
  if (message.type === "request_started") {
23705
23783
  pendingRequestId = typeof message.requestId === "string" ? message.requestId : pendingRequestId;
23706
23784
  pendingKind = typeof message.kind === "string" ? message.kind : "unknown";
@@ -24157,6 +24235,7 @@
24157
24235
 
24158
24236
  if (message.type === "busy") {
24159
24237
  if (typeof message.requestId === "string") {
24238
+ submittedEditorDrafts.discard(message.requestId);
24160
24239
  restoreReservedPiEditorDraftSnapshot(message.requestId);
24161
24240
  pendingSaveOperations.delete(message.requestId);
24162
24241
  failPendingCompanionLaunch(message.requestId, "Studio could not start the companion editor because another request was busy.");
@@ -24181,6 +24260,7 @@
24181
24260
 
24182
24261
  if (message.type === "error") {
24183
24262
  if (typeof message.requestId === "string") {
24263
+ submittedEditorDrafts.discard(message.requestId);
24184
24264
  restoreReservedPiEditorDraftSnapshot(message.requestId);
24185
24265
  pendingSaveOperations.delete(message.requestId);
24186
24266
  failPendingCompanionLaunch(message.requestId, "Studio could not prepare the companion editor. Return to the originating Studio page for details.");
@@ -24331,6 +24411,7 @@
24331
24411
  quartoPreviewActionRequestId = null;
24332
24412
  sideQuestionMarkdownExportRequest = null;
24333
24413
  pendingPiEditorDraftSnapshots.clear();
24414
+ submittedEditorDrafts.clearPending();
24334
24415
  failAllPendingCompanionLaunches("The originating Studio connection was lost before the companion editor was ready.");
24335
24416
  if (rightView === "editor-quarto-preview") renderQuartoPreviewView();
24336
24417
  setBusy(true);
@@ -25034,27 +25115,56 @@
25034
25115
  }
25035
25116
 
25036
25117
  async function loadSelectedResponseIntoEditor(options) {
25037
- if (!latestResponseMarkdown.trim()) {
25118
+ if (isEditorOnlyMode || isWatchedFilePreview || uiBusy || responseReplacementPending
25119
+ || studioModalBlocksDraftAction()) return false;
25120
+ const prepareForAnnotation = Boolean(options && options.annotate);
25121
+ if (prepareForAnnotation && latestResponseIsStructuredCritique) return false;
25122
+ if (!prepareForAnnotation && rightView === "editor-quarto-preview") {
25123
+ setStatus("Choose another right-pane view before loading a response without switching views, or use Annotate response.", "warning");
25124
+ return false;
25125
+ }
25126
+ const responseText = latestResponseMarkdown;
25127
+ if (!responseText.trim()) {
25038
25128
  setStatus("No response available yet.", "warning");
25039
25129
  return false;
25040
25130
  }
25041
- const prepareForAnnotation = Boolean(options && options.annotate);
25042
25131
  const currentEditorText = String(sourceTextEl.value || "");
25043
- const replacingEditedResponse = prepareForAnnotation
25044
- && sourceState.source === "last-response"
25045
- && Boolean(currentEditorText.trim())
25046
- && normalizeForCompare(currentEditorText) !== latestResponseNormalized;
25047
- if (replacingEditedResponse) {
25048
- const confirmed = await requestStudioConfirmation(
25049
- "Replace your edited response with a fresh copy? Existing edits and annotations will be lost.",
25050
- { title: "Replace edited response?", confirmLabel: "Replace", destructive: true },
25051
- );
25132
+ const sourceKey = getEditorDraftSourceKey();
25133
+ const responseIndex = responseHistoryIndex;
25134
+ const responseTimestamp = latestResponseTimestamp;
25135
+ const diskRevision = fileBackedDiskRevision;
25136
+ const needsConfirmation = editorDraftHelpers.needsDraftReplacementConfirmation({
25137
+ text: currentEditorText,
25138
+ responseText,
25139
+ fileBacked: hasRefreshableFilePath(),
25140
+ dirty: editorDiffersFromFileBackedBaseline(),
25141
+ submitted: submittedEditorDrafts.matches(currentEditorText, sourceKey),
25142
+ });
25143
+ if (needsConfirmation) {
25144
+ responseReplacementPending = true;
25145
+ let confirmed;
25146
+ try {
25147
+ confirmed = await requestStudioConfirmation(
25148
+ "Replace the current editor text with the selected response? Unsubmitted edits or unsaved file changes will be lost. Cancel to save or copy them first.",
25149
+ { title: "Replace editor text?", confirmLabel: "Replace", destructive: true },
25150
+ );
25151
+ } finally {
25152
+ responseReplacementPending = false;
25153
+ }
25052
25154
  if (!confirmed) {
25053
25155
  setStatus("Kept the current editor text.");
25054
25156
  return false;
25055
25157
  }
25158
+ if (uiBusy || studioModalBlocksDraftAction() || currentEditorText !== sourceTextEl.value
25159
+ || sourceKey !== getEditorDraftSourceKey() || diskRevision !== fileBackedDiskRevision
25160
+ || responseText !== latestResponseMarkdown || responseIndex !== responseHistoryIndex
25161
+ || responseTimestamp !== latestResponseTimestamp
25162
+ || (!prepareForAnnotation && rightView === "editor-quarto-preview")) {
25163
+ setStatus("Editor, response, or view changed while confirmation was open. Kept the editor text; try again.", "warning");
25164
+ return false;
25165
+ }
25056
25166
  }
25057
- setEditorText(latestResponseMarkdown, { preserveScroll: false, preserveSelection: false });
25167
+ setEditorText(responseText, { preserveScroll: false, preserveSelection: false });
25058
25168
  setSourceState({ source: "last-response", label: "last model response", path: null });
25059
25169
  if (prepareForAnnotation) {
25060
25170
  if (editorView !== "markdown") setEditorView("markdown");
@@ -25574,6 +25684,7 @@
25574
25684
 
25575
25685
  const requestId = beginUiAction("direct");
25576
25686
  if (!requestId) return;
25687
+ submittedEditorDrafts.remember(requestId, sourceTextEl.value, getEditorDraftSourceKey());
25577
25688
  const piEditorDraftSnapshot = reserveLinkedPiEditorDraftSnapshot(requestId);
25578
25689
 
25579
25690
  const sent = sendMessage({
@@ -25584,6 +25695,7 @@
25584
25695
  });
25585
25696
 
25586
25697
  if (!sent) {
25698
+ submittedEditorDrafts.discard(requestId);
25587
25699
  restoreReservedPiEditorDraftSnapshot(requestId);
25588
25700
  pendingRequestId = null;
25589
25701
  pendingKind = null;
@@ -25605,6 +25717,7 @@
25605
25717
 
25606
25718
  const requestId = makeRequestId();
25607
25719
  clearTitleAttention();
25720
+ submittedEditorDrafts.remember(requestId, sourceTextEl.value, getEditorDraftSourceKey());
25608
25721
  const piEditorDraftSnapshot = reserveLinkedPiEditorDraftSnapshot(requestId);
25609
25722
  const sent = sendMessage({
25610
25723
  type: "send_run_request",
@@ -25613,6 +25726,7 @@
25613
25726
  piEditorDraftSnapshot: piEditorDraftSnapshot || undefined,
25614
25727
  });
25615
25728
  if (!sent) {
25729
+ submittedEditorDrafts.discard(requestId);
25616
25730
  restoreReservedPiEditorDraftSnapshot(requestId);
25617
25731
  return;
25618
25732
  }
@@ -0,0 +1,64 @@
1
+ (() => {
2
+ // Page-memory-only. No persistence or authority over Pi's terminal composer.
3
+ const MAX_TEXT_CHARS = 900_000;
4
+ const MAX_PENDING = 8;
5
+ const MAX_PENDING_TEXT_CHARS = 2_700_000;
6
+
7
+ function createSubmittedEditorDraftTracker() {
8
+ const pending = new Map();
9
+ let pendingChars = 0;
10
+ let sequence = 0;
11
+ let accepted = null;
12
+
13
+ function discard(requestId) {
14
+ const snapshot = pending.get(requestId);
15
+ if (!snapshot) return false;
16
+ pendingChars -= snapshot.text.length;
17
+ pending.delete(requestId);
18
+ return true;
19
+ }
20
+
21
+ return Object.freeze({
22
+ remember(requestId, text, sourceKey) {
23
+ if (typeof requestId !== "string" || !requestId || requestId.length > 256
24
+ || pending.has(requestId) || (accepted && accepted.requestId === requestId)) return false;
25
+ if (typeof text !== "string" || text.length > MAX_TEXT_CHARS
26
+ || typeof sourceKey !== "string" || !sourceKey || sourceKey.length > 24_000) return false;
27
+ while (pending.size >= MAX_PENDING || pendingChars + text.length > MAX_PENDING_TEXT_CHARS) {
28
+ discard(pending.keys().next().value);
29
+ }
30
+ pending.set(requestId, { requestId, text, sourceKey, sequence: ++sequence });
31
+ pendingChars += text.length;
32
+ return true;
33
+ },
34
+ accept(requestId) {
35
+ const snapshot = pending.get(requestId);
36
+ if (!snapshot) return false;
37
+ discard(requestId);
38
+ // Late/duplicate acknowledgements cannot replace a newer accepted baseline.
39
+ if (accepted && snapshot.sequence <= accepted.sequence) return false;
40
+ accepted = snapshot;
41
+ return true;
42
+ },
43
+ matches(text, sourceKey) {
44
+ return Boolean(accepted && accepted.text === text && accepted.sourceKey === sourceKey);
45
+ },
46
+ discard,
47
+ clearPending() {
48
+ pending.clear();
49
+ pendingChars = 0;
50
+ },
51
+ });
52
+ }
53
+
54
+ function needsDraftReplacementConfirmation({ text, responseText, fileBacked, dirty, submitted }) {
55
+ // Even an empty file buffer can contain an unsaved deletion. Sending it is not saving it.
56
+ if (fileBacked) return Boolean(dirty);
57
+ return Boolean(text.trim() && text !== responseText && !submitted);
58
+ }
59
+
60
+ globalThis.PiStudioEditorDraftHelpers = Object.freeze({
61
+ createSubmittedEditorDraftTracker,
62
+ needsDraftReplacementConfirmation,
63
+ });
64
+ })();
package/index.ts CHANGED
@@ -196,6 +196,7 @@ const STUDIO_NAVIGATION_HELPERS_URL = new URL("./client/studio-navigation-helper
196
196
  const STUDIO_PREVIEW_RESOURCE_HELPERS_URL = new URL("./client/studio-preview-resource-helpers.js", import.meta.url);
197
197
  const STUDIO_SHOW_ME_HELPERS_URL = new URL("./client/studio-show-me-helpers.js", import.meta.url);
198
198
  const STUDIO_SIDE_QUESTION_HELPERS_URL = new URL("./client/studio-side-question-helpers.js", import.meta.url);
199
+ const STUDIO_EDITOR_DRAFT_HELPERS_URL = new URL("./client/studio-editor-draft-helpers.js", import.meta.url);
199
200
  const STUDIO_CLIENT_URL = new URL("./client/studio-client.js", import.meta.url);
200
201
 
201
202
  interface StudioServerState {
@@ -12388,6 +12389,7 @@ function buildStudioHtml(
12388
12389
  const mermaidHelpersScriptHref = `/studio-mermaid-helpers.js?token=${encodeURIComponent(studioToken ?? "")}`;
12389
12390
  const navigationHelpersScriptHref = `/studio-navigation-helpers.js?token=${encodeURIComponent(studioToken ?? "")}`;
12390
12391
  const previewResourceHelpersScriptHref = `/studio-preview-resource-helpers.js?token=${encodeURIComponent(studioToken ?? "")}`;
12392
+ const editorDraftHelpersScriptHref = `/studio-editor-draft-helpers.js?token=${encodeURIComponent(studioToken ?? "")}`;
12391
12393
  const showMeHelpersScriptHref = `/studio-show-me-helpers.js?token=${encodeURIComponent(studioToken ?? "")}`;
12392
12394
  const sideQuestionHelpersScriptHref = `/studio-side-question-helpers.js?token=${encodeURIComponent(studioToken ?? "")}`;
12393
12395
  const clientScriptHref = `/studio-client.js?token=${encodeURIComponent(studioToken ?? "")}`;
@@ -12552,7 +12554,7 @@ ${cssVarsBlock}
12552
12554
  <option value="editor-top">Layout: Editor above</option>
12553
12555
  <option value="response-top">Layout: Response above</option>
12554
12556
  </select>
12555
- <select id="activityTrackingSelect" class="studio-flat-select" aria-label="Follow main Pi activity" title="Optionally follow main Pi activity from Working to the response view; off by default.">
12557
+ <select id="activityTrackingSelect" class="studio-flat-select" aria-label="Follow main Pi activity" aria-keyshortcuts="Meta+Alt+A Control+Alt+A" title="Optionally follow main Pi activity from Working to the response view; off by default. Shortcut: Cmd/Ctrl+Option/Alt+A.">
12556
12558
  <option value="off">Follow activity: Off</option>
12557
12559
  <option value="on">Follow activity: On</option>
12558
12560
  </select>
@@ -12655,7 +12657,7 @@ ${cssVarsBlock}
12655
12657
  <div id="rightSectionHeader" class="section-header">
12656
12658
  <div class="section-header-main">
12657
12659
  <span id="rightViewSelectWrap" class="studio-header-select-wrap">
12658
- <select id="rightViewSelect" aria-label="Response view mode" title="Right pane view mode. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+1–8 switches directly between all right-pane views. Cmd/Ctrl+Alt+P/E/W/F/R/Q keep mnemonic shortcuts for Preview, Editor Preview, Working, Files, REPL, and Side questions.">
12660
+ <select id="rightViewSelect" aria-label="Response view mode" title="Right pane view mode. F7 cycles when the right pane is active; Cmd/Ctrl+Option/Alt+1–8 switches directly between all right-pane views. Cmd/Ctrl+Option/Alt+P/E/W/F/R/Q keep mnemonic shortcuts for Preview, Editor Preview, Working, Files, REPL, and Side questions.">
12659
12661
  <option value="markdown">Response (Raw)</option>
12660
12662
  <option value="preview" selected>Response (Preview)</option>
12661
12663
  <option value="editor-preview">Editor (Preview)</option>
@@ -12730,8 +12732,8 @@ ${cssVarsBlock}
12730
12732
  <button id="historyLastBtn" type="button" title="Jump to the latest loaded response in the current branch history.">Last response ▶|</button>
12731
12733
  </div>
12732
12734
  <div class="response-actions-row response-result-row">
12733
- <button id="annotateResponseBtn" type="button" title="Load the selected response into the raw editor and show Editor Preview. This replaces the current editor text.">Annotate response</button>
12734
- <button id="loadResponseBtn" type="button">Load response into editor</button>
12735
+ <button id="annotateResponseBtn" type="button" aria-keyshortcuts="Meta+Alt+Enter Control+Alt+Enter" title="Load the selected response into the raw editor and show Editor Preview. Ask before replacing unsubmitted or unsaved work. Shortcut: Cmd/Ctrl+Option/Alt+Enter.">Annotate response</button>
12736
+ <button id="loadResponseBtn" type="button" aria-keyshortcuts="Meta+Alt+Shift+Enter Control+Alt+Shift+Enter" title="Load the selected response into the editor without switching views. Choose another view first if Quarto Preview is active. Ask before replacing unsubmitted or unsaved work. Shortcut: Cmd/Ctrl+Option/Alt+Shift+Enter.">Load response into editor</button>
12735
12737
  <button id="loadCritiqueNotesBtn" type="button" hidden>Load critique notes into editor</button>
12736
12738
  <button id="loadCritiqueFullBtn" type="button" hidden>Load full critique into editor</button>
12737
12739
  <button id="loadHistoryPromptBtn" type="button" title="Load the prompt that generated the selected response into the editor.">Load response prompt into editor</button>
@@ -12768,13 +12770,13 @@ ${cssVarsBlock}
12768
12770
  <dl>
12769
12771
  <div><dt>F6</dt><dd>Switch between editor and right pane</dd></div>
12770
12772
  <div><dt>F7 / Shift+F7</dt><dd>Cycle the active pane's view</dd></div>
12771
- <div><dt>Cmd/Ctrl+Alt+1–8</dt><dd>Switch the right pane directly: Response Raw, Response Preview, Editor Preview, Working, Changes, Files, REPL, Side questions</dd></div>
12772
- <div><dt>Cmd/Ctrl+Alt+P</dt><dd>Switch the right pane directly to Response Preview; in editor-only views, Editor Preview</dd></div>
12773
- <div><dt>Cmd/Ctrl+Alt+E</dt><dd>Switch the right pane directly to Editor Preview</dd></div>
12774
- <div><dt>Cmd/Ctrl+Alt+W</dt><dd>Switch the right pane directly to Working</dd></div>
12775
- <div><dt>Cmd/Ctrl+Alt+F</dt><dd>Switch the right pane directly to Files</dd></div>
12776
- <div><dt>Cmd/Ctrl+Alt+R</dt><dd>Switch the right pane directly to REPL and focus Quick send when a session is selected</dd></div>
12777
- <div><dt>Cmd/Ctrl+Alt+Q</dt><dd>Switch the right pane directly to Side questions</dd></div>
12773
+ <div><dt>Cmd/Ctrl+Option/Alt+1–8</dt><dd>Switch the right pane directly: Response Raw, Response Preview, Editor Preview, Working, Changes, Files, REPL, Side questions</dd></div>
12774
+ <div><dt>Cmd/Ctrl+Option/Alt+P</dt><dd>Switch the right pane directly to Response Preview; in editor-only views, Editor Preview</dd></div>
12775
+ <div><dt>Cmd/Ctrl+Option/Alt+E</dt><dd>Switch the right pane directly to Editor Preview</dd></div>
12776
+ <div><dt>Cmd/Ctrl+Option/Alt+W</dt><dd>Switch the right pane directly to Working</dd></div>
12777
+ <div><dt>Cmd/Ctrl+Option/Alt+F</dt><dd>Switch the right pane directly to Files</dd></div>
12778
+ <div><dt>Cmd/Ctrl+Option/Alt+R</dt><dd>Switch the right pane directly to REPL and focus Quick send when a session is selected</dd></div>
12779
+ <div><dt>Cmd/Ctrl+Option/Alt+Q</dt><dd>Switch the right pane directly to Side questions</dd></div>
12778
12780
  <div><dt>F8</dt><dd>Focus editor text</dd></div>
12779
12781
  <div><dt>Shift+F8</dt><dd>Focus right-pane content</dd></div>
12780
12782
  <div><dt>F9</dt><dd>Toggle Zen mode and hide or restore the Studio header</dd></div>
@@ -12786,10 +12788,11 @@ ${cssVarsBlock}
12786
12788
  <section class="shortcuts-group">
12787
12789
  <h3>View</h3>
12788
12790
  <dl>
12789
- <div><dt>Alt/Option+=</dt><dd>Increase the active pane's text size when not editing text</dd></div>
12790
- <div><dt>Alt/Option+-</dt><dd>Decrease the active pane's text size when not editing text</dd></div>
12791
- <div><dt>Alt/Option+0</dt><dd>Reset the active pane's text size when not editing text</dd></div>
12792
- <div><dt>Cmd/Ctrl+Alt+Shift+R</dt><dd>Refresh the focused or visible PDF preview from disk</dd></div>
12791
+ <div><dt>Option/Alt+=</dt><dd>Increase the active pane's text size when not editing text</dd></div>
12792
+ <div><dt>Option/Alt+-</dt><dd>Decrease the active pane's text size when not editing text</dd></div>
12793
+ <div><dt>Option/Alt+0</dt><dd>Reset the active pane's text size when not editing text</dd></div>
12794
+ <div class="shortcuts-full-only"><dt>Cmd/Ctrl+Option/Alt+A</dt><dd>Toggle Follow activity in the main editable Studio workspace</dd></div>
12795
+ <div><dt>Cmd/Ctrl+Option/Alt+Shift+R</dt><dd>Refresh the focused or visible PDF preview from disk</dd></div>
12793
12796
  </dl>
12794
12797
  </section>
12795
12798
  <section class="shortcuts-group">
@@ -12808,9 +12811,11 @@ ${cssVarsBlock}
12808
12811
  <section class="shortcuts-group shortcuts-full-only">
12809
12812
  <h3>Response</h3>
12810
12813
  <dl>
12811
- <div><dt>Alt/Option+←</dt><dd>Previous response when not editing text</dd></div>
12812
- <div><dt>Alt/Option+→</dt><dd>Next response when not editing text</dd></div>
12813
- <div><dt>Alt/Option+l</dt><dd>Latest response when not editing text</dd></div>
12814
+ <div><dt>Cmd/Ctrl+Option/Alt+Enter</dt><dd>Annotate the selected response from either main pane; protect unsubmitted or unsaved work</dd></div>
12815
+ <div><dt>Cmd/Ctrl+Option/Alt+Shift+Enter</dt><dd>Load the selected response into the editor without switching views; same replacement safeguards</dd></div>
12816
+ <div><dt>Option/Alt+←</dt><dd>Previous response when not editing text</dd></div>
12817
+ <div><dt>Option/Alt+→</dt><dd>Next response when not editing text</dd></div>
12818
+ <div><dt>Option/Alt+l</dt><dd>Latest response when not editing text</dd></div>
12814
12819
  </dl>
12815
12820
  </section>
12816
12821
  <section class="shortcuts-group">
@@ -12863,6 +12868,7 @@ ${cssVarsBlock}
12863
12868
  <script src="${mermaidHelpersScriptHref}"></script>
12864
12869
  <script src="${navigationHelpersScriptHref}"></script>
12865
12870
  <script src="${previewResourceHelpersScriptHref}"></script>
12871
+ <script src="${editorDraftHelpersScriptHref}"></script>
12866
12872
  <script src="${showMeHelpersScriptHref}"></script>
12867
12873
  <script src="${sideQuestionHelpersScriptHref}"></script>
12868
12874
  <script src="${clientScriptHref}"></script>
@@ -15790,6 +15796,7 @@ export default function (pi: ExtensionAPI) {
15790
15796
 
15791
15797
  try {
15792
15798
  pi.sendUserMessage(msg.text, { deliverAs: "steer" });
15799
+ sendToClient(client, { type: "run_accepted", requestId: msg.requestId });
15793
15800
  broadcast({
15794
15801
  type: "request_queued",
15795
15802
  requestId: msg.requestId,
@@ -15823,6 +15830,7 @@ export default function (pi: ExtensionAPI) {
15823
15830
 
15824
15831
  try {
15825
15832
  pi.sendUserMessage(msg.text);
15833
+ sendToClient(client, { type: "run_accepted", requestId: msg.requestId });
15826
15834
  reportPiEditorDraftDisposition(client, msg.requestId, msg.piEditorDraftSnapshot);
15827
15835
  } catch (error) {
15828
15836
  clearStudioDirectRunState();
@@ -17540,6 +17548,7 @@ export default function (pi: ExtensionAPI) {
17540
17548
  || requestUrl.pathname === "/studio-preview-resource-helpers.js"
17541
17549
  || requestUrl.pathname === "/studio-show-me-helpers.js"
17542
17550
  || requestUrl.pathname === "/studio-side-question-helpers.js"
17551
+ || requestUrl.pathname === "/studio-editor-draft-helpers.js"
17543
17552
  || requestUrl.pathname === "/studio-client.js"
17544
17553
  ) {
17545
17554
  const token = requestUrl.searchParams.get("token") ?? "";
@@ -17567,7 +17576,9 @@ export default function (pi: ExtensionAPI) {
17567
17576
  ? STUDIO_SHOW_ME_HELPERS_URL
17568
17577
  : requestUrl.pathname === "/studio-side-question-helpers.js"
17569
17578
  ? STUDIO_SIDE_QUESTION_HELPERS_URL
17570
- : STUDIO_CLIENT_URL;
17579
+ : requestUrl.pathname === "/studio-editor-draft-helpers.js"
17580
+ ? STUDIO_EDITOR_DRAFT_HELPERS_URL
17581
+ : STUDIO_CLIENT_URL;
17571
17582
  const targetLabel = requestUrl.pathname === "/studio-annotation-helpers.js"
17572
17583
  ? "studio annotation helper script"
17573
17584
  : requestUrl.pathname === "/studio-mermaid-helpers.js"
@@ -17580,7 +17591,9 @@ export default function (pi: ExtensionAPI) {
17580
17591
  ? "studio Show me helper script"
17581
17592
  : requestUrl.pathname === "/studio-side-question-helpers.js"
17582
17593
  ? "studio side-question helper script"
17583
- : "studio client script";
17594
+ : requestUrl.pathname === "/studio-editor-draft-helpers.js"
17595
+ ? "studio editor draft helper script"
17596
+ : "studio client script";
17584
17597
 
17585
17598
  try {
17586
17599
  const clientScript = readFileSync(targetUrl, "utf-8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.9.60-rc.0",
3
+ "version": "0.9.60",
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",