pi-studio 0.9.54 → 0.9.56

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.
@@ -74,7 +74,9 @@
74
74
  : null;
75
75
  const referenceBadgeEl = document.getElementById("referenceBadge");
76
76
  const editorViewSelect = document.getElementById("editorViewSelect");
77
+ const editorViewSelectWrap = document.getElementById("editorViewSelectWrap");
77
78
  const rightViewSelect = document.getElementById("rightViewSelect");
79
+ const rightViewSelectWrap = document.getElementById("rightViewSelectWrap");
78
80
  const followSelect = document.getElementById("followSelect");
79
81
  const responseHighlightSelect = document.getElementById("responseHighlightSelect");
80
82
  const responseFontSizeSelect = document.getElementById("responseFontSizeSelect");
@@ -486,6 +488,7 @@
486
488
  const EDITOR_TAB_TEXT = " ";
487
489
  const QUIZ_DEFAULT_COUNT = 5;
488
490
  const SIDE_QUESTION_THINKING_STORAGE_KEY = "piStudio.sideQuestionThinking";
491
+ const SIDE_QUESTION_THINKING_LEVELS = Object.freeze(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
489
492
  const SIDE_QUESTION_GATHER_STORAGE_KEY = "piStudio.sideQuestionGatherScope";
490
493
  const SIDE_QUESTION_TOOLS_STORAGE_KEY = "piStudio.sideQuestionTools";
491
494
  const SIDE_QUESTION_TRANSCRIPT_RENDER_MAX_CHARS = 400_000;
@@ -496,6 +499,7 @@
496
499
  const QUIZ_ANGLES = ["general", "scientist", "mathematician", "statistician", "developer", "reviewer"];
497
500
  const QUIZ_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
498
501
  let sideQuestionState = null;
502
+ let sideQuestionThinkingLevels = [...SIDE_QUESTION_THINKING_LEVELS];
499
503
  let sideQuestionWebSearchAvailable = false;
500
504
  let sideQuestionAvailablePiTools = [];
501
505
  let sideQuestionPreviewRenderNonce = 0;
@@ -524,11 +528,12 @@
524
528
  thinking: (() => {
525
529
  try {
526
530
  const value = window.localStorage && window.localStorage.getItem(SIDE_QUESTION_THINKING_STORAGE_KEY);
527
- return ["off", "minimal", "low", "medium", "high"].includes(value) ? value : "low";
531
+ return SIDE_QUESTION_THINKING_LEVELS.includes(value) ? value : "low";
528
532
  } catch { return "low"; }
529
533
  })(),
530
534
  draft: "",
531
535
  };
536
+ let sideQuestionPreferredThinking = sideQuestionUi.thinking;
532
537
  let quizOverlayEl = null;
533
538
  let quizDialogEl = null;
534
539
  let quizPreviewRenderNonce = 0;
@@ -592,13 +597,18 @@
592
597
  })();
