@liiift-studio/sanity-visitor-insights 0.72.0 → 0.74.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.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-CMhL0Chn.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-CMhL0Chn.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-DfjCK0XY.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-DfjCK0XY.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-CMhL0Chn.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-CMhL0Chn.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-DfjCK0XY.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-DfjCK0XY.js';
5
5
 
6
6
  /**
7
7
  * Data hook for the Studio panels.
package/dist/index.js CHANGED
@@ -279,10 +279,6 @@ var SERIES = {
279
279
  /** Revenue, same source, different unit. */
280
280
  revenue: "#BF5D9B"
281
281
  };
282
- var REGION_ALPHA = 0.18;
283
- function seriesFill(key, alpha = REGION_ALPHA) {
284
- return withAlpha(SERIES[key], alpha);
285
- }
286
282
  function withAlpha(hex, alpha) {
287
283
  const r = parseInt(hex.slice(1, 3), 16);
288
284
  const g = parseInt(hex.slice(3, 5), 16);
@@ -304,21 +300,14 @@ var MARKS = {
304
300
  "chart.coverage": { key: "ga4Sessions", alpha: 1, role: "data", note: "Share one source saw of another" },
305
301
  "chart.stem": { key: "orders", alpha: 1, role: "data", note: "One day that had orders" },
306
302
  "chart.zeroTick": { key: "orders", alpha: 1, role: "data", note: "A day MEASURED at zero \u2014 the package's founding distinction" },
307
- "chart.regionEdge": { key: "ga4Pageviews", alpha: 1, role: "data", note: "The upper boundary of the shortfall region" },
308
- "chart.region": {
309
- key: "ga4Pageviews",
310
- alpha: 0.22,
311
- role: "fill",
312
- boundary: "chart.regionEdge",
313
- note: "What the lossier source missed. Light on purpose \u2014 the line it covers must stay readable"
314
- },
303
+ // Renamed from `chart.regionEdge`. It was the boundary of a filled region; the region is gone
304
+ // its width was the absolute gap, which under a flat coverage rate is the traffic curve scaled
305
+ // down, so it widened on busy days with no change in the instrument. This is now simply the
306
+ // lossier source's own line, which is what the collapse detector and the spoken summary read.
307
+ "chart.lossy": { key: "ga4Pageviews", alpha: 1, role: "data", note: "The lossier source's own level, dashed beneath the complete one" },
315
308
  "chart.ghost": { key: "comparison", alpha: 1, role: "data", note: "The same window last period, behind everything" },
316
309
  "chart.grid": { key: "neutral", alpha: 0.18, role: "furniture", note: "Grid rules" },
317
310
  "bar.fill": { key: "vercel", alpha: 1, role: "data", note: "Every proportion, comparison and funnel bar" },
318
- // The hatch on a rung whose count covers fewer days than the rail it sits in. A `data` mark, not
319
- // furniture: it is the only thing on that rung saying the figure is not comparable, so it has to
320
- // be as visible as a bar would have been.
321
- "bar.partial": { key: "vercel", alpha: 1, role: "data", note: "A funnel rung measured over part of the window" },
322
311
  "bar.track": { key: "neutral", alpha: 0.12, role: "furniture", note: "The rail a bar sits in" },
323
312
  // Neither a source nor money. It was drawn in the REVENUE hue, so a magenta bar between funnel
324
313
  // rungs read as takings; GA4's hue was rejected for the opposite reason, since that hue means
@@ -750,27 +739,14 @@ function FunnelChart({ stages, measurement }) {
750
739
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, weight: "medium", children: stage.label }),
751
740
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, weight: "semibold", children: formatCount(stage.value) })
752
741
  ] }),
753
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-hidden": "true", style: { ...barTrack, height: 10 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
754
- "div",
755
- {
756
- style: stage.partial ? {
757
- height: "100%",
758
- width: "100%",
759
- borderRadius: 3,
760
- backgroundImage: `repeating-linear-gradient(135deg, ${mark("bar.partial")} 0 4px, transparent 4px 8px)`
761
- } : { ...barFill, width: `${width}%` }
762
- }
763
- ) }),
742
+ stage.partial ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: partialSlot }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-hidden": "true", style: { ...barTrack, height: 10 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...barFill, width: `${width}%` } }) }),
764
743
  rateLine(stage, index, entry, share, stages[0]?.label ?? "")
765
744
  ]
766
745
  }
767
746
  )
