pi-studio 0.9.19 → 0.9.20
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 +7 -0
- package/README.md +2 -2
- package/client/studio-client.js +543 -59
- package/client/studio.css +34 -2
- package/index.ts +20 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.20] — 2026-05-27
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Added an opt-in HTML preview comment mode for editor HTML previews: use **Comment mode** to select text or click elements inside the sandboxed preview, or **Page** for a page-level comment; comments are stored in the existing local Comments rail and can be loaded into a prompt.
|
|
11
|
+
- Added **Refresh** controls to Studio PDF previews and the PDF focus viewer so file-backed PDFs can be reloaded from disk without rerendering the whole Studio pane.
|
|
12
|
+
- Editor-only Studio views now expose the right-pane **Files** and **REPL** views; REPL mode keeps Pi run/critique disabled but allows **Send to REPL** from the editor, including the `Cmd/Ctrl+Shift+Enter` shortcut.
|
|
13
|
+
|
|
7
14
|
## [0.9.19] — 2026-05-27
|
|
8
15
|
|
|
9
16
|
### Changed
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Extension for [pi](https://pi.dev) that opens a local two-pane browser workspace
|
|
|
19
19
|
## What it does
|
|
20
20
|
|
|
21
21
|
- Opens a two-pane browser workspace: **Editor** (left) + **Response/Working/Editor Preview** (right)
|
|
22
|
-
- Supports one canonical full Studio view per Pi session, plus additional editor-only companion views when you
|
|
22
|
+
- Supports one canonical full Studio view per Pi session, plus additional editor-only companion views when you want extra editing/preview surfaces; editor-only views can also browse files and use the Studio REPL send controls without taking over the full Studio session view
|
|
23
23
|
- Includes a global **Zen** mode for hiding secondary Studio chrome without changing the current left/right pane layout
|
|
24
24
|
- Runs editor text directly, asks for structured critique (auto/writing/code focus), offers 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
|
|
25
25
|
- Includes a live **Working** view for following current model/tool activity, with `All` / `Thinking` / `Tools` filters, image previews for image-producing tool outputs, plus **Load visible into editor** and **Copy visible** actions; when cycling response history, Working follows saved working details for the selected response when available
|
|
@@ -29,7 +29,7 @@ Extension for [pi](https://pi.dev) that opens a local two-pane browser workspace
|
|
|
29
29
|
- Includes a docked **Outline** rail for navigating document structure in the current editor text, with clickable entries that jump in the raw editor and reveal matching preview locations when available
|
|
30
30
|
- Restores the current browser-tab editor workspace after refresh and provides an explicit **Reset editor** action when you want to discard the restored draft and return the tab to a fresh blank draft without changing responses or saved files
|
|
31
31
|
- 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
|
|
32
|
-
- 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,
|
|
32
|
+
- 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
|
|
33
33
|
- Browses response history (`Prev/Next/Last`) and loads either:
|
|
34
34
|
- response text
|
|
35
35
|
- critique notes/full critique
|
package/client/studio-client.js
CHANGED
|
@@ -229,6 +229,35 @@
|
|
|
229
229
|
let stickyStudioKind = null;
|
|
230
230
|
const pendingCompanionWindows = new Map();
|
|
231
231
|
let initialDocumentApplied = false;
|
|
232
|
+
function normalizeRightViewValue(nextView) {
|
|
233
|
+
const raw = String(nextView || "").trim();
|
|
234
|
+
const normalized = raw === "preview"
|
|
235
|
+
? "preview"
|
|
236
|
+
: (raw === "editor-preview"
|
|
237
|
+
? "editor-preview"
|
|
238
|
+
: (raw === "repl"
|
|
239
|
+
? "repl"
|
|
240
|
+
: (raw === "files"
|
|
241
|
+
? "files"
|
|
242
|
+
: ((raw === "trace" || raw === "thinking") ? "trace" : "markdown"))));
|
|
243
|
+
if (isEditorOnlyMode && normalized !== "editor-preview" && normalized !== "files" && normalized !== "repl") {
|
|
244
|
+
return "editor-preview";
|
|
245
|
+
}
|
|
246
|
+
return normalized;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function syncRightViewModeOptions() {
|
|
250
|
+
if (!rightViewSelect || !rightViewSelect.options) return;
|
|
251
|
+
const editorOnlyAllowed = new Set(["editor-preview", "files", "repl"]);
|
|
252
|
+
Array.from(rightViewSelect.options).forEach((option) => {
|
|
253
|
+
if (!option) return;
|
|
254
|
+
option.disabled = isEditorOnlyMode && !editorOnlyAllowed.has(option.value);
|
|
255
|
+
});
|
|
256
|
+
rightViewSelect.title = isEditorOnlyMode
|
|
257
|
+
? "Editor-only views: editor preview, Files, or REPL. Shortcut: F7 when the right pane is active; F6 switches panes."
|
|
258
|
+
: "Right pane view mode. Shortcut: F7 when the right pane is active; F6 switches panes.";
|
|
259
|
+
}
|
|
260
|
+
|
|
232
261
|
function getInitialRightView(source) {
|
|
233
262
|
if (isEditorOnlyMode) return "editor-preview";
|
|
234
263
|
return String(source || "").trim() === "file" ? "editor-preview" : "preview";
|
|
@@ -2427,11 +2456,7 @@
|
|
|
2427
2456
|
rightTitleGroupEl.appendChild(rightFocusBtn);
|
|
2428
2457
|
rightTitleGroupEl.appendChild(makeStudioUiRefreshSeparator());
|
|
2429
2458
|
}
|
|
2430
|
-
|
|
2431
|
-
rightTitleGroupEl.appendChild(makeStudioUiRefreshElement("span", "studio-refresh-static-title", "Editor (Preview)"));
|
|
2432
|
-
} else {
|
|
2433
|
-
rightTitleGroupEl.appendChild(rightViewSelect);
|
|
2434
|
-
}
|
|
2459
|
+
rightTitleGroupEl.appendChild(rightViewSelect);
|
|
2435
2460
|
rightIdentityEl.appendChild(rightTitleGroupEl);
|
|
2436
2461
|
const rightToolsEl = makeStudioUiRefreshElement("div", "studio-refresh-pane-tools");
|
|
2437
2462
|
if (exportPreviewControlsEl) {
|
|
@@ -2455,8 +2480,8 @@
|
|
|
2455
2480
|
if (!isEditorOnlyMode && sendEditorBtn) actionLineTwoEl.appendChild(sendEditorBtn);
|
|
2456
2481
|
const replActionLineEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line repl-action-line");
|
|
2457
2482
|
replActionLineEl.hidden = true;
|
|
2458
|
-
if (
|
|
2459
|
-
if (
|
|
2483
|
+
if (sendReplBtn) replActionLineEl.appendChild(sendReplBtn);
|
|
2484
|
+
if (replSendModeSelect) replActionLineEl.appendChild(replSendModeSelect);
|
|
2460
2485
|
if (actionLineOneEl.childNodes.length > 0) actionsEl.appendChild(actionLineOneEl);
|
|
2461
2486
|
actionsEl.appendChild(actionLineTwoEl);
|
|
2462
2487
|
if (replActionLineEl.childNodes.length > 0) actionsEl.appendChild(replActionLineEl);
|
|
@@ -2612,7 +2637,7 @@
|
|
|
2612
2637
|
|
|
2613
2638
|
function getIdleStatus() {
|
|
2614
2639
|
if (isEditorOnlyMode) {
|
|
2615
|
-
return "Editor-only mode: edit,
|
|
2640
|
+
return "Editor-only mode: edit, browse files, annotate, preview, save, suggest, refresh file-backed text, or send to a REPL.";
|
|
2616
2641
|
}
|
|
2617
2642
|
return "Edit, load, or annotate text, then run, save, send to pi editor, or critique.";
|
|
2618
2643
|
}
|
|
@@ -3530,7 +3555,7 @@
|
|
|
3530
3555
|
|
|
3531
3556
|
function cycleActivePaneView(direction) {
|
|
3532
3557
|
if (activePane === "right") {
|
|
3533
|
-
if (
|
|
3558
|
+
if (!rightViewSelect || rightViewSelect.disabled) {
|
|
3534
3559
|
setStatus("The right-pane view selector is unavailable.", "warning");
|
|
3535
3560
|
return;
|
|
3536
3561
|
}
|
|
@@ -3886,7 +3911,6 @@
|
|
|
3886
3911
|
&& !event.altKey
|
|
3887
3912
|
&& event.shiftKey
|
|
3888
3913
|
&& activePane === "left"
|
|
3889
|
-
&& !isEditorOnlyMode
|
|
3890
3914
|
&& rightView === "repl"
|
|
3891
3915
|
) {
|
|
3892
3916
|
event.preventDefault();
|
|
@@ -4953,6 +4977,132 @@
|
|
|
4953
4977
|
+ " });\n"
|
|
4954
4978
|
+ " scheduleHeight();\n"
|
|
4955
4979
|
+ " }\n"
|
|
4980
|
+
+ " let htmlCommentMode = false;\n"
|
|
4981
|
+
+ " let htmlCommentHoverEl = null;\n"
|
|
4982
|
+
+ " let htmlCommentHighlightTimer = null;\n"
|
|
4983
|
+
+ " let htmlCommentLastPostAt = 0;\n"
|
|
4984
|
+
+ " function htmlCommentCssEscape(value) {\n"
|
|
4985
|
+
+ " const text = String(value || '');\n"
|
|
4986
|
+
+ " try { if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(text); } catch {}\n"
|
|
4987
|
+
+ " return text.replace(/[^A-Za-z0-9_-]/g, function(ch) { return '\\\\' + ch; });\n"
|
|
4988
|
+
+ " }\n"
|
|
4989
|
+
+ " function getHtmlCommentSelector(element) {\n"
|
|
4990
|
+
+ " if (!element || element.nodeType !== 1) return '';\n"
|
|
4991
|
+
+ " if (element.id) return '#' + htmlCommentCssEscape(element.id);\n"
|
|
4992
|
+
+ " const parts = [];\n"
|
|
4993
|
+
+ " let el = element;\n"
|
|
4994
|
+
+ " while (el && el.nodeType === 1 && el !== document.documentElement) {\n"
|
|
4995
|
+
+ " const tag = el.tagName ? el.tagName.toLowerCase() : '';\n"
|
|
4996
|
+
+ " if (!tag) break;\n"
|
|
4997
|
+
+ " if (el.id) { parts.unshift(tag + '#' + htmlCommentCssEscape(el.id)); break; }\n"
|
|
4998
|
+
+ " let index = 1;\n"
|
|
4999
|
+
+ " let sibling = el.previousElementSibling;\n"
|
|
5000
|
+
+ " while (sibling) { if ((sibling.tagName || '').toLowerCase() === tag) index += 1; sibling = sibling.previousElementSibling; }\n"
|
|
5001
|
+
+ " parts.unshift(tag + ':nth-of-type(' + index + ')');\n"
|
|
5002
|
+
+ " if (tag === 'body') break;\n"
|
|
5003
|
+
+ " el = el.parentElement;\n"
|
|
5004
|
+
+ " }\n"
|
|
5005
|
+
+ " return parts.join(' > ');\n"
|
|
5006
|
+
+ " }\n"
|
|
5007
|
+
+ " function normalizeHtmlCommentText(value, maxLength) {\n"
|
|
5008
|
+
+ " const text = String(value || '').replace(/\\s+/g, ' ').trim();\n"
|
|
5009
|
+
+ " const limit = Math.max(24, Number(maxLength) || 200);\n"
|
|
5010
|
+
+ " return text.length > limit ? text.slice(0, limit - 1).trimEnd() + '…' : text;\n"
|
|
5011
|
+
+ " }\n"
|
|
5012
|
+
+ " function getHtmlCommentElementLabel(element) {\n"
|
|
5013
|
+
+ " if (!element || element.nodeType !== 1) return '';\n"
|
|
5014
|
+
+ " const attrText = element.getAttribute('aria-label') || element.getAttribute('alt') || element.getAttribute('title') || '';\n"
|
|
5015
|
+
+ " if (attrText) return normalizeHtmlCommentText(attrText, 220);\n"
|
|
5016
|
+
+ " const tag = (element.tagName || '').toLowerCase();\n"
|
|
5017
|
+
+ " if (tag === 'img') {\n"
|
|
5018
|
+
+ " const src = String(element.getAttribute('src') || '').split(/[?#]/)[0].split('/').pop() || 'image';\n"
|
|
5019
|
+
+ " return normalizeHtmlCommentText(src, 220);\n"
|
|
5020
|
+
+ " }\n"
|
|
5021
|
+
+ " return normalizeHtmlCommentText(element.innerText || element.textContent || '', 220);\n"
|
|
5022
|
+
+ " }\n"
|
|
5023
|
+
+ " function getHtmlCommentTarget(target) {\n"
|
|
5024
|
+
+ " let node = target;\n"
|
|
5025
|
+
+ " if (node && node.nodeType === 3) node = node.parentElement;\n"
|
|
5026
|
+
+ " if (!node || node.nodeType !== 1) return document.body || document.documentElement;\n"
|
|
5027
|
+
+ " if (typeof node.closest === 'function') {\n"
|
|
5028
|
+
+ " return node.closest('img,figure,table,section,article,main,aside,nav,header,footer,pre,blockquote,ul,ol,li,canvas,svg,h1,h2,h3,h4,h5,h6,p,button,a,input,textarea,select,div') || node;\n"
|
|
5029
|
+
+ " }\n"
|
|
5030
|
+
+ " return node;\n"
|
|
5031
|
+
+ " }\n"
|
|
5032
|
+
+ " function getHtmlCommentSelectionText() {\n"
|
|
5033
|
+
+ " const selection = typeof window.getSelection === 'function' ? window.getSelection() : null;\n"
|
|
5034
|
+
+ " if (!selection || selection.rangeCount <= 0 || selection.isCollapsed) return '';\n"
|
|
5035
|
+
+ " return normalizeHtmlCommentText(selection.toString(), 1000);\n"
|
|
5036
|
+
+ " }\n"
|
|
5037
|
+
+ " function getHtmlCommentSelectionElement() {\n"
|
|
5038
|
+
+ " const selection = typeof window.getSelection === 'function' ? window.getSelection() : null;\n"
|
|
5039
|
+
+ " if (!selection || selection.rangeCount <= 0) return null;\n"
|
|
5040
|
+
+ " const range = selection.getRangeAt(0);\n"
|
|
5041
|
+
+ " let node = range.commonAncestorContainer;\n"
|
|
5042
|
+
+ " if (node && node.nodeType === 3) node = node.parentElement;\n"
|
|
5043
|
+
+ " return node && node.nodeType === 1 ? node : null;\n"
|
|
5044
|
+
+ " }\n"
|
|
5045
|
+
+ " function postHtmlCommentTarget(kind, element, event, selectedText) {\n"
|
|
5046
|
+
+ " const target = getHtmlCommentTarget(element || (event && event.target));\n"
|
|
5047
|
+
+ " if (!target) return false;\n"
|
|
5048
|
+
+ " htmlCommentLastPostAt = Date.now();\n"
|
|
5049
|
+
+ " try {\n"
|
|
5050
|
+
+ " parent.postMessage({\n"
|
|
5051
|
+
+ " type: 'pi-studio-html-artifact-comment-target',\n"
|
|
5052
|
+
+ " id: PREVIEW_ID,\n"
|
|
5053
|
+
+ " kind: kind === 'selection' ? 'selection' : 'element',\n"
|
|
5054
|
+
+ " selector: getHtmlCommentSelector(target),\n"
|
|
5055
|
+
+ " tag: (target.tagName || '').toLowerCase(),\n"
|
|
5056
|
+
+ " label: getHtmlCommentElementLabel(target),\n"
|
|
5057
|
+
+ " text: normalizeHtmlCommentText(selectedText || '', 1000),\n"
|
|
5058
|
+
+ " clientX: event && event.clientX || 0,\n"
|
|
5059
|
+
+ " clientY: event && event.clientY || 0\n"
|
|
5060
|
+
+ " }, '*');\n"
|
|
5061
|
+
+ " return true;\n"
|
|
5062
|
+
+ " } catch { return false; }\n"
|
|
5063
|
+
+ " }\n"
|
|
5064
|
+
+ " function clearHtmlCommentHover() {\n"
|
|
5065
|
+
+ " if (htmlCommentHoverEl && htmlCommentHoverEl.classList) htmlCommentHoverEl.classList.remove('pi-studio-html-comment-hover');\n"
|
|
5066
|
+
+ " htmlCommentHoverEl = null;\n"
|
|
5067
|
+
+ " }\n"
|
|
5068
|
+
+ " function setHtmlCommentMode(enabled) {\n"
|
|
5069
|
+
+ " htmlCommentMode = Boolean(enabled);\n"
|
|
5070
|
+
+ " if (document.documentElement && document.documentElement.classList) document.documentElement.classList.toggle('pi-studio-html-comment-mode', htmlCommentMode);\n"
|
|
5071
|
+
+ " if (!htmlCommentMode) clearHtmlCommentHover();\n"
|
|
5072
|
+
+ " }\n"
|
|
5073
|
+
+ " function handleHtmlCommentMouseMove(event) {\n"
|
|
5074
|
+
+ " if (!htmlCommentMode) return;\n"
|
|
5075
|
+
+ " const target = getHtmlCommentTarget(event && event.target);\n"
|
|
5076
|
+
+ " if (target === htmlCommentHoverEl) return;\n"
|
|
5077
|
+
+ " clearHtmlCommentHover();\n"
|
|
5078
|
+
+ " htmlCommentHoverEl = target;\n"
|
|
5079
|
+
+ " if (htmlCommentHoverEl && htmlCommentHoverEl.classList) htmlCommentHoverEl.classList.add('pi-studio-html-comment-hover');\n"
|
|
5080
|
+
+ " }\n"
|
|
5081
|
+
+ " function handleHtmlCommentMouseUp(event) {\n"
|
|
5082
|
+
+ " if (!htmlCommentMode) return;\n"
|
|
5083
|
+
+ " const selectedText = getHtmlCommentSelectionText();\n"
|
|
5084
|
+
+ " if (!selectedText) return;\n"
|
|
5085
|
+
+ " postHtmlCommentTarget('selection', getHtmlCommentSelectionElement() || (event && event.target), event, selectedText);\n"
|
|
5086
|
+
+ " if (event) { event.preventDefault(); event.stopPropagation(); }\n"
|
|
5087
|
+
+ " }\n"
|
|
5088
|
+
+ " function handleHtmlCommentClick(event) {\n"
|
|
5089
|
+
+ " if (!htmlCommentMode) return;\n"
|
|
5090
|
+
+ " if (Date.now() - htmlCommentLastPostAt < 450) { event.preventDefault(); event.stopPropagation(); return; }\n"
|
|
5091
|
+
+ " postHtmlCommentTarget('element', event && event.target, event, '');\n"
|
|
5092
|
+
+ " event.preventDefault();\n"
|
|
5093
|
+
+ " event.stopPropagation();\n"
|
|
5094
|
+
+ " }\n"
|
|
5095
|
+
+ " function highlightHtmlCommentTarget(selector, anchorKind) {\n"
|
|
5096
|
+
+ " if (htmlCommentHighlightTimer) { clearTimeout(htmlCommentHighlightTimer); htmlCommentHighlightTimer = null; }\n"
|
|
5097
|
+
+ " Array.prototype.slice.call(document.querySelectorAll('.pi-studio-html-comment-highlight')).forEach(function(el) { el.classList.remove('pi-studio-html-comment-highlight'); });\n"
|
|
5098
|
+
+ " if (anchorKind === 'html-page' || !selector) { try { window.scrollTo({ top: 0, behavior: 'smooth' }); } catch { window.scrollTo(0, 0); } return; }\n"
|
|
5099
|
+
+ " let target = null;\n"
|
|
5100
|
+
+ " try { target = document.querySelector(String(selector || '')); } catch {}\n"
|
|
5101
|
+
+ " if (!target) return;\n"
|
|
5102
|
+
+ " try { target.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'smooth' }); } catch { try { target.scrollIntoView(true); } catch {} }\n"
|
|
5103
|
+
+ " if (target.classList) target.classList.add('pi-studio-html-comment-highlight');\n"
|
|
5104
|
+
+ " htmlCommentHighlightTimer = setTimeout(function() { if (target && target.classList) target.classList.remove('pi-studio-html-comment-highlight'); }, 2400);\n"
|
|
5105
|
+
+ " }\n"
|
|
4956
5106
|
+ " window.addEventListener('message', (event) => {\n"
|
|
4957
5107
|
+ " const data = event && event.data;\n"
|
|
4958
5108
|
+ " if (!data || typeof data !== 'object' || data.id !== PREVIEW_ID) return;\n"
|
|
@@ -4966,11 +5116,23 @@
|
|
|
4966
5116
|
+ " }\n"
|
|
4967
5117
|
+ " if (data.type === 'pi-studio-html-artifact-resources-resolved') {\n"
|
|
4968
5118
|
+ " applyResolvedHtmlPreviewResources(data.results);\n"
|
|
5119
|
+
+ " return;\n"
|
|
5120
|
+
+ " }\n"
|
|
5121
|
+
+ " if (data.type === 'pi-studio-html-artifact-comment-mode') {\n"
|
|
5122
|
+
+ " setHtmlCommentMode(data.enabled);\n"
|
|
5123
|
+
+ " return;\n"
|
|
5124
|
+
+ " }\n"
|
|
5125
|
+
+ " if (data.type === 'pi-studio-html-artifact-highlight-comment') {\n"
|
|
5126
|
+
+ " highlightHtmlCommentTarget(data.selector, data.anchorKind);\n"
|
|
4969
5127
|
+ " }\n"
|
|
4970
5128
|
+ " });\n"
|
|
4971
5129
|
+ " document.addEventListener('click', handleFragmentAnchorClick);\n"
|
|
4972
5130
|
+ " document.addEventListener('click', handleHtmlPreviewLocalLinkClick);\n"
|
|
4973
5131
|
+ " document.addEventListener('contextmenu', handleHtmlPreviewLocalLinkContextMenu);\n"
|
|
5132
|
+
+ " document.addEventListener('mousemove', handleHtmlCommentMouseMove, true);\n"
|
|
5133
|
+
+ " document.addEventListener('mouseleave', clearHtmlCommentHover, true);\n"
|
|
5134
|
+
+ " document.addEventListener('mouseup', handleHtmlCommentMouseUp, true);\n"
|
|
5135
|
+
+ " document.addEventListener('click', handleHtmlCommentClick, true);\n"
|
|
4974
5136
|
+ " document.addEventListener('DOMContentLoaded', () => { scheduleHtmlMathRenderScan(); scheduleHtmlPreviewResourceScan(); });\n"
|
|
4975
5137
|
+ " window.addEventListener('hashchange', () => {\n"
|
|
4976
5138
|
+ " const hash = String(window.location && window.location.hash || '');\n"
|
|
@@ -5002,6 +5164,9 @@
|
|
|
5002
5164
|
+ ".pi-studio-html-math-display{display:block;margin:0.75em 0;overflow-x:auto;text-align:center;}\n"
|
|
5003
5165
|
+ ".pi-studio-html-math-display>math{display:block;margin:0 auto;}\n"
|
|
5004
5166
|
+ ".pi-studio-html-math-inline>math{vertical-align:-0.15em;}\n"
|
|
5167
|
+
+ "html.pi-studio-html-comment-mode,html.pi-studio-html-comment-mode body{cursor:crosshair!important;}\n"
|
|
5168
|
+
+ ".pi-studio-html-comment-hover{outline:2px solid #0f8b8d!important;outline-offset:3px!important;}\n"
|
|
5169
|
+
+ ".pi-studio-html-comment-highlight{outline:3px solid #d97706!important;outline-offset:4px!important;box-shadow:0 0 0 6px rgba(217,119,6,.18)!important;}\n"
|
|
5005
5170
|
+ "</style>\n";
|
|
5006
5171
|
}
|
|
5007
5172
|
|
|
@@ -5036,6 +5201,11 @@
|
|
|
5036
5201
|
});
|
|
5037
5202
|
}
|
|
5038
5203
|
|
|
5204
|
+
function setHtmlArtifactDetailText(record, text) {
|
|
5205
|
+
if (!record || !record.detail) return;
|
|
5206
|
+
record.detail.textContent = record.commentMode ? "HTML preview · comment mode" : (text || "HTML preview");
|
|
5207
|
+
}
|
|
5208
|
+
|
|
5039
5209
|
function handleHtmlArtifactFrameSizeMessage(event) {
|
|
5040
5210
|
const data = event && event.data;
|
|
5041
5211
|
if (!data || typeof data !== "object" || data.type !== "pi-studio-html-artifact-size") return;
|
|
@@ -5047,7 +5217,7 @@
|
|
|
5047
5217
|
}
|
|
5048
5218
|
if (event.source && record.iframe.contentWindow && event.source !== record.iframe.contentWindow) return;
|
|
5049
5219
|
if (record.shell && record.shell.classList && record.shell.classList.contains("is-focused")) {
|
|
5050
|
-
|
|
5220
|
+
setHtmlArtifactDetailText(record, "HTML preview");
|
|
5051
5221
|
return;
|
|
5052
5222
|
}
|
|
5053
5223
|
const rawHeight = Number(data.height);
|
|
@@ -5064,9 +5234,7 @@
|
|
|
5064
5234
|
record.shell.style.minHeight = "0";
|
|
5065
5235
|
record.shell.classList.toggle("is-height-capped", capped);
|
|
5066
5236
|
}
|
|
5067
|
-
|
|
5068
|
-
record.detail.textContent = "HTML preview";
|
|
5069
|
-
}
|
|
5237
|
+
setHtmlArtifactDetailText(record, "HTML preview");
|
|
5070
5238
|
}
|
|
5071
5239
|
|
|
5072
5240
|
function handleHtmlArtifactFrameFragmentMessage(event) {
|
|
@@ -5190,7 +5358,7 @@
|
|
|
5190
5358
|
error: error && error.message ? error.message : String(error || "HTML preview math render failed."),
|
|
5191
5359
|
})));
|
|
5192
5360
|
} finally {
|
|
5193
|
-
|
|
5361
|
+
setHtmlArtifactDetailText(record, "HTML preview");
|
|
5194
5362
|
}
|
|
5195
5363
|
}
|
|
5196
5364
|
|
|
@@ -5280,7 +5448,7 @@
|
|
|
5280
5448
|
if (record.detail) record.detail.textContent = "HTML preview · loading local images";
|
|
5281
5449
|
const results = await Promise.all(items.map((item) => fetchHtmlArtifactResource(record, item)));
|
|
5282
5450
|
postHtmlArtifactResourceResults(record, results);
|
|
5283
|
-
|
|
5451
|
+
setHtmlArtifactDetailText(record, "HTML preview");
|
|
5284
5452
|
}
|
|
5285
5453
|
|
|
5286
5454
|
function handleHtmlArtifactFrameResourceMessage(event) {
|
|
@@ -5363,11 +5531,36 @@
|
|
|
5363
5531
|
setStatus("Right-click this local HTML preview link for file actions.", "warning");
|
|
5364
5532
|
}
|
|
5365
5533
|
|
|
5534
|
+
function handleHtmlArtifactFrameCommentTargetMessage(event) {
|
|
5535
|
+
const data = event && event.data;
|
|
5536
|
+
if (!data || typeof data !== "object" || data.type !== "pi-studio-html-artifact-comment-target") return;
|
|
5537
|
+
const id = typeof data.id === "string" ? data.id : "";
|
|
5538
|
+
const record = id ? htmlArtifactFramesById.get(id) : null;
|
|
5539
|
+
if (!record || !record.iframe || !record.iframe.isConnected) {
|
|
5540
|
+
if (id) htmlArtifactFramesById.delete(id);
|
|
5541
|
+
return;
|
|
5542
|
+
}
|
|
5543
|
+
if (!record.commentable) return;
|
|
5544
|
+
if (event.source && record.iframe.contentWindow && event.source !== record.iframe.contentWindow) return;
|
|
5545
|
+
const note = addReviewNoteFromHtmlArtifactTarget(record, data);
|
|
5546
|
+
if (note && record.iframe && record.iframe.contentWindow) {
|
|
5547
|
+
try {
|
|
5548
|
+
record.iframe.contentWindow.postMessage({
|
|
5549
|
+
type: "pi-studio-html-artifact-highlight-comment",
|
|
5550
|
+
id: record.id || "",
|
|
5551
|
+
selector: note.htmlSelector || "",
|
|
5552
|
+
anchorKind: note.anchorKind || "html-element",
|
|
5553
|
+
}, "*");
|
|
5554
|
+
} catch {}
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
|
|
5366
5558
|
window.addEventListener("message", handleHtmlArtifactFrameSizeMessage);
|
|
5367
5559
|
window.addEventListener("message", handleHtmlArtifactFrameFragmentMessage);
|
|
5368
5560
|
window.addEventListener("message", handleHtmlArtifactFrameMathRenderMessage);
|
|
5369
5561
|
window.addEventListener("message", handleHtmlArtifactFrameResourceMessage);
|
|
5370
5562
|
window.addEventListener("message", handleHtmlArtifactFrameLocalLinkMessage);
|
|
5563
|
+
window.addEventListener("message", handleHtmlArtifactFrameCommentTargetMessage);
|
|
5371
5564
|
|
|
5372
5565
|
function isStudioHtmlFocusOpen() {
|
|
5373
5566
|
return Boolean(studioHtmlFocusOverlayEl && studioHtmlFocusOverlayEl.hidden === false && studioHtmlFocusShellEl);
|
|
@@ -5595,6 +5788,36 @@
|
|
|
5595
5788
|
|
|
5596
5789
|
const tools = document.createElement("span");
|
|
5597
5790
|
tools.className = "studio-html-artifact-tools";
|
|
5791
|
+
const commentable = Boolean(options && options.commentable);
|
|
5792
|
+
|
|
5793
|
+
let commentBtn = null;
|
|
5794
|
+
let pageCommentBtn = null;
|
|
5795
|
+
const makeCommentButton = (text, title, onClick) => {
|
|
5796
|
+
const button = document.createElement("button");
|
|
5797
|
+
button.type = "button";
|
|
5798
|
+
button.className = "studio-html-artifact-comment-btn";
|
|
5799
|
+
button.textContent = text;
|
|
5800
|
+
button.title = title;
|
|
5801
|
+
button.addEventListener("pointerdown", (event) => { event.stopPropagation(); });
|
|
5802
|
+
button.addEventListener("mousedown", (event) => { event.stopPropagation(); });
|
|
5803
|
+
button.addEventListener("click", (event) => {
|
|
5804
|
+
event.preventDefault();
|
|
5805
|
+
event.stopPropagation();
|
|
5806
|
+
onClick();
|
|
5807
|
+
});
|
|
5808
|
+
return button;
|
|
5809
|
+
};
|
|
5810
|
+
if (commentable) {
|
|
5811
|
+
commentBtn = makeCommentButton("Comment mode", "Turn on HTML preview comment mode. Select text or click an element in the preview to add a local comment.", () => {
|
|
5812
|
+
const record = htmlArtifactFramesById.get(previewId);
|
|
5813
|
+
setHtmlArtifactRecordCommentMode(record, !(record && record.commentMode));
|
|
5814
|
+
});
|
|
5815
|
+
commentBtn.setAttribute("aria-pressed", "false");
|
|
5816
|
+
pageCommentBtn = makeCommentButton("Page", "Add a page-level local comment for this HTML preview.", () => {
|
|
5817
|
+
const record = htmlArtifactFramesById.get(previewId);
|
|
5818
|
+
addReviewNoteFromHtmlArtifactPage(record || null);
|
|
5819
|
+
});
|
|
5820
|
+
}
|
|
5598
5821
|
|
|
5599
5822
|
const zoomControls = document.createElement("span");
|
|
5600
5823
|
zoomControls.className = "studio-html-artifact-zoom-controls";
|
|
@@ -5658,6 +5881,8 @@
|
|
|
5658
5881
|
fullscreenBtn.appendChild(makeStudioUiRefreshIcon("fullscreen"));
|
|
5659
5882
|
updateZoomUi();
|
|
5660
5883
|
tools.appendChild(detail);
|
|
5884
|
+
if (commentBtn) tools.appendChild(commentBtn);
|
|
5885
|
+
if (pageCommentBtn) tools.appendChild(pageCommentBtn);
|
|
5661
5886
|
tools.appendChild(zoomControls);
|
|
5662
5887
|
tools.appendChild(fullscreenBtn);
|
|
5663
5888
|
|
|
@@ -5672,7 +5897,7 @@
|
|
|
5672
5897
|
iframe.referrerPolicy = "no-referrer";
|
|
5673
5898
|
iframe.setAttribute("sandbox", "allow-scripts allow-modals");
|
|
5674
5899
|
iframe.setAttribute("allow", "clipboard-write");
|
|
5675
|
-
iframe.addEventListener("load", () => { postArtifactZoom(); });
|
|
5900
|
+
iframe.addEventListener("load", () => { postArtifactZoom(); postHtmlArtifactCommentMode(htmlArtifactFramesById.get(previewId)); });
|
|
5676
5901
|
iframe.srcdoc = buildHtmlArtifactSrcdoc(html, previewId);
|
|
5677
5902
|
shell.appendChild(iframe);
|
|
5678
5903
|
htmlArtifactFramesById.set(previewId, {
|
|
@@ -5681,6 +5906,11 @@
|
|
|
5681
5906
|
shell,
|
|
5682
5907
|
detail,
|
|
5683
5908
|
zoomControls,
|
|
5909
|
+
commentBtn,
|
|
5910
|
+
pageCommentBtn,
|
|
5911
|
+
commentMode: false,
|
|
5912
|
+
commentable,
|
|
5913
|
+
title,
|
|
5684
5914
|
sourcePath: options && options.sourcePath ? String(options.sourcePath) : "",
|
|
5685
5915
|
resourceDir: options && options.resourceDir ? String(options.resourceDir) : "",
|
|
5686
5916
|
mathRenderBatchCount: 0,
|
|
@@ -5697,6 +5927,33 @@
|
|
|
5697
5927
|
}
|
|
5698
5928
|
}
|
|
5699
5929
|
|
|
5930
|
+
function postHtmlArtifactCommentMode(record) {
|
|
5931
|
+
if (!record || !record.iframe || !record.iframe.contentWindow) return;
|
|
5932
|
+
try {
|
|
5933
|
+
record.iframe.contentWindow.postMessage({
|
|
5934
|
+
type: "pi-studio-html-artifact-comment-mode",
|
|
5935
|
+
id: record.id || "",
|
|
5936
|
+
enabled: Boolean(record.commentMode),
|
|
5937
|
+
}, "*");
|
|
5938
|
+
} catch {}
|
|
5939
|
+
}
|
|
5940
|
+
|
|
5941
|
+
function setHtmlArtifactRecordCommentMode(record, enabled) {
|
|
5942
|
+
if (!record) return;
|
|
5943
|
+
record.commentMode = Boolean(enabled);
|
|
5944
|
+
if (record.shell && record.shell.classList) record.shell.classList.toggle("is-comment-mode", record.commentMode);
|
|
5945
|
+
if (record.commentBtn) {
|
|
5946
|
+
record.commentBtn.classList.toggle("is-active", record.commentMode);
|
|
5947
|
+
record.commentBtn.setAttribute("aria-pressed", record.commentMode ? "true" : "false");
|
|
5948
|
+
record.commentBtn.textContent = "Comment mode";
|
|
5949
|
+
record.commentBtn.title = record.commentMode
|
|
5950
|
+
? "HTML comment mode is on. Select text or click an element in the preview to add a local comment."
|
|
5951
|
+
: "Turn on HTML preview comment mode. Select text or click an element in the preview to add a local comment.";
|
|
5952
|
+
}
|
|
5953
|
+
if (record.detail) record.detail.textContent = record.commentMode ? "HTML preview · comment mode" : "HTML preview";
|
|
5954
|
+
postHtmlArtifactCommentMode(record);
|
|
5955
|
+
}
|
|
5956
|
+
|
|
5700
5957
|
function getRightPaneHtmlArtifactSource() {
|
|
5701
5958
|
if (rightView === "editor-preview") {
|
|
5702
5959
|
const editorText = prepareEditorTextForPreview(sourceTextEl.value || "");
|
|
@@ -5814,6 +6071,15 @@
|
|
|
5814
6071
|
openLink.textContent = "Open PDF";
|
|
5815
6072
|
actions.appendChild(openLink);
|
|
5816
6073
|
|
|
6074
|
+
const refreshBtn = document.createElement("button");
|
|
6075
|
+
refreshBtn.type = "button";
|
|
6076
|
+
refreshBtn.className = "studio-pdf-focus-btn studio-pdf-focus-refresh";
|
|
6077
|
+
refreshBtn.textContent = "Refresh";
|
|
6078
|
+
refreshBtn.title = "Reload this PDF preview from disk.";
|
|
6079
|
+
refreshBtn.setAttribute("aria-label", "Refresh PDF preview from disk");
|
|
6080
|
+
refreshBtn.addEventListener("click", () => refreshStudioPdfFocusViewer());
|
|
6081
|
+
actions.appendChild(refreshBtn);
|
|
6082
|
+
|
|
5817
6083
|
const fullscreenBtn = document.createElement("button");
|
|
5818
6084
|
fullscreenBtn.type = "button";
|
|
5819
6085
|
fullscreenBtn.className = "studio-pdf-focus-btn studio-pdf-focus-fullscreen";
|
|
@@ -5935,6 +6201,70 @@
|
|
|
5935
6201
|
return "/pdf-resource?" + params.toString();
|
|
5936
6202
|
}
|
|
5937
6203
|
|
|
6204
|
+
function buildRefreshedStudioPdfViewerUrl(value) {
|
|
6205
|
+
const raw = String(value || "").trim();
|
|
6206
|
+
if (!raw) return "";
|
|
6207
|
+
const hashIndex = raw.indexOf("#");
|
|
6208
|
+
const base = hashIndex >= 0 ? raw.slice(0, hashIndex) : raw;
|
|
6209
|
+
const hash = hashIndex >= 0 ? raw.slice(hashIndex) : "";
|
|
6210
|
+
const nonce = Date.now().toString(36);
|
|
6211
|
+
try {
|
|
6212
|
+
const url = new URL(base || window.location.href, window.location.href);
|
|
6213
|
+
url.searchParams.set("_studioPdfRefresh", nonce);
|
|
6214
|
+
return url.href + hash;
|
|
6215
|
+
} catch {
|
|
6216
|
+
const separator = base.indexOf("?") >= 0 ? "&" : "?";
|
|
6217
|
+
return base + separator + "_studioPdfRefresh=" + encodeURIComponent(nonce) + hash;
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
|
|
6221
|
+
function syncStudioPdfCardViewerUrl(card, viewerUrl) {
|
|
6222
|
+
if (!card) return;
|
|
6223
|
+
const nextUrl = String(viewerUrl || "").trim();
|
|
6224
|
+
if (!nextUrl) return;
|
|
6225
|
+
if (card.dataset) card.dataset.studioPdfViewerUrl = nextUrl;
|
|
6226
|
+
const frame = typeof card.querySelector === "function" ? card.querySelector("iframe.studio-pdf-frame") : null;
|
|
6227
|
+
if (frame) frame.src = nextUrl;
|
|
6228
|
+
const openLink = typeof card.querySelector === "function" ? card.querySelector("a.studio-pdf-card-link") : null;
|
|
6229
|
+
if (openLink) openLink.href = nextUrl;
|
|
6230
|
+
const focusBtn = typeof card.querySelector === "function" ? card.querySelector("button.studio-pdf-card-focus") : null;
|
|
6231
|
+
if (focusBtn && focusBtn.dataset) focusBtn.dataset.studioPdfViewerUrl = nextUrl;
|
|
6232
|
+
}
|
|
6233
|
+
|
|
6234
|
+
function refreshStudioPdfCard(card) {
|
|
6235
|
+
if (!card) return false;
|
|
6236
|
+
const frame = typeof card.querySelector === "function" ? card.querySelector("iframe.studio-pdf-frame") : null;
|
|
6237
|
+
const currentUrl = String(card.dataset && card.dataset.studioPdfViewerUrl ? card.dataset.studioPdfViewerUrl : "").trim()
|
|
6238
|
+
|| String(frame && frame.src ? frame.src : "").trim();
|
|
6239
|
+
const nextUrl = buildRefreshedStudioPdfViewerUrl(currentUrl);
|
|
6240
|
+
if (!nextUrl) return false;
|
|
6241
|
+
syncStudioPdfCardViewerUrl(card, nextUrl);
|
|
6242
|
+
setStatus("Refreshed PDF preview from disk.", "success");
|
|
6243
|
+
return true;
|
|
6244
|
+
}
|
|
6245
|
+
|
|
6246
|
+
function getStudioPdfFocusActiveFrame() {
|
|
6247
|
+
if (studioPdfFocusMovedFrameState && studioPdfFocusMovedFrameState.frame && studioPdfFocusMovedFrameState.frame.isConnected) {
|
|
6248
|
+
return studioPdfFocusMovedFrameState.frame;
|
|
6249
|
+
}
|
|
6250
|
+
return studioPdfFocusFrameEl;
|
|
6251
|
+
}
|
|
6252
|
+
|
|
6253
|
+
function refreshStudioPdfFocusViewer() {
|
|
6254
|
+
const frame = getStudioPdfFocusActiveFrame();
|
|
6255
|
+
const currentUrl = String(frame && frame.src ? frame.src : "").trim()
|
|
6256
|
+
|| String(studioPdfFocusOpenLinkEl && studioPdfFocusOpenLinkEl.href ? studioPdfFocusOpenLinkEl.href : "").trim();
|
|
6257
|
+
const nextUrl = buildRefreshedStudioPdfViewerUrl(currentUrl);
|
|
6258
|
+
if (!nextUrl) {
|
|
6259
|
+
setStatus("Could not refresh this PDF preview.", "warning");
|
|
6260
|
+
return false;
|
|
6261
|
+
}
|
|
6262
|
+
if (frame) frame.src = nextUrl;
|
|
6263
|
+
if (studioPdfFocusOpenLinkEl) studioPdfFocusOpenLinkEl.href = nextUrl;
|
|
6264
|
+
setStatus("Refreshed PDF preview from disk.", "success");
|
|
6265
|
+
return true;
|
|
6266
|
+
}
|
|
6267
|
+
|
|
5938
6268
|
function syncStudioPdfFocusFullscreenButton() {
|
|
5939
6269
|
if (!studioPdfFocusFullscreenBtn) return;
|
|
5940
6270
|
const isFullscreen = Boolean(document.fullscreenElement && studioPdfFocusDialogEl && document.fullscreenElement === studioPdfFocusDialogEl);
|
|
@@ -6465,6 +6795,18 @@
|
|
|
6465
6795
|
openLink.textContent = "Open PDF";
|
|
6466
6796
|
actions.appendChild(openLink);
|
|
6467
6797
|
|
|
6798
|
+
const refreshBtn = document.createElement("button");
|
|
6799
|
+
refreshBtn.type = "button";
|
|
6800
|
+
refreshBtn.className = "studio-pdf-card-action studio-pdf-card-refresh";
|
|
6801
|
+
refreshBtn.textContent = "Refresh";
|
|
6802
|
+
refreshBtn.title = "Reload this PDF preview from disk.";
|
|
6803
|
+
refreshBtn.addEventListener("click", (event) => {
|
|
6804
|
+
event.preventDefault();
|
|
6805
|
+
event.stopPropagation();
|
|
6806
|
+
if (!refreshStudioPdfCard(card)) setStatus("Could not refresh this PDF preview.", "warning");
|
|
6807
|
+
});
|
|
6808
|
+
actions.appendChild(refreshBtn);
|
|
6809
|
+
|
|
6468
6810
|
header.appendChild(actions);
|
|
6469
6811
|
}
|
|
6470
6812
|
card.appendChild(header);
|
|
@@ -8245,7 +8587,7 @@
|
|
|
8245
8587
|
const text = prepareEditorTextForPreview(sourceTextEl.value || "");
|
|
8246
8588
|
const previewLanguage = getEditorLanguageForPreview();
|
|
8247
8589
|
if (isHtmlArtifactPreviewText(text, previewLanguage)) {
|
|
8248
|
-
renderHtmlArtifactPreview(sourcePreviewEl, text, "source", { title: "Editor HTML preview", ...getHtmlPreviewResourceContextOptions() });
|
|
8590
|
+
renderHtmlArtifactPreview(sourcePreviewEl, text, "source", { title: "Editor HTML preview", commentable: true, ...getHtmlPreviewResourceContextOptions() });
|
|
8249
8591
|
return;
|
|
8250
8592
|
}
|
|
8251
8593
|
if (renderDelimitedTextPreview(sourcePreviewEl, text, "source", previewLanguage)) {
|
|
@@ -9149,7 +9491,7 @@
|
|
|
9149
9491
|
}
|
|
9150
9492
|
const previewLanguage = getEditorLanguageForPreview();
|
|
9151
9493
|
if (isHtmlArtifactPreviewText(editorText, previewLanguage)) {
|
|
9152
|
-
renderHtmlArtifactPreview(critiqueViewEl, editorText, "response", { title: "Editor HTML preview", ...getHtmlPreviewResourceContextOptions() });
|
|
9494
|
+
renderHtmlArtifactPreview(critiqueViewEl, editorText, "response", { title: "Editor HTML preview", commentable: true, ...getHtmlPreviewResourceContextOptions() });
|
|
9153
9495
|
return;
|
|
9154
9496
|
}
|
|
9155
9497
|
if (renderDelimitedTextPreview(critiqueViewEl, editorText, "response", previewLanguage)) {
|
|
@@ -9439,7 +9781,8 @@
|
|
|
9439
9781
|
if (stripAnnotationsBtn) stripAnnotationsBtn.disabled = uiBusy || !hasAnnotationMarkers(sourceTextEl.value);
|
|
9440
9782
|
if (compactBtn) compactBtn.disabled = isEditorOnlyMode || uiBusy || compactInProgress || wsState === "Disconnected";
|
|
9441
9783
|
editorViewSelect.disabled = isEditorOnlyMode;
|
|
9442
|
-
|
|
9784
|
+
syncRightViewModeOptions();
|
|
9785
|
+
rightViewSelect.disabled = false;
|
|
9443
9786
|
followSelect.disabled = isEditorOnlyMode || uiBusy;
|
|
9444
9787
|
if (responseHighlightSelect) responseHighlightSelect.disabled = isEditorOnlyMode || rightView !== "markdown";
|
|
9445
9788
|
insertHeaderBtn.disabled = uiBusy;
|
|
@@ -9550,7 +9893,7 @@
|
|
|
9550
9893
|
sourceState: normalizeWorkspaceSourceState(sourceState),
|
|
9551
9894
|
resourceDir: getCurrentResourceDirValue(),
|
|
9552
9895
|
editorView,
|
|
9553
|
-
rightView:
|
|
9896
|
+
rightView: normalizeRightViewValue(rightView),
|
|
9554
9897
|
editorLanguage,
|
|
9555
9898
|
followLatest,
|
|
9556
9899
|
responseHistoryIndex,
|
|
@@ -9625,15 +9968,7 @@
|
|
|
9625
9968
|
setEditorLanguage(state.editorLanguage.trim());
|
|
9626
9969
|
}
|
|
9627
9970
|
editorView = state.editorView === "preview" ? "preview" : "markdown";
|
|
9628
|
-
rightView =
|
|
9629
|
-
? "editor-preview"
|
|
9630
|
-
: (state.rightView === "preview"
|
|
9631
|
-
? "preview"
|
|
9632
|
-
: (state.rightView === "editor-preview"
|
|
9633
|
-
? "editor-preview"
|
|
9634
|
-
: (state.rightView === "repl"
|
|
9635
|
-
? "repl"
|
|
9636
|
-
: (state.rightView === "files" ? "files" : ((state.rightView === "trace" || state.rightView === "thinking") ? "trace" : "markdown")))));
|
|
9971
|
+
rightView = normalizeRightViewValue(state.rightView);
|
|
9637
9972
|
if (typeof state.followLatest === "boolean") {
|
|
9638
9973
|
followLatest = state.followLatest;
|
|
9639
9974
|
}
|
|
@@ -10176,15 +10511,8 @@
|
|
|
10176
10511
|
|
|
10177
10512
|
function setRightView(nextView) {
|
|
10178
10513
|
const previousView = rightView;
|
|
10179
|
-
rightView = nextView
|
|
10180
|
-
|
|
10181
|
-
: (nextView === "editor-preview"
|
|
10182
|
-
? "editor-preview"
|
|
10183
|
-
: (nextView === "repl"
|
|
10184
|
-
? "repl"
|
|
10185
|
-
: (nextView === "files"
|
|
10186
|
-
? "files"
|
|
10187
|
-
: ((nextView === "trace" || nextView === "thinking") ? "trace" : "markdown"))));
|
|
10514
|
+
rightView = normalizeRightViewValue(nextView);
|
|
10515
|
+
syncRightViewModeOptions();
|
|
10188
10516
|
rightViewSelect.value = rightView;
|
|
10189
10517
|
if (rightView === "trace" && previousView !== "trace") {
|
|
10190
10518
|
traceAutoScroll = true;
|
|
@@ -12589,10 +12917,21 @@
|
|
|
12589
12917
|
scheduleScratchpadPersistence(value, descriptor.key);
|
|
12590
12918
|
}
|
|
12591
12919
|
|
|
12920
|
+
function normalizeReviewNoteAnchorKind(value) {
|
|
12921
|
+
const raw = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
12922
|
+
if (raw === "html-selection" || raw === "html-element" || raw === "html-page") return raw;
|
|
12923
|
+
return "source";
|
|
12924
|
+
}
|
|
12925
|
+
|
|
12926
|
+
function isReviewNoteDomAnchor(note) {
|
|
12927
|
+
return Boolean(note && normalizeReviewNoteAnchorKind(note.anchorKind) !== "source");
|
|
12928
|
+
}
|
|
12929
|
+
|
|
12592
12930
|
function normalizeReviewNote(note) {
|
|
12593
12931
|
if (!note || typeof note !== "object") return null;
|
|
12594
12932
|
const id = typeof note.id === "string" && note.id.trim() ? note.id : makeRequestId();
|
|
12595
12933
|
const text = typeof note.text === "string" ? note.text : "";
|
|
12934
|
+
const anchorKind = normalizeReviewNoteAnchorKind(note.anchorKind);
|
|
12596
12935
|
const createdAt = typeof note.createdAt === "number" && Number.isFinite(note.createdAt)
|
|
12597
12936
|
? note.createdAt
|
|
12598
12937
|
: Date.now();
|
|
@@ -12622,6 +12961,11 @@
|
|
|
12622
12961
|
lineEnd,
|
|
12623
12962
|
selectedText: typeof note.selectedText === "string" ? note.selectedText : "",
|
|
12624
12963
|
selectedDisplayText: typeof note.selectedDisplayText === "string" ? note.selectedDisplayText : "",
|
|
12964
|
+
anchorKind,
|
|
12965
|
+
htmlSelector: typeof note.htmlSelector === "string" ? note.htmlSelector : "",
|
|
12966
|
+
htmlTag: typeof note.htmlTag === "string" ? note.htmlTag : "",
|
|
12967
|
+
htmlLabel: typeof note.htmlLabel === "string" ? note.htmlLabel : "",
|
|
12968
|
+
htmlPreviewTitle: typeof note.htmlPreviewTitle === "string" ? note.htmlPreviewTitle : "",
|
|
12625
12969
|
};
|
|
12626
12970
|
}
|
|
12627
12971
|
|
|
@@ -13120,17 +13464,26 @@
|
|
|
13120
13464
|
}
|
|
13121
13465
|
}
|
|
13122
13466
|
|
|
13467
|
+
function formatHtmlReviewNoteAnchorLabel(note) {
|
|
13468
|
+
const kind = normalizeReviewNoteAnchorKind(note && note.anchorKind);
|
|
13469
|
+
const tag = String(note && note.htmlTag ? note.htmlTag : "").trim().toLowerCase();
|
|
13470
|
+
if (kind === "html-selection") return "HTML selection";
|
|
13471
|
+
if (kind === "html-page") return "HTML page";
|
|
13472
|
+
return tag ? ("HTML <" + tag + ">") : "HTML element";
|
|
13473
|
+
}
|
|
13474
|
+
|
|
13123
13475
|
function summarizeReviewNoteAnchor(note) {
|
|
13476
|
+
if (isReviewNoteDomAnchor(note)) return formatHtmlReviewNoteAnchorLabel(note);
|
|
13124
13477
|
const start = Math.max(1, Number(note && note.lineStart) || 1);
|
|
13125
13478
|
const end = Math.max(start, Number(note && note.lineEnd) || start);
|
|
13126
13479
|
return start === end ? "Line " + start : ("Lines " + start + "–" + end);
|
|
13127
13480
|
}
|
|
13128
13481
|
|
|
13129
13482
|
function summarizeReviewNoteQuote(note) {
|
|
13130
|
-
const normalized = String(note && (note.selectedDisplayText || note.selectedText) ? (note.selectedDisplayText || note.selectedText) : "")
|
|
13483
|
+
const normalized = String(note && (note.selectedDisplayText || note.selectedText || note.htmlLabel || note.htmlSelector) ? (note.selectedDisplayText || note.selectedText || note.htmlLabel || note.htmlSelector) : "")
|
|
13131
13484
|
.replace(/\s+/g, " ")
|
|
13132
13485
|
.trim();
|
|
13133
|
-
if (!normalized) return "Anchor: current line / empty selection";
|
|
13486
|
+
if (!normalized) return isReviewNoteDomAnchor(note) ? "Anchor: HTML preview" : "Anchor: current line / empty selection";
|
|
13134
13487
|
return normalized.length > 140 ? normalized.slice(0, 137) + "…" : normalized;
|
|
13135
13488
|
}
|
|
13136
13489
|
|
|
@@ -13218,6 +13571,7 @@
|
|
|
13218
13571
|
}
|
|
13219
13572
|
|
|
13220
13573
|
function resolveReviewNoteRange(note, text) {
|
|
13574
|
+
if (isReviewNoteDomAnchor(note)) return null;
|
|
13221
13575
|
const source = String(text || "");
|
|
13222
13576
|
const safeStart = Math.max(0, Math.min(Number(note && note.selectionStart) || 0, source.length));
|
|
13223
13577
|
const safeEnd = Math.max(safeStart, Math.min(Number(note && note.selectionEnd) || safeStart, source.length));
|
|
@@ -13281,6 +13635,7 @@
|
|
|
13281
13635
|
}
|
|
13282
13636
|
|
|
13283
13637
|
function formatReviewNotePromptLineRange(bounds, note) {
|
|
13638
|
+
if (isReviewNoteDomAnchor(note)) return summarizeReviewNoteAnchor(note);
|
|
13284
13639
|
const start = bounds ? bounds.lineStart : Math.max(1, Number(note && note.lineStart) || 1);
|
|
13285
13640
|
const end = bounds ? bounds.lineEnd : Math.max(start, Number(note && note.lineEnd) || start);
|
|
13286
13641
|
return start === end ? "L" + start : ("L" + start + "-L" + end);
|
|
@@ -13294,7 +13649,7 @@
|
|
|
13294
13649
|
const descriptor = getCurrentStudioDocumentDescriptor();
|
|
13295
13650
|
const documentLabel = descriptor && descriptor.label ? descriptor.label : (sourceState && sourceState.label ? sourceState.label : "Studio document");
|
|
13296
13651
|
const parts = [
|
|
13297
|
-
"Please address the following Studio comments. Use
|
|
13652
|
+
"Please address the following Studio comments. Use file names, line numbers, and preview anchors to locate each comment. The full document is not included here, only the comments and their anchors.",
|
|
13298
13653
|
"Document: " + documentLabel,
|
|
13299
13654
|
"",
|
|
13300
13655
|
"## Comments",
|
|
@@ -13316,6 +13671,9 @@
|
|
|
13316
13671
|
if (anchor) {
|
|
13317
13672
|
parts.push("", "> " + anchor.replace(/\n/g, "\n> "));
|
|
13318
13673
|
}
|
|
13674
|
+
if (isReviewNoteDomAnchor(note) && note.htmlSelector) {
|
|
13675
|
+
parts.push("", "Preview selector: `" + String(note.htmlSelector).replace(/`/g, "\\`") + "`");
|
|
13676
|
+
}
|
|
13319
13677
|
parts.push("");
|
|
13320
13678
|
});
|
|
13321
13679
|
|
|
@@ -13337,6 +13695,7 @@
|
|
|
13337
13695
|
const source = String(text || "");
|
|
13338
13696
|
const lineMap = new Map();
|
|
13339
13697
|
for (const note of reviewNotes) {
|
|
13698
|
+
if (isReviewNoteDomAnchor(note)) continue;
|
|
13340
13699
|
const bounds = getResolvedReviewNoteLineBounds(note, source);
|
|
13341
13700
|
if (!bounds) continue;
|
|
13342
13701
|
for (let line = bounds.lineStart; line <= bounds.lineEnd; line += 1) {
|
|
@@ -15875,8 +16234,8 @@
|
|
|
15875
16234
|
return reviewNotes.slice().sort((left, right) => {
|
|
15876
16235
|
const leftBounds = getResolvedReviewNoteLineBounds(left, source);
|
|
15877
16236
|
const rightBounds = getResolvedReviewNoteLineBounds(right, source);
|
|
15878
|
-
const leftLine = leftBounds ? leftBounds.lineStart : Math.max(1, Number(left && left.lineStart) || 1);
|
|
15879
|
-
const rightLine = rightBounds ? rightBounds.lineStart : Math.max(1, Number(right && right.lineStart) || 1);
|
|
16237
|
+
const leftLine = leftBounds ? leftBounds.lineStart : (isReviewNoteDomAnchor(left) ? Number.MAX_SAFE_INTEGER : Math.max(1, Number(left && left.lineStart) || 1));
|
|
16238
|
+
const rightLine = rightBounds ? rightBounds.lineStart : (isReviewNoteDomAnchor(right) ? Number.MAX_SAFE_INTEGER : Math.max(1, Number(right && right.lineStart) || 1));
|
|
15880
16239
|
if (leftLine !== rightLine) return leftLine - rightLine;
|
|
15881
16240
|
|
|
15882
16241
|
const leftStart = leftBounds ? leftBounds.start : Math.max(0, Number(left && left.selectionStart) || 0);
|
|
@@ -15908,6 +16267,15 @@
|
|
|
15908
16267
|
function getReviewNoteInlineState(note, text) {
|
|
15909
16268
|
const source = String(text || "");
|
|
15910
16269
|
const annotationBody = escapeReviewNoteAnnotationText(note && note.text);
|
|
16270
|
+
if (isReviewNoteDomAnchor(note)) {
|
|
16271
|
+
return {
|
|
16272
|
+
annotationBody,
|
|
16273
|
+
range: null,
|
|
16274
|
+
markerText: "",
|
|
16275
|
+
exists: false,
|
|
16276
|
+
canToggle: false,
|
|
16277
|
+
};
|
|
16278
|
+
}
|
|
15911
16279
|
if (!annotationBody) {
|
|
15912
16280
|
return {
|
|
15913
16281
|
annotationBody: "",
|
|
@@ -16233,7 +16601,9 @@
|
|
|
16233
16601
|
const jumpBtn = document.createElement("button");
|
|
16234
16602
|
jumpBtn.type = "button";
|
|
16235
16603
|
jumpBtn.textContent = "Jump";
|
|
16236
|
-
jumpBtn.title =
|
|
16604
|
+
jumpBtn.title = isReviewNoteDomAnchor(note)
|
|
16605
|
+
? "Jump to this comment's HTML preview anchor."
|
|
16606
|
+
: "Jump to this comment's anchored location in the editor.";
|
|
16237
16607
|
jumpBtn.addEventListener("click", () => {
|
|
16238
16608
|
jumpToReviewNote(note.id);
|
|
16239
16609
|
});
|
|
@@ -16246,9 +16616,11 @@
|
|
|
16246
16616
|
convertBtn.textContent = inlineState.exists ? "Inline: On" : "Inline: Off";
|
|
16247
16617
|
convertBtn.setAttribute("aria-pressed", inlineState.exists ? "true" : "false");
|
|
16248
16618
|
convertBtn.disabled = !inlineState.canToggle || uiBusy;
|
|
16249
|
-
convertBtn.title =
|
|
16250
|
-
? "
|
|
16251
|
-
:
|
|
16619
|
+
convertBtn.title = isReviewNoteDomAnchor(note)
|
|
16620
|
+
? "Inline annotations are only available for comments anchored to source text."
|
|
16621
|
+
: (inlineState.exists
|
|
16622
|
+
? "This comment currently has an inline [an: ...] annotation in the editor. Click to remove it."
|
|
16623
|
+
: "This comment is currently not inline in the editor. Click to add it as an inline [an: ...] annotation.");
|
|
16252
16624
|
convertBtn.addEventListener("click", () => {
|
|
16253
16625
|
convertReviewNoteToAnnotation(note.id);
|
|
16254
16626
|
});
|
|
@@ -16276,9 +16648,11 @@
|
|
|
16276
16648
|
convertBtn.disabled = !nextInlineState.canToggle || uiBusy;
|
|
16277
16649
|
convertBtn.textContent = nextInlineState.exists ? "Inline: On" : "Inline: Off";
|
|
16278
16650
|
convertBtn.setAttribute("aria-pressed", nextInlineState.exists ? "true" : "false");
|
|
16279
|
-
convertBtn.title =
|
|
16280
|
-
? "
|
|
16281
|
-
:
|
|
16651
|
+
convertBtn.title = isReviewNoteDomAnchor(note)
|
|
16652
|
+
? "Inline annotations are only available for comments anchored to source text."
|
|
16653
|
+
: (nextInlineState.exists
|
|
16654
|
+
? "This comment currently has an inline [an: ...] annotation in the editor. Click to remove it."
|
|
16655
|
+
: "This comment is currently not inline in the editor. Click to add it as an inline [an: ...] annotation.");
|
|
16282
16656
|
scheduleReviewNotesPersistence();
|
|
16283
16657
|
updateReviewNotesUi();
|
|
16284
16658
|
});
|
|
@@ -16350,6 +16724,55 @@
|
|
|
16350
16724
|
return note;
|
|
16351
16725
|
}
|
|
16352
16726
|
|
|
16727
|
+
function addReviewNoteFromHtmlArtifactTarget(record, data) {
|
|
16728
|
+
if (!record || !record.commentable) return null;
|
|
16729
|
+
const kind = data && data.kind === "selection" ? "html-selection" : "html-element";
|
|
16730
|
+
const selector = typeof data.selector === "string" ? data.selector : "";
|
|
16731
|
+
const tag = typeof data.tag === "string" ? data.tag : "";
|
|
16732
|
+
const text = typeof data.text === "string" ? data.text : "";
|
|
16733
|
+
const label = typeof data.label === "string" ? data.label : "";
|
|
16734
|
+
const display = text || label || selector || (tag ? ("<" + tag + ">") : "HTML element");
|
|
16735
|
+
return addReviewNoteFromAnchor({
|
|
16736
|
+
selectionStart: 0,
|
|
16737
|
+
selectionEnd: 0,
|
|
16738
|
+
lineStart: 1,
|
|
16739
|
+
lineEnd: 1,
|
|
16740
|
+
selectedText: "",
|
|
16741
|
+
selectedDisplayText: display,
|
|
16742
|
+
anchorKind: kind,
|
|
16743
|
+
htmlSelector: selector,
|
|
16744
|
+
htmlTag: tag,
|
|
16745
|
+
htmlLabel: label,
|
|
16746
|
+
htmlPreviewTitle: record.title || "HTML preview",
|
|
16747
|
+
}, {
|
|
16748
|
+
statusMessage: kind === "html-selection"
|
|
16749
|
+
? "Added local comment from HTML preview selection."
|
|
16750
|
+
: "Added local comment from HTML preview element.",
|
|
16751
|
+
});
|
|
16752
|
+
}
|
|
16753
|
+
|
|
16754
|
+
function addReviewNoteFromHtmlArtifactPage(record) {
|
|
16755
|
+
if (!record || !record.commentable) {
|
|
16756
|
+
setStatus("HTML preview comments are only available for editor previews.", "warning");
|
|
16757
|
+
return null;
|
|
16758
|
+
}
|
|
16759
|
+
return addReviewNoteFromAnchor({
|
|
16760
|
+
selectionStart: 0,
|
|
16761
|
+
selectionEnd: 0,
|
|
16762
|
+
lineStart: 1,
|
|
16763
|
+
lineEnd: 1,
|
|
16764
|
+
selectedText: "",
|
|
16765
|
+
selectedDisplayText: record.title || "HTML preview",
|
|
16766
|
+
anchorKind: "html-page",
|
|
16767
|
+
htmlSelector: "",
|
|
16768
|
+
htmlTag: "",
|
|
16769
|
+
htmlLabel: record.title || "HTML preview",
|
|
16770
|
+
htmlPreviewTitle: record.title || "HTML preview",
|
|
16771
|
+
}, {
|
|
16772
|
+
statusMessage: "Added page-level local comment for HTML preview.",
|
|
16773
|
+
});
|
|
16774
|
+
}
|
|
16775
|
+
|
|
16353
16776
|
function addReviewNoteFromAnchor(anchor, options) {
|
|
16354
16777
|
if (!anchor || typeof anchor !== "object") return null;
|
|
16355
16778
|
const note = normalizeReviewNote({
|
|
@@ -16363,6 +16786,11 @@
|
|
|
16363
16786
|
lineEnd: anchor.lineEnd,
|
|
16364
16787
|
selectedText: anchor.selectedText,
|
|
16365
16788
|
selectedDisplayText: typeof anchor.selectedDisplayText === "string" ? anchor.selectedDisplayText : (typeof anchor.selectedText === "string" ? anchor.selectedText : ""),
|
|
16789
|
+
anchorKind: anchor.anchorKind,
|
|
16790
|
+
htmlSelector: anchor.htmlSelector,
|
|
16791
|
+
htmlTag: anchor.htmlTag,
|
|
16792
|
+
htmlLabel: anchor.htmlLabel,
|
|
16793
|
+
htmlPreviewTitle: anchor.htmlPreviewTitle,
|
|
16366
16794
|
});
|
|
16367
16795
|
if (!note) return null;
|
|
16368
16796
|
if (editorSelectionCommentBtn) {
|
|
@@ -16510,9 +16938,55 @@
|
|
|
16510
16938
|
return jumped;
|
|
16511
16939
|
}
|
|
16512
16940
|
|
|
16941
|
+
function getConnectedHtmlArtifactRecords() {
|
|
16942
|
+
const records = [];
|
|
16943
|
+
htmlArtifactFramesById.forEach((record, id) => {
|
|
16944
|
+
if (!record || !record.iframe || !record.iframe.isConnected || !record.iframe.contentWindow) {
|
|
16945
|
+
if (id) htmlArtifactFramesById.delete(id);
|
|
16946
|
+
return;
|
|
16947
|
+
}
|
|
16948
|
+
records.push(record);
|
|
16949
|
+
});
|
|
16950
|
+
return records;
|
|
16951
|
+
}
|
|
16952
|
+
|
|
16953
|
+
function jumpToHtmlReviewNote(note) {
|
|
16954
|
+
if (!isReviewNoteDomAnchor(note)) return false;
|
|
16955
|
+
const records = getConnectedHtmlArtifactRecords().filter((record) => record && record.commentable);
|
|
16956
|
+
if (records.length === 0) {
|
|
16957
|
+
setStatus("Open the HTML preview before jumping to this comment.", "warning");
|
|
16958
|
+
return false;
|
|
16959
|
+
}
|
|
16960
|
+
const record = records[0];
|
|
16961
|
+
if (record.shell && typeof record.shell.scrollIntoView === "function") {
|
|
16962
|
+
try {
|
|
16963
|
+
record.shell.scrollIntoView({ block: "nearest", inline: "nearest", behavior: "smooth" });
|
|
16964
|
+
} catch {
|
|
16965
|
+
try { record.shell.scrollIntoView(false); } catch {}
|
|
16966
|
+
}
|
|
16967
|
+
}
|
|
16968
|
+
try {
|
|
16969
|
+
record.iframe.contentWindow.postMessage({
|
|
16970
|
+
type: "pi-studio-html-artifact-highlight-comment",
|
|
16971
|
+
id: record.id || "",
|
|
16972
|
+
selector: note.htmlSelector || "",
|
|
16973
|
+
anchorKind: normalizeReviewNoteAnchorKind(note.anchorKind),
|
|
16974
|
+
}, "*");
|
|
16975
|
+
setStatus("Jumped to HTML preview comment anchor.", "success");
|
|
16976
|
+
return true;
|
|
16977
|
+
} catch {
|
|
16978
|
+
setStatus("Could not jump to this HTML preview comment.", "warning");
|
|
16979
|
+
return false;
|
|
16980
|
+
}
|
|
16981
|
+
}
|
|
16982
|
+
|
|
16513
16983
|
function jumpToReviewNote(noteId) {
|
|
16514
16984
|
const note = reviewNotes.find((entry) => entry && entry.id === noteId);
|
|
16515
16985
|
if (!note) return;
|
|
16986
|
+
if (isReviewNoteDomAnchor(note)) {
|
|
16987
|
+
jumpToHtmlReviewNote(note);
|
|
16988
|
+
return;
|
|
16989
|
+
}
|
|
16516
16990
|
jumpToReviewAnchor(note, {
|
|
16517
16991
|
status: false,
|
|
16518
16992
|
notFoundStatusMessage: "Could not find the anchored location for this comment.",
|
|
@@ -16927,6 +17401,8 @@
|
|
|
16927
17401
|
const directIsStop = activeKind === "direct";
|
|
16928
17402
|
const critiqueIsStop = activeKind === "critique";
|
|
16929
17403
|
const canQueueSteering = studioRunChainActive && !critiqueIsStop;
|
|
17404
|
+
const hasReplSession = Boolean(getActiveReplSessionForCurrentRuntime());
|
|
17405
|
+
const showReplSend = rightView === "repl";
|
|
16930
17406
|
|
|
16931
17407
|
if (isEditorOnlyMode) {
|
|
16932
17408
|
if (sendRunBtn) {
|
|
@@ -16942,15 +17418,25 @@
|
|
|
16942
17418
|
queueSteerBtn.title = "Queue steering is unavailable in editor-only mode.";
|
|
16943
17419
|
}
|
|
16944
17420
|
if (sendReplBtn) {
|
|
16945
|
-
sendReplBtn.hidden =
|
|
16946
|
-
sendReplBtn.disabled =
|
|
16947
|
-
sendReplBtn.classList.
|
|
17421
|
+
sendReplBtn.hidden = !showReplSend;
|
|
17422
|
+
sendReplBtn.disabled = !showReplSend || wsState === "Disconnected" || uiBusy || replBusy || !hasReplSession;
|
|
17423
|
+
sendReplBtn.classList.toggle("repl-primary-action", showReplSend);
|
|
17424
|
+
sendReplBtn.textContent = showReplSend ? withStudioShortcutLabel(replSendMode === "literate" ? "Send selection/chunks" : "Send to REPL", "repl-send") : "Send to REPL";
|
|
17425
|
+
sendReplBtn.title = hasReplSession
|
|
17426
|
+
? (replSendMode === "literate"
|
|
17427
|
+
? "Literate send: selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk. Shortcut: Cmd/Ctrl+Shift+Enter."
|
|
17428
|
+
: "Raw send: selection, or full editor if no selection. Shortcut: Cmd/Ctrl+Shift+Enter.")
|
|
17429
|
+
: "Start or select a REPL session in the right pane first.";
|
|
16948
17430
|
const replActionLine = sendReplBtn.closest(".repl-action-line");
|
|
16949
|
-
if (replActionLine instanceof HTMLElement) replActionLine.hidden =
|
|
17431
|
+
if (replActionLine instanceof HTMLElement) replActionLine.hidden = !showReplSend;
|
|
16950
17432
|
}
|
|
16951
17433
|
if (replSendModeSelect) {
|
|
16952
|
-
replSendModeSelect.hidden =
|
|
16953
|
-
replSendModeSelect.disabled =
|
|
17434
|
+
replSendModeSelect.hidden = !showReplSend;
|
|
17435
|
+
replSendModeSelect.disabled = !showReplSend || wsState === "Disconnected" || uiBusy || replBusy;
|
|
17436
|
+
replSendModeSelect.value = replSendMode;
|
|
17437
|
+
replSendModeSelect.title = replSendMode === "literate"
|
|
17438
|
+
? "Literate send: Send to REPL uses the selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk."
|
|
17439
|
+
: "Raw send: Send to REPL uses the selection, or full editor if no selection.";
|
|
16954
17440
|
}
|
|
16955
17441
|
if (critiqueBtn) {
|
|
16956
17442
|
critiqueBtn.textContent = "Critique text";
|
|
@@ -16992,9 +17478,7 @@
|
|
|
16992
17478
|
: "Queue steering is available while Run editor text is active.";
|
|
16993
17479
|
}
|
|
16994
17480
|
|
|
16995
|
-
const hasReplSession = Boolean(getActiveReplSessionForCurrentRuntime());
|
|
16996
17481
|
if (sendReplBtn) {
|
|
16997
|
-
const showReplSend = rightView === "repl";
|
|
16998
17482
|
sendReplBtn.hidden = !showReplSend;
|
|
16999
17483
|
sendReplBtn.disabled = !showReplSend || wsState === "Disconnected" || uiBusy || replBusy || !hasReplSession;
|
|
17000
17484
|
sendReplBtn.classList.toggle("repl-primary-action", showReplSend);
|
package/client/studio.css
CHANGED
|
@@ -392,7 +392,6 @@
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
body[data-studio-mode="editor-only"] #editorViewSelect,
|
|
395
|
-
body[data-studio-mode="editor-only"] #rightViewSelect,
|
|
396
395
|
body[data-studio-mode="editor-only"] #sendRunBtn,
|
|
397
396
|
body[data-studio-mode="editor-only"] #queueSteerBtn,
|
|
398
397
|
body[data-studio-mode="editor-only"] #sendEditorBtn,
|
|
@@ -424,7 +423,7 @@
|
|
|
424
423
|
}
|
|
425
424
|
|
|
426
425
|
body[data-studio-mode="editor-only"] #rightSectionHeader .section-header-main::before {
|
|
427
|
-
content: "
|
|
426
|
+
content: "View";
|
|
428
427
|
font-weight: 600;
|
|
429
428
|
font-size: 14px;
|
|
430
429
|
}
|
|
@@ -2506,6 +2505,35 @@
|
|
|
2506
2505
|
white-space: nowrap;
|
|
2507
2506
|
}
|
|
2508
2507
|
|
|
2508
|
+
.rendered-markdown .studio-html-artifact-comment-btn {
|
|
2509
|
+
flex: 0 0 auto;
|
|
2510
|
+
min-height: 24px;
|
|
2511
|
+
padding: 0 9px;
|
|
2512
|
+
border: 1px solid var(--control-border);
|
|
2513
|
+
border-radius: 999px;
|
|
2514
|
+
background: var(--panel);
|
|
2515
|
+
color: var(--text);
|
|
2516
|
+
font: inherit;
|
|
2517
|
+
font-size: 11px;
|
|
2518
|
+
line-height: 1;
|
|
2519
|
+
cursor: pointer;
|
|
2520
|
+
white-space: nowrap;
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
.rendered-markdown .studio-html-artifact-comment-btn:not(:disabled):hover,
|
|
2524
|
+
.rendered-markdown .studio-html-artifact-comment-btn:focus-visible {
|
|
2525
|
+
background: var(--control-hover-bg, var(--inline-code-bg));
|
|
2526
|
+
border-color: var(--control-border-hover, var(--accent));
|
|
2527
|
+
outline: none;
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
.rendered-markdown .studio-html-artifact-comment-btn.is-active,
|
|
2531
|
+
.rendered-markdown .studio-html-artifact-shell.is-comment-mode .studio-html-artifact-comment-btn.is-active {
|
|
2532
|
+
background: var(--accent-soft);
|
|
2533
|
+
border-color: var(--accent);
|
|
2534
|
+
color: var(--accent);
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2509
2537
|
.rendered-markdown .studio-html-artifact-zoom-controls {
|
|
2510
2538
|
flex: 0 0 auto;
|
|
2511
2539
|
display: inline-flex;
|
|
@@ -4036,6 +4064,10 @@
|
|
|
4036
4064
|
line-height: 1.35;
|
|
4037
4065
|
}
|
|
4038
4066
|
|
|
4067
|
+
body[data-studio-mode="editor-only"] .shortcuts-full-only {
|
|
4068
|
+
display: none !important;
|
|
4069
|
+
}
|
|
4070
|
+
|
|
4039
4071
|
.scratchpad-textarea {
|
|
4040
4072
|
width: 100%;
|
|
4041
4073
|
min-height: 280px;
|
package/index.ts
CHANGED
|
@@ -209,6 +209,8 @@ interface InitialStudioDocument {
|
|
|
209
209
|
resourceDir?: string;
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
type PersistedStudioReviewNoteAnchorKind = "source" | "html-selection" | "html-element" | "html-page";
|
|
213
|
+
|
|
212
214
|
interface PersistedStudioReviewNote {
|
|
213
215
|
id: string;
|
|
214
216
|
text: string;
|
|
@@ -220,6 +222,11 @@ interface PersistedStudioReviewNote {
|
|
|
220
222
|
lineEnd: number;
|
|
221
223
|
selectedText: string;
|
|
222
224
|
selectedDisplayText?: string;
|
|
225
|
+
anchorKind?: PersistedStudioReviewNoteAnchorKind;
|
|
226
|
+
htmlSelector?: string;
|
|
227
|
+
htmlTag?: string;
|
|
228
|
+
htmlLabel?: string;
|
|
229
|
+
htmlPreviewTitle?: string;
|
|
223
230
|
}
|
|
224
231
|
|
|
225
232
|
interface StudioPersistentState {
|
|
@@ -725,6 +732,10 @@ function createEmptyStudioPersistentState(): StudioPersistentState {
|
|
|
725
732
|
};
|
|
726
733
|
}
|
|
727
734
|
|
|
735
|
+
function normalizePersistedStudioReviewNoteAnchorKind(value: unknown): PersistedStudioReviewNoteAnchorKind {
|
|
736
|
+
return value === "html-selection" || value === "html-element" || value === "html-page" ? value : "source";
|
|
737
|
+
}
|
|
738
|
+
|
|
728
739
|
function normalizePersistedStudioReviewNote(value: unknown): PersistedStudioReviewNote | null {
|
|
729
740
|
if (!value || typeof value !== "object") return null;
|
|
730
741
|
const candidate = value as Partial<PersistedStudioReviewNote>;
|
|
@@ -759,6 +770,11 @@ function normalizePersistedStudioReviewNote(value: unknown): PersistedStudioRevi
|
|
|
759
770
|
lineEnd,
|
|
760
771
|
selectedText: typeof candidate.selectedText === "string" ? candidate.selectedText : "",
|
|
761
772
|
selectedDisplayText: typeof candidate.selectedDisplayText === "string" ? candidate.selectedDisplayText : "",
|
|
773
|
+
anchorKind: normalizePersistedStudioReviewNoteAnchorKind(candidate.anchorKind),
|
|
774
|
+
htmlSelector: typeof candidate.htmlSelector === "string" ? candidate.htmlSelector : "",
|
|
775
|
+
htmlTag: typeof candidate.htmlTag === "string" ? candidate.htmlTag : "",
|
|
776
|
+
htmlLabel: typeof candidate.htmlLabel === "string" ? candidate.htmlLabel : "",
|
|
777
|
+
htmlPreviewTitle: typeof candidate.htmlPreviewTitle === "string" ? candidate.htmlPreviewTitle : "",
|
|
762
778
|
};
|
|
763
779
|
}
|
|
764
780
|
|
|
@@ -10042,14 +10058,14 @@ ${cssVarsBlock}
|
|
|
10042
10058
|
<div class="scratchpad-header">
|
|
10043
10059
|
<div>
|
|
10044
10060
|
<h2 id="reviewNotesTitle">Comments</h2>
|
|
10045
|
-
<p class="scratchpad-description">Local comments for editor text.
|
|
10061
|
+
<p class="scratchpad-description">Local comments for editor text and editor previews. They stay out of the text; source-anchored comments can be converted into inline <span class="review-notes-inline-token">[an: ...]</span> annotations.</p>
|
|
10046
10062
|
</div>
|
|
10047
10063
|
<button id="reviewNotesCloseBtn" type="button" class="scratchpad-close-btn" aria-label="Hide comments" title="Hide comments">✕</button>
|
|
10048
10064
|
</div>
|
|
10049
10065
|
<div class="review-notes-toolbar">
|
|
10050
10066
|
<span id="reviewNotesMeta" class="scratchpad-meta">No comments</span>
|
|
10051
10067
|
</div>
|
|
10052
|
-
<div id="reviewNotesEmptyState" class="review-notes-empty">No comments yet for this document. Select text in <strong>Editor (Raw)</strong> or <strong>Editor (Preview)</strong> and use <em>Comment</em>,
|
|
10068
|
+
<div id="reviewNotesEmptyState" class="review-notes-empty">No comments yet for this document. Select text in <strong>Editor (Raw)</strong> or <strong>Editor (Preview)</strong> and use <em>Comment</em>, use <em>Line comment</em> in <strong>Editor (Raw)</strong>, or use <em>Comment mode</em> in an editor HTML preview.</div>
|
|
10053
10069
|
<div id="reviewNotesList" class="review-notes-list" aria-live="polite"></div>
|
|
10054
10070
|
<div class="review-notes-dock-footer">
|
|
10055
10071
|
<div class="scratchpad-actions">
|
|
@@ -10181,14 +10197,14 @@ ${cssVarsBlock}
|
|
|
10181
10197
|
<h3>Editor</h3>
|
|
10182
10198
|
<dl>
|
|
10183
10199
|
<div><dt>Cmd/Ctrl+S</dt><dd>Save editor</dd></div>
|
|
10184
|
-
<div><dt>Cmd/Ctrl+Enter</dt><dd>Run editor text, or queue steering during an active run</dd></div>
|
|
10200
|
+
<div class="shortcuts-full-only"><dt>Cmd/Ctrl+Enter</dt><dd>Run editor text, or queue steering during an active run</dd></div>
|
|
10185
10201
|
<div><dt>Option/Alt+Tab or Cmd/Ctrl+Shift+Space</dt><dd>Suggest a completion at the editor cursor</dd></div>
|
|
10186
10202
|
<div><dt>Tab</dt><dd>Insert a visible completion suggestion; otherwise indent selected editor text</dd></div>
|
|
10187
10203
|
<div><dt>Esc</dt><dd>Dismiss a visible completion suggestion, close overlays, exit pane focus, or stop an active request</dd></div>
|
|
10188
10204
|
<div><dt>Shift+Tab</dt><dd>Unindent selected editor text</dd></div>
|
|
10189
10205
|
</dl>
|
|
10190
10206
|
</section>
|
|
10191
|
-
<section class="shortcuts-group">
|
|
10207
|
+
<section class="shortcuts-group shortcuts-full-only">
|
|
10192
10208
|
<h3>Response</h3>
|
|
10193
10209
|
<dl>
|
|
10194
10210
|
<div><dt>Alt/Option+←</dt><dd>Previous response when not editing text</dd></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.20",
|
|
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",
|