@vizzly-testing/cli 0.30.0 → 0.31.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 (77) hide show
  1. package/README.md +9 -9
  2. package/dist/api/client.js +6 -3
  3. package/dist/cli.js +58 -29
  4. package/dist/client/index.js +21 -29
  5. package/dist/commands/api.js +145 -90
  6. package/dist/commands/baselines.js +78 -47
  7. package/dist/commands/builds.js +9 -8
  8. package/dist/commands/comparisons.js +7 -8
  9. package/dist/commands/config-cmd.js +15 -7
  10. package/dist/commands/context.js +329 -18
  11. package/dist/commands/doctor.js +116 -81
  12. package/dist/commands/init.js +202 -201
  13. package/dist/commands/login.js +134 -65
  14. package/dist/commands/logout.js +17 -6
  15. package/dist/commands/preview.js +29 -57
  16. package/dist/commands/project.js +120 -0
  17. package/dist/commands/projects.js +9 -2
  18. package/dist/commands/review.js +236 -202
  19. package/dist/commands/run.js +75 -72
  20. package/dist/commands/status.js +228 -150
  21. package/dist/commands/tdd-daemon-process.js +98 -0
  22. package/dist/commands/tdd-daemon.js +318 -212
  23. package/dist/commands/tdd.js +23 -8
  24. package/dist/commands/upload.js +10 -9
  25. package/dist/commands/whoami.js +63 -25
  26. package/dist/config/core.js +36 -35
  27. package/dist/context/local-workspace-provider.js +132 -12
  28. package/dist/context/provider-resolver.js +5 -1
  29. package/dist/index.js +5 -4
  30. package/dist/plugin-loader.js +55 -49
  31. package/dist/project/core.js +2 -2
  32. package/dist/project/operations.js +6 -3
  33. package/dist/reporter/reporter-bundle.css +1 -1
  34. package/dist/reporter/reporter-bundle.iife.js +7 -7
  35. package/dist/sdk/index.js +162 -107
  36. package/dist/server/handlers/tdd-handler.js +30 -54
  37. package/dist/server/routers/baseline.js +195 -178
  38. package/dist/server/routers/events.js +212 -174
  39. package/dist/server/routers/projects.js +14 -5
  40. package/dist/server/routers/screenshot.js +1 -31
  41. package/dist/services/build-manager.js +19 -97
  42. package/dist/services/config-service.js +26 -34
  43. package/dist/services/index.js +2 -4
  44. package/dist/services/static-report-generator.js +11 -12
  45. package/dist/services/test-runner.js +21 -27
  46. package/dist/tdd/index.js +1 -1
  47. package/dist/tdd/metadata/region-metadata.js +0 -37
  48. package/dist/tdd/runtime-context.js +78 -0
  49. package/dist/tdd/server-registry.js +38 -17
  50. package/dist/tdd/services/baseline-manager.js +23 -0
  51. package/dist/tdd/services/hotspot-service.js +0 -13
  52. package/dist/tdd/tdd-service.js +82 -113
  53. package/dist/types/client.d.ts +36 -1
  54. package/dist/types/index.d.ts +114 -22
  55. package/dist/types/sdk.d.ts +40 -0
  56. package/dist/uploader/core.js +5 -4
  57. package/dist/uploader/index.js +79 -2
  58. package/dist/uploader/operations.js +40 -5
  59. package/dist/utils/api-url.js +14 -0
  60. package/dist/utils/async-utils.js +26 -0
  61. package/dist/utils/ci-env.js +19 -12
  62. package/dist/utils/config-loader.js +39 -65
  63. package/dist/utils/config-schema.js +19 -55
  64. package/dist/utils/fetch-utils.js +12 -5
  65. package/dist/utils/git.js +35 -72
  66. package/dist/utils/global-config.js +20 -1
  67. package/dist/utils/output.js +19 -7
  68. package/dist/utils/patterns.js +14 -0
  69. package/dist/utils/project-link-store.js +105 -0
  70. package/dist/utils/screenshot-options.js +28 -0
  71. package/dist/utils/secret-store.js +65 -0
  72. package/package.json +11 -8
  73. package/dist/services/screenshot-server.js +0 -65
  74. package/dist/services/server-manager.js +0 -81
  75. package/dist/services/uploader.js +0 -100
  76. package/dist/tdd/services/region-service.js +0 -75
  77. package/dist/utils/build-history.js +0 -103
@@ -189,7 +189,7 @@ Error generating stack: `+e.message+`
189
189
  bg-slate-800/30 hover:bg-slate-800/50
190
190
  border rounded-lg transition-all
191
191
  ${o?`border-red-500/40 hover:border-red-500/60`:s?`border-blue-500/40 hover:border-blue-500/60`:`border-slate-700/50 hover:border-slate-600`}
