pi-studio 0.2.0 → 0.2.1

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
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to `pi-studio` are documented here.
4
4
 
5
+ ## [0.2.1] — 2026-03-02
6
+
7
+ ### Added
8
+ - **Language-aware syntax highlighting**: selectable `Lang:` dropdown (Markdown, JavaScript, TypeScript, Python, Bash, JSON, Rust, C, C++, Julia, Fortran, R, MATLAB).
9
+ - Language auto-detected from file extension when loading files; manually overridable via dropdown.
10
+ - Full-document code highlighting in editor Raw view when a non-markdown language is selected (reuses fenced-block tokenizer across entire content).
11
+ - Code-aware Preview: when a code language is selected, Preview renders syntax-highlighted `<pre>` instead of sending to pandoc.
12
+ - Language preference persisted to `localStorage` across sessions.
13
+ - New tokenizer patterns for Rust, C/C++, Julia, Fortran, R, and MATLAB (keywords, strings, comments, numbers).
14
+ - Expanded file-accept list for Load file content (`.h`, `.hpp`, `.jl`, `.f90`, `.f95`, `.f03`, `.f`, `.for`, `.r`, `.R`, `.m`, `.lua`).
15
+
16
+ ### Changed
17
+ - Renamed "Load file in editor" → "Load file content" (clarifies that file content is copied, not edited in-place).
18
+ - Lang selector visibility: shown when syntax highlight is On (Raw view) or in Preview mode; hidden otherwise.
19
+ - Updated README with comprehensive screenshot gallery (markdown, math, mermaid, code mode, fenced code).
20
+
5
21
  ## [0.2.0] — 2026-03-02
6
22
 
7
23
  ### Added
package/README.md CHANGED
@@ -6,21 +6,37 @@ Status: experimental alpha.
6
6
 
7
7
  ## Screenshots
8
8
 
9
- **Dark workspace**
9
+ **Markdown workspace — dark** (syntax-highlighted editor + rendered preview with code blocks, inline math, blockquotes)
10
10
 
11
11
  ![Dark workspace](./assets/screenshots/dark-workspace.png)
12
12
 
13
- **Dark critique flow**
13
+ **Markdown workspace — light**
14
14
 
15
- ![Dark critique flow](./assets/screenshots/dark-critique.png)
15
+ ![Light workspace](./assets/screenshots/light-workspace.png)
16
16
 
17
- **Light workspace**
17
+ **Math rendering** (LaTeX source → MathML: PDEs, matrices, display equations)
18
18
 
19
- ![Light workspace](./assets/screenshots/light-workspace.png)
19
+ ![Dark math](./assets/screenshots/dark-math.png)
20
+
21
+ **Math rendering — light**
22
+
23
+ ![Light math](./assets/screenshots/light-math.png)
24
+
25
+ **Mermaid diagrams** (fenced mermaid block → rendered flowchart with theme-aware colors)
26
+
27
+ ![Dark mermaid](./assets/screenshots/dark-mermaid.png)
28
+
29
+ **Code mode — dark** (TypeScript file with language-aware syntax highlighting, auto-detected from file extension)
30
+
31
+ ![Dark code mode](./assets/screenshots/dark-code-mode.png)
32
+
33
+ **Code mode — light**
34
+
35
+ ![Light code mode](./assets/screenshots/light-code-mode.png)
20
36
 
21
- **Dark annotation editing**
37
+ **Fenced code highlighting** (Julia code block inside markdown with token coloring)
22
38
 
23
- ![Dark annotation editing](./assets/screenshots/dark-annotation.png)
39
+ ![Dark Julia fenced](./assets/screenshots/dark-julia-fenced.png)
24
40
 
25
41
  ## Features
26
42
 
@@ -31,10 +47,17 @@ Status: experimental alpha.
31
47
  - Response load helpers:
32
48
  - non-critique: **Load response into editor**
33
49
  - critique: **Load critique (notes)** / **Load critique (full)**
