pi-studio 0.5.58 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.6.0] — 2026-04-27
8
+
9
+ ### Added
10
+ - The comments rail now includes **Comments → prompt**, which turns non-empty local comments into an editor prompt with line anchors and file labels when available.
11
+
12
+ ### Changed
13
+ - The refreshed Studio layout is now the default, with the classic layout still available via the footer UI switch or `?uiRefresh=0`.
14
+ - Working-view tool output now replaces image/base64 payloads with compact placeholders instead of dumping raw image data.
15
+
16
+ ### Fixed
17
+ - Queued steering now updates Studio's active effective-prompt metadata so response history and prompt loading reflect the original run plus steering messages.
18
+ - Newly arrived responses now force the right response pane to reset to the top, while editor-preview/document views still preserve scroll.
19
+
20
+ ## [0.5.59] — 2026-04-27
21
+
22
+ ### Added
23
+ - Added an opt-in refreshed Studio layout, available via the footer UI switch or `?uiRefresh=1`, while keeping the classic layout as the default.
24
+ - Added refreshed layout support for editor-only Studio mode.
25
+
26
+ ### Changed
27
+ - Tuned refreshed pane headers, toolbar grouping, focus controls, dropdown labels, queue steering sizing, and preview export styling.
28
+
7
29
  ## [0.5.58] — 2026-04-24
8
30
 
9
31
  ### Added
@@ -132,6 +132,7 @@
132
132
  const reviewNotesListEl = document.getElementById("reviewNotesList");
133
133
  const reviewNotesEmptyStateEl = document.getElementById("reviewNotesEmptyState");
134
134
  const reviewNotesAddBtn = document.getElementById("reviewNotesAddBtn");
135
+ const reviewNotesPromptBtn = document.getElementById("reviewNotesPromptBtn");
135
136
  const reviewNotesInlineAllBtn = document.getElementById("reviewNotesInlineAllBtn");
136
137
  const reviewNotesDeleteAllBtn = document.getElementById("reviewNotesDeleteAllBtn");
137
138
  const reviewNotesCloseBtn = document.getElementById("reviewNotesCloseBtn");
@@ -644,6 +645,12 @@
644
645
  let lineNumbersEnabled = false;
645
646
  let lineNumbersRenderRaf = null;
646
647
  let annotationsEnabled = true;
648
+ const STUDIO_UI_REFRESH_STORAGE_KEY = "piStudio.uiRefresh";
649
+ const studioUiRefreshEnabled = readStudioUiRefreshEnabled();
650
+ let studioUiRefreshUi = null;
651
+ if (studioUiRefreshEnabled && document.body) {
652
+ document.body.classList.add("studio-ui-refresh");
653
+ }
647
654
  let scratchpadText = "";
648
655
  let scratchpadReturnFocusEl = null;
649
656
  let scratchpadPersistTimer = null;
@@ -662,6 +669,332 @@
662
669
  let suppressedEditorSelectionEnd = null;
663
670
  const previewJumpHighlightState = new WeakMap();
664
671
  const PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX = "PISTUDIOANNOT";
