regen.mde 0.8.1 → 0.8.2

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.
Files changed (41) hide show
  1. package/LICENSE +16 -16
  2. package/bin/build-corpus-editor.js +83 -83
  3. package/bin/build-corpus.js +41 -41
  4. package/bin/regen-mdeditor-install.js +27 -27
  5. package/bin/regen-mdeditor-uninstall.js +19 -19
  6. package/bin/validate-katex.js +93 -93
  7. package/desktop/BuildCorpusEditor/BuildCorpusEditor.csproj +22 -22
  8. package/desktop/BuildCorpusEditor/EditorForm.cs +58 -58
  9. package/desktop/BuildCorpusEditor/app.manifest +16 -16
  10. package/dist/release/{regen-mde-0.8.0-win-x64.zip → regen-mde-0.6.1-win-x64.zip} +0 -0
  11. package/dist/release/{regen-mde-0.8.1-win-x64.zip → regen-mde-0.8.2-win-x64.zip} +0 -0
  12. package/dist/windows-editor/BuildCorpusEditor.dll +0 -0
  13. package/dist/windows-editor/BuildCorpusEditor.exe +0 -0
  14. package/dist/windows-editor/BuildCorpusEditor.pdb +0 -0
  15. package/dist/windows-editor/wwwroot/index.html +20 -20
  16. package/editor-web/index.html +21 -21
  17. package/editor-web/src/main.jsx +107 -107
  18. package/editor-web/src/styles.css +99 -99
  19. package/editor-web/vite.config.js +13 -13
  20. package/examples/build-corpus.config.example.json +21 -21
  21. package/installer/install-regen-mde.ps1 +214 -214
  22. package/installer/regen-mde.nsi +81 -81
  23. package/package.json +1 -1
  24. package/pyproject.toml +1 -1
  25. package/scripts/build-windows-editor.ps1 +47 -47
  26. package/scripts/package-windows-editor.ps1 +90 -90
  27. package/scripts/run-corpus.ps1 +28 -28
  28. package/scripts/run-editor-implementation-plane.ps1 +226 -226
  29. package/scripts/run-required-tests.ps1 +98 -98
  30. package/scripts/run-smoke.ps1 +28 -28
  31. package/src/build_corpus/__init__.py +3 -3
  32. package/src/build_corpus/docx_exporter.py +10 -4
  33. package/src/build_corpus/equations.py +1345 -1345
  34. package/src/build_corpus/templates/__init__.py +1 -1
  35. package/src/build_corpus/validate_assets.py +46 -46
  36. package/tools/audit_corpus.py +203 -203
  37. package/tools/collect_microsoft_word_templates.py +228 -228
  38. package/tools/collect_online_docx_corpus.py +272 -272
  39. package/tools/collect_online_pptx_corpus.py +252 -252
  40. package/tools/compare_pptx_inputs_outputs.py +87 -87
  41. package/tools/roundtrip_docx_corpus.py +171 -171
@@ -634,69 +634,69 @@ console.log("style fixture");
634
634
  await ClickButtonAsync("Run Check");
635
635
  await WaitForBodyTextAsync("Checked ", "Run Check did not update status.");
636
636
 