34
- - File actions: **Save As…**, **Save file**, **Load file in editor**
35
- - View toggles: `Editor: Markdown|Preview`, `Response: Markdown|Preview`
50
+ - File actions: **Save As…**, **Save file**, **Load file content**
51
+ - View toggles: `Editor: Raw|Preview`, `Response: Raw|Preview`
36
52
  - Preview mode supports MathML equations and Mermaid fenced diagrams
37
- - Optional markdown highlighting toggles for editor and response markdown views (including fenced-code token colors for common languages)
53
+ - **Language-aware syntax highlighting** with selectable language mode:
54
+ - Markdown (default): headings, links, code fences, lists, quotes, inline code
55
+ - Code languages: JavaScript, TypeScript, Python, Bash, JSON, Rust, C, C++, Julia, Fortran, R, MATLAB
56
+ - Keywords, strings, comments, numbers, and variables highlighted using theme syntax color tokens
57
+ - Language auto-detected from file extension on file load; manually selectable via `Lang:` dropdown
58
+ - Applies to both editor Raw view (highlight overlay) and fenced code blocks in markdown
59
+ - Preview mode renders syntax-highlighted code when a non-markdown language is selected
60
+ - Separate syntax highlight toggles for editor and response Raw views, with local preference persistence
38
61
  - Theme-aware browser UI based on current pi theme, with refined surface depth and lighter visual chrome
39
62
 
40
63
  ## Commands
@@ -73,7 +96,7 @@ pi -e https://github.com/omaclaren/pi-studio
73
96
 
74
97
  - Local-only server (`127.0.0.1`) with rotating session tokens.
75
98
  - One studio request at a time.
76
- - Pi Studio is currently optimized for markdown workflows (model responses, plans, and notes), including fenced code blocks. Pure code files are supported, but highlighting is tuned for markdown and fenced blocks rather than full-file language mode.
99
+ - Pi Studio supports both markdown workflows (model responses, plans, and notes) and code file editing with language-aware syntax highlighting.
77
100
  - Studio URLs include a token query parameter; avoid sharing full Studio URLs.
78
101
  - Preview panes render markdown via `pandoc` (`gfm+tex_math_dollars` → HTML5 + MathML), including pandoc code syntax highlighting, sanitized in-browser with `dompurify`.
79
102
  - Preview markdown/code colors are mapped from active theme markdown (`md*`) and syntax (`syntax*`) tokens for closer terminal-vs-browser parity.
@@ -82,7 +105,6 @@ pi -e https://github.com/omaclaren/pi-studio
82
105
  - Preview rendering normalizes Obsidian wiki-image syntax (`![[path]]`, `![[path|alt]]`) into standard markdown images.
83
106
  - Install pandoc for full preview rendering (`brew install pandoc` on macOS).
84
107
  - If `pandoc` is unavailable, preview falls back to plain markdown text with an inline warning.
85
- - Some screenshots may show the Grammarly browser widget. Grammarly is a separate browser extension and is not part of pi-studio, but it works alongside it.
86
108
 
87
109
  ## License
88
110
 
Binary file
package/index.ts CHANGED
@@ -1917,7 +1917,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
1917
1917
  </select>
1918
1918
  <button id="saveAsBtn" type="button" title="Save editor text to a new file path.">Save As…</button>
1919
1919
  <button id="saveOverBtn" type="button" title="Overwrite current file with editor text." disabled>Save file</button>
1920
- <label class="file-label" title="Load a local file into editor text.">Load file in editor<input id="fileInput" type="file" accept=".txt,.md,.markdown,.rst,.adoc,.tex,.json,.js,.ts,.py,.java,.c,.cpp,.go,.rs,.rb,.swift,.sh,.html,.css,.xml,.yaml,.yml,.toml" /></label>
1920
+ <label class="file-label" title="Load a local file into editor text.">Load file content<input id="fileInput" type="file" accept=".txt,.md,.markdown,.rst,.adoc,.tex,.json,.js,.ts,.py,.java,.c,.cpp,.h,.hpp,.go,.rs,.rb,.swift,.sh,.html,.css,.xml,.yaml,.yml,.toml,.jl,.f90,.f95,.f03,.f,.for,.r,.R,.m,.lua" /></label>
1921
1921
  </div>
