@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 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)(import_sanity_ui_compat.Flex, { align: "center", justify: "space-between", gap: 3, children: [
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)(import_sanity_ui_compat.Flex, { gap: 3, align: "flex-start", children: [
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)(import_sanity_ui_compat.Flex, { gap: 3, align: "center", wrap: "wrap", children: [
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.length >= 3 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
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)(import_sanity_ui_compat2.Grid, { columns: [1, 3], gap: 4, children: [
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)(import_sanity_ui_compat2.Grid, { columns: [1, 3], gap: 4, children: [
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)(import_sanity_ui_compat2.Flex, { gap: 2, align: "center", children: [
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.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_sanity_ui_compat2.Stack, { space: 3, children: [
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 }) }),