@liiift-studio/sanity-visitor-insights 0.77.0 → 0.78.1

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.d.mts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as sanity from 'sanity';
2
2
  import React from 'react';
3
- import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-B2W_3Z0t.mjs';
4
- export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DateRange, j as DiagnosticCheck, E as EmailCampaign, k as EventCutover, l as JourneyOutcome, m as JourneySegment, n as JourneyStep, L as LandingPage, o as LicenceTierRow, P as PREEXISTING, p as REPORT_NAMES, q as ReportError, S as SiteAnalyticsConfig, r as SourceName, s as SourceRow, t as SourceStatus, u as TimelineEvent, v as TypefaceInterestRow, U as UnavailableReason, w as coverageForRange, x as estimated, y as isReportName, z as ok, B as partial, F as previousRange, G as resolveRange, H as unavailable, I as validateSiteConfig, K as valueOrNull } from './ranges-B2W_3Z0t.mjs';
3
+ import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-BgcKmdw3.mjs';
4
+ export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DateRange, j as DiagnosticCheck, E as EmailCampaign, k as EventCutover, l as JourneyOutcome, m as JourneySegment, n as JourneyStep, L as LandingPage, o as LicenceTierRow, P as PREEXISTING, p as REPORT_NAMES, q as ReportError, S as SiteAnalyticsConfig, r as SourceName, s as SourceRow, t as SourceStatus, u as TimelineEvent, v as TypefaceInterestRow, U as UnavailableReason, w as coverageForRange, x as estimated, y as isReportName, z as ok, B as partial, F as previousRange, G as resolveRange, H as unavailable, I as validateSiteConfig, K as valueOrNull } from './ranges-BgcKmdw3.mjs';
5
5
 
