@office-kit/pptx-dev 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @office-kit/pptx-dev
2
2
 
3
3
  Local TSX authoring tools for `@office-kit/pptx-dsl`. Build and preview an editable
4
- PowerPoint presentation while changing its source in VSCode or Claude Code.
4
+ presentation while changing its source in VSCode or Claude Code.
5
5
  Requires Node.js 22.18 or later.
6
6
 
7
7
  For Claude Code, [install the office-kit skill](https://office-kit.github.io/pptx/docs/authoring)
@@ -106,15 +106,15 @@ the saved edited presentation, including after restarting the server.
106
106
 
107
107
  Saving a slide file, `theme.ts` or `deck.tsx` rebuilds the source. Changes from
108
108
  TSX merge with canvas edits, saved or not yet saved: shapes, slide settings,
109
- media and links changed on only one side are combined, and the editor reloads
110
- the merged presentation, which starts a new Undo history. The editor asks you
109
+ media and links changed on only one side are combined, and the editor applies
110
+ the merged presentation as one Undo step, "Source changed". The editor asks you
111
111
  to choose **Keep my edits** or **Use source** only when the same item was
112
112
  changed in both places, such as one shape edited on the canvas and in TSX, a
113
113
  shape deleted on one side and changed on the other, or slides added, removed or
114
114
  reordered on both sides. The message names each collision, for example
115
115
  "Slide 2: Title 1 was changed both here and in the source." Keeping edits keeps
116
116
  the entire edited deck without the TSX changes; using source discards the saved
117
- canvas version. You can download the source version before choosing. CLI export
117
+ canvas version (Undo in the editor brings it back as unsaved edits). You can download the source version before choosing. CLI export
118
118
  refuses an unresolved conflict so that it cannot silently export the wrong
119
119
  version. Editor files from versions before merging was available cannot merge
120
120
  until the conflict is resolved once.
@@ -134,7 +134,7 @@ npm run build
134
134
  `check` runs TypeScript; `build` writes `deck.pptx`. Compilation alone does not
135
135
  perform TypeScript checking, so run both before delivery. Exported charts, text,
136
136
  images and tables remain editable. The renderer previews the serialized PPTX,
137
- but is not a guarantee of identical rendering in PowerPoint.
137
+ but is not a guarantee of identical rendering in desktop presentation apps.
138
138
 
139
139
  The generated `CLAUDE.md` explains authoring conventions. VSCode picks up the
140
140
  included `tsconfig.json` for completion and diagnostics. Start the **Preview
@@ -151,7 +151,7 @@ slides. Update time depends on the deck size and any code it runs.
151
151
 
152
152
  **Agents** at the right end of the editor's tab row, beside Comments and Share,
153
153
  opens the Agents task pane docked on the right (closed by default, like
154
- PowerPoint's panes; × closes it). The pane follows the editor's light or dark
154
+ the reference desktop app's panes; × closes it). The pane follows the editor's light or dark
155
155
  appearance, including the terminal colors. Each agent defaults to **Claude Code**. Click **Start** to open the locally
156
156
  installed `claude` CLI in an interactive terminal. Install and sign in to Claude
157
157
  Code first. Use a current version with HTTP `UserPromptSubmit` hook support.
@@ -1 +1 @@
1
- var fe=["appear","disappear"],me=["fadeIn","appear","flyIn","zoomIn"],pe=["fadeOut","disappear","flyOut","zoomOut"],ye=["spin"],se={appear:"none",disappear:"none",fadeIn:"fade",fadeOut:"fade",flyIn:"fly",flyOut:"fly",zoomIn:"zoom",zoomOut:"zoom",spin:"spin"},ge=360,te=t=>t instanceof HTMLElement||t instanceof SVGElement?t:null,he=t=>t.effect===null?null:me.includes(t.effect)?"entrance":pe.includes(t.effect)?"exit":ye.includes(t.effect)?"emphasis":null,be=t=>t.durationMs!==null&&Number.isFinite(t.durationMs)&&t.durationMs>=0?t.durationMs:t.effect!==null&&fe.includes(t.effect)?0:null,Se=t=>{let m=[],S=[],r=(l,E)=>{S.push({step:l,reason:E})},p=null,c=0,i=0,a=!1;for(let l of t??[]){if(l.sequence!=="mainSeq"){r(l,"notInMainSeq");continue}let E=!a;if(a=!0,l.start==="unknown"){c=null,i=null,r(l,"notModelled");continue}l.start==="click"||E?(p={items:[],auto:E&&l.start!=="click"},m.push(p),c=0,i=0):l.start==="afterPrevious"&&(c=i,i=c);let h=l.playable?he(l):null,I=h===null?null:be(l),M=l.delayMs;if(h===null||M===null||c===null){r(l,h===null||M===null?"notModelled":"unknownTiming"),i=null;continue}let T=c+M;p?.items.push({step:l,kind:h,begin:T,duration:I}),i=I===null||i===null?null:Math.max(i,T+I)}return{stops:m,unsupported:S}},le=t=>`[data-pptx-shape-id="${t}"]`,oe=(t,m,S)=>{let r=m.target;if(r.kind!=="shape"&&r.kind!=="paragraphs")return[];if(!Number.isInteger(r.shapeId))return[];let p=String(r.shapeId);if(S.has(p))return[];let c=t.querySelectorAll(le(p)),i=c.length===1?te(c[0]):null;if(i===null)return[];if(r.kind!=="paragraphs")return[i];let a=[];for(let l of i.querySelectorAll("[data-pptx-paragraph]")){if(l.closest("[data-pptx-shape-id]")!==i)continue;let E=Number(l.getAttribute("data-pptx-paragraph"));if(E<r.firstParagraph||E>r.lastParagraph)continue;let h=te(l);h!==null&&a.push(h)}return a},re=()=>typeof performance=="object"?performance.now():Date.now(),Ee=(t,m)=>{let S=t.closest("svg");return S!==null?S.getBoundingClientRect():m instanceof Element?m.getBoundingClientRect():null},ve=t=>{if(t instanceof SVGGraphicsElement)return t.getScreenCTM();let m=t.closest("foreignObject");return m instanceof SVGGraphicsElement?m.getScreenCTM():null},Ae=(t,m,S)=>{let r=Ee(t,S);if(r===null||r.width<=0||r.height<=0)return null;let p=t.getBoundingClientRect(),c=m==="right"?r.right-p.left:m==="left"?r.left-p.right:0,i=m==="bottom"?r.bottom-p.top:m==="top"?r.top-p.bottom:0,a=ve(t);if(a===null)return{dx:c,dy:i};let l=a.a*a.d-a.b*a.c;return l===0?null:{dx:(a.d*c-a.c*i)/l,dy:(a.a*i-a.b*c)/l}},Ie=t=>t.step.effect!==null&&se[t.step.effect]==="fly",xe=(t,m,S)=>{let r=t.step.effect,p=t.kind==="entrance";switch(r===null?"none":se[r]??"none"){case"none":return null;case"fade":return{frames:p?[{opacity:0},{opacity:1}]:[{opacity:1},{opacity:0}],aboutOwnCentre:!1};case"zoom":{let c={scale:"0"},i={scale:"1"};return{frames:p?[c,i]:[i,c],aboutOwnCentre:!0}}case"spin":return{frames:[{rotate:"0deg"},{rotate:`${ge}deg`}],aboutOwnCentre:!0};case"fly":{let c=t.step.direction;if(c===null)return null;let i=Ae(m,c,S);if(i===null)return null;let a={translate:`${i.dx}px ${i.dy}px`},l={translate:"0px 0px"};return{frames:p?[a,l]:[l,a],aboutOwnCentre:!1}}}},Me=t=>{let m=()=>typeof t.root=="function"?t.root():t.root,S=Se(t.steps),r=S.stops,p=[...S.unsupported],c=()=>t.reducedMotion?.()===!0,i=t.onChange??(()=>{}),a=[],l=[],E=new Set,h=new Map,I=new Map,M=new Map,T=new Set,g=0,P=!1,O=null,k=0,R=new Set;for(let e of p)for(let n of e.step.targetShapeIds)R.add(String(n));let z=new Map;for(let e of r)for(let n of e.items){let s=String(n.step.target.shapeId);z.has(s)||z.set(s,m().querySelectorAll(le(s)).length)}for(let e of r)for(let n of e.items)(z.get(String(n.step.target.shapeId))??0)>1&&p.push({step:n.step,reason:"ambiguousTarget"});for(let[e,n]of z)n>1&&R.add(e);let ie=e=>{let n=new Set;for(let s of r){let f=new Map;for(let o of s.items){let u=o.duration===null?Number.POSITIVE_INFINITY:o.begin+o.duration;for(let y of oe(e,o.step,R)){let d={item:o,begin:o.begin,end:u},b=f.get(y);b===void 0?f.set(y,[d]):b.push(d)}}for(let o of f.values()){if(o.length<2)continue;o.sort((d,b)=>d.begin-b.begin);let u=Number.NEGATIVE_INFINITY;for(let d of o)d.begin<u&&n.add(d.item),u=Math.max(u,d.end);let y=Number.POSITIVE_INFINITY;for(let d=o.length-1;d>=0;d-=1){let b=o[d];b!==void 0&&(b.end>y&&n.add(b.item),y=Math.min(y,b.begin))}}}for(let s of r){let f=0;for(let o of s.items){if(o.step.valueAfterEnd==="unstated"&&n.has(o)){p.push({step:o.step,reason:"notModelled"});for(let u of o.step.targetShapeIds)R.add(String(u));continue}s.items[f]=o,f+=1}s.items.length=f}},C=()=>{for(let e of l)clearTimeout(e);l.length=0;for(let e of a)e.cancel();a.length=0;for(let e of I.values())for(let n of e)n.cancel();I.clear(),h.clear(),M.clear(),T.clear()},$=(e,n)=>{let s=e.indexOf(n);s>=0&&e.splice(s,1)},Y=()=>{P||(l.length===0&&a.length===0&&(O=null),i(x))},ae=e=>{E.add(e),e.style.transformBox="fill-box",e.style.transformOrigin="50% 50%"},U=e=>{e.style.transformBox="",e.style.transformOrigin=""},ue=e=>{E.add(e),e.style.visibility="",e.style.opacity="",U(e)},de=e=>{E.add(e),e.style.visibility="hidden",e.style.opacity="",U(e)},H=null,V=!0,w=new Map,B=new Map,K=0,L=e=>e.stopIndex<K||T.has(e.item),X=()=>{for(let e of w.values()){e.top=null;for(let s of e.gates)L(s)&&(e.top=s);let n=e.gates[0];e.firstPlayed=n!==void 0&&L(n)}},G=()=>{let e=m(),n=w.keys().next();if(!(e===H&&n.done!==!0&&n.value.isConnected&&V)){H=e,V=!0,ie(e),w=new Map,B=new Map,r.forEach((s,f)=>{for(let o of s.items){let u=[];for(let y of oe(e,o.step,R)){let d=w.get(y)??{list:[],gates:[],top:null,firstPlayed:!1};w.set(y,d);let b={item:o,stopIndex:f,order:0};d.list.push(b),o.kind!=="emphasis"&&d.gates.push(b);let A=xe(o,y,e);A===null&&Ie(o)&&(V=!1),u.push({el:y,plan:d,entry:b,movement:A})}B.set(o,u)}});for(let s of w.values()){let f=(o,u)=>o.stopIndex-u.stopIndex||o.item.begin-u.item.begin;s.list.sort(f),s.gates.sort(f),s.list.forEach((o,u)=>{o.order=u})}X()}},ce=e=>{let n=e.gates[0];return n===void 0?!0:n.item.kind==="entrance"&&!e.firstPlayed?!1:e.top===null||e.top.item.kind!=="exit"},J=(e,n)=>{h.has(e)||(ce(n)?ue(e):de(e))},F=e=>{T.clear(),K=e,G(),X();for(let[n,s]of w)J(n,s)},j=e=>{let{plan:n,entry:s}=e;s.item.kind!=="emphasis"&&(s===n.gates[0]&&(n.firstPlayed=!0),(n.top===null||s.order>n.top.order)&&(n.top=s)),J(e.el,n)},D=e=>{T.add(e),G();for(let n of B.get(e)??[])j(n)},Q=(e,n)=>{G();let s=B.get(e)??[];if(s.length===0){D(e);return}let f=s.filter(A=>A.movement!==null),o=e.duration,u=Math.max(0,n),y=o===null||o===0||u>=o,d=e.step.valueAfterEnd!=="removed";if(c()||f.length===0||y&&(!d||!f.some(A=>h.has(A.el)))){D(e);return}for(let A of s)A.movement===null&&j(A);let b=y?0:o;M.set(e,f.length);for(let A of f){let v=A.el,ee=A.movement;E.add(v),e.kind!=="emphasis"&&(v.style.visibility=""),ee.aboutOwnCentre&&ae(v),h.set(v,(h.get(v)??0)+1);let N=v.animate(ee.frames,{duration:b,fill:"both"});N.currentTime=y?b:u,a.push(N),N.finished.then(()=>{if(P)return;$(a,N);let _=(h.get(v)??1)-1;if(_<=0||!d){if(_<=0){h.delete(v);for(let q of I.get(v)??[])q.cancel();I.delete(v)}else h.set(v,_);N.cancel()}else{h.set(v,_);let q=I.get(v);q===void 0?I.set(v,[N]):q.push(N)}j(A);let ne=(M.get(e)??1)-1;M.set(e,ne),ne<=0&&D(e),Y()},()=>{})}},W=(e,n)=>{let s=r[e];if(s===void 0)return;let f=Math.max(0,n);O=re()-f;let o=[...s.items].sort((u,y)=>u.begin-y.begin);for(let u of o){let y=u.begin-f;if(c()||y<=0){Q(u,f-u.begin);continue}let d=setTimeout(()=>{$(l,d),Q(u,0),Y()},y);l.push(d)}},Z=e=>Math.max(0,Math.min(Number.isFinite(e)?Math.round(e):0,r.length)),x={get cursor(){return g},get stopCount(){return r.length},get pending(){return g<r.length},get running(){return l.length>0||a.length>0},get progress(){return{cursor:g,stops:r.length,elapsed:O===null?null:Math.round(re()-O),generation:k}},get unsupported(){return[...p]},reset(){k+=1,C(),O=null,g=0,F(0),i(x),r[0]?.auto===!0&&x.advance()},advance(){if(P||g>=r.length)return!1;k+=1,C(),F(g);let e=g;return g+=1,W(e,0),i(x),!0},back(){return P||g<=0?!1:(k+=1,C(),O=null,g-=1,F(g),i(x),!0)},jumpTo(e){P||(k+=1,C(),O=null,g=Z(e),F(g),i(x))},resume(e,n){P||(k+=1,C(),O=null,g=Z(e),g>0&&n!==null&&Number.isFinite(n)?(F(g-1),W(g-1,n)):F(g),i(x))},finish(){x.jumpTo(r.length)},dispose(){if(!P){P=!0,C();for(let e of E)e.style.visibility="",e.style.opacity="",U(e);E.clear()}}};return G(),x};export{Se as buildAnimationStops,Me as createAnimationPlayer};
1
+ var St=["appear","disappear"],Ft=["blinds","checkerboard","peek","randomBars","shape","split","strips","wedge","wheel","wipe"],Dt={entr:"entrance",exit:"exit",emph:"emphasis"},It=t=>{if(St.includes(t))return"none";let o=t.replace(/(In|Out)$/,"");return o==="fly"||o==="credits"?"fly":o==="peek"?"peek":Ft.includes(o)?"filter":"pose"},Bt={fade:{opacity:0},dissolve:{opacity:0},expand:{opacity:0,scale:"0.7 1"},contract:{opacity:0,scale:"0.7 1"},swivel:{opacity:0,scale:"0 1"},zoom:{opacity:0,scale:"0"},basicZoom:{scale:"0"},float:{opacity:0,translate:"0% 50%"},growTurn:{opacity:0,scale:"0",rotate:"90deg"},shrinkTurn:{opacity:0,scale:"0",rotate:"90deg"},spinner:{opacity:0,scale:"0",rotate:"-90deg"},stretch:{scale:"0 1"},stretchy:{scale:"0 1"},collapse:{scale:"0 1"},pinwheel:{opacity:0,scale:"0",rotate:"720deg"},spiral:{scale:"0",translate:"-100% 100%"},basicSwivel:{scale:"0 1"}},Lt={opacity:1,scale:"1",rotate:"0deg",translate:"0% 0%"},Gt=t=>Object.fromEntries(Object.keys(t).map(o=>[o,Lt[o]])),Y=(t,o)=>[{filter:t},{filter:o}],Ot=(t,o)=>[{filter:t},{filter:o},{filter:t}],N=Y("hue-rotate(0deg)","hue-rotate(180deg)"),ut=Ot("contrast(1)","contrast(1.8)"),Ut={pulse:[{offset:0,scale:"1",opacity:1},{offset:.2,opacity:.5},{offset:.5,scale:"1.05"},{offset:.8,opacity:.5},{offset:1,scale:"1",opacity:1}],teeter:[{offset:0,rotate:"0deg"},{offset:.1,rotate:"2deg"},{offset:.2,rotate:"2deg"},{offset:.4,rotate:"-2deg"},{offset:.6,rotate:"2deg"},{offset:.8,rotate:"-2deg"},{offset:1,rotate:"0deg"}],wave:[{offset:0,translate:"0% 0%",rotate:"0deg",easing:"ease-in-out"},{offset:.25,rotate:"25deg"},{offset:.5,translate:"0% -20%",rotate:"0deg",easing:"ease-in-out"},{offset:.75,rotate:"-25deg"},{offset:1,translate:"0% 0%",rotate:"0deg"}],shimmer:[{scale:"1",translate:"0% 0%",rotate:"0deg"},{scale:"0.8 1",translate:"10% -10%",rotate:"-8deg"},{scale:"1",translate:"0% 0%",rotate:"0deg"}],blink:[{offset:0,opacity:0,easing:"step-end"},{offset:.5,opacity:1},{offset:1,opacity:1}],fillColor:N,lineColor:N,fontColor:N,objectColor:N,brushColor:N,complementaryColor:N,complementaryColor2:N,contrastingColor:N,darken:Y("brightness(1)","brightness(0.6)"),lighten:Y("brightness(1)","brightness(1.4)"),desaturate:Y("saturate(1)","saturate(0)"),colorPulse:Ot("brightness(1)","brightness(1.4)"),growWithColor:[{scale:"1",filter:"hue-rotate(0deg)"},{scale:"1.1",filter:"hue-rotate(180deg)"}],boldFlash:[{offset:0,filter:"contrast(1)",easing:"step-end"},{offset:.5,filter:"contrast(1.8)",easing:"step-end"},{offset:.6,filter:"contrast(1)"},{offset:1,filter:"contrast(1)"}],boldReveal:ut,underline:ut},Vt=t=>{switch(t.effect){case"spin":return[{rotate:"0deg"},{rotate:`${(t.spinDirection==="counterclockwise"?-1:1)*(t.spinDegrees??360)}deg`}];case"growShrink":{let o=(t.scalePercent??150)/100;return[{scale:"1"},{scale:t.scaleDirection==="horizontal"?`${o} 1`:t.scaleDirection==="vertical"?`1 ${o}`:`${o}`}]}case"transparency":return[{opacity:1},{opacity:1-(t.transparencyPercent??50)/100}];default:return null}},jt=(t,o)=>{for(let s=1;s<t.length;s+=1){let[e,r]=t[s],[a,i]=t[s-1];if(o<=e)return i+(r-i)*(o-a)/(e-a)}return 1},Ht=[[0,0],[.14,.36],[.43,.73],[.71,.91],[1,1]],dt=1822,pt=[664,1324,1656,1822],qt=[1/9,1/27,1/81],zt=[[0,0,.31],[.25,.1096,.2646],[.5,.155,.155],[.75,.1096,.0454],[1,0,0]],z=[[-.46736,.92887],[.0908,.66613],[.23177,.40825],[.18264,.09152],[0,0]],p=(t,o,s={})=>o===null?null:{offset:t,translate:o,...s},k=t=>t.every(o=>o!==null)?t:null,ht={bounce:t=>{let o=(e,r,a)=>p(e/dt,t.shift(-.25*(1-jt(Ht,e/dt)),-r),{easing:a}),s=[o(0,.3333333333333333,"ease-in")];return pt.forEach((e,r)=>{s.push(o(e,0,"ease-out"));let a=pt[r+1];a!==void 0&&s.push(o((e+a)/2,qt[r],"ease-in"))}),k(s)},boomerang:t=>k([p(0,t.shift(.4,-.2),{rotate:"-90deg",scale:"1 1",opacity:0,easing:"ease-out"}),p(.5,t.shift(0,.1),{rotate:"0deg",scale:"0.05 1",opacity:.5,easing:"ease-in"}),p(1,t.shift(0,0),{rotate:"0deg",scale:"1 1",opacity:1})]),centerRevolve:t=>k([p(0,t.shift(0,.31),{opacity:0}),p(.1,t.shift(0,.31),{opacity:1}),...zt.map(([o,s,e])=>p(.4+.6*o,t.shift(s,e),{opacity:1}))]),riseUp:t=>k([p(0,t.shift(0,1),{opacity:0,easing:"ease-out"}),p(.9,t.shift(0,-.03),{opacity:.9,easing:"ease-in"}),p(1,t.shift(0,0),{opacity:1})]),floating:t=>k([p(0,t.shift(.4,-.4),{rotate:"-90deg",opacity:0,easing:"ease-out"}),p(.8,t.shift(-.05,.1),{rotate:"0deg",opacity:1,easing:"ease-in"}),p(1,t.shift(0,0),{rotate:"0deg",opacity:1})]),whip:t=>k([p(0,t.shift(0,0),{scale:"0.1",opacity:0}),p(.5,t.shift(.1,0),{scale:"1.02",opacity:1}),p(1,t.shift(0,0),{scale:"1",opacity:1})]),curveUp:t=>k(z.map(([o,s],e)=>p(e/(z.length-1),t.shift(o,s),{scale:`${2.5-1.5*e/(z.length-1)}`,opacity:e/(z.length-1)}))),drop:t=>k([p(0,t.shift(0,-1),{rotate:"-45deg",easing:"ease-in"}),p(.455,t.shift(0,0,0,-10),{rotate:"-45deg",easing:"ease-out"}),p(.611,t.shift(0,0,0,-50),{rotate:"0deg",easing:"ease-in"}),p(.767,t.shift(0,0,0,-10),{rotate:"42deg"}),p(.864,t.shift(0,0,0,-10),{rotate:"15deg"}),p(.91,t.shift(0,0,0,-5),{rotate:"0deg"}),p(1,t.shift(0,0),{rotate:"0deg"})]),flip:t=>Et(t,"top")},Et=(t,o)=>{let s=t.past(o);if(s===null)return null;let e=(d,c)=>`calc(${s.dx*d}px + ${c}%) calc(${s.dy*d}px + 0%)`,r=o==="top",a=r?1:0,i=r?0:1;return[{offset:0,translate:e(a,0),scale:"1 1",rotate:"0deg"},{offset:.5,translate:e(.5,50),scale:"-1 1",rotate:"180deg"},{offset:1,translate:e(i,0),scale:"1 1",rotate:"360deg"}]},Kt={drop:t=>k([p(0,t.shift(0,0),{rotate:"0deg",easing:"ease-in"}),p(1,t.shift(0,1),{rotate:"45deg"})]),flip:t=>Et(t,"bottom")},Yt={sinkDown:"riseUp",curveDown:"curveUp"},Wt={"ease-in":"ease-out","ease-out":"ease-in"},Xt=t=>{let o=t.length;return t.map((s,e)=>{let r={...t[o-1-e]};delete r.easing,typeof r.offset=="number"&&(r.offset=1-r.offset);let a=t[o-2-e]?.easing;return typeof a=="string"&&(r.easing=Wt[a]??a),r})},xt=t=>{let o=t.step.effect?.replace(/(In|Out)$/,"");if(o===void 0)return;if(t.kind==="entrance")return ht[o];let s=Kt[o];if(s!==void 0)return s;let e=ht[Yt[o]??o];return e===void 0?void 0:r=>{let a=e(r);return a===null?null:Xt(a)}},gt=t=>t instanceof HTMLElement||t instanceof SVGElement?t:null,Qt=t=>t.effect===null||t.presetClass===null?null:Dt[t.presetClass]??null,Zt=t=>t.durationMs!==null&&Number.isFinite(t.durationMs)&&t.durationMs>=0?t.durationMs:t.effect!==null&&St.includes(t.effect)?0:null,Ct=t=>{let o=[],s=[],e=(c,w)=>{s.push({step:c,reason:w})},r=null,a=0,i=0,d=!1;for(let c of t??[]){if(c.sequence!=="mainSeq"){e(c,"notInMainSeq");continue}let w=!d;if(d=!0,c.start==="unknown"){a=null,i=null,e(c,"notModelled");continue}c.start==="click"||w?(r={items:[],auto:w&&c.start!=="click"},o.push(r),a=0,i=0):c.start==="afterPrevious"&&(a=i,i=a);let y=c.playable?Qt(c):null,E=y===null?null:Zt(c),M=c.delayMs;if(y===null||M===null||a===null){e(c,y===null||M===null?"notModelled":"unknownTiming"),i=null;continue}let _=a+M;r?.items.push({step:c,kind:y,begin:_,duration:E}),i=E===null||i===null?null:Math.max(i,_+E)}return{stops:o,unsupported:s}},kt=t=>`[data-pptx-shape-id="${t}"]`,mt=(t,o,s)=>{let e=o.target;if(e.kind!=="shape"&&e.kind!=="paragraphs")return[];if(!Number.isInteger(e.shapeId))return[];let r=String(e.shapeId);if(s.has(r))return[];let a=t.querySelectorAll(kt(r)),i=a.length===1?gt(a[0]):null;if(i===null)return[];if(e.kind!=="paragraphs")return[i];let d=[];for(let c of i.querySelectorAll("[data-pptx-paragraph]")){if(c.closest("[data-pptx-shape-id]")!==i)continue;let w=Number(c.getAttribute("data-pptx-paragraph"));if(w<e.firstParagraph||w>e.lastParagraph)continue;let y=gt(c);y!==null&&d.push(y)}return d},yt=()=>typeof performance=="object"?performance.now():Date.now(),Mt=(t,o)=>{let s=t.closest("svg");return s!==null?s.getBoundingClientRect():o instanceof Element?o.getBoundingClientRect():null},Jt=t=>{if(t instanceof SVGGraphicsElement)return t.getScreenCTM();let o=t.closest("foreignObject");return o instanceof SVGGraphicsElement?o.getScreenCTM():null},Tt=(t,o,s)=>{let e=Mt(t,s);if(e===null||e.width<=0||e.height<=0)return null;let r=t.getBoundingClientRect(),a=o.toLowerCase();return At(t,a.includes("right")?e.right-r.left:a.includes("left")?e.left-r.right:0,a.includes("bottom")?e.bottom-r.top:a.includes("top")?e.top-r.bottom:0)},At=(t,o,s)=>{let e=Jt(t);if(e===null)return{dx:o,dy:s};let r=e.a*e.d-e.b*e.c;return r===0?null:{dx:(e.d*o-e.c*s)/r,dy:(e.a*s-e.b*o)/r}},te=(t,o)=>({shift(s,e,r=0,a=0){let i=Mt(t,o);if(i===null||i.width<=0||i.height<=0)return null;let d=At(t,s*i.width,e*i.height);return d===null?null:`calc(${d.dx}px + ${r}%) calc(${d.dy}px + ${a}%)`},past(s){return Tt(t,s,o)}}),ee=t=>t.step.effect!==null&&(It(t.step.effect)==="fly"||xt(t)!==void 0),ne=(t,o,s)=>{let e=t.step.effect,r=t.kind==="entrance";if(e===null)return null;switch(It(e)){case"none":return null;case"pose":{if(t.kind==="emphasis"){let c=Vt(t.step)??Ut[e];return c===void 0?null:{frames:c,aboutOwnCentre:!0}}let a=xt(t);if(a!==void 0){let c=a(te(o,s));return c===null?null:{frames:c,aboutOwnCentre:!0}}let i=Bt[e.replace(/(In|Out)$/,"")];if(i===void 0)return null;let d=Gt(i);return{frames:r?[i,d]:[d,i],aboutOwnCentre:!0}}case"fly":{let a=e==="creditsIn"?"bottom":e==="creditsOut"?"top":t.step.direction;if(a===null)return null;let i=Tt(o,a,s);if(i===null)return null;let d={translate:`${i.dx}px ${i.dy}px`},c={translate:"0px 0px"};return{frames:r?[d,c]:[c,d],aboutOwnCentre:!1}}case"filter":case"peek":{let a=ce(t.step);if(a===null)return null;let i={clipPath:a.covered},d={clipPath:a.uncovered},c=t.step.direction===null?void 0:oe[t.step.direction];if(e==="peekIn"||e==="peekOut"){if(c===void 0)return null;i.translate=`${c[0]*112.5}% ${c[1]*112.5}%`,d.translate="0% 0%"}return{frames:r?[i,d]:[d,i],aboutOwnCentre:!0}}}},oe={top:[0,-1],bottom:[0,1],left:[-1,0],right:[1,0]},se={top:"bottom",bottom:"top",left:"right",right:"left"},re={top:"inset(0% 0% 100% 0%)",bottom:"inset(100% 0% 0% 0%)",left:"inset(0% 100% 0% 0%)",right:"inset(0% 0% 0% 100%)"},ae={topLeft:[0,0],topRight:[100,0],bottomLeft:[0,100],bottomRight:[100,100]},x=(t,o=!1)=>`polygon(${o?"evenodd, ":""}${t.map(([s,e])=>`${s}% ${e}%`).join(", ")}) fill-box`,vt=[[0,0],[100,0],[100,100],[0,100]],B=(t,o,s)=>{let e=[];for(let r=0;r<t;r++){let a=100*r/t,i=a+100*s/t;o?e.push([0,a],[100,a],[100,i],[0,i]):e.push([a,0],[a,100],[i,100],[i,0])}return x(e)},bt=50*Math.SQRT2,wt=32,ie=12,K=(t,o)=>{let s=(e,r)=>[50+e*o,50+r*o];switch(t){case"box":return[s(-50,-50),s(50,-50),s(50,50),s(-50,50)];case"diamond":return[s(0,-100),s(100,0),s(0,100),s(-100,0)];case"plus":return[s(-50,-100),s(50,-100),s(50,-50),s(100,-50),s(100,50),s(50,50),s(50,100),s(-50,100),s(-50,50),s(-100,50),s(-100,-50),s(-50,-50)];default:return Array.from({length:wt},(e,r)=>{let a=2*Math.PI*r/wt;return s(bt*Math.cos(a),bt*Math.sin(a))})}},G=(t,o)=>[[50,50],...Array.from({length:13},(s,e)=>{let r=2*Math.PI*(t+o*e/ie);return[50+100*Math.sin(r),50-100*Math.cos(r)]})],ce=t=>{let o=t.effect;if(o===null)return null;let s=o.replace(/(In|Out)$/,""),e=t.orientation!=="vertical";switch(s){case"wipe":case"peek":{let r=t.direction===null?void 0:s==="peek"?se[t.direction]:t.direction,a=r===void 0?void 0:re[r];return a===void 0?null:{covered:`${a} fill-box`,uncovered:"inset(0% 0% 0% 0%) fill-box"}}case"blinds":return{covered:B(6,e,0),uncovered:B(6,e,1)};case"randomBars":return{covered:B(16,e,0),uncovered:B(16,e,1)};case"checkerboard":return{covered:B(8,!e,0),uncovered:B(8,!e,1)};case"split":{let r=t.orientation==="vertical";if(t.inOut==="out"){let i=d=>{let c=`${50-d/2}%`;return r?`inset(0% ${c} 0% ${c}) fill-box`:`inset(${c} 0% ${c} 0%) fill-box`};return{covered:i(0),uncovered:i(100)}}let a=i=>x(r?[[0,0],[i,0],[i,100],[100-i,100],[100-i,0],[100,0],[100,100],[0,100]]:[[0,0],[100,0],[100,i],[0,i],[0,100-i],[100,100-i],[100,100],[0,100]]);return{covered:a(0),uncovered:a(50)}}case"shape":{let r=t.shape??"circle";return t.inOut==="out"?{covered:x(K(r,0)),uncovered:x(K(r,1))}:{covered:x([...vt,...K(r,1)],!0),uncovered:x([...vt,...K(r,0)],!0)}}case"strips":{let r=t.direction===null?void 0:ae[t.direction];if(r===void 0)return null;let[a,i]=r,d=a===0?1:-1,c=i===0?1:-1,w=y=>x([[a,i],[a+d*y,i],[a,i+c*y]]);return{covered:w(0),uncovered:w(200)}}case"wedge":return{covered:x([...G(0,0),...G(0,0)]),uncovered:x([...G(0,.5),...G(0,-.5)])};case"wheel":{let r=t.spokes??1,a=i=>x(Array.from({length:r},(d,c)=>G(c/r,i/r)).flat());return{covered:a(0),uncovered:a(1)}}default:return null}},le=t=>{let o=()=>typeof t.root=="function"?t.root():t.root,s=Ct(t.steps),e=s.stops,r=[...s.unsupported],a=()=>t.reducedMotion?.()===!0,i=t.onChange??(()=>{}),d=[],c=[],w=new Set,y=new Map,E=new Map,M=new Map,_=new Set,b=0,T=!1,A=null,R=0,L=new Set;for(let n of r)for(let l of n.step.targetShapeIds)L.add(String(l));let U=new Map;for(let n of e)for(let l of n.items){let u=String(l.step.target.shapeId);U.has(u)||U.set(u,o().querySelectorAll(kt(u)).length)}for(let n of e)for(let l of n.items)(U.get(String(l.step.target.shapeId))??0)>1&&r.push({step:l.step,reason:"ambiguousTarget"});for(let[n,l]of U)l>1&&L.add(n);let Nt=n=>{let l=new Set;for(let u of e){let m=new Map;for(let f of u.items){let h=f.duration===null?Number.POSITIVE_INFINITY:f.begin+f.duration;for(let v of mt(n,f.step,L)){let g={item:f,begin:f.begin,end:h},S=m.get(v);S===void 0?m.set(v,[g]):S.push(g)}}for(let f of m.values()){if(f.length<2)continue;f.sort((g,S)=>g.begin-S.begin);let h=Number.NEGATIVE_INFINITY;for(let g of f)g.begin<h&&l.add(g.item),h=Math.max(h,g.end);let v=Number.POSITIVE_INFINITY;for(let g=f.length-1;g>=0;g-=1){let S=f[g];S!==void 0&&(S.end>v&&l.add(S.item),v=Math.min(v,S.begin))}}}for(let u of e){let m=0;for(let f of u.items){if(f.step.valueAfterEnd==="unstated"&&l.has(f)){r.push({step:f.step,reason:"notModelled"});for(let h of f.step.targetShapeIds)L.add(String(h));continue}u.items[m]=f,m+=1}u.items.length=m}},F=()=>{for(let n of c)clearTimeout(n);c.length=0;for(let n of d)n.cancel();d.length=0;for(let n of E.values())for(let l of n)l.cancel();E.clear(),y.clear(),M.clear(),_.clear()},J=(n,l)=>{let u=n.indexOf(l);u>=0&&n.splice(u,1)},tt=()=>{T||(c.length===0&&d.length===0&&(A=null),i(C))},_t=n=>{w.add(n),n.style.transformBox="fill-box",n.style.transformOrigin="50% 50%"},W=n=>{n.style.transformBox="",n.style.transformOrigin=""},$t=n=>{w.add(n),n.style.visibility="",n.style.opacity="",W(n)},Pt=n=>{w.add(n),n.style.visibility="hidden",n.style.opacity="",W(n)},et=null,X=!0,$=new Map,V=new Map,nt=0,ot=n=>n.stopIndex<nt||_.has(n.item),st=()=>{for(let n of $.values()){n.top=null;for(let u of n.gates)ot(u)&&(n.top=u);let l=n.gates[0];n.firstPlayed=l!==void 0&&ot(l)}},j=()=>{let n=o(),l=$.keys().next();if(!(n===et&&l.done!==!0&&l.value.isConnected&&X)){et=n,X=!0,Nt(n),$=new Map,V=new Map,e.forEach((u,m)=>{for(let f of u.items){let h=[];for(let v of mt(n,f.step,L)){let g=$.get(v)??{list:[],gates:[],top:null,firstPlayed:!1};$.set(v,g);let S={item:f,stopIndex:m,order:0};g.list.push(S),f.kind!=="emphasis"&&g.gates.push(S);let O=ne(f,v,n);O===null&&ee(f)&&(X=!1),h.push({el:v,plan:g,entry:S,movement:O})}V.set(f,h)}});for(let u of $.values()){let m=(f,h)=>f.stopIndex-h.stopIndex||f.item.begin-h.item.begin;u.list.sort(m),u.gates.sort(m),u.list.forEach((f,h)=>{f.order=h})}st()}},Rt=n=>{let l=n.gates[0];return l===void 0?!0:l.item.kind==="entrance"&&!n.firstPlayed?!1:n.top===null||n.top.item.kind!=="exit"},rt=(n,l)=>{y.has(n)||(Rt(l)?$t(n):Pt(n))},D=n=>{_.clear(),nt=n,j(),st();for(let[l,u]of $)rt(l,u)},Q=n=>{let{plan:l,entry:u}=n;u.item.kind!=="emphasis"&&(u===l.gates[0]&&(l.firstPlayed=!0),(l.top===null||u.order>l.top.order)&&(l.top=u)),rt(n.el,l)},Z=n=>{_.add(n),j();for(let l of V.get(n)??[])Q(l)},at=(n,l)=>{j();let u=V.get(n)??[];if(u.length===0){Z(n);return}let m=u.filter(O=>O.movement!==null),f=n.duration,h=Math.max(0,l),v=f===null||f===0||h>=f,g=n.step.valueAfterEnd!=="removed";if(a()||m.length===0||v&&(!g||!m.some(O=>y.has(O.el)))){Z(n);return}for(let O of u)O.movement===null&&Q(O);let S=v?0:f;M.set(n,m.length);for(let O of m){let I=O.el,lt=O.movement;w.add(I),n.kind!=="emphasis"&&(I.style.visibility=""),lt.aboutOwnCentre&&_t(I),y.set(I,(y.get(I)??0)+1);let P=I.animate(lt.frames,{duration:S,fill:"both"});P.currentTime=v?S:h,d.push(P),P.finished.then(()=>{if(T)return;J(d,P);let H=(y.get(I)??1)-1;if(H<=0||!g){if(H<=0){y.delete(I);for(let q of E.get(I)??[])q.cancel();E.delete(I)}else y.set(I,H);P.cancel()}else{y.set(I,H);let q=E.get(I);q===void 0?E.set(I,[P]):q.push(P)}Q(O);let ft=(M.get(n)??1)-1;M.set(n,ft),ft<=0&&Z(n),tt()},()=>{})}},it=(n,l)=>{let u=e[n];if(u===void 0)return;let m=Math.max(0,l);A=yt()-m;let f=[...u.items].sort((h,v)=>h.begin-v.begin);for(let h of f){let v=h.begin-m;if(a()||v<=0){at(h,m-h.begin);continue}let g=setTimeout(()=>{J(c,g),at(h,0),tt()},v);c.push(g)}},ct=n=>Math.max(0,Math.min(Number.isFinite(n)?Math.round(n):0,e.length)),C={get cursor(){return b},get stopCount(){return e.length},get pending(){return b<e.length},get running(){return c.length>0||d.length>0},get progress(){return{cursor:b,stops:e.length,elapsed:A===null?null:Math.round(yt()-A),generation:R}},get unsupported(){return[...r]},reset(){R+=1,F(),A=null,b=0,D(0),i(C),e[0]?.auto===!0&&C.advance()},advance(){if(T||b>=e.length)return!1;R+=1,F(),D(b);let n=b;return b+=1,it(n,0),i(C),!0},back(){return T||b<=0?!1:(R+=1,F(),A=null,b-=1,D(b),i(C),!0)},jumpTo(n){T||(R+=1,F(),A=null,b=ct(n),D(b),i(C))},resume(n,l){T||(R+=1,F(),A=null,b=ct(n),b>0&&l!==null&&Number.isFinite(l)?(D(b-1),it(b-1,l)):D(b),i(C))},finish(){C.jumpTo(e.length)},dispose(){if(!T){T=!0,F();for(let n of w)n.style.visibility="",n.style.opacity="",W(n);w.clear()}}};return j(),C};export{Ct as buildAnimationStops,le as createAnimationPlayer};
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as editorStore, c as createDeckBuilder, i as initProject, n as exportDeck, o as mergeDecks, r as inspectTemplate, s as sourceFingerprint } from "./src-CI73YbYS.mjs";
2
+ import { a as editorStore, i as initProject, n as exportDeck, o as sourceFingerprint, r as inspectTemplate, s as createDeckBuilder } from "./src-DASlwPDd.mjs";
3
3
  import { n as renderDeck } from "./build-cpmt-r9Q.mjs";
4
4
  import { createRequire } from "node:module";
5
5
  import { chmod, lstat, mkdir, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
@@ -7,6 +7,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "nod
7
7
  import { watch } from "node:fs";
8
8
  import { createHash, randomBytes, randomUUID } from "node:crypto";
9
9
  import { getGroupChildren, getShapeBounds, getShapeId, getShapeKind, getShapeRotation, getShapeXmlString, getSlideShapes, getSlideXmlString, getSlides, loadPresentation } from "@office-kit/pptx";
10
+ import { mergeDecks } from "@office-kit/pptx-editor/merge";
10
11
  import { planPropEdit, planTextEdit, verifyTextEdit } from "@office-kit/pptx-dsl/source-edit";
11
12
  import { chromium } from "playwright";
12
13
  import { createServer } from "node:http";
@@ -638,7 +639,7 @@ const previewJapanese={
638
639
  'Changes appear automatically · Text can be selected and copied':'変更は自動的に反映されます・文字を選択してコピーできます',
639
640
  'Skipped during presentation':'プレゼンテーションでスキップされます',
640
641
  'Allow popups to open presenter view.':'発表者ビューを開くにはポップアップを許可してください。',
641
- 'Office Kit — PowerPoint preview':'Office Kit — PowerPoint プレビュー'
642
+ 'Presentation preview':'プレゼンテーションのプレビュー'
642
643
  };
643
644
  const pt=key=>previewLocale==='ja'?(previewJapanese[key]??key):key;
644
645
  const slideLabel=i=>previewLocale==='ja'?'スライド '+(i+1):'Slide '+(i+1);
@@ -647,7 +648,7 @@ function updatePreviewStatus(){
647
648
  byId('status').textContent=connectionLost?pt('Reconnecting…'):state.error?pt('Build failed · showing last successful output'):state.building?pt('Updating…'):previewLocale==='ja'?state.slides.length+' スライド・ライブ':state.slides.length+' slides · Live';
648
649
  }
649
650
  function updatePreviewLabels(){
650
- document.documentElement.lang=previewLocale;document.title=pt('Office Kit — PowerPoint preview');
651
+ document.documentElement.lang=previewLocale;document.title=pt('Presentation preview');
651
652
  const labels={'#present':'Present','#presenter':'Presenter view','.download':'Download PPTX','#toggle-chat span':'Agents','#agents-title':'Agents','.filmstrip h2':'Slides','#empty':'Waiting for slides…','footer .hint':'Changes appear automatically · Text can be selected and copied','label[for="zoom"]':'Zoom','#zoom option[value="fit"]':'Fit'};
652
653
  for(const [selector,key] of Object.entries(labels))document.querySelector(selector).textContent=pt(key);
653
654
  for(const [selector,key] of Object.entries({'.filmstrip':'Slides','main':'Slide viewer','#chat-resizer':'Chat width','#close-chat':'Close Agents','#toggle-editor':'Normal','.status-views':'Presentation views','#prev':'Previous slide','#present-prev':'Previous slide','#next':'Next slide','#present-next':'Next slide'}))document.querySelector(selector).setAttribute('aria-label',pt(key));
@@ -686,6 +687,13 @@ function renderSlide(svg,options){
686
687
  const style='<style>svg{display:block;width:100%;height:100%}.transition-layer{position:absolute;inset:0;background:white;overflow:hidden}.transition-old{pointer-events:none}</style>';
687
688
  canvas.innerHTML=svg?style+svg:'';
688
689
  const playableEffects=['fade','push','wipe','cover','pull','zoom','split','circle','diamond','plus','blinds','comb','checker','strips','randomBar','dissolve','wedge','newsflash','wheel'];
690
+ // The reference desktop app (2010+) effects (p14 / p15 / p159) play as the nearest flat
691
+ // effect above: their 3-D turns, particles and page curls are not drawn.
692
+ const approximations={morph:'fade',flash:'fade',reveal:'fade',prstTrans:'fade',honeycomb:'dissolve',glitter:'dissolve',vortex:'dissolve',shred:'dissolve',ripple:'circle',switch:'push',flip:'push',gallery:'push',conveyor:'push',ferris:'push',pan:'push',prism:'push',doors:'split',window:'split',warp:'zoom',flythrough:'zoom',wheelReverse:'wheel'};
693
+ const alias=Object.hasOwn(approximations,options?.effect??'')?approximations[options.effect]:undefined;
694
+ if(alias)options=alias==='split'
695
+ ?{...options,effect:alias,orientation:options.direction==='horz'?'horz':'vert',direction:'out'}
696
+ :{...options,effect:alias};
689
697
  // Choose per visit without replacing the persisted random transition or its timing.
690
698
  const effect=options?.effect==='random'?playableEffects[Math.floor(Math.random()*playableEffects.length)]:options?.effect;
691
699
  if(!previous||!svg||reducedMotion.matches||(effect==='cut'&&!options.thruBlack)||!(effect==='cut'||playableEffects.includes(effect)))return;
@@ -694,8 +702,9 @@ function renderSlide(svg,options){
694
702
  outgoing.className='transition-layer transition-old';outgoing.innerHTML=previousMarkup;
695
703
  outgoing.setAttribute('aria-hidden','true');outgoing.inert=true;
696
704
  canvas.innerHTML=style;canvas.append(outgoing,incoming);
697
- // PowerPoint plays fast/med/slow as 0.5/0.75/1 s; p14:dur overrides them.
698
- const duration=options.durationMs??(options.speed==='slow'?1000:options.speed==='fast'?500:750);
705
+ // The reference desktop app plays fast/med/slow as 0.5/0.75/1 s; p14:dur overrides them.
706
+ // No spd is the schema default, fast.
707
+ const duration=options.durationMs??(options.speed==='slow'?1000:options.speed==='med'?750:500);
699
708
  const timing={duration,easing:'ease-in-out',fill:'both'};
700
709
  const animate=(node,frames,easing=timing.easing)=>transitionAnimations.push(node.animate(frames,{...timing,easing}));
701
710
  const vectors={l:[-100,0],r:[100,0],u:[0,-100],d:[0,100],lu:[-100,-100],ru:[100,-100],ld:[-100,100],rd:[100,100]};
@@ -986,7 +995,7 @@ body{display:flex;flex-direction:column;background:var(--ok-panel);height:100dvh
986
995
  const page = `<!doctype html>
987
996
  <html lang="en">
988
997
  <meta charset="utf-8"><meta name="viewport" content="width=device-width">
989
- <title>Office Kit — PowerPoint preview</title>
998
+ <title>Presentation preview</title>
990
999
  <link rel="stylesheet" href="/terminal.css">
991
1000
  <style>
992
1001
  ${previewStyles}
@@ -1116,7 +1125,7 @@ function moveSlide(step,skipHidden=presenting,focusThumbnail=false){
1116
1125
  const nextPosition=skipHidden&&presenting?nextShowPosition(step):-1;
1117
1126
  const next=skipHidden&&presenting?(nextPosition<0?-1:showOrder[nextPosition]):findSlide(index+step,step,skipHidden);
1118
1127
  // Arriving backwards lands on a slide that has already played out, the way
1119
- // PowerPoint shows it; arriving forwards starts its build from the top.
1128
+ // the reference desktop app shows it; arriving forwards starts its build from the top.
1120
1129
  if(next>=0){
1121
1130
  selectSlide(next,focusThumbnail,true,step<0?'end':'start',nextPosition);
1122
1131
  if(presenting&&step>0&&nextPosition>=0&&nextPosition<=previousShowCursor&&loopShow())scheduleKioskRestart();
@@ -1684,7 +1693,7 @@ stage.onclick=event=>{
1684
1693
  };
1685
1694
  document.addEventListener('keydown',event=>{
1686
1695
  if(event.key==='Escape'&&presenting){event.preventDefault();void exitPresentation();return;}
1687
- // As in PowerPoint's Reading View, Esc goes back to Normal view, unless it
1696
+ // As in the reference desktop app's Reading View, Esc goes back to Normal view, unless it
1688
1697
  // is closing the viewer's own text field or area selection first.
1689
1698
  if(event.key==='Escape'&&!document.body.classList.contains('editing')&&!event.target.closest('select,input,textarea,[contenteditable]')&&document.querySelector('.slide-edit-panel')?.hidden!==false){event.preventDefault();chooseView(true);return;}
1690
1699
  if(presenting&&state.showProperties?.mode?.kind==='kiosk')return;
@@ -1718,7 +1727,9 @@ async function refresh(){
1718
1727
  }
1719
1728
  const editorFrame=byId('editor-frame');
1720
1729
  let editorFocus;
1721
- // The Agents task pane opens on demand, like PowerPoint's panes; the editor's
1730
+ // The preview revision the editor last saw; it reports this apart from its focus.
1731
+ let editorRevision=0;
1732
+ // The Agents task pane opens on demand, like the reference desktop app's panes; the editor's
1722
1733
  // Agents button (in its tab row) mirrors that state, so every change is reported back.
1723
1734
  const agentsKey='office-kit-agents-open';
1724
1735
  function agentsOpen(){return !document.body.classList.contains('chat-hidden');}
@@ -1753,7 +1764,7 @@ function applyEditorFocus(){
1753
1764
  if(!editorFocus)return;
1754
1765
  index=Math.max(0,editorFocus.slide);
1755
1766
  byId('chat-context').textContent=(editorFocus.locale==='ja'?'スライド ':'Slide ')+(index+1)+' / '+editorFocus.count+(editorFocus.dirty?' · '+(editorFocus.locale==='ja'?'未保存':'Unsaved'):'');
1756
- byId('chat-context').dataset.focus=JSON.stringify({slide:editorFocus.count?index:null,revision:editorFocus.dirty?-1:editorFocus.revision});
1767
+ byId('chat-context').dataset.focus=JSON.stringify({slide:editorFocus.count?index:null,revision:editorFocus.dirty?-1:editorRevision});
1757
1768
  window.dispatchEvent(new Event('agent-focus'));
1758
1769
  }
1759
1770
  window.addEventListener('message',event=>{
@@ -1773,6 +1784,11 @@ window.addEventListener('message',event=>{
1773
1784
  else if(event.data.action==='presenter')byId('presenter').click();
1774
1785
  else if(event.data.action==='rehearse'){void startPresentation();if(presenting)startRehearsal();}
1775
1786
  });
1787
+ window.addEventListener('message',event=>{
1788
+ if(event.origin!==location.origin||event.source!==editorFrame.contentWindow||event.data?.type!=='editor-revision'||!Number.isInteger(event.data.revision))return;
1789
+ editorRevision=event.data.revision;
1790
+ if(document.body.classList.contains('editing'))applyEditorFocus();
1791
+ });
1776
1792
  window.addEventListener('message',event=>{
1777
1793
  if(event.origin!==location.origin||event.source!==editorFrame.contentWindow||event.data?.type!=='editor-focus')return;
1778
1794
  editorFocus=event.data;
@@ -1789,7 +1805,7 @@ const events=new EventSource('/events');events.onmessage=event=>{if(event.data==
1789
1805
  /** Separate window so speaker notes never appear on the audience surface. */
1790
1806
  const presenterPage = `<!doctype html>
1791
1807
  <html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width">
1792
- <title>Presenter view — Office Kit</title>
1808
+ <title>Presenter view</title>
1793
1809
  <style>
1794
1810
  *{box-sizing:border-box}body{margin:0;background:#141929;color:#e4e8f0;font:15px system-ui,sans-serif;height:100vh;display:flex;flex-direction:column}header,footer{display:flex;align-items:center;gap:14px;padding:16px;flex-wrap:wrap}h1{font-size:18px;margin:0;flex:1}h2{font-size:13px;color:#b9c2d8;margin:0 0 10px}button{font:inherit;color:inherit;background:#29243e;border:1px solid #675487;border-radius:6px;padding:8px 12px;cursor:pointer}button:disabled{opacity:.4;cursor:default}button:focus-visible{outline:2px solid #bca3ff;outline-offset:3px}main{display:grid;grid-template-columns:minmax(0,3fr) minmax(240px,2fr);gap:20px;padding:0 20px;flex:1;min-height:0;overflow:auto}section{min-width:0}#current{position:relative}#current [hidden]{display:none!important}#current,#next{background:#090c14;aspect-ratio:16/9}#current svg,#next svg{width:100%;height:100%}#notes{white-space:pre-wrap;overflow-wrap:anywhere;line-height:1.6;max-height:40vh;overflow:auto;padding:14px;background:#1e2538;border-radius:8px}#notes-title{margin-top:20px}#count{flex:1}#message{padding:0 20px;color:#ffc9bd}#elapsed{font-variant-numeric:tabular-nums}footer{justify-content:flex-end}@media(max-width:640px){main{grid-template-columns:1fr}#notes{max-height:none}}
1795
1811
  </style>
@@ -1883,7 +1899,7 @@ for(const canvas of Object.values(canvases))canvas.addEventListener('click',even
1883
1899
  function update(data){
1884
1900
  connected=true;locale=data.locale;document.documentElement.lang=locale;
1885
1901
  for(const [id,en,ja] of [['title','Presenter view','発表者ビュー'],['timer-label','Elapsed','経過時間'],['reset','Reset timer','タイマーをリセット'],['current-title','Current slide','現在のスライド'],['notes-title','Speaker notes','発表者ノート'],['next-title','Next slide','次のスライド'],['prev','Previous','前へ'],['next-button','Next','次へ'],['exit','Exit presentation','プレゼンテーションを終了']])byId(id).textContent=text(en,ja);
1886
- document.title=text('Presenter view — Office Kit','発表者ビュー — Office Kit');
1902
+ document.title=text('Presenter view','発表者ビュー');
1887
1903
  byId('message').textContent=data.presenting?'':text('Presentation is stopped.','プレゼンテーションは停止中です。');
1888
1904
  const animation=data.animation&&data.animation.stops>0
1889
1905
  ? ' · '+text('Click '+data.animation.cursor+' of '+data.animation.stops,'クリック '+data.animation.cursor+' / '+data.animation.stops)
@@ -2117,7 +2133,7 @@ function createTerminal(entry, busy = () => false, base = "", verify = async ()
2117
2133
  review?.begin();
2118
2134
  json(200, { hookSpecificOutput: {
2119
2135
  hookEventName: "UserPromptSubmit",
2120
- additionalContext: `You are editing the local PowerPoint TSX project at ${resolve(entry)}.
2136
+ additionalContext: `You are editing the local presentation TSX project at ${resolve(entry)}.
2121
2137
  Read the project's authoring instructions. Make focused source patches and preserve unrelated work.
2122
2138
  The preview rebuilds on save; do not start another dev server. A Stop hook checks the actual build and returns errors for repair.
2123
2139
  ${authoringGuidance}
@@ -2345,7 +2361,7 @@ function createChat(entry, notify, busy = () => false, verify = async () => null
2345
2361
  ...message,
2346
2362
  text: message.text.slice(-12e3)
2347
2363
  }));
2348
- const prompt = `You are editing a local PowerPoint TSX project in ${dirname(resolve(entry))}.
2364
+ const prompt = `You are editing a local presentation TSX project in ${dirname(resolve(entry))}.
2349
2365
  Read the project's authoring instructions. Make focused patches to the TSX source, preserving unrelated work.
2350
2366
  The preview rebuilds automatically on save; do not start another dev server.
2351
2367
  ${authoringGuidance}
@@ -2612,11 +2628,11 @@ function mediaRange(value, length) {
2612
2628
  };
2613
2629
  }
2614
2630
  const REMEMBERED_DOCUMENTS = 32;
2631
+ const EDITOR_CHUNK = /^\/editor-chunks\/[\w-]+\.js$/;
2615
2632
  const BUNDLED_ASSETS = {
2616
2633
  "/terminal.js": "terminal-client.js",
2617
2634
  "/terminal.css": "terminal-client.css",
2618
2635
  "/editor.js": "editor.js",
2619
- "/editor.css": "editor.css",
2620
2636
  "/animation-player.js": "animation-player.js",
2621
2637
  "/media-player.js": "media-player.js"
2622
2638
  };
@@ -3084,9 +3100,18 @@ async function serveDeck(entry, port = 4173) {
3084
3100
  response.writeHead(200, { "Content-Type": asset.endsWith(".css") ? "text/css" : "text/javascript" });
3085
3101
  response.end(bytes);
3086
3102
  }, () => response.writeHead(500).end("Preview assets unavailable. Rebuild pptx-dev."));
3087
- } else if (request.url?.startsWith("/editor/") && request.method !== "GET") edit(request, response);
3103
+ } else if (request.method === "GET" && EDITOR_CHUNK.test(request.url ?? "")) readFile(new URL(`.${request.url}`, import.meta.url)).then((bytes) => response.writeHead(200, { "Content-Type": "text/javascript" }).end(bytes), () => response.writeHead(404).end());
3104
+ else if (request.url?.startsWith("/editor/") && request.method !== "GET") edit(request, response);
3088
3105
  else if (request.method === "GET" && request.url === "/editor/state") response.writeHead(200, { "Content-Type": "application/json" }).end(JSON.stringify(editorState()));
3089
- else if (request.method === "GET" && ["/editor/document", "/editor/source"].includes(request.url ?? "")) {
3106
+ else if (request.method === "GET" && (request.url === "/editor/base" || request.url?.startsWith("/editor/document?revision="))) {
3107
+ const bytes = request.url === "/editor/base" ? saved?.base : documents.get(new URL(request.url, "http://localhost").searchParams.get("revision") ?? "");
3108
+ if (!bytes) {
3109
+ response.writeHead(404).end();
3110
+ return;
3111
+ }
3112
+ response.writeHead(200, { "Content-Type": "application/octet-stream" });
3113
+ response.end(bytes);
3114
+ } else if (request.method === "GET" && ["/editor/document", "/editor/source"].includes(request.url ?? "")) {
3090
3115
  const result = request.url === "/editor/source" ? source : latest;
3091
3116
  if (!result) {
3092
3117
  response.writeHead(503).end();
@@ -3133,7 +3158,7 @@ async function serveDeck(entry, port = 4173) {
3133
3158
  response.end(latest.bytes);
3134
3159
  } else if (request.url === "/editor") {
3135
3160
  response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
3136
- response.end("<!doctype html><html><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width\"><title>Office Kit Editor</title><link rel=\"stylesheet\" href=\"/editor.css\"><body><script type=\"module\" src=\"/editor.js\"><\/script></body></html>");
3161
+ response.end("<!doctype html><html><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width\"><title>Presentation editor</title><body><script type=\"module\" src=\"/editor.js\"><\/script></body></html>");
3137
3162
  } else if (request.url === "/presenter") {
3138
3163
  response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
3139
3164
  response.end(presenterPage);