593
598
  function normalizeReplJournalEntry(entry) {
594
599
  if (!entry || typeof entry !== "object") return null;
600
+ const hasCompatibleOrigin = entry.origin === "pi-repl" || entry.origin === "pi-studio";
601
+ const sharedSynced = entry.sharedSynced === true || (entry.sharedSynced !== false && hasCompatibleOrigin);
595
602
  const normalized = {
596
603
  id: typeof entry.id === "string" && entry.id ? entry.id : ("repl-journal-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 8)),
597
604
  requestId: typeof entry.requestId === "string" ? entry.requestId : "",
598
605
  createdAt: typeof entry.createdAt === "number" && Number.isFinite(entry.createdAt) ? entry.createdAt : Date.now(),
599
606
  updatedAt: typeof entry.updatedAt === "number" && Number.isFinite(entry.updatedAt) ? entry.updatedAt : Date.now(),
607
+ completedAt: typeof entry.completedAt === "number" && Number.isFinite(entry.completedAt) ? entry.completedAt : null,
600
608
  sessionName: typeof entry.sessionName === "string" ? entry.sessionName : "",
601
609
  runtime: typeof entry.runtime === "string" ? entry.runtime : "python",
610
+ origin: hasCompatibleOrigin ? entry.origin : "",
611
+ legacyLocal: !sharedSynced,
602
612
  label: typeof entry.label === "string" ? entry.label : "REPL send",
603
613
  mode: typeof entry.mode === "string" ? entry.mode : "raw",
604
614
  prose: typeof entry.prose === "string" ? entry.prose : "",
@@ -630,8 +640,11 @@
630
640
  requestId: entry.requestId,
631
641
  createdAt: entry.createdAt,
632
642
  updatedAt: entry.updatedAt,
643
+ completedAt: entry.completedAt,
633
644
  sessionName: entry.sessionName,
634
645
  runtime: entry.runtime,
646
+ origin: entry.origin,
647
+ sharedSynced: !entry.legacyLocal,
635
648
  label: entry.label,
636
649
  mode: entry.mode,
637
650
  prose: entry.prose,
@@ -666,6 +679,7 @@
666
679
  mode: existing.mode || entry.mode,
667
680
  prose: existing.prose || entry.prose,
668
681
  beforeTranscript: existing.beforeTranscript || "",
682
+ legacyLocal: entry.legacyLocal,
669
683
  createdAt: existing.createdAt || entry.createdAt,
670
684
  updatedAt: Math.max(existing.updatedAt || 0, entry.updatedAt || 0),
671
685
  skippedChunks: existing.skippedChunks || entry.skippedChunks,
@@ -689,6 +703,7 @@
689
703
 
690
704
  let replJournalEntries = loadPersistedReplJournalEntries();
691
705
  let activeReplJournalEntryId = "";
706
+ const replJournalImportPendingSessions = new Set();
692
707
  let replJournalCollapsed = (() => {
693
708
  try {
694
709
  const stored = window.localStorage ? window.localStorage.getItem("piStudio.replStudioCollapsed") : null;
@@ -1810,13 +1825,17 @@
1810
1825
  }
1811
1826
 
1812
1827
  function createReplJournalEntry(details) {
1828
+ const sharedSynced = details.sharedSynced === true;
1813
1829
  return {
1814
1830
  id: "repl-journal-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 8),
1815
1831
  requestId: details.requestId || "",
1816
1832
  createdAt: Date.now(),
1817
1833
  updatedAt: Date.now(),
1834
+ completedAt: typeof details.completedAt === "number" ? details.completedAt : null,
1818
1835
  sessionName: details.sessionName || "",
1819
1836
  runtime: details.runtime || getActiveReplRuntime(),
1837
+ origin: details.origin === "pi-repl" ? "pi-repl" : "pi-studio",
1838
+ legacyLocal: !sharedSynced,
1820
1839
  label: details.label || "REPL send",
1821
1840
  mode: details.mode || replSendMode,
1822
1841
  prose: String(details.prose || ""),
@@ -1835,6 +1854,65 @@
1835
1854
  return entry;
1836
1855
  }
1837
1856
 
1857
+ function syncReplJournalEntry(entry) {
1858
+ if (!entry || !entry.sessionName || wsState === "Disconnected") return false;
1859
+ return sendMessage({
1860
+ type: "repl_journal_upsert_request",
1861
+ requestId: makeRequestId(),
1862
+ sessionName: entry.sessionName,
1863
+ entry: {
1864
+ id: entry.id,
1865
+ requestId: entry.requestId,
1866
+ createdAt: entry.createdAt,
1867
+ updatedAt: entry.updatedAt,
1868
+ completedAt: entry.completedAt,
1869
+ runtime: entry.runtime,
1870
+ origin: "pi-studio",
1871
+ label: entry.label,
1872
+ mode: entry.mode,
1873
+ prose: entry.prose,
1874
+ code: entry.code,
1875
+ output: entry.output,
1876
+ status: entry.status,
1877
+ skippedChunks: entry.skippedChunks,
1878
+ },
1879
+ });
1880
+ }
1881
+
1882
+ function maybeImportLegacyReplJournalEntries(sessionName) {
1883
+ const name = String(sessionName || "").trim();
1884
+ if (!name || replJournalImportPendingSessions.has(name) || wsState === "Disconnected") return false;
1885
+ const entries = replJournalEntries.filter((entry) => entry.sessionName === name && entry.legacyLocal).slice(-80);
1886
+ if (!entries.length) return false;
1887
+ replJournalImportPendingSessions.add(name);
1888
+ const sent = sendMessage({
1889
+ type: "repl_journal_import_request",
1890
+ requestId: makeRequestId(),
1891
+ sessionName: name,
1892
+ entries: entries.map((entry) => ({
1893
+ id: entry.id,
1894
+ requestId: entry.requestId,
1895
+ createdAt: entry.createdAt,
1896
+ updatedAt: entry.updatedAt,
1897
+ runtime: entry.runtime,
1898
+ origin: "pi-studio",
1899
+ label: entry.label,
1900
+ mode: entry.mode,
1901
+ prose: entry.prose,
1902
+ code: entry.code,
1903
+ output: entry.output,
1904
+ status: entry.status,
1905
+ skippedChunks: entry.skippedChunks,
1906
+ })),
1907
+ });
1908
+ if (!sent) {
1909
+ replJournalImportPendingSessions.delete(name);
1910
+ } else {
1911
+ window.setTimeout(() => replJournalImportPendingSessions.delete(name), 5000);
1912
+ }
1913
+ return sent;
1914
+ }
1915
+
1838
1916
  function recordReplToolSend(message) {
1839
1917
  const requestId = typeof message.toolCallId === "string" && message.toolCallId.trim()
1840
1918
  ? "tool:" + message.toolCallId.trim()
@@ -1848,6 +1926,8 @@
1848
1926
  requestId,
1849
1927
  sessionName,
1850
1928
  runtime,
1929
+ origin: message.origin === "pi-repl" ? "pi-repl" : "pi-studio",
1930
+ sharedSynced: message.sharedSynced === true,
1851
1931
  label: typeof message.label === "string" && message.label.trim() ? message.label.trim() : "Pi",
1852
1932
  mode: "agent",
1853
1933
  code,
@@ -1966,19 +2046,28 @@
1966
2046
  function buildReplJournalMarkdown(entries) {
1967
2047
  const visibleEntries = Array.isArray(entries) ? entries : getVisibleReplJournalEntries();
1968
2048
  const sessionName = getActiveReplJournalSessionName();
1969
- const lines = ["# Studio REPL Record", "", "Generated: " + new Date().toLocaleString()];
1970
- if (sessionName) lines.push("Session: `" + sessionName + "`");
1971
- lines.push("");
2049
+ const updatedAt = visibleEntries.reduce((latest, entry) => Math.max(latest, Number(entry.updatedAt) || 0), 0) || Date.now();
2050
+ const lines = [
2051
+ "# Shared REPL Record",
2052
+ "",
2053
+ "Session: `" + (sessionName || "unknown") + "`",
2054
+ "Record protocol: pi-repl-session-record v1",
2055
+ "Updated: " + new Date(updatedAt).toISOString(),
2056
+ "",
2057
+ ];
1972
2058
  if (!visibleEntries.length) {
1973
- lines.push(sessionName ? ("_No Studio REPL record entries for `" + sessionName + "` yet._") : "_No Studio REPL record entries yet._");
2059
+ lines.push("_No compatible-client entries have been recorded for this tmux session._", "");
2060
+ lines.push("_This clean record contains submissions made through compatible clients. Commands typed directly into an attached tmux pane remain available only in the raw pane/history mirror._", "");
1974
2061
  return lines.join("\n");
1975
2062
  }
1976
2063
  visibleEntries.forEach((entry, index) => {
1977
2064
  lines.push("## " + (index + 1) + ". " + (entry.label || "REPL entry"));
1978
2065
  lines.push("");
1979
- lines.push("- Time: " + new Date(entry.createdAt || Date.now()).toLocaleString());
2066
+ lines.push("- Time: " + new Date(entry.createdAt || Date.now()).toISOString());
2067
+ lines.push("- Origin: " + (entry.origin || "unknown"));
2068
+ lines.push("- Mode: " + (entry.mode || "raw"));
2069
+ lines.push("- Status: " + (entry.status || "sent"));
1980
2070
  if (entry.runtime) lines.push("- Runtime: " + entry.runtime);
1981
- if (entry.sessionName) lines.push("- Session: `" + entry.sessionName + "`");
1982
2071
  if (entry.skippedChunks) lines.push("- Skipped chunks: " + entry.skippedChunks);
1983
2072
  lines.push("");
1984
2073
  if (String(entry.prose || "").trim()) {
@@ -1986,7 +2075,10 @@
1986
2075
  lines.push("");
1987
2076
  }
1988
2077
  if (String(entry.code || "").trim()) {
1989
- lines.push(getMarkdownFenceForText(entry.code, entry.runtime === "ipython" ? "python" : entry.runtime));
2078
+ const markdownRuntime = entry.runtime === "ipython"
2079
+ ? "python"
2080
+ : (entry.runtime === "unknown" || entry.runtime === "shell" ? "" : entry.runtime);
2081
+ lines.push(getMarkdownFenceForText(entry.code, markdownRuntime));
1990
2082
  lines.push("");
1991
2083
  }
1992
2084
  if (String(entry.output || "").trim()) {
@@ -1996,17 +2088,18 @@
1996
2088
  lines.push("");
1997
2089
  }
1998
2090
  });
2091
+ lines.push("_This clean record contains submissions made through compatible clients. Commands typed directly into an attached tmux pane remain available only in the raw pane/history mirror._", "");
1999
2092
  return lines.join("\n").replace(/\n{4,}/g, "\n\n\n").trimEnd() + "\n";
2000
2093
  }
2001
2094
 
2002
2095
  async function copyReplJournalToClipboard() {
2003
2096
  const entries = getVisibleReplJournalEntries();
2004
2097
  if (!entries.length) {
2005
- setStatus("No Studio REPL record entries to copy for this session yet.", "warning");
2098
+ setStatus("No shared REPL record entries to copy for this session yet.", "warning");
2006
2099
  return;
2007
2100
  }
2008
2101
  if (await writeTextToClipboard(buildReplJournalMarkdown(entries))) {
2009
- setStatus("Copied Studio REPL record as Markdown.", "success");
2102
+ setStatus("Copied shared REPL record as Markdown.", "success");
2010
2103
  } else {
2011
2104
  setStatus("Clipboard write failed.", "warning");
2012
2105
  }
@@ -2015,7 +2108,7 @@
2015
2108
  function exportReplJournalMarkdown() {
2016
2109
  const entries = getVisibleReplJournalEntries();
2017
2110
  if (!entries.length) {
2018
- setStatus("No Studio REPL record entries to export for this session yet.", "warning");
2111
+ setStatus("No shared REPL record entries to export for this session yet.", "warning");
2019
2112
  return;
2020
2113
  }
2021
2114
  const blob = new Blob([buildReplJournalMarkdown(entries)], { type: "text/markdown;charset=utf-8" });
@@ -2024,12 +2117,12 @@
2024
2117
  const stamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
2025
2118
  const sessionSlug = getActiveReplJournalSessionName().replace(/[^-_.A-Za-z0-9]+/g, "-");
2026
2119
  link.href = blobUrl;
2027
- link.download = "repl-studio" + (sessionSlug ? "-" + sessionSlug : "") + "-" + stamp + ".md";
2120
+ link.download = "repl-record" + (sessionSlug ? "-" + sessionSlug : "") + "-" + stamp + ".md";
2028
2121
  document.body.appendChild(link);
2029
2122
  link.click();
2030
2123
  link.remove();
2031
2124
  window.setTimeout(() => URL.revokeObjectURL(blobUrl), 1000);
2032
- setStatus("Exported Studio REPL record Markdown.", "success");
2125
+ setStatus("Exported shared REPL record Markdown.", "success");
2033
2126
  }
2034
2127
 
2035
2128
  function clearReplJournal() {
@@ -2041,30 +2134,33 @@
2041
2134
  }
2042
2135
  activeReplJournalEntryId = "";
2043
2136
  persistReplJournalEntries();
2044
- setStatus(sessionName ? "Cleared Studio REPL record for this session." : "Cleared Studio REPL record.", "success");
2137
+ if (sessionName) {
2138
+ sendMessage({ type: "repl_journal_clear_request", requestId: makeRequestId(), sessionName });
2139
+ }
2140
+ setStatus(sessionName ? "Cleared shared REPL record for this session." : "Cleared local REPL record cache.", "success");
2045
2141
  renderReplViewIfActive({ force: true });
2046
2142
  }
2047
2143
 
2048
2144
  function loadReplJournalIntoEditor() {
2049
2145
  const entries = getVisibleReplJournalEntries();
2050
2146
  if (!entries.length) {
2051
- setStatus("No Studio REPL record entries to load for this session yet.", "warning");
2147
+ setStatus("No shared REPL record entries to load for this session yet.", "warning");
2052
2148
  return;
2053
2149
  }
2054
2150
  const markdown = buildReplJournalMarkdown(entries);
2055
2151
  setEditorText(markdown, { preserveScroll: false, preserveSelection: false });
2056
- setSourceState({ source: "blank", label: "Studio REPL Record", path: null });
2152
+ setSourceState({ source: "blank", label: "Shared REPL Record", path: null });
2057
2153
  setEditorLanguage("markdown");
2058
- setStatus("Loaded Studio REPL record into editor.", "success");
2154
+ setStatus("Loaded shared REPL record into editor.", "success");
2059
2155
  }
2060
2156
 
2061
2157
  function addSelectedReplJournalNote() {
2062
2158
  const note = getSelectedOrCurrentParagraphForReplNote();
2063
2159
  if (!note.trim()) {
2064
- setStatus("Select prose or place the cursor in a paragraph to add a Studio REPL record note.", "warning");
2160
+ setStatus("Select prose or place the cursor in a paragraph to add a shared REPL record note.", "warning");
2065
2161
  return;
2066
2162
  }
2067
- addReplJournalEntry({
2163
+ const entry = addReplJournalEntry({
2068
2164
  label: "note",
2069
2165
  prose: note,
2070
2166
  status: "note",
@@ -2072,7 +2168,8 @@
2072
2168
  sessionName: replActiveSessionName,
2073
2169
  runtime: getActiveReplRuntime(),
2074
2170
  });
2075
- setStatus("Added note to Studio REPL record.", "success");
2171
+ syncReplJournalEntry(entry);
2172
+ setStatus("Added note to shared REPL record.", "success");
2076
2173
  renderReplViewIfActive({ force: true });
2077
2174
  }
2078
2175
 
@@ -2088,7 +2185,7 @@
2088
2185
  }
2089
2186
  if (payload.noteOnly) {
2090
2187
  if (String(payload.prose || "").trim()) {
2091
- addReplJournalEntry({
2188
+ const entry = addReplJournalEntry({
2092
2189
  label: payload.label || "note",
2093
2190
  prose: payload.prose,
2094
2191
  status: "note",
@@ -2097,7 +2194,8 @@
2097
2194
  runtime: getActiveReplRuntime(),
2098
2195
  skippedChunks: payload.skippedChunks,
2099
2196
  });
2100
- setStatus("Added prose to Studio REPL record.", "success");
2197
+ syncReplJournalEntry(entry);
2198
+ setStatus("Added prose to shared REPL record.", "success");
2101
2199
  renderReplViewIfActive({ force: true });
2102
2200
  } else {
2103
2201
  setStatus("No code or prose found to send.", "warning");
@@ -2128,7 +2226,18 @@
2128
2226
  renderReplViewIfActive({ force: true });
2129
2227
  const skippedSuffix = payload.skippedChunks ? " (skipped " + payload.skippedChunks + " incompatible chunk" + (payload.skippedChunks === 1 ? "" : "s") + ")" : "";
2130
2228
  setStatus("Sending " + (payload.label || "editor text") + " to REPL…" + skippedSuffix, "info");
2131
- if (!sendMessage({ type: "repl_send_request", requestId, sessionName: session.sessionName, text })) {
2229
+ if (!sendMessage({
2230
+ type: "repl_send_request",
2231
+ requestId,
2232
+ sessionName: session.sessionName,
2233
+ text,
2234
+ journalEntryId: journalEntry.id,
2235
+ createdAt: journalEntry.createdAt,
2236
+ label: journalEntry.label,
2237
+ mode: journalEntry.mode,
2238
+ prose: journalEntry.prose,
2239
+ skippedChunks: journalEntry.skippedChunks,
2240
+ })) {
2132
2241
  replBusy = false;
2133
2242
  replJournalEntries = replJournalEntries.map((entry) => entry.id === journalEntry.id ? { ...entry, status: "error" } : entry);
2134
2243
  persistReplJournalEntries();
@@ -2914,9 +3023,28 @@
2914
3023
  }
2915
3024
  titleGroupEl.appendChild(makeStudioUiRefreshSeparator());
2916
3025
  if (isEditorOnlyMode) {
2917
- titleGroupEl.appendChild(makeStudioUiRefreshElement("span", "studio-refresh-static-title", "Editor (Raw)"));
3026
+ const staticTitleEl = makeStudioUiRefreshElement(
3027
+ "span",
3028
+ "studio-refresh-static-title",
3029
+ isWatchedFilePreview ? "Source" : "Editor (Raw)",
3030
+ );
3031
+ if (isWatchedFilePreview) {
3032
+ staticTitleEl.classList.add("studio-watched-source-title");
3033
+ staticTitleEl.setAttribute("aria-label", "Source, read-only. Follows the file on disk.");
3034
+ staticTitleEl.title = "This source is read-only and follows the file on disk.";
3035
+ }
3036
+ titleGroupEl.appendChild(staticTitleEl);
3037
+ if (isWatchedFilePreview) {
3038
+ const readOnlyBadgeEl = makeStudioUiRefreshElement(
3039
+ "span",
3040
+ "studio-watched-source-read-only-badge",
3041
+ "Read-only · follows disk",
3042
+ );
3043
+ readOnlyBadgeEl.setAttribute("aria-hidden", "true");
3044
+ titleGroupEl.appendChild(readOnlyBadgeEl);
3045
+ }
2918
3046
  } else if (editorViewSelect) {
2919
- titleGroupEl.appendChild(editorViewSelect);
3047
+ titleGroupEl.appendChild(editorViewSelectWrap || editorViewSelect);
2920
3048
  }
2921
3049
  if (contextMenu) {
2922
3050
  titleGroupEl.appendChild(makeStudioUiRefreshSeparator());
@@ -2942,7 +3070,7 @@
2942
3070
  rightTitleGroupEl.appendChild(rightFocusBtn);
2943
3071
  rightTitleGroupEl.appendChild(makeStudioUiRefreshSeparator());
2944
3072
  }
2945
- rightTitleGroupEl.appendChild(rightViewSelect);
3073
+ rightTitleGroupEl.appendChild(rightViewSelectWrap || rightViewSelect);
2946
3074
  rightIdentityEl.appendChild(rightTitleGroupEl);
2947
3075
  const rightToolsEl = makeStudioUiRefreshElement("div", "studio-refresh-pane-tools");
2948
3076
  if (watchedOpenEditableBtn && isWatchedFilePreview) rightToolsEl.appendChild(watchedOpenEditableBtn);
@@ -3615,6 +3743,9 @@
3615
3743
  if (typeof message.thinkingLevel === "string") {
3616
3744
  piThinkingLevel = message.thinkingLevel.trim();
3617
3745
  }
3746
+ if (Array.isArray(message.sideQuestionThinkingLevels)) {
3747
+ applySideQuestionThinkingLevels(message.sideQuestionThinkingLevels);
3748
+ }
3618
3749
  renderFooterModelMenu();
3619
3750
  }
3620
3751
 
@@ -3649,8 +3780,8 @@
3649
3780
  });
3650
3781
  footerModelMenuEl.innerHTML = ""
3651
3782
  + "<div class='footer-model-menu-heading'>Pi model & thinking</div>"
3652
- + "<label class='footer-model-menu-field'><span>Pi model</span><select id='footerPiModelSelect'>" + modelOptionsHtml.join("") + "</select></label>"
3653
- + "<label class='footer-model-menu-field'><span>Thinking</span><select id='footerPiThinkingSelect'>" + thinkingOptionsHtml.join("") + "</select></label>"
3783
+ + "<label class='footer-model-menu-field'><span>Pi model</span><span class='studio-menu-select-wrap'><select id='footerPiModelSelect'>" + modelOptionsHtml.join("") + "</select></span></label>"
3784
+ + "<label class='footer-model-menu-field'><span>Thinking</span><span class='studio-menu-select-wrap'><select id='footerPiThinkingSelect'>" + thinkingOptionsHtml.join("") + "</select></span></label>"
3654
3785
  + "<div class='footer-model-menu-note'>Affects future Pi turns. Studio Suggest has its own model setting.</div>";
3655
3786
  }
3656
3787
 
@@ -3730,7 +3861,7 @@
3730
3861
  footerThemeMenuEl.innerHTML = ""
3731
3862
  + "<div class='footer-model-menu-heading'>Pi theme</div>"
3732
3863
  + (optionsHtml.length
3733
- ? "<label class='footer-model-menu-field'><span>Active theme</span><select id='footerPiThemeSelect'>" + optionsHtml.join("") + "</select></label>"
3864
+ ? "<label class='footer-model-menu-field'><span>Active theme</span><span class='studio-menu-select-wrap'><select id='footerPiThemeSelect'>" + optionsHtml.join("") + "</select></span></label>"
3734
3865
  : "<div class='footer-model-menu-note'>No Pi themes are available yet.</div>")
3735
3866
  + "<div class='footer-model-menu-note'>Switches the active Pi theme and persists it to Pi settings.</div>";
3736
3867
  }
@@ -4359,7 +4490,9 @@
4359
4490
  ].forEach(([btn, pane]) => {
4360
4491
  if (!btn) return;
4361
4492
  const isFocusedPane = paneFocusTarget === pane;
4362
- const paneName = pane === "right" ? "response" : "editor";
4493
+ const paneName = pane === "right"
4494
+ ? (isWatchedFilePreview ? "watched preview" : "response")
4495
+ : (isWatchedFilePreview ? "read-only source" : "editor");
4363
4496
  btn.classList.toggle("is-active", isFocusedPane);
4364
4497
  btn.setAttribute("aria-pressed", isFocusedPane ? "true" : "false");
4365
4498
  btn.textContent = isFocusedPane ? "Exit focus" : "Focus pane";
@@ -10194,7 +10327,7 @@
10194
10327
  }
10195
10328
  const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
10196
10329
  if (exportingReplJournal && !replJournalExportEntries.length) {
10197
- setStatus("No Studio REPL record entries to export for this session yet.", "warning");
10330
+ setStatus("No shared REPL record entries to export for this session yet.", "warning");
10198
10331
  return;
10199
10332
  }
10200
10333
 
@@ -10439,7 +10572,7 @@
10439
10572
  }
10440
10573
  const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
10441
10574
  if (exportingReplJournal && !replJournalExportEntries.length) {
10442
- setStatus("No Studio REPL record entries to export for this session yet.", "warning");
10575
+ setStatus("No shared REPL record entries to export for this session yet.", "warning");
10443
10576
  return;
10444
10577
  }
10445
10578
 
@@ -10472,7 +10605,7 @@
10472
10605
  let filenameHint = exportingSideThread
10473
10606
  ? getSideQuestionTranscriptFilename(sideThreadExportedAt).replace(/\.md$/i, ".html")
10474
10607
  : (exportingReplJournal ? "repl-studio.html" : (isEditorPreview ? "studio-editor-preview.html" : ("studio-response-" + formatStudioExportTimestamp() + ".studio.html")));
10475
- let titleHint = exportingSideThread ? "Pi Studio side questions" : (exportingReplJournal ? "Studio REPL Record" : (isEditorPreview ? "Studio editor preview" : "Studio response preview"));
10608
+ let titleHint = exportingSideThread ? "Pi Studio side questions" : (exportingReplJournal ? "Shared REPL Record" : (isEditorPreview ? "Studio editor preview" : "Studio response preview"));
10476
10609
  if (sourcePath) {
10477
10610
  const baseName = sourcePath.split(/[\\/]/).pop() || "studio";
10478
10611
  const stem = baseName.replace(/\.[^.]+$/, "") || "studio";
@@ -11450,6 +11583,7 @@
11450
11583
  const parts = [];
11451
11584
  const kind = getReplStudioEntryKind(entry);
11452
11585
  if (kind !== "Raw") parts.push(kind);
11586
+ if (entry.origin) parts.push(entry.origin);
11453
11587
  const time = formatReferenceTime(entry.createdAt);
11454
11588
  if (time) parts.push(time);
11455
11589
  if (entry.skippedChunks) parts.push("skipped " + String(entry.skippedChunks));
@@ -11503,12 +11637,12 @@
11503
11637
  const toggleButton = "<button type='button' data-repl-action='journal-toggle' aria-expanded='" + (replJournalCollapsed ? "false" : "true") + "'>" + (replJournalCollapsed ? "Show record" : "Hide record") + "</button>";
11504
11638
  const toggleActions = "<div class='repl-journal-actions'>" + toggleButton + "</div>";
11505
11639
  const summaryText = hasEntries
11506
- ? (entryCount + " Studio entr" + (entryCount === 1 ? "y" : "ies") + (sessionName ? " for " + sessionName : "") + ". Export is Markdown.")
11507
- : (sessionName ? "No Studio entries for " + sessionName + "." : "Studio-sent code and notes will appear here.");
11640
+ ? (entryCount + " shared record entr" + (entryCount === 1 ? "y" : "ies") + (sessionName ? " for " + sessionName : "") + ". Export is Markdown.")
11641
+ : (sessionName ? "No compatible-client entries for " + sessionName + "." : "pi-repl and pi-studio submissions will appear here.");
11508
11642
  if (replJournalCollapsed) {
11509
11643
  return "<section class='repl-journal repl-journal-compact" + collapsedClass + "'>"
11510
11644
  + "<div class='repl-journal-compact-row'>"
11511
- + "<div class='repl-journal-compact-title'><span class='repl-journal-chip'>Studio REPL Record</span><span>" + escapeHtml(summaryText) + "</span></div>"
11645
+ + "<div class='repl-journal-compact-title'><span class='repl-journal-chip'>Shared REPL Record</span><span>" + escapeHtml(summaryText) + "</span></div>"
11512
11646
  + "<div class='repl-journal-actions'>" + toggleButton + "</div>"
11513
11647
  + "</div>"
11514
11648
  + "</section>";
@@ -11546,14 +11680,14 @@
11546
11680
  }).join("");
11547
11681
  const emptyText = sessionName
11548
11682
  ? (String(transcript || "").trim()
11549
- ? "No Studio REPL record entries yet. The raw tmux mirror below still has this session's history; send code from Studio to build a clean record."
11550
- : "No Studio REPL record entries yet. Send code from the editor, or use More → Add note (Literate send) to record prose.")
11551
- : "No Studio REPL record entries yet. Send code from the editor, or use More → Add note (Literate send) to record prose.";
11683
+ ? "No compatible-client record entries yet. The raw tmux mirror below still has this session's history; send code through pi-repl or Studio to build a clean record."
11684
+ : "No compatible-client record entries yet. Send code from pi-repl or Studio, or use More → Add note (Literate send) to record prose.")
11685
+ : "No compatible-client record entries yet. Send code from pi-repl or Studio, or use More → Add note (Literate send) to record prose.";
11552
11686
  const terminalContent = banner
11553
11687
  + (hasEntries ? cards : "<div class='repl-studio-empty'>" + escapeHtml(emptyText) + "</div>");
11554
11688
  return "<section class='repl-journal'>"
11555
- + "<div class='repl-journal-header'><h3>Studio REPL Record</h3>" + toggleActions + "</div>"
11556
- + "<p class='repl-journal-description'>Clean record for the selected tmux session. Raw tmux mirror below.</p>"
11689
+ + "<div class='repl-journal-header'><h3>Shared REPL Record</h3>" + toggleActions + "</div>"
11690
+ + "<p class='repl-journal-description'>Clean record shared by compatible pi-repl and pi-studio clients for this exact tmux session. Direct pane typing remains in the raw mirror below.</p>"
11557
11691
  + (omitted ? "<div class='repl-journal-omitted'>Showing latest 12 entries for this session; " + escapeHtml(String(omitted)) + " older entries remain in export.</div>" : "")
11558
11692
  + "<div class='repl-journal-list'>" + terminalContent + "</div>"
11559
11693
  + "</section>";
@@ -11577,7 +11711,7 @@
11577
11711
  + "</section>";
11578
11712
  }
11579
11713
  return "<section class='repl-mirror'>"
11580
- + "<div class='repl-journal-header'><div><h3>Raw REPL Mirror</h3><p>Best-effort tmux pane mirror. Useful for directly typed commands and debugging; the Studio record above is cleaner.</p></div>" + actions + "</div>"
11714
+ + "<div class='repl-journal-header'><div><h3>Raw REPL Mirror</h3><p>Best-effort tmux pane mirror. Useful for directly typed commands and debugging; the shared clean record above has reliable client-submission boundaries.</p></div>" + actions + "</div>"
11581
11715
  + body
11582
11716
  + "</section>";
11583
11717
  }
@@ -11616,10 +11750,10 @@
11616
11750
  return "<div class='repl-panel'>"
11617
11751
  + "<div class='repl-toolbar'>"
11618
11752
  + "<div class='repl-controls'>"
11619
- + "<label class='repl-control-label'>Runtime <select data-repl-runtime aria-label='REPL runtime'>" + runtimeOptions + "</select></label>"
11753
+ + "<label class='repl-control-label'>Runtime <select class='studio-flat-select' data-repl-runtime aria-label='REPL runtime'>" + runtimeOptions + "</select></label>"
11620
11754
  + "<label class='repl-control-label repl-command-label'>Start cmd <input data-repl-command type='text' value='" + escapeHtml(replCommand) + "' placeholder='default' aria-label='REPL start command' title='Command used by Start for this runtime. Leave blank for the default; use this for envs, e.g. .venv/bin/python, uv run python, or conda run --no-capture-output -n env python.'></label>"
11621
11755
  + "<button class='repl-start-btn' type='button' data-repl-action='start'" + (replBusy || replTmuxAvailable === false ? " disabled" : "") + " title='Start or switch to a " + escapeHtml(runtimeLabel) + " session using the selected runtime and start command.'>Start</button>"
11622
- + "<label class='repl-control-label repl-session-label'>Session <select data-repl-session aria-label='REPL session'" + (visibleSessions.length ? "" : " disabled") + ">" + sessionOptions + "</select></label>"
11756
+ + "<label class='repl-control-label repl-session-label'>Session <select class='studio-flat-select' data-repl-session aria-label='REPL session'" + (visibleSessions.length ? "" : " disabled") + ">" + sessionOptions + "</select></label>"
11623
11757
  + "<details class='repl-more-controls'>"
11624
11758
  + "<summary title='More REPL actions'>More</summary>"
11625
11759
  + "<div class='repl-more-menu'>"
@@ -11627,7 +11761,7 @@
11627
11761
  + "<button type='button' data-repl-action='interrupt'" + (activeSession && !replBusy ? "" : " disabled") + " title='Send Ctrl+C to the active REPL session.'>Interrupt</button>"
11628
11762
  + "<button type='button' data-repl-action='copy-attach-command'" + (activeSession ? "" : " disabled") + " title='Copy command for attaching to this tmux session in a terminal.'>Copy attach command</button>"
11629
11763
  + "<button type='button' data-repl-action='run-all-chunks'" + (canSendToActiveSession ? "" : " disabled") + " title='Literate send: send all fenced code chunks matching the active REPL runtime.'>Run all chunks</button>"
11630
- + "<button type='button' data-repl-action='journal-note' title='Add the selected prose/current paragraph to the Studio REPL record (Literate send) without sending it to the runtime.'>Add note</button>"
11764
+ + "<button type='button' data-repl-action='journal-note' title='Add the selected prose/current paragraph to the shared REPL record (Literate send) without sending it to the runtime.'>Add note</button>"
11631
11765
  + "<button type='button' data-repl-action='refresh'>Refresh</button>"
11632
11766
  + "<button type='button' data-repl-action='follow'>Follow: " + (replFollow ? "On" : "Off") + "</button>"
11633
11767
  + "</div>"
@@ -11845,7 +11979,7 @@
11845
11979
  const options = getFileBrowserSortOptions().map((option) => {
11846
11980
  return "<option value='" + escapeHtml(option.value) + "'" + (option.value === currentSort ? " selected" : "") + ">" + escapeHtml(option.label) + "</option>";
11847
11981
  }).join("");
11848
- return "<select class='files-sort-select' data-files-sort aria-label='Files sort order' title='Sort file browser entries. Folders remain grouped first.'>" + options + "</select>";
11982
+ return "<select class='files-sort-select studio-flat-select' data-files-sort aria-label='Files sort order' title='Sort file browser entries. Folders remain grouped first.'>" + options + "</select>";
11849
11983
  }
11850
11984
 
11851
11985
  function buildFileBrowserEntryRowHtml(entry) {
@@ -11900,7 +12034,7 @@
11900
12034
  const label = String((location && location.label) || basenameForStudioPath(path) || path);
11901
12035
  return "<option value='" + escapeHtml(path) + "'" + (path === rootDir ? " selected" : "") + ">" + escapeHtml(label + " — " + path) + "</option>";
11902
12036
  }).join("");
11903
- return "<select class='files-location-select' data-files-location aria-label='Allowed Files location' title='Choose a folder allowed for this Studio session on the computer running Pi.'>" + options + "</select>";
12037
+ return "<select class='files-location-select studio-flat-select' data-files-location aria-label='Allowed Files location' title='Choose a folder allowed for this Studio session on the computer running Pi.'>" + options + "</select>";
11904
12038
  }
11905
12039
 
11906
12040
  function buildFileBrowserPanelHtml() {
@@ -12990,6 +13124,49 @@
12990
13124
  return values.map(([value, label]) => "<option value='" + escapeHtml(value) + "'" + (value === current ? " selected" : "") + ">" + escapeHtml(label) + "</option>").join("");
12991
13125
  }
12992
13126
 
13127
+ function clampSideQuestionThinkingLevel(value, levels) {
13128
+ const available = Array.isArray(levels) && levels.length ? levels : [...SIDE_QUESTION_THINKING_LEVELS];
13129
+ if (available.includes(value)) return value;
13130
+ const requestedIndex = SIDE_QUESTION_THINKING_LEVELS.indexOf(value);
13131
+ if (requestedIndex < 0) return available.includes("low") ? "low" : available[0];
13132
+ for (let index = requestedIndex; index < SIDE_QUESTION_THINKING_LEVELS.length; index += 1) {
13133
+ const candidate = SIDE_QUESTION_THINKING_LEVELS[index];
13134
+ if (available.includes(candidate)) return candidate;
13135
+ }
13136
+ for (let index = requestedIndex - 1; index >= 0; index -= 1) {
13137
+ const candidate = SIDE_QUESTION_THINKING_LEVELS[index];
13138
+ if (available.includes(candidate)) return candidate;
13139
+ }
13140
+ return "off";
13141
+ }
13142
+
13143
+ function applySideQuestionThinkingLevels(value) {
13144
+ const seen = new Set();
13145
+ const normalized = (Array.isArray(value) ? value : []).filter((level) => {
13146
+ if (typeof level !== "string" || !SIDE_QUESTION_THINKING_LEVELS.includes(level) || seen.has(level)) return false;
13147
+ seen.add(level);
13148
+ return true;
13149
+ });
13150
+ const next = normalized.length ? normalized : [...SIDE_QUESTION_THINKING_LEVELS];
13151
+ const levelsChanged = next.length !== sideQuestionThinkingLevels.length
13152
+ || next.some((level, index) => level !== sideQuestionThinkingLevels[index]);
13153
+ sideQuestionThinkingLevels = next;
13154
+ const nextThinking = clampSideQuestionThinkingLevel(sideQuestionPreferredThinking, next);
13155
+ const thinkingChanged = nextThinking !== sideQuestionUi.thinking;
13156
+ if (thinkingChanged) sideQuestionUi.thinking = nextThinking;
13157
+ if ((levelsChanged || thinkingChanged) && rightView === "side-questions" && (!sideQuestionState || !sideQuestionState.threadId)) {
13158
+ renderSideQuestionView();
13159
+ }
13160
+ }
13161
+
13162
+ function getSideQuestionThinkingOptions() {
13163
+ return sideQuestionThinkingLevels.map((level) => {
13164
+ if (level === "xhigh") return [level, "X-high (slower)"];
13165
+ if (level === "max") return [level, "Max (slowest)"];
13166
+ return [level, level.charAt(0).toUpperCase() + level.slice(1)];
13167
+ });
13168
+ }
13169
+
12993
13170
  function renderSideQuestionPiToolPicker() {
12994
13171
  const selected = new Set(sideQuestionUi.toolIds);
12995
13172
  const selectedCount = sideQuestionUi.toolIds.length;
@@ -13026,15 +13203,15 @@
13026
13203
  return "<div class='side-question-empty'>"
13027
13204
  + "<div class='side-question-intro'><h2>Side question</h2><p>Ask something without adding it to the main Pi conversation. Choose the starting text and whether Studio may look through related files.</p></div>"
13028
13205
  + "<div class='side-question-context-grid'>"
13029
- + "<label>Starting text<select data-side-question-field='focusMode' aria-describedby='sideQuestionContextRule'>" + sideQuestionSelectOptions([
13206
+ + "<label>Starting text<select class='studio-flat-select' data-side-question-field='focusMode' aria-describedby='sideQuestionContextRule'>" + sideQuestionSelectOptions([
13030
13207
  ["auto", "Automatic"], ["selection", "Editor selection only"], ["section", "Heading block or nearby text at cursor"], ["editor", "Whole editor document"], ["response", "Displayed response"], ["none", "No starting text"],
13031
13208
  ], sideQuestionUi.focusMode) + "</select></label>"
13032
- + "<label>Also use files from<select data-side-question-field='gatherScope'>" + sideQuestionSelectOptions([
13209
+ + "<label>Also use files from<select class='studio-flat-select' data-side-question-field='gatherScope'>" + sideQuestionSelectOptions([
13033
13210
  ["none", "No other files"], ["folder", "Same folder as document"], ["repo", "Repository"], ["custom", "Choose a folder"],
13034
13211
  ], scope) + "</select></label>"
13035
- + "<label>Thinking<select data-side-question-field='thinking'>" + sideQuestionSelectOptions([
13036
- ["off", "Off"], ["minimal", "Minimal"], ["low", "Low"], ["medium", "Medium"], ["high", "High"],
13037
- ], sideQuestionUi.thinking) + "</select></label>"
13212
+ + "<label>Thinking<select class='studio-flat-select' data-side-question-field='thinking'>" + sideQuestionSelectOptions(
13213
+ getSideQuestionThinkingOptions(), sideQuestionUi.thinking
13214
+ ) + "</select></label>"
13038
13215
  + "</div>"
13039
13216
  + "<details class='side-question-context-rule'><summary id='sideQuestionContextRule'>Automatic: selection → heading block at cursor → nearby text</summary><p>A heading block starts at the nearest Markdown/LaTeX heading above the cursor and ends before the next heading of the same or higher level. With no heading, Studio uses the surrounding text block or a nearby excerpt.</p></details>"
13040
13217
  + (scope === "none" ? "" : "<p class='side-question-context-access-note'>Related-file access is limited to folders allowed for this Studio session. Studio asks before starting if this folder is not already allowed.</p>")
@@ -13392,9 +13569,10 @@
13392
13569
  if (sideQuestionUi.gatherScope !== "repo") sideQuestionUi.gitContext = false;
13393
13570
  try { if (window.localStorage) window.localStorage.setItem(SIDE_QUESTION_GATHER_STORAGE_KEY, sideQuestionUi.gatherScope); } catch {}
13394
13571
  }
13395
- if (field === "thinking") {
13572
+ if (field === "thinking" && sideQuestionThinkingLevels.includes(target.value)) {
13573
+ sideQuestionPreferredThinking = target.value;
13396
13574
  sideQuestionUi.thinking = target.value;
13397
- try { if (window.localStorage) window.localStorage.setItem(SIDE_QUESTION_THINKING_STORAGE_KEY, sideQuestionUi.thinking); } catch {}
13575
+ try { if (window.localStorage) window.localStorage.setItem(SIDE_QUESTION_THINKING_STORAGE_KEY, sideQuestionPreferredThinking); } catch {}
13398
13576
  }
13399
13577
  if (field === "includeConversation") sideQuestionUi.includeConversation = target.checked;
13400
13578
  if (field === "gitContext") sideQuestionUi.gitContext = target.checked && getSideQuestionGatherScope() === "repo";
@@ -13604,7 +13782,7 @@
13604
13782
  } else if (exportingSideThread) {
13605
13783
  exportPdfBtn.title = "Save or copy Markdown, open it in an editor, or export the visible side discussion as PDF or HTML.";
13606
13784
  } else if (exportingReplJournal && !replJournalExportEntries.length) {
13607
- exportPdfBtn.title = "No Studio REPL record entries to export for this session yet.";
13785
+ exportPdfBtn.title = "No shared REPL record entries to export for this session yet.";
13608
13786
  } else if (rightView === "markdown") {
13609
13787
  exportPdfBtn.title = "Switch right pane to Response (Preview), Editor (Preview), REPL, or Side questions to export.";
13610
13788
  } else if (!canExportPreview) {
@@ -13612,7 +13790,7 @@
13612
13790
  } else if (isHtmlArtifactPreview) {
13613
13791
  exportPdfBtn.title = "This is an interactive HTML preview. Export as HTML; PDF export is not available yet.";
13614
13792
  } else if (exportingReplJournal) {
13615
- exportPdfBtn.title = "Choose PDF export or an HTML export destination for the Studio REPL record.";
13793
+ exportPdfBtn.title = "Choose PDF export or an HTML export destination for the shared REPL record.";
13616
13794
  } else {
13617
13795
  exportPdfBtn.title = "Choose PDF export or an HTML export destination for the current right-pane preview.";
13618
13796
  }
@@ -13624,7 +13802,7 @@
13624
13802
  ? "This transcript is too large for rendered export; save or copy the Markdown instead."
13625
13803
  : (isHtmlArtifactPreview
13626
13804
  ? "Interactive HTML preview PDF export is not available yet."
13627
- : (exportingSideThread ? "Export the side-thread transcript as PDF and open it in Studio." : (exportingReplJournal ? "Export the Studio REPL record as PDF and open it in Studio." : "Export the current right-pane preview as PDF and open it in Studio.")));
13805
+ : (exportingSideThread ? "Export the side-thread transcript as PDF and open it in Studio." : (exportingReplJournal ? "Export the shared REPL record as PDF and open it in Studio." : "Export the current right-pane preview as PDF and open it in Studio.")));
13628
13806
  }
13629
13807
  if (exportPreviewPdfBtn) {
13630
13808
  exportPreviewPdfBtn.disabled = exportBusy || !canExportPreview || isHtmlArtifactPreview || sideThreadRenderTooLarge;
@@ -13632,7 +13810,7 @@
13632
13810
  ? "This transcript is too large for rendered export; save or copy the Markdown instead."
13633
13811
  : (isHtmlArtifactPreview
13634
13812
  ? "Interactive HTML preview PDF export is not available yet."
13635
- : (exportingSideThread ? "Export the side-thread transcript as PDF and open it in the default PDF viewer." : (exportingReplJournal ? "Export the Studio REPL record as PDF and open it in the default PDF viewer." : "Export the current right-pane preview as PDF and open it in the default PDF viewer.")));
13813
+ : (exportingSideThread ? "Export the side-thread transcript as PDF and open it in the default PDF viewer." : (exportingReplJournal ? "Export the shared REPL record as PDF and open it in the default PDF viewer." : "Export the current right-pane preview as PDF and open it in the default PDF viewer.")));
13636
13814
  }
13637
13815
  if (exportPreviewHtmlStudioBtn) {
13638
13816
  exportPreviewHtmlStudioBtn.disabled = exportBusy || !canExportPreview || sideThreadRenderTooLarge;
@@ -13640,7 +13818,7 @@
13640
13818
  ? "This transcript is too large for rendered export; save or copy the Markdown instead."
13641
13819
  : (isHtmlArtifactPreview
13642
13820
  ? "Export the authored HTML preview and open it in a new Studio editor tab."
13643
- : (exportingSideThread ? "Export the side-thread transcript as standalone HTML and open it in a new Studio editor tab." : (exportingReplJournal ? "Export the Studio REPL record as standalone HTML and open it in a new Studio editor tab." : "Export the current right-pane preview as standalone HTML and open it in a new Studio editor tab.")));
13821
+ : (exportingSideThread ? "Export the side-thread transcript as standalone HTML and open it in a new Studio editor tab." : (exportingReplJournal ? "Export the shared REPL record as standalone HTML and open it in a new Studio editor tab." : "Export the current right-pane preview as standalone HTML and open it in a new Studio editor tab.")));
13644
13822
  }
13645
13823
  if (exportPreviewHtmlBtn) {
13646
13824
  exportPreviewHtmlBtn.disabled = exportBusy || !canExportPreview || sideThreadRenderTooLarge;
@@ -13648,7 +13826,7 @@
13648
13826
  ? "This transcript is too large for rendered export; save or copy the Markdown instead."
13649
13827
  : (isHtmlArtifactPreview
13650
13828
  ? "Export the authored HTML preview and open it in the default browser."
13651
- : (exportingSideThread ? "Export the side-thread transcript as standalone HTML and open it in the default browser." : (exportingReplJournal ? "Export the Studio REPL record as standalone HTML and open it in the default browser." : "Export the current right-pane preview as standalone HTML and open it in the default browser.")));
13829
+ : (exportingSideThread ? "Export the side-thread transcript as standalone HTML and open it in the default browser." : (exportingReplJournal ? "Export the shared REPL record as standalone HTML and open it in the default browser." : "Export the current right-pane preview as standalone HTML and open it in the default browser.")));
13652
13830
  }
