@liiift-studio/sanity-visitor-insights 0.60.0 → 0.62.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.
package/dist/index.js CHANGED
@@ -348,10 +348,14 @@ var import_jsx_runtime = require("react/jsx-runtime");
348
348
  var REASON_TEXT = {
349
349
  not_instrumented: "Not tracked on this site",
350
350
  before_cutover: "Not tracked during this period",
351
- suppressed: "Withheld by GA4 for privacy",
351
+ // "Withheld ... for privacy" reads as a legal hold on the reader's own data. What actually
352
+ // happened is that too few people did the thing for Google to report it.
353
+ suppressed: "Google hid it: too few people",
352
354
  outage: "Not recorded during part of this period",
353
355
  source_error: "Source did not respond",
354
- not_applicable: "Does not apply to this site",
356
+ // "Does not apply" reads as a choice someone made. It is not — there is simply nothing here to
357
+ // compute it from.
358
+ not_applicable: "Nothing here to work this out from",
355
359
  // Its own reason, because it was borrowing not_applicable — so a figure that is temporarily
356
360
  // missing rendered "Does not apply to this site. This site's API route predates this figure."
357
361
  // Two flatly contradictory sentences, and a hurried reader takes the first: this foundry has no
@@ -463,7 +467,7 @@ function MetricFigure({ metric, label, size = 4, unit = "count" }) {
463
467
  metric.note
464
468
  ] })
465
469
  ] }),
466
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Badge, { tone: "caution", fontSize: 0, children: "Partial" })
470
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Badge, { tone: "caution", fontSize: 0, children: "Some orders only" })
467
471
  ] });
468
472
  }
469
473
  if (metric.status === "estimated") {
@@ -735,7 +739,7 @@ function NoticeList({ notices }) {
735
739
  const alwaysShown = notices.slice(0, 2);
736
740
  const hidden = notices.slice(2);
737
741
  const item = (notice) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Card, { padding: 3, radius: 2, tone: "caution", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: noticeRow, children: [
738
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: noticeBadge, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Badge, { tone: "caution", fontSize: 0, children: "Caveat" }) }),
742
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: noticeBadge, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Badge, { tone: "caution", fontSize: 0, children: "Note" }) }),
739
743
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, children: notice })
740
744
  ] }) }) }, notice);