1922
1922
  </header>
1923
1923
 
@@ -1945,6 +1945,21 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
1945
1945
  <option value="off">Syntax highlight: Off</option>
1946
1946
  <option value="on" selected>Syntax highlight: On</option>
1947
1947
  </select>
1948
+ <select id="langSelect" aria-label="Highlight language">
1949
+ <option value="markdown" selected>Lang: Markdown</option>
1950
+ <option value="javascript">Lang: JavaScript</option>
1951
+ <option value="typescript">Lang: TypeScript</option>
1952
+ <option value="python">Lang: Python</option>
1953
+ <option value="bash">Lang: Bash</option>
1954
+ <option value="json">Lang: JSON</option>
1955
+ <option value="rust">Lang: Rust</option>
1956
+ <option value="c">Lang: C</option>
1957
+ <option value="cpp">Lang: C++</option>
1958
+ <option value="julia">Lang: Julia</option>
1959
+ <option value="fortran">Lang: Fortran</option>
1960
+ <option value="r">Lang: R</option>
1961
+ <option value="matlab">Lang: MATLAB</option>
1962
+ </select>
1948
1963
  </div>
1949
1964
  </div>
1950
1965
  <div id="sourceEditorWrap" class="editor-highlight-wrap">
@@ -2043,6 +2058,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2043
2058
  const sendRunBtn = document.getElementById("sendRunBtn");
2044
2059
  const copyDraftBtn = document.getElementById("copyDraftBtn");
2045
2060
  const highlightSelect = document.getElementById("highlightSelect");
2061
+ const langSelect = document.getElementById("langSelect");
2046
2062
 
2047
2063
  const initialSourceState = {
2048
2064
  source: (document.body && document.body.dataset && document.body.dataset.initialSource) || "blank",
@@ -2075,12 +2091,15 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2075
2091
  let paneFocusTarget = "off";
2076
2092
  const EDITOR_HIGHLIGHT_MAX_CHARS = 80_000;
2077
2093
  const EDITOR_HIGHLIGHT_STORAGE_KEY = "piStudio.editorHighlightEnabled";
2094
+ const EDITOR_LANGUAGE_STORAGE_KEY = "piStudio.editorLanguage";
2095
+ const SUPPORTED_LANGUAGES = ["markdown", "javascript", "typescript", "python", "bash", "json", "rust", "c", "cpp", "julia", "fortran", "r", "matlab"];
2078
2096
  const RESPONSE_HIGHLIGHT_MAX_CHARS = 120_000;
2079
2097
  const RESPONSE_HIGHLIGHT_STORAGE_KEY = "piStudio.responseHighlightEnabled";
2080
2098
  let sourcePreviewRenderTimer = null;
2081
2099
  let sourcePreviewRenderNonce = 0;
2082
2100
  let responsePreviewRenderNonce = 0;
2083
2101
  let editorHighlightEnabled = false;
2102
+ let editorLanguage = "markdown";
2084
2103
  let responseHighlightEnabled = false;
2085
2104
  let editorHighlightRenderRaf = null;
2086
2105
  const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
@@ -2470,10 +2489,14 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2470
2489
 
2471
2490
  function renderSourcePreviewNow() {
2472
2491
  if (editorView !== "preview") return;
2473
- const markdown = sourceTextEl.value || "";
2492
+ const text = sourceTextEl.value || "";
2493
+ if (editorLanguage && editorLanguage !== "markdown") {
2494
+ sourcePreviewEl.innerHTML = "<div class='response-markdown-highlight' style='white-space:pre;font-family:var(--font-mono);font-size:13px;line-height:1.5;padding:16px;overflow:auto;'>" + highlightCode(text, editorLanguage) + "</div>";
2495
+ return;
2496
+ }
2474
2497
  const nonce = ++sourcePreviewRenderNonce;
2475
2498
  sourcePreviewEl.innerHTML = "<div class='preview-loading'>Rendering preview…</div>";
2476
- void applyRenderedMarkdown(sourcePreviewEl, markdown, "source", nonce);
2499
+ void applyRenderedMarkdown(sourcePreviewEl, text, "source", nonce);
2477
2500
  }
2478
2501
 
2479
2502
  function scheduleSourcePreviewRender(delayMs) {
@@ -2598,6 +2621,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2598
2621
  sendRunBtn.disabled = uiBusy;
2599
2622
  copyDraftBtn.disabled = uiBusy;
2600
2623
  if (highlightSelect) highlightSelect.disabled = uiBusy;
2624
+ if (langSelect) langSelect.disabled = uiBusy;
2601
2625
  editorViewSelect.disabled = uiBusy;
2602
2626
  rightViewSelect.disabled = uiBusy;
2603
2627
  followSelect.disabled = uiBusy;
@@ -2644,6 +2668,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2644
2668
  }
2645
2669
 
2646
2670
  updateEditorHighlightState();
2671
+ updateLangSelectVisibility();
2647
2672
  }