13653
13831
  if (exportPreviewControlsEl) {
13654
13832
  exportPreviewControlsEl.hidden = rightView === "editor-quarto-preview";
@@ -13656,11 +13834,11 @@
13656
13834
  ? (exportingSideThread
13657
13835
  ? "Choose a durable export for the visible side discussion."
13658
13836
  : (exportingReplJournal
13659
- ? "Choose a format and export destination for the Studio REPL record."
13837
+ ? "Choose a format and export destination for the shared REPL record."
13660
13838
  : (isHtmlArtifactPreview ? "Export this HTML preview to Studio or browser." : "Choose a format and export destination for the current right-pane preview.")))
13661
13839
  : (exportingSideThread
13662
13840
  ? "No completed side discussion is available to export yet."
13663
- : (exportingReplJournal ? "No Studio REPL record entries to export for this session yet." : "Switch right pane to a non-empty preview before exporting."));
13841
+ : (exportingReplJournal ? "No shared REPL record entries to export for this session yet." : "Switch right pane to a non-empty preview before exporting."));
13664
13842
  }
13665
13843
  if (!canExportPreview || previewExportInProgress || sideQuestionMarkdownExportRequest) {
13666
13844
  closeExportPreviewMenu();
@@ -16321,13 +16499,13 @@
16321
16499
  return "<div class='studio-quiz-setup'>"
16322
16500
  + "<p class='studio-quiz-copy'>A short active-recall loop: answer one question, check it, ask about the card if useful, then move on.</p>"
16323
16501
  + "<div class='studio-quiz-fields'>"
16324
- + "<label>Scope<select data-quiz-field='scope'>"
16502
+ + "<label>Scope<select class='studio-flat-select' data-quiz-field='scope'>"
16325
16503
  + QUIZ_SCOPES.map((candidate) => candidate === "selection" && !hasSelection ? "" : renderQuizOption(candidate, scope, getQuizScopeLabel(candidate))).join("")
16326
16504
  + "</select></label>"
16327
- + "<label>Angle<select data-quiz-field='angle'>"
16505
+ + "<label>Angle<select class='studio-flat-select' data-quiz-field='angle'>"
16328
16506
  + QUIZ_ANGLES.map((candidate) => renderQuizOption(candidate, angle, getQuizAngleLabel(candidate))).join("")
16329
16507
  + "</select></label>"
16330
- + "<label>Thinking<select data-quiz-field='thinking'>"
16508
+ + "<label>Thinking<select class='studio-flat-select' data-quiz-field='thinking'>"
16331
16509
  + QUIZ_THINKING_LEVELS.map((candidate) => renderQuizOption(candidate, thinking, getQuizThinkingLabel(candidate))).join("")
16332
16510
  + "</select></label>"
16333
16511
  + "<label>Questions<input data-quiz-field='count' type='number' min='1' max='8' value='" + String(count) + "'></label>"
@@ -22490,6 +22668,7 @@
22490
22668
  }
