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 +16 -0
- package/README.md +5 -4
- package/ROADMAP.md +58 -13
- package/WORKFLOW.md +4 -2
- package/client/studio-client.js +131 -17
- package/client/studio-editor-draft-helpers.js +64 -0
- package/index.ts +33 -20
- package/package.json +1 -1
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
|
|
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
|
|
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
|
-
|
|
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 —
|
|
131
|
+
## 0.9.60 — Networking and prompt-workflow shortcuts (2026-09-07)
|
|
120
132
|
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
package/client/studio-client.js
CHANGED
|
@@ -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.
|
|
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 (
|
|
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
|
|
25044
|
-
|
|
25045
|
-
|
|
25046
|
-
|
|
25047
|
-
|
|
25048
|
-
|
|
25049
|
-
|
|
25050
|
-
|
|
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(
|
|
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.
|
|
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
|
|
12790
|
-
<div><dt>Alt
|
|
12791
|
-
<div><dt>Alt
|
|
12792
|
-
<div><dt>Cmd/Ctrl+Alt+
|
|
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>
|
|
12812
|
-
<div><dt>
|
|
12813
|
-
<div><dt>Alt
|
|
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
|
-
:
|
|
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
|
-
:
|
|
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
|
|
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",
|