2648
2673
 
2649
2674
  function setRightView(nextView) {
@@ -2729,6 +2754,13 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2729
2754
  if (first === "py" || first === "python") return "python";
2730
2755
  if (first === "sh" || first === "bash" || first === "zsh" || first === "shell") return "bash";
2731
2756
  if (first === "json" || first === "jsonc") return "json";
2757
+ if (first === "rust" || first === "rs") return "rust";
2758
+ if (first === "c" || first === "h") return "c";
2759
+ if (first === "cpp" || first === "c++" || first === "cxx" || first === "hpp") return "cpp";
2760
+ if (first === "julia" || first === "jl") return "julia";
2761
+ if (first === "fortran" || first === "f90" || first === "f95" || first === "f03" || first === "f" || first === "for") return "fortran";
2762
+ if (first === "r") return "r";
2763
+ if (first === "matlab" || first === "m") return "matlab";
2732
2764
 
2733
2765
  return "";
2734
2766
  }
@@ -2821,6 +2853,80 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2821
2853
  return "<span class='hl-code'>" + highlighted + "</span>";
2822
2854
  }
2823
2855
 
2856
+ if (lang === "rust") {
2857
+ const rustPattern = /(\\/\\/.*$)|("(?:[^"\\\\]|\\\\.)*")|(\\b(?:fn|let|mut|const|struct|enum|impl|trait|pub|mod|use|crate|self|super|match|if|else|for|while|loop|return|break|continue|where|as|in|ref|move|async|await|unsafe|extern|type|static|true|false|Some|None|Ok|Err|Self)\\b)|(\\b\\d[\\d_]*(?:\\.\\d[\\d_]*)?(?:f32|f64|u8|u16|u32|u64|u128|usize|i8|i16|i32|i64|i128|isize)?\\b)/g;
2858
+ const highlighted = highlightCodeTokens(source, rustPattern, (match) => {
2859
+ if (match[1]) return "hl-code-com";
2860
+ if (match[2]) return "hl-code-str";
2861
+ if (match[3]) return "hl-code-kw";
2862
+ if (match[4]) return "hl-code-num";
2863
+ return "hl-code";
2864
+ });
2865
+ return "<span class='hl-code'>" + highlighted + "</span>";
2866
+ }
2867
+
2868
+ if (lang === "c" || lang === "cpp") {
2869
+ const cPattern = /(\\/\\/.*$)|("(?:[^"\\\\]|\\\\.)*"|'(?:[^'\\\\]|\\\\.)')|(#\\s*\\w+)|(\\b(?:if|else|for|while|do|switch|case|break|continue|return|goto|struct|union|enum|typedef|sizeof|void|int|char|short|long|float|double|unsigned|signed|const|static|extern|volatile|register|inline|auto|restrict|true|false|NULL|nullptr|class|public|private|protected|virtual|override|template|typename|namespace|using|new|delete|try|catch|throw|noexcept|constexpr|auto|decltype|static_cast|dynamic_cast|reinterpret_cast|const_cast|std|include|define|ifdef|ifndef|endif|pragma)\\b)|(\\b\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?[fFlLuU]*\\b)/g;
2870
+ const highlighted = highlightCodeTokens(source, cPattern, (match) => {
2871
+ if (match[1]) return "hl-code-com";
2872
+ if (match[2]) return "hl-code-str";
2873
+ if (match[3]) return "hl-code-kw";
2874
+ if (match[4]) return "hl-code-kw";
2875
+ if (match[5]) return "hl-code-num";
2876
+ return "hl-code";
2877
+ });
2878
+ return "<span class='hl-code'>" + highlighted + "</span>";
2879
+ }
2880
+
2881
+ if (lang === "julia") {
2882
+ const jlPattern = /(#.*$)|("(?:[^"\\\\]|\\\\.)*"|'(?:[^'\\\\]|\\\\.)*')|(\\b(?:function|end|if|elseif|else|for|while|begin|let|local|global|const|return|break|continue|do|try|catch|finally|throw|module|import|using|export|struct|mutable|abstract|primitive|where|macro|quote|true|false|nothing|missing|in|isa|typeof)\\b)|(\\b\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?\\b)/g;
2883
+ const highlighted = highlightCodeTokens(source, jlPattern, (match) => {
2884
+ if (match[1]) return "hl-code-com";
2885
+ if (match[2]) return "hl-code-str";
2886
+ if (match[3]) return "hl-code-kw";
2887
+ if (match[4]) return "hl-code-num";
2888
+ return "hl-code";
2889
+ });
2890
+ return "<span class='hl-code'>" + highlighted + "</span>";
2891
+ }
2892
+
2893
+ if (lang === "fortran") {
2894
+ const fPattern = /(!.*$)|("(?:[^"\\\\]|\\\\.)*"|'(?:[^'\\\\]|\\\\.)*')|(\\b(?:program|end|subroutine|function|module|use|implicit|none|integer|real|double|precision|complex|character|logical|dimension|allocatable|intent|in|out|inout|parameter|data|do|if|then|else|elseif|endif|enddo|call|return|write|read|print|format|stop|contains|type|class|select|case|where|forall|associate|block|procedure|interface|abstract|extends|allocate|deallocate|cycle|exit|go|to|common|equivalence|save|external|intrinsic)\\b)|(\\b\\d+(?:\\.\\d+)?(?:[dDeE][+-]?\\d+)?\\b)/gi;
2895
+ const highlighted = highlightCodeTokens(source, fPattern, (match) => {
2896
+ if (match[1]) return "hl-code-com";
2897
+ if (match[2]) return "hl-code-str";
2898
+ if (match[3]) return "hl-code-kw";
2899
+ if (match[4]) return "hl-code-num";
2900
+ return "hl-code";
2901
+ });
2902
+ return "<span class='hl-code'>" + highlighted + "</span>";
2903
+ }
2904
+
2905
+ if (lang === "r") {
2906
+ const rPattern = /(#.*$)|("(?:[^"\\\\]|\\\\.)*"|'(?:[^'\\\\]|\\\\.)*')|(\\b(?:function|if|else|for|while|repeat|in|next|break|return|TRUE|FALSE|NULL|NA|NA_integer_|NA_real_|NA_complex_|NA_character_|Inf|NaN|library|require|source|local|switch)\\b)|(<-|->|<<-|->>)|(\\b\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?[Li]?\\b)/g;
2907
+ const highlighted = highlightCodeTokens(source, rPattern, (match) => {
2908
+ if (match[1]) return "hl-code-com";
2909
+ if (match[2]) return "hl-code-str";
2910
+ if (match[3]) return "hl-code-kw";
2911
+ if (match[4]) return "hl-code-kw";
2912
+ if (match[5]) return "hl-code-num";
2913
+ return "hl-code";
2914
+ });
2915
+ return "<span class='hl-code'>" + highlighted + "</span>";
2916
+ }
2917
+
2918
+ if (lang === "matlab") {
2919
+ const matPattern = /(%.*$)|('(?:[^']|'')*'|"(?:[^"\\\\]|\\\\.)*")|(\\b(?:function|end|if|elseif|else|for|while|switch|case|otherwise|try|catch|return|break|continue|global|persistent|classdef|properties|methods|events|enumeration|true|false)\\b)|(\\b\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?[i]?\\b)/g;
2920
+ const highlighted = highlightCodeTokens(source, matPattern, (match) => {
2921
+ if (match[1]) return "hl-code-com";
2922
+ if (match[2]) return "hl-code-str";
2923
+ if (match[3]) return "hl-code-kw";
2924
+ if (match[4]) return "hl-code-num";
2925
+ return "hl-code";
2926
+ });
2927
+ return "<span class='hl-code'>" + highlighted + "</span>";
2928
+ }
2929
+
2824
2930
  return wrapHighlight("hl-code", source);
