pi-studio 0.5.58 → 0.5.59

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,15 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.5.59] — 2026-04-27
8
+
9
+ ### Added
10
+ - Added an opt-in refreshed Studio layout, available via the footer UI switch or `?uiRefresh=1`, while keeping the classic layout as the default.
11
+ - Added refreshed layout support for editor-only Studio mode.
12
+
13
+ ### Changed
14
+ - Tuned refreshed pane headers, toolbar grouping, focus controls, dropdown labels, queue steering sizing, and preview export styling.
15
+
7
16
  ## [0.5.58] — 2026-04-24
8
17
 
9
18
  ### Added
@@ -644,6 +644,12 @@
644
644
  let lineNumbersEnabled = false;
645
645
  let lineNumbersRenderRaf = null;
646
646
  let annotationsEnabled = true;
647
+ const STUDIO_UI_REFRESH_STORAGE_KEY = "piStudio.uiRefresh";
648
+ const studioUiRefreshEnabled = readStudioUiRefreshEnabled();
649
+ let studioUiRefreshUi = null;
650
+ if (studioUiRefreshEnabled && document.body) {
651
+ document.body.classList.add("studio-ui-refresh");
652
+ }
647
653
  let scratchpadText = "";
648
654
  let scratchpadReturnFocusEl = null;
649
655
  let scratchpadPersistTimer = null;
@@ -662,6 +668,333 @@
662
668
  let suppressedEditorSelectionEnd = null;
663
669
  const previewJumpHighlightState = new WeakMap();
664
670
  const PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX = "PISTUDIOANNOT";
