regen.mde 0.7.0 → 0.8.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.
Files changed (49) hide show
  1. package/LICENSE +16 -16
  2. package/README.md +2 -1
  3. package/bin/build-corpus-editor.js +83 -83
  4. package/bin/build-corpus.js +41 -41
  5. package/bin/regen-mdeditor-install.js +27 -27
  6. package/bin/regen-mdeditor-uninstall.js +19 -19
  7. package/bin/validate-katex.js +93 -93
  8. package/desktop/BuildCorpusEditor/BuildCorpusEditor.csproj +22 -22
  9. package/desktop/BuildCorpusEditor/EditorForm.cs +58 -10
  10. package/desktop/BuildCorpusEditor/app.manifest +16 -16
  11. package/dist/release/{regen-mde-0.7.0-win-x64.zip → regen-mde-0.8.0-win-x64.zip} +0 -0
  12. package/dist/release/regen-mde-0.8.1-win-x64.zip +0 -0
  13. package/dist/windows-editor/BuildCorpusEditor.dll +0 -0
  14. package/dist/windows-editor/BuildCorpusEditor.exe +0 -0
  15. package/dist/windows-editor/BuildCorpusEditor.pdb +0 -0
  16. package/dist/windows-editor/BuildCorpusEditor.runtimeconfig.json +1 -1
  17. package/dist/windows-editor/wwwroot/assets/{index-C_VxJk4k.js → index-BB0sbZaD.js} +107 -107
  18. package/dist/windows-editor/wwwroot/assets/index-CtOv7qsC.css +1 -0
  19. package/dist/windows-editor/wwwroot/index.html +22 -22
  20. package/editor-web/index.html +21 -21
  21. package/editor-web/src/main.jsx +107 -69
  22. package/editor-web/src/styles.css +99 -35
  23. package/editor-web/vite.config.js +13 -13
  24. package/examples/build-corpus.config.example.json +21 -21
  25. package/installer/install-regen-mde.ps1 +214 -214
  26. package/installer/regen-mde.nsi +81 -81
  27. package/package.json +90 -90
  28. package/pyproject.toml +34 -35
  29. package/requirements.txt +0 -1
  30. package/scripts/build-windows-editor.ps1 +47 -47
  31. package/scripts/package-windows-editor.ps1 +90 -90
  32. package/scripts/run-corpus.ps1 +28 -28
  33. package/scripts/run-editor-implementation-plane.ps1 +226 -226
  34. package/scripts/run-required-tests.ps1 +98 -98
  35. package/scripts/run-smoke.ps1 +28 -28
  36. package/src/build_corpus/__init__.py +1 -1
  37. package/src/build_corpus/equations.py +1345 -80
  38. package/src/build_corpus/templates/__init__.py +1 -1
  39. package/src/build_corpus/validate_assets.py +46 -46
  40. package/tools/audit_corpus.py +203 -203
  41. package/tools/collect_microsoft_word_templates.py +228 -228
  42. package/tools/collect_online_docx_corpus.py +272 -272
  43. package/tools/collect_online_pptx_corpus.py +252 -252
  44. package/tools/compare_pptx_inputs_outputs.py +87 -87
  45. package/tools/roundtrip_docx_corpus.py +171 -171
  46. package/dist/release/regen-mde-0.3.0-win-x64-setup.exe +0 -0
  47. package/dist/release/regen-mde-0.3.0-win-x64.zip +0 -0
  48. package/dist/release/regen-mde-0.7.0-win-x64-setup.exe +0 -0
  49. package/dist/windows-editor/wwwroot/assets/index-Wt9zSjIw.css +0 -1
