pi-studio 0.6.0 → 0.6.2
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 +16 -0
- package/README.md +3 -1
- package/client/studio-client.js +123 -5
- package/client/studio.css +206 -98
- package/index.ts +350 -64
- package/package.json +8 -2
- package/themes/pi-studio-dark.json +78 -0
- package/themes/pi-studio-light.json +77 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.6.2] — 2026-04-29
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Polished refreshed Studio theme surfaces so light-mode header actions, scratchpad panels, response-history badges, and editor surfaces are more consistent.
|
|
11
|
+
- Softened overly sharp derived borders in high-contrast themes while preserving theme hue and tightened secondary info text contrast in comment, outline, and scratchpad panels.
|
|
12
|
+
|
|
13
|
+
## [0.6.1] — 2026-04-29
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- Added independent editor and response text-size controls, persisted locally and available in both full Studio and editor-only views.
|
|
17
|
+
- Added optional `pi-studio-dark` and `pi-studio-light` package themes tuned for Studio's browser workspace.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- Improved theme adaptation for Studio surfaces, borders, Markdown/code colours, light/dark detection, and softer active-pane borders across bundled and custom pi themes.
|
|
21
|
+
- Footer model/session/context metadata now keeps context usage visible by truncating longer model/session labels first, with full working-directory details available in hover text.
|
|
22
|
+
|
|
7
23
|
## [0.6.0] — 2026-04-27
|
|
8
24
|
|
|
9
25
|
### Added
|
package/README.md
CHANGED
|
@@ -35,6 +35,7 @@ Extension for [pi](https://pi.dev) that opens a local two-pane browser workspace
|
|
|
35
35
|
- strips markers before send (optional)
|
|
36
36
|
- saves `.annotated.md`
|
|
37
37
|
- Renders Markdown/LaTeX/code previews (math + Mermaid), theme-synced with pi
|
|
38
|
+
- Ships optional `pi-studio-dark` and `pi-studio-light` themes tuned for Studio's browser workspace
|
|
38
39
|
- Exports right-pane preview as PDF (pandoc + LaTeX)
|
|
39
40
|
- Exports local files headlessly via `/studio-pdf <path>` to `<name>.studio.pdf`
|
|
40
41
|
- Shows model/session/context usage in the footer, plus a compact-context action
|
|
@@ -77,7 +78,8 @@ pi -e https://github.com/omaclaren/pi-studio
|
|
|
77
78
|
- For remote SSH sessions, keep Studio bound to localhost and use SSH local port forwarding; `/studio` prints the localhost URL and an SSH tunnel hint when SSH is detected.
|
|
78
79
|
- Full Studio is a singleton per Pi session: use `/studio` to open it, `/studio-replace` to explicitly replace it, and `/studio-editor-only` for extra editing/preview tabs that do not take over the full Studio session view.
|
|
79
80
|
- Studio is designed as a complement to terminal pi, not a replacement.
|
|
80
|
-
-
|
|
81
|
+
- Installing pi-studio makes the optional `pi-studio-dark` and `pi-studio-light` themes available in pi's theme selector; it does not change your active theme.
|
|
82
|
+
- Editor/code font uses a best-effort terminal-monospace match when the current terminal config exposes it; set `PI_STUDIO_FONT_MONO` to force a specific CSS `font-family` stack. Use `PI_STUDIO_FONT_UI` or `PI_STUDIO_FONT_PROSE` to override the Studio UI or rendered-preview font stacks.
|
|
81
83
|
- Full preview/PDF quality depends on `pandoc` (and `xelatex` for PDF):
|
|
82
84
|
- `brew install pandoc`
|
|
83
85
|
- install TeX Live/MacTeX for PDF export
|
package/client/studio-client.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
const statusSpinnerEl = document.getElementById("statusSpinner");
|
|
5
5
|
const footerMetaEl = document.getElementById("footerMeta");
|
|
6
6
|
const footerMetaTextEl = document.getElementById("footerMetaText");
|
|
7
|
+
const footerMetaModelEl = document.getElementById("footerMetaModel");
|
|
8
|
+
const footerMetaTerminalEl = document.getElementById("footerMetaTerminal");
|
|
9
|
+
const footerMetaContextEl = document.getElementById("footerMetaContext");
|
|
7
10
|
let faviconLinkEl = document.querySelector('link[rel="icon"], link[rel="shortcut icon"]');
|
|
8
11
|
if (!faviconLinkEl) {
|
|
9
12
|
faviconLinkEl = document.createElement("link");
|
|
@@ -70,6 +73,7 @@
|
|
|
70
73
|
const rightViewSelect = document.getElementById("rightViewSelect");
|
|
71
74
|
const followSelect = document.getElementById("followSelect");
|
|
72
75
|
const responseHighlightSelect = document.getElementById("responseHighlightSelect");
|
|
76
|
+
const responseFontSizeSelect = document.getElementById("responseFontSizeSelect");
|
|
73
77
|
const pullLatestBtn = document.getElementById("pullLatestBtn");
|
|
74
78
|
const insertHeaderBtn = document.getElementById("insertHeaderBtn");
|
|
75
79
|
const critiqueBtn = document.getElementById("critiqueBtn");
|
|
@@ -103,6 +107,7 @@
|
|
|
103
107
|
const stripAnnotationsBtn = document.getElementById("stripAnnotationsBtn");
|
|
104
108
|
const highlightSelect = document.getElementById("highlightSelect");
|
|
105
109
|
const lineNumbersSelect = document.getElementById("lineNumbersSelect");
|
|
110
|
+
const editorFontSizeSelect = document.getElementById("editorFontSizeSelect");
|
|
106
111
|
const annotationModeSelect = document.getElementById("annotationModeSelect");
|
|
107
112
|
const compactBtn = document.getElementById("compactBtn");
|
|
108
113
|
const leftFocusBtn = document.getElementById("leftFocusBtn");
|
|
@@ -201,6 +206,7 @@
|
|
|
201
206
|
let compactInProgress = false;
|
|
202
207
|
let modelLabel = (document.body && document.body.dataset && document.body.dataset.modelLabel) || "none";
|
|
203
208
|
let terminalSessionLabel = (document.body && document.body.dataset && document.body.dataset.terminalLabel) || "unknown";
|
|
209
|
+
let terminalSessionDetail = (document.body && document.body.dataset && document.body.dataset.terminalDetail) || terminalSessionLabel;
|
|
204
210
|
let contextTokens = null;
|
|
205
211
|
let contextWindow = null;
|
|
206
212
|
let contextPercent = null;
|
|
@@ -588,6 +594,7 @@
|
|
|
588
594
|
const EDITOR_HIGHLIGHT_STORAGE_KEY = "piStudio.editorHighlightEnabled";
|
|
589
595
|
const EDITOR_LANGUAGE_STORAGE_KEY = "piStudio.editorLanguage";
|
|
590
596
|
const EDITOR_LINE_NUMBERS_STORAGE_KEY = "piStudio.editorLineNumbersEnabled";
|
|
597
|
+
const EDITOR_FONT_SIZE_STORAGE_KEY = "piStudio.editorFontSize";
|
|
591
598
|
// Single source of truth: language -> file extensions (and display label)
|
|
592
599
|
var LANG_EXT_MAP = {
|
|
593
600
|
markdown: { label: "Markdown", exts: ["md", "markdown", "mdx", "qmd"] },
|
|
@@ -628,6 +635,7 @@
|
|
|
628
635
|
var SUPPORTED_LANGUAGES = Object.keys(LANG_EXT_MAP);
|
|
629
636
|
const RESPONSE_HIGHLIGHT_MAX_CHARS = 120_000;
|
|
630
637
|
const RESPONSE_HIGHLIGHT_STORAGE_KEY = "piStudio.responseHighlightEnabled";
|
|
638
|
+
const RESPONSE_FONT_SIZE_STORAGE_KEY = "piStudio.responseFontSize";
|
|
631
639
|
const ANNOTATION_MODE_STORAGE_KEY = "piStudio.annotationsEnabled";
|
|
632
640
|
const PREVIEW_INPUT_DEBOUNCE_MS = 0;
|
|
633
641
|
const PREVIEW_PENDING_BADGE_DELAY_MS = 220;
|
|
@@ -647,6 +655,12 @@
|
|
|
647
655
|
let annotationsEnabled = true;
|
|
648
656
|
const STUDIO_UI_REFRESH_STORAGE_KEY = "piStudio.uiRefresh";
|
|
649
657
|
const studioUiRefreshEnabled = readStudioUiRefreshEnabled();
|
|
658
|
+
const EDITOR_FONT_SIZE_OPTIONS = [10, 11, 12, 13, 14, 15, 16, 18];
|
|
659
|
+
const RESPONSE_FONT_SIZE_OPTIONS = [11, 12, 12.5, 13, 13.5, 14, 14.5, 15, 15.5, 16, 18, 20];
|
|
660
|
+
const DEFAULT_EDITOR_FONT_SIZE = studioUiRefreshEnabled ? 12 : 13;
|
|
661
|
+
const DEFAULT_RESPONSE_FONT_SIZE = studioUiRefreshEnabled ? 13.5 : 15;
|
|
662
|
+
let editorFontSize = DEFAULT_EDITOR_FONT_SIZE;
|
|
663
|
+
let responseFontSize = DEFAULT_RESPONSE_FONT_SIZE;
|
|
650
664
|
let studioUiRefreshUi = null;
|
|
651
665
|
if (studioUiRefreshEnabled && document.body) {
|
|
652
666
|
document.body.classList.add("studio-ui-refresh");
|
|
@@ -752,6 +766,74 @@
|
|
|
752
766
|
buttonEl.textContent = text;
|
|
753
767
|
}
|
|
754
768
|
|
|
769
|
+
function formatStudioFontSizeLabel(size) {
|
|
770
|
+
const value = Number(size);
|
|
771
|
+
if (!Number.isFinite(value)) return "";
|
|
772
|
+
return String(value).replace(/\.0$/, "") + "px";
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
function normalizeStudioFontSize(value, options, fallback) {
|
|
776
|
+
const parsed = Number(value);
|
|
777
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
778
|
+
for (const option of options) {
|
|
779
|
+
if (Math.abs(option - parsed) < 0.001) return option;
|
|
780
|
+
}
|
|
781
|
+
return fallback;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function readStoredFontSize(storageKey, options, fallback) {
|
|
785
|
+
try {
|
|
786
|
+
if (!window.localStorage) return fallback;
|
|
787
|
+
return normalizeStudioFontSize(window.localStorage.getItem(storageKey), options, fallback);
|
|
788
|
+
} catch {
|
|
789
|
+
return fallback;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
function persistStoredFontSize(storageKey, size) {
|
|
794
|
+
try {
|
|
795
|
+
if (window.localStorage) window.localStorage.setItem(storageKey, String(size));
|
|
796
|
+
} catch {
|
|
797
|
+
// ignore storage failures
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function syncFontSizeSelect(selectEl, size) {
|
|
802
|
+
if (!selectEl) return;
|
|
803
|
+
selectEl.value = String(size);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function applyStudioFontSizeVariables() {
|
|
807
|
+
if (!document.body || !document.body.style) return;
|
|
808
|
+
const editorLineNumberSize = Math.max(10, editorFontSize - 1);
|
|
809
|
+
const responseRawSize = Math.max(11, responseFontSize - 1.5);
|
|
810
|
+
document.body.style.setProperty("--studio-editor-font-size", formatStudioFontSizeLabel(editorFontSize));
|
|
811
|
+
document.body.style.setProperty("--studio-editor-line-number-font-size", formatStudioFontSizeLabel(editorLineNumberSize));
|
|
812
|
+
document.body.style.setProperty("--studio-response-font-size", formatStudioFontSizeLabel(responseFontSize));
|
|
813
|
+
document.body.style.setProperty("--studio-response-raw-font-size", formatStudioFontSizeLabel(responseRawSize));
|
|
814
|
+
document.body.style.setProperty("--studio-working-font-size", formatStudioFontSizeLabel(responseRawSize));
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function setEditorFontSize(size, options) {
|
|
818
|
+
editorFontSize = normalizeStudioFontSize(size, EDITOR_FONT_SIZE_OPTIONS, DEFAULT_EDITOR_FONT_SIZE);
|
|
819
|
+
if (!options || options.persist !== false) persistStoredFontSize(EDITOR_FONT_SIZE_STORAGE_KEY, editorFontSize);
|
|
820
|
+
syncFontSizeSelect(editorFontSizeSelect, editorFontSize);
|
|
821
|
+
applyStudioFontSizeVariables();
|
|
822
|
+
syncStudioUiRefreshSummaries();
|
|
823
|
+
scheduleEditorLineNumberRender();
|
|
824
|
+
if (editorHighlightEnabled && editorView === "markdown") {
|
|
825
|
+
scheduleEditorHighlightRender();
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function setResponseFontSize(size, options) {
|
|
830
|
+
responseFontSize = normalizeStudioFontSize(size, RESPONSE_FONT_SIZE_OPTIONS, DEFAULT_RESPONSE_FONT_SIZE);
|
|
831
|
+
if (!options || options.persist !== false) persistStoredFontSize(RESPONSE_FONT_SIZE_STORAGE_KEY, responseFontSize);
|
|
832
|
+
syncFontSizeSelect(responseFontSizeSelect, responseFontSize);
|
|
833
|
+
applyStudioFontSizeVariables();
|
|
834
|
+
scheduleResponsePaneRepaintNudge();
|
|
835
|
+
}
|
|
836
|
+
|
|
755
837
|
function getStudioUiRefreshAnnotationHeaderEnabled() {
|
|
756
838
|
try {
|
|
757
839
|
return Boolean(stripAnnotationHeader(sourceTextEl.value).hadHeader);
|
|
@@ -776,7 +858,8 @@
|
|
|
776
858
|
? (getStudioUiRefreshSelectSummary(highlightSelect, "Syntax highlight") || editorLanguage || "Markdown")
|
|
777
859
|
: "Off";
|
|
778
860
|
const lineLabel = lineNumbersEnabled ? "Lines on" : "Lines off";
|
|
779
|
-
|
|
861
|
+
const editorSizeLabel = formatStudioFontSizeLabel(editorFontSize);
|
|
862
|
+
setStudioUiRefreshButtonText(studioUiRefreshUi.viewButton, "View: " + syntaxLabel + " · " + lineLabel + " · " + editorSizeLabel);
|
|
780
863
|
}
|
|
781
864
|
syncStudioUiRefreshReviewTrigger();
|
|
782
865
|
}
|
|
@@ -957,7 +1040,7 @@
|
|
|
957
1040
|
appendStudioUiRefreshMenuSection(annotationsMenu.menu, "Actions", [stripAnnotationsBtn, saveAnnotatedBtn]);
|
|
958
1041
|
const viewButton = makeStudioUiRefreshElement("button", "", "View");
|
|
959
1042
|
const viewMenu = makeStudioUiRefreshMenu(viewButton, "view", "studio-refresh-view-anchor");
|
|
960
|
-
appendStudioUiRefreshMenuSection(viewMenu.menu, "Display", [highlightSelect, lineNumbersSelect]);
|
|
1043
|
+
appendStudioUiRefreshMenuSection(viewMenu.menu, "Display", [highlightSelect, lineNumbersSelect, editorFontSizeSelect]);
|
|
961
1044
|
stateEl.appendChild(annotationsMenu.anchor);
|
|
962
1045
|
stateEl.appendChild(viewMenu.anchor);
|
|
963
1046
|
|
|
@@ -1532,11 +1615,22 @@
|
|
|
1532
1615
|
function updateFooterMeta() {
|
|
1533
1616
|
const modelText = modelLabel && modelLabel.trim() ? modelLabel.trim() : "none";
|
|
1534
1617
|
const terminalText = terminalSessionLabel && terminalSessionLabel.trim() ? terminalSessionLabel.trim() : "unknown";
|
|
1618
|
+
const terminalDetailText = terminalSessionDetail && terminalSessionDetail.trim() ? terminalSessionDetail.trim() : terminalText;
|
|
1535
1619
|
const contextText = formatContextUsageText(true);
|
|
1536
1620
|
const contextTitleText = formatContextUsageText(false);
|
|
1537
|
-
const
|
|
1538
|
-
const
|
|
1539
|
-
|
|
1621
|
+
const contextDisplayText = contextText.replace(/^Context:\s*/i, "");
|
|
1622
|
+
const text = modelText + " · " + terminalText + " · " + contextDisplayText;
|
|
1623
|
+
const titleText = "Model: " + modelText + " · " + terminalDetailText + " · " + contextTitleText;
|
|
1624
|
+
if (footerMetaModelEl && footerMetaTerminalEl && footerMetaContextEl) {
|
|
1625
|
+
footerMetaModelEl.textContent = modelText;
|
|
1626
|
+
footerMetaTerminalEl.textContent = terminalText;
|
|
1627
|
+
footerMetaContextEl.textContent = contextDisplayText;
|
|
1628
|
+
footerMetaModelEl.title = "Model: " + modelText;
|
|
1629
|
+
footerMetaTerminalEl.title = terminalDetailText;
|
|
1630
|
+
footerMetaContextEl.title = contextTitleText;
|
|
1631
|
+
if (footerMetaTextEl) footerMetaTextEl.title = titleText;
|
|
1632
|
+
if (footerMetaEl) footerMetaEl.title = titleText;
|
|
1633
|
+
} else if (footerMetaTextEl) {
|
|
1540
1634
|
footerMetaTextEl.textContent = text;
|
|
1541
1635
|
footerMetaTextEl.title = titleText;
|
|
1542
1636
|
} else if (footerMetaEl) {
|
|
@@ -9620,6 +9714,9 @@
|
|
|
9620
9714
|
if (typeof message.terminalSessionLabel === "string") {
|
|
9621
9715
|
terminalSessionLabel = message.terminalSessionLabel;
|
|
9622
9716
|
}
|
|
9717
|
+
if (typeof message.terminalSessionDetail === "string") {
|
|
9718
|
+
terminalSessionDetail = message.terminalSessionDetail;
|
|
9719
|
+
}
|
|
9623
9720
|
applyStudioRunQueueStateFromMessage(message);
|
|
9624
9721
|
updateFooterMeta();
|
|
9625
9722
|
setBusy(busy);
|
|
@@ -10028,6 +10125,9 @@
|
|
|
10028
10125
|
if (typeof message.terminalSessionLabel === "string") {
|
|
10029
10126
|
terminalSessionLabel = message.terminalSessionLabel;
|
|
10030
10127
|
}
|
|
10128
|
+
if (typeof message.terminalSessionDetail === "string") {
|
|
10129
|
+
terminalSessionDetail = message.terminalSessionDetail;
|
|
10130
|
+
}
|
|
10031
10131
|
applyStudioRunQueueStateFromMessage(message);
|
|
10032
10132
|
updateFooterMeta();
|
|
10033
10133
|
|
|
@@ -10477,6 +10577,18 @@
|
|
|
10477
10577
|
});
|
|
10478
10578
|
}
|
|
10479
10579
|
|
|
10580
|
+
if (editorFontSizeSelect) {
|
|
10581
|
+
editorFontSizeSelect.addEventListener("change", () => {
|
|
10582
|
+
setEditorFontSize(editorFontSizeSelect.value);
|
|
10583
|
+
});
|
|
10584
|
+
}
|
|
10585
|
+
|
|
10586
|
+
if (responseFontSizeSelect) {
|
|
10587
|
+
responseFontSizeSelect.addEventListener("change", () => {
|
|
10588
|
+
setResponseFontSize(responseFontSizeSelect.value);
|
|
10589
|
+
});
|
|
10590
|
+
}
|
|
10591
|
+
|
|
10480
10592
|
if (lineNumbersSelect) {
|
|
10481
10593
|
lineNumbersSelect.addEventListener("change", () => {
|
|
10482
10594
|
setLineNumbersEnabled(lineNumbersSelect.value === "on");
|
|
@@ -11336,6 +11448,12 @@
|
|
|
11336
11448
|
editorResizeObserver.observe(sourceEditorWrapEl);
|
|
11337
11449
|
}
|
|
11338
11450
|
|
|
11451
|
+
const initialEditorFontSize = readStoredFontSize(EDITOR_FONT_SIZE_STORAGE_KEY, EDITOR_FONT_SIZE_OPTIONS, DEFAULT_EDITOR_FONT_SIZE);
|
|
11452
|
+
setEditorFontSize(initialEditorFontSize, { persist: false });
|
|
11453
|
+
|
|
11454
|
+
const initialResponseFontSize = readStoredFontSize(RESPONSE_FONT_SIZE_STORAGE_KEY, RESPONSE_FONT_SIZE_OPTIONS, DEFAULT_RESPONSE_FONT_SIZE);
|
|
11455
|
+
setResponseFontSize(initialResponseFontSize, { persist: false });
|
|
11456
|
+
|
|
11339
11457
|
setSourceState(initialSourceState);
|
|
11340
11458
|
refreshResponseUi();
|
|
11341
11459
|
updateAnnotatedReplyHeaderButton();
|