@liiift-studio/sanity-visitor-insights 0.1.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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +181 -0
  3. package/dist/index.d.mts +155 -0
  4. package/dist/index.d.ts +155 -0
  5. package/dist/index.js +652 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +609 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/dist/ranges-D6AZwpmm.d.mts +282 -0
  10. package/dist/ranges-D6AZwpmm.d.ts +282 -0
  11. package/dist/server.d.mts +280 -0
  12. package/dist/server.d.ts +280 -0
  13. package/dist/server.js +937 -0
  14. package/dist/server.js.map +1 -0
  15. package/dist/server.mjs +882 -0
  16. package/dist/server.mjs.map +1 -0
  17. package/package.json +68 -0
  18. package/src/boundary.test.ts +93 -0
  19. package/src/core/core.test.ts +175 -0
  20. package/src/core/cutover.ts +109 -0
  21. package/src/core/ranges.ts +103 -0
  22. package/src/core/siteConfig.ts +150 -0
  23. package/src/index.ts +86 -0
  24. package/src/reportData.ts +120 -0
  25. package/src/server/auth.ts +133 -0
  26. package/src/server/cache.ts +75 -0
  27. package/src/server/createHandler.ts +199 -0
  28. package/src/server/ga4.ts +149 -0
  29. package/src/server/googleAuth.ts +139 -0
  30. package/src/server/orders.ts +127 -0
  31. package/src/server/reports/acquisition.ts +85 -0
  32. package/src/server/reports/journey.ts +112 -0
  33. package/src/server/reports/measurementHealth.ts +170 -0
  34. package/src/server/reports/typefaceInterest.ts +140 -0
  35. package/src/server/vercel.ts +68 -0
  36. package/src/server.ts +27 -0
  37. package/src/studio/Figure.tsx +175 -0
  38. package/src/studio/VisitorInsightsTool.tsx +247 -0
  39. package/src/studio/panels.tsx +233 -0
  40. package/src/studio/useReport.ts +99 -0
  41. package/src/types.ts +135 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Liiift Studio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # @liiift-studio/sanity-visitor-insights
