@valbuild/shared 0.148.2 → 0.148.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,56 @@
1
1
  # @valbuild/shared
2
2
 
3
+ ## 0.148.6
4
+
5
+ ### Patch Changes
6
+
7
+ - [#902](https://github.com/valbuild/val/pull/902) [`b580292`](https://github.com/valbuild/val/commit/b580292ac3c335b055f5ed5f18d6a12d075169c7) Thanks [@freekh](https://github.com/freekh)! - The demo has a script: `<ValApp demo={{ tours, audiences }}>`. Instead of a tour that opens by itself, the demo card asks "What brings you here?" and lists short tours for the answer, each ticked off once done. `/val?tour=<id>` starts one from a link.
8
+
9
+ A tour can start on a page of its own (`start`). A step can ask for a new page (`moveOn: { newPage }`), write any value or add one (`moveOn: { edit, value, add }`), and show the code behind what it is about (`code`).
10
+
11
+ ## 0.148.3
12
+
13
+ ### Patch Changes
14
+
15
+ - [#881](https://github.com/valbuild/val/pull/881) [`0139436`](https://github.com/valbuild/val/commit/0139436426142d91cbb29ec6c696dfb86f34ab9d) Thanks [@freekh](https://github.com/freekh)! - The content analytics script. With analytics on (it is off by default, and turned on in a hosted project's settings), a TanStack site counts which content each visitor's page **rendered** and which they actually **viewed** (at least half on screen for a second), and sends it to `/api/val/analytics`.
16
+
17
+ - No cookies, no storage, no visitor IDs. Each page view also says where it came from (the referring site's host and any `utm_source` / `utm_medium` / `utm_campaign`) and whether it was a phone, tablet or desktop.
18
+ - A page is sent as its route's pattern (`/posts/$postId`), never its address, so a path that holds something about one visitor (an order number, a token) is never stored. `startValAnalytics` takes this as `routeFor`; `ValProvider` passes TanStack's matched route.
19
+ - Editors are never counted: nothing is sent in draft mode, in the Studio, with Val enabled, or at a proposal's address, and nothing from automated browsers. A page the browser prerenders counts nothing until it is shown.
20
+ - The script is the new `@valbuild/react/analytics` entry, loaded only for visitors of a site with analytics on, after the page has hydrated. It imports nothing else from Val.
21
+ - Storing the counts and showing them is part of Val's hosting, which takes these beacons before they reach the site. Anywhere else, `/api/val/analytics` checks each beacon and stores nothing; in local development, with `VAL_DEBUG=1`, it logs a line per beacon, so you can see it working.
22
+ - `ValProvider`'s Studio check now matches `/val` and what is below it only, not every route starting with those letters (`/values`). The same fix is in `@valbuild/next`.
23
+
24
+ - [#896](https://github.com/valbuild/val/pull/896) [`5eff8a0`](https://github.com/valbuild/val/commit/5eff8a055a4ef50d476797c28f9c3e50deb73c89) Thanks [@freekh](https://github.com/freekh)! - A demo's tour can now be written by the site: `<ValApp demo={{ tour }}>`, with each step as data (`ValDemoTourStep`), so what it says changes with the site rather than with a Val release.
25
+
26
+ It is drawn as a ring around what a step is about, with nothing dimmed and the Studio usable underneath, and a step can move on by itself once the visitor has done what it asks:
27
+
28
+ ```ts
29
+ const tour: ValDemoTourStep[] = [
30
+ {
31
+ id: "click",
32
+ title: "This is Val",
33
+ body: "Click the heading on the page.",
34
+ target: "canvas",
35
+ page: HEADING, // outlined on the page
36
+ moveOn: { open: HEADING },
37
+ },
38
+ {
39
+ id: "type",
40
+ title: "Change a word",
41
+ body: "The page changes as you type.",
42
+ field: HEADING, // ringed wherever the field is shown
43
+ moveOn: {
44
+ edit: true,
45
+ path: HEADING,
46
+ value: "Coffee, roasted this weekend",
47
+ },
48
+ },
49
+ ];
50
+ ```
51
+
52
+ A step that moves on has **Do it for me**, which does it the way the Studio would, and **Skip**. On a phone the card is a sheet along the bottom. The Studio's own tour is unchanged.
53
+
3
54
  ## 0.148.2
4
55
 
5
56
  ### Patch Changes
@@ -45,6 +45,27 @@ type CookieValue = EnableCookieValue | {
45
45
  * this. Shared so client and server agree on the limit.
46
46
  */
47
47
  export declare const JSON_ENTRIES_BATCH_MAX = 100;
48
+ export declare const AnalyticsBeacon: z.ZodObject<{
49
+ v: z.ZodLiteral<1>;
50
+ route: z.ZodString;
51
+ cv: z.ZodOptional<z.ZodString>;
52
+ view: z.ZodOptional<z.ZodObject<{
53
+ ref: z.ZodOptional<z.ZodString>;
54
+ utm: z.ZodOptional<z.ZodObject<{
55
+ source: z.ZodOptional<z.ZodString>;
56
+ medium: z.ZodOptional<z.ZodString>;
57
+ campaign: z.ZodOptional<z.ZodString>;
58
+ }, z.core.$strip>>;
59
+ device: z.ZodEnum<{
60
+ phone: "phone";
61
+ tablet: "tablet";
62
+ desktop: "desktop";
63
+ }>;
64
+ }, z.core.$strip>>;
65
+ rendered: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
66
+ viewed: z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>;
67
+ }, z.core.$strip>;
68
+ export type AnalyticsBeacon = z.infer<typeof AnalyticsBeacon>;
48
69
  export declare const Api: {
49
70
  "/draft/enable": {
50
71
  GET: {
@@ -98,6 +119,24 @@ export declare const Api: {
98
119
  }, z.core.$strip>]>;
99
120
  };
100
121
  };
122
+ "/analytics": {
123
+ POST: {
124
+ req: {
125
+ body: z.ZodUnknown;
126
+ };
127
+ res: z.ZodUnion<readonly [z.ZodObject<{
128
+ status: z.ZodLiteral<200>;
129
+ json: z.ZodObject<{
130
+ counted: z.ZodBoolean;
131
+ }, z.core.$strip>;
132
+ }, z.core.$strip>, z.ZodObject<{
133
+ status: z.ZodLiteral<400>;
134
+ json: z.ZodObject<{
135
+ message: z.ZodString;
136
+ }, z.core.$strip>;
137
+ }, z.core.$strip>]>;
138
+ };
139
+ };
101
140
  "/draft/stat": {
102
141
  GET: {
103
142
  req: {
@@ -1,3 +1,4 @@
1
+ import type { JSONValue } from "@valbuild/core/patch";
1
2
  /**
2
3
  * A Val demo, as the page hands it to the Studio.
3
4
  *
@@ -23,6 +24,151 @@ export type ValDemoRuntime = {
23
24
  * refuses uploads before sending anything, and says why.
24
25
  */
25
26
  uploads: boolean;
27
+ /**
28
+ * The tour the site wrote, in place of the Studio's own demo tour. See
29
+ * `ValDemoTourStep`. Absent: the Studio's.
30
+ */
31
+ tour?: ValDemoTourStep[];
32
+ /** The demo script. See `ValDemoOptions.tours`. */
33
+ tours?: ValDemoTour[];
34
+ /** "What brings you here?". See `ValDemoOptions.audiences`. */
35
+ audiences?: ValDemoAudience[];
36
+ };
37
+ /**
38
+ * What the site starts the demo with: `<ValApp demo={{ tour }}>`.
39
+ *
40
+ * `demo` alone (`true`) is the same with nothing given.
41
+ */
42
+ export type ValDemoOptions = {
43
+ /**
44
+ * One tour, behind the demo card's Tour button, opened on a first visit.
45
+ * `tours` replaces it: given both, `tours` is used.
46
+ */
47
+ tour?: ValDemoTourStep[];
48
+ /**
49
+ * The demo script: short tours the visitor picks from on the demo card,
50
+ * each ticked off once done. A tour can be started from a link too:
51
+ * `/val?tour=<id>`.
52
+ */
53
+ tours?: ValDemoTour[];
54
+ /**
55
+ * What the demo card asks first, "What brings you here?": each answer is a
56
+ * list of `tours`, by id, and the card shows the list for the answer. With
57
+ * two or more, the Studio adds "Show me everything", all of them in order.
58
+ * Absent: every tour, in the order given.
59
+ */
60
+ audiences?: ValDemoAudience[];
61
+ };
62
+ /** One item of the demo script. */
63
+ export type ValDemoTour = {
64
+ /** Stable: the answer's list names it, and `?tour=` starts it. */
65
+ id: string;
66
+ title: string;
67
+ /** How long it takes, as the card says it: "30 s", "1 min". */
68
+ duration?: string;
69
+ /**
70
+ * Where the tour starts, as a source path: a page of the site (a page
71
+ * router's module and the page's key). The Studio goes there first, so a
72
+ * tour about the home page is not started on whatever page the last one
73
+ * left open.
74
+ */
75
+ start?: string;
76
+ steps: ValDemoTourStep[];
77
+ };
78
+ /**
79
+ * An answer to "What brings you here?".
80
+ *
81
+ * The first tour of its list is the one the card offers first, with a
82
+ * button of its own: the one to start with.
83
+ */
84
+ export type ValDemoAudience = {
85
+ id: string;
86
+ /** "I write and edit content". */
87
+ title: string;
88
+ /** What the list has in it, in a line: "Pages, photos, languages". */
89
+ description?: string;
90
+ /** Tours by id, in order. */
91
+ tours: string[];
92
+ };
93
+ /**
94
+ * A control of the Studio's a step can point at.
95
+ *
96
+ * The `data-val-tour` markers the Studio puts on them. One that is not on
97
+ * screen (the rail on a phone) leaves the card on its own, so a step's words
98
+ * must work without the arrow: never "this button here".
99
+ */
100
+ export type ValDemoTourTarget = "canvas" | "pages" | "media" | "data" | "review" | "preview" | "publish" | "ai" | "locale" | "demo" | "utility" | "new-page";
101
+ /** A panel a step opens, so what it is about is on screen behind the card. */
102
+ export type ValDemoTourPanel = "pages" | "media" | "data" | "settings" | "ai";
103
+ /**
104
+ * What moves a step on by itself. A step without one has Next.
105
+ *
106
+ * Each is something the visitor does, because doing it is the lesson, and
107
+ * each has a "Do it for me" that does it the way the Studio would:
108
+ *
109
+ * - `open`: a field at or below this source path is opened -- by clicking it
110
+ * on the page, or anywhere else. Do it for me opens it.
111
+ * - `edit`: a change is made. Do it for me writes `value` at `path` --
112
+ * replacing what is there, or with `add: true` adding it, as a new entry of
113
+ * a record or a new item of a list at that index; without them, it is
114
+ * offered nowhere and the step waits for the visitor.
115
+ * - `newPage`: a page is added. Do it for me adds the page this source path
116
+ * names (a page router's module and the new page's key, `?p="/journal/x"`)
117
+ * the way the New page form does, empty, and opens it.
118
+ * - `panel`: that panel is opened. Do it for me opens it.
119
+ * - `locale`: the language shown is changed. Do it for me picks the next one.
120
+ */
121
+ export type ValDemoTourMoveOn = {
122
+ open: string;
123
+ } | {
124
+ edit: true;
125
+ path?: string;
126
+ value?: JSONValue;
127
+ add?: boolean;
128
+ } | {
129
+ newPage: string;
130
+ } | {
131
+ panel: ValDemoTourPanel;
132
+ } | {
133
+ locale: true;
134
+ };
135
+ /**
136
+ * One stop of the demo's tour, as the site writes it.
137
+ *
138
+ * Data rather than components, so the tour can be rewritten -- and tried on
139
+ * people -- without a Val release: the Studio draws it, the site says what it
140
+ * says. Written for someone who has never seen Val.
141
+ */
142
+ export type ValDemoTourStep = {
143
+ /** Stable, so a test can name a step without matching its words. */
144
+ id: string;
145
+ title: string;
146
+ body: string;
147
+ /** The control this step is about. */
148
+ target?: ValDemoTourTarget;
149
+ /**
150
+ * A field this step is about, as a source path: ringed wherever the Studio
151
+ * shows it -- the page's fields, or the module open in the editor. Takes
152
+ * precedence over `target`, which is what is ringed when the field is not
153
+ * on screen.
154
+ */
155
+ field?: string;
156
+ /**
157
+ * A place on the page this step is about, as a source path: the page
158
+ * outlines it and brings it into view.
159
+ */
160
+ page?: string;
161
+ panel?: ValDemoTourPanel;
162
+ moveOn?: ValDemoTourMoveOn;
163
+ /**
164
+ * Code to show in the card: the line of a `.val.ts` behind what the step is
165
+ * about, for a visitor who builds sites. Written by the site, so it has to
166
+ * be kept the same as the file; `file` says which one.
167
+ */
168
+ code?: {
169
+ file: string;
170
+ text: string;
171
+ };
26
172
  };
27
173
  declare global {
28
174
  var __VAL_DEMO__: ValDemoRuntime | undefined;
@@ -1782,6 +1782,48 @@ var onlyOneIntQueryParam = onlyOneStringQueryParam.refine(function (arg) {
1782
1782
  * this. Shared so client and server agree on the limit.
1783
1783
  */
1784
1784
  var JSON_ENTRIES_BATCH_MAX = 100;
1785
+
1786
+ /**
1787
+ * Content analytics, wire format version 1: the beacon the browser script
1788
+ * (`@valbuild/react/analytics`) sends to `POST /api/val/analytics`. Its
1789
+ * `AnalyticsBeacon` type is written by hand there, because that code is on
1790
+ * every visitor's page and must not load zod; `analytics/beacon.test.ts` in
1791
+ * `@valbuild/react` checks that what it builds parses here.
1792
+ */
1793
+ /*
1794
+ * The limits the browser script keeps to (`MAX_*` in
1795
+ * `@valbuild/react/analytics`'s `beacon.ts`, which cannot import this). The
1796
+ * endpoint takes anyone's POST, so it checks what it is sent rather than
1797
+ * trusting the sender; `beacon.test.ts` builds beacons at the limits and
1798
+ * parses them here.
1799
+ */
1800
+ var MAX_ANALYTICS_PATHS_PER_BEACON = 200;
1801
+ var AnalyticsPaths = zod.z.record(zod.z.string().max(512), zod.z.array(zod.z.string().max(1024)).max(MAX_ANALYTICS_PATHS_PER_BEACON));
1802
+ var countAnalyticsPaths = function countAnalyticsPaths(paths) {
1803
+ return Object.values(paths).reduce(function (n, list) {
1804
+ return n + list.length;
1805
+ }, 0);
1806
+ };
1807
+ var AnalyticsBeacon = zod.z.object({
1808
+ v: zod.z.literal(1),
1809
+ route: zod.z.string().max(512),
1810
+ cv: zod.z.string().max(128).optional(),
1811
+ view: zod.z.object({
1812
+ ref: zod.z.string().max(253).optional(),
1813
+ utm: zod.z.object({
1814
+ source: zod.z.string().max(100).optional(),
1815
+ medium: zod.z.string().max(100).optional(),
1816
+ campaign: zod.z.string().max(100).optional()
1817
+ }).optional(),
1818
+ device: zod.z["enum"](["phone", "tablet", "desktop"])
1819
+ }).optional(),
1820
+ rendered: AnalyticsPaths,
1821
+ viewed: AnalyticsPaths
1822
+ }).refine(function (beacon) {
1823
+ return countAnalyticsPaths(beacon.rendered) + countAnalyticsPaths(beacon.viewed) <= MAX_ANALYTICS_PATHS_PER_BEACON;
1824
+ }, {
1825
+ message: "At most ".concat(MAX_ANALYTICS_PATHS_PER_BEACON, " paths a beacon")
1826
+ });
1785
1827
  var Api = {
1786
1828
  "/draft/enable": {
1787
1829
  GET: {
@@ -1827,6 +1869,27 @@ var Api = {
1827
1869
  })])
1828
1870
  }
1829
1871
  },
1872
+ "/analytics": {
1873
+ POST: {
1874
+ // Parsed by the handler rather than here, so a beacon of a version this
1875
+ // server does not know is ignored instead of refused.
1876
+ req: {
1877
+ body: zod.z.unknown()
1878
+ },
1879
+ res: zod.z.union([
1880
+ // `counted` is false wherever nothing stores the counts -- every
1881
+ // server but the hosted platform's, for now.
1882
+ zod.z.object({
1883
+ status: zod.z.literal(200),
1884
+ json: zod.z.object({
1885
+ counted: zod.z["boolean"]()
1886
+ })
1887
+ }), zod.z.object({
1888
+ status: zod.z.literal(400),
1889
+ json: GenericError
1890
+ })])
1891
+ }
1892
+ },
1830
1893
  "/draft/stat": {
1831
1894
  GET: {
1832
1895
  req: {
@@ -4093,6 +4156,58 @@ function isRetryable(error) {
4093
4156
  * modules. See `docs/plans/demo-mode.md`.
4094
4157
  */
4095
4158
 
4159
+ /**
4160
+ * What the site starts the demo with: `<ValApp demo={{ tour }}>`.
4161
+ *
4162
+ * `demo` alone (`true`) is the same with nothing given.
4163
+ */
4164
+
4165
+ /** One item of the demo script. */
4166
+
4167
+ /**
4168
+ * An answer to "What brings you here?".
4169
+ *
4170
+ * The first tour of its list is the one the card offers first, with a
4171
+ * button of its own: the one to start with.
4172
+ */
4173
+
4174
+ /**
4175
+ * A control of the Studio's a step can point at.
4176
+ *
4177
+ * The `data-val-tour` markers the Studio puts on them. One that is not on
4178
+ * screen (the rail on a phone) leaves the card on its own, so a step's words
4179
+ * must work without the arrow: never "this button here".
4180
+ */
4181
+
4182
+ /** A panel a step opens, so what it is about is on screen behind the card. */
4183
+
4184
+ /**
4185
+ * What moves a step on by itself. A step without one has Next.
4186
+ *
4187
+ * Each is something the visitor does, because doing it is the lesson, and
4188
+ * each has a "Do it for me" that does it the way the Studio would:
4189
+ *
4190
+ * - `open`: a field at or below this source path is opened -- by clicking it
4191
+ * on the page, or anywhere else. Do it for me opens it.
4192
+ * - `edit`: a change is made. Do it for me writes `value` at `path` --
4193
+ * replacing what is there, or with `add: true` adding it, as a new entry of
4194
+ * a record or a new item of a list at that index; without them, it is
4195
+ * offered nowhere and the step waits for the visitor.
4196
+ * - `newPage`: a page is added. Do it for me adds the page this source path
4197
+ * names (a page router's module and the new page's key, `?p="/journal/x"`)
4198
+ * the way the New page form does, empty, and opens it.
4199
+ * - `panel`: that panel is opened. Do it for me opens it.
4200
+ * - `locale`: the language shown is changed. Do it for me picks the next one.
4201
+ */
4202
+
4203
+ /**
4204
+ * One stop of the demo's tour, as the site writes it.
4205
+ *
4206
+ * Data rather than components, so the tour can be rewritten -- and tried on
4207
+ * people -- without a Val release: the Studio draws it, the site says what it
4208
+ * says. Written for someone who has never seen Val.
4209
+ */
4210
+
4096
4211
  /*
4097
4212
  * A global `var` rather than a `Window` member: this package is compiled
4098
4213
  * without the DOM's types, and a `var` in global scope is also a property of
@@ -8943,6 +9058,7 @@ exports.parseRoutePattern = getSitemapTree.parseRoutePattern;
8943
9058
  exports.withValCanvasParam = getSitemapTree.withValCanvasParam;
8944
9059
  exports.AA_LARGE = AA_LARGE;
8945
9060
  exports.AA_TEXT = AA_TEXT;
9061
+ exports.AnalyticsBeacon = AnalyticsBeacon;
8946
9062
  exports.Api = Api;
8947
9063
  exports.BRAND_CONTRAST_PAIRS = BRAND_CONTRAST_PAIRS;
8948
9064
  exports.BinaryFileRef = BinaryFileRef;
@@ -1782,6 +1782,48 @@ var onlyOneIntQueryParam = onlyOneStringQueryParam.refine(function (arg) {
1782
1782
  * this. Shared so client and server agree on the limit.
1783
1783
  */
1784
1784
  var JSON_ENTRIES_BATCH_MAX = 100;
1785
+
1786
+ /**
1787
+ * Content analytics, wire format version 1: the beacon the browser script
1788
+ * (`@valbuild/react/analytics`) sends to `POST /api/val/analytics`. Its
1789
+ * `AnalyticsBeacon` type is written by hand there, because that code is on
1790
+ * every visitor's page and must not load zod; `analytics/beacon.test.ts` in
1791
+ * `@valbuild/react` checks that what it builds parses here.
1792
+ */
1793
+ /*
1794
+ * The limits the browser script keeps to (`MAX_*` in
1795
+ * `@valbuild/react/analytics`'s `beacon.ts`, which cannot import this). The
1796
+ * endpoint takes anyone's POST, so it checks what it is sent rather than
1797
+ * trusting the sender; `beacon.test.ts` builds beacons at the limits and
1798
+ * parses them here.
1799
+ */
1800
+ var MAX_ANALYTICS_PATHS_PER_BEACON = 200;
1801
+ var AnalyticsPaths = zod.z.record(zod.z.string().max(512), zod.z.array(zod.z.string().max(1024)).max(MAX_ANALYTICS_PATHS_PER_BEACON));
1802
+ var countAnalyticsPaths = function countAnalyticsPaths(paths) {
1803
+ return Object.values(paths).reduce(function (n, list) {
1804
+ return n + list.length;
1805
+ }, 0);
1806
+ };
1807
+ var AnalyticsBeacon = zod.z.object({
1808
+ v: zod.z.literal(1),
1809
+ route: zod.z.string().max(512),
1810
+ cv: zod.z.string().max(128).optional(),
1811
+ view: zod.z.object({
1812
+ ref: zod.z.string().max(253).optional(),
1813
+ utm: zod.z.object({
1814
+ source: zod.z.string().max(100).optional(),
1815
+ medium: zod.z.string().max(100).optional(),
1816
+ campaign: zod.z.string().max(100).optional()
1817
+ }).optional(),
1818
+ device: zod.z["enum"](["phone", "tablet", "desktop"])
1819
+ }).optional(),
1820
+ rendered: AnalyticsPaths,
1821
+ viewed: AnalyticsPaths
1822
+ }).refine(function (beacon) {
1823
+ return countAnalyticsPaths(beacon.rendered) + countAnalyticsPaths(beacon.viewed) <= MAX_ANALYTICS_PATHS_PER_BEACON;
1824
+ }, {
1825
+ message: "At most ".concat(MAX_ANALYTICS_PATHS_PER_BEACON, " paths a beacon")
1826
+ });
1785
1827
  var Api = {
1786
1828
  "/draft/enable": {
1787
1829
  GET: {
@@ -1827,6 +1869,27 @@ var Api = {
1827
1869
  })])
1828
1870
  }
1829
1871
  },
1872
+ "/analytics": {
1873
+ POST: {
1874
+ // Parsed by the handler rather than here, so a beacon of a version this
1875
+ // server does not know is ignored instead of refused.
1876
+ req: {
1877
+ body: zod.z.unknown()
1878
+ },
1879
+ res: zod.z.union([
1880
+ // `counted` is false wherever nothing stores the counts -- every
1881
+ // server but the hosted platform's, for now.
1882
+ zod.z.object({
1883
+ status: zod.z.literal(200),
1884
+ json: zod.z.object({
1885
+ counted: zod.z["boolean"]()
1886
+ })
1887
+ }), zod.z.object({
1888
+ status: zod.z.literal(400),
1889
+ json: GenericError
1890
+ })])
1891
+ }
1892
+ },
1830
1893
  "/draft/stat": {
1831
1894
  GET: {
1832
1895
  req: {
@@ -4093,6 +4156,58 @@ function isRetryable(error) {
4093
4156
  * modules. See `docs/plans/demo-mode.md`.
4094
4157
  */
4095
4158
 
4159
+ /**
4160
+ * What the site starts the demo with: `<ValApp demo={{ tour }}>`.
4161
+ *
4162
+ * `demo` alone (`true`) is the same with nothing given.
4163
+ */
4164
+
4165
+ /** One item of the demo script. */
4166
+
4167
+ /**
4168
+ * An answer to "What brings you here?".
4169
+ *
4170
+ * The first tour of its list is the one the card offers first, with a
4171
+ * button of its own: the one to start with.
4172
+ */
4173
+
4174
+ /**
4175
+ * A control of the Studio's a step can point at.
4176
+ *
4177
+ * The `data-val-tour` markers the Studio puts on them. One that is not on
4178
+ * screen (the rail on a phone) leaves the card on its own, so a step's words
4179
+ * must work without the arrow: never "this button here".
4180
+ */
4181
+
4182
+ /** A panel a step opens, so what it is about is on screen behind the card. */
4183
+
4184
+ /**
4185
+ * What moves a step on by itself. A step without one has Next.
4186
+ *
4187
+ * Each is something the visitor does, because doing it is the lesson, and
4188
+ * each has a "Do it for me" that does it the way the Studio would:
4189
+ *
4190
+ * - `open`: a field at or below this source path is opened -- by clicking it
4191
+ * on the page, or anywhere else. Do it for me opens it.
4192
+ * - `edit`: a change is made. Do it for me writes `value` at `path` --
4193
+ * replacing what is there, or with `add: true` adding it, as a new entry of
4194
+ * a record or a new item of a list at that index; without them, it is
4195
+ * offered nowhere and the step waits for the visitor.
4196
+ * - `newPage`: a page is added. Do it for me adds the page this source path
4197
+ * names (a page router's module and the new page's key, `?p="/journal/x"`)
4198
+ * the way the New page form does, empty, and opens it.
4199
+ * - `panel`: that panel is opened. Do it for me opens it.
4200
+ * - `locale`: the language shown is changed. Do it for me picks the next one.
4201
+ */
4202
+
4203
+ /**
4204
+ * One stop of the demo's tour, as the site writes it.
4205
+ *
4206
+ * Data rather than components, so the tour can be rewritten -- and tried on
4207
+ * people -- without a Val release: the Studio draws it, the site says what it
4208
+ * says. Written for someone who has never seen Val.
4209
+ */
4210
+
4096
4211
  /*
4097
4212
  * A global `var` rather than a `Window` member: this package is compiled
4098
4213
  * without the DOM's types, and a `var` in global scope is also a property of
@@ -8943,6 +9058,7 @@ exports.parseRoutePattern = getSitemapTree.parseRoutePattern;
8943
9058
  exports.withValCanvasParam = getSitemapTree.withValCanvasParam;
8944
9059
  exports.AA_LARGE = AA_LARGE;
8945
9060
  exports.AA_TEXT = AA_TEXT;
9061
+ exports.AnalyticsBeacon = AnalyticsBeacon;
8946
9062
  exports.Api = Api;
8947
9063
  exports.BRAND_CONTRAST_PAIRS = BRAND_CONTRAST_PAIRS;
8948
9064
  exports.BinaryFileRef = BinaryFileRef;
@@ -1775,6 +1775,48 @@ var onlyOneIntQueryParam = onlyOneStringQueryParam.refine(function (arg) {
1775
1775
  * this. Shared so client and server agree on the limit.
1776
1776
  */
1777
1777
  var JSON_ENTRIES_BATCH_MAX = 100;
1778
+
1779
+ /**
1780
+ * Content analytics, wire format version 1: the beacon the browser script
1781
+ * (`@valbuild/react/analytics`) sends to `POST /api/val/analytics`. Its
1782
+ * `AnalyticsBeacon` type is written by hand there, because that code is on
1783
+ * every visitor's page and must not load zod; `analytics/beacon.test.ts` in
1784
+ * `@valbuild/react` checks that what it builds parses here.
1785
+ */
1786
+ /*
1787
+ * The limits the browser script keeps to (`MAX_*` in
1788
+ * `@valbuild/react/analytics`'s `beacon.ts`, which cannot import this). The
1789
+ * endpoint takes anyone's POST, so it checks what it is sent rather than
1790
+ * trusting the sender; `beacon.test.ts` builds beacons at the limits and
1791
+ * parses them here.
1792
+ */
1793
+ var MAX_ANALYTICS_PATHS_PER_BEACON = 200;
1794
+ var AnalyticsPaths = z.record(z.string().max(512), z.array(z.string().max(1024)).max(MAX_ANALYTICS_PATHS_PER_BEACON));
1795
+ var countAnalyticsPaths = function countAnalyticsPaths(paths) {
1796
+ return Object.values(paths).reduce(function (n, list) {
1797
+ return n + list.length;
1798
+ }, 0);
1799
+ };
1800
+ var AnalyticsBeacon = z.object({
1801
+ v: z.literal(1),
1802
+ route: z.string().max(512),
1803
+ cv: z.string().max(128).optional(),
1804
+ view: z.object({
1805
+ ref: z.string().max(253).optional(),
1806
+ utm: z.object({
1807
+ source: z.string().max(100).optional(),
1808
+ medium: z.string().max(100).optional(),
1809
+ campaign: z.string().max(100).optional()
1810
+ }).optional(),
1811
+ device: z["enum"](["phone", "tablet", "desktop"])
1812
+ }).optional(),
1813
+ rendered: AnalyticsPaths,
1814
+ viewed: AnalyticsPaths
1815
+ }).refine(function (beacon) {
1816
+ return countAnalyticsPaths(beacon.rendered) + countAnalyticsPaths(beacon.viewed) <= MAX_ANALYTICS_PATHS_PER_BEACON;
1817
+ }, {
1818
+ message: "At most ".concat(MAX_ANALYTICS_PATHS_PER_BEACON, " paths a beacon")
1819
+ });
1778
1820
  var Api = {
1779
1821
  "/draft/enable": {
1780
1822
  GET: {
@@ -1820,6 +1862,27 @@ var Api = {
1820
1862
  })])
1821
1863
  }
1822
1864
  },
1865
+ "/analytics": {
1866
+ POST: {
1867
+ // Parsed by the handler rather than here, so a beacon of a version this
1868
+ // server does not know is ignored instead of refused.
1869
+ req: {
1870
+ body: z.unknown()
1871
+ },
1872
+ res: z.union([
1873
+ // `counted` is false wherever nothing stores the counts -- every
1874
+ // server but the hosted platform's, for now.
1875
+ z.object({
1876
+ status: z.literal(200),
1877
+ json: z.object({
1878
+ counted: z["boolean"]()
1879
+ })
1880
+ }), z.object({
1881
+ status: z.literal(400),
1882
+ json: GenericError
1883
+ })])
1884
+ }
1885
+ },
1823
1886
  "/draft/stat": {
1824
1887
  GET: {
1825
1888
  req: {
@@ -4086,6 +4149,58 @@ function isRetryable(error) {
4086
4149
  * modules. See `docs/plans/demo-mode.md`.
4087
4150
  */
4088
4151
 
4152
+ /**
4153
+ * What the site starts the demo with: `<ValApp demo={{ tour }}>`.
4154
+ *
4155
+ * `demo` alone (`true`) is the same with nothing given.
4156
+ */
4157
+
4158
+ /** One item of the demo script. */
4159
+
4160
+ /**
4161
+ * An answer to "What brings you here?".
4162
+ *
4163
+ * The first tour of its list is the one the card offers first, with a
4164
+ * button of its own: the one to start with.
4165
+ */
4166
+
4167
+ /**
4168
+ * A control of the Studio's a step can point at.
4169
+ *
4170
+ * The `data-val-tour` markers the Studio puts on them. One that is not on
4171
+ * screen (the rail on a phone) leaves the card on its own, so a step's words
4172
+ * must work without the arrow: never "this button here".
4173
+ */
4174
+
4175
+ /** A panel a step opens, so what it is about is on screen behind the card. */
4176
+
4177
+ /**
4178
+ * What moves a step on by itself. A step without one has Next.
4179
+ *
4180
+ * Each is something the visitor does, because doing it is the lesson, and
4181
+ * each has a "Do it for me" that does it the way the Studio would:
4182
+ *
4183
+ * - `open`: a field at or below this source path is opened -- by clicking it
4184
+ * on the page, or anywhere else. Do it for me opens it.
4185
+ * - `edit`: a change is made. Do it for me writes `value` at `path` --
4186
+ * replacing what is there, or with `add: true` adding it, as a new entry of
4187
+ * a record or a new item of a list at that index; without them, it is
4188
+ * offered nowhere and the step waits for the visitor.
4189
+ * - `newPage`: a page is added. Do it for me adds the page this source path
4190
+ * names (a page router's module and the new page's key, `?p="/journal/x"`)
4191
+ * the way the New page form does, empty, and opens it.
4192
+ * - `panel`: that panel is opened. Do it for me opens it.
4193
+ * - `locale`: the language shown is changed. Do it for me picks the next one.
4194
+ */
4195
+
4196
+ /**
4197
+ * One stop of the demo's tour, as the site writes it.
4198
+ *
4199
+ * Data rather than components, so the tour can be rewritten -- and tried on
4200
+ * people -- without a Val release: the Studio draws it, the site says what it
4201
+ * says. Written for someone who has never seen Val.
4202
+ */
4203
+
4089
4204
  /*
4090
4205
  * A global `var` rather than a `Window` member: this package is compiled
4091
4206
  * without the DOM's types, and a `var` in global scope is also a property of
@@ -8915,4 +9030,4 @@ function schemaErrorsOf(schema, moduleFilePath, source) {
8915
9030
  return errors;
8916
9031
  }
8917
9032
 
8918
- export { AA_LARGE, AA_TEXT, Api, BRAND_CONTRAST_PAIRS, BinaryFileRef, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, ENCODE_DEFAULTS, ENCODE_MIME_TYPE_OF, FileOptions, HistoricalCommit, HistoricalModule, HistoricalPatchSet, HistoryError, ImageEncodeOption, ImageOptions, JSONValue, JSON_ENTRIES_BATCH_MAX, ModuleComparison, NEVER_ENCODED, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchId, PatchJSON, PatchSets, PlanRefusalError, PublishProtocolError, RAMP_STEPS, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RestoreVerdict, RichTextOptions, SCHEMA_SOURCE_FIXES, STUDIO_FIXES, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedDiscriminatedUnionSchema, SerializedEnumSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedLocaleSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedSettingsSchema, SerializedStringSchema, SerializedValViewSchema, SerializedVideoSchema, SharedValConfig, VAL_ENABLE_COOKIE_NAME, VAL_GREEN_RAMP, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, VAL_STUDIO_HEADER, ValCommit, ValDeployment, VideoOptions, VideoStreamOption, WASM_ENCODED_MIME_TYPES, accentRamp, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, chainHeadOf, checkCompatibility, chooseEncoded, concatModulePath, contrastRatio, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, enumLocaleTag, explainIncompatible, fallbackMimeType, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, forEachRichTextImage, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, getValDemo, indexModule, isInsidePatchSetPath, isLocaleSchema, isMimeTypeAccepted, isRemoteSchema, isRepairedOnSave, isSchemaSourceFixError, isSkippedSource, isStudioFix, joinSharedChanges, keyOfRecordPath, levenshtein, localeAt, localeMeantBy, memoizePerRequest, newestCommitSha, parseArtifacts, parseBuildTarget, parseCancel, parseDeclare, parseDiscard, parseJob, parseNewestCiRun, parsePress, parseProblems, parseProjectSource, parsePromote, parsePublishToken, parseRenew, parseRequestStatus, parseStatus, parseUpdateTarget, parseVerify, partitionValidationErrors, performSearch, relativeLuminance, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolvePairGround, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextImageSchema, richTextToRemirror, safeParsePatch, schemaErrorsOf, schemaTypesOfPath, selectionClosure, shouldEncodeWithWasm, sideFiles, sideSource, sourcePathOfChild, themeCustomProperties, toolNames, traverseSchemaSource, urlOf, validateRoutePatterns, withExtension };
9033
+ export { AA_LARGE, AA_TEXT, AnalyticsBeacon, Api, BRAND_CONTRAST_PAIRS, BinaryFileRef, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, ENCODE_DEFAULTS, ENCODE_MIME_TYPE_OF, FileOptions, HistoricalCommit, HistoricalModule, HistoricalPatchSet, HistoryError, ImageEncodeOption, ImageOptions, JSONValue, JSON_ENTRIES_BATCH_MAX, ModuleComparison, NEVER_ENCODED, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchId, PatchJSON, PatchSets, PlanRefusalError, PublishProtocolError, RAMP_STEPS, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RestoreVerdict, RichTextOptions, SCHEMA_SOURCE_FIXES, STUDIO_FIXES, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedDiscriminatedUnionSchema, SerializedEnumSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedLocaleSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedSettingsSchema, SerializedStringSchema, SerializedValViewSchema, SerializedVideoSchema, SharedValConfig, VAL_ENABLE_COOKIE_NAME, VAL_GREEN_RAMP, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, VAL_STUDIO_HEADER, ValCommit, ValDeployment, VideoOptions, VideoStreamOption, WASM_ENCODED_MIME_TYPES, accentRamp, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, chainHeadOf, checkCompatibility, chooseEncoded, concatModulePath, contrastRatio, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, enumLocaleTag, explainIncompatible, fallbackMimeType, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, forEachRichTextImage, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, getValDemo, indexModule, isInsidePatchSetPath, isLocaleSchema, isMimeTypeAccepted, isRemoteSchema, isRepairedOnSave, isSchemaSourceFixError, isSkippedSource, isStudioFix, joinSharedChanges, keyOfRecordPath, levenshtein, localeAt, localeMeantBy, memoizePerRequest, newestCommitSha, parseArtifacts, parseBuildTarget, parseCancel, parseDeclare, parseDiscard, parseJob, parseNewestCiRun, parsePress, parseProblems, parseProjectSource, parsePromote, parsePublishToken, parseRenew, parseRequestStatus, parseStatus, parseUpdateTarget, parseVerify, partitionValidationErrors, performSearch, relativeLuminance, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolvePairGround, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextImageSchema, richTextToRemirror, safeParsePatch, schemaErrorsOf, schemaTypesOfPath, selectionClosure, shouldEncodeWithWasm, sideFiles, sideSource, sourcePathOfChild, themeCustomProperties, toolNames, traverseSchemaSource, urlOf, validateRoutePatterns, withExtension };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valbuild/shared",
3
- "version": "0.148.2",
3
+ "version": "0.148.6",
4
4
  "private": false,
5
5
  "description": "Val shared types and utilities",
6
6
  "repository": {