@vizzly-testing/cli 0.33.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +53 -3
  2. package/dist/api/core.js +2 -1
  3. package/dist/cli.js +21 -8
  4. package/dist/client/index.js +62 -21
  5. package/dist/commands/api.js +9 -3
  6. package/dist/commands/init.js +214 -2
  7. package/dist/commands/preview.js +13 -11
  8. package/dist/commands/project.js +1 -1
  9. package/dist/commands/run.js +18 -6
  10. package/dist/commands/tdd-daemon.js +8 -3
  11. package/dist/commands/tdd.js +24 -4
  12. package/dist/commands/upload.js +31 -13
  13. package/dist/reporter/reporter-bundle.iife.js +1 -1
  14. package/dist/sdk/index.js +23 -9
  15. package/dist/server/handlers/api-handler.js +3 -2
  16. package/dist/server/handlers/tdd-handler.js +5 -1
  17. package/dist/server/routers/screenshot.js +11 -4
  18. package/dist/services/build-manager.js +6 -1
  19. package/dist/test-runner/core.js +4 -1
  20. package/dist/test-runner/operations.js +2 -1
  21. package/dist/types/client.d.ts +32 -5
  22. package/dist/types/index.d.ts +33 -5
  23. package/dist/uploader/core.js +41 -9
  24. package/dist/uploader/operations.js +19 -17
  25. package/dist/utils/config-loader.js +12 -6
  26. package/dist/utils/context.js +46 -76
  27. package/dist/utils/environment-config.js +20 -0
  28. package/dist/utils/output.js +5 -5
  29. package/dist/utils/screenshot-options.js +60 -10
  30. package/package.json +2 -1
  31. package/skills/vizzly/SKILL.md +99 -0
  32. package/skills/vizzly/agents/openai.yaml +4 -0
  33. package/skills/vizzly/references/cli-context.md +42 -0
  34. package/skills/vizzly/references/dynamic-content.md +44 -0
  35. package/skills/vizzly/references/public-screenshots.md +44 -0
  36. package/skills/vizzly/references/sdks.md +62 -0
  37. package/skills/vizzly/references/setup-ci.md +42 -0
@@ -194,7 +194,7 @@ Error generating stack: `+e.message+`
194
194
  linear-gradient(-45deg, var(--vz-checkerboard-tile) 25%, transparent 25%),
195
195
  linear-gradient(45deg, transparent 75%, var(--vz-checkerboard-tile) 75%),
196
196
  linear-gradient(-45deg, transparent 75%, var(--vz-checkerboard-tile) 75%)