671
+
672
+ function readStudioUiRefreshEnabled() {
673
+ const normalize = (value) => String(value == null ? "" : value).trim().toLowerCase();
674
+ const queryValue = initialQueryParams.has("uiRefresh")
675
+ ? initialQueryParams.get("uiRefresh")
676
+ : (initialQueryParams.has("studioUiRefresh") ? initialQueryParams.get("studioUiRefresh") : null);
677
+ const isTruthy = (value) => ["1", "true", "yes", "on", "v2", "refresh"].indexOf(normalize(value)) !== -1;
678
+ const isFalsey = (value) => ["0", "false", "no", "off"].indexOf(normalize(value)) !== -1;
679
+ if (queryValue !== null) {
680
+ const enabled = isTruthy(queryValue) || (!isFalsey(queryValue) && normalize(queryValue) !== "");
681
+ try {
682
+ if (enabled) window.localStorage && window.localStorage.setItem(STUDIO_UI_REFRESH_STORAGE_KEY, "1");
683
+ else window.localStorage && window.localStorage.removeItem(STUDIO_UI_REFRESH_STORAGE_KEY);
684
+ } catch {}
685
+ return enabled;
686
+ }
687
+ try {
688
+ return Boolean(window.localStorage && window.localStorage.getItem(STUDIO_UI_REFRESH_STORAGE_KEY) === "1");
689
+ } catch {
690
+ return false;
691
+ }
692
+ }
693
+
694
+ function makeStudioUiRefreshElement(tagName, className, text) {
695
+ const element = document.createElement(tagName);
696
+ if (className) element.className = className;
697
+ if (typeof text === "string") element.textContent = text;
698
+ return element;
699
+ }
700
+
701
+ function makeStudioUiRefreshSeparator() {
702
+ return makeStudioUiRefreshElement("span", "studio-refresh-sep");
703
+ }
704
+
705
+ function makeStudioUiRefreshIcon(kind) {
706
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
707
+ svg.setAttribute("viewBox", "0 0 24 24");
708
+ svg.setAttribute("aria-hidden", "true");
709
+ svg.classList.add("studio-refresh-icon");
710
+ const paths = kind === "focus-exit"
711
+ ? ["M4 4l6 6", "M10 4v6H4", "M20 20l-6-6", "M14 20v-6h6"]
712
+ : ["M14 4h6v6", "M20 4l-6 6", "M10 20H4v-6", "M4 20l6-6"];
713
+ for (const d of paths) {
714
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
715
+ path.setAttribute("d", d);
716
+ svg.appendChild(path);
717
+ }
718
+ return svg;
719
+ }
720
+
721
+ function setStudioUiRefreshFocusButtonIcon(buttonEl, isFocusedPane) {
722
+ if (!buttonEl || !studioUiRefreshEnabled) return;
723
+ buttonEl.replaceChildren(makeStudioUiRefreshIcon(isFocusedPane ? "focus-exit" : "focus"));
724
+ buttonEl.setAttribute("aria-label", isFocusedPane ? "Exit focus" : "Focus pane");
725
+ }
726
+
727
+ function appendStudioUiRefreshMenuSection(menuEl, heading, controls) {
728
+ const sectionEl = makeStudioUiRefreshElement("div", "studio-refresh-menu-section");
729
+ if (heading) {
730
+ sectionEl.appendChild(makeStudioUiRefreshElement("div", "studio-refresh-menu-heading", heading));
731
+ }
732
+ for (const control of controls) {
733
+ if (!control) continue;
734
+ const itemEl = makeStudioUiRefreshElement("div", "studio-refresh-menu-item");
735
+ itemEl.appendChild(control);
736
+ sectionEl.appendChild(itemEl);
737
+ }
738
+ menuEl.appendChild(sectionEl);
739
+ }
740
+
741
+ function getStudioUiRefreshSelectSummary(selectEl, prefix) {
742
+ if (!selectEl) return "";
743
+ const option = selectEl.options && selectEl.selectedIndex >= 0 ? selectEl.options[selectEl.selectedIndex] : null;
744
+ let label = option ? String(option.textContent || option.label || option.value || "") : String(selectEl.value || "");
745
+ if (prefix) label = label.replace(new RegExp("^" + prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\s*:?\\s*", "i"), "");
746
+ return label.trim();
747
+ }
748
+
749
+ function setStudioUiRefreshButtonText(buttonEl, text) {
750
+ if (!buttonEl) return;
751
+ buttonEl.textContent = text;
752
+ }
753
+
754
+ function getStudioUiRefreshAnnotationHeaderEnabled() {
755
+ try {
756
+ return Boolean(stripAnnotationHeader(sourceTextEl.value).hadHeader);
757
+ } catch {
758
+ return false;
759
+ }
760
+ }
761
+
762
+ function syncStudioUiRefreshSummaries() {
763
+ if (!studioUiRefreshUi) return;
764
+ if (studioUiRefreshUi.annotationsButton) {
765
+ const inlineLabel = annotationsEnabled ? "Inline on" : "Inline hidden";
766
+ if (isEditorOnlyMode) {
767
+ setStudioUiRefreshButtonText(studioUiRefreshUi.annotationsButton, "Annotations: " + inlineLabel);
768
+ } else {
769
+ const headerLabel = getStudioUiRefreshAnnotationHeaderEnabled() ? "Header on" : "Header off";
770
+ setStudioUiRefreshButtonText(studioUiRefreshUi.annotationsButton, "Annotations: " + inlineLabel + " · " + headerLabel);
771
+ }
772
+ }
773
+ if (studioUiRefreshUi.viewButton) {
774
+ const syntaxLabel = editorHighlightEnabled
775
+ ? (getStudioUiRefreshSelectSummary(highlightSelect, "Syntax highlight") || editorLanguage || "Markdown")
776
+ : "Off";
777
+ const lineLabel = lineNumbersEnabled ? "Lines on" : "Lines off";
778
+ setStudioUiRefreshButtonText(studioUiRefreshUi.viewButton, "View: " + syntaxLabel + " · " + lineLabel);
779
+ }
780
+ syncStudioUiRefreshReviewTrigger();
781
+ }
782
+
783
+ function closeStudioUiRefreshMenus() {
784
+ if (!studioUiRefreshUi || !studioUiRefreshUi.menus) return;
785
+ for (const item of studioUiRefreshUi.menus) {
786
+ item.menu.hidden = true;
787
+ item.button.classList.remove("is-open");
788
+ item.button.setAttribute("aria-expanded", "false");
789
+ }
790
+ }
791
+
792
+ function toggleStudioUiRefreshMenu(name) {
793
+ if (!studioUiRefreshUi || !studioUiRefreshUi.menus) return;
794
+ let willOpen = false;
795
+ for (const item of studioUiRefreshUi.menus) {
796
+ if (item.name === name) willOpen = item.menu.hidden;
797
+ }
798
+ for (const item of studioUiRefreshUi.menus) {
799
+ const isOpen = willOpen && item.name === name;
800
+ item.menu.hidden = !isOpen;
801
+ item.button.classList.toggle("is-open", isOpen);
802
+ item.button.setAttribute("aria-expanded", isOpen ? "true" : "false");
803
+ }
804
+ }
805
+
806
+ function syncStudioUiRefreshReviewTrigger() {
807
+ if (!studioUiRefreshUi || !studioUiRefreshUi.reviewButton) return;
808
+ const critiqueIsStop = getAbortablePendingKind() === "critique";
809
+ const reviewButton = studioUiRefreshUi.reviewButton;
810
+ reviewButton.textContent = critiqueIsStop ? "Stop critique" : "Review";
811
+ reviewButton.classList.toggle("request-stop-active", critiqueIsStop);
812
+ reviewButton.disabled = critiqueIsStop ? Boolean(critiqueBtn && critiqueBtn.disabled) : false;
813
+ reviewButton.title = critiqueIsStop
814
+ ? "Stop the running critique request. Shortcut: Esc."
815
+ : "Open review actions and settings.";
816
+ if (critiqueIsStop) {
817
+ closeStudioUiRefreshMenus();
818
+ }
819
+ }
820
+
821
+ function makeStudioUiRefreshMenu(buttonEl, name, menuClassName) {
822
+ const anchorEl = makeStudioUiRefreshElement("span", "studio-refresh-menu-anchor " + (menuClassName || ""));
823
+ const menuEl = makeStudioUiRefreshElement("div", "studio-refresh-menu");
824
+ menuEl.hidden = true;
825
+ buttonEl.type = "button";
826
+ buttonEl.classList.add("studio-refresh-chip");
827
+ buttonEl.setAttribute("aria-haspopup", "menu");
828
+ buttonEl.setAttribute("aria-expanded", "false");
829
+ buttonEl.addEventListener("click", (event) => {
830
+ event.stopPropagation();
831
+ if (name === "review" && getAbortablePendingKind() === "critique") {
832
+ requestCancelForPendingRequest("critique");
833
+ return;
834
+ }
835
+ toggleStudioUiRefreshMenu(name);
836
+ });
837
+ anchorEl.appendChild(buttonEl);
838
+ anchorEl.appendChild(menuEl);
839
+ return { name, anchor: anchorEl, button: buttonEl, menu: menuEl };
840
+ }
841
+
842
+ function setStudioUiRefreshPreference(enabled) {
843
+ try {
844
+ if (enabled) window.localStorage && window.localStorage.setItem(STUDIO_UI_REFRESH_STORAGE_KEY, "1");
845
+ else window.localStorage && window.localStorage.removeItem(STUDIO_UI_REFRESH_STORAGE_KEY);
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: Refresh" : "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.");
@@ -1301,7 +1634,7 @@
1301
1634
 
1302
1635
  function updateSourceBadge() {
1303
1636
  const label = sourceState && sourceState.label ? sourceState.label : "blank";
1304
- sourceBadgeEl.textContent = "Editor origin: " + label;
1637
+ sourceBadgeEl.textContent = (studioUiRefreshEnabled ? "Origin: " : "Editor origin: ") + label;
1305
1638
  const descriptor = getCurrentStudioDocumentDescriptor();
1306
1639
  if (sourceBadgeEl) {
1307
1640
  sourceBadgeEl.title = descriptor.fileBacked
@@ -1362,6 +1695,9 @@
1362
1695
  btn.classList.toggle("is-active", isFocusedPane);
1363
1696
  btn.setAttribute("aria-pressed", isFocusedPane ? "true" : "false");
1364
1697
  btn.textContent = isFocusedPane ? "Exit focus" : "Focus pane";
1698
+ if (studioUiRefreshEnabled) {
1699
+ setStudioUiRefreshFocusButtonIcon(btn, isFocusedPane);
1700
+ }
1365
1701
  btn.title = isFocusedPane
1366
1702
  ? "Return to the two-pane layout. Shortcut: F10 or Cmd/Ctrl+Esc."
1367
1703
  : "Show only the " + paneName + " pane. Shortcut: F10 or Cmd/Ctrl+Esc.";
@@ -3849,6 +4185,7 @@
3849
4185
  if (lineNumbersSelect) {
3850
4186
  lineNumbersSelect.value = lineNumbersEnabled ? "on" : "off";
3851
4187
  }
4188
+ syncStudioUiRefreshSummaries();
3852
4189
  updateLineNumberGutterVisibility();
3853
4190
  scheduleEditorLineNumberRender();
3854
4191
  if (editorHighlightEnabled && editorView === "markdown") {
@@ -4537,6 +4874,7 @@
4537
4874
  if (stripAnnotationsBtn) {
4538
4875
  stripAnnotationsBtn.disabled = uiBusy || !hasAnnotationMarkers(sourceTextEl.value);
4539
4876
  }
4877
+ syncStudioUiRefreshSummaries();
4540
4878
  }
4541
4879
 
4542
4880
  function scheduleEditorMetaUpdate() {
@@ -8870,11 +9208,13 @@
8870
9208
  if (!highlightSelect) return;
8871
9209
  if (!editorHighlightEnabled) {
8872
9210
  highlightSelect.value = "off";
9211
+ syncStudioUiRefreshSummaries();
8873
9212
  return;
8874
9213
  }
8875
9214
  highlightSelect.value = (editorLanguage && SUPPORTED_LANGUAGES.indexOf(editorLanguage) !== -1)
8876
9215
  ? editorLanguage
8877
9216
  : "markdown";
9217
+ syncStudioUiRefreshSummaries();
8878
9218
  }
8879
9219
 
8880
9220
  function setEditorHighlightEnabled(enabled) {
@@ -8977,6 +9317,7 @@
8977
9317
  critiqueBtn.disabled = true;
8978
9318
  critiqueBtn.title = "Critique is unavailable in editor-only mode.";
8979
9319
  }
9320
+ syncStudioUiRefreshReviewTrigger();
8980
9321
  return;
8981
9322
  }
8982
9323
 
@@ -9014,6 +9355,7 @@
9014
9355
  ? "Critique text as-is (includes [an: ...] markers)."
9015
9356
  : "Critique text with [an: ...] markers stripped."));
9016
9357
  }
9358
+ syncStudioUiRefreshReviewTrigger();
9017
9359
  }
9018
9360
 
9019
9361
  function updateAnnotationModeUi() {
@@ -9024,6 +9366,7 @@
9024
9366
  : "Inline annotations Hide: keep markers in the editor, hide them in preview, and strip before Run/Critique.";
9025
9367
  }
9026
9368
 
9369
+ syncStudioUiRefreshSummaries();
9027
9370
  syncRunAndCritiqueButtons();
9028
9371
  }
