@primestyleai/tryon 5.10.194 → 5.10.196

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.
@@ -9321,53 +9321,6 @@ reactJsxRuntime_production_min.jsxs = q;
9321
9321
  jsxRuntime.exports = reactJsxRuntime_production_min;
9322
9322
  }
9323
9323
  var jsxRuntimeExports = jsxRuntime.exports;
9324
- function cx(base, override) {
9325
- return override ? `${base} ${override}` : base;
9326
- }
9327
- const MOBILE_BREAKPOINT = 768;
9328
- function useIsMobile() {
9329
- const [isMobile, setIsMobile] = reactExports.useState(false);
9330
- reactExports.useEffect(() => {
9331
- if (typeof window === "undefined") return;
9332
- const check = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
9333
- check();
9334
- window.addEventListener("resize", check);
9335
- return () => window.removeEventListener("resize", check);
9336
- }, []);
9337
- return isMobile;
9338
- }
9339
- function ErrorView({
9340
- productImage,
9341
- productTitle,
9342
- onManualMeasurements,
9343
- cn,
9344
- t: t2
9345
- }) {
9346
- const isMobile = useIsMobile();
9347
- const RightColumn = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx("ps-tryon-no-chart-content ps-tryon-error-fallback", cn.error), children: [
9348
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-icon ps-tryon-error-fallback-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", width: "44", height: "44", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
9349
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 3l8.5 15H3.5L12 3z" }),
9350
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 9v4" }),
9351
- /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 17h.01" })
9352
- ] }) }),
9353
- /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-no-chart-title", children: t2("We couldn't find a matching size for you") }),
9354
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: cx("ps-tryon-no-chart-msg", cn.errorText), children: t2("Please try the manual measurement screen so we can calculate your fit from your measurements.") }),
9355
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-actions", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: onManualMeasurements, className: cx("ps-tryon-no-chart-cta", cn.submitButton), children: [
9356
- t2("Try manual measurements"),
9357
- " →"
9358
- ] }) })
9359
- ] });
9360
- if (isMobile) {
9361
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr", children: [
9362
- productImage && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-img-col", style: { flex: "0 0 auto", maxHeight: "38vh" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: productImage, alt: productTitle || "", className: "ps-tryon-sr-product-img" }) }),
9363
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-right-col", children: RightColumn })
9364
- ] });
9365
- }
9366
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr-split", children: [
9367
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-img-col", children: productImage && /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: productImage, alt: productTitle || "", className: "ps-tryon-sr-product-img" }) }),
9368
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-right-col ps-tryon-no-chart-right-col", children: RightColumn })
9369
- ] }) });
9370
- }
9371
9324
  const STORAGE_KEY = "ps_session";
9372
9325
  const MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
9373
9326
  const FALLBACK_PREFIX = "ps_mem_";
@@ -9412,6 +9365,14 @@ function writeRecord(record) {
9412
9365
  } catch {
9413
9366
  }
9414
9367
  }
9368
+ function attachReplaySession(sessionId) {
9369
+ if (typeof window === "undefined") return;
9370
+ try {
9371
+ const clarity = window.clarity;
9372
+ if (typeof clarity === "function") clarity("set", STORAGE_KEY, sessionId);
9373
+ } catch {
9374
+ }
9375
+ }
9415
9376
  function getOrCreateSessionId() {
9416
9377
  const now = Date.now();
9417
9378
  const existing = readRecord();
@@ -9419,10 +9380,12 @@ function getOrCreateSessionId() {
9419
9380
  if (now - existing.lastSeenAt > 5 * 60 * 1e3) {
9420
9381
  writeRecord({ ...existing, lastSeenAt: now });
9421
9382
  }
9383
+ attachReplaySession(existing.id);
9422
9384
  return existing.id;
9423
9385
  }
9424
9386
  const fresh = { id: uuid(), issuedAt: now, lastSeenAt: now };
9425
9387
  writeRecord(fresh);
9388
+ attachReplaySession(fresh.id);
9426
9389
  return fresh.id;
9427
9390
  }