637
- await AssertControlStepAsync("layout still usable", """
638
- (() => {
639
- const modeStrip = document.querySelector(".mode-strip");
640
- if (!modeStrip) throw new Error("Mode strip missing.");
637
+ await AssertControlStepAsync("layout still usable", """
638
+ (() => {
639
+ const modeStrip = document.querySelector(".mode-strip");
640
+ if (!modeStrip) throw new Error("Mode strip missing.");
641
641
  const canvas = document.querySelector(".canvas-wrap");
642
642
  if (!canvas || canvas.getBoundingClientRect().width < 300) {
643
643
  throw new Error("Editor canvas did not render with usable width.");
644
644
  }
645
645
  return [...document.querySelectorAll(".statusbar span")].map((node) => node.textContent).join(" | ");
646
- })()
647
- """);
648
-
649
- await AssertControlStepAsync("side panels collapse clicks", """
650
- (() => {
651
- const canvas = document.querySelector(".canvas-wrap");
652
- const left = document.querySelector(".left-panel");
653
- const right = document.querySelector(".right-panel");
654
- if (!canvas || !left || !right) throw new Error("Panel collapse elements missing.");
655
- const before = canvas.getBoundingClientRect().width;
656
- document.querySelector('button[aria-label="Collapse Document Flow panel"]')?.click();
657
- document.querySelector('button[aria-label="Collapse Output Health panel"]')?.click();
658
- window.__REGEN_PANEL_BEFORE = before;
659
- return `collapse clicks scheduled from ${before}`;
660
- })()
661
- """);
662
- await Task.Delay(250);
663
- await AssertControlStepAsync("side panels collapse without focus", """
664
- (() => {
665
- const shell = document.querySelector(".app-shell");
666
- const canvas = document.querySelector(".canvas-wrap");
667
- const left = document.querySelector(".left-panel");
668
- const right = document.querySelector(".right-panel");
669
- if (!shell || !canvas || !left || !right) throw new Error("Panel collapse elements missing.");
670
- const before = Number(window.__REGEN_PANEL_BEFORE || 0);
671
- if (!shell.classList.contains("left-collapsed") || !shell.classList.contains("right-collapsed")) {
672
- throw new Error("Panel collapse classes were not applied.");
673
- }
674
- if (!left.classList.contains("collapsed") || !right.classList.contains("collapsed")) {
675
- throw new Error("Panel elements were not marked collapsed.");
676
- }
677
- const after = canvas.getBoundingClientRect().width;
678
- if (after <= before + 120) throw new Error(`Editor canvas did not reclaim enough width: ${before} -> ${after}`);
679
- document.querySelector('button[aria-label="Expand Document Flow panel"]')?.click();
680
- document.querySelector('button[aria-label="Expand Output Health panel"]')?.click();
681
- window.__REGEN_PANEL_AFTER = after;
682
- return `panels collapsed: ${before} -> ${after}`;
683
- })()
684
- """);
685
- await Task.Delay(250);
686
- await AssertControlStepAsync("side panels expand without focus", """
687
- (() => {
688
- const shell = document.querySelector(".app-shell");
689
- if (!shell) throw new Error("Panel collapse shell missing.");
690
- if (shell.classList.contains("left-collapsed") || shell.classList.contains("right-collapsed")) {
691
- throw new Error("Panel expand did not restore the app shell.");
692
- }
693
- return `panels expanded after ${window.__REGEN_PANEL_AFTER}`;
694
- })()
695
- """);
696
-
697
- if (!File.Exists(markdownOutput) || !File.ReadAllText(markdownOutput).Contains("UI save-as edit marker", StringComparison.Ordinal))
698
- {
699
- throw new InvalidOperationException("Save MD As did not write the smoke Markdown output.");
646
+ })()
647
+ """);
648
+
649
+ await AssertControlStepAsync("side panels collapse clicks", """
650
+ (() => {
651
+ const canvas = document.querySelector(".canvas-wrap");
652
+ const left = document.querySelector(".left-panel");
653
+ const right = document.querySelector(".right-panel");
654
+ if (!canvas || !left || !right) throw new Error("Panel collapse elements missing.");
655
+ const before = canvas.getBoundingClientRect().width;
656
+ document.querySelector('button[aria-label="Collapse Document Flow panel"]')?.click();
657
+ document.querySelector('button[aria-label="Collapse Output Health panel"]')?.click();
658
+ window.__REGEN_PANEL_BEFORE = before;
659
+ return `collapse clicks scheduled from ${before}`;
660
+ })()
661
+ """);
662
+ await Task.Delay(250);
663
+ await AssertControlStepAsync("side panels collapse without focus", """
664
+ (() => {
665
+ const shell = document.querySelector(".app-shell");
666
+ const canvas = document.querySelector(".canvas-wrap");
667
+ const left = document.querySelector(".left-panel");
668
+ const right = document.querySelector(".right-panel");
669
+ if (!shell || !canvas || !left || !right) throw new Error("Panel collapse elements missing.");
670
+ const before = Number(window.__REGEN_PANEL_BEFORE || 0);
671
+ if (!shell.classList.contains("left-collapsed") || !shell.classList.contains("right-collapsed")) {
672
+ throw new Error("Panel collapse classes were not applied.");
673
+ }
674
+ if (!left.classList.contains("collapsed") || !right.classList.contains("collapsed")) {
675
+ throw new Error("Panel elements were not marked collapsed.");
676
+ }
677
+ const after = canvas.getBoundingClientRect().width;
678
+ if (after <= before + 120) throw new Error(`Editor canvas did not reclaim enough width: ${before} -> ${after}`);
679
+ document.querySelector('button[aria-label="Expand Document Flow panel"]')?.click();
680
+ document.querySelector('button[aria-label="Expand Output Health panel"]')?.click();
681
+ window.__REGEN_PANEL_AFTER = after;
682
+ return `panels collapsed: ${before} -> ${after}`;
683
+ })()
684
+ """);
685
+ await Task.Delay(250);
686
+ await AssertControlStepAsync("side panels expand without focus", """
687
+ (() => {
688
+ const shell = document.querySelector(".app-shell");
689
+ if (!shell) throw new Error("Panel collapse shell missing.");
690
+ if (shell.classList.contains("left-collapsed") || shell.classList.contains("right-collapsed")) {
691
+ throw new Error("Panel expand did not restore the app shell.");
692
+ }
693
+ return `panels expanded after ${window.__REGEN_PANEL_AFTER}`;
694
+ })()
695
+ """);
696
+
697
+ if (!File.Exists(markdownOutput) || !File.ReadAllText(markdownOutput).Contains("UI save-as edit marker", StringComparison.Ordinal))
698
+ {
699
+ throw new InvalidOperationException("Save MD As did not write the smoke Markdown output.");
700
700
  }