@@ -0,0 +1 @@
1
+ :root{color-scheme:light;font-family:Segoe UI,Inter,system-ui,sans-serif;background:#fbf8f0;color:#192028;--ink: oklch(24% .018 250);--muted: oklch(47% .022 250);--paper: oklch(98% .011 92);--panel: oklch(95% .014 92);--line: oklch(82% .018 92);--soft: oklch(91% .022 92);--accent: oklch(56% .135 176);--accent-dark: oklch(36% .09 176);--warning: oklch(52% .12 23);--violet: oklch(60% .11 284);--source: oklch(99% .005 92);--source-ink: oklch(27% .018 250);--shadow: 0 24px 70px rgba(34, 38, 46, .18);--button-bg: oklch(99% .006 92);--button-hover: oklch(94% .034 176);--topbar-bg: rgba(252, 249, 240, .94);--panel-bg: rgba(248, 245, 236, .96);--card-bg: oklch(99% .006 92);--page-bg: oklch(99% .005 92);--page-ink: oklch(27% .018 250);--rail-bg: oklch(28% .026 250);--rail-item-bg: oklch(37% .04 250);--grid-line-strong: rgba(41, 143, 132, .08);--grid-line-soft: rgba(41, 143, 132, .05);--warning-bg: oklch(93% .044 72);--warning-border: oklch(74% .075 72);--warning-ink: oklch(35% .075 72);--bubble-bg: oklch(24% .022 250);--bubble-line: oklch(35% .025 250);--bubble-ink: oklch(94% .01 92);--table-head: oklch(92% .025 92)}[data-theme=dark]{color-scheme:dark;--ink: oklch(91% .014 250);--muted: oklch(68% .018 250);--paper: oklch(20% .018 250);--panel: oklch(26% .018 250);--line: oklch(38% .022 250);--soft: oklch(16% .018 250);--accent: oklch(65% .13 176);--accent-dark: oklch(88% .058 176);--warning: oklch(71% .14 68);--violet: oklch(71% .1 284);--source: oklch(96% .008 92);--source-ink: oklch(25% .018 250);--shadow: 0 24px 80px rgba(0, 0, 0, .46);--button-bg: oklch(27% .018 250);--button-hover: oklch(32% .038 176);--topbar-bg: rgba(25, 29, 36, .96);--panel-bg: rgba(29, 33, 40, .96);--card-bg: oklch(24% .018 250);--page-bg: oklch(96% .008 92);--page-ink: oklch(25% .018 250);--rail-bg: oklch(13% .02 250);--rail-item-bg: oklch(24% .03 250);--grid-line-strong: rgba(86, 205, 190, .08);--grid-line-soft: rgba(86, 205, 190, .045);--warning-bg: oklch(25% .044 68);--warning-border: oklch(48% .11 68);--warning-ink: oklch(84% .09 68);--bubble-bg: oklch(91% .012 92);--bubble-line: oklch(72% .02 92);--bubble-ink: oklch(20% .022 250);--table-head: oklch(90% .018 92)}*{box-sizing:border-box}body{margin:0;min-width:920px}button{min-height:28px;border:1px solid var(--line);border-radius:5px;padding:4px 9px;background:var(--button-bg);color:var(--ink);font:600 12px/1 Segoe UI,system-ui,sans-serif;letter-spacing:0;cursor:pointer}button:hover:not(:disabled),button.active{border-color:var(--accent);background:var(--button-hover);color:var(--accent-dark)}button:disabled{cursor:default;opacity:.42}button.primary{background:var(--accent);border-color:#006453;border-color:oklch(45% .11 176);color:#f2fbf8}button.ghost{background:transparent}main.app-shell{width:100vw;height:100vh;min-height:720px;display:grid;grid-template-columns:58px 260px minmax(560px,1fr) 320px;grid-template-rows:48px 1fr 30px;overflow:hidden;background:linear-gradient(90deg,var(--grid-line-strong) 0 1px,transparent 1px 72px),linear-gradient(var(--grid-line-soft) 0 1px,transparent 1px 72px),var(--paper)}main.app-shell.left-collapsed{grid-template-columns:58px 44px minmax(560px,1fr) 320px}main.app-shell.right-collapsed{grid-template-columns:58px 260px minmax(560px,1fr) 44px}main.app-shell.left-collapsed.right-collapsed{grid-template-columns:58px 44px minmax(560px,1fr) 44px}.brand-rail{grid-row:1 / 4;border-right:1px solid var(--line);background:var(--rail-bg);color:#e7e4dc;display:grid;grid-template-rows:64px repeat(5,42px) 1fr 42px;place-items:center}.mark{width:32px;height:32px;border-radius:8px;display:grid;place-items:center;background:var(--accent);color:#f2fbf8;font-weight:800}.rail-item{width:34px;height:34px;border-radius:7px;display:grid;place-items:center;color:#c7c4bc;font-size:12px;font-weight:700}.rail-item.active{background:var(--rail-item-bg);color:#fbf8f1}.topbar{grid-column:2 / 5;border-bottom:1px solid var(--line);background:var(--topbar-bg);display:flex;align-items:center;justify-content:space-between;gap:16px;padding:0 14px}.file-title{min-width:0;display:flex;align-items:baseline;gap:10px}.file-title strong{font-size:15px}.file-title span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted);font:12px/1.2 Consolas,Cascadia Mono,monospace}.actions{display:flex;align-items:center;gap:6px}.side-panel{border-color:var(--line);background:var(--panel-bg);padding:14px;overflow:auto}.side-panel.collapsed{overflow:hidden;padding:8px 5px}.left-panel{grid-row:2 / 3;grid-column:2 / 3;border-right:1px solid var(--line)}.right-panel{grid-row:2 / 3;grid-column:4 / 5;border-left:1px solid var(--line)}.panel-chrome{min-height:28px;display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:10px}.left-panel .panel-chrome{flex-direction:row-reverse}.panel-toggle{width:28px;min-width:28px;height:28px;min-height:28px;display:grid;place-items:center;padding:0;font-size:17px;line-height:1}.side-panel.collapsed .panel-chrome{height:100%;min-height:0;flex-direction:column;justify-content:flex-start;margin:0}.side-panel.collapsed .panel-title{writing-mode:vertical-rl;transform:rotate(180deg);white-space:nowrap;margin:10px 0 0;max-height:calc(100vh - 150px);overflow:hidden;text-overflow:ellipsis}.side-panel.collapsed .panel-body{display:none}.panel-title{margin:0 0 10px;color:var(--muted);font-size:11px;font-weight:800;text-transform:uppercase}.source-card,.check{border:1px solid var(--line);border-radius:7px;background:var(--card-bg)}.source-card{padding:12px;margin-bottom:12px}.source-card strong{display:block;font-size:13px;margin-bottom:5px}.source-card p{margin:0;color:var(--muted);font-size:12px;line-height:1.4}.batch-option{display:flex;align-items:center;gap:8px;margin-top:10px;color:var(--text);font-size:12px;line-height:1.35}.format-flow,.checklist{display:grid;gap:8px;margin:14px 0}.flow-step{display:grid;grid-template-columns:32px 1fr auto;align-items:center;gap:9px;min-height:42px;padding:7px;border:1px solid var(--line);border-radius:7px;background:var(--panel)}.badge{height:26px;border-radius:5px;display:grid;place-items:center;background:#c0dfd6;color:#003d32;color:oklch(32% .07 176);font-weight:800;font-size:10px}.flow-step small{display:block;color:var(--muted);font-size:11px}.insert-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.insert-tile{min-height:58px;border:1px solid var(--line);border-radius:7px;padding:9px;background:var(--card-bg);font-size:12px;text-align:left}.insert-tile b{display:block;margin-bottom:5px}.workspace{grid-row:2 / 3;grid-column:3 / 4;min-width:0;min-height:0;display:grid;grid-template-rows:42px 1fr;overflow:hidden;background:var(--soft)}.mode-strip{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);padding:0 14px}.segmented{display:inline-flex;gap:2px;padding:3px;border:1px solid var(--line);border-radius:7px;background:var(--card-bg)}.segmented button{border:0;min-height:24px;background:transparent}.segmented button.active{background:var(--ink);color:#fbf8f1}.canvas-wrap{min-height:0;height:100%;display:grid;grid-template-columns:minmax(360px,1fr) minmax(300px,.72fr);overflow:hidden}.workspace.markdown .canvas-wrap,.workspace.markdown.large .canvas-wrap{grid-template-columns:1fr}.workspace.split .canvas-wrap{grid-template-columns:minmax(360px,1fr) minmax(300px,.72fr)}.workspace.raw .canvas-wrap{grid-template-columns:1fr}.workspace.markdown .markdown-pane,.workspace.raw .page-area{display:none}.page-area{min-width:0;min-height:0;height:100%;overflow:auto;padding:26px 38px 60px}.page{position:relative;width:min(760px,100%);min-height:900px;margin:0 auto;padding:58px 64px;border:1px solid oklch(84% .014 92);border-radius:6px;background:var(--page-bg);box-shadow:var(--shadow)}.page-label{margin:0 0 18px;color:var(--warning);font-size:11px;font-weight:800;text-transform:uppercase}.rich .ProseMirror{min-height:720px;outline:none;color:var(--page-ink);line-height:1.62}.rich .ProseMirror h1,.rendered-markdown h1{margin:0 0 18px;max-width:14ch;font-size:42px;line-height:.98}.rich .ProseMirror h2,.rendered-markdown h2{margin-top:32px;font-size:25px}.rich .ProseMirror p,.rich .ProseMirror li,.rendered-markdown p,.rendered-markdown li{font-size:15px}.rich .ProseMirror blockquote,.rendered-markdown blockquote{margin:24px 0;padding:15px;border:1px solid oklch(78% .055 176);border-radius:7px;background:color-mix(in oklch,var(--accent) 14%,var(--page-bg))}.rich .ProseMirror table,.rendered-markdown table{width:100%;margin:22px 0;border-collapse:collapse;font-size:13px}.rich .ProseMirror th,.rich .ProseMirror td,.rendered-markdown th,.rendered-markdown td{border:1px solid oklch(78% .016 92);padding:8px;text-align:left}.rich .ProseMirror th,.rendered-markdown th{background:var(--table-head)}.rich img,.rendered-markdown img{max-width:100%;height:auto;border-radius:5px}.readonly-preview{color:var(--page-ink);line-height:1.62}.rendered-markdown{min-height:720px}.rendered-markdown pre{overflow:auto;padding:12px;border-radius:6px;background:color-mix(in oklch,var(--page-ink) 8%,var(--page-bg))}.rendered-markdown code{font-family:Consolas,Cascadia Mono,monospace}.markdown-pane{min-width:0;min-height:0;height:100%;overflow:auto;padding:18px;background:var(--source);color:var(--source-ink);border-left:1px solid var(--line)}.editable-markdown{display:flex;flex-direction:column;gap:10px}.markdown-toolbar{display:flex;flex-wrap:wrap;gap:4px;padding-bottom:8px;border-bottom:1px solid var(--line)}.markdown-toolbar button{min-width:30px;min-height:26px;border-color:var(--line);background:color-mix(in oklch,var(--page-ink) 4%,var(--source));color:var(--source-ink)}.source{width:100%;min-height:0;flex:1;border:0;outline:none;background:transparent;color:inherit;font:13px/1.55 Consolas,Cascadia Mono,monospace}.code-source,.code-source>div,.code-source [class*=cm-theme],.code-source .cm-editor,.code-source .cm-scroller{min-height:0;height:100%}.code-source .cm-editor{border-radius:6px;border:1px solid oklch(84% .014 92);background:var(--source);color:var(--source-ink)}.code-source .cm-scroller{background:var(--source);color:var(--source-ink);font-family:Consolas,Cascadia Mono,monospace;font-size:13px;line-height:1.55}.code-source .cm-content{color:var(--source-ink)}.code-source .cm-gutters{border-right:1px solid var(--line);background:var(--source);color:var(--muted)}.code-source .cm-lineNumbers .cm-gutterElement{min-width:26px;padding:0 6px 0 2px;color:var(--muted);font-size:10px;opacity:.52}.code-source .cm-activeLine,.code-source .cm-activeLineGutter{background:color-mix(in oklch,var(--accent) 8%,transparent)}.bubble-menu,.floating-menu{display:flex;align-items:center;gap:3px;padding:5px;border:1px solid var(--bubble-line);border-radius:8px;background:var(--bubble-bg);box-shadow:0 16px 32px #181c2438}.floating-menu{background:var(--card-bg);border-color:var(--line)}.bubble-menu button{min-width:28px;border-color:#3e4c5a;background:transparent;color:var(--bubble-ink)}.floating-menu button{min-width:28px;min-height:26px}.meter{height:8px;overflow:hidden;border-radius:999px;background:#d8d4c7;margin-top:10px}.meter span{display:block;height:100%;background:linear-gradient(90deg,var(--accent),var(--violet))}.check{display:grid;grid-template-columns:24px 1fr;gap:8px;align-items:start;padding:8px;font-size:12px}.dot{width:16px;height:16px;margin-top:1px;border-radius:999px;background:var(--accent)}.dot.warn{background:var(--warning)}.tool-group{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-bottom:12px}.tool-group button{min-width:0}.statusbar{grid-column:2 / 5;border-top:1px solid var(--line);background:#f5f2e9;color:var(--muted);display:flex;align-items:center;justify-content:space-between;gap:20px;padding:0 12px;font-size:12px}.statusbar span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:840px){body{min-width:760px}main.app-shell{grid-template-columns:52px 1fr;grid-template-rows:48px 1fr 30px}.topbar,.statusbar{grid-column:2 / 3}.left-panel,.right-panel{display:none}.workspace{grid-column:2 / 3}.canvas-wrap{grid-template-columns:1fr}.workspace.split .canvas-wrap{grid-template-columns:minmax(340px,1fr) minmax(280px,.78fr)}}.compose-shell{display:grid;grid-template-rows:auto 1fr auto;height:100vh;width:100vw;background:#101214;color:#eef1f4;font-family:Segoe UI,Inter,system-ui,sans-serif}.compose-topbar,.compose-statusbar{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 16px;background:#181b1f;border-bottom:1px solid #353b44}.compose-statusbar{border-bottom:0;border-top:1px solid #353b44;color:#9ca8b4;font-size:12.5px;gap:14px}.compose-statusbar span:first-child{flex:1 1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.compose-title{display:flex;flex-direction:column;gap:2px;min-width:0}.compose-title strong{font-size:16px}.compose-title span{font-size:12px;color:#9ca8b4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.compose-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end}.compose-actions button{min-height:34px;border:1px solid #353b44;background:#20242a;color:#eef1f4;border-radius:6px;padding:0 12px;font:inherit;font-size:13px;cursor:pointer}.compose-actions button:hover{border-color:#9cc7ff}.compose-actions button.active{border-color:#65d0a4;color:#65d0a4}.compose-actions button.primary{background:#65d0a4;color:#06120d;border-color:#65d0a4;font-weight:650}.compose-actions button.primary:disabled{opacity:.6;cursor:default}.compose-editor-wrap{padding:14px;min-height:0;display:flex}.compose-editor-wrap.dragging{outline:3px solid #9cc7ff;outline-offset:-10px}.compose-editor{width:100%;height:100%;resize:none;border:1px solid #353b44;border-radius:8px;background:#0c0e10;color:#eef1f4;padding:16px;font:15px/1.55 Cascadia Code,Consolas,monospace;outline:none}.compose-editor:focus{border-color:#9cc7ff}
@@ -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>
16
- <script type="module" crossorigin src="./assets/index-C_VxJk4k.js"></script>
17
- <link rel="stylesheet" crossorigin href="./assets/index-Wt9zSjIw.css">
18
- </head>
19
- <body>
20
- <div id="root"></div>
21
- </body>
22
- </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
+ <script type="module" crossorigin src="./assets/index-BB0sbZaD.js"></script>
17
+ <link rel="stylesheet" crossorigin href="./assets/index-CtOv7qsC.css">
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,7 +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);
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");
167
169
  const sourceViewRef = React.useRef(null);
