@office-kit/pptx-dev 0.4.0 → 0.4.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.
package/README.md CHANGED
@@ -45,6 +45,11 @@ presentation** task, then use **Simple Browser: Show** with the printed URL and
45
45
  move it to a side editor group. No custom editor extension is needed for this
46
46
  workflow. Clicking a slide supplies chat context; opening its exact TSX location is not implemented.
47
47
 
48
+ Saved TSX edits reuse unchanged source transforms and slide previews to reduce update
49
+ latency. The deck is still evaluated in a fresh worker on every build. Changes to
50
+ shared resources, including themes, relationships, charts and images, redraw all
51
+ slides. Update time depends on the deck size and any code it runs.
52
+
48
53
  ## Chat in the preview
49
54
 
50
55
  The right panel defaults to **Claude Code**. Click **Start** to open the locally
@@ -93,6 +98,11 @@ each request. The dev server retains up to 100 messages until it exits.
93
98
  sends a message. Model/settings menus in the embedded terminal are currently
94
99
  specific to Claude Code.
95
100
 
101
+ Drag the left edge of the chat panel to adjust its width. The preview remembers the
102
+ width in this browser; double-click the edge to reset it. You can also focus the
103
+ edge and use the left/right arrow keys (Shift for larger steps), or Home/End for
104
+ the minimum/maximum width. Narrow screens keep the stacked layout.
105
+
96
106
  Use **Chat** in the header to hide/show the panel. On narrow screens the panel
97
107
  moves below the preview. Presentation mode hides it without ending the session.
98
108
  Both providers save TSX directly, which triggers the usual automatic rebuild.
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as createDeckBuilder, i as initProject, n as exportDeck, r as inspectTemplate } from "./src-31HzWTvZ.mjs";
2
+ import { a as createDeckBuilder, i as initProject, n as exportDeck, r as inspectTemplate } from "./src-CShdm4G1.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { chmod, readFile, stat } from "node:fs/promises";
5
5
  import { dirname, join, resolve, sep } from "node:path";