701
701
  if (!largeSmokeDocument && (!File.Exists(wordOutput) || new FileInfo(wordOutput).Length == 0))
702
702
  {
@@ -1,16 +1,16 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
3
- <assemblyIdentity version="1.0.0.0" name="LifeAI.BuildCorpusEditor.app"/>
4
- <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
5
- <security>
6
- <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
7
- <requestedExecutionLevel level="asInvoker" uiAccess="false" />
8
- </requestedPrivileges>
9
- </security>
10
- </trustInfo>
11
- <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
12
- <application>
13
- <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
14
- </application>
15
- </compatibility>
16
- </assembly>
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
3
+ <assemblyIdentity version="1.0.0.0" name="LifeAI.BuildCorpusEditor.app"/>
4
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
5
+ <security>
6
+ <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
7
+ <requestedExecutionLevel level="asInvoker" uiAccess="false" />
8
+ </requestedPrivileges>
9
+ </security>
10
+ </trustInfo>
11
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
12
+ <application>
13
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
14
+ </application>
15
+ </compatibility>
16
+ </assembly>
@@ -1,22 +1,22 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>regen-mde</title>
7
- <script>
8
- window.__REGEN_MDEDITOR_ERROR = "";
9
- window.addEventListener("error", function (event) {
10
- window.__REGEN_MDEDITOR_ERROR = event.message || "window error";
11
- });
12
- window.addEventListener("unhandledrejection", function (event) {
13
- window.__REGEN_MDEDITOR_ERROR = event.reason && event.reason.message ? event.reason.message : String(event.reason || "unhandled rejection");
14
- });
15
- </script>
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>regen-mde</title>
7
+ <script>
8
+ window.__REGEN_MDEDITOR_ERROR = "";
9
+ window.addEventListener("error", function (event) {
10
+ window.__REGEN_MDEDITOR_ERROR = event.message || "window error";
11
+ });
12
+ window.addEventListener("unhandledrejection", function (event) {
13
+ window.__REGEN_MDEDITOR_ERROR = event.reason && event.reason.message ? event.reason.message : String(event.reason || "unhandled rejection");
14
+ });
15
+ </script>
16
16
  <script type="module" crossorigin src="./assets/index-BB0sbZaD.js"></script>
17
17
  <link rel="stylesheet" crossorigin href="./assets/index-CtOv7qsC.css">
18
- </head>
19
- <body>
20
- <div id="root"></div>
21
- </body>
22
- </html>
18
+ </head>
19
+ <body>
20
+ <div id="root"></div>
21
+ </body>
22
+ </html>
@@ -1,21 +1,21 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>regen-mde</title>
7
- <script>
8
- window.__REGEN_MDEDITOR_ERROR = "";
9
- window.addEventListener("error", function (event) {
10
- window.__REGEN_MDEDITOR_ERROR = event.message || "window error";
11
- });
12
- window.addEventListener("unhandledrejection", function (event) {
13
- window.__REGEN_MDEDITOR_ERROR = event.reason && event.reason.message ? event.reason.message : String(event.reason || "unhandled rejection");
14
- });
15
- </script>
16
- </head>
17
- <body>
18
- <div id="root"></div>
19
- <script type="module" src="/src/main.jsx"></script>
20
- </body>
21
- </html>
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>regen-mde</title>
7
+ <script>
8
+ window.__REGEN_MDEDITOR_ERROR = "";
9
+ window.addEventListener("error", function (event) {
10
+ window.__REGEN_MDEDITOR_ERROR = event.message || "window error";
11
+ });
12
+ window.addEventListener("unhandledrejection", function (event) {
13
+ window.__REGEN_MDEDITOR_ERROR = event.reason && event.reason.message ? event.reason.message : String(event.reason || "unhandled rejection");
14
+ });
15
+ </script>
16
+ </head>
17
+ <body>
18
+ <div id="root"></div>
19
+ <script type="module" src="/src/main.jsx"></script>
20
+ </body>
21
+ </html>
@@ -163,9 +163,9 @@ function App() {
163
163
  });
