diff --git a/crates/context/assets/component-review.js b/crates/context/assets/component-review.js index 07a199f8e..5abe5ef1d 100644 --- a/crates/context/assets/component-review.js +++ b/crates/context/assets/component-review.js @@ -1,4 +1,4 @@ -(()=>{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=` +(()=>{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((s)=>o.decisions[s.id]?.revision===s.revision?o.decisions[s.id]:void 0),r=a.filter((s)=>s?.action==="approve").length,n=a.filter((s)=>s?.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((s)=>s.name.trim()&&Ho(s.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} @@ -34,13 +34,13 @@ .mobile-panes{position:relative;z-index:7;box-shadow:0 3px 6px #202b2510} /* Utility actions share a compact icon language; decisions retain explicit labels. */ .utility-icon{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0} -.review .icon-button{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:36px;height:36px;min-height:36px;padding:7px;text-decoration:none;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--muted)} -.review .icon-button:hover{background:#e5ebe4;border-color:#b9c8bd;color:var(--teal)} -.review .icon-button[aria-pressed=true]{background:#e5eee8;color:var(--teal)} -.review .icon-button:focus-visible{outline:2px solid var(--teal);outline-offset:2px} +:is(.review,.comparison-panel) .icon-button{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:36px;height:36px;min-height:36px;padding:7px;text-decoration:none;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--muted)} +:is(.review,.comparison-panel) .icon-button:hover{background:#e5ebe4;border-color:#b9c8bd;color:var(--teal)} +:is(.review,.comparison-panel) .icon-button[aria-pressed=true]{background:#e5eee8;color:var(--teal)} +:is(.review,.comparison-panel) .icon-button:focus-visible{outline:2px solid var(--teal);outline-offset:2px} .review .label-icon{display:inline-flex;align-items:center;justify-content:center;gap:7px} .review .tray-actions .utility-icon{width:18px;height:18px;stroke-width:1.6} -.material{align-items:center;margin-bottom:10px}.material .source-link{margin-left:auto;width:30px;height:30px;min-height:30px;padding:5px} +.material{align-items:center;margin-bottom:10px} .preview-round{justify-content:flex-end;margin-bottom:10px} .compare-toolbar{margin-bottom:10px} .view-controls{min-height:0}.view-controls>.background-options{margin-left:auto;gap:2px}.view-controls .swatch-button{display:flex;align-items:center;justify-content:center;min-width:32px;height:32px;padding:5px} @@ -101,7 +101,7 @@ .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}} -.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-panel{min-width:0}.comparison-panel .expanded-title{display:none}.comparison-panel .compare-toolbar{justify-content:flex-start}.comparison-panel .overlay-control{margin-left:0}.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} @@ -111,34 +111,46 @@ #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``}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"}.
`:"",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=`
+ +.comparison-panel .compare-toolbar{gap:8px;padding:4px 0;min-width:0} +.comparison-actions{display:flex;align-items:center;gap:2px;margin-left:auto;flex-shrink:0} +.comparison-panel .zoom-control{position:relative;display:block;flex-shrink:0} +.zoom-control select{appearance:none;-webkit-appearance:none;width:104px;height:36px;min-height:36px;padding:6px 30px 6px 32px;font-size:12px;line-height:1.4;border-color:var(--line);background:var(--paper);cursor:pointer} +.zoom-control>.utility-icon{position:absolute;top:50%;transform:translateY(-50%);pointer-events:none} +.zoom-control>.utility-icon:first-child{left:10px;width:15px;height:15px;color:var(--muted)} +.zoom-control>.utility-icon:last-child{right:10px;width:13px;height:13px;color:var(--muted)} +.comparison-panel .overlay-control{height:36px;min-height:36px;font-size:12px;padding:6px 10px;gap:6px} +.comparison-panel .overlay-control svg{width:18px;height:18px;stroke-width:1.6} +.comparison-actions .source-link{color:var(--muted)} +@media(forced-colors:active){.zoom-control select{appearance:auto;padding:6px 8px}.zoom-control>.utility-icon{display:none}} +`;var Vo={chevronDown:'',code:'',image:'',expand:'',compact:'',hideTray:'',showTray:'',next:'',mark:'',close:'',undo:'',external:'',zoom:''};function V(p){return``}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((f,m)=>F(f,n,a.history).priority-F(m,n,a.history).priority),h=c().find((f)=>F(f,n,a.history).kind!=="approved")?.id??o.components[0]?.id,s=a.completed?"all":"attention",Y=!1,_=!1,X=a.completed??!1,ji="",E={},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",Yi=q,W="fit",xi="checker",L=!1,ii="isolated",qi,_i="fit",Z=null,I=null,pi=null,Mi='',gi='',Li=(f)=>`left:${C(f.x)};top:${C(f.y)};width:${C(f.w)};height:${C(f.h)}`;function li(f){r.getElementById(f)?.focus({preventScroll:!0})}function so(f){let m=to(o,n,f);if(J=!m,m)h=m;if(q="component",L=!1,B=!1,W="fit",ii="isolated",s==="approved")s="attention";g(),li(J?"review-summary":E[h]?"feedback":"approve")}function Hi(f){if(_||X||L)return;let m=o.components.find((Vi)=>Vi.id===h);if(!m)return;let T=n.decisions[m.id],di=T?.revision===m.revision?T:void 0;U={id:m.id,name:m.name,action:f,previous:T?{...T}:void 0};let si=E[m.id]??di;if(n.decisions[m.id]={revision:m.revision,action:f,feedback:f==="revise"?si?.feedback??"":"",split:f==="revise"&&(si?.split??!1)},delete E[m.id],ni)M=!0,ni=!1;so(m.id)}function fo(){if(_||X||L)return;let f=o.components.find((di)=>di.id===h);if(!f)return;let m=n.decisions[f.id],T=m?.revision===f.revision?m:void 0;if(E[f.id]??={feedback:T?.feedback??"",split:T?.split??!1},M&&(r.querySelector(".workbench")?.clientHeight??0)<420)ni=!0,M=!1;J=!1,g(),li("feedback")}function Ti(f){let m=`missing-${crypto.randomUUID()}`;n.missing.push({id:m,name:"Missing component",feedback:"",box:f}),n.inventoryConfirmed=!1,J=!1,h=m,q="component",Y=!1,Z=null,I=null,g(),r.querySelector("#missing-name")?.focus()}function g(){pi?.disconnect();let f=r.activeElement,m=f?.id,T=f?.dataset.select,di=window.scrollX,si=window.scrollY,Vi=r.querySelector(".inventory")?.scrollLeft??0,fi=qi===h,co=q==="component"&&(!fi||Yi!==q);Yi=q;let mo=fi?r.querySelector(".inspection-content")?.scrollTop??0:0,wo=fi&&(r.querySelector(".changed-files")?.open??!1),bo=r.querySelector(".comparison-slot")?.clientHeight??200,Xi=r.querySelector(".pan-viewport"),Ui=qi===h&&_i===W,uo=Ui?Xi?.scrollLeft??0:0,vo=Ui?Xi?.scrollTop??0:0;qi=h,_i=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?E[d.id]:void 0,ai=Object.keys(E).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"}.
`:"",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=L&&!!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=(s==="attention"?c():o.components).filter((i)=>s==="all"||hi(i).kind==="approved"===(s==="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.":X?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=`

Review the components.

${x(o.title)} · Round ${o.round}

${a.preview?'Interactive preview':""}
${a.preview?'

Historical hotel artwork for testing this interface. Decisions stay in this preview; no run is changed.

':""} - ${j?`

${y.pending} ${y.pending===1?"component":"components"} to review${ko}

${y.pending?``:""}${j.removed.length?`
Removed from the map

${j.removed.map((i)=>x(i.name)).join(" · ")}. Confirm these omissions are intentional before accepting the map.

`:""}
`:""} + ${j?`

${y.pending} ${y.pending===1?"component":"components"} to review${ko}

${y.pending?``:""}${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

-
+

Approved comp

+
Approved composition for ${x(o.title)} - ${Wi&&!J?`
`:""} + ${Wi&&!J?`
`:""} ${o.components.map((i,e)=>{let l=hi(i);return``}).join("")} ${n.missing.map((i,e)=>``).join("")}
# To review${gi} Feedback ready${Mi} Approved
- ${_?'
Draw around the missing piece.
':""} + ${Y?'
Draw around the missing piece.
':""}

${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``}).join("")}${n.missing.map((i)=>``).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?`

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``}).join("")}${n.missing.map((i)=>``).join("")}
${Ai}
`:d?`
${V(R.code?"code":"image")}${x(qo)}${t?.material?`${t.material.width} × ${t.material.height} px`:""}
${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)}
`:""}
`:""} + ${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)}
`:""}
`:""}
`:""} ${ei?`
`:""} -

${x(t.name)}

+

${x(t.name)}

${t?.preview.kind==="image"?`${V("external")}`:""}
${A?`Comp · Round ${j.packet.round}`:"In the comp"}
Reference region for ${x(t.name)}
${A?`Previous · Round ${j.packet.round}`:j?`Current · Round ${o.round}`:D?"In the page":R.caption}
${!Fi?`Produced ${x(t.name)}`:``}${B?`Reference overlay`:""}
@@ -146,12 +158,12 @@ ${Si?`
${t.context?`
`:""}
`:""}

${x(t?.context?.layering??"Layer placement not recorded.")}

${x(t.note)}

-
${Ai}${A?'

Viewing the previous round. Return to Current to make a decision.

':""}
Your review Round ${o.round}${A?"

Return to Current to review this round.

":""}
${oi?``:""}
+
${Ai}${A?'

Viewing the previous round. Return to Current to make a decision.

':""}
Your review Round ${o.round}${A?"

Return to Current to review this round.

":""}
${oi?``:""}
${Ei?`
`:oi?.action==="revise"?`

${x(oi.feedback||"No note — agent will diagnose.")}

`:""}
`:v?`

This piece will be added to the unresolved inventory.

${["x","y","w","h"].map((i)=>``).join("")}
`:"

No components supplied.

"}
-

Components

-
${Di.map((i)=>{let e=o.components.indexOf(i),l=hi(i);return``}).join("")}${(f==="approved"?[]:n.missing).map((i,e)=>``).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."}

`:""}
-

${x(jo)}

-
`;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)});})(); +

Components

+
${Di.map((i)=>{let e=o.components.indexOf(i),l=hi(i);return``}).join("")}${(s==="approved"?[]:n.missing).map((i,e)=>``).join("")}${!Di.length&&(s==="approved"||!n.missing.length)?`

${s==="attention"?"Every component is approved. Confirm the map is complete, then continue.":"No components approved yet."}

`:""}
+

${x(jo)}

+ `;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=mo,X||_)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,!fi&&M)Array.from(r.querySelectorAll(".inventory [data-select]")).find((i)=>i.dataset.select===h)?.scrollIntoView({block:"nearest",inline:"nearest"});if(m)r.getElementById(m)?.focus({preventScroll:!0});else if(T)Array.from(r.querySelectorAll(".item[data-select]")).find((i)=>i.dataset.select===T)?.focus({preventScroll:!0});let b=(i,e)=>r.querySelector(`#${i}`)?.addEventListener("click",e);r.querySelectorAll("[data-select]").forEach((i)=>i.onclick=()=>{if(Y)return;J=!1,h=i.dataset.select,q="component",B=!1,W="fit",ii="isolated",L=!1,g()}),b("previous-round",()=>{L=!0,g()}),b("current-round",()=>{L=!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",s="attention",L=!1,W="fit",B=!1,ii="isolated",g()}),r.querySelectorAll("[data-filter]").forEach((i)=>i.onclick=()=>{J=!1,s=i.dataset.filter;let e=c().filter((u)=>s==="all"||hi(u).kind==="approved"===(s==="approved"));if(!(s!=="approved"&&n.missing.some((u)=>u.id===h))&&!e.some((u)=>u.id===h)&&e.length)h=e[0].id,q="component",L=!1,W="fit",B=!1,ii="isolated";g()}),b("approve",()=>Hi("approve")),b("revise",fo),b("clear",()=>{if(d)delete n.decisions[d.id],delete E[d.id];U=null,g()}),b("cancel-feedback",()=>{if(d)delete E[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||_||X)return;let i=U;if(i.previous)n.decisions[i.id]=i.previous;else delete n.decisions[i.id];delete E[i.id],h=i.id,J=!1,L=!1,q="component",s="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",()=>{Y=!Y,g()}),b("add-box",()=>Ti({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&&E[d.id])E[d.id].feedback=i.target.value}),r.querySelector("#split")?.addEventListener("change",(i)=>{if(d&&E[d.id])E[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||_||X||!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||_||X)return;_=!0,ji="",g();try{await a.onSubmit(ao(o,n)),X=!0}catch(i){ji=i instanceof Error?i.message:"Could not save. Try again."}finally{_=!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(!Y)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=Li(I)}),S.addEventListener("pointerup",()=>{if(I&&I.w>0.01&&I.h>0.01)Ti(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 mi(){let i=r.querySelector(".map-space");if(i&&i.clientWidth&&i.clientHeight){let w=Ki(o.comp.width,o.comp.height,Math.max(1,i.clientWidth-32),Math.max(1,i.clientHeight-32),"fit");S.style.width=`${w.width}px`,S.style.height=`${w.height}px`}let e=r.querySelector(".inspection-content"),l=Array.from(r.querySelectorAll(".pan-viewport"));if(e?.clientHeight){let w=$?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=`${w}px`)}if(t&&l.length){let w=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=`${w.width}px`,z.style.height=`${w.height}px`})}if(l.forEach((w)=>{let z=w.scrollWidth>w.clientWidth+1||w.scrollHeight>w.clientHeight+1;w.classList.toggle("pannable",z),w.title=z?"Move your pointer to pan. You can also scroll, swipe, or use arrow keys.":""}),io&&ci&&t){let w=io.clientWidth/(t.box.w*G.comp.width);ci.style.transform=`scale(${w})`,ci.style.left=`${-t.box.x*G.comp.width*w}px`,ci.style.top=`${-t.box.y*G.comp.height*w}px`}let u=oo.getBoundingClientRect(),Q=r.querySelector(".region"),bi=r.querySelector(".number"),ui=r.querySelector(".connector path");if(Q&&bi&&ui){let w=Q.getBoundingClientRect(),z=bi.getBoundingClientRect(),k=w.right-u.left,vi=w.top+w.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,w=()=>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=V("expand"),e.setAttribute("aria-label","Enlarge comparison"),e.title="Enlarge comparison",e.setAttribute("aria-expanded","false"),mi(),w(),e.focus({preventScroll:!0})};if(i){ri.style.height=`${l.height}px`,H.append(K),H.showModal(),$=!0,e.innerHTML=V("compact"),e.setAttribute("aria-label","Restore comparison"),e.title="Restore comparison (Esc)",e.setAttribute("aria-expanded","true"),mi(),w(),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(()=>{mi(),$i()}),pi.observe(oo),pi.observe(H);let eo=r.querySelector(".inspection-content");if(eo)pi.observe(eo);mi();let wi=Array.from(r.querySelectorAll(".pan-viewport"));if(wi.forEach((i)=>{i.scrollLeft=uo,i.scrollTop=vo}),wi.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]})}),wi.forEach((i)=>i.addEventListener("scroll",()=>{for(let e of wi)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,si)}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/app-layout.ts b/ui/component-review/app-layout.ts index bdc7a88d3..6bbfc80ba 100644 --- a/ui/component-review/app-layout.ts +++ b/ui/component-review/app-layout.ts @@ -35,13 +35,13 @@ export const appLayout = ` .mobile-panes{position:relative;z-index:7;box-shadow:0 3px 6px #202b2510} /* Utility actions share a compact icon language; decisions retain explicit labels. */ .utility-icon{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;flex-shrink:0} -.review .icon-button{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:36px;height:36px;min-height:36px;padding:7px;text-decoration:none;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--muted)} -.review .icon-button:hover{background:#e5ebe4;border-color:#b9c8bd;color:var(--teal)} -.review .icon-button[aria-pressed=true]{background:#e5eee8;color:var(--teal)} -.review .icon-button:focus-visible{outline:2px solid var(--teal);outline-offset:2px} +:is(.review,.comparison-panel) .icon-button{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:36px;height:36px;min-height:36px;padding:7px;text-decoration:none;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--muted)} +:is(.review,.comparison-panel) .icon-button:hover{background:#e5ebe4;border-color:#b9c8bd;color:var(--teal)} +:is(.review,.comparison-panel) .icon-button[aria-pressed=true]{background:#e5eee8;color:var(--teal)} +:is(.review,.comparison-panel) .icon-button:focus-visible{outline:2px solid var(--teal);outline-offset:2px} .review .label-icon{display:inline-flex;align-items:center;justify-content:center;gap:7px} .review .tray-actions .utility-icon{width:18px;height:18px;stroke-width:1.6} -.material{align-items:center;margin-bottom:10px}.material .source-link{margin-left:auto;width:30px;height:30px;min-height:30px;padding:5px} +.material{align-items:center;margin-bottom:10px} .preview-round{justify-content:flex-end;margin-bottom:10px} .compare-toolbar{margin-bottom:10px} .view-controls{min-height:0}.view-controls>.background-options{margin-left:auto;gap:2px}.view-controls .swatch-button{display:flex;align-items:center;justify-content:center;min-width:32px;height:32px;padding:5px} diff --git a/ui/component-review/icons.ts b/ui/component-review/icons.ts index 390660d06..3deedea0c 100644 --- a/ui/component-review/icons.ts +++ b/ui/component-review/icons.ts @@ -1,5 +1,6 @@ /** Small, consistent utility icons; callers supply accessible action labels. */ const paths = { + chevronDown: '', code: '', image: '', expand: '', diff --git a/ui/component-review/review.ts b/ui/component-review/review.ts index b7f607091..d1db67988 100644 --- a/ui/component-review/review.ts +++ b/ui/component-review/review.ts @@ -167,13 +167,13 @@ export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, op

${finished ? 'Review summary' : `${index} ${esc(c?.name ?? missing?.name ?? 'Component')}`}

- ${finished ? `

Components reviewed.

${stats.approved} approved · ${stats.revisions} flagged for repair${draft.missing.length ? ` · ${draft.missing.length} missing` : ''}

Check your decisions below. ${stats.hasFeedback ? 'Send feedback when you’re ready.' : 'Confirm nothing is missing, then approve and continue.'}

${packet.components.map(item=>{const decision=draft.decisions[item.id];return ``;}).join('')}${draft.missing.map(item=>``).join('')}
${notice}
` : c ? `
${icon(presentation!.code ? 'code' : 'image')}${esc(materialLabel)}${v?.material ? `${v.material.width} × ${v.material.height} px` : ''}${v?.preview.kind==='image'?`${icon('external')}`:''}
+ ${finished ? `

Components reviewed.

${stats.approved} approved · ${stats.revisions} flagged for repair${draft.missing.length ? ` · ${draft.missing.length} missing` : ''}

Check your decisions below. ${stats.hasFeedback ? 'Send feedback when you’re ready.' : 'Confirm nothing is missing, then approve and continue.'}

${packet.components.map(item=>{const decision=draft.decisions[item.id];return ``;}).join('')}${draft.missing.map(item=>``).join('')}
${notice}
` : c ? `
${icon(presentation!.code ? 'code' : 'image')}${esc(materialLabel)}${v?.material ? `${v.material.width} × ${v.material.height} px` : ''}
${history ? `
${repair?.prior?.action==='revise'?`

Previous feedback Round ${repair.feedbackRound}

Needs work

${esc(repair.prior.feedback || 'No written feedback was supplied.')}
${repair.prior.split?'

Requested: split into separately reviewable components.

':''}
`:repair?.carried?`

Unchanged · approval kept

`:''} ${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?`
`:''} -

${esc(v!.name)}

+

${esc(v!.name)}

${v?.preview.kind==='image'?`${icon('external')}`:''}
${viewingPrevious ? `Comp · Round ${history!.packet.round}` : 'In the comp'}
Reference region for ${esc(v!.name)}
${viewingPrevious ? `Previous · Round ${history!.packet.round}` : history ? `Current · Round ${packet.round}` : useContext ? 'In the page' : presentation!.caption}
${!useFrame ? `Produced ${esc(v!.name)}` : ``}${overlay ? `Reference overlay` : ''}
diff --git a/ui/component-review/styles.ts b/ui/component-review/styles.ts index cb1ef08a4..d94e97654 100644 --- a/ui/component-review/styles.ts +++ b/ui/component-review/styles.ts @@ -46,7 +46,7 @@ export const styles = ` .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}} -.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-panel{min-width:0}.comparison-panel .expanded-title{display:none}.comparison-panel .compare-toolbar{justify-content:flex-start}.comparison-panel .overlay-control{margin-left:0}.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} @@ -56,4 +56,16 @@ export const styles = ` #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}} + +.comparison-panel .compare-toolbar{gap:8px;padding:4px 0;min-width:0} +.comparison-actions{display:flex;align-items:center;gap:2px;margin-left:auto;flex-shrink:0} +.comparison-panel .zoom-control{position:relative;display:block;flex-shrink:0} +.zoom-control select{appearance:none;-webkit-appearance:none;width:104px;height:36px;min-height:36px;padding:6px 30px 6px 32px;font-size:12px;line-height:1.4;border-color:var(--line);background:var(--paper);cursor:pointer} +.zoom-control>.utility-icon{position:absolute;top:50%;transform:translateY(-50%);pointer-events:none} +.zoom-control>.utility-icon:first-child{left:10px;width:15px;height:15px;color:var(--muted)} +.zoom-control>.utility-icon:last-child{right:10px;width:13px;height:13px;color:var(--muted)} +.comparison-panel .overlay-control{height:36px;min-height:36px;font-size:12px;padding:6px 10px;gap:6px} +.comparison-panel .overlay-control svg{width:18px;height:18px;stroke-width:1.6} +.comparison-actions .source-link{color:var(--muted)} +@media(forced-colors:active){.zoom-control select{appearance:auto;padding:6px 8px}.zoom-control>.utility-icon{display:none}} `;