22491
22669
 
22492
22670
  if (message.type === "side_question_state") {
22671
+ if (Array.isArray(message.sideQuestionThinkingLevels)) applySideQuestionThinkingLevels(message.sideQuestionThinkingLevels);
22493
22672
  sideQuestionWebSearchAvailable = message.webSearchAvailable === true;
22494
22673
  if (Array.isArray(message.availablePiTools)) applySideQuestionToolCatalog(message.availablePiTools);
22495
22674
  const previousStatus = sideQuestionState && sideQuestionState.status;
@@ -22837,6 +23016,7 @@
22837
23016
  setActiveReplSessionForCurrentRuntime(message.activeSessionName);
22838
23017
  }
22839
23018
  const journalChanged = mergeReplJournalEntries(message.journalEntries);
23019
+ maybeImportLegacyReplJournalEntries(replActiveSessionName);
22840
23020
  if (typeof message.transcript === "string") replTranscript = trimReplTranscript(message.transcript);
22841
23021
  if (typeof message.capturedAt === "number") replCapturedAt = message.capturedAt;
22842
23022
  replError = typeof message.replError === "string" ? message.replError : (typeof message.captureError === "string" ? message.captureError : "");
@@ -22890,6 +23070,7 @@
22890
23070
  setActiveReplSessionForCurrentRuntime(message.activeSessionName);