164
164
  const [dirty, setDirty] = React.useState(false);
165
165
  const [largeFile, setLargeFile] = React.useState(false);
166
- const [moveSources, setMoveSources] = React.useState(false);
167
- const [leftPanelCollapsed, setLeftPanelCollapsed] = React.useState(() => window.localStorage?.getItem("build-corpus-editor-left-panel") === "collapsed");
168
- const [rightPanelCollapsed, setRightPanelCollapsed] = React.useState(() => window.localStorage?.getItem("build-corpus-editor-right-panel") === "collapsed");
166
+ const [moveSources, setMoveSources] = React.useState(false);
167
+ const [leftPanelCollapsed, setLeftPanelCollapsed] = React.useState(() => window.localStorage?.getItem("build-corpus-editor-left-panel") === "collapsed");
168
+ const [rightPanelCollapsed, setRightPanelCollapsed] = React.useState(() => window.localStorage?.getItem("build-corpus-editor-right-panel") === "collapsed");
169
169
  const sourceViewRef = React.useRef(null);
170
170
  const pageAreaRef = React.useRef(null);
171
171
  const scrollSyncRef = React.useRef(false);
@@ -223,17 +223,17 @@ function App() {
223
223
  if (editor && !dirty && markdown.length <= RICH_EDITOR_LIMIT) editor.commands.setContent(markdownToHtml(markdown));
224
224
  }, [editor, markdown, dirty]);
225
225
 
226
- React.useEffect(() => {
227
- window.localStorage?.setItem("build-corpus-editor-theme", theme);
228
- }, [theme]);
229
-
230
- React.useEffect(() => {
231
- window.localStorage?.setItem("build-corpus-editor-left-panel", leftPanelCollapsed ? "collapsed" : "open");
232
- }, [leftPanelCollapsed]);
233
-
234
- React.useEffect(() => {
235
- window.localStorage?.setItem("build-corpus-editor-right-panel", rightPanelCollapsed ? "collapsed" : "open");
236
- }, [rightPanelCollapsed]);
226
+ React.useEffect(() => {
227
+ window.localStorage?.setItem("build-corpus-editor-theme", theme);
228
+ }, [theme]);
229
+
230
+ React.useEffect(() => {
231
+ window.localStorage?.setItem("build-corpus-editor-left-panel", leftPanelCollapsed ? "collapsed" : "open");
232
+ }, [leftPanelCollapsed]);
233
+
234
+ React.useEffect(() => {
235
+ window.localStorage?.setItem("build-corpus-editor-right-panel", rightPanelCollapsed ? "collapsed" : "open");
236
+ }, [rightPanelCollapsed]);
237
237
 