9428
9391
  function getDeviceHint() {
@@ -9433,6 +9396,16 @@ function getDeviceHint() {
9433
9396
  return "desktop";
9434
9397
  }
9435
9398
  const DEFAULT_API_URL = "http://localhost:4000";
9399
+ function isShopifyProxyBase(baseUrl) {
9400
+ return /\/apps\/primestyle(?:\/|$)/.test(baseUrl) || /\/api\/proxy(?:\/|$)/.test(baseUrl);
9401
+ }
9402
+ function getShopDomain$2() {
9403
+ if (typeof window === "undefined") return null;
9404
+ const w2 = window;
9405
+ if (w2.Shopify?.shop) return w2.Shopify.shop;
9406
+ const hostname = window.location.hostname;
9407
+ return hostname.endsWith(".myshopify.com") ? hostname : null;
9408
+ }
9436
9409
  class ApiClient {
9437
9410
  constructor(apiKey, apiUrl) {
9438
9411
  this.apiKey = apiKey || void 0;
@@ -9544,8 +9517,69 @@ class ApiClient {
9544
9517
  }
9545
9518
  return res.json();
9546
9519
  }
9547
- getStreamUrl() {
9548
- const streamUrl = `${this.baseUrl}/api/v1/tryon/stream`;
9520
+ async reportEvent(input) {
9521
+ const res = await fetch(`${this.baseUrl}/api/v1/tryon/event`, {
9522
+ method: "POST",
9523
+ headers: this.headers,
9524
+ keepalive: true,
9525
+ body: JSON.stringify({
9526
+ ...input,
9527
+ sessionId: getOrCreateSessionId(),
9528
+ deviceHint: getDeviceHint()
9529
+ })
9530
+ });
9531
+ if (!res.ok) {
9532
+ const data = await res.json().catch(() => ({}));
9533
+ throw new PrimeStyleError(
9534
+ data.message || "Failed to report SDK event",
9535
+ "EVENT_REPORT_FAILED"
9536
+ );
9537
+ }
9538
+ return res.json();
9539
+ }
9540
+ async reportClientError(input) {
9541
+ const res = await fetch(`${this.baseUrl}/api/v1/tryon/client-error`, {
9542
+ method: "POST",
9543
+ headers: this.headers,
9544
+ keepalive: true,
9545
+ body: JSON.stringify({
9546
+ ...input,
9547
+ sessionId: getOrCreateSessionId(),
9548
+ deviceHint: getDeviceHint()
9549
+ })
9550
+ });
9551
+ if (!res.ok) {
9552
+ const data = await res.json().catch(() => ({}));
9553
+ throw new PrimeStyleError(
9554
+ data.message || "Failed to report SDK client error",
9555
+ "CLIENT_ERROR_REPORT_FAILED"
9556
+ );
9557
+ }
9558
+ return res.json();
9559
+ }
9560
+ async reportReplayChunk(input) {
9561
+ const shopifyProxy = isShopifyProxyBase(this.baseUrl);
9562
+ const shopDomain = shopifyProxy ? getShopDomain$2() : null;
9563
+ const url = shopifyProxy && shopDomain ? `${this.baseUrl}/events/replay` : `${this.baseUrl}/api/v1/tryon/replay`;
9564
+ const headers = shopifyProxy && shopDomain ? jsonHeaders() : this.headers;
9565
+ const body = shopifyProxy && shopDomain ? { shopDomain, ...input } : input;
9566
+ const res = await fetch(url, {
9567
+ method: "POST",
9568
+ headers,
9569
+ body: JSON.stringify(body)
9570
+ });
9571
+ if (!res.ok) {
9572
+ const data = await res.json().catch(() => ({}));
9573
+ throw new PrimeStyleError(
9574
+ data.message || "Failed to report SDK replay",
9575
+ "REPLAY_REPORT_FAILED"
9576
+ );
9577
+ }
9578
+ return res.json();
9579
+ }
9580
+ getStreamUrl(jobId) {
9581
+ const streamPath = jobId ? `/api/v1/tryon/stream/${encodeURIComponent(jobId)}` : "/api/v1/tryon/stream";
9582
+ const streamUrl = `${this.baseUrl}${streamPath}`;
9549
9583
  return this.apiKey ? `${streamUrl}?key=${encodeURIComponent(this.apiKey)}` : streamUrl;
9550
9584
  }
9551
9585
  }
@@ -9561,6 +9595,89 @@ class PrimeStyleError extends Error {
9561
9595
  this.code = code;
9562
9596
  }
9563
9597
  }
9598
+ function detectLocale() {
9599
+ if (typeof navigator === "undefined") return "US";
9600
+ const lang = navigator.language || "";
9601
+ const region = lang.split("-")[1]?.toUpperCase();
9602
+ if (region === "GB") return "UK";
9603
+ if (region) return region;
9604
+ const map = {
9605
+ en: "US",
9606
+ ja: "JP",
9607
+ ko: "KR",
9608
+ zh: "CN",
9609
+ fr: "FR",
9610
+ it: "IT",
9611
+ de: "DE",
9612
+ es: "ES",
9613
+ pt: "BR"
9614
+ };
9615
+ return map[lang.split("-")[0].toLowerCase()] || "US";
9616
+ }
9617
+ function getApiKey() {
9618
+ let key = "";
9619
+ try {
9620
+ key = "shopify-proxy";
9621
+ } catch {
9622
+ }
9623
+ return key;
9624
+ }
9625
+ function getApiUrl(override) {
9626
+ if (override) return override;
9627
+ let envUrl = "";
9628
+ try {
9629
+ envUrl = "";
9630
+ } catch {
9631
+ }
9632
+ return envUrl || "http://localhost:4000";
9633
+ }
9634
+ function cx(base, override) {
9635
+ return override ? `${base} ${override}` : base;
9636
+ }
9637
+ const MOBILE_BREAKPOINT = 768;
9638
+ function useIsMobile() {
9639
+ const [isMobile, setIsMobile] = reactExports.useState(false);
9640
+ reactExports.useEffect(() => {
9641
+ if (typeof window === "undefined") return;
9642
+ const check = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
9643
+ check();
9644
+ window.addEventListener("resize", check);
9645
+ return () => window.removeEventListener("resize", check);
9646
+ }, []);
9647
+ return isMobile;
9648
+ }
9649
+ function ErrorView({
9650
+ productImage,
9651
+ productTitle,
9652
+ onManualMeasurements,
9653
+ cn,
9654
+ t: t2
9655
+ }) {
9656
+ const isMobile = useIsMobile();
9657
+ const RightColumn = /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx("ps-tryon-no-chart-content ps-tryon-error-fallback", cn.error), children: [
9658
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-icon ps-tryon-error-fallback-icon", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { viewBox: "0 0 24 24", width: "44", height: "44", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
9659
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 3l8.5 15H3.5L12 3z" }),
9660
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 9v4" }),
9661
+ /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M12 17h.01" })
9662
+ ] }) }),
9663
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h3", { className: "ps-tryon-no-chart-title", children: t2("We couldn't find a matching size for you") }),
9664
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: cx("ps-tryon-no-chart-msg", cn.errorText), children: t2("Please try the manual measurement screen so we can calculate your fit from your measurements.") }),
9665
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-actions", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", onClick: onManualMeasurements, className: cx("ps-tryon-no-chart-cta", cn.submitButton), children: [
9666
+ t2("Try manual measurements"),
9667
+ " →"
9668
+ ] }) })
9669
+ ] });
9670
+ if (isMobile) {
9671
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr", children: [
9672
+ productImage && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-img-col", style: { flex: "0 0 auto", maxHeight: "38vh" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: productImage, alt: productTitle || "", className: "ps-tryon-sr-product-img" }) }),
9673
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-no-chart-right-col", children: RightColumn })
9674
+ ] });
9675
+ }
9676
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr-split", children: [
9677
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-img-col", children: productImage && /* @__PURE__ */ jsxRuntimeExports.jsx("img", { src: productImage, alt: productTitle || "", className: "ps-tryon-sr-product-img" }) }),
9678
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-tryon-sr-right-col ps-tryon-no-chart-right-col", children: RightColumn })
9679
+ ] }) });
9680
+ }
9564
9681
  class SseClient {
9565
9682
  constructor(streamUrl) {
9566
9683
  this.eventSource = null;
@@ -10932,42 +11049,6 @@ function installCartHook() {
10932
11049
  }
10933
11050
  console.log(TAG$1, "attribution hook installed");
10934
11051
  }
10935
- function detectLocale() {
10936
- if (typeof navigator === "undefined") return "US";
10937
- const lang = navigator.language || "";
10938
- const region = lang.split("-")[1]?.toUpperCase();
10939
- if (region === "GB") return "UK";
10940
- if (region) return region;
10941
- const map = {
10942
- en: "US",
10943
- ja: "JP",
10944
- ko: "KR",
10945
- zh: "CN",
10946
- fr: "FR",
10947
- it: "IT",
10948
- de: "DE",
10949
- es: "ES",
10950
- pt: "BR"
10951
- };
10952
- return map[lang.split("-")[0].toLowerCase()] || "US";
10953
- }
10954
- function getApiKey() {
10955
- let key = "";
10956
- try {
10957
- key = "shopify-proxy";
10958
- } catch {
10959
- }
10960
- return key;
10961
- }
10962
- function getApiUrl(override) {
10963
- if (override) return override;
10964
- let envUrl = "";
10965
- try {
10966
- envUrl = "";
10967
- } catch {
10968
- }
10969
- return envUrl || "http://localhost:4000";
10970
- }
10971
11052
  const SESSION_KEY = "primestyle_profile_session";
10972
11053
  const AUTH_MESSAGE_TYPE = "PRIMESTYLE_SDK_AUTH";
10973
11054
  const POPUP_TIMEOUT_MS = 12e4;
@@ -11150,6 +11231,30 @@ async function saveRemoteProfiles(apiUrl, accessToken, profiles, activeProfileId
11150
11231
  });
11151
11232
  return normalizeStore(await parseResponse(response));
11152
11233
  }
11234
+ function shouldUseShopifyProxy(apiUrl) {
11235
+ if (typeof window === "undefined") return false;
11236
+ try {
11237
+ const w2 = window;
11238
+ if (w2.Shopify?.shop) return true;
11239
+ return /\/apps\/primestyle\/?$/.test(apiUrl);
11240
+ } catch {
11241
+ return false;
11242
+ }
11243
+ }
11244
+ function reportDirectSdkEvent(apiUrl, apiKey, body) {
11245
+ if (!apiKey || shouldUseShopifyProxy(apiUrl)) return;
11246
+ void fetch(`${apiUrl}/api/v1/tryon/event`, {
11247
+ method: "POST",
11248
+ headers: jsonHeaders(apiKey),
11249
+ keepalive: true,
11250
+ body: JSON.stringify({
11251
+ ...body,
11252
+ sessionId: getOrCreateSessionId(),
11253
+ deviceHint: getDeviceHint()
11254
+ })
11255
+ }).catch(() => {
11256
+ });
11257
+ }
11153
11258
  let cachedMP = null;
11154
11259
  const MP_CACHE_TTL_MS = 6e4;
11155
11260
  function setCachedMediaPipe(landmarks) {
@@ -11223,6 +11328,13 @@ async function recommendForProduct(input) {
11223
11328
  recommendedSize: cached.recommendedSize,
11224
11329
  fromCache: true
11225
11330
  });