@@ -23,7 +23,7 @@ button:hover:not(:disabled),.download:hover{background:#f0f3f9}button:disabled{o
23
23
  header{display:flex;align-items:center;gap:16px;padding:0 20px;background:#fff;border-bottom:1px solid #d4d9e2;min-width:0}
24
24
  .brand{font-weight:700;font-size:17px;white-space:nowrap}.badge{font-size:11px;color:#616b7c;background:#f1f3f7;border-radius:4px;padding:3px 6px}
25
25
  #status{flex:1;color:#667085;min-width:0}#present{background:#293c73;color:#fff;border-color:#293c73}
26
- .workspace{display:grid;grid-template-columns:190px minmax(0,1fr) minmax(380px,38vw);min-height:0}
26
+ .workspace{--filmstrip-width:190px;--chat-width:38vw;display:grid;grid-template-columns:var(--filmstrip-width) minmax(0,1fr) clamp(280px,var(--chat-width),calc(100vw - var(--filmstrip-width) - 240px));min-height:0}
27
27
  .filmstrip{background:#f7f8fa;border-right:1px solid #d4d9e2;overflow:auto;overscroll-behavior:contain;padding:16px 12px}
28
28
  .filmstrip h2{margin:0 0 12px 26px;text-transform:uppercase;letter-spacing:.1em;font-size:10px;color:#7c8596;font-weight:600}
29
29
  #thumbnails{display:flex;flex-direction:column;gap:12px;margin:0;padding:0;list-style:none}
@@ -48,15 +48,16 @@ body.presenting{grid-template-rows:minmax(0,1fr);background:#111}
48
48
  .presenting #presentation-controls:hover,.presenting #presentation-controls:focus-within{opacity:1}
49
49
  #presentation-controls button{background:transparent;color:white;border-color:#5d6575}
50
50
  @media(max-width:700px){.workspace{grid-template-columns:140px minmax(0,1fr)}.filmstrip{padding:12px 5px}header{padding:0 12px;gap:10px}.badge,footer .hint{display:none}#stage{padding:16px}footer{gap:8px}#status{font-size:11px}.download{padding:7px 8px}}
51
- #chat{min-height:0;display:flex;flex-direction:column;background:#fff;border-left:1px solid #d4d9e2}
51
+ #chat{position:relative;min-width:0;min-height:0;display:flex;flex-direction:column;background:#fff;border-left:1px solid #d4d9e2}
52
+ #chat-resizer{position:absolute;left:-5px;top:0;bottom:0;width:10px;z-index:3;cursor:col-resize;touch-action:none}#chat-resizer::after{content:"";position:absolute;left:4px;top:0;bottom:0;width:2px}#chat-resizer:hover::after,#chat-resizer:focus-visible::after,.resizing-chat #chat-resizer::after{background:#4967dd}body.resizing-chat,body.resizing-chat *{cursor:col-resize!important;user-select:none!important}
52
53
  .chat-heading{display:flex;align-items:center;gap:8px;padding:16px;border-bottom:1px solid #e6e9ef}.chat-heading strong{flex:1}.chat-heading button{padding:5px 8px}
53
54
  #messages{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:16px;overscroll-behavior:contain}
54
55
  .chat-message{white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.6}.chat-message.user{background:#eef2ff;border-radius:8px;padding:10px 12px}.chat-message small{display:block;color:#697386;font-size:11px;margin-bottom:4px}.chat-intro{color:#737d8e;line-height:1.7}
55
56
  #chat-form{padding:14px;border-top:1px solid #e6e9ef}#chat-context{font-size:11px;color:#4967dd;margin-bottom:8px}#chat-input{font:inherit;resize:vertical;min-height:90px;max-height:200px;width:100%;padding:10px;border:1px solid #d4d9e2;border-radius:8px} .chat-actions{display:flex;gap:8px;margin-top:8px;align-items:center}.chat-actions select{min-width:0;flex:1}#chat-send{background:#293c73;color:white}#chat-status{padding:0 14px 12px;color:#667085;font-size:11px;white-space:pre-wrap;max-height:90px;overflow:auto}
56
57
  body.chat-hidden .workspace{grid-template-columns:190px minmax(0,1fr)}body.chat-hidden #chat{display:none}
57
58
  body.presenting .workspace{grid-template-columns:minmax(0,1fr)}
58
- @media(max-width:1000px){.workspace{grid-template-columns:120px minmax(0,1fr) 380px}.filmstrip{padding:12px 5px}#stage{padding:16px}}
59
- @media(max-width:700px){.workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(160px,1fr) minmax(200px,1fr)}.filmstrip{display:none}#chat{border-top:1px solid #d4d9e2}.chat-heading{padding:8px 12px}#chat-form{padding:8px 12px}#chat-input{min-height:50px}body.chat-hidden .workspace,.presenting .workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr)}.brand{font-size:14px}header{gap:6px}.download{font-size:11px}}
59
+ @media(max-width:1000px){.workspace{--filmstrip-width:120px;--chat-width:380px}.filmstrip{padding:12px 5px}#stage{padding:16px}}
60
+ @media(max-width:700px){#chat-resizer{display:none}.workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(160px,1fr) minmax(200px,1fr)}.filmstrip{display:none}#chat{border-top:1px solid #d4d9e2}.chat-heading{padding:8px 12px}#chat-form{padding:8px 12px}#chat-input{min-height:50px}body.chat-hidden .workspace,.presenting .workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr)}.brand{font-size:14px}header{gap:6px}.download{font-size:11px}}
60
61
  @media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}
61
62
 
62
63
  [hidden]{display:none!important}
@@ -75,6 +76,7 @@ body.presenting .workspace{grid-template-columns:minmax(0,1fr)}
75
76
  <nav class="filmstrip" aria-label="Slides"><h2>Slides</h2><ol id="thumbnails"></ol></nav>
76
77
  <main aria-label="Slide viewer"><pre id="error" role="alert" hidden></pre><div id="stage" tabindex="-1"><div id="empty">Waiting for slides…</div><div id="slide" hidden></div></div></main>
77
78
  <aside id="chat" aria-label="Slide chat">
79
+ <div id="chat-resizer" role="separator" tabindex="0" aria-label="Chat width" aria-orientation="vertical" aria-controls="chat" title="Drag to resize · Double-click to reset"></div>
78
80
  <div class="chat-heading"><select id="chat-provider" aria-label="AI provider"><option value="claude">Claude Code</option><option value="codex">Codex</option></select><button id="chat-reset" hidden>New chat</button></div>
79
81
  <div id="chat-context">No slide selected</div>
80
82
  <section id="claude-panel" aria-label="Claude Code terminal"><div class="terminal-toolbar"><span id="terminal-status" role="status">Start Claude Code to edit your slides.</span><button id="terminal-start">Start</button><button id="terminal-stop" hidden>End session</button></div><div id="terminal"></div></section>
@@ -92,6 +94,36 @@ let state={slides:[],error:null,aspectRatio:16/9},index=0,urls=[],presenting=fal
92
94
  let displayedSvg;
93
95
  const byId=id=>document.getElementById(id);
94
96
  const stage=byId('stage'),slide=byId('slide'),thumbnails=byId('thumbnails');
97
+ const workspace=document.querySelector('.workspace'),chatResizer=byId('chat-resizer');
98
+ const chatWidthKey='office-kit-chat-width',minChatWidth=280,minStageWidth=240;
99
+ function chatWidthLimits(){return {min:minChatWidth,max:Math.max(minChatWidth,workspace.clientWidth-document.querySelector('.filmstrip').getBoundingClientRect().width-minStageWidth)};}
100
+ function updateChatWidthAria(){
101
+ const {min,max}=chatWidthLimits();
102
+ chatResizer.setAttribute('aria-valuemin',String(min));chatResizer.setAttribute('aria-valuemax',String(Math.round(max)));
103
+ const width=Math.round(byId('chat').getBoundingClientRect().width);
104
+ chatResizer.setAttribute('aria-valuenow',String(width));chatResizer.setAttribute('aria-valuetext',width+' pixels');
105
+ }
106
+ function setChatWidth(width){const {min,max}=chatWidthLimits();workspace.style.setProperty('--chat-width',Math.min(max,Math.max(min,width))+'px');}
107
+ function saveChatWidth(){try{const width=workspace.style.getPropertyValue('--chat-width');if(width)localStorage.setItem(chatWidthKey,String(parseFloat(width)));else localStorage.removeItem(chatWidthKey);}catch(error){console.warn('Could not save chat width',error);}}
108
+ try{const saved=Number(localStorage.getItem(chatWidthKey));if(Number.isFinite(saved)&&saved>=minChatWidth)workspace.style.setProperty('--chat-width',saved+'px');}catch(error){console.warn('Could not restore chat width',error);}
109
+ let chatDrag=null;
110
+ chatResizer.onpointerdown=event=>{
111
+ if(event.button!==0||!event.isPrimary)return;
112
+ event.preventDefault();chatResizer.focus();chatDrag={id:event.pointerId,x:event.clientX,width:byId('chat').getBoundingClientRect().width};
113
+ chatResizer.setPointerCapture(event.pointerId);document.body.classList.add('resizing-chat');
114
+ };
115
+ chatResizer.onpointermove=event=>{if(chatDrag?.id===event.pointerId)setChatWidth(chatDrag.width+chatDrag.x-event.clientX);};
116
+ function finishChatResize(){if(!chatDrag)return;chatDrag=null;document.body.classList.remove('resizing-chat');saveChatWidth();}
117
+ chatResizer.onpointerup=finishChatResize;chatResizer.onpointercancel=finishChatResize;chatResizer.onlostpointercapture=finishChatResize;
118
+ chatResizer.ondblclick=()=>{workspace.style.removeProperty('--chat-width');saveChatWidth();};
119
+ chatResizer.onkeydown=event=>{
120
+ const {min,max}=chatWidthLimits(),width=byId('chat').getBoundingClientRect().width,step=event.shiftKey?50:10;
121
+ let next;
122
+ if(event.key==='ArrowLeft')next=width+step;else if(event.key==='ArrowRight')next=width-step;else if(event.key==='Home')next=min;else if(event.key==='End')next=max;else return;
123
+ event.preventDefault();event.stopPropagation();setChatWidth(next);saveChatWidth();
124
+ };
125
+ new ResizeObserver(updateChatWidthAria).observe(byId('chat'));
126
+
95
127
  // The slide lives in a shadow root rather than a sandboxed iframe so its text
96
128
  // (XHTML inside <foreignObject>) can be selected and copied while the deck's
97
129
  // SVG stays out of the viewer's own DOM and CSS. Keyboard events still reach the
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../src/page.ts","../src/terminal.ts","../src/chat.ts","../src/server.ts","../src/cli.ts"],"sourcesContent":["export const page = `<!doctype html>\n<html lang=\"en\">\n<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width\">\n<title>Office Kit — PowerPoint preview</title>\n<link rel=\"stylesheet\" href=\"/terminal.css\">\n<style>\n*{box-sizing:border-box}\nbody{margin:0;height:100dvh;overflow:hidden;background:#e9ecf1;color:#202735;font:13px system-ui;display:grid;grid-template-rows:60px minmax(0,1fr) 42px}\nbutton,a,select{font:inherit;color:inherit}button,select,.download{border:1px solid #d4d9e2;border-radius:6px;background:#fff;padding:7px 12px;text-decoration:none;cursor:pointer}\nbutton:hover:not(:disabled),.download:hover{background:#f0f3f9}button:disabled{opacity:.4;cursor:default}\n:focus-visible{outline:2px solid #4967dd;outline-offset:3px}\nheader{display:flex;align-items:center;gap:16px;padding:0 20px;background:#fff;border-bottom:1px solid #d4d9e2;min-width:0}\n.brand{font-weight:700;font-size:17px;white-space:nowrap}.badge{font-size:11px;color:#616b7c;background:#f1f3f7;border-radius:4px;padding:3px 6px}\n#status{flex:1;color:#667085;min-width:0}#present{background:#293c73;color:#fff;border-color:#293c73}\n.workspace{display:grid;grid-template-columns:190px minmax(0,1fr) minmax(380px,38vw);min-height:0}\n.filmstrip{background:#f7f8fa;border-right:1px solid #d4d9e2;overflow:auto;overscroll-behavior:contain;padding:16px 12px}\n.filmstrip h2{margin:0 0 12px 26px;text-transform:uppercase;letter-spacing:.1em;font-size:10px;color:#7c8596;font-weight:600}\n#thumbnails{display:flex;flex-direction:column;gap:12px;margin:0;padding:0;list-style:none}\n.thumbnail{display:flex;align-items:flex-start;gap:8px;width:100%;padding:4px 3px;border:0;background:transparent;border-radius:5px;text-align:left}\n.slide-number{width:18px;flex:none;text-align:right;font-size:11px;color:#737d8e;padding-top:5px}\n.thumbnail img{display:block;min-width:0;width:calc(100% - 26px);background:white;box-shadow:0 1px 4px #19212d18;border:2px solid transparent;border-radius:3px;aspect-ratio:var(--slide-ratio,16/9);object-fit:contain}\n.thumbnail[aria-current=\"true\"]{background:#e7edff}.thumbnail[aria-current=\"true\"] img{border-color:#4967dd}.thumbnail[aria-current=\"true\"] .slide-number{color:#3654c1;font-weight:700}\nmain{min-width:0;min-height:0;display:flex;flex-direction:column}\n#error{flex:none;max-height:30%;overflow:auto;background:#fff0ef;color:#922e25;margin:0;padding:16px 20px;white-space:pre-wrap;border-bottom:1px solid #e8bbb7}\n#error[hidden]{display:none}\n#stage{flex:1;min-height:0;overflow:auto;display:flex;padding:32px;overscroll-behavior:contain}\n#slide{position:relative;flex:none;margin:auto;background:white;box-shadow:0 3px 24px #19212d20;overflow:hidden}\n#slide{user-select:text;-webkit-user-select:text}\n#empty{margin:auto;color:#737d8e}\nfooter{display:flex;align-items:center;gap:14px;padding:0 16px;background:#fff;border-top:1px solid #d4d9e2;font-size:12px}\n#count{min-width:90px}footer .hint{flex:1;color:#737d8e}footer button{padding:3px 10px}footer select{padding:3px 8px}\n#presentation-controls{display:none}\nbody.presenting{grid-template-rows:minmax(0,1fr);background:#111}\n.presenting header,.presenting footer,.presenting .filmstrip,.presenting #chat{display:none}\n.presenting .workspace{grid-template-columns:minmax(0,1fr)}.presenting #stage{padding:0;background:#111}.presenting #slide{box-shadow:none}\n.presenting #presentation-controls{display:flex;position:fixed;bottom:16px;left:50%;transform:translateX(-50%);align-items:center;gap:12px;background:#202735e8;color:white;padding:6px;border-radius:8px;opacity:0;transition:opacity .15s}\n.presenting #presentation-controls:hover,.presenting #presentation-controls:focus-within{opacity:1}\n#presentation-controls button{background:transparent;color:white;border-color:#5d6575}\n@media(max-width:700px){.workspace{grid-template-columns:140px minmax(0,1fr)}.filmstrip{padding:12px 5px}header{padding:0 12px;gap:10px}.badge,footer .hint{display:none}#stage{padding:16px}footer{gap:8px}#status{font-size:11px}.download{padding:7px 8px}}\n#chat{min-height:0;display:flex;flex-direction:column;background:#fff;border-left:1px solid #d4d9e2}\n.chat-heading{display:flex;align-items:center;gap:8px;padding:16px;border-bottom:1px solid #e6e9ef}.chat-heading strong{flex:1}.chat-heading button{padding:5px 8px}\n#messages{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:16px;overscroll-behavior:contain}\n.chat-message{white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.6}.chat-message.user{background:#eef2ff;border-radius:8px;padding:10px 12px}.chat-message small{display:block;color:#697386;font-size:11px;margin-bottom:4px}.chat-intro{color:#737d8e;line-height:1.7}\n#chat-form{padding:14px;border-top:1px solid #e6e9ef}#chat-context{font-size:11px;color:#4967dd;margin-bottom:8px}#chat-input{font:inherit;resize:vertical;min-height:90px;max-height:200px;width:100%;padding:10px;border:1px solid #d4d9e2;border-radius:8px} .chat-actions{display:flex;gap:8px;margin-top:8px;align-items:center}.chat-actions select{min-width:0;flex:1}#chat-send{background:#293c73;color:white}#chat-status{padding:0 14px 12px;color:#667085;font-size:11px;white-space:pre-wrap;max-height:90px;overflow:auto}\nbody.chat-hidden .workspace{grid-template-columns:190px minmax(0,1fr)}body.chat-hidden #chat{display:none}\nbody.presenting .workspace{grid-template-columns:minmax(0,1fr)}\n@media(max-width:1000px){.workspace{grid-template-columns:120px minmax(0,1fr) 380px}.filmstrip{padding:12px 5px}#stage{padding:16px}}\n@media(max-width:700px){.workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(160px,1fr) minmax(200px,1fr)}.filmstrip{display:none}#chat{border-top:1px solid #d4d9e2}.chat-heading{padding:8px 12px}#chat-form{padding:8px 12px}#chat-input{min-height:50px}body.chat-hidden .workspace,.presenting .workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr)}.brand{font-size:14px}header{gap:6px}.download{font-size:11px}}\n@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}\n\n[hidden]{display:none!important}\n.chat-heading{padding:10px 14px}.chat-heading select{flex:1;min-width:0;font-weight:600;border:0;padding-left:0}\n#chat-context{margin:0;padding:8px 14px;border-bottom:1px solid #e6e9ef;color:#667085;font-size:11px}\n#claude-panel,#codex-panel{display:flex;flex-direction:column;flex:1;min-height:0}\n#claude-panel{background:#171b24;color:#e4e8f0}\n.terminal-toolbar{display:flex;align-items:center;gap:8px;padding:8px 12px;font-size:11px;min-height:40px}.terminal-toolbar span{flex:1;overflow-wrap:anywhere}.terminal-toolbar button{font-size:11px;padding:5px 8px;background:#262d3b;border-color:#45516a;color:#e4e8f0}\n.terminal-toolbar button:hover:not(:disabled){background:#35415a}\n#terminal{flex:1;min-height:0;overflow:hidden;padding:4px 8px 8px}.xterm{height:100%}\n#chat-input{min-height:70px;resize:vertical}.chat-shortcut{flex:1;color:#9098a7;font-size:10px}\n.markdown{white-space:normal;line-height:1.65}.markdown>:first-child{margin-top:0}.markdown>:last-child{margin-bottom:0}.markdown p{margin:0 0 .8em}.markdown pre{overflow:auto;padding:10px;background:#f3f5f8;border:1px solid #e6e9ef;border-radius:6px;white-space:pre}.markdown code{font-size:.9em;background:#f3f5f8;border-radius:3px;padding:1px 4px}.markdown pre code{padding:0}.markdown ul,.markdown ol{padding-left:22px}.markdown blockquote{margin-left:0;padding-left:12px;border-left:3px solid #d4d9e2;color:#667085}.markdown table{display:block;overflow:auto;border-collapse:collapse}.markdown th,.markdown td{padding:6px 8px;border:1px solid #d4d9e2}.markdown h1,.markdown h2,.markdown h3{font-size:1.1em}.markdown a{color:#3654c1;text-decoration:underline}\n</style>\n<header><span class=\"brand\">Office Kit</span><span class=\"badge\">Preview</span><span id=\"status\" role=\"status\">Building…</span><button id=\"toggle-chat\" aria-expanded=\"true\" aria-controls=\"chat\">Chat</button><button id=\"present\" disabled>Present</button><a class=\"download\" href=\"/deck.pptx\">Download PPTX</a></header>\n<div class=\"workspace\">\n<nav class=\"filmstrip\" aria-label=\"Slides\"><h2>Slides</h2><ol id=\"thumbnails\"></ol></nav>\n<main aria-label=\"Slide viewer\"><pre id=\"error\" role=\"alert\" hidden></pre><div id=\"stage\" tabindex=\"-1\"><div id=\"empty\">Waiting for slides…</div><div id=\"slide\" hidden></div></div></main>\n<aside id=\"chat\" aria-label=\"Slide chat\">\n<div class=\"chat-heading\"><select id=\"chat-provider\" aria-label=\"AI provider\"><option value=\"claude\">Claude Code</option><option value=\"codex\">Codex</option></select><button id=\"chat-reset\" hidden>New chat</button></div>\n<div id=\"chat-context\">No slide selected</div>\n<section id=\"claude-panel\" aria-label=\"Claude Code terminal\"><div class=\"terminal-toolbar\"><span id=\"terminal-status\" role=\"status\">Start Claude Code to edit your slides.</span><button id=\"terminal-start\">Start</button><button id=\"terminal-stop\" hidden>End session</button></div><div id=\"terminal\"></div></section>\n<div id=\"codex-panel\" hidden>\n<div id=\"messages\" role=\"log\" aria-label=\"Conversation\"><p class=\"chat-intro\">Describe what you want to change. The current slide is included automatically. You can also ask about other slides or the whole deck.<br><br>Uses your locally installed Codex. Sign in through its CLI first. Sending a message allows it to edit this project.</p></div>\n<form id=\"chat-form\"><textarea id=\"chat-input\" aria-label=\"Message\" maxlength=\"16000\" placeholder=\"Ask for a change…\" required></textarea><div class=\"chat-actions\"><span class=\"chat-shortcut\">⌘ / Ctrl + Enter</span><button id=\"chat-stop\" type=\"button\" hidden>Stop</button><button id=\"chat-send\" type=\"submit\">Send</button></div></form>\n<div id=\"chat-status\" role=\"status\">Ready</div>\n</div>\n</aside>\n</div>\n<footer><span id=\"count\" aria-live=\"polite\">No slides</span><span class=\"hint\">Changes appear automatically · Text can be selected and copied</span><button id=\"prev\" aria-label=\"Previous slide\" disabled>‹</button><button id=\"next\" aria-label=\"Next slide\" disabled>›</button><label for=\"zoom\">Zoom</label><select id=\"zoom\"><option value=\"fit\">Fit</option><option value=\"0.5\">50%</option><option value=\"0.75\">75%</option><option value=\"1\">100%</option><option value=\"1.25\">125%</option><option value=\"1.5\">150%</option><option value=\"2\">200%</option></select></footer>\n<div id=\"presentation-controls\"><button id=\"present-prev\" aria-label=\"Previous slide\">‹</button><span id=\"present-count\"></span><button id=\"present-next\" aria-label=\"Next slide\">›</button><button id=\"exit-present\">Exit · Esc</button></div>\n<script>\nlet state={slides:[],error:null,aspectRatio:16/9},index=0,urls=[],presenting=false;\nlet displayedSvg;\nconst byId=id=>document.getElementById(id);\nconst stage=byId('stage'),slide=byId('slide'),thumbnails=byId('thumbnails');\n// The slide lives in a shadow root rather than a sandboxed iframe so its text\n// (XHTML inside <foreignObject>) can be selected and copied while the deck's\n// SVG stays out of the viewer's own DOM and CSS. Keyboard events still reach the\n// document, so arrow-key navigation keeps working after clicking into a slide.\nconst canvas=slide.attachShadow({mode:'open'});\nfunction resize(){\n if(!state.slides.length)return;\n const style=getComputedStyle(stage);\n const width=Math.max(1,stage.clientWidth-parseFloat(style.paddingLeft)-parseFloat(style.paddingRight));\n const height=Math.max(1,stage.clientHeight-parseFloat(style.paddingTop)-parseFloat(style.paddingBottom));\n const ratio=state.aspectRatio;\n const zoom=byId('zoom').value;\n const slideWidth=presenting||zoom==='fit'?Math.min(width,height*ratio):1280*Number(zoom);\n slide.style.width=slideWidth+'px';slide.style.height=slideWidth/ratio+'px';\n}\nfunction selectSlide(next,focusThumbnail=false,reveal=true){\n index=Math.max(0,Math.min(next,state.slides.length-1));\n const count=state.slides.length?'Slide '+(index+1)+' of '+state.slides.length:'No slides';\n byId('chat-context').textContent=state.slides.length?count:'Whole project';\n byId('chat-context').dataset.focus=JSON.stringify({slide:state.slides.length?index:null,revision:state.revision??0});\n byId('count').textContent=count;byId('present-count').textContent=count;\n for(const id of ['prev','present-prev'])byId(id).disabled=index===0;\n for(const id of ['next','present-next'])byId(id).disabled=index>=state.slides.length-1;\n byId('present').disabled=!state.slides.length;\n byId('zoom').disabled=!state.slides.length;\n slide.hidden=!state.slides.length;byId('empty').hidden=!!state.slides.length;\n const svg=state.slides[index];\n if(svg!==displayedSvg){\n displayedSvg=svg;\n canvas.innerHTML=svg?'<style>svg{display:block;width:100%;height:100%}</style>'+svg:'';\n }\n slide.setAttribute('aria-label','Slide '+(index+1));\n for(const [position,item] of Array.from(thumbnails.children).entries()){\n const button=item.firstElementChild,selected=position===index;\n button.setAttribute('aria-current',String(selected));button.tabIndex=selected?0:-1;\n if(selected){if(reveal)button.scrollIntoView({block:'nearest'});if(focusThumbnail)button.focus({preventScroll:true});}\n }\n resize();\n}\nfunction update(updated){\n const focusedThumbnail=thumbnails.contains(document.activeElement);\n const previous=state;\n state=updated;\n byId('status').textContent=state.error?'Build failed · showing last successful output':state.building?'Updating…':state.slides.length+' slides · Live';\n byId('error').textContent=state.error||'';byId('error').hidden=!state.error;\n document.documentElement.style.setProperty('--slide-ratio',String(state.aspectRatio));\n for(let i=state.slides.length;i<urls.length;i++){\n URL.revokeObjectURL(urls[i]);thumbnails.lastElementChild.remove();\n }\n urls.length=state.slides.length;\n state.slides.forEach((svg,i)=>{\n if(svg===previous.slides[i])return;\n const oldUrl=urls[i];\n urls[i]=URL.createObjectURL(new Blob([svg],{type:'image/svg+xml'}));\n let item=thumbnails.children[i];\n if(!item){\n item=document.createElement('li');\n const button=document.createElement('button'),number=document.createElement('span'),image=document.createElement('img');\n button.className='thumbnail';button.setAttribute('aria-label','Slide '+(i+1));button.onclick=()=>selectSlide(i,true);\n number.className='slide-number';number.textContent=String(i+1);\n image.alt='';image.draggable=false;image.loading='lazy';\n button.append(number,image);item.append(button);thumbnails.append(item);\n }\n item.querySelector('img').src=urls[i];\n if(oldUrl)URL.revokeObjectURL(oldUrl);\n });\n selectSlide(index,focusedThumbnail,false);\n if(!state.slides.length&&presenting)void exitPresentation();\n}\nfunction setPresenting(value){\n presenting=value;document.body.classList.toggle('presenting',value);resize();\n if(value)stage.focus();else{\n byId('present').focus();\n thumbnails.children[index]?.firstElementChild.scrollIntoView({block:'nearest'});\n }\n}\nasync function exitPresentation(){\n setPresenting(false);\n if(document.fullscreenElement)await document.exitFullscreen();\n}\nbyId('present').onclick=async()=>{\n setPresenting(true);\n try{await document.documentElement.requestFullscreen();}\n catch{byId('exit-present').textContent='Exit view · Esc';}\n};\nbyId('exit-present').onclick=exitPresentation;\ndocument.addEventListener('fullscreenchange',()=>{if(!document.fullscreenElement&&presenting)setPresenting(false);});\nfor(const id of ['prev','present-prev'])byId(id).onclick=()=>selectSlide(index-1);\nfor(const id of ['next','present-next'])byId(id).onclick=()=>selectSlide(index+1);\nbyId('zoom').onchange=resize;\nstage.onclick=()=>{if(presenting&&!getSelection().toString())selectSlide(index+1);};\ndocument.addEventListener('keydown',event=>{\n if(event.key==='Escape'&&presenting){event.preventDefault();void exitPresentation();return;}\n if(event.altKey||event.ctrlKey||event.metaKey||event.target.closest('select,input,textarea,[contenteditable]'))return;\n let next=index;\n if(['ArrowLeft','ArrowUp','PageUp'].includes(event.key))next--;\n else if(['ArrowRight','ArrowDown','PageDown'].includes(event.key))next++;\n else if(event.key==='Home')next=0;\n else if(event.key==='End')next=state.slides.length-1;\n else if(event.key===' '&&presenting&&!event.target.closest('button,a'))next+=event.shiftKey?-1:1;\n else return;\n event.preventDefault();selectSlide(next,thumbnails.contains(document.activeElement));\n});\nnew ResizeObserver(resize).observe(stage);\nlet refreshId=0;\nasync function refresh(){\n const id=++refreshId;\n try{const response=await fetch('/state'+(state.revision===undefined?'':'?since='+state.revision));if(!response.ok)throw new Error('Preview unavailable');const updated=await response.json();if(id===refreshId){if(updated.changes){updated.slides=state.slides.slice(0,updated.count);updated.slides.length=updated.count;for(const [position,svg] of Object.entries(updated.changes))updated.slides[Number(position)]=svg;}update(updated);}}\n catch{if(id===refreshId)byId('status').textContent='Reconnecting…';}\n}\nlet chatRequest=0,chatBusy=false,lastMessages='';\nfunction renderChat(chat){\n chatBusy=chat.running;\n byId('chat-send').disabled=chat.running;\n byId('chat-reset').disabled=chat.running;\n if(byId('chat-provider').value==='codex')byId('chat-provider').disabled=chat.running;\n byId('chat-stop').hidden=!chat.running;\n byId('chat-status').textContent=chat.status;\n\n const serialized=JSON.stringify(chat.messages);\n if(serialized===lastMessages)return;\n lastMessages=serialized;\n const log=byId('messages'),atBottom=log.scrollHeight-log.scrollTop-log.clientHeight<60;\n log.replaceChildren();\n if(!chat.messages.length){const intro=document.createElement('p');intro.className='chat-intro';intro.textContent='Ask for a focused slide edit or a change across the deck. Uses your locally installed CLI and its login. Sending allows edits to this project.';log.append(intro);}\n for(const message of chat.messages){\n const item=document.createElement('div'),label=document.createElement('small'),body=document.createElement('div');\n item.className='chat-message '+message.role;\n label.textContent=message.role==='user'?'You · '+(message.context?.slide?'Slide '+message.context.slide:'Whole project'):'Assistant';\n if(message.role==='assistant'&&message.html){body.className='markdown';body.innerHTML=message.html;}else body.textContent=message.text||(chat.running?'Working…':'No response');item.append(label,body);log.append(item);\n }\n if(atBottom)log.scrollTop=log.scrollHeight;\n}\nasync function refreshChat(){\n const id=++chatRequest;\n try{const response=await fetch('/chat');if(!response.ok)throw new Error('Chat unavailable');const chat=await response.json();if(id===chatRequest)renderChat(chat);}\n catch(error){if(id===chatRequest)byId('chat-status').textContent=error.message;}\n}\nasync function chatAction(path,payload){\n ++chatRequest;\n const response=await fetch(path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});\n const result=await response.json();if(!response.ok)throw new Error(result.error||'Chat request failed');\n await refreshChat();\n}\nbyId('chat-form').onsubmit=async event=>{\n event.preventDefault();if(chatBusy)return;\n const input=byId('chat-input'),message=input.value.trim();if(!message)return;\n chatBusy=true;byId('chat-send').disabled=true;\n try{await chatAction('/chat',{message,provider:byId('chat-provider').value,slide:state.slides.length?index:null,revision:state.revision??0});if(input.value.trim()===message)input.value='';}\n catch(error){byId('chat-status').textContent=error.message;chatBusy=false;byId('chat-send').disabled=false;}\n};\nbyId('chat-input').onkeydown=event=>{if(event.key==='Enter'&&(event.metaKey||event.ctrlKey)&&!event.isComposing){event.preventDefault();byId('chat-form').requestSubmit();}};\nfor(const action of ['stop','reset'])byId('chat-'+action).onclick=()=>chatAction('/chat/'+action,{}).catch(error=>{byId('chat-status').textContent=error.message;});\nbyId('toggle-chat').onclick=()=>{const hidden=document.body.classList.toggle('chat-hidden');byId('toggle-chat').setAttribute('aria-expanded',String(!hidden));resize();};\nbyId('chat-provider').onchange=()=>{const claude=byId('chat-provider').value==='claude';byId('claude-panel').hidden=!claude;byId('codex-panel').hidden=claude;byId('chat-reset').hidden=claude;};\nvoid refreshChat();\nconst events=new EventSource('/events');events.onmessage=event=>{if(event.data==='chat'){void refreshChat();return;}if(event.data==='ready'){delete state.revision;void refreshChat();}void refresh();};events.onerror=()=>{byId('status').textContent='Reconnecting…'};refresh();\n</script><script type=\"module\" src=\"/terminal.js\"></script></html>`;\n","import { randomBytes } from 'node:crypto';\nimport { dirname, resolve, join } from 'node:path';\nimport { createRequire } from 'node:module';\nimport { stat, chmod } from 'node:fs/promises';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { IPty } from 'node-pty';\nimport type { FocusContext } from './chat.ts';\n\nconst OUTPUT_LIMIT = 2_000_000;\nconst REQUEST_LIMIT = 128_000;\n\n/** A real interactive Claude session, shared across reloads of its owning tab. */\nexport function createTerminal(entry: string, busy = () => false) {\n let process: IPty | undefined;\n let owner = '';\n let output = '';\n let context: FocusContext | undefined;\n let status = 'Start Claude Code to edit your slides.';\n let completion = Promise.resolve();\n let closing = false;\n let stopping = false;\n let starting = false;\n const token = randomBytes(32).toString('hex');\n const clients = new Set<ServerResponse>();\n const snapshot = () => ({ running: !!process, owner, status });\n const emit = (event: string, value: unknown) => {\n for (const client of clients) {\n if (!client.write(`event: ${event}\\ndata: ${JSON.stringify(value)}\\n\\n`)) {\n clients.delete(client);\n client.end();\n }\n }\n };\n async function stop() {\n if (!process || stopping) return completion;\n stopping = true;\n status = 'Stopping…';\n emit('state', snapshot());\n const running = process;\n running.kill();\n const timer = setTimeout(() => {\n if (process === running) running.kill('SIGKILL');\n }, 2000);\n await completion;\n clearTimeout(timer);\n }\n async function handle(\n request: IncomingMessage,\n response: ServerResponse,\n focus: (index: number | null, revision: number) => FocusContext,\n ) {\n const json = (code: number, value: unknown) => {\n response.writeHead(code, { 'Content-Type': 'application/json' });\n response.end(JSON.stringify(value));\n };\n if (request.url === '/terminal/context') {\n if (request.method !== 'POST' || request.headers.authorization !== `Bearer ${token}`) {\n json(403, { error: 'Invalid hook credentials' });\n return;\n }\n request.resume();\n json(200, {\n hookSpecificOutput: {\n hookEventName: 'UserPromptSubmit',\n additionalContext: `You are editing the local PowerPoint TSX project at ${resolve(entry)}.\nRead the project's authoring instructions. Make focused source patches and preserve unrelated work.\nThe preview rebuilds on save; do not start another dev server.\nThe focused slide is context, NOT a restriction. For \"this slide\", edit only its relevant source. For other slides or deck-wide requests, locate the relevant source or shared theme.\nSlide numbers are 1-based. Verify source before editing; dependency paths are candidates, not an exact slide-to-file mapping.\nPreview context captured with the latest terminal input: ${JSON.stringify(context)}`,\n },\n });\n return;\n }\n const origin = `http://${request.headers.host}`;\n if (\n request.headers['sec-fetch-site'] === 'cross-site' ||\n (request.headers.origin && request.headers.origin !== origin)\n ) {\n json(403, { error: 'Same-origin requests only' });\n return;\n }\n if (request.method === 'GET' && request.url === '/terminal/events') {\n response.writeHead(200, { 'Content-Type': 'text/event-stream' });\n response.write(`event: replay\\ndata: ${JSON.stringify(output)}\\n\\n`);\n response.write(`event: state\\ndata: ${JSON.stringify(snapshot())}\\n\\n`);\n clients.add(response);\n request.on('close', () => clients.delete(response));\n return;\n }\n if (\n request.method !== 'POST' ||\n request.headers.origin !== origin ||\n request.headers['content-type'] !== 'application/json'\n ) {\n json(403, { error: 'Same-origin JSON requests only' });\n return;\n }\n try {\n let body = '';\n request.setEncoding('utf8');\n for await (const chunk of request) {\n body += chunk;\n if (Buffer.byteLength(body) > REQUEST_LIMIT) throw new Error('Request too large');\n }\n const value = JSON.parse(body);\n if (!value || typeof value.client !== 'string' || !/^[\\w-]{1,64}$/.test(value.client))\n throw new Error('Invalid terminal client');\n if (closing || starting || stopping || (process && owner !== value.client)) {\n json(409, {\n error: stopping\n ? 'Claude Code is stopping'\n : 'Claude Code is open in another tab. Use that tab to control it.',\n });\n return;\n }\n const size = () => {\n if (\n !Number.isInteger(value.cols) ||\n value.cols < 20 ||\n value.cols > 500 ||\n !Number.isInteger(value.rows) ||\n value.rows < 5 ||\n value.rows > 300\n )\n throw new Error('Invalid terminal dimensions');\n };\n const updateFocus = () => {\n if (\n !(value.slide === null || (Number.isInteger(value.slide) && value.slide >= 0)) ||\n !Number.isInteger(value.revision)\n )\n throw new Error('Invalid preview context');\n context = focus(value.slide, value.revision);\n };\n if (request.url === '/terminal/start') {\n if (busy()) throw new Error('A Codex edit is already running');\n size();\n updateFocus();\n if (!process) {\n starting = true;\n try {\n // Lazy loading leaves preview/export usable if a native PTY cannot load.\n // node-pty 1.1.0 ships its macOS spawn helper without executable bits.\n // Restore the packaged helper's mode before its first launch.\n if (globalThis.process.platform === 'darwin') {\n const root = dirname(createRequire(import.meta.url).resolve('node-pty/package.json'));\n const helper = join(\n root,\n 'prebuilds',\n `darwin-${globalThis.process.arch}`,\n 'spawn-helper',\n );\n const info = await stat(helper).catch((error: NodeJS.ErrnoException) => {\n if (error.code === 'ENOENT') return undefined;\n throw error;\n });\n if (info && !(info.mode & 0o100)) await chmod(helper, info.mode | 0o100);\n }\n const pty = await import('node-pty');\n if (process || closing) throw new Error('Terminal state changed. Try again.');\n const settings = {\n hooks: {\n UserPromptSubmit: [\n {\n hooks: [\n {\n type: 'http',\n url: `${origin}/terminal/context`,\n headers: { Authorization: `Bearer ${token}` },\n timeout: 5,\n },\n ],\n },\n ],\n },\n };\n const env = { ...globalThis.process.env };\n // This is an independent interactive session, even if the preview was started by Claude.\n delete env.CLAUDECODE;\n process = pty.spawn('claude', ['--settings', JSON.stringify(settings)], {\n cwd: dirname(resolve(entry)),\n name: 'xterm-256color',\n env,\n cols: value.cols,\n rows: value.rows,\n });\n } finally {\n starting = false;\n }\n owner = value.client;\n output = '';\n status = 'Claude Code';\n emit('replay', '');\n process.onData((data) => {\n output = (output + data).slice(-OUTPUT_LIMIT);\n emit('output', data);\n });\n completion = new Promise<void>((done) =>\n process!.onExit(({ exitCode }) => {\n process = undefined;\n stopping = false;\n status = `Claude Code exited (${exitCode}). Start to open a new session.`;\n emit('state', snapshot());\n done();\n }),\n );\n }\n } else if (request.url === '/terminal/input') {\n if (!process) throw new Error('Start Claude Code first');\n if (typeof value.data !== 'string' || value.data.length > 32000)\n throw new Error('Invalid terminal input');\n updateFocus();\n process.write(value.data);\n } else if (request.url === '/terminal/resize') {\n size();\n process?.resize(value.cols, value.rows);\n } else if (request.url === '/terminal/stop') {\n await stop();\n } else {\n json(404, { error: 'Not found' });\n return;\n }\n emit('state', snapshot());\n json(200, snapshot());\n } catch (cause) {\n json(400, { error: cause instanceof Error ? cause.message : 'Invalid terminal request' });\n }\n }\n return {\n handle,\n isRunning: () => starting || !!process,\n async close() {\n closing = true;\n await stop();\n for (const client of clients) client.end();\n clients.clear();\n },\n };\n}\n","import MarkdownIt from 'markdown-it';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { dirname, resolve } from 'node:path';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nexport type Provider = 'codex';\nconst markdown = new MarkdownIt({ html: false, linkify: false });\n// Do not fetch remote images from model output.\nmarkdown.renderer.rules.image = (tokens, index) =>\n markdown.utils.escapeHtml(tokens[index]?.content ?? '');\nexport interface FocusContext {\n slide: number | null;\n count: number;\n revision: number;\n text: string;\n entry: string;\n files: string[];\n buildError: string | null;\n}\ninterface Message {\n role: 'user' | 'assistant';\n text: string;\n context?: FocusContext;\n}\n\n/** One shared conversation and one editing process per dev server. */\nexport function createChat(entry: string, notify: () => void, busy = () => false) {\n let messages: Message[] = [];\n let provider: Provider = 'codex';\n let child: ChildProcess | undefined;\n let status = 'Ready';\n let stopping = false;\n let killTimer: ReturnType<typeof setTimeout> | undefined;\n let completion: Promise<void> = Promise.resolve();\n const snapshot = () => ({\n messages: messages.map((message) => ({\n ...message,\n ...(message.role === 'assistant' ? { html: markdown.render(message.text) } : {}),\n })),\n provider,\n running: !!child,\n status,\n });\n function stop() {\n if (!child || stopping) return;\n stopping = true;\n status = 'Stopping…';\n const processToStop = child;\n const kill = (signal: NodeJS.Signals) => {\n try {\n if (process.platform !== 'win32' && processToStop.pid)\n process.kill(-processToStop.pid, signal);\n else processToStop.kill(signal);\n } catch {\n /* Already exited. */\n }\n };\n kill('SIGTERM');\n killTimer = setTimeout(() => kill('SIGKILL'), 2000);\n notify();\n }\n function start(text: string, selected: Provider, context: FocusContext) {\n provider = selected;\n const user: Message = { role: 'user', text, context };\n // Keep recent conversational intent, including each turn's original focus.\n const history = messages.slice(-12).map((message) => ({\n ...message,\n text: message.text.slice(-12000),\n }));\n const prompt = `You are editing a local PowerPoint TSX project in ${dirname(resolve(entry))}.\nRead the project's authoring instructions. Make focused patches to the TSX source, preserving unrelated work.\nThe preview rebuilds automatically on save; do not start another dev server.\nThe attached focus is context, NOT a restriction: use it for references such as \"this slide\". For other slides or deck-wide requests, find the relevant source or shared theme instead. Do not rewrite the whole deck for a local edit.\nSlide numbers are 1-based and refer to the last successful preview at send time. Source may have changed since then; verify it before editing. Files are dependency candidates, not an exact slide-to-source mapping.\nRespond in the user's language, briefly describing edits or answering their question. If permissions prevent an action, explain that instead of claiming success.\nRecent conversation (JSON): ${JSON.stringify(history)}\nCurrent request and preview context (JSON): ${JSON.stringify(user)}`;\n messages.push(user);\n const answer: Message = { role: 'assistant', text: '' };\n messages.push(answer);\n if (messages.length > 100) messages = messages.slice(-100);\n status = 'Working…';\n stopping = false;\n const args = ['exec', '--json', '--sandbox', 'workspace-write', '--skip-git-repo-check', '-'];\n child = spawn(selected, args, {\n cwd: dirname(resolve(entry)),\n stdio: ['pipe', 'pipe', 'pipe'],\n detached: process.platform !== 'win32',\n shell: false,\n });\n const running = child;\n let buffer = '',\n stderr = '',\n failure = '',\n sawResult = false;\n function append(value: string) {\n answer.text = (answer.text + (answer.text ? '\\n\\n' : '') + value).slice(-100000);\n notify();\n }\n function consume(line: string) {\n if (!line.trim()) return;\n try {\n const event = JSON.parse(line);\n if (event.type === 'item.completed' && event.item?.type === 'agent_message')\n append(event.item.text ?? '');\n if (event.type === 'item.started') {\n status = 'Editing / inspecting…';\n notify();\n }\n if (event.type === 'turn.completed') sawResult = true;\n if (event.type === 'turn.failed' || event.type === 'error')\n failure = event.error?.message ?? event.message ?? 'Agent failed';\n } catch {\n failure = 'Unexpected CLI output. Update your CLI and try again.';\n }\n }\n running.stdout!.setEncoding('utf8');\n running.stdout!.on('data', (chunk: string) => {\n buffer += chunk;\n let end: number;\n while ((end = buffer.indexOf('\\n')) !== -1) {\n consume(buffer.slice(0, end));\n buffer = buffer.slice(end + 1);\n }\n if (buffer.length > 2_000_000) {\n failure = 'CLI output exceeded the limit.';\n stop();\n }\n });\n running.stderr!.setEncoding('utf8');\n running.stderr!.on('data', (chunk: string) => {\n stderr = (stderr + chunk).slice(-8000);\n });\n running.stdin!.on('error', () => {\n /* Early process exit is handled below. */\n });\n running.stdin!.end(prompt);\n completion = new Promise<void>((done) => {\n running.on('error', (error) => {\n failure = `Could not run ${selected}. Install it and log in from your terminal first. ${error.message}`;\n });\n running.on('close', (code) => {\n if (buffer.trim()) consume(buffer);\n clearTimeout(killTimer);\n status = stopping\n ? 'Stopped. Saved edits remain in the preview.'\n : failure ||\n (code !== 0\n ? stderr || `${selected} exited with code ${code}`\n : !sawResult\n ? 'CLI exited without a completed result.'\n : 'Done');\n if (status !== 'Done') append(status);\n child = undefined;\n notify();\n done();\n });\n });\n notify();\n }\n async function handle(\n request: IncomingMessage,\n response: ServerResponse,\n focus: (index: number | null, revision: number) => FocusContext,\n ) {\n const json = (code: number, value: unknown) => {\n response.writeHead(code, { 'Content-Type': 'application/json' });\n response.end(JSON.stringify(value));\n };\n if (request.method === 'GET' && request.url === '/chat') {\n json(200, snapshot());\n return;\n }\n if (request.method !== 'POST') {\n json(405, { error: 'Method not allowed' });\n return;\n }\n // Same-origin JSON prevents other sites from invoking local editing tools.\n if (\n request.headers.origin !== `http://${request.headers.host}` ||\n request.headers['content-type']?.split(';')[0] !== 'application/json'\n ) {\n json(403, { error: 'Same-origin JSON required' });\n return;\n }\n try {\n let body = '';\n request.setEncoding('utf8');\n for await (const chunk of request) {\n body += chunk;\n if (Buffer.byteLength(body) > 32000) {\n json(413, { error: 'Message too large' });\n return;\n }\n }\n const value = JSON.parse(body);\n if (request.url === '/chat/stop') {\n stop();\n json(200, snapshot());\n return;\n }\n if (child) {\n json(409, { error: 'An edit is already running' });\n return;\n }\n if (request.url === '/chat/reset') {\n messages = [];\n status = 'Ready';\n notify();\n json(200, snapshot());\n return;\n }\n if (request.url !== '/chat') {\n json(404, { error: 'Not found' });\n return;\n }\n if (\n typeof value.message !== 'string' ||\n !value.message.trim() ||\n value.message.length > 16000 ||\n value.provider !== 'codex' ||\n !(value.slide === null || (Number.isInteger(value.slide) && value.slide >= 0)) ||\n !Number.isInteger(value.revision)\n ) {\n json(400, { error: 'Invalid chat request' });\n return;\n }\n if (busy()) {\n json(409, { error: 'End the Claude Code session before starting a Codex edit' });\n return;\n }\n const context = focus(value.slide, value.revision);\n start(value.message.trim(), value.provider, context);\n json(202, snapshot());\n } catch (cause) {\n json(400, { error: cause instanceof Error ? cause.message : 'Invalid request' });\n }\n }\n return {\n handle,\n isRunning: () => !!child,\n async close() {\n stop();\n await completion;\n },\n };\n}\n","import { createServer, type ServerResponse } from 'node:http';\nimport { watch } from 'node:fs';\nimport { dirname, resolve, sep } from 'node:path';\nimport type { BuildResult } from './build.ts';\nimport { createDeckBuilder } from './build-runner.ts';\nimport { page } from './page.ts';\nimport { readFile } from 'node:fs/promises';\nimport { createTerminal } from './terminal.ts';\nimport { createChat } from './chat.ts';\n\nexport async function serveDeck(entry: string, port = 4173) {\n let latest: BuildResult | undefined;\n let error: string | null = null;\n let building = false;\n let revision = 0;\n let generation = 0;\n let patch: Record<number, string> = {};\n let pending = false;\n let closed = false;\n const clients = new Set<ServerResponse>();\n const chat = createChat(\n entry,\n () => {\n for (const client of clients) client.write('data: chat\\n\\n');\n },\n () => terminal.isRunning(),\n );\n const terminal = createTerminal(entry, () => chat.isRunning());\n const server = createServer((request, response) => {\n const host = request.headers.host;\n if (host !== `127.0.0.1:${actualPort}` && host !== `localhost:${actualPort}`) {\n response.writeHead(403).end();\n return;\n }\n response.setHeader('Cache-Control', 'no-store');\n if (\n request.url === '/chat' ||\n request.url?.startsWith('/chat/') ||\n request.url?.startsWith('/terminal/')\n ) {\n const handler = request.url.startsWith('/terminal/') ? terminal : chat;\n void handler.handle(request, response, (index, viewedRevision) => {\n if (viewedRevision !== revision)\n throw new Error('Preview changed. Review the current slide and send again.');\n if (index !== null && (!latest || index >= latest.slides.length))\n throw new Error('Slide no longer exists.');\n return {\n slide: index === null ? null : index + 1,\n count: latest?.slides.length ?? 0,\n revision,\n text: index === null ? '' : (latest?.slideTexts[index] ?? '').slice(0, 12000),\n entry: resolve(entry),\n files: latest?.dependencies ?? [resolve(entry)],\n buildError: error,\n };\n });\n } else if (\n request.method === 'GET' &&\n ['/terminal.js', '/terminal.css'].includes(request.url ?? '')\n ) {\n const asset = request.url === '/terminal.js' ? 'terminal-client.js' : 'terminal-client.css';\n void readFile(new URL(asset, import.meta.url)).then(\n (bytes) => {\n response.writeHead(200, {\n 'Content-Type': asset.endsWith('.css') ? 'text/css' : 'text/javascript',\n });\n response.end(bytes);\n },\n () => response.writeHead(500).end('Terminal assets unavailable. Rebuild pptx-dev.'),\n );\n } else if (request.method !== 'GET') {\n response.writeHead(405).end();\n } else if (request.url === '/events') {\n response.writeHead(200, { 'Content-Type': 'text/event-stream' });\n response.write('data: ready\\n\\n');\n clients.add(response);\n request.on('close', () => clients.delete(response));\n } else if (request.url === '/state' || request.url?.startsWith('/state?')) {\n const since = new URL(request.url, 'http://localhost').searchParams.get('since');\n const incremental =\n since !== null && (since === String(revision) || since === String(revision - 1));\n response.writeHead(200, { 'Content-Type': 'application/json' });\n response.end(\n JSON.stringify({\n revision,\n building,\n ...(incremental\n ? {\n changes: since === String(revision) ? {} : patch,\n count: latest?.slides.length ?? 0,\n }\n : { slides: latest?.slides ?? [] }),\n aspectRatio: latest?.aspectRatio ?? 16 / 9,\n error,\n diagnostics: latest?.diagnostics ?? [],\n }),\n );\n } else if (request.url === '/deck.pptx' && latest) {\n response.writeHead(200, {\n 'Content-Type': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'Content-Disposition': 'attachment; filename=\"deck.pptx\"',\n });\n response.end(latest.bytes);\n } else if (request.url === '/') {\n response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n response.end(page);\n } else {\n response.writeHead(404).end();\n }\n });\n async function rebuild() {\n if (closed) return;\n if (building) {\n pending = true;\n return;\n }\n building = true;\n const started = generation;\n for (const client of clients) client.write('data: building\\n\\n');\n try {\n const result = await builder.build();\n if (started === generation && !closed) {\n patch = {};\n result.slides.forEach((svg, index) => {\n if (svg !== latest?.slides[index]) patch[index] = svg;\n });\n latest = result;\n revision++;\n error = null;\n }\n } catch (cause) {\n if (started === generation)\n error = cause instanceof Error ? (cause.stack ?? cause.message) : String(cause);\n } finally {\n building = false;\n for (const client of clients) client.write('data: updated\\n\\n');\n if (pending && !timer) {\n pending = false;\n void rebuild();\n }\n }\n }\n let timer: ReturnType<typeof setTimeout> | undefined;\n const root = dirname(resolve(entry));\n const watcher = watch(root, { recursive: true }, (_, filename) => {\n if (\n !filename ||\n filename\n .split(sep)\n .some((part) => ['node_modules', '.git', 'dist', '.office-kit'].includes(part))\n )\n return;\n if (!/\\.([cm]?[jt]sx?|json|pptx|png|jpe?g|gif|bmp|tiff?|emf|wmf|svg)$/i.test(filename)) return;\n generation++;\n void builder.cancel();\n clearTimeout(timer);\n timer = setTimeout(() => {\n timer = undefined;\n pending = false;\n void rebuild();\n }, 30);\n });\n const builder = createDeckBuilder(entry);\n let actualPort = port;\n try {\n await new Promise<void>((resolveListen, reject) => {\n server.once('error', reject);\n server.listen(port, '127.0.0.1', () => {\n server.off('error', reject);\n resolveListen();\n });\n });\n } catch (cause) {\n watcher.close();\n await builder.close();\n throw cause;\n }\n const address = server.address();\n if (address && typeof address !== 'string') actualPort = address.port;\n await rebuild();\n return {\n url: `http://127.0.0.1:${actualPort}`,\n async close() {\n closed = true;\n clearTimeout(timer);\n watcher.close();\n await terminal.close();\n await chat.close();\n await builder.close();\n for (const client of clients) client.end();\n await new Promise<void>((done, reject) =>\n server.close((cause) => (cause ? reject(cause) : done())),\n );\n },\n };\n}\n","#!/usr/bin/env node\nimport { resolve } from 'node:path';\nimport { exportDeck } from './index.ts';\nimport { serveDeck } from './server.ts';\nimport { initProject } from './init.ts';\nimport { inspectTemplate } from './inspect.ts';\n\nconst usage = `Usage: office-pptx init <new-directory>\n office-pptx dev <deck.tsx> [--port 4173]\n office-pptx build <deck.tsx> [--out deck.pptx]\n office-pptx inspect <template.pptx>`;\nconst [command, entry, ...args] = process.argv.slice(2);\ntry {\n if (command === '--help' || command === '-h') {\n console.log(usage);\n } else {\n if (!entry) throw new Error(usage);\n const option = command === 'build' ? '--out' : command === 'dev' ? '--port' : undefined;\n if (args.length && (args.length !== 2 || !option || args[0] !== option || !args[1])) {\n throw new Error(`Unexpected arguments: ${args.join(' ')}\\n${usage}`);\n }\n if (command === 'init') {\n const directory = await initProject(entry);\n console.log(`Created ${directory}\\nRun npm install, then npm run dev inside that directory.`);\n } else if (command === 'inspect') {\n console.log(JSON.stringify(await inspectTemplate(entry), null, 2));\n } else if (command === 'build') {\n const output = resolve(args[1] ?? 'deck.pptx');\n const result = await exportDeck(entry, output);\n console.log(`Wrote ${output} (${result.slides.length} slides)`);\n } else if (command === 'dev') {\n const port = args[1] === undefined ? 4173 : Number(args[1]);\n if (!Number.isInteger(port) || port < 0 || port > 65535)\n throw new Error('Port must be an integer between 0 and 65535.');\n const server = await serveDeck(entry, port);\n console.log(`Preview: ${server.url}`);\n const stop = () => {\n void server.close().then(() => process.exit(0));\n };\n process.once('SIGINT', stop);\n process.once('SIGTERM', stop);\n } else {\n throw new Error(usage);\n }\n }\n} catch (cause) {\n console.error(cause instanceof Error ? cause.message : cause);\n process.exitCode = 1;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/page.ts","../src/terminal.ts","../src/chat.ts","../src/server.ts","../src/cli.ts"],"sourcesContent":["export const page = `<!doctype html>\n<html lang=\"en\">\n<meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width\">\n<title>Office Kit — PowerPoint preview</title>\n<link rel=\"stylesheet\" href=\"/terminal.css\">\n<style>\n*{box-sizing:border-box}\nbody{margin:0;height:100dvh;overflow:hidden;background:#e9ecf1;color:#202735;font:13px system-ui;display:grid;grid-template-rows:60px minmax(0,1fr) 42px}\nbutton,a,select{font:inherit;color:inherit}button,select,.download{border:1px solid #d4d9e2;border-radius:6px;background:#fff;padding:7px 12px;text-decoration:none;cursor:pointer}\nbutton:hover:not(:disabled),.download:hover{background:#f0f3f9}button:disabled{opacity:.4;cursor:default}\n:focus-visible{outline:2px solid #4967dd;outline-offset:3px}\nheader{display:flex;align-items:center;gap:16px;padding:0 20px;background:#fff;border-bottom:1px solid #d4d9e2;min-width:0}\n.brand{font-weight:700;font-size:17px;white-space:nowrap}.badge{font-size:11px;color:#616b7c;background:#f1f3f7;border-radius:4px;padding:3px 6px}\n#status{flex:1;color:#667085;min-width:0}#present{background:#293c73;color:#fff;border-color:#293c73}\n.workspace{--filmstrip-width:190px;--chat-width:38vw;display:grid;grid-template-columns:var(--filmstrip-width) minmax(0,1fr) clamp(280px,var(--chat-width),calc(100vw - var(--filmstrip-width) - 240px));min-height:0}\n.filmstrip{background:#f7f8fa;border-right:1px solid #d4d9e2;overflow:auto;overscroll-behavior:contain;padding:16px 12px}\n.filmstrip h2{margin:0 0 12px 26px;text-transform:uppercase;letter-spacing:.1em;font-size:10px;color:#7c8596;font-weight:600}\n#thumbnails{display:flex;flex-direction:column;gap:12px;margin:0;padding:0;list-style:none}\n.thumbnail{display:flex;align-items:flex-start;gap:8px;width:100%;padding:4px 3px;border:0;background:transparent;border-radius:5px;text-align:left}\n.slide-number{width:18px;flex:none;text-align:right;font-size:11px;color:#737d8e;padding-top:5px}\n.thumbnail img{display:block;min-width:0;width:calc(100% - 26px);background:white;box-shadow:0 1px 4px #19212d18;border:2px solid transparent;border-radius:3px;aspect-ratio:var(--slide-ratio,16/9);object-fit:contain}\n.thumbnail[aria-current=\"true\"]{background:#e7edff}.thumbnail[aria-current=\"true\"] img{border-color:#4967dd}.thumbnail[aria-current=\"true\"] .slide-number{color:#3654c1;font-weight:700}\nmain{min-width:0;min-height:0;display:flex;flex-direction:column}\n#error{flex:none;max-height:30%;overflow:auto;background:#fff0ef;color:#922e25;margin:0;padding:16px 20px;white-space:pre-wrap;border-bottom:1px solid #e8bbb7}\n#error[hidden]{display:none}\n#stage{flex:1;min-height:0;overflow:auto;display:flex;padding:32px;overscroll-behavior:contain}\n#slide{position:relative;flex:none;margin:auto;background:white;box-shadow:0 3px 24px #19212d20;overflow:hidden}\n#slide{user-select:text;-webkit-user-select:text}\n#empty{margin:auto;color:#737d8e}\nfooter{display:flex;align-items:center;gap:14px;padding:0 16px;background:#fff;border-top:1px solid #d4d9e2;font-size:12px}\n#count{min-width:90px}footer .hint{flex:1;color:#737d8e}footer button{padding:3px 10px}footer select{padding:3px 8px}\n#presentation-controls{display:none}\nbody.presenting{grid-template-rows:minmax(0,1fr);background:#111}\n.presenting header,.presenting footer,.presenting .filmstrip,.presenting #chat{display:none}\n.presenting .workspace{grid-template-columns:minmax(0,1fr)}.presenting #stage{padding:0;background:#111}.presenting #slide{box-shadow:none}\n.presenting #presentation-controls{display:flex;position:fixed;bottom:16px;left:50%;transform:translateX(-50%);align-items:center;gap:12px;background:#202735e8;color:white;padding:6px;border-radius:8px;opacity:0;transition:opacity .15s}\n.presenting #presentation-controls:hover,.presenting #presentation-controls:focus-within{opacity:1}\n#presentation-controls button{background:transparent;color:white;border-color:#5d6575}\n@media(max-width:700px){.workspace{grid-template-columns:140px minmax(0,1fr)}.filmstrip{padding:12px 5px}header{padding:0 12px;gap:10px}.badge,footer .hint{display:none}#stage{padding:16px}footer{gap:8px}#status{font-size:11px}.download{padding:7px 8px}}\n#chat{position:relative;min-width:0;min-height:0;display:flex;flex-direction:column;background:#fff;border-left:1px solid #d4d9e2}\n#chat-resizer{position:absolute;left:-5px;top:0;bottom:0;width:10px;z-index:3;cursor:col-resize;touch-action:none}#chat-resizer::after{content:\"\";position:absolute;left:4px;top:0;bottom:0;width:2px}#chat-resizer:hover::after,#chat-resizer:focus-visible::after,.resizing-chat #chat-resizer::after{background:#4967dd}body.resizing-chat,body.resizing-chat *{cursor:col-resize!important;user-select:none!important}\n.chat-heading{display:flex;align-items:center;gap:8px;padding:16px;border-bottom:1px solid #e6e9ef}.chat-heading strong{flex:1}.chat-heading button{padding:5px 8px}\n#messages{flex:1;overflow:auto;padding:16px;display:flex;flex-direction:column;gap:16px;overscroll-behavior:contain}\n.chat-message{white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.6}.chat-message.user{background:#eef2ff;border-radius:8px;padding:10px 12px}.chat-message small{display:block;color:#697386;font-size:11px;margin-bottom:4px}.chat-intro{color:#737d8e;line-height:1.7}\n#chat-form{padding:14px;border-top:1px solid #e6e9ef}#chat-context{font-size:11px;color:#4967dd;margin-bottom:8px}#chat-input{font:inherit;resize:vertical;min-height:90px;max-height:200px;width:100%;padding:10px;border:1px solid #d4d9e2;border-radius:8px} .chat-actions{display:flex;gap:8px;margin-top:8px;align-items:center}.chat-actions select{min-width:0;flex:1}#chat-send{background:#293c73;color:white}#chat-status{padding:0 14px 12px;color:#667085;font-size:11px;white-space:pre-wrap;max-height:90px;overflow:auto}\nbody.chat-hidden .workspace{grid-template-columns:190px minmax(0,1fr)}body.chat-hidden #chat{display:none}\nbody.presenting .workspace{grid-template-columns:minmax(0,1fr)}\n@media(max-width:1000px){.workspace{--filmstrip-width:120px;--chat-width:380px}.filmstrip{padding:12px 5px}#stage{padding:16px}}\n@media(max-width:700px){#chat-resizer{display:none}.workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(160px,1fr) minmax(200px,1fr)}.filmstrip{display:none}#chat{border-top:1px solid #d4d9e2}.chat-heading{padding:8px 12px}#chat-form{padding:8px 12px}#chat-input{min-height:50px}body.chat-hidden .workspace,.presenting .workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr)}.brand{font-size:14px}header{gap:6px}.download{font-size:11px}}\n@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}\n\n[hidden]{display:none!important}\n.chat-heading{padding:10px 14px}.chat-heading select{flex:1;min-width:0;font-weight:600;border:0;padding-left:0}\n#chat-context{margin:0;padding:8px 14px;border-bottom:1px solid #e6e9ef;color:#667085;font-size:11px}\n#claude-panel,#codex-panel{display:flex;flex-direction:column;flex:1;min-height:0}\n#claude-panel{background:#171b24;color:#e4e8f0}\n.terminal-toolbar{display:flex;align-items:center;gap:8px;padding:8px 12px;font-size:11px;min-height:40px}.terminal-toolbar span{flex:1;overflow-wrap:anywhere}.terminal-toolbar button{font-size:11px;padding:5px 8px;background:#262d3b;border-color:#45516a;color:#e4e8f0}\n.terminal-toolbar button:hover:not(:disabled){background:#35415a}\n#terminal{flex:1;min-height:0;overflow:hidden;padding:4px 8px 8px}.xterm{height:100%}\n#chat-input{min-height:70px;resize:vertical}.chat-shortcut{flex:1;color:#9098a7;font-size:10px}\n.markdown{white-space:normal;line-height:1.65}.markdown>:first-child{margin-top:0}.markdown>:last-child{margin-bottom:0}.markdown p{margin:0 0 .8em}.markdown pre{overflow:auto;padding:10px;background:#f3f5f8;border:1px solid #e6e9ef;border-radius:6px;white-space:pre}.markdown code{font-size:.9em;background:#f3f5f8;border-radius:3px;padding:1px 4px}.markdown pre code{padding:0}.markdown ul,.markdown ol{padding-left:22px}.markdown blockquote{margin-left:0;padding-left:12px;border-left:3px solid #d4d9e2;color:#667085}.markdown table{display:block;overflow:auto;border-collapse:collapse}.markdown th,.markdown td{padding:6px 8px;border:1px solid #d4d9e2}.markdown h1,.markdown h2,.markdown h3{font-size:1.1em}.markdown a{color:#3654c1;text-decoration:underline}\n</style>\n<header><span class=\"brand\">Office Kit</span><span class=\"badge\">Preview</span><span id=\"status\" role=\"status\">Building…</span><button id=\"toggle-chat\" aria-expanded=\"true\" aria-controls=\"chat\">Chat</button><button id=\"present\" disabled>Present</button><a class=\"download\" href=\"/deck.pptx\">Download PPTX</a></header>\n<div class=\"workspace\">\n<nav class=\"filmstrip\" aria-label=\"Slides\"><h2>Slides</h2><ol id=\"thumbnails\"></ol></nav>\n<main aria-label=\"Slide viewer\"><pre id=\"error\" role=\"alert\" hidden></pre><div id=\"stage\" tabindex=\"-1\"><div id=\"empty\">Waiting for slides…</div><div id=\"slide\" hidden></div></div></main>\n<aside id=\"chat\" aria-label=\"Slide chat\">\n<div id=\"chat-resizer\" role=\"separator\" tabindex=\"0\" aria-label=\"Chat width\" aria-orientation=\"vertical\" aria-controls=\"chat\" title=\"Drag to resize · Double-click to reset\"></div>\n<div class=\"chat-heading\"><select id=\"chat-provider\" aria-label=\"AI provider\"><option value=\"claude\">Claude Code</option><option value=\"codex\">Codex</option></select><button id=\"chat-reset\" hidden>New chat</button></div>\n<div id=\"chat-context\">No slide selected</div>\n<section id=\"claude-panel\" aria-label=\"Claude Code terminal\"><div class=\"terminal-toolbar\"><span id=\"terminal-status\" role=\"status\">Start Claude Code to edit your slides.</span><button id=\"terminal-start\">Start</button><button id=\"terminal-stop\" hidden>End session</button></div><div id=\"terminal\"></div></section>\n<div id=\"codex-panel\" hidden>\n<div id=\"messages\" role=\"log\" aria-label=\"Conversation\"><p class=\"chat-intro\">Describe what you want to change. The current slide is included automatically. You can also ask about other slides or the whole deck.<br><br>Uses your locally installed Codex. Sign in through its CLI first. Sending a message allows it to edit this project.</p></div>\n<form id=\"chat-form\"><textarea id=\"chat-input\" aria-label=\"Message\" maxlength=\"16000\" placeholder=\"Ask for a change…\" required></textarea><div class=\"chat-actions\"><span class=\"chat-shortcut\">⌘ / Ctrl + Enter</span><button id=\"chat-stop\" type=\"button\" hidden>Stop</button><button id=\"chat-send\" type=\"submit\">Send</button></div></form>\n<div id=\"chat-status\" role=\"status\">Ready</div>\n</div>\n</aside>\n</div>\n<footer><span id=\"count\" aria-live=\"polite\">No slides</span><span class=\"hint\">Changes appear automatically · Text can be selected and copied</span><button id=\"prev\" aria-label=\"Previous slide\" disabled>‹</button><button id=\"next\" aria-label=\"Next slide\" disabled>›</button><label for=\"zoom\">Zoom</label><select id=\"zoom\"><option value=\"fit\">Fit</option><option value=\"0.5\">50%</option><option value=\"0.75\">75%</option><option value=\"1\">100%</option><option value=\"1.25\">125%</option><option value=\"1.5\">150%</option><option value=\"2\">200%</option></select></footer>\n<div id=\"presentation-controls\"><button id=\"present-prev\" aria-label=\"Previous slide\">‹</button><span id=\"present-count\"></span><button id=\"present-next\" aria-label=\"Next slide\">›</button><button id=\"exit-present\">Exit · Esc</button></div>\n<script>\nlet state={slides:[],error:null,aspectRatio:16/9},index=0,urls=[],presenting=false;\nlet displayedSvg;\nconst byId=id=>document.getElementById(id);\nconst stage=byId('stage'),slide=byId('slide'),thumbnails=byId('thumbnails');\nconst workspace=document.querySelector('.workspace'),chatResizer=byId('chat-resizer');\nconst chatWidthKey='office-kit-chat-width',minChatWidth=280,minStageWidth=240;\nfunction chatWidthLimits(){return {min:minChatWidth,max:Math.max(minChatWidth,workspace.clientWidth-document.querySelector('.filmstrip').getBoundingClientRect().width-minStageWidth)};}\nfunction updateChatWidthAria(){\n const {min,max}=chatWidthLimits();\n chatResizer.setAttribute('aria-valuemin',String(min));chatResizer.setAttribute('aria-valuemax',String(Math.round(max)));\n const width=Math.round(byId('chat').getBoundingClientRect().width);\n chatResizer.setAttribute('aria-valuenow',String(width));chatResizer.setAttribute('aria-valuetext',width+' pixels');\n}\nfunction setChatWidth(width){const {min,max}=chatWidthLimits();workspace.style.setProperty('--chat-width',Math.min(max,Math.max(min,width))+'px');}\nfunction saveChatWidth(){try{const width=workspace.style.getPropertyValue('--chat-width');if(width)localStorage.setItem(chatWidthKey,String(parseFloat(width)));else localStorage.removeItem(chatWidthKey);}catch(error){console.warn('Could not save chat width',error);}}\ntry{const saved=Number(localStorage.getItem(chatWidthKey));if(Number.isFinite(saved)&&saved>=minChatWidth)workspace.style.setProperty('--chat-width',saved+'px');}catch(error){console.warn('Could not restore chat width',error);}\nlet chatDrag=null;\nchatResizer.onpointerdown=event=>{\n if(event.button!==0||!event.isPrimary)return;\n event.preventDefault();chatResizer.focus();chatDrag={id:event.pointerId,x:event.clientX,width:byId('chat').getBoundingClientRect().width};\n chatResizer.setPointerCapture(event.pointerId);document.body.classList.add('resizing-chat');\n};\nchatResizer.onpointermove=event=>{if(chatDrag?.id===event.pointerId)setChatWidth(chatDrag.width+chatDrag.x-event.clientX);};\nfunction finishChatResize(){if(!chatDrag)return;chatDrag=null;document.body.classList.remove('resizing-chat');saveChatWidth();}\nchatResizer.onpointerup=finishChatResize;chatResizer.onpointercancel=finishChatResize;chatResizer.onlostpointercapture=finishChatResize;\nchatResizer.ondblclick=()=>{workspace.style.removeProperty('--chat-width');saveChatWidth();};\nchatResizer.onkeydown=event=>{\n const {min,max}=chatWidthLimits(),width=byId('chat').getBoundingClientRect().width,step=event.shiftKey?50:10;\n let next;\n if(event.key==='ArrowLeft')next=width+step;else if(event.key==='ArrowRight')next=width-step;else if(event.key==='Home')next=min;else if(event.key==='End')next=max;else return;\n event.preventDefault();event.stopPropagation();setChatWidth(next);saveChatWidth();\n};\nnew ResizeObserver(updateChatWidthAria).observe(byId('chat'));\n\n// The slide lives in a shadow root rather than a sandboxed iframe so its text\n// (XHTML inside <foreignObject>) can be selected and copied while the deck's\n// SVG stays out of the viewer's own DOM and CSS. Keyboard events still reach the\n// document, so arrow-key navigation keeps working after clicking into a slide.\nconst canvas=slide.attachShadow({mode:'open'});\nfunction resize(){\n if(!state.slides.length)return;\n const style=getComputedStyle(stage);\n const width=Math.max(1,stage.clientWidth-parseFloat(style.paddingLeft)-parseFloat(style.paddingRight));\n const height=Math.max(1,stage.clientHeight-parseFloat(style.paddingTop)-parseFloat(style.paddingBottom));\n const ratio=state.aspectRatio;\n const zoom=byId('zoom').value;\n const slideWidth=presenting||zoom==='fit'?Math.min(width,height*ratio):1280*Number(zoom);\n slide.style.width=slideWidth+'px';slide.style.height=slideWidth/ratio+'px';\n}\nfunction selectSlide(next,focusThumbnail=false,reveal=true){\n index=Math.max(0,Math.min(next,state.slides.length-1));\n const count=state.slides.length?'Slide '+(index+1)+' of '+state.slides.length:'No slides';\n byId('chat-context').textContent=state.slides.length?count:'Whole project';\n byId('chat-context').dataset.focus=JSON.stringify({slide:state.slides.length?index:null,revision:state.revision??0});\n byId('count').textContent=count;byId('present-count').textContent=count;\n for(const id of ['prev','present-prev'])byId(id).disabled=index===0;\n for(const id of ['next','present-next'])byId(id).disabled=index>=state.slides.length-1;\n byId('present').disabled=!state.slides.length;\n byId('zoom').disabled=!state.slides.length;\n slide.hidden=!state.slides.length;byId('empty').hidden=!!state.slides.length;\n const svg=state.slides[index];\n if(svg!==displayedSvg){\n displayedSvg=svg;\n canvas.innerHTML=svg?'<style>svg{display:block;width:100%;height:100%}</style>'+svg:'';\n }\n slide.setAttribute('aria-label','Slide '+(index+1));\n for(const [position,item] of Array.from(thumbnails.children).entries()){\n const button=item.firstElementChild,selected=position===index;\n button.setAttribute('aria-current',String(selected));button.tabIndex=selected?0:-1;\n if(selected){if(reveal)button.scrollIntoView({block:'nearest'});if(focusThumbnail)button.focus({preventScroll:true});}\n }\n resize();\n}\nfunction update(updated){\n const focusedThumbnail=thumbnails.contains(document.activeElement);\n const previous=state;\n state=updated;\n byId('status').textContent=state.error?'Build failed · showing last successful output':state.building?'Updating…':state.slides.length+' slides · Live';\n byId('error').textContent=state.error||'';byId('error').hidden=!state.error;\n document.documentElement.style.setProperty('--slide-ratio',String(state.aspectRatio));\n for(let i=state.slides.length;i<urls.length;i++){\n URL.revokeObjectURL(urls[i]);thumbnails.lastElementChild.remove();\n }\n urls.length=state.slides.length;\n state.slides.forEach((svg,i)=>{\n if(svg===previous.slides[i])return;\n const oldUrl=urls[i];\n urls[i]=URL.createObjectURL(new Blob([svg],{type:'image/svg+xml'}));\n let item=thumbnails.children[i];\n if(!item){\n item=document.createElement('li');\n const button=document.createElement('button'),number=document.createElement('span'),image=document.createElement('img');\n button.className='thumbnail';button.setAttribute('aria-label','Slide '+(i+1));button.onclick=()=>selectSlide(i,true);\n number.className='slide-number';number.textContent=String(i+1);\n image.alt='';image.draggable=false;image.loading='lazy';\n button.append(number,image);item.append(button);thumbnails.append(item);\n }\n item.querySelector('img').src=urls[i];\n if(oldUrl)URL.revokeObjectURL(oldUrl);\n });\n selectSlide(index,focusedThumbnail,false);\n if(!state.slides.length&&presenting)void exitPresentation();\n}\nfunction setPresenting(value){\n presenting=value;document.body.classList.toggle('presenting',value);resize();\n if(value)stage.focus();else{\n byId('present').focus();\n thumbnails.children[index]?.firstElementChild.scrollIntoView({block:'nearest'});\n }\n}\nasync function exitPresentation(){\n setPresenting(false);\n if(document.fullscreenElement)await document.exitFullscreen();\n}\nbyId('present').onclick=async()=>{\n setPresenting(true);\n try{await document.documentElement.requestFullscreen();}\n catch{byId('exit-present').textContent='Exit view · Esc';}\n};\nbyId('exit-present').onclick=exitPresentation;\ndocument.addEventListener('fullscreenchange',()=>{if(!document.fullscreenElement&&presenting)setPresenting(false);});\nfor(const id of ['prev','present-prev'])byId(id).onclick=()=>selectSlide(index-1);\nfor(const id of ['next','present-next'])byId(id).onclick=()=>selectSlide(index+1);\nbyId('zoom').onchange=resize;\nstage.onclick=()=>{if(presenting&&!getSelection().toString())selectSlide(index+1);};\ndocument.addEventListener('keydown',event=>{\n if(event.key==='Escape'&&presenting){event.preventDefault();void exitPresentation();return;}\n if(event.altKey||event.ctrlKey||event.metaKey||event.target.closest('select,input,textarea,[contenteditable]'))return;\n let next=index;\n if(['ArrowLeft','ArrowUp','PageUp'].includes(event.key))next--;\n else if(['ArrowRight','ArrowDown','PageDown'].includes(event.key))next++;\n else if(event.key==='Home')next=0;\n else if(event.key==='End')next=state.slides.length-1;\n else if(event.key===' '&&presenting&&!event.target.closest('button,a'))next+=event.shiftKey?-1:1;\n else return;\n event.preventDefault();selectSlide(next,thumbnails.contains(document.activeElement));\n});\nnew ResizeObserver(resize).observe(stage);\nlet refreshId=0;\nasync function refresh(){\n const id=++refreshId;\n try{const response=await fetch('/state'+(state.revision===undefined?'':'?since='+state.revision));if(!response.ok)throw new Error('Preview unavailable');const updated=await response.json();if(id===refreshId){if(updated.changes){updated.slides=state.slides.slice(0,updated.count);updated.slides.length=updated.count;for(const [position,svg] of Object.entries(updated.changes))updated.slides[Number(position)]=svg;}update(updated);}}\n catch{if(id===refreshId)byId('status').textContent='Reconnecting…';}\n}\nlet chatRequest=0,chatBusy=false,lastMessages='';\nfunction renderChat(chat){\n chatBusy=chat.running;\n byId('chat-send').disabled=chat.running;\n byId('chat-reset').disabled=chat.running;\n if(byId('chat-provider').value==='codex')byId('chat-provider').disabled=chat.running;\n byId('chat-stop').hidden=!chat.running;\n byId('chat-status').textContent=chat.status;\n\n const serialized=JSON.stringify(chat.messages);\n if(serialized===lastMessages)return;\n lastMessages=serialized;\n const log=byId('messages'),atBottom=log.scrollHeight-log.scrollTop-log.clientHeight<60;\n log.replaceChildren();\n if(!chat.messages.length){const intro=document.createElement('p');intro.className='chat-intro';intro.textContent='Ask for a focused slide edit or a change across the deck. Uses your locally installed CLI and its login. Sending allows edits to this project.';log.append(intro);}\n for(const message of chat.messages){\n const item=document.createElement('div'),label=document.createElement('small'),body=document.createElement('div');\n item.className='chat-message '+message.role;\n label.textContent=message.role==='user'?'You · '+(message.context?.slide?'Slide '+message.context.slide:'Whole project'):'Assistant';\n if(message.role==='assistant'&&message.html){body.className='markdown';body.innerHTML=message.html;}else body.textContent=message.text||(chat.running?'Working…':'No response');item.append(label,body);log.append(item);\n }\n if(atBottom)log.scrollTop=log.scrollHeight;\n}\nasync function refreshChat(){\n const id=++chatRequest;\n try{const response=await fetch('/chat');if(!response.ok)throw new Error('Chat unavailable');const chat=await response.json();if(id===chatRequest)renderChat(chat);}\n catch(error){if(id===chatRequest)byId('chat-status').textContent=error.message;}\n}\nasync function chatAction(path,payload){\n ++chatRequest;\n const response=await fetch(path,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)});\n const result=await response.json();if(!response.ok)throw new Error(result.error||'Chat request failed');\n await refreshChat();\n}\nbyId('chat-form').onsubmit=async event=>{\n event.preventDefault();if(chatBusy)return;\n const input=byId('chat-input'),message=input.value.trim();if(!message)return;\n chatBusy=true;byId('chat-send').disabled=true;\n try{await chatAction('/chat',{message,provider:byId('chat-provider').value,slide:state.slides.length?index:null,revision:state.revision??0});if(input.value.trim()===message)input.value='';}\n catch(error){byId('chat-status').textContent=error.message;chatBusy=false;byId('chat-send').disabled=false;}\n};\nbyId('chat-input').onkeydown=event=>{if(event.key==='Enter'&&(event.metaKey||event.ctrlKey)&&!event.isComposing){event.preventDefault();byId('chat-form').requestSubmit();}};\nfor(const action of ['stop','reset'])byId('chat-'+action).onclick=()=>chatAction('/chat/'+action,{}).catch(error=>{byId('chat-status').textContent=error.message;});\nbyId('toggle-chat').onclick=()=>{const hidden=document.body.classList.toggle('chat-hidden');byId('toggle-chat').setAttribute('aria-expanded',String(!hidden));resize();};\nbyId('chat-provider').onchange=()=>{const claude=byId('chat-provider').value==='claude';byId('claude-panel').hidden=!claude;byId('codex-panel').hidden=claude;byId('chat-reset').hidden=claude;};\nvoid refreshChat();\nconst events=new EventSource('/events');events.onmessage=event=>{if(event.data==='chat'){void refreshChat();return;}if(event.data==='ready'){delete state.revision;void refreshChat();}void refresh();};events.onerror=()=>{byId('status').textContent='Reconnecting…'};refresh();\n</script><script type=\"module\" src=\"/terminal.js\"></script></html>`;\n","import { randomBytes } from 'node:crypto';\nimport { dirname, resolve, join } from 'node:path';\nimport { createRequire } from 'node:module';\nimport { stat, chmod } from 'node:fs/promises';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport type { IPty } from 'node-pty';\nimport type { FocusContext } from './chat.ts';\n\nconst OUTPUT_LIMIT = 2_000_000;\nconst REQUEST_LIMIT = 128_000;\n\n/** A real interactive Claude session, shared across reloads of its owning tab. */\nexport function createTerminal(entry: string, busy = () => false) {\n let process: IPty | undefined;\n let owner = '';\n let output = '';\n let context: FocusContext | undefined;\n let status = 'Start Claude Code to edit your slides.';\n let completion = Promise.resolve();\n let closing = false;\n let stopping = false;\n let starting = false;\n const token = randomBytes(32).toString('hex');\n const clients = new Set<ServerResponse>();\n const snapshot = () => ({ running: !!process, owner, status });\n const emit = (event: string, value: unknown) => {\n for (const client of clients) {\n if (!client.write(`event: ${event}\\ndata: ${JSON.stringify(value)}\\n\\n`)) {\n clients.delete(client);\n client.end();\n }\n }\n };\n async function stop() {\n if (!process || stopping) return completion;\n stopping = true;\n status = 'Stopping…';\n emit('state', snapshot());\n const running = process;\n running.kill();\n const timer = setTimeout(() => {\n if (process === running) running.kill('SIGKILL');\n }, 2000);\n await completion;\n clearTimeout(timer);\n }\n async function handle(\n request: IncomingMessage,\n response: ServerResponse,\n focus: (index: number | null, revision: number) => FocusContext,\n ) {\n const json = (code: number, value: unknown) => {\n response.writeHead(code, { 'Content-Type': 'application/json' });\n response.end(JSON.stringify(value));\n };\n if (request.url === '/terminal/context') {\n if (request.method !== 'POST' || request.headers.authorization !== `Bearer ${token}`) {\n json(403, { error: 'Invalid hook credentials' });\n return;\n }\n request.resume();\n json(200, {\n hookSpecificOutput: {\n hookEventName: 'UserPromptSubmit',\n additionalContext: `You are editing the local PowerPoint TSX project at ${resolve(entry)}.\nRead the project's authoring instructions. Make focused source patches and preserve unrelated work.\nThe preview rebuilds on save; do not start another dev server.\nThe focused slide is context, NOT a restriction. For \"this slide\", edit only its relevant source. For other slides or deck-wide requests, locate the relevant source or shared theme.\nSlide numbers are 1-based. Verify source before editing; dependency paths are candidates, not an exact slide-to-file mapping.\nPreview context captured with the latest terminal input: ${JSON.stringify(context)}`,\n },\n });\n return;\n }\n const origin = `http://${request.headers.host}`;\n if (\n request.headers['sec-fetch-site'] === 'cross-site' ||\n (request.headers.origin && request.headers.origin !== origin)\n ) {\n json(403, { error: 'Same-origin requests only' });\n return;\n }\n if (request.method === 'GET' && request.url === '/terminal/events') {\n response.writeHead(200, { 'Content-Type': 'text/event-stream' });\n response.write(`event: replay\\ndata: ${JSON.stringify(output)}\\n\\n`);\n response.write(`event: state\\ndata: ${JSON.stringify(snapshot())}\\n\\n`);\n clients.add(response);\n request.on('close', () => clients.delete(response));\n return;\n }\n if (\n request.method !== 'POST' ||\n request.headers.origin !== origin ||\n request.headers['content-type'] !== 'application/json'\n ) {\n json(403, { error: 'Same-origin JSON requests only' });\n return;\n }\n try {\n let body = '';\n request.setEncoding('utf8');\n for await (const chunk of request) {\n body += chunk;\n if (Buffer.byteLength(body) > REQUEST_LIMIT) throw new Error('Request too large');\n }\n const value = JSON.parse(body);\n if (!value || typeof value.client !== 'string' || !/^[\\w-]{1,64}$/.test(value.client))\n throw new Error('Invalid terminal client');\n if (closing || starting || stopping || (process && owner !== value.client)) {\n json(409, {\n error: stopping\n ? 'Claude Code is stopping'\n : 'Claude Code is open in another tab. Use that tab to control it.',\n });\n return;\n }\n const size = () => {\n if (\n !Number.isInteger(value.cols) ||\n value.cols < 20 ||\n value.cols > 500 ||\n !Number.isInteger(value.rows) ||\n value.rows < 5 ||\n value.rows > 300\n )\n throw new Error('Invalid terminal dimensions');\n };\n const updateFocus = () => {\n if (\n !(value.slide === null || (Number.isInteger(value.slide) && value.slide >= 0)) ||\n !Number.isInteger(value.revision)\n )\n throw new Error('Invalid preview context');\n context = focus(value.slide, value.revision);\n };\n if (request.url === '/terminal/start') {\n if (busy()) throw new Error('A Codex edit is already running');\n size();\n updateFocus();\n if (!process) {\n starting = true;\n try {\n // Lazy loading leaves preview/export usable if a native PTY cannot load.\n // node-pty 1.1.0 ships its macOS spawn helper without executable bits.\n // Restore the packaged helper's mode before its first launch.\n if (globalThis.process.platform === 'darwin') {\n const root = dirname(createRequire(import.meta.url).resolve('node-pty/package.json'));\n const helper = join(\n root,\n 'prebuilds',\n `darwin-${globalThis.process.arch}`,\n 'spawn-helper',\n );\n const info = await stat(helper).catch((error: NodeJS.ErrnoException) => {\n if (error.code === 'ENOENT') return undefined;\n throw error;\n });\n if (info && !(info.mode & 0o100)) await chmod(helper, info.mode | 0o100);\n }\n const pty = await import('node-pty');\n if (process || closing) throw new Error('Terminal state changed. Try again.');\n const settings = {\n hooks: {\n UserPromptSubmit: [\n {\n hooks: [\n {\n type: 'http',\n url: `${origin}/terminal/context`,\n headers: { Authorization: `Bearer ${token}` },\n timeout: 5,\n },\n ],\n },\n ],\n },\n };\n const env = { ...globalThis.process.env };\n // This is an independent interactive session, even if the preview was started by Claude.\n delete env.CLAUDECODE;\n process = pty.spawn('claude', ['--settings', JSON.stringify(settings)], {\n cwd: dirname(resolve(entry)),\n name: 'xterm-256color',\n env,\n cols: value.cols,\n rows: value.rows,\n });\n } finally {\n starting = false;\n }\n owner = value.client;\n output = '';\n status = 'Claude Code';\n emit('replay', '');\n process.onData((data) => {\n output = (output + data).slice(-OUTPUT_LIMIT);\n emit('output', data);\n });\n completion = new Promise<void>((done) =>\n process!.onExit(({ exitCode }) => {\n process = undefined;\n stopping = false;\n status = `Claude Code exited (${exitCode}). Start to open a new session.`;\n emit('state', snapshot());\n done();\n }),\n );\n }\n } else if (request.url === '/terminal/input') {\n if (!process) throw new Error('Start Claude Code first');\n if (typeof value.data !== 'string' || value.data.length > 32000)\n throw new Error('Invalid terminal input');\n updateFocus();\n process.write(value.data);\n } else if (request.url === '/terminal/resize') {\n size();\n process?.resize(value.cols, value.rows);\n } else if (request.url === '/terminal/stop') {\n await stop();\n } else {\n json(404, { error: 'Not found' });\n return;\n }\n emit('state', snapshot());\n json(200, snapshot());\n } catch (cause) {\n json(400, { error: cause instanceof Error ? cause.message : 'Invalid terminal request' });\n }\n }\n return {\n handle,\n isRunning: () => starting || !!process,\n async close() {\n closing = true;\n await stop();\n for (const client of clients) client.end();\n clients.clear();\n },\n };\n}\n","import MarkdownIt from 'markdown-it';\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { dirname, resolve } from 'node:path';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nexport type Provider = 'codex';\nconst markdown = new MarkdownIt({ html: false, linkify: false });\n// Do not fetch remote images from model output.\nmarkdown.renderer.rules.image = (tokens, index) =>\n markdown.utils.escapeHtml(tokens[index]?.content ?? '');\nexport interface FocusContext {\n slide: number | null;\n count: number;\n revision: number;\n text: string;\n entry: string;\n files: string[];\n buildError: string | null;\n}\ninterface Message {\n role: 'user' | 'assistant';\n text: string;\n context?: FocusContext;\n}\n\n/** One shared conversation and one editing process per dev server. */\nexport function createChat(entry: string, notify: () => void, busy = () => false) {\n let messages: Message[] = [];\n let provider: Provider = 'codex';\n let child: ChildProcess | undefined;\n let status = 'Ready';\n let stopping = false;\n let killTimer: ReturnType<typeof setTimeout> | undefined;\n let completion: Promise<void> = Promise.resolve();\n const snapshot = () => ({\n messages: messages.map((message) => ({\n ...message,\n ...(message.role === 'assistant' ? { html: markdown.render(message.text) } : {}),\n })),\n provider,\n running: !!child,\n status,\n });\n function stop() {\n if (!child || stopping) return;\n stopping = true;\n status = 'Stopping…';\n const processToStop = child;\n const kill = (signal: NodeJS.Signals) => {\n try {\n if (process.platform !== 'win32' && processToStop.pid)\n process.kill(-processToStop.pid, signal);\n else processToStop.kill(signal);\n } catch {\n /* Already exited. */\n }\n };\n kill('SIGTERM');\n killTimer = setTimeout(() => kill('SIGKILL'), 2000);\n notify();\n }\n function start(text: string, selected: Provider, context: FocusContext) {\n provider = selected;\n const user: Message = { role: 'user', text, context };\n // Keep recent conversational intent, including each turn's original focus.\n const history = messages.slice(-12).map((message) => ({\n ...message,\n text: message.text.slice(-12000),\n }));\n const prompt = `You are editing a local PowerPoint TSX project in ${dirname(resolve(entry))}.\nRead the project's authoring instructions. Make focused patches to the TSX source, preserving unrelated work.\nThe preview rebuilds automatically on save; do not start another dev server.\nThe attached focus is context, NOT a restriction: use it for references such as \"this slide\". For other slides or deck-wide requests, find the relevant source or shared theme instead. Do not rewrite the whole deck for a local edit.\nSlide numbers are 1-based and refer to the last successful preview at send time. Source may have changed since then; verify it before editing. Files are dependency candidates, not an exact slide-to-source mapping.\nRespond in the user's language, briefly describing edits or answering their question. If permissions prevent an action, explain that instead of claiming success.\nRecent conversation (JSON): ${JSON.stringify(history)}\nCurrent request and preview context (JSON): ${JSON.stringify(user)}`;\n messages.push(user);\n const answer: Message = { role: 'assistant', text: '' };\n messages.push(answer);\n if (messages.length > 100) messages = messages.slice(-100);\n status = 'Working…';\n stopping = false;\n const args = ['exec', '--json', '--sandbox', 'workspace-write', '--skip-git-repo-check', '-'];\n child = spawn(selected, args, {\n cwd: dirname(resolve(entry)),\n stdio: ['pipe', 'pipe', 'pipe'],\n detached: process.platform !== 'win32',\n shell: false,\n });\n const running = child;\n let buffer = '',\n stderr = '',\n failure = '',\n sawResult = false;\n function append(value: string) {\n answer.text = (answer.text + (answer.text ? '\\n\\n' : '') + value).slice(-100000);\n notify();\n }\n function consume(line: string) {\n if (!line.trim()) return;\n try {\n const event = JSON.parse(line);\n if (event.type === 'item.completed' && event.item?.type === 'agent_message')\n append(event.item.text ?? '');\n if (event.type === 'item.started') {\n status = 'Editing / inspecting…';\n notify();\n }\n if (event.type === 'turn.completed') sawResult = true;\n if (event.type === 'turn.failed' || event.type === 'error')\n failure = event.error?.message ?? event.message ?? 'Agent failed';\n } catch {\n failure = 'Unexpected CLI output. Update your CLI and try again.';\n }\n }\n running.stdout!.setEncoding('utf8');\n running.stdout!.on('data', (chunk: string) => {\n buffer += chunk;\n let end: number;\n while ((end = buffer.indexOf('\\n')) !== -1) {\n consume(buffer.slice(0, end));\n buffer = buffer.slice(end + 1);\n }\n if (buffer.length > 2_000_000) {\n failure = 'CLI output exceeded the limit.';\n stop();\n }\n });\n running.stderr!.setEncoding('utf8');\n running.stderr!.on('data', (chunk: string) => {\n stderr = (stderr + chunk).slice(-8000);\n });\n running.stdin!.on('error', () => {\n /* Early process exit is handled below. */\n });\n running.stdin!.end(prompt);\n completion = new Promise<void>((done) => {\n running.on('error', (error) => {\n failure = `Could not run ${selected}. Install it and log in from your terminal first. ${error.message}`;\n });\n running.on('close', (code) => {\n if (buffer.trim()) consume(buffer);\n clearTimeout(killTimer);\n status = stopping\n ? 'Stopped. Saved edits remain in the preview.'\n : failure ||\n (code !== 0\n ? stderr || `${selected} exited with code ${code}`\n : !sawResult\n ? 'CLI exited without a completed result.'\n : 'Done');\n if (status !== 'Done') append(status);\n child = undefined;\n notify();\n done();\n });\n });\n notify();\n }\n async function handle(\n request: IncomingMessage,\n response: ServerResponse,\n focus: (index: number | null, revision: number) => FocusContext,\n ) {\n const json = (code: number, value: unknown) => {\n response.writeHead(code, { 'Content-Type': 'application/json' });\n response.end(JSON.stringify(value));\n };\n if (request.method === 'GET' && request.url === '/chat') {\n json(200, snapshot());\n return;\n }\n if (request.method !== 'POST') {\n json(405, { error: 'Method not allowed' });\n return;\n }\n // Same-origin JSON prevents other sites from invoking local editing tools.\n if (\n request.headers.origin !== `http://${request.headers.host}` ||\n request.headers['content-type']?.split(';')[0] !== 'application/json'\n ) {\n json(403, { error: 'Same-origin JSON required' });\n return;\n }\n try {\n let body = '';\n request.setEncoding('utf8');\n for await (const chunk of request) {\n body += chunk;\n if (Buffer.byteLength(body) > 32000) {\n json(413, { error: 'Message too large' });\n return;\n }\n }\n const value = JSON.parse(body);\n if (request.url === '/chat/stop') {\n stop();\n json(200, snapshot());\n return;\n }\n if (child) {\n json(409, { error: 'An edit is already running' });\n return;\n }\n if (request.url === '/chat/reset') {\n messages = [];\n status = 'Ready';\n notify();\n json(200, snapshot());\n return;\n }\n if (request.url !== '/chat') {\n json(404, { error: 'Not found' });\n return;\n }\n if (\n typeof value.message !== 'string' ||\n !value.message.trim() ||\n value.message.length > 16000 ||\n value.provider !== 'codex' ||\n !(value.slide === null || (Number.isInteger(value.slide) && value.slide >= 0)) ||\n !Number.isInteger(value.revision)\n ) {\n json(400, { error: 'Invalid chat request' });\n return;\n }\n if (busy()) {\n json(409, { error: 'End the Claude Code session before starting a Codex edit' });\n return;\n }\n const context = focus(value.slide, value.revision);\n start(value.message.trim(), value.provider, context);\n json(202, snapshot());\n } catch (cause) {\n json(400, { error: cause instanceof Error ? cause.message : 'Invalid request' });\n }\n }\n return {\n handle,\n isRunning: () => !!child,\n async close() {\n stop();\n await completion;\n },\n };\n}\n","import { createServer, type ServerResponse } from 'node:http';\nimport { watch } from 'node:fs';\nimport { dirname, resolve, sep } from 'node:path';\nimport type { BuildResult } from './build.ts';\nimport { createDeckBuilder } from './build-runner.ts';\nimport { page } from './page.ts';\nimport { readFile } from 'node:fs/promises';\nimport { createTerminal } from './terminal.ts';\nimport { createChat } from './chat.ts';\n\nexport async function serveDeck(entry: string, port = 4173) {\n let latest: BuildResult | undefined;\n let error: string | null = null;\n let building = false;\n let revision = 0;\n let generation = 0;\n let patch: Record<number, string> = {};\n let pending = false;\n let closed = false;\n const clients = new Set<ServerResponse>();\n const chat = createChat(\n entry,\n () => {\n for (const client of clients) client.write('data: chat\\n\\n');\n },\n () => terminal.isRunning(),\n );\n const terminal = createTerminal(entry, () => chat.isRunning());\n const server = createServer((request, response) => {\n const host = request.headers.host;\n if (host !== `127.0.0.1:${actualPort}` && host !== `localhost:${actualPort}`) {\n response.writeHead(403).end();\n return;\n }\n response.setHeader('Cache-Control', 'no-store');\n if (\n request.url === '/chat' ||\n request.url?.startsWith('/chat/') ||\n request.url?.startsWith('/terminal/')\n ) {\n const handler = request.url.startsWith('/terminal/') ? terminal : chat;\n void handler.handle(request, response, (index, viewedRevision) => {\n if (viewedRevision !== revision)\n throw new Error('Preview changed. Review the current slide and send again.');\n if (index !== null && (!latest || index >= latest.slides.length))\n throw new Error('Slide no longer exists.');\n return {\n slide: index === null ? null : index + 1,\n count: latest?.slides.length ?? 0,\n revision,\n text: index === null ? '' : (latest?.slideTexts[index] ?? '').slice(0, 12000),\n entry: resolve(entry),\n files: latest?.dependencies ?? [resolve(entry)],\n buildError: error,\n };\n });\n } else if (\n request.method === 'GET' &&\n ['/terminal.js', '/terminal.css'].includes(request.url ?? '')\n ) {\n const asset = request.url === '/terminal.js' ? 'terminal-client.js' : 'terminal-client.css';\n void readFile(new URL(asset, import.meta.url)).then(\n (bytes) => {\n response.writeHead(200, {\n 'Content-Type': asset.endsWith('.css') ? 'text/css' : 'text/javascript',\n });\n response.end(bytes);\n },\n () => response.writeHead(500).end('Terminal assets unavailable. Rebuild pptx-dev.'),\n );\n } else if (request.method !== 'GET') {\n response.writeHead(405).end();\n } else if (request.url === '/events') {\n response.writeHead(200, { 'Content-Type': 'text/event-stream' });\n response.write('data: ready\\n\\n');\n clients.add(response);\n request.on('close', () => clients.delete(response));\n } else if (request.url === '/state' || request.url?.startsWith('/state?')) {\n const since = new URL(request.url, 'http://localhost').searchParams.get('since');\n const incremental =\n since !== null && (since === String(revision) || since === String(revision - 1));\n response.writeHead(200, { 'Content-Type': 'application/json' });\n response.end(\n JSON.stringify({\n revision,\n building,\n ...(incremental\n ? {\n changes: since === String(revision) ? {} : patch,\n count: latest?.slides.length ?? 0,\n }\n : { slides: latest?.slides ?? [] }),\n aspectRatio: latest?.aspectRatio ?? 16 / 9,\n error,\n diagnostics: latest?.diagnostics ?? [],\n }),\n );\n } else if (request.url === '/deck.pptx' && latest) {\n response.writeHead(200, {\n 'Content-Type': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n 'Content-Disposition': 'attachment; filename=\"deck.pptx\"',\n });\n response.end(latest.bytes);\n } else if (request.url === '/') {\n response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n response.end(page);\n } else {\n response.writeHead(404).end();\n }\n });\n async function rebuild() {\n if (closed) return;\n if (building) {\n pending = true;\n return;\n }\n building = true;\n const started = generation;\n for (const client of clients) client.write('data: building\\n\\n');\n try {\n const result = await builder.build();\n if (started === generation && !closed) {\n patch = {};\n result.slides.forEach((svg, index) => {\n if (svg !== latest?.slides[index]) patch[index] = svg;\n });\n latest = result;\n revision++;\n error = null;\n }\n } catch (cause) {\n if (started === generation)\n error = cause instanceof Error ? (cause.stack ?? cause.message) : String(cause);\n } finally {\n building = false;\n for (const client of clients) client.write('data: updated\\n\\n');\n if (pending && !timer) {\n pending = false;\n void rebuild();\n }\n }\n }\n let timer: ReturnType<typeof setTimeout> | undefined;\n const root = dirname(resolve(entry));\n const watcher = watch(root, { recursive: true }, (_, filename) => {\n if (\n !filename ||\n filename\n .split(sep)\n .some((part) => ['node_modules', '.git', 'dist', '.office-kit'].includes(part))\n )\n return;\n if (!/\\.([cm]?[jt]sx?|json|pptx|png|jpe?g|gif|bmp|tiff?|emf|wmf|svg)$/i.test(filename)) return;\n generation++;\n void builder.cancel();\n clearTimeout(timer);\n timer = setTimeout(() => {\n timer = undefined;\n pending = false;\n void rebuild();\n }, 30);\n });\n const builder = createDeckBuilder(entry);\n let actualPort = port;\n try {\n await new Promise<void>((resolveListen, reject) => {\n server.once('error', reject);\n server.listen(port, '127.0.0.1', () => {\n server.off('error', reject);\n resolveListen();\n });\n });\n } catch (cause) {\n watcher.close();\n await builder.close();\n throw cause;\n }\n const address = server.address();\n if (address && typeof address !== 'string') actualPort = address.port;\n await rebuild();\n return {\n url: `http://127.0.0.1:${actualPort}`,\n async close() {\n closed = true;\n clearTimeout(timer);\n watcher.close();\n await terminal.close();\n await chat.close();\n await builder.close();\n for (const client of clients) client.end();\n await new Promise<void>((done, reject) =>\n server.close((cause) => (cause ? reject(cause) : done())),\n );\n },\n };\n}\n","#!/usr/bin/env node\nimport { resolve } from 'node:path';\nimport { exportDeck } from './index.ts';\nimport { serveDeck } from './server.ts';\nimport { initProject } from './init.ts';\nimport { inspectTemplate } from './inspect.ts';\n\nconst usage = `Usage: office-pptx init <new-directory>\n office-pptx dev <deck.tsx> [--port 4173]\n office-pptx build <deck.tsx> [--out deck.pptx]\n office-pptx inspect <template.pptx>`;\nconst [command, entry, ...args] = process.argv.slice(2);\ntry {\n if (command === '--help' || command === '-h') {\n console.log(usage);\n } else {\n if (!entry) throw new Error(usage);\n const option = command === 'build' ? '--out' : command === 'dev' ? '--port' : undefined;\n if (args.length && (args.length !== 2 || !option || args[0] !== option || !args[1])) {\n throw new Error(`Unexpected arguments: ${args.join(' ')}\\n${usage}`);\n }\n if (command === 'init') {\n const directory = await initProject(entry);\n console.log(`Created ${directory}\\nRun npm install, then npm run dev inside that directory.`);\n } else if (command === 'inspect') {\n console.log(JSON.stringify(await inspectTemplate(entry), null, 2));\n } else if (command === 'build') {\n const output = resolve(args[1] ?? 'deck.pptx');\n const result = await exportDeck(entry, output);\n console.log(`Wrote ${output} (${result.slides.length} slides)`);\n } else if (command === 'dev') {\n const port = args[1] === undefined ? 4173 : Number(args[1]);\n if (!Number.isInteger(port) || port < 0 || port > 65535)\n throw new Error('Port must be an integer between 0 and 65535.');\n const server = await serveDeck(entry, port);\n console.log(`Preview: ${server.url}`);\n const stop = () => {\n void server.close().then(() => process.exit(0));\n };\n process.once('SIGINT', stop);\n process.once('SIGTERM', stop);\n } else {\n throw new Error(usage);\n }\n }\n} catch (cause) {\n console.error(cause instanceof Error ? cause.message : cause);\n process.exitCode = 1;\n}\n"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { i as initProject, n as exportDeck, r as inspectTemplate, t as buildDeck } from "./src-31HzWTvZ.mjs";
1
+ import { i as initProject, n as exportDeck, r as inspectTemplate, t as buildDeck } from "./src-CShdm4G1.mjs";
2
2
  export { buildDeck, exportDeck, initProject, inspectTemplate };
@@ -1,18 +1,88 @@
1
1
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import { Worker } from "node:worker_threads";
3
- import { join, resolve } from "node:path";
3
+ import { join, resolve, sep } from "node:path";
4
4
  import { mkdtempSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
+ import { context, transform } from "esbuild";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
6
8
  import { getShapeId, getShapeKind, getShapeName, getShapePlaceholderIdx, getShapePlaceholderType, getSlideLayoutName, getSlideLayoutPartName, getSlideLayoutType, getSlideLayouts, getSlidePartName, getSlideShapes, getSlideTitle, getSlides, loadPresentation } from "@office-kit/pptx";
9
+ //#region src/bundle.ts
10
+ const resolvePackage = (name) => fileURLToPath(import.meta.resolve(name));
11
+ async function createDeckCompiler(entry) {
12
+ const transformedSources = /* @__PURE__ */ new Map();
13
+ const compiler = await context({
14
+ entryPoints: [resolve(entry)],
15
+ outfile: "deck.mjs",
16
+ write: false,
17
+ bundle: true,
18
+ platform: "node",
19
+ format: "esm",
20
+ target: "node22",
21
+ jsx: "automatic",
22
+ jsxImportSource: "@office-kit/pptx-dsl",
23
+ sourcemap: "inline",
24
+ sourceRoot: pathToFileURL(process.cwd() + sep).href,
25
+ metafile: true,
26
+ logLevel: "silent",
27
+ plugins: [{
28
+ name: "source-location",
29
+ setup(builder) {
30
+ builder.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async ({ path }) => {
31
+ const source = await readFile(path, "utf8");
32
+ const cached = transformedSources.get(path);
33
+ if (cached?.source === source) return {
34
+ contents: cached.code,
35
+ loader: "js"
36
+ };
37
+ const transformed = await transform(source, {
38
+ loader: path.endsWith(".tsx") ? "tsx" : path.endsWith(".ts") || path.endsWith(".mts") || path.endsWith(".cts") ? "ts" : path.endsWith(".jsx") ? "jsx" : "js",
39
+ sourcefile: path,
40
+ sourcemap: "inline",
41
+ jsx: "automatic",
42
+ jsxDev: true,
43
+ jsxImportSource: "@office-kit/pptx-dsl",
44
+ define: { "import.meta.url": JSON.stringify(pathToFileURL(path).href) }
45
+ });
46
+ transformedSources.set(path, {
47
+ source,
48
+ code: transformed.code
49
+ });
50
+ return {
51
+ contents: transformed.code,
52
+ loader: "js"
53
+ };
54
+ });
55
+ }
56
+ }, {
57
+ name: "shared-office-kit",
58
+ setup(builder) {
59
+ builder.onResolve({ filter: /^@office-kit\/pptx(?:-dsl)?(?:\/.*)?$/ }, ({ path }) => ({
60
+ path: resolvePackage(path),
61
+ external: true
62
+ }));
63
+ }
64
+ }]
65
+ });
66
+ return {
67
+ async build(output) {
68
+ const result = await compiler.rebuild();
69
+ await writeFile(output, result.outputFiles[0].contents);
70
+ const dependencies = Object.keys(result.metafile.inputs).map((path) => resolve(path));
71
+ const used = new Set(dependencies);
72
+ for (const path of transformedSources.keys()) if (!used.has(path)) transformedSources.delete(path);
73
+ return dependencies;
74
+ },
75
+ cancel: () => compiler.cancel(),
76
+ close: () => compiler.dispose()
77
+ };
78
+ }
79
+ //#endregion
7
80
  //#region src/build-runner.ts
8
81
  const BUILD_TIMEOUT_MS = 6e4;
9
- function prepareWorker(entry) {
82
+ function prepareWorker() {
10
83
  const directory = mkdtempSync(join(tmpdir(), "office-pptx-"));
11
84
  const worker = new Worker(new URL("./worker.mjs", import.meta.url), {
12
- workerData: {
13
- entry: resolve(entry),
14
- directory
15
- },
85
+ workerData: { directory },
16
86
  execArgv: ["--enable-source-maps"]
17
87
  });
18
88
  const result = new Promise((resolveBuild, reject) => {
@@ -29,39 +99,56 @@ function prepareWorker(entry) {
29
99
  }
30
100
  /** Internal watch session. Preload libraries, but never reuse a user module cache. */
31
101
  function createDeckBuilder(entry, keepWarm = true) {
32
- let prepared = prepareWorker(entry);
102
+ const compiler = createDeckCompiler(entry);
103
+ compiler.catch(() => {});
104
+ let prepared = prepareWorker();
105
+ let cache;
33
106
  let closed = false;
34
107
  let active = false;
35
108
  return {
36
109
  async build() {
37
110
  if (closed || active) throw new Error("Deck builder is closed or already building.");
38
111
  active = true;
39
- const current = prepared ?? prepareWorker(entry);
112
+ const current = prepared ?? prepareWorker();
40
113
  prepared = current;
41
114
  let timer;
42
115
  try {
43
- current.worker.postMessage("build");
44
- return await Promise.race([current.result, new Promise((_, reject) => {
116
+ const built = await Promise.race([(async () => {
117
+ const dependencies = await (await compiler).build(join(current.directory, "deck.mjs"));
118
+ current.worker.postMessage({
119
+ dependencies,
120
+ cache
121
+ });
122
+ return current.result;
123
+ })(), new Promise((_, reject) => {
45
124
  timer = setTimeout(() => reject(/* @__PURE__ */ new Error("Build exceeded 60 seconds. Check the deck for loops or unresolved promises.")), BUILD_TIMEOUT_MS);
46
125
  })]);
126
+ cache = built.cache;
127
+ return built.result;
47
128
  } finally {
48
129
  clearTimeout(timer);
130
+ await compiler.then((compiler) => compiler.cancel(), () => {});
49
131
  await current.worker.terminate();
50
132
  await rm(current.directory, {
51
133
  recursive: true,
52
134
  force: true
53
135
  });
54
136
  active = false;
55
- prepared = !closed && keepWarm ? prepareWorker(entry) : void 0;
137
+ prepared = !closed && keepWarm ? prepareWorker() : void 0;
56
138
  }
57
139
  },
58
140
  async cancel() {
59
- if (active) await prepared?.worker.terminate();
141
+ if (!active) return;
142
+ const current = prepared;
143
+ await compiler.then((compiler) => compiler.cancel(), () => {});
144
+ await current?.worker.terminate();
60
145
  },
61
146
  async close() {
62
147
  closed = true;
63
148
  const current = prepared;
149
+ await compiler.then((compiler) => compiler.cancel(), () => {});
64
150
  await current?.worker.terminate();
151
+ await compiler.then((compiler) => compiler.close(), () => {});
65
152
  if (current) await rm(current.directory, {
66
153
  recursive: true,
67
154
  force: true
@@ -287,4 +374,4 @@ async function exportDeck(entry, output) {
287
374
  //#endregion
288
375
  export { createDeckBuilder as a, initProject as i, exportDeck as n, inspectTemplate as r, buildDeck as t };
289
376
 
290
- //# sourceMappingURL=src-31HzWTvZ.mjs.map
377
+ //# sourceMappingURL=src-CShdm4G1.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-CShdm4G1.mjs","names":[],"sources":["../src/bundle.ts","../src/build-runner.ts","../src/init.ts","../src/inspect.ts","../src/index.ts"],"sourcesContent":["import { context, transform } from 'esbuild';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { resolve, sep } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\n\nconst resolvePackage = (name: string) => fileURLToPath(import.meta.resolve(name));\n\nexport async function createDeckCompiler(entry: string) {\n const transformedSources = new Map<string, { source: string; code: string }>();\n const compiler = await context({\n entryPoints: [resolve(entry)],\n outfile: 'deck.mjs',\n write: false,\n bundle: true,\n platform: 'node',\n format: 'esm',\n target: 'node22',\n jsx: 'automatic',\n jsxImportSource: '@office-kit/pptx-dsl',\n sourcemap: 'inline',\n sourceRoot: pathToFileURL(process.cwd() + sep).href,\n metafile: true,\n logLevel: 'silent',\n plugins: [\n {\n name: 'source-location',\n setup(builder) {\n // A bundled module lives in a temporary directory. Keep source-relative\n // asset URLs pointing at each original module, including imported files.\n builder.onLoad({ filter: /\\.[cm]?[jt]sx?$/ }, async ({ path }) => {\n const source = await readFile(path, 'utf8');\n const cached = transformedSources.get(path);\n if (cached?.source === source) return { contents: cached.code, loader: 'js' };\n const loader = path.endsWith('.tsx')\n ? 'tsx'\n : path.endsWith('.ts') || path.endsWith('.mts') || path.endsWith('.cts')\n ? 'ts'\n : path.endsWith('.jsx')\n ? 'jsx'\n : 'js';\n const transformed = await transform(source, {\n loader,\n sourcefile: path,\n sourcemap: 'inline',\n jsx: 'automatic',\n jsxDev: true,\n jsxImportSource: '@office-kit/pptx-dsl',\n define: { 'import.meta.url': JSON.stringify(pathToFileURL(path).href) },\n });\n transformedSources.set(path, { source, code: transformed.code });\n return { contents: transformed.code, loader: 'js' };\n });\n },\n },\n {\n name: 'shared-office-kit',\n setup(builder) {\n // Core uses symbol-backed handles; all consumers must share its instance.\n builder.onResolve({ filter: /^@office-kit\\/pptx(?:-dsl)?(?:\\/.*)?$/ }, ({ path }) => ({\n path: resolvePackage(path),\n external: true,\n }));\n },\n },\n ],\n });\n return {\n async build(output: string) {\n const result = await compiler.rebuild();\n await writeFile(output, result.outputFiles[0]!.contents);\n const dependencies = Object.keys(result.metafile!.inputs).map((path) => resolve(path));\n const used = new Set(dependencies);\n for (const path of transformedSources.keys())\n if (!used.has(path)) transformedSources.delete(path);\n return dependencies;\n },\n cancel: () => compiler.cancel(),\n close: () => compiler.dispose(),\n };\n}\n","import { Worker } from 'node:worker_threads';\nimport { join } from 'node:path';\nimport { mkdtempSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport type { BuildResult } from './build.ts';\nimport type { PreviewCache } from './preview-cache.ts';\nimport { createDeckCompiler } from './bundle.ts';\n\nconst BUILD_TIMEOUT_MS = 60_000;\n\nfunction prepareWorker() {\n const directory = mkdtempSync(join(tmpdir(), 'office-pptx-'));\n const worker = new Worker(new URL('./worker.mjs', import.meta.url), {\n workerData: { directory },\n execArgv: ['--enable-source-maps'],\n });\n const result = new Promise<{ result: BuildResult; cache: PreviewCache }>(\n (resolveBuild, reject) => {\n worker.once('message', resolveBuild);\n worker.once('error', reject);\n worker.once('exit', (code) =>\n reject(new Error(`Build worker exited without a result (code ${code}).`)),\n );\n },\n );\n // Startup may fail before the next edit asks to use this worker.\n void result.catch(() => {});\n return { worker, result, directory };\n}\n\n/** Internal watch session. Preload libraries, but never reuse a user module cache. */\nexport function createDeckBuilder(entry: string, keepWarm = true) {\n const compiler = createDeckCompiler(entry);\n void compiler.catch(() => {});\n let prepared: ReturnType<typeof prepareWorker> | undefined = prepareWorker();\n let cache: PreviewCache | undefined;\n let closed = false;\n let active = false;\n return {\n async build(): Promise<BuildResult> {\n if (closed || active) throw new Error('Deck builder is closed or already building.');\n active = true;\n const current = prepared ?? prepareWorker();\n prepared = current;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const built = await Promise.race([\n (async () => {\n const dependencies = await (await compiler).build(join(current.directory, 'deck.mjs'));\n current.worker.postMessage({ dependencies, cache });\n return current.result;\n })(),\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new Error(\n 'Build exceeded 60 seconds. Check the deck for loops or unresolved promises.',\n ),\n ),\n BUILD_TIMEOUT_MS,\n );\n }),\n ]);\n cache = built.cache;\n return built.result;\n } finally {\n clearTimeout(timer);\n await compiler.then(\n (compiler) => compiler.cancel(),\n () => {},\n );\n await current.worker.terminate();\n await rm(current.directory, { recursive: true, force: true });\n active = false;\n prepared = !closed && keepWarm ? prepareWorker() : undefined;\n }\n },\n async cancel() {\n if (!active) return;\n const current = prepared;\n await compiler.then(\n (compiler) => compiler.cancel(),\n () => {},\n );\n await current?.worker.terminate();\n },\n async close() {\n closed = true;\n const current = prepared;\n await compiler.then(\n (compiler) => compiler.cancel(),\n () => {},\n );\n await current?.worker.terminate();\n await compiler.then(\n (compiler) => compiler.close(),\n () => {},\n );\n if (current) await rm(current.directory, { recursive: true, force: true });\n },\n };\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\n\nconst starter = `import { Presentation } from '@office-kit/pptx-dsl';\nimport { Cover } from './slides/cover.tsx';\n\nexport default (\n <Presentation>\n <Cover />\n </Presentation>\n);\n`;\n\nconst theme = `export const theme = {\n background: '#15171C',\n accent: '#E5481F',\n text: '#FFFFFF',\n muted: '#B4B9C4',\n} as const;\n`;\n\nconst cover = `import { Slide, Text, Shape } from '@office-kit/pptx-dsl';\nimport { theme } from '../theme.ts';\n\nexport function Cover() {\n return (\n <Slide background={theme.background}>\n <Shape preset=\"rect\" x={0.9} y={2.55} width={0.14} height={1.75} fill={theme.accent} stroke={false} />\n <Text x={1.25} y={2.4} width={10} height={1.1} size={48} bold color={theme.text}>\n Your next presentation\n </Text>\n <Text x={1.25} y={3.55} width={10} height={0.6} size={22} color={theme.muted}>\n Edit this TSX and save to update the preview.\n </Text>\n </Slide>\n );\n}\n`;\n\nconst guide = `# Writing this presentation\n\nUse deck.tsx for slide order, slides/*.tsx for individual slides, and theme.ts\nfor shared design values. The JSX runtime is\n@office-kit/pptx-dsl, with no React or Vue dependency.\n\n- Run npm run dev, then open its local URL to preview. Saving source files\n updates the preview. A failed build retains the last successful output.\n- The preview is view-only: use the vertical thumbnails to select slides, zoom\n to inspect details, and Present to view full-screen (Escape exits). Make all\n content and layout changes in TSX; there are no canvas editing controls.\n- For a local revision, locate the slide through deck.tsx or a text search and\n read only the relevant source and dependencies. Patch the requested text, data\n or props; do not regenerate the deck or reformat unrelated code. Keep descriptive\n filenames stable when reordering slides. Preserve existing project structures.\n- Change shared theme values/components only for changes intended for all their\n consumers; use a local prop override for a one-slide exception.\n- Keep the preview running during revisions and inspect affected slides after\n saving. Review all affected consumers for shared changes. Do not run a separate\n export for every intermediate edit. Before delivery, run npm run check and\n npm run build to export deck.pptx, then review the whole deck.\n- Coordinates and dimensions are inches; font sizes and stroke widths are points.\n The default 16:9 canvas is 13.333 × 7.5 inches.\n- Use native Text, Shape, Image, Table and Chart elements. Use JS functions,\n arrays and map to compose elements; do not import React.\n- Chart takes the core ChartSpec in its spec prop. Text accepts rich ParagraphSpec\n arrays through paragraphs. Raw is an escape hatch for public core APIs.\n- For templates: readFile(new URL('./template.pptx', import.meta.url)), then use\n Presentation source={bytes} mode=\"edit\" and Slide target={{index: 0}}.\n Fill target={{name: 'Title 1'}} changes an existing shape. Indices are zero-based.\n Use office-pptx inspect template.pptx to discover names, layouts and placeholders.\n- Use mode=\"compose\" and Slide from={{index: 0}} to build a new sequence from a\n source deck. This intentionally removes the original slide sequence.\n- Keep assets inside this project so the dev server sees changes. Use source-relative\n new URL('./image.png', import.meta.url) when loading local assets.\n- Preserve existing slides and formatting unless the request calls for changes.\n Never rasterize a chart/table as a shortcut. Raw-only features are not DSL coverage.\n- Preview is a rendering aid, not a guarantee of identical PowerPoint rendering.\n Check the exported file in the target application for final delivery.\n\nVSCode uses tsconfig.json for completion and diagnostics. Start the\n\"Preview presentation\" task, then run \"Simple Browser: Show\" with the printed URL\nand move that editor to a side group. Type errors appear in the Problems panel.\n`;\n\n/** Creates a new directory; existing projects are never overwritten. */\nexport async function initProject(directory: string): Promise<string> {\n const root = resolve(directory);\n const packageLocations = [\n import.meta.resolve('@office-kit/pptx/package.json'),\n import.meta.resolve('@office-kit/pptx-dsl/package.json'),\n new URL('../package.json', import.meta.url),\n ];\n const versions = await Promise.all(\n packageLocations.map(async (location) => {\n const manifest: { version: string } = JSON.parse(await readFile(new URL(location), 'utf8'));\n return `^${manifest.version}`;\n }),\n );\n await mkdir(root);\n const files: Record<string, string> = {\n 'package.json':\n JSON.stringify(\n {\n name: 'my-presentation',\n private: true,\n type: 'module',\n scripts: {\n dev: 'office-pptx dev deck.tsx',\n build: 'office-pptx build deck.tsx --out deck.pptx',\n check: 'tsc --noEmit',\n },\n dependencies: { '@office-kit/pptx': versions[0]!, '@office-kit/pptx-dsl': versions[1]! },\n devDependencies: {\n '@office-kit/pptx-dev': versions[2]!,\n '@types/node': '^24.13.2',\n typescript: '^6.0.3',\n },\n },\n null,\n 2,\n ) + '\\n',\n 'tsconfig.json':\n JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n module: 'ESNext',\n moduleResolution: 'Bundler',\n jsx: 'react-jsx',\n jsxImportSource: '@office-kit/pptx-dsl',\n strict: true,\n exactOptionalPropertyTypes: true,\n noEmit: true,\n allowImportingTsExtensions: true,\n types: ['node'],\n },\n include: ['**/*.ts', '**/*.tsx'],\n exclude: ['node_modules'],\n },\n null,\n 2,\n ) + '\\n',\n 'deck.tsx': starter,\n 'theme.ts': theme,\n 'slides/cover.tsx': cover,\n 'CLAUDE.md': guide,\n '.gitignore': 'node_modules/\\ndeck.pptx\\n',\n '.vscode/tasks.json':\n JSON.stringify(\n {\n version: '2.0.0',\n tasks: [\n {\n label: 'Preview presentation',\n type: 'shell',\n command: 'npm run dev',\n isBackground: true,\n problemMatcher: {\n pattern: { regexp: '^NEVER_MATCH$', file: 1, message: 2 },\n background: {\n activeOnStart: true,\n beginsPattern: '^.*office-pptx dev',\n endsPattern: '^Preview: ',\n },\n },\n },\n {\n label: 'Check presentation',\n type: 'shell',\n command: 'npm run check',\n problemMatcher: '$tsc',\n },\n {\n label: 'Export presentation',\n type: 'shell',\n command: 'npm run build',\n problemMatcher: [],\n },\n ],\n },\n null,\n 2,\n ) + '\\n',\n };\n await Promise.all(['.vscode', 'slides'].map((directory) => mkdir(join(root, directory))));\n await Promise.all(\n Object.entries(files).map(([name, content]) =>\n writeFile(join(root, name), content, { flag: 'wx' }),\n ),\n );\n return root;\n}\n","import { readFile } from 'node:fs/promises';\nimport {\n loadPresentation,\n getSlides,\n getSlidePartName,\n getSlideTitle,\n getSlideShapes,\n getShapeName,\n getShapeId,\n getShapeKind,\n getShapePlaceholderType,\n getShapePlaceholderIdx,\n getSlideLayouts,\n getSlideLayoutName,\n getSlideLayoutPartName,\n getSlideLayoutType,\n} from '@office-kit/pptx';\n\n/** Source references usable directly in Slide, Fill and Remove props. */\nexport async function inspectTemplate(file: string) {\n const presentation = await loadPresentation(await readFile(file));\n return {\n slides: getSlides(presentation).map((slide, index) => ({\n index,\n part: getSlidePartName(slide),\n title: getSlideTitle(slide),\n shapes: getSlideShapes(slide).map((shape) => ({\n id: getShapeId(shape),\n name: getShapeName(shape),\n kind: getShapeKind(shape),\n placeholder: { type: getShapePlaceholderType(shape), idx: getShapePlaceholderIdx(shape) },\n })),\n })),\n layouts: getSlideLayouts(presentation).map((layout) => ({\n part: getSlideLayoutPartName(layout),\n name: getSlideLayoutName(layout),\n type: getSlideLayoutType(layout),\n })),\n };\n}\n","import { writeFile } from 'node:fs/promises';\nimport { createDeckBuilder } from './build-runner.ts';\nimport type { BuildResult } from './build.ts';\nexport type { BuildResult } from './build.ts';\nexport { initProject } from './init.ts';\nexport { inspectTemplate } from './inspect.ts';\n\n/** Each evaluation gets a fresh module cache and releases it when finished. */\nexport async function buildDeck(entry: string): Promise<BuildResult> {\n const builder = createDeckBuilder(entry, false);\n try {\n return await builder.build();\n } finally {\n await builder.close();\n }\n}\nexport async function exportDeck(entry: string, output: string): Promise<BuildResult> {\n const result = await buildDeck(entry);\n await writeFile(output, result.bytes);\n return result;\n}\n"],"mappings":"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"}
package/dist/worker.mjs CHANGED
@@ -1,58 +1,48 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import { parentPort, workerData } from "node:worker_threads";
3
- import { join, resolve } from "node:path";
4
- import { getSlideSize, getSlideText, getSlides, loadPresentation, savePresentation, validatePresentation } from "@office-kit/pptx";
2
+ import { join } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { getSlidePartName, getSlideSize, getSlideText, getSlides, loadPresentation, savePresentation, validatePresentation } from "@office-kit/pptx";
5
+ import { createHash } from "node:crypto";
5
6
  import { once } from "node:events";
6
- import { build, transform } from "esbuild";
7
- import { fileURLToPath, pathToFileURL } from "node:url";
8
7
  import { compile } from "@office-kit/pptx-dsl";
8
+ import { unzipSync } from "fflate";
9
9
  import { renderSlideToSvg } from "@office-kit/pptx-preview";
10
+ //#region src/preview-cache.ts
11
+ /** Reuse SVG only when both slide XML and every shared rendering resource match. */
12
+ function renderPreview(presentation, bytes, previous) {
13
+ const slides = getSlides(presentation);
14
+ const names = slides.map((slide) => getSlidePartName(slide).replace(/^\//, ""));
15
+ const slideParts = new Set(names);
16
+ const parts = unzipSync(bytes);
17
+ const hashes = new Map(Object.keys(parts).sort().map((name) => [name, createHash("sha256").update(parts[name]).digest("hex")]));
18
+ const shared = createHash("sha256").update(JSON.stringify([...hashes].filter(([name]) => !slideParts.has(name) && !name.startsWith("docProps/")))).digest("hex");
19
+ const cache = {
20
+ shared,
21
+ slides: {}
22
+ };
23
+ const rendered = slides.map((slide, index) => {
24
+ const name = names[index];
25
+ const hash = hashes.get(name);
26
+ const cached = previous?.shared === shared ? previous.slides[name] : void 0;
27
+ const result = cached?.hash === hash ? cached : {
28
+ hash,
29
+ svg: renderSlideToSvg(presentation, slide),
30
+ text: getSlideText(slide)
31
+ };
32
+ cache.slides[name] = result;
33
+ return result;
34
+ });
35
+ return {
36
+ slides: rendered.map((slide) => slide.svg),
37
+ slideTexts: rendered.map((slide) => slide.text),
38
+ cache
39
+ };
40
+ }
41
+ //#endregion
10
42
  //#region src/build.ts
11
- const resolvePackage = (name) => fileURLToPath(import.meta.resolve(name));
12
43
  /** Evaluates trusted local TSX. This is code execution, not a sandbox. */
13
- async function buildDeck(entry, directory) {
14
- const output = join(directory, "deck.mjs");
15
- const result = await build({
16
- entryPoints: [resolve(entry)],
17
- outfile: output,
18
- bundle: true,
19
- platform: "node",
20
- format: "esm",
21
- target: "node22",
22
- jsx: "automatic",
23
- jsxImportSource: "@office-kit/pptx-dsl",
24
- sourcemap: "inline",
25
- metafile: true,
26
- logLevel: "silent",
27
- plugins: [{
28
- name: "source-location",
29
- setup(builder) {
30
- builder.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async ({ path }) => {
31
- return {
32
- contents: (await transform(await readFile(path, "utf8"), {
33
- loader: path.endsWith(".tsx") ? "tsx" : path.endsWith(".ts") || path.endsWith(".mts") || path.endsWith(".cts") ? "ts" : path.endsWith(".jsx") ? "jsx" : "js",
34
- sourcefile: path,
35
- sourcemap: "inline",
36
- jsx: "automatic",
37
- jsxDev: true,
38
- jsxImportSource: "@office-kit/pptx-dsl",
39
- define: { "import.meta.url": JSON.stringify(pathToFileURL(path).href) }
40
- })).code,
41
- loader: "js"
42
- };
43
- });
44
- }
45
- }, {
46
- name: "shared-office-kit",
47
- setup(builder) {
48
- builder.onResolve({ filter: /^@office-kit\/pptx(?:-dsl)?(?:\/.*)?$/ }, ({ path }) => ({
49
- path: resolvePackage(path),
50
- external: true
51
- }));
52
- }
53
- }]
54
- });
55
- const module = await import(pathToFileURL(output).href);
44
+ async function buildDeck(directory, dependencies, previous) {
45
+ const module = await import(pathToFileURL(join(directory, "deck.mjs")).href);
56
46
  if (!module.default) throw new Error("The TSX file must default-export a Presentation.");
57
47
  const presentation = await compile(module.default);
58
48
  const diagnostics = validatePresentation(presentation);
@@ -61,21 +51,25 @@ async function buildDeck(entry, directory) {
61
51
  const bytes = await savePresentation(presentation);
62
52
  const saved = await loadPresentation(bytes);
63
53
  const size = getSlideSize(saved);
54
+ const { slides, slideTexts, cache } = renderPreview(saved, bytes, previous);
64
55
  return {
65
- bytes,
66
- aspectRatio: size ? size.width / size.height : 16 / 9,
67
- slides: getSlides(saved).map((slide) => renderSlideToSvg(saved, slide)),
68
- dependencies: Object.keys(result.metafile.inputs).map((path) => resolve(path)),
69
- slideTexts: getSlides(saved).map((slide) => getSlideText(slide)),
70
- diagnostics
56
+ cache,
57
+ result: {
58
+ bytes,
59
+ aspectRatio: size ? size.width / size.height : 16 / 9,
60
+ slides,
61
+ dependencies,
62
+ slideTexts,
63
+ diagnostics
64
+ }
71
65
  };
72
66
  }
73
67
  //#endregion
74
68
  //#region src/worker.ts
75
69
  if (!parentPort) throw new Error("The build worker must run in a worker thread.");
76
- const { entry, directory } = workerData;
77
- await once(parentPort, "message");
78
- parentPort.postMessage(await buildDeck(entry, directory));
70
+ const { directory } = workerData;
71
+ const [request] = await once(parentPort, "message");
72
+ parentPort.postMessage(await buildDeck(directory, request.dependencies, request.cache));
79
73
  //#endregion
80
74
  export {};
81
75
 
@@ -1 +1 @@
1
- {"version":3,"file":"worker.mjs","names":[],"sources":["../src/build.ts","../src/worker.ts"],"sourcesContent":["import { build, transform } from 'esbuild';\nimport { readFile } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { compile, type Node } from '@office-kit/pptx-dsl';\nimport {\n getSlideSize,\n getSlides,\n getSlideText,\n loadPresentation,\n savePresentation,\n validatePresentation,\n} from '@office-kit/pptx';\nimport { renderSlideToSvg } from '@office-kit/pptx-preview';\n\nconst resolvePackage = (name: string) => fileURLToPath(import.meta.resolve(name));\nexport interface BuildResult {\n bytes: Uint8Array;\n slides: string[];\n slideTexts: string[];\n aspectRatio: number;\n dependencies: string[];\n diagnostics: ReturnType<typeof validatePresentation>;\n}\n/** Evaluates trusted local TSX. This is code execution, not a sandbox. */\nexport async function buildDeck(entry: string, directory: string): Promise<BuildResult> {\n const output = join(directory, 'deck.mjs');\n const result = await build({\n entryPoints: [resolve(entry)],\n outfile: output,\n bundle: true,\n platform: 'node',\n format: 'esm',\n target: 'node22',\n jsx: 'automatic',\n jsxImportSource: '@office-kit/pptx-dsl',\n sourcemap: 'inline',\n metafile: true,\n logLevel: 'silent',\n plugins: [\n {\n name: 'source-location',\n setup(builder) {\n // A bundled module lives in a temporary directory. Keep source-relative\n // asset URLs pointing at each original module, including imported files.\n builder.onLoad({ filter: /\\.[cm]?[jt]sx?$/ }, async ({ path }) => {\n const source = await readFile(path, 'utf8');\n const loader = path.endsWith('.tsx')\n ? 'tsx'\n : path.endsWith('.ts') || path.endsWith('.mts') || path.endsWith('.cts')\n ? 'ts'\n : path.endsWith('.jsx')\n ? 'jsx'\n : 'js';\n const transformed = await transform(source, {\n loader,\n sourcefile: path,\n sourcemap: 'inline',\n jsx: 'automatic',\n jsxDev: true,\n jsxImportSource: '@office-kit/pptx-dsl',\n define: { 'import.meta.url': JSON.stringify(pathToFileURL(path).href) },\n });\n return { contents: transformed.code, loader: 'js' };\n });\n },\n },\n {\n name: 'shared-office-kit',\n setup(builder) {\n // Core uses symbol-backed handles; all consumers must share its instance.\n builder.onResolve({ filter: /^@office-kit\\/pptx(?:-dsl)?(?:\\/.*)?$/ }, ({ path }) => ({\n path: resolvePackage(path),\n external: true,\n }));\n },\n },\n ],\n });\n const module: { default?: Node } = await import(pathToFileURL(output).href);\n if (!module.default) throw new Error('The TSX file must default-export a Presentation.');\n const presentation = await compile(module.default);\n const diagnostics = validatePresentation(presentation);\n const errors = diagnostics.filter((issue) => issue.severity === 'error');\n if (errors.length) throw new Error(`Invalid presentation: ${JSON.stringify(errors)}`);\n const bytes = await savePresentation(presentation);\n // Preview serialized output too, so persistence defects are visible during authoring.\n const saved = await loadPresentation(bytes);\n const size = getSlideSize(saved);\n return {\n bytes,\n aspectRatio: size ? size.width / size.height : 16 / 9,\n slides: getSlides(saved).map((slide) => renderSlideToSvg(saved, slide)),\n dependencies: Object.keys(result.metafile.inputs).map((path) => resolve(path)),\n slideTexts: getSlides(saved).map((slide) => getSlideText(slide)),\n diagnostics,\n };\n}\n","import { once } from 'node:events';\nimport { parentPort, workerData } from 'node:worker_threads';\nimport { buildDeck } from './build.ts';\n\nif (!parentPort) throw new Error('The build worker must run in a worker thread.');\nconst { entry, directory } = workerData as { entry: string; directory: string };\nawait once(parentPort, 'message');\nparentPort.postMessage(await buildDeck(entry, directory));\n"],"mappings":";;;;;;;;;;AAeA,MAAM,kBAAkB,SAAiB,cAAc,OAAO,KAAK,QAAQ,IAAI,CAAC;;AAUhF,eAAsB,UAAU,OAAe,WAAyC;CACtF,MAAM,SAAS,KAAK,WAAW,UAAU;CACzC,MAAM,SAAS,MAAM,MAAM;EACzB,aAAa,CAAC,QAAQ,KAAK,CAAC;EAC5B,SAAS;EACT,QAAQ;EACR,UAAU;EACV,QAAQ;EACR,QAAQ;EACR,KAAK;EACL,iBAAiB;EACjB,WAAW;EACX,UAAU;EACV,UAAU;EACV,SAAS,CACP;GACE,MAAM;GACN,MAAM,SAAS;IAGb,QAAQ,OAAO,EAAE,QAAQ,kBAAkB,GAAG,OAAO,EAAE,WAAW;KAkBhE,OAAO;MAAE,WAAU,MATO,UAAU,MARf,SAAS,MAAM,MAAM,GAQE;OAC1C,QARa,KAAK,SAAS,MAAM,IAC/B,QACA,KAAK,SAAS,KAAK,KAAK,KAAK,SAAS,MAAM,KAAK,KAAK,SAAS,MAAM,IACnE,OACA,KAAK,SAAS,MAAM,IAClB,QACA;OAGN,YAAY;OACZ,WAAW;OACX,KAAK;OACL,QAAQ;OACR,iBAAiB;OACjB,QAAQ,EAAE,mBAAmB,KAAK,UAAU,cAAc,IAAI,CAAC,CAAC,IAAI,EAAE;MACxE,CAAC,EAAA,CAC8B;MAAM,QAAQ;KAAK;IACpD,CAAC;GACH;EACF,GACA;GACE,MAAM;GACN,MAAM,SAAS;IAEb,QAAQ,UAAU,EAAE,QAAQ,wCAAwC,IAAI,EAAE,YAAY;KACpF,MAAM,eAAe,IAAI;KACzB,UAAU;IACZ,EAAE;GACJ;EACF,CACF;CACF,CAAC;CACD,MAAM,SAA6B,MAAM,OAAO,cAAc,MAAM,CAAC,CAAC;CACtE,IAAI,CAAC,OAAO,SAAS,MAAM,IAAI,MAAM,kDAAkD;CACvF,MAAM,eAAe,MAAM,QAAQ,OAAO,OAAO;CACjD,MAAM,cAAc,qBAAqB,YAAY;CACrD,MAAM,SAAS,YAAY,QAAQ,UAAU,MAAM,aAAa,OAAO;CACvE,IAAI,OAAO,QAAQ,MAAM,IAAI,MAAM,yBAAyB,KAAK,UAAU,MAAM,GAAG;CACpF,MAAM,QAAQ,MAAM,iBAAiB,YAAY;CAEjD,MAAM,QAAQ,MAAM,iBAAiB,KAAK;CAC1C,MAAM,OAAO,aAAa,KAAK;CAC/B,OAAO;EACL;EACA,aAAa,OAAO,KAAK,QAAQ,KAAK,SAAS,KAAK;EACpD,QAAQ,UAAU,KAAK,CAAC,CAAC,KAAK,UAAU,iBAAiB,OAAO,KAAK,CAAC;EACtE,cAAc,OAAO,KAAK,OAAO,SAAS,MAAM,CAAC,CAAC,KAAK,SAAS,QAAQ,IAAI,CAAC;EAC7E,YAAY,UAAU,KAAK,CAAC,CAAC,KAAK,UAAU,aAAa,KAAK,CAAC;EAC/D;CACF;AACF;;;AC7FA,IAAI,CAAC,YAAY,MAAM,IAAI,MAAM,+CAA+C;AAChF,MAAM,EAAE,OAAO,cAAc;AAC7B,MAAM,KAAK,YAAY,SAAS;AAChC,WAAW,YAAY,MAAM,UAAU,OAAO,SAAS,CAAC"}
1
+ {"version":3,"file":"worker.mjs","names":[],"sources":["../src/preview-cache.ts","../src/build.ts","../src/worker.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { unzipSync } from 'fflate';\nimport { getSlidePartName, getSlides, getSlideText, type PresentationData } from '@office-kit/pptx';\nimport { renderSlideToSvg } from '@office-kit/pptx-preview';\n\nexport interface PreviewCache {\n shared: string;\n slides: Record<string, { hash: string; svg: string; text: string }>;\n}\n\n/** Reuse SVG only when both slide XML and every shared rendering resource match. */\nexport function renderPreview(\n presentation: PresentationData,\n bytes: Uint8Array,\n previous?: PreviewCache,\n) {\n const slides = getSlides(presentation);\n const names = slides.map((slide) => getSlidePartName(slide).replace(/^\\//, ''));\n const slideParts = new Set(names);\n const parts = unzipSync(bytes);\n const hashes = new Map(\n Object.keys(parts)\n .sort()\n .map((name) => [name, createHash('sha256').update(parts[name]!).digest('hex')]),\n );\n // Relationships, themes, layouts, charts and media all invalidate the cache.\n // Document metadata changes on each save but cannot affect slide rendering.\n const shared = createHash('sha256')\n .update(\n JSON.stringify(\n [...hashes].filter(([name]) => !slideParts.has(name) && !name.startsWith('docProps/')),\n ),\n )\n .digest('hex');\n const cache: PreviewCache = { shared, slides: {} };\n const rendered = slides.map((slide, index) => {\n const name = names[index]!;\n const hash = hashes.get(name)!;\n const cached = previous?.shared === shared ? previous.slides[name] : undefined;\n const result =\n cached?.hash === hash\n ? cached\n : { hash, svg: renderSlideToSvg(presentation, slide), text: getSlideText(slide) };\n cache.slides[name] = result;\n return result;\n });\n return {\n slides: rendered.map((slide) => slide.svg),\n slideTexts: rendered.map((slide) => slide.text),\n cache,\n };\n}\n","import { join } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { compile, type Node } from '@office-kit/pptx-dsl';\nimport {\n getSlideSize,\n loadPresentation,\n savePresentation,\n validatePresentation,\n} from '@office-kit/pptx';\nimport { renderPreview, type PreviewCache } from './preview-cache.ts';\n\nexport interface BuildResult {\n bytes: Uint8Array;\n slides: string[];\n slideTexts: string[];\n aspectRatio: number;\n dependencies: string[];\n diagnostics: ReturnType<typeof validatePresentation>;\n}\n/** Evaluates trusted local TSX. This is code execution, not a sandbox. */\nexport async function buildDeck(\n directory: string,\n dependencies: string[],\n previous?: PreviewCache,\n): Promise<{ result: BuildResult; cache: PreviewCache }> {\n const output = join(directory, 'deck.mjs');\n const module: { default?: Node } = await import(pathToFileURL(output).href);\n if (!module.default) throw new Error('The TSX file must default-export a Presentation.');\n const presentation = await compile(module.default);\n const diagnostics = validatePresentation(presentation);\n const errors = diagnostics.filter((issue) => issue.severity === 'error');\n if (errors.length) throw new Error(`Invalid presentation: ${JSON.stringify(errors)}`);\n const bytes = await savePresentation(presentation);\n // Preview serialized output too, so persistence defects are visible during authoring.\n const saved = await loadPresentation(bytes);\n const size = getSlideSize(saved);\n const { slides, slideTexts, cache } = renderPreview(saved, bytes, previous);\n return {\n cache,\n result: {\n bytes,\n aspectRatio: size ? size.width / size.height : 16 / 9,\n slides,\n dependencies,\n slideTexts,\n diagnostics,\n },\n };\n}\n","import { once } from 'node:events';\nimport { parentPort, workerData } from 'node:worker_threads';\nimport type { PreviewCache } from './preview-cache.ts';\nimport { buildDeck } from './build.ts';\n\nif (!parentPort) throw new Error('The build worker must run in a worker thread.');\nconst { directory } = workerData as { directory: string };\nconst [request] = (await once(parentPort, 'message')) as [\n { dependencies: string[]; cache?: PreviewCache },\n];\nparentPort.postMessage(await buildDeck(directory, request.dependencies, request.cache));\n"],"mappings":";;;;;;;;;;;AAWA,SAAgB,cACd,cACA,OACA,UACA;CACA,MAAM,SAAS,UAAU,YAAY;CACrC,MAAM,QAAQ,OAAO,KAAK,UAAU,iBAAiB,KAAK,CAAC,CAAC,QAAQ,OAAO,EAAE,CAAC;CAC9E,MAAM,aAAa,IAAI,IAAI,KAAK;CAChC,MAAM,QAAQ,UAAU,KAAK;CAC7B,MAAM,SAAS,IAAI,IACjB,OAAO,KAAK,KAAK,CAAC,CACf,KAAK,CAAC,CACN,KAAK,SAAS,CAAC,MAAM,WAAW,QAAQ,CAAC,CAAC,OAAO,MAAM,KAAM,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,CAClF;CAGA,MAAM,SAAS,WAAW,QAAQ,CAAC,CAChC,OACC,KAAK,UACH,CAAC,GAAG,MAAM,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,WAAW,IAAI,IAAI,KAAK,CAAC,KAAK,WAAW,WAAW,CAAC,CACvF,CACF,CAAC,CACA,OAAO,KAAK;CACf,MAAM,QAAsB;EAAE;EAAQ,QAAQ,CAAC;CAAE;CACjD,MAAM,WAAW,OAAO,KAAK,OAAO,UAAU;EAC5C,MAAM,OAAO,MAAM;EACnB,MAAM,OAAO,OAAO,IAAI,IAAI;EAC5B,MAAM,SAAS,UAAU,WAAW,SAAS,SAAS,OAAO,QAAQ,KAAA;EACrE,MAAM,SACJ,QAAQ,SAAS,OACb,SACA;GAAE;GAAM,KAAK,iBAAiB,cAAc,KAAK;GAAG,MAAM,aAAa,KAAK;EAAE;EACpF,MAAM,OAAO,QAAQ;EACrB,OAAO;CACT,CAAC;CACD,OAAO;EACL,QAAQ,SAAS,KAAK,UAAU,MAAM,GAAG;EACzC,YAAY,SAAS,KAAK,UAAU,MAAM,IAAI;EAC9C;CACF;AACF;;;;AC/BA,eAAsB,UACpB,WACA,cACA,UACuD;CAEvD,MAAM,SAA6B,MAAM,OAAO,cADjC,KAAK,WAAW,UACoC,CAAC,CAAC,CAAC;CACtE,IAAI,CAAC,OAAO,SAAS,MAAM,IAAI,MAAM,kDAAkD;CACvF,MAAM,eAAe,MAAM,QAAQ,OAAO,OAAO;CACjD,MAAM,cAAc,qBAAqB,YAAY;CACrD,MAAM,SAAS,YAAY,QAAQ,UAAU,MAAM,aAAa,OAAO;CACvE,IAAI,OAAO,QAAQ,MAAM,IAAI,MAAM,yBAAyB,KAAK,UAAU,MAAM,GAAG;CACpF,MAAM,QAAQ,MAAM,iBAAiB,YAAY;CAEjD,MAAM,QAAQ,MAAM,iBAAiB,KAAK;CAC1C,MAAM,OAAO,aAAa,KAAK;CAC/B,MAAM,EAAE,QAAQ,YAAY,UAAU,cAAc,OAAO,OAAO,QAAQ;CAC1E,OAAO;EACL;EACA,QAAQ;GACN;GACA,aAAa,OAAO,KAAK,QAAQ,KAAK,SAAS,KAAK;GACpD;GACA;GACA;GACA;EACF;CACF;AACF;;;AC3CA,IAAI,CAAC,YAAY,MAAM,IAAI,MAAM,+CAA+C;AAChF,MAAM,EAAE,cAAc;AACtB,MAAM,CAAC,WAAY,MAAM,KAAK,YAAY,SAAS;AAGnD,WAAW,YAAY,MAAM,UAAU,WAAW,QAAQ,cAAc,QAAQ,KAAK,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@office-kit/pptx-dev",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Build and preview PowerPoint TSX presentations locally",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -27,14 +27,15 @@
27
27
  "provenance": true
28
28
  },
29
29
  "dependencies": {
30
+ "@office-kit/pptx": "^0.18.2",
31
+ "@office-kit/pptx-dsl": "^0.5.0",
32
+ "@office-kit/pptx-preview": "^0.9.10",
30
33
  "@xterm/addon-fit": "^0.11.0",
31
34
  "@xterm/xterm": "^6.0.0",
32
35
  "esbuild": "^0.28.1",
36
+ "fflate": "^0.8.3",
33
37
  "markdown-it": "^15.0.2",
34
- "node-pty": "^1.1.0",
35
- "@office-kit/pptx": "^0.18.2",
36
- "@office-kit/pptx-preview": "^0.9.9",
37
- "@office-kit/pptx-dsl": "^0.5.0"
38
+ "node-pty": "^1.1.0"
38
39
  },
39
40
  "devDependencies": {
40
41
  "@types/markdown-it": "^14.2.0",
@@ -1 +0,0 @@
1
- {"version":3,"file":"src-31HzWTvZ.mjs","names":[],"sources":["../src/build-runner.ts","../src/init.ts","../src/inspect.ts","../src/index.ts"],"sourcesContent":["import { Worker } from 'node:worker_threads';\nimport { resolve, join } from 'node:path';\nimport { mkdtempSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport type { BuildResult } from './build.ts';\n\nconst BUILD_TIMEOUT_MS = 60_000;\n\nfunction prepareWorker(entry: string) {\n const directory = mkdtempSync(join(tmpdir(), 'office-pptx-'));\n const worker = new Worker(new URL('./worker.mjs', import.meta.url), {\n workerData: { entry: resolve(entry), directory },\n execArgv: ['--enable-source-maps'],\n });\n const result = new Promise<BuildResult>((resolveBuild, reject) => {\n worker.once('message', resolveBuild);\n worker.once('error', reject);\n worker.once('exit', (code) =>\n reject(new Error(`Build worker exited without a result (code ${code}).`)),\n );\n });\n // Startup may fail before the next edit asks to use this worker.\n void result.catch(() => {});\n return { worker, result, directory };\n}\n\n/** Internal watch session. Preload libraries, but never reuse a user module cache. */\nexport function createDeckBuilder(entry: string, keepWarm = true) {\n let prepared: ReturnType<typeof prepareWorker> | undefined = prepareWorker(entry);\n let closed = false;\n let active = false;\n return {\n async build(): Promise<BuildResult> {\n if (closed || active) throw new Error('Deck builder is closed or already building.');\n active = true;\n const current = prepared ?? prepareWorker(entry);\n prepared = current;\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n current.worker.postMessage('build');\n return await Promise.race([\n current.result,\n new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new Error(\n 'Build exceeded 60 seconds. Check the deck for loops or unresolved promises.',\n ),\n ),\n BUILD_TIMEOUT_MS,\n );\n }),\n ]);\n } finally {\n clearTimeout(timer);\n await current.worker.terminate();\n await rm(current.directory, { recursive: true, force: true });\n active = false;\n prepared = !closed && keepWarm ? prepareWorker(entry) : undefined;\n }\n },\n async cancel() {\n if (active) await prepared?.worker.terminate();\n },\n async close() {\n closed = true;\n const current = prepared;\n await current?.worker.terminate();\n if (current) await rm(current.directory, { recursive: true, force: true });\n },\n };\n}\n","import { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\n\nconst starter = `import { Presentation } from '@office-kit/pptx-dsl';\nimport { Cover } from './slides/cover.tsx';\n\nexport default (\n <Presentation>\n <Cover />\n </Presentation>\n);\n`;\n\nconst theme = `export const theme = {\n background: '#15171C',\n accent: '#E5481F',\n text: '#FFFFFF',\n muted: '#B4B9C4',\n} as const;\n`;\n\nconst cover = `import { Slide, Text, Shape } from '@office-kit/pptx-dsl';\nimport { theme } from '../theme.ts';\n\nexport function Cover() {\n return (\n <Slide background={theme.background}>\n <Shape preset=\"rect\" x={0.9} y={2.55} width={0.14} height={1.75} fill={theme.accent} stroke={false} />\n <Text x={1.25} y={2.4} width={10} height={1.1} size={48} bold color={theme.text}>\n Your next presentation\n </Text>\n <Text x={1.25} y={3.55} width={10} height={0.6} size={22} color={theme.muted}>\n Edit this TSX and save to update the preview.\n </Text>\n </Slide>\n );\n}\n`;\n\nconst guide = `# Writing this presentation\n\nUse deck.tsx for slide order, slides/*.tsx for individual slides, and theme.ts\nfor shared design values. The JSX runtime is\n@office-kit/pptx-dsl, with no React or Vue dependency.\n\n- Run npm run dev, then open its local URL to preview. Saving source files\n updates the preview. A failed build retains the last successful output.\n- The preview is view-only: use the vertical thumbnails to select slides, zoom\n to inspect details, and Present to view full-screen (Escape exits). Make all\n content and layout changes in TSX; there are no canvas editing controls.\n- For a local revision, locate the slide through deck.tsx or a text search and\n read only the relevant source and dependencies. Patch the requested text, data\n or props; do not regenerate the deck or reformat unrelated code. Keep descriptive\n filenames stable when reordering slides. Preserve existing project structures.\n- Change shared theme values/components only for changes intended for all their\n consumers; use a local prop override for a one-slide exception.\n- Keep the preview running during revisions and inspect affected slides after\n saving. Review all affected consumers for shared changes. Do not run a separate\n export for every intermediate edit. Before delivery, run npm run check and\n npm run build to export deck.pptx, then review the whole deck.\n- Coordinates and dimensions are inches; font sizes and stroke widths are points.\n The default 16:9 canvas is 13.333 × 7.5 inches.\n- Use native Text, Shape, Image, Table and Chart elements. Use JS functions,\n arrays and map to compose elements; do not import React.\n- Chart takes the core ChartSpec in its spec prop. Text accepts rich ParagraphSpec\n arrays through paragraphs. Raw is an escape hatch for public core APIs.\n- For templates: readFile(new URL('./template.pptx', import.meta.url)), then use\n Presentation source={bytes} mode=\"edit\" and Slide target={{index: 0}}.\n Fill target={{name: 'Title 1'}} changes an existing shape. Indices are zero-based.\n Use office-pptx inspect template.pptx to discover names, layouts and placeholders.\n- Use mode=\"compose\" and Slide from={{index: 0}} to build a new sequence from a\n source deck. This intentionally removes the original slide sequence.\n- Keep assets inside this project so the dev server sees changes. Use source-relative\n new URL('./image.png', import.meta.url) when loading local assets.\n- Preserve existing slides and formatting unless the request calls for changes.\n Never rasterize a chart/table as a shortcut. Raw-only features are not DSL coverage.\n- Preview is a rendering aid, not a guarantee of identical PowerPoint rendering.\n Check the exported file in the target application for final delivery.\n\nVSCode uses tsconfig.json for completion and diagnostics. Start the\n\"Preview presentation\" task, then run \"Simple Browser: Show\" with the printed URL\nand move that editor to a side group. Type errors appear in the Problems panel.\n`;\n\n/** Creates a new directory; existing projects are never overwritten. */\nexport async function initProject(directory: string): Promise<string> {\n const root = resolve(directory);\n const packageLocations = [\n import.meta.resolve('@office-kit/pptx/package.json'),\n import.meta.resolve('@office-kit/pptx-dsl/package.json'),\n new URL('../package.json', import.meta.url),\n ];\n const versions = await Promise.all(\n packageLocations.map(async (location) => {\n const manifest: { version: string } = JSON.parse(await readFile(new URL(location), 'utf8'));\n return `^${manifest.version}`;\n }),\n );\n await mkdir(root);\n const files: Record<string, string> = {\n 'package.json':\n JSON.stringify(\n {\n name: 'my-presentation',\n private: true,\n type: 'module',\n scripts: {\n dev: 'office-pptx dev deck.tsx',\n build: 'office-pptx build deck.tsx --out deck.pptx',\n check: 'tsc --noEmit',\n },\n dependencies: { '@office-kit/pptx': versions[0]!, '@office-kit/pptx-dsl': versions[1]! },\n devDependencies: {\n '@office-kit/pptx-dev': versions[2]!,\n '@types/node': '^24.13.2',\n typescript: '^6.0.3',\n },\n },\n null,\n 2,\n ) + '\\n',\n 'tsconfig.json':\n JSON.stringify(\n {\n compilerOptions: {\n target: 'ES2022',\n module: 'ESNext',\n moduleResolution: 'Bundler',\n jsx: 'react-jsx',\n jsxImportSource: '@office-kit/pptx-dsl',\n strict: true,\n exactOptionalPropertyTypes: true,\n noEmit: true,\n allowImportingTsExtensions: true,\n types: ['node'],\n },\n include: ['**/*.ts', '**/*.tsx'],\n exclude: ['node_modules'],\n },\n null,\n 2,\n ) + '\\n',\n 'deck.tsx': starter,\n 'theme.ts': theme,\n 'slides/cover.tsx': cover,\n 'CLAUDE.md': guide,\n '.gitignore': 'node_modules/\\ndeck.pptx\\n',\n '.vscode/tasks.json':\n JSON.stringify(\n {\n version: '2.0.0',\n tasks: [\n {\n label: 'Preview presentation',\n type: 'shell',\n command: 'npm run dev',\n isBackground: true,\n problemMatcher: {\n pattern: { regexp: '^NEVER_MATCH$', file: 1, message: 2 },\n background: {\n activeOnStart: true,\n beginsPattern: '^.*office-pptx dev',\n endsPattern: '^Preview: ',\n },\n },\n },\n {\n label: 'Check presentation',\n type: 'shell',\n command: 'npm run check',\n problemMatcher: '$tsc',\n },\n {\n label: 'Export presentation',\n type: 'shell',\n command: 'npm run build',\n problemMatcher: [],\n },\n ],\n },\n null,\n 2,\n ) + '\\n',\n };\n await Promise.all(['.vscode', 'slides'].map((directory) => mkdir(join(root, directory))));\n await Promise.all(\n Object.entries(files).map(([name, content]) =>\n writeFile(join(root, name), content, { flag: 'wx' }),\n ),\n );\n return root;\n}\n","import { readFile } from 'node:fs/promises';\nimport {\n loadPresentation,\n getSlides,\n getSlidePartName,\n getSlideTitle,\n getSlideShapes,\n getShapeName,\n getShapeId,\n getShapeKind,\n getShapePlaceholderType,\n getShapePlaceholderIdx,\n getSlideLayouts,\n getSlideLayoutName,\n getSlideLayoutPartName,\n getSlideLayoutType,\n} from '@office-kit/pptx';\n\n/** Source references usable directly in Slide, Fill and Remove props. */\nexport async function inspectTemplate(file: string) {\n const presentation = await loadPresentation(await readFile(file));\n return {\n slides: getSlides(presentation).map((slide, index) => ({\n index,\n part: getSlidePartName(slide),\n title: getSlideTitle(slide),\n shapes: getSlideShapes(slide).map((shape) => ({\n id: getShapeId(shape),\n name: getShapeName(shape),\n kind: getShapeKind(shape),\n placeholder: { type: getShapePlaceholderType(shape), idx: getShapePlaceholderIdx(shape) },\n })),\n })),\n layouts: getSlideLayouts(presentation).map((layout) => ({\n part: getSlideLayoutPartName(layout),\n name: getSlideLayoutName(layout),\n type: getSlideLayoutType(layout),\n })),\n };\n}\n","import { writeFile } from 'node:fs/promises';\nimport { createDeckBuilder } from './build-runner.ts';\nimport type { BuildResult } from './build.ts';\nexport type { BuildResult } from './build.ts';\nexport { initProject } from './init.ts';\nexport { inspectTemplate } from './inspect.ts';\n\n/** Each evaluation gets a fresh module cache and releases it when finished. */\nexport async function buildDeck(entry: string): Promise<BuildResult> {\n const builder = createDeckBuilder(entry, false);\n try {\n return await builder.build();\n } finally {\n await builder.close();\n }\n}\nexport async function exportDeck(entry: string, output: string): Promise<BuildResult> {\n const result = await buildDeck(entry);\n await writeFile(output, result.bytes);\n return result;\n}\n"],"mappings":";;;;;;;AAOA,MAAM,mBAAmB;AAEzB,SAAS,cAAc,OAAe;CACpC,MAAM,YAAY,YAAY,KAAK,OAAO,GAAG,cAAc,CAAC;CAC5D,MAAM,SAAS,IAAI,OAAO,IAAI,IAAI,gBAAgB,OAAO,KAAK,GAAG,GAAG;EAClE,YAAY;GAAE,OAAO,QAAQ,KAAK;GAAG;EAAU;EAC/C,UAAU,CAAC,sBAAsB;CACnC,CAAC;CACD,MAAM,SAAS,IAAI,SAAsB,cAAc,WAAW;EAChE,OAAO,KAAK,WAAW,YAAY;EACnC,OAAO,KAAK,SAAS,MAAM;EAC3B,OAAO,KAAK,SAAS,SACnB,uBAAO,IAAI,MAAM,8CAA8C,KAAK,GAAG,CAAC,CAC1E;CACF,CAAC;CAED,OAAY,YAAY,CAAC,CAAC;CAC1B,OAAO;EAAE;EAAQ;EAAQ;CAAU;AACrC;;AAGA,SAAgB,kBAAkB,OAAe,WAAW,MAAM;CAChE,IAAI,WAAyD,cAAc,KAAK;CAChF,IAAI,SAAS;CACb,IAAI,SAAS;CACb,OAAO;EACL,MAAM,QAA8B;GAClC,IAAI,UAAU,QAAQ,MAAM,IAAI,MAAM,6CAA6C;GACnF,SAAS;GACT,MAAM,UAAU,YAAY,cAAc,KAAK;GAC/C,WAAW;GACX,IAAI;GACJ,IAAI;IACF,QAAQ,OAAO,YAAY,OAAO;IAClC,OAAO,MAAM,QAAQ,KAAK,CACxB,QAAQ,QACR,IAAI,SAAgB,GAAG,WAAW;KAChC,QAAQ,iBAEJ,uBACE,IAAI,MACF,6EACF,CACF,GACF,gBACF;IACF,CAAC,CACH,CAAC;GACH,UAAU;IACR,aAAa,KAAK;IAClB,MAAM,QAAQ,OAAO,UAAU;IAC/B,MAAM,GAAG,QAAQ,WAAW;KAAE,WAAW;KAAM,OAAO;IAAK,CAAC;IAC5D,SAAS;IACT,WAAW,CAAC,UAAU,WAAW,cAAc,KAAK,IAAI,KAAA;GAC1D;EACF;EACA,MAAM,SAAS;GACb,IAAI,QAAQ,MAAM,UAAU,OAAO,UAAU;EAC/C;EACA,MAAM,QAAQ;GACZ,SAAS;GACT,MAAM,UAAU;GAChB,MAAM,SAAS,OAAO,UAAU;GAChC,IAAI,SAAS,MAAM,GAAG,QAAQ,WAAW;IAAE,WAAW;IAAM,OAAO;GAAK,CAAC;EAC3E;CACF;AACF;;;ACtEA,MAAM,UAAU;;;;;;;;;AAUhB,MAAM,QAAQ;;;;;;;AAQd,MAAM,QAAQ;;;;;;;;;;;;;;;;;AAkBd,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8Cd,eAAsB,YAAY,WAAoC;CACpE,MAAM,OAAO,QAAQ,SAAS;CAC9B,MAAM,mBAAmB;EACvB,OAAO,KAAK,QAAQ,+BAA+B;EACnD,OAAO,KAAK,QAAQ,mCAAmC;EACvD,IAAI,IAAI,mBAAmB,OAAO,KAAK,GAAG;CAC5C;CACA,MAAM,WAAW,MAAM,QAAQ,IAC7B,iBAAiB,IAAI,OAAO,aAAa;EAEvC,OAAO,IAD+B,KAAK,MAAM,MAAM,SAAS,IAAI,IAAI,QAAQ,GAAG,MAAM,CACvE,CAAC,CAAC;CACtB,CAAC,CACH;CACA,MAAM,MAAM,IAAI;CAChB,MAAM,QAAgC;EACpC,gBACE,KAAK,UACH;GACE,MAAM;GACN,SAAS;GACT,MAAM;GACN,SAAS;IACP,KAAK;IACL,OAAO;IACP,OAAO;GACT;GACA,cAAc;IAAE,oBAAoB,SAAS;IAAK,wBAAwB,SAAS;GAAI;GACvF,iBAAiB;IACf,wBAAwB,SAAS;IACjC,eAAe;IACf,YAAY;GACd;EACF,GACA,MACA,CACF,IAAI;EACN,iBACE,KAAK,UACH;GACE,iBAAiB;IACf,QAAQ;IACR,QAAQ;IACR,kBAAkB;IAClB,KAAK;IACL,iBAAiB;IACjB,QAAQ;IACR,4BAA4B;IAC5B,QAAQ;IACR,4BAA4B;IAC5B,OAAO,CAAC,MAAM;GAChB;GACA,SAAS,CAAC,WAAW,UAAU;GAC/B,SAAS,CAAC,cAAc;EAC1B,GACA,MACA,CACF,IAAI;EACN,YAAY;EACZ,YAAY;EACZ,oBAAoB;EACpB,aAAa;EACb,cAAc;EACd,sBACE,KAAK,UACH;GACE,SAAS;GACT,OAAO;IACL;KACE,OAAO;KACP,MAAM;KACN,SAAS;KACT,cAAc;KACd,gBAAgB;MACd,SAAS;OAAE,QAAQ;OAAiB,MAAM;OAAG,SAAS;MAAE;MACxD,YAAY;OACV,eAAe;OACf,eAAe;OACf,aAAa;MACf;KACF;IACF;IACA;KACE,OAAO;KACP,MAAM;KACN,SAAS;KACT,gBAAgB;IAClB;IACA;KACE,OAAO;KACP,MAAM;KACN,SAAS;KACT,gBAAgB,CAAC;IACnB;GACF;EACF,GACA,MACA,CACF,IAAI;CACR;CACA,MAAM,QAAQ,IAAI,CAAC,WAAW,QAAQ,CAAC,CAAC,KAAK,cAAc,MAAM,KAAK,MAAM,SAAS,CAAC,CAAC,CAAC;CACxF,MAAM,QAAQ,IACZ,OAAO,QAAQ,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,aAChC,UAAU,KAAK,MAAM,IAAI,GAAG,SAAS,EAAE,MAAM,KAAK,CAAC,CACrD,CACF;CACA,OAAO;AACT;;;;AC5KA,eAAsB,gBAAgB,MAAc;CAClD,MAAM,eAAe,MAAM,iBAAiB,MAAM,SAAS,IAAI,CAAC;CAChE,OAAO;EACL,QAAQ,UAAU,YAAY,CAAC,CAAC,KAAK,OAAO,WAAW;GACrD;GACA,MAAM,iBAAiB,KAAK;GAC5B,OAAO,cAAc,KAAK;GAC1B,QAAQ,eAAe,KAAK,CAAC,CAAC,KAAK,WAAW;IAC5C,IAAI,WAAW,KAAK;IACpB,MAAM,aAAa,KAAK;IACxB,MAAM,aAAa,KAAK;IACxB,aAAa;KAAE,MAAM,wBAAwB,KAAK;KAAG,KAAK,uBAAuB,KAAK;IAAE;GAC1F,EAAE;EACJ,EAAE;EACF,SAAS,gBAAgB,YAAY,CAAC,CAAC,KAAK,YAAY;GACtD,MAAM,uBAAuB,MAAM;GACnC,MAAM,mBAAmB,MAAM;GAC/B,MAAM,mBAAmB,MAAM;EACjC,EAAE;CACJ;AACF;;;;AC/BA,eAAsB,UAAU,OAAqC;CACnE,MAAM,UAAU,kBAAkB,OAAO,KAAK;CAC9C,IAAI;EACF,OAAO,MAAM,QAAQ,MAAM;CAC7B,UAAU;EACR,MAAM,QAAQ,MAAM;CACtB;AACF;AACA,eAAsB,WAAW,OAAe,QAAsC;CACpF,MAAM,SAAS,MAAM,UAAU,KAAK;CACpC,MAAM,UAAU,QAAQ,OAAO,KAAK;CACpC,OAAO;AACT"}