pi-studio 0.9.45 → 0.9.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,16 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.47] — 2026-08-20
8
+
9
+ ### Fixed
10
+ - Fall back to Pandoc’s compatible self-contained resource mode when its capability probe times out, and evict failed probes so one transient delay cannot disable previews for the rest of the Studio process.
11
+
12
+ ## [0.9.46] — 2026-08-20
13
+
14
+ ### Fixed
15
+ - Re-resolve local Markdown images and refresh active previews when their file or working-directory context changes, preventing stale relative image URLs from rendering as broken links in Muxy and other browser hosts.
16
+
7
17
  ## [0.9.45] — 2026-08-20
8
18
 
9
19
  ### Changed
@@ -222,6 +222,14 @@
222
222
  ) {
223
223
  throw new Error("Studio navigation helpers failed to load.");
224
224
  }
225
+ const previewResourceHelpers = globalThis.PiStudioPreviewResourceHelpers;
226
+ if (
227
+ !previewResourceHelpers
228
+ || typeof previewResourceHelpers.areStudioPreviewResourceContextsEqual !== "function"
229
+ || typeof previewResourceHelpers.hydrateStudioPreviewLocalImages !== "function"
230
+ ) {
231
+ throw new Error("Studio preview resource helpers failed to load.");
232
+ }
225
233
  const showMeHelpers = globalThis.PiStudioShowMeHelpers;
226
234
  if (!showMeHelpers || typeof showMeHelpers.chooseStudioShowMeFocus !== "function") {
227
235
  throw new Error("Studio Show me helpers failed to load.");
@@ -403,6 +411,7 @@
403
411
  const HTML_EXPORT_FETCH_TIMEOUT_MS = 180_000;
404
412
  const HTML_ARTIFACT_MATH_RENDER_FETCH_TIMEOUT_MS = 30_000;
405
413
  const HTML_ARTIFACT_RESOURCE_FETCH_TIMEOUT_MS = 30_000;
414
+ const RENDERED_PREVIEW_IMAGE_FETCH_TIMEOUT_MS = 8_000;
406
415
  const EDITOR_TAB_TEXT = " ";
407
416
  const QUIZ_DEFAULT_COUNT = 5;
408
417
  const COMPLETION_CONTEXT_STORAGE_KEY = "piStudio.completionContextMode";
@@ -6035,19 +6044,33 @@
6035
6044
  }).filter(Boolean);
6036
6045
  }
6037
6046
 
