pi-studio 0.5.20 → 0.5.22

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,21 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.5.22] — 2026-03-20
8
+
9
+ ### Fixed
10
+ - Citeproc-rendered LaTeX bibliographies now request a visible `References` section heading in Studio preview/PDF output.
11
+ - LaTeX preview now regroups `subfigure`-based figures so adjacent subfigures keep their shared overall figure/caption structure instead of rendering as unrelated standalone figures, including visible `(a)` / `(b)` subfigure markers and `Figure n` main-caption labels when `.aux` labels are available.
12
+ - LaTeX preview now converts common `algorithm` / `algorithmic` / `algpseudocode` blocks into readable algorithm cards with preserved captions, indentation, and optional line numbers instead of showing the raw environment text.
13
+ - The editor language dropdown is now alphabetised for quicker scanning.
14
+
15
+ ## [0.5.21] — 2026-03-19
16
+
17
+ ### Fixed
18
+ - PDF export now uses a two-step prepare/download flow and opens the generated PDF in the system’s default viewer first, so browser surfaces like cmux do not need to navigate away from the current Studio page.
19
+ - LaTeX preview and PDF export now use the document `.aux` file when available to substitute basic `\eqref{...}`, `\ref{...}`, and `\autoref{...}` values more reliably, and preview decorates block equations with their resolved equation numbers.
20
+ - Upload + working-directory LaTeX workflows now derive the effective source path more reliably, helping Studio find the correct `.aux` file for reference resolution.
21
+
7
22
  ## [0.5.20] — 2026-03-19
8
23
 
9
24
  ### Fixed
@@ -1351,6 +1351,8 @@
1351
1351
 
1352
1352
  let response;
1353
1353
  try {
1354
+ const effectivePath = getEffectiveSavePath();
1355
+ const sourcePath = effectivePath || sourceState.path || "";
1354
1356
  response = await fetch("/render-preview?token=" + encodeURIComponent(token), {
1355
1357
  method: "POST",
1356
1358
  headers: {
@@ -1358,8 +1360,8 @@
1358
1360
  },
1359
1361
  body: JSON.stringify({
1360
1362
  markdown: String(markdown || ""),
1361
- sourcePath: sourceState.path || "",
1362
- resourceDir: (!sourceState.path && resourceDirInput) ? resourceDirInput.value.trim() : "",
1363
+ sourcePath: sourcePath,
1364
+ resourceDir: (!sourcePath && resourceDirInput) ? resourceDirInput.value.trim() : "",
1363
1365
  }),
1364
1366
  signal: controller ? controller.signal : undefined,
1365
1367
  });
@@ -1444,16 +1446,17 @@
1444
1446
  return;
1445
1447
  }
1446
1448
 
1447
- const sourcePath = sourceState.path || "";
1448
- const resourceDir = (!sourceState.path && resourceDirInput) ? resourceDirInput.value.trim() : "";
1449
+ const effectivePath = getEffectiveSavePath();
1450
+ const sourcePath = effectivePath || sourceState.path || "";
1451
+ const resourceDir = (!sourcePath && resourceDirInput) ? resourceDirInput.value.trim() : "";
1449
1452
  const isEditorPreview = rightView === "editor-preview";
1450
1453
  const editorPdfLanguage = isEditorPreview ? normalizeFenceLanguage(editorLanguage || "") : "";
1451
1454
  const isLatex = isEditorPreview
1452
1455
  ? editorPdfLanguage === "latex"
1453
1456
  : /\\documentclass\b|\\begin\{document\}/.test(markdown);
1454
1457
  let filenameHint = isEditorPreview ? "studio-editor-preview.pdf" : "studio-response-preview.pdf";
1455
- if (sourceState.path) {
1456
- const baseName = sourceState.path.split(/[\\/]/).pop() || "studio";
1458
+ if (sourcePath) {
1459
+ const baseName = sourcePath.split(/[\\/]/).pop() || "studio";
1457
1460
  const stem = baseName.replace(/\.[^.]+$/, "") || "studio";
1458
1461
  filenameHint = stem + "-preview.pdf";
1459
1462
  }
@@ -1478,8 +1481,8 @@
1478
1481
  }),
1479
1482
  });
1480
1483
 