741
745
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Stack, { space: 2, children: [
@@ -2066,7 +2070,7 @@ function OverviewPanel({ data, previous, onBrush }) {
2066
2070
  */
2067
2071
  ...(data.crossSource ?? []).some((d) => d.vercelPageviews !== null && d.ga4Pageviews !== null) ? [{
2068
2072
  key: "coverage",
2069
- label: "GA4 coverage",
2073
+ label: "Share GA4 saw",
2070
2074
  source: "GA4",
2071
2075
  complete: true,
2072
2076
  unit: "percent",
@@ -2163,7 +2167,7 @@ function OverviewPanel({ data, previous, onBrush }) {
2163
2167
  * worst. Sorting on it is how a reader dates a collapse.
2164
2168
  */
2165
2169
  key: "coverage",
2166
- label: "GA4 coverage",
2170
+ label: "Share GA4 saw",
2167
2171
  numeric: true,
2168
2172
  sortValue: (d) => coverageOf(d),
2169
2173
  // The FORMATTED value, matching the cell. Passing the raw ratio through
@@ -2285,16 +2289,21 @@ function Verdict({ data, previous }) {
2285
2289
  parts.push(Math.abs(change) < 5e-3 ? `${label} flat` : `${label} little changed (${change > 0 ? "+" : "\u2212"}${formatPercent(Math.abs(change), 0)})`);
2286
2290
  } else if (unit === "money") {
2287
2291
  parts.push(`${label} ${change > 0 ? "up" : "down"} to ${formatMoney(a, data.currency ?? null)} from ${formatMoney(b, data.currency ?? null)}`);
2292
+ } else if (Math.abs(b) < MIN_DELTA_BASE) {
2293
+ parts.push(`${label} ${formatCount(a)}, was ${formatCount(b)}`);
2288
2294
  } else parts.push(`${label} ${change > 0 ? "up" : "down"} ${formatPercent(Math.abs(change), 0)}`);
2289
2295
  };
2290
2296
  say("Revenue", data.revenue, previous?.revenue, "money");
2297
+ say("Orders", data.orders, previous?.orders, "count");
2291
2298
  say("Traffic", data.vercelPageviews, previous?.vercelPageviews, "count");
2292
2299
  const sends = data.campaigns?.length ?? 0;
2293
2300
  const shortfall = data.shortfallRatio;
2294
2301
  const seeing = shortfall !== null && shortfall !== void 0 ? `GA4 is seeing ${formatPercent(1 - shortfall, 0)} of your traffic` : null;
2295
2302
  const broken = shortfall === null || shortfall === void 0 ? data.capture?.discrepancy ? "measurement disagrees between sources" : null : shortfall > 0.6 ? `${seeing} \u2014 treat its figures as broken` : shortfall > 0.35 ? `${seeing} \u2014 its figures are undercounts, not what happened` : data.capture?.discrepancy ? "measurement disagrees between sources" : null;
2296
2303
  if (broken) parts.push(broken);
2297
- if (parts.length === 0) return null;
2304
+ if (parts.length === 0) {
2305
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", border: true, style: { borderLeftWidth: 3, borderLeftStyle: "solid" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 4, children: "No figures arrived for this window." }) });
2306
+ }
2298
2307
  const noticeable = shortfall !== null && shortfall !== void 0 && shortfall > 0.2;
2299
2308
  const closing = broken || !noticeable ? "" : ` \xB7 ${seeing}`;
2300
2309
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -2340,7 +2349,7 @@ function DataHealthPanel({ data, diagnostics }) {
2340
2349
  ((data.capture?.estimates?.length ?? 0) > 0 || data.estimatedSessions?.status === "estimated") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
2341
2350
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SectionTitle, { title: "How much GA4 is seeing" }),
2342
2351
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: cardGrid, children: (data.capture?.estimates ?? []).map((estimate) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
2343
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: estimate.basis === "orders" ? "Measured against orders" : estimate.basis === "email" ? "Measured against email clicks" : "Measured against Vercel" }),
2352
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: estimate.basis === "orders" ? "Checked against your orders" : estimate.basis === "email" ? "Checked against email clicks" : "Checked against Vercel" }),
2344
2353
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 4, children: formatPercent(estimate.rate, 0) }),
2345
2354
  estimate.rate > 1.05 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, children: "Above 100%: GA4 is counting more than the source it is measured against. That is usually a tag firing twice, not extra traffic." }),
2346
2355
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
@@ -2444,7 +2453,7 @@ function AcquisitionPanel({ data, previous }) {
2444
2453
  ] })
2445
2454
  ] }) }),
2446
2455
  unattributedShare !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
2447
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Unattributed" }),
2456
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "No source" }),
2448
2457
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
2449
2458
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 4, children: formatPercent(unattributedShare, 1) }),
2450
2459
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -2495,7 +2504,7 @@ function AcquisitionPanel({ data, previous }) {
2495
2504
  }
2496
2505
  ) : row.source }),
2497
2506
  row.designIndustry && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Badge, { tone: "primary", fontSize: 0, children: "Design" }),
2498
- row.unattributed && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Badge, { tone: "caution", fontSize: 0, children: "Unattributed" })
2507
+ row.unattributed && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Badge, { tone: "caution", fontSize: 0, children: "No source" })
2499
2508
  ] })
2500
2509
  },
2501
2510
  {
@@ -2681,7 +2690,7 @@ function JourneyPanel({ data }) {
2681
2690
  const tracked = data.measurement === "sequence";
2682
2691
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 4, children: [
2683
2692
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: tracked ? "transparent" : "caution", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 2, children: [
2684
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, weight: "medium", children: tracked ? "Tracked funnel" : "Independent per-step totals" }),
2693
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, weight: "medium", children: tracked ? "Tracked path" : "Not a tracked path" }),
2685
2694
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: tracked, children: data.approximationNote })
2686
2695
  ] }) }),