11331
+ reportDirectSdkEvent(apiUrl, apiKey, {
11332
+ eventType: "SIZE_RECOMMENDATION_SHOWN",
11333
+ productId: input.productId,
11334
+ productTitle: input.productTitle,
11335
+ recommendedSize: cached.recommendedSize,
11336
+ metadata: { fromCache: true, profileId: profile.id }
11337
+ });
11226
11338
  const reconstructedRaw = cached.sectionsFull ? {
11227
11339
  recommendedSize: cached.recommendedSize,
11228
11340
  confidence: cached.confidence || "high",
@@ -11434,6 +11546,19 @@ async function recommendForProduct(input) {
11434
11546
  size: result.recommendedSize,
11435
11547
  recommendedSize: result.recommendedSize
11436
11548
  });
11549
+ logSizeShown({
11550
+ productId: input.productId,
11551
+ productTitle: input.productTitle,
11552
+ recommendedSize: result.recommendedSize,
11553
+ fromCache: false
11554
+ });
11555
+ reportDirectSdkEvent(apiUrl, apiKey, {
11556
+ eventType: "SIZE_RECOMMENDATION_SHOWN",
11557
+ productId: input.productId,
11558
+ productTitle: input.productTitle,
11559
+ recommendedSize: result.recommendedSize,
11560
+ metadata: { fromCache: false, profileId: profile.id, found: result.found }
11561
+ });
11437
11562
  return {
11438
11563
  recommendedSize: result.recommendedSize,
11439
11564
  confidence: result.confidence,
@@ -13117,7 +13242,7 @@ const STYLES = `
13117
13242
  }
13118
13243
 
13119
13244
  /* ── Product photo strip (single-garment, below the size card) ──
13120
- Three thumbnails per row, evenly spaced, auto-advances. Lives at
13245
+ Two thumbnails per row, evenly spaced, auto-advances. Lives at
13121
13246
  the bottom of the right panel as decoration / entertainment. */
13122
13247
  .ps-tryon-photo-strip {
13123
13248
  margin-top: 0.65vw;
@@ -13136,17 +13261,16 @@ const STYLES = `
13136
13261
  }
13137
13262
  .ps-tryon-photo-strip-row {
13138
13263
  display: grid;
13139
- grid-template-columns: repeat(3, minmax(0, 1fr));
13264
+ grid-template-columns: repeat(2, minmax(0, 1fr));
13140
13265
  gap: 0.45vw;
13141
13266
  animation: ps-tryon-photo-strip-fade 0.5s ease;
13142
13267
  }
13143
13268
  .ps-tryon-photo-strip-row.ps-count-1 {
13144
- grid-template-columns: minmax(0, calc((100% - 0.9vw) / 3));
13269
+ grid-template-columns: minmax(0, calc((100% - 0.45vw) / 2));
13145
13270
  justify-content: center;
13146
13271
  }
13147
13272
  .ps-tryon-photo-strip-row.ps-count-2 {
13148
- grid-template-columns: repeat(2, minmax(0, calc((100% - 0.9vw) / 3)));
13149
- justify-content: center;
13273
+ grid-template-columns: repeat(2, minmax(0, 1fr));
13150
13274
  }
13151
13275
  .ps-tryon-photo-strip-cell {
13152
13276
  position: relative;
@@ -13207,13 +13331,14 @@ const STYLES = `
13207
13331
  gap: 8px;
13208
13332
  }
13209
13333
  .ps-tryon-photo-strip-row {
13210
- grid-template-columns: repeat(3, minmax(0, 1fr));
13334
+ grid-template-columns: repeat(2, minmax(0, 1fr));
13211
13335
  }
13212
13336
  .ps-tryon-photo-strip-row.ps-count-1 {
13213
- grid-template-columns: minmax(0, calc((100% - 16px) / 3));
13337
+ grid-template-columns: minmax(0, calc((100% - 8px) / 2));
13338
+ justify-content: center;
13214
13339
  }
13215
13340
  .ps-tryon-photo-strip-row.ps-count-2 {
13216
- grid-template-columns: repeat(2, minmax(0, calc((100% - 16px) / 3)));
13341
+ grid-template-columns: repeat(2, minmax(0, 1fr));
13217
13342
  }
13218
13343
  .ps-tryon-photo-strip-cell {
13219
13344
  aspect-ratio: 1 / 1; border-radius: 12px;
@@ -22458,16 +22583,16 @@ const STYLES = `
22458
22583
  }
22459
22584
  .ps-tryon-photo-strip-row {
22460
22585
  display: grid !important;
22461
- grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
22586
+ grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
22462
22587
  gap: 8px !important;
22463
22588
  }
22464
22589
  .ps-tryon-photo-strip-row.ps-count-1 {
22465
- grid-template-columns: minmax(0, calc((100% - 16px) / 3)) !important;
22590
+ grid-template-columns: minmax(0, calc((100% - 8px) / 2)) !important;
22466
22591
  justify-content: center !important;
22467
22592
  }
22468
22593
  .ps-tryon-photo-strip-row.ps-count-2 {
22469
- grid-template-columns: repeat(2, minmax(0, calc((100% - 16px) / 3))) !important;
22470
- justify-content: center !important;
22594
+ grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
22595
+ justify-content: stretch !important;
22471
22596
  }