1484
+ const contentType = String(response.headers.get("content-type") || "").toLowerCase();
1481
1485
  if (!response.ok) {
1482
- const contentType = String(response.headers.get("content-type") || "").toLowerCase();
1483
1486
  let message = "PDF export failed with HTTP " + response.status + ".";
1484
1487
  if (contentType.includes("application/json")) {
1485
1488
  const payload = await response.json().catch(() => null);
@@ -1495,6 +1498,53 @@
1495
1498
  throw new Error(message);
1496
1499
  }
1497
1500
 
1501
+ if (contentType.includes("application/json")) {
1502
+ const payload = await response.json().catch(() => null);
1503
+ if (!payload || typeof payload.downloadUrl !== "string") {
1504
+ throw new Error("PDF export prepared successfully, but Studio did not receive a download URL.");
1505
+ }
1506
+
1507
+ const exportWarning = typeof payload.warning === "string" ? payload.warning.trim() : "";
1508
+ const openError = typeof payload.openError === "string" ? payload.openError.trim() : "";
1509
+ const openedExternal = payload.openedExternal === true;
1510
+ let downloadName = typeof payload.filename === "string" && payload.filename.trim()
1511
+ ? payload.filename.trim()
1512
+ : (filenameHint || "studio-preview.pdf");
1513
+ if (!/\.pdf$/i.test(downloadName)) {
1514
+ downloadName += ".pdf";
1515
+ }
1516
+
1517
+ if (openedExternal) {
1518
+ if (exportWarning) {
1519
+ setStatus("Opened PDF in default viewer with warning: " + exportWarning, "warning");
1520
+ } else {
1521
+ setStatus("Opened PDF in default viewer: " + downloadName, "success");
1522
+ }
1523
+ return;
1524
+ }
1525
+
1526
+ const link = document.createElement("a");
1527
+ link.href = payload.downloadUrl;
1528
+ link.download = downloadName;
1529
+ link.rel = "noopener";
1530
+ document.body.appendChild(link);
1531
+ link.click();
1532
+ link.remove();
1533
+
1534
+ if (openError) {
1535
+ if (exportWarning) {
1536
+ setStatus("Opened browser fallback because external viewer failed (" + openError + "). Warning: " + exportWarning, "warning");
1537
+ } else {
1538
+ setStatus("Opened browser fallback because external viewer failed (" + openError + ").", "warning");
1539
+ }
1540
+ } else if (exportWarning) {
1541
+ setStatus("Exported PDF with warning: " + exportWarning, "warning");
1542
+ } else {
1543
+ setStatus("Exported PDF: " + downloadName, "success");
1544
+ }
1545
+ return;
1546
+ }
1547
+
1498
1548
  const exportWarning = String(response.headers.get("x-pi-studio-export-warning") || "").trim();
1499
1549
  const blob = await response.blob();
1500
1550
  const headerFilename = parseContentDispositionFilename(response.headers.get("content-disposition"));
@@ -4030,7 +4080,7 @@
4030
4080
  const text = typeof reader.result === "string" ? reader.result : "";
4031
4081
  setEditorText(text, { preserveScroll: false, preserveSelection: false });
4032
4082
  setSourceState({
4033
- source: "blank",
4083
+ source: "upload",
4034
4084
  label: "upload: " + file.name,
4035
4085
  path: null,
4036
4086
  });
package/client/studio.css CHANGED
@@ -569,6 +569,14 @@
569
569
  text-align: center;
570
570
  margin-bottom: 2em;
571
571
  }
572
+
573
+ .rendered-markdown #bibliography {
574
+ margin-bottom: 0.75em;
575
+ }
576
+
577
+ .rendered-markdown #refs {
578
+ margin-top: 0.5em;
579
+ }
572
580
  .rendered-markdown #title-block-header .title {
573
581
  margin-bottom: 0.25em;
574
582
  }
@@ -758,10 +766,135 @@
758
766
  max-width: 100%;
759
767
  }
760
768
 