238
238
  async function chooseOpen() {
239
239
  try {
@@ -537,7 +537,7 @@ function App() {
537
537
  const codeMirrorTheme = "light";
538
538
 
539
539
  return (
540
- <main className={`app-shell${leftPanelCollapsed ? " left-collapsed" : ""}${rightPanelCollapsed ? " right-collapsed" : ""}`} data-theme={theme}>
540
+ <main className={`app-shell${leftPanelCollapsed ? " left-collapsed" : ""}${rightPanelCollapsed ? " right-collapsed" : ""}`} data-theme={theme}>
541
541
  <aside className="brand-rail" aria-label="Primary tools">
542
542
  <div className="mark">BC</div>
543
543
  <div className="rail-item active">Ed</div>
@@ -566,53 +566,53 @@ function App() {
566
566
  </div>
567
567
  </header>
568
568
 
569
- <section className={`side-panel left-panel${leftPanelCollapsed ? " collapsed" : ""}`}>
570
- <div className="panel-chrome">
571
- <button
572
- type="button"
573
- className="panel-toggle"
574
- aria-label={leftPanelCollapsed ? "Expand Document Flow panel" : "Collapse Document Flow panel"}
575
- aria-expanded={!leftPanelCollapsed}
576
- onClick={() => setLeftPanelCollapsed((collapsed) => !collapsed)}
577
- title={leftPanelCollapsed ? "Expand Document Flow" : "Collapse Document Flow"}
578
- >
579
- <span aria-hidden="true">≡</span>
580
- </button>
581
- <h2 className="panel-title">Document Flow</h2>
582
- </div>
583
- <div className="panel-body">
584
- <div className="source-card">
585
- <strong>{sourceText}</strong>
586
- <p>{dirty ? "The Markdown draft has unsaved changes." : "The Markdown draft is in sync with its save target."}</p>
587
- <label className="batch-option"><input type="checkbox" checked={moveSources} onChange={(event) => setMoveSources(event.target.checked)} /> Move processed files to sources</label>
588
- </div>
589
- <div className="format-flow">
590
- <div className="flow-step">
591
- <div className="badge">{sourceBadge}</div>
592
- <div><b>Original</b><small>{sourcePath || "Choose a file to begin"}</small></div>
593
- <small>{sourcePath ? "loaded" : "empty"}</small>
594
- </div>
595
- <div className="flow-step">
596
- <div className="badge">MD</div>
597
- <div><b>Working draft</b><small>{draftText}</small></div>
598
- <small>{dirty ? "dirty" : "saved"}</small>
599
- </div>
600
- <div className="flow-step">
601
- <div className="badge">DOCX</div>
602
- <div><b>Word export</b><small>{exportText}</small></div>
603
- <small>{exportPath ? "done" : "ready"}</small>
604
- </div>
605
- </div>
606
-
607
- <h2 className="panel-title">Insert</h2>
608
- <div className="insert-grid">
609
- <button className="insert-tile" disabled={!canUseRich} onClick={() => editor?.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()}><b>Table</b>Rows, columns, headers</button>
610
- <button className="insert-tile" disabled={!canUseRich} onClick={insertImageFile}><b>Image</b>File with alt text</button>
611
- <button className="insert-tile" disabled={!canUseRich} onClick={() => editor?.chain().focus().toggleBlockquote().run()}><b>Callout</b>Note or quotation</button>
612
- <button className="insert-tile" disabled={!canUseRich} onClick={() => editor?.chain().focus().toggleTaskList().run()}><b>Task</b>Checklist block</button>
613
- </div>
614
- </div>
615
- </section>
569
+ <section className={`side-panel left-panel${leftPanelCollapsed ? " collapsed" : ""}`}>
570
+ <div className="panel-chrome">
571
+ <button
572
+ type="button"
573
+ className="panel-toggle"
574
+ aria-label={leftPanelCollapsed ? "Expand Document Flow panel" : "Collapse Document Flow panel"}
575
+ aria-expanded={!leftPanelCollapsed}
576
+ onClick={() => setLeftPanelCollapsed((collapsed) => !collapsed)}
577
+ title={leftPanelCollapsed ? "Expand Document Flow" : "Collapse Document Flow"}
578
+ >
579
+ <span aria-hidden="true">≡</span>
580
+ </button>
581
+ <h2 className="panel-title">Document Flow</h2>
582
+ </div>
583
+ <div className="panel-body">
584
+ <div className="source-card">
585
+ <strong>{sourceText}</strong>
586
+ <p>{dirty ? "The Markdown draft has unsaved changes." : "The Markdown draft is in sync with its save target."}</p>
587
+ <label className="batch-option"><input type="checkbox" checked={moveSources} onChange={(event) => setMoveSources(event.target.checked)} /> Move processed files to sources</label>
588
+ </div>
589
+ <div className="format-flow">
590
+ <div className="flow-step">
591
+ <div className="badge">{sourceBadge}</div>
592
+ <div><b>Original</b><small>{sourcePath || "Choose a file to begin"}</small></div>
593
+ <small>{sourcePath ? "loaded" : "empty"}</small>
594
+ </div>
595
+ <div className="flow-step">
596
+ <div className="badge">MD</div>
597
+ <div><b>Working draft</b><small>{draftText}</small></div>
598
+ <small>{dirty ? "dirty" : "saved"}</small>
599
+ </div>
600
+ <div className="flow-step">
601
+ <div className="badge">DOCX</div>
602
+ <div><b>Word export</b><small>{exportText}</small></div>
603
+ <small>{exportPath ? "done" : "ready"}</small>
604
+ </div>
605
+ </div>
606
+
607
+ <h2 className="panel-title">Insert</h2>
608
+ <div className="insert-grid">
609
+ <button className="insert-tile" disabled={!canUseRich} onClick={() => editor?.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()}><b>Table</b>Rows, columns, headers</button>
610
+ <button className="insert-tile" disabled={!canUseRich} onClick={insertImageFile}><b>Image</b>File with alt text</button>
611
+ <button className="insert-tile" disabled={!canUseRich} onClick={() => editor?.chain().focus().toggleBlockquote().run()}><b>Callout</b>Note or quotation</button>
612
+ <button className="insert-tile" disabled={!canUseRich} onClick={() => editor?.chain().focus().toggleTaskList().run()}><b>Task</b>Checklist block</button>
613
+ </div>
614
+ </div>
615
+ </section>
616
616
 
617
617
  <section className={`workspace ${mode} ${largeFile ? "large" : ""}`}>
618
618
  <div className="mode-strip">
@@ -700,51 +700,51 @@ function App() {
700
700
  </div>
701
701
  </section>
702
702
 
703
- <aside className={`side-panel right-panel${rightPanelCollapsed ? " collapsed" : ""}`}>
704
- <div className="panel-chrome">
705
- <h2 className="panel-title">Output Health</h2>
706
- <button
707
- type="button"
708
- className="panel-toggle"
709
- aria-label={rightPanelCollapsed ? "Expand Output Health panel" : "Collapse Output Health panel"}
710
- aria-expanded={!rightPanelCollapsed}
711
- onClick={() => setRightPanelCollapsed((collapsed) => !collapsed)}
712
- title={rightPanelCollapsed ? "Expand Output Health" : "Collapse Output Health"}
713
- >
714
- <span aria-hidden="true">≡</span>
715
- </button>
716
- </div>
717
- <div className="panel-body">
718
- <div className="source-card">
719
- <strong>Round-trip confidence</strong>
720
- <p>Tables, images, links, and headings are tracked as conversion risks.</p>
721
- <div className="meter" aria-label="Round-trip confidence"><span style={{ width: `${confidence}%` }} /></div>
722
- </div>
723
-
724
- <h2 className="panel-title">Table Tools</h2>
725
- <div className="tool-group">
726
- <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().addRowAfter().run()}>Row +</button>
727
- <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().addColumnAfter().run()}>Col +</button>
728
- <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().deleteRow().run()}>Row -</button>
729
- <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().deleteColumn().run()}>Col -</button>
730
- <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().toggleHeaderRow().run()}>Header</button>
731
- <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().deleteTable().run()}>Delete</button>
732
- </div>
733
-
734
- <h2 className="panel-title">Image Tools</h2>
735
- <div className="tool-group">
736
- <button disabled={!canUseRich} onClick={insertImageFile}>File</button>
737
- <button disabled={!canUseRich} onClick={insertImageUrl}>URL</button>
738
- </div>
739
-
740
- <div className="checklist">
741
- <div className="check"><span className="dot" /><div><b>Markdown target</b><br />{markdownPath || "Choose Save MD As..."}</div></div>
742
- <div className="check"><span className="dot" /><div><b>Word export</b><br />{exportPath || "Ready to export with template hook."}</div></div>
743
- <div className="check"><span className={imageCount ? "dot warn" : "dot"} /><div><b>Images</b><br />{imageCount} detected. Asset folder policy still needs final pass.</div></div>
744
- <div className="check"><span className={tableCount ? "dot warn" : "dot"} /><div><b>Tables</b><br />{tableCount} detected. Advanced cell controls are surfaced here.</div></div>
745
- </div>
746
- </div>
747
- </aside>
703
+ <aside className={`side-panel right-panel${rightPanelCollapsed ? " collapsed" : ""}`}>
704
+ <div className="panel-chrome">
705
+ <h2 className="panel-title">Output Health</h2>
706
+ <button
707
+ type="button"
708
+ className="panel-toggle"
709
+ aria-label={rightPanelCollapsed ? "Expand Output Health panel" : "Collapse Output Health panel"}
710
+ aria-expanded={!rightPanelCollapsed}
711
+ onClick={() => setRightPanelCollapsed((collapsed) => !collapsed)}
712
+ title={rightPanelCollapsed ? "Expand Output Health" : "Collapse Output Health"}
713
+ >
714
+ <span aria-hidden="true">≡</span>
715
+ </button>
716
+ </div>
717
+ <div className="panel-body">
718
+ <div className="source-card">
719
+ <strong>Round-trip confidence</strong>
720
+ <p>Tables, images, links, and headings are tracked as conversion risks.</p>
721
+ <div className="meter" aria-label="Round-trip confidence"><span style={{ width: `${confidence}%` }} /></div>
722
+ </div>
723
+
724
+ <h2 className="panel-title">Table Tools</h2>
725
+ <div className="tool-group">
726
+ <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().addRowAfter().run()}>Row +</button>
727
+ <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().addColumnAfter().run()}>Col +</button>
728
+ <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().deleteRow().run()}>Row -</button>
729
+ <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().deleteColumn().run()}>Col -</button>
730
+ <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().toggleHeaderRow().run()}>Header</button>
731
+ <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().deleteTable().run()}>Delete</button>
732
+ </div>
733
+
734
+ <h2 className="panel-title">Image Tools</h2>
735
+ <div className="tool-group">
736
+ <button disabled={!canUseRich} onClick={insertImageFile}>File</button>
737
+ <button disabled={!canUseRich} onClick={insertImageUrl}>URL</button>
738
+ </div>
739
+
740
+ <div className="checklist">
741
+ <div className="check"><span className="dot" /><div><b>Markdown target</b><br />{markdownPath || "Choose Save MD As..."}</div></div>
742
+ <div className="check"><span className="dot" /><div><b>Word export</b><br />{exportPath || "Ready to export with template hook."}</div></div>
743
+ <div className="check"><span className={imageCount ? "dot warn" : "dot"} /><div><b>Images</b><br />{imageCount} detected. Asset folder policy still needs final pass.</div></div>
744
+ <div className="check"><span className={tableCount ? "dot warn" : "dot"} /><div><b>Tables</b><br />{tableCount} detected. Advanced cell controls are surfaced here.</div></div>
745
+ </div>
746
+ </div>
747
+ </aside>
748
748
 
749
749
  <footer className="statusbar">
750
750
  <span>{status}{exportPath ? ` - Last Word export: ${exportPath}` : ""}</span>