672
+
673
+ function readStudioUiRefreshEnabled() {
674
+ const normalize = (value) => String(value == null ? "" : value).trim().toLowerCase();
675
+ const queryValue = initialQueryParams.has("uiRefresh")
676
+ ? initialQueryParams.get("uiRefresh")
677
+ : (initialQueryParams.has("studioUiRefresh") ? initialQueryParams.get("studioUiRefresh") : null);
678
+ const isTruthy = (value) => ["1", "true", "yes", "on", "v2", "refresh", "fresh"].indexOf(normalize(value)) !== -1;
679
+ const isFalsey = (value) => ["0", "false", "no", "off", "classic"].indexOf(normalize(value)) !== -1;
680
+ if (queryValue !== null) {
681
+ const normalizedQuery = normalize(queryValue);
682
+ const enabled = isTruthy(queryValue) || (!isFalsey(queryValue) && normalizedQuery !== "");
683
+ try {
684
+ window.localStorage && window.localStorage.setItem(STUDIO_UI_REFRESH_STORAGE_KEY, enabled ? "1" : "0");
685
+ } catch {}
686
+ return enabled;
687
+ }
688
+ try {
689
+ const stored = window.localStorage ? window.localStorage.getItem(STUDIO_UI_REFRESH_STORAGE_KEY) : null;
690
+ if (stored !== null) return stored !== "0" && !isFalsey(stored);
691
+ } catch {}
692
+ return true;
693
+ }
694
+
695
+ function makeStudioUiRefreshElement(tagName, className, text) {
696
+ const element = document.createElement(tagName);
697
+ if (className) element.className = className;
698
+ if (typeof text === "string") element.textContent = text;
699
+ return element;
700
+ }
701
+
702
+ function makeStudioUiRefreshSeparator() {
703
+ return makeStudioUiRefreshElement("span", "studio-refresh-sep");
704
+ }
705
+
706
+ function makeStudioUiRefreshIcon(kind) {
707
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
708
+ svg.setAttribute("viewBox", "0 0 24 24");
709
+ svg.setAttribute("aria-hidden", "true");
710
+ svg.classList.add("studio-refresh-icon");
711
+ const paths = kind === "focus-exit"
712
+ ? ["M4 4l6 6", "M10 4v6H4", "M20 20l-6-6", "M14 20v-6h6"]
713
+ : ["M14 4h6v6", "M20 4l-6 6", "M10 20H4v-6", "M4 20l6-6"];
714
+ for (const d of paths) {
715
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
716
+ path.setAttribute("d", d);
717
+ svg.appendChild(path);
718
+ }
719
+ return svg;
720
+ }
721
+
722
+ function setStudioUiRefreshFocusButtonIcon(buttonEl, isFocusedPane) {
723
+ if (!buttonEl || !studioUiRefreshEnabled) return;
724
+ buttonEl.replaceChildren(makeStudioUiRefreshIcon(isFocusedPane ? "focus-exit" : "focus"));
725
+ buttonEl.setAttribute("aria-label", isFocusedPane ? "Exit focus" : "Focus pane");
726
+ }
727
+
728
+ function appendStudioUiRefreshMenuSection(menuEl, heading, controls) {
729
+ const sectionEl = makeStudioUiRefreshElement("div", "studio-refresh-menu-section");
730
+ if (heading) {
731
+ sectionEl.appendChild(makeStudioUiRefreshElement("div", "studio-refresh-menu-heading", heading));
732
+ }
733
+ for (const control of controls) {
734
+ if (!control) continue;
735
+ const itemEl = makeStudioUiRefreshElement("div", "studio-refresh-menu-item");
736
+ itemEl.appendChild(control);
737
+ sectionEl.appendChild(itemEl);
738
+ }
739
+ menuEl.appendChild(sectionEl);
740
+ }
741
+
742
+ function getStudioUiRefreshSelectSummary(selectEl, prefix) {
743
+ if (!selectEl) return "";
744
+ const option = selectEl.options && selectEl.selectedIndex >= 0 ? selectEl.options[selectEl.selectedIndex] : null;
745
+ let label = option ? String(option.textContent || option.label || option.value || "") : String(selectEl.value || "");
746
+ if (prefix) label = label.replace(new RegExp("^" + prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s*:?\\s*", "i"), "");
747
+ return label.trim();
748
+ }
749
+
750
+ function setStudioUiRefreshButtonText(buttonEl, text) {
751
+ if (!buttonEl) return;
752
+ buttonEl.textContent = text;
753
+ }
754
+
755
+ function getStudioUiRefreshAnnotationHeaderEnabled() {
756
+ try {
757
+ return Boolean(stripAnnotationHeader(sourceTextEl.value).hadHeader);
758
+ } catch {
759
+ return false;
760
+ }
761
+ }
762
+
763
+ function syncStudioUiRefreshSummaries() {
764
+ if (!studioUiRefreshUi) return;
765
+ if (studioUiRefreshUi.annotationsButton) {
766
+ const inlineLabel = annotationsEnabled ? "Inline on" : "Inline hidden";
767
+ if (isEditorOnlyMode) {
768
+ setStudioUiRefreshButtonText(studioUiRefreshUi.annotationsButton, "Annotations: " + inlineLabel);
769
+ } else {
770
+ const headerLabel = getStudioUiRefreshAnnotationHeaderEnabled() ? "Header on" : "Header off";
771
+ setStudioUiRefreshButtonText(studioUiRefreshUi.annotationsButton, "Annotations: " + inlineLabel + " · " + headerLabel);
772
+ }
773
+ }
774
+ if (studioUiRefreshUi.viewButton) {
775
+ const syntaxLabel = editorHighlightEnabled
776
+ ? (getStudioUiRefreshSelectSummary(highlightSelect, "Syntax highlight") || editorLanguage || "Markdown")
777
+ : "Off";
778
+ const lineLabel = lineNumbersEnabled ? "Lines on" : "Lines off";
779
+ setStudioUiRefreshButtonText(studioUiRefreshUi.viewButton, "View: " + syntaxLabel + " · " + lineLabel);
780
+ }
781
+ syncStudioUiRefreshReviewTrigger();
782
+ }
783
+
784
+ function closeStudioUiRefreshMenus() {
785
+ if (!studioUiRefreshUi || !studioUiRefreshUi.menus) return;
786
+ for (const item of studioUiRefreshUi.menus) {
787
+ item.menu.hidden = true;
788
+ item.button.classList.remove("is-open");
789
+ item.button.setAttribute("aria-expanded", "false");
790
+ }
791
+ }
792
+
793
+ function toggleStudioUiRefreshMenu(name) {
794
+ if (!studioUiRefreshUi || !studioUiRefreshUi.menus) return;
795
+ let willOpen = false;
796
+ for (const item of studioUiRefreshUi.menus) {
797
+ if (item.name === name) willOpen = item.menu.hidden;
798
+ }
799
+ for (const item of studioUiRefreshUi.menus) {
800
+ const isOpen = willOpen && item.name === name;
801
+ item.menu.hidden = !isOpen;
802
+ item.button.classList.toggle("is-open", isOpen);
803
+ item.button.setAttribute("aria-expanded", isOpen ? "true" : "false");
804
+ }
805
+ }
806
+
807
+ function syncStudioUiRefreshReviewTrigger() {
808
+ if (!studioUiRefreshUi || !studioUiRefreshUi.reviewButton) return;
809
+ const critiqueIsStop = getAbortablePendingKind() === "critique";
810
+ const reviewButton = studioUiRefreshUi.reviewButton;
811
+ reviewButton.textContent = critiqueIsStop ? "Stop critique" : "Review";
812
+ reviewButton.classList.toggle("request-stop-active", critiqueIsStop);
813
+ reviewButton.disabled = critiqueIsStop ? Boolean(critiqueBtn && critiqueBtn.disabled) : false;
814
+ reviewButton.title = critiqueIsStop
815
+ ? "Stop the running critique request. Shortcut: Esc."
816
+ : "Open review actions and settings.";
817
+ if (critiqueIsStop) {
818
+ closeStudioUiRefreshMenus();
819
+ }
820
+ }
821
+
822
+ function makeStudioUiRefreshMenu(buttonEl, name, menuClassName) {
823
+ const anchorEl = makeStudioUiRefreshElement("span", "studio-refresh-menu-anchor " + (menuClassName || ""));
824
+ const menuEl = makeStudioUiRefreshElement("div", "studio-refresh-menu");
825
+ menuEl.hidden = true;
826
+ buttonEl.type = "button";
827
+ buttonEl.classList.add("studio-refresh-chip");
828
+ buttonEl.setAttribute("aria-haspopup", "menu");
829
+ buttonEl.setAttribute("aria-expanded", "false");
830
+ buttonEl.addEventListener("click", (event) => {
831
+ event.stopPropagation();
832
+ if (name === "review" && getAbortablePendingKind() === "critique") {
833
+ requestCancelForPendingRequest("critique");
834
+ return;
835
+ }
836
+ toggleStudioUiRefreshMenu(name);
837
+ });
838
+ anchorEl.appendChild(buttonEl);
839
+ anchorEl.appendChild(menuEl);
840
+ return { name, anchor: anchorEl, button: buttonEl, menu: menuEl };
841
+ }
842
+
843
+ function setStudioUiRefreshPreference(enabled) {
844
+ try {
845
+ window.localStorage && window.localStorage.setItem(STUDIO_UI_REFRESH_STORAGE_KEY, enabled ? "1" : "0");
846
+ } catch {}
847
+ try {
848
+ const url = new URL(window.location.href);
849
+ url.searchParams.set("uiRefresh", enabled ? "1" : "0");
850
+ window.location.assign(url.toString());
851
+ } catch {
852
+ window.location.reload();
853
+ }
854
+ }
855
+
856
+ function setupStudioUiRefreshToggleButton() {
857
+ if (!footerMetaEl || document.getElementById("studioUiRefreshToggleBtn")) return;
858
+ const button = makeStudioUiRefreshElement("button", "footer-compact-btn studio-ui-refresh-toggle", studioUiRefreshEnabled ? "UI: Fresh" : "UI: Classic");
859
+ button.id = "studioUiRefreshToggleBtn";
860
+ button.type = "button";
861
+ button.title = studioUiRefreshEnabled
862
+ ? "Switch Studio to the classic layout."
863
+ : "Switch Studio to the refreshed layout.";
864
+ button.addEventListener("click", () => {
865
+ setStudioUiRefreshPreference(!studioUiRefreshEnabled);
866
+ });
867
+ if (compactBtn && compactBtn.parentNode === footerMetaEl) {
868
+ compactBtn.insertAdjacentElement("afterend", button);
869
+ } else {
870
+ footerMetaEl.appendChild(button);
871
+ }
872
+ }
873
+
874
+ function setupStudioUiRefreshPrototype() {
875
+ if (!studioUiRefreshEnabled || studioUiRefreshUi) return;
876
+ const leftHeaderEl = document.getElementById("leftSectionHeader");
877
+ const sourceMetaEl = leftPaneEl ? leftPaneEl.querySelector(".source-meta") : null;
878
+ if (!leftHeaderEl || !sourceMetaEl || !copyDraftBtn) return;
879
+
880
+ let reviewMenu = null;
881
+ if (!isEditorOnlyMode && critiqueBtn && lensSelect) {
882
+ const reviewButton = makeStudioUiRefreshElement("button", "studio-refresh-tool-tab studio-refresh-review-btn", "Review");
883
+ reviewMenu = makeStudioUiRefreshMenu(reviewButton, "review", "studio-refresh-review-anchor");
884
+ appendStudioUiRefreshMenuSection(reviewMenu.menu, "Action", [critiqueBtn]);
885
+ appendStudioUiRefreshMenuSection(reviewMenu.menu, "Setting", [lensSelect]);
886
+ }
887
+
888
+ const headerTopEl = makeStudioUiRefreshElement("div", "studio-refresh-header-top");
889
+ const titleGroupEl = makeStudioUiRefreshElement("div", "studio-refresh-title-group");
890
+ if (leftFocusBtn) {
891
+ setStudioUiRefreshFocusButtonIcon(leftFocusBtn, false);
892
+ titleGroupEl.appendChild(leftFocusBtn);
893
+ }
894
+ titleGroupEl.appendChild(makeStudioUiRefreshSeparator());
895
+ if (isEditorOnlyMode) {
896
+ titleGroupEl.appendChild(makeStudioUiRefreshElement("span", "studio-refresh-static-title", "Editor (Raw)"));
897
+ } else if (editorViewSelect) {
898
+ titleGroupEl.appendChild(editorViewSelect);
899
+ }
900
+ headerTopEl.appendChild(titleGroupEl);
901
+ const headerToolsEl = makeStudioUiRefreshElement("div", "studio-refresh-pane-tools");
902
+ if (reviewNotesBtn) headerToolsEl.appendChild(reviewNotesBtn);
903
+ if (outlineBtn) headerToolsEl.appendChild(outlineBtn);
904
+ if (scratchpadBtn) headerToolsEl.appendChild(scratchpadBtn);
905
+ if (reviewMenu) headerToolsEl.appendChild(reviewMenu.anchor);
906
+ headerTopEl.appendChild(headerToolsEl);
907
+
908
+ const headerUtilityEl = makeStudioUiRefreshElement("div", "studio-refresh-header-utility");
909
+ const utilityLeftEl = makeStudioUiRefreshElement("div", "studio-refresh-utility-left");
910
+ if (sourceBadgeEl) utilityLeftEl.appendChild(sourceBadgeEl);
911
+ if (sourceBadgeEl && (resourceDirBtn || resourceDirLabel || resourceDirInputWrap || syncBadgeEl)) {
912
+ utilityLeftEl.appendChild(makeStudioUiRefreshSeparator());
913
+ }
914
+ if (resourceDirBtn) utilityLeftEl.appendChild(resourceDirBtn);
915
+ if (resourceDirLabel) utilityLeftEl.appendChild(resourceDirLabel);
916
+ if (resourceDirInputWrap) utilityLeftEl.appendChild(resourceDirInputWrap);
917
+ if (syncBadgeEl) utilityLeftEl.appendChild(syncBadgeEl);
918
+ headerUtilityEl.appendChild(utilityLeftEl);
919
+ leftHeaderEl.replaceChildren(headerTopEl, headerUtilityEl);
920
+
921
+ const rightHeaderEl = document.getElementById("rightSectionHeader");
922
+ if (rightHeaderEl && rightViewSelect) {
923
+ const rightIdentityEl = makeStudioUiRefreshElement("div", "studio-refresh-pane-identity studio-refresh-pane-identity-right");
924
+ const rightTitleGroupEl = makeStudioUiRefreshElement("div", "studio-refresh-title-group");
925
+ if (rightFocusBtn) {
926
+ setStudioUiRefreshFocusButtonIcon(rightFocusBtn, false);
927
+ rightTitleGroupEl.appendChild(rightFocusBtn);
928
+ rightTitleGroupEl.appendChild(makeStudioUiRefreshSeparator());
929
+ }
930
+ if (isEditorOnlyMode) {
931
+ rightTitleGroupEl.appendChild(makeStudioUiRefreshElement("span", "studio-refresh-static-title", "Editor (Preview)"));
932
+ } else {
933
+ rightTitleGroupEl.appendChild(rightViewSelect);
934
+ }
935
+ rightIdentityEl.appendChild(rightTitleGroupEl);
936
+ const rightToolsEl = makeStudioUiRefreshElement("div", "studio-refresh-pane-tools");
937
+ if (exportPdfBtn) rightToolsEl.appendChild(exportPdfBtn);
938
+ rightHeaderEl.replaceChildren(rightIdentityEl, rightToolsEl);
939
+ }
940
+
941
+ const toolbarEl = makeStudioUiRefreshElement("div", "studio-refresh-toolbar");
942
+ const toolbarMainEl = makeStudioUiRefreshElement("div", "studio-refresh-toolbar-main");
943
+ const actionsEl = makeStudioUiRefreshElement("div", "studio-refresh-toolbar-actions");
944
+ const actionLineOneEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line");
945
+ if (!isEditorOnlyMode && sendRunBtn) actionLineOneEl.appendChild(sendRunBtn);
946
+ if (!isEditorOnlyMode && queueSteerBtn) actionLineOneEl.appendChild(queueSteerBtn);
947
+ const actionLineTwoEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line");
948
+ actionLineTwoEl.appendChild(copyDraftBtn);
949
+ if (!isEditorOnlyMode && sendEditorBtn) actionLineTwoEl.appendChild(sendEditorBtn);
950
+ if (actionLineOneEl.childNodes.length > 0) actionsEl.appendChild(actionLineOneEl);
951
+ actionsEl.appendChild(actionLineTwoEl);
952
+
953
+ const stateEl = makeStudioUiRefreshElement("div", "studio-refresh-toolbar-state");
954
+ const annotationsButton = makeStudioUiRefreshElement("button", "", "Annotations");
955
+ const annotationsMenu = makeStudioUiRefreshMenu(annotationsButton, "annotations", "studio-refresh-annotations-anchor");
956
+ appendStudioUiRefreshMenuSection(annotationsMenu.menu, "Display", isEditorOnlyMode ? [annotationModeSelect] : [annotationModeSelect, insertHeaderBtn]);
957
+ appendStudioUiRefreshMenuSection(annotationsMenu.menu, "Actions", [stripAnnotationsBtn, saveAnnotatedBtn]);
958
+ const viewButton = makeStudioUiRefreshElement("button", "", "View");
959
+ const viewMenu = makeStudioUiRefreshMenu(viewButton, "view", "studio-refresh-view-anchor");
960
+ appendStudioUiRefreshMenuSection(viewMenu.menu, "Display", [highlightSelect, lineNumbersSelect]);
961
+ stateEl.appendChild(annotationsMenu.anchor);
962
+ stateEl.appendChild(viewMenu.anchor);
963
+
964
+ toolbarMainEl.appendChild(actionsEl);
965
+ toolbarMainEl.appendChild(stateEl);
966
+ toolbarEl.appendChild(toolbarMainEl);
967
+ sourceMetaEl.replaceChildren(toolbarEl);
968
+
969
+ studioUiRefreshUi = {
970
+ annotationsButton,
971
+ viewButton,
972
+ reviewButton: reviewMenu ? reviewMenu.button : null,
973
+ menus: [annotationsMenu, viewMenu].concat(reviewMenu ? [reviewMenu] : []),
974
+ };
975
+
976
+ document.addEventListener("click", (event) => {
977
+ const target = event.target;
978
+ if (target instanceof Element && target.closest(".studio-refresh-menu-anchor")) return;
979
+ closeStudioUiRefreshMenus();
980
+ });
981
+ document.addEventListener("keydown", (event) => {
982
+ if (event.key === "Escape") closeStudioUiRefreshMenus();
983
+ });
984
+ toolbarEl.addEventListener("change", () => {
985
+ window.setTimeout(syncStudioUiRefreshSummaries, 0);
986
+ });
987
+ toolbarEl.addEventListener("click", (event) => {
988
+ const target = event.target;
989
+ if (target instanceof Element && target.closest(".studio-refresh-menu")) {
990
+ window.setTimeout(syncStudioUiRefreshSummaries, 0);
991
+ }
992
+ });
993
+ syncStudioUiRefreshSummaries();
994
+ }
995
+
996
+ setupStudioUiRefreshToggleButton();
997
+ setupStudioUiRefreshPrototype();
665
998
  const annotationHelpers = globalThis.PiStudioAnnotationHelpers;
666
999
  if (!annotationHelpers || typeof annotationHelpers.collectInlineAnnotationMarkers !== "function") {
667
1000
  throw new Error("Studio annotation helpers failed to load.");
@@ -932,7 +1265,24 @@
932
1265
  }
933
1266
  }
934
1267
 
935
- function formatContextUsageText() {
1268
+ function formatCompactNumber(value) {
1269
+ if (typeof value !== "number" || !Number.isFinite(value)) return "?";
1270
+ const sign = value < 0 ? "-" : "";
1271
+ const abs = Math.abs(value);
1272
+ if (abs < 1000) return sign + formatNumber(abs);
1273
+ const units = [
1274
+ { divisor: 1_000_000_000, suffix: "B" },
1275
+ { divisor: 1_000_000, suffix: "M" },
1276
+ { divisor: 1_000, suffix: "k" },
1277
+ ];
1278
+ const unit = units.find((entry) => abs >= entry.divisor) || units[units.length - 1];
1279
+ const scaled = abs / unit.divisor;
1280
+ const decimals = scaled >= 100 ? 0 : 1;
1281
+ return sign + scaled.toFixed(decimals).replace(/\.0$/, "") + unit.suffix;
1282
+ }
1283
+
1284
+ function formatContextUsageText(compact) {
1285
+ const formatContextNumber = compact ? formatCompactNumber : formatNumber;
936
1286
  const hasWindow = typeof contextWindow === "number" && Number.isFinite(contextWindow) && contextWindow > 0;
937
1287
  const hasTokens = typeof contextTokens === "number" && Number.isFinite(contextTokens) && contextTokens >= 0;
938
1288
  let percentValue = typeof contextPercent === "number" && Number.isFinite(contextPercent)
@@ -947,12 +1297,12 @@
947
1297
  return "Context: unknown";
948
1298
  }
949
1299
  if (!hasTokens && hasWindow) {
950
- return "Context: ? / " + formatNumber(contextWindow);
1300
+ return "Context: ? / " + formatContextNumber(contextWindow);
951
1301
  }
952
1302
 
953
- let text = "Context: " + formatNumber(contextTokens);
1303
+ let text = "Context: " + formatContextNumber(contextTokens);
954
1304
  if (hasWindow) {
955
- text += " / " + formatNumber(contextWindow);
1305
+ text += " / " + formatContextNumber(contextWindow);
956
1306
  }
957
1307
  if (percentValue != null && Number.isFinite(percentValue)) {
958
1308
  const bounded = Math.max(0, Math.min(100, percentValue));
@@ -1182,14 +1532,16 @@
1182
1532
  function updateFooterMeta() {
1183
1533
  const modelText = modelLabel && modelLabel.trim() ? modelLabel.trim() : "none";
1184
1534
  const terminalText = terminalSessionLabel && terminalSessionLabel.trim() ? terminalSessionLabel.trim() : "unknown";
1185
- const contextText = formatContextUsageText();
1535
+ const contextText = formatContextUsageText(true);
1536
+ const contextTitleText = formatContextUsageText(false);
1186
1537
  const text = "Model: " + modelText + " · Terminal: " + terminalText + " · " + contextText;
1538
+ const titleText = "Model: " + modelText + " · Terminal: " + terminalText + " · " + contextTitleText;
1187
1539
  if (footerMetaTextEl) {
1188
1540
  footerMetaTextEl.textContent = text;
1189
- footerMetaTextEl.title = text;
1541
+ footerMetaTextEl.title = titleText;
1190
1542
  } else if (footerMetaEl) {
1191
1543
  footerMetaEl.textContent = text;
1192
- footerMetaEl.title = text;
1544
+ footerMetaEl.title = titleText;
1193
1545
  }
1194
1546
  updateDocumentTitle();
1195
1547
  }
@@ -1301,7 +1653,7 @@
1301
1653
 
1302
1654
  function updateSourceBadge() {
1303
1655
  const label = sourceState && sourceState.label ? sourceState.label : "blank";
1304
- sourceBadgeEl.textContent = "Editor origin: " + label;
1656
+ sourceBadgeEl.textContent = (studioUiRefreshEnabled ? "Origin: " : "Editor origin: ") + label;
1305
1657
  const descriptor = getCurrentStudioDocumentDescriptor();
1306
1658
  if (sourceBadgeEl) {
1307
1659
  sourceBadgeEl.title = descriptor.fileBacked
@@ -1362,6 +1714,9 @@
1362
1714
  btn.classList.toggle("is-active", isFocusedPane);
1363
1715
  btn.setAttribute("aria-pressed", isFocusedPane ? "true" : "false");
1364
1716
  btn.textContent = isFocusedPane ? "Exit focus" : "Focus pane";
1717
+ if (studioUiRefreshEnabled) {
1718
+ setStudioUiRefreshFocusButtonIcon(btn, isFocusedPane);
1719
+ }
1365
1720
  btn.title = isFocusedPane
1366
1721
  ? "Return to the two-pane layout. Shortcut: F10 or Cmd/Ctrl+Esc."
1367
1722
  : "Show only the " + paneName + " pane. Shortcut: F10 or Cmd/Ctrl+Esc.";
@@ -3849,6 +4204,7 @@
3849
4204
  if (lineNumbersSelect) {
3850
4205
  lineNumbersSelect.value = lineNumbersEnabled ? "on" : "off";
3851
4206
  }
4207
+ syncStudioUiRefreshSummaries();
3852
4208
  updateLineNumberGutterVisibility();
3853
4209
  scheduleEditorLineNumberRender();
3854
4210
  if (editorHighlightEnabled && editorView === "markdown") {
@@ -4537,6 +4893,7 @@
4537
4893
  if (stripAnnotationsBtn) {
4538
4894
  stripAnnotationsBtn.disabled = uiBusy || !hasAnnotationMarkers(sourceTextEl.value);
4539
4895
  }
4896
+ syncStudioUiRefreshSummaries();
4540
4897
  }
4541
4898
 
4542
4899
  function scheduleEditorMetaUpdate() {
@@ -5412,6 +5769,87 @@
5412
5769
  };
5413
5770
  }
5414
5771
 
5772
+ function getDiffFileLabelForLine(source, lineNumber) {
5773
+ const lines = String(source || "").replace(/\r\n/g, "\n").split("\n");
5774
+ const safeLine = Math.max(1, Math.min(Math.floor(Number(lineNumber) || 1), Math.max(1, lines.length)));
5775
+ let currentFile = "";
5776
+ for (let i = 0; i < safeLine; i += 1) {
5777
+ const line = String(lines[i] || "");
5778
+ const diffMatch = line.match(/^diff --git\s+a\/(.+?)\s+b\/(.+?)\s*$/);
5779
+ if (diffMatch) {
5780
+ currentFile = diffMatch[2] || diffMatch[1] || currentFile;
5781
+ continue;
5782
+ }
5783
+ const plusMatch = line.match(/^\+\+\+\s+(?:b\/)?(.+)\s*$/);
5784
+ if (plusMatch && plusMatch[1] && plusMatch[1] !== "/dev/null") {
5785
+ currentFile = plusMatch[1];
5786
+ }
5787
+ }
5788
+ return currentFile.trim();
5789
+ }
5790
+
5791
+ function getReviewNotePromptFileLabel(note, source) {
5792
+ if (sourceState && sourceState.path) return String(sourceState.path);
5793
+ const bounds = getResolvedReviewNoteLineBounds(note, source);
5794
+ const diffFile = bounds ? getDiffFileLabelForLine(source, bounds.lineStart) : "";
5795
+ if (diffFile) return diffFile;
5796
+ const descriptor = getCurrentStudioDocumentDescriptor();
5797
+ return descriptor && descriptor.fileBacked ? descriptor.label : "";
5798
+ }
5799
+
5800
+ function formatReviewNotePromptLineRange(bounds, note) {
5801
+ const start = bounds ? bounds.lineStart : Math.max(1, Number(note && note.lineStart) || 1);
5802
+ const end = bounds ? bounds.lineEnd : Math.max(start, Number(note && note.lineEnd) || start);
5803
+ return start === end ? "L" + start : ("L" + start + "-L" + end);
5804
+ }
5805
+
5806
+ function buildReviewNotesPrompt() {
5807
+ const source = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
5808
+ const notes = getDisplayReviewNotes().filter((note) => String(note && note.text ? note.text : "").trim());
5809
+ if (!notes.length) return "";
5810
+
5811
+ const descriptor = getCurrentStudioDocumentDescriptor();
5812
+ const documentLabel = descriptor && descriptor.label ? descriptor.label : (sourceState && sourceState.label ? sourceState.label : "Studio document");
5813
+ const parts = [
5814
+ "Please address the following Studio comments. Use the file names and line numbers as anchors. The full document is not included here, only the comments and their anchors.",
5815
+ "Document: " + documentLabel,
5816
+ "",
5817
+ "## Comments",
5818
+ ];
5819
+
5820
+ notes.forEach((note, index) => {
5821
+ const bounds = getResolvedReviewNoteLineBounds(note, source);
5822
+ const fileLabel = getReviewNotePromptFileLabel(note, source);
5823
+ const location = (fileLabel ? (fileLabel + ":") : "") + formatReviewNotePromptLineRange(bounds, note);
5824
+ const comment = String(note && note.text ? note.text : "").trim();
5825
+ const anchor = String(note && (note.selectedDisplayText || note.selectedText) ? (note.selectedDisplayText || note.selectedText) : "")
5826
+ .replace(/\s+/g, " ")
5827
+ .trim();
5828
+ parts.push(
5829
+ "### Comment " + (index + 1) + " — " + location,
5830
+ "",
5831
+ comment,
5832
+ );
5833
+ if (anchor) {
5834
+ parts.push("", "> " + anchor.replace(/\n/g, "\n> "));
5835
+ }
5836
+ parts.push("");
5837
+ });
5838
+
5839
+ return parts.join("\n").replace(/\n{3,}/g, "\n\n").trim() + "\n";
5840
+ }
5841
+
5842
+ function loadReviewNotesPromptIntoEditor() {
5843
+ const prompt = buildReviewNotesPrompt();
5844
+ if (!prompt.trim()) {
5845
+ setStatus("No non-empty comments to load as a prompt.", "warning");
5846
+ return;
5847
+ }
5848
+ setEditorText(prompt, { preserveScroll: false, preserveSelection: false });
5849
+ setSourceState({ source: "blank", label: "comments prompt", path: null });
5850
+ setStatus("Loaded comments prompt into editor.", "success");
5851
+ }
5852
+
5415
5853
  function buildReviewNoteLineMap(text) {
5416
5854
  const source = String(text || "");
5417
5855
  const lineMap = new Map();
@@ -8235,12 +8673,19 @@
8235
8673
  ? "Select preview text and use Comment for a local preview-anchored comment."
8236
8674
  : "Switch to Editor (Raw) to comment on the current line.");
8237
8675
  }
8676
+ if (reviewNotesPromptBtn) {
8677
+ const promptCandidates = reviewNotes.filter((note) => String(note && note.text ? note.text : "").trim());
8678
+ reviewNotesPromptBtn.disabled = uiBusy || promptCandidates.length === 0;
8679
+ reviewNotesPromptBtn.title = promptCandidates.length > 0
8680
+ ? "Load local comments, line numbers, and file labels into the editor as a prompt."
8681
+ : "No non-empty local comments to load as a prompt.";
8682
+ }
8238
8683
  if (reviewNotesInlineAllBtn) {
8239
8684
  const currentText = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
8240
8685
  const toggleCandidates = getDisplayReviewNotes().filter((note) => getReviewNoteInlineState(note, currentText).canToggle);
8241
8686
  const allInline = toggleCandidates.length > 0 && toggleCandidates.every((note) => getReviewNoteInlineState(note, currentText).exists);
8242
8687
  reviewNotesInlineAllBtn.disabled = uiBusy || toggleCandidates.length === 0;
8243
- reviewNotesInlineAllBtn.textContent = allInline ? "All inline: On" : "All inline: Off";
8688
+ reviewNotesInlineAllBtn.textContent = allInline ? "Inline: On" : "Inline: Off";
8244
8689
  reviewNotesInlineAllBtn.setAttribute("aria-pressed", allInline ? "true" : "false");
8245
8690
  reviewNotesInlineAllBtn.title = allInline
8246
8691
  ? "Inline annotations derived from all non-empty comments are currently on. Click to remove them."
@@ -8870,11 +9315,13 @@
8870
9315
  if (!highlightSelect) return;
8871
9316
  if (!editorHighlightEnabled) {
8872
9317
  highlightSelect.value = "off";
9318
+ syncStudioUiRefreshSummaries();
8873
9319
  return;
8874
9320
  }
8875
9321
  highlightSelect.value = (editorLanguage && SUPPORTED_LANGUAGES.indexOf(editorLanguage) !== -1)
8876
9322
  ? editorLanguage
8877
9323
  : "markdown";
9324
+ syncStudioUiRefreshSummaries();
8878
9325
  }
8879
9326
 
8880
9327
  function setEditorHighlightEnabled(enabled) {
@@ -8977,6 +9424,7 @@
8977
9424
  critiqueBtn.disabled = true;
8978
9425
  critiqueBtn.title = "Critique is unavailable in editor-only mode.";
8979
9426
  }
9427
+ syncStudioUiRefreshReviewTrigger();
8980
9428
  return;
8981
9429
  }
8982
9430
 
@@ -9014,6 +9462,7 @@
9014
9462
  ? "Critique text as-is (includes [an: ...] markers)."
9015
9463
  : "Critique text with [an: ...] markers stripped."));