768
747
  ] }, stage.key);
769
748
  }) }),
770
- stages.some((stage) => stage.partial) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: funnelKey, children: [
771
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: funnelKeySwatch }),
772
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: "Striped: tracking started partway through this period, so the step covers fewer days than the ones above it and cannot be compared with them." })
773
- ] }),
749
+ stages.some((stage) => stage.partial) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: "Steps with no bar were only tracked for part of this period, so they cover fewer days than the ones above and cannot be drawn against them. Their counts are exact for the days they do cover." }),
774
750
  withheldAny && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: [
775
751
  "Shares are shown only for steps at least ",
776
752
  MIN_RATE_DENOMINATOR,
@@ -787,15 +763,7 @@ function gapLabel(delta, measurement, entry = 0) {
787
763
  }
788
764
  return `${formatCount(-delta)} more \u2014 not a subset of the step above`;
789
765
  }
790
- var funnelKey = { display: "flex", alignItems: "center", gap: 8, paddingTop: 4 };
791
- var funnelKeySwatch = {
792
- display: "inline-block",
793
- width: 24,
794
- height: 10,
795
- flex: "0 0 auto",
796
- borderRadius: 3,
797
- backgroundImage: `repeating-linear-gradient(135deg, ${mark("bar.partial")} 0 4px, transparent 4px 8px)`
798
- };
766
+ var partialSlot = { height: 10 };
799
767
  var funnelList = { listStyle: "none", margin: 0, padding: 0 };
800
768
  var funnelItem = { display: "grid", gap: 4 };
801
769
  function funnelGapStyle(hasBar) {
@@ -1013,11 +981,12 @@ function EstimateDotPlot({
1013
981
  " of ",
1014
982
  formatCount(estimate.actual),
1015
983
  wide && " \u2014 too small a sample to lean on",
984
+ estimate.note && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: estimateNote, children: estimate.note }),
1016
985
  estimate.rate > 1.05 && " \u2014 over 100%, so GA4 is counting more than the source it is checked against. That is usually a tag firing twice, not extra traffic."
1017
986
  ] })
1018
987
  ] }, estimate.basis);
1019
988
  }),
1020
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: "Each is a separate way of asking the same question. Where they disagree, the spread is the answer\u2019s uncertainty \u2014 the rule marks 100%." })
989
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: "The rule marks 100%." })
1021
990
  ] });
1022
991
  }
1023
992
  function SurvivalLines({
@@ -1435,6 +1404,7 @@ var estimateTrack = {
1435
1404
  borderRadius: 2,
1436
1405
  background: mark("bar.track")
1437
1406
  };
1407
+ var estimateNote = { display: "block", marginTop: 2, opacity: 0.85 };
1438
1408
  var estimateRule = {
1439
1409
  position: "absolute",
1440
1410
  top: 0,
@@ -2008,8 +1978,6 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
2008
1978
  const defined = (p) => p.value !== null;
2009
1979
  const lineGen = (0, import_d3_shape.line)().defined(defined).x(at).y((p) => y(p.value)).curve(import_d3_shape.curveLinear);
2010
1980
  const shortfallPoints = row.shortfall?.points ?? [];
2011
- const byDate = new Map(shortfallPoints.map((p) => [p.date, p.value]));
2012
- const gapGen = (0, import_d3_shape.area)().defined((p) => p.value !== null && byDate.get(p.date) != null).x(at).y0((p) => y(byDate.get(p.date))).y1((p) => y(p.value)).curve(import_d3_shape.curveLinear);
2013
1981
  const path = row.mark === "events" ? "" : lineGen(row.points) ?? "";
2014
1982
  const ghost = row.comparison && row.comparison.length > 1 ? lineGen(row.comparison.slice(0, dates.length).map((point, i) => ({
2015
1983
  date: dates[i] ?? point.date,
@@ -2019,7 +1987,6 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
2019
1987
  const stemWidth = Math.max(1, Math.min(9, pitch > 2 ? pitch - 1 : pitch));
2020
1988
  const stems = row.mark === "events" ? row.points.filter((p) => p.value !== null && p.value !== 0) : [];
2021
1989
  const zeroes = row.mark === "events" && pitch >= 6 ? row.points.filter((p) => p.value === 0) : [];
2022
- const gap = row.shortfall ? gapGen(row.points) ?? "" : "";
2023
1990
  const shortfallLine = row.shortfall ? lineGen(shortfallPoints) ?? "" : "";
2024
1991
  const clipId = `${instanceId}-row-${rowIndex}`;
2025
1992
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { children: [
@@ -2044,20 +2011,12 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
2044
2011
  strokeDasharray: "2 3"
2045
2012
  }
2046
2013
  ),
2047
- gap && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2048
- "path",
2049
- {
2050
- d: gap,
2051
- fill: shortfallStroke ? seriesFill(colorFor(row.shortfall.source, row.unit, row.shortfall.color), MARKS["chart.region"].alpha) : "currentColor",
2052
- opacity: shortfallStroke ? 1 : 0.48
2053
- }
2054
- ),
2055
2014
  shortfallLine && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2056
2015
  "path",
2057
2016
  {
2058
2017
  d: shortfallLine,
2059
2018
  fill: "none",
2060
- stroke: mark("chart.regionEdge"),
2019
+ stroke: mark("chart.lossy"),
2061
2020
  strokeWidth: 1.5,
2062
2021
  strokeDasharray: "6 4"
2063
2022
  }
@@ -2229,22 +2188,13 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
2229
2188
  ] }, m.label))
2230
2189
  ] }),