22891
23071
  }
22892
23072
  let journalChanged = mergeReplJournalEntries(message.journalEntries);
23073
+ maybeImportLegacyReplJournalEntries(replActiveSessionName);
22893
23074
  if (typeof message.transcript === "string") {
22894
23075
  replTranscript = trimReplTranscript(message.transcript);
22895
23076
  journalChanged = updateActiveReplJournalEntryFromTranscript(
@@ -22914,6 +23095,19 @@
22914
23095
  return;
22915
23096
  }
22916
23097
 
23098
+ if (message.type === "repl_journal_ack") {
23099
+ const sessionName = typeof message.sessionName === "string" ? message.sessionName : "";
23100
+ if (sessionName) replJournalImportPendingSessions.delete(sessionName);
23101
+ if (message.cleared && sessionName) {
23102
+ replJournalEntries = replJournalEntries.filter((entry) => entry.sessionName !== sessionName);
23103
+ persistReplJournalEntries();
23104
+ } else {
23105
+ mergeReplJournalEntries(message.journalEntries);
23106
+ }
23107
+ renderReplViewIfActive({ force: true });
23108
+ return;
23109
+ }
23110
+
22917
23111
  if (message.type === "repl_send_ack") {
22918
23112
  replBusy = false;
22919
23113
  replMessage = "";