9016
9464
  }
9465
+ syncStudioUiRefreshReviewTrigger();
9017
9466
  }
9018
9467
 
9019
9468
  function updateAnnotationModeUi() {
@@ -9024,6 +9473,7 @@
9024
9473
  : "Inline annotations Hide: keep markers in the editor, hide them in preview, and strip before Run/Critique.";
9025
9474
  }
9026
9475
 
9476
+ syncStudioUiRefreshSummaries();
9027
9477
  syncRunAndCritiqueButtons();
9028
9478
  }
9029
9479
 
@@ -9362,6 +9812,7 @@
9362
9812
  setBusy(false);
9363
9813
  setWsState("Ready");
9364
9814
 
9815
+ pendingResponseScrollReset = true;
9365
9816
  let appliedFromHistory = false;
9366
9817
  if (Array.isArray(message.responseHistory)) {
9367
9818
  appliedFromHistory = setResponseHistory(message.responseHistory, {
@@ -9390,6 +9841,9 @@
9390
9841
  if (pendingRequestId) return;
9391
9842
 
9392
9843
  const hasHistory = Array.isArray(message.responseHistory);
9844
+ if (followLatest) {
9845
+ pendingResponseScrollReset = true;
9846
+ }
9393
9847
  if (hasHistory) {
9394
9848
  setResponseHistory(message.responseHistory, {
9395
9849
  autoSelectLatest: followLatest,
@@ -9413,7 +9867,7 @@
9413
9867
  return;
9414
9868
  }
9415
9869
 
9416
- if (!hasHistory && applyLatestPayload(payload)) {
9870
+ if (!hasHistory && applyLatestPayload(payload, { resetScroll: true })) {
9417
9871
  queuedLatestResponse = null;
9418
9872
  updateResultActionButtons();
9419
9873
  setStatus("Updated from latest response.", "success");
@@ -9909,10 +10363,12 @@
9909
10363
  if (hasHeader) {
9910
10364
  insertHeaderBtn.textContent = "Annotation header: On";
9911
10365
  insertHeaderBtn.title = "Remove annotated-reply protocol header while keeping body text.";
10366
+ syncStudioUiRefreshSummaries();
9912
10367
  return;
9913
10368
  }
9914
10369
  insertHeaderBtn.textContent = "Annotation header: Off";
9915
10370
  insertHeaderBtn.title = "Insert annotated-reply protocol header (source metadata, [an: ...] syntax hint, precedence note, and end marker).";
10371
+ syncStudioUiRefreshSummaries();
9916
10372
  }
9917
10373
 
9918
10374
  function toggleAnnotatedReplyHeader() {
@@ -10581,6 +11037,12 @@
10581
11037
  });
10582
11038
  }
10583
11039
 
11040
+ if (reviewNotesPromptBtn) {
11041
+ reviewNotesPromptBtn.addEventListener("click", () => {
11042
+ loadReviewNotesPromptIntoEditor();
11043
+ });
11044
+ }
11045
+
10584
11046
  if (reviewNotesInlineAllBtn) {
10585
11047
  reviewNotesInlineAllBtn.addEventListener("click", () => {
10586
11048
  toggleAllReviewNotesInlineAnnotations();