192
- `,children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>t(a),className:`relative w-16 h-11 rounded-md overflow-hidden flex-shrink-0 bg-slate-900 hover:ring-2 hover:ring-amber-500/50 transition-all`,children:[u?(0,P.jsx)(yo,{src:u,alt:e.name,className:`w-full h-full object-cover object-top`}):(0,P.jsx)(`div`,{className:`w-full h-full flex items-center justify-center text-slate-600`,children:(0,P.jsx)(an,{className:`w-5 h-5`})}),o&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-red-500/20 flex items-center justify-center`,children:(0,P.jsx)(`div`,{className:`w-2 h-2 bg-red-500 rounded-full animate-pulse`})}),!o&&s&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-blue-500/10`}),e.comparisons.length>1&&(0,P.jsx)(`div`,{className:`absolute bottom-0.5 right-0.5 px-1 py-0.5 text-[9px] font-bold bg-slate-900/90 text-slate-300 rounded`,children:e.comparisons.length})]}),(0,P.jsxs)(`button`,{type:`button`,onClick:()=>t(a),className:`flex-1 min-w-0 text-left`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(`h3`,{className:`text-sm font-medium text-white truncate`,children:e.name}),c>0&&(0,P.jsxs)(`span`,{className:`text-xs font-mono text-red-400`,children:[c.toFixed(1),`%`]})]}),(0,P.jsx)(`div`,{className:`mt-1`,children:(0,P.jsx)(wo,{group:e})})]}),(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-shrink-0`,children:[(0,P.jsx)(Co,{group:e}),l&&(0,P.jsxs)(`div`,{className:`flex items-center gap-1 ${e.acceptedCount===0?`opacity-100`:`opacity-0 group-hover:opacity-100`} transition-opacity`,children:[(0,P.jsx)(L,{variant:`ghost`,size:`sm`,onClick:t=>{t.stopPropagation(),n(e)},disabled:i,className:`!p-1.5 text-emerald-400 hover:bg-emerald-500/20`,title:`Accept ${e.comparisons.length>1?`all ${e.comparisons.length} variants`:``}`,children:(0,P.jsx)(Bt,{className:`w-4 h-4`})}),(0,P.jsx)(L,{variant:`ghost`,size:`sm`,onClick:t=>{t.stopPropagation(),r(e)},disabled:i,className:`!p-1.5 text-red-400 hover:bg-red-500/20`,title:`Reject`,children:(0,P.jsx)($n,{className:`w-4 h-4`})})]}),(0,P.jsx)(Jt,{className:`w-4 h-4 text-slate-500 group-hover:text-slate-300 transition-colors`})]})]})}function Eo({comparisons:e,onSelectComparison:t,onAcceptComparison:n,onRejectComparison:r,onAcceptGroup:i,onRejectGroup:a,loadingStates:o={}}){let s=(0,N.useMemo)(()=>!e||e.length===0?[]:xo(e),[e]);if(s.length===0)return(0,P.jsx)(`div`,{className:`text-center py-12 text-slate-400`,children:`No screenshots to display`});let c=e=>{let t=e.comparisons.map(bo).filter(Boolean);if(i){i(t);return}if(n)for(let e of t)n(e)},l=e=>{let t=e.comparisons.map(bo).filter(Boolean);if(a){a(t);return}if(r)for(let e of t)r(e)},u=Object.values(o).some(e=>e===`accepting`);return(0,P.jsx)(`div`,{className:`space-y-2`,children:s.map(e=>(0,P.jsx)(To,{group:e,onSelect:t,onAcceptGroup:c,onRejectGroup:l,isAccepting:u},e.name))})}function Do(e){return e<=480?ln:e<=1024?dn:an}function Oo({value:e,onChange:t,options:n,placeholder:r,renderOption:i,renderValue:a,testId:o}){let[s,c]=(0,N.useState)(!1),l=(0,N.useRef)(null),u=(0,N.useCallback)(e=>{t(e),c(!1)},[t]);return(0,P.jsxs)(`div`,{className:`relative`,ref:l,onBlur:(0,N.useCallback)(e=>{l.current?.contains(e.relatedTarget)||c(!1)},[]),children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>c(!s),"data-testid":o,className:`
192
+ `,children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>t(a),className:`relative w-16 h-11 rounded-md overflow-hidden flex-shrink-0 bg-slate-900 hover:ring-2 hover:ring-amber-500/50 transition-all`,children:[u?(0,P.jsx)(yo,{src:u,alt:e.name,className:`w-full h-full object-cover object-top`}):(0,P.jsx)(`div`,{className:`w-full h-full flex items-center justify-center text-slate-600`,children:(0,P.jsx)(an,{className:`w-5 h-5`})}),o&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-red-500/20 flex items-center justify-center`,children:(0,P.jsx)(`div`,{className:`w-2 h-2 bg-red-500 rounded-full animate-pulse`})}),!o&&s&&(0,P.jsx)(`div`,{className:`absolute inset-0 bg-blue-500/10`}),e.comparisons.length>1&&(0,P.jsx)(`div`,{className:`absolute bottom-0.5 right-0.5 px-1 py-0.5 text-[9px] font-bold bg-slate-900/90 text-slate-300 rounded`,children:e.comparisons.length})]}),(0,P.jsxs)(`button`,{type:`button`,onClick:()=>t(a),className:`flex-1 min-w-0 text-left`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(`h3`,{className:`text-sm font-medium text-white truncate`,children:e.name}),c>0&&(0,P.jsxs)(`span`,{className:`text-xs font-mono text-red-400`,children:[c.toFixed(1),`%`]})]}),(0,P.jsx)(`div`,{className:`mt-1`,children:(0,P.jsx)(wo,{group:e})})]}),(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-shrink-0`,children:[(0,P.jsx)(Co,{group:e}),l&&(0,P.jsxs)(`div`,{className:`flex items-center gap-1 ${e.acceptedCount===0?`opacity-100`:`opacity-0 group-hover:opacity-100`} transition-opacity`,children:[(0,P.jsx)(L,{variant:`ghost`,size:`sm`,onClick:t=>{t.stopPropagation(),n(e)},disabled:i,className:`!p-1.5 text-emerald-400 hover:bg-emerald-500/20`,title:`Accept ${e.comparisons.length>1?`all ${e.comparisons.length} variants`:``}`,children:(0,P.jsx)(Bt,{className:`w-4 h-4`})}),(0,P.jsx)(L,{variant:`ghost`,size:`sm`,onClick:t=>{t.stopPropagation(),r(e)},disabled:i,className:`!p-1.5 text-red-400 hover:bg-red-500/20`,title:`Reject`,children:(0,P.jsx)($n,{className:`w-4 h-4`})})]}),(0,P.jsx)(Jt,{className:`w-4 h-4 text-slate-500 group-hover:text-slate-300 transition-colors`})]})]})}function Eo({comparisons:e,onSelectComparison:t,onAcceptComparison:n,onRejectComparison:r,onAcceptGroup:i,onRejectGroup:a,loadingStates:o={}}){let s=(0,N.useMemo)(()=>!e||e.length===0?[]:xo(e),[e]);if(s.length===0)return(0,P.jsx)(`div`,{className:`text-center py-12 text-slate-400`,children:`No screenshots to display`});let c=e=>{let t=e.comparisons.map(bo).filter(Boolean);if(i){i(t);return}if(n)for(let e of t)n(e)},l=e=>{let t=e.comparisons.map(bo).filter(Boolean);if(a){a(t);return}if(r)for(let e of t)r(e)},u=Object.values(o).some(e=>e===`accepting`);return(0,P.jsx)(`div`,{className:`space-y-2`,children:s.map(e=>(0,P.jsx)(To,{group:e,onSelect:t,onAcceptGroup:c,onRejectGroup:l,isAccepting:u},e.name))})}function Do(e){return e<=480?ln:e<=1024?dn:an}function Oo(e){return Number(e.split(`×`)[0])}function ko({value:e,onChange:t,options:n,placeholder:r,renderOption:i,renderValue:a,testId:o}){let[s,c]=(0,N.useState)(!1),l=(0,N.useRef)(null),u=(0,N.useCallback)(e=>{t(e),c(!1)},[t]);return(0,P.jsxs)(`div`,{className:`relative`,ref:l,onBlur:(0,N.useCallback)(e=>{l.current?.contains(e.relatedTarget)||c(!1)},[]),children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>c(!s),"data-testid":o,className:`
193
193
  inline-flex items-center gap-2 px-2.5 h-8 rounded-md text-xs font-medium
194
194
  bg-transparent border transition-all
195
195
  ${s?`border-amber-500/50 ring-1 ring-amber-500/20`:`border-slate-700/50 hover:border-slate-600`}
@@ -199,26 +199,26 @@ Error generating stack: `+e.message+`
199
199
  `,children:[(0,P.jsx)(`span`,{className:`w-4 h-4`}),(0,P.jsx)(`span`,{children:r})]}),n.map(t=>(0,P.jsx)(`button`,{type:`button`,onClick:()=>u(t),className:`
200
200
  w-full flex items-center gap-2 px-3 py-2 text-sm text-left transition-colors
201
201
  ${e===t?`bg-amber-500/10 text-amber-400`:`text-slate-300 hover:bg-slate-700/50`}
202
- `,children:i(t)},t))]})})]})}function ko({value:e,onChange:t}){let[n,r]=(0,N.useState)(!1),i=(0,N.useRef)(null),a=[{value:oo.PRIORITY,label:`Priority`,description:`Failed first`},{value:oo.NAME,label:`Name`,description:`A to Z`},{value:oo.TIME,label:`Time`,description:`Recent first`}],o=a.find(t=>t.value===e)||a[0];return(0,P.jsxs)(`div`,{className:`relative`,ref:i,children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>r(!n),className:`
202
+ `,children:i(t)},t))]})})]})}function Ao({value:e,onChange:t}){let[n,r]=(0,N.useState)(!1),i=(0,N.useRef)(null),a=[{value:oo.PRIORITY,label:`Priority`,description:`Failed first`},{value:oo.NAME,label:`Name`,description:`A to Z`},{value:oo.TIME,label:`Time`,description:`Recent first`}],o=a.find(t=>t.value===e)||a[0];return(0,P.jsxs)(`div`,{className:`relative`,ref:i,children:[(0,P.jsxs)(`button`,{type:`button`,onClick:()=>r(!n),className:`
203
203
  inline-flex items-center gap-2 px-2.5 h-8 rounded-md text-xs font-medium
204
204
  bg-transparent border transition-all
205
205
  ${n?`border-amber-500/50 ring-1 ring-amber-500/20`:`border-slate-700/50 hover:border-slate-600`}
206
206
  `,children:[(0,P.jsx)(Ot,{className:`w-3.5 h-3.5 text-slate-500`}),(0,P.jsx)(`span`,{className:`text-slate-300`,children:o.label}),(0,P.jsx)(Wt,{className:`w-3 h-3 text-slate-500 transition-transform ${n?`rotate-180`:``}`})]}),n&&(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(`div`,{className:`fixed inset-0 z-40`,onClick:()=>r(!1)}),(0,P.jsx)(`div`,{className:`absolute top-full right-0 mt-1 z-50 w-48 bg-slate-800 border border-slate-700/50 rounded-lg shadow-xl overflow-hidden`,children:(0,P.jsx)(`div`,{className:`py-1`,children:a.map(n=>(0,P.jsxs)(`button`,{type:`button`,onClick:()=>{t(n.value),r(!1)},className:`
207
207
  w-full flex flex-col items-start px-3 py-2 text-left transition-colors
208
208
  ${e===n.value?`bg-amber-500/10`:`hover:bg-slate-700/50`}
209
- `,children:[(0,P.jsx)(`span`,{className:e===n.value?`text-amber-400 font-medium`:`text-slate-300`,children:n.label}),(0,P.jsx)(`span`,{className:`text-xs text-slate-500`,children:n.description})]},n.value))})})]})]})}function Ao({label:e,icon:t,onRemove:n}){return(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 px-2 py-1 bg-amber-500/10 border border-amber-500/20 rounded-md text-xs text-amber-400`,children:[t&&(0,P.jsx)(t,{className:`w-3 h-3`}),(0,P.jsx)(`span`,{children:e}),(0,P.jsx)(`button`,{type:`button`,onClick:n,className:`ml-0.5 hover:text-amber-200 transition-colors`,children:(0,P.jsx)(tr,{className:`w-3 h-3`})})]})}function jo({filter:e,setFilter:t,sortBy:n,setSortBy:r,searchQuery:i,setSearchQuery:a,selectedBrowser:o,setSelectedBrowser:s,selectedViewport:c,setSelectedViewport:l,availableFilters:u,counts:d,onRefresh:f,loading:p}){let[m,h]=(0,N.useState)(!1),g=u.browsers.length>1||u.viewports.length>1,_=(0,N.useMemo)(()=>{let t=0;return e!==H.ALL&&t++,o!==`all`&&t++,c!==`all`&&t++,i.trim()&&t++,t},[e,o,c,i]),v=(0,N.useCallback)(()=>{t(H.ALL),s(`all`),l(`all`),a(``)},[t,s,l,a]),y=(0,N.useCallback)(e=>(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Di,{browser:e,className:`w-4 h-4`}),(0,P.jsx)(`span`,{className:`capitalize`,children:e})]}),[]),b=(0,N.useCallback)(e=>e===`all`?(0,P.jsx)(`span`,{className:`text-slate-400`,children:`All browsers`}):(0,P.jsxs)(`span`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(Di,{browser:e,className:`w-4 h-4`}),(0,P.jsx)(`span`,{className:`text-slate-300 capitalize`,children:e})]}),[]),x=(0,N.useCallback)(e=>(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Do(parseInt(e.split(`×`)[0],10)),{className:`w-4 h-4 text-slate-400`}),(0,P.jsx)(`span`,{className:`font-mono text-xs`,children:e})]}),[]),S=(0,N.useCallback)(e=>e===`all`?(0,P.jsx)(`span`,{className:`text-slate-400`,children:`All viewports`}):(0,P.jsxs)(`span`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(Do(parseInt(e.split(`×`)[0],10)),{className:`w-4 h-4 text-slate-400`}),(0,P.jsx)(`span`,{className:`text-slate-300 font-mono text-xs`,children:e})]}),[]);return(0,P.jsxs)(`div`,{className:`space-y-3`,children:[(0,P.jsxs)(`div`,{className:`flex flex-col gap-3 p-3 bg-slate-800/30 border border-slate-700/40 rounded-xl`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(`div`,{className:`flex-1`,children:(0,P.jsx)(ba,{value:i,onChange:a,placeholder:`Search screenshots...`,className:`w-full`})}),g&&(0,P.jsxs)(`button`,{type:`button`,onClick:()=>h(!m),className:`
209
+ `,children:[(0,P.jsx)(`span`,{className:e===n.value?`text-amber-400 font-medium`:`text-slate-300`,children:n.label}),(0,P.jsx)(`span`,{className:`text-xs text-slate-500`,children:n.description})]},n.value))})})]})]})}function jo({label:e,icon:t,onRemove:n}){return(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 px-2 py-1 bg-amber-500/10 border border-amber-500/20 rounded-md text-xs text-amber-400`,children:[t&&(0,P.jsx)(t,{className:`w-3 h-3`}),(0,P.jsx)(`span`,{children:e}),(0,P.jsx)(`button`,{type:`button`,onClick:n,className:`ml-0.5 hover:text-amber-200 transition-colors`,children:(0,P.jsx)(tr,{className:`w-3 h-3`})})]})}function Mo({filter:e,setFilter:t,sortBy:n,setSortBy:r,searchQuery:i,setSearchQuery:a,selectedBrowser:o,setSelectedBrowser:s,selectedViewport:c,setSelectedViewport:l,availableFilters:u,counts:d,onRefresh:f,loading:p}){let[m,h]=(0,N.useState)(!1),g=u.browsers.length>1||u.viewports.length>1,_=(0,N.useMemo)(()=>{let t=0;return e!==H.ALL&&t++,o!==`all`&&t++,c!==`all`&&t++,i.trim()&&t++,t},[e,o,c,i]),v=(0,N.useCallback)(()=>{t(H.ALL),s(`all`),l(`all`),a(``)},[t,s,l,a]),y=(0,N.useCallback)(e=>(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Di,{browser:e,className:`w-4 h-4`}),(0,P.jsx)(`span`,{className:`capitalize`,children:e})]}),[]),b=(0,N.useCallback)(e=>e===`all`?(0,P.jsx)(`span`,{className:`text-slate-400`,children:`All browsers`}):(0,P.jsxs)(`span`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(Di,{browser:e,className:`w-4 h-4`}),(0,P.jsx)(`span`,{className:`text-slate-300 capitalize`,children:e})]}),[]),x=(0,N.useCallback)(e=>(0,P.jsxs)(P.Fragment,{children:[(0,P.jsx)(Do(Oo(e)),{className:`w-4 h-4 text-slate-400`}),(0,P.jsx)(`span`,{className:`font-mono text-xs`,children:e})]}),[]),S=(0,N.useCallback)(e=>e===`all`?(0,P.jsx)(`span`,{className:`text-slate-400`,children:`All viewports`}):(0,P.jsxs)(`span`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(Do(Oo(e)),{className:`w-4 h-4 text-slate-400`}),(0,P.jsx)(`span`,{className:`text-slate-300 font-mono text-xs`,children:e})]}),[]);return(0,P.jsxs)(`div`,{className:`space-y-3`,children:[(0,P.jsxs)(`div`,{className:`flex flex-col gap-3 p-3 bg-slate-800/30 border border-slate-700/40 rounded-xl`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,P.jsx)(`div`,{className:`flex-1`,children:(0,P.jsx)(ba,{value:i,onChange:a,placeholder:`Search screenshots...`,className:`w-full`})}),g&&(0,P.jsxs)(`button`,{type:`button`,onClick:()=>h(!m),className:`
210
210
  relative md:hidden inline-flex items-center justify-center w-8 h-8 rounded-md transition-all
211
211
  ${m||_>0?`bg-amber-500/20 text-amber-400 border border-amber-500/30`:`text-slate-400 border border-slate-700/50 hover:border-slate-600`}
212
- `,children:[(0,P.jsx)(mt,{className:`w-4 h-4`}),_>0&&(0,P.jsx)(`span`,{className:`absolute -top-1 -right-1 w-4 h-4 bg-amber-500 text-slate-900 text-[10px] font-bold rounded-full flex items-center justify-center`,children:_})]}),(0,P.jsx)(`div`,{className:`hidden md:block`,children:(0,P.jsx)(ko,{value:n,onChange:r})}),(0,P.jsx)(`button`,{type:`button`,onClick:f,disabled:p,className:`inline-flex items-center justify-center w-8 h-8 rounded-md border border-slate-700/50 text-slate-400 hover:text-white hover:border-slate-600 transition-all disabled:opacity-50`,title:`Refresh`,children:(0,P.jsx)(vt,{className:`w-4 h-4 ${p?`animate-spin`:``}`})})]}),(0,P.jsxs)(`div`,{className:`hidden md:flex items-center gap-3 flex-wrap`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,P.jsx)(z,{label:`All`,count:d.all,color:`gray`,active:e===H.ALL,onClick:()=>t(H.ALL),testId:`filter-status-all`}),(0,P.jsx)(z,{label:`Failed`,count:d.failed,color:`red`,active:e===H.FAILED,onClick:()=>t(H.FAILED),testId:`filter-status-failed`}),(0,P.jsx)(z,{label:`New`,count:d.new,color:`blue`,active:e===H.NEW,onClick:()=>t(H.NEW),testId:`filter-status-new`}),(0,P.jsx)(z,{label:`Passed`,count:d.passed,color:`emerald`,active:e===H.PASSED,onClick:()=>t(H.PASSED),testId:`filter-status-passed`})]}),g&&(0,P.jsx)(`div`,{className:`w-px h-6 bg-slate-700/50`}),u.browsers.length>1&&(0,P.jsx)(Oo,{value:o,onChange:s,options:u.browsers,placeholder:`All browsers`,renderOption:y,renderValue:b,testId:`filter-browser`}),u.viewports.length>1&&(0,P.jsx)(Oo,{value:c,onChange:l,options:u.viewports,placeholder:`All viewports`,renderOption:x,renderValue:S,testId:`filter-viewport`})]}),(0,P.jsxs)(`div`,{className:`md:hidden flex items-center gap-1.5 overflow-x-auto pb-1 -mx-1 px-1 scrollbar-hide`,children:[(0,P.jsx)(z,{label:`All`,count:d.all,color:`gray`,active:e===H.ALL,onClick:()=>t(H.ALL),testId:`mobile-filter-status-all`}),(0,P.jsx)(z,{label:`Failed`,count:d.failed,color:`red`,active:e===H.FAILED,onClick:()=>t(H.FAILED),testId:`mobile-filter-status-failed`}),(0,P.jsx)(z,{label:`New`,count:d.new,color:`blue`,active:e===H.NEW,onClick:()=>t(H.NEW),testId:`mobile-filter-status-new`}),(0,P.jsx)(z,{label:`Passed`,count:d.passed,color:`emerald`,active:e===H.PASSED,onClick:()=>t(H.PASSED),testId:`mobile-filter-status-passed`})]}),m&&g&&(0,P.jsxs)(`div`,{className:`md:hidden space-y-3 pt-2 border-t border-slate-700/30`,children:[(0,P.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`span`,{className:`text-sm text-slate-400`,children:`Sort by`}),(0,P.jsx)(ko,{value:n,onChange:r})]}),u.browsers.length>1&&(0,P.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`span`,{className:`text-sm text-slate-400`,children:`Browser`}),(0,P.jsx)(Oo,{value:o,onChange:s,options:u.browsers,placeholder:`All browsers`,renderOption:y,renderValue:b,testId:`filter-browser-mobile`})]}),u.viewports.length>1&&(0,P.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`span`,{className:`text-sm text-slate-400`,children:`Viewport`}),(0,P.jsx)(Oo,{value:c,onChange:l,options:u.viewports,placeholder:`All viewports`,renderOption:x,renderValue:S,testId:`filter-viewport-mobile`})]})]})]}),_>0&&(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,P.jsx)(`span`,{className:`text-xs text-slate-500`,children:`Filters:`}),e!==H.ALL&&(0,P.jsx)(Ao,{label:e.charAt(0).toUpperCase()+e.slice(1),onRemove:()=>t(H.ALL)}),o!==`all`&&(0,P.jsx)(Ao,{label:o,icon:()=>(0,P.jsx)(Di,{browser:o,className:`w-3 h-3`}),onRemove:()=>s(`all`)}),c!==`all`&&(0,P.jsx)(Ao,{label:c,icon:Do(parseInt(c.split(`×`)[0],10)),onRemove:()=>l(`all`)}),i.trim()&&(0,P.jsx)(Ao,{label:`"${i}"`,onRemove:()=>a(``)}),(0,P.jsx)(`button`,{type:`button`,onClick:v,className:`text-xs text-slate-500 hover:text-slate-300 transition-colors ml-2`,children:`Clear all`})]})]})}function Mo(e){return Array.isArray(e)?e.filter(Boolean):e?[e]:[]}function No({failedCount:e,newCount:t,onAcceptAll:n,isLoading:r}){let i=e+t,a=e>0;return(0,P.jsxs)(`div`,{className:`
212
+ `,children:[(0,P.jsx)(mt,{className:`w-4 h-4`}),_>0&&(0,P.jsx)(`span`,{className:`absolute -top-1 -right-1 w-4 h-4 bg-amber-500 text-slate-900 text-[10px] font-bold rounded-full flex items-center justify-center`,children:_})]}),(0,P.jsx)(`div`,{className:`hidden md:block`,children:(0,P.jsx)(Ao,{value:n,onChange:r})}),(0,P.jsx)(`button`,{type:`button`,onClick:f,disabled:p,className:`inline-flex items-center justify-center w-8 h-8 rounded-md border border-slate-700/50 text-slate-400 hover:text-white hover:border-slate-600 transition-all disabled:opacity-50`,title:`Refresh`,children:(0,P.jsx)(vt,{className:`w-4 h-4 ${p?`animate-spin`:``}`})})]}),(0,P.jsxs)(`div`,{className:`hidden md:flex items-center gap-3 flex-wrap`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-1.5`,children:[(0,P.jsx)(z,{label:`All`,count:d.all,color:`gray`,active:e===H.ALL,onClick:()=>t(H.ALL),testId:`filter-status-all`}),(0,P.jsx)(z,{label:`Failed`,count:d.failed,color:`red`,active:e===H.FAILED,onClick:()=>t(H.FAILED),testId:`filter-status-failed`}),(0,P.jsx)(z,{label:`New`,count:d.new,color:`blue`,active:e===H.NEW,onClick:()=>t(H.NEW),testId:`filter-status-new`}),(0,P.jsx)(z,{label:`Passed`,count:d.passed,color:`emerald`,active:e===H.PASSED,onClick:()=>t(H.PASSED),testId:`filter-status-passed`})]}),g&&(0,P.jsx)(`div`,{className:`w-px h-6 bg-slate-700/50`}),u.browsers.length>1&&(0,P.jsx)(ko,{value:o,onChange:s,options:u.browsers,placeholder:`All browsers`,renderOption:y,renderValue:b,testId:`filter-browser`}),u.viewports.length>1&&(0,P.jsx)(ko,{value:c,onChange:l,options:u.viewports,placeholder:`All viewports`,renderOption:x,renderValue:S,testId:`filter-viewport`})]}),(0,P.jsxs)(`div`,{className:`md:hidden flex items-center gap-1.5 overflow-x-auto pb-1 -mx-1 px-1 scrollbar-hide`,children:[(0,P.jsx)(z,{label:`All`,count:d.all,color:`gray`,active:e===H.ALL,onClick:()=>t(H.ALL),testId:`mobile-filter-status-all`}),(0,P.jsx)(z,{label:`Failed`,count:d.failed,color:`red`,active:e===H.FAILED,onClick:()=>t(H.FAILED),testId:`mobile-filter-status-failed`}),(0,P.jsx)(z,{label:`New`,count:d.new,color:`blue`,active:e===H.NEW,onClick:()=>t(H.NEW),testId:`mobile-filter-status-new`}),(0,P.jsx)(z,{label:`Passed`,count:d.passed,color:`emerald`,active:e===H.PASSED,onClick:()=>t(H.PASSED),testId:`mobile-filter-status-passed`})]}),m&&g&&(0,P.jsxs)(`div`,{className:`md:hidden space-y-3 pt-2 border-t border-slate-700/30`,children:[(0,P.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`span`,{className:`text-sm text-slate-400`,children:`Sort by`}),(0,P.jsx)(Ao,{value:n,onChange:r})]}),u.browsers.length>1&&(0,P.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`span`,{className:`text-sm text-slate-400`,children:`Browser`}),(0,P.jsx)(ko,{value:o,onChange:s,options:u.browsers,placeholder:`All browsers`,renderOption:y,renderValue:b,testId:`filter-browser-mobile`})]}),u.viewports.length>1&&(0,P.jsxs)(`div`,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`span`,{className:`text-sm text-slate-400`,children:`Viewport`}),(0,P.jsx)(ko,{value:c,onChange:l,options:u.viewports,placeholder:`All viewports`,renderOption:x,renderValue:S,testId:`filter-viewport-mobile`})]})]})]}),_>0&&(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,P.jsx)(`span`,{className:`text-xs text-slate-500`,children:`Filters:`}),e!==H.ALL&&(0,P.jsx)(jo,{label:e.charAt(0).toUpperCase()+e.slice(1),onRemove:()=>t(H.ALL)}),o!==`all`&&(0,P.jsx)(jo,{label:o,icon:()=>(0,P.jsx)(Di,{browser:o,className:`w-3 h-3`}),onRemove:()=>s(`all`)}),c!==`all`&&(0,P.jsx)(jo,{label:c,icon:Do(Oo(c)),onRemove:()=>l(`all`)}),i.trim()&&(0,P.jsx)(jo,{label:`"${i}"`,onRemove:()=>a(``)}),(0,P.jsx)(`button`,{type:`button`,onClick:v,className:`text-xs text-slate-500 hover:text-slate-300 transition-colors ml-2`,children:`Clear all`})]})]})}function No(e){return Array.isArray(e)?e.filter(Boolean):e?[e]:[]}function Po({failedCount:e,newCount:t,onAcceptAll:n,isLoading:r}){let i=e+t,a=e>0;return(0,P.jsxs)(`div`,{className:`
213
213
  relative overflow-hidden rounded-xl border
214
214
  ${a?`bg-gradient-to-r from-red-500/10 via-red-500/5 to-transparent border-red-500/20`:`bg-gradient-to-r from-emerald-500/10 via-emerald-500/5 to-transparent border-emerald-500/20`}
215
215
  `,children:[(0,P.jsx)(`div`,{className:`absolute inset-0 opacity-5`,children:(0,P.jsx)(`div`,{className:`absolute inset-0`,style:{backgroundImage:`radial-gradient(circle at 1px 1px, currentColor 1px, transparent 0)`,backgroundSize:`24px 24px`}})}),(0,P.jsxs)(`div`,{className:`relative flex items-center gap-4 p-4`,children:[(0,P.jsx)(`div`,{className:`
216
216
  flex-shrink-0 w-10 h-10 rounded-lg flex items-center justify-center
217
217
  ${a?`bg-red-500/20 text-red-400`:`bg-emerald-500/20 text-emerald-400`}
218
- `,children:a?(0,P.jsx)(hn,{className:`w-5 h-5`}):(0,P.jsx)(Vn,{className:`w-5 h-5`})}),(0,P.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[e>0&&(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-red-500/20 text-red-400 text-sm font-medium`,children:[(0,P.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-red-400`}),e,` changed`]}),t>0&&(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-blue-500/20 text-blue-400 text-sm font-medium`,children:[(0,P.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-blue-400`}),t,` new`]})]}),(0,P.jsx)(`p`,{className:`text-sm text-slate-400 mt-1`,children:a?`Review visual differences and accept to update baselines`:`New screenshots ready to be saved as baselines`})]}),(0,P.jsxs)(L,{variant:`success`,onClick:n,loading:r,icon:Bt,className:`flex-shrink-0`,"data-testid":`btn-accept-all`,children:[`Accept All (`,i,`)`]})]})]})}function Po(){let[,e]=Fr(),{addToast:t,confirm:n}=Ja(),[r,i]=(0,N.useState)({}),{data:a,isLoading:o,refetch:s}=di(),c=pi(),l=gi(),u=_i(),{filteredComparisons:d,filter:f,setFilter:p,sortBy:m,setSortBy:h,searchQuery:g,setSearchQuery:_,selectedBrowser:v,setSelectedBrowser:y,selectedViewport:b,setSelectedViewport:x,availableFilters:S,counts:ee}=vo(a?.comparisons),C=(0,N.useCallback)(t=>{let n=t.id||t.signature||t.name;n&&e(`/comparison/${encodeURIComponent(n)}`)},[e]),w=(0,N.useCallback)((e,t)=>{i(n=>{let r={...n};for(let n of e)n&&(t?r[n]=t:delete r[n]);return r})},[]),te=(0,N.useCallback)(e=>{!e||e.length===0||(w(e,`accepting`),l.mutate(e,{onSuccess:()=>{w(e,`accepted`)},onError:n=>{let r=Mo(n?.succeededIds),i=Mo(n?.failedIds);r.length>0&&w(r,`accepted`),i.length>0?w(i,null):w(e,null),t(`Failed to accept: ${n.message}`,`error`)}}))},[l,t,w]),ne=(0,N.useCallback)(e=>{!e||e.length===0||(w(e,`rejecting`),u.mutate(e,{onSuccess:()=>{w(e,`rejected`)},onError:n=>{let r=Mo(n?.succeededIds),i=Mo(n?.failedIds);r.length>0&&w(r,`rejected`),i.length>0?w(i,null):w(e,null),t(`Failed to reject: ${n.message}`,`error`)}}))},[u,t,w]),T=(0,N.useCallback)(e=>te(e?[e]:[]),[te]),re=(0,N.useCallback)(e=>ne(e?[e]:[]),[ne]),E=(0,N.useCallback)(async()=>{await n(`This will update all failed and new screenshots.`,`Accept all changes as new baselines?`)&&c.mutate(void 0,{onSuccess:()=>{t(`All baselines accepted successfully`,`success`)},onError:e=>{console.error(`Failed to accept all baselines:`,e),t(`Failed to accept all baselines. Please try again.`,`error`)}})},[c,t,n]),D=!a?.comparisons||a.comparisons.length===0,O=f!==`all`||g.trim()||v!==`all`||b!==`all`,k=a?.comparisons?.some(e=>go(e.status)),A=a?.comparisons?.filter(e=>e.status===`failed`).length||0,ie=a?.comparisons?.filter(e=>ho(e.status)).length||0;return D?(0,P.jsx)(Mi,{icon:It,title:`No Screenshots Yet`,description:`Run your tests with vizzlyScreenshot() to start capturing visual comparisons.`}):(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(jo,{filter:f,setFilter:p,sortBy:m,setSortBy:h,searchQuery:g,setSearchQuery:_,selectedBrowser:v,setSelectedBrowser:y,selectedViewport:b,setSelectedViewport:x,availableFilters:S,counts:ee,onRefresh:s,loading:o}),k&&(0,P.jsx)(No,{failedCount:A,newCount:ie,onAcceptAll:E,isLoading:c.isPending}),d.length===0?(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{className:`py-12`,children:(0,P.jsx)(Mi,{icon:xn,title:`No matches`,description:`No comparisons match your current filters.`,action:O&&(0,P.jsx)(L,{variant:`ghost`,size:`sm`,onClick:()=>{p(`all`),_(``),y(`all`),x(`all`)},children:`Clear all filters`})})})}):(0,P.jsx)(Eo,{comparisons:d,onSelectComparison:C,onAcceptComparison:T,onRejectComparison:re,onAcceptGroup:te,onRejectGroup:ne,loadingStates:r})]})}function Fo(e={}){return at({queryKey:F.config,queryFn:Jr.get,...e})}function Io(){let e=We();return ot({mutationFn:Jr.updateProject,onSuccess:()=>{e.invalidateQueries({queryKey:F.config})}})}function Lo(e){return{threshold:String(e?.comparison?.threshold??2),port:String(e?.server?.port??47392),timeout:String(e?.server?.timeout??3e4),buildName:e?.build?.name??`Build {timestamp}`,environment:e?.build?.environment??`test`,openReport:e?.tdd?.openReport??!1}}function Ro(e){if(e==null||String(e).trim()===``)return null;let t=Number(e);return Number.isFinite(t)?t:null}function zo(e){let t=Ro(e);return Number.isInteger(t)?t:null}function Bo(e){let t={threshold:null,port:null,timeout:null},n=Ro(e.threshold);(n===null||n<0)&&(t.threshold=`Threshold must be a number greater than or equal to 0.`);let r=zo(e.port);(r===null||r<1||r>65535)&&(t.port=`Port must be a whole number between 1 and 65535.`);let i=zo(e.timeout);return(i===null||i<0)&&(t.timeout=`Timeout must be a whole number greater than or equal to 0.`),t}function Vo({source:e}){return(0,P.jsx)(Ti,{variant:{default:`default`,project:`info`,global:`purple`,env:`success`,cli:`warning`}[e]||`default`,size:`sm`,children:{default:`Default`,project:`Project`,global:`Global`,env:`Environment`,cli:`CLI Flag`}[e]||e})}function Ho({title:e,source:t,description:n,children:r,noSource:i}){return(0,P.jsxs)(`div`,{className:`space-y-4`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,P.jsx)(`h3`,{className:`text-sm font-semibold text-white uppercase tracking-wide`,children:e}),!i&&(0,P.jsx)(Vo,{source:t||`default`})]}),n&&(0,P.jsx)(`p`,{className:`text-sm text-slate-400`,children:n}),(0,P.jsx)(`div`,{className:`space-y-4`,children:r})]})}function Uo({config:e,sources:t,onSave:n,isSaving:r}){let[i,a]=(0,N.useState)(Lo(e)),[o,s]=(0,N.useState)(!1),c=(0,N.useMemo)(()=>Bo(i),[i]),l=Object.values(c).some(Boolean),u=(0,N.useCallback)((e,t)=>{a(n=>({...n,[e]:t})),s(!0)},[]),d=(0,N.useCallback)(()=>{a(Lo(e)),s(!1)},[e]),f=(0,N.useCallback)(()=>{let e=Ro(i.threshold),t=zo(i.port),r=zo(i.timeout);e===null||t===null||r===null||n({comparison:{threshold:e},server:{port:t,timeout:r},build:{name:i.buildName,environment:i.environment},tdd:{openReport:i.openReport}},()=>s(!1))},[i,n]);return(0,P.jsxs)(`div`,{className:`grid grid-cols-1 lg:grid-cols-2 gap-6`,children:[(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsxs)(Ho,{title:`Comparison`,source:t?.comparison,description:`Configure how screenshots are compared`,children:[(0,P.jsx)(B,{label:`Threshold`,type:`number`,value:i.threshold,onChange:e=>u(`threshold`,e.target.value),hint:`CIEDE2000 Delta E. 0 = exact, 2 = recommended`,step:`0.1`,min:`0`}),c.threshold&&(0,P.jsx)(`p`,{className:`-mt-2 text-xs text-red-400`,children:c.threshold})]})})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsxs)(Ho,{title:`Server`,source:t?.server,description:`Local screenshot server configuration`,children:[(0,P.jsx)(B,{label:`Port`,type:`number`,value:i.port,onChange:e=>u(`port`,e.target.value),hint:`Default: 47392`}),c.port&&(0,P.jsx)(`p`,{className:`-mt-2 text-xs text-red-400`,children:c.port}),(0,P.jsx)(B,{label:`Timeout`,type:`number`,value:i.timeout,onChange:e=>u(`timeout`,e.target.value),hint:`Request timeout in milliseconds`}),c.timeout&&(0,P.jsx)(`p`,{className:`-mt-2 text-xs text-red-400`,children:c.timeout})]})})})]}),(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsxs)(Ho,{title:`Build`,source:t?.build,description:`Build naming and environment`,children:[(0,P.jsx)(B,{label:`Name Template`,type:`text`,value:i.buildName,onChange:e=>u(`buildName`,e.target.value),hint:`Use {timestamp} for current time`}),(0,P.jsx)(Pi,{label:`Environment`,value:i.environment,onChange:e=>u(`environment`,e.target.value),options:[{value:`test`,label:`Test`},{value:`development`,label:`Development`},{value:`staging`,label:`Staging`},{value:`production`,label:`Production`}]})]})})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsx)(Ho,{title:`TDD Mode`,source:t?.tdd,description:`Development workflow preferences`,children:(0,P.jsx)(Fi,{label:`Auto-open Report`,description:`Open dashboard in browser when starting TDD mode`,checked:i.openReport,onChange:e=>u(`openReport`,e.target.checked)})})})})]}),(0,P.jsx)(`div`,{className:`lg:col-span-2`,children:(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`div`,{className:`text-sm text-slate-400`,children:l?(0,P.jsx)(`span`,{className:`text-red-400`,children:`Fix validation errors before saving`}):o?(0,P.jsx)(`span`,{className:`text-amber-400`,children:`You have unsaved changes`}):(0,P.jsx)(`span`,{children:`Settings saved to project config`})}),(0,P.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,P.jsx)(L,{variant:`ghost`,onClick:d,disabled:!o||r,children:`Reset`}),(0,P.jsx)(L,{variant:`primary`,onClick:f,loading:r,disabled:!o||l,children:`Save Changes`})]})]})})})]})}function Wo(){let{data:e,isLoading:t,error:n,refetch:r}=Fo(),i=Io(),{addToast:a}=Ja(),o=(0,N.useCallback)((e,t)=>{i.mutate(e,{onSuccess:()=>{t(),a(`Settings saved successfully!`,`success`)},onError:e=>{a(`Failed to save settings: ${e.message}`,`error`)}})},[i,a]);return t?(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(V,{variant:`heading`,className:`w-32 mb-2`}),(0,P.jsx)(V,{variant:`text`,className:`w-64`})]}),(0,P.jsxs)(`div`,{className:`grid grid-cols-1 lg:grid-cols-2 gap-6`,children:[(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-24`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-20`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})})]}),(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-16`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-24`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})})]})]})]}):n?(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white`,children:`Settings`}),(0,P.jsx)(`p`,{className:`text-slate-400 mt-1`,children:`Configure your Vizzly local development server`})]}),(0,P.jsxs)(xi,{variant:`danger`,title:`Failed to load settings`,dismissible:!0,onDismiss:()=>r(),children:[n.message,(0,P.jsx)(L,{variant:`danger`,size:`sm`,onClick:()=>r(),className:`mt-3`,children:`Retry`})]})]}):(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white`,children:`Settings`}),(0,P.jsx)(`p`,{className:`text-slate-400 mt-1`,children:`Configure your Vizzly local development server`})]}),(0,P.jsx)(Uo,{config:e?.config,sources:e?.sources,onSave:o,isSaving:i.isPending},JSON.stringify(e?.config))]})}function Go({icon:e,label:t,value:n,subvalue:r,variant:i,iconColor:a}){return(0,P.jsx)(R,{variant:i,hover:!1,children:(0,P.jsx)(ki,{padding:`p-5`,children:(0,P.jsxs)(`div`,{className:`flex items-start justify-between`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`p`,{className:`text-xs font-medium text-slate-500 uppercase tracking-wide mb-1`,children:t}),(0,P.jsx)(`p`,{className:`text-2xl font-semibold font-mono text-white`,children:n}),r&&(0,P.jsx)(`p`,{className:`text-xs text-slate-400 mt-1`,children:r})]}),(0,P.jsx)(`div`,{className:`w-10 h-10 rounded-lg flex items-center justify-center ${a}`,children:(0,P.jsx)(e,{className:`w-5 h-5`})})]})})})}function Ko(){let{addToast:e,confirm:t}=Ja(),{data:n,isLoading:r,refetch:i}=di(),a=pi(),o=mi(),s=n?.comparisons,c=n?.baseline,l=s?.length||0,u=s?.filter(e=>e.status===`passed`).length||0,d=s?.filter(e=>e.status===`failed`).length||0,f=s?.filter(e=>ho(e.status)).length||0,p=l>0?Math.round(u/l*100):0,m=s?.some(e=>go(e.status)),h=(0,N.useCallback)(async()=>{await t(`This will update all failed and new screenshots.`,`Accept all changes as new baselines?`)&&a.mutate(void 0,{onSuccess:()=>{e(`All baselines accepted successfully`,`success`)},onError:t=>{console.error(`Failed to accept all baselines:`,t),e(`Failed to accept all baselines. Please try again.`,`error`)}})},[a,e,t]),g=(0,N.useCallback)(async()=>{await t(`This will delete all baseline images and clear comparison history.`,`Reset all baselines?`)&&o.mutate(void 0,{onSuccess:()=>{e(`Baselines reset successfully`,`success`)},onError:t=>{console.error(`Failed to reset baselines:`,t),e(`Failed to reset baselines. Please try again.`,`error`)}})},[o,e,t]);return(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white`,children:`Statistics`}),(0,P.jsx)(`p`,{className:`text-slate-400 mt-1`,children:`Visual regression testing overview`})]}),(0,P.jsxs)(`div`,{className:`grid grid-cols-1 lg:grid-cols-3 gap-4`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`flex flex-col items-center justify-center py-8`,children:[(0,P.jsx)(Ii,{value:p,label:`Pass Rate`}),(0,P.jsxs)(`p`,{className:`text-sm text-slate-400 mt-4`,children:[u,` of `,l,` screenshots passing`]})]})}),(0,P.jsxs)(`div`,{className:`lg:col-span-2 grid grid-cols-2 gap-4`,children:[(0,P.jsx)(Go,{icon:zn,label:`Total Screenshots`,value:l,iconColor:`bg-slate-700/50 text-slate-400`}),(0,P.jsx)(Go,{icon:Ht,label:`Passed`,value:u,variant:`success`,iconColor:`bg-emerald-500/10 text-emerald-400`}),(0,P.jsx)(Go,{icon:hn,label:`Failed`,value:d,variant:d>0?`danger`:void 0,iconColor:`bg-red-500/10 text-red-400`}),(0,P.jsx)(Go,{icon:Vn,label:`New`,value:f,variant:f>0?`info`:void 0,iconColor:`bg-blue-500/10 text-blue-400`})]})]}),c&&(0,P.jsxs)(R,{hover:!1,children:[(0,P.jsx)(Oi,{icon:Qt,title:`Current Baseline`,iconColor:`bg-amber-500/10 text-amber-400`,actions:f>0&&(0,P.jsxs)(Ti,{variant:`warning`,dot:!0,children:[f,` new without baseline`]})}),(0,P.jsx)(ki,{children:(0,P.jsxs)(`div`,{className:`grid grid-cols-2 gap-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`p`,{className:`text-xs font-medium text-slate-500 uppercase tracking-wide mb-1`,children:`Build Name`}),(0,P.jsx)(`p`,{className:`text-white font-medium`,children:c.buildName||`default`})]}),(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`p`,{className:`text-xs font-medium text-slate-500 uppercase tracking-wide mb-1`,children:`Created`}),(0,P.jsx)(`p`,{className:`text-white font-medium`,children:c.createdAt?new Date(c.createdAt).toLocaleString():`Unknown`})]})]})})]}),(0,P.jsxs)(R,{hover:!1,children:[(0,P.jsx)(Oi,{title:`Baseline Management`,description:`Accept changes to update baselines or reset to restore previous state`}),(0,P.jsxs)(Ai,{className:`flex flex-wrap gap-3`,children:[(0,P.jsx)(L,{variant:`success`,onClick:h,loading:a.isPending,disabled:!m||r,icon:Ht,children:m?`Accept All Changes`:`No Changes to Accept`}),(0,P.jsx)(L,{variant:`secondary`,onClick:g,loading:o.isPending,disabled:r,icon:vt,children:`Reset Baselines`}),(0,P.jsx)(L,{variant:`ghost`,onClick:()=>i(),loading:r,icon:vt,className:`ml-auto`,children:`Refresh`})]})]})]})}function qo(e,t){let n=[];if(t===`shell`)return e.split(`
218
+ `,children:a?(0,P.jsx)(hn,{className:`w-5 h-5`}):(0,P.jsx)(Vn,{className:`w-5 h-5`})}),(0,P.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-3 flex-wrap`,children:[e>0&&(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-red-500/20 text-red-400 text-sm font-medium`,children:[(0,P.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-red-400`}),e,` changed`]}),t>0&&(0,P.jsxs)(`span`,{className:`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md bg-blue-500/20 text-blue-400 text-sm font-medium`,children:[(0,P.jsx)(`span`,{className:`w-1.5 h-1.5 rounded-full bg-blue-400`}),t,` new`]})]}),(0,P.jsx)(`p`,{className:`text-sm text-slate-400 mt-1`,children:a?`Review visual differences and accept to update baselines`:`New screenshots ready to be saved as baselines`})]}),(0,P.jsxs)(L,{variant:`success`,onClick:n,loading:r,icon:Bt,className:`flex-shrink-0`,"data-testid":`btn-accept-all`,children:[`Accept All (`,i,`)`]})]})]})}function Fo(){let[,e]=Fr(),{addToast:t,confirm:n}=Ja(),[r,i]=(0,N.useState)({}),{data:a,isLoading:o,refetch:s}=di(),c=pi(),l=gi(),u=_i(),{filteredComparisons:d,filter:f,setFilter:p,sortBy:m,setSortBy:h,searchQuery:g,setSearchQuery:_,selectedBrowser:v,setSelectedBrowser:y,selectedViewport:b,setSelectedViewport:x,availableFilters:S,counts:ee}=vo(a?.comparisons),C=(0,N.useCallback)(t=>{let n=t.id||t.signature||t.name;n&&e(`/comparison/${encodeURIComponent(n)}`)},[e]),w=(0,N.useCallback)((e,t)=>{i(n=>{let r={...n};for(let n of e)n&&(t?r[n]=t:delete r[n]);return r})},[]),te=(0,N.useCallback)(e=>{!e||e.length===0||(w(e,`accepting`),l.mutate(e,{onSuccess:()=>{w(e,`accepted`)},onError:n=>{let r=No(n?.succeededIds),i=No(n?.failedIds);r.length>0&&w(r,`accepted`),i.length>0?w(i,null):w(e,null),t(`Failed to accept: ${n.message}`,`error`)}}))},[l,t,w]),ne=(0,N.useCallback)(e=>{!e||e.length===0||(w(e,`rejecting`),u.mutate(e,{onSuccess:()=>{w(e,`rejected`)},onError:n=>{let r=No(n?.succeededIds),i=No(n?.failedIds);r.length>0&&w(r,`rejected`),i.length>0?w(i,null):w(e,null),t(`Failed to reject: ${n.message}`,`error`)}}))},[u,t,w]),T=(0,N.useCallback)(e=>te(e?[e]:[]),[te]),re=(0,N.useCallback)(e=>ne(e?[e]:[]),[ne]),E=(0,N.useCallback)(async()=>{await n(`This will update all failed and new screenshots.`,`Accept all changes as new baselines?`)&&c.mutate(void 0,{onSuccess:()=>{t(`All baselines accepted successfully`,`success`)},onError:e=>{console.error(`Failed to accept all baselines:`,e),t(`Failed to accept all baselines. Please try again.`,`error`)}})},[c,t,n]),D=!a?.comparisons||a.comparisons.length===0,O=f!==`all`||g.trim()||v!==`all`||b!==`all`,k=a?.comparisons?.some(e=>go(e.status)),A=a?.comparisons?.filter(e=>e.status===`failed`).length||0,ie=a?.comparisons?.filter(e=>ho(e.status)).length||0;return D?(0,P.jsx)(Mi,{icon:It,title:`No Screenshots Yet`,description:`Run your tests with vizzlyScreenshot() to start capturing visual comparisons.`}):(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(Mo,{filter:f,setFilter:p,sortBy:m,setSortBy:h,searchQuery:g,setSearchQuery:_,selectedBrowser:v,setSelectedBrowser:y,selectedViewport:b,setSelectedViewport:x,availableFilters:S,counts:ee,onRefresh:s,loading:o}),k&&(0,P.jsx)(Po,{failedCount:A,newCount:ie,onAcceptAll:E,isLoading:c.isPending}),d.length===0?(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{className:`py-12`,children:(0,P.jsx)(Mi,{icon:xn,title:`No matches`,description:`No comparisons match your current filters.`,action:O&&(0,P.jsx)(L,{variant:`ghost`,size:`sm`,onClick:()=>{p(`all`),_(``),y(`all`),x(`all`)},children:`Clear all filters`})})})}):(0,P.jsx)(Eo,{comparisons:d,onSelectComparison:C,onAcceptComparison:T,onRejectComparison:re,onAcceptGroup:te,onRejectGroup:ne,loadingStates:r})]})}function Io(e={}){return at({queryKey:F.config,queryFn:Jr.get,...e})}function Lo(){let e=We();return ot({mutationFn:Jr.updateProject,onSuccess:()=>{e.invalidateQueries({queryKey:F.config})}})}function Ro(e){return{threshold:String(e?.comparison?.threshold??2),port:String(e?.server?.port??47392),timeout:String(e?.server?.timeout??3e4),buildName:e?.build?.name??`Build {timestamp}`,environment:e?.build?.environment??`test`,openReport:e?.tdd?.openReport??!1}}function zo(e){if(e==null||String(e).trim()===``)return null;let t=Number(e);return Number.isFinite(t)?t:null}function Bo(e){let t=zo(e);return Number.isInteger(t)?t:null}function Vo(e){let t={threshold:null,port:null,timeout:null},n=zo(e.threshold);(n===null||n<0)&&(t.threshold=`Threshold must be a number greater than or equal to 0.`);let r=Bo(e.port);(r===null||r<1||r>65535)&&(t.port=`Port must be a whole number between 1 and 65535.`);let i=Bo(e.timeout);return(i===null||i<0)&&(t.timeout=`Timeout must be a whole number greater than or equal to 0.`),t}function Ho({source:e}){return(0,P.jsx)(Ti,{variant:{default:`default`,project:`info`,global:`purple`,env:`success`,cli:`warning`}[e]||`default`,size:`sm`,children:{default:`Default`,project:`Project`,global:`Global`,env:`Environment`,cli:`CLI Flag`}[e]||e})}function Uo({title:e,source:t,description:n,children:r,noSource:i}){return(0,P.jsxs)(`div`,{className:`space-y-4`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,P.jsx)(`h3`,{className:`text-sm font-semibold text-white uppercase tracking-wide`,children:e}),!i&&(0,P.jsx)(Ho,{source:t||`default`})]}),n&&(0,P.jsx)(`p`,{className:`text-sm text-slate-400`,children:n}),(0,P.jsx)(`div`,{className:`space-y-4`,children:r})]})}function Wo({config:e,sources:t,onSave:n,isSaving:r}){let[i,a]=(0,N.useState)(Ro(e)),[o,s]=(0,N.useState)(!1),c=(0,N.useMemo)(()=>Vo(i),[i]),l=Object.values(c).some(Boolean),u=(0,N.useCallback)((e,t)=>{a(n=>({...n,[e]:t})),s(!0)},[]),d=(0,N.useCallback)(()=>{a(Ro(e)),s(!1)},[e]),f=(0,N.useCallback)(()=>{let e=zo(i.threshold),t=Bo(i.port),r=Bo(i.timeout);e===null||t===null||r===null||n({comparison:{threshold:e},server:{port:t,timeout:r},build:{name:i.buildName,environment:i.environment},tdd:{openReport:i.openReport}},()=>s(!1))},[i,n]);return(0,P.jsxs)(`div`,{className:`grid grid-cols-1 lg:grid-cols-2 gap-6`,children:[(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsxs)(Uo,{title:`Comparison`,source:t?.comparison,description:`Configure how screenshots are compared`,children:[(0,P.jsx)(B,{label:`Threshold`,type:`number`,value:i.threshold,onChange:e=>u(`threshold`,e.target.value),hint:`CIEDE2000 Delta E. 0 = exact, 2 = recommended`,step:`0.1`,min:`0`}),c.threshold&&(0,P.jsx)(`p`,{className:`-mt-2 text-xs text-red-400`,children:c.threshold})]})})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsxs)(Uo,{title:`Server`,source:t?.server,description:`Local screenshot server configuration`,children:[(0,P.jsx)(B,{label:`Port`,type:`number`,value:i.port,onChange:e=>u(`port`,e.target.value),hint:`Default: 47392`}),c.port&&(0,P.jsx)(`p`,{className:`-mt-2 text-xs text-red-400`,children:c.port}),(0,P.jsx)(B,{label:`Timeout`,type:`number`,value:i.timeout,onChange:e=>u(`timeout`,e.target.value),hint:`Request timeout in milliseconds`}),c.timeout&&(0,P.jsx)(`p`,{className:`-mt-2 text-xs text-red-400`,children:c.timeout})]})})})]}),(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsxs)(Uo,{title:`Build`,source:t?.build,description:`Build naming and environment`,children:[(0,P.jsx)(B,{label:`Name Template`,type:`text`,value:i.buildName,onChange:e=>u(`buildName`,e.target.value),hint:`Use {timestamp} for current time`}),(0,P.jsx)(Pi,{label:`Environment`,value:i.environment,onChange:e=>u(`environment`,e.target.value),options:[{value:`test`,label:`Test`},{value:`development`,label:`Development`},{value:`staging`,label:`Staging`},{value:`production`,label:`Production`}]})]})})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsx)(ki,{children:(0,P.jsx)(Uo,{title:`TDD Mode`,source:t?.tdd,description:`Development workflow preferences`,children:(0,P.jsx)(Fi,{label:`Auto-open Report`,description:`Open dashboard in browser when starting TDD mode`,checked:i.openReport,onChange:e=>u(`openReport`,e.target.checked)})})})})]}),(0,P.jsx)(`div`,{className:`lg:col-span-2`,children:(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`flex items-center justify-between`,children:[(0,P.jsx)(`div`,{className:`text-sm text-slate-400`,children:l?(0,P.jsx)(`span`,{className:`text-red-400`,children:`Fix validation errors before saving`}):o?(0,P.jsx)(`span`,{className:`text-amber-400`,children:`You have unsaved changes`}):(0,P.jsx)(`span`,{children:`Settings saved to project config`})}),(0,P.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,P.jsx)(L,{variant:`ghost`,onClick:d,disabled:!o||r,children:`Reset`}),(0,P.jsx)(L,{variant:`primary`,onClick:f,loading:r,disabled:!o||l,children:`Save Changes`})]})]})})})]})}function Go(){let{data:e,isLoading:t,error:n,refetch:r}=Io(),i=Lo(),{addToast:a}=Ja(),o=(0,N.useCallback)((e,t)=>{i.mutate(e,{onSuccess:()=>{t(),a(`Settings saved successfully!`,`success`)},onError:e=>{a(`Failed to save settings: ${e.message}`,`error`)}})},[i,a]);return t?(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(V,{variant:`heading`,className:`w-32 mb-2`}),(0,P.jsx)(V,{variant:`text`,className:`w-64`})]}),(0,P.jsxs)(`div`,{className:`grid grid-cols-1 lg:grid-cols-2 gap-6`,children:[(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-24`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-20`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})})]}),(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-16`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})}),(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`space-y-4`,children:[(0,P.jsx)(V,{variant:`text`,className:`w-24`}),(0,P.jsx)(V,{variant:`text`,className:`w-full h-10`})]})})]})]})]}):n?(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white`,children:`Settings`}),(0,P.jsx)(`p`,{className:`text-slate-400 mt-1`,children:`Configure your Vizzly local development server`})]}),(0,P.jsxs)(xi,{variant:`danger`,title:`Failed to load settings`,dismissible:!0,onDismiss:()=>r(),children:[n.message,(0,P.jsx)(L,{variant:`danger`,size:`sm`,onClick:()=>r(),className:`mt-3`,children:`Retry`})]})]}):(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white`,children:`Settings`}),(0,P.jsx)(`p`,{className:`text-slate-400 mt-1`,children:`Configure your Vizzly local development server`})]}),(0,P.jsx)(Wo,{config:e?.config,sources:e?.sources,onSave:o,isSaving:i.isPending},JSON.stringify(e?.config))]})}function Ko({icon:e,label:t,value:n,subvalue:r,variant:i,iconColor:a}){return(0,P.jsx)(R,{variant:i,hover:!1,children:(0,P.jsx)(ki,{padding:`p-5`,children:(0,P.jsxs)(`div`,{className:`flex items-start justify-between`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`p`,{className:`text-xs font-medium text-slate-500 uppercase tracking-wide mb-1`,children:t}),(0,P.jsx)(`p`,{className:`text-2xl font-semibold font-mono text-white`,children:n}),r&&(0,P.jsx)(`p`,{className:`text-xs text-slate-400 mt-1`,children:r})]}),(0,P.jsx)(`div`,{className:`w-10 h-10 rounded-lg flex items-center justify-center ${a}`,children:(0,P.jsx)(e,{className:`w-5 h-5`})})]})})})}function qo(){let{addToast:e,confirm:t}=Ja(),{data:n,isLoading:r,refetch:i}=di(),a=pi(),o=mi(),s=n?.comparisons,c=n?.baseline,l=s?.length||0,u=s?.filter(e=>e.status===`passed`).length||0,d=s?.filter(e=>e.status===`failed`).length||0,f=s?.filter(e=>ho(e.status)).length||0,p=l>0?Math.round(u/l*100):0,m=s?.some(e=>go(e.status)),h=(0,N.useCallback)(async()=>{await t(`This will update all failed and new screenshots.`,`Accept all changes as new baselines?`)&&a.mutate(void 0,{onSuccess:()=>{e(`All baselines accepted successfully`,`success`)},onError:t=>{console.error(`Failed to accept all baselines:`,t),e(`Failed to accept all baselines. Please try again.`,`error`)}})},[a,e,t]),g=(0,N.useCallback)(async()=>{await t(`This will delete all baseline images and clear comparison history.`,`Reset all baselines?`)&&o.mutate(void 0,{onSuccess:()=>{e(`Baselines reset successfully`,`success`)},onError:t=>{console.error(`Failed to reset baselines:`,t),e(`Failed to reset baselines. Please try again.`,`error`)}})},[o,e,t]);return(0,P.jsxs)(`div`,{className:`space-y-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white`,children:`Statistics`}),(0,P.jsx)(`p`,{className:`text-slate-400 mt-1`,children:`Visual regression testing overview`})]}),(0,P.jsxs)(`div`,{className:`grid grid-cols-1 lg:grid-cols-3 gap-4`,children:[(0,P.jsx)(R,{hover:!1,children:(0,P.jsxs)(ki,{className:`flex flex-col items-center justify-center py-8`,children:[(0,P.jsx)(Ii,{value:p,label:`Pass Rate`}),(0,P.jsxs)(`p`,{className:`text-sm text-slate-400 mt-4`,children:[u,` of `,l,` screenshots passing`]})]})}),(0,P.jsxs)(`div`,{className:`lg:col-span-2 grid grid-cols-2 gap-4`,children:[(0,P.jsx)(Ko,{icon:zn,label:`Total Screenshots`,value:l,iconColor:`bg-slate-700/50 text-slate-400`}),(0,P.jsx)(Ko,{icon:Ht,label:`Passed`,value:u,variant:`success`,iconColor:`bg-emerald-500/10 text-emerald-400`}),(0,P.jsx)(Ko,{icon:hn,label:`Failed`,value:d,variant:d>0?`danger`:void 0,iconColor:`bg-red-500/10 text-red-400`}),(0,P.jsx)(Ko,{icon:Vn,label:`New`,value:f,variant:f>0?`info`:void 0,iconColor:`bg-blue-500/10 text-blue-400`})]})]}),c&&(0,P.jsxs)(R,{hover:!1,children:[(0,P.jsx)(Oi,{icon:Qt,title:`Current Baseline`,iconColor:`bg-amber-500/10 text-amber-400`,actions:f>0&&(0,P.jsxs)(Ti,{variant:`warning`,dot:!0,children:[f,` new without baseline`]})}),(0,P.jsx)(ki,{children:(0,P.jsxs)(`div`,{className:`grid grid-cols-2 gap-6`,children:[(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`p`,{className:`text-xs font-medium text-slate-500 uppercase tracking-wide mb-1`,children:`Build Name`}),(0,P.jsx)(`p`,{className:`text-white font-medium`,children:c.buildName||`default`})]}),(0,P.jsxs)(`div`,{children:[(0,P.jsx)(`p`,{className:`text-xs font-medium text-slate-500 uppercase tracking-wide mb-1`,children:`Created`}),(0,P.jsx)(`p`,{className:`text-white font-medium`,children:c.createdAt?new Date(c.createdAt).toLocaleString():`Unknown`})]})]})})]}),(0,P.jsxs)(R,{hover:!1,children:[(0,P.jsx)(Oi,{title:`Baseline Management`,description:`Accept changes to update baselines or reset to restore previous state`}),(0,P.jsxs)(Ai,{className:`flex flex-wrap gap-3`,children:[(0,P.jsx)(L,{variant:`success`,onClick:h,loading:a.isPending,disabled:!m||r,icon:Ht,children:m?`Accept All Changes`:`No Changes to Accept`}),(0,P.jsx)(L,{variant:`secondary`,onClick:g,loading:o.isPending,disabled:r,icon:vt,children:`Reset Baselines`}),(0,P.jsx)(L,{variant:`ghost`,onClick:()=>i(),loading:r,icon:vt,className:`ml-auto`,children:`Refresh`})]})]})]})}function Jo(e,t){let n=[];if(t===`shell`)return e.split(`
219
219
  `).forEach((t,r)=>{let i=e.split(`
220
220
  `).slice(0,r).join(`
221
- `).length+ +(r>0);t.trim().startsWith(`#`)?n.push({type:`comment`,start:i,end:i+t.length}):(t.trim().startsWith(`npm`)||t.trim().startsWith(`npx`))&&n.push({type:`command`,start:i,end:i+t.length})}),n;for(let{type:t,regex:r}of[{type:`comment`,regex:/\/\/.*$/gm},{type:`comment`,regex:/\/\*[\s\S]*?\*\//g},{type:`string`,regex:/'([^'\\]|\\.)*'/g},{type:`string`,regex:/"([^"\\]|\\.)*"/g},{type:`string`,regex:/`([^`\\]|\\.)*`/g},{type:`keyword`,regex:/\b(import|export|from|const|let|var|function|class|if|else|return|await|async|test|it|describe|expect|func|let|class|import|require|RSpec|do|end|def|Vizzly)\b/g},{type:`function`,regex:/\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g},{type:`number`,regex:/\b\d+\.?\d*\b/g}]){let i=r.exec(e);for(;i!==null;)n.push({type:t,start:i.index,end:i.index+i[0].length}),i=r.exec(e)}n.sort((e,t)=>e.start-t.start);let r=[],i=0;return n.forEach(e=>{e.start>=i&&(r.push(e),i=e.end)}),r}function Jo({code:e,language:t=`javascript`}){let n=(0,N.useRef)(null),r=t===`shell`||e.trim().startsWith(`#`)||e.trim().startsWith(`npm`);return(0,N.useEffect)(()=>{if(typeof window>`u`||!window.CSS?.highlights||!n.current)return;let i=n.current.firstChild;if(!i||i.nodeType!==window.Node.TEXT_NODE)return;let a=qo(e,r?`shell`:t).map(e=>{let t=new window.Range;return t.setStart(i,e.start),t.setEnd(i,e.end),{type:e.type,range:t}}),o=new Map;a.forEach(({type:e,range:t})=>{o.has(e)||o.set(e,[]),o.get(e).push(t)});let s=new Map;for(let[e,t]of o){let n=new window.Highlight(...t);s.set(e,n),window.CSS.highlights.set(`code-${e}`,n)}return()=>{for(let[e]of s)window.CSS.highlights.delete(`code-${e}`)}},[e,t,r]),(0,P.jsx)(`pre`,{className:`bg-slate-950 rounded-lg p-4 overflow-x-auto text-sm`,children:(0,P.jsx)(`code`,{ref:n,className:`font-mono text-slate-300 block whitespace-pre`,children:e})})}var Yo=[{id:`playwright`,name:`Playwright`,code:`import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
221
+ `).length+ +(r>0);t.trim().startsWith(`#`)?n.push({type:`comment`,start:i,end:i+t.length}):(t.trim().startsWith(`npm`)||t.trim().startsWith(`npx`))&&n.push({type:`command`,start:i,end:i+t.length})}),n;for(let{type:t,regex:r}of[{type:`comment`,regex:/\/\/.*$/gm},{type:`comment`,regex:/\/\*[\s\S]*?\*\//g},{type:`string`,regex:/'([^'\\]|\\.)*'/g},{type:`string`,regex:/"([^"\\]|\\.)*"/g},{type:`string`,regex:/`([^`\\]|\\.)*`/g},{type:`keyword`,regex:/\b(import|export|from|const|let|var|function|class|if|else|return|await|async|test|it|describe|expect|func|let|class|import|require|RSpec|do|end|def|Vizzly)\b/g},{type:`function`,regex:/\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(/g},{type:`number`,regex:/\b\d+\.?\d*\b/g}]){let i=r.exec(e);for(;i!==null;)n.push({type:t,start:i.index,end:i.index+i[0].length}),i=r.exec(e)}n.sort((e,t)=>e.start-t.start);let r=[],i=0;return n.forEach(e=>{e.start>=i&&(r.push(e),i=e.end)}),r}function Yo(e){return`code-token code-token--${e}`}function Xo(e,t){let n=Jo(e,t),r=[],i=0;return n.forEach((t,n)=>{t.start>i&&r.push(e.slice(i,t.start)),r.push((0,P.jsx)(`span`,{className:Yo(t.type),children:e.slice(t.start,t.end)},`${t.type}-${n}`)),i=t.end}),i<e.length&&r.push(e.slice(i)),r}function Zo({code:e,language:t=`javascript`}){return(0,P.jsx)(`pre`,{className:`bg-slate-950 rounded-lg p-4 overflow-x-auto text-sm`,children:(0,P.jsx)(`code`,{className:`font-mono text-slate-300 block whitespace-pre`,children:Xo(e,t===`shell`||e.trim().startsWith(`#`)||e.trim().startsWith(`npm`)?`shell`:t)})})}var Qo=[{id:`playwright`,name:`Playwright`,code:`import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
222
222
 
223
223
  test('homepage', async ({ page }) => {
224
224
  await page.goto('/');
@@ -267,4 +267,4 @@ class VisualTests: XCTestCase {
267
267
  # Just run the CLI against your Storybook build:
268
268
 
269
269
  npm run build-storybook
270
- npx vizzly storybook ./storybook-static`}];function Xo(){let[e,t]=(0,N.useState)(`playwright`),n=Yo.find(t=>t.id===e);return(0,P.jsx)(`div`,{className:`flex flex-col items-center justify-center py-12 px-6`,children:(0,P.jsxs)(`div`,{className:`max-w-2xl w-full`,children:[(0,P.jsxs)(`div`,{className:`text-center mb-8`,children:[(0,P.jsx)(`div`,{className:`w-16 h-16 rounded-2xl bg-amber-500/10 flex items-center justify-center mx-auto mb-5`,children:(0,P.jsx)(Qt,{className:`w-8 h-8 text-amber-400`})}),(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white mb-2`,children:`Waiting for Screenshots`}),(0,P.jsx)(`p`,{className:`text-slate-400`,children:`Run your tests to start capturing visual comparisons`}),(0,P.jsxs)(`div`,{className:`mt-4 flex items-center justify-center gap-2 text-sm text-slate-500`,children:[(0,P.jsx)(`div`,{className:`w-2 h-2 rounded-full bg-amber-500 animate-pulse`}),`Listening for screenshots...`]})]}),(0,P.jsxs)(`div`,{className:`vz-card overflow-hidden`,children:[(0,P.jsx)(`div`,{className:`border-b border-slate-700/50 px-4 pt-4`,children:(0,P.jsx)(`div`,{className:`flex items-center gap-1 overflow-x-auto scrollbar-hide -mb-px`,children:Yo.map(n=>(0,P.jsx)(`button`,{type:`button`,onClick:()=>t(n.id),className:`px-4 py-2.5 text-sm font-medium rounded-t-lg transition-colors whitespace-nowrap ${e===n.id?`bg-slate-800 text-white border-t border-x border-slate-700/50`:`text-slate-400 hover:text-white hover:bg-slate-800/50`}`,children:n.name},n.id))})}),(0,P.jsx)(`div`,{className:`p-4`,children:(0,P.jsx)(Jo,{code:n?.code||``})})]}),(0,P.jsx)(`div`,{className:`mt-6 text-center`,children:(0,P.jsxs)(`a`,{href:`https://docs.vizzly.dev/`,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-2 text-sm text-slate-400 hover:text-amber-400 transition-colors`,children:[(0,P.jsx)(Mt,{className:`w-4 h-4`}),`View full documentation`]})})]})})}function Zo(){return(0,P.jsxs)(`div`,{className:`flex flex-col items-center justify-center py-32`,children:[(0,P.jsx)(Sa,{size:`lg`,className:`text-amber-400 mb-4`}),(0,P.jsx)(`p`,{className:`text-slate-400 text-sm`,children:`Loading report data...`})]})}function Qo({error:e,onRetry:t}){return(0,P.jsx)(Mi,{icon:hn,title:`Failed to load report`,description:e||`An unexpected error occurred while loading the report data.`,action:(0,P.jsx)(`button`,{type:`button`,onClick:t,className:`px-4 py-2 bg-amber-500 hover:bg-amber-400 text-slate-900 font-medium rounded-lg transition-colors`,children:`Try Again`})})}function $o(){let[e,t]=Fr(),n=e===`/settings`||e===`/builds`,{data:r,isLoading:i,error:a,refetch:o}=di({enabled:!n,polling:!n}),s=e.startsWith(`/comparison/`),c=e===`/stats`?`stats`:e===`/settings`?`settings`:e===`/builds`?`builds`:`comparisons`,l=e=>{t(e===`stats`?`/stats`:e===`settings`?`/settings`:e===`builds`?`/builds`:`/`)};return i&&!r&&!n?(0,P.jsx)(za,{currentView:c,onNavigate:l,loading:i,children:(0,P.jsx)(Zo,{})}):a&&!r&&!n?(0,P.jsx)(za,{currentView:c,onNavigate:l,loading:i,children:(0,P.jsx)(Qo,{error:a.message,onRetry:o})}):!r&&!n?(0,P.jsx)(za,{currentView:c,onNavigate:l,loading:i,children:(0,P.jsx)(Xo,{})}):s?r?(0,P.jsx)(Ur,{children:(0,P.jsx)(Vr,{path:`/comparison/:id`,children:(0,P.jsx)(fo,{})})}):(0,P.jsx)(za,{currentView:`comparisons`,onNavigate:l,loading:i,children:(0,P.jsx)(Xo,{})}):(0,P.jsx)(za,{currentView:c,onNavigate:l,loading:i,children:(0,P.jsxs)(Ur,{children:[(0,P.jsx)(Vr,{path:`/stats`,children:r?(0,P.jsx)(Ko,{}):(0,P.jsx)(Xo,{})}),(0,P.jsx)(Vr,{path:`/settings`,children:(0,P.jsx)(Wo,{})}),(0,P.jsx)(Vr,{path:`/builds`,children:(0,P.jsx)(io,{})}),(0,P.jsx)(Vr,{path:`/`,children:r?(0,P.jsx)(Po,{}):(0,P.jsx)(Xo,{})})]})})}var es=new Re({defaultOptions:{queries:{staleTime:5*1e3,refetchOnWindowFocus:!0,retry:1}}}),ts=()=>{let e=document.getElementById(`vizzly-reporter-root`);e||(e=document.createElement(`div`),e.id=`vizzly-reporter-root`,document.body.appendChild(e)),nr.createRoot(e).render((0,P.jsx)(N.StrictMode,{children:(0,P.jsx)(Ge,{client:es,children:(0,P.jsx)($r,{children:(0,P.jsx)(qa,{children:(0,P.jsx)($o,{})})})})}))};document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,ts):ts()})();
270
+ npx vizzly storybook ./storybook-static`}];function $o(){let[e,t]=(0,N.useState)(`playwright`),n=Qo.find(t=>t.id===e);return(0,P.jsx)(`div`,{className:`flex flex-col items-center justify-center py-12 px-6`,children:(0,P.jsxs)(`div`,{className:`max-w-2xl w-full`,children:[(0,P.jsxs)(`div`,{className:`text-center mb-8`,children:[(0,P.jsx)(`div`,{className:`w-16 h-16 rounded-2xl bg-amber-500/10 flex items-center justify-center mx-auto mb-5`,children:(0,P.jsx)(Qt,{className:`w-8 h-8 text-amber-400`})}),(0,P.jsx)(`h1`,{className:`text-2xl font-bold text-white mb-2`,children:`Waiting for Screenshots`}),(0,P.jsx)(`p`,{className:`text-slate-400`,children:`Run your tests to start capturing visual comparisons`}),(0,P.jsxs)(`div`,{className:`mt-4 flex items-center justify-center gap-2 text-sm text-slate-500`,children:[(0,P.jsx)(`div`,{className:`w-2 h-2 rounded-full bg-amber-500 animate-pulse`}),`Listening for screenshots...`]})]}),(0,P.jsxs)(`div`,{className:`vz-card overflow-hidden`,children:[(0,P.jsx)(`div`,{className:`border-b border-slate-700/50 px-4 pt-4`,children:(0,P.jsx)(`div`,{className:`flex items-center gap-1 overflow-x-auto scrollbar-hide -mb-px`,children:Qo.map(n=>(0,P.jsx)(`button`,{type:`button`,onClick:()=>t(n.id),className:`px-4 py-2.5 text-sm font-medium rounded-t-lg transition-colors whitespace-nowrap ${e===n.id?`bg-slate-800 text-white border-t border-x border-slate-700/50`:`text-slate-400 hover:text-white hover:bg-slate-800/50`}`,children:n.name},n.id))})}),(0,P.jsx)(`div`,{className:`p-4`,children:(0,P.jsx)(Zo,{code:n?.code||``})})]}),(0,P.jsx)(`div`,{className:`mt-6 text-center`,children:(0,P.jsxs)(`a`,{href:`https://docs.vizzly.dev/`,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-2 text-sm text-slate-400 hover:text-amber-400 transition-colors`,children:[(0,P.jsx)(Mt,{className:`w-4 h-4`}),`View full documentation`]})})]})})}function es(){return(0,P.jsxs)(`div`,{className:`flex flex-col items-center justify-center py-32`,children:[(0,P.jsx)(Sa,{size:`lg`,className:`text-amber-400 mb-4`}),(0,P.jsx)(`p`,{className:`text-slate-400 text-sm`,children:`Loading report data...`})]})}function ts({error:e,onRetry:t}){return(0,P.jsx)(Mi,{icon:hn,title:`Failed to load report`,description:e||`An unexpected error occurred while loading the report data.`,action:(0,P.jsx)(`button`,{type:`button`,onClick:t,className:`px-4 py-2 bg-amber-500 hover:bg-amber-400 text-slate-900 font-medium rounded-lg transition-colors`,children:`Try Again`})})}function ns(){let[e,t]=Fr(),n=e===`/settings`||e===`/builds`,{data:r,isLoading:i,error:a,refetch:o}=di({enabled:!n,polling:!n}),s=e.startsWith(`/comparison/`),c=e===`/stats`?`stats`:e===`/settings`?`settings`:e===`/builds`?`builds`:`comparisons`,l=e=>{t(e===`stats`?`/stats`:e===`settings`?`/settings`:e===`builds`?`/builds`:`/`)};return i&&!r&&!n?(0,P.jsx)(za,{currentView:c,onNavigate:l,loading:i,children:(0,P.jsx)(es,{})}):a&&!r&&!n?(0,P.jsx)(za,{currentView:c,onNavigate:l,loading:i,children:(0,P.jsx)(ts,{error:a.message,onRetry:o})}):!r&&!n?(0,P.jsx)(za,{currentView:c,onNavigate:l,loading:i,children:(0,P.jsx)($o,{})}):s?r?(0,P.jsx)(Ur,{children:(0,P.jsx)(Vr,{path:`/comparison/:id`,children:(0,P.jsx)(fo,{})})}):(0,P.jsx)(za,{currentView:`comparisons`,onNavigate:l,loading:i,children:(0,P.jsx)($o,{})}):(0,P.jsx)(za,{currentView:c,onNavigate:l,loading:i,children:(0,P.jsxs)(Ur,{children:[(0,P.jsx)(Vr,{path:`/stats`,children:r?(0,P.jsx)(qo,{}):(0,P.jsx)($o,{})}),(0,P.jsx)(Vr,{path:`/settings`,children:(0,P.jsx)(Go,{})}),(0,P.jsx)(Vr,{path:`/builds`,children:(0,P.jsx)(io,{})}),(0,P.jsx)(Vr,{path:`/`,children:r?(0,P.jsx)(Fo,{}):(0,P.jsx)($o,{})})]})})}var rs=new Re({defaultOptions:{queries:{staleTime:5*1e3,refetchOnWindowFocus:!0,retry:1}}}),is=()=>{let e=document.getElementById(`vizzly-reporter-root`);e||(e=document.createElement(`div`),e.id=`vizzly-reporter-root`,document.body.appendChild(e)),nr.createRoot(e).render((0,P.jsx)(N.StrictMode,{children:(0,P.jsx)(Ge,{client:rs,children:(0,P.jsx)($r,{children:(0,P.jsx)(qa,{children:(0,P.jsx)(ns,{})})})})}))};document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,is):is()})();
package/dist/sdk/index.js CHANGED
@@ -11,13 +11,15 @@
11
11
  */
12
12
 
13
13
  import { EventEmitter } from 'node:events';
14
+ import { createServer } from 'node:http';
14
15
  import { VizzlyError } from '../errors/vizzly-error.js';
15
- import { ScreenshotServer } from '../services/screenshot-server.js';
16
- import { createUploader } from '../services/uploader.js';
16
+ import { handleRequest as handleScreenshotRequest, startServer as startScreenshotServer, stopServer as stopScreenshotServer } from '../screenshot-server/index.js';
17
17
  import { createTDDService } from '../tdd/tdd-service.js';
18
+ import { createUploader } from '../uploader/index.js';
18
19
  import { loadConfig } from '../utils/config-loader.js';
19
20
  import { resolveImageBuffer } from '../utils/file-helpers.js';
20
21
  import * as output from '../utils/output.js';
22
+ import { createScreenshotProperties } from '../utils/screenshot-options.js';
21
23
 
22
24
  /**
23
25
  * Create a new Vizzly instance with custom configuration
@@ -52,79 +54,94 @@ import * as output from '../utils/output.js';
52
54
  * // Cleanup
53
55
  * await vizzly.stop();
54
56
  */
55
- export function createVizzly(config = {}, options = {}) {
57
+ export async function createVizzly(config = {}, options = {}) {
56
58
  // Configure output based on options
57
59
  output.configure({
58
60
  verbose: options.verbose || false
59
61
  });
60
-
61
- // Merge with loaded config
62
- const resolvedConfig = {
62
+ let loadConfigFn = options.loadConfig || loadConfig;
63
+ let fileConfig = await loadConfigFn();
64
+ let resolvedConfig = {
65
+ ...fileConfig,
63
66
  ...config
64
67
  };
65
-
66
- /**
67
- * Initialize SDK with config loading
68
- */
69
- const init = async () => {
70
- const fileConfig = await loadConfig();
71
- Object.assign(resolvedConfig, fileConfig, config); // CLI config takes precedence
72
- return resolvedConfig;
73
- };
74
-
75
- /**
76
- * Create uploader service
77
- */
78
- const createUploaderService = (uploaderOptions = {}) => {
79
- return createUploader({
80
- apiKey: resolvedConfig.apiKey,
81
- apiUrl: resolvedConfig.apiUrl
82
- }, {
83
- ...options,
84
- ...uploaderOptions
85
- });
86
- };
87
-
88
- /**
89
- * Create TDD service
90
- */
91
- const createTDDServiceInstance = (tddOptions = {}) => {
92
- return createTDDService(resolvedConfig, {
93
- ...options,
94
- ...tddOptions
95
- });
96
- };
97
-
98
- /**
99
- * Upload screenshots (convenience method)
100
- */
101
- const upload = async uploadOptions => {
102
- const uploader = createUploaderService();
103
- return uploader.upload(uploadOptions);
68
+ return new VizzlySDK(resolvedConfig, {
69
+ createUploader: (uploaderOptions = {}, activeConfig = resolvedConfig) => {
70
+ let {
71
+ upload: uploadConfig = activeConfig.upload,
72
+ ...serviceOptions
73
+ } = uploaderOptions;
74
+ let createUploaderService = options.createUploader || createUploader;
75
+ return createUploaderService({
76
+ apiKey: activeConfig.apiKey,
77
+ apiUrl: activeConfig.apiUrl,
78
+ upload: uploadConfig
79
+ }, {
80
+ ...options,
81
+ ...serviceOptions
82
+ });
83
+ },
84
+ createTDDService: (tddOptions = {}, activeConfig = resolvedConfig) => {
85
+ let createTDDServiceInstance = options.createTDDService || createTDDService;
86
+ return createTDDServiceInstance(activeConfig, {
87
+ ...options,
88
+ ...tddOptions
89
+ });
90
+ },
91
+ createScreenshotServer: createLocalScreenshotServer,
92
+ fetch: options.fetch,
93
+ loadConfig: loadConfigFn
94
+ });
95
+ }
96
+ function createLocalBuildManager() {
97
+ let screenshots = new Map();
98
+ return {
99
+ async addScreenshot(buildId, screenshot) {
100
+ if (!screenshots.has(buildId)) {
101
+ screenshots.set(buildId, []);
102
+ }
103
+ screenshots.get(buildId).push(screenshot);
104
+ },
105
+ getScreenshots(buildId) {
106
+ return screenshots.get(buildId) || [];
107
+ }
104
108
  };
105
-
106
- /**
107
- * Start TDD mode (convenience method)
108
- */
109
- const startTDD = async (tddOptions = {}) => {
110
- const tddService = createTDDServiceInstance();
111
- return tddService.start(tddOptions);
109
+ }
110
+ function createLocalScreenshotServer(config, buildManager, options = {}) {
111
+ let server = null;
112
+ let deps = {
113
+ createHttpServer: options.createHttpServer || createServer,
114
+ output: options.output || output,
115
+ createError: options.createError || ((message, code) => new VizzlyError(message, code))
112
116
  };
113
117
  return {
114
- // Core methods
115
- init,
116
- upload,
117
- startTDD,
118
- // Service factories
119
- createUploader: createUploaderService,
120
- createTDDService: createTDDServiceInstance,
121
- // Utilities
122
- loadConfig: () => loadConfig(),
123
- // Config access
124
- getConfig: () => ({
125
- ...resolvedConfig
126
- }),
127
- updateConfig: newConfig => Object.assign(resolvedConfig, newConfig)
118
+ async start() {
119
+ server = await startScreenshotServer({
120
+ config,
121
+ requestHandler: (req, res) => handleScreenshotRequest({
122
+ req,
123
+ res,
124
+ deps: {
125
+ buildManager,
126
+ createError: deps.createError,
127
+ output: deps.output
128
+ }
129
+ }),
130
+ deps
131
+ });
132
+ },
133
+ async stop() {
134
+ await stopScreenshotServer({
135
+ server,
136
+ deps: {
137
+ output: deps.output
138
+ }
139
+ });
140
+ server = null;
141
+ },
142
+ isRunning() {
143
+ return Boolean(server?.listening);
144
+ }
128
145
  };
129
146
  }
130
147
 
@@ -153,7 +170,7 @@ export class VizzlySDK extends EventEmitter {
153
170
  constructor(config, services) {
154
171
  super();
155
172
  this.config = config;
156
- this.services = services;
173
+ this.services = services || {};
157
174
  this.server = null;
158
175
  this.currentBuildId = null;
159
176
  }
@@ -181,6 +198,59 @@ export class VizzlySDK extends EventEmitter {
181
198
  };
182
199
  }
183
200
 
201
+ /**
202
+ * Merge new config values into the active SDK config.
203
+ * @param {Object} newConfig - Config updates
204
+ * @returns {Object} Updated config
205
+ */
206
+ updateConfig(newConfig) {
207
+ Object.assign(this.config, newConfig);
208
+ return this.getConfig();
209
+ }
210
+
211
+ /**
212
+ * Reload file config and re-apply current in-memory overrides.
213
+ * @returns {Promise<Object>} Updated config
214
+ */
215
+ async init() {
216
+ let loadConfigFn = this.services.loadConfig || loadConfig;
217
+ let fileConfig = await loadConfigFn();
218
+ let currentConfig = {
219
+ ...this.config
220
+ };
221
+ Object.assign(this.config, fileConfig, currentConfig);
222
+ return this.getConfig();
223
+ }
224
+ createUploader(options = {}) {
225
+ let {
226
+ upload: uploadConfig = this.config.upload,
227
+ ...serviceOptions
228
+ } = options;
229
+ let createUploaderService = this.services.createUploader || ((uploaderOptions, activeConfig = this.config) => {
230
+ let {
231
+ upload: resolvedUploadConfig = activeConfig.upload,
232
+ ...resolvedServiceOptions
233
+ } = uploaderOptions;
234
+ return createUploader({
235
+ apiKey: activeConfig.apiKey,
236
+ apiUrl: activeConfig.apiUrl,
237
+ upload: resolvedUploadConfig
238
+ }, resolvedServiceOptions);
239
+ });
240
+ return createUploaderService({
241
+ upload: uploadConfig,
242
+ ...serviceOptions
243
+ }, this.config);
244
+ }
245
+ createTDDService(options = {}) {
246
+ let createTDDServiceInstance = this.services.createTDDService || ((tddOptions, activeConfig = this.config) => createTDDService(activeConfig, tddOptions));
247
+ return createTDDServiceInstance(options, this.config);
248
+ }
249
+ async startTDD(options = {}) {
250
+ let tddService = this.createTDDService(options);
251
+ return tddService.start(options);
252
+ }
253
+
184
254
  /**
185
255
  * Start the Vizzly server
186
256
  * @returns {Promise<{port: number, url: string}>} Server information
@@ -193,25 +263,12 @@ export class VizzlySDK extends EventEmitter {
193
263
  url: `http://localhost:${this.config.server?.port || 3000}`
194
264
  };
195
265
  }
196
-
197
- // Create a simple build manager for screenshot collection
198
- const buildManager = {
199
- screenshots: new Map(),
200
- currentBuildId: null,
201
- async addScreenshot(buildId, screenshot) {
202
- if (!this.screenshots.has(buildId)) {
203
- this.screenshots.set(buildId, []);
204
- }
205
- this.screenshots.get(buildId).push(screenshot);
206
- },
207
- getScreenshots(buildId) {
208
- return this.screenshots.get(buildId) || [];
209
- }
210
- };
211
- this.server = new ScreenshotServer(this.config, buildManager);
266
+ let createScreenshotServer = this.services.createScreenshotServer || createLocalScreenshotServer;
267
+ let buildManager = createLocalBuildManager();
268
+ this.server = createScreenshotServer(this.config, buildManager);
212
269
  await this.server.start();
213
- const port = this.config.server?.port || 3000;
214
- const serverInfo = {
270
+ let port = this.config.server?.port || 3000;
271
+ let serverInfo = {
215
272
  port,
216
273
  url: `http://localhost:${port}`
217
274
  };
@@ -230,31 +287,32 @@ export class VizzlySDK extends EventEmitter {
230
287
  * @throws {VizzlyError} When file cannot be read due to permissions or I/O errors
231
288
  */
232
289
  async screenshot(name, imageBuffer, options = {}) {
233
- if (!this.server?.isRunning()) {
290
+ if (this.server?.isRunning?.() !== true) {
234
291
  throw new VizzlyError('Server not running. Call start() first.', 'SERVER_NOT_RUNNING');
235
292
  }
236
293
 
237
294
  // Resolve Buffer or file path using shared utility
238
- const buffer = resolveImageBuffer(imageBuffer, 'screenshot');
295
+ let buffer = resolveImageBuffer(imageBuffer, 'screenshot');
239
296
 
240
297
  // Generate or use provided build ID
241
- const buildId = options.buildId || this.currentBuildId || 'default';
298
+ let buildId = options.buildId || this.currentBuildId || 'default';
242
299
  this.currentBuildId = buildId;
243
300
 
244
301
  // Convert Buffer to base64 for JSON transport
245
- const imageBase64 = buffer.toString('base64');
246
- const screenshotData = {
302
+ let imageBase64 = buffer.toString('base64');
303
+ let screenshotData = {
247
304
  buildId,
248
305
  name,
249
306
  image: imageBase64,
250
307
  type: 'base64',
251
- properties: options.properties || {}
308
+ properties: createScreenshotProperties(options)
252
309
  };
253
310
 
254
311
  // POST to the local screenshot server
255
- const serverUrl = `http://localhost:${this.config.server?.port || 3000}`;
312
+ let serverUrl = `http://localhost:${this.config.server?.port || 3000}`;
313
+ let fetchFn = this.services.fetch || fetch;
256
314
  try {
257
- const response = await fetch(`${serverUrl}/screenshot`, {
315
+ let response = await fetchFn(`${serverUrl}/screenshot`, {
258
316
  method: 'POST',
259
317
  headers: {
260
318
  'Content-Type': 'application/json'
@@ -262,7 +320,7 @@ export class VizzlySDK extends EventEmitter {
262
320
  body: JSON.stringify(screenshotData)
263
321
  });
264
322
  if (!response.ok) {
265
- const errorData = await response.json().catch(() => ({
323
+ let errorData = await response.json().catch(() => ({
266
324
  error: 'Unknown error'
267
325
  }));
268
326
  throw new VizzlyError(`Screenshot capture failed: ${errorData.error}`, 'SCREENSHOT_FAILED', {
@@ -295,23 +353,21 @@ export class VizzlySDK extends EventEmitter {
295
353
  async upload(options = {}) {
296
354
  if (!this.services?.uploader) {
297
355
  this.services = this.services || {};
298
- this.services.uploader = createUploader({
299
- apiKey: this.config.apiKey,
300
- apiUrl: this.config.apiUrl,
356
+ this.services.uploader = this.createUploader({
301
357
  upload: this.config.upload
302
358
  });
303
359
  }
304
360
 
305
361
  // Get the screenshots directory from config or default
306
- const screenshotsDir = options.screenshotsDir || this.config?.upload?.screenshotsDir || './screenshots';
307
- const uploadOptions = {
362
+ let screenshotsDir = options.screenshotsDir || this.config?.upload?.screenshotsDir || './screenshots';
363
+ let uploadOptions = {
308
364
  screenshotsDir,
309
365
  buildName: options.buildName || this.config.buildName,
310
366
  branch: options.branch || this.config.branch,
311
367
  commit: options.commit || this.config.commit,
312
368
  message: options.message || this.config.message,
313
369
  environment: options.environment || this.config.environment || 'production',
314
- threshold: options.threshold || this.config.threshold,
370
+ threshold: options.threshold ?? this.config.threshold,
315
371
  onProgress: progress => {
316
372
  this.emit('upload:progress', progress);
317
373
  if (options.onProgress) {
@@ -320,7 +376,7 @@ export class VizzlySDK extends EventEmitter {
320
376
  }
321
377
  };
322
378
  try {
323
- const result = await this.services.uploader.upload(uploadOptions);
379
+ let result = await this.services.uploader.upload(uploadOptions);
324
380
  this.emit('upload:completed', result);
325
381
  return result;
326
382
  } catch (error) {
@@ -340,13 +396,13 @@ export class VizzlySDK extends EventEmitter {
340
396
  async compare(name, imageBuffer) {
341
397
  if (!this.services?.tddService) {
342
398
  this.services = this.services || {};
343
- this.services.tddService = createTDDService(this.config);
399
+ this.services.tddService = this.createTDDService();
344
400
  }
345
401
 
346
402
  // Resolve Buffer or file path using shared utility
347
- const buffer = resolveImageBuffer(imageBuffer, 'compare');
403
+ let buffer = resolveImageBuffer(imageBuffer, 'compare');
348
404
  try {
349
- const result = await this.services.tddService.compareScreenshot(name, buffer);
405
+ let result = await this.services.tddService.compareScreenshot(name, buffer);
350
406
  this.emit('comparison:completed', result);
351
407
  return result;
352
408
  } catch (error) {
@@ -358,10 +414,9 @@ export class VizzlySDK extends EventEmitter {
358
414
  }
359
415
  }
360
416
  }
361
-
362
- // Export service creators for advanced usage
363
- export { createUploader } from '../services/uploader.js';
364
417
  export { createTDDService } from '../tdd/tdd-service.js';
418
+ // Export service creators for advanced usage
419
+ export { createUploader } from '../uploader/index.js';
365
420
  // Re-export key utilities and errors
366
421
  export { loadConfig } from '../utils/config-loader.js';
367
422
  export * as output from '../utils/output.js';