pi-studio 0.9.48 → 0.9.50

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.
@@ -116,6 +116,10 @@
116
116
  const sendEditorBtn = document.getElementById("sendEditorBtn");
117
117
  const openCompanionBtn = document.getElementById("openCompanionBtn");
118
118
  const getEditorBtn = document.getElementById("getEditorBtn");
119
+ const studioHeaderEl = document.getElementById("studioHeader");
120
+ const hideStudioHeaderBtn = document.getElementById("hideStudioHeaderBtn");
121
+ const studioHeaderRevealEl = document.getElementById("studioHeaderReveal");
122
+ const studioHeaderRevealBtn = document.getElementById("studioHeaderRevealBtn");
119
123
  const zenModeBtn = document.getElementById("zenModeBtn");
120
124
  const sendRunBtn = document.getElementById("sendRunBtn");
121
125
  const queueSteerBtn = document.getElementById("queueSteerBtn");
@@ -227,7 +231,10 @@
227
231
  if (
228
232
  !previewResourceHelpers
229
233
  || typeof previewResourceHelpers.areStudioPreviewResourceContextsEqual !== "function"
234
+ || typeof previewResourceHelpers.buildStudioPdfVersionSignature !== "function"
235
+ || typeof previewResourceHelpers.createStudioPdfVersionObservationState !== "function"
230
236
  || typeof previewResourceHelpers.hydrateStudioPreviewLocalImages !== "function"
237
+ || typeof previewResourceHelpers.observeStudioPdfVersion !== "function"
231
238
  ) {
232
239
  throw new Error("Studio preview resource helpers failed to load.");
233
240
  }
@@ -271,11 +278,17 @@
271
278
  let studioPdfFocusOpenLinkEl = null;
272
279
  let studioPdfFocusSystemViewerBtn = null;
273
280
  let studioPdfFocusRevealBtn = null;
281
+ let studioPdfFocusAutoRefreshBtn = null;
274
282
  let studioPdfFocusFullscreenBtn = null;
275
283
  let studioPdfFocusCloseBtn = null;
276
284
  let studioPdfFocusLastFocusedEl = null;
277
285
  let studioPdfFocusMovedFrameState = null;
278
286
  let studioPdfFocusResourceQuery = null;
287
+ let studioPdfFocusSourceCard = null;
288
+ let studioPdfFocusStandaloneAutoRefreshState = null;
289
+ const studioPdfCardAutoRefreshStates = new WeakMap();
290
+ const STUDIO_PDF_AUTO_REFRESH_INTERVAL_MS = 1_000;
291
+ const STUDIO_PDF_AUTO_REFRESH_STABLE_OBSERVATIONS = 2;
279
292
  let studioHtmlFocusOverlayEl = null;
280
293
  let studioHtmlFocusShellEl = null;
281
294
  let studioHtmlFocusFullscreenBtn = null;
@@ -2178,6 +2191,7 @@
2178
2191
  let lineNumbersRenderRaf = null;
2179
2192
  let annotationsEnabled = true;
2180
2193
  const STUDIO_ZEN_MODE_STORAGE_KEY = "piStudio.zenMode";
2194
+ const STUDIO_HEADER_HIDDEN_STORAGE_KEY = "piStudio.headerHidden";
2181
2195
  const studioUiRefreshEnabled = readStudioUiRefreshEnabled();
2182
2196
  const EDITOR_FONT_SIZE_OPTIONS = [10, 11, 12, 13, 14, 15, 16, 18];
2183
2197
  const RESPONSE_FONT_SIZE_OPTIONS = [11, 12, 12.5, 13, 13.5, 14, 14.5, 15, 15.5, 16, 18, 20];
@@ -2230,12 +2244,17 @@
2230
2244
  let fileBrowserLoadNonce = 0;
2231
2245
  let studioUiRefreshUi = null;
2232
2246
  let studioZenModeEnabled = readStudioZenModeEnabled();
2247
+ let studioHeaderHidden = readStudioHeaderHiddenEnabled();
2248
+ let studioHeaderRevealTimer = null;
2233
2249
  if (studioUiRefreshEnabled && document.body) {
2234
2250
  document.body.classList.add("studio-ui-refresh");
2235
2251
  }
2236
2252
  if (studioZenModeEnabled && document.body) {
2237
2253
  document.body.classList.add("studio-zen-mode");
2238
2254
  }
2255
+ if ((studioZenModeEnabled || studioHeaderHidden) && document.body) {
2256
+ document.body.classList.add("studio-header-hidden");
2257
+ }
2239
2258
  let scratchpadText = "";
2240
2259
  let scratchpadReturnFocusEl = null;
2241
2260
  let scratchpadPersistTimer = null;
@@ -2299,22 +2318,129 @@
2299
2318
  }
2300
2319
  }
2301
2320
 
