diff --git a/crates/context/assets/component-review.js b/crates/context/assets/component-review.js
index 49ac0b57b..07a199f8e 100644
--- a/crates/context/assets/component-review.js
+++ b/crates/context/assets/component-review.js
@@ -1,4 +1,4 @@
-(()=>{function di(p,e){let a=e?.changes[p],o=e?.feedback?.[p],n=o?.decision??(e?.submitted?e.draft.decisions[p]:void 0),c=a?.kind==="unchanged"&&(a.carried??(e?.submitted&&n?.action==="approve"))===!0;return{change:a,prior:n,feedbackRound:o?.round??e?.packet.round,carried:c,label:a?.kind==="added"?"New component":a?.kind==="changed"?"Review again":c?"Approval kept":n?.action==="revise"?"Changes still requested":"Awaiting review"}}function B(p,e,a){let o=e.decisions[p.id],n=o?.revision===p.revision?o:void 0,c=di(p.id,a);if(n?.action==="approve")return{kind:"approved",label:c.carried?"Approval kept":"Approved",priority:3};if(n?.action==="revise")return{kind:"feedback",label:"Feedback ready",priority:2};return{kind:"pending",label:c.change?.kind==="changed"?"Review again":c.change?.kind==="added"?"New · review needed":"Not reviewed",priority:c.change?.kind==="changed"||c.change?.kind==="added"?0:1}}function Si(p){return{packetRevision:p.revision,decisions:{},missing:[],inventoryConfirmed:!1}}function ue(p){return Object.values(p).every(Number.isFinite)&&p.x>=0&&p.y>=0&&p.w>0&&p.h>0&&p.x+p.w<=1.00001&&p.y+p.h<=1.00001}function hi(p,e){let a=p.components.map((g)=>e.decisions[g.id]?.revision===g.revision?e.decisions[g.id]:void 0),o=a.filter((g)=>g?.action==="approve").length,n=a.filter((g)=>g?.action==="revise").length,c=a.length-o-n,d=n>0||e.missing.length>0;return{approved:o,revisions:n,pending:c,hasFeedback:d,canSubmit:e.packetRevision===p.revision&&e.missing.every((g)=>g.name.trim()&&ue(g.box))&&(d||!c&&e.inventoryConfirmed)}}function Ci(p,e){let a={...e.decisions};for(let o of p.components)if(!a[o.id]||a[o.id].revision!==o.revision)a[o.id]={revision:o.revision,action:"approve",feedback:"",split:!1};return{...e,decisions:a}}function Fi(p,e){if(!hi(p,e).canSubmit)throw Error("Review is incomplete or stale");return{schemaVersion:1,requestId:p.id,...structuredClone(e)}}function gi(p){let e=p.preview.kind==="page"||p.preview.sourceKind==="page",a=p.preview.sourceKind==="page";return{code:e,captured:a,label:e?p.medium.match(/html|css|svg/i)?p.medium:"HTML / CSS / SVG":"Raster",caption:e?a?"Rendered component":"Live component":"Produced asset",fileLabel:a?"Open captured preview":"Open source image"}}function Pi(p,e,a){let o=p.components.findIndex((n)=>n.id===a);for(let n=1;n<=p.components.length;n++){let c=p.components[(o+n)%p.components.length];if(B(c,e).kind==="pending")return c.id}}function ki(p,e,a,o,n){let c=n==="fit"?Math.min(a/p,o/e):n;return{scale:c,width:p*c,height:e*c}}var ie=`
+(()=>{function yi(p,o){let a=o?.changes[p],r=o?.feedback?.[p],n=r?.decision??(o?.submitted?o.draft.decisions[p]:void 0),c=a?.kind==="unchanged"&&(a.carried??(o?.submitted&&n?.action==="approve"))===!0;return{change:a,prior:n,feedbackRound:r?.round??o?.packet.round,carried:c,label:a?.kind==="added"?"New component":a?.kind==="changed"?"Review again":c?"Approval kept":n?.action==="revise"?"Changes still requested":"Awaiting review"}}function F(p,o,a){let r=o.decisions[p.id],n=r?.revision===p.revision?r:void 0,c=yi(p.id,a);if(n?.action==="approve")return{kind:"approved",label:c.carried?"Approval kept":"Approved",priority:3};if(n?.action==="revise")return{kind:"feedback",label:"Feedback ready",priority:2};return{kind:"pending",label:c.change?.kind==="changed"?"Review again":c.change?.kind==="added"?"New · review needed":"Not reviewed",priority:c.change?.kind==="changed"||c.change?.kind==="added"?0:1}}function no(p){return{packetRevision:p.revision,decisions:{},missing:[],inventoryConfirmed:!1}}function Ho(p){return Object.values(p).every(Number.isFinite)&&p.x>=0&&p.y>=0&&p.w>0&&p.h>0&&p.x+p.w<=1.00001&&p.y+p.h<=1.00001}function zi(p,o){let a=p.components.map((f)=>o.decisions[f.id]?.revision===f.revision?o.decisions[f.id]:void 0),r=a.filter((f)=>f?.action==="approve").length,n=a.filter((f)=>f?.action==="revise").length,c=a.length-r-n,h=n>0||o.missing.length>0;return{approved:r,revisions:n,pending:c,hasFeedback:h,canSubmit:o.packetRevision===p.revision&&o.missing.every((f)=>f.name.trim()&&Ho(f.box))&&(h||!c&&o.inventoryConfirmed)}}function po(p,o){let a={...o.decisions};for(let r of p.components)if(!a[r.id]||a[r.id].revision!==r.revision)a[r.id]={revision:r.revision,action:"approve",feedback:"",split:!1};return{...o,decisions:a}}function ao(p,o){if(!zi(p,o).canSubmit)throw Error("Review is incomplete or stale");return{schemaVersion:1,requestId:p.id,...structuredClone(o)}}function ki(p){let o=p.preview.kind==="page"||p.preview.sourceKind==="page",a=p.preview.sourceKind==="page";return{code:o,captured:a,label:o?p.medium.match(/html|css|svg/i)?p.medium:"HTML / CSS / SVG":"Raster",caption:o?a?"Rendered component":"Live component":"Produced asset",fileLabel:a?"Open captured preview":"Open source image"}}function to(p,o,a){let r=p.components.findIndex((n)=>n.id===a);for(let n=1;n<=p.components.length;n++){let c=p.components[(r+n)%p.components.length];if(F(c,o).kind==="pending")return c.id}}function Ki(p,o,a,r,n){let c=n==="fit"?Math.min(a/p,r/o):n;return{scale:c,width:p*c,height:o*c}}function Qi(p,o,a,r){if(a<=0||r<=a)return 0;return Math.max(0,Math.min(1,((p-o)/a-0.08)/0.84))*(r-a)}var xo=`
:host{height:var(--component-review-height,100dvh);min-height:0;overflow:hidden}
.review{height:100%;max-width:none;min-height:0;padding:0;display:flex;flex-direction:column;overflow:hidden;background:var(--color-bg,#fafafa)}
.review>header{flex-shrink:0;padding:16px 24px;margin:0;border-bottom:1px solid var(--line);gap:16px}.review>header>div{min-width:0}.review h1{font-size:30px;line-height:1}.review>header p{font-size:12px;margin-top:6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.review .badge{font-size:11px}
@@ -54,7 +54,7 @@
.review>.inventory-section{height:160px;padding:6px 14px}.inventory-section>.section-head{flex-direction:row;flex-wrap:nowrap;align-items:center;gap:6px;min-height:34px;margin-bottom:4px}.inventory-section h2{display:none}.inventory-filters{flex:1;width:auto;min-width:0;padding:2px}.inventory-filters button{padding:4px 6px;font-size:10px;min-height:28px}.inventory-filters b{margin-left:3px}.tray-actions #show-all{display:none}.tray-actions #toggle-tray{padding:6px;min-width:32px;min-height:32px}.tray-actions #toggle-tray span{display:none}.tray-actions svg{width:16px;height:16px}.review>.inventory-section.tray-collapsed{height:46px;padding:6px 14px}.review>.inventory-section.tray-expanded{height:160px}.inventory .item{flex-basis:130px;grid-template-rows:auto 1fr auto;padding:6px}.inventory .item-thumb{display:none}.inventory.all{display:flex;overflow-x:auto;overflow-y:hidden}.inventory .item strong{font-size:11px;min-height:22px}.inventory .state{font-size:10px}.inventory .item-number{font-size:10px;min-height:14px}.inventory-empty{padding:8px 0;font-size:12px}
.review>footer{padding:8px 14px calc(8px + env(safe-area-inset-bottom));gap:8px;flex-direction:column;align-items:stretch}.review>footer>div:first-child{gap:10px;justify-content:space-between}.review>footer #approve-rest{font-size:10px;min-height:32px;max-width:47%;padding:5px 8px}.review>footer .check{font-size:10px;max-width:48%;gap:4px}.review>footer .check input{width:14px;height:14px}.review>footer .submit-area{justify-content:space-between;gap:10px}.review>footer .submit-area p{font-size:10px;max-width:22ch}.review>footer .primary{min-height:34px;font-size:12px;padding:6px 10px}
}
-`;var ee=`
+`;var lo=`
:host{display:block;color:var(--color-text,#292929);font:14px/1.45 var(--font-sans,Arial,sans-serif);--line:var(--color-border,#ddd);--paper:var(--color-panel,#fff);--muted:var(--color-muted,#666);--teal:var(--color-patina,#28625e);--warn:var(--color-warn,#8a5b30);--selection:#43897f}
*{box-sizing:border-box}h1,h2,p,figure{margin:0}button,input,textarea{font:inherit}button{cursor:pointer;border:1px solid var(--line);border-radius:4px;background:var(--paper);color:inherit;padding:8px 12px;min-height:36px}button:hover{border-color:var(--teal);color:var(--teal)}button:disabled{cursor:default;opacity:.45}button:focus-visible,input:focus-visible,textarea:focus-visible{outline:2px solid var(--teal);outline-offset:3px}button[aria-pressed=true]{box-shadow:inset 0 0 0 1px var(--teal)}input[type=checkbox]{accent-color:var(--teal);width:16px;height:16px;flex-shrink:0}textarea,input:not([type=checkbox]){width:100%;background:var(--paper);color:inherit;border:1px solid #999;border-radius:4px;padding:9px 10px}textarea{resize:vertical;min-height:80px}::selection{background:#c7ddd8}a{color:var(--teal)}
.review{max-width:1600px;margin:auto;padding:24px 28px 0}header{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:16px}h1{font:400 40px/1.05 var(--font-display,Arial,sans-serif);letter-spacing:-.02em}header p{margin-top:8px;font-size:15px}header p span,.medium{color:var(--muted)}.badge{border:1px solid var(--line);padding:5px 10px;font-size:12px;white-space:nowrap}.preview-note{color:var(--muted);font-size:12px;border-bottom:1px solid var(--line);padding-bottom:16px;margin-bottom:24px}
@@ -89,7 +89,7 @@
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto}}
.item-medium{margin-left:auto;font-weight:400;display:flex;align-items:center;gap:4px;font-size:10px;color:var(--muted);min-height:16px}.item-medium .utility-icon{width:14px;height:14px;flex-shrink:0}.material>.utility-icon{width:18px;height:18px;align-self:center;color:var(--teal)}
.feedback-actions{display:flex;align-items:center;gap:10px;margin-top:12px;flex-wrap:wrap}.feedback-actions .primary{margin-left:auto;display:inline-flex;align-items:center;gap:10px}.feedback-actions svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.5}.feedback-hint,.shortcut-hint{font-size:12px;color:var(--muted)}.feedback-hint{margin-top:6px}.shortcut-hint{flex-basis:100%;text-align:right}.saved-feedback{font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere;margin:8px 0}.decision-notice{display:flex;align-items:center;gap:12px;font-size:12px;margin-bottom:10px}.decision-notice>span{flex:1;overflow-wrap:anywhere}.decision-notice button{color:var(--teal);flex-shrink:0}.review-summary{padding:16px 8px}.review-summary h2{font-size:24px}.review-summary p{margin-top:12px;color:var(--muted)}.summary-decisions{margin-top:24px;display:grid}.summary-decisions button{display:grid;grid-template-columns:1fr auto;gap:6px 16px;text-align:left;border:0;border-bottom:1px solid var(--line);border-radius:0;padding:12px 0;background:transparent}.summary-decisions strong{font-size:13px;font-weight:500}.summary-decisions span{font-size:12px;color:var(--teal)}.summary-decisions small{grid-column:1/-1;font-size:12px;color:var(--muted);white-space:pre-wrap;overflow-wrap:anywhere}
-`+ie+`
+`+xo+`
.review-form:has(#feedback-form){max-height:75%;min-height:0;flex-shrink:1;display:flex;flex-direction:column;overflow:hidden;padding-top:8px}
#feedback-form{display:flex;flex-direction:column;flex:1;min-height:0}
/* Leave room inside the scrollport for the 2px focus ring and 3px offset. */
@@ -100,47 +100,58 @@
.feedback-actions .shortcut-hint{flex-basis:auto;order:-1;margin-right:auto}
.feedback-actions .primary{margin-left:0}
@media(max-height:800px),(max-width:800px){.feedback-actions .shortcut-hint{display:none}.feedback-actions .primary{margin-left:auto}.review-form .feedback textarea{min-height:56px}.review-form:has(#feedback-form) .decision-notice{margin-bottom:4px}}
-`;var ve={code:' ',image:' ',expand:' ',compact:' ',hideTray:' ',showTray:' ',next:' ',mark:' ',close:' ',undo:' ',external:' ',zoom:' '};function K(p){return`${ve[p]} `}var x=(p)=>p.replace(/[&<>"']/g,(e)=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[e]),D=(p)=>`${p*100}%`,O=(p)=>{let e=new URL(p,location.href);if(!["http:","https:"].includes(e.protocol))throw Error("Unsupported preview URL");return x(e.href)};function oe(p,e,a){let o=p.attachShadow({mode:"open"}),n=structuredClone(a.initialDraft??Si(e)),c=()=>[...e.components].sort((s,b)=>B(s,n,a.history).priority-B(b,n,a.history).priority),d=c().find((s)=>B(s,n,a.history).kind!=="approved")?.id??e.components[0]?.id,g=a.completed?"all":"attention",V=!1,J=!1,Q=a.completed??!1,si="",H={},M=!1,W=null,ne=/Mac|iPhone|iPad/.test(navigator.platform)?"⌘Enter":"Ctrl+Enter",Y=!1,U=!1,k=!0,F=!1,y="comp",ji=y,_="fit",ei="checker",N=!1,R="isolated",fi,Hi="fit",L=null,T=null,oi=null,ci=' ',ai=' ',Mi=(s)=>`left:${D(s.x)};top:${D(s.y)};width:${D(s.w)};height:${D(s.h)}`;function ni(s){o.getElementById(s)?.focus({preventScroll:!0})}function re(s){let b=Pi(e,n,s);if(M=!b,b)d=b;if(y="component",N=!1,Y=!1,_="fit",R="isolated",g==="approved")g="attention";h(),ni(M?"review-summary":H[d]?"feedback":"approve")}function bi(s){if(J||Q||N)return;let b=e.components.find((mi)=>mi.id===d);if(!b)return;let $=n.decisions[b.id],ri=$?.revision===b.revision?$:void 0;W={id:b.id,name:b.name,action:s,previous:$?{...$}:void 0};let ti=H[b.id]??ri;if(n.decisions[b.id]={revision:b.revision,action:s,feedback:s==="revise"?ti?.feedback??"":"",split:s==="revise"&&(ti?.split??!1)},delete H[b.id],F)k=!0,F=!1;re(b.id)}function pe(){if(J||Q||N)return;let s=e.components.find((ri)=>ri.id===d);if(!s)return;let b=n.decisions[s.id],$=b?.revision===s.revision?b:void 0;if(H[s.id]??={feedback:$?.feedback??"",split:$?.split??!1},k&&(o.querySelector(".workbench")?.clientHeight??0)<420)F=!0,k=!1;M=!1,h(),ni("feedback")}function qi(s){let b=`missing-${crypto.randomUUID()}`;n.missing.push({id:b,name:"Missing component",feedback:"",box:s}),n.inventoryConfirmed=!1,M=!1,d=b,y="component",V=!1,L=null,T=null,h(),o.querySelector("#missing-name")?.focus()}function h(){oi?.disconnect();let s=o.activeElement,b=s?.id,$=s?.dataset.select,ri=window.scrollX,ti=window.scrollY,mi=o.querySelector(".inventory")?.scrollLeft??0,xi=fi===d,ae=y==="component"&&(!xi||ji!==y);ji=y;let te=xi?o.querySelector(".inspection-content")?.scrollTop??0:0,xe=xi&&(o.querySelector(".changed-files")?.open??!1),Ei=o.querySelector(".pan-viewport"),Vi=fi===d&&Hi===_,le=Vi?Ei?.scrollLeft??0:0,de=Vi?Ei?.scrollTop??0:0;fi=d,Hi=_;let l=e.components.find((i)=>i.id===d),w=n.missing.find((i)=>i.id===d),Ji=l?.box??w?.box,Ni=l?e.components.indexOf(l)+1:e.components.length+n.missing.findIndex((i)=>i.id===d)+1,$i=l?n.decisions[l.id]:void 0,S=$i?.revision===l?.revision?$i:void 0,wi=l?H[l.id]:void 0,P=Object.keys(H).length>0,he=l?!e.components.some((i)=>i.id!==l.id&&B(i,n).kind==="pending"):!1,Gi=W?`
${x(W.name)} ${W.action==="approve"?"approved":"flagged for repair"}. Undo
`:"",u=hi(e,n),v=a.history,q=l?di(l.id,v):void 0,C=v?.packet.components.find((i)=>i.id===l?.id),X=N&&!!C,t=X?C:l,E=X?v.packet:e,Ki=Object.values(v?.changes??{}),Qi=Ki.filter((i)=>i.kind==="changed").length,Wi=Ki.filter((i)=>i.kind==="added").length,ui=e.components.filter((i)=>B(i,n,v).kind==="approved"&&di(i.id,v).carried).length,pi=(i)=>B(i,n,v),ge=e.components.length-u.approved+n.missing.length,_i=(g==="attention"?c():e.components).filter((i)=>g==="all"||pi(i).kind==="approved"===(g==="approved")),se=[u.revisions+n.missing.length?`${u.revisions+n.missing.length} feedback ready`:"",ui?`${ui} ${ui===1?"approval":"approvals"} kept`:"",Qi?`${Qi} changed`:"",Wi?`${Wi} added`:"",v?.removed.length?`${v.removed.length} removed`:""].filter(Boolean).join(" · "),fe=si||(P?"Save or cancel your open feedback before sending.":Q?a.preview?"Preview submitted. No run changed.":"Review submitted.":u.hasFeedback?"Ready to send for corrections.":u.pending?`${u.pending} left to review`:!n.inventoryConfirmed?"Confirm the map is complete.":"Ready to continue."),Z=t?gi(t):null,Li=t?.preview.kind==="image"&&!Z?.code,I=!!(t?.context&&R==="context"),Ti=t&&(I?t.context?.kind!=="image":t.preview.kind==="page"),Xi=I&&t?.context?t.context.url:t?.preview.url,ce=Z?.code?`${Z.label} · ${Z.captured?"captured from code":"live preview"}`:t?.material?`${t.material.alpha==="transparent"?"Transparent":t.material.alpha==="opaque"?"Opaque":"Transparency unverified"} ${t.material.format}`:"Raster · transparency unverified";if(o.innerHTML=`
-
+
+.comparison-panel{min-width:0}.comparison-panel .expanded-title{display:none}.comparison-panel .compare-toolbar{justify-content:flex-start}.comparison-panel .overlay-control{margin-left:auto}.comparison-panel #expand-comparison{flex-shrink:0}.pan-viewport.pannable{cursor:move}.pan-viewport img{user-select:none;-webkit-user-drag:none}
+#comparison-dialog{position:fixed;inset:16px;width:calc(100vw - 32px);height:calc(100dvh - 32px);max-width:none;max-height:none;margin:0;padding:24px;background:var(--paper);color:inherit;border:1px solid var(--line);border-radius:8px;box-shadow:0 20px 60px #14292340;overflow:hidden;transform-origin:top left}
+#comparison-dialog::backdrop{background:#172821a6}
+#comparison-dialog .comparison-panel{height:100%;display:flex;flex-direction:column;gap:12px}
+#comparison-dialog .expanded-title{display:block;font-size:20px;line-height:1.3;margin:0;font-weight:500}
+#comparison-dialog .compare-toolbar{flex-shrink:0;margin:0}
+#comparison-dialog .compare{flex:1;min-height:0;max-width:none;grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
+#comparison-dialog .compare figure{display:flex;flex-direction:column;min-height:0}#comparison-dialog .pan-viewport{flex:1;min-height:0}#comparison-dialog .compare figcaption{flex-shrink:0}
+#comparison-dialog .view-controls{flex-shrink:0;margin:0}
+@media(max-width:600px){#comparison-dialog{inset:6px;width:calc(100vw - 12px);height:calc(100dvh - 12px);padding:14px}.comparison-panel .compare-toolbar{gap:6px}.comparison-panel .overlay-control{padding-inline:8px;font-size:12px}#comparison-dialog .compare{gap:8px}#comparison-dialog .compare figcaption{font-size:11px}}
+`;var Vo={code:' ',image:' ',expand:' ',compact:' ',hideTray:' ',showTray:' ',next:' ',mark:' ',close:' ',undo:' ',external:' ',zoom:' '};function E(p){return`${Vo[p]} `}var x=(p)=>p.replace(/[&<>"']/g,(o)=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[o]),C=(p)=>`${p*100}%`,P=(p)=>{let o=new URL(p,location.href);if(!["http:","https:"].includes(o.protocol))throw Error("Unsupported preview URL");return x(o.href)};function ho(p,o,a){let r=p.attachShadow({mode:"open"}),n=structuredClone(a.initialDraft??no(o)),c=()=>[...o.components].sort((s,w)=>F(s,n,a.history).priority-F(w,n,a.history).priority),h=c().find((s)=>F(s,n,a.history).kind!=="approved")?.id??o.components[0]?.id,f=a.completed?"all":"attention",_=!1,L=!1,Y=a.completed??!1,ji="",V={},J=!1,U=null,go=/Mac|iPhone|iPad/.test(navigator.platform)?"⌘Enter":"Ctrl+Enter",B=!1,$=!1,O=!1,M=!0,ni=!1,q="comp",_i=q,W="fit",xi="checker",T=!1,ii="isolated",qi,Li="fit",Z=null,I=null,pi=null,Mi=' ',gi=' ',Ti=(s)=>`left:${C(s.x)};top:${C(s.y)};width:${C(s.w)};height:${C(s.h)}`;function li(s){r.getElementById(s)?.focus({preventScroll:!0})}function fo(s){let w=to(o,n,s);if(J=!w,w)h=w;if(q="component",T=!1,B=!1,W="fit",ii="isolated",f==="approved")f="attention";g(),li(J?"review-summary":V[h]?"feedback":"approve")}function Hi(s){if(L||Y||T)return;let w=o.components.find((Vi)=>Vi.id===h);if(!w)return;let X=n.decisions[w.id],di=X?.revision===w.revision?X:void 0;U={id:w.id,name:w.name,action:s,previous:X?{...X}:void 0};let fi=V[w.id]??di;if(n.decisions[w.id]={revision:w.revision,action:s,feedback:s==="revise"?fi?.feedback??"":"",split:s==="revise"&&(fi?.split??!1)},delete V[w.id],ni)M=!0,ni=!1;fo(w.id)}function so(){if(L||Y||T)return;let s=o.components.find((di)=>di.id===h);if(!s)return;let w=n.decisions[s.id],X=w?.revision===s.revision?w:void 0;if(V[s.id]??={feedback:X?.feedback??"",split:X?.split??!1},M&&(r.querySelector(".workbench")?.clientHeight??0)<420)ni=!0,M=!1;J=!1,g(),li("feedback")}function Xi(s){let w=`missing-${crypto.randomUUID()}`;n.missing.push({id:w,name:"Missing component",feedback:"",box:s}),n.inventoryConfirmed=!1,J=!1,h=w,q="component",_=!1,Z=null,I=null,g(),r.querySelector("#missing-name")?.focus()}function g(){pi?.disconnect();let s=r.activeElement,w=s?.id,X=s?.dataset.select,di=window.scrollX,fi=window.scrollY,Vi=r.querySelector(".inventory")?.scrollLeft??0,si=qi===h,co=q==="component"&&(!si||_i!==q);_i=q;let wo=si?r.querySelector(".inspection-content")?.scrollTop??0:0,mo=si&&(r.querySelector(".changed-files")?.open??!1),bo=r.querySelector(".comparison-slot")?.clientHeight??200,Yi=r.querySelector(".pan-viewport"),Ui=qi===h&&Li===W,uo=Ui?Yi?.scrollLeft??0:0,vo=Ui?Yi?.scrollTop??0:0;qi=h,Li=W;let d=o.components.find((i)=>i.id===h),v=n.missing.find((i)=>i.id===h),Wi=d?.box??v?.box,Zi=d?o.components.indexOf(d)+1:o.components.length+n.missing.findIndex((i)=>i.id===h)+1,Ii=d?n.decisions[d.id]:void 0,oi=Ii?.revision===d?.revision?Ii:void 0,Ei=d?V[d.id]:void 0,ai=Object.keys(V).length>0,yo=d?!o.components.some((i)=>i.id!==d.id&&F(i,n).kind==="pending"):!1,Ai=U?`${x(U.name)} ${U.action==="approve"?"approved":"flagged for repair"}. Undo
`:"",y=zi(o,n),j=a.history,N=d?yi(d.id,j):void 0,ei=j?.packet.components.find((i)=>i.id===d?.id),A=T&&!!ei,t=A?ei:d,G=A?j.packet:o,Bi=Object.values(j?.changes??{}),Oi=Bi.filter((i)=>i.kind==="changed").length,Ri=Bi.filter((i)=>i.kind==="added").length,Ji=o.components.filter((i)=>F(i,n,j).kind==="approved"&&yi(i.id,j).carried).length,hi=(i)=>F(i,n,j),zo=o.components.length-y.approved+n.missing.length,Di=(f==="attention"?c():o.components).filter((i)=>f==="all"||hi(i).kind==="approved"===(f==="approved")),ko=[y.revisions+n.missing.length?`${y.revisions+n.missing.length} feedback ready`:"",Ji?`${Ji} ${Ji===1?"approval":"approvals"} kept`:"",Oi?`${Oi} changed`:"",Ri?`${Ri} added`:"",j?.removed.length?`${j.removed.length} removed`:""].filter(Boolean).join(" · "),jo=ji||(ai?"Save or cancel your open feedback before sending.":Y?a.preview?"Preview submitted. No run changed.":"Review submitted.":y.hasFeedback?"Ready to send for corrections.":y.pending?`${y.pending} left to review`:!n.inventoryConfirmed?"Confirm the map is complete.":"Ready to continue."),R=t?ki(t):null,Si=t?.preview.kind==="image"&&!R?.code,D=!!(t?.context&&ii==="context"),Fi=t&&(D?t.context?.kind!=="image":t.preview.kind==="page"),Ci=D&&t?.context?t.context.url:t?.preview.url,qo=R?.code?`${R.label} · ${R.captured?"captured from code":"live preview"}`:t?.material?`${t.material.alpha==="transparent"?"Transparent":t.material.alpha==="opaque"?"Opaque":"Transparency unverified"} ${t.material.format}`:"Raster · transparency unverified";r.innerHTML=`
+
${a.preview?'Historical hotel artwork for testing this interface. Decisions stay in this preview; no run is changed.
':""}
- ${v?`${u.pending} ${u.pending===1?"component":"components"} to review ${se}
${u.pending?`${K("next")} `:""}${v.removed.length?`Removed from the map ${v.removed.map((i)=>x(i.name)).join(" · ")}. Confirm these omissions are intentional before accepting the map.
`:""} `:""}
- Approved comp Component ${Ni}
-
+ ${j?`
${y.pending} ${y.pending===1?"component":"components"} to review ${ko}
${y.pending?`${E("next")} `:""}${j.removed.length?`Removed from the map ${j.removed.map((i)=>x(i.name)).join(" · ")}. Confirm these omissions are intentional before accepting the map.
`:""}`:""}
+
Approved comp Component ${Zi}
+
-
Approved comp ${K(V?"close":"mark")}${V?"Cancel":"Mark missing"}
-
-
- ${Ji&&!M?`
`:""}
- ${e.components.map((i,r)=>{let f=pi(i);return`
${f.kind==="approved"?ci:f.kind==="feedback"?ai:""}${r+1} `}).join("")}
- ${n.missing.map((i,r)=>`
${ai}${e.components.length+r+1} `).join("")}
+
Approved comp ${E(_?"close":"mark")}${_?"Cancel":"Mark missing"}
+
+
+ ${Wi&&!J?`
`:""}
+ ${o.components.map((i,e)=>{let l=hi(i);return`
${l.kind==="approved"?Mi:l.kind==="feedback"?gi:""}${e+1} `}).join("")}
+ ${n.missing.map((i,e)=>`
${gi}${o.components.length+e+1} `).join("")}
-
# To review${ai} Feedback ready${ci} Approved
- ${V?'
Draw around the missing piece.Add an adjustable box
':""}
+
# To review${gi} Feedback ready${Mi} Approved
+ ${_?'
Draw around the missing piece.Add an adjustable box
':""}
-
${M?"Review summary":`${Ni} ${x(l?.name??w?.name??"Component")}`}
- ${M?`
Components reviewed. ${u.approved} approved · ${u.revisions} flagged for repair${n.missing.length?` · ${n.missing.length} missing`:""}
Check your decisions below. ${u.hasFeedback?"Send feedback when you’re ready.":"Confirm nothing is missing, then approve and continue."}
${e.components.map((i)=>{let r=n.decisions[i.id];return`${x(i.name)} ${r?.action==="revise"?"Needs work":"Approved"} ${r?.action==="revise"?`${x(r.feedback||"No note — agent will diagnose.")} `:""} `}).join("")}${n.missing.map((i)=>`${x(i.name)} Missing ${x(i.feedback)} `).join("")}
${Gi}
`:l?`${K(Z.code?"code":"image")}
${x(ce)} ${t?.material?`${t.material.width} × ${t.material.height} px`:""} ${t?.preview.kind==="image"?`
${K("external")} `:""}
- ${v?`
- ${q?.prior?.action==="revise"?`
Previous feedback Round ${q.feedbackRound} Needs work
${x(q.prior.feedback||"No written feedback was supplied.")} ${q.prior.split?"Requested: split into separately reviewable components.
":""}`:q?.carried?'
Unchanged · approval kept
':""}
- ${q?.change?.kind==="changed"?`
${q.change.files.length?`${q.change.files.length} changed ${q.change.files.length===1?"file":"files"}`:q.change.reasons.includes("region")?"Region changed":C?.note!==l.note?"Description changed · files unchanged":"Component definition changed · files unchanged"} ${q.change.files.length?`${q.change.files.map((i)=>`${x(i)} `).join("")} `:""}${C&&C.note!==l.note?`Previous description ${x(C.note)} Current description ${x(l.note)} `:""}`:""}
+
${J?"Review summary":`${Zi} ${x(d?.name??v?.name??"Component")}`}
+ ${J?`
Components reviewed. ${y.approved} approved · ${y.revisions} flagged for repair${n.missing.length?` · ${n.missing.length} missing`:""}
Check your decisions below. ${y.hasFeedback?"Send feedback when you’re ready.":"Confirm nothing is missing, then approve and continue."}
${o.components.map((i)=>{let e=n.decisions[i.id];return`${x(i.name)} ${e?.action==="revise"?"Needs work":"Approved"} ${e?.action==="revise"?`${x(e.feedback||"No note — agent will diagnose.")} `:""} `}).join("")}${n.missing.map((i)=>`${x(i.name)} Missing ${x(i.feedback)} `).join("")}
${Ai}
`:d?`
${E(R.code?"code":"image")}
${x(qo)} ${t?.material?`${t.material.width} × ${t.material.height} px`:""} ${t?.preview.kind==="image"?`
${E("external")} `:""}
+ ${j?`
+ ${N?.prior?.action==="revise"?`
Previous feedback Round ${N.feedbackRound} Needs work
${x(N.prior.feedback||"No written feedback was supplied.")} ${N.prior.split?"Requested: split into separately reviewable components.
":""}`:N?.carried?'
Unchanged · approval kept
':""}
+ ${N?.change?.kind==="changed"?`
${N.change.files.length?`${N.change.files.length} changed ${N.change.files.length===1?"file":"files"}`:N.change.reasons.includes("region")?"Region changed":ei?.note!==d.note?"Description changed · files unchanged":"Component definition changed · files unchanged"} ${N.change.files.length?`${N.change.files.map((i)=>`${x(i)} `).join("")} `:""}${ei&&ei.note!==d.note?`Previous description ${x(ei.note)} Current description ${x(d.note)} `:""}`:""}
`:""}
- ${C?`
Current · round ${e.round} Previous · round ${v.packet.round}
`:""}
-
${K("zoom")}${[["fit","Fit"],["1","100%"],["2","200%"],["4","400%"]].map(([i,r])=>`${r} `).join("")} Overlay comp
+ ${ei?`
Current · round ${o.round} Previous · round ${j.packet.round}
`:""}
+
${x(t.name)} ${E("zoom")}${[["fit","Fit"],["1","100%"],["2","200%"],["4","400%"]].map(([i,e])=>`${e} `).join("")} Overlay comp${E($?"compact":"expand")}
-
${X?`Comp · Round ${v.packet.round}`:"In the comp"}
-
${X?`Previous · Round ${v.packet.round}`:v?`Current · Round ${e.round}`:I?"In the page":Z.caption} ${!Ti?`
`:`
`}${Y?`
`:""}
+
${A?`Comp · Round ${j.packet.round}`:"In the comp"}
+
${A?`Previous · Round ${j.packet.round}`:j?`Current · Round ${o.round}`:D?"In the page":R.caption} ${!Fi?`
`:`
`}${B?`
`:""}
- ${Li?`
${t.context?`
Asset only In page
`:""}
`:""}
-
${x(t?.context?.layering??"Layer placement not recorded.")}
+ ${Si?`
${t.context?`
Asset only In page
`:""}
`:""}
+
${x(t?.context?.layering??"Layer placement not recorded.")}
${x(t.note)}
-
`:w?`
This piece will be added to the unresolved inventory.
Name What is missing?${x(w.feedback)} ${["x","y","w","h"].map((i)=>`${{x:"Left",y:"Top",w:"Width",h:"Height"}[i]} % `).join("")}
Remove this mark `:"No components supplied.
"}
+
`:v?`
This piece will be added to the unresolved inventory.
Name What is missing?${x(v.feedback)} ${["x","y","w","h"].map((i)=>`${{x:"Left",y:"Top",w:"Width",h:"Height"}[i]} % `).join("")}
Remove this mark `:"No components supplied.
"}
- Components Needs attention ${ge} Approved ${u.approved} All ${e.components.length+n.missing.length}
${K(U?"compact":"expand")} ${K(k?"hideTray":"showTray")}
- ${_i.map((i)=>{let r=e.components.indexOf(i),f=pi(i);return`
${i.thumbnail?`${i.thumbnail.box?` `:` `} `:""}${f.kind==="approved"?ci:f.kind==="feedback"?ai:""}${r+1}${K(gi(i).code?"code":"image")}${x(gi(i).label)} ${x(i.name)} ${x(f.label)} `}).join("")}${(g==="approved"?[]:n.missing).map((i,r)=>`
${e.components.length+r+1} ${x(i.name)} Missing `).join("")}${!_i.length&&(g==="approved"||!n.missing.length)?`
${g==="attention"?"Every component is approved. Confirm the map is complete, then continue.":"No components approved yet."}
`:""}
-
- `,X)o.querySelectorAll(".decisions button,#feedback,#split,#save-feedback,#cancel-feedback,#undo-decision,#approve-rest,#submit,#inventory-confirm").forEach((i)=>i.disabled=!0);if(o.querySelector(".inspection-content").scrollTop=te,Q||J)o.querySelectorAll(".decisions button,#save-feedback,#cancel-feedback,#undo-decision,#approve-rest,#mark,#inventory-confirm,#missing-name,#missing-feedback,#feedback,#split,#remove-missing,[data-coordinate]").forEach((i)=>i.disabled=!0);if(o.querySelector(".inventory").scrollLeft=mi,!xi&&k)Array.from(o.querySelectorAll(".inventory [data-select]")).find((i)=>i.dataset.select===d)?.scrollIntoView({block:"nearest",inline:"nearest"});if(b)o.getElementById(b)?.focus({preventScroll:!0});else if($)Array.from(o.querySelectorAll(".item[data-select]")).find((i)=>i.dataset.select===$)?.focus({preventScroll:!0});let m=(i,r)=>o.querySelector(`#${i}`)?.addEventListener("click",r);o.querySelectorAll("[data-select]").forEach((i)=>i.onclick=()=>{if(V)return;M=!1,d=i.dataset.select,y="component",Y=!1,_="fit",R="isolated",N=!1,h()}),m("previous-round",()=>{N=!0,h()}),m("current-round",()=>{N=!1,h()}),m("review-changes",()=>{let i=c().filter((j)=>pi(j).kind==="pending"),r=i.findIndex((j)=>j.id===d),f=i[(r+1)%i.length];if(f)M=!1,d=f.id,y="component",g="attention",N=!1,_="fit",Y=!1,R="isolated",h()}),o.querySelectorAll("[data-filter]").forEach((i)=>i.onclick=()=>{M=!1,g=i.dataset.filter;let r=c().filter((j)=>g==="all"||pi(j).kind==="approved"===(g==="approved"));if(!(g!=="approved"&&n.missing.some((j)=>j.id===d))&&!r.some((j)=>j.id===d)&&r.length)d=r[0].id,y="component",N=!1,_="fit",Y=!1,R="isolated";h()}),m("approve",()=>bi("approve")),m("revise",pe),m("clear",()=>{if(l)delete n.decisions[l.id],delete H[l.id];W=null,h()}),m("cancel-feedback",()=>{if(l)delete H[l.id];if(F)k=!0,F=!1;h(),ni("revise")}),o.querySelector("#feedback-form")?.addEventListener("submit",(i)=>{i.preventDefault(),bi("revise")}),o.querySelector("#feedback")?.addEventListener("keydown",(i)=>{let r=i;if(r.key==="Enter"&&(r.metaKey||r.ctrlKey)&&!r.isComposing)r.preventDefault(),r.stopPropagation(),bi("revise")}),m("undo-decision",()=>{if(!W||J||Q)return;let i=W;if(i.previous)n.decisions[i.id]=i.previous;else delete n.decisions[i.id];delete H[i.id],d=i.id,M=!1,N=!1,y="component",g="all",W=null,h(),ni("approve")}),m("overlay",()=>{Y=!Y,h()}),m("isolated",()=>{R="isolated",h()}),m("context",()=>{R="context",h()}),o.querySelector("#zoom")?.addEventListener("change",(i)=>{let r=i.target.value;_=r==="fit"?"fit":Number(r),h()}),m("background-checker",()=>{ei="checker",h()}),m("background-page",()=>{ei="page",h()}),m("show-all",()=>{U=!U,k=!0,h()}),m("toggle-tray",()=>{F=!1,k=!k,h()}),m("show-comp",()=>{y="comp",h()}),m("show-component",()=>{y="component",h()}),m("mark",()=>{V=!V,h()}),m("add-box",()=>qi({x:0.35,y:0.35,w:0.2,h:0.2})),m("remove-missing",()=>{n.missing=n.missing.filter((i)=>i.id!==d),d=e.components[0]?.id,h()}),m("approve-rest",()=>{if(P)return;n=Ci(e,n),W=null,M=!0,y="component",h(),ni("review-summary")}),o.querySelector("#inventory-confirm")?.addEventListener("change",(i)=>{n.inventoryConfirmed=i.target.checked,h()}),o.querySelector("#feedback")?.addEventListener("input",(i)=>{if(l&&H[l.id])H[l.id].feedback=i.target.value}),o.querySelector("#split")?.addEventListener("change",(i)=>{if(l&&H[l.id])H[l.id].split=i.target.checked}),o.querySelector("#missing-name")?.addEventListener("input",(i)=>{if(w)w.name=i.target.value;let r=o.querySelector("#submit");if(r)r.disabled=P||J||Q||!hi(e,n).canSubmit}),o.querySelector("#missing-feedback")?.addEventListener("input",(i)=>{if(w)w.feedback=i.target.value}),o.querySelectorAll("[data-coordinate]").forEach((i)=>i.addEventListener("change",()=>{if(!w)return;let r=i.dataset.coordinate,f=Number(i.value)/100;if(Number.isFinite(f))w.box[r]=Math.max(r==="w"||r==="h"?0.001:0,Math.min(1,f));w.box.w=Math.min(w.box.w,1-w.box.x),w.box.h=Math.min(w.box.h,1-w.box.y),h()})),m("submit",async()=>{if(P||J||Q)return;J=!0,si="",h();try{await a.onSubmit(Fi(e,n)),Q=!0}catch(i){si=i instanceof Error?i.message:"Could not save. Try again."}finally{J=!1,h()}});let A=o.querySelector(".map");function Yi(i){let r=A.getBoundingClientRect();return{x:Math.max(0,Math.min(1,(i.clientX-r.left)/r.width)),y:Math.max(0,Math.min(1,(i.clientY-r.top)/r.height))}}A.addEventListener("pointerdown",(i)=>{if(!V)return;L=Yi(i),A.setPointerCapture(i.pointerId),i.preventDefault()}),A.addEventListener("pointermove",(i)=>{if(!L)return;let r=Yi(i);T={x:Math.min(L.x,r.x),y:Math.min(L.y,r.y),w:Math.abs(r.x-L.x),h:Math.abs(r.y-L.y)};let f=o.querySelector(".draw-box");f.hidden=!1,f.style.cssText=Mi(T)}),A.addEventListener("pointerup",()=>{if(T&&T.w>0.01&&T.h>0.01)qi(T);else L=null,T=null}),A.addEventListener("pointercancel",()=>{L=null,T=null,h()});let Ui=o.querySelector(".output"),li=o.querySelector("iframe"),Zi=o.querySelector(".workbench"),ii=o.querySelector(".inspection-content"),vi=o.querySelector(".inspector");function yi(){if(!ii||!vi)return;vi.dataset.scrollAbove=String(ii.scrollTop>1),vi.dataset.scrollBelow=String(ii.scrollHeight-ii.clientHeight-ii.scrollTop>1)}ii?.addEventListener("scroll",yi,{passive:!0});function Ii(){let i=o.querySelector(".map-space");if(i&&i.clientWidth&&i.clientHeight){let z=ki(e.comp.width,e.comp.height,Math.max(1,i.clientWidth-32),Math.max(1,i.clientHeight-32),"fit");A.style.width=`${z.width}px`,A.style.height=`${z.height}px`}let r=o.querySelector(".inspection-content"),f=Array.from(o.querySelectorAll(".pan-viewport"));if(r?.clientHeight)f.forEach((z)=>z.style.height=`${Math.min(248,Math.max(100,r.clientHeight-40))}px`);if(t&&f.length){let z=ki(t.box.w*E.comp.width,t.box.h*E.comp.height,Math.min(...f.map((G)=>G.clientWidth)),Math.min(...f.map((G)=>G.clientHeight)),_);o.querySelectorAll(".crop-stage").forEach((G)=>{G.style.width=`${z.width}px`,G.style.height=`${z.height}px`})}if(Ui&&li&&t){let z=Ui.clientWidth/(t.box.w*E.comp.width);li.style.transform=`scale(${z})`,li.style.left=`${-t.box.x*E.comp.width*z}px`,li.style.top=`${-t.box.y*E.comp.height*z}px`}let j=Zi.getBoundingClientRect(),Bi=o.querySelector(".region"),Di=o.querySelector(".number"),Oi=o.querySelector(".connector path");if(Bi&&Di&&Oi){let z=Bi.getBoundingClientRect(),G=Di.getBoundingClientRect(),be=z.right-j.left,me=z.top+z.height/2-j.top,Ri=G.left-j.left-8,we=G.top+G.height/2-j.top;Oi.setAttribute("d",`M ${be} ${me} H ${Ri-14} V ${we} H ${Ri}`)}}oi=new ResizeObserver(()=>{Ii(),yi()}),oi.observe(Zi);let Ai=o.querySelector(".inspection-content");if(Ai)oi.observe(Ai);Ii();let zi=Array.from(o.querySelectorAll(".pan-viewport"));if(zi.forEach((i)=>{i.scrollLeft=le,i.scrollTop=de}),zi.forEach((i)=>i.addEventListener("scroll",()=>{for(let r of zi)if(r!==i){if(r.scrollLeft!==i.scrollLeft)r.scrollLeft=i.scrollLeft;if(r.scrollTop!==i.scrollTop)r.scrollTop=i.scrollTop}})),ae&&window.matchMedia("(max-width:800px)").matches){let i=o.querySelector(".inspection-content"),r=o.querySelector(".compare");if(i&&r)i.scrollTop+=r.getBoundingClientRect().top-i.getBoundingClientRect().top}yi(),window.scrollTo(ri,ti)}return h(),{destroy(){oi?.disconnect(),o.replaceChildren()},getDraft(){return structuredClone(n)}}}async function ye(){let p=document.getElementById("review"),e=await fetch("/packet",{cache:"no-store"});if(!e.ok)throw Error("The review packet could not be loaded. Reload to retry.");let a=await e.json();if(a.sourceStatus){let o=document.createElement("p");o.textContent=a.sourceStatus,p.before(o)}oe(p,a.packet,{initialDraft:a.draft,history:a.history,completed:!!a.receipt,onSubmit:async(o)=>{let n=await fetch("/decision",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)}),c=await n.json();if(!n.ok)throw Error(c.error??"The review could not be saved. Try again.")}})}ye().catch((p)=>{let e=document.createElement("p");e.textContent=p instanceof Error?p.message:String(p),document.getElementById("review")?.replaceChildren(e)});})();
+ Components Needs attention ${zo} Approved ${y.approved} All ${o.components.length+n.missing.length}
${E(O?"compact":"expand")} ${E(M?"hideTray":"showTray")}
+ ${Di.map((i)=>{let e=o.components.indexOf(i),l=hi(i);return`
${i.thumbnail?`${i.thumbnail.box?` `:` `} `:""}${l.kind==="approved"?Mi:l.kind==="feedback"?gi:""}${e+1}${E(ki(i).code?"code":"image")}${x(ki(i).label)} ${x(i.name)} ${x(l.label)} `}).join("")}${(f==="approved"?[]:n.missing).map((i,e)=>`
${o.components.length+e+1} ${x(i.name)} Missing `).join("")}${!Di.length&&(f==="approved"||!n.missing.length)?`
${f==="attention"?"Every component is approved. Confirm the map is complete, then continue.":"No components approved yet."}
`:""}
+
+ `;let H=r.querySelector("#comparison-dialog"),K=r.querySelector(".comparison-panel"),ri=r.querySelector(".comparison-slot");if($&&K&&ri)ri.style.height=`${bo}px`,H.append(K),H.showModal();if(A)r.querySelectorAll(".decisions button,#feedback,#split,#save-feedback,#cancel-feedback,#undo-decision,#approve-rest,#submit,#inventory-confirm").forEach((i)=>i.disabled=!0);if(r.querySelector(".inspection-content").scrollTop=wo,Y||L)r.querySelectorAll(".decisions button,#save-feedback,#cancel-feedback,#undo-decision,#approve-rest,#mark,#inventory-confirm,#missing-name,#missing-feedback,#feedback,#split,#remove-missing,[data-coordinate]").forEach((i)=>i.disabled=!0);if(r.querySelector(".inventory").scrollLeft=Vi,!si&&M)Array.from(r.querySelectorAll(".inventory [data-select]")).find((i)=>i.dataset.select===h)?.scrollIntoView({block:"nearest",inline:"nearest"});if(w)r.getElementById(w)?.focus({preventScroll:!0});else if(X)Array.from(r.querySelectorAll(".item[data-select]")).find((i)=>i.dataset.select===X)?.focus({preventScroll:!0});let b=(i,e)=>r.querySelector(`#${i}`)?.addEventListener("click",e);r.querySelectorAll("[data-select]").forEach((i)=>i.onclick=()=>{if(_)return;J=!1,h=i.dataset.select,q="component",B=!1,W="fit",ii="isolated",T=!1,g()}),b("previous-round",()=>{T=!0,g()}),b("current-round",()=>{T=!1,g()}),b("review-changes",()=>{let i=c().filter((u)=>hi(u).kind==="pending"),e=i.findIndex((u)=>u.id===h),l=i[(e+1)%i.length];if(l)J=!1,h=l.id,q="component",f="attention",T=!1,W="fit",B=!1,ii="isolated",g()}),r.querySelectorAll("[data-filter]").forEach((i)=>i.onclick=()=>{J=!1,f=i.dataset.filter;let e=c().filter((u)=>f==="all"||hi(u).kind==="approved"===(f==="approved"));if(!(f!=="approved"&&n.missing.some((u)=>u.id===h))&&!e.some((u)=>u.id===h)&&e.length)h=e[0].id,q="component",T=!1,W="fit",B=!1,ii="isolated";g()}),b("approve",()=>Hi("approve")),b("revise",so),b("clear",()=>{if(d)delete n.decisions[d.id],delete V[d.id];U=null,g()}),b("cancel-feedback",()=>{if(d)delete V[d.id];if(ni)M=!0,ni=!1;g(),li("revise")}),r.querySelector("#feedback-form")?.addEventListener("submit",(i)=>{i.preventDefault(),Hi("revise")}),r.querySelector("#feedback")?.addEventListener("keydown",(i)=>{let e=i;if(e.key==="Enter"&&(e.metaKey||e.ctrlKey)&&!e.isComposing)e.preventDefault(),e.stopPropagation(),Hi("revise")}),b("undo-decision",()=>{if(!U||L||Y)return;let i=U;if(i.previous)n.decisions[i.id]=i.previous;else delete n.decisions[i.id];delete V[i.id],h=i.id,J=!1,T=!1,q="component",f="all",U=null,g(),li("approve")}),b("overlay",()=>{B=!B,g()}),b("isolated",()=>{ii="isolated",g()}),b("context",()=>{ii="context",g()}),r.querySelector("#zoom")?.addEventListener("change",(i)=>{let e=i.target.value;W=e==="fit"?"fit":Number(e),g()}),b("background-checker",()=>{xi="checker",g()}),b("background-page",()=>{xi="page",g()}),b("show-all",()=>{O=!O,M=!0,g()}),b("toggle-tray",()=>{ni=!1,M=!M,g()}),b("show-comp",()=>{q="comp",g()}),b("show-component",()=>{q="component",g()}),b("mark",()=>{_=!_,g()}),b("add-box",()=>Xi({x:0.35,y:0.35,w:0.2,h:0.2})),b("remove-missing",()=>{n.missing=n.missing.filter((i)=>i.id!==h),h=o.components[0]?.id,g()}),b("approve-rest",()=>{if(ai)return;n=po(o,n),U=null,J=!0,q="component",g(),li("review-summary")}),r.querySelector("#inventory-confirm")?.addEventListener("change",(i)=>{n.inventoryConfirmed=i.target.checked,g()}),r.querySelector("#feedback")?.addEventListener("input",(i)=>{if(d&&V[d.id])V[d.id].feedback=i.target.value}),r.querySelector("#split")?.addEventListener("change",(i)=>{if(d&&V[d.id])V[d.id].split=i.target.checked}),r.querySelector("#missing-name")?.addEventListener("input",(i)=>{if(v)v.name=i.target.value;let e=r.querySelector("#submit");if(e)e.disabled=ai||L||Y||!zi(o,n).canSubmit}),r.querySelector("#missing-feedback")?.addEventListener("input",(i)=>{if(v)v.feedback=i.target.value}),r.querySelectorAll("[data-coordinate]").forEach((i)=>i.addEventListener("change",()=>{if(!v)return;let e=i.dataset.coordinate,l=Number(i.value)/100;if(Number.isFinite(l))v.box[e]=Math.max(e==="w"||e==="h"?0.001:0,Math.min(1,l));v.box.w=Math.min(v.box.w,1-v.box.x),v.box.h=Math.min(v.box.h,1-v.box.y),g()})),b("submit",async()=>{if(ai||L||Y)return;L=!0,ji="",g();try{await a.onSubmit(ao(o,n)),Y=!0}catch(i){ji=i instanceof Error?i.message:"Could not save. Try again."}finally{L=!1,g()}});let S=r.querySelector(".map");function Pi(i){let e=S.getBoundingClientRect();return{x:Math.max(0,Math.min(1,(i.clientX-e.left)/e.width)),y:Math.max(0,Math.min(1,(i.clientY-e.top)/e.height))}}S.addEventListener("pointerdown",(i)=>{if(!_)return;Z=Pi(i),S.setPointerCapture(i.pointerId),i.preventDefault()}),S.addEventListener("pointermove",(i)=>{if(!Z)return;let e=Pi(i);I={x:Math.min(Z.x,e.x),y:Math.min(Z.y,e.y),w:Math.abs(e.x-Z.x),h:Math.abs(e.y-Z.y)};let l=r.querySelector(".draw-box");l.hidden=!1,l.style.cssText=Ti(I)}),S.addEventListener("pointerup",()=>{if(I&&I.w>0.01&&I.h>0.01)Xi(I);else Z=null,I=null}),S.addEventListener("pointercancel",()=>{Z=null,I=null,g()});let io=r.querySelector(".output"),ci=r.querySelector("iframe"),oo=r.querySelector(".workbench"),ti=r.querySelector(".inspection-content"),Ni=r.querySelector(".inspector");function $i(){if(!ti||!Ni)return;Ni.dataset.scrollAbove=String(ti.scrollTop>1),Ni.dataset.scrollBelow=String(ti.scrollHeight-ti.clientHeight-ti.scrollTop>1)}ti?.addEventListener("scroll",$i,{passive:!0});function wi(){let i=r.querySelector(".map-space");if(i&&i.clientWidth&&i.clientHeight){let m=Ki(o.comp.width,o.comp.height,Math.max(1,i.clientWidth-32),Math.max(1,i.clientHeight-32),"fit");S.style.width=`${m.width}px`,S.style.height=`${m.height}px`}let e=r.querySelector(".inspection-content"),l=Array.from(r.querySelectorAll(".pan-viewport"));if(e?.clientHeight){let m=$?Math.max(100,H.clientHeight-(K?.querySelector(".compare-toolbar")?.clientHeight??0)-(K?.querySelector(".expanded-title")?.clientHeight??0)-(K?.querySelector(".view-controls")?.clientHeight??0)-112):Math.min(248,Math.max(100,e.clientHeight-40));l.forEach((z)=>z.style.height=`${m}px`)}if(t&&l.length){let m=Ki(t.box.w*G.comp.width,t.box.h*G.comp.height,Math.min(...l.map((z)=>z.clientWidth)),Math.min(...l.map((z)=>z.clientHeight)),W);r.querySelectorAll(".crop-stage").forEach((z)=>{z.style.width=`${m.width}px`,z.style.height=`${m.height}px`})}if(l.forEach((m)=>{let z=m.scrollWidth>m.clientWidth+1||m.scrollHeight>m.clientHeight+1;m.classList.toggle("pannable",z),m.title=z?"Move your pointer to pan. You can also scroll, swipe, or use arrow keys.":""}),io&&ci&&t){let m=io.clientWidth/(t.box.w*G.comp.width);ci.style.transform=`scale(${m})`,ci.style.left=`${-t.box.x*G.comp.width*m}px`,ci.style.top=`${-t.box.y*G.comp.height*m}px`}let u=oo.getBoundingClientRect(),Q=r.querySelector(".region"),bi=r.querySelector(".number"),ui=r.querySelector(".connector path");if(Q&&bi&&ui){let m=Q.getBoundingClientRect(),z=bi.getBoundingClientRect(),k=m.right-u.left,vi=m.top+m.height/2-u.top,ro=z.left-u.left-8,Mo=z.top+z.height/2-u.top;ui.setAttribute("d",`M ${k} ${vi} H ${ro-14} V ${Mo} H ${ro}`)}}function Gi(i){if(!K||!ri||i===$)return;let e=r.querySelector("#expand-comparison"),l=($?H:K).getBoundingClientRect(),u=window.matchMedia("(prefers-reduced-motion: reduce)").matches,Q=K.querySelector(".pan-viewport"),bi=Q?Q.scrollLeft/Math.max(1,Q.scrollWidth-Q.clientWidth):0,ui=Q?Q.scrollTop/Math.max(1,Q.scrollHeight-Q.clientHeight):0,m=()=>K.querySelectorAll(".pan-viewport").forEach((k)=>{k.scrollLeft=bi*Math.max(0,k.scrollWidth-k.clientWidth),k.scrollTop=ui*Math.max(0,k.scrollHeight-k.clientHeight)}),z=()=>{ri.append(K),H.close(),ri.style.height="",$=!1,e.innerHTML=E("expand"),e.setAttribute("aria-label","Enlarge comparison"),e.title="Enlarge comparison",e.setAttribute("aria-expanded","false"),wi(),m(),e.focus({preventScroll:!0})};if(i){ri.style.height=`${l.height}px`,H.append(K),H.showModal(),$=!0,e.innerHTML=E("compact"),e.setAttribute("aria-label","Restore comparison"),e.title="Restore comparison (Esc)",e.setAttribute("aria-expanded","true"),wi(),m(),e.focus({preventScroll:!0});let k=H.getBoundingClientRect();if(!u)H.animate([{transform:`translate(${l.x-k.x}px,${l.y-k.y}px) scale(${l.width/k.width},${l.height/k.height})`,opacity:0.6},{transform:"none",opacity:1}],{duration:240,easing:"cubic-bezier(.2,.8,.2,1)"})}else{if(e.disabled)return;let k=ri.getBoundingClientRect();if(u){z();return}e.disabled=!0;let vi=H.animate([{transform:"none",opacity:1},{transform:`translate(${k.x-l.x}px,${k.y-l.y}px) scale(${k.width/l.width},${k.height/l.height})`,opacity:0.6}],{duration:200,easing:"cubic-bezier(.4,0,.2,1)",fill:"forwards"});vi.finished.then(()=>{if(H.isConnected)vi.cancel(),e.disabled=!1,z()}).catch(()=>{})}}b("expand-comparison",()=>Gi(!$)),H.addEventListener("cancel",(i)=>{i.preventDefault(),i.stopPropagation(),Gi(!1)}),H.addEventListener("keydown",(i)=>{if(i.key==="Escape")i.preventDefault(),i.stopPropagation(),Gi(!1)}),pi=new ResizeObserver(()=>{wi(),$i()}),pi.observe(oo),pi.observe(H);let eo=r.querySelector(".inspection-content");if(eo)pi.observe(eo);wi();let mi=Array.from(r.querySelectorAll(".pan-viewport"));if(mi.forEach((i)=>{i.scrollLeft=uo,i.scrollTop=vo}),mi.forEach((i)=>{i.querySelectorAll("img").forEach((e)=>e.draggable=!1),i.addEventListener("pointermove",(e)=>{if(e.pointerType!=="mouse"||e.buttons||!i.classList.contains("pannable"))return;let l=i.getBoundingClientRect();i.scrollLeft=Qi(e.clientX,l.left,i.clientWidth,i.scrollWidth),i.scrollTop=Qi(e.clientY,l.top,i.clientHeight,i.scrollHeight)}),i.addEventListener("keydown",(e)=>{let u={ArrowLeft:[-48,0],ArrowRight:[48,0],ArrowUp:[0,-48],ArrowDown:[0,48]}[e.key];if(!u)return;e.preventDefault(),e.stopPropagation(),i.scrollLeft+=u[0],i.scrollTop+=u[1]})}),mi.forEach((i)=>i.addEventListener("scroll",()=>{for(let e of mi)if(e!==i){if(e.scrollLeft!==i.scrollLeft)e.scrollLeft=i.scrollLeft;if(e.scrollTop!==i.scrollTop)e.scrollTop=i.scrollTop}})),co&&window.matchMedia("(max-width:800px)").matches){let i=r.querySelector(".inspection-content"),e=r.querySelector(".compare");if(i&&e)i.scrollTop+=e.getBoundingClientRect().top-i.getBoundingClientRect().top}$i(),window.scrollTo(di,fi)}return g(),{destroy(){pi?.disconnect(),r.replaceChildren()},getDraft(){return structuredClone(n)}}}async function Eo(){let p=document.getElementById("review"),o=await fetch("/packet",{cache:"no-store"});if(!o.ok)throw Error("The review packet could not be loaded. Reload to retry.");let a=await o.json();if(a.sourceStatus){let r=document.createElement("p");r.textContent=a.sourceStatus,p.before(r)}ho(p,a.packet,{initialDraft:a.draft,history:a.history,completed:!!a.receipt,onSubmit:async(r)=>{let n=await fetch("/decision",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)}),c=await n.json();if(!n.ok)throw Error(c.error??"The review could not be saved. Try again.")}})}Eo().catch((p)=>{let o=document.createElement("p");o.textContent=p instanceof Error?p.message:String(p),document.getElementById("review")?.replaceChildren(o)});})();
diff --git a/ui/component-review/review.ts b/ui/component-review/review.ts
index 1bbcc79b8..b7f607091 100644
--- a/ui/component-review/review.ts
+++ b/ui/component-review/review.ts
@@ -1,5 +1,5 @@
import { componentPresentation, nextUnreviewed, approveRemaining, componentState, repairStatus, newDraft, submission, summarize, type Box, type Decision, type Draft, type ReviewPacket, type ReviewHistory } from './model';
-import { comparisonSize } from './viewport';
+import { comparisonSize, hoverPan } from './viewport';
import { styles } from './styles';
import { icon } from './icons';
@@ -28,6 +28,7 @@ export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, op
let lastDecision: {id: string; name: string; action: 'approve' | 'revise'; previous?: Decision} | null = null;
const shortcutLabel = /Mac|iPhone|iPad/.test(navigator.platform) ? '⌘Enter' : 'Ctrl+Enter';
let overlay = false;
+ let expandedComparison = false;
let showAll = false;
let trayOpen = true;
let restoreTrayAfterFeedback = false;
@@ -102,6 +103,7 @@ export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, op
renderedMobilePane=mobilePane;
const inspectorTop=keepInspector?(root.querySelector('.inspection-content')?.scrollTop??0):0;
const filesOpen=keepInspector&&(root.querySelector('.changed-files')?.open??false);
+ const oldComparisonHeight=root.querySelector('.comparison-slot')?.clientHeight ?? 200;
const oldPane=root.querySelector('.pan-viewport');
const retainPan=renderedSelection===selected&&renderedZoom===zoom;
const panLeft=retainPan?(oldPane?.scrollLeft??0):0, panTop=retainPan?(oldPane?.scrollTop??0):0;
@@ -171,13 +173,13 @@ export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, op
${repair?.change?.kind==='changed'?`${repair.change.files.length?`${repair.change.files.length} changed ${repair.change.files.length===1?'file':'files'}`:repair.change.reasons.includes('region')?'Region changed':priorComponent?.note!==c.note?'Description changed · files unchanged':'Component definition changed · files unchanged'} ${repair.change.files.length?`${repair.change.files.map(path=>`${esc(path)} `).join('')} `:''}${priorComponent&&priorComponent.note!==c.note?`Previous description ${esc(priorComponent.note)} Current description ${esc(c.note)} `:''} `:''}
`:''}
${priorComponent?`Current · round ${packet.round} Previous · round ${history!.packet.round}
`:''}
- ${icon('zoom')}${[['fit','Fit'],['1','100%'],['2','200%'],['4','400%']].map(([value,label])=>`${label} `).join('')} Overlay comp
+ ${esc(v!.name)} ${icon('zoom')}${[['fit','Fit'],['1','100%'],['2','200%'],['4','400%']].map(([value,label])=>`${label} `).join('')} Overlay comp${icon(expandedComparison?'compact':'expand')}
${viewingPrevious ? `Comp · Round ${history!.packet.round}` : 'In the comp'}
${viewingPrevious ? `Previous · Round ${history!.packet.round}` : history ? `Current · Round ${packet.round}` : useContext ? 'In the page' : presentation!.caption} ${!useFrame ? `
` : `
`}${overlay ? `
` : ''}
${isRaster ? `
${v!.context ? `
Asset only In page
` : ''}
` : ''}
-
${esc(v?.context?.layering ?? 'Layer placement not recorded.')}
+
${esc(v?.context?.layering ?? 'Layer placement not recorded.')}
${esc(v!.note)}