2
+
3
+ Visitor-**behaviour** analytics inside Sanity Studio, for Darden, TDF and MCKL.
4
+
5
+ This is not a sales tool and does not duplicate the sales portal. It reports what visitors do —
6
+ where they come from, how far they get, which typefaces they engage with — and reconciles GA4
7
+ against Vercel so you can see how much of that behaviour you are actually measuring. Orders appear
8
+ only as a conversion anchor and a count; no revenue, and no customer data.
9
+
10
+ ---
11
+
12
+ ## What it reports
13
+
14
+ | Panel | Question it answers |
15
+ |---|---|
16
+ | **Measurement health** | How much of reality does each source actually see? |
17
+ | **Acquisition** | Where do visitors come from, and how many are design-industry referrals? |
18
+ | **Journey** | How far do visitors get, and where do they stop? |
19
+ | **Typeface interest** | Which families get viewed, tested and bought? |
20
+
21
+ Each panel offers a week, quarter or year range.
22
+
23
+ ---
24
+
25
+ ## Design decisions worth knowing
26
+
27
+ These are deliberate and load-bearing. Changing them changes whether the numbers are honest.
28
+
29
+ **Pageviews are compared to pageviews.** An earlier design subtracted Vercel pageviews from GA4
30
+ sessions and attributed the difference to consent, ad-blockers and bots. Sessions and pageviews are
31
+ different units, so that difference is dominated by the unit mismatch rather than by anything
32
+ missing — and GA4 exposes no signal separating those three causes anyway. The panel now compares
33
+ like with like and labels the remainder unexplained.
34
+
35
+ **Consent is measured, not inferred.** A `consent_granted` event turns the single largest cause of
36
+ the GA4/Vercel gap into a real number. Until a site instruments it, the panel says so instead of
37
+ guessing.
38
+
39
+ **A missing metric is never zero.** Every figure is a `MetricValue` tagged union, so an
40
+ uninstrumented event cannot silently coerce to `0` and be charted as a real trough. Unavailable
41
+ figures render as an em dash with a stated reason.
42
+
43
+ **The journey is a funnel, not a path.** GA4's Data API has no path-exploration endpoint, and
44
+ `runFunnelReport` returns step-conversion marginals rather than observed sequences. Drawing a flow
45
+ diagram from that would assert co-occurrence nobody measured. Steps are reported as independent
46
+ totals and the response is flagged approximate.
47
+
48
+ **Auth is the Studio's own session token.** The Studio forwards its Sanity token and the route
49
+ verifies it against Sanity. A shared secret would be extractable from the public Studio bundle,
50
+ moving the bar from "know the URL" to "open devtools", and would give no identity to audit.
51
+
52
+ **No charting library.** These panels compare and rank a handful of values, which labelled bars and
53
+ tables do as well as a chart — without a large Studio-bundle dependency or theme-token bridging for
54
+ light and dark. Tables are the accessible representation *and* the visual one, so they cannot drift
55
+ apart.
56
+
57
+ **No customer data leaves Sanity.** Order queries project `{_createdAt, orderStatus}` and, for the
58
+ interest panel, dereference only a typeface title. Joining behavioural data to order PII would turn
59
+ aggregate statistics into personal-data processing.
60
+
61
+ ---
62
+
63
+ ## Installing
64
+
65
+ ```bash
66
+ npm install @liiift-studio/sanity-visitor-insights
67
+ ```
68
+
69
+ ### 1. Studio
70
+
71
+ ```ts
72
+ // sanity.config.ts
73
+ import { visitorInsights } from '@liiift-studio/sanity-visitor-insights'
74
+
75
+ export default defineConfig({
76
+ plugins: [
77
+ visitorInsights({
78
+ apiBaseUrl: 'https://dardenstudio.com',
79
+ siteLabel: 'Darden Studio',
80
+ roles: ['administrator'],
81
+ }),
82
+ ],
83
+ })
84
+ ```
85
+
86
+ Pass `apiBaseUrl: ''` when the Studio is served from the same origin as the site (Darden and TDF).
87
+ MCKL's Studio deploys separately to `mckl.sanity.studio`, so it needs the full site URL.
88
+
89
+ Omit `roles` to show the tool to every Studio user. These panels read order-derived conversion
90
+ figures, so gating to administrators is usually right.
91
+
92
+ ### 2. Site API route
93
+
94
+ ```js
95
+ // pages/api/visitor-insights/[report].js
96
+ import { createVisitorInsightsHandler } from '@liiift-studio/sanity-visitor-insights/server'
97
+ import { client } from '../../../lib/sanityClient'
98
+
99
+ export default createVisitorInsightsHandler({
100
+ sanityClient: client,
101
+ config: {
102
+ siteId: 'darden',
103
+ label: 'Darden Studio',
104
+ ga4: { propertyId: '123456789', timezone: 'America/New_York' },
105
+ vercel: { projectId: 'prj_...' },
106
+ orders: { documentType: 'order', typefacesField: 'typefaces' },
107
+ eventCutovers: {
108
+ page_view: 'preexisting',
109
+ view_item: 'preexisting',
110
+ add_to_cart: 'preexisting',
111
+ begin_checkout: 'preexisting',
112
+ purchase: 'preexisting',
113
+ consent_granted: null,
114
+ tester_engaged: null,
115
+ },
116
+ // Only needed where the Studio is on a different origin.
117
+ allowedStudioOrigins: ['https://mckl.sanity.studio'],
118
+ },
119
+ })
120
+ ```
121
+
122
+ `propertyId` is the **numeric** GA4 property id from GA4 Admin, not the `G-XXXXXXX` measurement id.
123
+ The config is validated at construction and will throw on deploy rather than silently returning
124
+ empty charts — pasting a measurement id here is caught by name.
125
+
126
+ ### 3. Environment variables
127
+
128
+ | Variable | Where | What |
129
+ |---|---|---|
130
+ | `VISITOR_INSIGHTS_GA4_SERVICE_ACCOUNT` | Site (server) | Service-account JSON, raw or base64 |
131
+ | `VISITOR_INSIGHTS_VERCEL_TOKEN` | Site (server) | Vercel API token with project read access |
132
+ | `SANITY_STUDIO_PROJECT_ID` | Site (server) | Already set; used to verify Studio tokens |
133
+
134
+ The service account needs **Viewer** on each GA4 property. Nothing here is `NEXT_PUBLIC_`; none of
135
+ it reaches the browser.
136
+
137
+ ---
138
+
139
+ ## Before it will show anything useful
140
+
141
+ - **Set GA4 data retention to 14 months** on every property (Admin → Data Settings → Data
142
+ Retention). The default may be 2 months, which makes the year range return nothing for most of
143
+ its span. It does not backfill, so every day at the default is data lost permanently.
144
+ - **Register custom dimensions before deploying the events that populate them.** A dimension only
145
+ reports from its registration date forward.
146
+ - **Instrument `consent_granted` and `tester_engaged`.** Without them the measurement-health
147
+ residual stays unexplained and the journey funnel has a hole where the tester belongs.
148
+ - **`tester_engaged` must require a change from the tester's default state** — custom text, or a
149
+ weight/size change. Firing on tester open counts every page load as a test and makes the
150
+ viewed-to-tested ratio meaningless.
151
+
152
+ ---
153
+
154
+ ## Development
155
+
156
+ ```bash
157
+ npm install
158
+ npm test # pure logic plus the client/server boundary guard
159
+ npm run build
160
+ ```
161
+
162
+ The boundary test walks the real import graph from each entry point and fails if anything reachable
163
+ from the Studio entry touches `src/server/`, `node:crypto` or `process.env`. That check exists
164
+ because the failure it prevents — a credential-reading module shipped in a public Studio bundle —
165
+ is invisible to type checking and to review.
166
+
167
+ ---
168
+
169
+ ## Architecture
170
+
171
+ ```
172
+ src/
173
+ types.ts MetricValue, ranges, report envelope
174
+ reportData.ts Result shapes shared by server and panels, importing neither
175
+ core/ Pure logic: cutovers, range resolution, config validation
176
+ server/ Node only — GA4, Vercel, Sanity, auth, cache, handler factory
177
+ studio/ Browser only — the tool, panels and figure renderers
178
+ ```
179
+
180
+ `core/` and `reportData.ts` are shared. Nothing in `studio/` may import from `server/`, which is
181
+ what the split export subpaths and the boundary test enforce together.
@@ -0,0 +1,155 @@
1
+ import * as sanity from 'sanity';
2
+ import React from 'react';
3
+ import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue } from './ranges-D6AZwpmm.mjs';
4
+ export { C as Coverage, D as DateRange, E as EventCutover, P as PREEXISTING, c as REPORT_NAMES, d as ReportError, S as SiteAnalyticsConfig, e as SourceName, f as SourceStatus, U as UnavailableReason, g as coverageForRange, i as isReportName, o as ok, p as partial, h as previousRange, r as resolveRange, u as unavailable, v as validateSiteConfig, j as valueOrNull } from './ranges-D6AZwpmm.mjs';
5
+
6
+ /**
7
+ * The Visitor Insights Studio tool.
8
+ *
9
+ * One range control drives every panel, implemented once here as a proper radiogroup with
10
+ * roving tabindex — repeating a bespoke toggle per panel is how keyboard behaviour ends up
11
+ * inconsistent between them.
12
+ */
13
+
14
+ /** Props supplied by the plugin config. */
15
+ interface VisitorInsightsToolProps {
16
+ apiBaseUrl: string;
17
+ siteLabel: string;
18
+ }
19
+ /** The tool itself. */
20
+ declare function VisitorInsightsTool({ apiBaseUrl, siteLabel }: VisitorInsightsToolProps): React.ReactElement;
21
+
22
+ /**
23
+ * Data hook for the Studio panels.
24
+ *
25
+ * Forwards the Studio's own Sanity session token to the site's API route, which verifies it against
26
+ * Sanity. There is no shared secret: the Studio bundle is public, so anything compiled into it is
27
+ * extractable.
28
+ */
29
+
30
+ /** Loading state for a report. */
31
+ type ReportState<T> = {
32
+ status: 'idle';
33
+ } | {
34
+ status: 'loading';
35
+ } | {
36
+ status: 'ready';
37
+ envelope: ReportEnvelope<T>;
38
+ } | {
39
+ status: 'error';
40
+ message: string;
41
+ };
42
+ /** Options for useReport. */
43
+ interface UseReportOptions {
44
+ /** Base URL of the site serving the reports, e.g. `https://dardenstudio.com`. */
45
+ apiBaseUrl: string;
46
+ report: ReportName;
47
+ range: RangeKey;
48
+ }
49
+ /**
50
+ * Fetch a report, re-fetching when the range changes.
51
+ *
52
+ * @returns the current state plus a `reload` for manual refresh
53
+ */
54
+ declare function useReport<T>({ apiBaseUrl, report, range }: UseReportOptions): {
55
+ state: ReportState<T>;
56
+ reload: () => void;
57
+ };
58
+
59
+ /**
60
+ * Shared renderers for metric values and comparison bars.
61
+ *
62
+ * Two rules are enforced here rather than left to each panel:
63
+ *
64
+ * 1. An unavailable metric renders as an em dash with a stated reason — never as "0". A missing
65
+ * measurement and a measured zero mean opposite things, and must never look alike.
66
+ * 2. Nothing is distinguished by colour alone. Bars carry a text value, a percentage and an
67
+ * accessible label, so the figure survives greyscale, colour blindness and both Studio themes.
68
+ */
69
+
70
+ /** Format a number with thousands separators. */
71
+ declare function formatCount(value: number): string;
72
+ /** Format a 0–1 ratio as a percentage. */
73
+ declare function formatPercent(ratio: number, digits?: number): string;
74
+ /** Props for MetricFigure. */
75
+ interface MetricFigureProps {
76
+ metric: MetricValue;
77
+ /** Accessible label describing what this number counts. */
78
+ label: string;
79
+ size?: number;
80
+ }
81
+ /**
82
+ * Render a metric value, handling the absent case visibly.
83
+ * Screen readers get the reason text rather than an unexplained dash.
84
+ */
85
+ declare function MetricFigure({ metric, label, size }: MetricFigureProps): React.ReactElement;
86
+ /** Props for ComparisonBar. */
87
+ interface ComparisonBarProps {
88
+ label: string;
89
+ metric: MetricValue;
90
+ /** Largest value across the sibling bars, used to scale width. */
91
+ max: number;
92
+ /** Tone conveys category, but never carries meaning on its own. */
93
+ tone?: 'primary' | 'positive' | 'caution' | 'default';
94
+ }
95
+ /**
96
+ * A horizontal bar with its value printed alongside.
97
+ *
98
+ * Deliberately CSS rather than a charting library: these panels compare and rank a handful of
99
+ * values, which a labelled bar does as well as a chart while avoiding a large dependency in the
100
+ * Studio bundle and the theme-token bridging that a chart library would need for light and dark.
101
+ */
102
+ declare function ComparisonBar({ label, metric, max, tone }: ComparisonBarProps): React.ReactElement;
103
+ /** Props for NoticeList. */
104
+ interface NoticeListProps {
105
+ notices: string[];
106
+ }
107
+ /**
108
+ * Caveats attached to a report — sampling, processing lag, instrumentation cutovers.
109
+ * Rendered in the panel rather than a README: a caveat nobody sees does not prevent a wrong read.
110
+ */
111
+ declare function NoticeList({ notices }: NoticeListProps): React.ReactElement | null;
112
+
113
+ /**
114
+ * Studio entry point — browser only.
115
+ *
116
+ * Must never import from `./server` or anything under `src/server/`, which reads credentials and
117
+ * uses node:crypto. The split export subpaths are what keep that guarantee enforceable.
118
+ */
119
+ /** Options for the Studio plugin. */
120
+ interface VisitorInsightsPluginOptions {
121
+ /**
122
+ * Base URL of the site serving the reports, e.g. `https://dardenstudio.com`.
123
+ * Same-origin studios can pass an empty string.
124
+ */
125
+ apiBaseUrl: string;
126
+ /** Label shown in the tool header. */
127
+ siteLabel: string;
128
+ /** Tool name in the Studio URL. Defaults to `visitor-insights`. */
129
+ name?: string;
130
+ /** Title in the Studio nav. Defaults to `Insights`. */
131
+ title?: string;
132
+ /**
133
+ * Restrict the tool to these Sanity roles. Omit to show it to every Studio user.
134
+ * These panels read order-derived conversion figures, so gating to administrators is
135
+ * usually right — matching how the deploy and utilities tools are already gated.
136
+ */
137
+ roles?: string[];
138
+ }
139
+ /**
140
+ * Visitor Insights — visitor-behaviour analytics inside the Studio.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * plugins: [
145
+ * visitorInsights({
146
+ * apiBaseUrl: 'https://dardenstudio.com',
147
+ * siteLabel: 'Darden Studio',
148
+ * roles: ['administrator'],
149
+ * }),
150
+ * ]
151
+ * ```
152
+ */
153
+ declare const visitorInsights: sanity.Plugin<VisitorInsightsPluginOptions>;
154
+
155
+ export { ComparisonBar, MetricFigure, MetricValue, NoticeList, RangeKey, ReportEnvelope, ReportName, type ReportState, type UseReportOptions, type VisitorInsightsPluginOptions, VisitorInsightsTool, type VisitorInsightsToolProps, visitorInsights as default, formatCount, formatPercent, useReport, visitorInsights };
@@ -0,0 +1,155 @@
1
+ import * as sanity from 'sanity';
2
+ import React from 'react';
3
+ import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue } from './ranges-D6AZwpmm.js';
4
+ export { C as Coverage, D as DateRange, E as EventCutover, P as PREEXISTING, c as REPORT_NAMES, d as ReportError, S as SiteAnalyticsConfig, e as SourceName, f as SourceStatus, U as UnavailableReason, g as coverageForRange, i as isReportName, o as ok, p as partial, h as previousRange, r as resolveRange, u as unavailable, v as validateSiteConfig, j as valueOrNull } from './ranges-D6AZwpmm.js';
5
+
6
+ /**
7
+ * The Visitor Insights Studio tool.
8
+ *
9
+ * One range control drives every panel, implemented once here as a proper radiogroup with
10
+ * roving tabindex — repeating a bespoke toggle per panel is how keyboard behaviour ends up
11
+ * inconsistent between them.
12
+ */
13
+
14
+ /** Props supplied by the plugin config. */
15
+ interface VisitorInsightsToolProps {
16
+ apiBaseUrl: string;
17
+ siteLabel: string;
18
+ }
19
+ /** The tool itself. */
20
+ declare function VisitorInsightsTool({ apiBaseUrl, siteLabel }: VisitorInsightsToolProps): React.ReactElement;
21
+
22
+ /**
23
+ * Data hook for the Studio panels.
24
+ *
25
+ * Forwards the Studio's own Sanity session token to the site's API route, which verifies it against
26
+ * Sanity. There is no shared secret: the Studio bundle is public, so anything compiled into it is
27
+ * extractable.
28
+ */
29
+
30
+ /** Loading state for a report. */
31
+ type ReportState<T> = {
32
+ status: 'idle';
33
+ } | {
34
+ status: 'loading';
35
+ } | {
36
+ status: 'ready';
37
+ envelope: ReportEnvelope<T>;
38
+ } | {
39
+ status: 'error';
40
+ message: string;
41
+ };
42
+ /** Options for useReport. */
43
+ interface UseReportOptions {
44
+ /** Base URL of the site serving the reports, e.g. `https://dardenstudio.com`. */
45
+ apiBaseUrl: string;
46
+ report: ReportName;
47
+ range: RangeKey;
48
+ }
49
+ /**
50
+ * Fetch a report, re-fetching when the range changes.
51
+ *
52
+ * @returns the current state plus a `reload` for manual refresh
53
+ */
54
+ declare function useReport<T>({ apiBaseUrl, report, range }: UseReportOptions): {
55
+ state: ReportState<T>;
56
+ reload: () => void;
57
+ };
58
+
59
+ /**
60
+ * Shared renderers for metric values and comparison bars.
61
+ *
62
+ * Two rules are enforced here rather than left to each panel:
63
+ *
64
+ * 1. An unavailable metric renders as an em dash with a stated reason — never as "0". A missing
65
+ * measurement and a measured zero mean opposite things, and must never look alike.
66
+ * 2. Nothing is distinguished by colour alone. Bars carry a text value, a percentage and an
67
+ * accessible label, so the figure survives greyscale, colour blindness and both Studio themes.
68
+ */
69
+
70
+ /** Format a number with thousands separators. */
71
+ declare function formatCount(value: number): string;
72
+ /** Format a 0–1 ratio as a percentage. */
73
+ declare function formatPercent(ratio: number, digits?: number): string;
74
+ /** Props for MetricFigure. */
75
+ interface MetricFigureProps {
76
+ metric: MetricValue;
77
+ /** Accessible label describing what this number counts. */
78
+ label: string;
79
+ size?: number;
80
+ }
81
+ /**
82
+ * Render a metric value, handling the absent case visibly.
83
+ * Screen readers get the reason text rather than an unexplained dash.
84
+ */
85
+ declare function MetricFigure({ metric, label, size }: MetricFigureProps): React.ReactElement;
86
+ /** Props for ComparisonBar. */
87
+ interface ComparisonBarProps {
88
+ label: string;
89
+ metric: MetricValue;
90
+ /** Largest value across the sibling bars, used to scale width. */
91
+ max: number;
92
+ /** Tone conveys category, but never carries meaning on its own. */
93
+ tone?: 'primary' | 'positive' | 'caution' | 'default';
94
+ }
95
+ /**
96
+ * A horizontal bar with its value printed alongside.
97
+ *
98
+ * Deliberately CSS rather than a charting library: these panels compare and rank a handful of
99
+ * values, which a labelled bar does as well as a chart while avoiding a large dependency in the
100
+ * Studio bundle and the theme-token bridging that a chart library would need for light and dark.
101
+ */
102
+ declare function ComparisonBar({ label, metric, max, tone }: ComparisonBarProps): React.ReactElement;
103
+ /** Props for NoticeList. */
104
+ interface NoticeListProps {
105
+ notices: string[];
106
+ }
107
+ /**
108
+ * Caveats attached to a report — sampling, processing lag, instrumentation cutovers.
109
+ * Rendered in the panel rather than a README: a caveat nobody sees does not prevent a wrong read.
110
+ */
111
+ declare function NoticeList({ notices }: NoticeListProps): React.ReactElement | null;
112
+
113
+ /**
114
+ * Studio entry point — browser only.
115
+ *
116
+ * Must never import from `./server` or anything under `src/server/`, which reads credentials and
117
+ * uses node:crypto. The split export subpaths are what keep that guarantee enforceable.
118
+ */
119
+ /** Options for the Studio plugin. */
120
+ interface VisitorInsightsPluginOptions {
121
+ /**
122
+ * Base URL of the site serving the reports, e.g. `https://dardenstudio.com`.
123
+ * Same-origin studios can pass an empty string.
124
+ */
125
+ apiBaseUrl: string;
126
+ /** Label shown in the tool header. */
127
+ siteLabel: string;
128
+ /** Tool name in the Studio URL. Defaults to `visitor-insights`. */
129
+ name?: string;
130
+ /** Title in the Studio nav. Defaults to `Insights`. */
131
+ title?: string;
132
+ /**
133
+ * Restrict the tool to these Sanity roles. Omit to show it to every Studio user.
134
+ * These panels read order-derived conversion figures, so gating to administrators is
135
+ * usually right — matching how the deploy and utilities tools are already gated.
136
+ */
137
+ roles?: string[];
138
+ }
139
+ /**
140
+ * Visitor Insights — visitor-behaviour analytics inside the Studio.
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * plugins: [
145
+ * visitorInsights({
146
+ * apiBaseUrl: 'https://dardenstudio.com',
147
+ * siteLabel: 'Darden Studio',
148
+ * roles: ['administrator'],
149
+ * }),
150
+ * ]
151
+ * ```
152
+ */
153
+ declare const visitorInsights: sanity.Plugin<VisitorInsightsPluginOptions>;
154
+
155
+ export { ComparisonBar, MetricFigure, MetricValue, NoticeList, RangeKey, ReportEnvelope, ReportName, type ReportState, type UseReportOptions, type VisitorInsightsPluginOptions, VisitorInsightsTool, type VisitorInsightsToolProps, visitorInsights as default, formatCount, formatPercent, useReport, visitorInsights };