6038
- function buildHtmlArtifactResourceFetchUrl(record, resourceUrl) {
6047
+ function buildLocalPreviewResourceFetchUrl(context, resourceUrl) {
6039
6048
  const token = getToken();
6040
6049
  if (!token) return "";
6041
6050
  const params = new URLSearchParams({ token, path: String(resourceUrl || "") });
6042
- if (record && record.sourcePath) {
6043
- params.set("sourcePath", record.sourcePath);
6051
+ if (context && context.sourcePath) {
6052
+ params.set("sourcePath", context.sourcePath);
6044
6053
  }
6045
- if (record && record.resourceDir) {
6046
- params.set("resourceDir", record.resourceDir);
6054
+ if (context && context.resourceDir) {
6055
+ params.set("resourceDir", context.resourceDir);
6047
6056
  }
6048
6057
  return "/html-preview-resource?" + params.toString();
6049
6058
  }
6050
6059
 
6060
+ async function fetchLocalPreviewResourceDataUrl(context, resourceUrl, timeoutMs, timeoutLabel) {
6061
+ const fetchUrl = buildLocalPreviewResourceFetchUrl(context, resourceUrl);
6062
+ if (!fetchUrl) throw new Error("Missing Studio token in URL.");
6063
+ const response = await fetchWithTimeout(fetchUrl, { method: "GET" }, timeoutMs, timeoutLabel);
6064
+ const payload = await response.json().catch(() => null);
6065
+ if (!response.ok || !payload || payload.ok !== true || typeof payload.dataUrl !== "string") {
6066
+ const message = payload && typeof payload.error === "string"
6067
+ ? payload.error
6068
+ : (timeoutLabel || "Local preview resource load") + " failed with HTTP " + response.status + ".";
6069
+ throw new Error(message);
6070
+ }
6071
+ return payload.dataUrl;
6072
+ }
6073
+
6051
6074
  function postHtmlArtifactResourceResults(record, results) {
6052
6075
  if (!record || !record.iframe || !record.iframe.isConnected || !record.iframe.contentWindow) return;
6053
6076
  try {
@@ -6064,15 +6087,13 @@
6064
6087
  async function fetchHtmlArtifactResource(record, item) {
6065
6088
  const resourceId = item && item.resourceId ? item.resourceId : "";
6066
6089
  try {
6067
- const fetchUrl = buildHtmlArtifactResourceFetchUrl(record, item.url);
6068
- if (!fetchUrl) throw new Error("Missing Studio token in URL.");
6069
- const response = await fetchWithTimeout(fetchUrl, { method: "GET" }, HTML_ARTIFACT_RESOURCE_FETCH_TIMEOUT_MS, "HTML preview resource load");
6070
- const payload = await response.json().catch(() => null);
6071
- if (!response.ok || !payload || payload.ok !== true || typeof payload.dataUrl !== "string") {
6072
- const message = payload && typeof payload.error === "string" ? payload.error : "HTML preview resource load failed with HTTP " + response.status + ".";
6073
- throw new Error(message);
6074
- }
6075
- return { resourceId, ok: true, dataUrl: payload.dataUrl };
6090
+ const dataUrl = await fetchLocalPreviewResourceDataUrl(
6091
+ record,
6092
+ item.url,
6093
+ HTML_ARTIFACT_RESOURCE_FETCH_TIMEOUT_MS,
6094
+ "HTML preview resource load",
6095
+ );
6096
+ return { resourceId, ok: true, dataUrl };
6076
6097
  } catch (error) {
6077
6098
  return { resourceId, ok: false, error: error && error.message ? error.message : String(error || "HTML preview resource load failed.") };
6078
6099
  }
@@ -8513,15 +8534,23 @@
8513
8534
  const timeoutId = controller ? window.setTimeout(() => controller.abort(), 8000) : null;
8514
8535
 
8515
8536
  const previewOptions = options && typeof options === "object" ? options : {};
8537
+ const requestedResourceContext = previewOptions.resourceContext && typeof previewOptions.resourceContext === "object"
8538
+ ? previewOptions.resourceContext
8539
+ : null;
8516
8540
 
8517
8541
  let response;
8518
8542
  try {
8519
8543
  const effectivePath = getEffectiveSavePath();
8520
- const sourcePath = effectivePath || sourceState.path || "";
8544
+ const sourcePath = requestedResourceContext
8545
+ ? String(requestedResourceContext.sourcePath || "")
8546
+ : (effectivePath || sourceState.path || "");
8547
+ const resourceDir = requestedResourceContext
8548
+ ? String(requestedResourceContext.resourceDir || "")
8549
+ : ((resourceDirInput && !sourcePath) ? getCurrentResourceDirValue() : "");
8521
8550
  const payload = {
8522
8551
  markdown: String(markdown || ""),
8523
8552
  sourcePath: sourcePath,
8524
- resourceDir: (!sourcePath && resourceDirInput) ? getCurrentResourceDirValue() : "",
8553
+ resourceDir: sourcePath ? "" : resourceDir,
8525
8554
  };
8526
8555
  if (previewOptions.includeEditorLanguage) {
8527
8556
  payload.editorLanguage = String(editorLanguage || "");
@@ -9282,10 +9311,12 @@
9282
9311
  stripMarkdownHtmlComments: !previewingEditorText || editorLanguage !== "latex",
9283
9312
  };
9284
9313
  const pdfPrepared = prepareStudioPdfBlocksForPreview(previewPrepared.markdown);
9314
+ const previewResourceContext = getHtmlPreviewResourceContextOptions();
9285
9315
 
9286
9316
  try {
9287
9317
  const renderedHtml = await renderMarkdownWithPandoc(pdfPrepared.markdown, {
9288
9318
  includeEditorLanguage: pane === "source" || rightView === "editor-preview",
9319
+ resourceContext: previewResourceContext,
9289
9320
  });
9290
9321
 
9291
9322
  if (pane === "source") {
@@ -9297,6 +9328,14 @@
9297
9328
  clearPreviewJumpHighlight(targetEl);
9298
9329
  finishPreviewRender(targetEl);
9299
9330
  targetEl.innerHTML = sanitizeRenderedHtml(renderedHtml, markdown, previewFallbackOptions);
9331
+ await previewResourceHelpers.hydrateStudioPreviewLocalImages(targetEl, (resourceUrl) => (
9332
+ fetchLocalPreviewResourceDataUrl(
9333
+ previewResourceContext,
9334
+ resourceUrl,
9335
+ RENDERED_PREVIEW_IMAGE_FETCH_TIMEOUT_MS,
9336
+ "Preview image load",
9337
+ )
9338
+ ));
9300
9339
  renderStudioPdfBlocksInElement(targetEl, pdfPrepared.blocks, previewingEditorText);
9301
9340
  applyPreviewAnnotationPlaceholdersToElement(targetEl, previewPrepared.placeholders);
9302
9341
  await renderAnnotationMathInElement(targetEl);
@@ -9406,6 +9445,13 @@
9406
9445
  }
9407
9446
  }
9408
9447
 
9448
+ function refreshPreviewsForResourceContextChange() {
9449
+ renderSourcePreview();
9450
+ if (rightView === "preview") {
9451
+ renderActiveResult();
9452
+ }
9453
+ }
9454
+
9409
9455
  function scheduleResponseEditorPreviewRender(delayMs) {
9410
9456
  if (responseEditorPreviewTimer) {
9411
9457
  window.clearTimeout(responseEditorPreviewTimer);
@@ -11414,6 +11460,7 @@
11414
11460
  function setSourceState(next, options) {
11415
11461
  const previousDescriptor = getCurrentStudioDocumentDescriptor();
11416
11462
  const previousQuartoPath = getCurrentStudioQuartoSourcePath();
11463
+ const previousPreviewResourceContext = getHtmlPreviewResourceContextOptions();
11417
11464
  const nextPath = next && next.path ? next.path : null;
11418
11465
  sourceState = {
11419
11466
  source: next && next.source ? next.source : "blank",
@@ -11457,6 +11504,10 @@
11457
11504
  const refreshChangedQuartoView = rightView === "editor-quarto-preview" && quartoSourceChanged && isCurrentStudioQuartoDocument();
11458
11505
  if (refreshChangedQuartoView) requestStudioQuartoPreviewCheck(false);
11459
11506
  if (leavingUnavailableQuartoView || refreshChangedQuartoView) refreshResponseUi();
11507
+ const nextPreviewResourceContext = getHtmlPreviewResourceContextOptions();
11508
+ if (!previewResourceHelpers.areStudioPreviewResourceContextsEqual(previousPreviewResourceContext, nextPreviewResourceContext)) {
11509
+ refreshPreviewsForResourceContextChange();
11510
+ }
11460
11511
  scheduleWorkspacePersistence();
11461
11512
  }
11462
11513
 
@@ -21993,7 +22044,7 @@
21993
22044
  }
21994
22045
  updateSaveFileTooltip();
21995
22046
  syncActionButtons();
21996
- renderSourcePreview();
22047
+ refreshPreviewsForResourceContextChange();
21997
22048
  scheduleWorkspacePersistence();
21998
22049
  }
21999
22050
  if (sourceBadgeEl) {
@@ -22036,7 +22087,7 @@
22036
22087
  showResourceDirState("button");
22037
22088
  updateSaveFileTooltip();
22038
22089
  syncActionButtons();
22039
- renderSourcePreview();
22090
+ refreshPreviewsForResourceContextChange();
22040
22091
  scheduleWorkspacePersistence();
22041
22092
  });
22042
22093
  }
@@ -0,0 +1,52 @@
1
+ (() => {
2
+ const STUDIO_PREVIEW_LOCAL_IMAGE_LIMIT = 100;
3
+ const STUDIO_PREVIEW_IMAGE_DATA_URL_PATTERN = /^data:image\/(?:png|jpeg|gif|webp);base64,/i;
4
+
5
+ function isResolvableStudioPreviewImageSource(value) {
6
+ const source = String(value || "").trim();
7
+ if (!source || source.startsWith("#") || source.startsWith("//")) return false;
8
+ if (/^(?:data|blob|https?|about|javascript):/i.test(source)) return false;
9
+ if (/^[a-z][a-z0-9+.-]*:/i.test(source) && !/^[a-z]:[\\/]/i.test(source)) return false;
10
+ return true;
11
+ }
12
+
13
+ function areStudioPreviewResourceContextsEqual(left, right) {
14
+ const a = left && typeof left === "object" ? left : {};
15
+ const b = right && typeof right === "object" ? right : {};
16
+ return String(a.sourcePath || "") === String(b.sourcePath || "")
17
+ && String(a.resourceDir || "") === String(b.resourceDir || "");
18
+ }
19
+
20
+ async function hydrateStudioPreviewLocalImages(target, resolveResource) {
21
+ if (!target || typeof target.querySelectorAll !== "function" || typeof resolveResource !== "function") {
22
+ return { attempted: 0, resolved: 0 };
23
+ }
24
+
25
+ const images = Array.from(target.querySelectorAll("img[src]"))
26
+ .filter((image) => image && typeof image.getAttribute === "function" && isResolvableStudioPreviewImageSource(image.getAttribute("src")))
27
+ .slice(0, STUDIO_PREVIEW_LOCAL_IMAGE_LIMIT);
28
+
29
+ let resolved = 0;
30
+ await Promise.all(images.map(async (image) => {
31
+ const originalSource = String(image.getAttribute("src") || "").trim();
32
+ try {
33
+ const dataUrl = await resolveResource(originalSource);
34
+ if (!STUDIO_PREVIEW_IMAGE_DATA_URL_PATTERN.test(String(dataUrl || ""))) return;
35
+ if (image.isConnected === false || String(image.getAttribute("src") || "").trim() !== originalSource) return;
36
+ image.setAttribute("src", dataUrl);
37
+ resolved += 1;
38
+ } catch {
39
+ // Leave unresolved images unchanged so authored browser URLs retain their normal behavior.
40
+ }
41
+ }));
42
+
43
+ return { attempted: images.length, resolved };
44
+ }
45
+
46
+ globalThis.PiStudioPreviewResourceHelpers = Object.freeze({
47
+ STUDIO_PREVIEW_LOCAL_IMAGE_LIMIT,
48
+ areStudioPreviewResourceContextsEqual,
49
+ hydrateStudioPreviewLocalImages,
50
+ isResolvableStudioPreviewImageSource,
51
+ });
52
+ })();
package/index.ts CHANGED
@@ -29,6 +29,7 @@ import {
29
29
  } from "./shared/studio-markdown-latex-literals.js";
30
30
  import { escapeStudioPdfLatexTextFragment } from "./shared/studio-pdf-escape.js";
31
31
  import { resolveStudioPdfResourceFile } from "./shared/studio-pdf-resource.js";
32
+ import { createStudioPandocHtmlResourceFlagResolver } from "./shared/studio-pandoc-resource-flag.js";
32
33
  import { isStudioCmuxSession, openStudioUrlInBrowser } from "./shared/studio-browser-launcher.js";
33
34
  import { buildStudioReplTmuxStartArgs } from "./shared/studio-repl-tmux.js";
34
35
  import { buildStudioForwardingHint, buildStudioSshTunnelHint, isStudioSshSession as isSshSession } from "./shared/studio-ssh-hint.js";
@@ -108,6 +109,7 @@ const STUDIO_CSS_URL = new URL("./client/studio.css", import.meta.url);
108
109
  const STUDIO_ANNOTATION_HELPERS_URL = new URL("./client/studio-annotation-helpers.js", import.meta.url);
109
110
  const STUDIO_MERMAID_HELPERS_URL = new URL("./client/studio-mermaid-helpers.js", import.meta.url);
110
111
  const STUDIO_NAVIGATION_HELPERS_URL = new URL("./client/studio-navigation-helpers.js", import.meta.url);
112
+ const STUDIO_PREVIEW_RESOURCE_HELPERS_URL = new URL("./client/studio-preview-resource-helpers.js", import.meta.url);
111
113
  const STUDIO_SHOW_ME_HELPERS_URL = new URL("./client/studio-show-me-helpers.js", import.meta.url);
112
114
  const STUDIO_CLIENT_URL = new URL("./client/studio-client.js", import.meta.url);
113
115
 
@@ -6125,27 +6127,19 @@ function decorateStudioPandocSyntaxHtml(html: string): string {
6125
6127
  );
6126
6128
  }
6127
6129
 
6128
- const studioPandocHtmlResourceFlagCache = new Map<string, Promise<"--embed-resources" | "--self-contained">>();
6129
-
6130
- async function getStudioPandocHtmlResourceFlag(pandocCommand: string): Promise<"--embed-resources" | "--self-contained"> {
6131
- let cached = studioPandocHtmlResourceFlagCache.get(pandocCommand);
6132
- if (!cached) {
6133
- cached = runStudioSubprocess(pandocCommand, ["--help"], {
6134
- timeoutMs: 5_000,
6135
- stdoutMaxBytes: 250_000,
6136
- stderrMaxBytes: 20_000,
6137
- label: "pandoc capability probe",
6138
- notFoundMessage: "pandoc was not found. Install pandoc or set PANDOC_PATH to the pandoc binary.",
6139
- }).then((result) => {
6140
- if (result.code !== 0) {
6141
- throw new Error(`pandoc capability probe failed with exit code ${result.code}${result.stderr ? `: ${result.stderr}` : ""}`);
6142
- }
6143
- return result.stdout.includes("--embed-resources") ? "--embed-resources" : "--self-contained";
6144
- });
6145
- studioPandocHtmlResourceFlagCache.set(pandocCommand, cached);
6130
+ const resolveStudioPandocHtmlResourceFlag = createStudioPandocHtmlResourceFlagResolver(async (pandocCommand: string) => {
6131
+ const result = await runStudioSubprocess(pandocCommand, ["--help"], {
6132
+ timeoutMs: 5_000,
6133
+ stdoutMaxBytes: 250_000,
6134
+ stderrMaxBytes: 20_000,
6135
+ label: "pandoc capability probe",
6136
+ notFoundMessage: "pandoc was not found. Install pandoc or set PANDOC_PATH to the pandoc binary.",
6137
+ });
6138
+ if (result.code !== 0) {
6139
+ throw new Error(`pandoc capability probe failed with exit code ${result.code}${result.stderr ? `: ${result.stderr}` : ""}`);
6146
6140
  }
6147
- return cached;
6148
- }
6141
+ return result.stdout;
6142
+ });
6149
6143
 
6150
6144
  function preprocessStudioLatexFootnotemarksForPreview(latex: string): string {
6151
6145
  return String(latex ?? "").replace(/\\footnotemark\s*\[\s*([^\]\r\n]+?)\s*\]/g, (_match, marker: string) => {
@@ -6185,7 +6179,7 @@ async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolea
6185
6179
  await mkdir(htmlTemplateDir, { recursive: true });
6186
6180
  const htmlTemplatePath = join(htmlTemplateDir, "template.html");
6187
6181
  await writeFile(htmlTemplatePath, STUDIO_PANDOC_HTML_FRAGMENT_TEMPLATE, "utf-8");
6188
- if (resourcePath) args.push(await getStudioPandocHtmlResourceFlag(pandocCommand));
6182
+ if (resourcePath) args.push(await resolveStudioPandocHtmlResourceFlag(pandocCommand));
6189
6183
  args.push("--standalone", `--template=${htmlTemplatePath}`);
6190
6184
  }
6191
6185
  const normalizedMarkdown = isLatex
@@ -10563,6 +10557,7 @@ function buildStudioHtml(
10563
10557
  const annotationHelpersScriptHref = `/studio-annotation-helpers.js?token=${encodeURIComponent(studioToken ?? "")}`;
10564
10558
  const mermaidHelpersScriptHref = `/studio-mermaid-helpers.js?token=${encodeURIComponent(studioToken ?? "")}`;
10565
10559
  const navigationHelpersScriptHref = `/studio-navigation-helpers.js?token=${encodeURIComponent(studioToken ?? "")}`;
10560
+ const previewResourceHelpersScriptHref = `/studio-preview-resource-helpers.js?token=${encodeURIComponent(studioToken ?? "")}`;
10566
10561
  const showMeHelpersScriptHref = `/studio-show-me-helpers.js?token=${encodeURIComponent(studioToken ?? "")}`;
10567
10562
  const clientScriptHref = `/studio-client.js?token=${encodeURIComponent(studioToken ?? "")}`;
10568
10563
  const faviconHref = buildStudioFaviconDataUri(style);
@@ -10986,6 +10981,7 @@ ${cssVarsBlock}
10986
10981
  <script src="${annotationHelpersScriptHref}"></script>
10987
10982
  <script src="${mermaidHelpersScriptHref}"></script>
10988
10983
  <script src="${navigationHelpersScriptHref}"></script>
10984
+ <script src="${previewResourceHelpersScriptHref}"></script>
10989
10985
  <script src="${showMeHelpersScriptHref}"></script>
10990
10986
  <script src="${clientScriptHref}"></script>
10991
10987
  </body>
@@ -14601,6 +14597,7 @@ export default function (pi: ExtensionAPI) {
14601
14597
  requestUrl.pathname === "/studio-annotation-helpers.js"
14602
14598
  || requestUrl.pathname === "/studio-mermaid-helpers.js"
14603
14599
  || requestUrl.pathname === "/studio-navigation-helpers.js"
14600
+ || requestUrl.pathname === "/studio-preview-resource-helpers.js"
14604
14601
  || requestUrl.pathname === "/studio-show-me-helpers.js"
14605
14602
  || requestUrl.pathname === "/studio-client.js"
14606
14603
  ) {
@@ -14623,18 +14620,22 @@ export default function (pi: ExtensionAPI) {
14623
14620
  ? STUDIO_MERMAID_HELPERS_URL
14624
14621
  : requestUrl.pathname === "/studio-navigation-helpers.js"
14625
14622
  ? STUDIO_NAVIGATION_HELPERS_URL
14626
- : requestUrl.pathname === "/studio-show-me-helpers.js"
14627
- ? STUDIO_SHOW_ME_HELPERS_URL
14628
- : STUDIO_CLIENT_URL;
14623
+ : requestUrl.pathname === "/studio-preview-resource-helpers.js"
14624
+ ? STUDIO_PREVIEW_RESOURCE_HELPERS_URL
14625
+ : requestUrl.pathname === "/studio-show-me-helpers.js"
14626
+ ? STUDIO_SHOW_ME_HELPERS_URL
14627
+ : STUDIO_CLIENT_URL;
14629
14628
  const targetLabel = requestUrl.pathname === "/studio-annotation-helpers.js"
14630
14629
  ? "studio annotation helper script"
14631
14630
  : requestUrl.pathname === "/studio-mermaid-helpers.js"
14632
14631
  ? "studio Mermaid helper script"
14633
14632
  : requestUrl.pathname === "/studio-navigation-helpers.js"
14634
14633
  ? "studio navigation helper script"
14635
- : requestUrl.pathname === "/studio-show-me-helpers.js"
14636
- ? "studio Show me helper script"
14637
- : "studio client script";
14634
+ : requestUrl.pathname === "/studio-preview-resource-helpers.js"
14635
+ ? "studio preview resource helper script"
14636
+ : requestUrl.pathname === "/studio-show-me-helpers.js"
14637
+ ? "studio Show me helper script"
14638
+ : "studio client script";
14638
14639
 
14639
14640
  try {
14640
14641
  const clientScript = readFileSync(targetUrl, "utf-8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.9.45",
3
+ "version": "0.9.47",
4
4
  "description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, active quiz, prompt/response history, live previews, and tmux-backed REPL/literate REPL workflows",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,34 @@
1
+ export function createStudioPandocHtmlResourceFlagResolver(probeHelp) {
2
+ if (typeof probeHelp !== "function") {
3
+ throw new TypeError("A Pandoc capability probe function is required.");
4
+ }
5
+
6
+ const cache = new Map();
7
+
8
+ async function getResourceFlag(pandocCommand) {
9
+ const command = String(pandocCommand || "pandoc");
10
+ let cached = cache.get(command);
11
+ if (!cached) {
12
+ cached = Promise.resolve()
13
+ .then(() => probeHelp(command))
14
+ .then((helpText) => String(helpText || "").includes("--embed-resources")
15
+ ? "--embed-resources"
16
+ : "--self-contained");
17
+ cache.set(command, cached);
18
+ void cached.catch(() => {
19
+ if (cache.get(command) === cached) cache.delete(command);
20
+ });
21
+ }
22
+ return cached;
23
+ }
24
+
25
+ return async function resolveStudioPandocHtmlResourceFlag(pandocCommand) {
26
+ try {
27
+ return await getResourceFlag(pandocCommand);
28
+ } catch {
29
+ // Old and current Pandoc versions accept --self-contained. The actual
30
+ // render can now succeed or report the executable's useful error.
31
+ return "--self-contained";
32
+ }
33
+ };
34
+ }