2302
- function syncStudioZenModeUi() {
2303
- if (document.body) document.body.classList.toggle("studio-zen-mode", studioZenModeEnabled);
2304
- if (!zenModeBtn) return;
2305
- zenModeBtn.textContent = studioZenModeEnabled ? "Exit Zen" : "Zen";
2306
- zenModeBtn.title = studioZenModeEnabled ? "Show full Studio controls. Shortcut: F9." : "Hide secondary Studio controls. Shortcut: F9.";
2307
- zenModeBtn.setAttribute("aria-pressed", studioZenModeEnabled ? "true" : "false");
2321
+ function readStudioHeaderHiddenEnabled() {
2322
+ const normalize = (value) => String(value == null ? "" : value).trim().toLowerCase();
2323
+ const isTruthy = (value) => ["1", "true", "yes", "on", "hidden"].indexOf(normalize(value)) !== -1;
2324
+ try {
2325
+ const stored = window.localStorage ? window.localStorage.getItem(STUDIO_HEADER_HIDDEN_STORAGE_KEY) : null;
2326
+ if (stored === null) return false;
2327
+ return isTruthy(stored);
2328
+ } catch {
2329
+ return false;
2330
+ }
2308
2331
  }
2309
2332
 
2310
- function setStudioZenMode(enabled) {
2311
- studioZenModeEnabled = Boolean(enabled);
2333
+ function persistStudioHeaderHiddenEnabled() {
2334
+ try {
2335
+ window.localStorage && window.localStorage.setItem(STUDIO_HEADER_HIDDEN_STORAGE_KEY, studioHeaderHidden ? "1" : "0");
2336
+ } catch {}
2337
+ }
2338
+
2339
+ function focusStudioChromeControl(control) {
2340
+ if (!control || typeof control.focus !== "function") return;
2341
+ window.setTimeout(() => {
2342
+ try {
2343
+ control.focus({ preventScroll: true });
2344
+ } catch {
2345
+ try { control.focus(); } catch {}
2346
+ }
2347
+ }, 0);
2348
+ }
2349
+
2350
+ function showStudioHeaderRevealControl() {
2351
+ if (!studioHeaderRevealEl || !studioHeaderRevealBtn) return;
2352
+ studioHeaderRevealEl.classList.add("is-open");
2353
+ if (studioHeaderRevealTimer) window.clearTimeout(studioHeaderRevealTimer);
2354
+ studioHeaderRevealTimer = window.setTimeout(() => {
2355
+ studioHeaderRevealTimer = null;
2356
+ studioHeaderRevealEl.classList.remove("is-open");
2357
+ }, 1800);
2358
+ focusStudioChromeControl(studioHeaderRevealBtn);
2359
+ }
2360
+
2361
+ function syncStudioHeaderVisibilityUi() {
2362
+ const hidden = Boolean(studioZenModeEnabled || studioHeaderHidden);
2363
+ if (document.body) document.body.classList.toggle("studio-header-hidden", hidden);
2364
+ if (studioHeaderRevealEl) {
2365
+ studioHeaderRevealEl.hidden = !hidden;
2366
+ studioHeaderRevealEl.dataset.mode = studioZenModeEnabled ? "zen" : "header";
2367
+ if (!hidden) studioHeaderRevealEl.classList.remove("is-open");
2368
+ }
2369
+ if (!hidden && studioHeaderRevealTimer) {
2370
+ window.clearTimeout(studioHeaderRevealTimer);
2371
+ studioHeaderRevealTimer = null;
2372
+ }
2373
+ if (studioHeaderRevealBtn) {
2374
+ const label = studioZenModeEnabled ? "Exit Zen" : "Show header";
2375
+ studioHeaderRevealBtn.textContent = label;
2376
+ studioHeaderRevealBtn.setAttribute("aria-label", label);
2377
+ studioHeaderRevealBtn.title = studioZenModeEnabled
2378
+ ? "Exit Zen and restore the Studio header. Shortcut: F9."
2379
+ : "Restore the Studio header.";
2380
+ }
2381
+ }
2382
+
2383
+ function syncStudioZenModeUi() {
2384
+ if (document.body) document.body.classList.toggle("studio-zen-mode", studioZenModeEnabled);
2385
+ if (zenModeBtn) {
2386
+ zenModeBtn.textContent = studioZenModeEnabled ? "Exit Zen" : "Zen";
2387
+ zenModeBtn.title = studioZenModeEnabled
2388
+ ? "Exit Zen and restore the Studio header. Shortcut: F9."
2389
+ : "Hide the Studio header and secondary controls. Shortcut: F9.";
2390
+ zenModeBtn.setAttribute("aria-pressed", studioZenModeEnabled ? "true" : "false");
2391
+ }
2392
+ syncStudioHeaderVisibilityUi();
2393
+ }
2394
+
2395
+ function setStudioHeaderHidden(enabled, options) {
2396
+ const nextHidden = Boolean(enabled);
2397
+ const moveFocusToReveal = Boolean(
2398
+ nextHidden
2399
+ && (
2400
+ (options && options.focusReveal)
2401
+ || (studioHeaderEl && document.activeElement && studioHeaderEl.contains(document.activeElement))
2402
+ )
2403
+ );
2404
+ studioHeaderHidden = nextHidden;
2405
+ persistStudioHeaderHiddenEnabled();
2406
+ closeStudioUiRefreshMenus();
2407
+ closeExportPreviewMenu();
2408
+ syncStudioHeaderVisibilityUi();
2409
+ if (moveFocusToReveal) showStudioHeaderRevealControl();
2410
+ }
2411
+
2412
+ function setStudioZenMode(enabled, options) {
2413
+ const nextEnabled = Boolean(enabled);
2414
+ const moveFocusToReveal = Boolean(
2415
+ nextEnabled
2416
+ && (
2417
+ (options && options.focusReveal)
2418
+ || (studioHeaderEl && document.activeElement && studioHeaderEl.contains(document.activeElement))
2419
+ )
2420
+ );
2421
+ studioZenModeEnabled = nextEnabled;
2422
+ if (!studioZenModeEnabled) {
2423
+ studioHeaderHidden = false;
2424
+ persistStudioHeaderHiddenEnabled();
2425
+ }
2312
2426
  try {
2313
2427
  window.localStorage && window.localStorage.setItem(STUDIO_ZEN_MODE_STORAGE_KEY, studioZenModeEnabled ? "1" : "0");
2314
2428
  } catch {}
2315
2429
  closeStudioUiRefreshMenus();
2316
2430
  closeExportPreviewMenu();
2317
2431
  syncStudioZenModeUi();
2432
+ if (moveFocusToReveal) showStudioHeaderRevealControl();
2433
+ }
2434
+
2435
+ function restoreStudioHeaderFromReveal() {
2436
+ const wasZenMode = studioZenModeEnabled;
2437
+ if (wasZenMode) {
2438
+ setStudioZenMode(false);
2439
+ } else {
2440
+ setStudioHeaderHidden(false);
2441
+ }
2442
+ focusStudioChromeControl(wasZenMode ? zenModeBtn : hideStudioHeaderBtn);
2443
+ setStatus(wasZenMode ? "Zen mode off. Studio header restored." : "Studio header shown.");
2318
2444
  }
2319
2445
 
2320
2446
  function makeStudioUiRefreshElement(tagName, className, text) {
@@ -4489,6 +4615,15 @@
4489
4615
 
4490
4616
  if (handleStudioImageFocusShortcut(event)) return;
4491
4617
 
4618
+ const otherModalOwnsEvent = scratchpadOwnsEvent
4619
+ || reviewNotesOwnsEvent
4620
+ || outlineOwnsEvent
4621
+ || shortcutsOwnsEvent
4622
+ || htmlFocusOwnsEvent
4623
+ || imageFocusOwnsEvent
4624
+ || quizOwnsEvent;
4625
+ if (!otherModalOwnsEvent && handleStudioPdfRefreshShortcut(event)) return;
4626
+
4492
4627
  if (isScratchpadOpen() && plainEscape) {
4493
4628
  event.preventDefault();
4494
4629
  closeScratchpad();
@@ -4640,8 +4775,17 @@
4640
4775
  const isZenModeShortcut = key === "F9" && !event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
4641
4776
  if (isZenModeShortcut) {
4642
4777
  event.preventDefault();
4643
- setStudioZenMode(!studioZenModeEnabled);
4644
- setStatus(studioZenModeEnabled ? "Zen mode on." : "Zen mode off.");
4778
+ const restoringHeaderOnly = Boolean(!studioZenModeEnabled && studioHeaderHidden);
4779
+ if (studioZenModeEnabled) {
4780
+ setStudioZenMode(false);
4781
+ } else if (studioHeaderHidden) {
4782
+ setStudioHeaderHidden(false);
4783
+ } else {
4784
+ setStudioZenMode(true);
4785
+ }
4786
+ setStatus(studioZenModeEnabled
4787
+ ? "Zen mode on. Studio header hidden; press F9 to exit."
4788
+ : (restoringHeaderOnly ? "Studio header restored." : "Zen mode off. Studio header restored."));
4645
4789
  return;
4646
4790
  }
4647
4791
 
@@ -6851,7 +6995,7 @@
6851
6995
  }
6852
6996
 
6853
6997
  function parseStudioPdfBlockOptions(body) {
6854
- const options = { path: "", title: "", caption: "", page: "", height: "" };
6998
+ const options = { path: "", title: "", caption: "", page: "", height: "", watch: "" };
6855
6999
  String(body || "").split(/\r?\n/).forEach((line) => {
6856
7000
  const raw = String(line || "").trim();
6857
7001
  if (!raw || raw.startsWith("#")) return;
@@ -6864,6 +7008,7 @@
6864
7008
  else if (key === "caption") options.caption = value;
6865
7009
  else if (key === "page") options.page = value;
6866
7010
  else if (key === "height") options.height = value;
7011
+ else if (key === "watch" || key === "auto-refresh" || key === "autorefresh") options.watch = value;
6867
7012
  return;
6868
7013
  }
6869
7014
  if (!options.path) options.path = stripMatchingQuotes(raw);
@@ -6895,6 +7040,10 @@
6895
7040
  return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
6896
7041
  }
6897
7042
 
7043
+ function normalizeStudioPdfAutoRefresh(value) {
7044
+ return /^(?:1|true|yes|on|watch)$/i.test(String(value || "").trim());
7045
+ }
7046
+
6898
7047
  function isStudioPdfFocusOpen() {
6899
7048
  return Boolean(studioPdfFocusOverlayEl && studioPdfFocusOverlayEl.hidden === false);
6900
7049
  }
@@ -6969,11 +7118,21 @@
6969
7118
  refreshBtn.type = "button";
6970
7119
  refreshBtn.className = "studio-pdf-focus-btn studio-pdf-focus-refresh";
6971
7120
  refreshBtn.textContent = "Refresh";
6972
- refreshBtn.title = "Reload this PDF preview from disk.";
7121
+ refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+R.";
6973
7122
  refreshBtn.setAttribute("aria-label", "Refresh PDF preview from disk");
6974
7123
  refreshBtn.addEventListener("click", () => refreshStudioPdfFocusViewer());
6975
7124
  actions.appendChild(refreshBtn);
6976
7125
 
7126
+ const autoRefreshBtn = document.createElement("button");
7127
+ autoRefreshBtn.type = "button";
7128
+ autoRefreshBtn.className = "studio-pdf-focus-btn studio-pdf-focus-auto-refresh";
7129
+ autoRefreshBtn.textContent = "Auto-refresh";
7130
+ autoRefreshBtn.title = "Watch this local PDF and reload it after a changed file is stable on disk.";
7131
+ autoRefreshBtn.setAttribute("aria-label", "Enable PDF auto-refresh");
7132
+ autoRefreshBtn.setAttribute("aria-pressed", "false");
7133
+ autoRefreshBtn.addEventListener("click", () => toggleStudioPdfFocusAutoRefresh());
7134
+ actions.appendChild(autoRefreshBtn);
7135
+
6977
7136
  const fullscreenBtn = document.createElement("button");
6978
7137
  fullscreenBtn.type = "button";
6979
7138
  fullscreenBtn.className = "studio-pdf-focus-btn studio-pdf-focus-fullscreen";
@@ -7030,6 +7189,7 @@
7030
7189
  studioPdfFocusOpenLinkEl = openLink;
7031
7190
  studioPdfFocusSystemViewerBtn = systemViewerBtn;
7032
7191
  studioPdfFocusRevealBtn = revealBtn;
7192
+ studioPdfFocusAutoRefreshBtn = autoRefreshBtn;
7033
7193
  studioPdfFocusFullscreenBtn = fullscreenBtn;
7034
7194
  studioPdfFocusCloseBtn = closeBtn;
7035
7195
  syncStudioPdfFocusFullscreenButton();
@@ -7037,11 +7197,12 @@
7037
7197
  return overlay;
7038
7198
  }
7039
7199
 
7040
- function openStudioPdfFocusViewer(viewerUrl, title, sourceFrame, resourceQuery) {
7200
+ function openStudioPdfFocusViewer(viewerUrl, title, sourceFrame, resourceQuery, sourceCard) {
7041
7201
  const src = String(viewerUrl || "").trim();
7042
7202
  if (!src) return;
7043
7203
  ensureStudioPdfFocusViewer();
7044
7204
  studioPdfFocusResourceQuery = normalizeStudioPdfResourceQuery(resourceQuery);
7205
+ setStudioPdfFocusAutoRefreshSource(sourceCard, studioPdfFocusResourceQuery);
7045
7206
  syncStudioPdfFocusResourceActions();
7046
7207
  studioPdfFocusLastFocusedEl = document.activeElement instanceof HTMLElement ? document.activeElement : null;
7047
7208
  if (studioPdfFocusTitleEl) studioPdfFocusTitleEl.textContent = String(title || "PDF preview").trim() || "PDF preview";
@@ -7071,6 +7232,7 @@
7071
7232
  restoreStudioPdfFocusMovedFrame();
7072
7233
  if (studioPdfFocusFrameEl) studioPdfFocusFrameEl.src = "about:blank";
7073
7234
  if (document.body) document.body.classList.remove("studio-pdf-focus-open");
7235
+ clearStudioPdfFocusAutoRefreshSource();
7074
7236
  studioPdfFocusResourceQuery = null;
7075
7237
  syncStudioPdfFocusResourceActions();
7076
7238
  syncStudioPdfFocusFullscreenButton();
@@ -7113,6 +7275,214 @@
7113
7275
  return query;
7114
7276
  }
7115
7277
 
7278
+ function syncStudioPdfAutoRefreshButton(button, enabled, available) {
7279
+ if (!button) return;
7280
+ const active = Boolean(enabled && available);
7281
+ button.disabled = !available;
7282
+ button.setAttribute("aria-pressed", active ? "true" : "false");
7283
+ button.setAttribute("aria-label", active ? "Disable PDF auto-refresh" : "Enable PDF auto-refresh");
7284
+ button.textContent = "Auto-refresh";
7285
+ button.title = active
7286
+ ? "Auto-refresh is on. Studio checks this PDF while the tab is visible and reloads it after a changed file is stable on disk."
7287
+ : "Watch this local PDF and reload it after a changed file is stable on disk.";
7288
+ }
7289
+
7290
+ function createStudioPdfAutoRefreshState(resourceQuery, hooks) {
7291
+ return {
7292
+ resourceQuery: normalizeStudioPdfResourceQuery(resourceQuery),
7293
+ enabled: false,
7294
+ timer: null,
7295
+ inFlight: false,
7296
+ generation: 0,
7297
+ observation: previewResourceHelpers.createStudioPdfVersionObservationState(),
7298
+ isAlive: hooks && typeof hooks.isAlive === "function" ? hooks.isAlive : () => false,
7299
+ refresh: hooks && typeof hooks.refresh === "function" ? hooks.refresh : () => false,
7300
+ sync: hooks && typeof hooks.sync === "function" ? hooks.sync : () => {},
7301
+ };
7302
+ }
7303
+
7304
+ function resetStudioPdfAutoRefreshObservation(state) {
7305
+ if (!state) return;
7306
+ state.observation = previewResourceHelpers.createStudioPdfVersionObservationState();
7307
+ }
7308
+
7309
+ function scheduleStudioPdfAutoRefreshPoll(state, delayMs) {
7310
+ if (!state || !state.enabled) return;
7311
+ if (state.timer) window.clearTimeout(state.timer);
7312
+ state.timer = window.setTimeout(() => {
7313
+ state.timer = null;
7314
+ void pollStudioPdfAutoRefreshState(state);
7315
+ }, Math.max(0, Number(delayMs) || 0));
7316
+ }
7317
+
7318
+ function stopStudioPdfAutoRefreshState(state) {
7319
+ if (!state) return;
7320
+ state.enabled = false;
7321
+ state.inFlight = false;
7322
+ state.generation += 1;
7323
+ if (state.timer) window.clearTimeout(state.timer);
7324
+ state.timer = null;
7325
+ resetStudioPdfAutoRefreshObservation(state);
7326
+ state.sync();
7327
+ }
7328
+
7329
+ function setStudioPdfAutoRefreshEnabled(state, enabled, options) {
7330
+ if (!state) return false;
7331
+ const nextEnabled = Boolean(enabled && state.resourceQuery);
7332
+ if (state.enabled === nextEnabled) {
7333
+ state.sync();
7334
+ return nextEnabled;
7335
+ }
7336
+ if (state.timer) window.clearTimeout(state.timer);
7337
+ state.timer = null;
7338
+ state.enabled = nextEnabled;
7339
+ state.inFlight = false;
7340
+ state.generation += 1;
7341
+ resetStudioPdfAutoRefreshObservation(state);
7342
+ state.sync();
7343
+ if (nextEnabled) {
7344
+ if (!options || !options.silent) state.refresh();
7345
+ scheduleStudioPdfAutoRefreshPoll(state, 0);
7346
+ }
7347
+ if (!options || !options.silent) {
7348
+ setStatus(nextEnabled ? "PDF auto-refresh on." : "PDF auto-refresh off.", "success");
7349
+ }
7350
+ return nextEnabled;
7351
+ }
7352
+
7353
+ async function fetchStudioPdfVersionSignature(resourceQuery) {
7354
+ const url = buildStudioPdfResourceUrl(resourceQuery, false);
7355
+ if (!url) throw new Error("Could not resolve this PDF for auto-refresh.");
7356
+ const response = await fetchWithTimeout(url, {
7357
+ method: "HEAD",
7358
+ cache: "no-store",
7359
+ }, 4_000, "PDF auto-refresh check");
7360
+ if (!response.ok) throw new Error("PDF auto-refresh check failed with HTTP " + response.status + ".");
7361
+ return previewResourceHelpers.buildStudioPdfVersionSignature(response.headers);
7362
+ }
7363
+
7364
+ async function pollStudioPdfAutoRefreshState(state) {
7365
+ if (!state || !state.enabled || state.inFlight) return;
7366
+ if (!state.isAlive()) {
7367
+ stopStudioPdfAutoRefreshState(state);
7368
+ return;
7369
+ }
7370
+ if (document.hidden || document.visibilityState === "hidden") {
7371
+ scheduleStudioPdfAutoRefreshPoll(state, STUDIO_PDF_AUTO_REFRESH_INTERVAL_MS);
7372
+ return;
7373
+ }
7374
+ const generation = state.generation;
7375
+ state.inFlight = true;
7376
+ try {
7377
+ const signature = await fetchStudioPdfVersionSignature(state.resourceQuery);
7378
+ if (!state.enabled || state.generation !== generation) return;
7379
+ if (document.hidden || document.visibilityState === "hidden") return;
7380
+ if (!state.isAlive()) {
7381
+ stopStudioPdfAutoRefreshState(state);
7382
+ return;
7383
+ }
7384
+ const observed = previewResourceHelpers.observeStudioPdfVersion(
7385
+ state.observation,
7386
+ signature,
7387
+ STUDIO_PDF_AUTO_REFRESH_STABLE_OBSERVATIONS,
7388
+ );
7389
+ state.observation = observed.state;
7390
+ if (observed.changed) state.refresh();
7391
+ } catch {
7392
+ // LaTeX tools may briefly replace or lock the output PDF. Retry quietly.
7393
+ } finally {
7394
+ if (state.generation !== generation) return;
7395
+ state.inFlight = false;
7396
+ if (state.enabled) scheduleStudioPdfAutoRefreshPoll(state, STUDIO_PDF_AUTO_REFRESH_INTERVAL_MS);
7397
+ }
7398
+ }
7399
+
7400
+ function syncStudioPdfCardAutoRefreshButton(card) {
7401
+ if (!card) return;
7402
+ const state = studioPdfCardAutoRefreshStates.get(card) || null;
7403
+ const button = typeof card.querySelector === "function" ? card.querySelector(".studio-pdf-card-auto-refresh") : null;
7404
+ syncStudioPdfAutoRefreshButton(button, state && state.enabled, Boolean(state && state.resourceQuery));
7405
+ }
7406
+
7407
+ function ensureStudioPdfCardAutoRefreshState(card, resourceQuery) {
7408
+ if (!card) return null;
7409
+ let state = studioPdfCardAutoRefreshStates.get(card) || null;
7410
+ if (state) return state;
7411
+ state = createStudioPdfAutoRefreshState(resourceQuery, {
7412
+ isAlive: () => Boolean(card.isConnected || (isStudioPdfFocusOpen() && studioPdfFocusSourceCard === card)),
7413
+ refresh: () => {
7414
+ if (isStudioPdfFocusOpen() && studioPdfFocusSourceCard === card) {
7415
+ return refreshStudioPdfFocusViewer({ automatic: true });
7416
+ }
7417
+ return refreshStudioPdfCard(card, { automatic: true });
7418
+ },
7419
+ sync: () => {
7420
+ syncStudioPdfCardAutoRefreshButton(card);
7421
+ if (studioPdfFocusSourceCard === card) syncStudioPdfFocusAutoRefreshButton();
7422
+ },
7423
+ });
7424
+ studioPdfCardAutoRefreshStates.set(card, state);
7425
+ syncStudioPdfCardAutoRefreshButton(card);
7426
+ return state;
7427
+ }
7428
+
7429
+ function setStudioPdfCardAutoRefresh(card, enabled, options) {
7430
+ const state = studioPdfCardAutoRefreshStates.get(card) || null;
7431
+ return setStudioPdfAutoRefreshEnabled(state, enabled, options);
7432
+ }
7433
+
7434
+ function getStudioPdfFocusAutoRefreshState() {
7435
+ if (studioPdfFocusSourceCard) return studioPdfCardAutoRefreshStates.get(studioPdfFocusSourceCard) || null;
7436
+ return studioPdfFocusStandaloneAutoRefreshState;
7437
+ }
7438
+
7439
+ function syncStudioPdfFocusAutoRefreshButton() {
7440
+ const state = getStudioPdfFocusAutoRefreshState();
7441
+ syncStudioPdfAutoRefreshButton(
7442
+ studioPdfFocusAutoRefreshBtn,
7443
+ state && state.enabled,
7444
+ Boolean(state && state.resourceQuery),
7445
+ );
7446
+ }
7447
+
7448
+ function clearStudioPdfFocusAutoRefreshSource() {
7449
+ if (studioPdfFocusStandaloneAutoRefreshState) stopStudioPdfAutoRefreshState(studioPdfFocusStandaloneAutoRefreshState);
7450
+ studioPdfFocusStandaloneAutoRefreshState = null;
7451
+ studioPdfFocusSourceCard = null;
7452
+ syncStudioPdfFocusAutoRefreshButton();
7453
+ }
7454
+
7455
+ function setStudioPdfFocusAutoRefreshSource(sourceCard, resourceQuery) {
7456
+ clearStudioPdfFocusAutoRefreshSource();
7457
+ const card = sourceCard instanceof Element ? sourceCard : null;
7458
+ if (card) {
7459
+ studioPdfFocusSourceCard = card;
7460
+ ensureStudioPdfCardAutoRefreshState(card, resourceQuery);
7461
+ syncStudioPdfFocusAutoRefreshButton();
7462
+ return;
7463
+ }
7464
+ const query = normalizeStudioPdfResourceQuery(resourceQuery);
7465
+ if (!query) {
7466
+ syncStudioPdfFocusAutoRefreshButton();
7467
+ return;
7468
+ }
7469
+ studioPdfFocusStandaloneAutoRefreshState = createStudioPdfAutoRefreshState(query, {
7470
+ isAlive: () => Boolean(isStudioPdfFocusOpen() && !studioPdfFocusSourceCard),
7471
+ refresh: () => refreshStudioPdfFocusViewer({ automatic: true }),
7472
+ sync: () => syncStudioPdfFocusAutoRefreshButton(),
7473
+ });
7474
+ syncStudioPdfFocusAutoRefreshButton();
7475
+ }
7476
+
7477
+ function toggleStudioPdfFocusAutoRefresh() {
7478
+ const state = getStudioPdfFocusAutoRefreshState();
7479
+ if (!state) {
7480
+ setStatus("Could not resolve this PDF for auto-refresh.", "warning");
7481
+ return false;
7482
+ }
7483
+ return setStudioPdfAutoRefreshEnabled(state, !state.enabled);
7484
+ }
7485
+
7116
7486
  async function runStudioPdfLocalAction(action, resourceQuery) {
7117
7487
  const query = normalizeStudioPdfResourceQuery(resourceQuery);
7118
7488
  if (!query) {
@@ -7174,7 +7544,7 @@
7174
7544
  if (focusBtn && focusBtn.dataset) focusBtn.dataset.studioPdfViewerUrl = nextUrl;
7175
7545
  }
7176
7546
 
7177
- function refreshStudioPdfCard(card) {
7547
+ function refreshStudioPdfCard(card, options) {
7178
7548
  if (!card) return false;
7179
7549
  const frame = typeof card.querySelector === "function" ? card.querySelector("iframe.studio-pdf-frame") : null;
7180
7550
  const currentUrl = String(card.dataset && card.dataset.studioPdfViewerUrl ? card.dataset.studioPdfViewerUrl : "").trim()
@@ -7182,7 +7552,14 @@
7182
7552
  const nextUrl = buildRefreshedStudioPdfViewerUrl(currentUrl);
7183
7553
  if (!nextUrl) return false;
7184
7554
  syncStudioPdfCardViewerUrl(card, nextUrl);
7185
- setStatus("Refreshed PDF preview from disk.", "success");
7555
+ if (!options || !options.automatic) {
7556
+ resetStudioPdfAutoRefreshObservation(studioPdfCardAutoRefreshStates.get(card) || null);
7557
+ }
7558
+ if (!options || !options.silent) {
7559
+ setStatus(options && options.automatic
7560
+ ? "PDF changed on disk; refreshed preview."
7561
+ : "Refreshed PDF preview from disk.", "success");
7562
+ }
7186
7563
  return true;
7187
7564
  }
7188
7565
 
@@ -7193,7 +7570,7 @@
7193
7570
  return studioPdfFocusFrameEl;
7194
7571
  }
7195
7572
 
7196
- function refreshStudioPdfFocusViewer() {
7573
+ function refreshStudioPdfFocusViewer(options) {
7197
7574
  const frame = getStudioPdfFocusActiveFrame();
7198
7575
  const currentUrl = String(frame && frame.src ? frame.src : "").trim()
7199
7576
  || String(studioPdfFocusOpenLinkEl && studioPdfFocusOpenLinkEl.href ? studioPdfFocusOpenLinkEl.href : "").trim();
@@ -7204,7 +7581,58 @@
7204
7581
  }
7205
7582
  if (frame) frame.src = nextUrl;
7206
7583
  if (studioPdfFocusOpenLinkEl) studioPdfFocusOpenLinkEl.href = nextUrl;
7207
- setStatus("Refreshed PDF preview from disk.", "success");
7584
+ if (studioPdfFocusSourceCard) syncStudioPdfCardViewerUrl(studioPdfFocusSourceCard, nextUrl);
7585
+ if (!options || !options.automatic) {
7586
+ resetStudioPdfAutoRefreshObservation(getStudioPdfFocusAutoRefreshState());
7587
+ }
7588
+ if (!options || !options.silent) {
7589
+ setStatus(options && options.automatic
7590
+ ? "PDF changed on disk; refreshed preview."
7591
+ : "Refreshed PDF preview from disk.", "success");
7592
+ }
7593
+ return true;
7594
+ }
7595
+
7596
+ function getVisibleStudioPdfCards() {
7597
+ return Array.from(document.querySelectorAll(".studio-pdf-card")).filter((card) => {
7598
+ if (!card || !card.isConnected || card.hidden) return false;
7599
+ if (typeof card.getClientRects === "function" && card.getClientRects().length === 0) return false;
7600
+ try {
7601
+ const style = window.getComputedStyle(card);
7602
+ return style.display !== "none" && style.visibility !== "hidden";
7603
+ } catch {
7604
+ return true;
7605
+ }
7606
+ });
7607
+ }
7608
+
7609
+ function refreshVisibleStudioPdfPreviews() {
7610
+ if (isStudioPdfFocusOpen()) return refreshStudioPdfFocusViewer();
7611
+ const cards = getVisibleStudioPdfCards();
7612
+ let refreshed = 0;
7613
+ cards.forEach((card) => {
7614
+ if (refreshStudioPdfCard(card, { silent: true })) refreshed += 1;
7615
+ });
7616
+ if (refreshed > 0) {
7617
+ setStatus(refreshed === 1
7618
+ ? "Refreshed PDF preview from disk."
7619
+ : ("Refreshed " + refreshed + " PDF previews from disk."), "success");
7620
+ return true;
7621
+ }
7622
+ return false;
7623
+ }
7624
+
7625
+ function handleStudioPdfRefreshShortcut(event) {
7626
+ if (!event) return false;
7627
+ const key = typeof event.key === "string" ? event.key.toLowerCase() : "";
7628
+ const code = typeof event.code === "string" ? event.code : "";
7629
+ const matches = (key === "r" || code === "KeyR")
7630
+ && (event.metaKey || event.ctrlKey)
7631
+ && event.altKey
7632
+ && !event.shiftKey;
7633
+ if (!matches) return false;
7634
+ if (!refreshVisibleStudioPdfPreviews()) return false;
7635
+ event.preventDefault();
7208
7636
  return true;
7209
7637
  }
7210
7638
 
@@ -7285,7 +7713,7 @@
7285
7713
  })
7286
7714
  : null;
7287
7715
  if (!viewerUrl) return false;
7288
- openStudioPdfFocusViewer(viewerUrl, title, sourceFrame, resourceQuery);
7716
+ openStudioPdfFocusViewer(viewerUrl, title, sourceFrame, resourceQuery, card);
7289
7717
  return true;
7290
7718
  }
7291
7719
 
@@ -7698,6 +8126,7 @@
7698
8126
  const caption = String(options.caption || "").trim();
7699
8127
  const height = normalizeStudioPdfHeight(options.height);
7700
8128
  const page = normalizeStudioPdfPage(options.page);
8129
+ const autoRefreshRequested = normalizeStudioPdfAutoRefresh(options.watch);
7701
8130
  const resourceQuery = buildStudioPdfResourceQuery(options, useEditorResourceContext);
7702
8131
  const resourceUrl = buildStudioPdfResourceUrl(options, useEditorResourceContext);
7703
8132
  const viewerUrl = resourceUrl && page ? resourceUrl + "#page=" + encodeURIComponent(String(page)) : resourceUrl;
@@ -7778,7 +8207,7 @@
7778
8207
  refreshBtn.type = "button";
7779
8208
  refreshBtn.className = "studio-pdf-card-action studio-pdf-card-refresh";
7780
8209
  refreshBtn.textContent = "Refresh";
7781
- refreshBtn.title = "Reload this PDF preview from disk.";
8210
+ refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+R.";
7782
8211
  refreshBtn.addEventListener("click", (event) => {
7783
8212
  event.preventDefault();
7784
8213
  event.stopPropagation();
@@ -7786,6 +8215,25 @@
7786
8215
  });
7787
8216
  actions.appendChild(refreshBtn);
7788
8217
 
8218
+ const autoRefreshBtn = document.createElement("button");
8219
+ autoRefreshBtn.type = "button";
8220
+ autoRefreshBtn.className = "studio-pdf-card-action studio-pdf-card-auto-refresh";
8221
+ autoRefreshBtn.textContent = "Auto-refresh";
8222
+ autoRefreshBtn.title = "Watch this local PDF and reload it after a changed file is stable on disk.";
8223
+ autoRefreshBtn.setAttribute("aria-label", "Enable PDF auto-refresh");
8224
+ autoRefreshBtn.setAttribute("aria-pressed", "false");
8225
+ autoRefreshBtn.addEventListener("click", (event) => {
8226
+ event.preventDefault();
8227
+ event.stopPropagation();
8228
+ const state = ensureStudioPdfCardAutoRefreshState(card, resourceQuery);
8229
+ if (!state) {
8230
+ setStatus("Could not resolve this PDF for auto-refresh.", "warning");
8231
+ return;
8232
+ }
8233
+ setStudioPdfCardAutoRefresh(card, !state.enabled);
8234
+ });
8235
+ actions.appendChild(autoRefreshBtn);
8236
+
7789
8237
  header.appendChild(actions);
7790
8238
  }
7791
8239
  card.appendChild(header);
@@ -7812,6 +8260,12 @@
7812
8260
  iframe.loading = "lazy";
7813
8261
  iframe.style.height = height + "px";
7814
8262
  card.appendChild(iframe);
8263
+ const autoRefreshState = ensureStudioPdfCardAutoRefreshState(card, resourceQuery);
8264
+ if (autoRefreshRequested) {
8265
+ setStudioPdfAutoRefreshEnabled(autoRefreshState, true, { silent: true });
8266
+ } else {
8267
+ syncStudioPdfCardAutoRefreshButton(card);
8268
+ }
7815
8269
  return card;
7816
8270
  }
