pi-studio 0.9.63 → 0.10.1
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/BUFFER-DESIGN.md +197 -0
- package/CHANGELOG.md +31 -0
- package/PRERELEASE-CHECKLIST.md +100 -0
- package/README.md +110 -193
- package/ROADMAP.md +63 -6
- package/WORKFLOW.md +32 -2
- package/assets/screenshots/dark-workspace.png +0 -0
- package/assets/screenshots/light-workspace.png +0 -0
- package/assets/screenshots/theme-zen-code.png +0 -0
- package/client/studio-client.js +6694 -942
- package/client/studio-editor-draft-helpers.js +162 -0
- package/client/studio-navigation-helpers.js +31 -5
- package/client/studio.css +460 -12
- package/index.ts +1178 -79
- package/package.json +5 -3
- package/shared/STUDIO_BUFFER_FOUNDATION.md +164 -0
- package/shared/studio-buffer-client.js +663 -0
- package/shared/studio-buffer-decisions.js +129 -0
- package/shared/studio-buffer-lineage.js +48 -0
- package/shared/studio-buffer-recovery-panel.js +132 -0
- package/shared/studio-buffer-recovery.js +181 -0
- package/shared/studio-buffer-server.js +700 -0
- package/shared/studio-buffer-store.js +227 -0
- package/shared/studio-buffer-switching.js +53 -0
- package/shared/studio-disk-revisions.js +15 -0
- package/shared/studio-document-entrypoints.js +38 -0
- package/shared/studio-document-escrow.js +147 -0
- package/shared/studio-document-file-claims.js +215 -0
- package/shared/studio-document-host-client.js +268 -0
- package/shared/studio-document-hosting.js +319 -0
- package/shared/studio-document-launch-client.js +153 -0
- package/shared/studio-document-open-client.js +96 -0
- package/shared/studio-document-open-storage.js +21 -0
- package/shared/studio-document-paths.js +29 -0
- package/shared/studio-editor-mode.js +48 -0
- package/shared/studio-linked-reader-ui.js +232 -0
- package/shared/studio-linked-reader.js +110 -0
- package/shared/studio-metadata-write-order.js +35 -0
- package/shared/studio-read-only-media.js +24 -0
- package/shared/studio-resource-grants.js +3 -0
- package/shared/studio-run-lifecycle.js +47 -0
- package/shared/studio-system-theme.js +9 -0
- package/assets/screenshots/dark-code-mode.png +0 -0
- package/assets/screenshots/dark-critique.png +0 -0
- package/assets/screenshots/dark-fenced-code.png +0 -0
- package/assets/screenshots/dark-focus-mode.png +0 -0
- package/assets/screenshots/dark-julia-fenced.png +0 -0
- package/assets/screenshots/dark-math.png +0 -0
- package/assets/screenshots/dark-mermaid.png +0 -0
- package/assets/screenshots/dark-zen.png +0 -0
- package/assets/screenshots/light-code-mode.png +0 -0
- package/assets/screenshots/light-fenced-code.png +0 -0
- package/assets/screenshots/light-math.png +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
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",
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
"README.md",
|
|
26
26
|
"CHANGELOG.md",
|
|
27
27
|
"ROADMAP.md",
|
|
28
|
+
"BUFFER-DESIGN.md",
|
|
29
|
+
"PRERELEASE-CHECKLIST.md",
|
|
28
30
|
"WORKFLOW.md",
|
|
29
31
|
"assets/screenshots",
|
|
30
32
|
"assets/demo"
|
|
@@ -51,8 +53,8 @@
|
|
|
51
53
|
"ws": "^8.21.3"
|
|
52
54
|
},
|
|
53
55
|
"devDependencies": {
|
|
54
|
-
"@earendil-works/pi-ai": "^
|
|
55
|
-
"@earendil-works/pi-coding-agent": "^
|
|
56
|
+
"@earendil-works/pi-ai": "^1.2.0",
|
|
57
|
+
"@earendil-works/pi-coding-agent": "^1.2.0",
|
|
56
58
|
"@iconify-json/logos": "1.2.11",
|
|
57
59
|
"@iconify-json/lucide": "1.2.120",
|
|
58
60
|
"@sinclair/typebox": "^0.34.49",
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Buffer foundation and opt-in switching prototype
|
|
2
|
+
|
|
3
|
+
The browser-safe store/codec/migration foundation and fixed-slot workflow are implemented through local runtime checkpoint `40d4462`. Bounded preparation of local candidate `0.10.0-alpha.1` is approved. The **process-scoped `PI_STUDIO_BUFFER_RECOVERY=1` opt-in** connects the existing editor to v2 recovery and a separate process-memory fallback. Without it, v1 remains the default. Test in an isolated installation/runtime; stable `0.9.60` and normal defaults remain unchanged. Publication and further commits are separate decisions. See [README](../README.md#local-alpha-one-prompt--one-document) for activation, limits and rollback.
|
|
4
|
+
|
|
5
|
+
With switching off, the successful editor layout, Run behaviour and browser-opening actions remain unchanged. A separate `PI_STUDIO_BUFFER_SWITCHING=1` opt-in now supplies the narrow two-entry prototype described below; context-transfer messages and general tab management remain future work. The opt-in build has **Source & context → Recover unsaved text…** for inspection, export, explicit recovery choice and persistence retry; no footer control is added. The targeted single-editor callback/metadata ownership pass and current isolated Brave/Chrome validation are complete locally. The historical targeted Safari 26.5 foundation smoke passed after remote automation was re-enabled; it does not validate the current switcher. Recovery remains opt-in; default activation still requires explicit approval.
|
|
6
|
+
|
|
7
|
+
The 2026-09-23 uncommitted toolbar follow-up groups append actions under **Add to Prompt** and source loaders under **Open / load**. Import and Pi-draft loading still replace the selected buffer without changing its role; opening a file in Document still keeps Prompt. Menu help replaces permanent explanatory rows. These are UI changes over `40d4462`, not a new store schema or multi-document feature. The earlier artifact/evidence remains separate from the rebuild.
|
|
8
|
+
|
|
9
|
+
## Model contract
|
|
10
|
+
|
|
11
|
+
`createStudioBuffer(options)` creates an immutable buffer. `createStudioBufferStore(snapshot)` owns an immutable schema-v2 workspace. `validateStudioBufferWorkspace(value, expected)` validates and copies the complete snapshot, returning `{ ok, state }` or `{ ok: false, reason, message }`. Constructors throw `StudioBufferStateError` for invalid initial input; store actions return result objects.
|
|
12
|
+
|
|
13
|
+
A workspace contains:
|
|
14
|
+
|
|
15
|
+
- `version: 2`, `workspaceId` (the existing browser-tab identity), trusted launch `mode` (`full` or `editor-only`), monotonic `revision` and `savedAt`;
|
|
16
|
+
- separate `selectedBufferId` and `activePromptId`, explicit ordering, and the complete buffer collection;
|
|
17
|
+
- per-buffer `id`, explicit `role`, revision, exact text and nullable text baseline;
|
|
18
|
+
- source kind/label/path/draft identity, expected disk revision and resource directory;
|
|
19
|
+
- editor/preview modes, language, selection/direction, editor/preview scroll, optional validated `view.rightScrollTop`, and response-reading position;
|
|
20
|
+
- annotation preference and review-note/scratchpad association keys, not copies of their external records.
|
|
21
|
+
|
|
22
|
+
Conversation contents, resource grants, Shared REPL Record, accepted-submission baselines and Pi-terminal cleanup fingerprints are **not** buffer recovery fields. Unknown fields are rejected, not silently stripped. Resource directories, source paths, disk revisions and metadata keys are claims to be revalidated by existing server authority; this module performs no filesystem I/O and grants no access.
|
|
23
|
+
|
|
24
|
+
Full workspaces must have an active Prompt. Editor-only workspaces contain documents only and have no active Prompt. Watched previews do not own editable stores. Selecting, opening or closing documents does not activate a different Prompt. A path does not determine role: a Prompt can be saved without turning it into a document.
|
|
25
|
+
|
|
26
|
+
## Mutations and asynchronous ownership
|
|
27
|
+
|
|
28
|
+
- `snapshot()` / `get(id)` return immutable state; references cannot mutate the store.
|
|
29
|
+
- `update(id, expectedRevision, patch)` rejects a changed/closed origin. IDs and roles cannot be patched. Changing the file path clears the previous baseline/revision unless explicitly supplied for the new source.
|
|
30
|
+
- `add(buffer)`, `select(id)` and `activatePrompt(id)` are explicit independent operations. Activation does not select the Prompt automatically.
|
|
31
|
+
- `beginOperation(id, requestId, { requireActivePrompt })` returns an opaque, page-memory target containing the originating ID/revision/request ID. Retain that exact handle before beginning asynchronous work or asking for consent.
|
|
32
|
+
- `finishOperation(target, patch)` consumes matching ownership and updates only the origin. It does not select it. Duplicate, forged, reconstructed, closed/reopened or edited targets are rejected. Switching the active destination away and back invalidates active-Prompt-targeted work.
|
|
33
|
+
- `close(target, { discard, replacementPromptId })` uses the same captured ownership, not merely an ID/revision pair. Dirty buffers and nonempty Prompts require `discard: true`; closing the active Prompt requires an explicit replacement Prompt. Confirmation must be captured before the asynchronous decision, not reconstructed afterward.
|
|
34
|
+
- `cancelOperation(target)` / `clearPendingOperations()` release failed/cancelled/disconnected work. Pending ownership is never serialized or restored.
|
|
35
|
+
|
|
36
|
+
For this first slice **every buffer-local mutation, including view changes, increments its revision**. This deliberately conservative policy prevents stale consent/results; later renderer adapters must decide whether to split view revision from content/identity revision, with tests, rather than bypass stale checks. Workspace selection alone does not change buffer revisions. UI adapters must also clear their transient work when replacing a whole store instance.
|
|
37
|
+
|
|
38
|
+
A completed operation is not a file save. Updating a disk baseline/revision is an explicit operation following a successful existing server save; it is not a submission side effect. The transitional adapter now tests source-owned save acknowledgements: later typing remains visible and dirty against the exact content actually saved. A changed/reopened source cannot inherit an older save acknowledgement. Detaching a file clears its baseline in the store rather than lending that saved comparison to an unrelated detached document.
|
|
39
|
+
|
|
40
|
+
The opt-in browser adapter extends that rule to the current single-editor surface. Completion/import/Pi-editor replacements, preview renders and their committed DOM actions, HTML/PDF/image focus controls, Quarto requests, annotations, comments and metadata decisions retain exact origin predicates through asynchronous stages. Replacing a response pane does not clone away those predicates. Resource-grant dialog clicks remain part of the local-link operation they approve, while genuine outside clicks still cancel it. Connection loss revokes editor-preview owners, and the handshake rebuilds each visible editor preview before its DOM can act again. Recovery initialization and Reset use whole-workspace consent covering source/text, disk baseline, view/resource state, selection/scroll and metadata generations; a late continuation cannot silently roll those states back.
|
|
41
|
+
|
|
42
|
+
## Limits and failure behaviour
|
|
43
|
+
|
|
44
|
+
- 16 open buffers;
|
|
45
|
+
- 900,000 UTF-16 characters per text or baseline;
|
|
46
|
+
- 3,000,000 aggregate text **including baselines**;
|
|
47
|
+
- 64 pending operations;
|
|
48
|
+
- bounded identity, view and association strings; 20,000,000 serialized recovery characters.
|
|
49
|
+
|
|
50
|
+
Updates validate the complete candidate before replacing state. No buffer eviction, identity/text truncation, partial normalization or automatic dirty-close occurs. Resource/capacity failures are result objects; the opt-in adapter displays them persistently in the existing status area. These are application limits, not a promise of browser storage capacity: sessionStorage can reject smaller writes.
|
|
51
|
+
|
|
52
|
+
## V1 migration and storage
|
|
53
|
+
|
|
54
|
+
`migrateStudioWorkspaceV1(value, { workspaceId, mode, makeBufferId })` is pure. The launch owner supplies mode and workspace identity; migration does not infer new permissions from stored data.
|
|
55
|
+
|
|
56
|
+
- Detached full-editor state becomes a Prompt, preserving exact text and source identity.
|
|
57
|
+
- File-backed full-editor state becomes the selected document plus an independent empty active Prompt.
|
|
58
|
+
- All editor-only sources remain documents.
|
|
59
|
+
- V1 never persisted a text baseline. File recovery retains its expected disk revision but uses `baselineText: null`, meaning cleanliness is unknown and must be protected. It never rereads disk to guess that the draft is saved.
|
|
60
|
+
- Selections are clamped to the recovered text, as in the old UI. Prose and identities are not truncated or normalized away.
|
|
61
|
+
- Malformed/future payloads and `recovery-cleared` / `recovery-omitted` markers require a recovery decision, rather than becoming an empty draft silently.
|
|
62
|
+
|
|
63
|
+
`encodeStudioBufferRecovery` and `decodeStudioBufferRecovery` implement the strict v2 codec. `createStudioBufferRecoveryStorage({ storage, workspaceId, mode })` provides a synchronous **same-page sessionStorage adapter**, not server compare-and-swap or a cross-client lease:
|
|
64
|
+
|
|
65
|
+
1. `read()` distinguishes empty, existing v2, legacy-only, unavailable and invalid/conflicting storage. Missing/unreadable storage is not an empty-editor instruction.
|
|
66
|
+
2. `migrateLegacy({ expectedLegacyRaw, makeBufferId })` checks the exact legacy read, validates the full new state, rechecks both source and destination, writes the new key and verifies it by reading/decoding it back.
|
|
67
|
+
3. `write(snapshot, { expectedRaw })` requires the exact previous v2 read and a newer workspace revision (or an identical retry). It cannot bypass pending legacy migration, overwrite future/malformed data, or hide newer legacy work with a newer candidate timestamp.
|
|
68
|
+
4. `adopt(snapshot, { expectedLegacyRaw })` admits an externally recovered server/v1 candidate only against the exact local legacy read and an absent v2 destination; the ordinary schema/timestamp/readback checks still apply.
|
|
69
|
+
5. No storage method erases old recovery. Failed/quota-limited/unverified writes preserve legacy bytes; the caller retains its live store and expected raw snapshot. No rollback overwrites another writer after a verification mismatch.
|
|
70
|
+
|
|
71
|
+
Namespace: `piStudio.bufferWorkspace.v2:<tab-id>`. The old per-tab key is misleadingly named `piStudio.workspaceState.v2:<tab-id>` but contains **schema v1**; it remains untouched. The unscoped `piStudio.workspaceState.v1` fallback is not adopted automatically by new tabs. A later UI adapter must retain the existing URL/document-identity gate when offering any older unscoped recovery.
|
|
72
|
+
|
|
73
|
+
Retained v1 data is validated even when v2 exists. Newer legacy timestamps, corruption or reset/omission markers block automatic restore/write until explicitly resolved. The low-level storage adapter does not itself decide between copies; the explicit choice layer below always prepares a new namespace. Timestamp comparison follows the existing v1 monotonic-write convention; this is not authentication or a multi-client protocol.
|
|
74
|
+
|
|
75
|
+
## Opt-in single-editor/server integration
|
|
76
|
+
|
|
77
|
+
`studio-buffer-client.js` projects the existing editor into a store. It preserves the selected buffer's role/identity when existing UI actions change its source. With switching off, a file-backed initial full view has a selected document plus an empty Prompt, but **Run still submits the visible existing editor**, never the hidden Prompt. The second-flag interaction below changes the controls and submission guard together, not by quietly changing the submission expression. Collections containing other inaccessible valuable buffers are refused until a compatible UI exists.
|
|
78
|
+
|
|
79
|
+
`studio-buffer-server.js` supplies a separate `/tab-buffer-state` endpoint. It requires the ordinary session token and a server-issued page capability bound to the HTML workspace ID and launch mode. Watched views receive no capability. The fixed browser-safe module graph is token-authenticated too; neither route grants filesystem access or adds watched-socket mutations.
|
|
80
|
+
|
|
81
|
+
- GET reads a complete validated workspace. POST requires an opaque previous server revision; divergent/stale updates fail, while exact retries are acknowledged idempotently. Explicit GET with `inspect=1` exposes the retained v2/v1 raw copies even when a legacy conflict blocks normal adoption. It requires the same workspace/mode-bound capability and returns no persistence acknowledgement or extra filesystem authority.
|
|
82
|
+
- Server limits: 16 workspaces, 12,000,000 aggregate text/baseline characters, 256 page capabilities, a 24-hour idle TTL, and a 32,000,000-byte request cap. Capacity failures do not evict another live workspace.
|
|
83
|
+
- Acknowledgements identify the workspace revision and remaining lifetime. They are distinct from Run acceptance, disk-save acknowledgements and terminal cleanup.
|
|
84
|
+
- DELETE releases **only that page capability**, never its recovery. Non-cached page exits send a best-effort release; crashes/cached-page lifetime fall back to TTL. An exhausted capability pool returns HTTP 503 rather than silently serving an unbound editor.
|
|
85
|
+
- Browser writes precede serialized/coalesced server writes. The separate `:serverAck` browser marker records acknowledged ancestry, not copied text or authority. Both v2 candidates are structurally validated first; ancestry chooses a candidate before its URL/source and single-editor compatibility are checked. Thus an older pre-Save-As identity does not veto newer matching recovery. Divergent branches without matching ancestry require a decision.
|
|
86
|
+
- Both legacy layers are fully validated and retained. Newer/reset/corrupt v1 blocks automatic adoption. Older valid v1 identities do not veto newer v2 after Save As; legacy-only adoption first chooses the newest valid candidate and then checks the URL/source identity. Equal-time divergent legacy candidates are refused.
|
|
87
|
+
- Startup editing, disposal or local-storage races abort adoption. The subsequent websocket handshake cannot overwrite the text that an aborted recovery kept.
|
|
88
|
+
- Save As, annotated saving, disk conflicts, refresh, response replacement and reset capture exact relevant ownership. Save results are source-owned; a late refresh cannot overwrite later typing. Completion/import/Pi-editor callbacks, committed preview interactions, focus viewers, Quarto context and review-note prompt replacement are covered by the opt-in ownership regressions. Comments-prompt replacement protects dirty/unknown-baseline file state even when the editor is empty or already equals the generated prompt; baseline/revision changes invalidate pending consent alongside editor/source/comment changes.
|
|
89
|
+
|
|
90
|
+
Session storage can disappear when its tab closes. Since `beforeunload` cannot distinguish closing from reloading, silent navigation requires the **current, unexpired server acknowledgement**, not merely a local copy or queued POST. Its lifetime is measured from request start, not response receipt. Observed connection loss invalidates it, including acknowledgements arriving late from the previous connection epoch. Dirty or unsynced scratchpad/comment metadata also protects unload even when editor recovery is acknowledged. Otherwise, native navigation consent lets the user cancel and wait/save/copy. Small POSTs use keepalive, but no asynchronous unload delivery guarantee is made. Explicitly confirmed Reset prepares and verifies a canonical blank snapshot under a new namespace, retaining even older unscoped recovery. Recovery navigation consent covers the captured editor, view/resource/selection and metadata generations plus connection, and is consumed by one `beforeunload` event; cancelled navigation does not authorize a later closure.
|
|
91
|
+
|
|
92
|
+
Scratchpad and review-note records remain external to buffer snapshots, but their association keys are accepted only for the recovered source identity. Reads cannot replace newer page-local edits or interpret failure as an empty record. In the opt-in adapter, comments/scratchpad mutations and all POST/beacon paths require page-local proof of a successful first read for that exact document key, including valid empty records. Loading, failed, malformed and timed-out first reads leave editing paused rather than overwriting unseen saved notes. Reopening an unavailable panel retries; previously loaded page-local records remain editable during refresh. Reads time out after 15 seconds, and stale-source read completions cannot establish that proof. Writes are serialized per document key; failed records remain dirty and retry before a same-key reload. A stalled key retains at most one in-flight request plus its latest unsynced snapshot; intermediate autosaves are coalesced, the request is aborted after 15 seconds, and drain/beacon retries preserve newer or other-document debounce timers. `beforeunload` and `pagehide` beacon every unsynced key, including documents no longer selected. Session-scoped, tab-monotonic write versions let the server's serialized state writer reject an older POST/beacon from the same Studio tab if it arrives after a newer persisted snapshot, including across reload. The ordering marker advances only after the persistent-state write succeeds. A queued beacon is not treated as acknowledgement when recovery is enabled.
|
|
93
|
+
|
|
94
|
+
## Opt-in one-Prompt/one-document integration
|
|
95
|
+
|
|
96
|
+
Oliver separately approved this bounded prototype after the foundation checkpoints and visual guide. It is a local checkpoint, not the complete stage-2 tab manager. Both `PI_STUDIO_BUFFER_RECOVERY=1` and `PI_STUDIO_BUFFER_SWITCHING=1` are required; the server/client additionally require a full editable workspace. Editor-only and watched views never enable the strip.
|
|
97
|
+
|
|
98
|
+
- `studio-buffer-switching.js` accepts one active Prompt and at most one document, adding an empty document slot when necessary. Additional/parked buffers are refused, never discarded. Fresh/v1 visible-file intent makes that file the Prompt while retaining its source and baseline, including unknown v1 cleanliness. An older v2 file beside the pristine empty placeholder Prompt requires an explicit role choice; Cancel or stale consent leaves the original copy untouched.
|
|
99
|
+
- `createStudioBufferClient({ switching: true, ... })` adds guarded `select(id)` and revision-checked `replace(id, expectedRevision, editor, baseline, extra)` to the existing adapter. Failed capture/capacity/ownership checks preserve the current editor. Replacement does not implicitly select its target. Default/editor-only clients reject these switching operations.
|
|
100
|
+
- The existing textarea binds one buffer at a time. Binding suppresses intermediate capture/persistence while restoring text, source, file baseline/revision, metadata associations, view and reading positions. A single owner-checked animation-frame restoration accommodates native unfocused-textarea selection settling after click dispatch. Pending intended positions survive a rapid next switch; direct textarea input/pointer/key/scroll gestures cancel the restoration, and stale source/editor/connection owners cannot apply it.
|
|
101
|
+
- Transient source-generation/submission identity and linked Pi-terminal fingerprints follow the exact buffer in page memory only. A file replacement starts without inherited terminal cleanup authority; no fingerprint, accepted-submission baseline or resource grant enters recovery.
|
|
102
|
+
- Local mutating work and modal decisions gate switch/open execution. Underlying controls are not latched disabled by modal focus changes; the actual action rechecks the gate. Passive previews/metadata still use their exact owners. Late Files lists, file reads, preview scroll and response-focus continuations cannot redirect another buffer.
|
|
103
|
+
- A separately approved local checkpoint allows selection through the busy fence only for an identified direct Run on an open, submitting socket. Its submitted raw text/source key is already captured by the page-memory tracker. Save, file opening/replacement (including post-fetch continuation), recovery/reset, unresolved Pi-terminal snapshots, other local operations and modal decisions retain their locks. Unknown activity and disconnected/reconnecting sockets do not qualify. A busy handshake refreshes controls only after installing the authoritative request identity.
|
|
104
|
+
- **Open / load → Open file in Document…** and open-here use the document slot. Existing canonical-path resource authorization performs the fetch. Replacing valuable text requires current origin/target consent; the destination revision is rechecked. File-open consent ignores only native selection/scroll movement caused by dialog focus; text/source, disk baseline/revision, resource/view, metadata and connection remain protected. General recovery/reset consent is not weakened. Reopening the exact already-open canonical path retains its unsaved text; explicit disk refresh remains separate.
|
|
105
|
+
- **Files → Use as Prompt…** explicitly loads a file into Prompt without changing the separate document. It reads authorized file text, not an unsaved cross-buffer snapshot. There is no implicit copying on return; manual copy/paste still works. The separately approved local editor-selection append is described below; it is not a file load or companion transfer.
|
|
106
|
+
- **Return to Prompt** never sends either buffer. A separate Run uses the visible active Prompt and existing annotation send/strip policy. A button-local Enter auto-repeat guard prevents native `click.detail === 0` activations from crossing Return→Run or Run→Stop; textarea repeats and a fresh activation after release remain available. The existing Cmd/Ctrl+Enter and double-click guards also remain. Within Run's existing shortcut scope, Cmd/Ctrl+Enter in Document returns without steering or triggering Stop; the actual Stop button and normal Escape cancellation remain global. Rejected steering retires its own pending snapshot and reports failure without settling the parent Run; stale error/busy callbacks cannot unlock a different active Run.
|
|
107
|
+
- Response history is shared; Follow activity and response-loading actions do not steal Document's view or text. Normal and empty history notifications cache/clear response state without reconstructing Document's live preview, preserving iframe-local state and its committed interaction owner. Primary Run completion in Document is queued just like a background response. Per-buffer page-memory activity ownership resumes only for the same request; completion elsewhere retires automatically selected Working without overriding a manually selected view or opt-out. Disabling Follow activity clears both live and saved ownership/latches, without changing source or terminal provenance. Explicit re-enabling in Document may arm the exact ongoing Prompt request for return; it cannot move Document's view or revive a finished request. Binding a Follow-on Prompt applies its queued update using the existing history-first/payload-fallback policy and clears the queue only after application. Authoritative Pi tree changes invalidate the prior branch's queue, even for empty history; ordinary empty history does not discard a legitimate current-branch payload. The new response does not inherit the old response's scroll offset. Follow-off, or no queued update, retains its previous selection/reading position. Editor-preview, Quarto-editor-preview and side-discussion scroll positions remain independent of response catch-up.
|
|
108
|
+
- Recovery compatibility admits the complete pair under the second flag. Use-copy/Keep-current retain both buffers in a verified fresh namespace. **Reset both buffers** explicitly confirms a fresh blank pair without modifying files. General create/close/park, live companion synchronization and durable recovery remain outside this prototype.
|
|
109
|
+
|
|
110
|
+
Experimental v2 copies may be refused or invisible in an older build. Save/export needed work before moving between builds; retaining original bytes is not a promise that an older UI can display them.
|
|
111
|
+
|
|
112
|
+
## Local editor-selection append checkpoint
|
|
113
|
+
|
|
114
|
+
The same two opt-ins and full editable mode gate **Add selection to Prompt**. `createStudioBufferClient` exposes `appendSelectionToPrompt({ sourceId, sourceRevision, promptId, promptRevision, start, end })`. It requires the selected Document in editor mode, its exact stored selection/revision, and the current active Prompt/revision. Disabled/default/editor-only/disposed/unrepresented clients reject it. The update is synchronous and text-only, not `replace()` or `select()`; strict aggregate/per-buffer validation precedes commit. A repeated old intent fails the destination-revision check.
|
|
115
|
+
|
|
116
|
+
It appends an exact substring with a safely quoted code-span source label and editor-line range. Paired padding separates delimiter runs from leading/trailing backticks, including imported labels, without changing the displayed label or interpreting filename syntax as media/links. Existing Prompt text is untouched, including its trailing whitespace; copied text is neither trimmed nor newline-normalized. The label describes the in-memory snapshot, not a disk read, live link or file grant. Each buffer keeps its own source/baseline/disk revision, resources, metadata, annotation policy, selection and reading positions. No submission or Pi-terminal provenance comes from Document. The normal local/server recovery path persists the new pair; errors preserve live text and prior stored copies rather than silently dropping the addition.
|
|
117
|
+
|
|
118
|
+
The client captures intent before pointer/keyboard activation and checks source/selection ownership, whole-editor consent, connection and destination revision at application. Other-field, preview and iframe interaction retires the previous editor selection; action-button keyboard focus does not. A monotonic owner also observes range/direction changes and new source focus/pointer/key gestures, so A→B→A cannot revive a held press. Arming samples the current range before capture; a later notification of that unchanged range remains valid. Document-wide selection observation does not itself reclaim a revoked source. A retained hidden textarea range is never a preview-copy source. Native repeated Enter/Space and double-click continuations cannot append twice; a fresh activation can deliberately add another snapshot. Document remains bound, without repainting its live preview. A connected identified Run permits preparing the next Prompt draft, never changing its submitted snapshot or steering/stopping it. Local open/import/Pi-editor operations, terminal disposition, recovery/modals and reconnection remain fenced.
|
|
119
|
+
|
|
120
|
+
There is no new schema, endpoint, server permission or companion protocol. Whole-document copying is a separate follow-up below; selection ownership/range checks are unchanged. Preview extraction, a new undo library and general tab management remain deferred.
|
|
121
|
+
|
|
122
|
+
## Local whole-document append checkpoint
|
|
123
|
+
|
|
124
|
+
The same two opt-ins/full editable mode gate **Add document to Prompt**. `documentAppendInfo()` is a pure eligibility/size query; `appendDocumentToPrompt({ sourceId, sourceRevision, promptId, promptRevision })` requires the current selected Document and active Prompt with exact revisions. The payload is derived from the represented store, not caller-supplied text or artificial selection offsets. Raw-editor view and nonempty text are required, but no selected range or prior textarea focus is needed. Empty text is refused; whitespace-only text remains exact.
|
|
125
|
+
|
|
126
|
+
The action appends the complete editor string, including unsaved edits, after the existing Prompt prefix and a literal **whole document, snapshot** label. The two append operations reuse the same label quoting without broadening selection copying. Both identities, sources, file baselines/revisions, resources, metadata and reading positions remain independent; Document stays selected and its live DOM is not reconstructed. No source focus, range replacement, Save, Run, steering or terminal cleanup occurs. Accepted text uses ordinary recovery; write failures retain live text, older stored bytes and warnings.
|
|
127
|
+
|
|
128
|
+
The size description in the Add disclosure/action tooltip and the application share one framing/capacity calculation. The addition includes separators and the literal header; available capacity is the minimum of per-Prompt text headroom and aggregate workspace headroom including baselines. Counts use `String.length` UTF-16 units, not bytes or model tokens. The shared operation recomputes capacity and the store validates the full candidate atomically before a text-only update. No truncation, partial append or eviction occurs.
|
|
129
|
+
|
|
130
|
+
The UI captures client/source/destination revisions, editor/connection consent and a separate monotonic interaction generation at activation. Outside pointer/key/focus/input events, cancellation and window blur retire held intent; equal final text after edit/undo or a buffer round trip cannot revive it. Normal button focus and unchanged selection notifications remain valid. Repeats/double-click continuation add once; a fresh activation can deliberately add again. This does not require or reauthorize the selection action's textarea-range owner. Identified connected Run permits next-draft preparation only, with unchanged submitted snapshot/global Stop and existing unsafe-operation/recovery/modal/terminal fences.
|
|
131
|
+
|
|
132
|
+
Pending file-open entry and cleanup synchronize both append controls, including stale/error results, without clearing a newer operation's fence. Separate pointer/keyboard continuation leases distinguish retired native gestures from fresh click-only activations. Activating one does not authorize the other's old continuation. Keyup retains a retired keyboard lease through the native activation task, then releases it even when the browser cancelled that click; a generation check protects any newer press, including a refused one.
|
|
133
|
+
|
|
134
|
+
## Explicit recovery choices and exports
|
|
135
|
+
|
|
136
|
+
`studio-buffer-decisions.js` reads browser v2, scoped v1, older unscoped v1 and the authenticated server inspection without adopting them. `studio-buffer-recovery-panel.js` provides a native modal using plain text, not HTML rendering of stored content. Keyboard actions stay inside the modal; queued close events cannot dispose a newly reopened inspection. The action lives in the source menu’s Document section (source actions in the optional classic layout); it is absent in watched/default-v1 views. Menu items other than recovery remain inert during work, so inspection/export stays accessible without unlocking other source actions. Hosts without native inert retain the old busy-menu restriction. Closing the modal returns focus to the visible menu trigger. Short introductory copy leads the modal; storage/privacy/compatibility details are under **About these copies**.
|
|
137
|
+
|
|
138
|
+
- The **Recheck copies** action refreshes the inspection list. Download the current inspection first if it should be retained separately.
|
|
139
|
+
- **Download recovery archive** writes experimental `pi-studio-recovery-export` JSON with the complete raw recovery strings, channel errors and current editor text. JSON string escaping preserves malformed/future data, including lone UTF-16 surrogates. No transport token, capability or ancestry sidecar is added. User text and recorded paths may be sensitive.
|
|
140
|
+
- **Copy/download selected text** exports a validated buffer's complete text, including buffers that this UI cannot activate. The display preview is explicitly limited to 20,000 characters; exports are not truncated. The text selector is for preview/export, not a change to the stored selected editor.
|
|
141
|
+
- **Use selected copy…** requires a valid same-workspace/mode candidate compatible with the current source and single-editor surface. After confirmation, it rechecks the exact source copy and whole-workspace/connection consent, then verifies a new sessionStorage namespace before navigation. Originals remain untouched; no file is saved, text submitted, or hidden Prompt activated.
|
|
142
|
+
- **Keep current text…** instead captures the live editor and its known baseline. This works even when existing recovery is corrupt; preparation failure or stale consent leaves the editor in place. Reset uses the same verified-fresh-copy principle for an explicitly blank draft.
|
|
143
|
+
- **Retry recovery** preserves the live editor. It retires the old upload epoch, rereads recovery and refuses divergent server revisions or readable changed/corrupt browser copies. Unavailable browser storage does not prevent supported server-only recovery; no new ancestry marker is attached to browser data that could not be read. A failed refresh/disconnect clears its pending refresh ownership so it cannot indefinitely block recovery actions.
|
|
144
|
+
|
|
145
|
+
Other-source, wrong-mode, future/malformed and inaccessible multi-buffer collections are export-only. The second flag admits the supported Prompt/document pair; larger collections are still refused. They are never relabelled or partially adopted to bypass validation. Archive import, restoring another source directly and durable backup are not supplied by this slice.
|
|
146
|
+
|
|
147
|
+
## Validation and activation boundary
|
|
148
|
+
|
|
149
|
+
Current runtime checkpoint `40d4462` includes later replacement/annotation, append, history/reading-state and pane-scroll fixes. Fresh checkpoint evidence: 240 focused passes, normal typecheck/static checks and bounded old/fixed Chrome Headless Shell pane-scroll comparisons. The older 718 full-suite result belongs to `7bdc891`; exact alpha-artifact results are recorded separately during preparation, not inferred from these counts. Whole-document and composed-Prompt hands-on trials subsequently completed. Blank-looking Working remains unreproduced; actual hidden-tab/visible-browser painting is unverified. See [ROADMAP](../ROADMAP.md) and the [maintainer checklist](../PRERELEASE-CHECKLIST.md).
|
|
150
|
+
|
|
151
|
+
The following records retain their original checkpoint boundaries; later work does not retroactively change them.
|
|
152
|
+
|
|
153
|
+
- Historical whole-document follow-up: **549 tests /107 focused tests**. Eighteen regressions failed before initial implementation. A fresh read-only review identified stale-open control state and cancelled-press defects; both were reproduced on the unchanged candidate and fixed with separate approval. Nine more regressions cover pending/stale/error/newer opens, separate pointer/keyboard continuations and generation-guarded keyup cleanup. Five native fix groups, eleven whole-document groups and nine renderer/import/held-press cases pass per Brave/Chrome, plus fourteen prior compatibility runs (selection, selection review fixes, Run review fixes, Run, switching, prior switching review, foundation): twenty passing sequential fixed-candidate runs in total. Cancellation uses trusted mouse/touch input; the later click-only assistive action is simulated, not a VoiceOver test. No page errors or Studio provider calls. The separate static reviewer used a subscription model. Typecheck/syntax/diff, production audit and package dry-run pass; supplementary check-JS still has other diagnostics. Locally committed but unreleased; no independent post-fix sign-off, hands-on trial, switching-specific Safari, complete mobile layout or release validation for this follow-up.
|
|
154
|
+
- Historical selection checkpoint: **522 tests /80 focused tests**, typecheck/syntax/diff checks, production audit and package dry-run pass. A fresh read-only review identified label-boundary and stale-reselection defects; both were reproduced and fixed with separate approval, adding failing unit/native regressions first. Nine native fix cases per Brave/Chrome cover literal labels through the actual renderer/import route and held-pointer/F8/A→B→A with rejected-stale and accepted-fresh controls. The reviewer's leading-backtick image example did not reproduce; an imported trailing-backtick name established the real label defect. Eleven existing selection groups per browser still cover state preservation, preview/iframe exclusion, repeats, metadata isolation, Run ownership, held opens, reload, live iframe state and narrow switcher layout. Ten sequential prior compatibility runs also pass: fourteen passing browser runs in total. Controlled messages are intercepted before providers; no Studio provider trial or independent post-fix sign-off is claimed. This is not complete page-wide mobile-layout, Safari or release validation.
|
|
155
|
+
- Historical Run-switching follow-up: **500 tests** (58 focused switching/Run tests), with type/syntax checks. Six new native Run scenarios per Brave/Chrome cover Follow on/off, activity ownership, late acceptance, editing both buffers, held Cmd/Ctrl+Enter, steering rejection, global Stop/Escape, stale outcomes, busy handshake and interactive Document iframe preservation. The existing 17 switching groups, six earlier review regressions and foundation-only matrices are rerun separately; their controlled messages make no provider calls. The earlier review's findings and the new busy/history/iframe failures were reproduced before their fixes. A fresh read-only review then identified stale branch queues and hidden activity policy state. Both were independently reproduced and fixed with separate approval; twelve additional native cases per browser cover tree changes, valid historyless payloads, policy off/on timing and visible-buffer controls. This is not a switching-specific Safari result, independent post-fix sign-off, or fresh-install/release validation.
|
|
156
|
+
- Before the two latest review fixes, a bounded Brave live check used subscription `openai-codex/gpt-6-astra`, medium, with no model tools/shared REPL: one real Studio Run completes while Document is selected and one is stopped globally from Document. It verifies kept document edits/local scratchpad, Prompt-owned inputs, no automatic context transfer, Follow catch-up and automatic Working retirement. Temporary access-only OAuth stays in memory; owned processes stop afterward and trial files/recovery/profile are retained.
|
|
157
|
+
- The committed Run-switching checkpoint, including both review fixes, subsequently passed a separately approved isolated live-model hands-on trial. Oliver confirmed the interaction, then approved backing up/stopping it. Its drafts/profile are retained. This predates the selection append and does not validate that new code against a provider. The selection checkpoint later passed a separate credential-free hands-on trial; Oliver confirmed it worked, then approved its verified backup-before-stop. That trial predates whole-document append.
|
|
158
|
+
- Historical foundation checkpoint: all 432 tests and static checks passed. The expanded 20-workflow matrix plus native grant-continuation, reconnect-owner, empty dirty-file consent and unread/failed metadata-preservation probes pass in isolated Brave and Google Chrome. After Safari's **Allow Remote Automation** setting was re-enabled, the current SafariDriver smoke on Safari 26.5 passed acknowledged reload recovery, metadata reload/ordering, resource-grant continuation, PDF focus replacement, stale comments-prompt consent and the native recovery dialog with zero captured page errors. This is the targeted WebKit smoke, not the full Chromium matrix or its new held-first-read/empty-dirty-file probes.
|
|
159
|
+
- An independent Codex review found five actionable P2 issues, then two follow-up autosave-timer interleavings. Each was reproduced before editing, fixed with focused regression coverage, and rechecked. Its final mixed-event and server-ordering probes reported no actionable findings. A later in-session Astra review nevertheless reproduced two further gaps: empty dirty-file prompt replacement and deletion of unread saved comments. Both are now fixed with focused regressions and native Brave/Chrome preservation assertions, including the analogous scratchpad path. The earlier clean review is not a blanket sign-off for subsequent edits.
|
|
160
|
+
- Preserve conservative export-only handling for unsupported states and extend the current origin/association regressions as new callbacks or switching controls are introduced. Do not weaken the separate page-memory submission tracker, terminal fingerprint provenance or file/grant authority.
|
|
161
|
+
- Continue production UI tests under isolated configuration, source paths, browser profiles and disposable files; preserve independent browser companions and watched previews alongside eventual internal tabs.
|
|
162
|
+
- The local foundation/header checkpoint commits were explicitly approved on 2026-09-09. The later one-Prompt/one-document implementation and its local prototype checkpoint were separately approved, not a general tab manager or release. Enabling either flag by default, expanding scope, further commits, switching the installed source, pushing or releasing still requires separate approval. Host validation does not grant that authority.
|
|
163
|
+
|
|
164
|
+
Recovery remains browser session storage plus bounded **process-memory** server fallback. It is not durable storage, guaranteed tab-close delivery, or guaranteed Pi-restart recovery.
|