2687
2696
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SegmentTable, { segments, dimension: data.segmentDimension ?? "segment" }),
@@ -3081,11 +3090,11 @@ var RANGES = [
3081
3090
  ];
3082
3091
  var GA4_ONLY_TABS = ["acquisition", "journey", "typeface-interest"];
3083
3092
  var PANELS = [
3084
- { id: "overview", report: "measurement-health", label: "Overview", blurb: "Money, traffic and what moved this period" },
3085
- { id: "acquisition", report: "acquisition", label: "Acquisition", blurb: "Where visitors come from" },
3086
- { id: "journey", report: "journey", label: "Journey", blurb: "How far visitors get" },
3087
- { id: "typeface-interest", report: "typeface-interest", label: "Typeface interest", blurb: "Viewed, tested and bought, by family" },
3088
- { id: "data-health", report: "measurement-health", label: "Data health", blurb: "Whether to trust what the other tabs are telling you" }
3093
+ { id: "overview", report: "measurement-health", label: "Overview" },
3094
+ { id: "acquisition", report: "acquisition", label: "Acquisition" },
3095
+ { id: "journey", report: "journey", label: "Journey" },
3096
+ { id: "typeface-interest", report: "typeface-interest", label: "Typeface interest" },
3097
+ { id: "data-health", report: "measurement-health", label: "Data health" }
3089
3098
  ];
3090
3099
  function RangeSelector({
3091
3100
  value,
@@ -3491,7 +3500,7 @@ function VisitorInsightsTool(props) {
3491
3500
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Heading, { size: 2, children: "Visitor insights" }),
3492
3501
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Stack, { space: 2, children: [
3493
3502
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RangeSelector, { value: range, custom, onChange: setRange, onCustomChange: setCustom }),
3494
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BasisSelector, { value: compare, onChange: setCompare })
3503
+ COMPARED_TABS.includes(activePanel) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BasisSelector, { value: compare, onChange: setCompare })
3495
3504
  ] })
3496
3505
  ] }),
3497
3506
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanelTabs, { value: activePanel, onChange: setActivePanel }),
@@ -3551,25 +3560,22 @@ function VisitorInsightsTool(props) {
3551
3560
  "aria-labelledby": `tab-${activePanel}`,
3552
3561
  tabIndex: 0,
3553
3562
  style: { position: "relative" },
3554
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Stack, { space: 4, children: [
3555
- active && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Text, { size: 1, muted: true, children: active.blurb }),
3556
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanelBoundary, { resetKey: `${activePanel}|${range}|${custom.start}|${custom.end}`, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3557
- ReportPanel,
3558
- {
3559
- tabId: active?.id ?? "overview",
3560
- report: active?.report ?? "measurement-health",
3561
- apiBaseUrl,
3562
- range,
3563
- custom,
3564
- compare,
3565
- onBrush: (start, end) => {
3566
- if (range !== "custom") setRangeBeforeBrush(range);
3567
- setCustom({ start, end });
3568
- setRange("custom");
3569
- }
3563
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Stack, { space: 4, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanelBoundary, { resetKey: `${activePanel}|${range}|${custom.start}|${custom.end}`, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3564
+ ReportPanel,
3565
+ {
3566
+ tabId: active?.id ?? "overview",
3567
+ report: active?.report ?? "measurement-health",
3568
+ apiBaseUrl,
3569
+ range,
3570
+ custom,
3571
+ compare,
3572
+ onBrush: (start, end) => {
3573
+ if (range !== "custom") setRangeBeforeBrush(range);
3574
+ setCustom({ start, end });
3575
+ setRange("custom");
3570
3576
  }
3571
- ) })
3572
- ] })
3577
+ }
3578
+ ) }) })
3573
3579
  }
3574
3580
  )
3575
3581
  ] }) })