6
6
  /**
7
7
  * Data hook for the Studio panels.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as sanity from 'sanity';
2
2
  import React from 'react';
3
- import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-B2W_3Z0t.js';
4
- export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DateRange, j as DiagnosticCheck, E as EmailCampaign, k as EventCutover, l as JourneyOutcome, m as JourneySegment, n as JourneyStep, L as LandingPage, o as LicenceTierRow, P as PREEXISTING, p as REPORT_NAMES, q as ReportError, S as SiteAnalyticsConfig, r as SourceName, s as SourceRow, t as SourceStatus, u as TimelineEvent, v as TypefaceInterestRow, U as UnavailableReason, w as coverageForRange, x as estimated, y as isReportName, z as ok, B as partial, F as previousRange, G as resolveRange, H as unavailable, I as validateSiteConfig, K as valueOrNull } from './ranges-B2W_3Z0t.js';
3
+ import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-BgcKmdw3.js';
4
+ export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DateRange, j as DiagnosticCheck, E as EmailCampaign, k as EventCutover, l as JourneyOutcome, m as JourneySegment, n as JourneyStep, L as LandingPage, o as LicenceTierRow, P as PREEXISTING, p as REPORT_NAMES, q as ReportError, S as SiteAnalyticsConfig, r as SourceName, s as SourceRow, t as SourceStatus, u as TimelineEvent, v as TypefaceInterestRow, U as UnavailableReason, w as coverageForRange, x as estimated, y as isReportName, z as ok, B as partial, F as previousRange, G as resolveRange, H as unavailable, I as validateSiteConfig, K as valueOrNull } from './ranges-BgcKmdw3.js';
5
5
 
6
6
  /**
7
7
  * Data hook for the Studio panels.
package/dist/index.js CHANGED
@@ -1331,12 +1331,6 @@ var sectionToggle = {
1331
1331
  cursor: "pointer",
1332
1332
  flex: "0 0 auto"
1333
1333
  };
1334
- var splitGrid = {
1335
- display: "grid",
1336
- gridTemplateColumns: "repeat(auto-fit, minmax(min(26rem, 100%), 1fr))",
1337
- gap: 24,
1338
- alignItems: "start"
1339
- };
1340
1334
  var estimateTrack = {
1341
1335
  position: "relative",
1342
1336
  height: 18,
@@ -2519,84 +2513,90 @@ function OverviewPanel({ data, previous, onBrush }) {
2519
2513
  }
2520
2514
  )
2521
2515
  ] }),
2522
- (data.campaigns?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: stackBlock, children: [
2523
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SectionTitle, { title: "Email campaigns" }),
2524
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "Clicks are distinct subscribers. Opens are not shown: Apple Mail fetches images on the recipient\u2019s behalf, so an open is often the mail client rather than a person." }),
2525
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: [
2526
- "The last two columns count what your order book records in the ",
2527
- SEND_WINDOW_DAYS,
2528
- " days after each send, ending early if another send lands first. They are what happened next, not what the send caused \u2014 but unlike anything GA4 can tell you about email, both the send times and the order times are exact."
2529
- ] }),
2530
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2531
- SortableTable,
2532
- {
2533
- caption: "Email campaigns by clicks",
2534
- initialSort: "clicks",
2535
- rows: data.campaigns ?? [],
2536
- rowKey: (c) => `${c.sentAt}-${c.title}`,
2537
- filterOn: (c) => `${c.title} ${c.subject}`,
2538
- filterPlaceholder: "Filter campaigns",
2539
- exportName: "email-campaigns",
2540
- columns: [
2541
- {
2542
- key: "title",
2543
- label: "Campaign",
2544
- sortValue: (c) => c.title,
2545
- render: (c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 1, children: [
2546
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: c.title }),
2547
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: c.sentAt.slice(0, 10) })
2548
- ] })
2549
- },
2550
- { key: "sent", label: "Sent", numeric: true, sortValue: (c) => c.sent, render: (c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: formatCount(c.sent) }) },
2551
- // No Opens column. The caption above the table told the reader, in the tool's
2552
- // own words, to "sort on clicks, not opens" because Apple Mail Privacy
2553
- // Protection fetches images on the recipient's behalf. A column whose own
2554
- // caption instructs you not to read it should not be a column — and the
2555
- // warning gets shorter as a result.
2556
- { key: "clicks", label: "Clicks", numeric: true, sortValue: (c) => c.clicks, render: (c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: formatCount(c.clicks) }) },
2557
- {
2558
- key: "unsub",
2559
- label: "Unsubscribed",
2560
- numeric: true,
2561
- sortValue: (c) => c.unsubscribed,
2562
- render: (c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: formatCount(c.unsubscribed) })
2563
- },
2564
- {
2565
- key: "ordersAfter",
2566
- label: "Orders after",
2567
- numeric: true,
2568
- sortValue: (c) => c.ordersAfter ?? null,
2569
- exportValue: (c) => c.ordersAfter ?? null,
2570
- render: (c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 1, children: [
2571
- c.ordersAfter === null || c.ordersAfter === void 0 ? "\u2014" : formatCount(c.ordersAfter),
2572
- c.windowComplete === false && c.ordersAfter !== null && c.ordersAfter !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { as: "span", size: 0, muted: true, children: " so far" })
2573
- ] })
2574
- },
2516
+ (data.campaigns?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2517
+ Section,
2518
+ {
2519
+ title: "Email campaigns",
2520
+ subtitle: "What your order book recorded after each send \u2014 what happened next, not what the send caused.",
2521
+ children: [
2522
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "Clicks are distinct subscribers. Opens are not shown: Apple Mail fetches images on the recipient\u2019s behalf, so an open is often the mail client rather than a person." }),
2523
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: [
2524
+ "The last two columns count what your order book records in the ",
2525
+ SEND_WINDOW_DAYS,
2526
+ " days after each send, ending early if another send lands first. They are what happened next, not what the send caused \u2014 but unlike anything GA4 can tell you about email, both the send times and the order times are exact."
2527
+ ] }),
2528
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2529
+ SortableTable,
2575
2530
  {
2576
- key: "revenuePerThousand",
2577
- label: "Per 1,000 sent",
2578
- numeric: true,
2579
- // Per thousand, not per send: a foundry's sends run to thousands of addresses
2580
- // against a handful of orders, so per-send lands at fractions of a cent and
2581
- // every campaign renders as the same rounded zero.
2582
- sortValue: (c) => revenuePerThousandSent(c),
2583
- exportValue: (c) => revenuePerThousandSent(c),
2584
- render: (c) => {
2585
- const value = revenuePerThousandSent(c);
2586
- if (value === null) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: "\u2014" });
2587
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 1, children: [
2588
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: formatMoney(value, data.currency ?? null) }),
2589
- c.revenueAfter !== null && c.revenueAfter !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
2590
- "from ",
2591
- formatMoney(c.revenueAfter, data.currency ?? null)
2531
+ caption: "Email campaigns by clicks",
2532
+ initialSort: "clicks",
2533
+ rows: data.campaigns ?? [],
2534
+ rowKey: (c) => `${c.sentAt}-${c.title}`,
2535
+ filterOn: (c) => `${c.title} ${c.subject}`,
2536
+ filterPlaceholder: "Filter campaigns",
2537
+ exportName: "email-campaigns",
2538
+ columns: [
2539
+ {
2540
+ key: "title",
2541
+ label: "Campaign",
2542
+ sortValue: (c) => c.title,
2543
+ render: (c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 1, children: [
2544
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: c.title }),
2545
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: c.sentAt.slice(0, 10) })
2592
2546
  ] })
2593
- ] });
2594
- }
2547
+ },
2548
+ { key: "sent", label: "Sent", numeric: true, sortValue: (c) => c.sent, render: (c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: formatCount(c.sent) }) },
2549
+ // No Opens column. The caption above the table told the reader, in the tool's
2550
+ // own words, to "sort on clicks, not opens" because Apple Mail Privacy
2551
+ // Protection fetches images on the recipient's behalf. A column whose own
2552
+ // caption instructs you not to read it should not be a column — and the
2553
+ // warning gets shorter as a result.
2554
+ { key: "clicks", label: "Clicks", numeric: true, sortValue: (c) => c.clicks, render: (c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: formatCount(c.clicks) }) },
2555
+ {
2556
+ key: "unsub",
2557
+ label: "Unsubscribed",
2558
+ numeric: true,
2559
+ sortValue: (c) => c.unsubscribed,
2560
+ render: (c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: formatCount(c.unsubscribed) })
2561
+ },
2562
+ {
2563
+ key: "ordersAfter",
2564
+ label: "Orders after",
2565
+ numeric: true,
2566
+ sortValue: (c) => c.ordersAfter ?? null,
2567
+ exportValue: (c) => c.ordersAfter ?? null,
2568
+ render: (c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 1, children: [
2569
+ c.ordersAfter === null || c.ordersAfter === void 0 ? "\u2014" : formatCount(c.ordersAfter),
2570
+ c.windowComplete === false && c.ordersAfter !== null && c.ordersAfter !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { as: "span", size: 0, muted: true, children: " so far" })
2571
+ ] })
2572
+ },
2573
+ {
2574
+ key: "revenuePerThousand",
2575
+ label: "Per 1,000 sent",
2576
+ numeric: true,
2577
+ // Per thousand, not per send: a foundry's sends run to thousands of addresses
2578
+ // against a handful of orders, so per-send lands at fractions of a cent and
2579
+ // every campaign renders as the same rounded zero.
2580
+ sortValue: (c) => revenuePerThousandSent(c),
2581
+ exportValue: (c) => revenuePerThousandSent(c),
2582
+ render: (c) => {
2583
+ const value = revenuePerThousandSent(c);
2584
+ if (value === null) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: "\u2014" });
2585
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 1, children: [
2586
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: formatMoney(value, data.currency ?? null) }),
2587
+ c.revenueAfter !== null && c.revenueAfter !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
2588
+ "from ",
2589
+ formatMoney(c.revenueAfter, data.currency ?? null)
2590
+ ] })
2591
+ ] });
2592
+ }
2593
+ }
2594
+ ]
2595
2595
  }
2596
- ]
2597
- }
2598
- )
2599
- ] })
2596
+ )
2597
+ ]
2598
+ }
2599
+ )
2600
2600
  ] });
2601
2601
  }
2602
2602
  function Verdict({ data, previous }) {
@@ -2790,56 +2790,15 @@ function DataHealthPanel({ data, diagnostics }) {
2790
2790
  ] });
2791
2791
  }
2792
2792
  function AcquisitionPanel({ data, previous }) {
2793
- const designShare = finiteOrNull(data.designIndustryShare);
2794
- const unattributedShare = finiteOrNull(data.unattributedShare);
2795
2793
  const sessions = finiteOrNull(data.totalSessions);
2796
2794
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: panelStack, children: [
2797
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: cardGrid, children: [
2798
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: stackBlock, children: [
2799
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Sessions" }),
2800
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
2801
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 4, children: sessions === null ? "\u2014" : formatCount(sessions) }),
2802
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Delta, { current: sessions, previous: finiteOrNull(previous?.totalSessions) })
2803
- ] })
2804
- ] }) }),
2805
- designShare !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: stackBlock, children: [
2806
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "From design-industry referrers" }),
2807
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
2808
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 4, children: [
2809
- data.rowsTruncated ? "at least " : "",
2810
- formatPercent(designShare, 1)
2811
- ] }),
2812
- !data.rowsTruncated && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2813
- Delta,
2814
- {
2815
- current: designShare * 100,
2816
- previous: finiteOrNull(previous?.designIndustryShare) !== null ? previous.designIndustryShare * 100 : null,
2817
- unit: "percent"
2818
- }
2819
- )
2820
- ] }),
2821
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
2822
- "Share of sessions from a known design-press referrer.",
2823
- data.rowsTruncated && " GA4 held more sources than are listed here, so the real share is higher."
2824
- ] })
2825
- ] }) }),
2826
- unattributedShare !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: stackBlock, children: [
2827
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "No source" }),
2828
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
2829
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 4, children: formatPercent(unattributedShare, 1) }),
2830
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2831
- Delta,
2832
- {
2833
- current: unattributedShare * 100,
2834
- previous: finiteOrNull(previous?.unattributedShare) !== null ? previous.unattributedShare * 100 : null,
2835
- riseIsGood: false,
2836
- unit: "percent"
2837
- }
2838
- )
2839
- ] }),
2840
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Direct visits plus rows GA4 could not attribute." })
2841
- ] }) })
2842
- ] }),
2795
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: cardGrid, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: stackBlock, children: [
2796
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Sessions" }),
2797
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: figureRow, children: [
2798
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 4, children: sessions === null ? "\u2014" : formatCount(sessions) }),
2799
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Delta, { current: sessions, previous: finiteOrNull(previous?.totalSessions) })
2800
+ ] })
2801
+ ] }) }) }),
2843
2802
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: stackBlock, children: [
2844
2803
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SectionTitle, { title: "Traffic sources" }),
2845
2804
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "Sort or filter to find a source. Excluding a row hides it from this table." }),
@@ -2981,7 +2940,16 @@ function AcquisitionPanel({ data, previous }) {
2981
2940
  " of those sales; the rest came from sources outside this list, so this column does not add up to the whole."
2982
2941
  ] })
2983
2942
  ] })
2984
- ] })
2943
+ ] }),
2944
+ (data.topLandingPages?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2945
+ Section,
2946
+ {
2947
+ title: "Where sessions began",
2948
+ tone: "secondary",
2949
+ subtitle: "The page a visit started on, busiest first.",
2950
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LandingPagesTable, { rows: data.topLandingPages ?? [] })
2951
+ }
2952
+ )
2985
2953
  ] });
2986
2954
  }
2987
2955
  function firstStepRate(steps) {
@@ -3085,23 +3053,20 @@ function JourneyPanel({ data }) {
3085
3053
  ]
3086
3054
  }
3087
3055
  ),
3088
- spread(segments) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: spread(segments) }),
3089
3056
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FunnelChart, { stages, measurement: data.measurement ?? "independent-totals" }),
3090
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: splitGrid, children: [
3091
- (data.outcomes?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3092
- Section,
3093
- {
3094
- title: "Other outcomes",
3095
- subtitle: "Successful outcomes that are not a licence sale, counted over the same window as the funnel but not a step within it.",
3096
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: cardGrid, children: (data.outcomes ?? []).map((outcome) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: stackBlock, children: [
3097
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: outcome.label }),
3098
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: outcome.count, label: outcome.label, size: 4 }),
3099
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: outcome.note })
3100
- ] }) }, outcome.key)) })
3101
- }
3102
- ),
3103
- (data.topLandingPages?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Section, { title: "Where sessions began", subtitle: "The page a visit started on, busiest first.", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LandingPagesTable, { rows: data.topLandingPages ?? [] }) })
3104
- ] }),
3057
+ spread(segments) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: spread(segments) }),
3058
+ (data.outcomes?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3059
+ Section,
3060
+ {
3061
+ title: "Other outcomes",
3062
+ subtitle: "Successful outcomes that are not a licence sale, counted over the same window as the funnel but not a step within it.",
3063
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: cardGrid, children: (data.outcomes ?? []).map((outcome) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: stackBlock, children: [
3064
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: outcome.label }),
3065
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: outcome.count, label: outcome.label, size: 4 }),
3066
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: outcome.note })
3067
+ ] }) }, outcome.key)) })
3068
+ }
3069
+ ),
3105
3070
  hiddenSteps.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: [
3106
3071
  "Not shown: ",
3107
3072
  hiddenSteps.map((step) => step.label.toLowerCase()).join(", "),
@@ -3508,9 +3473,8 @@ var GA4_ONLY_TABS = ["acquisition", "journey", "typeface-interest"];
3508
3473
  var PANELS = [
3509
3474
  { id: "overview", report: "measurement-health", label: "Overview" },
3510
3475
  { id: "acquisition", report: "acquisition", label: "Acquisition" },
3511
- { id: "journey", report: "journey", label: "Journey" },
3512
3476
  { id: "typeface-interest", report: "typeface-interest", label: "Typeface interest" },
3513
- { id: "data-health", report: "measurement-health", label: "Data health" }
3477
+ { id: "journey", report: "journey", label: "Journey" }
3514
3478
  ];
3515
3479
  function RangeSelector({
3516
3480
  value,
@@ -3691,7 +3655,7 @@ function PanelTabs({ value, onChange }) {
3691
3655
  }
3692
3656
  function CoverageRibbon({ ratio }) {
3693
3657
  if (ratio === null) {
3694
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Text, { size: 0, muted: true, children: "Every figure on this tab comes from Google Analytics. How much of your traffic it is seeing has not been checked for this window \u2014 open Overview or Data health to measure it." });
3658
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Text, { size: 0, muted: true, children: "Figures marked (GA4) on this tab come from Google Analytics. How much of your traffic it is seeing has not been checked for this window \u2014 open Overview to measure it." });
3695
3659
  }
3696
3660
  if (ratio < 0.2) return null;
3697
3661
  const seen = Math.round((1 - ratio) * 100);
@@ -3730,20 +3694,32 @@ function ReadyReport({
3730
3694
  envelope.comparison.provisional && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: " This window\u2019s last days are still being processed by GA4, so changes read low." })
3731
3695
  ] }),
3732
3696
  GA4_ONLY_TABS.includes(tabId) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CoverageRibbon, { ratio: knownShortfall(apiBaseUrl, range, range === "custom" ? custom : void 0) }),
3733
- tabId === "overview" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(OverviewPanel, { data: envelope.data, previous: envelope.comparison?.data, onBrush }),
3734
- tabId === "data-health" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Stack, { space: 4, children: [
3735
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3736
- DataHealthPanel,
3697
+ tabId === "overview" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: panelStack, children: [
3698
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(OverviewPanel, { data: envelope.data, previous: envelope.comparison?.data, onBrush }),
3699
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3700
+ Section,
3737
3701
  {
3738
- data: envelope.data,
3739
- diagnostics: diagnostics.status === "ready" ? diagnostics.envelope.data : void 0
3702
+ title: "Can I trust these figures",
3703
+ tone: "secondary",
3704
+ subtitle: trustSubtitle(envelope.data),
3705
+ collapsible: true,
3706
+ defaultOpen: false,
3707
+ children: [
3708
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3709
+ DataHealthPanel,
3710
+ {
3711
+ data: envelope.data,
3712
+ diagnostics: diagnostics.status === "ready" ? diagnostics.envelope.data : void 0
3713
+ }
3714
+ ),
3715
+ diagnostics.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Text, { size: 1, muted: true, children: "Checking configuration\u2026" }),
3716
+ diagnostics.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Card, { padding: 3, radius: 2, tone: "caution", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Text, { size: 1, children: [
3717
+ "Configuration checks could not run: ",
3718
+ diagnostics.message
3719
+ ] }) })
3720
+ ]
3740
3721
  }
3741
- ),
3742
- diagnostics.status === "loading" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Text, { size: 1, muted: true, children: "Checking configuration\u2026" }),
3743
- diagnostics.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Card, { padding: 3, radius: 2, tone: "caution", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Text, { size: 1, children: [
3744
- "Configuration checks could not run: ",
3745
- diagnostics.message
3746
- ] }) })
3722
+ )
3747
3723
  ] }),
3748
3724
  tabId === "acquisition" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AcquisitionPanel, { data: envelope.data, previous: envelope.comparison?.data }),
3749
3725
  tabId === "journey" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(JourneyPanel, { data: envelope.data }),
@@ -3789,6 +3765,14 @@ function BasisSelector({
3789
3765
  )
3790
3766
  ] });
3791
3767
  }
3768
+ function trustSubtitle(data) {
3769
+ const shortfall = data?.shortfallRatio;
3770
+ if (shortfall === null || shortfall === void 0 || !Number.isFinite(shortfall)) {
3771
+ return "How much of your traffic Google Analytics is seeing, and what it is missing.";
3772
+ }
3773
+ const seeing = Math.round((1 - shortfall) * 100);
3774
+ return shortfall >= 0 ? `Google Analytics is seeing about ${seeing}% of your traffic. Open for how that was measured.` : "Google Analytics is counting more than your server does. Open for how that was measured.";
3775
+ }
3792
3776
  function ReportPanel({
3793
3777
  tabId,
3794
3778
  report,
@@ -3803,7 +3787,7 @@ function ReportPanel({
3803
3787
  apiBaseUrl,
3804
3788
  report: "diagnostics",
3805
3789
  range: "week",
3806
- enabled: tabId === "data-health"
3790
+ enabled: tabId === "overview"
3807
3791
  }).state;
3808
3792
  const liveMessage = state.status === "loading" ? "Loading report" : state.status === "ready" ? `${PANELS.find((p) => p.id === tabId)?.label ?? tabId} updated for ${range === "custom" ? `${custom.start} to ${custom.end}` : `the selected ${range}`}` : state.status === "error" ? state.disabled ? "Visitor insights is switched off for this site" : `Report failed: ${state.message}` : "";
3809
3793
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Stack, { space: 4, children: [