2825
2931
  }
2826
2932
 
@@ -2889,6 +2995,43 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2889
2995
  return out.join("<br>");
2890
2996
  }
2891
2997
 
2998
+ function highlightCode(text, language) {
2999
+ const lines = String(text || "").replace(/\\r\\n/g, "\\n").split("\\n");
3000
+ const lang = normalizeFenceLanguage(language);
3001
+ const out = [];
3002
+ for (const line of lines) {
3003
+ if (line.length === 0) {
3004
+ out.push("");
3005
+ } else if (lang) {
3006
+ out.push(highlightCodeLine(line, lang));
3007
+ } else {
3008
+ out.push(escapeHtml(line));
3009
+ }
3010
+ }
3011
+ return out.join("<br>");
3012
+ }
3013
+
3014
+ function detectLanguageFromName(name) {
3015
+ if (!name) return "";
3016
+ const dot = name.lastIndexOf(".");
3017
+ if (dot < 0) return "";
3018
+ const ext = name.slice(dot + 1).toLowerCase();
3019
+ if (ext === "js" || ext === "mjs" || ext === "cjs" || ext === "jsx") return "javascript";
3020
+ if (ext === "ts" || ext === "mts" || ext === "cts" || ext === "tsx") return "typescript";
3021
+ if (ext === "py" || ext === "pyw") return "python";
3022
+ if (ext === "sh" || ext === "bash" || ext === "zsh") return "bash";
3023
+ if (ext === "json" || ext === "jsonc" || ext === "json5") return "json";
3024
+ if (ext === "rs") return "rust";
3025
+ if (ext === "c" || ext === "h") return "c";
3026
+ if (ext === "cpp" || ext === "cxx" || ext === "cc" || ext === "hpp" || ext === "hxx") return "cpp";
3027
+ if (ext === "jl") return "julia";
3028
+ if (ext === "f90" || ext === "f95" || ext === "f03" || ext === "f" || ext === "for") return "fortran";
3029
+ if (ext === "r" || ext === "R") return "r";
3030
+ if (ext === "m") return "matlab";
3031
+ if (ext === "md" || ext === "markdown" || ext === "mdx") return "markdown";
3032
+ return "";
3033
+ }
3034
+
2892
3035
  function renderEditorHighlightNow() {
2893
3036
  if (!sourceHighlightEl) return;
2894
3037
  if (!editorHighlightEnabled || editorView !== "markdown") {
@@ -2903,7 +3046,11 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2903
3046
  return;
2904
3047
  }
2905
3048
 
2906
- sourceHighlightEl.innerHTML = highlightMarkdown(text);
3049
+ if (editorLanguage === "markdown" || !editorLanguage) {
3050
+ sourceHighlightEl.innerHTML = highlightMarkdown(text);
3051
+ } else {
3052
+ sourceHighlightEl.innerHTML = highlightCode(text, editorLanguage);
3053
+ }
2907
3054
  syncEditorHighlightScroll();
2908
3055
  }
