@liiift-studio/sanity-visitor-insights 0.8.0 → 0.8.2
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 +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +40 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +42 -17
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/studio/Figure.tsx +42 -7
- package/src/studio/VisitorInsightsTool.tsx +15 -0
- package/src/studio/panels.test.tsx +83 -0
- package/src/studio/panels.tsx +35 -14
package/dist/index.d.mts
CHANGED
|
@@ -144,6 +144,13 @@ declare function NoticeList({ notices }: NoticeListProps): React.ReactElement |
|
|
|
144
144
|
* Every panel renders a table or labelled bars rather than a chart, and every one surfaces its own
|
|
145
145
|
* caveats inline. Tables are the accessible representation as well as the visual one, so there is
|
|
146
146
|
* no separate "view as data" toggle that could drift out of sync with what is displayed.
|
|
147
|
+
*
|
|
148
|
+
* Every array these panels read is accessed defensively, and that is not paranoia about the server.
|
|
149
|
+
* The Studio bundle and the site's API route are separate deployments on separate schedules: a
|
|
150
|
+
* Studio can be upgraded while its route still runs an older package, and then a field added in the
|
|
151
|
+
* newer version simply is not in the response. On 2026-09-01 a Studio on 0.8.0 read `data.daily`
|
|
152
|
+
* from a route still on 0.6.2 and took the whole tool down with "Cannot read properties of
|
|
153
|
+
* undefined". A panel must degrade to showing less, never to a stack trace.
|
|
147
154
|
*/
|
|
148
155
|
|
|
149
156
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -144,6 +144,13 @@ declare function NoticeList({ notices }: NoticeListProps): React.ReactElement |
|
|
|
144
144
|
* Every panel renders a table or labelled bars rather than a chart, and every one surfaces its own
|
|
145
145
|
* caveats inline. Tables are the accessible representation as well as the visual one, so there is
|
|
146
146
|
* no separate "view as data" toggle that could drift out of sync with what is displayed.
|
|
147
|
+
*
|
|
148
|
+
* Every array these panels read is accessed defensively, and that is not paranoia about the server.
|
|
149
|
+
* The Studio bundle and the site's API route are separate deployments on separate schedules: a
|
|
150
|
+
* Studio can be upgraded while its route still runs an older package, and then a field added in the
|
|
151
|
+
* newer version simply is not in the response. On 2026-09-01 a Studio on 0.8.0 read `data.daily`
|
|
152
|
+
* from a route still on 0.6.2 and took the whole tool down with "Cannot read properties of
|
|
153
|
+
* undefined". A panel must degrade to showing less, never to a stack trace.
|
|
147
154
|
*/
|
|
148
155
|
|
|
149
156
|
/**
|
package/dist/index.js
CHANGED
|
@@ -141,7 +141,7 @@ function ComparisonBar({ label, metric, max, tone = "default" }) {
|
|
|
141
141
|
const value = metric.status === "unavailable" ? null : metric.value;
|
|
142
142
|
const width = value !== null && max > 0 ? Math.max(2, value / max * 100) : 0;
|
|
143
143
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Stack, { space: 2, children: [
|
|
144
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
144
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: barHeader, children: [
|
|
145
145
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, weight: "medium", children: label }),
|
|
146
146
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MetricFigure, { metric, label, size: 1 })
|
|
147
147
|
] }),
|
|
@@ -164,10 +164,30 @@ function ComparisonBar({ label, metric, max, tone = "default" }) {
|
|
|
164
164
|
)
|
|
165
165
|
] });
|
|
166
166
|
}
|
|
167
|
+
var barHeader = {
|
|
168
|
+
display: "flex",
|
|
169
|
+
alignItems: "center",
|
|
170
|
+
justifyContent: "space-between",
|
|
171
|
+
gap: 12,
|
|
172
|
+
flexWrap: "wrap"
|
|
173
|
+
};
|
|
174
|
+
var legendRow = {
|
|
175
|
+
display: "flex",
|
|
176
|
+
gap: 12,
|
|
177
|
+
alignItems: "center",
|
|
178
|
+
flexWrap: "wrap"
|
|
179
|
+
};
|
|
180
|
+
var noticeRow = {
|
|
181
|
+
display: "flex",
|
|
182
|
+
gap: 12,
|
|
183
|
+
alignItems: "flex-start",
|
|
184
|
+
flexWrap: "wrap"
|
|
185
|
+
};
|
|
186
|
+
var noticeBadge = { flex: "0 0 auto" };
|
|
167
187
|
function NoticeList({ notices }) {
|
|
168
188
|
if (notices.length === 0) return null;
|
|
169
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "grid", gap: 8 }, children: notices.map((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)(
|
|
170
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Badge, { tone: "caution", fontSize: 0, children: "Caveat" }),
|
|
189
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "grid", gap: 8 }, children: notices.map((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: [
|
|
190
|
+
/* @__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" }) }),
|
|
171
191
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, children: notice })
|
|
172
192
|
] }) }) }, notice)) });
|
|
173
193
|
}
|
|
@@ -216,7 +236,7 @@ function TrendChart({ points, label = "Daily pageviews, GA4 against Vercel" }) {
|
|
|
216
236
|
]
|
|
217
237
|
}
|
|
218
238
|
) }),
|
|
219
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
239
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: legendRow, children: [
|
|
220
240
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: "\u2501\u2501 Vercel" }),
|
|
221
241
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: "\u2504\u2504 GA4" }),
|
|
222
242
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: [
|
|
@@ -243,6 +263,11 @@ function maxOf(metrics) {
|
|
|
243
263
|
return metrics.reduce((max, metric) => metric.status === "unavailable" ? max : Math.max(max, metric.value), 0);
|
|
244
264
|
}
|
|
245
265
|
var tableWrap = { overflowX: "auto", width: "100%" };
|
|
266
|
+
var cardGrid = {
|
|
267
|
+
display: "grid",
|
|
268
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(min(14rem, 100%), 1fr))",
|
|
269
|
+
gap: 16
|
|
270
|
+
};
|
|
246
271
|
var table = { width: "100%", borderCollapse: "collapse", minWidth: 420 };
|
|
247
272
|
var cell = { padding: "8px 12px", textAlign: "left", borderBottom: "1px solid var(--card-border-color)" };
|
|
248
273
|
var numericCell = { ...cell, textAlign: "right" };
|
|
@@ -259,15 +284,15 @@ function MeasurementHealthPanel({ data }) {
|
|
|
259
284
|
data.shortfallRatio !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, weight: "semibold", children: data.shortfallRatio >= 0 ? `GA4 saw ${formatPercent(data.shortfallRatio, 1)} fewer pageviews than Vercel` : `GA4 saw ${formatPercent(-data.shortfallRatio, 1)} more pageviews than Vercel` }),
|
|
260
285
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: data.interpretation })
|
|
261
286
|
] }) }),
|
|
262
|
-
data.daily
|
|
287
|
+
(data.daily?.length ?? 0) >= 3 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
263
288
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, children: "Day by day" }),
|
|
264
289
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: "A gap that has always been there is consent and blocking. A gap that opens on one day is an incident." }),
|
|
265
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TrendChart, { points: data.daily })
|
|
290
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(TrendChart, { points: data.daily ?? [] })
|
|
266
291
|
] }),
|
|
267
292
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
268
293
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, children: "Context" }),
|
|
269
294
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, muted: true, children: "Different units to the figures above, and to each other. Shown for scale, never differenced." }),
|
|
270
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
295
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: cardGrid, children: [
|
|
271
296
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
272
297
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "GA4 sessions" }),
|
|
273
298
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MetricFigure, { metric: data.ga4Sessions, label: "GA4 sessions" })
|
|
@@ -286,7 +311,7 @@ function MeasurementHealthPanel({ data }) {
|
|
|
286
311
|
}
|
|
287
312
|
function AcquisitionPanel({ data }) {
|
|
288
313
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 4, children: [
|
|
289
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
314
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: cardGrid, children: [
|
|
290
315
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
291
316
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Sessions" }),
|
|
292
317
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 4, children: formatCount(data.totalSessions) })
|
|
@@ -307,8 +332,8 @@ function AcquisitionPanel({ data }) {
|
|
|
307
332
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: cell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Channel" }) }),
|
|
308
333
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Sessions" }) })
|
|
309
334
|
] }) }),
|
|
310
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: data.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
311
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "row", style: cell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
335
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: (data.rows ?? []).map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
336
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "row", style: cell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
312
337
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: row.source }),
|
|
313
338
|
row.designIndustry && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Badge, { tone: "primary", fontSize: 0, children: "design industry" }),
|
|
314
339
|
row.unattributed && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Badge, { tone: "caution", fontSize: 0, children: "unattributed" })
|
|
@@ -321,24 +346,24 @@ function AcquisitionPanel({ data }) {
|
|
|
321
346
|
] });
|
|
322
347
|
}
|
|
323
348
|
function JourneyPanel({ data }) {
|
|
324
|
-
const max = maxOf(data.steps.map((step) => step.count));
|
|
349
|
+
const max = maxOf((data.steps ?? []).map((step) => step.count));
|
|
325
350
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 4, children: [
|
|
326
351
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { padding: 3, radius: 2, tone: "caution", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: data.approximationNote }) }),
|
|
327
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Stack, { space: 3, children: data.steps.map((step) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 2, children: [
|
|
352
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Stack, { space: 3, children: (data.steps ?? []).map((step) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 2, children: [
|
|
328
353
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ComparisonBar, { label: step.label, metric: step.count, max, tone: "primary" }),
|
|
329
354
|
step.conversionFromPrevious !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Text, { size: 0, muted: true, children: [
|
|
330
355
|
formatPercent(step.conversionFromPrevious, 1),
|
|
331
356
|
" of the previous measurable step"
|
|
332
357
|
] })
|
|
333
358
|
] }, step.key)) }),
|
|
334
|
-
data.topLandingPages
|
|
359
|
+
(data.topLandingPages?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
|
|
335
360
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Heading, { size: 1, children: "Where sessions began" }),
|
|
336
361
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Card, { radius: 2, tone: "transparent", border: true, style: tableWrap, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("table", { style: table, children: [
|
|
337
362
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
338
363
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: cell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Page" }) }),
|
|
339
364
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Sessions" }) })
|
|
340
365
|
] }) }),
|
|
341
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: data.topLandingPages.map((page) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
366
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: (data.topLandingPages ?? []).map((page) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
342
367
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "row", style: cell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: page.path }) }),
|
|
343
368
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: formatCount(page.sessions) }) })
|
|
344
369
|
] }, page.path)) })
|
|
@@ -358,7 +383,7 @@ function TypefaceInterestPanel({ data }) {
|
|
|
358
383
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Bought" }) }),
|
|
359
384
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "col", style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Label, { size: 1, muted: true, children: "Test rate" }) })
|
|
360
385
|
] }) }),
|
|
361
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: data.rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
386
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: (data.rows ?? []).map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
362
387
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { scope: "row", style: cell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_sanity_ui_compat2.Text, { size: 1, children: row.typeface }) }),
|
|
363
388
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MetricFigure, { metric: row.viewed, label: `${row.typeface} viewed`, size: 1 }) }),
|
|
364
389
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: numericCell, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MetricFigure, { metric: row.tested, label: `${row.typeface} tested`, size: 1 }) }),
|