2231
2190
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: legendRow, children: [
2232
- series.some((row) => row.shortfall) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2233
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2234
- LegendKey,
2235
- {
2236
- swatch: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: legendDash(shortfallColour(series)) }),
2237
- label: `${shortfallLabel(series)} \u2014 the dashed line`
2238
- }
2239
- ),
2240
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2241
- LegendKey,
2242
- {
2243
- swatch: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: legendBlock(seriesFill(shortfallKey(series), MARKS["chart.region"].alpha), shortfallColour(series)) }),
2244
- label: "Shaded: what your analytics did not see"
2245
- }
2246
- )
2247
- ] }),
2191
+ series.some((row) => row.shortfall) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2192
+ LegendKey,
2193
+ {
2194
+ swatch: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: legendDash(shortfallColour(series)) }),
2195
+ label: `${shortfallLabel(series)} \u2014 the dashed line`
2196
+ }
2197
+ ) }),
2248
2198
  series.some((row) => (row.comparison?.length ?? 0) > 1) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2249
2199
  LegendKey,
2250
2200
  {
@@ -2281,15 +2231,6 @@ function legendDash(colour) {
2281
2231
  borderTop: `2px dashed ${colour}`
2282
2232
  };
2283
2233
  }
2284
- function legendBlock(fill, edge) {
2285
- return {
2286
- display: "inline-block",
2287
- width: 18,
2288
- height: 10,
2289
- background: fill,
2290
- borderTop: `1.5px dashed ${edge}`
2291
- };
2292
- }
2293
2234
  var legendMarker = {
2294
2235
  display: "inline-block",
2295
2236
  position: "relative",
@@ -2383,9 +2324,6 @@ function metricOr(metric, detail) {
2383
2324
  return { status: "unavailable", reason: "route_outdated", detail };
2384
2325
  }
2385
2326
  var OLDER_ROUTE = "This site\u2019s analytics route is older than this figure \u2014 it will appear after the site is next deployed.";
2386
- function maxOf(metrics) {
2387
- return metrics.reduce((max, metric) => !metric || metric.status === "unavailable" ? max : Math.max(max, metric.value), 0);
2388
- }
2389
2327
  function finiteOrNull(value) {
2390
2328
  return typeof value === "number" && Number.isFinite(value) ? value : null;
2391
2329
  }
@@ -2438,7 +2376,7 @@ function OverviewPanel({ data, previous, onBrush }) {
2438
2376
  ),
2439
2377
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Delta, { current: metricSortValue(data.revenue), previous: metricSortValue(previous?.revenue) })
2440
2378
  ] }),
2441
- rankLine(data.crossSource, (d) => d.revenue) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: rankLine(data.crossSource, (d) => d.revenue) })
2379
+ revenueCoversEveryOrder(data) && rankLine(data.crossSource, (d) => d.revenue) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: rankLine(data.crossSource, (d) => d.revenue) })
2442
2380
  ] }) }),
