codex-plus-patcher 0.4.1 → 0.5.0

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/README.md CHANGED
@@ -30,6 +30,7 @@ patches plus readable runtime plugins:
30
30
  - add user-message bubble color controls in Appearance settings
31
31
  - add adaptive project colors for sidebar projects, grouped threads, pinned threads, user-message accents, and the composer
32
32
  - add the `Toggle sidebar blur` command palette entry to blur sidebar chat and project names for the current session
33
+ - add a fullscreen Mermaid diagram viewer with zoom controls
33
34
 
34
35
  The generated app includes a readable Codex Plus runtime under
35
36
  `webview/assets/codex-plus/`. Versioned ASAR patches install the runtime,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-plus-patcher",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "Patch queue tool for building a local Codex Plus.app from an installed Codex.app.",
6
6
  "repository": {
@@ -16,6 +16,7 @@ module.exports = buildCodexPlusPatchSet({
16
16
  "userMessageAttachments": "webview/assets/user-message-attachments-5G1ZKim-.js",
17
17
  "composer": "webview/assets/composer-DlMDPaCL.js",
18
18
  "localTaskRow": "webview/assets/local-task-row-CoPNn6SW.js",
19
+ "mermaidDiagramShell": "webview/assets/mermaid-diagram-shell-BO-t9BGx.js",
19
20
  "keyboardShortcutsSearchInput": "webview/assets/keyboard-shortcuts-search-input-C1dmntOi.js",
20
21
  "src": "src-l0hbMZ-p.js",
21
22
  "sidebarThreadKeys": "sidebar-thread-keys-Ch_amVKz.js",
@@ -16,6 +16,7 @@ module.exports = buildCodexPlusPatchSet({
16
16
  "userMessageAttachments": "webview/assets/user-message-attachments-5G1ZKim-.js",
17
17
  "composer": "webview/assets/composer-DlMDPaCL.js",
18
18
  "localTaskRow": "webview/assets/local-task-row-CoPNn6SW.js",
19
+ "mermaidDiagramShell": "webview/assets/mermaid-diagram-shell-BO-t9BGx.js",
19
20
  "keyboardShortcutsSearchInput": "webview/assets/keyboard-shortcuts-search-input-C1dmntOi.js",
20
21
  "src": "src-l0hbMZ-p.js",
21
22
  "sidebarThreadKeys": "sidebar-thread-keys-Ch_amVKz.js",
@@ -7,6 +7,7 @@ function buildCodexPlusPatchSet(config) {
7
7
  const newTitle = '<title>Codex Plus</title><script src="./assets/codex-plus/runtime.js"></script>';
8
8
  const titleFile = "webview/index.html";
9
9
  const workerFile = ".vite/build/worker.js";
10
+ const preloadFile = ".vite/build/preload.js";
10
11
  const files = config.files;
11
12
  const anchors = config.anchors;
12
13
  const mainFile = files.main;
@@ -19,6 +20,7 @@ function buildCodexPlusPatchSet(config) {
19
20
  const userMessageAttachmentsFile = files.userMessageAttachments;
20
21
  const composerFile = files.composer;
21
22
  const localTaskRowFile = files.localTaskRow;
23
+ const mermaidDiagramShellFile = files.mermaidDiagramShell;
22
24
  const electronMenuShortcutsFile = files.electronMenuShortcuts;
23
25
  const keyboardShortcutsSearchInputFile = files.keyboardShortcutsSearchInput;
24
26
  const srcFile = files.src;
@@ -128,6 +130,12 @@ const codexPlusSubrepoDiffHelpers = `
128
130
  const codexPlusDiagnosticHelpers = `
129
131
  function CPXDiagnosticDetails(e){return window.CodexPlus?.ui?.errors?.renderDetails?.(e)??null}`;
130
132
 
133
+ const codexPlusMermaidHelpers = `
134
+ function CPXMermaidDiagramProps(e){return window.CodexPlus?.ui?.mermaid?.diagramProps?.(e)}`;
135
+
136
+ const codexPlusNativeMainHelpers = `
137
+ function CPXOpenMermaidViewer(e){let t=e?.html;if(typeof t!==\`string\`||t.length===0)return{ok:!1};let n=(0,s.join)((0,o.tmpdir)(),\`codex-plus-mermaid-\${(0,u.randomUUID)()}.html\`);(0,l.writeFileSync)(n,t,\`utf8\`);let r=new a.BrowserWindow({height:900,resizable:!0,show:!0,title:\`Mermaid diagram viewer\`,webPreferences:{contextIsolation:!0,nodeIntegration:!1,sandbox:!0},width:1400});return r.webContents.setWindowOpenHandler(e=>{try{let t=new URL(e.url);if(t.protocol===\`https:\`&&t.hostname===\`mermaid.live\`)a.shell.openExternal(e.url)}catch{}return{action:\`deny\`}}),r.on(\`closed\`,()=>{try{(0,l.unlinkSync)(n)}catch{}}),r.loadURL((0,S.pathToFileURL)(n).toString()).catch(()=>{}),{ok:!0}}function CPXRegisterNativeRequest(e){return a.ipcMain.handle(\`codex_plus:native-request\`,async(t,n)=>{if(!e.isTrustedIpcEvent(t))return{ok:!1};switch(n?.method){case\`mermaid/openViewer\`:return CPXOpenMermaidViewer(n.params);default:return{ok:!1}}})}`;
138
+
131
139
  function patchThreadSidePanelTabs(text) {
132
140
  let patched = replaceOnce(
133
141
  text,
@@ -536,6 +544,45 @@ function patchLocalTaskRow(text) {
536
544
  );
537
545
  }
538
546
 
547
+ function patchMermaidDiagramShell(text) {
548
+ let patched = replaceOnce(
549
+ text,
550
+ "function d(e){let t=(0,s.c)(18),{Renderer:n,className:r,code:i,fallback:d,isCodeFenceOpen:f,wideBlockKind:p}=e,",
551
+ `${codexPlusMermaidHelpers}function d(e){let t=(0,s.c)(18),{Renderer:n,className:r,code:i,fallback:d,isCodeFenceOpen:f,wideBlockKind:p}=e,`,
552
+ "mermaid diagram shell helper insertion anchor",
553
+ );
554
+ return replaceOnce(
555
+ patched,
556
+ "O=(0,c.jsx)(`div`,{className:T,\"data-wide-markdown-block\":E,\"data-wide-markdown-block-kind\":p,children:D})",
557
+ "O=(0,c.jsx)(`div`,{className:T,...CPXMermaidDiagramProps({code:i}),\"data-wide-markdown-block\":E,\"data-wide-markdown-block-kind\":p,children:D})",
558
+ "mermaid diagram shell host props anchor",
559
+ );
560
+ }
561
+
562
+ function patchPreloadNativeBridge(text) {
563
+ return replaceOnce(
564
+ text,
565
+ "e.contextBridge.exposeInMainWorld(`codexWindowType`,m),e.contextBridge.exposeInMainWorld(`electronBridge`,D),typeof window<`u`",
566
+ "e.contextBridge.exposeInMainWorld(`codexWindowType`,m),e.contextBridge.exposeInMainWorld(`electronBridge`,D),e.contextBridge.exposeInMainWorld(`codexPlusNative`,{request:(t,n)=>e.ipcRenderer.invoke(`codex_plus:native-request`,{method:t,params:n})}),typeof window<`u`",
567
+ "codex plus native preload bridge anchor",
568
+ );
569
+ }
570
+
571
+ function patchMainNativeBridge(text) {
572
+ let patched = replaceOnce(
573
+ text,
574
+ "function z1(e){return a.ipcMain.handle(Tl,async(t,n)=>{",
575
+ `${codexPlusNativeMainHelpers}function z1(e){return a.ipcMain.handle(Tl,async(t,n)=>{`,
576
+ "codex plus native main helper insertion anchor",
577
+ );
578
+ return replaceOnce(
579
+ patched,
580
+ "v0({buildFlavor:i,getContextForWebContents:N.getContextForWebContents,isTrustedIpcEvent:te,usesOwlAppShell:y}),a.ipcMain.on(kl,",
581
+ "v0({buildFlavor:i,getContextForWebContents:N.getContextForWebContents,isTrustedIpcEvent:te,usesOwlAppShell:y}),CPXRegisterNativeRequest({isTrustedIpcEvent:te}),a.ipcMain.on(kl,",
582
+ "codex plus native main registration anchor",
583
+ );
584
+ }
585
+
539
586
  return makePatchSet({
540
587
  id: config.id,
541
588
  codexVersion: config.codexVersion,
@@ -597,6 +644,17 @@ return makePatchSet({
597
644
  [keyboardShortcutsSearchInputFile, patchKeyboardShortcutsSearchInput],
598
645
  ],
599
646
  },
647
+ ...(mermaidDiagramShellFile ? [{
648
+ id: "codex-plus-native-bridge",
649
+ fileTransforms: [
650
+ [preloadFile, patchPreloadNativeBridge],
651
+ [mainFile, patchMainNativeBridge],
652
+ ],
653
+ }] : []),
654
+ ...(mermaidDiagramShellFile ? [{
655
+ id: "mermaid-fullscreen-viewer",
656
+ fileTransforms: [[mermaidDiagramShellFile, patchMermaidDiagramShell]],
657
+ }] : []),
600
658
  ],
601
659
  });
602
660
  }
@@ -12,6 +12,7 @@ const runtimeFiles = [
12
12
  ["webview/assets/codex-plus/plugins/userBubbleColors.js", "plugins/userBubbleColors.js"],
13
13
  ["webview/assets/codex-plus/plugins/projectColors.js", "plugins/projectColors.js"],
14
14
  ["webview/assets/codex-plus/plugins/sidebarNameBlur.js", "plugins/sidebarNameBlur.js"],
15
+ ["webview/assets/codex-plus/plugins/mermaidFullscreen.js", "plugins/mermaidFullscreen.js"],
15
16
  ];
16
17
 
17
18
  function codexPlusRuntimeAssets() {
@@ -0,0 +1,275 @@
1
+ (function () {
2
+ const CodexPlus = window.CodexPlus;
3
+ const SELECTOR = "[data-codex-plus-mermaid-diagram]";
4
+ const BUTTON_CLASS = "codex-plus-mermaid-expand-button";
5
+
6
+ function sourceFor(container) {
7
+ return container.querySelector("pre.sr-only")?.textContent || "";
8
+ }
9
+
10
+ function assetUrl(assetPath) {
11
+ const appScript = document.querySelector('script[type="module"][src*="/assets/"],script[type="module"][src^="./assets/"]');
12
+ if (appScript?.src) return new URL(assetPath, new URL(".", appScript.src)).href;
13
+ return new URL(`assets/${assetPath}`, document.baseURI).href;
14
+ }
15
+
16
+ function escapeHtml(value) {
17
+ return String(value).replace(/[&<>"']/g, (char) => ({
18
+ "&": "&amp;",
19
+ "<": "&lt;",
20
+ ">": "&gt;",
21
+ '"': "&quot;",
22
+ "'": "&#39;",
23
+ })[char]);
24
+ }
25
+
26
+ function mermaidLiveUrl(source) {
27
+ const state = JSON.stringify({
28
+ code: source,
29
+ mermaid: { theme: "default" },
30
+ updateEditor: false,
31
+ });
32
+ return `https://mermaid.live/edit#base64:${btoa(unescape(encodeURIComponent(state))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")}`;
33
+ }
34
+
35
+ function viewerHtml({ source, isDark, mermaidModuleUrl, debug }) {
36
+ return `<!doctype html>
37
+ <html>
38
+ <head>
39
+ <meta charset="utf-8">
40
+ <title>Mermaid diagram viewer</title>
41
+ <style>
42
+ :root{color-scheme:${isDark ? "dark" : "light"};--viewer-bg:${isDark ? "#0a0a0a" : "#fff"};--viewer-fg:${isDark ? "#fff" : "#111"};--viewer-toolbar-bg:${isDark ? "#252525" : "#f7f7f7"};--viewer-border:${isDark ? "rgba(255,255,255,.18)" : "rgba(0,0,0,.16)"};--viewer-button-border:${isDark ? "rgba(255,255,255,.22)" : "rgba(0,0,0,.18)"};--viewer-button-bg:${isDark ? "rgba(255,255,255,.08)" : "rgba(0,0,0,.06)"};--viewer-muted:${isDark ? "#cfcfcf" : "#333"}}
43
+ :root[data-theme="dark"]{color-scheme:dark;--viewer-bg:#0a0a0a;--viewer-fg:#fff;--viewer-toolbar-bg:#252525;--viewer-border:rgba(255,255,255,.18);--viewer-button-border:rgba(255,255,255,.22);--viewer-button-bg:rgba(255,255,255,.08);--viewer-muted:#cfcfcf}
44
+ :root[data-theme="light"]{color-scheme:light;--viewer-bg:#fff;--viewer-fg:#111;--viewer-toolbar-bg:#f7f7f7;--viewer-border:rgba(0,0,0,.16);--viewer-button-border:rgba(0,0,0,.18);--viewer-button-bg:rgba(0,0,0,.06);--viewer-muted:#333}
45
+ *{box-sizing:border-box}
46
+ html,body{height:100%;margin:0}
47
+ body{display:flex;flex-direction:column;background:var(--viewer-bg);color:var(--viewer-fg);font:13px -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
48
+ .toolbar{display:flex;gap:8px;justify-content:flex-end;padding:10px;border-bottom:1px solid var(--viewer-border);background:var(--viewer-toolbar-bg)}
49
+ button{min-width:42px;border:1px solid var(--viewer-button-border);border-radius:6px;background:var(--viewer-button-bg);color:inherit;padding:6px 10px}
50
+ button:focus-visible{outline:2px solid #60a5fa;outline-offset:2px}
51
+ .viewport{flex:1;overflow:auto;padding:16px}
52
+ .stage{width:max-content;min-width:100%}
53
+ .stage svg{display:block;max-width:none;background:var(--viewer-bg)}
54
+ .render-status{position:fixed;left:12px;bottom:10px;z-index:10;color:var(--viewer-muted);font-size:12px}
55
+ .render-status[hidden]{display:none}
56
+ </style>
57
+ </head>
58
+ <body>
59
+ <div class="toolbar">
60
+ <button id="zoom-fit" type="button" aria-label="Zoom to fit">Fit</button>
61
+ <button id="zoom-width" type="button" aria-label="Zoom to width">Width</button>
62
+ <button id="zoom-height" type="button" aria-label="Zoom to height">Height</button>
63
+ <button id="zoom-out" type="button" aria-label="Zoom out">-</button>
64
+ <button id="zoom-reset" type="button" aria-label="Reset zoom">100%</button>
65
+ <button id="zoom-in" type="button" aria-label="Zoom in">+</button>
66
+ <button id="theme-toggle" type="button" aria-label="Toggle Mermaid theme">${isDark ? "Dark" : "Light"}</button>
67
+ <button id="open-live" type="button" aria-label="Open in Mermaid Live">Live</button>
68
+ <button id="close" type="button" aria-label="Close Mermaid diagram viewer">Close</button>
69
+ </div>
70
+ <div class="viewport"><div class="stage" id="stage"></div></div>
71
+ <div class="render-status" id="render-status" hidden>Rendering Mermaid source...</div>
72
+ <script type="module">
73
+ let scale = 1;
74
+ let darkTheme = ${isDark ? "true" : "false"};
75
+ const source = ${JSON.stringify(source)};
76
+ const mermaidModuleUrl = ${JSON.stringify(mermaidModuleUrl)};
77
+ const liveUrl = ${JSON.stringify(mermaidLiveUrl(source))};
78
+ const debug = ${debug ? "true" : "false"} || localStorage.getItem("codexPlusMermaidDebug") === "1";
79
+ const stage = document.getElementById("stage");
80
+ const viewport = document.querySelector(".viewport");
81
+ const reset = document.getElementById("zoom-reset");
82
+ const themeToggle = document.getElementById("theme-toggle");
83
+ const renderStatus = document.getElementById("render-status");
84
+ let fitMode = "fit";
85
+ let renderCount = 0;
86
+ let renderInFlight = false;
87
+ let renderQueued = false;
88
+ function diagram() {
89
+ return stage.querySelector("svg");
90
+ }
91
+ function baseSize() {
92
+ const svg = diagram();
93
+ const viewBox = svg?.viewBox?.baseVal;
94
+ if (viewBox && viewBox.width > 0 && viewBox.height > 0) return { width: viewBox.width, height: viewBox.height };
95
+ const rect = svg?.getBoundingClientRect();
96
+ return { width: rect?.width || 800, height: rect?.height || 600 };
97
+ }
98
+ let base = { width: 800, height: 600 };
99
+ function setScale(next, mode = null) {
100
+ fitMode = mode;
101
+ scale = Math.max(0.05, Math.min(8, next));
102
+ const svg = diagram();
103
+ if (svg) {
104
+ svg.removeAttribute("width");
105
+ svg.removeAttribute("height");
106
+ svg.style.width = Math.round(base.width * scale) + "px";
107
+ svg.style.height = Math.round(base.height * scale) + "px";
108
+ }
109
+ reset.textContent = Math.round(scale * 100) + "%";
110
+ }
111
+ function fitScale(mode) {
112
+ const width = Math.max(1, viewport.clientWidth - 32);
113
+ const height = Math.max(1, viewport.clientHeight - 32);
114
+ const byWidth = width / base.width;
115
+ const byHeight = height / base.height;
116
+ if (mode === "width") return byWidth;
117
+ if (mode === "height") return byHeight;
118
+ return Math.min(byWidth, byHeight);
119
+ }
120
+ function applyFit(mode) {
121
+ setScale(fitScale(mode), mode);
122
+ }
123
+ function applyThemeChrome() {
124
+ document.documentElement.dataset.theme = darkTheme ? "dark" : "light";
125
+ document.documentElement.style.colorScheme = darkTheme ? "dark" : "light";
126
+ themeToggle.textContent = darkTheme ? "Dark" : "Light";
127
+ themeToggle.setAttribute("aria-pressed", String(darkTheme));
128
+ }
129
+ function themeDirective() {
130
+ return "%%{init: " + JSON.stringify({ theme: darkTheme ? "dark" : "default" }) + "}%%" + String.fromCharCode(10);
131
+ }
132
+ function sourceForTheme() {
133
+ const trimmed = source.trimStart();
134
+ if (!trimmed.startsWith("%%{")) return themeDirective() + source;
135
+ const markerEnd = trimmed.indexOf("}%%");
136
+ if (markerEnd < 0) return themeDirective() + source;
137
+ const directive = trimmed.slice(0, markerEnd + 3).toLowerCase();
138
+ if (!directive.startsWith("%%{init:") && !directive.startsWith("%%{initialize:")) return themeDirective() + source;
139
+ let rest = trimmed.slice(markerEnd + 3);
140
+ while ([9, 10, 13, 32].includes(rest.charCodeAt(0))) rest = rest.slice(1);
141
+ return themeDirective() + rest;
142
+ }
143
+ async function renderFromSource() {
144
+ if (renderInFlight) {
145
+ renderQueued = true;
146
+ return;
147
+ }
148
+ renderInFlight = true;
149
+ renderStatus.hidden = !debug;
150
+ renderStatus.textContent = "Rendering Mermaid source...";
151
+ themeToggle.disabled = true;
152
+ try {
153
+ const mermaid = (await import(mermaidModuleUrl)).default;
154
+ mermaid.initialize({
155
+ startOnLoad: false,
156
+ securityLevel: "strict",
157
+ suppressErrorRendering: true,
158
+ deterministicIds: true,
159
+ deterministicIDSeed: "codex-plus-mermaid-viewer",
160
+ htmlLabels: false,
161
+ flowchart: { htmlLabels: false },
162
+ darkMode: darkTheme,
163
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
164
+ theme: darkTheme ? "dark" : "default",
165
+ });
166
+ applyThemeChrome();
167
+ const rendered = await mermaid.render("codex-plus-mermaid-viewer-" + String(renderCount += 1), sourceForTheme());
168
+ stage.innerHTML = rendered.svg;
169
+ renderStatus.textContent = "Rendered from Mermaid source";
170
+ base = baseSize();
171
+ applyFit(fitMode || "fit");
172
+ } finally {
173
+ renderInFlight = false;
174
+ themeToggle.disabled = false;
175
+ if (renderQueued) {
176
+ renderQueued = false;
177
+ renderFromSource();
178
+ }
179
+ }
180
+ }
181
+ document.getElementById("zoom-fit").addEventListener("click", () => applyFit("fit"));
182
+ document.getElementById("zoom-width").addEventListener("click", () => applyFit("width"));
183
+ document.getElementById("zoom-height").addEventListener("click", () => applyFit("height"));
184
+ document.getElementById("zoom-out").addEventListener("click", () => setScale(scale - 0.2));
185
+ reset.addEventListener("click", () => setScale(1));
186
+ document.getElementById("zoom-in").addEventListener("click", () => setScale(scale + 0.2));
187
+ themeToggle.addEventListener("click", () => { darkTheme = !darkTheme; applyThemeChrome(); renderQueued = true; renderFromSource(); });
188
+ document.getElementById("open-live").addEventListener("click", () => window.open(liveUrl, "_blank", "noopener"));
189
+ document.getElementById("close").addEventListener("click", () => window.close());
190
+ window.addEventListener("resize", () => { if (fitMode) applyFit(fitMode); });
191
+ document.addEventListener("keydown", (event) => {
192
+ if (event.key === "Escape") window.close();
193
+ if ((event.metaKey || event.ctrlKey) && event.key === "=") { event.preventDefault(); setScale(scale + 0.2); }
194
+ if ((event.metaKey || event.ctrlKey) && event.key === "-") { event.preventDefault(); setScale(scale - 0.2); }
195
+ if ((event.metaKey || event.ctrlKey) && event.key === "0") { event.preventDefault(); setScale(1); }
196
+ });
197
+ applyThemeChrome();
198
+ renderFromSource().catch((error) => {
199
+ renderStatus.hidden = false;
200
+ renderStatus.textContent = "Mermaid render failed: " + String(error?.message || error);
201
+ console.error("[Codex Plus] Mermaid source render failed", error);
202
+ });
203
+ </script>
204
+ </body>
205
+ </html>`;
206
+ }
207
+
208
+ function button(label) {
209
+ const element = document.createElement("button");
210
+ element.type = "button";
211
+ element.className = BUTTON_CLASS;
212
+ element.setAttribute("aria-label", label);
213
+ element.title = label;
214
+ return element;
215
+ }
216
+
217
+ function openViewer(container) {
218
+ const source = sourceFor(container);
219
+ const isDark = document.documentElement.classList.contains("dark") || document.documentElement.classList.contains("electron-dark");
220
+ const debug = localStorage.getItem("codexPlusMermaidDebug") === "1";
221
+ const html = source
222
+ ? viewerHtml({ source, isDark, mermaidModuleUrl: assetUrl("mermaid.core-eIokQLcr.js"), debug })
223
+ : `<!doctype html><meta charset="utf-8"><body>${escapeHtml("No Mermaid source was found.")}</body>`;
224
+ CodexPlus.native.request("mermaid/openViewer", { html }).catch(() => {});
225
+ }
226
+
227
+ function decorate(container) {
228
+ if (container.querySelector(`:scope > .${BUTTON_CLASS}`)) return;
229
+ container.style.position ||= "relative";
230
+ const control = button("Open Mermaid diagram fullscreen");
231
+ control.addEventListener("click", () => openViewer(container));
232
+ container.prepend(control);
233
+ }
234
+
235
+ function decorateAll(root = document) {
236
+ for (const container of root.querySelectorAll(SELECTOR)) decorate(container);
237
+ }
238
+
239
+ CodexPlus.registerPlugin(
240
+ CodexPlus.definePlugin({
241
+ id: "mermaidFullscreen",
242
+ name: "Mermaid Fullscreen Viewer",
243
+ description: "Adds a separate fullscreen viewer with zoom controls to rendered Mermaid diagrams.",
244
+ required: true,
245
+ styles:
246
+ `[data-codex-plus-mermaid-diagram]{position:relative}` +
247
+ `.${BUTTON_CLASS}{position:absolute;left:.5rem;top:.5rem;z-index:30;display:inline-flex;width:1.75rem;height:1.75rem;align-items:center;justify-content:center;border:1px solid var(--color-token-input-border,rgba(127,127,127,.35));border-radius:.375rem;background:var(--color-background-elevated-primary,#fff);color:var(--color-token-foreground,#111);box-shadow:0 2px 8px rgba(0,0,0,.12);opacity:.82}` +
248
+ `.${BUTTON_CLASS}::before,.${BUTTON_CLASS}::after{content:"";position:absolute;width:.42rem;height:.42rem;border-color:currentColor;border-style:solid}` +
249
+ `.${BUTTON_CLASS}::before{right:.42rem;top:.42rem;border-width:2px 2px 0 0}` +
250
+ `.${BUTTON_CLASS}::after{left:.42rem;bottom:.42rem;border-width:0 0 2px 2px}` +
251
+ `.${BUTTON_CLASS}:hover,.${BUTTON_CLASS}:focus-visible{opacity:1;outline:2px solid var(--color-token-focus-border,#3b82f6);outline-offset:2px}` +
252
+ `:root.dark .${BUTTON_CLASS},:root.electron-dark .${BUTTON_CLASS}{background:var(--color-background-elevated-primary,#111);color:var(--color-token-foreground,#fff);box-shadow:0 2px 10px rgba(0,0,0,.45)}`,
253
+ exports: { decorateAll, openViewer },
254
+ start(api) {
255
+ api.ui.mermaid.decorateDiagram(() => ({ "data-codex-plus-mermaid-diagram": "" }));
256
+ const install = () => {
257
+ if (!document.body) return;
258
+ decorateAll();
259
+ const observer = new MutationObserver((records) => {
260
+ for (const record of records) {
261
+ for (const node of record.addedNodes) {
262
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
263
+ if (node.matches?.(SELECTOR)) decorate(node);
264
+ decorateAll(node);
265
+ }
266
+ }
267
+ });
268
+ observer.observe(document.body, { childList: true, subtree: true });
269
+ };
270
+ if (document.body) install();
271
+ else document.addEventListener("DOMContentLoaded", install, { once: true });
272
+ },
273
+ }),
274
+ );
275
+ })();
@@ -330,10 +330,15 @@
330
330
  composer: { surfaceDecorators: [], decorateSurface(fn) { this.surfaceDecorators.push(fn); return fn; }, surfaceProps(props) { return applyDecorators(props, this.surfaceDecorators); } },
331
331
  about: { buildInfo: [], addBuildInfo(fn) { this.buildInfo.push(fn); return fn; } },
332
332
  errors: { boundaryDecorators: [], decorateBoundary(fn) { this.boundaryDecorators.push(fn); return fn; }, renderDetails: renderErrorDetails },
333
+ mermaid: {
334
+ diagramDecorators: [],
335
+ decorateDiagram(fn) { this.diagramDecorators.push(fn); return fn; },
336
+ diagramProps(props) { return applyDecorators(props, this.diagramDecorators); },
337
+ },
333
338
  },
334
339
  commands: { register: registerCommand, run: runCommand, all: () => Array.from(commands.values()), menuItems: (group) => Array.from(commands.values()).filter((command) => commandGroups(command).includes(group)) },
335
340
  settings: { define: defineSettings },
336
- native: { async request(method, params) { return globalObject.CodexPlusHost?.nativeRequest?.(method, params); } },
341
+ native: { async request(method, params) { return globalObject.codexPlusNative?.request?.(method, params) ?? globalObject.CodexPlusHost?.nativeRequest?.(method, params); } },
337
342
  styles: { register: registerStyle, setRootVars },
338
343
  };
339
344
 
@@ -348,6 +353,7 @@
348
353
  "plugins/userBubbleColors.js",
349
354
  "plugins/projectColors.js",
350
355
  "plugins/sidebarNameBlur.js",
356
+ "plugins/mermaidFullscreen.js",
351
357
  ];
352
358
 
353
359
  if (typeof document !== "undefined") {