22472
22597
  .ps-tryon-photo-strip-cell {
22473
22598
  aspect-ratio: 1 / 1 !important;
@@ -23141,6 +23266,232 @@ const STYLES = `
23141
23266
  }
23142
23267
  }
23143
23268
  `;
23269
+ const RRWEB_SCRIPT_URL = "https://cdn.jsdelivr.net/npm/rrweb@2.0.1/dist/rrweb.min.js";
23270
+ const CHUNK_SIZE = 20;
23271
+ const FLUSH_MS = 2500;
23272
+ const MAX_EVENT_JSON_BYTES = 5e6;
23273
+ const MAX_CHUNK_JSON_BYTES = 6e6;
23274
+ let globalSequence = 0;
23275
+ let rrwebLoadPromise = null;
23276
+ function estimateJsonBytes(value) {
23277
+ try {
23278
+ return JSON.stringify(value).length;
23279
+ } catch {
23280
+ return MAX_EVENT_JSON_BYTES + 1;
23281
+ }
23282
+ }
23283
+ function canRecord() {
23284
+ return typeof window !== "undefined" && typeof document !== "undefined";
23285
+ }
23286
+ function getLoadedRrwebRecord() {
23287
+ const rrweb = window.rrweb;
23288
+ return typeof rrweb?.record === "function" ? rrweb.record : null;
23289
+ }
23290
+ function loadRrwebRecord() {
23291
+ const loaded = getLoadedRrwebRecord();
23292
+ if (loaded) return Promise.resolve(loaded);
23293
+ if (rrwebLoadPromise) return rrwebLoadPromise;
23294
+ rrwebLoadPromise = new Promise((resolve) => {
23295
+ const script = document.createElement("script");
23296
+ script.src = RRWEB_SCRIPT_URL;
23297
+ script.async = true;
23298
+ script.crossOrigin = "anonymous";
23299
+ script.onload = () => resolve(getLoadedRrwebRecord());
23300
+ script.onerror = () => resolve(null);
23301
+ document.head.appendChild(script);
23302
+ });
23303
+ return rrwebLoadPromise;
23304
+ }
23305
+ function startSdkReplay(input) {
23306
+ if (!canRecord()) return () => {
23307
+ };
23308
+ const sessionId = getOrCreateSessionId();
23309
+ const queue = [];
23310
+ let stopped = false;
23311
+ let timer = null;
23312
+ let stopRecord;
23313
+ const flush = (all = false) => {
23314
+ if (timer) {
23315
+ window.clearTimeout(timer);
23316
+ timer = null;
23317
+ }
23318
+ if (!queue.length) return;
23319
+ do {
23320
+ const events = [];
23321
+ let chunkBytes = 0;
23322
+ while (queue.length && events.length < CHUNK_SIZE) {
23323
+ const event = queue[0];
23324
+ const eventBytes = estimateJsonBytes(event);
23325
+ if (events.length && chunkBytes + eventBytes > MAX_CHUNK_JSON_BYTES) break;
23326
+ events.push(queue.shift());
23327
+ chunkBytes += eventBytes;
23328
+ if (chunkBytes >= MAX_CHUNK_JSON_BYTES) break;
23329
+ }
23330
+ if (!events.length) events.push(queue.shift());
23331
+ const sequence = globalSequence++;
23332
+ void input.client.reportReplayChunk({
23333
+ sessionId,
23334
+ sequence,
23335
+ events,
23336
+ productId: input.productId,
23337
+ productTitle: input.productTitle,
23338
+ productUrl: input.productUrl,
23339
+ deviceHint: getDeviceHint()
23340
+ }).catch((error) => {
23341
+ console.warn("[ps-sdk:replay] failed to upload replay chunk", error);
23342
+ });
23343
+ } while (all && queue.length);
23344
+ if (queue.length) scheduleFlush(0);
23345
+ };
23346
+ const scheduleFlush = (delay = FLUSH_MS) => {
23347
+ if (stopped || timer) return;
23348
+ timer = window.setTimeout(flush, delay);
23349
+ };
23350
+ void loadRrwebRecord().then((record) => {
23351
+ if (!record || stopped) return;
23352
+ stopRecord = record({
23353
+ emit(event) {
23354
+ if (stopped) return;
23355
+ if (estimateJsonBytes(event) > MAX_EVENT_JSON_BYTES) return;
23356
+ queue.push(event);
23357
+ if (queue.length >= CHUNK_SIZE) flush();
23358
+ else scheduleFlush();
23359
+ },
23360
+ checkoutEveryNms: 1e4,
23361
+ maskAllInputs: false,
23362
+ maskInputOptions: {
23363
+ password: true,
23364
+ email: true,
23365
+ tel: true
23366
+ },
23367
+ blockClass: "ps-replay-block",
23368
+ blockSelector: "body > *:not([data-ps-tryon-portal]):not(.ps-tryon-root)",
23369
+ inlineStylesheet: true,
23370
+ inlineImages: true,
23371
+ collectFonts: false,
23372
+ recordCanvas: false,
23373
+ recordCrossOriginIframes: false,
23374
+ slimDOMOptions: {
23375
+ script: true,
23376
+ comment: true,
23377
+ headFavicon: true,
23378
+ headMetaDescKeywords: true,
23379
+ headMetaSocial: true,
23380
+ headMetaRobots: true,
23381
+ headMetaHttpEquiv: true,
23382
+ headMetaAuthorship: true,
23383
+ headMetaVerification: true
23384
+ },
23385
+ sampling: {
23386
+ mousemove: 80,
23387
+ mouseInteraction: true,
23388
+ scroll: 150,
23389
+ media: 800,
23390
+ input: "last"
23391
+ },
23392
+ errorHandler(error) {
23393
+ console.warn("[ps-sdk:replay] recorder error", error);
23394
+ }
23395
+ });
23396
+ });
23397
+ return () => {
23398
+ stopped = true;
23399
+ if (timer) {
23400
+ window.clearTimeout(timer);
23401
+ timer = null;
23402
+ }
23403
+ try {
23404
+ stopRecord?.();
23405
+ } catch {
23406
+ }
23407
+ flush(true);
23408
+ };
23409
+ }
23410
+ var define_process_env_default = {};
23411
+ const SCRIPT_ID_PREFIX = "ps-clarity-script";
23412
+ const DEFAULT_PROJECT_ID = "wwb75cfm2z";
23413
+ let initializedProjectId = null;
23414
+ function cleanProjectId(value) {
23415
+ const trimmed = String(value || "").trim();
23416
+ if (!trimmed) return null;
23417
+ return /^[a-z0-9]+$/i.test(trimmed) ? trimmed : null;
23418
+ }
23419
+ function readEnvProjectId() {
23420
+ let id2 = "";
23421
+ try {
23422
+ id2 = define_process_env_default.NEXT_PUBLIC_PRIMESTYLE_CLARITY_PROJECT_ID ?? "";
23423
+ } catch {
23424
+ }
23425
+ if (!id2) {
23426
+ try {
23427
+ id2 = define_process_env_default.NEXT_PUBLIC_CLARITY_PROJECT_ID ?? "";
23428
+ } catch {
23429
+ }
23430
+ }
23431
+ return cleanProjectId(id2);
23432
+ }
23433
+ function readWindowProjectId() {
23434
+ if (typeof window === "undefined") return null;
23435
+ const w2 = window;
23436
+ return cleanProjectId(w2.PrimeStyleAIClarityProjectId || w2.PRIMESTYLE_CLARITY_PROJECT_ID);
23437
+ }
23438
+ function resolveSdkClarityProjectId(projectId) {
23439
+ return cleanProjectId(projectId) || readWindowProjectId() || readEnvProjectId() || DEFAULT_PROJECT_ID;
23440
+ }
23441
+ function getClarity() {
23442
+ if (typeof window === "undefined") return null;
23443
+ const clarity = window.clarity;
23444
+ return typeof clarity === "function" ? clarity : null;
23445
+ }
23446
+ function installClarity(projectId) {
23447
+ if (typeof window === "undefined" || typeof document === "undefined") return null;
23448
+ const scriptId = `${SCRIPT_ID_PREFIX}-${projectId}`;
23449
+ if (!document.getElementById(scriptId)) {
23450
+ ((c, l2, a, r2, i) => {
23451
+ c[a] = c[a] || function(...args) {
23452
+ const queued = c[a];
23453
+ (queued.q = queued.q || []).push(args);
23454
+ };
23455
+ const script = l2.createElement(r2);
23456
+ const firstScript = l2.getElementsByTagName(r2)[0];
23457
+ script.id = scriptId;
23458
+ script.async = true;
23459
+ script.src = "https://www.clarity.ms/tag/" + i;
23460
+ firstScript?.parentNode?.insertBefore(script, firstScript);
23461
+ })(window, document, "clarity", "script", projectId);
23462
+ }
23463
+ return getClarity();
23464
+ }
23465
+ function setTag(key, value) {
23466
+ const clarity = getClarity();
23467
+ if (!clarity || value === void 0 || value === null || value === "") return;
23468
+ try {
23469
+ clarity("set", key, String(value).slice(0, 512));
23470
+ } catch {
23471
+ }
23472
+ }
23473
+ function tagSdkClaritySession(input = {}) {
23474
+ const sessionId = getOrCreateSessionId();
23475
+ setTag("ps_session", sessionId);
23476
+ setTag("ps_sdk_session", sessionId);
23477
+ setTag("ps_product_id", input.productId || void 0);
23478
+ setTag("ps_product_title", input.productTitle || void 0);
23479
+ setTag("ps_product_url", input.productUrl || void 0);
23480
+ setTag("ps_sdk_view", input.view || void 0);
23481
+ setTag("ps_tryon_job_id", input.jobId || void 0);
23482
+ setTag("ps_source", input.source || void 0);
23483
+ }
23484
+ function startSdkClarity(input = {}) {
23485
+ if (typeof window === "undefined" || typeof document === "undefined") return false;
23486
+ const projectId = resolveSdkClarityProjectId(input.projectId);
23487
+ {
23488
+ initializedProjectId = initializedProjectId || projectId;
23489
+ installClarity(initializedProjectId);
23490
+ }
23491
+ if (!getClarity()) return false;
23492
+ tagSdkClaritySession(input);
23493
+ return true;
23494
+ }
23144
23495
  function CameraIcon$1({ size = 18 }) {
23145
23496
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
23146
23497
  /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" }),
@@ -24327,8 +24678,8 @@ function MultiSectionMobile({
24327
24678
  sizeGuide ? null : null
24328
24679
  ] });
24329
24680
  }