2909
3056
 
@@ -3008,6 +3155,46 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
3008
3155
  highlightSelect.value = editorHighlightEnabled ? "on" : "off";
3009
3156
  }
3010
3157
  updateEditorHighlightState();
3158
+ updateLangSelectVisibility();
3159
+ }
3160
+
3161
+ function readStoredEditorLanguage() {
3162
+ if (!window.localStorage) return null;
3163
+ try {
3164
+ const value = window.localStorage.getItem(EDITOR_LANGUAGE_STORAGE_KEY);
3165
+ if (value && SUPPORTED_LANGUAGES.indexOf(value) !== -1) return value;
3166
+ return null;
3167
+ } catch {
3168
+ return null;
3169
+ }
3170
+ }
3171
+
3172
+ function persistEditorLanguage(lang) {
3173
+ if (!window.localStorage) return;
3174
+ try {
3175
+ window.localStorage.setItem(EDITOR_LANGUAGE_STORAGE_KEY, lang || "markdown");
3176
+ } catch {}
3177
+ }
3178
+
3179
+ function setEditorLanguage(lang) {
3180
+ editorLanguage = (lang && SUPPORTED_LANGUAGES.indexOf(lang) !== -1) ? lang : "markdown";
3181
+ persistEditorLanguage(editorLanguage);
3182
+ if (langSelect) {
3183
+ langSelect.value = editorLanguage;
3184
+ }
3185
+ if (editorHighlightEnabled && editorView === "markdown") {
3186
+ scheduleEditorHighlightRender();
3187
+ }
3188
+ if (editorView === "preview") {
3189
+ scheduleSourcePreviewRender(0);
3190
+ }
3191
+ }
3192
+
3193
+ function updateLangSelectVisibility() {
3194
+ if (!langSelect) return;
3195
+ const highlightActive = editorHighlightEnabled && editorView === "markdown";
3196
+ const previewActive = editorView === "preview";
3197
+ langSelect.hidden = !(highlightActive || previewActive);
3011
3198
  }