769
+ .rendered-markdown .studio-subfigure-group {
770
+ margin: 1.25em auto;
771
+ }
772
+
773
+ .rendered-markdown .studio-subfigure-grid {
774
+ display: flex;
775
+ flex-wrap: wrap;
776
+ gap: 1rem;
777
+ justify-content: center;
778
+ align-items: flex-start;
779
+ }
780
+
781
+ .rendered-markdown .studio-subfigure-entry {
782
+ flex: 1 1 220px;
783
+ margin: 0;
784
+ max-width: 100%;
785
+ }
786
+
787
+ .rendered-markdown .studio-subfigure-entry img {
788
+ width: 100%;
789
+ height: auto;
790
+ display: block;
791
+ }
792
+
793
+ .rendered-markdown .studio-subfigure-entry > figcaption,
794
+ .rendered-markdown .studio-subfigure-group > figcaption {
795
+ text-align: center;
796
+ }
797
+
798
+ .rendered-markdown .studio-subfigure-group > figcaption {
799
+ margin-top: 0.8em;
800
+ }
801
+
802
+ .rendered-markdown .studio-subfigure-caption-label,
803
+ .rendered-markdown .studio-figure-caption-label {
804
+ font-weight: 600;
805
+ }
806
+
807
+ .rendered-markdown .studio-subfigure-caption-label {
808
+ margin-right: 0.35em;
809
+ }
810
+
811
+ .rendered-markdown .studio-figure-caption-label {
812
+ margin-right: 0.45em;
813
+ }
814
+
815
+ .rendered-markdown .studio-algorithm-block {
816
+ margin: 1.25em 0;
817
+ border: 1px solid var(--md-codeblock-border);
818
+ border-radius: 10px;
819
+ background: var(--panel-2);
820
+ overflow: hidden;
821
+ }
822
+
823
+ .rendered-markdown .studio-algorithm-block > figcaption {
824
+ padding: 0.8em 1em 0.65em;
825
+ border-bottom: 1px solid var(--border-muted);
826
+ background: rgba(127, 127, 127, 0.06);
827
+ text-align: left;
828
+ }
829
+
830
+ .rendered-markdown .studio-algorithm-caption-label {
831
+ font-weight: 600;
832
+ margin-right: 0.45em;
833
+ }
834
+
835
+ .rendered-markdown .studio-algorithm-body {
836
+ padding: 0.7em 0.9em 0.85em;
837
+ font-family: var(--font-mono);
838
+ font-size: 0.95em;
839
+ }
840
+
841
+ .rendered-markdown .studio-algorithm-line {
842
+ display: grid;
843
+ grid-template-columns: 2.6em minmax(0, 1fr);
844
+ gap: 0.8em;
845
+ align-items: baseline;
846
+ padding: 0.08em 0;
847
+ }
848
+
849
+ .rendered-markdown .studio-algorithm-line-number {
850
+ color: var(--muted);
851
+ text-align: right;
852
+ font-variant-numeric: tabular-nums;
853
+ user-select: none;
854
+ }
855
+
856
+ .rendered-markdown .studio-algorithm-line-content {
857
+ min-width: 0;
858
+ padding-left: calc(var(--studio-algorithm-indent, 0) * 1.35em);
859
+ white-space: pre-wrap;
860
+ overflow-wrap: anywhere;
861
+ }
862
+
863
+ .rendered-markdown .studio-algorithm-line-content math {
864
+ font-size: 1em;
865
+ }
866
+
761
867
  .rendered-markdown math {
762
868
  font-family: "STIX Two Math", "Cambria Math", "Latin Modern Math", "STIXGeneral", serif;
763
869
  }
764
870
 
871
+ .rendered-markdown .studio-display-equation {
872
+ position: relative;
873
+ margin: 1em 0;
874
+ }
875
+
876
+ .rendered-markdown .studio-display-equation-body {
877
+ padding-right: 4.5em;
878
+ overflow-x: auto;
879
+ overflow-y: hidden;
880
+ }
881
+
882
+ .rendered-markdown .studio-display-equation-body math[display="block"] {
883
+ margin: 0;
884
+ }
885
+
886
+ .rendered-markdown .studio-display-equation-number {
887
+ position: absolute;
888
+ top: 50%;
889
+ right: 0;
890
+ transform: translateY(-50%);
891
+ color: var(--muted);
892
+ font-size: 0.95em;
893
+ line-height: 1;
894
+ white-space: nowrap;
895
+ font-variant-numeric: tabular-nums;
896
+ }
897
+
765
898
  .rendered-markdown math[display="block"] {
766
899
  display: block;
767
900
  margin: 1em 0;