7817
8271
 
@@ -21926,9 +22380,29 @@
21926
22380
  });
21927
22381
  }
21928
22382
 
22383
+ if (hideStudioHeaderBtn) {
22384
+ hideStudioHeaderBtn.addEventListener("click", () => {
22385
+ setStudioHeaderHidden(true, { focusReveal: true });
22386
+ setStatus("Studio header hidden. Move to the top-right edge to restore it.");
22387
+ });
22388
+ }
22389
+
22390
+ if (studioHeaderRevealEl) {
22391
+ studioHeaderRevealEl.addEventListener("click", (event) => {
22392
+ if (event.target === studioHeaderRevealEl) restoreStudioHeaderFromReveal();
22393
+ });
22394
+ }
22395
+
22396
+ if (studioHeaderRevealBtn) {
22397
+ studioHeaderRevealBtn.addEventListener("click", restoreStudioHeaderFromReveal);
22398
+ }
22399
+
21929
22400
  if (zenModeBtn) {
21930
22401
  zenModeBtn.addEventListener("click", () => {
21931
- setStudioZenMode(!studioZenModeEnabled);
22402
+ setStudioZenMode(!studioZenModeEnabled, { focusReveal: !studioZenModeEnabled });
22403
+ setStatus(studioZenModeEnabled
22404
+ ? "Zen mode on. Studio header hidden; press F9 to exit."
22405
+ : "Zen mode off. Studio header restored.");
21932
22406
  });
21933
22407
  }
21934
22408