197
- `,backgroundSize:`16px 16px`,backgroundPosition:`0 0, 0 8px, 8px -8px, -8px 0px`,backgroundColor:`var(--vz-checkerboard-bg)`};function Oo({comparison:e,viewMode:t=`overlay`,showDiffOverlay:n=!0,showBaseline:r=!0,onToggleBaseline:i,onionSkinPosition:a=50,onOnionSkinChange:o,onDiffToggle:s,disableLoadingOverlay:c=!1,zoom:l=`fit`,className:u=``,showRegions:d=!1}){let[f,p]=(0,N.useState)(new Set),[m,h]=(0,N.useState)(new Map),[g,_]=(0,N.useState)(!1),[v,y]=(0,N.useState)(!1),[b,x]=(0,N.useState)(!1),[S,ee]=(0,N.useState)(!1),[C,w]=(0,N.useState)(1),[te,ne]=(0,N.useState)({width:0,height:0}),T=(0,N.useRef)(null),re=(0,N.useRef)(null);(0,N.useEffect)(()=>{let e=()=>{let e=T.current,t=e?.querySelector(`img`);if(!e||!t?.naturalWidth)return;ne({width:t.naturalWidth,height:t.naturalHeight});let n=e.getBoundingClientRect(),r=n.width-40,i=n.height-40,a=r/t.naturalWidth,o=i/t.naturalHeight;w(Math.min(a,o,1))};e();let t=new window.ResizeObserver(e);T.current&&t.observe(T.current);let n=T.current?.querySelectorAll(`img`)||[];for(let t of n)t.addEventListener(`load`,e);return()=>{t.disconnect();for(let t of n)t.removeEventListener(`load`,e)}},[]);let E=(0,N.useMemo)(()=>{let e=l===`fit`,t=e?C:typeof l==`number`?l:1,n=!e&&t>C;return{isFit:e,scale:t,allowsOverflow:n,containerClass:n?`overflow-auto`:`overflow-hidden`}},[l,C]),D=(0,N.useCallback)(e=>{p(t=>new Set([...t,e]))},[]),O=(0,N.useCallback)(e=>{h(t=>new Map(t).set(e,`loaded`))},[]),k=(0,N.useMemo)(()=>e?{current:Eo(e.current,e.timestamp),baseline:Eo(e.baseline,e.timestamp),diff:Eo(e.diff,e.timestamp)}:{},[e]),A=(0,N.useMemo)(()=>e?{id:e.id||e.signature||`unknown`,name:e.name||e.originalName||`Screenshot`}:null,[e]);return(0,N.useEffect)(()=>{if(!A?.id){_(!1),y(!1),x(!1),ee(!1);return}_(!1),y(!1),x(!1),ee(!1)},[A?.id]),!e||e.status===`new`||e.status===`baseline-created`?(0,P.jsx)(`div`,{className:`h-full ${u}`,children:(0,P.jsxs)(`div`,{ref:T,className:`bg-[var(--vz-elevated)] relative h-full ${E.containerClass}`,style:Do,children:[(0,P.jsx)(`div`,{className:`relative ${E.allowsOverflow?`flex justify-center items-start`:`min-w-full min-h-full flex justify-center items-center`}`,style:{...E.allowsOverflow?{minWidth:te.width*E.scale+40,minHeight:te.height*E.scale+40,padding:`20px`}:{zoom:E.scale,padding:`20px`}},children:(0,P.jsx)(`div`,{ref:re,className:`relative inline-block`,style:E.allowsOverflow?{transform:`scale(${E.scale})`,transformOrigin:`top center`}:{},children:e&&(0,P.jsx)(`img`,{src:k.current,alt:e.name||`New screenshot`,className:`block`,onLoad:()=>O(`current-${A?.id}`),onError:()=>D(`current-${A?.id}`)})})}),!c&&e&&!m.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] animate-pulse flex items-center justify-center`,children:(0,P.jsx)(`div`,{className:`text-[var(--text-tertiary)]`,children:`Loading...`})}),e&&f.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center text-[var(--text-tertiary)]`,children:[(0,P.jsx)(hn,{className:`w-8 h-8 mx-auto mb-2`}),(0,P.jsx)(`div`,{className:`text-sm`,children:`Failed to load image`})]})})]})}):(0,P.jsx)(`div`,{className:`h-full ${u}`,children:(0,P.jsxs)(`div`,{ref:T,className:`bg-[var(--vz-elevated)] relative unified-screenshot-container h-full ${E.containerClass}`,style:Do,children:[(0,P.jsx)(`div`,{className:`relative ${E.allowsOverflow?`flex justify-center items-start`:`min-w-full min-h-full flex justify-center items-center`}`,style:{...E.allowsOverflow?{minWidth:te.width*E.scale+40,minHeight:te.height*E.scale+40,padding:`20px`}:{zoom:E.scale,padding:`20px`}},children:(0,P.jsxs)(`div`,{ref:re,className:`relative inline-block`,style:E.allowsOverflow?{transform:`scale(${E.scale})`,transformOrigin:`top center`}:{},children:[t===`toggle`&&k.baseline?(0,P.jsx)(ja,{baselineImageUrl:k.baseline,currentImageUrl:k.current,baselineImageLoaded:g,setBaselineImageLoaded:_,currentImageLoaded:v,setCurrentImageLoaded:y,baselineImageError:b,setBaselineImageError:x,currentImageError:S,setCurrentImageError:ee,screenshot:A,onImageError:D,onImageLoad:O,imageErrors:f,showBaseline:r,onToggle:i}):t===`onion-skin`&&k.baseline?(0,P.jsx)(ka,{baselineImageUrl:k.baseline,currentImageUrl:k.current,sliderPosition:a,onSliderChange:o,screenshot:A,onImageError:D,onImageLoad:O,imageErrors:f}):(0,P.jsx)(Aa,{baselineImageUrl:k.baseline,currentImageUrl:k.current,diffImageUrl:k.diff,showDiffOverlay:n,screenshot:A,onImageError:D,onImageLoad:O,imageErrors:f,onDiffToggle:s}),d&&e?.confirmedRegions?.length>0&&(0,P.jsx)(Ca,{confirmed:e.confirmedRegions,candidates:[],imageWidth:te.width,imageHeight:te.height,showConfirmed:!0,showCandidates:!1,disabled:!0})]})}),!c&&!m.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] animate-pulse flex items-center justify-center`,children:(0,P.jsx)(`div`,{className:`text-[var(--text-tertiary)]`,children:`Loading...`})}),f.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center text-[var(--text-tertiary)]`,children:[(0,P.jsx)(hn,{className:`w-8 h-8 mx-auto mb-2`}),(0,P.jsx)(`div`,{className:`text-sm`,children:`Failed to load image`})]})})]})})}function G(e){return e?.id||e?.signature||e?.name}function ko(e){switch(e){case`failed`:return`changed`;case`new`:case`baseline-created`:return`new`;case`passed`:return`unchanged`;default:return e}}function Ao(e){return(0,P.jsx)(to,{children:(0,P.jsx)(jo,{...e})})}function jo({comparison:e,comparisons:t=[],onClose:n,onAccept:r,onReject:i,onDelete:a,onNavigate:o}){let[s,c]=(0,N.useState)(So.OVERLAY),[l,u]=(0,N.useState)(!0),[d,f]=(0,N.useState)(50),[p,m]=(0,N.useState)(!0),[h,g]=(0,N.useState)(!1),[_,v]=(0,N.useState)(`needs-review`),[y,b]=(0,N.useState)(!0),[x,S]=(0,N.useState)(!1),{zoom:ee,setZoom:C}=bo(`fit`),{isActive:w}=no(),te=(0,N.useRef)(null),ne=(0,N.useCallback)(()=>{g(e=>!e)},[]),T=(0,N.useMemo)(()=>t.map(e=>({...e,name:e.name||e.originalName||`Unknown`,result:ko(e.status),status:e.status,approval_status:e.userAction===`accepted`?`approved`:e.userAction===`rejected`?`rejected`:e.status===`passed`?`approved`:`pending`,diff_percentage:e.diffPercentage??e.diff_percentage,diffClusters:e.diffClusters||[],viewport_width:e.properties?.viewport_width??e.viewport_width,viewport_height:e.properties?.viewport_height??e.viewport_height,browser:e.properties?.browser??e.browser})),[t]),re=(0,N.useMemo)(()=>{if(!e)return null;let t=e.name||e.originalName;return{name:t,comparisons:T.filter(e=>(e.name||e.originalName)===t)}},[e,T]),E=(0,N.useCallback)(e=>{e&&o&&o(e)},[o]),D=(0,N.useMemo)(()=>{let e={all:()=>!0,"needs-review":e=>(e.result===`changed`||e.result===`new`)&&e.approval_status!==`approved`&&e.approval_status!==`rejected`,changes:e=>e.result===`changed`,new:e=>e.result===`new`},t=e[_]||e.all;return T.filter(t)},[T,_]),O=(0,N.useMemo)(()=>{if(!e)return-1;let t=G(e);return D.findIndex(e=>G(e)===t)},[D,e]),k=(0,N.useMemo)(()=>({prev:O>0,next:O<D.length-1&&O!==-1}),[O,D.length]),A=(0,N.useCallback)(()=>{if(k.prev){let e=D[O-1];o(e)}},[k.prev,D,O,o]),ie=(0,N.useCallback)(()=>{if(k.next){let e=D[O+1];o(e)}},[k.next,D,O,o]),ae=(0,N.useCallback)(()=>O<D.length-1?D[O+1]:O>0?D[O-1]:null,[O,D]),oe=(0,N.useCallback)(()=>{if(!r||!e)return;let t=_===`needs-review`?ae():null;r(G(e)),t?o(t):_===`needs-review`&&D.length<=1&&n()},[r,e,_,ae,D.length,o,n]),se=(0,N.useCallback)(()=>{if(!i||!e)return;let t=_===`needs-review`?ae():null;i(G(e)),t?o(t):_===`needs-review`&&D.length<=1&&n()},[i,e,_,ae,D.length,o,n]),ce=(0,N.useCallback)(()=>{!a||!e||a(G(e))},[a,e]),j=(0,N.useCallback)(()=>{c(e=>e===So.OVERLAY?So.TOGGLE:So.OVERLAY)},[]),le=(0,N.useCallback)(()=>{s===So.TOGGLE?b(e=>!e):u(e=>!e)},[s]);if(ro((0,N.useMemo)(()=>({a:r?oe:void 0,r:i?se:void 0,d:le,t:j,1:()=>c(So.OVERLAY),2:()=>c(So.TOGGLE),3:()=>c(So.ONION)}),[oe,se,r,i,j,le])),(0,N.useEffect)(()=>{let e=e=>{if(!(e.target.tagName===`INPUT`||e.target.tagName===`TEXTAREA`))switch(e.key){case`ArrowUp`:case`ArrowLeft`:e.preventDefault(),k.prev&&A();break;case`ArrowDown`:case`ArrowRight`:e.preventDefault(),k.next&&ie();break;case`Escape`:e.preventDefault(),n();break;case`i`:!e.metaKey&&!e.ctrlKey&&(e.preventDefault(),ne());break;case`g`:!e.metaKey&&!e.ctrlKey&&(e.preventDefault(),S(e=>!e));break}};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[k,A,ie,n,ne]),(0,N.useEffect)(()=>{if(!p||O<0||O>=D.length)return;let e=te.current;if(!e)return;let t=e.closest(`.overflow-y-auto`);if(!t)return;let n=e.getBoundingClientRect(),r=t.getBoundingClientRect();n.top<r.top?t.scrollBy({top:n.top-r.top-8,behavior:`smooth`}):n.bottom>r.bottom&&t.scrollBy({top:n.bottom-r.bottom+8,behavior:`smooth`})},[O,D.length,p]),!e)return(0,P.jsx)(`div`,{className:`min-h-screen bg-[var(--vz-bg)] flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center text-[var(--text-tertiary)]`,children:[(0,P.jsx)(`div`,{className:`text-lg mb-2`,children:`Comparison not found`}),(0,P.jsx)(`button`,{type:`button`,onClick:n,className:`text-[var(--accent-info)] hover:text-[var(--text-primary)]`,children:`Return to list`})]})});let ue=ko(e.status),de=e.userAction===`accepted`?`approved`:e.userAction===`rejected`?`rejected`:e.status===`passed`?`approved`:`pending`,M={new:{variant:`info`,label:`New`},changed:{variant:`warning`,label:`Changed`},unchanged:{variant:`default`,label:`Unchanged`}}[ue],fe=e.status===`new`||e.status===`baseline-created`,pe=e.properties||{};return(0,P.jsxs)(`div`,{className:`h-screen bg-[var(--vz-bg)] flex flex-col overflow-hidden`,"data-testid":`fullscreen-viewer`,children:[(0,P.jsxs)(`header`,{className:`flex-shrink-0 bg-[var(--vz-bg)] border-b border-[var(--vz-border-subtle)] z-30`,children:[(0,P.jsxs)(`div`,{className:`px-2 sm:px-4 py-2 flex items-center gap-1 sm:gap-3`,children:[(0,P.jsx)(qa,{onClick:n}),(0,P.jsxs)(`div`,{className:`flex-1 min-w-0 flex items-center gap-2`,children:[(0,P.jsx)(`h1`,{className:`text-sm font-medium text-[var(--text-primary)] truncate`,children:e.name||e.originalName||`Unknown`}),M&&(0,P.jsx)(nr,{variant:M.variant,size:`sm`,className:`hidden xs:flex`,children:M.label}),w&&(0,P.jsx)(nr,{variant:`warning`,size:`sm`,dot:!0,pulseDot:!0,className:`hidden sm:flex`,children:`Review Mode`})]}),(0,P.jsxs)(`div`,{className:`flex items-center`,children:[(0,P.jsx)(`button`,{type:`button`,onClick:A,disabled:!k.prev,className:`p-2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] disabled:opacity-85 disabled:cursor-not-allowed rounded-md hover:bg-[var(--vz-raised)] transition-colors`,"aria-label":`Previous`,children:(0,P.jsx)(Kt,{className:`w-5 h-5`})}),(0,P.jsxs)(`span`,{className:`hidden sm:block text-xs text-[var(--text-muted)] font-medium tabular-nums px-1 min-w-[3rem] text-center`,children:[O+1,`/`,D.length]}),(0,P.jsx)(`button`,{type:`button`,onClick:ie,disabled:!k.next,className:`p-2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] disabled:opacity-85 disabled:cursor-not-allowed rounded-md hover:bg-[var(--vz-raised)] transition-colors`,"aria-label":`Next`,children:(0,P.jsx)(Jt,{className:`w-5 h-5`})})]}),(0,P.jsx)(`div`,{className:`hidden sm:block h-5 w-px bg-[var(--vz-border-subtle)]`}),(0,P.jsxs)(`div`,{className:`hidden sm:flex items-center gap-2`,children:[fe&&a?(0,P.jsx)(`button`,{type:`button`,onClick:ce,className:`px-3 py-1.5 text-xs font-medium text-[var(--accent-danger)] hover:text-[var(--text-primary)] hover:bg-[var(--accent-danger-muted)] rounded-md transition-colors`,"data-testid":`btn-delete`,children:`Delete`}):null,(0,P.jsx)(ba,{status:de,onApprove:r?oe:null,onReject:i?se:null,compact:!0})]}),(0,P.jsxs)(`div`,{className:`hidden sm:flex items-center`,children:[(0,P.jsx)(`button`,{type:`button`,onClick:()=>m(!p),className:`flex items-center justify-center p-2 rounded-md transition-colors ${p?`bg-[var(--accent-brand-muted)] text-[var(--accent-brand)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)]`}`,title:`Queue`,"aria-label":`Toggle queue`,"data-testid":`toggle-queue-btn`,children:(0,P.jsx)(Tn,{className:`w-5 h-5 pointer-events-none`})}),(e?.confirmedRegions?.length>0||e?.hasConfirmedRegions)&&(0,P.jsx)(`button`,{type:`button`,onClick:()=>S(!x),className:`p-2 rounded-md transition-colors ${x?`bg-[var(--accent-success-muted)] text-[var(--accent-success)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)]`}`,title:`Show Regions (G)`,"aria-label":`Toggle regions`,"data-testid":`toggle-regions-btn`,children:(0,P.jsx)(jn,{className:`w-5 h-5 pointer-events-none`})}),(0,P.jsx)(`button`,{type:`button`,onClick:ne,className:`p-2 rounded-md transition-colors ${h?`bg-[var(--accent-info-muted)] text-[var(--accent-info)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)]`}`,title:`Details (I)`,"aria-label":`Screenshot details`,"data-testid":`toggle-inspector-btn`,children:(0,P.jsx)(Cn,{className:`w-5 h-5 pointer-events-none`})})]})]}),(0,P.jsxs)(`div`,{className:`px-2 sm:px-4 py-1.5 sm:py-2 border-t border-[var(--vz-border-subtle)] flex items-center gap-2 sm:gap-3`,children:[re&&re.comparisons.length>0&&(0,P.jsx)(go,{variants:re.comparisons,currentVariantId:G(e),onVariantSelect:E}),(0,P.jsx)(`div`,{className:`flex-1`}),e.diff&&(0,P.jsx)(vo,{value:s,onChange:c,compact:!0,className:`hidden sm:flex`}),(0,P.jsx)(xo,{zoom:ee,onZoomChange:C})]})]}),(0,P.jsxs)(`div`,{className:`flex-1 min-h-0 flex overflow-hidden`,children:[(0,P.jsxs)(Va,{isOpen:p,onClose:()=>m(!1),position:`bottom`,desktopPosition:`left`,width:`w-80`,mobileHeight:`30vh`,expandedHeight:`80vh`,alwaysVisibleOnDesktop:!1,showBackdrop:!1,testId:`queue-drawer`,children:[(0,P.jsx)(Ha,{title:`Queue`,subtitle:`${D.length} of ${T.length} items`,onClose:()=>m(!1)}),(0,P.jsx)(Ga,{children:[{id:`needs-review`,label:`To Review`},{id:`changes`,label:`Changes`},{id:`new`,label:`New`},{id:`all`,label:`All`}].map(e=>(0,P.jsx)(Ka,{isActive:_===e.id,onClick:()=>v(e.id),children:e.label},e.id))}),(0,P.jsxs)(Ua,{className:`px-2 py-2 space-y-1`,children:[D.map(t=>{let n=G(t)===G(e);return(0,P.jsx)(`div`,{ref:n?te:null,children:(0,P.jsx)($a,{item:t,isActive:n,thumbnailUrl:Eo(t.current||t.baseline,t.timestamp),onClick:()=>o(t)})},G(t))}),D.length===0&&(0,P.jsx)(`div`,{className:`text-center py-8 text-[var(--text-muted)] text-xs`,children:_===`needs-review`?`All screenshots reviewed!`:`No items match this filter`})]}),(0,P.jsxs)(Wa,{className:`hidden sm:flex flex-col items-center gap-1.5`,children:[(0,P.jsxs)(`p`,{className:`text-xs text-[var(--text-muted)] text-center`,children:[(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)]`,children:`↑↓`}),` navigate`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)]`,children:`Space`}),` `,w?`exit`:`review mode`]}),w&&(0,P.jsxs)(`p`,{className:`text-xs text-[var(--text-muted)] text-center`,children:[(0,P.jsx)(`span`,{className:`text-[var(--accent-success)] font-medium`,children:`A`}),` `,`approve`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--accent-danger)] font-medium`,children:`R`}),` `,`reject`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)] font-medium`,children:`D`}),` `,`diff`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)] font-medium`,children:`T`}),` `,`view`]})]})]}),(0,P.jsx)(`main`,{className:`flex-1 min-h-0 overflow-hidden relative`,children:(0,P.jsx)(Oo,{comparison:e,viewMode:s===So.ONION?`onion-skin`:s,showDiffOverlay:l,onDiffToggle:()=>u(e=>!e),showBaseline:y,onToggleBaseline:()=>b(e=>!e),onionSkinPosition:d,onOnionSkinChange:f,zoom:ee,disableLoadingOverlay:!0,showRegions:x,className:`w-full h-full`},G(e))}),(0,P.jsxs)(Za,{isOpen:h,onClose:()=>g(!1),title:`Screenshot Details`,subtitle:e.name||e.originalName,className:`order-last`,children:[(0,P.jsx)(Za.Section,{title:`Status`,icon:sn,children:(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[M&&(0,P.jsx)(nr,{variant:M.variant,size:`sm`,children:M.label}),(0,P.jsx)(nr,{variant:de===`approved`?`success`:de===`rejected`?`danger`:`default`,size:`sm`,children:de})]})}),(0,P.jsxs)(Za.Section,{title:`Screenshot`,icon:an,children:[pe.browser&&(0,P.jsx)(Za.Row,{label:`Browser`,value:pe.browser,icon:()=>(0,P.jsx)(ir,{browser:pe.browser,className:`w-3.5 h-3.5`})}),(0,P.jsx)(Za.Row,{label:`Viewport`,value:pe.viewport_width&&pe.viewport_height?`${pe.viewport_width} × ${pe.viewport_height}`:null,mono:!0}),(0,P.jsx)(Za.Row,{label:`Device`,value:pe.device})]}),ue===`changed`&&e.diffPercentage!=null&&(0,P.jsxs)(Za.Section,{title:`Diff Analysis`,icon:pn,children:[(0,P.jsx)(Za.Row,{label:`Changed`,value:`${Number(e.diffPercentage).toFixed(3)}%`,mono:!0}),e.diffPixels!=null&&(0,P.jsx)(Za.Row,{label:`Pixels`,value:e.diffPixels.toLocaleString(),mono:!0}),e.threshold!=null&&(0,P.jsx)(Za.Row,{label:`Threshold`,value:Number(e.threshold).toFixed(2),mono:!0})]}),(0,P.jsx)(Za.Section,{title:`Identifier`,icon:sn,children:(0,P.jsx)(Za.Row,{label:`Signature`,value:e.signature||e.id,mono:!0,copyable:!0})})]})]}),(0,P.jsxs)(`div`,{className:`sm:hidden flex-shrink-0 bg-[var(--vz-bg)] border-t border-[var(--vz-border-subtle)] safe-area-pb`,children:[(0,P.jsx)(xa,{status:de,onApprove:r?oe:null,onReject:i?se:null}),(0,P.jsxs)(`div`,{className:`px-3 py-2 flex items-center gap-1 border-t border-[var(--vz-border-subtle)]`,children:[(0,P.jsx)(`button`,{type:`button`,onClick:()=>m(!p),className:`flex items-center justify-center p-2.5 rounded-lg transition-colors ${p?`bg-[var(--accent-brand-muted)] text-[var(--accent-brand)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)] active:bg-[var(--vz-elevated)]`}`,"aria-label":`Queue`,"data-testid":`mobile-toggle-queue-btn`,children:(0,P.jsx)(Tn,{className:`w-5 h-5 pointer-events-none`})}),(0,P.jsx)(`button`,{type:`button`,onClick:ne,className:`flex items-center justify-center p-2.5 rounded-lg transition-colors ${h?`bg-[var(--accent-info-muted)] text-[var(--accent-info)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)] active:bg-[var(--vz-elevated)]`}`,"aria-label":`Screenshot details`,"data-testid":`mobile-toggle-inspector-btn`,children:(0,P.jsx)(Cn,{className:`w-5 h-5 pointer-events-none`})}),(e?.confirmedRegions?.length>0||e?.hasConfirmedRegions)&&(0,P.jsx)(`button`,{type:`button`,onClick:()=>S(!x),className:`flex items-center justify-center p-2.5 rounded-lg transition-colors ${x?`bg-[var(--accent-success-muted)] text-[var(--accent-success)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)] active:bg-[var(--vz-elevated)]`}`,"aria-label":`Toggle regions`,"data-testid":`mobile-toggle-regions-btn`,children:(0,P.jsx)(jn,{className:`w-5 h-5 pointer-events-none`})}),fe&&a&&(0,P.jsx)(`button`,{type:`button`,onClick:ce,className:`flex items-center justify-center p-2.5 rounded-lg text-[var(--accent-danger)] hover:text-[var(--text-primary)] hover:bg-[var(--accent-danger-muted)] active:bg-[var(--accent-danger-muted)] transition-colors`,"aria-label":`Delete`,"data-testid":`mobile-delete-btn`,children:(0,P.jsx)(`span`,{className:`text-xs font-medium`,children:`Delete`})}),(0,P.jsx)(`div`,{className:`flex-1`}),(0,P.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,P.jsxs)(`span`,{className:`text-xs text-[var(--text-muted)] tabular-nums`,children:[O+1,` / `,D.length]}),M&&(0,P.jsx)(nr,{variant:M.variant,size:`sm`,children:M.label})]})]})]})]})}function Mo(){let[,e]=gi(),[,t]=vi(`/comparison/:id`),{data:n}=Ui(),r=Wi(),i=qi(),a=Xi(),o=(0,N.useMemo)(()=>n?.comparisons||[],[n?.comparisons]),s=(0,N.useMemo)(()=>{let e=t?.id?decodeURIComponent(t.id):null;return!e||o.length===0?null:o.find(t=>t.id===e||t.signature===e||t.name===e)},[t,o]),{data:c}=Hi(s?.id),l=(0,N.useMemo)(()=>s?c?{...s,diffClusters:c.diffClusters,confirmedRegions:c.confirmedRegions,intensityStats:c.intensityStats,boundingBox:c.boundingBox,regionAnalysis:c.regionAnalysis,hotspotAnalysis:c.hotspotAnalysis}:s:null,[s,c]),u=(0,N.useCallback)(t=>{let n=t.id||t.signature||t.name;n&&e(`/comparison/${encodeURIComponent(n)}`)},[e]),d=(0,N.useCallback)(()=>{e(`/`)},[e]),f=(0,N.useCallback)(e=>{r.mutate(e)},[r]),p=(0,N.useCallback)(e=>{i.mutate(e)},[i]),m=(0,N.useCallback)(t=>{a.mutate(t,{onSuccess:()=>{e(`/`)}})},[a,e]);return l?(0,P.jsx)(Ao,{comparison:l,comparisons:o,onClose:d,onAccept:f,onReject:p,onDelete:m,onNavigate:u}):(0,P.jsx)(`div`,{className:`fixed inset-0 bg-[var(--vz-bg)] z-50 flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center`,children:[(0,P.jsx)(`div`,{className:`text-lg text-[var(--text-primary)] font-medium mb-2`,children:`Comparison not found`}),(0,P.jsxs)(`p`,{className:`text-sm text-[var(--text-tertiary)] mb-4`,children:[`ID: `,t?.id||`none`]}),(0,P.jsx)(`button`,{type:`button`,onClick:d,className:`text-[var(--accent-media)] hover:text-[var(--accent-media)] font-medium transition-colors`,children:`Return to list`})]})})}To.NEW,To.BASELINE_CREATED,To.PASSED,To.FAILED,To.ERROR,To.REJECTED;function No(e,t){return[...e].sort((e,n)=>{if(t===`priority`){let t={[To.FAILED]:3,[To.NEW]:2,[To.PASSED]:1},r=e.initialStatus||e.status,i=n.initialStatus||n.status,a=t[r]||0,o=t[i]||0;return a===o?(n.diffPercentage||0)-(e.diffPercentage||0):o-a}return t===`name`?(e.name||``).localeCompare(n.name||``):t===`time`?(n.timestamp||0)-(e.timestamp||0):0})}function Po(e,t){return t===`failed`?e.filter(e=>e.status===To.FAILED):t===`passed`?e.filter(e=>e.status===To.PASSED):t===`new`?e.filter(e=>e.status===To.NEW||e.status===To.BASELINE_CREATED):e}function Fo(e){return e===`new`||e===`baseline-created`}function Io(e){return e===`failed`||Fo(e)}var Lo=()=>{let e=new URLSearchParams(window.location.search);return{filter:e.get(`filter`)||Co.ALL,sortBy:e.get(`sort`)||wo.PRIORITY,searchQuery:e.get(`search`)||``,selectedBrowser:e.get(`browser`)||`all`,selectedViewport:e.get(`viewport`)||`all`}};function Ro(e=[]){let t=Lo(),[n,r]=(0,N.useState)(t.filter),[i,a]=(0,N.useState)(t.sortBy),[o,s]=(0,N.useState)(t.searchQuery),[c,l]=(0,N.useState)(t.selectedBrowser),[u,d]=(0,N.useState)(t.selectedViewport);(0,N.useEffect)(()=>{let e=new URLSearchParams;n!==Co.ALL&&e.set(`filter`,n),i!==wo.PRIORITY&&e.set(`sort`,i),o&&e.set(`search`,o),c!==`all`&&e.set(`browser`,c),u!==`all`&&e.set(`viewport`,u);let t=e.toString()?`?${e.toString()}`:window.location.pathname;window.history.replaceState({},``,t)},[n,i,o,c,u]);let f=(0,N.useMemo)(()=>{let t=new Set,n=new Set;return e.forEach(e=>{if(e.properties?.browser&&t.add(e.properties.browser),e.properties?.viewport_width&&e.properties?.viewport_height){let t=`${e.properties.viewport_width}x${e.properties.viewport_height}`;n.add(t)}else if(e.properties?.viewport){let t=`${e.properties.viewport.width}x${e.properties.viewport.height}`;n.add(t)}}),{browsers:Array.from(t).sort(),viewports:Array.from(n).sort()}},[e]);return{filteredComparisons:(0,N.useMemo)(()=>{let t=Po(e,n);if(o.trim()){let e=o.toLowerCase();t=t.filter(t=>t.name.toLowerCase().includes(e)||t.originalName?.toLowerCase().includes(e))}return c!==`all`&&(t=t.filter(e=>e.properties?.browser===c)),u!==`all`&&(t=t.filter(e=>{let t=null;return e.properties?.viewport_width&&e.properties?.viewport_height?t=`${e.properties.viewport_width}x${e.properties.viewport_height}`:e.properties?.viewport&&(t=`${e.properties.viewport.width}x${e.properties.viewport.height}`),t===u})),No(t,i)},[e,n,i,o,c,u]),filter:n,setFilter:r,sortBy:i,setSortBy:a,searchQuery:o,setSearchQuery:s,selectedBrowser:c,setSelectedBrowser:l,selectedViewport:u,setSelectedViewport:d,availableFilters:f,counts:{all:e.length,failed:e.filter(e=>e.status===`failed`).length,passed:e.filter(e=>e.status===`passed`).length,new:e.filter(e=>Fo(e.status)).length,rejected:e.filter(e=>e.status===`rejected`).length}}}function zo({src:e,alt:t,className:n,style:r,onClick:i}){let[a,o]=(0,N.useState)(e?`loading`:`missing`);if((0,N.useEffect)(()=>{if(!e){o(`missing`);return}o(`loading`)},[e]),a===`missing`)return(0,P.jsx)(`div`,{className:`flex items-center justify-center bg-[var(--vz-raised)] border border-[var(--vz-border)] rounded min-h-[200px]`,style:r,children:(0,P.jsxs)(`div`,{className:`text-[var(--text-tertiary)] text-center`,children:[(0,P.jsx)(Fn,{className:`w-12 h-12 mx-auto mb-3`}),(0,P.jsxs)(`div`,{className:`text-sm`,children:[`No `,t.toLowerCase(),` available`]})]})});if(a===`error`)return(0,P.jsx)(`div`,{className:`flex items-center justify-center bg-[var(--accent-danger-muted)] border border-[color-mix(in_srgb,var(--accent-danger)_28%,transparent)] rounded min-h-[200px]`,style:r,children:(0,P.jsxs)(`div`,{className:`text-[var(--accent-danger)] text-center`,children:[(0,P.jsx)(hn,{className:`w-12 h-12 mx-auto mb-3`}),(0,P.jsxs)(`div`,{className:`text-sm`,children:[`Failed to load `,t.toLowerCase()]}),(0,P.jsx)(`div`,{className:`text-xs text-[var(--text-muted)] mt-1 font-mono break-all px-4`,children:e})]})});let s=null;return i&&(s=e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),i(e))}),(0,P.jsx)(`img`,{src:e,alt:t,className:n,style:r,loading:`lazy`,decoding:`async`,onLoad:()=>o(`loaded`),onError:()=>o(`error`),onClick:i,onKeyDown:s,role:i?`button`:void 0,tabIndex:i?0:void 0})}function Bo(e){return e.id||e.signature||e.name}function Vo(e){let t=new Map;for(let n of e){let e=n.name||n.originalName||`Unknown`;t.has(e)||t.set(e,{name:e,comparisons:[],browsers:new Set,viewports:[],hasChanges:!1,hasNew:!1,allPassed:!0,maxDiff:0,failedCount:0,newCount:0,passedCount:0,acceptedCount:0,rejectedCount:0});let r=t.get(e);r.comparisons.push(n),n.properties?.browser&&r.browsers.add(n.properties.browser),n.properties?.viewport_width&&n.properties?.viewport_height&&r.viewports.push({width:n.properties.viewport_width,height:n.properties.viewport_height,id:n.id,status:n.status}),n.status===`failed`?(r.hasChanges=!0,r.allPassed=!1,r.failedCount++,r.maxDiff=Math.max(r.maxDiff,n.diffPercentage||0)):n.status===`new`||n.status===`baseline-created`?(r.hasNew=!0,r.allPassed=!1,r.newCount++):n.status===`passed`&&r.passedCount++,n.userAction===`accepted`?r.acceptedCount++:n.userAction===`rejected`&&r.rejectedCount++}return Array.from(t.values()).map(e=>({...e,browsers:Array.from(e.browsers),viewports:e.viewports.sort((e,t)=>t.width*t.height-e.width*e.height),primary:e.comparisons[0]})).sort((e,t)=>e.hasChanges&&!t.hasChanges?-1:!e.hasChanges&&t.hasChanges?1:e.hasNew&&!t.hasNew?-1:!e.hasNew&&t.hasNew?1:e.maxDiff===t.maxDiff?e.name.localeCompare(t.name):t.maxDiff-e.maxDiff)}function Ho({width:e,className:t=`w-3.5 h-3.5`}){return e<=480?(0,P.jsx)(ln,{className:t}):e<=1024?(0,P.jsx)(dn,{className:t}):(0,P.jsx)(an,{className:t})}function Uo({group:e}){return e.acceptedCount===e.comparisons.length?(0,P.jsxs)(nr,{variant:`success`,size:`sm`,children:[(0,P.jsx)(Bt,{className:`w-3 h-3 mr-1`}),`Accepted`]}):e.rejectedCount>0?(0,P.jsxs)(nr,{variant:`warning`,size:`sm`,children:[(0,P.jsx)(Yn,{className:`w-3 h-3 mr-1`}),`Rejected`]}):e.hasChanges?(0,P.jsxs)(nr,{variant:`danger`,size:`sm`,children:[e.failedCount,` changed`]}):e.hasNew?(0,P.jsxs)(nr,{variant:`info`,size:`sm`,children:[e.newCount,` new`]}):(0,P.jsx)(nr,{variant:`success`,size:`sm`,children:`Passed`})}function Wo({group:e,maxVisible:t=4}){let n=e.comparisons.length;if(n<=1){let t=e.viewports[0],n=e.browsers[0];return(0,P.jsxs)(`div`,{className:`flex items-center gap-2 text-xs text-[var(--text-tertiary)]`,children:[t&&(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1`,children:[(0,P.jsx)(Ho,{width:t.width,className:`w-3 h-3`}),t.width,`×`,t.height]}),n&&(0,P.jsx)(`span`,{children:n})]})}let r=e.viewports.slice(0,t),i=n-t;return(0,P.jsxs)(`div`,{className:`flex items-center gap-1.5 flex-wrap`,children:[r.map((e,t)=>(0,P.jsxs)(`span`,{className:`
197
+ `,backgroundSize:`16px 16px`,backgroundPosition:`0 0, 0 8px, 8px -8px, -8px 0px`,backgroundColor:`var(--vz-checkerboard-bg)`};function Oo({comparison:e,viewMode:t=`overlay`,showDiffOverlay:n=!0,showBaseline:r=!0,onToggleBaseline:i,onionSkinPosition:a=50,onOnionSkinChange:o,onDiffToggle:s,disableLoadingOverlay:c=!1,zoom:l=`fit`,className:u=``,showRegions:d=!1}){let[f,p]=(0,N.useState)(new Set),[m,h]=(0,N.useState)(new Map),[g,_]=(0,N.useState)(!1),[v,y]=(0,N.useState)(!1),[b,x]=(0,N.useState)(!1),[S,ee]=(0,N.useState)(!1),[C,w]=(0,N.useState)(1),[te,ne]=(0,N.useState)({width:0,height:0}),T=(0,N.useRef)(null),re=(0,N.useRef)(null);(0,N.useEffect)(()=>{let e=()=>{let e=T.current,t=e?.querySelector(`img`);if(!e||!t?.naturalWidth)return;ne({width:t.naturalWidth,height:t.naturalHeight});let n=e.getBoundingClientRect(),r=n.width-40,i=n.height-40,a=r/t.naturalWidth,o=i/t.naturalHeight;w(Math.min(a,o,1))};e();let t=new window.ResizeObserver(e);T.current&&t.observe(T.current);let n=T.current?.querySelectorAll(`img`)||[];for(let t of n)t.addEventListener(`load`,e);return()=>{t.disconnect();for(let t of n)t.removeEventListener(`load`,e)}},[]);let E=(0,N.useMemo)(()=>{let e=l===`fit`,t=e?C:typeof l==`number`?l:1,n=!e&&t>C;return{isFit:e,scale:t,allowsOverflow:n,containerClass:n?`overflow-auto`:`overflow-hidden`}},[l,C]),D=(0,N.useCallback)(e=>{p(t=>new Set([...t,e]))},[]),O=(0,N.useCallback)(e=>{h(t=>new Map(t).set(e,`loaded`))},[]),k=(0,N.useMemo)(()=>e?{current:Eo(e.current,e.timestamp),baseline:Eo(e.baseline,e.timestamp),diff:Eo(e.diff,e.timestamp)}:{},[e]),A=(0,N.useMemo)(()=>e?{id:e.id||e.signature||`unknown`,name:e.name||e.originalName||`Screenshot`}:null,[e]);return(0,N.useEffect)(()=>{if(!A?.id){_(!1),y(!1),x(!1),ee(!1);return}_(!1),y(!1),x(!1),ee(!1)},[A?.id]),!e||e.status===`new`||e.status===`baseline-created`?(0,P.jsx)(`div`,{className:`h-full ${u}`,children:(0,P.jsxs)(`div`,{ref:T,className:`bg-[var(--vz-elevated)] relative h-full ${E.containerClass}`,style:Do,children:[(0,P.jsx)(`div`,{className:`relative ${E.allowsOverflow?`flex justify-center items-start`:`min-w-full min-h-full flex justify-center items-center`}`,style:{...E.allowsOverflow?{minWidth:te.width*E.scale+40,minHeight:te.height*E.scale+40,padding:`20px`}:{zoom:E.scale,padding:`20px`}},children:(0,P.jsx)(`div`,{ref:re,className:`relative inline-block`,style:E.allowsOverflow?{transform:`scale(${E.scale})`,transformOrigin:`top center`}:{},children:e&&(0,P.jsx)(`img`,{src:k.current,alt:e.name||`New screenshot`,className:`block`,onLoad:()=>O(`current-${A?.id}`),onError:()=>D(`current-${A?.id}`)})})}),!c&&e&&!m.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] animate-pulse flex items-center justify-center`,children:(0,P.jsx)(`div`,{className:`text-[var(--text-tertiary)]`,children:`Loading...`})}),e&&f.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center text-[var(--text-tertiary)]`,children:[(0,P.jsx)(hn,{className:`w-8 h-8 mx-auto mb-2`}),(0,P.jsx)(`div`,{className:`text-sm`,children:`Failed to load image`})]})})]})}):(0,P.jsx)(`div`,{className:`h-full ${u}`,children:(0,P.jsxs)(`div`,{ref:T,className:`bg-[var(--vz-elevated)] relative unified-screenshot-container h-full ${E.containerClass}`,style:Do,children:[(0,P.jsx)(`div`,{className:`relative ${E.allowsOverflow?`flex justify-center items-start`:`min-w-full min-h-full flex justify-center items-center`}`,style:{...E.allowsOverflow?{minWidth:te.width*E.scale+40,minHeight:te.height*E.scale+40,padding:`20px`}:{zoom:E.scale,padding:`20px`}},children:(0,P.jsxs)(`div`,{ref:re,className:`relative inline-block`,style:E.allowsOverflow?{transform:`scale(${E.scale})`,transformOrigin:`top center`}:{},children:[t===`toggle`&&k.baseline?(0,P.jsx)(ja,{baselineImageUrl:k.baseline,currentImageUrl:k.current,baselineImageLoaded:g,setBaselineImageLoaded:_,currentImageLoaded:v,setCurrentImageLoaded:y,baselineImageError:b,setBaselineImageError:x,currentImageError:S,setCurrentImageError:ee,screenshot:A,onImageError:D,onImageLoad:O,imageErrors:f,showBaseline:r,onToggle:i}):t===`onion-skin`&&k.baseline?(0,P.jsx)(ka,{baselineImageUrl:k.baseline,currentImageUrl:k.current,sliderPosition:a,onSliderChange:o,screenshot:A,onImageError:D,onImageLoad:O,imageErrors:f}):(0,P.jsx)(Aa,{baselineImageUrl:k.baseline,currentImageUrl:k.current,diffImageUrl:k.diff,showDiffOverlay:n,screenshot:A,onImageError:D,onImageLoad:O,imageErrors:f,onDiffToggle:s}),d&&e?.confirmedRegions?.length>0&&(0,P.jsx)(Ca,{confirmed:e.confirmedRegions,candidates:[],imageWidth:te.width,imageHeight:te.height,showConfirmed:!0,showCandidates:!1,disabled:!0})]})}),!c&&!m.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] animate-pulse flex items-center justify-center`,children:(0,P.jsx)(`div`,{className:`text-[var(--text-tertiary)]`,children:`Loading...`})}),f.has(`current-${A?.id}`)&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-[var(--vz-raised)] flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center text-[var(--text-tertiary)]`,children:[(0,P.jsx)(hn,{className:`w-8 h-8 mx-auto mb-2`}),(0,P.jsx)(`div`,{className:`text-sm`,children:`Failed to load image`})]})})]})})}function G(e){return e?.id||e?.signature||e?.name}function ko(e){switch(e){case`failed`:return`changed`;case`new`:case`baseline-created`:return`new`;case`passed`:return`unchanged`;default:return e}}function Ao(e){return(0,P.jsx)(to,{children:(0,P.jsx)(jo,{...e})})}function jo({comparison:e,comparisons:t=[],onClose:n,onAccept:r,onReject:i,onDelete:a,onNavigate:o}){let[s,c]=(0,N.useState)(So.OVERLAY),[l,u]=(0,N.useState)(!0),[d,f]=(0,N.useState)(50),[p,m]=(0,N.useState)(!0),[h,g]=(0,N.useState)(!1),[_,v]=(0,N.useState)(`needs-review`),[y,b]=(0,N.useState)(!0),[x,S]=(0,N.useState)(!1),{zoom:ee,setZoom:C}=bo(`fit`),{isActive:w}=no(),te=(0,N.useRef)(null),ne=(0,N.useCallback)(()=>{g(e=>!e)},[]),T=(0,N.useMemo)(()=>t.map(e=>({...e,name:e.name||e.originalName||`Unknown`,result:ko(e.status),status:e.status,approval_status:e.userAction===`accepted`?`approved`:e.userAction===`rejected`?`rejected`:e.status===`passed`?`approved`:`pending`,diff_percentage:e.diffPercentage??e.diff_percentage,diffClusters:e.diffClusters||[],viewport_width:e.properties?.viewport_width??e.viewport_width,viewport_height:e.properties?.viewport_height??e.viewport_height,browser:e.properties?.browser??e.browser})),[t]),re=(0,N.useMemo)(()=>{if(!e)return null;let t=e.name||e.originalName;return{name:t,comparisons:T.filter(e=>(e.name||e.originalName)===t)}},[e,T]),E=(0,N.useCallback)(e=>{e&&o&&o(e)},[o]),D=(0,N.useMemo)(()=>{let e={all:()=>!0,"needs-review":e=>(e.result===`changed`||e.result===`new`)&&e.approval_status!==`approved`&&e.approval_status!==`rejected`,changes:e=>e.result===`changed`,new:e=>e.result===`new`},t=e[_]||e.all;return T.filter(t)},[T,_]),O=(0,N.useMemo)(()=>{if(!e)return-1;let t=G(e);return D.findIndex(e=>G(e)===t)},[D,e]),k=(0,N.useMemo)(()=>({prev:O>0,next:O<D.length-1&&O!==-1}),[O,D.length]),A=(0,N.useCallback)(()=>{if(k.prev){let e=D[O-1];o(e)}},[k.prev,D,O,o]),ie=(0,N.useCallback)(()=>{if(k.next){let e=D[O+1];o(e)}},[k.next,D,O,o]),ae=(0,N.useCallback)(()=>O<D.length-1?D[O+1]:O>0?D[O-1]:null,[O,D]),oe=(0,N.useCallback)(()=>{if(!r||!e)return;let t=_===`needs-review`?ae():null;r(G(e)),t?o(t):_===`needs-review`&&D.length<=1&&n()},[r,e,_,ae,D.length,o,n]),se=(0,N.useCallback)(()=>{if(!i||!e)return;let t=_===`needs-review`?ae():null;i(G(e)),t?o(t):_===`needs-review`&&D.length<=1&&n()},[i,e,_,ae,D.length,o,n]),ce=(0,N.useCallback)(()=>{!a||!e||a(G(e))},[a,e]),j=(0,N.useCallback)(()=>{c(e=>e===So.OVERLAY?So.TOGGLE:So.OVERLAY)},[]),le=(0,N.useCallback)(()=>{s===So.TOGGLE?b(e=>!e):u(e=>!e)},[s]);if(ro((0,N.useMemo)(()=>({a:r?oe:void 0,r:i?se:void 0,d:le,t:j,1:()=>c(So.OVERLAY),2:()=>c(So.TOGGLE),3:()=>c(So.ONION)}),[oe,se,r,i,j,le])),(0,N.useEffect)(()=>{let e=e=>{if(!(e.target.tagName===`INPUT`||e.target.tagName===`TEXTAREA`))switch(e.key){case`ArrowUp`:case`ArrowLeft`:e.preventDefault(),k.prev&&A();break;case`ArrowDown`:case`ArrowRight`:e.preventDefault(),k.next&&ie();break;case`Escape`:e.preventDefault(),n();break;case`i`:!e.metaKey&&!e.ctrlKey&&(e.preventDefault(),ne());break;case`g`:!e.metaKey&&!e.ctrlKey&&(e.preventDefault(),S(e=>!e));break}};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[k,A,ie,n,ne]),(0,N.useEffect)(()=>{if(!p||O<0||O>=D.length)return;let e=te.current;if(!e)return;let t=e.closest(`.overflow-y-auto`);if(!t)return;let n=e.getBoundingClientRect(),r=t.getBoundingClientRect();n.top<r.top?t.scrollBy({top:n.top-r.top-8,behavior:`smooth`}):n.bottom>r.bottom&&t.scrollBy({top:n.bottom-r.bottom+8,behavior:`smooth`})},[O,D.length,p]),!e)return(0,P.jsx)(`div`,{className:`min-h-screen bg-[var(--vz-bg)] flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center text-[var(--text-tertiary)]`,children:[(0,P.jsx)(`div`,{className:`text-lg mb-2`,children:`Comparison not found`}),(0,P.jsx)(`button`,{type:`button`,onClick:n,className:`text-[var(--accent-info)] hover:text-[var(--text-primary)]`,children:`Return to list`})]})});let ue=ko(e.status),de=e.userAction===`accepted`?`approved`:e.userAction===`rejected`?`rejected`:e.status===`passed`?`approved`:`pending`,M={new:{variant:`info`,label:`New`},changed:{variant:`warning`,label:`Changed`},unchanged:{variant:`default`,label:`Unchanged`}}[ue],fe=e.status===`new`||e.status===`baseline-created`,pe=e.properties||{};return(0,P.jsxs)(`div`,{className:`h-screen bg-[var(--vz-bg)] flex flex-col overflow-hidden`,"data-testid":`fullscreen-viewer`,children:[(0,P.jsxs)(`header`,{className:`flex-shrink-0 bg-[var(--vz-bg)] border-b border-[var(--vz-border-subtle)] z-30`,children:[(0,P.jsxs)(`div`,{className:`px-2 sm:px-4 py-2 flex items-center gap-1 sm:gap-3`,children:[(0,P.jsx)(qa,{onClick:n}),(0,P.jsxs)(`div`,{className:`flex-1 min-w-0 flex items-center gap-2`,children:[(0,P.jsx)(`h1`,{className:`text-sm font-medium text-[var(--text-primary)] truncate`,children:e.name||e.originalName||`Unknown`}),M&&(0,P.jsx)(nr,{variant:M.variant,size:`sm`,className:`hidden xs:flex`,children:M.label}),w&&(0,P.jsx)(nr,{variant:`warning`,size:`sm`,dot:!0,pulseDot:!0,className:`hidden sm:flex`,children:`Review Mode`})]}),(0,P.jsxs)(`div`,{className:`flex items-center`,children:[(0,P.jsx)(`button`,{type:`button`,onClick:A,disabled:!k.prev,className:`p-2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] disabled:opacity-85 disabled:cursor-not-allowed rounded-md hover:bg-[var(--vz-raised)] transition-colors`,"aria-label":`Previous`,children:(0,P.jsx)(Kt,{className:`w-5 h-5`})}),(0,P.jsxs)(`span`,{className:`hidden sm:block text-xs text-[var(--text-muted)] font-medium tabular-nums px-1 min-w-[3rem] text-center`,children:[O+1,`/`,D.length]}),(0,P.jsx)(`button`,{type:`button`,onClick:ie,disabled:!k.next,className:`p-2 text-[var(--text-tertiary)] hover:text-[var(--text-primary)] disabled:opacity-85 disabled:cursor-not-allowed rounded-md hover:bg-[var(--vz-raised)] transition-colors`,"aria-label":`Next`,children:(0,P.jsx)(Jt,{className:`w-5 h-5`})})]}),(0,P.jsx)(`div`,{className:`hidden sm:block h-5 w-px bg-[var(--vz-border-subtle)]`}),(0,P.jsxs)(`div`,{className:`hidden sm:flex items-center gap-2`,children:[fe&&a?(0,P.jsx)(`button`,{type:`button`,onClick:ce,className:`px-3 py-1.5 text-xs font-medium text-[var(--accent-danger)] hover:text-[var(--text-primary)] hover:bg-[var(--accent-danger-muted)] rounded-md transition-colors`,"data-testid":`btn-delete`,children:`Delete`}):null,(0,P.jsx)(ba,{status:de,onApprove:r?oe:null,onReject:i?se:null,compact:!0})]}),(0,P.jsxs)(`div`,{className:`hidden sm:flex items-center`,children:[(0,P.jsx)(`button`,{type:`button`,onClick:()=>m(!p),className:`flex items-center justify-center p-2 rounded-md transition-colors ${p?`bg-[var(--accent-brand-muted)] text-[var(--accent-brand)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)]`}`,title:`Queue`,"aria-label":`Toggle queue`,"data-testid":`toggle-queue-btn`,children:(0,P.jsx)(Tn,{className:`w-5 h-5 pointer-events-none`})}),(e?.confirmedRegions?.length>0||e?.hasConfirmedRegions)&&(0,P.jsx)(`button`,{type:`button`,onClick:()=>S(!x),className:`p-2 rounded-md transition-colors ${x?`bg-[var(--accent-success-muted)] text-[var(--accent-success)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)]`}`,title:`Show Regions (G)`,"aria-label":`Toggle regions`,"data-testid":`toggle-regions-btn`,children:(0,P.jsx)(jn,{className:`w-5 h-5 pointer-events-none`})}),(0,P.jsx)(`button`,{type:`button`,onClick:ne,className:`p-2 rounded-md transition-colors ${h?`bg-[var(--accent-info-muted)] text-[var(--accent-info)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)]`}`,title:`Details (I)`,"aria-label":`Screenshot details`,"data-testid":`toggle-inspector-btn`,children:(0,P.jsx)(Cn,{className:`w-5 h-5 pointer-events-none`})})]})]}),(0,P.jsxs)(`div`,{className:`px-2 sm:px-4 py-1.5 sm:py-2 border-t border-[var(--vz-border-subtle)] flex items-center gap-2 sm:gap-3`,children:[re&&re.comparisons.length>0&&(0,P.jsx)(go,{variants:re.comparisons,currentVariantId:G(e),onVariantSelect:E}),(0,P.jsx)(`div`,{className:`flex-1`}),e.diff&&(0,P.jsx)(vo,{value:s,onChange:c,compact:!0,className:`hidden sm:flex`}),(0,P.jsx)(xo,{zoom:ee,onZoomChange:C})]})]}),(0,P.jsxs)(`div`,{className:`flex-1 min-h-0 flex overflow-hidden`,children:[(0,P.jsxs)(Va,{isOpen:p,onClose:()=>m(!1),position:`bottom`,desktopPosition:`left`,width:`w-80`,mobileHeight:`30vh`,expandedHeight:`80vh`,alwaysVisibleOnDesktop:!1,showBackdrop:!1,testId:`queue-drawer`,children:[(0,P.jsx)(Ha,{title:`Queue`,subtitle:`${D.length} of ${T.length} items`,onClose:()=>m(!1)}),(0,P.jsx)(Ga,{children:[{id:`needs-review`,label:`To Review`},{id:`changes`,label:`Changes`},{id:`new`,label:`New`},{id:`all`,label:`All`}].map(e=>(0,P.jsx)(Ka,{isActive:_===e.id,onClick:()=>v(e.id),children:e.label},e.id))}),(0,P.jsxs)(Ua,{className:`px-2 py-2 space-y-1`,children:[D.map(t=>{let n=G(t)===G(e);return(0,P.jsx)(`div`,{ref:n?te:null,children:(0,P.jsx)($a,{item:t,isActive:n,thumbnailUrl:Eo(t.current||t.baseline,t.timestamp),onClick:()=>o(t)})},G(t))}),D.length===0&&(0,P.jsx)(`div`,{className:`text-center py-8 text-[var(--text-muted)] text-xs`,children:_===`needs-review`?`All screenshots reviewed!`:`No items match this filter`})]}),(0,P.jsxs)(Wa,{className:`hidden sm:flex flex-col items-center gap-1.5`,children:[(0,P.jsxs)(`p`,{className:`text-xs text-[var(--text-muted)] text-center`,children:[(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)]`,children:`↑↓`}),` navigate`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)]`,children:`Space`}),` `,w?`exit`:`review mode`]}),w&&(0,P.jsxs)(`p`,{className:`text-xs text-[var(--text-muted)] text-center`,children:[(0,P.jsx)(`span`,{className:`text-[var(--accent-success)] font-medium`,children:`A`}),` `,`approve`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--accent-danger)] font-medium`,children:`R`}),` `,`reject`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)] font-medium`,children:`D`}),` `,`diff`,` · `,(0,P.jsx)(`span`,{className:`text-[var(--text-tertiary)] font-medium`,children:`T`}),` `,`view`]})]})]}),(0,P.jsx)(`main`,{className:`flex-1 min-h-0 overflow-hidden relative`,children:(0,P.jsx)(Oo,{comparison:e,viewMode:s===So.ONION?`onion-skin`:s,showDiffOverlay:l,onDiffToggle:()=>u(e=>!e),showBaseline:y,onToggleBaseline:()=>b(e=>!e),onionSkinPosition:d,onOnionSkinChange:f,zoom:ee,disableLoadingOverlay:!0,showRegions:x,className:`w-full h-full`},G(e))}),(0,P.jsxs)(Za,{isOpen:h,onClose:()=>g(!1),title:`Screenshot Details`,subtitle:e.name||e.originalName,className:`order-last`,children:[(0,P.jsx)(Za.Section,{title:`Status`,icon:sn,children:(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[M&&(0,P.jsx)(nr,{variant:M.variant,size:`sm`,children:M.label}),(0,P.jsx)(nr,{variant:de===`approved`?`success`:de===`rejected`?`danger`:`default`,size:`sm`,children:de})]})}),e.warnings?.length>0&&(0,P.jsx)(Za.Section,{title:`Warnings`,icon:hn,children:e.warnings.map(e=>(0,P.jsx)(Za.Row,{label:e.option||`Warning`,value:e.message},`${e.code}-${e.option}`))}),(0,P.jsxs)(Za.Section,{title:`Screenshot`,icon:an,children:[pe.browser&&(0,P.jsx)(Za.Row,{label:`Browser`,value:pe.browser,icon:()=>(0,P.jsx)(ir,{browser:pe.browser,className:`w-3.5 h-3.5`})}),(0,P.jsx)(Za.Row,{label:`Viewport`,value:pe.viewport_width&&pe.viewport_height?`${pe.viewport_width} × ${pe.viewport_height}`:null,mono:!0}),(0,P.jsx)(Za.Row,{label:`Device`,value:pe.device})]}),ue===`changed`&&e.diffPercentage!=null&&(0,P.jsxs)(Za.Section,{title:`Diff Analysis`,icon:pn,children:[(0,P.jsx)(Za.Row,{label:`Changed`,value:`${Number(e.diffPercentage).toFixed(3)}%`,mono:!0}),e.diffPixels!=null&&(0,P.jsx)(Za.Row,{label:`Pixels`,value:e.diffPixels.toLocaleString(),mono:!0}),e.threshold!=null&&(0,P.jsx)(Za.Row,{label:`Threshold`,value:Number(e.threshold).toFixed(2),mono:!0})]}),(0,P.jsx)(Za.Section,{title:`Identifier`,icon:sn,children:(0,P.jsx)(Za.Row,{label:`Signature`,value:e.signature||e.id,mono:!0,copyable:!0})})]})]}),(0,P.jsxs)(`div`,{className:`sm:hidden flex-shrink-0 bg-[var(--vz-bg)] border-t border-[var(--vz-border-subtle)] safe-area-pb`,children:[(0,P.jsx)(xa,{status:de,onApprove:r?oe:null,onReject:i?se:null}),(0,P.jsxs)(`div`,{className:`px-3 py-2 flex items-center gap-1 border-t border-[var(--vz-border-subtle)]`,children:[(0,P.jsx)(`button`,{type:`button`,onClick:()=>m(!p),className:`flex items-center justify-center p-2.5 rounded-lg transition-colors ${p?`bg-[var(--accent-brand-muted)] text-[var(--accent-brand)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)] active:bg-[var(--vz-elevated)]`}`,"aria-label":`Queue`,"data-testid":`mobile-toggle-queue-btn`,children:(0,P.jsx)(Tn,{className:`w-5 h-5 pointer-events-none`})}),(0,P.jsx)(`button`,{type:`button`,onClick:ne,className:`flex items-center justify-center p-2.5 rounded-lg transition-colors ${h?`bg-[var(--accent-info-muted)] text-[var(--accent-info)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)] active:bg-[var(--vz-elevated)]`}`,"aria-label":`Screenshot details`,"data-testid":`mobile-toggle-inspector-btn`,children:(0,P.jsx)(Cn,{className:`w-5 h-5 pointer-events-none`})}),(e?.confirmedRegions?.length>0||e?.hasConfirmedRegions)&&(0,P.jsx)(`button`,{type:`button`,onClick:()=>S(!x),className:`flex items-center justify-center p-2.5 rounded-lg transition-colors ${x?`bg-[var(--accent-success-muted)] text-[var(--accent-success)]`:`text-[var(--text-tertiary)] hover:text-[var(--text-primary)] hover:bg-[var(--vz-raised)] active:bg-[var(--vz-elevated)]`}`,"aria-label":`Toggle regions`,"data-testid":`mobile-toggle-regions-btn`,children:(0,P.jsx)(jn,{className:`w-5 h-5 pointer-events-none`})}),fe&&a&&(0,P.jsx)(`button`,{type:`button`,onClick:ce,className:`flex items-center justify-center p-2.5 rounded-lg text-[var(--accent-danger)] hover:text-[var(--text-primary)] hover:bg-[var(--accent-danger-muted)] active:bg-[var(--accent-danger-muted)] transition-colors`,"aria-label":`Delete`,"data-testid":`mobile-delete-btn`,children:(0,P.jsx)(`span`,{className:`text-xs font-medium`,children:`Delete`})}),(0,P.jsx)(`div`,{className:`flex-1`}),(0,P.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,P.jsxs)(`span`,{className:`text-xs text-[var(--text-muted)] tabular-nums`,children:[O+1,` / `,D.length]}),M&&(0,P.jsx)(nr,{variant:M.variant,size:`sm`,children:M.label})]})]})]})]})}function Mo(){let[,e]=gi(),[,t]=vi(`/comparison/:id`),{data:n}=Ui(),r=Wi(),i=qi(),a=Xi(),o=(0,N.useMemo)(()=>n?.comparisons||[],[n?.comparisons]),s=(0,N.useMemo)(()=>{let e=t?.id?decodeURIComponent(t.id):null;return!e||o.length===0?null:o.find(t=>t.id===e||t.signature===e||t.name===e)},[t,o]),{data:c}=Hi(s?.id),l=(0,N.useMemo)(()=>s?c?{...s,diffClusters:c.diffClusters,confirmedRegions:c.confirmedRegions,intensityStats:c.intensityStats,boundingBox:c.boundingBox,regionAnalysis:c.regionAnalysis,hotspotAnalysis:c.hotspotAnalysis}:s:null,[s,c]),u=(0,N.useCallback)(t=>{let n=t.id||t.signature||t.name;n&&e(`/comparison/${encodeURIComponent(n)}`)},[e]),d=(0,N.useCallback)(()=>{e(`/`)},[e]),f=(0,N.useCallback)(e=>{r.mutate(e)},[r]),p=(0,N.useCallback)(e=>{i.mutate(e)},[i]),m=(0,N.useCallback)(t=>{a.mutate(t,{onSuccess:()=>{e(`/`)}})},[a,e]);return l?(0,P.jsx)(Ao,{comparison:l,comparisons:o,onClose:d,onAccept:f,onReject:p,onDelete:m,onNavigate:u}):(0,P.jsx)(`div`,{className:`fixed inset-0 bg-[var(--vz-bg)] z-50 flex items-center justify-center`,children:(0,P.jsxs)(`div`,{className:`text-center`,children:[(0,P.jsx)(`div`,{className:`text-lg text-[var(--text-primary)] font-medium mb-2`,children:`Comparison not found`}),(0,P.jsxs)(`p`,{className:`text-sm text-[var(--text-tertiary)] mb-4`,children:[`ID: `,t?.id||`none`]}),(0,P.jsx)(`button`,{type:`button`,onClick:d,className:`text-[var(--accent-media)] hover:text-[var(--accent-media)] font-medium transition-colors`,children:`Return to list`})]})})}To.NEW,To.BASELINE_CREATED,To.PASSED,To.FAILED,To.ERROR,To.REJECTED;function No(e,t){return[...e].sort((e,n)=>{if(t===`priority`){let t={[To.FAILED]:3,[To.NEW]:2,[To.PASSED]:1},r=e.initialStatus||e.status,i=n.initialStatus||n.status,a=t[r]||0,o=t[i]||0;return a===o?(n.diffPercentage||0)-(e.diffPercentage||0):o-a}return t===`name`?(e.name||``).localeCompare(n.name||``):t===`time`?(n.timestamp||0)-(e.timestamp||0):0})}function Po(e,t){return t===`failed`?e.filter(e=>e.status===To.FAILED):t===`passed`?e.filter(e=>e.status===To.PASSED):t===`new`?e.filter(e=>e.status===To.NEW||e.status===To.BASELINE_CREATED):e}function Fo(e){return e===`new`||e===`baseline-created`}function Io(e){return e===`failed`||Fo(e)}var Lo=()=>{let e=new URLSearchParams(window.location.search);return{filter:e.get(`filter`)||Co.ALL,sortBy:e.get(`sort`)||wo.PRIORITY,searchQuery:e.get(`search`)||``,selectedBrowser:e.get(`browser`)||`all`,selectedViewport:e.get(`viewport`)||`all`}};function Ro(e=[]){let t=Lo(),[n,r]=(0,N.useState)(t.filter),[i,a]=(0,N.useState)(t.sortBy),[o,s]=(0,N.useState)(t.searchQuery),[c,l]=(0,N.useState)(t.selectedBrowser),[u,d]=(0,N.useState)(t.selectedViewport);(0,N.useEffect)(()=>{let e=new URLSearchParams;n!==Co.ALL&&e.set(`filter`,n),i!==wo.PRIORITY&&e.set(`sort`,i),o&&e.set(`search`,o),c!==`all`&&e.set(`browser`,c),u!==`all`&&e.set(`viewport`,u);let t=e.toString()?`?${e.toString()}`:window.location.pathname;window.history.replaceState({},``,t)},[n,i,o,c,u]);let f=(0,N.useMemo)(()=>{let t=new Set,n=new Set;return e.forEach(e=>{if(e.properties?.browser&&t.add(e.properties.browser),e.properties?.viewport_width&&e.properties?.viewport_height){let t=`${e.properties.viewport_width}x${e.properties.viewport_height}`;n.add(t)}else if(e.properties?.viewport){let t=`${e.properties.viewport.width}x${e.properties.viewport.height}`;n.add(t)}}),{browsers:Array.from(t).sort(),viewports:Array.from(n).sort()}},[e]);return{filteredComparisons:(0,N.useMemo)(()=>{let t=Po(e,n);if(o.trim()){let e=o.toLowerCase();t=t.filter(t=>t.name.toLowerCase().includes(e)||t.originalName?.toLowerCase().includes(e))}return c!==`all`&&(t=t.filter(e=>e.properties?.browser===c)),u!==`all`&&(t=t.filter(e=>{let t=null;return e.properties?.viewport_width&&e.properties?.viewport_height?t=`${e.properties.viewport_width}x${e.properties.viewport_height}`:e.properties?.viewport&&(t=`${e.properties.viewport.width}x${e.properties.viewport.height}`),t===u})),No(t,i)},[e,n,i,o,c,u]),filter:n,setFilter:r,sortBy:i,setSortBy:a,searchQuery:o,setSearchQuery:s,selectedBrowser:c,setSelectedBrowser:l,selectedViewport:u,setSelectedViewport:d,availableFilters:f,counts:{all:e.length,failed:e.filter(e=>e.status===`failed`).length,passed:e.filter(e=>e.status===`passed`).length,new:e.filter(e=>Fo(e.status)).length,rejected:e.filter(e=>e.status===`rejected`).length}}}function zo({src:e,alt:t,className:n,style:r,onClick:i}){let[a,o]=(0,N.useState)(e?`loading`:`missing`);if((0,N.useEffect)(()=>{if(!e){o(`missing`);return}o(`loading`)},[e]),a===`missing`)return(0,P.jsx)(`div`,{className:`flex items-center justify-center bg-[var(--vz-raised)] border border-[var(--vz-border)] rounded min-h-[200px]`,style:r,children:(0,P.jsxs)(`div`,{className:`text-[var(--text-tertiary)] text-center`,children:[(0,P.jsx)(Fn,{className:`w-12 h-12 mx-auto mb-3`}),(0,P.jsxs)(`div`,{className:`text-sm`,children:[`No `,t.toLowerCase(),` available`]})]})});if(a===`error`)return(0,P.jsx)(`div`,{className:`flex items-center justify-center bg-[var(--accent-danger-muted)] border border-[color-mix(in_srgb,var(--accent-danger)_28%,transparent)] rounded min-h-[200px]`,style:r,children:(0,P.jsxs)(`div`,{className:`text-[var(--accent-danger)] text-center`,children:[(0,P.jsx)(hn,{className:`w-12 h-12 mx-auto mb-3`}),(0,P.jsxs)(`div`,{className:`text-sm`,children:[`Failed to load `,t.toLowerCase()]}),(0,P.jsx)(`div`,{className:`text-xs text-[var(--text-muted)] mt-1 font-mono break-all px-4`,children:e})]})});let s=null;return i&&(s=e=>{(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),i(e))}),(0,P.jsx)(`img`,{src:e,alt:t,className:n,style:r,loading:`lazy`,decoding:`async`,onLoad:()=>o(`loaded`),onError:()=>o(`error`),onClick:i,onKeyDown:s,role:i?`button`:void 0,tabIndex:i?0:void 0})}function Bo(e){return e.id||e.signature||e.name}function Vo(e){let t=new Map;for(let n of e){let e=n.name||n.originalName||`Unknown`;t.has(e)||t.set(e,{name:e,comparisons:[],browsers:new Set,viewports:[],hasChanges:!1,hasNew:!1,allPassed:!0,maxDiff:0,failedCount:0,newCount:0,passedCount:0,acceptedCount:0,rejectedCount:0});let r=t.get(e);r.comparisons.push(n),n.properties?.browser&&r.browsers.add(n.properties.browser),n.properties?.viewport_width&&n.properties?.viewport_height&&r.viewports.push({width:n.properties.viewport_width,height:n.properties.viewport_height,id:n.id,status:n.status}),n.status===`failed`?(r.hasChanges=!0,r.allPassed=!1,r.failedCount++,r.maxDiff=Math.max(r.maxDiff,n.diffPercentage||0)):n.status===`new`||n.status===`baseline-created`?(r.hasNew=!0,r.allPassed=!1,r.newCount++):n.status===`passed`&&r.passedCount++,n.userAction===`accepted`?r.acceptedCount++:n.userAction===`rejected`&&r.rejectedCount++}return Array.from(t.values()).map(e=>({...e,browsers:Array.from(e.browsers),viewports:e.viewports.sort((e,t)=>t.width*t.height-e.width*e.height),primary:e.comparisons[0]})).sort((e,t)=>e.hasChanges&&!t.hasChanges?-1:!e.hasChanges&&t.hasChanges?1:e.hasNew&&!t.hasNew?-1:!e.hasNew&&t.hasNew?1:e.maxDiff===t.maxDiff?e.name.localeCompare(t.name):t.maxDiff-e.maxDiff)}function Ho({width:e,className:t=`w-3.5 h-3.5`}){return e<=480?(0,P.jsx)(ln,{className:t}):e<=1024?(0,P.jsx)(dn,{className:t}):(0,P.jsx)(an,{className:t})}function Uo({group:e}){return e.acceptedCount===e.comparisons.length?(0,P.jsxs)(nr,{variant:`success`,size:`sm`,children:[(0,P.jsx)(Bt,{className:`w-3 h-3 mr-1`}),`Accepted`]}):e.rejectedCount>0?(0,P.jsxs)(nr,{variant:`warning`,size:`sm`,children:[(0,P.jsx)(Yn,{className:`w-3 h-3 mr-1`}),`Rejected`]}):e.hasChanges?(0,P.jsxs)(nr,{variant:`danger`,size:`sm`,children:[e.failedCount,` changed`]}):e.hasNew?(0,P.jsxs)(nr,{variant:`info`,size:`sm`,children:[e.newCount,` new`]}):(0,P.jsx)(nr,{variant:`success`,size:`sm`,children:`Passed`})}function Wo({group:e,maxVisible:t=4}){let n=e.comparisons.length;if(n<=1){let t=e.viewports[0],n=e.browsers[0];return(0,P.jsxs)(`div`,{className:`flex items-center gap-2 text-xs text-[var(--text-tertiary)]`,children:[t&&(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1`,children:[(0,P.jsx)(Ho,{width:t.width,className:`w-3 h-3`}),t.width,`×`,t.height]}),n&&(0,P.jsx)(`span`,{children:n})]})}let r=e.viewports.slice(0,t),i=n-t;return(0,P.jsxs)(`div`,{className:`flex items-center gap-1.5 flex-wrap`,children:[r.map((e,t)=>(0,P.jsxs)(`span`,{className:`
198
198
  inline-flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-medium
199
199
  ${e.status===`failed`?`bg-[var(--accent-danger-muted)] text-[var(--accent-danger)] border border-[color-mix(in_srgb,var(--accent-danger)_32%,transparent)]`:e.status===`new`||e.status===`baseline-created`?`bg-[var(--accent-media-muted)] text-[var(--accent-media)] border border-[color-mix(in_srgb,var(--accent-media)_30%,transparent)]`:`bg-[var(--vz-raised)] text-[var(--text-tertiary)] border border-[var(--vz-border)]`}
200
200
  `,children:[(0,P.jsx)(Ho,{width:e.width,className:`w-2.5 h-2.5`}),e.width]},t)),i>0&&(0,P.jsxs)(`span`,{className:`text-[10px] text-[var(--text-muted)]`,children:[`+`,i,` more`]}),e.browsers.length>0&&(0,P.jsxs)(`span`,{className:`text-[10px] text-[var(--text-muted)] ml-1`,children:[`· `,e.browsers.join(`, `)]})]})}function Go({group:e,onSelect:t,onAcceptGroup:n,onRejectGroup:r,isAccepting:i}){let{primary:a,hasChanges:o,hasNew:s,maxDiff:c}=e,l=o||s,u=Eo(a.current||a.baseline,a.timestamp);return(0,P.jsxs)(`div`,{"data-testid":`screenshot-group-${(a.id||a.signature||e.name).replace(/[^a-zA-Z0-9-]/g,`-`)}`,className:`
package/dist/sdk/index.js CHANGED
@@ -19,7 +19,7 @@ import { createUploader } from '../uploader/index.js';
19
19
  import { loadConfig } from '../utils/config-loader.js';
20
20
  import { resolveImageBuffer } from '../utils/file-helpers.js';
21
21
  import * as output from '../utils/output.js';
22
- import { createScreenshotProperties } from '../utils/screenshot-options.js';
22
+ import { normalizeScreenshotOptions } from '../utils/screenshot-options.js';
23
23
 
24
24
  /**
25
25
  * Create a new Vizzly instance with custom configuration
@@ -293,9 +293,16 @@ export class VizzlySDK extends EventEmitter {
293
293
 
294
294
  // Resolve Buffer or file path using shared utility
295
295
  let buffer = resolveImageBuffer(imageBuffer, 'screenshot');
296
+ let normalizedOptions = normalizeScreenshotOptions(options);
297
+ for (let warning of normalizedOptions.warnings) {
298
+ output.warn(warning.message, {
299
+ code: warning.code,
300
+ option: warning.option
301
+ });
302
+ }
296
303
 
297
304
  // Generate or use provided build ID
298
- let buildId = options.buildId || this.currentBuildId || 'default';
305
+ let buildId = normalizedOptions.buildId || this.currentBuildId || 'default';
299
306
  this.currentBuildId = buildId;
300
307
 
301
308
  // Convert Buffer to base64 for JSON transport
@@ -305,8 +312,11 @@ export class VizzlySDK extends EventEmitter {
305
312
  name,
306
313
  image: imageBase64,
307
314
  type: 'base64',
308
- properties: createScreenshotProperties(options)
315
+ properties: normalizedOptions.properties
309
316
  };
317
+ if (normalizedOptions.warnings.length > 0) {
318
+ screenshotData.warnings = normalizedOptions.warnings;
319
+ }
310
320
 
311
321
  // POST to the local screenshot server
312
322
  let serverUrl = `http://localhost:${this.config.server?.port || 3000}`;
@@ -362,12 +372,16 @@ export class VizzlySDK extends EventEmitter {
362
372
  let screenshotsDir = options.screenshotsDir || this.config?.upload?.screenshotsDir || './screenshots';
363
373
  let uploadOptions = {
364
374
  screenshotsDir,
365
- buildName: options.buildName || this.config.buildName,
366
- branch: options.branch || this.config.branch,
367
- commit: options.commit || this.config.commit,
368
- message: options.message || this.config.message,
369
- environment: options.environment || this.config.environment || 'production',
370
- threshold: options.threshold ?? this.config.threshold,
375
+ buildName: options.buildName || this.config.build?.name,
376
+ branch: options.branch || this.config.build?.branch,
377
+ commit: options.commit || this.config.build?.commit,
378
+ message: options.message || this.config.build?.message,
379
+ environment: options.environment || this.config.build?.environment || 'production',
380
+ threshold: options.threshold ?? this.config.comparison?.threshold,
381
+ minClusterSize: options.minClusterSize ?? this.config.comparison?.minClusterSize,
382
+ metadata: options.metadata || {},
383
+ pullRequestNumber: options.pullRequestNumber,
384
+ parallelId: options.parallelId || this.config.parallelId,
371
385
  onProgress: progress => {
372
386
  this.emit('upload:progress', progress);
373
387
  if (options.onProgress) {
@@ -47,7 +47,7 @@ export const createApiHandler = (client, {
47
47
  let vizzlyDisabled = false;
48
48
  let screenshotCount = 0;
49
49
  let uploadPromises = [];
50
- const handleScreenshot = async (buildId, name, image, properties = {}, type) => {
50
+ const handleScreenshot = async (buildId, name, image, properties = {}, type, warnings = []) => {
51
51
  let handlerStart = Date.now();
52
52
  output.debug('upload', `${name} received`, {
53
53
  buildId: buildId?.slice(0, 8)
@@ -177,7 +177,8 @@ export const createApiHandler = (client, {
177
177
  body: {
178
178
  success: true,
179
179
  name,
180
- count: screenshotCount
180
+ count: screenshotCount,
181
+ warnings
181
182
  }
182
183
  };
183
184
  };
@@ -305,7 +305,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
305
305
  output.debug('tdd', `baseline: ${baseline.buildName}`);
306
306
  }
307
307
  };
308
- const handleScreenshot = async (_buildId, name, image, properties = {}, type) => {
308
+ const handleScreenshot = async (_buildId, name, image, properties = {}, type, warnings = []) => {
309
309
  let handlerStart = Date.now();
310
310
  output.debug('tdd', `${name} received`);
311
311
 
@@ -447,6 +447,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
447
447
  aaPercentage: comparison.aaPercentage,
448
448
  heightDiff: comparison.heightDiff,
449
449
  error: comparison.error,
450
+ warnings,
450
451
  originalName: name,
451
452
  timestamp: Date.now(),
452
453
  // Boolean hints so UI can show toggle buttons without fetching heavy data
@@ -490,6 +491,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
490
491
  diff: comparison.diff,
491
492
  diffPercentage: comparison.diffPercentage,
492
493
  threshold: comparison.threshold,
494
+ warnings,
493
495
  tddMode: true
494
496
  }
495
497
  };
@@ -503,6 +505,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
503
505
  message: `Baseline updated for '${name}'`,
504
506
  baseline: comparison.baseline,
505
507
  current: comparison.current,
508
+ warnings,
506
509
  tddMode: true
507
510
  }
508
511
  };
@@ -530,6 +533,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
530
533
  name: comparison.name,
531
534
  baseline: comparison.baseline,
532
535
  current: comparison.current,
536
+ warnings,
533
537
  tddMode: true
534
538
  }
535
539
  };
@@ -32,7 +32,8 @@ export function createScreenshotRouter({
32
32
  name,
33
33
  properties,
34
34
  image,
35
- type
35
+ type,
36
+ warnings
36
37
  } = body;
37
38
  if (!name || !image) {
38
39
  sendError(res, 400, 'name and image are required');
@@ -41,7 +42,7 @@ export function createScreenshotRouter({
41
42
 
42
43
  // Use buildId from request body, or fall back to server's buildId
43
44
  const effectiveBuildId = buildId || defaultBuildId;
44
- const result = await screenshotHandler.handleScreenshot(effectiveBuildId, name, image, properties, type);
45
+ const result = await screenshotHandler.handleScreenshot(effectiveBuildId, name, image, properties, type, warnings);
45
46
  sendJson(res, result.statusCode, result.body);
46
47
  return true;
47
48
  } catch (error) {
@@ -57,9 +58,15 @@ export function createScreenshotRouter({
57
58
  // Flush endpoint - signals test completion and prints summary
58
59
  if (pathname === '/flush') {
59
60
  try {
60
- if (screenshotHandler.getResults) {
61
+ if (screenshotHandler.flush) {
62
+ let stats = await screenshotHandler.flush();
63
+ sendJson(res, 200, {
64
+ success: true,
65
+ ...stats
66
+ });
67
+ } else if (screenshotHandler.getResults) {
61
68
  // This triggers printResults() which outputs the summary
62
- const results = await screenshotHandler.getResults();
69
+ let results = await screenshotHandler.getResults();
63
70
  sendJson(res, 200, {
64
71
  success: true,
65
72
  summary: {
@@ -20,9 +20,12 @@ import { randomUUID } from 'node:crypto';
20
20
  export function createBuildObject(buildOptions, deps = {}) {
21
21
  let {
22
22
  name,
23
+ buildName,
23
24
  branch,
24
25
  commit,
26
+ message,
25
27
  environment = 'test',
28
+ parallelId,
26
29
  metadata = {}
27
30
  } = buildOptions;
28
31
  let randomId = deps.randomId || randomUUID;
@@ -30,10 +33,12 @@ export function createBuildObject(buildOptions, deps = {}) {
30
33
  let timestamp = deps.timestamp || Date.now;
31
34
  return {
32
35
  id: `build-${randomId()}`,
33
- name: name || `build-${timestamp()}`,
36
+ name: name || buildName || `build-${timestamp()}`,
34
37
  branch,
35
38
  commit,
39
+ message,
36
40
  environment,
41
+ parallelId,
37
42
  metadata,
38
43
  status: 'pending',
39
44
  createdAt: now(),
@@ -14,6 +14,7 @@
14
14
  * @param {number} options.port - Server port
15
15
  * @param {string} options.buildId - Build ID
16
16
  * @param {boolean} [options.setBaseline] - Whether to set baseline
17
+ * @param {boolean} [options.failOnDiff] - Whether visual diffs should fail tests
17
18
  * @param {Object} [options.baseEnv] - Base environment (defaults to process.env)
18
19
  * @returns {Object} Environment variables object
19
20
  */
@@ -21,6 +22,7 @@ export function buildTestEnv({
21
22
  port,
22
23
  buildId,
23
24
  setBaseline = false,
25
+ failOnDiff = false,
24
26
  baseEnv = process.env
25
27
  }) {
26
28
  return {
@@ -28,7 +30,8 @@ export function buildTestEnv({
28
30
  VIZZLY_SERVER_URL: `http://localhost:${port}`,
29
31
  VIZZLY_BUILD_ID: buildId,
30
32
  VIZZLY_ENABLED: 'true',
31
- VIZZLY_SET_BASELINE: setBaseline ? 'true' : 'false'
33
+ VIZZLY_SET_BASELINE: setBaseline ? 'true' : 'false',
34
+ VIZZLY_FAIL_ON_DIFF: failOnDiff ? 'true' : 'false'
32
35
  };
33
36
  }
34
37
 
@@ -334,7 +334,8 @@ export async function runTests({
334
334
  let env = buildTestEnv({
335
335
  port: config.server?.port,
336
336
  buildId,
337
- setBaseline
337
+ setBaseline,
338
+ failOnDiff: runOptions.failOnDiff || false
338
339
  });
339
340
  try {
340
341
  await executeTestCommand({
@@ -24,6 +24,7 @@ export function autoDiscoverTddServer(
24
24
  deps?: {
25
25
  exists?: (path: string) => boolean;
26
26
  readFile?: (path: string, encoding: BufferEncoding) => string | Buffer;
27
+ env?: Record<string, string | undefined>;
27
28
  }
28
29
  ): string | null;
29
30
 
@@ -32,7 +33,14 @@ export function autoDiscoverTddServer(
32
33
  */
33
34
  export interface ScreenshotResult {
34
35
  success: boolean;
35
- status?: 'passed' | 'failed' | 'new';
36
+ status?:
37
+ | 'passed'
38
+ | 'failed'
39
+ | 'new'
40
+ | 'match'
41
+ | 'diff'
42
+ | 'baseline-updated'
43
+ | 'error';
36
44
  name?: string;
37
45
  diffPercentage?: number;
38
46
  [key: string]: unknown;
@@ -59,10 +67,16 @@ export interface ScreenshotResult {
59
67
  * @example
60
68
  * // With properties and comparison settings
61
69
  * await vizzlyScreenshot('checkout-form', screenshot, {
62
- * properties: { browser: 'chrome', viewport: '1920x1080' },
70
+ * properties: { browser: 'chrome', viewport: { width: 1920, height: 1080 } },
63
71
  * threshold: 5,
64
- * minClusterSize: 10
72
+ * minClusterSize: 10,
73
+ * fullPage: true,
74
+ * requestTimeout: 5000
65
75
  * });
76
+ *
77
+ * `properties` is the user metadata bag. Comparison options are normalized
78
+ * into the server metadata payload, while `requestTimeout` stays on the
79
+ * client request and `buildId` only routes the screenshot to a build.
66
80
  */
67
81
  export function vizzlyScreenshot(
68
82
  name: string,
@@ -72,7 +86,10 @@ export function vizzlyScreenshot(
72
86
  threshold?: number;
73
87
  minClusterSize?: number;
74
88
  fullPage?: boolean;
75
- [key: string]: unknown;
89
+ /** Transport-only build ID used to route the request. */
90
+ buildId?: string;
91
+ /** Client-side HTTP timeout in milliseconds; not stored as metadata. */
92
+ requestTimeout?: number;
76
93
  }
77
94
  ): Promise<ScreenshotResult | null>;
78
95
 
@@ -81,7 +98,15 @@ export function vizzlyScreenshot(
81
98
  */
82
99
  export interface FlushResult {
83
100
  success: boolean;
84
- summary: {
101
+ uploaded?: number;
102
+ flushed?: boolean;
103
+ total?: number;
104
+ passed?: number;
105
+ failed?: number;
106
+ new?: number;
107
+ errors?: number;
108
+ message?: string;
109
+ summary?: {
85
110
  total: number;
86
111
  passed: number;
87
112
  failed: number;
@@ -124,6 +149,7 @@ export function isVizzlyReady(): boolean;
124
149
  export function configure(config?: {
125
150
  serverUrl?: string;
126
151
  enabled?: boolean;
152
+ failOnDiff?: boolean;
127
153
  }): void;
128
154
 
129
155
  /**
@@ -145,4 +171,5 @@ export function getVizzlyInfo(): {
145
171
  buildId: string | null;
146
172
  tddMode: boolean;
147
173
  disabled: boolean;
174
+ failOnDiff: boolean;
148
175
  };
@@ -24,7 +24,9 @@ export interface BuildConfig {
24
24
 
25
25
  export interface UploadConfig {
26
26
  screenshotsDir?: string | string[];
27
+ /** Batch size used when uploading screenshots. */
27
28
  batchSize?: number;
29
+ /** Timeout in milliseconds for waiting on build processing after upload. */
28
30
  timeout?: number;
29
31
  }
30
32
 
@@ -76,13 +78,22 @@ export interface ScreenshotOptions {
76
78
  threshold?: number;
77
79
  minClusterSize?: number;
78
80
  fullPage?: boolean;
81
+ /** Transport-only build ID used to route the request. */
79
82
  buildId?: string;
80
- [key: string]: unknown;
83
+ /** Client-side HTTP timeout in milliseconds; not stored as metadata. */
84
+ requestTimeout?: number;
81
85
  }
82
86
 
83
87
  export interface ScreenshotResult {
84
88
  success: boolean;
85
- status?: 'passed' | 'failed' | 'new';
89
+ status?:
90
+ | 'passed'
91
+ | 'failed'
92
+ | 'new'
93
+ | 'match'
94
+ | 'diff'
95
+ | 'baseline-updated'
96
+ | 'error';
86
97
  name?: string;
87
98
  diffPercentage?: number;
88
99
  [key: string]: unknown;
@@ -95,7 +106,13 @@ export interface ScreenshotResult {
95
106
  export interface ComparisonResult {
96
107
  id: string;
97
108
  name: string;
98
- status: 'passed' | 'failed' | 'new' | 'error' | 'baseline-updated';
109
+ status:
110
+ | 'passed'
111
+ | 'failed'
112
+ | 'new'
113
+ | 'error'
114
+ | 'baseline-created'
115
+ | 'baseline-updated';
99
116
  baseline: string;
100
117
  current: string;
101
118
  diff: string | null;
@@ -150,7 +167,9 @@ export interface UploadOptions {
150
167
  message?: string;
151
168
  environment?: string;
152
169
  threshold?: number;
153
- pullRequestNumber?: string;
170
+ minClusterSize?: number;
171
+ metadata?: Record<string, unknown>;
172
+ pullRequestNumber?: string | number;
154
173
  parallelId?: string;
155
174
  onProgress?: (progress: UploadProgress) => void;
156
175
  }
@@ -186,8 +205,10 @@ export interface BuildResult {
186
205
  status: 'completed' | 'failed' | 'pending';
187
206
  build: unknown;
188
207
  comparisons?: number;
208
+ totalComparisons: number;
189
209
  passedComparisons?: number;
190
210
  failedComparisons?: number;
211
+ newComparisons: number;
191
212
  url?: string;
192
213
  }
193
214
 
@@ -487,15 +508,19 @@ export interface BuildOptions {
487
508
  buildName?: string;
488
509
  branch?: string;
489
510
  commit?: string;
511
+ commit_sha?: string;
490
512
  message?: string;
513
+ commit_message?: string;
491
514
  environment?: string;
492
515
  threshold?: number;
493
516
  eager?: boolean;
494
517
  allowNoToken?: boolean;
495
518
  wait?: boolean;
496
519
  uploadAll?: boolean;
497
- pullRequestNumber?: string;
520
+ pullRequestNumber?: string | number;
521
+ github_pull_request_number?: string | number;
498
522
  parallelId?: string;
523
+ parallel_id?: string;
499
524
  }
500
525
 
501
526
  /**
@@ -565,6 +590,8 @@ export function vizzlyScreenshot(
565
590
  threshold?: number;
566
591
  minClusterSize?: number;
567
592
  fullPage?: boolean;
593
+ requestTimeout?: number;
594
+ buildId?: string;
568
595
  [key: string]: unknown;
569
596
  }
570
597
  ): Promise<ScreenshotResult | null>;
@@ -573,6 +600,7 @@ export function vizzlyScreenshot(
573
600
  export function configure(config?: {
574
601
  serverUrl?: string;
575
602
  enabled?: boolean;
603
+ failOnDiff?: boolean;
576
604
  }): void;
577
605
 
578
606
  /** Enable or disable screenshot capture */
@@ -123,6 +123,25 @@ export function extractBrowserFromFilename(filename) {
123
123
  // Build Info Construction
124
124
  // ============================================================================
125
125
 
126
+ function buildUploadMetadata(options) {
127
+ let metadata = {
128
+ ...(options.metadata || {})
129
+ };
130
+ let comparison = {
131
+ ...(metadata.comparison || {})
132
+ };
133
+ if (options.threshold !== undefined) {
134
+ comparison.threshold = options.threshold;
135
+ }
136
+ if (options.minClusterSize !== undefined) {
137
+ comparison.minClusterSize = options.minClusterSize;
138
+ }
139
+ if (Object.keys(comparison).length > 0) {
140
+ metadata.comparison = comparison;
141
+ }
142
+ return Object.keys(metadata).length > 0 ? metadata : undefined;
143
+ }
144
+
126
145
  /**
127
146
  * Build API build info payload
128
147
  * @param {Object} options - Upload options
@@ -130,7 +149,7 @@ export function extractBrowserFromFilename(filename) {
130
149
  * @returns {Object} Build info payload
131
150
  */
132
151
  export function buildBuildInfo(options, defaultBranch = 'main') {
133
- return {
152
+ let buildInfo = {
134
153
  name: options.buildName || `Upload ${new Date().toISOString()}`,
135
154
  branch: options.branch || defaultBranch || 'main',
136
155
  commit_sha: options.commit,
@@ -140,6 +159,11 @@ export function buildBuildInfo(options, defaultBranch = 'main') {
140
159
  github_pull_request_number: options.pullRequestNumber,
141
160
  parallel_id: options.parallelId
142
161
  };
162
+ let metadata = buildUploadMetadata(options);
163
+ if (metadata) {
164
+ buildInfo.metadata = metadata;
165
+ }
166
+ return buildInfo;
143
167
  }
144
168
 
145
169
  // ============================================================================
@@ -316,17 +340,25 @@ export function buildUploadResult({
316
340
  * @returns {Object} Wait result
317
341
  */
318
342
  export function buildWaitResult(build) {
343
+ let totalComparisons = build.comparisonsTotal ?? build.total_comparisons ?? 0;
344
+ let passedComparisons = build.comparisonsPassed ?? build.passed_comparisons ?? build.identicalComparisons ?? build.identical_comparisons ?? 0;
345
+ let failedComparisons = build.comparisonsFailed ?? build.failed_comparisons ?? build.changedComparisons ?? build.changed_comparisons ?? 0;
346
+ let newComparisons = build.comparisonsNew ?? build.newComparisons ?? build.new_comparisons ?? 0;
347
+ let identicalComparisons = build.identicalComparisons ?? build.identical_comparisons ?? passedComparisons;
348
+ let approvalStatus = build.approvalStatus ?? build.approval_status ?? 'pending';
349
+ let hasComparisonCounts = build.comparisonsTotal !== undefined || build.total_comparisons !== undefined || build.comparisonsPassed !== undefined || build.passed_comparisons !== undefined || build.comparisonsFailed !== undefined || build.failed_comparisons !== undefined || build.comparisonsNew !== undefined || build.new_comparisons !== undefined || build.identicalComparisons !== undefined || build.identical_comparisons !== undefined;
319
350
  let result = {
320
351
  status: 'completed',
321
- build
352
+ build,
353
+ passedComparisons,
354
+ failedComparisons,
355
+ newComparisons,
356
+ identicalComparisons,
357
+ approvalStatus,
358
+ totalComparisons
322
359
  };
323
- if (typeof build.comparisonsTotal === 'number') {
324
- result.comparisons = build.comparisonsTotal;
325
- result.passedComparisons = build.comparisonsPassed || 0;
326
- result.failedComparisons = build.comparisonsFailed || 0;
327
- } else {
328
- result.passedComparisons = 0;
329
- result.failedComparisons = 0;
360
+ if (hasComparisonCounts) {
361
+ result.comparisons = totalComparisons;
330
362
  }
331
363
  if (build.url) {
332
364
  result.url = build.url;