3012
3199
 
3013
3200
  function setResponseHighlightEnabled(enabled) {
@@ -3468,6 +3655,12 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
3468
3655
  });
3469
3656
  }
3470
3657
 
3658
+ if (langSelect) {
3659
+ langSelect.addEventListener("change", () => {
3660
+ setEditorLanguage(langSelect.value);
3661
+ });
3662
+ }
3663
+
3471
3664
  pullLatestBtn.addEventListener("click", () => {
3472
3665
  if (queuedLatestResponse) {
3473
3666
  if (applyLatestPayload(queuedLatestResponse)) {
@@ -3703,6 +3896,10 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
3703
3896
  path: null,
3704
3897
  });
3705
3898
  refreshResponseUi();
3899
+ const detectedLang = detectLanguageFromName(file.name);
3900
+ if (detectedLang) {
3901
+ setEditorLanguage(detectedLang);
3902
+ }
3706
3903
  setStatus("Loaded file " + file.name + ".", "success");
3707
3904
  };
3708
3905
  reader.onerror = () => {
@@ -3719,6 +3916,10 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
3719
3916
  const initialHighlightEnabled = storedEditorHighlightEnabled ?? Boolean(highlightSelect && highlightSelect.value === "on");
3720
3917
  setEditorHighlightEnabled(initialHighlightEnabled);
3721
3918
 
3919
+ const initialDetectedLang = detectLanguageFromName(initialSourceState.path || initialSourceState.label || "");
3920
+ const storedLang = readStoredEditorLanguage();
3921
+ setEditorLanguage(initialDetectedLang || storedLang || "markdown");
3922
+
3722
3923
  const storedResponseHighlightEnabled = readStoredResponseHighlightEnabled();
3723
3924
  const initialResponseHighlightEnabled = storedResponseHighlightEnabled ?? Boolean(responseHighlightSelect && responseHighlightSelect.value === "on");
3724
3925
  setResponseHighlightEnabled(initialResponseHighlightEnabled);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Browser GUI for structured critique workflows in pi",
5
5
  "type": "module",
6
6
  "license": "MIT",