@liiift-studio/sanity-visitor-insights 0.37.0 → 0.38.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 +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +40 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +41 -16
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +21 -18
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +21 -18
- package/dist/server.mjs.map +1 -1
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{vercel-TiuokGk8.d.mts → vercel-BG4eJo0f.d.mts} +10 -4
- package/dist/{vercel-TiuokGk8.d.ts → vercel-BG4eJo0f.d.ts} +10 -4
- package/package.json +1 -1
- package/src/server/createHandler.ts +41 -2
- package/src/server/diagnostics.ts +1 -1
- package/src/server/ga4.ts +24 -19
- package/src/server/reports/measurementHealth.ts +1 -1
- package/src/server/reports/reports.test.ts +43 -16
- package/src/server/vercel.ts +46 -28
- package/src/studio/CrossSourceTimeline.tsx +86 -19
- package/src/studio/Figure.tsx +9 -14
- package/src/studio/panels.test.tsx +76 -1
- package/src/studio/panels.tsx +10 -0
package/dist/index.d.mts
CHANGED
|
@@ -467,6 +467,20 @@ interface Series {
|
|
|
467
467
|
* a site running at a flat 20% would show a full-height line and read as healthy.
|
|
468
468
|
*/
|
|
469
469
|
domain?: [number, number];
|
|
470
|
+
/**
|
|
471
|
+
* The same series in the previous equivalent window, aligned by day OFFSET rather than by date.
|
|
472
|
+
*
|
|
473
|
+
* Drawn as a quiet ghost behind the row. Without it every row is an unanchored silhouette: a
|
|
474
|
+
* flat line at 60 and a flat line at 6,000 are the same picture, and "traffic looks lumpy" is an
|
|
475
|
+
* impression nobody can falsify. The comparison envelope is already fetched and already handed
|
|
476
|
+
* to the metric cards, so this costs a prop and no network.
|
|
477
|
+
*
|
|
478
|
+
* What it buys is bigger than a reference line. It turns the tool's most fragile asset — a dated
|
|
479
|
+
* alarm produced by a heuristic with a 21-day floor and a percentile threshold — from something
|
|
480
|
+
* the reader has to trust into something they can check: the coverage row's step reads against
|
|
481
|
+
* last period's flat line whether or not the detector fired.
|
|
482
|
+
*/
|
|
483
|
+
comparison?: SeriesPoint[];
|
|
470
484
|
points: SeriesPoint[];
|
|
471
485
|
/**
|
|
472
486
|
* What a lossier source saw of the same thing.
|
package/dist/index.d.ts
CHANGED
|
@@ -467,6 +467,20 @@ interface Series {
|
|
|
467
467
|
* a site running at a flat 20% would show a full-height line and read as healthy.
|
|
468
468
|
*/
|
|
469
469
|
domain?: [number, number];
|
|
470
|
+
/**
|
|
471
|
+
* The same series in the previous equivalent window, aligned by day OFFSET rather than by date.
|
|
472
|
+
*
|
|
473
|
+
* Drawn as a quiet ghost behind the row. Without it every row is an unanchored silhouette: a
|
|
474
|
+
* flat line at 60 and a flat line at 6,000 are the same picture, and "traffic looks lumpy" is an
|
|
475
|
+
* impression nobody can falsify. The comparison envelope is already fetched and already handed
|
|
476
|
+
* to the metric cards, so this costs a prop and no network.
|
|
477
|
+
*
|
|
478
|
+
* What it buys is bigger than a reference line. It turns the tool's most fragile asset — a dated
|
|
479
|
+
* alarm produced by a heuristic with a 21-day floor and a percentile threshold — from something
|
|
480
|
+
* the reader has to trust into something they can check: the coverage row's step reads against
|
|
481
|
+
* last period's flat line whether or not the detector fired.
|
|
482
|
+
*/
|
|
483
|
+
comparison?: SeriesPoint[];
|
|
470
484
|
points: SeriesPoint[];
|
|
471
485
|
/**
|
|
472
486
|
* What a lossier source saw of the same thing.
|
package/dist/index.js
CHANGED
|
@@ -536,8 +536,8 @@ function FunnelChart({ stages, measurement }) {
|
|
|
536
536
|
] }),
|
|
537
537
|
/* @__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}%` } }) }),
|
|
538
538
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: [
|
|
539
|
-
index === 0 ? "entry step" : entry >= MIN_RATE_DENOMINATOR ? `${formatPercent(share, 1)} of ${stages[0]?.label.toLowerCase()}` : "too few to give a rate",
|
|
540
|
-
index > 1 && stage.conversionFromPrevious !== null && previous
|
|
539
|
+
index === 0 ? "entry step" : entry >= MIN_RATE_DENOMINATOR && stage.value >= MIN_RATE_DENOMINATOR ? `${formatPercent(share, 1)} of ${stages[0]?.label.toLowerCase()}` : "too few to give a rate",
|
|
540
|
+
index > 1 && stage.conversionFromPrevious !== null && previous ? (previous.value ?? 0) >= MIN_RATE_DENOMINATOR && stage.value >= MIN_RATE_DENOMINATOR ? ` \xB7 ${formatPercent(stage.conversionFromPrevious, 1)} of ${previous.label.toLowerCase()}` : ` \xB7 too few from ${previous.label.toLowerCase()} to give a rate` : ""
|
|
541
541
|
] })
|
|
542
542
|
]
|
|
543
543
|
}
|
|
@@ -920,7 +920,7 @@ var import_d3_shape = require("d3-shape");
|
|
|
920
920
|
var import_d3_array = require("d3-array");
|
|
921
921
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
922
922
|
var WIDTH = 760;
|
|
923
|
-
var
|
|
923
|
+
var BASELINE_GUTTER = 18;
|
|
924
924
|
var AXIS_HEIGHT = 26;
|
|
925
925
|
var GUTTER = 52;
|
|
926
926
|
var RIGHT_PAD = 12;
|
|
@@ -1013,6 +1013,8 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1013
1013
|
return [...all].sort();
|
|
1014
1014
|
}, [series]);
|
|
1015
1015
|
const plotWidth = Math.max(120, measured - GUTTER - RIGHT_PAD);
|
|
1016
|
+
const bandHeight = Math.round(Math.max(58, Math.min(150, plotWidth / 7)));
|
|
1017
|
+
const rowHeight = bandHeight + LABEL_STRIP + BASELINE_GUTTER;
|
|
1016
1018
|
import_react3.default.useLayoutEffect(() => {
|
|
1017
1019
|
const frame = frameNode;
|
|
1018
1020
|
if (!frame) return;
|
|
@@ -1030,7 +1032,7 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1030
1032
|
observer.observe(frame);
|
|
1031
1033
|
return () => observer.disconnect();
|
|
1032
1034
|
}, [frameNode]);
|
|
1033
|
-
const height = TOP_PAD + series.length *
|
|
1035
|
+
const height = TOP_PAD + series.length * rowHeight + AXIS_HEIGHT;
|
|
1034
1036
|
const x = (0, import_react3.useMemo)(() => {
|
|
1035
1037
|
if (dates.length === 0) return null;
|
|
1036
1038
|
return (0, import_d3_scale.scaleUtc)().domain([/* @__PURE__ */ new Date(`${dates[0]}T00:00:00Z`), /* @__PURE__ */ new Date(`${dates[dates.length - 1]}T00:00:00Z`)]).range([GUTTER, GUTTER + plotWidth]);
|
|
@@ -1217,17 +1219,17 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1217
1219
|
x1: at,
|
|
1218
1220
|
x2: at,
|
|
1219
1221
|
y1: TOP_PAD,
|
|
1220
|
-
y2:
|
|
1222
|
+
y2: height - AXIS_HEIGHT + 4,
|
|
1221
1223
|
stroke: "currentColor",
|
|
1222
1224
|
strokeWidth: 1,
|
|
1223
|
-
opacity: 0.
|
|
1225
|
+
opacity: 0.18
|
|
1224
1226
|
},
|
|
1225
1227
|
`grid-${date}`
|
|
1226
1228
|
);
|
|
1227
1229
|
}),
|
|
1228
1230
|
series.map((row, rowIndex) => {
|
|
1229
|
-
const top = TOP_PAD + rowIndex *
|
|
1230
|
-
const bottom = top +
|
|
1231
|
+
const top = TOP_PAD + rowIndex * rowHeight;
|
|
1232
|
+
const bottom = top + rowHeight - BASELINE_GUTTER;
|
|
1231
1233
|
const plotTop = top + LABEL_STRIP;
|
|
1232
1234
|
const values = row.points.map((p) => p.value).filter((v) => v !== null);
|
|
1233
1235
|
const peak = (0, import_d3_array.max)(values) ?? 0;
|
|
@@ -1236,11 +1238,15 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1236
1238
|
const zeroY = y(0);
|
|
1237
1239
|
const at = (p) => x(/* @__PURE__ */ new Date(`${p.date}T00:00:00Z`));
|
|
1238
1240
|
const defined = (p) => p.value !== null;
|
|
1239
|
-
const lineGen = (0, import_d3_shape.line)().defined(defined).x(at).y((p) => y(p.value)).curve(import_d3_shape.
|
|
1241
|
+
const lineGen = (0, import_d3_shape.line)().defined(defined).x(at).y((p) => y(p.value)).curve(import_d3_shape.curveLinear);
|
|
1240
1242
|
const shortfallPoints = row.shortfall?.points ?? [];
|
|
1241
1243
|
const byDate = new Map(shortfallPoints.map((p) => [p.date, p.value]));
|
|
1242
|
-
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.
|
|
1244
|
+
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);
|
|
1243
1245
|
const path = row.mark === "events" ? "" : lineGen(row.points) ?? "";
|
|
1246
|
+
const ghost = row.comparison && row.comparison.length > 1 ? lineGen(row.comparison.slice(0, dates.length).map((point, i) => ({
|
|
1247
|
+
date: dates[i] ?? point.date,
|
|
1248
|
+
value: point.value
|
|
1249
|
+
}))) ?? "" : "";
|
|
1244
1250
|
const pitch = plotWidth / Math.max(1, dates.length - 1);
|
|
1245
1251
|
const stemWidth = Math.max(1, Math.min(9, pitch > 2 ? pitch - 1 : pitch));
|
|
1246
1252
|
const stems = row.mark === "events" ? row.points.filter((p) => p.value !== null && p.value !== 0) : [];
|
|
@@ -1259,6 +1265,17 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1259
1265
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("text", { x: GUTTER - 6, y: y(y.domain()[1]) + 4, textAnchor: "end", fontSize: AXIS_TYPE, fill: "currentColor", opacity: 0.7, children: formatValue(y.domain()[1], row.unit, currency) }),
|
|
1260
1266
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("text", { x: GUTTER - 6, y: bottom + 4, textAnchor: "end", fontSize: AXIS_TYPE, fill: "currentColor", opacity: 0.7, children: formatValue(0, row.unit, currency) }),
|
|
1261
1267
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { clipPath: `url(#${clipId})`, children: [
|
|
1268
|
+
ghost && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1269
|
+
"path",
|
|
1270
|
+
{
|
|
1271
|
+
d: ghost,
|
|
1272
|
+
fill: "none",
|
|
1273
|
+
stroke: "currentColor",
|
|
1274
|
+
strokeWidth: 1,
|
|
1275
|
+
strokeDasharray: "2 3",
|
|
1276
|
+
opacity: 0.35
|
|
1277
|
+
}
|
|
1278
|
+
),
|
|
1262
1279
|
gap && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: gap, fill: "currentColor", opacity: 0.48 }),
|
|
1263
1280
|
shortfallLine && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: shortfallLine, fill: "none", stroke: "currentColor", strokeWidth: 1.5, strokeDasharray: "6 4", opacity: 0.8 }),
|
|
1264
1281
|
zeroes.map((p) => {
|
|
@@ -1318,7 +1335,7 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1318
1335
|
x1: at,
|
|
1319
1336
|
x2: at,
|
|
1320
1337
|
y1: TOP_PAD - 6,
|
|
1321
|
-
y2: TOP_PAD + series.length *
|
|
1338
|
+
y2: TOP_PAD + series.length * rowHeight - BASELINE_GUTTER,
|
|
1322
1339
|
stroke: "currentColor",
|
|
1323
1340
|
strokeWidth: 1.25,
|
|
1324
1341
|
strokeDasharray: "4 3",
|
|
@@ -1335,7 +1352,7 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1335
1352
|
x: GUTTER,
|
|
1336
1353
|
y: TOP_PAD - 8,
|
|
1337
1354
|
width: Math.max(0, x(/* @__PURE__ */ new Date(`${dates[brushed[0]]}T00:00:00Z`)) - GUTTER),
|
|
1338
|
-
height: series.length *
|
|
1355
|
+
height: series.length * rowHeight - 10,
|
|
1339
1356
|
fill: "var(--card-bg-color, #ffffff)",
|
|
1340
1357
|
opacity: 0.72
|
|
1341
1358
|
}
|
|
@@ -1346,7 +1363,7 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1346
1363
|
x: x(/* @__PURE__ */ new Date(`${dates[brushed[1]]}T00:00:00Z`)),
|
|
1347
1364
|
y: TOP_PAD - 8,
|
|
1348
1365
|
width: Math.max(0, GUTTER + plotWidth - x(/* @__PURE__ */ new Date(`${dates[brushed[1]]}T00:00:00Z`))),
|
|
1349
|
-
height: series.length *
|
|
1366
|
+
height: series.length * rowHeight - 10,
|
|
1350
1367
|
fill: "var(--card-bg-color, #ffffff)",
|
|
1351
1368
|
opacity: 0.72
|
|
1352
1369
|
}
|
|
@@ -1358,7 +1375,7 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
|
|
|
1358
1375
|
x1: x(/* @__PURE__ */ new Date(`${hoveredDate}T00:00:00Z`)),
|
|
1359
1376
|
x2: x(/* @__PURE__ */ new Date(`${hoveredDate}T00:00:00Z`)),
|
|
1360
1377
|
y1: TOP_PAD - 6,
|
|
1361
|
-
y2: TOP_PAD + series.length *
|
|
1378
|
+
y2: TOP_PAD + series.length * rowHeight - 18,
|
|
1362
1379
|
stroke: "currentColor",
|
|
1363
1380
|
strokeWidth: 1,
|
|
1364
1381
|
opacity: 0.8
|
|
@@ -1444,7 +1461,7 @@ var readoutRow = {
|
|
|
1444
1461
|
gap: 14,
|
|
1445
1462
|
flexWrap: "wrap",
|
|
1446
1463
|
alignItems: "baseline",
|
|
1447
|
-
minHeight:
|
|
1464
|
+
minHeight: 54
|
|
1448
1465
|
};
|
|
1449
1466
|
var revealButton = {
|
|
1450
1467
|
appearance: "none",
|
|
@@ -1578,6 +1595,8 @@ function OverviewPanel({ data, previous, onBrush }) {
|
|
|
1578
1595
|
complete: true,
|
|
1579
1596
|
unit: "count",
|
|
1580
1597
|
points: (data.crossSource ?? []).map((d) => ({ date: d.date, value: d.vercelPageviews })),
|
|
1598
|
+
// The same row last period, so the reader can see whether this shape is normal.
|
|
1599
|
+
comparison: (previous?.crossSource ?? []).map((d) => ({ date: d.date, value: d.vercelPageviews })),
|
|
1581
1600
|
shortfall: {
|
|
1582
1601
|
label: "Seen by GA4",
|
|
1583
1602
|
source: "GA4",
|
|
@@ -1617,6 +1636,12 @@ function OverviewPanel({ data, previous, onBrush }) {
|
|
|
1617
1636
|
points: (data.crossSource ?? []).map((d) => ({
|
|
1618
1637
|
date: d.date,
|
|
1619
1638
|
value: d.vercelPageviews !== null && d.ga4Pageviews !== null && d.vercelPageviews > 0 ? Math.min(1, d.ga4Pageviews / d.vercelPageviews) : null
|
|
1639
|
+
})),
|
|
1640
|
+
// The most valuable ghost in the chart: a collapse reads as a step away
|
|
1641
|
+
// from last period's flat line, whether or not the detector fired.
|
|
1642
|
+
comparison: (previous?.crossSource ?? []).map((d) => ({
|
|
1643
|
+
date: d.date,
|
|
1644
|
+
value: d.vercelPageviews !== null && d.ga4Pageviews !== null && d.vercelPageviews > 0 ? Math.min(1, d.ga4Pageviews / d.vercelPageviews) : null
|
|
1620
1645
|
}))
|
|
1621
1646
|
}] : [],
|
|
1622
1647
|
...data.crossSource?.some((d) => d.revenue !== null) ? [{
|