From d49867771fe8f61a66bd781b544afcdc30abcf74 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Sun, 20 Sep 2026 18:48:10 -0700 Subject: [PATCH] Report component inventory failures together Collect independent missing-region and rendered-code-preview errors before native capture so repairs do not require one model turn per omitted region. Preserve all rejection rules. Refresh the embedded review bundle from the unchanged source with pinned Bun 1.3.13. AI-assisted implementation and validation by Codex under maintainer direction. Validation: cargo test --workspace; release build; IMPECCABLE_BIN=target/release/impeccable bun run test; bun run build. --- crates/context/assets/component-review.js | 102 ++++++++++++------ .../context/src/component_review/manifest.rs | 21 ++-- crates/context/src/component_review/tests.rs | 15 +++ 3 files changed, 98 insertions(+), 40 deletions(-) diff --git a/crates/context/assets/component-review.js b/crates/context/assets/component-review.js index 73399d7fc..2979ff6ce 100644 --- a/crates/context/assets/component-review.js +++ b/crates/context/assets/component-review.js @@ -1,4 +1,4 @@ -(()=>{function Li(p,o){let x=o?.changes[p],e=o?.feedback?.[p],n=e?.decision??(o?.submitted?o.draft.decisions[p]:void 0),t=x?.kind==="unchanged"&&(x.carried??(o?.submitted&&n?.action==="approve"))===!0;return{change:x,prior:n,feedbackRound:e?.round??o?.packet.round,carried:t,label:x?.kind==="added"?"New component":x?.kind==="changed"?"Review again":t?"Approval kept":n?.action==="revise"?"Changes still requested":"Awaiting review"}}function R(p,o,x){let e=o.decisions[p.id],n=e?.revision===p.revision?e:void 0,t=Li(p.id,x);if(n?.action==="approve")return{kind:"approved",label:t.carried?"Approval kept":"Approved",priority:3};if(n?.action==="revise")return{kind:"feedback",label:"Feedback ready",priority:2};return{kind:"pending",label:t.change?.kind==="changed"?"Review again":t.change?.kind==="added"?"New · review needed":"Not reviewed",priority:t.change?.kind==="changed"||t.change?.kind==="added"?0:1}}function ko(p){return{packetRevision:p.revision,decisions:{},missing:[],inventoryConfirmed:!1}}function Io(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 wi(p,o){let x=p.components.map((h)=>o.decisions[h.id]?.revision===h.revision?o.decisions[h.id]:void 0),e=x.filter((h)=>h?.action==="approve").length,n=x.filter((h)=>h?.action==="revise").length,t=x.length-e-n,ni=n>0||o.missing.length>0;return{approved:e,revisions:n,pending:t,hasFeedback:ni,canSubmit:o.packetRevision===p.revision&&o.missing.every((h)=>h.name.trim()&&Io(h.box))&&(ni||!t&&o.inventoryConfirmed)}}function jo(p,o){let x={...o.decisions};for(let e of p.components)if(!x[e.id]||x[e.id].revision!==e.revision)x[e.id]={revision:e.revision,action:"approve",feedback:"",split:!1};return{...o,decisions:x}}function qo(p,o){if(!wi(p,o).canSubmit)throw Error("Review is incomplete or stale");return{schemaVersion:1,requestId:p.id,...structuredClone(o)}}function ci(p){let o=p.preview.kind==="page"||p.preview.sourceKind==="page",x=p.preview.sourceKind==="page";return{code:o,captured:x,label:o?p.medium.match(/html|css|svg/i)?p.medium:"HTML / CSS / SVG":"Raster",caption:o?x?p.preview.isolation?"Component only":"Region capture":"Live component":"Produced asset",fileLabel:x?"Open captured preview":"Open source image"}}function Mo(p,o,x){let e=p.components.findIndex((n)=>n.id===x);for(let n=1;n<=p.components.length;n++){let t=p.components[(e+n)%p.components.length];if(R(t,o).kind==="pending")return t.id}}function Ri(p,o,x){return x==="all"||R(p,o).kind==="pending"===(x==="pending")}function Di(p,o){if(!o.reviewGroup||!ci(o).code)return[o];return p.components.filter((x)=>x.reviewGroup===o.reviewGroup&&ci(x).code)}function Ni(p,o,x,e,n=[]){return(e?Di(p,x):[x]).filter((t)=>t.id===x.id||R(t,o).kind==="pending"&&!n.includes(t.id))}function Si(p,o,x,e,n){let t=n==="fit"?Math.min(1,x/p,e/o):n;return{scale:t,width:p*t,height:o*t}}function Fi(p,o,x,e){if(x<=0||e<=x)return 0;return Math.max(0,Math.min(1,((p-o)/x-0.08)/0.84))*(e-x)}var Ho=` +(()=>{function Ni(p,o){let x=o?.changes[p],e=o?.feedback?.[p],n=e?.decision??(o?.submitted?o.draft.decisions[p]:void 0),a=x?.kind==="unchanged"&&(x.carried??(o?.submitted&&n?.action==="approve"))===!0;return{change:x,prior:n,feedbackRound:e?.round??o?.packet.round,carried:a,label:x?.kind==="added"?"New component":x?.kind==="changed"?"Review again":a?"Approval kept":n?.action==="revise"?"Changes still requested":"Awaiting review"}}function I(p,o,x){let e=o.decisions[p.id],n=e?.revision===p.revision?e:void 0,a=Ni(p.id,x);if(n?.action==="approve")return{kind:"approved",label:a.carried?"Approval kept":"Approved",priority:3};if(n?.action==="revise")return{kind:"feedback",label:"Feedback ready",priority:2};return{kind:"pending",label:a.change?.kind==="changed"?"Review again":a.change?.kind==="added"?"New · review needed":"Not reviewed",priority:a.change?.kind==="changed"||a.change?.kind==="added"?0:1}}function Jo(p){return{packetRevision:p.revision,decisions:{},missing:[],inventoryConfirmed:!1}}function ir(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 x=p.components.map((g)=>o.decisions[g.id]?.revision===g.revision?o.decisions[g.id]:void 0),e=x.filter((g)=>g?.action==="approve").length,n=x.filter((g)=>g?.action==="revise").length,a=x.length-e-n,b=n>0||o.missing.length>0;return{approved:e,revisions:n,pending:a,hasFeedback:b,canSubmit:o.packetRevision===p.revision&&o.missing.every((g)=>g.name.trim()&&ir(g.box))&&(b||!a&&o.inventoryConfirmed)}}function $o(p,o){let x={...o.decisions};for(let e of p.components)if(!x[e.id]||x[e.id].revision!==e.revision)x[e.id]={revision:e.revision,action:"approve",feedback:"",split:!1};return{...o,decisions:x}}function Ko(p,o){if(!zi(p,o).canSubmit)throw Error("Review is incomplete or stale");return{schemaVersion:1,requestId:p.id,...structuredClone(o)}}function yi(p){let o=p.preview.kind==="page"||p.preview.sourceKind==="page",x=p.preview.sourceKind==="page";return{code:o,captured:x,label:o?p.medium.match(/html|css|svg/i)?p.medium:"HTML / CSS / SVG":"Raster",caption:o?x?p.preview.isolation?"Component only":"Region capture":"Live component":"Produced asset",fileLabel:x?"Open captured preview":"Open source image"}}function Qo(p,o,x){let e=p.components.findIndex((n)=>n.id===x);for(let n=1;n<=p.components.length;n++){let a=p.components[(e+n)%p.components.length];if(I(a,o).kind==="pending")return a.id}}function Wi(p,o){if(!o.reviewGroup||!yi(o).code)return[o];return p.components.filter((x)=>x.reviewGroup===o.reviewGroup&&yi(x).code)}function Mi(p,o,x,e,n=[]){let a=e?Wi(p,x):[x];if(a.length===1)return[x];return a.filter((b)=>I(b,o).kind==="pending"&&(b.id===x.id||!n.includes(b.id)))}function Yo(p,o,x){let e=new Set;return p.components.flatMap((n)=>{if(e.has(n.id))return[];let a=Wi(p,n);a.forEach((s)=>e.add(s.id));let b=a.filter((s)=>I(s,o,x).kind==="pending"),g=a.filter((s)=>I(s,o,x).kind==="feedback"),z=b[0]??g[0]??n,_=b.length?"pending":g.length?"feedback":"approved";return[{id:n.id,members:a,representative:z,pending:b.length,kind:_,label:a.length>1?n.reviewGroup.replace(/[-_]+/g," ").replace(/^./,(s)=>s.toUpperCase()):n.name,box:{x:Math.min(...a.map((s)=>s.box.x)),y:Math.min(...a.map((s)=>s.box.y)),w:Math.max(...a.map((s)=>s.box.x+s.box.w))-Math.min(...a.map((s)=>s.box.x)),h:Math.max(...a.map((s)=>s.box.y+s.box.h))-Math.min(...a.map((s)=>s.box.y))},stateLabel:b.length?a.length>1?`${b.length} to review`:I(z,o,x).label:g.length?a.length>1?`${g.length} ${g.length===1?"needs":"need"} work`:"Feedback ready":"Approved"}]})}function io(p,o){let x=new Set(o.preview.isolation?.excludedComponents??[]),e=o.box,n=p.components.filter((a)=>{if(a.id===o.id)return!1;if(x.has(a.id))return!0;let b=a.box,g=Math.max(0,Math.min(e.x+e.w,b.x+b.w)-Math.max(e.x,b.x))*Math.max(0,Math.min(e.y+e.h,b.y+b.h)-Math.max(e.y,b.y));return b.w*b.h=0.98});return{description:o.note.trim(),related:n,excluded:n.filter((a)=>x.has(a.id))}}function oo(p,o,x,e,n){let a=n==="fit"?Math.min(1,x/p,e/o):n;return{scale:a,width:p*a,height:o*a}}function ro(p,o,x,e){if(x<=0||e<=x)return 0;return Math.max(0,Math.min(1,((p-o)/x-0.08)/0.84))*(e-x)}var _o=` :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} @@ -53,7 +53,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 Vo=` +`;var Lo=` .review-peers{display:grid;gap:8px;padding:8px 0 12px;font-size:12px} .review-peers strong span{font-weight:400}.review-peers>div{display:flex;flex-wrap:wrap;gap:4px} .review-peers button{min-width:28px;min-height:28px;padding:3px;border:1px solid var(--line);border-radius:4px;background:var(--paper);color:inherit} @@ -94,7 +94,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}.record-verdict{display:flex;flex-wrap:wrap;align-items:baseline;gap:5px 12px;font-size:13px}.record-verdict span{font-size:11px;color:var(--muted)}.review>.record-footer{font-size:12px;color:var(--muted);flex-wrap:wrap;gap:6px 16px}.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:4px 2px}.completion-mark{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:50%;background:#e6efe8;color:#28625e;float:left;margin:0 10px 8px 0}.completion-mark svg{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:1.6}.completion-link{display:inline-flex;align-items:center;gap:8px;color:var(--teal);border:0;background:transparent;padding:0;font-size:12px;text-align:left}.completion-link svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.8;flex-shrink:0}.queue-complete #approve-rest{display:none}.review-summary h2{font-size:22px;line-height:32px}.review-summary p{clear:both;margin-top:8px;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} -`+Ho+` +`+_o+` .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. */ @@ -156,47 +156,81 @@ .assembled-review .decisions>button{flex:1;min-width:0} .assembled-review .page-review-status{text-align:left} } -`;var Oo={chevronDown:'',code:'',image:'',expand:'',compact:'',hideTray:'',showTray:'',next:'',mark:'',close:'',undo:'',external:'',zoom:''};function Y(p){return``}var l=(p)=>p.replace(/[&<>"']/g,(o)=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[o]),ri=(p)=>`${p*100}%`,ei=(p)=>{let o=new URL(p,location.href);if(!["http:","https:"].includes(o.protocol))throw Error("Unsupported preview URL");return l(o.href)};function Jo(p,o,x){let e=p.attachShadow({mode:"open"}),n=structuredClone(x.initialDraft??ko(o)),t=o.stage==="hero"&&o.components.length===1&&!n.missing.length,ni=()=>[...o.components].sort((s,b)=>R(s,n,x.history).priority-R(b,n,x.history).priority),h=ni().find((s)=>R(s,n,x.history).kind!=="approved")?.id??o.components[0]?.id,z=wi(o,n).pending?"pending":"reviewed",_=!1,N=!1,m=x.completed??!1,ki="",V={},y=!!x.completed||!wi(o,n).pending,B=null,li=!1,$o=/Mac|iPhone|iPad/.test(navigator.platform)?"⌘Enter":"Ctrl+Enter",ii=!1,k=!1,ji=!1,Xi=null,oi=!1,$=!0,di=!1,J="comp",Ci=J,D="fit",mi="checker",W=!1,pi="isolated",Ei,Pi="fit",S=null,F=null,C=null,bi='',qi='',io=(s)=>`left:${ri(s.x)};top:${ri(s.y)};width:${ri(s.w)};height:${ri(s.h)}`;function hi(s){e.getElementById(s)?.focus({preventScroll:!0})}function Ko(s){let b=Mo(o,n,s);if(y=!b,b)h=b;J="component",W=!1,ii=!1,D="fit",pi="isolated",li=!1,z=y?"reviewed":"pending";let Z=()=>{f(),hi(y?"review-summary":V[h]?"feedback":"approve")};if(y&&k&&Xi)Xi(Z);else Z()}async function oo(){if(N||m||W||Object.keys(V).length)return;N=!0,ki="",f();try{await x.onSubmit(qo(o,n)),m=!0,y=!0,_=!1,z="reviewed"}catch(s){ki=s instanceof Error?s.message:"Could not save. Try again."}finally{N=!1,f()}}function Qi(s){if(N||m||W||ji)return;let b=o.components.find((L)=>L.id===h);if(!b)return;let Z=n.decisions[b.id],vi=Z?.revision===b.revision?Z:void 0,gi=Ni(o,n,b,li,Object.keys(V));B={id:b.id,name:gi.length>1?`${b.reviewGroup} · ${gi.length} instances`:b.name,action:s,previous:Object.fromEntries(gi.map((L)=>[L.id,n.decisions[L.id]?{...n.decisions[L.id]}:void 0]))};let Mi=V[b.id]??vi;for(let L of gi)n.decisions[L.id]={revision:L.revision,action:s,feedback:s==="revise"?Mi?.feedback??"":"",split:s==="revise"&&(Mi?.split??!1)};if(delete V[b.id],di)$=!0,di=!1;if(t)n.inventoryConfirmed=s==="approve",B=null,oo();else Ko(b.id)}function Yo(){if(N||m||W||ji)return;let s=o.components.find((vi)=>vi.id===h);if(!s)return;let b=n.decisions[s.id],Z=b?.revision===s.revision?b:void 0;if(V[s.id]??={feedback:Z?.feedback??"",split:Z?.split??!1},$&&(e.querySelector(".workbench")?.clientHeight??0)<420)di=!0,$=!1;y=!1,f(),hi("feedback")}function ro(s){let b=`missing-${crypto.randomUUID()}`;n.missing.push({id:b,name:"Missing component",feedback:"",box:s}),n.inventoryConfirmed=!1,y=!1,h=b,J="component",_=!1,S=null,F=null,f(),e.querySelector("#missing-name")?.focus()}function f(){if(C?.disconnect(),m||y&&!t)k=!1;let s=e.activeElement,b=s?.id,Z=s?.dataset.select,vi=window.scrollX,gi=window.scrollY,Mi=e.querySelector(".inventory")?.scrollLeft??0,L=Ei===h,_o=J==="component"&&(!L||Ci!==J);Ci=J;let Lo=L?e.querySelector(".inspection-content")?.scrollTop??0:0,No=L&&(e.querySelector(".changed-files")?.open??!1),Xo=e.querySelector(".comparison-slot")?.clientHeight??200,eo=e.querySelector(".pan-viewport"),no=Ei===h&&Pi===D,Eo=no?eo?.scrollLeft??0:0,Qo=no?eo?.scrollTop??0:0;Ei=h,Pi=D;let g=o.components.find((i)=>i.id===h),j=n.missing.find((i)=>i.id===h),po=g?.box??j?.box,ao=g?o.components.indexOf(g)+1:o.components.length+n.missing.findIndex((i)=>i.id===h)+1,xo=g?n.decisions[g.id]:void 0,P=xo?.revision===g?.revision?xo:void 0,Hi=g?V[g.id]:void 0,ai=Object.keys(V).length>0,Wi=g?Di(o,g):[],ui=g?Ni(o,n,g,li,Object.keys(V)):[],Wo=g?!o.components.some((i)=>!ui.some((r)=>r.id===i.id)&&R(i,n).kind==="pending"):!1,Zi=B&&!m?`
${l(B.name)} ${B.action==="approve"?"approved":"flagged for repair"}.
`:"",v=wi(o,n),q=x.history,X=g?Li(g.id,q):void 0,xi=q?.packet.components.find((i)=>i.id===g?.id),U=W&&!!xi,d=U?xi:g,E=U?q.packet:o,to=Object.values(q?.changes??{}),lo=to.filter((i)=>i.kind==="changed").length,ho=to.filter((i)=>i.kind==="added").length,Ui=o.components.filter((i)=>R(i,n,q).kind==="approved"&&Li(i.id,q).carried).length,Vi=(i)=>{let r=R(i,n,q);return m&&r.kind==="feedback"?{...r,label:"Changes requested"}:r},Zo=v.approved+v.revisions+n.missing.length,go=(z==="pending"?ni():o.components).filter((i)=>Ri(i,n,z)),Uo=[v.revisions+n.missing.length?`${v.revisions+n.missing.length} feedback ready`:"",Ui?`${Ui} ${Ui===1?"approval":"approvals"} kept`:"",lo?`${lo} changed`:"",ho?`${ho} added`:"",q?.removed.length?`${q.removed.length} removed`:""].filter(Boolean).join(" · "),Go=ki||(ai?"Save or cancel your open feedback before sending.":m?x.preview?"Preview submitted. No run changed.":"Review submitted.":v.hasFeedback?"Ready to send for corrections.":v.pending?`${v.pending} left to review`:!n.inventoryConfirmed?"Confirm the map is complete.":"Ready to continue."),I=d?ci(d):null,Gi=d?.preview.kind==="image"&&!I?.code,Ti=Gi||d?.material?.alpha==="transparent",G=!!(d?.context&&pi==="context"),fo=d&&(G?d.context?.kind!=="image":d.preview.kind==="page"),Ai=G&&d?.context?d.context.url:d?.preview.url,To=I?.code?`${I.label} · ${I.captured?"captured from code":"live preview"}`:d?.material?`${d.material.alpha==="transparent"?"Transparent":d.material.alpha==="opaque"?"Opaque":"Transparency unverified"} ${d.material.format}`:"Raster · transparency unverified";e.innerHTML=`
-

${m?"Review record.":t?"Review the assembled page.":"Review the components."}

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

${m?'Submitted · read-only':x.preview?'Interactive preview':""}
+ +/* Grouped review is the default; instances are an explicit drill-down. */ +.pin small{font:600 10px/1 var(--font-body, sans-serif);margin-left:3px}.pin:has(small){width:auto;min-width:32px;padding:0 7px} +.instance-region{opacity:.55;border-width:1px;pointer-events:none} +.group-overview>.compare,.group-overview>.view-controls{display:none} +.group-overview{position:relative}.group-overview>.review-peers{padding:0 44px 4px 0;min-height:36px}.group-overview>.compare-toolbar{position:absolute;right:0;top:0;justify-content:flex-end;margin:0} +.group-overview>.compare-toolbar>:not(.comparison-actions),.group-overview .source-link{display:none} +.group-overview .comparison-actions{margin-left:auto} +.review-peers{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px} +.group-hint{font-size:12px;color:var(--muted)} +.instance-grid{display:grid;gap:0;flex:none;grid-auto-rows:max-content} +.instance-grid-labels{display:grid;grid-template-columns:1fr 1fr;gap:16px;color:var(--muted);font-size:12px;padding:8px 0} +.instance-row{display:block;height:auto;width:100%;text-align:left;padding:12px 0;border:0;border-top:1px solid var(--line);border-radius:0;background:transparent;color:inherit} +.instance-row:hover{background:#eef3f1} +.instance-caption{display:flex;justify-content:space-between;align-items:baseline;gap:12px;font-size:12px;margin-bottom:8px} +.instance-caption>span{color:var(--muted);font-size:11px} +.instance-pair{display:grid;grid-template-columns:1fr 1fr;align-items:center;gap:16px} +.instance-reference{justify-self:center;display:block;position:relative;overflow:hidden;max-height:180px;width:100%;background:var(--comp-background)} +.instance-reference img{position:absolute;max-width:none;height:auto} +.instance-produced{justify-self:center;display:flex;align-items:center;justify-content:center;min-height:36px;max-height:180px;overflow:hidden} +.instance-produced img{display:block;width:100%;height:100%;object-fit:contain} +.instance-row.approved{opacity:.65}.instance-row.feedback .instance-caption>span{color:var(--warn)} +.item strong small{font-size:11px;white-space:nowrap;color:var(--muted)} +#comparison-dialog .group-overview{overflow:auto;min-height:0} +#comparison-dialog .group-overview>.compare-toolbar{top:0} +#comparison-dialog .group-overview>.expanded-title{padding-right:44px} + +.review-scope{font-size:12px;line-height:1.5;margin:8px 0 12px;color:var(--color-text,#292929);text-align:left;white-space:normal} +.review-scope p{margin:4px 0;overflow-wrap:anywhere}.review-scope strong{font-weight:600;margin-right:5px} +.review-scope .separate-reviews,.review-scope .scope-excluded{font-size:11px;color:var(--muted)} +.instance-row .review-scope{margin:12px 0 0} +#comparison-dialog .review-scope{flex:none} +`;var or={chevronDown:'',code:'',image:'',expand:'',compact:'',hideTray:'',showTray:'',next:'',mark:'',close:'',undo:'',external:'',zoom:''};function W(p){return``}var l=(p)=>p.replace(/[&<>"']/g,(o)=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[o]),di=(p)=>`${p*100}%`,S=(p)=>{let o=new URL(p,location.href);if(!["http:","https:"].includes(o.protocol))throw Error("Unsupported preview URL");return l(o.href)};function Xo(p,o,x){let e=p.attachShadow({mode:"open"}),n=structuredClone(x.initialDraft??Jo(o)),a=o.stage==="hero"&&o.components.length===1&&!n.missing.length,b=()=>[...o.components].sort((f,m)=>I(f,n,x.history).priority-I(m,n,x.history).priority),g=b().find((f)=>I(f,n,x.history).kind!=="approved")?.id??o.components[0]?.id,z=zi(o,n).pending?"pending":"reviewed",_=!1,s=!1,y=x.completed??!1,Vi="",q={},k=!!x.completed||!zi(o,n).pending,F=null,C=!0,Eo=/Mac|iPhone|iPad/.test(navigator.platform)?"⌘Enter":"Ctrl+Enter",ti=!1,H=!1,Ji=!1,Zi=null,li=!1,L=!0,mi=!1,K="comp",eo=K,ei="fit",ki="checker",T=!1,hi="isolated",Gi,no="fit",ni=null,pi=null,ai=null,ji='',$i='',po=(f,m)=>{if(f.stage==="hero")return"";let Q=io(f,m);if(!Q.related.length)return"";let gi=Q.related.map((P)=>l(P.name)).join(" · ");return`

Reviewing ${l(Q.description||m.name)}

Separate review items ${gi}

${Q.excluded.length?`

Hidden in this preview: ${Q.excluded.map((P)=>l(P.name)).join(" · ")}

`:""}
`},Ti=(f)=>`left:${di(f.x)};top:${di(f.y)};width:${di(f.w)};height:${di(f.h)}`;function bi(f){e.getElementById(f)?.focus({preventScroll:!0})}function No(f){let m=Qo(o,n,f);if(k=!m,m)g=m;K="component",T=!1,ti=!1,ei="fit",hi="isolated",C=!0,z=k?"reviewed":"pending";let Q=()=>{w(),bi(k?"review-summary":q[g]?"feedback":"approve")};if(k&&H&&Zi)Zi(Q);else Q()}async function ao(){if(s||y||T||Object.keys(q).length)return;s=!0,Vi="",w();try{await x.onSubmit(Ko(o,n)),y=!0,k=!0,_=!1,z="reviewed"}catch(f){Vi=f instanceof Error?f.message:"Could not save. Try again."}finally{s=!1,w()}}function Ui(f){if(s||y||T||Ji)return;let m=o.components.find((Z)=>Z.id===g);if(!m||!Mi(o,n,m,C,Object.keys(q)).length)return;let Q=n.decisions[m.id],gi=Q?.revision===m.revision?Q:void 0,P=Mi(o,n,m,C,Object.keys(q));F={id:m.id,name:P.length>1?`${m.reviewGroup} · ${P.length} instances`:m.name,action:f,previous:Object.fromEntries(P.map((Z)=>[Z.id,n.decisions[Z.id]?{...n.decisions[Z.id]}:void 0]))};let Ki=q[m.id]??gi;for(let Z of P)n.decisions[Z.id]={revision:Z.revision,action:f,feedback:f==="revise"?Ki?.feedback??"":"",split:f==="revise"&&(Ki?.split??!1)};if(delete q[m.id],mi)L=!0,mi=!1;if(a)n.inventoryConfirmed=f==="approve",F=null,ao();else No(m.id)}function Wo(){if(s||y||T||Ji)return;let f=o.components.find((gi)=>gi.id===g);if(!f||!Mi(o,n,f,C,Object.keys(q)).length)return;let m=n.decisions[f.id],Q=m?.revision===f.revision?m:void 0;if(q[f.id]??={feedback:Q?.feedback??"",split:Q?.split??!1},L&&(e.querySelector(".workbench")?.clientHeight??0)<420)mi=!0,L=!1;k=!1,w(),bi("feedback")}function xo(f){let m=`missing-${crypto.randomUUID()}`;n.missing.push({id:m,name:"Missing component",feedback:"",box:f}),n.inventoryConfirmed=!1,k=!1,g=m,K="component",_=!1,ni=null,pi=null,w(),e.querySelector("#missing-name")?.focus()}function w(){if(ai?.disconnect(),y||k&&!a)H=!1;let f=e.activeElement,m=f?.id,Q=f?.dataset.select,gi=window.scrollX,P=window.scrollY,Ki=e.querySelector(".inventory")?.scrollLeft??0,Z=Gi===g,Zo=K==="component"&&(!Z||eo!==K);eo=K;let Go=Z?e.querySelector(".inspection-content")?.scrollTop??0:0,To=Z&&(e.querySelector(".changed-files")?.open??!1),Uo=e.querySelector(".comparison-slot")?.clientHeight??200,to=e.querySelector(".pan-viewport"),lo=Gi===g&&no===ei,Ao=lo?to?.scrollLeft??0:0,Bo=lo?to?.scrollTop??0:0;Gi=g,no=ei;let c=o.components.find((i)=>i.id===g),M=n.missing.find((i)=>i.id===g),Y=Yo(o,n,x.history),X=Y.find((i)=>i.members.some((r)=>r.id===g)),R=!!X&&X.members.length>1&&C,ho=R?X.box:c?.box??M?.box,go=X?Y.indexOf(X)+1:Y.length+n.missing.findIndex((i)=>i.id===g)+1,fo=c?n.decisions[c.id]:void 0,xi=fo?.revision===c?.revision?fo:void 0,Qi=c?q[c.id]:void 0,fi=Object.keys(q).length>0,so=c?Wi(o,c):[],si=c?Mi(o,n,c,C,Object.keys(q)):[],Io=c?!o.components.some((i)=>!si.some((r)=>r.id===i.id)&&I(i,n).kind==="pending"):!1,Ai=F&&!y?`
${l(F.name)} ${F.action==="approve"?"approved":"flagged for repair"}.
`:"",j=zi(o,n),V=x.history,U=c?Ni(c.id,V):void 0,wi=V?.packet.components.find((i)=>i.id===c?.id),ii=T&&!!wi,d=ii?wi:c,E=ii?V.packet:o,wo=Object.values(V?.changes??{}),co=wo.filter((i)=>i.kind==="changed").length,mo=wo.filter((i)=>i.kind==="added").length,Bi=o.components.filter((i)=>I(i,n,V).kind==="approved"&&Ni(i.id,V).carried).length,Ii=(i)=>{let r=I(i,n,V);return y&&r.kind==="feedback"?{...r,label:"Changes requested"}:r},qi=Y.filter((i)=>i.kind==="pending").length,Ro=Y.length-qi+n.missing.length,bo=Y.filter((i)=>z==="all"||i.kind==="pending"===(z==="pending")).map((i)=>i.representative),Oo=(i)=>Y.find((r)=>r.members.some((t)=>t.id===i))?.label??i,Do=[j.revisions+n.missing.length?`${j.revisions+n.missing.length} feedback ready`:"",Bi?`${Bi} ${Bi===1?"approval":"approvals"} kept`:"",co?`${co} changed`:"",mo?`${mo} added`:"",V?.removed.length?`${V.removed.length} removed`:""].filter(Boolean).join(" · "),So=Vi||(fi?"Save or cancel your open feedback before sending.":y?x.preview?"Preview submitted. No run changed.":"Review submitted.":j.hasFeedback?"Ready to send for corrections.":j.pending?`${qi} left to review`:!n.inventoryConfirmed?"Confirm the map is complete.":"Ready to continue."),oi=d?yi(d):null,Ri=d?.preview.kind==="image"&&!oi?.code,Oi=Ri||d?.material?.alpha==="transparent",O=!!(d?.context&&hi==="context"),vo=d&&(O?d.context?.kind!=="image":d.preview.kind==="page"),Di=O&&d?.context?d.context.url:d?.preview.url,uo=d?io(E,d):null,Fo=oi?.code?`${oi.label} · ${oi.captured?"captured from code":"live preview"}`:d?.material?`${d.material.alpha==="transparent"?"Transparent":d.material.alpha==="opaque"?"Opaque":"Transparency unverified"} ${d.material.format}`:"Raster · transparency unverified";e.innerHTML=`
+

${y?"Review record.":a?"Review the assembled page.":"Review the components."}

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

${y?'Submitted · read-only':x.preview?'Interactive preview':""}
${x.preview?'

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

':""} - ${q&&!t?`

${v.pending} ${v.pending===1?"component":"components"} to review${Uo}

${v.pending?``:""}${q.removed.length?`
Removed from the map

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

`:""}
`:""} - ${!t?`
`:""} -
${!t?` + ${V&&!a?`

${qi} ${qi===1?"item":"items"} to review${Do}

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

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

`:""}
`:""} + ${!a?`
`:""} +
${!a?`
-

Approved comp

${!m?``:""}
+

Approved comp

${!y?``:""}
- Approved composition for ${l(o.title)} - ${po&&!y?`
`:""} - ${o.components.map((i,r)=>{let a=Vi(i);return``}).join("")} - ${n.missing.map((i,r)=>``).join("")} + Approved composition for ${l(o.title)} + ${ho&&!k?`
`:""} + ${Y.map((i,r)=>{let t=i.representative,h={kind:i.kind,label:i.stateLabel},B=!k&&X?.id===i.id;return``}).join("")} + ${R&&!k?X.members.map((i)=>`
`).join(""):""} + ${n.missing.map((i,r)=>``).join("")}
-
# To review${qi} ${m?"Changes requested":"Feedback ready"}${bi} Approved
+
# To review${$i} ${y?"Changes requested":"Feedback ready"}${ji} Approved
${_?'
Draw around the missing piece.
':""}
`:""} -
- ${!t?`

${y?"Review summary":`${ao} ${l(g?.name??j?.name??"Component")}`}

`:""}
- ${y&&!t?`

${m?"Review sent.":"All components reviewed."}

${v.approved} approved · ${v.revisions} flagged for repair${n.missing.length?` · ${n.missing.length} missing`:""}

${m?"Your decisions are saved.":v.hasFeedback?"Send your feedback to start the next repair round.":"Confirm nothing is missing, then approve and continue."}

${o.components.map((i)=>{let r=n.decisions[i.id],a=Vi(i);return``}).join("")}${n.missing.map((i)=>``).join("")}
${Zi?`
${Zi}
`:""}`:g?` - ${q?`
- ${U&&X?.prior?.action==="revise"?`

Previous feedback · Round ${X.feedbackRound}

${l(X.prior.feedback||"No written feedback was supplied.")}
${X.prior.split?"

Requested: split into separately reviewable components.

":""}
`:X?.carried?'

Unchanged · approval kept

':""} - ${X?.change?.kind==="changed"?`
${X.change.files.length?`${X.change.files.length} changed ${X.change.files.length===1?"file":"files"}`:X.change.reasons.includes("region")?"Region changed":xi?.note!==g.note?"Description changed · files unchanged":"Component definition changed · files unchanged"}${X.change.files.length?`
    ${X.change.files.map((i)=>`
  • ${l(i)}
  • `).join("")}
`:""}${xi&&xi.note!==g.note?`
Previous description
${l(xi.note)}
Current description
${l(g.note)}
`:""}
`:""} +
+ ${!a?`

${k?"Review summary":`${go} ${l(R?X.label:c?.name??M?.name??"Component")}`}

`:""}
+ ${k&&!a?`

${y?"Review sent.":"All components reviewed."}

${j.approved} approved · ${j.revisions} flagged for repair${n.missing.length?` · ${n.missing.length} missing`:""}

${y?"Your decisions are saved.":j.hasFeedback?"Send your feedback to start the next repair round.":"Confirm nothing is missing, then approve and continue."}

${Y.map((i)=>{let r=i.representative,t=n.decisions[r.id],h=Ii(r);return``}).join("")}${n.missing.map((i)=>``).join("")}
${Ai?`
${Ai}
`:""}`:c?` + ${V?`
+ ${ii&&U?.prior?.action==="revise"?`

Previous feedback · Round ${U.feedbackRound}

${l(U.prior.feedback||"No written feedback was supplied.")}
${U.prior.split?"

Requested: split into separately reviewable components.

":""}
`:U?.carried?'

Unchanged · approval kept

':""} + ${U?.change?.kind==="changed"?`
${U.change.files.length?`${U.change.files.length} changed ${U.change.files.length===1?"file":"files"}`:U.change.reasons.includes("region")?"Region changed":wi?.note!==c.note?"Description changed · files unchanged":"Component definition changed · files unchanged"}${U.change.files.length?`
    ${U.change.files.map((i)=>`
  • ${l(i)}
  • `).join("")}
`:""}${wi&&wi.note!==c.note?`
Previous description
${l(wi.note)}
Current description
${l(c.note)}
`:""}
`:""}
`:""} -

${l(d.name)}

${Wi.length>1?`
${l(g.reviewGroup)} · ${Wi.length} instances
${Wi.map((i)=>``).join("")}
`:""}
${xi?`
`:""}
${d?.preview.kind==="image"?`${Y("external")}`:""}
-
-
${U?`Comp · Round ${q.packet.round}`:t?"Approved comp":"In the comp"}
Reference region for ${l(d.name)}
-
${U?`Previous · Round ${q.packet.round}`:t?"Assembled page":G?"In context":q?`${I.caption} · Round ${o.round}`:I.caption}
${!fo?`Produced ${l(d.name)}`:``}${ii?`Reference overlay`:""}
+

${l(R?X.label:d.name)}

${so.length>1?`
${so.length} instances${!R?'':'Select to inspect'}
`:""}
${wi?`
`:""}
${d?.preview.kind==="image"?`${W("external")}`:""}
+ ${R?`
Full comp cropComponent preview
${X.members.map((i,r)=>{let t=Ii(i);return``}).join("")}
`:""} + ${!R&&d?po(E,d):""}
+
${ii?`Comp · Round ${V.packet.round}`:a?"Approved comp":uo?.related.length?"Full comp crop":"In the comp"}
Reference region for ${l(d.name)}
+
${ii?`Previous · Round ${V.packet.round}`:a?"Assembled page":O?"In context":V?`${oi.caption} · Round ${o.round}`:oi.caption}
${!vo?`Produced ${l(d.name)}`:``}${ti?`Reference overlay`:""}
- ${Ti||d.context?`
${d.context?`
`:""}${Ti?`
`:""}
`:""} -
${!t?`
${Y(I.code?"code":"image")}${l(To)}${d?.material?`${d.material.width} × ${d.material.height} px`:""}
${E.stage==="components"&&I?.captured&&!d?.preview.isolation?'

Legacy region capture · may include overlapping components.

':""}${d?.context?.layering&&(Gi||G)?`

${l(d.context.layering)}

`:""} -

${l(d.note)}

-
`:""}
${Zi}${U?'

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

':""}${m?`
${P?.action==="approve"?"Approved":P?.action==="revise"?"Changes requested":"Not reviewed"}Submitted in round ${o.round} · read-only
`:`
${!t?`
Your review Round ${o.round}${U?"

Return to Current to review this round.

":""}
`:""}${P&&!t?``:""}
`} - ${Hi?`
`:P?.action==="revise"?`

${l(P.feedback||"No note — agent will diagnose.")}

`:""} - ${t?`

${l(ki||(m?"Your decision is saved.":N?"Sending…":Hi?"":"Approval confirms the composition and that nothing is missing."))}

`:""}
`:j?`

This piece will be added to the unresolved inventory.

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

No components supplied.

"} + ${Oi||d.context?`
${d.context?`
`:""}${Oi?`
`:""}
`:""} +
${!a&&!R?`
${W(oi.code?"code":"image")}${l(Fo)}${d?.material?`${d.material.width} × ${d.material.height} px`:""}
${E.stage==="components"&&oi?.captured&&!d?.preview.isolation?'

Legacy region capture · may include overlapping components.

':""}${d?.context?.layering&&(Ri||O)?`

${l(d.context.layering)}

`:""} +

${!uo?.related.length?l(d.note):""}

+
`:""}
${Ai}${ii?'

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

':""}${y?`
${xi?.action==="approve"?"Approved":xi?.action==="revise"?"Changes requested":"Not reviewed"}Submitted in round ${o.round} · read-only
`:`
${!a?`
Your review Round ${o.round}${ii?"

Return to Current to review this round.

":""}
`:""}${xi&&!a&&!R?``:""}
`} + ${Qi?`
`:xi?.action==="revise"?`

${l(xi.feedback||"No note — agent will diagnose.")}

`:""} + ${a?`

${l(Vi||(y?"Your decision is saved.":s?"Sending…":Qi?"":"Approval confirms the composition and that nothing is missing."))}

`:""}
`:M?`

This piece will be added to the unresolved inventory.

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

No components supplied.

"}
- ${!t?`

Components

-
${go.map((i)=>{let r=o.components.indexOf(i),a=Vi(i);return``}).join("")}${(z==="pending"?[]:n.missing).map((i,r)=>``).join("")}${!go.length&&(z==="pending"||!n.missing.length)?`

${z==="pending"?"Nothing left to review. Your decisions are ready.":"No components reviewed yet."}

`:""}
`:""} - ${t?"":m?`
Round ${o.round} submitted · read-only${v.approved} approved · ${v.revisions} changes requested
`:`
${!v.pending&&!ai?``:""}

${l(Go)}

`} -
`;let K=e.querySelector("#comparison-dialog"),Q=e.querySelector(".comparison-panel"),ti=e.querySelector(".comparison-slot"),O=e.querySelector(".inspector > .review-form"),Ao=e.querySelector(".inspector"),so=()=>{if(K.append(Q),O)K.append(O)};if(k&&Q&&ti)ti.style.height=`${Xo}px`,so(),K.showModal();if(U)e.querySelectorAll(".decisions button,#feedback,#split,#save-feedback,#cancel-feedback,#undo-decision,#approve-rest,#submit,#inventory-confirm").forEach((i)=>i.disabled=!0);if(e.querySelector(".inspection-content").scrollTop=Lo,m||N)e.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);let co=e.querySelector(".inventory");if(co)co.scrollLeft=Mi;if(!L&&$)Array.from(e.querySelectorAll(".inventory [data-select]")).find((i)=>i.dataset.select===h)?.scrollIntoView({block:"nearest",inline:"nearest"});if(b)e.getElementById(b)?.focus({preventScroll:!0});else if(Z)Array.from(e.querySelectorAll(".item[data-select]")).find((i)=>i.dataset.select===Z)?.focus({preventScroll:!0});let u=(i,r)=>e.querySelector(`#${i}`)?.addEventListener("click",r);function wo(i,r=!1){if(_)return;if(li=!1,y=!1,h=i,J="component",ii=!1,D="fit",pi="isolated",W=!1,f(),r)e.querySelector("#expand-comparison")?.click()}e.querySelectorAll("[data-select]").forEach((i)=>i.onclick=()=>wo(i.dataset.select,i.classList.contains("pin")&&o.components.some((r)=>r.id===i.dataset.select))),u("previous-round",()=>{W=!0,f()}),u("current-round",()=>{W=!1,f()}),u("review-changes",()=>{let i=ni().filter((c)=>Vi(c).kind==="pending"),r=i.findIndex((c)=>c.id===h),a=i[(r+1)%i.length];if(a)y=!1,h=a.id,J="component",z="pending",W=!1,D="fit",ii=!1,pi="isolated",f()}),e.querySelectorAll("[data-filter]").forEach((i)=>i.onclick=()=>{z=i.dataset.filter,y=!v.pending&&z==="pending";let r=ni().filter((c)=>Ri(c,n,z));if(!(z!=="pending"&&n.missing.some((c)=>c.id===h))&&!r.some((c)=>c.id===h)&&r.length)h=r[0].id,J="component",W=!1,D="fit",ii=!1,pi="isolated";f()}),u("show-summary",()=>{y=!0,J="component",z="reviewed",f(),hi("review-summary")}),u("approve",()=>Qi("approve")),u("revise",Yo),u("clear",()=>{if(g)delete n.decisions[g.id],delete V[g.id];B=null,y=!1,z="pending",f()}),u("cancel-feedback",()=>{if(g)delete V[g.id];if(di)$=!0,di=!1;f(),hi("revise")}),e.querySelector("#feedback-form")?.addEventListener("submit",(i)=>{i.preventDefault(),Qi("revise")}),e.querySelector("#feedback")?.addEventListener("keydown",(i)=>{let r=i;if(r.key==="Enter"&&(r.metaKey||r.ctrlKey)&&!r.isComposing)r.preventDefault(),r.stopPropagation(),Qi("revise")}),u("undo-decision",()=>{if(!B||N||m)return;let i=B;for(let[r,a]of Object.entries(i.previous))if(a)n.decisions[r]=a;else delete n.decisions[r];delete V[i.id],h=i.id,y=!1,W=!1,J="component",z="all",B=null,f(),hi("approve")}),e.querySelector("#apply-group")?.addEventListener("change",(i)=>{li=i.target.checked,f()}),u("overlay",()=>{ii=!ii,f()}),u("isolated",()=>{pi="isolated",f()}),u("context",()=>{pi="context",f()}),e.querySelector("#zoom")?.addEventListener("change",(i)=>{let r=i.target.value;D=r==="fit"?"fit":Number(r),f()}),u("background-checker",()=>{mi="checker",f()}),u("background-page",()=>{mi="page",f()}),u("show-all",()=>{oi=!oi,$=!0,f()}),u("toggle-tray",()=>{di=!1,$=!$,f()}),u("show-comp",()=>{J="comp",f()}),u("show-component",()=>{J="component",f()}),u("mark",()=>{_=!_,f()}),u("add-box",()=>ro({x:0.35,y:0.35,w:0.2,h:0.2})),u("remove-missing",()=>{n.missing=n.missing.filter((i)=>i.id!==h),h=o.components[0]?.id,f()}),u("approve-rest",()=>{if(ai)return;n=jo(o,n),B=null,y=!0,z="reviewed",J="component",f(),hi("review-summary")}),e.querySelector("#inventory-confirm")?.addEventListener("change",(i)=>{n.inventoryConfirmed=i.target.checked,f()}),e.querySelector("#feedback")?.addEventListener("input",(i)=>{if(g&&V[g.id])V[g.id].feedback=i.target.value}),e.querySelector("#split")?.addEventListener("change",(i)=>{if(g&&V[g.id])V[g.id].split=i.target.checked}),e.querySelector("#missing-name")?.addEventListener("input",(i)=>{if(j)j.name=i.target.value;let r=e.querySelector("#submit");if(r)r.disabled=ai||N||m||!wi(o,n).canSubmit}),e.querySelector("#missing-feedback")?.addEventListener("input",(i)=>{if(j)j.feedback=i.target.value}),e.querySelectorAll("[data-coordinate]").forEach((i)=>i.addEventListener("change",()=>{if(!j)return;let r=i.dataset.coordinate,a=Number(i.value)/100;if(Number.isFinite(a))j.box[r]=Math.max(r==="w"||r==="h"?0.001:0,Math.min(1,a));j.box.w=Math.min(j.box.w,1-j.box.x),j.box.h=Math.min(j.box.h,1-j.box.y),f()})),u("submit",()=>{oo()});let T=e.querySelector(".map");function Bi(i){let r=T.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))}}function mo(i){let r=Bi(i);return o.components.filter(({box:a})=>r.x>=a.x&&r.x<=a.x+a.w&&r.y>=a.y&&r.y<=a.y+a.h).sort((a,c)=>a.box.w*a.box.h-c.box.w*c.box.h)[0]}T?.addEventListener("click",(i)=>{if(_||i.target.closest("[data-select]"))return;let r=mo(i);if(r)wo(r.id,!0)}),T?.addEventListener("pointermove",(i)=>{if(!_)T.style.cursor=mo(i)?"zoom-in":""}),T?.addEventListener("pointerdown",(i)=>{if(!_)return;S=Bi(i),T.setPointerCapture(i.pointerId),i.preventDefault()}),T?.addEventListener("pointermove",(i)=>{if(!S)return;let r=Bi(i);F={x:Math.min(S.x,r.x),y:Math.min(S.y,r.y),w:Math.abs(r.x-S.x),h:Math.abs(r.y-S.y)};let a=e.querySelector(".draw-box");a.hidden=!1,a.style.cssText=io(F)}),T?.addEventListener("pointerup",()=>{if(F&&F.w>0.01&&F.h>0.01)ro(F);else S=null,F=null}),T?.addEventListener("pointercancel",()=>{S=null,F=null,f()});let bo=e.querySelector(".output"),Ji=e.querySelector("iframe"),vo=e.querySelector(".workbench"),fi=e.querySelector(".inspection-content"),Ii=e.querySelector(".inspector");function Oi(){if(!fi||!Ii)return;Ii.dataset.scrollAbove=String(fi.scrollTop>1),Ii.dataset.scrollBelow=String(fi.scrollHeight-fi.clientHeight-fi.scrollTop>1)}fi?.addEventListener("scroll",Oi,{passive:!0});function $i(){let i=e.querySelector(".map-space");if(i&&i.clientWidth&&i.clientHeight){let w=Si(o.comp.width,o.comp.height,Math.max(1,i.clientWidth-32),Math.max(1,i.clientHeight-32),"fit");T.style.width=`${w.width}px`,T.style.height=`${w.height}px`}let r=e.querySelector(".inspection-content"),a=Array.from(e.querySelectorAll(".pan-viewport"));if(r?.clientHeight){let w=k?Math.max(100,K.clientHeight-(Q?.querySelector(".compare-toolbar")?.clientHeight??0)-(Q?.querySelector(".expanded-title")?.clientHeight??0)-(Q?.querySelector(".view-controls")?.clientHeight??0)-(O?.getBoundingClientRect().height??0)-124):Math.min(t?Number.POSITIVE_INFINITY:248,Math.max(100,r.clientHeight-((a[0]?.getBoundingClientRect().top??r.getBoundingClientRect().top)-r.getBoundingClientRect().top+r.scrollTop)-(Q?.querySelector(".view-controls")?.clientHeight??0)-12));a.forEach((M)=>M.style.height=`${w}px`)}if(d&&a.length){let w=Si(d.box.w*E.comp.width,d.box.h*E.comp.height,Math.min(...a.map((M)=>M.clientWidth)),Math.min(...a.map((M)=>M.clientHeight)),D);e.querySelectorAll(".crop-stage").forEach((M)=>{M.style.width=`${w.width}px`,M.style.height=`${w.height}px`})}if(a.forEach((w)=>{let M=w.scrollWidth>w.clientWidth+1||w.scrollHeight>w.clientHeight+1;w.classList.toggle("pannable",M),w.style.cursor=k?"":"zoom-in",w.setAttribute("role",k?"region":"button"),w.title=k?M?"Move your pointer to pan. You can also scroll, swipe, or use arrow keys.":"":"Click to enlarge comparison"}),bo&&Ji&&d){let w=bo.clientWidth/(d.box.w*E.comp.width);Ji.style.transform=`scale(${w})`,Ji.style.left=`${-d.box.x*E.comp.width*w}px`,Ji.style.top=`${-d.box.y*E.comp.height*w}px`}let c=vo.getBoundingClientRect(),yi=e.querySelector(".region"),A=e.querySelector(".number"),Yi=e.querySelector(".connector path");if(yi&&A&&Yi){let w=yi.getBoundingClientRect(),M=A.getBoundingClientRect(),_i=w.right-c.left,H=w.top+w.height/2-c.top,zi=M.left-c.left-8,Bo=M.top+M.height/2-c.top;Yi.setAttribute("d",`M ${_i} ${H} H ${zi-14} V ${Bo} H ${zi}`)}}function si(i,r){if(!Q||!ti||i===k)return;let a=e.querySelector("#expand-comparison"),c=(k?K:Q).getBoundingClientRect(),yi=window.matchMedia("(prefers-reduced-motion: reduce)").matches,A=Q.querySelector(".pan-viewport"),Yi=A?A.scrollLeft/Math.max(1,A.scrollWidth-A.clientWidth):0,w=A?A.scrollTop/Math.max(1,A.scrollHeight-A.clientHeight):0,M=()=>Q.querySelectorAll(".pan-viewport").forEach((H)=>{H.scrollLeft=Yi*Math.max(0,H.scrollWidth-H.clientWidth),H.scrollTop=w*Math.max(0,H.scrollHeight-H.clientHeight)}),_i=()=>{if(ti.append(Q),O)Ao.append(O),O.inert=!1;K.close(),ti.style.height="",ji=!1,k=!1,a.innerHTML=Y("expand"),a.setAttribute("aria-label","Enlarge comparison"),a.title="Enlarge comparison",a.setAttribute("aria-expanded","false"),$i(),M(),a.focus({preventScroll:!0}),r?.()};if(i){ti.style.height=`${c.height}px`,so(),K.showModal(),k=!0,a.innerHTML=Y("compact"),a.setAttribute("aria-label","Restore comparison"),a.title="Restore comparison (Esc)",a.setAttribute("aria-expanded","true"),$i(),M(),a.focus({preventScroll:!0});let H=K.getBoundingClientRect();if(!yi)K.animate([{transform:`translate(${c.x-H.x}px,${c.y-H.y}px) scale(${c.width/H.width},${c.height/H.height})`,opacity:0.6},{transform:"none",opacity:1}],{duration:240,easing:"cubic-bezier(.2,.8,.2,1)"})}else{if(a.disabled)return;let H=ti.getBoundingClientRect();if(yi){_i();return}if(a.disabled=!0,ji=!0,O)O.inert=!0;let zi=K.animate([{transform:"none",opacity:1},{transform:`translate(${H.x-c.x}px,${H.y-c.y}px) scale(${H.width/c.width},${H.height/c.height})`,opacity:0.6}],{duration:200,easing:"cubic-bezier(.4,0,.2,1)",fill:"forwards"});zi.finished.then(()=>{if(K.isConnected)zi.cancel(),a.disabled=!1,_i()}).catch(()=>{})}}if(Xi=(i)=>si(!1,i),u("expand-comparison",()=>si(!k)),K.addEventListener("cancel",(i)=>{i.preventDefault(),i.stopPropagation(),si(!1)}),K.addEventListener("keydown",(i)=>{if(i.key==="Escape")i.preventDefault(),i.stopPropagation(),si(!1)}),C=new ResizeObserver(()=>{$i(),Oi()}),C.observe(vo),C.observe(K),O)C.observe(O);let uo=e.querySelector(".inspection-content");if(uo)C.observe(uo);let yo=e.querySelector(".repair-context");if(yo)C.observe(yo);let zo=Q?.querySelector(".compare-toolbar");if(zo)C.observe(zo);$i();let Ki=Array.from(e.querySelectorAll(".pan-viewport"));if(Ki.forEach((i)=>{i.scrollLeft=Eo,i.scrollTop=Qo}),Ki.forEach((i)=>{i.querySelectorAll("img").forEach((r)=>r.draggable=!1),i.addEventListener("click",()=>{if(!k)si(!0)}),i.addEventListener("pointermove",(r)=>{if(r.pointerType!=="mouse"||r.buttons||!i.classList.contains("pannable"))return;let a=i.getBoundingClientRect();i.scrollLeft=Fi(r.clientX,a.left,i.clientWidth,i.scrollWidth),i.scrollTop=Fi(r.clientY,a.top,i.clientHeight,i.scrollHeight)}),i.addEventListener("keydown",(r)=>{if(!k&&(r.key==="Enter"||r.key===" ")){r.preventDefault(),r.stopPropagation(),si(!0);return}let c={ArrowLeft:[-48,0],ArrowRight:[48,0],ArrowUp:[0,-48],ArrowDown:[0,48]}[r.key];if(!c)return;r.preventDefault(),r.stopPropagation(),i.scrollLeft+=c[0],i.scrollTop+=c[1]})}),Ki.forEach((i)=>i.addEventListener("scroll",()=>{for(let r of Ki)if(r!==i){if(r.scrollLeft!==i.scrollLeft)r.scrollLeft=i.scrollLeft;if(r.scrollTop!==i.scrollTop)r.scrollTop=i.scrollTop}})),_o&&window.matchMedia("(max-width:800px)").matches){let i=e.querySelector(".inspection-content"),r=e.querySelector(".compare");if(i&&r)i.scrollTop+=r.getBoundingClientRect().top-i.getBoundingClientRect().top}if(Oi(),window.scrollTo(vi,gi),!m)x.onDraftChange?.(structuredClone(n))}return f(),{destroy(){C?.disconnect(),e.replaceChildren()},getDraft(){return structuredClone(n)}}}async function Ro(){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 x=await o.json();if(x.sourceStatus){let e=document.createElement("p");e.textContent=x.sourceStatus,p.before(e)}Jo(p,x.packet,{initialDraft:x.draft,history:x.history,completed:!!x.receipt,onSubmit:async(e)=>{let n=await fetch("/decision",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),t=await n.json();if(!n.ok)throw Error(t.error??"The review could not be saved. Try again.")}})}Ro().catch((p)=>{let o=document.createElement("p");o.textContent=p instanceof Error?p.message:String(p),document.getElementById("review")?.replaceChildren(o)});})(); + ${!a?`

Components

+
${bo.map((i)=>{let r=Y.find((B)=>B.members.some((G)=>G.id===i.id)),t=Y.indexOf(r),h={kind:r.kind,label:r.stateLabel};return``}).join("")}${(z==="pending"?[]:n.missing).map((i,r)=>``).join("")}${!bo.length&&(z==="pending"||!n.missing.length)?`

${z==="pending"?"Nothing left to review. Your decisions are ready.":"No components reviewed yet."}

`:""}
`:""} + ${a?"":y?`
Round ${o.round} submitted · read-only${j.approved} approved · ${j.revisions} changes requested
`:`
${!j.pending&&!fi?``:""}

${l(So)}

`} + `;let N=e.querySelector("#comparison-dialog"),A=e.querySelector(".comparison-panel"),ci=e.querySelector(".comparison-slot"),ri=e.querySelector(".inspector > .review-form"),Co=e.querySelector(".inspector"),yo=()=>{if(N.append(A),ri)N.append(ri)};if(H&&A&&ci)ci.style.height=`${Uo}px`,yo(),N.showModal();if(ii)e.querySelectorAll(".decisions button,#feedback,#split,#save-feedback,#cancel-feedback,#undo-decision,#approve-rest,#submit,#inventory-confirm").forEach((i)=>i.disabled=!0);if(e.querySelector(".inspection-content").scrollTop=Go,y||s)e.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);let zo=e.querySelector(".inventory");if(zo)zo.scrollLeft=Ki;if(!Z&&L)Array.from(e.querySelectorAll(".inventory [data-select]")).find((i)=>i.dataset.select===g)?.scrollIntoView({block:"nearest",inline:"nearest"});if(m)e.getElementById(m)?.focus({preventScroll:!0});else if(Q)Array.from(e.querySelectorAll(".item[data-select]")).find((i)=>i.dataset.select===Q)?.focus({preventScroll:!0});let u=(i,r)=>e.querySelector(`#${i}`)?.addEventListener("click",r);function Si(i,r=!1,t=!1){if(_)return;if(!t)i=Y.find((h)=>h.members.some((B)=>B.id===i))?.representative.id??i;if(C=!t,k=!1,g=i,K="component",ti=!1,ei="fit",hi="isolated",T=!1,w(),r)e.querySelector("#expand-comparison")?.click()}e.querySelectorAll("[data-select]").forEach((i)=>i.onclick=()=>Si(i.dataset.select,i.classList.contains("pin")&&o.components.some((r)=>r.id===i.dataset.select))),u("previous-round",()=>{T=!0,w()}),u("current-round",()=>{T=!1,w()}),u("review-changes",()=>{let i=b().filter((h)=>Ii(h).kind==="pending"),r=i.findIndex((h)=>h.id===g),t=i[(r+1)%i.length];if(t)C=!0,k=!1,g=t.id,K="component",z="pending",T=!1,ei="fit",ti=!1,hi="isolated",w()}),e.querySelectorAll("[data-filter]").forEach((i)=>i.onclick=()=>{z=i.dataset.filter,k=!j.pending&&z==="pending";let r=Y.filter((h)=>z==="all"||h.kind==="pending"===(z==="pending")).map((h)=>h.representative);if(!(z!=="pending"&&n.missing.some((h)=>h.id===g))&&!r.some((h)=>h.id===g)&&r.length)C=!0,g=r[0].id,K="component",T=!1,ei="fit",ti=!1,hi="isolated";w()}),u("show-summary",()=>{k=!0,K="component",z="reviewed",w(),bi("review-summary")}),u("approve",()=>Ui("approve")),u("revise",Wo),u("clear",()=>{if(c)delete n.decisions[c.id],delete q[c.id];F=null,k=!1,z="pending",w()}),u("cancel-feedback",()=>{if(c)delete q[c.id];if(mi)L=!0,mi=!1;w(),bi("revise")}),e.querySelector("#feedback-form")?.addEventListener("submit",(i)=>{i.preventDefault(),Ui("revise")}),e.querySelector("#feedback")?.addEventListener("keydown",(i)=>{let r=i;if(r.key==="Enter"&&(r.metaKey||r.ctrlKey)&&!r.isComposing)r.preventDefault(),r.stopPropagation(),Ui("revise")}),u("undo-decision",()=>{if(!F||s||y)return;let i=F;for(let[r,t]of Object.entries(i.previous))if(t)n.decisions[r]=t;else delete n.decisions[r];delete q[i.id],g=i.id,k=!1,T=!1,K="component",z="all",F=null,w(),bi("approve")}),u("all-instances",()=>{C=!0,g=X.representative.id,T=!1,w()}),e.querySelectorAll("[data-instance]").forEach((i)=>i.onclick=()=>Si(i.dataset.instance,!1,!0)),u("overlay",()=>{ti=!ti,w()}),u("isolated",()=>{hi="isolated",w()}),u("context",()=>{hi="context",w()}),e.querySelector("#zoom")?.addEventListener("change",(i)=>{let r=i.target.value;ei=r==="fit"?"fit":Number(r),w()}),u("background-checker",()=>{ki="checker",w()}),u("background-page",()=>{ki="page",w()}),u("show-all",()=>{li=!li,L=!0,w()}),u("toggle-tray",()=>{mi=!1,L=!L,w()}),u("show-comp",()=>{K="comp",w()}),u("show-component",()=>{K="component",w()}),u("mark",()=>{_=!_,w()}),u("add-box",()=>xo({x:0.35,y:0.35,w:0.2,h:0.2})),u("remove-missing",()=>{n.missing=n.missing.filter((i)=>i.id!==g),g=o.components[0]?.id,w()}),u("approve-rest",()=>{if(fi)return;n=$o(o,n),F=null,k=!0,z="reviewed",K="component",w(),bi("review-summary")}),e.querySelector("#inventory-confirm")?.addEventListener("change",(i)=>{n.inventoryConfirmed=i.target.checked,w()}),e.querySelector("#feedback")?.addEventListener("input",(i)=>{if(c&&q[c.id])q[c.id].feedback=i.target.value}),e.querySelector("#split")?.addEventListener("change",(i)=>{if(c&&q[c.id])q[c.id].split=i.target.checked}),e.querySelector("#missing-name")?.addEventListener("input",(i)=>{if(M)M.name=i.target.value;let r=e.querySelector("#submit");if(r)r.disabled=fi||s||y||!zi(o,n).canSubmit}),e.querySelector("#missing-feedback")?.addEventListener("input",(i)=>{if(M)M.feedback=i.target.value}),e.querySelectorAll("[data-coordinate]").forEach((i)=>i.addEventListener("change",()=>{if(!M)return;let r=i.dataset.coordinate,t=Number(i.value)/100;if(Number.isFinite(t))M.box[r]=Math.max(r==="w"||r==="h"?0.001:0,Math.min(1,t));M.box.w=Math.min(M.box.w,1-M.box.x),M.box.h=Math.min(M.box.h,1-M.box.y),w()})),u("submit",()=>{ao()});let D=e.querySelector(".map");function Fi(i){let r=D.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))}}function ko(i){let r=Fi(i);return o.components.filter(({box:t})=>r.x>=t.x&&r.x<=t.x+t.w&&r.y>=t.y&&r.y<=t.y+t.h).sort((t,h)=>t.box.w*t.box.h-h.box.w*h.box.h)[0]}D?.addEventListener("click",(i)=>{if(_||i.target.closest("[data-select]"))return;let r=ko(i);if(r)Si(r.id,!0)}),D?.addEventListener("pointermove",(i)=>{if(!_)D.style.cursor=ko(i)?"zoom-in":""}),D?.addEventListener("pointerdown",(i)=>{if(!_)return;ni=Fi(i),D.setPointerCapture(i.pointerId),i.preventDefault()}),D?.addEventListener("pointermove",(i)=>{if(!ni)return;let r=Fi(i);pi={x:Math.min(ni.x,r.x),y:Math.min(ni.y,r.y),w:Math.abs(r.x-ni.x),h:Math.abs(r.y-ni.y)};let t=e.querySelector(".draw-box");t.hidden=!1,t.style.cssText=Ti(pi)}),D?.addEventListener("pointerup",()=>{if(pi&&pi.w>0.01&&pi.h>0.01)xo(pi);else ni=null,pi=null}),D?.addEventListener("pointercancel",()=>{ni=null,pi=null,w()});let jo=e.querySelector(".output"),Yi=e.querySelector("iframe"),qo=e.querySelector(".workbench"),vi=e.querySelector(".inspection-content"),Ci=e.querySelector(".inspector");function Pi(){if(!vi||!Ci)return;Ci.dataset.scrollAbove=String(vi.scrollTop>1),Ci.dataset.scrollBelow=String(vi.scrollHeight-vi.clientHeight-vi.scrollTop>1)}vi?.addEventListener("scroll",Pi,{passive:!0});function _i(){let i=e.querySelector(".map-space");if(i&&i.clientWidth&&i.clientHeight){let v=oo(o.comp.width,o.comp.height,Math.max(1,i.clientWidth-32),Math.max(1,i.clientHeight-32),"fit");D.style.width=`${v.width}px`,D.style.height=`${v.height}px`}let r=e.querySelector(".inspection-content"),t=Array.from(e.querySelectorAll(".pan-viewport"));if(r?.clientHeight){let v=H?Math.max(100,N.clientHeight-(A?.querySelector(".compare-toolbar")?.clientHeight??0)-(A?.querySelector(".expanded-title")?.clientHeight??0)-(A?.querySelector(".view-controls")?.clientHeight??0)-(ri?.getBoundingClientRect().height??0)-124):Math.min(a?Number.POSITIVE_INFINITY:248,Math.max(100,r.clientHeight-((t[0]?.getBoundingClientRect().top??r.getBoundingClientRect().top)-r.getBoundingClientRect().top+r.scrollTop)-(A?.querySelector(".view-controls")?.clientHeight??0)-12));t.forEach((J)=>J.style.height=`${v}px`)}if(d&&t.length&&!R){let v=oo(d.box.w*E.comp.width,d.box.h*E.comp.height,Math.min(...t.map((J)=>J.clientWidth)),Math.min(...t.map((J)=>J.clientHeight)),ei);e.querySelectorAll(".crop-stage").forEach((J)=>{J.style.width=`${v.width}px`,J.style.height=`${v.height}px`})}if(t.forEach((v)=>{let J=v.scrollWidth>v.clientWidth+1||v.scrollHeight>v.clientHeight+1;v.classList.toggle("pannable",J),v.style.cursor=H?"":"zoom-in",v.setAttribute("role",H?"region":"button"),v.title=H?J?"Move your pointer to pan. You can also scroll, swipe, or use arrow keys.":"":"Click to enlarge comparison"}),jo&&Yi&&d){let v=jo.clientWidth/(d.box.w*E.comp.width);Yi.style.transform=`scale(${v})`,Yi.style.left=`${-d.box.x*E.comp.width*v}px`,Yi.style.top=`${-d.box.y*E.comp.height*v}px`}let h=qo.getBoundingClientRect(),B=e.querySelector(".region"),G=e.querySelector(".number"),Xi=e.querySelector(".connector path");if(B&&G&&Xi){let v=B.getBoundingClientRect(),J=G.getBoundingClientRect(),Ei=v.right-h.left,$=v.top+v.height/2-h.top,Hi=J.left-h.left-8,Po=J.top+J.height/2-h.top;Xi.setAttribute("d",`M ${Ei} ${$} H ${Hi-14} V ${Po} H ${Hi}`)}}function ui(i,r){if(!A||!ci||i===H)return;let t=e.querySelector("#expand-comparison"),h=(H?N:A).getBoundingClientRect(),B=window.matchMedia("(prefers-reduced-motion: reduce)").matches,G=A.querySelector(".pan-viewport"),Xi=G?G.scrollLeft/Math.max(1,G.scrollWidth-G.clientWidth):0,v=G?G.scrollTop/Math.max(1,G.scrollHeight-G.clientHeight):0,J=()=>A.querySelectorAll(".pan-viewport").forEach(($)=>{$.scrollLeft=Xi*Math.max(0,$.scrollWidth-$.clientWidth),$.scrollTop=v*Math.max(0,$.scrollHeight-$.clientHeight)}),Ei=()=>{if(ci.append(A),ri)Co.append(ri),ri.inert=!1;N.close(),ci.style.height="",Ji=!1,H=!1,t.innerHTML=W("expand"),t.setAttribute("aria-label","Enlarge comparison"),t.title="Enlarge comparison",t.setAttribute("aria-expanded","false"),_i(),J(),t.focus({preventScroll:!0}),r?.()};if(i){ci.style.height=`${h.height}px`,yo(),N.showModal(),H=!0,t.innerHTML=W("compact"),t.setAttribute("aria-label","Restore comparison"),t.title="Restore comparison (Esc)",t.setAttribute("aria-expanded","true"),_i(),J(),t.focus({preventScroll:!0});let $=N.getBoundingClientRect();if(!B)N.animate([{transform:`translate(${h.x-$.x}px,${h.y-$.y}px) scale(${h.width/$.width},${h.height/$.height})`,opacity:0.6},{transform:"none",opacity:1}],{duration:240,easing:"cubic-bezier(.2,.8,.2,1)"})}else{if(t.disabled)return;let $=ci.getBoundingClientRect();if(B){Ei();return}if(t.disabled=!0,Ji=!0,ri)ri.inert=!0;let Hi=N.animate([{transform:"none",opacity:1},{transform:`translate(${$.x-h.x}px,${$.y-h.y}px) scale(${$.width/h.width},${$.height/h.height})`,opacity:0.6}],{duration:200,easing:"cubic-bezier(.4,0,.2,1)",fill:"forwards"});Hi.finished.then(()=>{if(N.isConnected)Hi.cancel(),t.disabled=!1,Ei()}).catch(()=>{})}}if(Zi=(i)=>ui(!1,i),u("expand-comparison",()=>ui(!H)),N.addEventListener("cancel",(i)=>{i.preventDefault(),i.stopPropagation(),ui(!1)}),N.addEventListener("keydown",(i)=>{if(i.key==="Escape")i.preventDefault(),i.stopPropagation(),ui(!1)}),ai=new ResizeObserver(()=>{_i(),Pi()}),ai.observe(qo),ai.observe(N),ri)ai.observe(ri);let Ho=e.querySelector(".inspection-content");if(Ho)ai.observe(Ho);let Mo=e.querySelector(".repair-context");if(Mo)ai.observe(Mo);let Vo=A?.querySelector(".compare-toolbar");if(Vo)ai.observe(Vo);_i();let Li=Array.from(e.querySelectorAll(".pan-viewport"));if(Li.forEach((i)=>{i.scrollLeft=Ao,i.scrollTop=Bo}),Li.forEach((i)=>{i.querySelectorAll("img").forEach((r)=>r.draggable=!1),i.addEventListener("click",()=>{if(!H)ui(!0)}),i.addEventListener("pointermove",(r)=>{if(r.pointerType!=="mouse"||r.buttons||!i.classList.contains("pannable"))return;let t=i.getBoundingClientRect();i.scrollLeft=ro(r.clientX,t.left,i.clientWidth,i.scrollWidth),i.scrollTop=ro(r.clientY,t.top,i.clientHeight,i.scrollHeight)}),i.addEventListener("keydown",(r)=>{if(!H&&(r.key==="Enter"||r.key===" ")){r.preventDefault(),r.stopPropagation(),ui(!0);return}let h={ArrowLeft:[-48,0],ArrowRight:[48,0],ArrowUp:[0,-48],ArrowDown:[0,48]}[r.key];if(!h)return;r.preventDefault(),r.stopPropagation(),i.scrollLeft+=h[0],i.scrollTop+=h[1]})}),Li.forEach((i)=>i.addEventListener("scroll",()=>{for(let r of Li)if(r!==i){if(r.scrollLeft!==i.scrollLeft)r.scrollLeft=i.scrollLeft;if(r.scrollTop!==i.scrollTop)r.scrollTop=i.scrollTop}})),Zo&&window.matchMedia("(max-width:800px)").matches){let i=e.querySelector(".inspection-content"),r=e.querySelector(".compare");if(i&&r)i.scrollTop+=r.getBoundingClientRect().top-i.getBoundingClientRect().top}if(Pi(),window.scrollTo(gi,P),!y)x.onDraftChange?.(structuredClone(n))}return w(),{destroy(){ai?.disconnect(),e.replaceChildren()},getDraft(){return structuredClone(n)}}}async function rr(){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 x=await o.json();if(x.sourceStatus){let e=document.createElement("p");e.textContent=x.sourceStatus,p.before(e)}Xo(p,x.packet,{initialDraft:x.draft,history:x.history,completed:!!x.receipt,onSubmit:async(e)=>{let n=await fetch("/decision",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)}),a=await n.json();if(!n.ok)throw Error(a.error??"The review could not be saved. Try again.")}})}rr().catch((p)=>{let o=document.createElement("p");o.textContent=p instanceof Error?p.message:String(p),document.getElementById("review")?.replaceChildren(o)});})(); diff --git a/crates/context/src/component_review/manifest.rs b/crates/context/src/component_review/manifest.rs index 1231f1985..f4244cf3a 100644 --- a/crates/context/src/component_review/manifest.rs +++ b/crates/context/src/component_review/manifest.rs @@ -121,13 +121,21 @@ pub fn freeze(project: &Path, input: &Value) -> Result<(Value, BTreeMap errors.push(format!("component review omitted measured region {}", region["id"])), + Some(component) if matches!(region["kind"].as_str(), Some("text" | "control")) && component["preview"]["kind"] != "page" => { + errors.push(format!("semantic region {} requires a rendered code preview", region["id"])); + } + _ => {} } } + if !errors.is_empty() { + return Err(errors.join("\n")); + } } view(&mut packet["comp"], project, &mut files, &mut comp_files)?; @@ -170,8 +178,9 @@ pub fn freeze(project: &Path, input: &Value) -> Result<(Value, BTreeMap>().join("\n")); } let mut ids = BTreeSet::new(); let components = packet["components"] diff --git a/crates/context/src/component_review/tests.rs b/crates/context/src/component_review/tests.rs index a711778eb..a4d48ebf5 100644 --- a/crates/context/src/component_review/tests.rs +++ b/crates/context/src/component_review/tests.rs @@ -709,3 +709,18 @@ fn component_review_refuses_groups_that_mix_measured_roles() { fs::write(&path, spec.to_string()).unwrap(); assert_eq!(manifest::freeze(&f.project,&input).unwrap().0["components"].as_array().unwrap().len(),3); } + +#[test] +fn measured_inventory_reports_all_independent_failures_before_capture() { + let f = Fixture::new(); + fs::create_dir_all(f.project.join(".impeccable/build")).unwrap(); + fs::write(f.project.join(".impeccable/build/spec.json"), br#"{"regions":[{"id":"missing-one","kind":"plate"},{"id":"control","kind":"control"},{"id":"missing-two","kind":"text"}]}"#).unwrap(); + let mut input = f.manifest(); + input["stage"] = json!("components"); + input["components"][1]["preview"] = json!({"kind":"image","path":"art.png"}); + let error = store::prepare(&f.store, &f.project, &input).unwrap_err(); + assert!(error.contains("missing-one")); + assert!(error.contains("missing-two")); + assert!(error.contains("semantic region \"control\" requires a rendered code preview")); + assert!(!f.store.exists(), "invalid inventory must not publish a review"); +}