9029
9372
 
@@ -9909,10 +10252,12 @@
9909
10252
  if (hasHeader) {
9910
10253
  insertHeaderBtn.textContent = "Annotation header: On";
9911
10254
  insertHeaderBtn.title = "Remove annotated-reply protocol header while keeping body text.";
10255
+ syncStudioUiRefreshSummaries();
9912
10256
  return;
9913
10257
  }
9914
10258
  insertHeaderBtn.textContent = "Annotation header: Off";
9915
10259
  insertHeaderBtn.title = "Insert annotated-reply protocol header (source metadata, [an: ...] syntax hint, precedence note, and end marker).";
10260
+ syncStudioUiRefreshSummaries();
9916
10261
  }
9917
10262
 
9918
10263
  function toggleAnnotatedReplyHeader() {
package/client/studio.css CHANGED
@@ -2314,3 +2314,516 @@
2314
2314
  grid-template-columns: 1fr;
2315
2315
  }
2316
2316
  }
2317
+
2318
+ /* Opt-in editor-side layout refresh prototype. Enabled with ?uiRefresh=1 or localStorage piStudio.uiRefresh=1. */
2319
+ body.studio-ui-refresh {
2320
+ font-size: 14px;
2321
+ }
2322
+
2323
+ body.studio-ui-refresh > header {
2324
+ padding: 9px 14px;
2325
+ gap: 10px;
2326
+ }
2327
+
2328
+ body.studio-ui-refresh h1 {
2329
+ font-size: 17px;
2330
+ }
2331
+
2332
+ body.studio-ui-refresh .app-logo {
2333
+ font-size: 20px;
2334
+ }
2335
+
2336
+ body.studio-ui-refresh .app-subtitle {
2337
+ font-size: 11px;
2338
+ }
2339
+
2340
+ body.studio-ui-refresh > header .controls {
2341
+ gap: 6px;
2342
+ }
2343
+
2344
+ body.studio-ui-refresh > header button,
2345
+ body.studio-ui-refresh > header .file-label,
2346
+ body.studio-ui-refresh #responseActions button,
2347
+ body.studio-ui-refresh #responseActions select {
2348
+ padding: 6px 8px;
2349
+ font-size: 13px;
2350
+ }
2351
+
2352
+ body.studio-ui-refresh main {
2353
+ gap: 10px;
2354
+ padding: 10px;
2355
+ }
2356
+
2357
+ body.studio-ui-refresh footer {
2358
+ padding: 8px 10px;
2359
+ font-size: 12px;
2360
+ }
2361
+
2362
+ .studio-ui-refresh-toggle {
2363
+ opacity: 0.9;
2364
+ }
2365
+
2366
+ .studio-ui-refresh-toggle:not(:disabled):hover {
2367
+ opacity: 1;
2368
+ }
2369
+
2370
+ body.studio-ui-refresh[data-studio-mode="editor-only"] #leftSectionHeader .section-header-main::before,
2371
+ body.studio-ui-refresh[data-studio-mode="editor-only"] #rightSectionHeader .section-header-main::before {
2372
+ content: none;
2373
+ display: none;
2374
+ }
2375
+
2376
+ body.studio-ui-refresh #leftSectionHeader,
2377
+ body.studio-ui-refresh #rightSectionHeader {
2378
+ position: relative;
2379
+ z-index: 30;
2380
+ display: grid;
2381
+ gap: 7px;
2382
+ background: transparent;
2383
+ padding: 8px 10px 7px;
2384
+ overflow: visible;
2385
+ }
2386
+
2387
+ body.studio-ui-refresh #leftSectionHeader {
2388
+ grid-template-columns: 1fr;
2389
+ align-items: stretch;
2390
+ }
2391
+
2392
+ body.studio-ui-refresh #rightSectionHeader {
2393
+ grid-template-columns: minmax(0, 1fr) auto;
2394
+ align-items: center;
2395
+ }
2396
+
2397
+ body.studio-ui-refresh .studio-refresh-utility-left,
2398
+ body.studio-ui-refresh .studio-refresh-pane-identity,
2399
+ body.studio-ui-refresh .studio-refresh-pane-tools,
2400
+ body.studio-ui-refresh .studio-refresh-title-group,
2401
+ body.studio-ui-refresh .studio-refresh-context-group,
2402
+ body.studio-ui-refresh .studio-refresh-action-line {
2403
+ display: flex;
2404
+ align-items: center;
2405
+ gap: 7px;
2406
+ min-width: 0;
2407
+ flex-wrap: wrap;
2408
+ }
2409
+
2410
+ body.studio-ui-refresh .studio-refresh-header-top,
2411
+ body.studio-ui-refresh .studio-refresh-header-utility {
2412
+ display: grid;
2413
+ grid-template-columns: minmax(0, 1fr) auto;
2414
+ align-items: center;
2415
+ gap: 10px;
2416
+ min-width: 0;
2417
+ }
2418
+
2419
+ body.studio-ui-refresh .studio-refresh-pane-identity {
2420
+ display: grid;
2421
+ grid-template-columns: auto minmax(0, 1fr);
2422
+ align-items: center;
2423
+ column-gap: 10px;
2424
+ row-gap: 6px;
2425
+ }
2426
+
2427
+ body.studio-ui-refresh .studio-refresh-title-group,
2428
+ body.studio-ui-refresh .studio-refresh-context-group,
2429
+ body.studio-ui-refresh .studio-refresh-utility-left {
2430
+ flex-wrap: nowrap;
2431
+ }
2432
+
2433
+ body.studio-ui-refresh .studio-refresh-context-group,
2434
+ body.studio-ui-refresh .studio-refresh-utility-left {
2435
+ overflow: hidden;
2436
+ white-space: nowrap;
2437
+ }
2438
+
2439
+ body.studio-ui-refresh .studio-refresh-title-group {
2440
+ gap: 3px;
2441
+ }
2442
+
2443
+ body.studio-ui-refresh .studio-refresh-pane-tools {
2444
+ justify-content: flex-end;
2445
+ flex-wrap: nowrap;
2446
+ }
2447
+
2448
+ body.studio-ui-refresh .studio-refresh-sep {
2449
+ display: inline-block;
2450
+ width: 1px;
2451
+ height: 18px;
2452
+ background: var(--border-muted);
2453
+ margin: 0 1px;
2454
+ flex: 0 0 1px;
2455
+ }
2456
+
2457
+ body.studio-ui-refresh .studio-refresh-icon {
2458
+ width: 17px;
2459
+ height: 17px;
2460
+ stroke: currentColor;
2461
+ stroke-width: 1.85;
2462
+ stroke-linecap: round;
2463
+ stroke-linejoin: round;
2464
+ fill: none;
2465
+ pointer-events: none;
2466
+ }
2467
+
2468
+ body.studio-ui-refresh #leftSectionHeader select,
2469
+ body.studio-ui-refresh #leftSectionHeader button,
2470
+ body.studio-ui-refresh #rightSectionHeader select,
2471
+ body.studio-ui-refresh #rightSectionHeader button,
2472
+ body.studio-ui-refresh .studio-refresh-toolbar button,
2473
+ body.studio-ui-refresh .studio-refresh-toolbar select {
2474
+ border-color: transparent;
2475
+ background: transparent;
2476
+ font-size: 14px;
2477
+ }
2478
+
2479
+ body.studio-ui-refresh #leftSectionHeader select:hover,
2480
+ body.studio-ui-refresh #leftSectionHeader button:not(:disabled):hover,
2481
+ body.studio-ui-refresh #rightSectionHeader select:hover,
2482
+ body.studio-ui-refresh #rightSectionHeader button:not(:disabled):hover,
2483
+ body.studio-ui-refresh .studio-refresh-toolbar button:not(:disabled):hover,
2484
+ body.studio-ui-refresh .studio-refresh-toolbar select:not(:disabled):hover {
2485
+ background: var(--panel-2);
2486
+ }
2487
+
2488
+ body.studio-ui-refresh #leftSectionHeader #editorViewSelect,
2489
+ body.studio-ui-refresh #rightSectionHeader #rightViewSelect {
2490
+ font-size: 15px;
2491
+ font-weight: 750;
2492
+ padding: 3px 5px;
2493
+ max-width: 230px;
2494
+ }
2495
+
2496
+ body.studio-ui-refresh .studio-refresh-static-title {
2497
+ color: var(--text);
2498
+ font-size: 15px;
2499
+ font-weight: 700;
2500
+ padding: 3px 5px;
2501
+ max-width: 230px;
2502
+ white-space: nowrap;
2503
+ }
2504
+
2505
+ body.studio-ui-refresh #leftFocusBtn,
2506
+ body.studio-ui-refresh #rightFocusBtn {
2507
+ width: 32px;
2508
+ min-width: 32px;
2509
+ min-height: 32px;
2510
+ padding: 0;
2511
+ color: var(--muted);
2512
+ align-items: center;
2513
+ justify-content: center;
2514
+ }
2515
+
2516
+ body.studio-ui-refresh #sourceBadge,
2517
+ body.studio-ui-refresh #resourceDirBtn,
2518
+ body.studio-ui-refresh #resourceDirLabel {
2519
+ border-color: transparent;
2520
+ background: transparent;
2521
+ padding: 4px 6px;
2522
+ font-size: 14px;
2523
+ border-radius: 8px;
2524
+ }
2525
+
2526
+ body.studio-ui-refresh #sourceBadge {
2527
+ color: var(--text);
2528
+ max-width: min(34rem, 54vw);
2529
+ min-width: 0;
2530
+ overflow: hidden;
2531
+ text-overflow: ellipsis;
2532
+ white-space: nowrap;
2533
+ }
2534
+
2535
+ body.studio-ui-refresh #resourceDirBtn,
2536
+ body.studio-ui-refresh #resourceDirLabel,
2537
+ body.studio-ui-refresh #reviewNotesBtn,
2538
+ body.studio-ui-refresh #outlineBtn,
2539
+ body.studio-ui-refresh #scratchpadBtn,
2540
+ body.studio-ui-refresh .studio-refresh-tool-tab {
2541
+ color: var(--text);
2542
+ }
2543
+
2544
+ body.studio-ui-refresh #resourceDirInputWrap.visible {
2545
+ display: inline-flex;
2546
+ }
2547
+
2548
+ body.studio-ui-refresh #syncBadge {
2549
+ display: inline-flex;
2550
+ align-items: center;
2551
+ gap: 6px;
2552
+ min-height: 26px;
2553
+ border: 0;
2554
+ border-radius: 999px;
2555
+ padding: 3px 9px;
2556
+ background: var(--panel-2);
2557
+ color: var(--muted);
2558
+ opacity: 1;
2559
+ }
2560
+
2561
+ body.studio-ui-refresh #syncBadge[hidden] {
2562
+ display: none !important;
2563
+ }
2564
+
2565
+ body.studio-ui-refresh #syncBadge::before {
2566
+ content: "";
2567
+ width: 7px;
2568
+ height: 7px;
2569
+ border-radius: 999px;
2570
+ background: var(--success, #22c55e);
2571
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--success, #22c55e) 14%, transparent);
2572
+ }
2573
+
2574
+ body.studio-ui-refresh #reviewNotesBtn,
2575
+ body.studio-ui-refresh #outlineBtn,
2576
+ body.studio-ui-refresh #scratchpadBtn,
2577
+ body.studio-ui-refresh #exportPdfBtn,
2578
+ body.studio-ui-refresh .studio-refresh-tool-tab {
2579
+ font-weight: 500;
2580
+ padding: 6px 9px;
2581
+ border-radius: 9px;
2582
+ }
2583
+
2584
+ body.studio-ui-refresh #exportPdfBtn {
2585
+ background: var(--panel-2);
2586
+ color: var(--text);
2587
+ border-color: transparent;
2588
+ }
2589
+
2590
+ body.studio-ui-refresh #reviewNotesBtn.is-active,
2591
+ body.studio-ui-refresh #outlineBtn.is-active,
2592
+ body.studio-ui-refresh #scratchpadBtn.has-content,
2593
+ body.studio-ui-refresh #reviewNotesBtn.has-content {
2594
+ background: var(--accent-soft);
2595
+ color: var(--accent);
2596
+ border-color: transparent;
2597
+ }
2598
+
2599
+ body.studio-ui-refresh .source-wrap {
2600
+ padding: 0;
2601
+ gap: 0;
2602
+ }
2603
+
2604
+ body.studio-ui-refresh .source-meta {
2605
+ display: block;
2606
+ padding: 0;
2607
+ border-bottom: 1px solid var(--border-muted);
2608
+ position: relative;
2609
+ z-index: 20;
2610
+ overflow: visible;
2611
+ }
2612
+
2613
+ body.studio-ui-refresh .source-body {
2614
+ padding: 8px;
2615
+ }
2616
+
2617
+ body.studio-ui-refresh .studio-refresh-toolbar {
2618
+ position: relative;
2619
+ padding: 9px 12px 10px;
2620
+ overflow: visible;
2621
+ }
2622
+
2623
+ body.studio-ui-refresh .studio-refresh-toolbar-main {
2624
+ display: grid;
2625
+ grid-template-columns: minmax(0, 1fr) auto;
2626
+ gap: 12px;
2627
+ align-items: start;
2628
+ min-width: 0;
2629
+ }
2630
+
2631
+ body.studio-ui-refresh .studio-refresh-toolbar-actions {
2632
+ display: grid;
2633
+ gap: 7px;
2634
+ justify-items: start;
2635
+ min-width: 0;
2636
+ }
2637
+
2638
+ body.studio-ui-refresh .studio-refresh-toolbar-state {
2639
+ display: grid;
2640
+ gap: 7px;
2641
+ justify-items: end;
2642
+ align-content: start;
2643
+ min-width: max-content;
2644
+ }
2645
+
2646
+ body.studio-ui-refresh .studio-refresh-chip {
2647
+ display: inline-flex;
2648
+ align-items: center;
2649
+ gap: 6px;
2650
+ border-radius: 9px;
2651
+ color: var(--text);
2652
+ white-space: nowrap;
2653
+ padding: 6px 9px;
2654
+ font-size: 14px;
2655
+ }
2656
+
2657
+ body.studio-ui-refresh .studio-refresh-chip::after {
2658
+ content: "⌄";
2659
+ color: var(--muted);
2660
+ font-size: 15px;
2661
+ line-height: 1;
2662
+ transform: translateY(-1px);
2663
+ }
2664
+
2665
+ body.studio-ui-refresh .studio-refresh-chip.is-open {
2666
+ background: var(--panel-2);
2667
+ color: var(--text);
2668
+ }
2669
+
2670
+ body.studio-ui-refresh .studio-refresh-menu-anchor {
2671
+ position: relative;
2672
+ display: inline-flex;
2673
+ align-items: center;
2674
+ overflow: visible;
2675
+ }
2676
+
2677
+ body.studio-ui-refresh .studio-refresh-menu {
2678
+ position: absolute;
2679
+ top: calc(100% + 8px);
2680
+ right: 0;
2681
+ width: min(420px, calc(100vw - 48px));
2682
+ padding: 10px;
2683
+ border: 1px solid var(--border);
2684
+ border-radius: 12px;
2685
+ background: var(--panel);
2686
+ box-shadow: 0 18px 46px rgba(0, 0, 0, 0.32);
2687
+ z-index: 100;
2688
+ }
2689
+
2690
+ body.studio-ui-refresh .studio-refresh-review-anchor .studio-refresh-menu {
2691
+ width: min(360px, calc(100vw - 48px));
2692
+ left: auto;
2693
+ right: 0;
2694
+ }
2695
+
2696
+ body.studio-ui-refresh .studio-refresh-view-anchor .studio-refresh-menu {
2697
+ width: min(320px, calc(100vw - 48px));
2698
+ }
2699
+
2700
+ body.studio-ui-refresh .studio-refresh-menu[hidden] {
2701
+ display: none !important;
2702
+ }
2703
+
2704
+ body.studio-ui-refresh .studio-refresh-menu-section {
2705
+ padding: 8px 0 6px;
2706
+ border-top: 1px solid var(--border-muted);
2707
+ }
2708
+
2709
+ body.studio-ui-refresh .studio-refresh-menu-section:first-child {
2710
+ border-top: 0;
2711
+ padding-top: 0;
2712
+ }
2713
+
2714
+ body.studio-ui-refresh .studio-refresh-menu-heading {
2715
+ margin: 0 6px 6px;
2716
+ color: var(--muted);
2717
+ font-size: 11px;
2718
+ font-weight: 700;
2719
+ text-transform: uppercase;
2720
+ letter-spacing: 0.08em;
2721
+ }
2722
+
2723
+ body.studio-ui-refresh .studio-refresh-menu-item {
2724
+ display: flex;
2725
+ align-items: center;
2726
+ gap: 8px;
2727
+ padding: 4px 0;
2728
+ min-width: 0;
2729
+ }
2730
+
2731
+ body.studio-ui-refresh .studio-refresh-menu-item > button,
2732
+ body.studio-ui-refresh .studio-refresh-menu-item > select {
2733
+ width: 100%;
2734
+ justify-content: flex-start;
2735
+ text-align: left;
2736
+ border-color: transparent;
2737
+ background: transparent;
2738
+ color: var(--text);
2739
+ }
2740
+
2741
+ body.studio-ui-refresh .studio-refresh-menu #critiqueBtn {
2742
+ justify-content: flex-start;
2743
+ text-align: left;
2744
+ }
2745
+
2746
+ body.studio-ui-refresh .studio-refresh-menu-item > button:not(:disabled):hover,
2747
+ body.studio-ui-refresh .studio-refresh-menu-item > select:not(:disabled):hover {
2748
+ background: var(--panel-2);
2749
+ }
2750
+
2751
+ body.studio-ui-refresh #copyDraftBtn:only-child {
2752
+ min-height: 32px;
2753
+ padding: 5px 9px;
2754
+ }
2755
+
2756
+ body.studio-ui-refresh #sendRunBtn,
2757
+ body.studio-ui-refresh #queueSteerBtn,
2758
+ body.studio-ui-refresh #loadResponseBtn:not([hidden]) {
2759
+ height: 30px;
2760
+ min-height: 30px;
2761
+ padding: 4px 10px;
2762
+ font-size: 13px;
2763
+ line-height: 1.2;
2764
+ border-radius: 8px;
2765
+ }
2766
+
2767
+ body.studio-ui-refresh #sendRunBtn {
2768
+ min-width: 9.2rem;
2769
+ }
2770
+
2771
+ body.studio-ui-refresh #queueSteerBtn {
2772
+ min-width: auto;
2773
+ }
2774
+
2775
+ body.studio-ui-refresh #queueSteerBtn:not(:disabled) {
2776
+ background: var(--accent);
2777
+ border-color: var(--accent);
2778
+ color: var(--accent-contrast);
2779
+ font-weight: 600;
2780
+ }
2781
+
2782
+ body.studio-ui-refresh #queueSteerBtn:not(:disabled):hover {
2783
+ background: var(--accent);
2784
+ filter: brightness(0.95);
2785
+ }
2786
+
2787
+ body.studio-ui-refresh .studio-refresh-review-btn.request-stop-active {
2788
+ background: var(--error);
2789
+ border-color: var(--error);
2790
+ color: var(--error-contrast);
2791
+ font-weight: 600;
2792
+ }
2793
+
2794
+ body.studio-ui-refresh .studio-refresh-review-btn.request-stop-active::after {
2795
+ content: none;
2796
+ }
2797
+
2798
+ body.studio-ui-refresh .studio-refresh-menu #critiqueBtn.request-stop-active {
2799
+ background: var(--error);
2800
+ border-color: var(--error);
2801
+ color: var(--error-contrast);
2802
+ font-weight: 600;
2803
+ }
2804
+
2805
+ @media (max-width: 1280px) {
2806
+ body.studio-ui-refresh #rightSectionHeader,
2807
+ body.studio-ui-refresh .studio-refresh-header-top,
2808
+ body.studio-ui-refresh .studio-refresh-header-utility,
2809
+ body.studio-ui-refresh .studio-refresh-toolbar-main,
2810
+ body.studio-ui-refresh .studio-refresh-pane-identity {
2811
+ grid-template-columns: 1fr;
2812
+ }
2813
+
2814
+ body.studio-ui-refresh .studio-refresh-pane-tools,
2815
+ body.studio-ui-refresh .studio-refresh-toolbar-state {
2816
+ justify-content: flex-start;
2817
+ justify-items: start;
2818
+ }
2819
+
2820
+ body.studio-ui-refresh .studio-refresh-pane-tools,
2821
+ body.studio-ui-refresh .studio-refresh-title-group,
2822
+ body.studio-ui-refresh .studio-refresh-utility-left {
2823
+ flex-wrap: wrap;
2824
+ }
2825
+
2826
+ body.studio-ui-refresh .studio-refresh-toolbar-state {
2827
+ min-width: 0;
2828
+ }
2829
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.5.58",
3
+ "version": "0.5.59",
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",