24330
- const PER_SLIDE = 3;
24331
- const CYCLE_MS = 4e3;
24681
+ const PER_SLIDE = 2;
24682
+ const CYCLE_MS = 2500;
24332
24683
  function ProductPhotoCarouselCard({
24333
24684
  photos,
24334
24685
  items,
@@ -34632,6 +34983,7 @@ function PrimeStyleTryonInner({
34632
34983
  continueShoppingLabel,
34633
34984
  backToProductPageLabel,
34634
34985
  sizeGuideData,
34986
+ clarityProjectId,
34635
34987
  initialView,
34636
34988
  initialBodyProfileStep
34637
34989
  }) {
@@ -35031,7 +35383,7 @@ function PrimeStyleTryonInner({
35031
35383
  const key = getApiKey();
35032
35384
  const url = getApiUrl(apiUrl);
35033
35385
  apiRef.current = new ApiClient(key, url);
35034
- sseRef.current = shouldUseShopifyCartAttribution(url) ? null : new SseClient(apiRef.current.getStreamUrl());
35386
+ sseRef.current = null;
35035
35387
  } catch {
35036
35388
  }
35037
35389
  return () => {
@@ -35040,6 +35392,76 @@ function PrimeStyleTryonInner({
35040
35392
  if (pollingRef.current) clearInterval(pollingRef.current);
35041
35393
  };
35042
35394
  }, [apiUrl]);
35395
+ const sdkReplayOpen = view !== "idle";
35396
+ reactExports.useEffect(() => {
35397
+ if (!sdkReplayOpen) return void 0;
35398
+ const client = apiRef.current;
35399
+ if (!client) return void 0;
35400
+ return startSdkReplay({
35401
+ client,
35402
+ productId: effectiveProductId,
35403
+ productTitle: restoredProductTitle || productTitle,
35404
+ productUrl: restoredProductUrl || effectiveProductUrl
35405
+ });
35406
+ }, [sdkReplayOpen, effectiveProductId, effectiveProductUrl, productTitle, restoredProductTitle, restoredProductUrl]);
35407
+ reactExports.useEffect(() => {
35408
+ if (!sdkReplayOpen) return;
35409
+ startSdkClarity({
35410
+ projectId: clarityProjectId,
35411
+ productId: effectiveProductId,
35412
+ productTitle: restoredProductTitle || productTitle,
35413
+ productUrl: restoredProductUrl || effectiveProductUrl,
35414
+ view,
35415
+ jobId: currentTryOnJobIdRef.current,
35416
+ source: shouldUseShopifyCartAttribution(apiUrl) ? "shopify-sdk" : "sdk"
35417
+ });
35418
+ }, [apiUrl, clarityProjectId, effectiveProductId, effectiveProductUrl, productTitle, restoredProductTitle, restoredProductUrl, sdkReplayOpen, view]);
35419
+ const reportSdkEvent = reactExports.useCallback((eventType, input = {}) => {
35420
+ if (shouldUseShopifyCartAttribution(apiUrl)) return;
35421
+ const client = apiRef.current;
35422
+ if (!client) return;
35423
+ void client.reportEvent({
35424
+ eventType,
35425
+ productId: effectiveProductId,
35426
+ productTitle: restoredProductTitle || productTitle,
35427
+ productUrl: restoredProductUrl || effectiveProductUrl,
35428
+ jobId: input.jobId ?? currentTryOnJobIdRef.current ?? void 0,
35429
+ recommendedSize: input.recommendedSize ?? sizingResultRef.current?.recommendedSize,
35430
+ metadata: input.metadata
35431
+ }).catch(() => {
35432
+ });
35433
+ }, [apiUrl, effectiveProductId, effectiveProductUrl, productTitle, restoredProductTitle, restoredProductUrl]);
35434
+ const reportSdkClientError = reactExports.useCallback((input) => {
35435
+ if (shouldUseShopifyCartAttribution(apiUrl)) return;
35436
+ const client = apiRef.current;
35437
+ if (!client) return;
35438
+ void client.reportClientError({
35439
+ ...input,
35440
+ productId: effectiveProductId,
35441
+ productTitle: restoredProductTitle || productTitle,
35442
+ productUrl: restoredProductUrl || effectiveProductUrl,
35443
+ jobId: input.jobId ?? currentTryOnJobIdRef.current ?? void 0
35444
+ }).catch(() => {
35445
+ });
35446
+ }, [apiUrl, effectiveProductId, effectiveProductUrl, productTitle, restoredProductTitle, restoredProductUrl]);
35447
+ const productViewLoggedRef = reactExports.useRef(null);
35448
+ reactExports.useEffect(() => {
35449
+ const key = `${effectiveProductId || ""}|${effectiveProductUrl || ""}`;
35450
+ if (!key.trim() || productViewLoggedRef.current === key) return;
35451
+ productViewLoggedRef.current = key;
35452
+ if (shouldUseShopifyCartAttribution(apiUrl)) {
35453
+ logProductView({ productId: effectiveProductId, productTitle });
35454
+ return;
35455
+ }
35456
+ reportSdkEvent("PRODUCT_VIEW", {
35457
+ metadata: {
35458
+ productUrl: effectiveProductUrl,
35459
+ productFitType: resolvedProductFitType,
35460
+ productCategory,
35461
+ productSubcategory
35462
+ }
35463
+ });
35464
+ }, [apiUrl, effectiveProductId, effectiveProductUrl, productCategory, productSubcategory, productTitle, reportSdkEvent, resolvedProductFitType]);
35043
35465
  const pickFireCountRef = reactExports.useRef(0);
35044
35466
  const toBackendFetchableImageUrl = reactExports.useCallback((url) => {
35045
35467
  const trimmed = String(url || "").trim();
@@ -35580,10 +36002,11 @@ function PrimeStyleTryonInner({
35580
36002
  });
35581
36003
  const handleOpen = reactExports.useCallback(() => {
35582
36004
  console.log("[ps-sdk] handleOpen fired — opening modal");
36005
+ reportSdkEvent("SDK_OPENED", { metadata: { view: "body-profile" } });
35583
36006
  setBodyProfileInitialStep(null);
35584
36007
  setView("body-profile");
35585
36008
  onOpen?.();
35586
- }, [onOpen]);
36009
+ }, [onOpen, reportSdkEvent]);
35587
36010
  const handleClose = reactExports.useCallback(() => {
35588
36011
  const tryOnInFlight = tryOnProcessing;
35589
36012
  setView("idle");
@@ -35673,12 +36096,28 @@ function PrimeStyleTryonInner({
35673
36096
  if (!isValidImageFile(file)) {
35674
36097
  setErrorMessage(t2("Please upload a JPEG, PNG, or WebP image."));
35675
36098
  setView("error");
36099
+ reportSdkClientError({
36100
+ message: "Invalid image file type",
36101
+ code: "INVALID_FILE",
36102
+ component: "PhotoUpload",
36103
+ view,
36104
+ severity: "low",
36105
+ metadata: { fileType: file.type, fileSize: file.size }
36106
+ });
35676
36107
  onError?.({ message: "Invalid file type", code: "INVALID_FILE" });
35677
36108
  return;
35678
36109
  }
35679
36110
  if (file.size > 10 * 1024 * 1024) {
35680
36111
  setErrorMessage(t2("Image must be under 10MB."));
35681
36112
  setView("error");
36113
+ reportSdkClientError({
36114
+ message: "Image file too large",
36115
+ code: "FILE_TOO_LARGE",
36116
+ component: "PhotoUpload",
36117
+ view,
36118
+ severity: "low",
36119
+ metadata: { fileType: file.type, fileSize: file.size }
36120
+ });
35682
36121
  onError?.({ message: "File too large", code: "FILE_TOO_LARGE" });
35683
36122
  return;
35684
36123
  }
@@ -35687,13 +36126,23 @@ function PrimeStyleTryonInner({
35687
36126
  modelImageIdRef.current = null;
35688
36127
  const objUrl = URL.createObjectURL(file);
35689
36128
  setPreviewUrl(objUrl);
36129
+ reportSdkEvent("PHOTO_UPLOADED", {
36130
+ metadata: { fileType: file.type, fileSize: file.size, view }
36131
+ });
35690
36132
  onUpload?.(file);
35691
36133
  modelPoseRef.current = null;
35692
36134
  detectMeasurementLines(objUrl).then((lines) => {
35693
36135
  modelPoseRef.current = lines;
35694
- }).catch(() => {
36136
+ }).catch((error) => {
36137
+ reportSdkClientError({
36138
+ message: error instanceof Error ? error.message : "Pose detection failed",
36139
+ code: "POSE_DETECTION_FAILED",
36140
+ component: "MediaPipe",
36141
+ view,
36142
+ severity: "low"
36143
+ });
35695
36144
  });
35696
- }, [onUpload, onError]);
36145
+ }, [onUpload, onError, reportSdkClientError, reportSdkEvent, t2, view]);
35697
36146
  const handleRemovePreview = reactExports.useCallback(() => {
35698
36147
  setSelectedFile(null);
35699
36148
  if (previewUrl) URL.revokeObjectURL(previewUrl);
@@ -35762,12 +36211,21 @@ function PrimeStyleTryonInner({
35762
36211
  setTryOnProcessing(false);
35763
36212
  setTryOnStartedAt(null);
35764
36213
  const msg = update.error || t2("Try-on generation failed");
36214
+ reportSdkClientError({
36215
+ message: msg,
36216
+ code: "TRYON_GENERATION_FAILED",
36217
+ component: "TryOn",
36218
+ view,
36219
+ severity: "medium",
36220
+ jobId: update.galleryId,
36221
+ metadata: { status: update.status }
36222
+ });
35765
36223
  setErrorMessage(msg);
35766
36224
  setView("error");
35767
36225
  onError?.({ message: msg });
35768
36226
  }
35769
36227
  }
35770
- }, [apiUrl, effectiveProductId, productTitle, onComplete, onError, cleanupJob]);
36228
+ }, [apiUrl, effectiveProductId, productTitle, onComplete, onError, cleanupJob, reportSdkClientError, t2, view]);
35771
36229
  const dynamicFields = reactExports.useMemo(() => {
35772
36230
  if (sizeGuide?.found && sizeGuide.requiredFields && sizeGuide.requiredFields.length > 0) {
35773
36231
  return sizeGuide.requiredFields;
@@ -35781,6 +36239,14 @@ function PrimeStyleTryonInner({
35781
36239
  const method = methodOverride || sizingMethod;
35782
36240
  const baseUrl = getApiUrl(apiUrl);
35783
36241
  const key = getApiKey();
36242
+ reportSdkEvent("SIZING_STARTED", {
36243
+ metadata: {
36244
+ method,
36245
+ measurementType,
36246
+ productFitType: resolvedProductFitType,
36247
+ hasSizeGuide: !!sizeGuide?.found
36248
+ }
36249
+ });
35784
36250
  if (measurementType === "face" || measurementType === "head") {
35785
36251
  const f2 = formRef.current;
35786
36252
  const toNum = (v2) => {
@@ -35839,16 +36305,42 @@ function PrimeStyleTryonInner({
35839
36305
  const data = await resp.json();
35840
36306
  await minVisible;
35841
36307
  if (data?.found === false) {
36308
+ reportSdkEvent("SIZING_FAILED", {
36309
+ metadata: {
36310
+ reason: data?.reasoning || "NO_MATCH",
36311
+ measurementType,
36312
+ found: false
36313
+ }
36314
+ });
35842
36315
  setNoSizeReason(data?.reasoning === "NO_SIZE_CHART" ? "no-chart" : "no-match");
35843
36316
  setView("no-chart");
35844
36317
  setEstimationDone(true);
35845
36318
  return;
35846
36319
  }
35847
36320
  setSizingResult(data);
36321
+ reportSdkEvent("SIZE_RECOMMENDATION_SHOWN", {
36322
+ recommendedSize: data?.recommendedSize,
36323
+ metadata: {
36324
+ confidence: data?.confidence,
36325
+ measurementType,
36326
+ source: "face-recommend"
36327
+ }
36328
+ });
35848
36329
  onComplete?.(data);
35849
36330
  } else {
35850
36331
  const body = await resp.text().catch(() => "");
35851
36332
  console.error("[PS-SDK] face-recommend failed:", resp.status, body);
36333
+ reportSdkEvent("SIZING_FAILED", {
36334
+ metadata: { status: resp.status, measurementType, source: "face-recommend" }
36335
+ });
36336
+ reportSdkClientError({
36337
+ message: body || "Face/head sizing request failed",
36338
+ code: "FACE_RECOMMEND_FAILED",
36339
+ component: "Sizing",
36340
+ view,
36341
+ severity: "medium",
36342
+ metadata: { status: resp.status, measurementType }
36343
+ });
35852
36344
  await minVisible;
35853
36345
  setErrorMessage(t2("Unable to get a size suggestion. Please try again."));
35854
36346
  setView("error");
@@ -35856,6 +36348,18 @@ function PrimeStyleTryonInner({
35856
36348
  }
35857
36349
  } catch (err) {
35858
36350
  console.error("[PS-SDK] face-recommend network error:", err);
36351
+ reportSdkEvent("SIZING_FAILED", {
36352
+ metadata: { measurementType, source: "face-recommend", network: true }
36353
+ });
36354
+ reportSdkClientError({
36355
+ message: err instanceof Error ? err.message : "Face/head sizing network error",
36356
+ code: "FACE_RECOMMEND_NETWORK_ERROR",
36357
+ stack: err instanceof Error ? err.stack : void 0,
36358
+ component: "Sizing",
36359
+ view,
36360
+ severity: "medium",
36361
+ metadata: { measurementType }
36362
+ });
35859
36363
  await minVisible;
35860
36364
  setErrorMessage(t2("Unable to connect to sizing service. Please try again."));
35861
36365
  setView("error");
@@ -35916,6 +36420,17 @@ function PrimeStyleTryonInner({
35916
36420
  const qWeight = parseFloat(formRef.current.weight || "0");
35917
36421
  if (!qHeight || !qWeight) {
35918
36422
  console.error("[PS-SDK] submitSizing ABORT — qHeight:", qHeight, "qWeight:", qWeight, "formRef:", JSON.stringify(formRef.current));
36423
+ reportSdkEvent("SIZING_FAILED", {
36424
+ metadata: { reason: "missing_height_or_weight", method, measurementType }
36425
+ });
36426
+ reportSdkClientError({
36427
+ message: "Sizing submitted without height or weight",
36428
+ code: "SIZING_INPUT_MISSING",
36429
+ component: "Sizing",
36430
+ view,
36431
+ severity: "low",
36432
+ metadata: { method, measurementType, qHeight, qWeight }
36433
+ });
35919
36434
  setSizingLoading(false);
35920
36435
  return;
35921
36436
  }
@@ -35948,12 +36463,29 @@ function PrimeStyleTryonInner({
35948
36463
  const data = await res.json();
35949
36464
  console.log("[PS-SDK] Sizing recommend RESULT:", JSON.stringify(data));
35950
36465
  if (data?.found === false) {
36466
+ reportSdkEvent("SIZING_FAILED", {
36467
+ metadata: {
36468
+ reason: data?.reasoning || "NO_MATCH",
36469
+ method,
36470
+ measurementType,
36471
+ found: false
36472
+ }
36473
+ });
35951
36474
  setNoSizeReason(data?.reasoning === "NO_SIZE_CHART" ? "no-chart" : "no-match");
35952
36475
  setView("no-chart");
35953
36476
  setEstimationDone(true);
35954
36477
  return;
35955
36478
  }
35956
36479
  setSizingResult(data);
36480
+ reportSdkEvent("SIZE_RECOMMENDATION_SHOWN", {
36481
+ recommendedSize: data?.recommendedSize,
36482
+ metadata: {
36483
+ confidence: data?.confidence,
36484
+ method,
36485
+ measurementType,
36486
+ found: data?.found
36487
+ }
36488
+ });
35957
36489
  onComplete?.(data);
35958
36490
  const m2 = payload.measurements || {};
35959
36491
  const qe2 = payload.quickEstimate || {};
@@ -35981,26 +36513,49 @@ function PrimeStyleTryonInner({
35981
36513
  } else {
35982
36514
  const errBody = await res.text().catch(() => "");
35983
36515
  console.error("[PS-SDK] Sizing recommend failed:", res.status, errBody);
36516
+ reportSdkEvent("SIZING_FAILED", {
36517
+ metadata: { status: res.status, method, measurementType, source: "sizing-recommend" }
36518
+ });
36519
+ reportSdkClientError({
36520
+ message: errBody || "Sizing request failed",
36521
+ code: "SIZING_RECOMMEND_FAILED",
36522
+ component: "Sizing",
36523
+ view,
36524
+ severity: "medium",
36525
+ metadata: { status: res.status, method, measurementType }
36526
+ });
35984
36527
  setErrorMessage(t2("Unable to get a size suggestion. Please try again."));
35985
36528
  setView("error");
35986
36529
  setEstimationDone(true);
35987
36530
  }
35988
36531
  } catch (err) {
35989
36532
  console.error("[PS-SDK] Sizing recommend network error:", err);
36533
+ reportSdkEvent("SIZING_FAILED", {
36534
+ metadata: { method, measurementType, source: "sizing-recommend", network: true }
36535
+ });
36536
+ reportSdkClientError({
36537
+ message: err instanceof Error ? err.message : "Sizing network error",
36538
+ code: "SIZING_RECOMMEND_NETWORK_ERROR",
36539
+ stack: err instanceof Error ? err.stack : void 0,
36540
+ component: "Sizing",
36541
+ view,
36542
+ severity: "medium",
36543
+ metadata: { method, measurementType }
36544
+ });
35990
36545
  setErrorMessage(t2("Unable to connect to sizing service. Please try again."));
35991
36546
  setView("error");
35992
36547
  setEstimationDone(true);
35993
36548
  } finally {
35994
36549
  setSizingLoading(false);
35995
36550
  }
35996
- }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productContext, measurementType, dynamicFields, persistResultToProfile]);
36551
+ }, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productContext, measurementType, resolvedProductFitType, dynamicFields, persistResultToProfile, reportSdkClientError, reportSdkEvent, t2, view]);
35997
36552
  const handleQuickEstimate = reactExports.useCallback(async (height, weight, heightUnit2, weightUnit2, gender, age, bodyType, chestProfile, midsectionProfile, hipProfile, bodyImage) => {
35998
36553
  if (!apiRef.current) {
35999
36554
  const msg = t2("SDK not configured. Please refresh and try again.");
36000
36555
  console.warn("[ps-sdk] handleQuickEstimate BAILED — apiRef is null. API key not loaded.");
36001
36556
  setErrorMessage(msg);
36002
36557
  setView("error");
36003
- onError?.({ message: msg, code: "SDK_NOT_CONFIGURED" });
36558
+ onError?.({ message: msg, code: !apiRef.current ? "SDK_NOT_CONFIGURED" : "PHOTO_MISSING" });
36004
36559
  return;
36005
36560
  }
36006
36561
  getApiUrl(apiUrl);
@@ -36379,6 +36934,13 @@ function PrimeStyleTryonInner({
36379
36934
  const msg = !apiRef.current ? t2("SDK not configured. Please provide an API key.") : t2("Please upload a photo first.");
36380
36935
  setErrorMessage(msg);
36381
36936
  setView("error");
36937
+ reportSdkClientError({
36938
+ message: msg,
36939
+ code: !apiRef.current ? "SDK_NOT_CONFIGURED" : "PHOTO_MISSING",
36940
+ component: "TryOn",
36941
+ view,
36942
+ severity: !apiRef.current ? "high" : "low"
36943
+ });
36382
36944
  onError?.({ message: msg, code: "SDK_NOT_CONFIGURED" });
36383
36945
  return;
36384
36946
  }
@@ -36533,12 +37095,20 @@ function PrimeStyleTryonInner({
36533
37095
  }
36534
37096
  );
36535
37097
  currentTryOnJobIdRef.current = response.jobId;
37098
+ tagSdkClaritySession({
37099
+ productId: effectiveProductId,
37100
+ productTitle: restoredProductTitle || productTitle,
37101
+ productUrl: restoredProductUrl || effectiveProductUrl,
37102
+ view,
37103
+ jobId: response.jobId,
37104
+ source: shouldUseShopifyCartAttribution(apiUrl) ? "shopify-sdk" : "sdk"
37105
+ });
36536
37106
  if (response.modelImageId) modelImageIdRef.current = response.modelImageId;
36537
37107
  onProcessing?.(response.jobId);
36538
37108
  const usePollingOnly = shouldUseShopifyCartAttribution(apiUrl);
36539
- if (!usePollingOnly && response.streamUrl) {
37109
+ if (!usePollingOnly) {
36540
37110
  sseRef.current?.disconnect();
36541
- sseRef.current = new SseClient(response.streamUrl);
37111
+ sseRef.current = new SseClient(response.streamUrl || apiRef.current.getStreamUrl(response.jobId));
36542
37112
  }
36543
37113
  unsubRef.current?.();
36544
37114
  unsubRef.current = usePollingOnly ? null : sseRef.current?.onJob(response.jobId, handleVtoUpdate) ?? null;
@@ -36566,11 +37136,19 @@ function PrimeStyleTryonInner({
36566
37136
  } catch (err) {
36567
37137
  const message = err instanceof Error ? err.message : t2("Failed to start try-on");
36568
37138
  const code = err instanceof PrimeStyleError ? err.code : void 0;
37139
+ reportSdkClientError({
37140
+ message,
37141
+ code: code || "TRYON_SUBMIT_FAILED",
37142
+ stack: err instanceof Error ? err.stack : void 0,
37143
+ component: "TryOn",
37144
+ view,
37145
+ severity: "medium"
37146
+ });
36569
37147
  setErrorMessage(message);
36570
37148
  setView("error");
36571
37149
  onError?.({ message, code });
36572
37150
  }
36573
- }, [selectedFile, productImage, effectiveProductImages, garmentReferenceImage, garmentDetailImage, productTitle, productCategory, productSubcategory, resolvedProductFitType, productType, productTagsList, productDescription, productMaterial, measurementType, sizingResult, sizeGuide, apiUrl, onProcessing, onError, handleVtoUpdate]);
37151
+ }, [selectedFile, productImage, effectiveProductImages, garmentReferenceImage, garmentDetailImage, effectiveProductId, effectiveProductUrl, productTitle, restoredProductTitle, restoredProductUrl, productCategory, productSubcategory, resolvedProductFitType, productType, productTagsList, productDescription, productMaterial, measurementType, sizingResult, sizeGuide, apiUrl, onProcessing, onError, handleVtoUpdate, reportSdkClientError, t2, view]);
36574
37152
  reactExports.useEffect(() => {
36575
37153
  if (view !== "size-result") {
36576
37154
  autoTryOnFiredRef.current = false;
@@ -36662,7 +37240,7 @@ function PrimeStyleTryonInner({
36662
37240
  }, [sizingResult]);
36663
37241
  const handleAddToBag = reactExports.useCallback(async () => {
36664
37242
  if (!onAddToBag || !sizingResult) return;
36665
- await onAddToBag({
37243
+ const payload = {
36666
37244
  productId,
36667
37245
  productTitle: restoredProductTitle || productTitle,
36668
37246
  productUrl: restoredProductUrl || effectiveProductUrl,
@@ -36671,7 +37249,33 @@ function PrimeStyleTryonInner({
36671
37249
  resultImageUrl: resultImageUrlRef.current || resultImageUrl,
36672
37250
  historyEntryId: currentHistoryEntryIdRef.current ?? void 0,
36673
37251
  selectedSizes: buildAddToBagSelectedSizes()
36674
- });
37252
+ };
37253
+ try {
37254
+ await onAddToBag(payload);
37255
+ reportSdkEvent("SIZE_RECOMMENDATION_ACCEPTED", {
37256
+ recommendedSize: sizingResult.recommendedSize,
37257
+ metadata: { selectedSizes: payload.selectedSizes }
37258
+ });
37259
+ reportSdkEvent("ADD_TO_CART_FROM_TRYON", {
37260
+ recommendedSize: sizingResult.recommendedSize,
37261
+ metadata: {
37262
+ selectedSizes: payload.selectedSizes,
37263
+ historyEntryId: payload.historyEntryId,
37264
+ hasResult: Boolean(payload.resultImageUrl)
37265
+ }
37266
+ });
37267
+ } catch (error) {
37268
+ reportSdkClientError({
37269
+ message: error instanceof Error ? error.message : "Add to bag failed",
37270
+ code: "ADD_TO_BAG_FAILED",
37271
+ stack: error instanceof Error ? error.stack : void 0,
37272
+ component: "ResultActions",
37273
+ view,
37274
+ severity: "medium",
37275
+ metadata: { selectedSizes: payload.selectedSizes }
37276
+ });
37277
+ throw error;
37278
+ }
36675
37279
  }, [
36676
37280
  buildAddToBagSelectedSizes,
36677
37281
  effectiveProductUrl,
@@ -36681,7 +37285,10 @@ function PrimeStyleTryonInner({
36681
37285
  restoredProductTitle,
36682
37286
  restoredProductUrl,
36683
37287
  resultImageUrl,
36684
- sizingResult
37288
+ reportSdkClientError,
37289
+ reportSdkEvent,
37290
+ sizingResult,
37291
+ view
36685
37292
  ]);
36686
37293
  const handleTryOnFeedbackSubmit = reactExports.useCallback(async ({ rating, note }) => {
36687
37294
  const profileLoggedIn = Boolean(profileSession);
@@ -37639,7 +38246,7 @@ function PrimeStyleTryonInner({
37639
38246
  return null;
37640
38247
  }
37641
38248
  }
37642
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx("ps-tryon-root", cn.root || className), style: { visibility: cssReady ? "visible" : "hidden", ...cssVars, ...style }, suppressHydrationWarning: true, "data-ps-tryon": true, children: [
38249
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx("ps-tryon-root", cn.root || className), style: { visibility: cssReady ? "visible" : "hidden", ...cssVars, ...style }, suppressHydrationWarning: true, "data-ps-tryon": true, "data-clarity-unmask": "true", children: [
37643
38250
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
37644
38251
  "button",
37645
38252
  {
@@ -37653,7 +38260,7 @@ function PrimeStyleTryonInner({
37653
38260
  }
37654
38261
  ),
37655
38262
  view !== "idle" && resolvedPortalContainer && reactDomExports.createPortal(
37656
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cx("ps-tryon-overlay", cn.overlay), style: cssVars, "data-ps-tryon-portal": true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx(`ps-tryon-modal${view === "result" && resultImageUrl && sizingResult || view === "size-result" || view === "estimation-review" || view === "body-profile" || view === "profiles" || view === "no-chart" || view === "photo-guide" || view === "error" ? " ps-tryon-modal-wide" : ""}`, cn.modal), onClick: (e) => e.stopPropagation(), children: [
38263
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: cx("ps-tryon-overlay", cn.overlay), style: cssVars, "data-ps-tryon-portal": true, "data-clarity-unmask": "true", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx(`ps-tryon-modal${view === "result" && resultImageUrl && sizingResult || view === "size-result" || view === "estimation-review" || view === "body-profile" || view === "profiles" || view === "no-chart" || view === "photo-guide" || view === "error" ? " ps-tryon-modal-wide" : ""}`, cn.modal), onClick: (e) => e.stopPropagation(), "data-clarity-unmask": "true", children: [
37657
38264
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: cx("ps-tryon-header ps-tryon-header-minimal", cn.header), children: [
37658
38265
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
37659
38266
  "a",
@@ -37811,7 +38418,7 @@ function PrimeStyleTryonInner({
37811
38418
  resolvedPortalContainer
37812
38419
  ),
37813
38420
  view !== "body-profile" && view !== "processing" && !(view === "size-result" && sizeGuide?.sections && Object.keys(sizeGuide.sections).length > 1) && /* @__PURE__ */ jsxRuntimeExports.jsx(Stepper, { view, stepIndex }),
37814
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: bodyRef, className: cx("ps-tryon-body", cn.body), style: { position: "relative", overflow: drawer ? "hidden" : void 0 }, children: [
38421
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: bodyRef, className: cx("ps-tryon-body", cn.body), style: { position: "relative", overflow: drawer ? "hidden" : void 0 }, "data-clarity-unmask": "true", children: [
37815
38422
  showBackButton && /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { className: "ps-tryon-back-btn", onClick: handleBack, children: [
37816
38423
  /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { width: "18", height: "12", viewBox: "0 0 18 12", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
37817
38424
  /* @__PURE__ */ jsxRuntimeExports.jsx("line", { x1: "17", y1: "6", x2: "1", y2: "6" }),
@@ -37891,8 +38498,20 @@ class PrimeStyleTryonErrorBoundary extends reactExports.Component {
37891
38498
  }
37892
38499
  componentDidCatch(error) {
37893
38500
  console.error("[ps-sdk] PrimeStyleTryon render failed:", error);
38501
+ const message = error instanceof Error ? error.message : "PrimeStyleTryon render failed";
38502
+ void new ApiClient(getApiKey(), getApiUrl(this.props.apiUrl)).reportClientError({
38503
+ message,
38504
+ code: "RENDER_ERROR",
38505
+ stack: error instanceof Error ? error.stack : void 0,
38506
+ component: "PrimeStyleTryon",
38507
+ productId: this.props.productId || this.props.productImage,
38508
+ productTitle: this.props.productTitle,
38509
+ productUrl: this.props.productUrl,
38510
+ severity: "high"
38511
+ }).catch(() => {
38512
+ });
37894
38513
  this.props.onError?.({
37895
- message: error instanceof Error ? error.message : "PrimeStyleTryon render failed",
38514
+ message,
37896
38515
  code: "RENDER_ERROR"
37897
38516
  });
37898
38517
  }
@@ -38447,7 +39066,8 @@ function buildPropsFromDataAttrs(data) {
38447
39066
  sizingCountry: data.sizingCountry,
38448
39067
  showPoweredBy: data.showPoweredBy === "true",
38449
39068
  buttonStyles: parseJsonAttr(data.buttonStyles, void 0),
38450
- modalStyles: parseJsonAttr(data.modalStyles, void 0)
39069
+ modalStyles: parseJsonAttr(data.modalStyles, void 0),
39070
+ clarityProjectId: data.clarityProjectId
38451
39071
  };
38452
39072
  console.log(`${TAG} built props`, props);
38453
39073
  return props;