pi-studio 0.5.30 → 0.5.31
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 +6 -0
- package/client/studio-client.js +76 -8
- package/client/studio.css +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.31] — 2026-03-24
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- The right-pane response view now nudges the browser to repaint after response renders complete, reducing cases where freshly rendered response content stayed visually blank until the user scrolled or interacted with the pane.
|
|
11
|
+
- Newly selected or newly arrived responses now reset the right-pane scroll position to the top by default, while **Editor (Preview)** continues to preserve scroll position so in-place edit/preview workflows still feel natural.
|
|
12
|
+
|
|
7
13
|
## [0.5.30] — 2026-03-24
|
|
8
14
|
|
|
9
15
|
### Fixed
|
package/client/studio-client.js
CHANGED
|
@@ -234,6 +234,7 @@
|
|
|
234
234
|
let sourcePreviewRenderNonce = 0;
|
|
235
235
|
let responsePreviewRenderNonce = 0;
|
|
236
236
|
let responseEditorPreviewTimer = null;
|
|
237
|
+
let pendingResponseScrollReset = false;
|
|
237
238
|
let editorMetaUpdateRaf = null;
|
|
238
239
|
let editorHighlightEnabled = false;
|
|
239
240
|
let editorLanguage = "markdown";
|
|
@@ -971,6 +972,7 @@
|
|
|
971
972
|
}
|
|
972
973
|
|
|
973
974
|
function clearActiveResponseView() {
|
|
975
|
+
pendingResponseScrollReset = false;
|
|
974
976
|
latestResponseMarkdown = "";
|
|
975
977
|
latestResponseThinking = "";
|
|
976
978
|
latestResponseKind = "annotation";
|
|
@@ -1016,13 +1018,13 @@
|
|
|
1016
1018
|
}
|
|
1017
1019
|
}
|
|
1018
1020
|
|
|
1019
|
-
function applySelectedHistoryItem() {
|
|
1021
|
+
function applySelectedHistoryItem(options) {
|
|
1020
1022
|
const item = getSelectedHistoryItem();
|
|
1021
1023
|
if (!item) {
|
|
1022
1024
|
clearActiveResponseView();
|
|
1023
1025
|
return false;
|
|
1024
1026
|
}
|
|
1025
|
-
handleIncomingResponse(item.markdown, item.kind, item.timestamp, item.thinking);
|
|
1027
|
+
handleIncomingResponse(item.markdown, item.kind, item.timestamp, item.thinking, options);
|
|
1026
1028
|
return true;
|
|
1027
1029
|
}
|
|
1028
1030
|
|
|
@@ -1035,9 +1037,13 @@
|
|
|
1035
1037
|
return false;
|
|
1036
1038
|
}
|
|
1037
1039
|
|
|
1040
|
+
const previousItem = getSelectedHistoryItem();
|
|
1041
|
+
const previousId = previousItem && typeof previousItem.id === "string" ? previousItem.id : null;
|
|
1038
1042
|
const nextIndex = Math.max(0, Math.min(total - 1, Number(index) || 0));
|
|
1039
1043
|
responseHistoryIndex = nextIndex;
|
|
1040
|
-
const
|
|
1044
|
+
const nextItem = getSelectedHistoryItem();
|
|
1045
|
+
const nextId = nextItem && typeof nextItem.id === "string" ? nextItem.id : null;
|
|
1046
|
+
const applied = applySelectedHistoryItem({ resetScroll: previousId !== nextId });
|
|
1041
1047
|
updateHistoryControls();
|
|
1042
1048
|
|
|
1043
1049
|
if (applied && !(options && options.silent)) {
|
|
@@ -1539,6 +1545,33 @@
|
|
|
1539
1545
|
targetEl.classList.remove("preview-pending");
|
|
1540
1546
|
}
|
|
1541
1547
|
|
|
1548
|
+
function scheduleResponsePaneRepaintNudge() {
|
|
1549
|
+
if (!critiqueViewEl || typeof critiqueViewEl.getBoundingClientRect !== "function") return;
|
|
1550
|
+
const schedule = typeof window.requestAnimationFrame === "function"
|
|
1551
|
+
? window.requestAnimationFrame.bind(window)
|
|
1552
|
+
: (cb) => window.setTimeout(cb, 16);
|
|
1553
|
+
|
|
1554
|
+
schedule(() => {
|
|
1555
|
+
if (!critiqueViewEl || !critiqueViewEl.isConnected) return;
|
|
1556
|
+
void critiqueViewEl.getBoundingClientRect();
|
|
1557
|
+
if (!critiqueViewEl.classList) return;
|
|
1558
|
+
critiqueViewEl.classList.add("response-repaint-nudge");
|
|
1559
|
+
schedule(() => {
|
|
1560
|
+
if (!critiqueViewEl || !critiqueViewEl.classList) return;
|
|
1561
|
+
critiqueViewEl.classList.remove("response-repaint-nudge");
|
|
1562
|
+
});
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
function applyPendingResponseScrollReset() {
|
|
1567
|
+
if (!pendingResponseScrollReset || !critiqueViewEl) return false;
|
|
1568
|
+
if (rightView === "editor-preview") return false;
|
|
1569
|
+
critiqueViewEl.scrollTop = 0;
|
|
1570
|
+
critiqueViewEl.scrollLeft = 0;
|
|
1571
|
+
pendingResponseScrollReset = false;
|
|
1572
|
+
return true;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1542
1575
|
async function getMermaidApi() {
|
|
1543
1576
|
if (mermaidModulePromise) {
|
|
1544
1577
|
return mermaidModulePromise;
|
|
@@ -1883,6 +1916,11 @@
|
|
|
1883
1916
|
appendPreviewNotice(targetEl, "Images not displaying? Set working dir in the editor pane or open via /studio <path>.");
|
|
1884
1917
|
}
|
|
1885
1918
|
}
|
|
1919
|
+
|
|
1920
|
+
if (pane === "response") {
|
|
1921
|
+
applyPendingResponseScrollReset();
|
|
1922
|
+
scheduleResponsePaneRepaintNudge();
|
|
1923
|
+
}
|
|
1886
1924
|
} catch (error) {
|
|
1887
1925
|
if (pane === "source") {
|
|
1888
1926
|
if (nonce !== sourcePreviewRenderNonce || editorView !== "preview") return;
|
|
@@ -1893,6 +1931,10 @@
|
|
|
1893
1931
|
const detail = error && error.message ? error.message : String(error || "unknown error");
|
|
1894
1932
|
finishPreviewRender(targetEl);
|
|
1895
1933
|
targetEl.innerHTML = buildPreviewErrorHtml("Preview renderer unavailable (" + detail + "). Showing plain markdown.", markdown);
|
|
1934
|
+
if (pane === "response") {
|
|
1935
|
+
applyPendingResponseScrollReset();
|
|
1936
|
+
scheduleResponsePaneRepaintNudge();
|
|
1937
|
+
}
|
|
1896
1938
|
}
|
|
1897
1939
|
}
|
|
1898
1940
|
|
|
@@ -1962,11 +2004,13 @@
|
|
|
1962
2004
|
if (!editorText.trim()) {
|
|
1963
2005
|
finishPreviewRender(critiqueViewEl);
|
|
1964
2006
|
critiqueViewEl.innerHTML = "<pre class='plain-markdown'>Editor is empty.</pre>";
|
|
2007
|
+
scheduleResponsePaneRepaintNudge();
|
|
1965
2008
|
return;
|
|
1966
2009
|
}
|
|
1967
2010
|
if (editorLanguage && editorLanguage !== "markdown" && editorLanguage !== "latex") {
|
|
1968
2011
|
finishPreviewRender(critiqueViewEl);
|
|
1969
2012
|
critiqueViewEl.innerHTML = "<div class='response-markdown-highlight'>" + highlightCode(editorText, editorLanguage, "preview") + "</div>";
|
|
2013
|
+
scheduleResponsePaneRepaintNudge();
|
|
1970
2014
|
return;
|
|
1971
2015
|
}
|
|
1972
2016
|
const nonce = ++responsePreviewRenderNonce;
|
|
@@ -1981,6 +2025,8 @@
|
|
|
1981
2025
|
critiqueViewEl.innerHTML = thinking && thinking.trim()
|
|
1982
2026
|
? buildPlainMarkdownHtml(thinking)
|
|
1983
2027
|
: "<pre class='plain-markdown'>No thinking available for this response.</pre>";
|
|
2028
|
+
applyPendingResponseScrollReset();
|
|
2029
|
+
scheduleResponsePaneRepaintNudge();
|
|
1984
2030
|
return;
|
|
1985
2031
|
}
|
|
1986
2032
|
|
|
@@ -1988,6 +2034,8 @@
|
|
|
1988
2034
|
if (!markdown || !markdown.trim()) {
|
|
1989
2035
|
finishPreviewRender(critiqueViewEl);
|
|
1990
2036
|
critiqueViewEl.innerHTML = "<pre class='plain-markdown'>No response yet. Run editor text or critique editor text.</pre>";
|
|
2037
|
+
applyPendingResponseScrollReset();
|
|
2038
|
+
scheduleResponsePaneRepaintNudge();
|
|
1991
2039
|
return;
|
|
1992
2040
|
}
|
|
1993
2041
|
|
|
@@ -2005,16 +2053,22 @@
|
|
|
2005
2053
|
"Response is too large for markdown highlighting. Showing plain markdown.",
|
|
2006
2054
|
markdown,
|
|
2007
2055
|
);
|
|
2056
|
+
applyPendingResponseScrollReset();
|
|
2057
|
+
scheduleResponsePaneRepaintNudge();
|
|
2008
2058
|
return;
|
|
2009
2059
|
}
|
|
2010
2060
|
|
|
2011
2061
|
finishPreviewRender(critiqueViewEl);
|
|
2012
2062
|
critiqueViewEl.innerHTML = "<div class='response-markdown-highlight'>" + highlightMarkdown(markdown) + "</div>";
|
|
2063
|
+
applyPendingResponseScrollReset();
|
|
2064
|
+
scheduleResponsePaneRepaintNudge();
|
|
2013
2065
|
return;
|
|
2014
2066
|
}
|
|
2015
2067
|
|
|
2016
2068
|
finishPreviewRender(critiqueViewEl);
|
|
2017
2069
|
critiqueViewEl.innerHTML = buildPlainMarkdownHtml(markdown);
|
|
2070
|
+
applyPendingResponseScrollReset();
|
|
2071
|
+
scheduleResponsePaneRepaintNudge();
|
|
2018
2072
|
}
|
|
2019
2073
|
|
|
2020
2074
|
function updateResultActionButtons(normalizedEditorText) {
|
|
@@ -3058,15 +3112,29 @@
|
|
|
3058
3112
|
return lower.indexOf("## critiques") !== -1 && lower.indexOf("## document") !== -1;
|
|
3059
3113
|
}
|
|
3060
3114
|
|
|
3061
|
-
function handleIncomingResponse(markdown, kind, timestamp, thinking) {
|
|
3115
|
+
function handleIncomingResponse(markdown, kind, timestamp, thinking, options) {
|
|
3062
3116
|
const responseTimestamp =
|
|
3063
3117
|
typeof timestamp === "number" && Number.isFinite(timestamp) && timestamp > 0
|
|
3064
3118
|
? timestamp
|
|
3065
3119
|
: Date.now();
|
|
3120
|
+
const responseThinking = typeof thinking === "string" ? thinking : "";
|
|
3121
|
+
const responseKind = kind === "critique" ? "critique" : "annotation";
|
|
3122
|
+
const resetScroll = options && Object.prototype.hasOwnProperty.call(options, "resetScroll")
|
|
3123
|
+
? Boolean(options.resetScroll)
|
|
3124
|
+
: (
|
|
3125
|
+
latestResponseKind !== responseKind
|
|
3126
|
+
|| latestResponseTimestamp !== responseTimestamp
|
|
3127
|
+
|| latestResponseNormalized !== normalizeForCompare(markdown)
|
|
3128
|
+
|| latestResponseThinkingNormalized !== normalizeForCompare(responseThinking)
|
|
3129
|
+
);
|
|
3130
|
+
|
|
3131
|
+
if (resetScroll) {
|
|
3132
|
+
pendingResponseScrollReset = true;
|
|
3133
|
+
}
|
|
3066
3134
|
|
|
3067
3135
|
latestResponseMarkdown = markdown;
|
|
3068
|
-
latestResponseThinking =
|
|
3069
|
-
latestResponseKind =
|
|
3136
|
+
latestResponseThinking = responseThinking;
|
|
3137
|
+
latestResponseKind = responseKind;
|
|
3070
3138
|
latestResponseTimestamp = responseTimestamp;
|
|
3071
3139
|
latestResponseIsStructuredCritique = isStructuredCritique(markdown);
|
|
3072
3140
|
latestResponseHasContent = Boolean(markdown && markdown.trim());
|
|
@@ -3084,10 +3152,10 @@
|
|
|
3084
3152
|
refreshResponseUi();
|
|
3085
3153
|
}
|
|
3086
3154
|
|
|
3087
|
-
function applyLatestPayload(payload) {
|
|
3155
|
+
function applyLatestPayload(payload, options) {
|
|
3088
3156
|
if (!payload || typeof payload.markdown !== "string") return false;
|
|
3089
3157
|
const responseKind = payload.kind === "critique" ? "critique" : "annotation";
|
|
3090
|
-
handleIncomingResponse(payload.markdown, responseKind, payload.timestamp, payload.thinking);
|
|
3158
|
+
handleIncomingResponse(payload.markdown, responseKind, payload.timestamp, payload.thinking, options);
|
|
3091
3159
|
return true;
|
|
3092
3160
|
}
|
|
3093
3161
|
|
package/client/studio.css
CHANGED
|
@@ -968,6 +968,12 @@
|
|
|
968
968
|
opacity: 0.64;
|
|
969
969
|
}
|
|
970
970
|
|
|
971
|
+
.panel-scroll.response-repaint-nudge {
|
|
972
|
+
outline: 1px solid transparent;
|
|
973
|
+
-webkit-transform: translateZ(0);
|
|
974
|
+
transform: translateZ(0);
|
|
975
|
+
}
|
|
976
|
+
|
|
971
977
|
.preview-error {
|
|
972
978
|
color: var(--warn);
|
|
973
979
|
margin-bottom: 0.75em;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.31",
|
|
4
4
|
"description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, prompt/response history, and live Markdown/LaTeX/code preview",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|