diff --git a/crates/context/assets/component-review.js b/crates/context/assets/component-review.js
index 5abe5ef1d..8edf4eb49 100644
--- a/crates/context/assets/component-review.js
+++ b/crates/context/assets/component-review.js
@@ -1,11 +1,11 @@
-(()=>{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=`
+(()=>{function ki(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 I(p,o,a){let r=o.decisions[p.id],n=r?.revision===p.revision?r:void 0,c=ki(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 po(p){return{packetRevision:p.revision,decisions:{},missing:[],inventoryConfirmed:!1}}function Vo(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 li(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,g=n>0||o.missing.length>0;return{approved:r,revisions:n,pending:c,hasFeedback:g,canSubmit:o.packetRevision===p.revision&&o.missing.every((s)=>s.name.trim()&&Vo(s.box))&&(g||!c&&o.inventoryConfirmed)}}function ao(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 to(p,o){if(!li(p,o).canSubmit)throw Error("Review is incomplete or stale");return{schemaVersion:1,requestId:p.id,...structuredClone(o)}}function ji(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 xo(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(I(c,o).kind==="pending")return c.id}}function Yi(p,o,a){return a==="all"||I(p,o).kind==="pending"===(a==="pending")}function _i(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 Li(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 lo=`
: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}
.review>.preview-note{flex-shrink:0;margin:0;padding:8px 24px;font-size:11px}.review>.round-summary{flex-shrink:0;margin:0;padding:8px 24px;border-top:0;gap:6px 16px;max-height:120px;overflow:auto}.round-summary p{font-size:12px;gap:6px 14px}.round-summary button{font-size:12px;min-height:32px;padding:5px 10px}
.review>.workbench{flex:1;min-height:0;align-items:stretch;padding:20px 24px;gap:32px;overflow:hidden;grid-template-columns:minmax(0,1.12fr) minmax(0,1fr)}.reference{display:flex;flex-direction:column;min-height:0;width:100%;max-width:none;margin:0}.reference>.section-head{flex-shrink:0;margin-bottom:10px}.map-space{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;background:#eeefec;border:1px solid var(--line);overflow:hidden}.map{flex-shrink:0;max-width:100%;max-height:100%}.map-legend{flex-shrink:0;margin-top:9px;gap:6px 14px}.map-caption{flex-shrink:0;min-height:0;padding-top:6px;font-size:10px}.map-caption button{display:inline-block;margin:0 0 0 8px;min-height:28px;padding:3px 7px}
.workbench>.inspector{height:100%;min-height:0;padding:0;border:0}.inspector>.section-head{margin-bottom:10px;min-height:32px}.section-head h2{font-size:16px}.inspection-content{padding-bottom:8px}.review-form{max-height:55%;overflow-y:auto;scrollbar-width:thin;scrollbar-gutter:stable}.material{min-height:28px}.material strong{font-size:13px}.compare-toolbar{margin-bottom:12px}.previous-feedback{padding:10px 12px}.repair-context{margin-bottom:12px}.preview-round{margin-bottom:10px}.decision-title p{font-size:11px}.decisions>button{min-height:40px;font-size:13px}.decision-title strong{font-size:13px}.view-controls{margin-top:8px}.component-details{height:auto;max-height:80px}
-.review>.inventory-section{height:236px;flex-shrink:0;display:flex;flex-direction:column;min-height:0;margin:0;padding:10px 24px 8px;border-top:1px solid var(--line);background:var(--color-panel,#f5f5f2);overflow:hidden}.inventory-section>.section-head{margin:0 0 8px;min-height:36px;flex-shrink:0;gap:10px}.inventory-section h2{font-size:14px}.inventory{flex:1;min-height:0;align-items:stretch;margin:0;padding:3px 3px 7px;overflow-x:auto;overflow-y:hidden}.inventory.all{overflow:auto;align-items:start;grid-auto-rows:160px}.inventory .item{flex:0 0 136px;padding:8px;gap:3px;grid-template-rows:auto auto minmax(24px,1fr) auto;min-height:0}.inventory .item-thumb{height:46px;margin-bottom:3px}.inventory .thumb-crop{max-height:46px}.inventory .item-number{font-size:11px;min-height:16px;padding:0}.inventory .item strong{font-size:12px;min-height:26px;line-height:1.2}.inventory .state{font-size:11px;padding-top:4px}.tray-actions{display:flex;gap:6px;align-items:center}.tray-actions button{white-space:nowrap;font-size:11px;min-height:32px}.tray-actions #toggle-tray{display:flex;align-items:center;gap:6px}.tray-actions svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}.review>.inventory-section.tray-expanded{height:min(42dvh,390px)}.review>.inventory-section.tray-collapsed{height:56px;padding-block:10px}.tray-collapsed .inventory{display:none}.tray-collapsed>.section-head{margin-bottom:0}
+.review>.inventory-section{height:236px;flex-shrink:0;display:flex;flex-direction:column;min-height:0;margin:0;padding:10px 24px 8px;border-top:1px solid var(--line);background:var(--color-panel,#f5f5f2);overflow:hidden}.inventory-section>.section-head{margin:0 0 8px;min-height:36px;flex-shrink:0;gap:10px}.inventory-section h2{font-size:14px}.inventory{flex:1;min-height:0;align-items:stretch;margin:0;padding:3px 3px 7px;overflow-x:auto;overflow-y:hidden}.inventory.all{overflow:auto;align-items:start;grid-auto-rows:160px}.inventory .item{flex:0 0 136px;padding:8px;gap:3px;grid-template-rows:auto auto minmax(24px,1fr) auto;min-height:0}.inventory .item-thumb{height:46px;margin-bottom:3px}.inventory .thumb-crop{max-height:46px}.inventory .item-number{font-size:11px;min-height:16px;padding:0}.inventory .item strong{font-size:12px;min-height:26px;line-height:1.2;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.inventory .state{font-size:11px;padding-top:4px}.tray-actions{display:flex;gap:6px;align-items:center}.tray-actions button{white-space:nowrap;font-size:11px;min-height:32px}.tray-actions #toggle-tray{display:flex;align-items:center;gap:6px}.tray-actions svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}.review>.inventory-section.tray-expanded{height:min(42dvh,390px)}.review>.inventory-section.tray-collapsed{height:56px;padding-block:10px}.tray-collapsed .inventory{display:none}.tray-collapsed>.section-head{margin-bottom:0}
.review>footer{flex-shrink:0;margin:0;padding:12px 24px;gap:16px;border-top:1px solid var(--line);background:var(--paper);align-items:center}.review>footer>div:first-child{display:flex;align-items:center;gap:14px;min-width:0}.review>footer .check{margin:0;max-width:240px;font-size:11px}.review>footer #approve-rest{min-height:36px;font-size:12px;max-width:210px;padding:7px 10px}.review>footer .submit-area{gap:12px}.review>footer .submit-area p{font-size:11px;max-width:24ch}.review>footer .primary{min-height:40px;font-size:13px}.mobile-panes{display:none}
/* Depth describes the shell: recessed work area, raised inspector, anchored docks. */
.review{--workspace:#e3e6e2;--canvas:#d5dad5;--dock:#f6f7f4;--surface:#fff;background:var(--workspace)}
@@ -27,7 +27,7 @@
.review>.inventory-section{position:relative;z-index:8;background:var(--dock);border-top-color:#b9c3b8;box-shadow:0 -3px 5px #2433280c,0 -10px 24px #24332814}
.inventory-section>.section-head{border-bottom:1px solid #d9dfd5;padding-bottom:8px;margin-bottom:8px}
.tray-collapsed>.section-head{border:0;padding-bottom:0;margin-bottom:0}
-.inventory{background:#e9ece5;border-radius:4px;padding:7px 7px 9px}
+.inventory{background:transparent;border-radius:0;padding:3px 3px 7px}
.inventory .item{background:#fafbf8}
.inventory .item.active{background:#fff}
.review>footer{position:relative;z-index:9;background:var(--surface);border-top-color:#ccd4c7;box-shadow:0 -2px 5px #2433280b}
@@ -50,16 +50,16 @@
@media(max-width:800px){
.review>header{padding:12px 14px;align-items:center}.review h1{font-size:25px}.review>header p{max-width:68vw;font-size:11px}.review .badge{display:none}.review>.preview-note{padding:6px 14px}.review>.round-summary{padding:6px 14px;max-height:76px;gap:4px 8px}.round-summary p{max-width:calc(100% - 44px);gap:4px 10px;font-size:11px}.round-summary p>span{font-size:10px}.round-summary button{font-size:11px}
.mobile-panes{display:flex;flex-shrink:0;gap:4px;padding:7px 14px;border-bottom:1px solid var(--line);background:var(--paper)}.mobile-panes button{flex:1;font-size:12px;min-height:32px;padding:5px;border-color:transparent;background:transparent}.mobile-panes button[aria-pressed=true]{background:#e8efec;color:var(--teal);box-shadow:none;border-color:#bdd0c8}
- .review>.workbench{display:block;padding:12px 14px;min-height:0;overflow:hidden}.workbench[data-mobile-pane=component]>.reference,.workbench[data-mobile-pane=comp]>.inspector{display:none}.reference{height:100%;max-width:none}.connector{display:none}.inspector{height:100%;border:0}.map-legend{gap:6px 12px;font-size:10px}.map-caption{font-size:9px}.reference .section-head button{min-height:30px;padding:4px 8px}.reference .section-head h2{font-size:14px}.inspector>.section-head{min-height:26px;margin-bottom:0;padding:9px 12px}.inspection-content{padding:10px 8px 10px 12px}.inspector>.review-form{padding:8px 8px 8px 12px}.number{width:23px;height:23px;font-size:11px}.section-head h2{font-size:14px}.review-form{padding-top:8px}.decision-title strong{font-size:12px}.decision-title p{font-size:10px}.decisions>button{min-height:36px}.review-form .feedback{font-size:12px}.review-form .feedback textarea{min-height:60px}.compare{max-width:none}
+ .review>.workbench{display:block;padding:12px 14px;min-height:0;overflow:hidden}.workbench[data-mobile-pane=component]>.reference,.workbench[data-mobile-pane=comp]>.inspector{display:none}.reference{height:100%;max-width:none}.connector{display:none}.inspector{height:100%;border:0}.map-legend{gap:6px 12px;font-size:10px}.map-caption{font-size:9px}.reference .section-head button{min-height:30px;padding:4px 8px}.reference .section-head h2{font-size:14px}.inspector>.section-head{min-height:26px;margin-bottom:0;padding:9px 12px}.inspection-content{padding:10px 8px 10px 12px}.inspector>.review-form{padding:8px 8px 8px 12px}.number{width:23px;height:23px;font-size:11px}.section-head h2{font-size:14px}.review-form{padding-top:8px}.decision-title strong{font-size:12px}.decision-title p{font-size:10px}.decisions>button{min-height:36px}.review-form .feedback-field{font-size:12px}.review-form .feedback-field textarea{min-height:60px}.compare{max-width:none}
.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 lo=`
+`;var ho=`
:host{display:block;color:var(--color-text,#292929);font:14px/1.45 var(--font-sans,Arial,sans-serif);--line:var(--color-border,#ddd);--paper:var(--color-panel,#fff);--muted:var(--color-muted,#666);--teal:var(--color-patina,#28625e);--warn:var(--color-warn,#8a5b30);--selection:#43897f}
*{box-sizing:border-box}h1,h2,p,figure{margin:0}button,input,textarea{font:inherit}button{cursor:pointer;border:1px solid var(--line);border-radius:4px;background:var(--paper);color:inherit;padding:8px 12px;min-height:36px}button:hover{border-color:var(--teal);color:var(--teal)}button:disabled{cursor:default;opacity:.45}button:focus-visible,input:focus-visible,textarea:focus-visible{outline:2px solid var(--teal);outline-offset:3px}button[aria-pressed=true]{box-shadow:inset 0 0 0 1px var(--teal)}input[type=checkbox]{accent-color:var(--teal);width:16px;height:16px;flex-shrink:0}textarea,input:not([type=checkbox]){width:100%;background:var(--paper);color:inherit;border:1px solid #999;border-radius:4px;padding:9px 10px}textarea{resize:vertical;min-height:80px}::selection{background:#c7ddd8}a{color:var(--teal)}
.review{max-width:1600px;margin:auto;padding:24px 28px 0}header{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:16px}h1{font:400 40px/1.05 var(--font-display,Arial,sans-serif);letter-spacing:-.02em}header p{margin-top:8px;font-size:15px}header p span,.medium{color:var(--muted)}.badge{border:1px solid var(--line);padding:5px 10px;font-size:12px;white-space:nowrap}.preview-note{color:var(--muted);font-size:12px;border-bottom:1px solid var(--line);padding-bottom:16px;margin-bottom:24px}
.connector{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:5;overflow:visible}.connector path{fill:none;stroke:var(--selection);stroke-width:2}.workbench{align-items:start;display:grid;grid-template-columns:minmax(0,1.18fr) minmax(0,1fr);gap:40px;position:relative}.section-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px;min-height:36px}.section-head h2{font-size:17px;font-weight:500;line-height:1.2}.section-head>span,.section-head h2>span:not(.number){font-size:12px;color:var(--muted)}.section-head button{font-size:12px}.number{display:inline-flex;align-items:center;justify-content:center;width:27px;height:27px;border:1px solid var(--teal);color:var(--teal);margin-right:7px;font:12px var(--font-mono,monospace)}
-.map{position:relative;background:#eaeaea;isolation:isolate}.comp{width:100%;height:100%;display:block;user-select:none}.map.marking{touch-action:none;cursor:crosshair}.map.marking .pin{pointer-events:none;opacity:.25}.pin{position:absolute;transform:translate(-50%,-50%);padding:0;min-height:25px;width:25px;height:25px;border-radius:50%;border:1px solid #fff;background:#fff;color:#292929;font:11px var(--font-mono,monospace);box-shadow:0 1px 4px #0006;z-index:2}.pin.selected{background:var(--teal);color:#fff;border-color:var(--teal);box-shadow:none;outline:none;z-index:3}.pin:focus-visible{outline:2px solid white;outline-offset:3px}.region,.draw-box{position:absolute;pointer-events:none;outline:2px solid var(--selection);z-index:1}.draw-box{background:#28625e33;z-index:4}.map-caption{font-size:12px;color:var(--muted);padding-top:12px;min-height:40px}.map-caption button{display:block;margin-top:10px}.compare{display:grid;grid-template-columns:1fr 1fr;gap:12px}.compare figure{min-width:0}.compare figcaption{height:24px;min-height:0;font-size:12px;margin-bottom:9px}.compare figcaption span{display:block;color:var(--muted);font-size:11px}.crop-stage{position:relative;overflow:hidden;background:var(--comp-background,#eee);min-width:0}.crop-image{position:absolute;max-width:none;height:auto}.asset{display:block;width:100%;height:100%;object-fit:cover}.crop-stage iframe{position:absolute;max-width:none;border:0;transform-origin:top left;pointer-events:none}.overlay-image{opacity:.5;pointer-events:none}.component-note{font-size:12px;line-height:1.5;color:var(--muted);margin:6px 0 0}.decisions{display:flex;gap:10px;padding:10px 0;background:var(--paper);position:sticky;bottom:0;z-index:6}.decisions>button{flex:1;min-height:48px;font-weight:600;font-size:15px;border-color:var(--teal)}.decision-approve{background:var(--teal);color:white}.decision-approve:hover{background:#224e4b;color:white}.decision-revise{color:var(--teal);background:var(--paper)}.decisions>.quiet{flex:0;border:0;background:transparent;font-size:12px}.decisions .approved{color:var(--teal);background:#e9f0ed;border-color:var(--teal)}.decisions .revise{color:var(--warn);background:#f7f0e6;border-color:var(--warn)}.feedback{display:block;margin-top:16px;font-size:13px}.feedback>span{float:right;color:var(--muted);font-size:12px}.feedback textarea,.feedback input{display:block;margin-top:7px}.check{display:flex;align-items:center;gap:7px;font-size:12px;line-height:1.5;margin-top:12px}.coordinates{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:16px 0}.coordinates label{font-size:12px}.coordinates input{margin-top:5px}
+.map{position:relative;background:#eaeaea;isolation:isolate}.comp{width:100%;height:100%;display:block;user-select:none}.map.marking{touch-action:none;cursor:crosshair}.map.marking .pin{pointer-events:none;opacity:.25}.pin{position:absolute;transform:translate(-50%,-50%);padding:0;min-height:25px;width:25px;height:25px;border-radius:50%;border:1px solid #fff;background:#fff;color:#292929;font:11px var(--font-mono,monospace);box-shadow:0 1px 4px #0006;z-index:2}.pin.selected{background:var(--teal);color:#fff;border-color:var(--teal);box-shadow:none;outline:none;z-index:3}.pin:focus-visible{outline:2px solid white;outline-offset:3px}.region,.draw-box{position:absolute;pointer-events:none;outline:2px solid var(--selection);z-index:1}.draw-box{background:#28625e33;z-index:4}.map-caption{font-size:12px;color:var(--muted);padding-top:12px;min-height:40px}.map-caption button{display:block;margin-top:10px}.compare{display:grid;grid-template-columns:1fr 1fr;gap:12px}.compare figure{min-width:0}.compare figcaption{height:24px;min-height:0;font-size:12px;margin-bottom:9px}.compare figcaption span{display:block;color:var(--muted);font-size:11px}.crop-stage{position:relative;overflow:hidden;background:var(--comp-background,#eee);min-width:0}.crop-image{position:absolute;max-width:none;height:auto}.asset{display:block;width:100%;height:100%;object-fit:cover}.crop-stage iframe{position:absolute;max-width:none;border:0;transform-origin:top left;pointer-events:none}.overlay-image{opacity:.5;pointer-events:none}.component-note{font-size:12px;line-height:1.5;color:var(--muted);margin:6px 0 0}.decisions{display:flex;gap:10px;padding:10px 0;background:var(--paper);position:sticky;bottom:0;z-index:6}.decisions>button{flex:1;min-height:48px;font-weight:600;font-size:15px;border-color:var(--teal)}.decision-approve{background:var(--teal);color:white}.decision-approve:hover{background:#224e4b;color:white}.decision-revise{color:var(--teal);background:var(--paper)}.decisions>.quiet{flex:0;border:0;background:transparent;font-size:12px}.decisions .approved{color:var(--teal);background:#e9f0ed;border-color:var(--teal)}.decisions .revise{color:var(--warn);background:#f7f0e6;border-color:var(--warn)}.feedback-field{display:block;margin-top:16px;font-size:13px}.feedback-field>span{float:right;color:var(--muted);font-size:12px}.feedback-field textarea,.feedback-field input{display:block;margin-top:7px}.check{display:flex;align-items:center;gap:7px;font-size:12px;line-height:1.5;margin-top:12px}.coordinates{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:16px 0}.coordinates label{font-size:12px}.coordinates input{margin-top:5px}
.inventory-section{margin-top:28px;border-top:1px solid var(--line);padding-top:16px}.inventory-section .section-head{margin-bottom:10px}.inventory{display:flex;gap:8px;overflow-x:auto;padding:3px 3px 14px;scrollbar-color:#a6bcb8 #eee;scrollbar-width:thin}.item{position:relative;flex:0 0 134px;display:grid;grid-template-columns:20px 1fr;column-gap:8px;row-gap:3px;padding:10px;text-align:left;background:transparent}.item strong{grid-column:2;font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item.active{background:var(--paper);border-color:var(--teal)}.thumb-crop{position:relative;display:block;overflow:hidden}.item-thumb{display:flex;align-items:center;justify-content:center;grid-column:1/-1;position:relative;overflow:hidden;width:100%;height:76px;background:var(--comp-background,#eee);margin-bottom:7px}.item-thumb img{width:100%;height:100%;object-fit:contain}.item-thumb img[style]{height:auto}.inventory.all{display:grid;grid-template-columns:repeat(auto-fill,minmax(128px,1fr));overflow:visible}.item-number{grid-row:2/4;font:12px var(--font-mono,monospace);color:var(--muted);padding-top:2px}.state{grid-column:2;font-size:11px;color:var(--muted)}.state.approve{color:var(--teal)}.state.revise{color:var(--warn)}footer{display:flex;justify-content:space-between;gap:24px;padding:20px 0 24px;border-top:1px solid var(--line);margin-top:8px}.submit-area{display:flex;align-items:center;gap:20px}.submit-area p{font-size:12px;color:var(--muted);max-width:28ch}.primary{min-height:46px;font-weight:600;background:var(--teal);border-color:var(--teal);color:white;white-space:nowrap}.primary:hover{color:white;background:#224e4b}.primary:disabled{opacity:.45}.inspector>p{margin:12px 0}
@media(min-width:1300px){.workbench{gap:56px}.review{padding-top:32px}.component-note{max-width:62ch}}
@media(max-width:800px){.connector{display:none}.review{padding:20px 16px 0}.workbench{grid-template-columns:1fr;gap:24px}.reference{max-width:640px;margin:auto;width:100%}.inspector{border-top:1px solid var(--line);padding-top:16px}.compare{max-width:640px}.inventory-section .section-head{align-items:flex-start;flex-direction:column;gap:4px}footer{flex-direction:column}.submit-area{justify-content:space-between}.badge{font-size:11px}.section-head{gap:8px}h1{font-size:34px}header{align-items:flex-start}.section-head h2{font-size:16px}}
@@ -88,8 +88,8 @@
@media(max-width:800px){.inventory-section .section-head{gap:10px}.inventory-filters{width:100%}.inventory-filters button{padding-inline:7px;font-size:11px;flex:1}.inventory-filters b{margin-left:3px}}
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto}}
.item-medium{margin-left:auto;font-weight:400;display:flex;align-items:center;gap:4px;font-size:10px;color:var(--muted);min-height:16px}.item-medium .utility-icon{width:14px;height:14px;flex-shrink:0}.material>.utility-icon{width:18px;height:18px;align-self:center;color:var(--teal)}
-.feedback-actions{display:flex;align-items:center;gap:10px;margin-top:12px;flex-wrap:wrap}.feedback-actions .primary{margin-left:auto;display:inline-flex;align-items:center;gap:10px}.feedback-actions svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.5}.feedback-hint,.shortcut-hint{font-size:12px;color:var(--muted)}.feedback-hint{margin-top:6px}.shortcut-hint{flex-basis:100%;text-align:right}.saved-feedback{font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere;margin:8px 0}.decision-notice{display:flex;align-items:center;gap:12px;font-size:12px;margin-bottom:10px}.decision-notice>span{flex:1;overflow-wrap:anywhere}.decision-notice button{color:var(--teal);flex-shrink:0}.review-summary{padding:16px 8px}.review-summary h2{font-size:24px}.review-summary p{margin-top:12px;color:var(--muted)}.summary-decisions{margin-top:24px;display:grid}.summary-decisions button{display:grid;grid-template-columns:1fr auto;gap:6px 16px;text-align:left;border:0;border-bottom:1px solid var(--line);border-radius:0;padding:12px 0;background:transparent}.summary-decisions strong{font-size:13px;font-weight:500}.summary-decisions span{font-size:12px;color:var(--teal)}.summary-decisions small{grid-column:1/-1;font-size:12px;color:var(--muted);white-space:pre-wrap;overflow-wrap:anywhere}
-`+xo+`
+.feedback-actions{display:flex;align-items:center;gap:10px;margin-top:12px;flex-wrap:wrap}.feedback-actions .primary{margin-left:auto;display:inline-flex;align-items:center;gap:10px}.feedback-actions svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.5}.feedback-hint,.shortcut-hint{font-size:12px;color:var(--muted)}.feedback-hint{margin-top:6px}.shortcut-hint{flex-basis:100%;text-align:right}.saved-feedback{font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere;margin:8px 0}.decision-notice{display:flex;align-items:center;gap:12px;font-size:12px;margin-bottom:10px}.decision-notice>span{flex:1;overflow-wrap:anywhere}.decision-notice button{color:var(--teal);flex-shrink:0}.review-summary{padding: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}
+`+lo+`
.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. */
@@ -123,47 +123,47 @@
.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`${Vo[p]} `}var x=(p)=>p.replace(/[&<>"']/g,(o)=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[o]),C=(p)=>`${p*100}%`,P=(p)=>{let o=new URL(p,location.href);if(!["http:","https:"].includes(o.protocol))throw Error("Unsupported preview URL");return x(o.href)};function ho(p,o,a){let r=p.attachShadow({mode:"open"}),n=structuredClone(a.initialDraft??no(o)),c=()=>[...o.components].sort((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"}. Undo
`:"",y=zi(o,n),j=a.history,N=d?yi(d.id,j):void 0,ei=j?.packet.components.find((i)=>i.id===d?.id),A=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=`
-
+`;var Eo={chevronDown:' ',code:' ',image:' ',expand:' ',compact:' ',hideTray:' ',showTray:' ',next:' ',mark:' ',close:' ',undo:' ',external:' ',zoom:' '};function J(p){return`${Eo[p]} `}var l=(p)=>p.replace(/[&<>"']/g,(o)=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[o]),P=(p)=>`${p*100}%`,ii=(p)=>{let o=new URL(p,location.href);if(!["http:","https:"].includes(o.protocol))throw Error("Unsupported preview URL");return l(o.href)};function go(p,o,a){let r=p.attachShadow({mode:"open"}),n=structuredClone(a.initialDraft??po(o)),c=()=>[...o.components].sort((f,m)=>I(f,n,a.history).priority-I(m,n,a.history).priority),g=c().find((f)=>I(f,n,a.history).kind!=="approved")?.id??o.components[0]?.id,s=li(o,n).pending?"pending":"reviewed",L=!1,T=!1,H=a.completed??!1,qi="",N={},v=!li(o,n).pending,U=null,so=/Mac|iPhone|iPad/.test(navigator.platform)?"⌘Enter":"Ctrl+Enter",O=!1,G=!1,R=!1,V=!0,pi=!1,q="comp",Ti=q,W="fit",di="checker",X=!1,oi="isolated",Mi,Xi="fit",Z=null,A=null,ai=null,hi=' ',si=' ',Qi=(f)=>`left:${P(f.x)};top:${P(f.y)};width:${P(f.w)};height:${P(f.h)}`;function ti(f){r.getElementById(f)?.focus({preventScroll:!0})}function fo(f){let m=xo(o,n,f);if(v=!m,m)g=m;q="component",X=!1,O=!1,W="fit",oi="isolated",s=v?"reviewed":"pending",d(),ti(v?"review-summary":N[g]?"feedback":"approve")}function Hi(f){if(T||H||X)return;let m=o.components.find((Vi)=>Vi.id===g);if(!m)return;let Q=n.decisions[m.id],gi=Q?.revision===m.revision?Q:void 0;U={id:m.id,name:m.name,action:f,previous:Q?{...Q}:void 0};let fi=N[m.id]??gi;if(n.decisions[m.id]={revision:m.revision,action:f,feedback:f==="revise"?fi?.feedback??"":"",split:f==="revise"&&(fi?.split??!1)},delete N[m.id],pi)V=!0,pi=!1;fo(m.id)}function co(){if(T||H||X)return;let f=o.components.find((gi)=>gi.id===g);if(!f)return;let m=n.decisions[f.id],Q=m?.revision===f.revision?m:void 0;if(N[f.id]??={feedback:Q?.feedback??"",split:Q?.split??!1},V&&(r.querySelector(".workbench")?.clientHeight??0)<420)pi=!0,V=!1;v=!1,d(),ti("feedback")}function Ui(f){let m=`missing-${crypto.randomUUID()}`;n.missing.push({id:m,name:"Missing component",feedback:"",box:f}),n.inventoryConfirmed=!1,v=!1,g=m,q="component",L=!1,Z=null,A=null,d(),r.querySelector("#missing-name")?.focus()}function d(){ai?.disconnect();let f=r.activeElement,m=f?.id,Q=f?.dataset.select,gi=window.scrollX,fi=window.scrollY,Vi=r.querySelector(".inventory")?.scrollLeft??0,ci=Mi===g,mo=q==="component"&&(!ci||Ti!==q);Ti=q;let wo=ci?r.querySelector(".inspection-content")?.scrollTop??0:0,bo=ci&&(r.querySelector(".changed-files")?.open??!1),uo=r.querySelector(".comparison-slot")?.clientHeight??200,Wi=r.querySelector(".pan-viewport"),Zi=Mi===g&&Xi===W,vo=Zi?Wi?.scrollLeft??0:0,yo=Zi?Wi?.scrollTop??0:0;Mi=g,Xi=W;let h=o.components.find((i)=>i.id===g),z=n.missing.find((i)=>i.id===g),Ai=h?.box??z?.box,Bi=h?o.components.indexOf(h)+1:o.components.length+n.missing.findIndex((i)=>i.id===g)+1,Ii=h?n.decisions[h.id]:void 0,ei=Ii?.revision===h?.revision?Ii:void 0,Ei=h?N[h.id]:void 0,D=Object.keys(N).length>0,zo=h?!o.components.some((i)=>i.id!==h.id&&I(i,n).kind==="pending"):!1,Ji=U?`${l(U.name)} ${U.action==="approve"?"approved":"flagged for repair"}. Undo
`:"",u=li(o,n),M=a.history,$=h?ki(h.id,M):void 0,ri=M?.packet.components.find((i)=>i.id===h?.id),B=X&&!!ri,t=B?ri:h,K=B?M.packet:o,Oi=Object.values(M?.changes??{}),Ri=Oi.filter((i)=>i.kind==="changed").length,Di=Oi.filter((i)=>i.kind==="added").length,Ni=o.components.filter((i)=>I(i,n,M).kind==="approved"&&ki(i.id,M).carried).length,mi=(i)=>I(i,n,M),ko=u.approved+u.revisions+n.missing.length,Si=(s==="pending"?c():o.components).filter((i)=>Yi(i,n,s)),jo=[u.revisions+n.missing.length?`${u.revisions+n.missing.length} feedback ready`:"",Ni?`${Ni} ${Ni===1?"approval":"approvals"} kept`:"",Ri?`${Ri} changed`:"",Di?`${Di} added`:"",M?.removed.length?`${M.removed.length} removed`:""].filter(Boolean).join(" · "),qo=qi||(D?"Save or cancel your open feedback before sending.":H?a.preview?"Preview submitted. No run changed.":"Review submitted.":u.hasFeedback?"Ready to send for corrections.":u.pending?`${u.pending} left to review`:!n.inventoryConfirmed?"Confirm the map is complete.":"Ready to continue."),S=t?ji(t):null,Ci=t?.preview.kind==="image"&&!S?.code,C=!!(t?.context&&oi==="context"),Fi=t&&(C?t.context?.kind!=="image":t.preview.kind==="page"),Pi=C&&t?.context?t.context.url:t?.preview.url,Mo=S?.code?`${S.label} · ${S.captured?"captured from code":"live preview"}`:t?.material?`${t.material.alpha==="transparent"?"Transparent":t.material.alpha==="opaque"?"Opaque":"Transparency unverified"} ${t.material.format}`:"Raster · transparency unverified";r.innerHTML=`
+
${a.preview?'Historical hotel artwork for testing this interface. Decisions stay in this preview; no run is changed.
':""}
- ${j?`${y.pending} ${y.pending===1?"component":"components"} to review ${ko}
${y.pending?`${V("next")} `:""}${j.removed.length?`Removed from the map ${j.removed.map((i)=>x(i.name)).join(" · ")}. Confirm these omissions are intentional before accepting the map.
`:""} `:""}
- Approved comp Component ${Zi}
+ ${M?`${u.pending} ${u.pending===1?"component":"components"} to review ${jo}
${u.pending?`${J("next")} `:""}${M.removed.length?`Removed from the map ${M.removed.map((i)=>l(i.name)).join(" · ")}. Confirm these omissions are intentional before accepting the map.
`:""} `:""}
+ Approved comp Component ${Bi}
-
Approved comp ${V(Y?"close":"mark")}${Y?"Cancel":"Mark missing"}
-
-
- ${Wi&&!J?`
`:""}
- ${o.components.map((i,e)=>{let l=hi(i);return`
${l.kind==="approved"?Mi:l.kind==="feedback"?gi:""}${e+1} `}).join("")}
- ${n.missing.map((i,e)=>`
${gi}${o.components.length+e+1} `).join("")}
+
Approved comp ${J(L?"close":"mark")}${L?"Cancel":"Mark missing"}
+
+
+ ${Ai&&!v?`
`:""}
+ ${o.components.map((i,e)=>{let x=mi(i);return`
${x.kind==="approved"?hi:x.kind==="feedback"?si:""}${e+1} `}).join("")}
+ ${n.missing.map((i,e)=>`
${si}${o.components.length+e+1} `).join("")}
-
# To review${gi} Feedback ready${Mi} Approved
- ${Y?'
Draw around the missing piece.Add an adjustable box
':""}
+
# To review${si} Feedback ready${hi} Approved
+ ${L?'
Draw around the missing piece.Add an adjustable box
':""}
-
${J?"Review summary":`${Zi} ${x(d?.name??v?.name??"Component")}`}
- ${J?`
Components reviewed. ${y.approved} approved · ${y.revisions} flagged for repair${n.missing.length?` · ${n.missing.length} missing`:""}
Check your decisions below. ${y.hasFeedback?"Send feedback when you’re ready.":"Confirm nothing is missing, then approve and continue."}
${o.components.map((i)=>{let e=n.decisions[i.id];return`${x(i.name)} ${e?.action==="revise"?"Needs work":"Approved"} ${e?.action==="revise"?`${x(e.feedback||"No note — agent will diagnose.")} `:""} `}).join("")}${n.missing.map((i)=>`${x(i.name)} Missing ${x(i.feedback)} `).join("")}
${Ai}
`:d?`${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)} `:""}`:""}
+
${v?"Review summary":`${Bi} ${l(h?.name??z?.name??"Component")}`}
+ ${v?`
${hi}
${H?"Review sent.":"All components reviewed."} ${u.approved} approved · ${u.revisions} flagged for repair${n.missing.length?` · ${n.missing.length} missing`:""}
${H?"Your decisions are saved.":u.hasFeedback?"Send your feedback to start the next repair round.":"Confirm nothing is missing, then approve and continue."}
${o.components.map((i)=>{let e=n.decisions[i.id],x=mi(i);return`${l(i.name)} ${x.kind==="pending"?"Not reviewed":x.kind==="feedback"?"Needs work":"Approved"} ${e?.action==="revise"?`${l(e.feedback||"No note — agent will diagnose.")} `:""} `}).join("")}${n.missing.map((i)=>`${l(i.name)} Missing ${l(i.feedback)} `).join("")}
${Ji?`
${Ji}
`:""}`:h?`
${J(S.code?"code":"image")}${l(Mo)} ${t?.material?`${t.material.width} × ${t.material.height} px`:""}
+ ${M?`
+ ${$?.prior?.action==="revise"?`
Previous feedback Round ${$.feedbackRound} Needs work
${l($.prior.feedback||"No written feedback was supplied.")} ${$.prior.split?"Requested: split into separately reviewable components.
":""}`:$?.carried?'
Unchanged · approval kept
':""}
+ ${$?.change?.kind==="changed"?`
${$.change.files.length?`${$.change.files.length} changed ${$.change.files.length===1?"file":"files"}`:$.change.reasons.includes("region")?"Region changed":ri?.note!==h.note?"Description changed · files unchanged":"Component definition changed · files unchanged"} ${$.change.files.length?`${$.change.files.map((i)=>`${l(i)} `).join("")} `:""}${ri&&ri.note!==h.note?`Previous description ${l(ri.note)} Current description ${l(h.note)} `:""}`:""}
`:""}
- ${ei?`
Current · round ${o.round} Previous · round ${j.packet.round}
`:""}
-
${x(t.name)}
+ ${ri?`
Current · round ${o.round} Previous · round ${M.packet.round}
`:""}
+
${l(t.name)}
-
${A?`Comp · Round ${j.packet.round}`:"In the comp"}
-
${A?`Previous · Round ${j.packet.round}`:j?`Current · Round ${o.round}`:D?"In the page":R.caption} ${!Fi?`
`:`
`}${B?`
`:""}
+
${B?`Comp · Round ${M.packet.round}`:"In the comp"}
+
${B?`Previous · Round ${M.packet.round}`:M?`Current · Round ${o.round}`:C?"In the page":S.caption} ${!Fi?`
`:`
`}${O?`
`:""}
- ${Si?`
${t.context?`
Asset only In page
`:""}
`:""}
-
${x(t?.context?.layering??"Layer placement not recorded.")}
-
${x(t.note)}
-
`:v?`
This piece will be added to the unresolved inventory.
Name What is missing?${x(v.feedback)} ${["x","y","w","h"].map((i)=>`${{x:"Left",y:"Top",w:"Width",h:"Height"}[i]} % `).join("")}
Remove this mark `:"
No components supplied.
"}
+ ${Ci?`${t.context?`
Asset only In page
`:""}
`:""}
+ ${l(t?.context?.layering??"Layer placement not recorded.")}
+
${l(t.note)}
+
`:z?`This piece will be added to the unresolved inventory.
Name What is missing?${l(z.feedback)} ${["x","y","w","h"].map((i)=>`${{x:"Left",y:"Top",w:"Width",h:"Height"}[i]} % `).join("")}
Remove this mark `:"No components supplied.
"}
- Components Needs attention ${zo} Approved ${y.approved} All ${o.components.length+n.missing.length}
${V(O?"compact":"expand")} ${V(M?"hideTray":"showTray")}
- ${Di.map((i)=>{let e=o.components.indexOf(i),l=hi(i);return`
${i.thumbnail?`${i.thumbnail.box?` `:` `} `:""}${l.kind==="approved"?Mi:l.kind==="feedback"?gi:""}${e+1}${V(ki(i).code?"code":"image")}${x(ki(i).label)} ${x(i.name)} ${x(l.label)} `}).join("")}${(s==="approved"?[]:n.missing).map((i,e)=>`
${o.components.length+e+1} ${x(i.name)} Missing `).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."}
`:""}
-
- `;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)});})();
+ Components To review ${u.pending} Reviewed ${ko} All ${o.components.length+n.missing.length}
${J(R?"compact":"expand")} ${J(V?"hideTray":"showTray")}
+ ${Si.map((i)=>{let e=o.components.indexOf(i),x=mi(i);return`
${i.thumbnail?`${i.thumbnail.box?` `:` `} `:""}${x.kind==="approved"?hi:x.kind==="feedback"?si:""}${e+1}${J(ji(i).code?"code":"image")}${l(ji(i).label)} ${l(i.name)} ${l(x.label)} `}).join("")}${(s==="pending"?[]:n.missing).map((i,e)=>`
${o.components.length+e+1} ${l(i.name)} Missing `).join("")}${!Si.length&&(s==="pending"||!n.missing.length)?`
${s==="pending"?"Nothing left to review. Your decisions are ready.":"No components reviewed yet."}
`:""}
+
+ `;let E=r.querySelector("#comparison-dialog"),Y=r.querySelector(".comparison-panel"),ni=r.querySelector(".comparison-slot");if(G&&Y&&ni)ni.style.height=`${uo}px`,E.append(Y),E.showModal();if(B)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,H||T)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,!ci&&V)Array.from(r.querySelectorAll(".inventory [data-select]")).find((i)=>i.dataset.select===g)?.scrollIntoView({block:"nearest",inline:"nearest"});if(m)r.getElementById(m)?.focus({preventScroll:!0});else if(Q)Array.from(r.querySelectorAll(".item[data-select]")).find((i)=>i.dataset.select===Q)?.focus({preventScroll:!0});let b=(i,e)=>r.querySelector(`#${i}`)?.addEventListener("click",e);r.querySelectorAll("[data-select]").forEach((i)=>i.onclick=()=>{if(L)return;v=!1,g=i.dataset.select,q="component",O=!1,W="fit",oi="isolated",X=!1,d()}),b("previous-round",()=>{X=!0,d()}),b("current-round",()=>{X=!1,d()}),b("review-changes",()=>{let i=c().filter((y)=>mi(y).kind==="pending"),e=i.findIndex((y)=>y.id===g),x=i[(e+1)%i.length];if(x)v=!1,g=x.id,q="component",s="pending",X=!1,W="fit",O=!1,oi="isolated",d()}),r.querySelectorAll("[data-filter]").forEach((i)=>i.onclick=()=>{s=i.dataset.filter,v=!u.pending&&s==="pending";let e=c().filter((y)=>Yi(y,n,s));if(!(s!=="pending"&&n.missing.some((y)=>y.id===g))&&!e.some((y)=>y.id===g)&&e.length)g=e[0].id,q="component",X=!1,W="fit",O=!1,oi="isolated";d()}),b("show-summary",()=>{v=!0,q="component",s="reviewed",d(),ti("review-summary")}),b("approve",()=>Hi("approve")),b("revise",co),b("clear",()=>{if(h)delete n.decisions[h.id],delete N[h.id];U=null,v=!1,s="pending",d()}),b("cancel-feedback",()=>{if(h)delete N[h.id];if(pi)V=!0,pi=!1;d(),ti("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||T||H)return;let i=U;if(i.previous)n.decisions[i.id]=i.previous;else delete n.decisions[i.id];delete N[i.id],g=i.id,v=!1,X=!1,q="component",s="all",U=null,d(),ti("approve")}),b("overlay",()=>{O=!O,d()}),b("isolated",()=>{oi="isolated",d()}),b("context",()=>{oi="context",d()}),r.querySelector("#zoom")?.addEventListener("change",(i)=>{let e=i.target.value;W=e==="fit"?"fit":Number(e),d()}),b("background-checker",()=>{di="checker",d()}),b("background-page",()=>{di="page",d()}),b("show-all",()=>{R=!R,V=!0,d()}),b("toggle-tray",()=>{pi=!1,V=!V,d()}),b("show-comp",()=>{q="comp",d()}),b("show-component",()=>{q="component",d()}),b("mark",()=>{L=!L,d()}),b("add-box",()=>Ui({x:0.35,y:0.35,w:0.2,h:0.2})),b("remove-missing",()=>{n.missing=n.missing.filter((i)=>i.id!==g),g=o.components[0]?.id,d()}),b("approve-rest",()=>{if(D)return;n=ao(o,n),U=null,v=!0,s="reviewed",q="component",d(),ti("review-summary")}),r.querySelector("#inventory-confirm")?.addEventListener("change",(i)=>{n.inventoryConfirmed=i.target.checked,d()}),r.querySelector("#feedback")?.addEventListener("input",(i)=>{if(h&&N[h.id])N[h.id].feedback=i.target.value}),r.querySelector("#split")?.addEventListener("change",(i)=>{if(h&&N[h.id])N[h.id].split=i.target.checked}),r.querySelector("#missing-name")?.addEventListener("input",(i)=>{if(z)z.name=i.target.value;let e=r.querySelector("#submit");if(e)e.disabled=D||T||H||!li(o,n).canSubmit}),r.querySelector("#missing-feedback")?.addEventListener("input",(i)=>{if(z)z.feedback=i.target.value}),r.querySelectorAll("[data-coordinate]").forEach((i)=>i.addEventListener("change",()=>{if(!z)return;let e=i.dataset.coordinate,x=Number(i.value)/100;if(Number.isFinite(x))z.box[e]=Math.max(e==="w"||e==="h"?0.001:0,Math.min(1,x));z.box.w=Math.min(z.box.w,1-z.box.x),z.box.h=Math.min(z.box.h,1-z.box.y),d()})),b("submit",async()=>{if(D||T||H)return;T=!0,qi="",d();try{await a.onSubmit(to(o,n)),H=!0,v=!0,s="reviewed"}catch(i){qi=i instanceof Error?i.message:"Could not save. Try again."}finally{T=!1,d()}});let F=r.querySelector(".map");function io(i){let e=F.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))}}F.addEventListener("pointerdown",(i)=>{if(!L)return;Z=io(i),F.setPointerCapture(i.pointerId),i.preventDefault()}),F.addEventListener("pointermove",(i)=>{if(!Z)return;let e=io(i);A={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 x=r.querySelector(".draw-box");x.hidden=!1,x.style.cssText=Qi(A)}),F.addEventListener("pointerup",()=>{if(A&&A.w>0.01&&A.h>0.01)Ui(A);else Z=null,A=null}),F.addEventListener("pointercancel",()=>{Z=null,A=null,d()});let oo=r.querySelector(".output"),wi=r.querySelector("iframe"),eo=r.querySelector(".workbench"),xi=r.querySelector(".inspection-content"),$i=r.querySelector(".inspector");function Gi(){if(!xi||!$i)return;$i.dataset.scrollAbove=String(xi.scrollTop>1),$i.dataset.scrollBelow=String(xi.scrollHeight-xi.clientHeight-xi.scrollTop>1)}xi?.addEventListener("scroll",Gi,{passive:!0});function bi(){let i=r.querySelector(".map-space");if(i&&i.clientWidth&&i.clientHeight){let w=_i(o.comp.width,o.comp.height,Math.max(1,i.clientWidth-32),Math.max(1,i.clientHeight-32),"fit");F.style.width=`${w.width}px`,F.style.height=`${w.height}px`}let e=r.querySelector(".inspection-content"),x=Array.from(r.querySelectorAll(".pan-viewport"));if(e?.clientHeight){let w=G?Math.max(100,E.clientHeight-(Y?.querySelector(".compare-toolbar")?.clientHeight??0)-(Y?.querySelector(".expanded-title")?.clientHeight??0)-(Y?.querySelector(".view-controls")?.clientHeight??0)-112):Math.min(248,Math.max(100,e.clientHeight-40));x.forEach((k)=>k.style.height=`${w}px`)}if(t&&x.length){let w=_i(t.box.w*K.comp.width,t.box.h*K.comp.height,Math.min(...x.map((k)=>k.clientWidth)),Math.min(...x.map((k)=>k.clientHeight)),W);r.querySelectorAll(".crop-stage").forEach((k)=>{k.style.width=`${w.width}px`,k.style.height=`${w.height}px`})}if(x.forEach((w)=>{let k=w.scrollWidth>w.clientWidth+1||w.scrollHeight>w.clientHeight+1;w.classList.toggle("pannable",k),w.title=k?"Move your pointer to pan. You can also scroll, swipe, or use arrow keys.":""}),oo&&wi&&t){let w=oo.clientWidth/(t.box.w*K.comp.width);wi.style.transform=`scale(${w})`,wi.style.left=`${-t.box.x*K.comp.width*w}px`,wi.style.top=`${-t.box.y*K.comp.height*w}px`}let y=eo.getBoundingClientRect(),_=r.querySelector(".region"),vi=r.querySelector(".number"),yi=r.querySelector(".connector path");if(_&&vi&&yi){let w=_.getBoundingClientRect(),k=vi.getBoundingClientRect(),j=w.right-y.left,zi=w.top+w.height/2-y.top,no=k.left-y.left-8,Ho=k.top+k.height/2-y.top;yi.setAttribute("d",`M ${j} ${zi} H ${no-14} V ${Ho} H ${no}`)}}function Ki(i){if(!Y||!ni||i===G)return;let e=r.querySelector("#expand-comparison"),x=(G?E:Y).getBoundingClientRect(),y=window.matchMedia("(prefers-reduced-motion: reduce)").matches,_=Y.querySelector(".pan-viewport"),vi=_?_.scrollLeft/Math.max(1,_.scrollWidth-_.clientWidth):0,yi=_?_.scrollTop/Math.max(1,_.scrollHeight-_.clientHeight):0,w=()=>Y.querySelectorAll(".pan-viewport").forEach((j)=>{j.scrollLeft=vi*Math.max(0,j.scrollWidth-j.clientWidth),j.scrollTop=yi*Math.max(0,j.scrollHeight-j.clientHeight)}),k=()=>{ni.append(Y),E.close(),ni.style.height="",G=!1,e.innerHTML=J("expand"),e.setAttribute("aria-label","Enlarge comparison"),e.title="Enlarge comparison",e.setAttribute("aria-expanded","false"),bi(),w(),e.focus({preventScroll:!0})};if(i){ni.style.height=`${x.height}px`,E.append(Y),E.showModal(),G=!0,e.innerHTML=J("compact"),e.setAttribute("aria-label","Restore comparison"),e.title="Restore comparison (Esc)",e.setAttribute("aria-expanded","true"),bi(),w(),e.focus({preventScroll:!0});let j=E.getBoundingClientRect();if(!y)E.animate([{transform:`translate(${x.x-j.x}px,${x.y-j.y}px) scale(${x.width/j.width},${x.height/j.height})`,opacity:0.6},{transform:"none",opacity:1}],{duration:240,easing:"cubic-bezier(.2,.8,.2,1)"})}else{if(e.disabled)return;let j=ni.getBoundingClientRect();if(y){k();return}e.disabled=!0;let zi=E.animate([{transform:"none",opacity:1},{transform:`translate(${j.x-x.x}px,${j.y-x.y}px) scale(${j.width/x.width},${j.height/x.height})`,opacity:0.6}],{duration:200,easing:"cubic-bezier(.4,0,.2,1)",fill:"forwards"});zi.finished.then(()=>{if(E.isConnected)zi.cancel(),e.disabled=!1,k()}).catch(()=>{})}}b("expand-comparison",()=>Ki(!G)),E.addEventListener("cancel",(i)=>{i.preventDefault(),i.stopPropagation(),Ki(!1)}),E.addEventListener("keydown",(i)=>{if(i.key==="Escape")i.preventDefault(),i.stopPropagation(),Ki(!1)}),ai=new ResizeObserver(()=>{bi(),Gi()}),ai.observe(eo),ai.observe(E);let ro=r.querySelector(".inspection-content");if(ro)ai.observe(ro);bi();let ui=Array.from(r.querySelectorAll(".pan-viewport"));if(ui.forEach((i)=>{i.scrollLeft=vo,i.scrollTop=yo}),ui.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 x=i.getBoundingClientRect();i.scrollLeft=Li(e.clientX,x.left,i.clientWidth,i.scrollWidth),i.scrollTop=Li(e.clientY,x.top,i.clientHeight,i.scrollHeight)}),i.addEventListener("keydown",(e)=>{let y={ArrowLeft:[-48,0],ArrowRight:[48,0],ArrowUp:[0,-48],ArrowDown:[0,48]}[e.key];if(!y)return;e.preventDefault(),e.stopPropagation(),i.scrollLeft+=y[0],i.scrollTop+=y[1]})}),ui.forEach((i)=>i.addEventListener("scroll",()=>{for(let e of ui)if(e!==i){if(e.scrollLeft!==i.scrollLeft)e.scrollLeft=i.scrollLeft;if(e.scrollTop!==i.scrollTop)e.scrollTop=i.scrollTop}})),mo&&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}if(Gi(),window.scrollTo(gi,fi),!H)a.onDraftChange?.(structuredClone(n))}return d(),{destroy(){ai?.disconnect(),r.replaceChildren()},getDraft(){return structuredClone(n)}}}async function Jo(){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)}go(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.")}})}Jo().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 6bbfc80ba..48e47e62e 100644
--- a/ui/component-review/app-layout.ts
+++ b/ui/component-review/app-layout.ts
@@ -6,7 +6,7 @@ export const appLayout = `
.review>.preview-note{flex-shrink:0;margin:0;padding:8px 24px;font-size:11px}.review>.round-summary{flex-shrink:0;margin:0;padding:8px 24px;border-top:0;gap:6px 16px;max-height:120px;overflow:auto}.round-summary p{font-size:12px;gap:6px 14px}.round-summary button{font-size:12px;min-height:32px;padding:5px 10px}
.review>.workbench{flex:1;min-height:0;align-items:stretch;padding:20px 24px;gap:32px;overflow:hidden;grid-template-columns:minmax(0,1.12fr) minmax(0,1fr)}.reference{display:flex;flex-direction:column;min-height:0;width:100%;max-width:none;margin:0}.reference>.section-head{flex-shrink:0;margin-bottom:10px}.map-space{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;background:#eeefec;border:1px solid var(--line);overflow:hidden}.map{flex-shrink:0;max-width:100%;max-height:100%}.map-legend{flex-shrink:0;margin-top:9px;gap:6px 14px}.map-caption{flex-shrink:0;min-height:0;padding-top:6px;font-size:10px}.map-caption button{display:inline-block;margin:0 0 0 8px;min-height:28px;padding:3px 7px}
.workbench>.inspector{height:100%;min-height:0;padding:0;border:0}.inspector>.section-head{margin-bottom:10px;min-height:32px}.section-head h2{font-size:16px}.inspection-content{padding-bottom:8px}.review-form{max-height:55%;overflow-y:auto;scrollbar-width:thin;scrollbar-gutter:stable}.material{min-height:28px}.material strong{font-size:13px}.compare-toolbar{margin-bottom:12px}.previous-feedback{padding:10px 12px}.repair-context{margin-bottom:12px}.preview-round{margin-bottom:10px}.decision-title p{font-size:11px}.decisions>button{min-height:40px;font-size:13px}.decision-title strong{font-size:13px}.view-controls{margin-top:8px}.component-details{height:auto;max-height:80px}
-.review>.inventory-section{height:236px;flex-shrink:0;display:flex;flex-direction:column;min-height:0;margin:0;padding:10px 24px 8px;border-top:1px solid var(--line);background:var(--color-panel,#f5f5f2);overflow:hidden}.inventory-section>.section-head{margin:0 0 8px;min-height:36px;flex-shrink:0;gap:10px}.inventory-section h2{font-size:14px}.inventory{flex:1;min-height:0;align-items:stretch;margin:0;padding:3px 3px 7px;overflow-x:auto;overflow-y:hidden}.inventory.all{overflow:auto;align-items:start;grid-auto-rows:160px}.inventory .item{flex:0 0 136px;padding:8px;gap:3px;grid-template-rows:auto auto minmax(24px,1fr) auto;min-height:0}.inventory .item-thumb{height:46px;margin-bottom:3px}.inventory .thumb-crop{max-height:46px}.inventory .item-number{font-size:11px;min-height:16px;padding:0}.inventory .item strong{font-size:12px;min-height:26px;line-height:1.2}.inventory .state{font-size:11px;padding-top:4px}.tray-actions{display:flex;gap:6px;align-items:center}.tray-actions button{white-space:nowrap;font-size:11px;min-height:32px}.tray-actions #toggle-tray{display:flex;align-items:center;gap:6px}.tray-actions svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}.review>.inventory-section.tray-expanded{height:min(42dvh,390px)}.review>.inventory-section.tray-collapsed{height:56px;padding-block:10px}.tray-collapsed .inventory{display:none}.tray-collapsed>.section-head{margin-bottom:0}
+.review>.inventory-section{height:236px;flex-shrink:0;display:flex;flex-direction:column;min-height:0;margin:0;padding:10px 24px 8px;border-top:1px solid var(--line);background:var(--color-panel,#f5f5f2);overflow:hidden}.inventory-section>.section-head{margin:0 0 8px;min-height:36px;flex-shrink:0;gap:10px}.inventory-section h2{font-size:14px}.inventory{flex:1;min-height:0;align-items:stretch;margin:0;padding:3px 3px 7px;overflow-x:auto;overflow-y:hidden}.inventory.all{overflow:auto;align-items:start;grid-auto-rows:160px}.inventory .item{flex:0 0 136px;padding:8px;gap:3px;grid-template-rows:auto auto minmax(24px,1fr) auto;min-height:0}.inventory .item-thumb{height:46px;margin-bottom:3px}.inventory .thumb-crop{max-height:46px}.inventory .item-number{font-size:11px;min-height:16px;padding:0}.inventory .item strong{font-size:12px;min-height:26px;line-height:1.2;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.inventory .state{font-size:11px;padding-top:4px}.tray-actions{display:flex;gap:6px;align-items:center}.tray-actions button{white-space:nowrap;font-size:11px;min-height:32px}.tray-actions #toggle-tray{display:flex;align-items:center;gap:6px}.tray-actions svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}.review>.inventory-section.tray-expanded{height:min(42dvh,390px)}.review>.inventory-section.tray-collapsed{height:56px;padding-block:10px}.tray-collapsed .inventory{display:none}.tray-collapsed>.section-head{margin-bottom:0}
.review>footer{flex-shrink:0;margin:0;padding:12px 24px;gap:16px;border-top:1px solid var(--line);background:var(--paper);align-items:center}.review>footer>div:first-child{display:flex;align-items:center;gap:14px;min-width:0}.review>footer .check{margin:0;max-width:240px;font-size:11px}.review>footer #approve-rest{min-height:36px;font-size:12px;max-width:210px;padding:7px 10px}.review>footer .submit-area{gap:12px}.review>footer .submit-area p{font-size:11px;max-width:24ch}.review>footer .primary{min-height:40px;font-size:13px}.mobile-panes{display:none}
/* Depth describes the shell: recessed work area, raised inspector, anchored docks. */
.review{--workspace:#e3e6e2;--canvas:#d5dad5;--dock:#f6f7f4;--surface:#fff;background:var(--workspace)}
@@ -28,7 +28,7 @@ export const appLayout = `
.review>.inventory-section{position:relative;z-index:8;background:var(--dock);border-top-color:#b9c3b8;box-shadow:0 -3px 5px #2433280c,0 -10px 24px #24332814}
.inventory-section>.section-head{border-bottom:1px solid #d9dfd5;padding-bottom:8px;margin-bottom:8px}
.tray-collapsed>.section-head{border:0;padding-bottom:0;margin-bottom:0}
-.inventory{background:#e9ece5;border-radius:4px;padding:7px 7px 9px}
+.inventory{background:transparent;border-radius:0;padding:3px 3px 7px}
.inventory .item{background:#fafbf8}
.inventory .item.active{background:#fff}
.review>footer{position:relative;z-index:9;background:var(--surface);border-top-color:#ccd4c7;box-shadow:0 -2px 5px #2433280b}
@@ -51,7 +51,7 @@ export const appLayout = `
@media(max-width:800px){
.review>header{padding:12px 14px;align-items:center}.review h1{font-size:25px}.review>header p{max-width:68vw;font-size:11px}.review .badge{display:none}.review>.preview-note{padding:6px 14px}.review>.round-summary{padding:6px 14px;max-height:76px;gap:4px 8px}.round-summary p{max-width:calc(100% - 44px);gap:4px 10px;font-size:11px}.round-summary p>span{font-size:10px}.round-summary button{font-size:11px}
.mobile-panes{display:flex;flex-shrink:0;gap:4px;padding:7px 14px;border-bottom:1px solid var(--line);background:var(--paper)}.mobile-panes button{flex:1;font-size:12px;min-height:32px;padding:5px;border-color:transparent;background:transparent}.mobile-panes button[aria-pressed=true]{background:#e8efec;color:var(--teal);box-shadow:none;border-color:#bdd0c8}
- .review>.workbench{display:block;padding:12px 14px;min-height:0;overflow:hidden}.workbench[data-mobile-pane=component]>.reference,.workbench[data-mobile-pane=comp]>.inspector{display:none}.reference{height:100%;max-width:none}.connector{display:none}.inspector{height:100%;border:0}.map-legend{gap:6px 12px;font-size:10px}.map-caption{font-size:9px}.reference .section-head button{min-height:30px;padding:4px 8px}.reference .section-head h2{font-size:14px}.inspector>.section-head{min-height:26px;margin-bottom:0;padding:9px 12px}.inspection-content{padding:10px 8px 10px 12px}.inspector>.review-form{padding:8px 8px 8px 12px}.number{width:23px;height:23px;font-size:11px}.section-head h2{font-size:14px}.review-form{padding-top:8px}.decision-title strong{font-size:12px}.decision-title p{font-size:10px}.decisions>button{min-height:36px}.review-form .feedback{font-size:12px}.review-form .feedback textarea{min-height:60px}.compare{max-width:none}
+ .review>.workbench{display:block;padding:12px 14px;min-height:0;overflow:hidden}.workbench[data-mobile-pane=component]>.reference,.workbench[data-mobile-pane=comp]>.inspector{display:none}.reference{height:100%;max-width:none}.connector{display:none}.inspector{height:100%;border:0}.map-legend{gap:6px 12px;font-size:10px}.map-caption{font-size:9px}.reference .section-head button{min-height:30px;padding:4px 8px}.reference .section-head h2{font-size:14px}.inspector>.section-head{min-height:26px;margin-bottom:0;padding:9px 12px}.inspection-content{padding:10px 8px 10px 12px}.inspector>.review-form{padding:8px 8px 8px 12px}.number{width:23px;height:23px;font-size:11px}.section-head h2{font-size:14px}.review-form{padding-top:8px}.decision-title strong{font-size:12px}.decision-title p{font-size:10px}.decisions>button{min-height:36px}.review-form .feedback-field{font-size:12px}.review-form .feedback-field textarea{min-height:60px}.compare{max-width:none}
.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}
}
diff --git a/ui/component-review/model.test.ts b/ui/component-review/model.test.ts
index e578e29d7..5e7b6c619 100644
--- a/ui/component-review/model.test.ts
+++ b/ui/component-review/model.test.ts
@@ -60,3 +60,15 @@ test('review progression skips decisions, wraps, and revisits stale components',
expect(nextUnreviewed(packet,draft,second.id)).toBe(first.id);
expect(nextUnreviewed({...packet,components:[]},draft)).toBeUndefined();
});
+
+test('reviewed queue includes feedback, pending queue includes stale decisions', async () => {
+ const { inReviewQueue } = await import('./model');
+ const draft=approveRemaining(packet,newDraft(packet));
+ draft.decisions.art.action='revise';
+ expect(packet.components.filter(c=>inReviewQueue(c,draft,'pending'))).toHaveLength(0);
+ expect(packet.components.filter(c=>inReviewQueue(c,draft,'reviewed'))).toHaveLength(2);
+ expect(summarize(packet,draft).pending).toBe(0);
+ draft.decisions.art.revision='old';
+ expect(packet.components.filter(c=>inReviewQueue(c,draft,'pending')).map(c=>c.id)).toEqual(['art']);
+ expect(packet.components.filter(c=>inReviewQueue(c,draft,'reviewed')).map(c=>c.id)).toEqual(['control']);
+});
diff --git a/ui/component-review/model.ts b/ui/component-review/model.ts
index 6bd1b3643..a3760c6de 100644
--- a/ui/component-review/model.ts
+++ b/ui/component-review/model.ts
@@ -90,3 +90,9 @@ export function nextUnreviewed(packet: ReviewPacket, draft: Draft, after?: strin
if (componentState(component, draft).kind === 'pending') return component.id;
}
}
+
+export type InventoryFilter = 'pending' | 'reviewed' | 'all';
+/** Repair requests are completed review decisions, not work left for the reviewer. */
+export function inReviewQueue(component: Component, draft: Draft, filter: InventoryFilter) {
+ return filter === 'all' || (componentState(component, draft).kind === 'pending') === (filter === 'pending');
+}
diff --git a/ui/component-review/review.ts b/ui/component-review/review.ts
index d1db67988..561dbabbc 100644
--- a/ui/component-review/review.ts
+++ b/ui/component-review/review.ts
@@ -1,4 +1,4 @@
-import { componentPresentation, nextUnreviewed, approveRemaining, componentState, repairStatus, newDraft, submission, summarize, type Box, type Decision, type Draft, type ReviewPacket, type ReviewHistory } from './model';
+import { inReviewQueue, type InventoryFilter, componentPresentation, nextUnreviewed, approveRemaining, componentState, repairStatus, newDraft, submission, summarize, type Box, type Decision, type Draft, type ReviewPacket, type ReviewHistory } from './model';
import { comparisonSize, hoverPan } from './viewport';
import { styles } from './styles';
import { icon } from './icons';
@@ -12,19 +12,19 @@ const url = (s: string) => {
return esc(parsed.href);
};
export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, options: {
- preview?: boolean; history?: ReviewHistory | null; initialDraft?: Draft; completed?: boolean; onSubmit: (value: ReturnType) => Promise;
+ preview?: boolean; history?: ReviewHistory | null; initialDraft?: Draft; completed?: boolean; onDraftChange?: (draft: Draft) => void; onSubmit: (value: ReturnType) => Promise;
}) {
const root = host.attachShadow({mode: 'open'});
let draft = structuredClone(options.initialDraft ?? newDraft(packet));
const orderedComponents = () => [...packet.components].sort((a,b)=>componentState(a,draft,options.history).priority-componentState(b,draft,options.history).priority);
let selected = orderedComponents().find(c=>componentState(c,draft,options.history).kind!=='approved')?.id ?? packet.components[0]?.id;
- let inventoryFilter: 'attention' | 'approved' | 'all' = options.completed ? 'all' : 'attention';
+ let inventoryFilter: InventoryFilter = summarize(packet,draft).pending ? 'pending' : 'reviewed';
let marking = false;
let sending = false;
let submitted = options.completed ?? false;
let error = '';
const edits: Record = {};
- let finished = false;
+ let finished = !summarize(packet,draft).pending;
let lastDecision: {id: string; name: string; action: 'approve' | 'revise'; previous?: Decision} | null = null;
const shortcutLabel = /Mac|iPhone|iPad/.test(navigator.platform) ? '⌘Enter' : 'Ctrl+Enter';
let overlay = false;
@@ -54,7 +54,7 @@ export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, op
finished = !next;
if (next) selected = next;
mobilePane='component'; previousRound=false; overlay=false; zoom='fit'; outputMode='isolated';
- if (inventoryFilter==='approved') inventoryFilter='attention';
+ inventoryFilter = finished ? 'reviewed' : 'pending';
render();
focusReview(finished ? 'review-summary' : edits[selected!] ? 'feedback' : 'approve');
}
@@ -130,8 +130,8 @@ export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, op
const addedCount = changes.filter(change=>change.kind==='added').length;
const carriedCount = packet.components.filter(item=>componentState(item,draft,history).kind==='approved'&&repairStatus(item.id,history).carried).length;
const stateFor = (item: typeof packet.components[number]) => componentState(item,draft,history);
- const attentionCount = packet.components.length-stats.approved+draft.missing.length;
- const shownComponents = (inventoryFilter==='attention'?orderedComponents():packet.components).filter(item=>inventoryFilter==='all'||(stateFor(item).kind==='approved')===(inventoryFilter==='approved'));
+ const reviewedCount = stats.approved+stats.revisions+draft.missing.length;
+ const shownComponents = (inventoryFilter==='pending'?orderedComponents():packet.components).filter(item=>inReviewQueue(item,draft,inventoryFilter));
const summaryDetails = [
stats.revisions+draft.missing.length ? `${stats.revisions+draft.missing.length} feedback ready` : '',
carriedCount ? `${carriedCount} ${carriedCount===1?'approval':'approvals'} kept` : '',
@@ -157,8 +157,8 @@ export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, op
${box && !finished ? `
` : ''}
- ${packet.components.map((item,i) => {const state=stateFor(item);return `
${state.kind==='approved'?checkIcon:state.kind==='feedback'?feedbackIcon:''}${i+1} `}).join('')}
- ${draft.missing.map((item,i)=>`
${feedbackIcon}${packet.components.length+i+1} `).join('')}
+ ${packet.components.map((item,i) => {const state=stateFor(item);return `
${state.kind==='approved'?checkIcon:state.kind==='feedback'?feedbackIcon:''}${i+1} `}).join('')}
+ ${draft.missing.map((item,i)=>`
${feedbackIcon}${packet.components.length+i+1} `).join('')}
@@ -167,7 +167,7 @@ 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 `${esc(item.name)} ${decision?.action==='revise' ? 'Needs work' : 'Approved'} ${decision?.action==='revise' ? `${esc(decision.feedback || 'No note — agent will diagnose.')} ` : ''} `;}).join('')}${draft.missing.map(item=>`${esc(item.name)} Missing ${esc(item.feedback)} `).join('')}
${notice}
` : c ? `${icon(presentation!.code ? 'code' : 'image')}${esc(materialLabel)} ${v?.material ? `${v.material.width} × ${v.material.height} px` : ''}
+ ${finished ? `${checkIcon}
${submitted ? 'Review sent.' : 'All components reviewed.'} ${stats.approved} approved · ${stats.revisions} flagged for repair${draft.missing.length ? ` · ${draft.missing.length} missing` : ''}
${submitted ? 'Your decisions are saved.' : stats.hasFeedback ? 'Send your feedback to start the next repair round.' : 'Confirm nothing is missing, then approve and continue.'}
${packet.components.map(item=>{const decision=draft.decisions[item.id];const state=stateFor(item);return `${esc(item.name)} ${state.kind==='pending' ? 'Not reviewed' : state.kind==='feedback' ? 'Needs work' : 'Approved'} ${decision?.action==='revise' ? `${esc(decision.feedback || 'No note — agent will diagnose.')} ` : ''} `;}).join('')}${draft.missing.map(item=>`${esc(item.name)} Missing ${esc(item.feedback)} `).join('')}
${notice?`${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)} `:''}`:''}
@@ -182,13 +182,13 @@ export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, op
${esc(v?.context?.layering ?? 'Layer placement not recorded.')}
${esc(v!.note)}
` : missing ? `This piece will be added to the unresolved inventory.
Name What is missing?${esc(missing.feedback)} ${(['x','y','w','h'] as const).map(k=>`${{x:'Left',y:'Top',w:'Width',h:'Height'}[k]} % `).join('')}
Remove this mark ` : 'No components supplied.
'}
+ ${edit ? `Cancel ${isLast?'Save & finish review':'Save & next'} ${shortcutLabel} ` : d?.action==='revise' ? `${esc(d.feedback || 'No note — agent will diagnose.')}
` : ''}
+ ` : missing ? `This piece will be added to the unresolved inventory.
Name What is missing?${esc(missing.feedback)} ${(['x','y','w','h'] as const).map(k=>`${{x:'Left',y:'Top',w:'Width',h:'Height'}[k]} % `).join('')}
Remove this mark ` : 'No components supplied.
'}
- Components Needs attention ${attentionCount} Approved ${stats.approved} All ${packet.components.length+draft.missing.length}
${icon(showAll?'compact':'expand')} ${icon(trayOpen?'hideTray':'showTray')}
- ${shownComponents.map(item=>{const i=packet.components.indexOf(item);const state=stateFor(item); return `
${item.thumbnail ? `${item.thumbnail.box ? ` ` : ` `} ` : ''}${state.kind==='approved'?checkIcon:state.kind==='feedback'?feedbackIcon:''}${i+1}${icon(componentPresentation(item).code ? 'code' : 'image')}${esc(componentPresentation(item).label)} ${esc(item.name)} ${esc(state.label)} `}).join('')}${(inventoryFilter==='approved'?[]:draft.missing).map((m,i)=>`
${packet.components.length+i+1} ${esc(m.name)} Missing `).join('')}${!shownComponents.length&&(inventoryFilter==='approved'||!draft.missing.length)?`
${inventoryFilter==='attention'?'Every component is approved. Confirm the map is complete, then continue.':'No components approved yet.'}
`:''}
-
+ Components To review ${stats.pending} Reviewed ${reviewedCount} All ${packet.components.length+draft.missing.length}
${icon(showAll?'compact':'expand')} ${icon(trayOpen?'hideTray':'showTray')}
+ ${shownComponents.map(item=>{const i=packet.components.indexOf(item);const state=stateFor(item); return `
${item.thumbnail ? `${item.thumbnail.box ? ` ` : ` `} ` : ''}${state.kind==='approved'?checkIcon:state.kind==='feedback'?feedbackIcon:''}${i+1}${icon(componentPresentation(item).code ? 'code' : 'image')}${esc(componentPresentation(item).label)} ${esc(item.name)} ${esc(state.label)} `}).join('')}${(inventoryFilter==='pending'?[]:draft.missing).map((m,i)=>`
${packet.components.length+i+1} ${esc(m.name)} Missing `).join('')}${!shownComponents.length&&(inventoryFilter==='pending'||!draft.missing.length)?`
${inventoryFilter==='pending'?'Nothing left to review. Your decisions are ready.':'No components reviewed yet.'}
`:''}
+
`;
const comparisonDialog=root.querySelector('#comparison-dialog')!;
const comparisonPanel=root.querySelector('.comparison-panel');
@@ -208,16 +208,17 @@ export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, op
root.querySelectorAll('[data-select]').forEach(el => el.onclick = () => {if(marking)return; finished=false; selected=el.dataset.select!; mobilePane='component'; overlay=false; zoom='fit'; outputMode='isolated'; previousRound=false; render();});
on('previous-round',()=>{previousRound=true;render();});
on('current-round',()=>{previousRound=false;render();});
- on('review-changes',()=>{const pending=orderedComponents().filter(item=>stateFor(item).kind==='pending');const current=pending.findIndex(item=>item.id===selected);const next=pending[(current+1)%pending.length];if(next){finished=false;selected=next.id;mobilePane='component';inventoryFilter='attention';previousRound=false;zoom='fit';overlay=false;outputMode='isolated';render();}});
+ on('review-changes',()=>{const pending=orderedComponents().filter(item=>stateFor(item).kind==='pending');const current=pending.findIndex(item=>item.id===selected);const next=pending[(current+1)%pending.length];if(next){finished=false;selected=next.id;mobilePane='component';inventoryFilter='pending';previousRound=false;zoom='fit';overlay=false;outputMode='isolated';render();}});
root.querySelectorAll('[data-filter]').forEach(button=>button.onclick=()=>{
- finished=false;inventoryFilter=button.dataset.filter as typeof inventoryFilter;
- const matches=orderedComponents().filter(item=>inventoryFilter==='all'||(stateFor(item).kind==='approved')===(inventoryFilter==='approved'));
- const selectedMissing=inventoryFilter!=='approved'&&draft.missing.some(item=>item.id===selected);
+ inventoryFilter=button.dataset.filter as InventoryFilter;finished=!stats.pending&&inventoryFilter==='pending';
+ const matches=orderedComponents().filter(item=>inReviewQueue(item,draft,inventoryFilter));
+ const selectedMissing=inventoryFilter!=='pending'&&draft.missing.some(item=>item.id===selected);
if(!selectedMissing&&!matches.some(item=>item.id===selected)&&matches.length){selected=matches[0].id;mobilePane='component';previousRound=false;zoom='fit';overlay=false;outputMode='isolated';}
render();
});
+ on('show-summary',()=>{finished=true;mobilePane='component';inventoryFilter='reviewed';render();focusReview('review-summary');});
on('approve',()=>updateDecision('approve')); on('revise',beginFeedback);
- on('clear',()=>{if(c){delete draft.decisions[c.id];delete edits[c.id];}lastDecision=null; render();});
+ on('clear',()=>{if(c){delete draft.decisions[c.id];delete edits[c.id];}lastDecision=null;finished=false;inventoryFilter='pending';render();});
on('cancel-feedback',()=>{if(c)delete edits[c.id];if(restoreTrayAfterFeedback){trayOpen=true;restoreTrayAfterFeedback=false;}render();focusReview('revise');});
root.querySelector('#feedback-form')?.addEventListener('submit',e=>{e.preventDefault();updateDecision('revise');});
root.querySelector('#feedback')?.addEventListener('keydown',e=>{
@@ -242,7 +243,7 @@ export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, op
on('show-component',()=>{mobilePane='component';render();});
on('mark',()=>{marking=!marking;render();}); on('add-box',()=>addMissing({x:.35,y:.35,w:.2,h:.2}));
on('remove-missing',()=>{draft.missing=draft.missing.filter(m=>m.id!==selected);selected=packet.components[0]?.id;render();});
- on('approve-rest',()=>{if(uncommitted)return;draft=approveRemaining(packet,draft);lastDecision=null;finished=true;mobilePane='component';render();focusReview('review-summary');});
+ on('approve-rest',()=>{if(uncommitted)return;draft=approveRemaining(packet,draft);lastDecision=null;finished=true;inventoryFilter='reviewed';mobilePane='component';render();focusReview('review-summary');});
root.querySelector('#inventory-confirm')?.addEventListener('change',e=>{draft.inventoryConfirmed=(e.target as HTMLInputElement).checked;render();});
root.querySelector('#feedback')?.addEventListener('input',e=>{if(c&&edits[c.id])edits[c.id].feedback=(e.target as HTMLTextAreaElement).value;});
root.querySelector('#split')?.addEventListener('change',e=>{if(c&&edits[c.id])edits[c.id].split=(e.target as HTMLInputElement).checked;});
@@ -254,7 +255,7 @@ export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, op
if(Number.isFinite(next)) missing.box[k]=Math.max(k==='w'||k==='h'?.001:0, Math.min(1,next));
missing.box.w=Math.min(missing.box.w,1-missing.box.x);missing.box.h=Math.min(missing.box.h,1-missing.box.y);render();
}));
- on('submit',async()=>{if(uncommitted||sending||submitted)return;sending=true;error='';render();try{await options.onSubmit(submission(packet,draft));submitted=true;}catch(e){error=e instanceof Error?e.message:'Could not save. Try again.';}finally{sending=false;render();}});
+ on('submit',async()=>{if(uncommitted||sending||submitted)return;sending=true;error='';render();try{await options.onSubmit(submission(packet,draft));submitted=true;finished=true;inventoryFilter='reviewed';}catch(e){error=e instanceof Error?e.message:'Could not save. Try again.';}finally{sending=false;render();}});
const map = root.querySelector('.map')!;
function point(e: PointerEvent) {const r=map.getBoundingClientRect();return {x:Math.max(0,Math.min(1,(e.clientX-r.left)/r.width)),y:Math.max(0,Math.min(1,(e.clientY-r.top)/r.height))};}
map.addEventListener('pointerdown',e=>{if(!marking)return;drag=point(e);map.setPointerCapture(e.pointerId);e.preventDefault();});
@@ -345,6 +346,7 @@ export function mountComponentReview(host: HTMLElement, packet: ReviewPacket, op
if(focusMobileComparison&&window.matchMedia('(max-width:800px)').matches){const body=root.querySelector('.inspection-content');const comparison=root.querySelector('.compare');if(body&&comparison)body.scrollTop+=comparison.getBoundingClientRect().top-body.getBoundingClientRect().top;}
updateScrollEdges();
window.scrollTo(scrollX,scrollY);
+ if(!submitted) options.onDraftChange?.(structuredClone(draft));
}
render();
return {destroy(){resize?.disconnect();root.replaceChildren();},getDraft():Draft{return structuredClone(draft);}};
diff --git a/ui/component-review/styles.ts b/ui/component-review/styles.ts
index d94e97654..fa4a4676f 100644
--- a/ui/component-review/styles.ts
+++ b/ui/component-review/styles.ts
@@ -4,7 +4,7 @@ export const styles = `
*{box-sizing:border-box}h1,h2,p,figure{margin:0}button,input,textarea{font:inherit}button{cursor:pointer;border:1px solid var(--line);border-radius:4px;background:var(--paper);color:inherit;padding:8px 12px;min-height:36px}button:hover{border-color:var(--teal);color:var(--teal)}button:disabled{cursor:default;opacity:.45}button:focus-visible,input:focus-visible,textarea:focus-visible{outline:2px solid var(--teal);outline-offset:3px}button[aria-pressed=true]{box-shadow:inset 0 0 0 1px var(--teal)}input[type=checkbox]{accent-color:var(--teal);width:16px;height:16px;flex-shrink:0}textarea,input:not([type=checkbox]){width:100%;background:var(--paper);color:inherit;border:1px solid #999;border-radius:4px;padding:9px 10px}textarea{resize:vertical;min-height:80px}::selection{background:#c7ddd8}a{color:var(--teal)}
.review{max-width:1600px;margin:auto;padding:24px 28px 0}header{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:16px}h1{font:400 40px/1.05 var(--font-display,Arial,sans-serif);letter-spacing:-.02em}header p{margin-top:8px;font-size:15px}header p span,.medium{color:var(--muted)}.badge{border:1px solid var(--line);padding:5px 10px;font-size:12px;white-space:nowrap}.preview-note{color:var(--muted);font-size:12px;border-bottom:1px solid var(--line);padding-bottom:16px;margin-bottom:24px}
.connector{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:5;overflow:visible}.connector path{fill:none;stroke:var(--selection);stroke-width:2}.workbench{align-items:start;display:grid;grid-template-columns:minmax(0,1.18fr) minmax(0,1fr);gap:40px;position:relative}.section-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px;min-height:36px}.section-head h2{font-size:17px;font-weight:500;line-height:1.2}.section-head>span,.section-head h2>span:not(.number){font-size:12px;color:var(--muted)}.section-head button{font-size:12px}.number{display:inline-flex;align-items:center;justify-content:center;width:27px;height:27px;border:1px solid var(--teal);color:var(--teal);margin-right:7px;font:12px var(--font-mono,monospace)}
-.map{position:relative;background:#eaeaea;isolation:isolate}.comp{width:100%;height:100%;display:block;user-select:none}.map.marking{touch-action:none;cursor:crosshair}.map.marking .pin{pointer-events:none;opacity:.25}.pin{position:absolute;transform:translate(-50%,-50%);padding:0;min-height:25px;width:25px;height:25px;border-radius:50%;border:1px solid #fff;background:#fff;color:#292929;font:11px var(--font-mono,monospace);box-shadow:0 1px 4px #0006;z-index:2}.pin.selected{background:var(--teal);color:#fff;border-color:var(--teal);box-shadow:none;outline:none;z-index:3}.pin:focus-visible{outline:2px solid white;outline-offset:3px}.region,.draw-box{position:absolute;pointer-events:none;outline:2px solid var(--selection);z-index:1}.draw-box{background:#28625e33;z-index:4}.map-caption{font-size:12px;color:var(--muted);padding-top:12px;min-height:40px}.map-caption button{display:block;margin-top:10px}.compare{display:grid;grid-template-columns:1fr 1fr;gap:12px}.compare figure{min-width:0}.compare figcaption{height:24px;min-height:0;font-size:12px;margin-bottom:9px}.compare figcaption span{display:block;color:var(--muted);font-size:11px}.crop-stage{position:relative;overflow:hidden;background:var(--comp-background,#eee);min-width:0}.crop-image{position:absolute;max-width:none;height:auto}.asset{display:block;width:100%;height:100%;object-fit:cover}.crop-stage iframe{position:absolute;max-width:none;border:0;transform-origin:top left;pointer-events:none}.overlay-image{opacity:.5;pointer-events:none}.component-note{font-size:12px;line-height:1.5;color:var(--muted);margin:6px 0 0}.decisions{display:flex;gap:10px;padding:10px 0;background:var(--paper);position:sticky;bottom:0;z-index:6}.decisions>button{flex:1;min-height:48px;font-weight:600;font-size:15px;border-color:var(--teal)}.decision-approve{background:var(--teal);color:white}.decision-approve:hover{background:#224e4b;color:white}.decision-revise{color:var(--teal);background:var(--paper)}.decisions>.quiet{flex:0;border:0;background:transparent;font-size:12px}.decisions .approved{color:var(--teal);background:#e9f0ed;border-color:var(--teal)}.decisions .revise{color:var(--warn);background:#f7f0e6;border-color:var(--warn)}.feedback{display:block;margin-top:16px;font-size:13px}.feedback>span{float:right;color:var(--muted);font-size:12px}.feedback textarea,.feedback input{display:block;margin-top:7px}.check{display:flex;align-items:center;gap:7px;font-size:12px;line-height:1.5;margin-top:12px}.coordinates{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:16px 0}.coordinates label{font-size:12px}.coordinates input{margin-top:5px}
+.map{position:relative;background:#eaeaea;isolation:isolate}.comp{width:100%;height:100%;display:block;user-select:none}.map.marking{touch-action:none;cursor:crosshair}.map.marking .pin{pointer-events:none;opacity:.25}.pin{position:absolute;transform:translate(-50%,-50%);padding:0;min-height:25px;width:25px;height:25px;border-radius:50%;border:1px solid #fff;background:#fff;color:#292929;font:11px var(--font-mono,monospace);box-shadow:0 1px 4px #0006;z-index:2}.pin.selected{background:var(--teal);color:#fff;border-color:var(--teal);box-shadow:none;outline:none;z-index:3}.pin:focus-visible{outline:2px solid white;outline-offset:3px}.region,.draw-box{position:absolute;pointer-events:none;outline:2px solid var(--selection);z-index:1}.draw-box{background:#28625e33;z-index:4}.map-caption{font-size:12px;color:var(--muted);padding-top:12px;min-height:40px}.map-caption button{display:block;margin-top:10px}.compare{display:grid;grid-template-columns:1fr 1fr;gap:12px}.compare figure{min-width:0}.compare figcaption{height:24px;min-height:0;font-size:12px;margin-bottom:9px}.compare figcaption span{display:block;color:var(--muted);font-size:11px}.crop-stage{position:relative;overflow:hidden;background:var(--comp-background,#eee);min-width:0}.crop-image{position:absolute;max-width:none;height:auto}.asset{display:block;width:100%;height:100%;object-fit:cover}.crop-stage iframe{position:absolute;max-width:none;border:0;transform-origin:top left;pointer-events:none}.overlay-image{opacity:.5;pointer-events:none}.component-note{font-size:12px;line-height:1.5;color:var(--muted);margin:6px 0 0}.decisions{display:flex;gap:10px;padding:10px 0;background:var(--paper);position:sticky;bottom:0;z-index:6}.decisions>button{flex:1;min-height:48px;font-weight:600;font-size:15px;border-color:var(--teal)}.decision-approve{background:var(--teal);color:white}.decision-approve:hover{background:#224e4b;color:white}.decision-revise{color:var(--teal);background:var(--paper)}.decisions>.quiet{flex:0;border:0;background:transparent;font-size:12px}.decisions .approved{color:var(--teal);background:#e9f0ed;border-color:var(--teal)}.decisions .revise{color:var(--warn);background:#f7f0e6;border-color:var(--warn)}.feedback-field{display:block;margin-top:16px;font-size:13px}.feedback-field>span{float:right;color:var(--muted);font-size:12px}.feedback-field textarea,.feedback-field input{display:block;margin-top:7px}.check{display:flex;align-items:center;gap:7px;font-size:12px;line-height:1.5;margin-top:12px}.coordinates{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:16px 0}.coordinates label{font-size:12px}.coordinates input{margin-top:5px}
.inventory-section{margin-top:28px;border-top:1px solid var(--line);padding-top:16px}.inventory-section .section-head{margin-bottom:10px}.inventory{display:flex;gap:8px;overflow-x:auto;padding:3px 3px 14px;scrollbar-color:#a6bcb8 #eee;scrollbar-width:thin}.item{position:relative;flex:0 0 134px;display:grid;grid-template-columns:20px 1fr;column-gap:8px;row-gap:3px;padding:10px;text-align:left;background:transparent}.item strong{grid-column:2;font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item.active{background:var(--paper);border-color:var(--teal)}.thumb-crop{position:relative;display:block;overflow:hidden}.item-thumb{display:flex;align-items:center;justify-content:center;grid-column:1/-1;position:relative;overflow:hidden;width:100%;height:76px;background:var(--comp-background,#eee);margin-bottom:7px}.item-thumb img{width:100%;height:100%;object-fit:contain}.item-thumb img[style]{height:auto}.inventory.all{display:grid;grid-template-columns:repeat(auto-fill,minmax(128px,1fr));overflow:visible}.item-number{grid-row:2/4;font:12px var(--font-mono,monospace);color:var(--muted);padding-top:2px}.state{grid-column:2;font-size:11px;color:var(--muted)}.state.approve{color:var(--teal)}.state.revise{color:var(--warn)}footer{display:flex;justify-content:space-between;gap:24px;padding:20px 0 24px;border-top:1px solid var(--line);margin-top:8px}.submit-area{display:flex;align-items:center;gap:20px}.submit-area p{font-size:12px;color:var(--muted);max-width:28ch}.primary{min-height:46px;font-weight:600;background:var(--teal);border-color:var(--teal);color:white;white-space:nowrap}.primary:hover{color:white;background:#224e4b}.primary:disabled{opacity:.45}.inspector>p{margin:12px 0}
@media(min-width:1300px){.workbench{gap:56px}.review{padding-top:32px}.component-note{max-width:62ch}}
@media(max-width:800px){.connector{display:none}.review{padding:20px 16px 0}.workbench{grid-template-columns:1fr;gap:24px}.reference{max-width:640px;margin:auto;width:100%}.inspector{border-top:1px solid var(--line);padding-top:16px}.compare{max-width:640px}.inventory-section .section-head{align-items:flex-start;flex-direction:column;gap:4px}footer{flex-direction:column}.submit-area{justify-content:space-between}.badge{font-size:11px}.section-head{gap:8px}h1{font-size:34px}header{align-items:flex-start}.section-head h2{font-size:16px}}
@@ -33,7 +33,7 @@ export const styles = `
@media(max-width:800px){.inventory-section .section-head{gap:10px}.inventory-filters{width:100%}.inventory-filters button{padding-inline:7px;font-size:11px;flex:1}.inventory-filters b{margin-left:3px}}
@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto}}
.item-medium{margin-left:auto;font-weight:400;display:flex;align-items:center;gap:4px;font-size:10px;color:var(--muted);min-height:16px}.item-medium .utility-icon{width:14px;height:14px;flex-shrink:0}.material>.utility-icon{width:18px;height:18px;align-self:center;color:var(--teal)}
-.feedback-actions{display:flex;align-items:center;gap:10px;margin-top:12px;flex-wrap:wrap}.feedback-actions .primary{margin-left:auto;display:inline-flex;align-items:center;gap:10px}.feedback-actions svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.5}.feedback-hint,.shortcut-hint{font-size:12px;color:var(--muted)}.feedback-hint{margin-top:6px}.shortcut-hint{flex-basis:100%;text-align:right}.saved-feedback{font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere;margin:8px 0}.decision-notice{display:flex;align-items:center;gap:12px;font-size:12px;margin-bottom:10px}.decision-notice>span{flex:1;overflow-wrap:anywhere}.decision-notice button{color:var(--teal);flex-shrink:0}.review-summary{padding:16px 8px}.review-summary h2{font-size:24px}.review-summary p{margin-top:12px;color:var(--muted)}.summary-decisions{margin-top:24px;display:grid}.summary-decisions button{display:grid;grid-template-columns:1fr auto;gap:6px 16px;text-align:left;border:0;border-bottom:1px solid var(--line);border-radius:0;padding:12px 0;background:transparent}.summary-decisions strong{font-size:13px;font-weight:500}.summary-decisions span{font-size:12px;color:var(--teal)}.summary-decisions small{grid-column:1/-1;font-size:12px;color:var(--muted);white-space:pre-wrap;overflow-wrap:anywhere}
+.feedback-actions{display:flex;align-items:center;gap:10px;margin-top:12px;flex-wrap:wrap}.feedback-actions .primary{margin-left:auto;display:inline-flex;align-items:center;gap:10px}.feedback-actions svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.5}.feedback-hint,.shortcut-hint{font-size:12px;color:var(--muted)}.feedback-hint{margin-top:6px}.shortcut-hint{flex-basis:100%;text-align:right}.saved-feedback{font-size:13px;white-space:pre-wrap;overflow-wrap:anywhere;margin:8px 0}.decision-notice{display:flex;align-items:center;gap:12px;font-size:12px;margin-bottom:10px}.decision-notice>span{flex:1;overflow-wrap:anywhere}.decision-notice button{color:var(--teal);flex-shrink:0}.review-summary{padding: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}
` + appLayout + `
.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}