168
170
  const pageAreaRef = React.useRef(null);
169
171
  const scrollSyncRef = React.useRef(false);
@@ -221,9 +223,17 @@ function App() {
221
223
  if (editor && !dirty && markdown.length <= RICH_EDITOR_LIMIT) editor.commands.setContent(markdownToHtml(markdown));
222
224
  }, [editor, markdown, dirty]);
223
225
 
224
- React.useEffect(() => {
225
- window.localStorage?.setItem("build-corpus-editor-theme", theme);
226
- }, [theme]);
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]);
227
237
 
228
238
  async function chooseOpen() {
229
239
  try {
@@ -527,7 +537,7 @@ function App() {
527
537
  const codeMirrorTheme = "light";
528
538
 
529
539
  return (
530
- <main className="app-shell" data-theme={theme}>
540
+ <main className={`app-shell${leftPanelCollapsed ? " left-collapsed" : ""}${rightPanelCollapsed ? " right-collapsed" : ""}`} data-theme={theme}>
531
541
  <aside className="brand-rail" aria-label="Primary tools">
532
542
  <div className="mark">BC</div>
533
543
  <div className="rail-item active">Ed</div>
@@ -556,39 +566,53 @@ function App() {
556
566
  </div>
557
567
  </header>
558
568
 
559
- <section className="side-panel left-panel">
560
- <h2 className="panel-title">Document Flow</h2>
561
- <div className="source-card">
562
- <strong>{sourceText}</strong>
563
- <p>{dirty ? "The Markdown draft has unsaved changes." : "The Markdown draft is in sync with its save target."}</p>
564
- <label className="batch-option"><input type="checkbox" checked={moveSources} onChange={(event) => setMoveSources(event.target.checked)} /> Move processed files to sources</label>
565
- </div>
566
- <div className="format-flow">
567
- <div className="flow-step">
568
- <div className="badge">{sourceBadge}</div>
569
- <div><b>Original</b><small>{sourcePath || "Choose a file to begin"}</small></div>
570
- <small>{sourcePath ? "loaded" : "empty"}</small>
571
- </div>
572
- <div className="flow-step">
573
- <div className="badge">MD</div>
574
- <div><b>Working draft</b><small>{draftText}</small></div>
575
- <small>{dirty ? "dirty" : "saved"}</small>
576
- </div>
577
- <div className="flow-step">
578
- <div className="badge">DOCX</div>
579
- <div><b>Word export</b><small>{exportText}</small></div>
580
- <small>{exportPath ? "done" : "ready"}</small>
581
- </div>
582
- </div>
583
-
584
- <h2 className="panel-title">Insert</h2>
585
- <div className="insert-grid">
586
- <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>
587
- <button className="insert-tile" disabled={!canUseRich} onClick={insertImageFile}><b>Image</b>File with alt text</button>
588
- <button className="insert-tile" disabled={!canUseRich} onClick={() => editor?.chain().focus().toggleBlockquote().run()}><b>Callout</b>Note or quotation</button>
589
- <button className="insert-tile" disabled={!canUseRich} onClick={() => editor?.chain().focus().toggleTaskList().run()}><b>Task</b>Checklist block</button>
590
- </div>
591
- </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>
592
616
 
593
617
  <section className={`workspace ${mode} ${largeFile ? "large" : ""}`}>
594
618
  <div className="mode-strip">
@@ -676,37 +700,51 @@ function App() {
676
700
  </div>
677
701
  </section>
678
702
 
679
- <aside className="side-panel right-panel">
680
- <h2 className="panel-title">Output Health</h2>
681
- <div className="source-card">
682
- <strong>Round-trip confidence</strong>
683
- <p>Tables, images, links, and headings are tracked as conversion risks.</p>
684
- <div className="meter" aria-label="Round-trip confidence"><span style={{ width: `${confidence}%` }} /></div>
685
- </div>
686
-
687
- <h2 className="panel-title">Table Tools</h2>
688
- <div className="tool-group">
689
- <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().addRowAfter().run()}>Row +</button>
690
- <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().addColumnAfter().run()}>Col +</button>
691
- <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().deleteRow().run()}>Row -</button>
692
- <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().deleteColumn().run()}>Col -</button>
693
- <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().toggleHeaderRow().run()}>Header</button>
694
- <button disabled={!canUseRich || !editor?.isActive("table")} onClick={() => editor?.chain().focus().deleteTable().run()}>Delete</button>
695
- </div>
696
-
697
- <h2 className="panel-title">Image Tools</h2>
698
- <div className="tool-group">
699
- <button disabled={!canUseRich} onClick={insertImageFile}>File</button>
700
- <button disabled={!canUseRich} onClick={insertImageUrl}>URL</button>
701
- </div>
702
-
703
- <div className="checklist">
704
- <div className="check"><span className="dot" /><div><b>Markdown target</b><br />{markdownPath || "Choose Save MD As..."}</div></div>
705
- <div className="check"><span className="dot" /><div><b>Word export</b><br />{exportPath || "Ready to export with template hook."}</div></div>
706
- <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>
707
- <div className="check"><span className={tableCount ? "dot warn" : "dot"} /><div><b>Tables</b><br />{tableCount} detected. Advanced cell controls are surfaced here.</div></div>
708
- </div>
709
- </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>
710
748
 
711
749
  <footer className="statusbar">
712
750
  <span>{status}{exportPath ? ` - Last Word export: ${exportPath}` : ""}</span>
@@ -113,20 +113,32 @@ button.ghost {
113
113
  background: transparent;
114
114
  }
115
115
 
116
- main.app-shell {
117
- width: 100vw;
118
- height: 100vh;
119
- min-height: 720px;
120
- display: grid;
121
- grid-template-columns: 58px 260px minmax(560px, 1fr) 320px;
116
+ main.app-shell {
117
+ width: 100vw;
118
+ height: 100vh;
119
+ min-height: 720px;
120
+ display: grid;
121
+ grid-template-columns: 58px 260px minmax(560px, 1fr) 320px;
122
122
  grid-template-rows: 48px 1fr 30px;
123
123
  overflow: hidden;
124
124
  background:
125
125
  linear-gradient(90deg, var(--grid-line-strong) 0 1px, transparent 1px 72px),
126
126
  linear-gradient(var(--grid-line-soft) 0 1px, transparent 1px 72px),
127
- var(--paper);
128
- }
129
-
127
+ var(--paper);
128
+ }
129
+
130
+ main.app-shell.left-collapsed {
131
+ grid-template-columns: 58px 44px minmax(560px, 1fr) 320px;
132
+ }
133
+
134
+ main.app-shell.right-collapsed {
135
+ grid-template-columns: 58px 260px minmax(560px, 1fr) 44px;
136
+ }
137
+
138
+ main.app-shell.left-collapsed.right-collapsed {
139
+ grid-template-columns: 58px 44px minmax(560px, 1fr) 44px;
140
+ }
141
+
130
142
  .brand-rail {
131
143
  grid-row: 1 / 4;
132
144
  border-right: 1px solid var(--line);
@@ -201,30 +213,82 @@ main.app-shell {
201
213
  gap: 6px;
202
214
  }
203
215
 
204
- .side-panel {
205
- border-color: var(--line);
206
- background: var(--panel-bg);
207
- padding: 14px;
208
- overflow: auto;
209
- }
210
-
211
- .left-panel {
212
- grid-row: 2 / 3;
213
- grid-column: 2 / 3;
214
- border-right: 1px solid var(--line);
215
- }
216
+ .side-panel {
217
+ border-color: var(--line);
218
+ background: var(--panel-bg);
219
+ padding: 14px;
220
+ overflow: auto;
221
+ }
222
+
223
+ .side-panel.collapsed {
224
+ overflow: hidden;
225
+ padding: 8px 5px;
226
+ }
227
+
228
+ .left-panel {
229
+ grid-row: 2 / 3;
230
+ grid-column: 2 / 3;
231
+ border-right: 1px solid var(--line);
232
+ }
216
233
 
217
234
  .right-panel {
218
235
  grid-row: 2 / 3;
219
236
  grid-column: 4 / 5;
220
- border-left: 1px solid var(--line);
221
- }
222
-
223
- .panel-title {
224
- margin: 0 0 10px;
225
- color: var(--muted);
226
- font-size: 11px;
227
- font-weight: 800;
237
+ border-left: 1px solid var(--line);
238
+ }
239
+
240
+ .panel-chrome {
241
+ min-height: 28px;
242
+ display: flex;
243
+ align-items: center;
244
+ justify-content: space-between;
245
+ gap: 8px;
246
+ margin-bottom: 10px;
247
+ }
248
+
249
+ .left-panel .panel-chrome {
250
+ flex-direction: row-reverse;
251
+ }
252
+
253
+ .panel-toggle {
254
+ width: 28px;
255
+ min-width: 28px;
256
+ height: 28px;
257
+ min-height: 28px;
258
+ display: grid;
259
+ place-items: center;
260
+ padding: 0;
261
+ font-size: 17px;
262
+ line-height: 1;
263
+ }
264
+
265
+ .side-panel.collapsed .panel-chrome {
266
+ height: 100%;
267
+ min-height: 0;
268
+ flex-direction: column;
269
+ justify-content: flex-start;
270
+ margin: 0;
271
+ }
272
+
273
+ .side-panel.collapsed .panel-title {
274
+ writing-mode: vertical-rl;
275
+ transform: rotate(180deg);
276
+ white-space: nowrap;
277
+ margin: 10px 0 0;
278
+ max-height: calc(100vh - 150px);
279
+ overflow: hidden;
280
+ text-overflow: ellipsis;
281
+ }
282
+
283
+ .side-panel.collapsed .panel-body {
284
+ display: none;
285
+ }
286
+
287
+ .panel-title {
288
+ margin: 0 0 10px;
289
+ color: var(--muted);
290
+ font-size: 11px;
291
+ font-weight: 800;
228
292
  text-transform: uppercase;
229
293
  }
230
294
 
@@ -685,12 +749,12 @@ main.app-shell {
685
749
  white-space: nowrap;
686
750
  }
687
751
 
688
- @media (max-width: 1120px) {
689
- body {
690
- min-width: 760px;
691
- }
692
-
693
- main.app-shell {
752
+ @media (max-width: 840px) {
753
+ body {
754
+ min-width: 760px;
755
+ }
756
+
757
+ main.app-shell {
694
758
  grid-template-columns: 52px 1fr;
695
759
  grid-template-rows: 48px 1fr 30px;
696
760
  }
@@ -1,13 +1,13 @@
1
- import { defineConfig } from "vite";
2
- import react from "@vitejs/plugin-react";
3
- import path from "node:path";
4
-
5
- export default defineConfig({
6
- root: path.resolve(import.meta.dirname),
7
- base: "./",
8
- plugins: [react()],
9
- build: {
10
- outDir: "../desktop/BuildCorpusEditor/wwwroot",
11
- emptyOutDir: true,
12
- },
13
- });
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+ import path from "node:path";
4
+
5
+ export default defineConfig({
6
+ root: path.resolve(import.meta.dirname),
7
+ base: "./",
8
+ plugins: [react()],
9
+ build: {
10
+ outDir: "../desktop/BuildCorpusEditor/wwwroot",
11
+ emptyOutDir: true,
12
+ },
13
+ });
@@ -1,21 +1,21 @@
1
- {
2
- "conversion": {
3
- "equations": "tex",
4
- "images": "assets"
5
- },
6
- "output": {
7
- "out": ".codex/build-corpus/out",
8
- "out_same_dir": false
9
- },
10
- "s3": {
11
- "bucket": "build-corpus-assets",
12
- "public_base_url": "https://assets.example.com",
13
- "prefix": "build-corpus",
14
- "endpoint_url": "https://ACCOUNT_ID.r2.cloudflarestorage.com",
15
- "region_name": "auto",
16
- "access_key_id": "%R2_ACCESS_KEY_ID%",
17
- "secret_access_key": "%R2_SECRET_ACCESS_KEY%",
18
- "cache_control": "public, max-age=31536000, immutable",
19
- "acl": null
20
- }
21
- }
1
+ {
2
+ "conversion": {
3
+ "equations": "tex",
4
+ "images": "assets"
5
+ },
6
+ "output": {
7
+ "out": ".codex/build-corpus/out",
8
+ "out_same_dir": false
9
+ },
10
+ "s3": {
11
+ "bucket": "build-corpus-assets",
12
+ "public_base_url": "https://assets.example.com",
13
+ "prefix": "build-corpus",
14
+ "endpoint_url": "https://ACCOUNT_ID.r2.cloudflarestorage.com",
15
+ "region_name": "auto",
16
+ "access_key_id": "%R2_ACCESS_KEY_ID%",
17
+ "secret_access_key": "%R2_SECRET_ACCESS_KEY%",
18
+ "cache_control": "public, max-age=31536000, immutable",
19
+ "acl": null
20
+ }
21
+ }