2443
2381
  /* @__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: [
2444
2382
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Orders" }),
@@ -2847,7 +2785,6 @@ function Verdict({ data, previous }) {
2847
2785
  );
2848
2786
  }
2849
2787
  function DataHealthPanel({ data, diagnostics }) {
2850
- const pageviewMax = maxOf([data.ga4Pageviews, data.vercelPageviews]);
2851
2788
  const contained = isContainment(data.vercelPageviews, data.ga4Pageviews);
2852
2789
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 4, children: [
2853
2790
  /* @__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: 2, children: [
@@ -3160,8 +3097,9 @@ function AcquisitionPanel({ data, previous }) {
3160
3097
  formatMoney(finiteOrNull(data.actualRevenue) ?? 0, data.currency ?? null),
3161
3098
  " from",
3162
3099
  " ",
3163
- finiteOrNull(data.actualOrders) === null ? "your orders" : `${formatCount(data.actualOrders)} orders`,
3164
- " in Sanity, split across the ",
3100
+ finiteOrNull(data.actualOrders) === null ? "your orders" : (data.ordersMissingTotal ?? 0) > 0 ? `the ${formatCount(data.actualOrders - data.ordersMissingTotal)} of ${formatCount(data.actualOrders)} orders that carry an amount` : `${formatCount(data.actualOrders)} orders`,
3101
+ " ",
3102
+ "in Sanity, split across the ",
3165
3103
  formatCount(data.trackedPurchases ?? 0),
3166
3104
  " purchase",
3167
3105
  data.trackedPurchases === 1 ? "" : "s",
@@ -3416,7 +3354,7 @@ function TypefaceInterestPanel({ data }) {
3416
3354
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: [
3417
3355
  "Sort by any column. \u201CSells vs catalogue\u201D compares each family\u2019s sales-per-view against the catalogue as a whole \u2014 1.0\xD7 is average, and sorting up finds the families that are looked at and do not sell. It is a comparison between your families, not the share of visitors who buy: Google Analytics sees only a fraction of the views, which moves every family together and so cancels out here. With one or two sales a family will still swing a long way, so read it alongside the order counts beside it.",
3418
3356
  " ",
3419
- "Bought and Revenue come from your own orders and are exact \u2014 do not scale those up.",
3357
+ "Bought and Revenue come from your own orders, not from Google Analytics \u2014 do not scale those up.",
3420
3358
  " ",
3421
3359
  "A family needs a reasonable number of views before it gets a comparison at all; below that it says so rather than ranking one order against the catalogue."
3422
3360
  ] }),
@@ -3464,6 +3402,8 @@ function TypefaceInterestPanel({ data }) {
3464
3402
  },
3465
3403
  {
3466
3404
  key: "bought",
3405
+ // Never folded. Zero sales is the finding, not an empty column.
3406
+ alwaysShow: true,
3467
3407
  label: "Bought (orders)",
3468
3408
  numeric: true,
3469
3409
  sortValue: (row) => metricSortValue(row.bought),
@@ -3471,6 +3411,8 @@ function TypefaceInterestPanel({ data }) {
3471
3411
  },
3472
3412
  {
3473
3413
  key: "revenue",
3414
+ // Same reason as Bought: an exact zero from the order book is a measurement.
3415
+ alwaysShow: true,
3474
3416
  label: "Revenue (orders)",
3475
3417
  numeric: true,
3476
3418
  sortValue: (row) => metricSortValue(row.revenue),
@@ -3564,7 +3506,19 @@ function TypefaceInterestPanel({ data }) {
3564
3506
  }
3565
3507
  )
3566
3508
  ] }),
3567
- data.revenueIsApportioned && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Revenue is apportioned: an order covering several families or licences is split evenly between them, because the order documents carry no per-line value." })
3509
+ data.revenueIsApportioned && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "An order covering several families is split evenly between them, because the order documents carry no per-line value." }),
3510
+ finiteOrNull(data.unattributedRevenue) !== null && data.unattributedRevenue > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
3511
+ formatMoney(data.unattributedRevenue, data.currency ?? null),
3512
+ " of this range\u2019s takings is not in the column above \u2014 those orders name no family in this catalogue."
3513
+ ] }),
3514
+ finiteOrNull(data.ordersMissingTotal) !== null && data.ordersMissingTotal > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
3515
+ formatCount(data.ordersMissingTotal),
3516
+ " order",
3517
+ data.ordersMissingTotal === 1 ? "" : "s",
3518
+ " in this range carry no amount, so the revenue column does not cover ",
3519
+ data.ordersMissingTotal === 1 ? "it" : "them",
3520
+ "."
3521
+ ] })
3568
3522
  ] });
3569
3523
  }
3570
3524
  var CHECK_TONE = {