@getvitops/config 8.3.0-alpha.3 → 8.3.0-alpha.5

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,37 @@
1
1
  # @getvitops/config
2
2
 
3
+ ## 8.3.0-alpha.5
4
+
5
+ ### Minor Changes
6
+
7
+ - bb61a35: **Call tracking numbers: show an ad visitor a number that attributes the phone call.** Two additive config blocks, no CSS, no breaking change — all formats unaffected.
8
+
9
+ - **`site.ads.google.callTracking`** — Google Ads _website call conversions_. `{ label, number, cache? }` makes the consent-gated Google tag fetch a Google forwarding number (Canada supported) and swap it in for visitors who arrived from a Google ad. Google replaces text only, so the capture script also rewrites the matching `tel:` href. Needs `pixelId` and `site.tracking.enabled`; rejected on any other platform (only Google can put a number on the website — Microsoft's exist only inside the ad, in the US/UK/AU).
10
+ - **`site.tracking.phoneNumbers`** — per-platform numbers for every other platform: the real `number`, `sources` keyed by `site.ads` platform (plus `default-paid`), and an informational `provider`. You provision the numbers with a call-tracking service (Twilio, CallRail, …). The landing URL decides first, then the `_ac` cookie, so a visitor who declines `marketing` sees a tracking number on the landing page only. An organic visitor is never swapped, and the server-rendered HTML always carries the real number.
11
+ - The `tel:` tap beacon now carries which number was shown (`phoneSource`), and the conversion notification says so.
12
+ - `vitops ads lint` flags a call-tracking provider that is not in `site.legal.privacyPolicy.processors`, and notes that `callTracking` plus `conversionLabel.call` can count one call twice (make one secondary in Google Ads).
13
+ - `@getvitops/utils/tracking` exports the pure `selectPhoneNumber()` and helpers; the capture script mirrors it and `tracking.test.ts` pins the two.
14
+ - `@getvitops/skills` gains a **`vitops-ads`** skill (overview plus a `call-tracking.md` topic). The templates gitignore synced skills by prefix (`vitops-*`, `emdash-*`), so it needs no `.gitignore` line of its own.
15
+
16
+ `@getvitops/core`, `@getvitops/vite` and `@getvitops/emdash` — no consumer-facing change in these packages.
17
+
18
+ - 4d94c8b: **Added: `site.chrome` — a configurable masthead and nav behaviour.** Non-breaking.
19
+
20
+ - `site.chrome.masthead` (new `@getvitops/config` schema): `position` (`static|sticky|fixed`), `behavior` (`always|hide-on-scroll`), `surface` (`solid|glass|transparent|transparent-until-scrolled`), `tone` (a `colors.roles` key), `layout` (`full|inset|floating`), `width`, `elevation` (`shadow`, `border`, `onScroll`), `height` and `inset`. `height` and `inset` take fluid lengths (`calc()`, `clamp()`, `min()`, `max()`, with `cqi`/`vw`/… units), checked against a closed grammar because they are written into CSS. `validateConfig` rejects a `tone` or `shadow` the design system does not define (only when the default theme states them directly, not via `extends`).
21
+ - `site.chrome.nav`: `compact` (`drawer|dropdown`), `drawerSide`, `breakpoint`/`mobileBreakpoint`/`megaBreakpoint`, `depth`, `megaDepth`.
22
+ - `<Masthead config={site}>` (`@getvitops/astro`, slots `brand`/default/`actions`) and `resolveMasthead()` / `resolveNavOptions()`. A prop overrides one field per layout. The key is `chrome`/`masthead` on purpose: `<header>` is valid in any section and `banner` already means the announcement bar and the landmark role.
23
+ - `<SiteNav>` gains `compact="dropdown"` (a columnar panel anchored under the toggle), `drawerSide` (previously hardcoded to the end edge in Astro), `megaDepth` and `megaPanel`. `.sitenav--mega` now styles second-level items as column headings and has an optional featured column.
24
+ - `@getvitops/emdash`: `VitopsMegaPanel` fills that column from the widget area `mega-<item url slug>` (or a `mega-area:<name>` class on the menu item). No area means no column.
25
+ - `@getvitops/create` (emdash template): the layout renders through `<Masthead>`; `site.json` ships a sticky glass masthead with a shadow once scrolled. The hand-written `.navbar { block-size: 64px }` is gone (use `masthead.height`).
26
+ - CSS (`patterns/masthead.css`, `patterns/sitenav.css`): **all formats**, including tailwind, where the patterns are inlined into `@layer components`. Hide-on-scroll and the `*-on-scroll` states are `scroll-state()` container queries that only subtract from a settled (solid, elevated, visible) bar, so a browser without them (Firefox today) shows that bar rather than a transparent one. A sticky or fixed masthead sets `--header-offset` / `--top-offset` so anchors land below it.
27
+ - `@getvitops/generator`: new `TIERS.masthead`; `TIERS.sitenav` documents the new props.
28
+
29
+ ### Patch Changes
30
+
31
+ - 785b8b3: Docs only: the `site.ads.google.conversionLabel` field descriptions and `vitops ads` now state which Google Ads category each conversion action needs (`call` → Phone call lead, `email` → Contact, `form` → Submit lead form). The category is set on the action in Google Ads, not by the tag, so a wrong one reports under the wrong goal with no code-side signal. `vitopsFormConversion()`'s docblock also warns against firing a form conversion from a success-page query parameter. No behaviour change.
32
+
33
+ ## 8.3.0-alpha.4
34
+
3
35
  ## 8.3.0-alpha.3
4
36
 
5
37
  ## 8.3.0-alpha.2
package/dist/index.d.mts CHANGED
@@ -88,6 +88,11 @@ export declare const AdPropertySchema: z.ZodMiniObject<{
88
88
  email: z.ZodMiniOptional<z.ZodMiniString<string>>;
89
89
  form: z.ZodMiniOptional<z.ZodMiniString<string>>;
90
90
  }, z.core.$strict>]>>;
91
+ callTracking: z.ZodMiniOptional<z.ZodMiniObject<{
92
+ label: z.ZodMiniString<string>;
93
+ number: z.ZodMiniString<string>;
94
+ cache: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
95
+ }, z.core.$strict>>;
91
96
  reporting: z.ZodMiniOptional<z.ZodMiniEnum<{
92
97
  client: "client";
93
98
  server: "server";
@@ -255,6 +260,21 @@ export declare const TrackingSchema: z.ZodMiniOptional<z.ZodMiniObject<{
255
260
  analytics: "analytics";
256
261
  }>>;
257
262
  platforms: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniString<string>>>;
263
+ phoneNumbers: z.ZodMiniOptional<z.ZodMiniObject<{
264
+ number: z.ZodMiniString<string>;
265
+ sources: z.ZodMiniRecord<z.ZodMiniEnum<{
266
+ google: "google";
267
+ meta: "meta";
268
+ linkedin: "linkedin";
269
+ reddit: "reddit";
270
+ tiktok: "tiktok";
271
+ microsoft: "microsoft";
272
+ pinterest: "pinterest";
273
+ snapchat: "snapchat";
274
+ "default-paid": "default-paid";
275
+ }> & z.core.$partial, z.ZodMiniString<string>>;
276
+ provider: z.ZodMiniOptional<z.ZodMiniString<string>>;
277
+ }, z.core.$strict>>;
258
278
  }, z.core.$strict>>;
259
279
  /** `site.security` */
260
280
  export declare const SecuritySchema: z.ZodMiniOptional<z.ZodMiniObject<{
@@ -335,5 +355,167 @@ export declare const SeoSchema: z.ZodMiniObject<{
335
355
  }, z.core.$strict>>;
336
356
  }, z.core.$strict>;
337
357
  export type SeoConfig = z.infer<typeof SeoSchema>;
358
+ /**
359
+ * `site.chrome` — the site's page-level furniture: the **masthead** (the top bar holding brand,
360
+ * navigation and actions) and how its navigation behaves at each width.
361
+ *
362
+ * Why `chrome`/`masthead` and not `header`/`banner`: `<header>` is valid inside any section or
363
+ * article, and `banner` already means the announcement bar (`.banner`, `vitops_banner`) — and is
364
+ * the ARIA landmark role auditors flag when written explicitly on a `<header>`.
365
+ *
366
+ * Read by `@getvitops/astro`'s `resolveMasthead()` / `resolveNavOptions()`; a `<Masthead>` prop
367
+ * overrides the matching field for one layout (e.g. a transparent-over-hero landing page).
368
+ */
369
+ export declare const MASTHEAD_POSITIONS: readonly ["static", "sticky", "fixed"];
370
+ export declare const MASTHEAD_BEHAVIORS: readonly ["always", "hide-on-scroll"];
371
+ export declare const MASTHEAD_SURFACES: readonly ["solid", "glass", "transparent", "transparent-until-scrolled"];
372
+ export declare const MASTHEAD_LAYOUTS: readonly ["full", "inset", "floating"];
373
+ export declare const MASTHEAD_WIDTHS: readonly ["full", "content"];
374
+ export declare const NAV_BREAKPOINTS: readonly ["sm", "md", "lg", "xl"];
375
+ export declare const NAV_COMPACT_MODES: readonly ["drawer", "dropdown"];
376
+ export declare const NAV_DRAWER_SIDES: readonly ["start", "end"];
377
+ export type MastheadPosition = (typeof MASTHEAD_POSITIONS)[number];
378
+ export type MastheadBehavior = (typeof MASTHEAD_BEHAVIORS)[number];
379
+ export type MastheadSurface = (typeof MASTHEAD_SURFACES)[number];
380
+ export type MastheadLayout = (typeof MASTHEAD_LAYOUTS)[number];
381
+ export type MastheadWidth = (typeof MASTHEAD_WIDTHS)[number];
382
+ export declare const MastheadElevationSchema: z.ZodMiniObject<{
383
+ shadow: z.ZodMiniOptional<z.ZodMiniString<string>>;
384
+ border: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
385
+ onScroll: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
386
+ }, z.core.$strict>;
387
+ export declare const MastheadSchema: z.ZodMiniObject<{
388
+ position: z.ZodMiniOptional<z.ZodMiniEnum<{
389
+ static: "static";
390
+ sticky: "sticky";
391
+ fixed: "fixed";
392
+ }>>;
393
+ behavior: z.ZodMiniOptional<z.ZodMiniEnum<{
394
+ always: "always";
395
+ "hide-on-scroll": "hide-on-scroll";
396
+ }>>;
397
+ surface: z.ZodMiniOptional<z.ZodMiniEnum<{
398
+ solid: "solid";
399
+ glass: "glass";
400
+ transparent: "transparent";
401
+ "transparent-until-scrolled": "transparent-until-scrolled";
402
+ }>>;
403
+ tone: z.ZodMiniOptional<z.ZodMiniString<string>>;
404
+ layout: z.ZodMiniOptional<z.ZodMiniEnum<{
405
+ full: "full";
406
+ inset: "inset";
407
+ floating: "floating";
408
+ }>>;
409
+ width: z.ZodMiniOptional<z.ZodMiniEnum<{
410
+ full: "full";
411
+ content: "content";
412
+ }>>;
413
+ elevation: z.ZodMiniOptional<z.ZodMiniObject<{
414
+ shadow: z.ZodMiniOptional<z.ZodMiniString<string>>;
415
+ border: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
416
+ onScroll: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
417
+ }, z.core.$strict>>;
418
+ height: z.ZodMiniOptional<z.ZodMiniString<string>>;
419
+ inset: z.ZodMiniOptional<z.ZodMiniString<string>>;
420
+ }, z.core.$strict>;
421
+ export declare const ChromeNavSchema: z.ZodMiniObject<{
422
+ breakpoint: z.ZodMiniOptional<z.ZodMiniEnum<{
423
+ sm: "sm";
424
+ md: "md";
425
+ lg: "lg";
426
+ xl: "xl";
427
+ }>>;
428
+ mobileBreakpoint: z.ZodMiniOptional<z.ZodMiniEnum<{
429
+ sm: "sm";
430
+ md: "md";
431
+ lg: "lg";
432
+ xl: "xl";
433
+ }>>;
434
+ megaBreakpoint: z.ZodMiniOptional<z.ZodMiniEnum<{
435
+ sm: "sm";
436
+ md: "md";
437
+ lg: "lg";
438
+ xl: "xl";
439
+ }>>;
440
+ depth: z.ZodMiniOptional<z.ZodMiniInt>;
441
+ megaDepth: z.ZodMiniOptional<z.ZodMiniInt>;
442
+ compact: z.ZodMiniOptional<z.ZodMiniEnum<{
443
+ drawer: "drawer";
444
+ dropdown: "dropdown";
445
+ }>>;
446
+ drawerSide: z.ZodMiniOptional<z.ZodMiniEnum<{
447
+ start: "start";
448
+ end: "end";
449
+ }>>;
450
+ }, z.core.$strict>;
451
+ export declare const ChromeSchema: z.ZodMiniObject<{
452
+ masthead: z.ZodMiniOptional<z.ZodMiniObject<{
453
+ position: z.ZodMiniOptional<z.ZodMiniEnum<{
454
+ static: "static";
455
+ sticky: "sticky";
456
+ fixed: "fixed";
457
+ }>>;
458
+ behavior: z.ZodMiniOptional<z.ZodMiniEnum<{
459
+ always: "always";
460
+ "hide-on-scroll": "hide-on-scroll";
461
+ }>>;
462
+ surface: z.ZodMiniOptional<z.ZodMiniEnum<{
463
+ solid: "solid";
464
+ glass: "glass";
465
+ transparent: "transparent";
466
+ "transparent-until-scrolled": "transparent-until-scrolled";
467
+ }>>;
468
+ tone: z.ZodMiniOptional<z.ZodMiniString<string>>;
469
+ layout: z.ZodMiniOptional<z.ZodMiniEnum<{
470
+ full: "full";
471
+ inset: "inset";
472
+ floating: "floating";
473
+ }>>;
474
+ width: z.ZodMiniOptional<z.ZodMiniEnum<{
475
+ full: "full";
476
+ content: "content";
477
+ }>>;
478
+ elevation: z.ZodMiniOptional<z.ZodMiniObject<{
479
+ shadow: z.ZodMiniOptional<z.ZodMiniString<string>>;
480
+ border: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
481
+ onScroll: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
482
+ }, z.core.$strict>>;
483
+ height: z.ZodMiniOptional<z.ZodMiniString<string>>;
484
+ inset: z.ZodMiniOptional<z.ZodMiniString<string>>;
485
+ }, z.core.$strict>>;
486
+ nav: z.ZodMiniOptional<z.ZodMiniObject<{
487
+ breakpoint: z.ZodMiniOptional<z.ZodMiniEnum<{
488
+ sm: "sm";
489
+ md: "md";
490
+ lg: "lg";
491
+ xl: "xl";
492
+ }>>;
493
+ mobileBreakpoint: z.ZodMiniOptional<z.ZodMiniEnum<{
494
+ sm: "sm";
495
+ md: "md";
496
+ lg: "lg";
497
+ xl: "xl";
498
+ }>>;
499
+ megaBreakpoint: z.ZodMiniOptional<z.ZodMiniEnum<{
500
+ sm: "sm";
501
+ md: "md";
502
+ lg: "lg";
503
+ xl: "xl";
504
+ }>>;
505
+ depth: z.ZodMiniOptional<z.ZodMiniInt>;
506
+ megaDepth: z.ZodMiniOptional<z.ZodMiniInt>;
507
+ compact: z.ZodMiniOptional<z.ZodMiniEnum<{
508
+ drawer: "drawer";
509
+ dropdown: "dropdown";
510
+ }>>;
511
+ drawerSide: z.ZodMiniOptional<z.ZodMiniEnum<{
512
+ start: "start";
513
+ end: "end";
514
+ }>>;
515
+ }, z.core.$strict>>;
516
+ }, z.core.$strict>;
517
+ export type MastheadConfig = z.infer<typeof MastheadSchema>;
518
+ export type ChromeNavConfig = z.infer<typeof ChromeNavSchema>;
519
+ export type ChromeConfig = z.infer<typeof ChromeSchema>;
338
520
  export { AD_PROVIDERS, AdProvider, BIGIN_DATA_CENTERS, CRM_PROVIDERS, CrmProviderConfig };
339
521
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/desc.ts","../src/indexing.ts","../src/ads.ts","../src/reviews.ts","../src/crm.ts","../src/blocks.ts","../src/seo.ts"],"mappings":";;;qBAGa,OAAQ;EAAY,SAAS,kBAAkB;GAAK,QAAQ,GAAG,iBAAe;;;;;;;;qBCO9E,0BAAwB,EAAA;;;GASnC,EAAA,KAAA;;;;;;;;;;;;;;;qBAgBW,gBAAc,EAAA;;;;GAazB,EAAA,KAAA;qBAEW,qBAAmB,EAAA;;;GAS9B,EAAA,KAAA;qBAEW,gBAAc,EAAA;;;;;;;;;;;;GAiBzB,EAAA,KAAA;YAEU,iBAAiB,EAAE,aAAa;YAChC,sBAAsB,EAAE,aAAa;YACrC,iBAAiB,EAAE,aAAa;YAChC,2BAA2B,EAAE,aAAa;qBC/EzC,kBAAgB,EAAA;;;;;;;;;;;;;;;;;;;;qBAYhB,kBAAgB,EAAA;;;;;;;;;;;;;;;;;;;;;;;;GAqE3B,EAAA,KAAA;YAEU,mBAAmB,EAAE,aAAa;;;;;;;;qBC7EjC,sBAAoB,EAAA;;;;;;;;;;GAmB/B,EAAA,KAAA;qBAEW,eAAa,EAAA;;;;;;;;;;;;;;;GAiBxB,EAAA,KAAA;YAEU,gBAAgB,EAAE,aAAa;YAC/B,uBAAuB,EAAE,aAAa;qBC/CrC,mBAAiB,EAAA;;;;;qBAEjB,qBAAmB,EAAA;;;;;;;GAO9B,EAAA,KAAA;qBAEW,2BAAyB,EAAA;;;;GAIpC,EAAA,KAAA;qBAEW,mBAAiB,EAAA;;;;;;;;;;;;;;;GAa5B,EAAA,KAAA;qBAEW,wBAAsB,EAAA;;GAKjC,EAAA,KAAA;qBAEW,oBAAkB,EAAA;;;;;;GAa7B,EAAA,KAAA;;;;;;;;qBASW,WAAS,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiBpB,EAAA,KAAA;YAEU,YAAY,EAAE,aAAa;YAC3B,gBAAgB,EAAE,aAAa;YAC/B,cAAc,EAAE,aAAa;YAC7B,sBAAsB,EAAE,aAAa;YACrC,eAAe,EAAE,aAAa;;qBCpF7B,qBAAmB,EAAA,gBAAA,EAAA;;;;;;;;;;;GA6C/B,EAAA,KAAA;;qBAGY,gBAAc,EAAA,gBAAA,EAAA;;;;;;;GAe1B,EAAA,KAAA;;qBAGY,gBAAc,EAAA,gBAAA,EAAA;;;;GAQ1B,EAAA,KAAA;;qBAGY,kBAAgB,EAAA,gBAAA,EAAA;;;;;;;;GAgB5B,EAAA,KAAA;YAEW,sBAAsB,YAAY,EAAE,aAAa;YACjD,iBAAiB,YAAY,EAAE,aAAa;YAC5C,iBAAiB,YAAY,EAAE,aAAa;YAC5C,mBAAmB,YAAY,EAAE,aAAa;;qBCjG7C,mBAAiB,EAAA,uBAAA,EAAA,uBAAA,EAAA,cAAA,EAAA,uBAAA,EAAA;;;;;;;;;qBA8BjB,WAAS,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DpB,EAAA,KAAA;YAEU,YAAY,EAAE,aAAa"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/desc.ts","../src/indexing.ts","../src/ads.ts","../src/reviews.ts","../src/crm.ts","../src/blocks.ts","../src/seo.ts","../src/chrome.ts"],"mappings":";;;qBAGa,OAAQ;EAAY,SAAS,kBAAkB;GAAK,QAAQ,GAAG,iBAAe;;;;;;;;qBCO9E,0BAAwB,EAAA;;;GASnC,EAAA,KAAA;;;;;;;;;;;;;;;qBAgBW,gBAAc,EAAA;;;;GAazB,EAAA,KAAA;qBAEW,qBAAmB,EAAA;;;GAS9B,EAAA,KAAA;qBAEW,gBAAc,EAAA;;;;;;;;;;;;GAiBzB,EAAA,KAAA;YAEU,iBAAiB,EAAE,aAAa;YAChC,sBAAsB,EAAE,aAAa;YACrC,iBAAiB,EAAE,aAAa;YAChC,2BAA2B,EAAE,aAAa;qBC/EzC,kBAAgB,EAAA;;;;;;;;;;;;;;;;;;;;qBAYhB,kBAAgB,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+F3B,EAAA,KAAA;YAEU,mBAAmB,EAAE,aAAa;;;;;;;;qBCvGjC,sBAAoB,EAAA;;;;;;;;;;GAmB/B,EAAA,KAAA;qBAEW,eAAa,EAAA;;;;;;;;;;;;;;;GAiBxB,EAAA,KAAA;YAEU,gBAAgB,EAAE,aAAa;YAC/B,uBAAuB,EAAE,aAAa;qBC/CrC,mBAAiB,EAAA;;;;;qBAEjB,qBAAmB,EAAA;;;;;;;GAO9B,EAAA,KAAA;qBAEW,2BAAyB,EAAA;;;;GAIpC,EAAA,KAAA;qBAEW,mBAAiB,EAAA;;;;;;;;;;;;;;;GAa5B,EAAA,KAAA;qBAEW,wBAAsB,EAAA;;GAKjC,EAAA,KAAA;qBAEW,oBAAkB,EAAA;;;;;;GAa7B,EAAA,KAAA;;;;;;;;qBASW,WAAS,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiBpB,EAAA,KAAA;YAEU,YAAY,EAAE,aAAa;YAC3B,gBAAgB,EAAE,aAAa;YAC/B,cAAc,EAAE,aAAa;YAC7B,sBAAsB,EAAE,aAAa;YACrC,eAAe,EAAE,aAAa;;qBCnF7B,qBAAmB,EAAA,gBAAA,EAAA;;;;;;;;;;;GA6C/B,EAAA,KAAA;;qBAGY,gBAAc,EAAA,gBAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;GAkC1B,EAAA,KAAA;;qBAGY,gBAAc,EAAA,gBAAA,EAAA;;;;GAQ1B,EAAA,KAAA;;qBAGY,kBAAgB,EAAA,gBAAA,EAAA;;;;;;;;GAgB5B,EAAA,KAAA;YAEW,sBAAsB,YAAY,EAAE,aAAa;YACjD,iBAAiB,YAAY,EAAE,aAAa;YAC5C,iBAAiB,YAAY,EAAE,aAAa;YAC5C,mBAAmB,YAAY,EAAE,aAAa;;qBCrH7C,mBAAiB,EAAA,uBAAA,EAAA,uBAAA,EAAA,cAAA,EAAA,uBAAA,EAAA;;;;;;;;;qBA8BjB,WAAS,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DpB,EAAA,KAAA;YAEU,YAAY,EAAE,aAAa;;;;;;;;;;;;qBCrF1B;qBACA;qBACA;qBAMA;qBACA;qBACA;qBACA;qBACA;YAED,2BAA2B;YAC3B,2BAA2B;YAC3B,0BAA0B;YAC1B,yBAAyB;YACzB,wBAAwB;qBAEvB,yBAAuB,EAAA;;;;GAalC,EAAA,KAAA;qBAEW,gBAAc,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCzB,EAAA,KAAA;qBAEW,iBAAe,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6B1B,EAAA,KAAA;qBAEW,cAAY,EAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAMvB,EAAA,KAAA;YAEU,iBAAiB,EAAE,aAAa;YAChC,kBAAkB,EAAE,aAAa;YACjC,eAAe,EAAE,aAAa"}
package/dist/index.mjs CHANGED
@@ -63,15 +63,20 @@ const AdPropertySchema = z.strictObject({
63
63
  accountId: desc(z.optional(z.string()), "The advertising account as the platform shows it — Google Ads customer ID, Meta ad account, LinkedIn partner ID, Reddit advertiser ID. For LinkedIn this is also the Insight Tag id; everywhere else the tag id is `pixelId`."),
64
64
  pixelId: desc(z.optional(z.string()), "The tag/pixel ID the browser snippet initialises — Meta pixel, TikTok pixel, Reddit pixel, Microsoft UET tag ID, Pinterest tag ID, Snap pixel, or the Google Ads conversion ID (`AW-…`, which is NOT the customer ID)."),
65
65
  conversionLabel: desc(z.optional(z.union([z.string(), z.strictObject({
66
- call: desc(z.optional(z.string()), "Fired on a tel: link click."),
67
- email: desc(z.optional(z.string()), "Fired on a mailto: link click."),
68
- form: desc(z.optional(z.string()), "Fired client-side by `vitopsFormConversion()` — the consumer's own submit handler calls it once a form POST succeeds.")
69
- })])), "Google Ads only: the conversion action label(s) paired with the `AW-…` conversion ID — either one bare label (back-compat, treated as the `call` action) or a map of action → label, so a call, an email click and a form submission each fire their own Google Ads conversion action instead of all three reporting as the same one."),
66
+ call: desc(z.optional(z.string()), "Fired on a tel: link click. Create the action in Google Ads with category \"Phone call lead\"."),
67
+ email: desc(z.optional(z.string()), "Fired on a mailto: link click. Create the action in Google Ads with category \"Contact\"."),
68
+ form: desc(z.optional(z.string()), "Fired client-side by `vitopsFormConversion()` — the consumer's own submit handler calls it once a form POST succeeds. Create the action in Google Ads with category \"Submit lead form\". Don't fire it from a success-page query parameter: that replays on refresh and the honeypot returns the same success page.")
69
+ })])), "Google Ads only: the conversion action label(s) paired with the `AW-…` conversion ID — the category (Phone call lead, Contact, …) is a property of the action in Google Ads, not of the tag call, so a wrong category can't be fixed from code; either one bare label (back-compat, treated as the `call` action) or a map of action → label, so a call, an email click and a form submission each fire their own Google Ads conversion action instead of all three reporting as the same one."),
70
70
  conversionActionId: desc(z.optional(z.union([z.string(), z.strictObject({
71
71
  call: desc(z.optional(z.string()), "Fired on a tel: link click."),
72
72
  email: desc(z.optional(z.string()), "Fired on a mailto: link click."),
73
73
  form: desc(z.optional(z.string()), "Fired on a form submission.")
74
74
  })])), "Google Ads only: the conversion action **resource id(s)** (a numeric id, or a full `customers/…/conversionActions/…` resource name) used for server-side offline conversion upload — NOT the same value as `conversionLabel`, which is the opaque label the web tag uses and which the Ads API's upload endpoint does not accept. Only meaningful when `reporting: \"server\"`."),
75
+ callTracking: desc(z.optional(z.strictObject({
76
+ label: desc(z.string(), "The conversion action label of a **separate** Google Ads action of type \"Calls from a phone number on your website\" (not the `conversionLabel.call` action, which counts `tel:` taps). Paired with `pixelId` as `AW-…/<label>`."),
77
+ number: desc(z.string(), "The phone number exactly as the site displays it, e.g. `613-555-0100`. Google swaps its forwarding number in only where the page text matches these digits, so a differently formatted number on the page is silently never replaced."),
78
+ cache: desc(z.optional(z.boolean()), "Set false to stop Google caching the fetched forwarding number in a cookie (`phone_conversion_options.cache`). Default true, so the number follows the visitor across pages.")
79
+ })), "Google Ads only: **website call conversions**. The Google tag fetches a Google forwarding number for visitors who arrived from a Google ad and swaps it in for `number`, so a phone call (not just a `tel:` tap) is attributed to the click. Google decides who sees it; this toolchain also rewrites the matching `tel:` links, which Google does not. Counts the same call as `conversionLabel.call` if both are primary — make one secondary in Google Ads. Any other platform has no equivalent: see `site.tracking.phoneNumbers`."),
75
80
  reporting: desc(z.optional(z.enum(["client", "server"])), "Google Ads only: `\"client\"` (default) fires the conversion via `gtag.js` in the browser at click/submit time; `\"server\"` uploads it via the Ads API from the conversion endpoint instead (see `conversionActionId`). An action with no matching `conversionActionId` keeps firing client-side regardless of this setting, so setting it before `conversionActionId` is filled in never silently drops a conversion."),
76
81
  domain: desc(z.optional(z.string()), "Bare hostname to verify with this platform. Defaults to the host of `domains.canonical` — set it only when the ad account is verified against a different domain."),
77
82
  domainVerification: desc(z.optional(z.string()), "The verification token from the platform UI, for the platforms that verify by DNS TXT (Meta, TikTok, Pinterest, Snapchat). `vitops ads setup` prompts for it and writes it here on first run. NOT a secret — it is published in DNS, and the platform fetching it back is the ownership proof, exactly like the IndexNow key. A value containing `=` is used as the whole record, which is the escape hatch if a platform changes its prefix."),
@@ -160,7 +165,12 @@ const NotificationsSchema = z.optional(z.strictObject({
160
165
  const TrackingSchema = z.optional(z.strictObject({
161
166
  enabled: desc(z.optional(z.boolean()), "Capture ad click IDs and UTMs from the landing URL into the `_ac` cookie, for attributing conversions."),
162
167
  category: desc(z.optional(z.enum(["marketing", "analytics"])), "Consent category the `_ac` cookie waits on (default `marketing`). It is a 90-day identifier tying a visitor to the ad that brought them, so it is only written once this category is granted — and asking for it is what raises the banner."),
163
- platforms: desc(z.optional(z.array(z.string())), "Informational list of ad platforms in use. Capture recognises every known click-ID parameter regardless.")
168
+ platforms: desc(z.optional(z.array(z.string())), "Informational list of ad platforms in use. Capture recognises every known click-ID parameter regardless."),
169
+ phoneNumbers: desc(z.optional(z.strictObject({
170
+ number: desc(z.string(), "The real phone number exactly as the site displays it. Server-rendered HTML always carries this one, so crawlers, structured data and visitors without JavaScript see it."),
171
+ sources: desc(z.partialRecord(z.enum([...AD_PROVIDERS, "default-paid"]), z.string()), "Tracking numbers keyed by ad platform (the `site.ads` provider names), each provisioned with a call-tracking provider and forwarding to `number`. `default-paid` is the fallback for a paid visit from a platform with no number of its own. Google needs no entry when `site.ads.google.callTracking` is set — Google supplies its own."),
172
+ provider: desc(z.optional(z.string()), "The call-tracking provider (e.g. `twilio`, `callrail`). Informational, but it records callers’ numbers, so it must be named in the privacy policy — declare it in `site.legal.privacyPolicy.processors` unless it is a known one.")
173
+ })), "Show a visitor from an ad the tracking number for that platform instead of the real one. Swapped in by the capture script from the landing URL (immediately) or the `_ac` cookie (later pages, once `marketing` is granted); the matching `tel:` links are rewritten too. An organic visitor is never swapped. Needs `enabled: true`.")
164
174
  }));
165
175
  /** `site.security` */
166
176
  const SecuritySchema = z.optional(z.strictObject({ turnstile: z.optional(z.strictObject({ siteKey: desc(z.optional(z.string()), "Cloudflare Turnstile site key (public).") })) }));
@@ -230,5 +240,73 @@ const SeoSchema = z.strictObject({
230
240
  indexing: desc(z.optional(IndexingSchema), "How `vitops search notify` tells search engines about a deploy: which sitemap, IndexNow key, Search Console property, and which pages to verify afterwards.")
231
241
  });
232
242
 
233
- export { AD_PROVIDERS, AdPropertySchema, AdProviderSchema, BIGIN_DATA_CENTERS, BiginConfigSchema, BiginPipelineConfigSchema, CRM_PROVIDERS, CrmProviderSchema, CrmSchema, DeploymentSchema, HubSpotConfigSchema, IndexNowSchema, IndexingSchema, JobberConfigSchema, JobberNoteConfigSchema, LocalizableSchema, NotificationsSchema, ReviewLocationSchema, ReviewsSchema, SearchConsoleSchema, SearchConsoleSetupSchema, SecuritySchema, SeoSchema, TrackingSchema, desc };
243
+ // src/chrome.ts
244
+ /**
245
+ * `site.chrome` — the site's page-level furniture: the **masthead** (the top bar holding brand,
246
+ * navigation and actions) and how its navigation behaves at each width.
247
+ *
248
+ * Why `chrome`/`masthead` and not `header`/`banner`: `<header>` is valid inside any section or
249
+ * article, and `banner` already means the announcement bar (`.banner`, `vitops_banner`) — and is
250
+ * the ARIA landmark role auditors flag when written explicitly on a `<header>`.
251
+ *
252
+ * Read by `@getvitops/astro`'s `resolveMasthead()` / `resolveNavOptions()`; a `<Masthead>` prop
253
+ * overrides the matching field for one layout (e.g. a transparent-over-hero landing page).
254
+ */
255
+ const MASTHEAD_POSITIONS = [
256
+ "static",
257
+ "sticky",
258
+ "fixed"
259
+ ];
260
+ const MASTHEAD_BEHAVIORS = ["always", "hide-on-scroll"];
261
+ const MASTHEAD_SURFACES = [
262
+ "solid",
263
+ "glass",
264
+ "transparent",
265
+ "transparent-until-scrolled"
266
+ ];
267
+ const MASTHEAD_LAYOUTS = [
268
+ "full",
269
+ "inset",
270
+ "floating"
271
+ ];
272
+ const MASTHEAD_WIDTHS = ["full", "content"];
273
+ const NAV_BREAKPOINTS = [
274
+ "sm",
275
+ "md",
276
+ "lg",
277
+ "xl"
278
+ ];
279
+ const NAV_COMPACT_MODES = ["drawer", "dropdown"];
280
+ const NAV_DRAWER_SIDES = ["start", "end"];
281
+ const MastheadElevationSchema = z.strictObject({
282
+ shadow: desc(z.optional(z.string()), "A `designSystem.shadows` key (e.g. `sm`). Omit for no shadow."),
283
+ border: desc(z.optional(z.boolean()), "A bottom border (a full outline when `layout` is not `full`)."),
284
+ onScroll: desc(z.optional(z.boolean()), "Apply the shadow/border only once the page has scrolled, so the masthead sits flush with the page at the top. Uses `scroll-state()` container queries; in a browser without them (Firefox today) the elevation is simply always on.")
285
+ });
286
+ const MastheadSchema = z.strictObject({
287
+ position: desc(z.optional(z.enum(MASTHEAD_POSITIONS)), "`static` scrolls away; `sticky` stays in flow and pins to the top; `fixed` is out of flow and overlays the first section (for a transparent masthead over a hero). Default `static`."),
288
+ behavior: desc(z.optional(z.enum(MASTHEAD_BEHAVIORS)), "`hide-on-scroll` slides the masthead away while scrolling down and back on scrolling up. Ignored when `position` is `static`. It never hides while a menu inside it is open or has focus. Uses `scroll-state()`; in a browser without it (Firefox today) the masthead just stays visible. Default `always`."),
289
+ surface: desc(z.optional(z.enum(MASTHEAD_SURFACES)), "`solid`, `glass` (blurred + tinted via `--surface-glass`), `transparent`, or `transparent-until-scrolled` (clear while the page is at the top, solid once it scrolls; a browser without `scroll-state()` shows it solid throughout). Default `solid`."),
290
+ tone: desc(z.optional(z.string()), "A `colors.roles` key, optionally `<role>:solid`, applied as the masthead `.tone` so text contrast stays the generator's guarantee."),
291
+ layout: desc(z.optional(z.enum(MASTHEAD_LAYOUTS)), "`full` is flush to the viewport edges; `inset` leaves a gap to them; `floating` is inset with a panel radius. Default `full`."),
292
+ width: desc(z.optional(z.enum(MASTHEAD_WIDTHS)), "`content` keeps the masthead row on the page's `.centered` track; `full` lets it span the masthead. Default `content`."),
293
+ elevation: desc(z.optional(MastheadElevationSchema), "Shadow and border."),
294
+ height: desc(z.optional(z.string()), "CSS length for the masthead row (default `4rem`). `calc()`, `clamp()`, `min()` and `max()` are accepted, so it can be fluid: `clamp(3.5rem, 8cqi, 4.5rem)`. `cqi` resolves against the nearest size container (`body`, the same one the nav breakpoints use), `vw` against the viewport. Becomes `--masthead-height` and, when the masthead is sticky or fixed, `--header-offset` so in-page anchors land below it."),
295
+ inset: desc(z.optional(z.string()), "Gap between a `layout: inset` or `floating` masthead and the viewport edge (default `0.75rem`). Same length grammar as `height`, e.g. `clamp(0.5rem, 2cqi, 1.5rem)`. Ignored when `layout` is `full`. Becomes `--masthead-inset` and is counted in `--header-offset`.")
296
+ });
297
+ const ChromeNavSchema = z.strictObject({
298
+ breakpoint: desc(z.optional(z.enum(NAV_BREAKPOINTS)), "Container width where the default `primary` menu promotes from the compact control to a navbar. Default `md`."),
299
+ mobileBreakpoint: desc(z.optional(z.enum(NAV_BREAKPOINTS)), "Where a `primary-mobile` menu hands over to `primary`. Default = `breakpoint`."),
300
+ megaBreakpoint: desc(z.optional(z.enum(NAV_BREAKPOINTS)), "Where a `primary-mega` menu takes over. Default `xl`."),
301
+ depth: desc(z.optional(z.int().check(z.gte(1), z.lte(3))), "Levels in the default nav (1–3). Default 3."),
302
+ megaDepth: desc(z.optional(z.int().check(z.gte(1), z.lte(3))), "Levels in the mega panel (1–3). Default 3."),
303
+ compact: desc(z.optional(z.enum(NAV_COMPACT_MODES)), "What the toggle opens below its breakpoint: an edge `drawer`, or a columnar `dropdown` panel anchored under the toggle. Default `drawer`."),
304
+ drawerSide: desc(z.optional(z.enum(NAV_DRAWER_SIDES)), "Edge the drawer slides from when `compact` is `drawer`. Default `end`.")
305
+ });
306
+ const ChromeSchema = z.strictObject({
307
+ masthead: desc(z.optional(MastheadSchema), "The top bar: position, scroll behaviour, surface, layout, elevation."),
308
+ nav: desc(z.optional(ChromeNavSchema), "How the masthead navigation behaves at each width.")
309
+ });
310
+
311
+ export { AD_PROVIDERS, AdPropertySchema, AdProviderSchema, BIGIN_DATA_CENTERS, BiginConfigSchema, BiginPipelineConfigSchema, CRM_PROVIDERS, ChromeNavSchema, ChromeSchema, CrmProviderSchema, CrmSchema, DeploymentSchema, HubSpotConfigSchema, IndexNowSchema, IndexingSchema, JobberConfigSchema, JobberNoteConfigSchema, LocalizableSchema, MASTHEAD_BEHAVIORS, MASTHEAD_LAYOUTS, MASTHEAD_POSITIONS, MASTHEAD_SURFACES, MASTHEAD_WIDTHS, MastheadElevationSchema, MastheadSchema, NAV_BREAKPOINTS, NAV_COMPACT_MODES, NAV_DRAWER_SIDES, NotificationsSchema, ReviewLocationSchema, ReviewsSchema, SearchConsoleSchema, SearchConsoleSetupSchema, SecuritySchema, SeoSchema, TrackingSchema, desc };
234
312
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/desc.ts","../src/indexing.ts","../src/ads.ts","../src/reviews.ts","../src/crm.ts","../src/blocks.ts","../src/seo.ts"],"sourcesContent":["import * as z from '@zod/mini';\n\n/** Attach a human description to a schema; it becomes the JSON Schema `description` editors show as a hover. */\nexport const desc = <T extends { check(...checks: never[]): T }>(schema: T, text: string): T =>\n schema.check(z.describe(text) as never);\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\n\n/**\n * One domain to onboard into Google Search Console via `vitops search setup`.\n *\n * The verification token is fetched live from the Site Verification API at run\n * time and written to DNS — it is never stored here. What the config records is\n * the *intent*: which domains to onboard, and who should co-own them.\n */\nexport const SearchConsoleSetupSchema = z.strictObject({\n delegatedOwners: desc(\n z.optional(z.array(z.email())),\n 'Emails added as verification owners on the Site Verification web resource. Automated via the API — additive and idempotent, never removes an existing owner.',\n ),\n fullUserGroup: desc(\n z.optional(z.email()),\n 'A Google Group to grant Full-User access in Search Console. SURFACED AS A REMINDER only: Search Console exposes no user/permission API, so this step stays manual by design.',\n ),\n});\n\n/**\n * What `vitops search notify` needs to tell search engines a deploy happened.\n *\n * Scoped by what search engines actually accept, which is narrower than it looks:\n * Google exposes **no** \"request indexing\" API (the Search Console button is not in\n * its API, and URL Inspection is read-only), and it removed the sitemap ping\n * endpoint in 2023. So the sanctioned set is: an accurate sitemap, a Search Console\n * sitemap resubmit, an IndexNow ping — which Google does *not* honour but Bing,\n * Yandex, Naver, Seznam and Yep do — and a read-only inspection pass to find out\n * what Google actually did.\n *\n * Facts only, the same rule the legal block follows: this records what the site has\n * (a key, a property, which pages matter), never what to say about it.\n */\nexport const IndexNowSchema = z.strictObject({\n key: desc(\n z.string().check(z.regex(/^[a-zA-Z0-9-]{8,128}$/)),\n 'IndexNow key (8–128 chars, hex is conventional). NOT a secret — it is served publicly at `keyLocation` so the engine can verify you own the host. Generate one with `vitops search notify --new-key`.',\n ),\n keyLocation: desc(\n z.optional(z.url()),\n 'Absolute URL of the key file. Defaults to `<canonical>/<key>.txt`; set it only when the file lives elsewhere.',\n ),\n endpoint: desc(\n z.optional(z.url()),\n 'IndexNow endpoint (default `https://api.indexnow.org/indexnow`). Any participating engine shares submissions with the rest, so one is normally enough.',\n ),\n});\n\nexport const SearchConsoleSchema = z.strictObject({\n siteUrl: desc(\n z.string(),\n 'The property exactly as Search Console identifies it — `sc-domain:acme.ca` for a domain property, or the URL-prefix form `https://acme.ca/`. A mismatch here is a 403, not a \"not found\".',\n ),\n resubmitSitemap: desc(\n z.optional(z.boolean()),\n 'Re-submit the sitemap through the Search Console API on each notify (default true when `searchConsole` is set). This is the automated equivalent of the manual resubmit in the UI.',\n ),\n});\n\nexport const IndexingSchema = z.strictObject({\n sitemapUrl: desc(\n z.optional(z.url()),\n 'The sitemap to submit and to diff for changed URLs. Defaults to `<canonical>/sitemap-index.xml`.',\n ),\n indexNow: desc(\n z.optional(IndexNowSchema),\n 'IndexNow submission (Bing, Yandex, Naver, Seznam, Yep — not Google). Omit to skip the channel.',\n ),\n searchConsole: desc(\n z.optional(SearchConsoleSchema),\n 'Google Search Console property. Needs a credential at run time — either a service account in `VITOPS_GSC_SERVICE_ACCOUNT` / `GOOGLE_APPLICATION_CREDENTIALS`, or the user OAuth credential `vitops search setup` uses (`VITOPS_GOOGLE_CLIENT_ID` / `_CLIENT_SECRET` / `_REFRESH_TOKEN`). The service account wins when both are set. Never put a key in this file.',\n ),\n priorityUrls: desc(\n z.optional(z.array(z.url())),\n 'The pages whose indexing actually matters. `vitops search notify --check` inspects these and exits non-zero if Google has not indexed one. Kept explicit because URL Inspection is quota-bound (2000/day), so checking every page is neither affordable nor informative.',\n ),\n});\n\nexport type IndexNowConfig = z.infer<typeof IndexNowSchema>;\nexport type SearchConsoleConfig = z.infer<typeof SearchConsoleSchema>;\nexport type IndexingConfig = z.infer<typeof IndexingSchema>;\nexport type SearchConsoleSetupConfig = z.infer<typeof SearchConsoleSetupSchema>;\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\nimport { AD_PROVIDERS } from './providers.ts';\n\nexport const AdProviderSchema = z.enum(AD_PROVIDERS);\n\n/**\n * One ad property this site is linked to.\n *\n * Facts, not prose — the same rule the legal block follows. This records *which*\n * account and *which* tag id, and the platform capability table decides what can be\n * done about them. It is also what finally lets the rest of the toolchain see an ad\n * pixel: the generated cookie notice discloses its cookies, `vitops ads lint`\n * checks its click ID is captured, and the consent gate is handed the cookie names\n * to clear on revoke. A pixel pasted into a template is invisible to all three.\n */\nexport const AdPropertySchema = z.strictObject({\n accountId: desc(\n z.optional(z.string()),\n 'The advertising account as the platform shows it — Google Ads customer ID, Meta ad account, LinkedIn partner ID, Reddit advertiser ID. For LinkedIn this is also the Insight Tag id; everywhere else the tag id is `pixelId`.',\n ),\n pixelId: desc(\n z.optional(z.string()),\n 'The tag/pixel ID the browser snippet initialises — Meta pixel, TikTok pixel, Reddit pixel, Microsoft UET tag ID, Pinterest tag ID, Snap pixel, or the Google Ads conversion ID (`AW-…`, which is NOT the customer ID).',\n ),\n conversionLabel: desc(\n z.optional(\n z.union([\n z.string(),\n z.strictObject({\n call: desc(z.optional(z.string()), 'Fired on a tel: link click.'),\n email: desc(z.optional(z.string()), 'Fired on a mailto: link click.'),\n form: desc(\n z.optional(z.string()),\n \"Fired client-side by `vitopsFormConversion()` — the consumer's own submit handler calls it once a form POST succeeds.\",\n ),\n }),\n ]),\n ),\n 'Google Ads only: the conversion action label(s) paired with the `AW-…` conversion ID — ' +\n 'either one bare label (back-compat, treated as the `call` action) or a map of ' +\n 'action → label, so a call, an email click and a form submission each fire their own ' +\n 'Google Ads conversion action instead of all three reporting as the same one.',\n ),\n conversionActionId: desc(\n z.optional(\n z.union([\n z.string(),\n z.strictObject({\n call: desc(z.optional(z.string()), 'Fired on a tel: link click.'),\n email: desc(z.optional(z.string()), 'Fired on a mailto: link click.'),\n form: desc(z.optional(z.string()), 'Fired on a form submission.'),\n }),\n ]),\n ),\n 'Google Ads only: the conversion action **resource id(s)** (a numeric id, or a full ' +\n '`customers/…/conversionActions/…` resource name) used for server-side offline ' +\n 'conversion upload — NOT the same value as `conversionLabel`, which is the opaque ' +\n \"label the web tag uses and which the Ads API's upload endpoint does not accept. \" +\n 'Only meaningful when `reporting: \"server\"`.',\n ),\n reporting: desc(\n z.optional(z.enum(['client', 'server'])),\n 'Google Ads only: `\"client\"` (default) fires the conversion via `gtag.js` in the ' +\n 'browser at click/submit time; `\"server\"` uploads it via the Ads API from the ' +\n 'conversion endpoint instead (see `conversionActionId`). An action with no matching ' +\n '`conversionActionId` keeps firing client-side regardless of this setting, so setting ' +\n 'it before `conversionActionId` is filled in never silently drops a conversion.',\n ),\n domain: desc(\n z.optional(z.string()),\n 'Bare hostname to verify with this platform. Defaults to the host of `domains.canonical` — set it only when the ad account is verified against a different domain.',\n ),\n domainVerification: desc(\n z.optional(z.string()),\n 'The verification token from the platform UI, for the platforms that verify by DNS TXT (Meta, TikTok, Pinterest, Snapchat). `vitops ads setup` prompts for it and writes it here on first run. NOT a secret — it is published in DNS, and the platform fetching it back is the ownership proof, exactly like the IndexNow key. A value containing `=` is used as the whole record, which is the escape hatch if a platform changes its prefix.',\n ),\n category: desc(\n z.optional(z.enum(['marketing', 'analytics'])),\n 'Consent category the tag waits on (default `marketing`). An ad pixel is advertising by default; `analytics` is for a site using one purely for its own measurement.',\n ),\n enabled: desc(\n z.optional(z.boolean()),\n 'Set false to keep the property on record without emitting its tag.',\n ),\n});\n\nexport type AdPropertyConfig = z.infer<typeof AdPropertySchema>;\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\n\n/**\n * How one location's Google reviews are selected and ordered for display.\n *\n * Keyed by `organization.locations` key, mirroring `site.ads`/`site.searchConsole`\n * — `vitops reviews` needs to know which physical location a fetched review set\n * belongs to, and that record already exists.\n */\nexport const ReviewLocationSchema = z.strictObject({\n accountId: desc(\n z.optional(z.string()),\n 'GBP account resource id (`accounts/…`), overriding `site.reviews.accountId` for this location.',\n ),\n locationId: desc(z.string(), 'GBP location resource id (`locations/…`) to fetch reviews for.'),\n featured: desc(\n z.optional(z.array(z.string())),\n 'Review ids to always include, in this order, ahead of everything else automatically selected.',\n ),\n minRating: desc(\n z.optional(z.number()),\n 'Drop reviews below this star rating (default 4) from the automatic (non-featured) selection.',\n ),\n limit: desc(z.optional(z.number()), 'Keep at most this many reviews after filtering.'),\n sort: desc(\n z.optional(z.enum(['recent', 'rating'])),\n 'Ordering for the non-featured remainder (default `recent`).',\n ),\n});\n\nexport const ReviewsSchema = z.strictObject({\n accountId: desc(\n z.optional(z.string()),\n 'Default GBP account resource id (`accounts/…`) for locations that do not override it.',\n ),\n locations: desc(\n z.record(z.string(), ReviewLocationSchema),\n 'Which `organization.locations` to fetch Google reviews for, and how each one’s reviews are filtered/ordered. `vitops reviews` writes the result to `.vitops/reviews.json`.',\n ),\n schema: desc(\n z.optional(z.boolean()),\n 'Emit `Review` JSON-LD for these reviews (default false). Off by default: Google’s structured-data policy treats a business marking up reviews of itself as ineligible for review rich results, and risks a manual action.',\n ),\n avatars: desc(\n z.optional(z.boolean()),\n 'Render the reviewer’s Google-hosted avatar photo (default false). Off by default: every visitor would otherwise issue a third-party request to googleusercontent.com, and the URLs are not guaranteed to stay live.',\n ),\n});\n\nexport type ReviewsConfig = z.infer<typeof ReviewsSchema>;\nexport type ReviewLocationConfig = z.infer<typeof ReviewLocationSchema>;\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\nimport { BIGIN_DATA_CENTERS, CRM_PROVIDERS } from './providers.ts';\n\nexport const CrmProviderSchema = z.enum(CRM_PROVIDERS);\n\nexport const HubSpotConfigSchema = z.strictObject({\n portalId: desc(z.string(), 'The HubSpot portal (hub) ID the form belongs to.'),\n formGuid: desc(z.string(), 'The HubSpot form GUID to submit against.'),\n dataCenter: desc(\n z.optional(z.enum(['na', 'eu'])),\n 'Which regional submissions API to post to (default `na`).',\n ),\n});\n\nexport const BiginPipelineConfigSchema = z.strictObject({\n enabled: desc(z.boolean(), 'Also open a Pipelines record for the new/updated contact.'),\n subPipeline: desc(z.optional(z.string()), 'Sub-pipeline to file the record under.'),\n stage: desc(z.optional(z.string()), 'Starting stage for the pipeline record.'),\n});\n\nexport const BiginConfigSchema = z.strictObject({\n dataCenter: desc(\n z.enum(BIGIN_DATA_CENTERS),\n \"The self-client's data centre — determines both the OAuth accounts host and the API host (`www.zohoapis.<dc>`).\",\n ),\n duplicateCheckField: desc(\n z.optional(z.string()),\n \"Bigin field the upsert dedupes on (default `Email`, Bigin's own unique field).\",\n ),\n pipeline: desc(\n z.optional(BiginPipelineConfigSchema),\n 'Also open a Pipelines record alongside the Contact.',\n ),\n});\n\nexport const JobberNoteConfigSchema = z.strictObject({\n enabled: desc(\n z.optional(z.boolean()),\n 'Attach a best-effort note carrying the message and unmapped attribution (default true). A failure here never loses the client record itself.',\n ),\n});\n\nexport const JobberConfigSchema = z.strictObject({\n apiVersion: desc(\n z.optional(z.string()),\n \"Override the pinned `X-JOBBER-GRAPHQL-VERSION` date this integration is tested against. An escape hatch for an app pinned to a different version — Jobber's schema can change field names between versions.\",\n ),\n dedupe: desc(\n z.optional(z.boolean()),\n \"Look up an existing client by email before creating one (default false pending verification of Jobber's search API against a live schema — see AGENTS.md). Jobber's `clientCreate` has no upsert, so without this a resubmitting visitor creates a second client.\",\n ),\n note: desc(\n z.optional(JobberNoteConfigSchema),\n 'Attach a best-effort note carrying the submitted message and any attribution/extra fields with no dedicated Jobber field.',\n ),\n});\n\n/**\n * A client's CRM, wired to the same conversion route `site.notifications`\n * already reaches — see AGENTS.md's \"CRM submissions\" section for why this is\n * server-side-only and hung off `createConversionRoute()` rather than a\n * client-side embed. Credentials never live here — the toolchain has no\n * secret store, matching every other integration.\n */\nexport const CrmSchema = z.strictObject({\n provider: desc(CrmProviderSchema, 'Which CRM this site pushes form submissions into.'),\n enabled: desc(\n z.optional(z.boolean()),\n 'Set false to keep the block on record without writing to the CRM.',\n ),\n hubspot: desc(z.optional(HubSpotConfigSchema), 'Required when `provider` is `hubspot`.'),\n bigin: desc(z.optional(BiginConfigSchema), 'Required when `provider` is `bigin`.'),\n jobber: desc(z.optional(JobberConfigSchema), 'Required when `provider` is `jobber`.'),\n fieldMap: desc(\n z.optional(z.record(z.string(), z.string())),\n 'Submitted form field name → CRM property name. A field with no entry here is dropped, not guessed — an unmapped field never reaches the CRM.',\n ),\n trackingFields: desc(\n z.optional(z.record(z.string(), z.string())),\n '`_ac` attribution key (a click-ID param, a UTM param, `landingPage`, `referrer`, or `ab_variant`) → CRM property name. Same drop-if-unmapped rule as `fieldMap`; a visitor with no `_ac` cookie maps to no attribution fields at all, never an error.',\n ),\n});\n\nexport type CrmConfig = z.infer<typeof CrmSchema>;\nexport type HubSpotConfig = z.infer<typeof HubSpotConfigSchema>;\nexport type BiginConfig = z.infer<typeof BiginConfigSchema>;\nexport type BiginPipelineConfig = z.infer<typeof BiginPipelineConfigSchema>;\nexport type JobberConfig = z.infer<typeof JobberConfigSchema>;\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\n\n/** `site.notifications` */\nexport const NotificationsSchema = z.optional(\n z.strictObject({\n email: desc(\n z.optional(\n z.union([\n z.email(),\n z.strictObject({\n provider: desc(\n z.literal('cloudflare'),\n 'Delivery provider. Only Cloudflare Email Sending is implemented; the field is stated so adding another is additive.',\n ),\n to: desc(\n z.optional(z.email()),\n 'Recipient. Defaults to the primary location’s `email`, then the first location that has one.',\n ),\n from: desc(\n z.optional(z.email()),\n 'Sender. Defaults to `noreply@<domains.canonical>`. Its domain must be onboarded (`wrangler email sending enable <domain>`) or every send fails.',\n ),\n fromName: desc(\n z.optional(z.string()),\n 'Display name on the From header. Defaults to `organization.name`.',\n ),\n replyTo: desc(z.optional(z.email()), 'Reply-To header.'),\n binding: desc(\n z.optional(z.string()),\n 'Workers `send_email` binding name (default `EMAIL`). Set it when using a restricted binding.',\n ),\n verifySender: desc(\n z.optional(z.boolean()),\n \"Set false to skip `vitops email check`'s deploy-time onboarding warning for this site — for a `from` domain the scan cannot see is correct (e.g. built dynamically), which would otherwise be a false positive.\",\n ),\n }),\n ]),\n ),\n 'Where conversion notifications are e-mailed. A bare address is shorthand for `{ provider: \"cloudflare\", to }`.',\n ),\n actionUrl: desc(\n z.optional(z.string()),\n 'Optional action-link template added to the conversion email (e.g. a link into your ' +\n 'ad platform or your own CRM/portal). `{clickId}` is replaced with the ad click ID ' +\n 'captured on `_ac` (gclid, fbclid, …). Omitted from the email when the event carries ' +\n 'no click ID to interpolate, rather than rendering a broken link.',\n ),\n }),\n);\n\n/** `site.tracking` */\nexport const TrackingSchema = z.optional(\n z.strictObject({\n enabled: desc(\n z.optional(z.boolean()),\n 'Capture ad click IDs and UTMs from the landing URL into the `_ac` cookie, for attributing conversions.',\n ),\n category: desc(\n z.optional(z.enum(['marketing', 'analytics'])),\n 'Consent category the `_ac` cookie waits on (default `marketing`). It is a 90-day identifier tying a visitor to the ad that brought them, so it is only written once this category is granted — and asking for it is what raises the banner.',\n ),\n platforms: desc(\n z.optional(z.array(z.string())),\n 'Informational list of ad platforms in use. Capture recognises every known click-ID parameter regardless.',\n ),\n }),\n);\n\n/** `site.security` */\nexport const SecuritySchema = z.optional(\n z.strictObject({\n turnstile: z.optional(\n z.strictObject({\n siteKey: desc(z.optional(z.string()), 'Cloudflare Turnstile site key (public).'),\n }),\n ),\n }),\n);\n\n/** `site.deployment` */\nexport const DeploymentSchema = z.optional(\n z.strictObject({\n platform: z.optional(z.string()),\n buildCommand: z.optional(z.string()),\n deployCommand: z.optional(z.string()),\n outputDirectory: z.optional(z.string()),\n type: z.optional(z.string()),\n repo: desc(\n z.optional(z.string()),\n \"`owner/name` of this site's GitHub repository. Read by the EmDash design-system-editor plugin route to auto-commit a client's admin edit back to `branch` — see AGENTS.md's \\\"EmDash plugin as the design-system editing surface\\\" section. Absent means that sync is skipped (the live override still applies); nothing else in this toolchain reads it today.\",\n ),\n branch: desc(\n z.optional(z.string()),\n 'Branch the design-system-editor sync commits to. Default `\"dev\"` — never `main`/production directly; a commit here still needs this repo\\'s own `promote` PR + admin approval to reach production, the same gate every other change goes through.',\n ),\n }),\n);\n\nexport type NotificationsConfig = NonNullable<z.infer<typeof NotificationsSchema>>;\nexport type TrackingConfig = NonNullable<z.infer<typeof TrackingSchema>>;\nexport type SecurityConfig = NonNullable<z.infer<typeof SecuritySchema>>;\nexport type DeploymentConfig = NonNullable<z.infer<typeof DeploymentSchema>>;\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\nimport { IndexingSchema } from './indexing.ts';\n\n/** A string, or a per-locale map of strings (`{ en: \"…\", fr: \"…\" }`). */\nexport const LocalizableSchema = desc(\n z.union([z.string(), z.record(z.string(), z.string())]),\n 'A string, or a per-locale map of strings (`{ en: \"…\", fr: \"…\" }`).',\n);\n\n/**\n * A share image: a bare URL, or the URL with the metadata crawlers read alongside it.\n *\n * Distinct from a content image (`alt` is required there): a site-wide fallback card often has\n * no meaningful alt text, and `<Seo />` already omits `og:image:alt` when none is given.\n */\nconst SeoImageSchema = z.union([\n z.string(),\n z.strictObject({\n src: desc(z.string(), 'Absolute, or relative to `site` (resolved at render time).'),\n alt: z.optional(z.string()),\n width: z.optional(z.number()),\n height: z.optional(z.number()),\n type: desc(z.optional(z.string()), 'MIME type, e.g. `image/png`.'),\n }),\n]);\n\n/**\n * `site.seo` — the single source of site-level SEO defaults.\n *\n * Read by `<Seo />` (through `@getvitops/astro`'s `resolveSeoDefaults`), `vitops robots` and\n * `vitops search notify`. Every display field here is consumed by `resolveSeoDefaults`;\n * `seo-defaults.test.ts` fails when a field is added to this schema and not read, which is how\n * four fields once sat here doing nothing (ADR-0002).\n */\nexport const SeoSchema = z.strictObject({\n siteName: desc(\n z.optional(LocalizableSchema),\n '`og:site_name`, and the `<title>` when a page passes none. Defaults to `organization.name`, so most sites never state it.',\n ),\n titleTemplate: desc(\n z.optional(LocalizableSchema),\n 'Page-title pattern; `%s` is the page title (e.g. \"%s · Acme\"). Applies to `<title>` only, never to `og:title`, and is skipped when the page title already equals the site name.',\n ),\n defaultTitle: desc(\n z.optional(LocalizableSchema),\n '`<title>` for pages that pass none. Used verbatim — the template is not applied. Defaults to the site name.',\n ),\n defaultDescription: desc(\n z.optional(LocalizableSchema),\n '`<meta name=\"description\">` for pages that pass none.',\n ),\n robots: desc(\n z.optional(z.string()),\n 'Site-wide robots directive (e.g. \"index,follow\" / \"noindex,nofollow\"). `site.environments.<env>.robots` wins over it.',\n ),\n googleSiteVerification: desc(\n z.optional(z.string()),\n 'Google Search Console verification token (`<meta name=\"google-site-verification\">`).',\n ),\n bingSiteVerification: desc(\n z.optional(z.string()),\n 'Bing Webmaster Tools verification token (`<meta name=\"msvalidate.01\">`).',\n ),\n openGraph: desc(\n z.optional(\n z.strictObject({\n type: desc(\n z.optional(z.enum(['website', 'article', 'profile'])),\n 'Default `og:type`. Default `website`.',\n ),\n locale: desc(z.optional(z.string()), '`og:locale`. `en-CA` and `en_CA` both emit `en_CA`.'),\n localeAlternates: desc(\n z.optional(z.array(z.string())),\n 'Other locales the same content exists in → `og:locale:alternate`.',\n ),\n image: desc(z.optional(SeoImageSchema), 'Fallback share image for pages that bring none.'),\n }),\n ),\n 'Open Graph defaults. Per-page `<Seo />` props override them.',\n ),\n twitter: desc(\n z.optional(\n z.strictObject({\n card: desc(\n z.optional(z.enum(['summary', 'summary_large_image', 'app', 'player'])),\n 'Card type when no image resolves (default `summary`).',\n ),\n site: desc(z.optional(z.string()), 'The site\\'s own handle, e.g. \"@acme\".'),\n creator: desc(z.optional(z.string()), 'Default author handle, e.g. \"@ada\".'),\n }),\n ),\n 'Twitter/X card defaults.',\n ),\n indexing: desc(\n z.optional(IndexingSchema),\n 'How `vitops search notify` tells search engines about a deploy: which sitemap, IndexNow key, Search Console property, and which pages to verify afterwards.',\n ),\n});\n\nexport type SeoConfig = z.infer<typeof SeoSchema>;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/desc.ts","../src/indexing.ts","../src/ads.ts","../src/reviews.ts","../src/crm.ts","../src/blocks.ts","../src/seo.ts","../src/chrome.ts"],"sourcesContent":["import * as z from '@zod/mini';\n\n/** Attach a human description to a schema; it becomes the JSON Schema `description` editors show as a hover. */\nexport const desc = <T extends { check(...checks: never[]): T }>(schema: T, text: string): T =>\n schema.check(z.describe(text) as never);\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\n\n/**\n * One domain to onboard into Google Search Console via `vitops search setup`.\n *\n * The verification token is fetched live from the Site Verification API at run\n * time and written to DNS — it is never stored here. What the config records is\n * the *intent*: which domains to onboard, and who should co-own them.\n */\nexport const SearchConsoleSetupSchema = z.strictObject({\n delegatedOwners: desc(\n z.optional(z.array(z.email())),\n 'Emails added as verification owners on the Site Verification web resource. Automated via the API — additive and idempotent, never removes an existing owner.',\n ),\n fullUserGroup: desc(\n z.optional(z.email()),\n 'A Google Group to grant Full-User access in Search Console. SURFACED AS A REMINDER only: Search Console exposes no user/permission API, so this step stays manual by design.',\n ),\n});\n\n/**\n * What `vitops search notify` needs to tell search engines a deploy happened.\n *\n * Scoped by what search engines actually accept, which is narrower than it looks:\n * Google exposes **no** \"request indexing\" API (the Search Console button is not in\n * its API, and URL Inspection is read-only), and it removed the sitemap ping\n * endpoint in 2023. So the sanctioned set is: an accurate sitemap, a Search Console\n * sitemap resubmit, an IndexNow ping — which Google does *not* honour but Bing,\n * Yandex, Naver, Seznam and Yep do — and a read-only inspection pass to find out\n * what Google actually did.\n *\n * Facts only, the same rule the legal block follows: this records what the site has\n * (a key, a property, which pages matter), never what to say about it.\n */\nexport const IndexNowSchema = z.strictObject({\n key: desc(\n z.string().check(z.regex(/^[a-zA-Z0-9-]{8,128}$/)),\n 'IndexNow key (8–128 chars, hex is conventional). NOT a secret — it is served publicly at `keyLocation` so the engine can verify you own the host. Generate one with `vitops search notify --new-key`.',\n ),\n keyLocation: desc(\n z.optional(z.url()),\n 'Absolute URL of the key file. Defaults to `<canonical>/<key>.txt`; set it only when the file lives elsewhere.',\n ),\n endpoint: desc(\n z.optional(z.url()),\n 'IndexNow endpoint (default `https://api.indexnow.org/indexnow`). Any participating engine shares submissions with the rest, so one is normally enough.',\n ),\n});\n\nexport const SearchConsoleSchema = z.strictObject({\n siteUrl: desc(\n z.string(),\n 'The property exactly as Search Console identifies it — `sc-domain:acme.ca` for a domain property, or the URL-prefix form `https://acme.ca/`. A mismatch here is a 403, not a \"not found\".',\n ),\n resubmitSitemap: desc(\n z.optional(z.boolean()),\n 'Re-submit the sitemap through the Search Console API on each notify (default true when `searchConsole` is set). This is the automated equivalent of the manual resubmit in the UI.',\n ),\n});\n\nexport const IndexingSchema = z.strictObject({\n sitemapUrl: desc(\n z.optional(z.url()),\n 'The sitemap to submit and to diff for changed URLs. Defaults to `<canonical>/sitemap-index.xml`.',\n ),\n indexNow: desc(\n z.optional(IndexNowSchema),\n 'IndexNow submission (Bing, Yandex, Naver, Seznam, Yep — not Google). Omit to skip the channel.',\n ),\n searchConsole: desc(\n z.optional(SearchConsoleSchema),\n 'Google Search Console property. Needs a credential at run time — either a service account in `VITOPS_GSC_SERVICE_ACCOUNT` / `GOOGLE_APPLICATION_CREDENTIALS`, or the user OAuth credential `vitops search setup` uses (`VITOPS_GOOGLE_CLIENT_ID` / `_CLIENT_SECRET` / `_REFRESH_TOKEN`). The service account wins when both are set. Never put a key in this file.',\n ),\n priorityUrls: desc(\n z.optional(z.array(z.url())),\n 'The pages whose indexing actually matters. `vitops search notify --check` inspects these and exits non-zero if Google has not indexed one. Kept explicit because URL Inspection is quota-bound (2000/day), so checking every page is neither affordable nor informative.',\n ),\n});\n\nexport type IndexNowConfig = z.infer<typeof IndexNowSchema>;\nexport type SearchConsoleConfig = z.infer<typeof SearchConsoleSchema>;\nexport type IndexingConfig = z.infer<typeof IndexingSchema>;\nexport type SearchConsoleSetupConfig = z.infer<typeof SearchConsoleSetupSchema>;\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\nimport { AD_PROVIDERS } from './providers.ts';\n\nexport const AdProviderSchema = z.enum(AD_PROVIDERS);\n\n/**\n * One ad property this site is linked to.\n *\n * Facts, not prose — the same rule the legal block follows. This records *which*\n * account and *which* tag id, and the platform capability table decides what can be\n * done about them. It is also what finally lets the rest of the toolchain see an ad\n * pixel: the generated cookie notice discloses its cookies, `vitops ads lint`\n * checks its click ID is captured, and the consent gate is handed the cookie names\n * to clear on revoke. A pixel pasted into a template is invisible to all three.\n */\nexport const AdPropertySchema = z.strictObject({\n accountId: desc(\n z.optional(z.string()),\n 'The advertising account as the platform shows it — Google Ads customer ID, Meta ad account, LinkedIn partner ID, Reddit advertiser ID. For LinkedIn this is also the Insight Tag id; everywhere else the tag id is `pixelId`.',\n ),\n pixelId: desc(\n z.optional(z.string()),\n 'The tag/pixel ID the browser snippet initialises — Meta pixel, TikTok pixel, Reddit pixel, Microsoft UET tag ID, Pinterest tag ID, Snap pixel, or the Google Ads conversion ID (`AW-…`, which is NOT the customer ID).',\n ),\n conversionLabel: desc(\n z.optional(\n z.union([\n z.string(),\n z.strictObject({\n call: desc(\n z.optional(z.string()),\n 'Fired on a tel: link click. Create the action in Google Ads with category \"Phone call lead\".',\n ),\n email: desc(\n z.optional(z.string()),\n 'Fired on a mailto: link click. Create the action in Google Ads with category \"Contact\".',\n ),\n form: desc(\n z.optional(z.string()),\n 'Fired client-side by `vitopsFormConversion()` — the consumer\\'s own submit handler calls it once a form POST succeeds. Create the action in Google Ads with category \"Submit lead form\". Don\\'t fire it from a success-page query parameter: that replays on refresh and the honeypot returns the same success page.',\n ),\n }),\n ]),\n ),\n 'Google Ads only: the conversion action label(s) paired with the `AW-…` conversion ID — ' +\n \"the category (Phone call lead, Contact, …) is a property of the action in Google Ads, not of the tag call, so a wrong category can't be fixed from code; \" +\n 'either one bare label (back-compat, treated as the `call` action) or a map of ' +\n 'action → label, so a call, an email click and a form submission each fire their own ' +\n 'Google Ads conversion action instead of all three reporting as the same one.',\n ),\n conversionActionId: desc(\n z.optional(\n z.union([\n z.string(),\n z.strictObject({\n call: desc(z.optional(z.string()), 'Fired on a tel: link click.'),\n email: desc(z.optional(z.string()), 'Fired on a mailto: link click.'),\n form: desc(z.optional(z.string()), 'Fired on a form submission.'),\n }),\n ]),\n ),\n 'Google Ads only: the conversion action **resource id(s)** (a numeric id, or a full ' +\n '`customers/…/conversionActions/…` resource name) used for server-side offline ' +\n 'conversion upload — NOT the same value as `conversionLabel`, which is the opaque ' +\n \"label the web tag uses and which the Ads API's upload endpoint does not accept. \" +\n 'Only meaningful when `reporting: \"server\"`.',\n ),\n callTracking: desc(\n z.optional(\n z.strictObject({\n label: desc(\n z.string(),\n 'The conversion action label of a **separate** Google Ads action of type \"Calls from a phone number on your website\" (not the `conversionLabel.call` action, which counts `tel:` taps). Paired with `pixelId` as `AW-…/<label>`.',\n ),\n number: desc(\n z.string(),\n 'The phone number exactly as the site displays it, e.g. `613-555-0100`. Google swaps its forwarding number in only where the page text matches these digits, so a differently formatted number on the page is silently never replaced.',\n ),\n cache: desc(\n z.optional(z.boolean()),\n 'Set false to stop Google caching the fetched forwarding number in a cookie (`phone_conversion_options.cache`). Default true, so the number follows the visitor across pages.',\n ),\n }),\n ),\n 'Google Ads only: **website call conversions**. The Google tag fetches a Google forwarding number for visitors who arrived from a Google ad and swaps it in for `number`, so a phone call (not just a `tel:` tap) is attributed to the click. Google decides who sees it; this toolchain also rewrites the matching `tel:` links, which Google does not. Counts the same call as `conversionLabel.call` if both are primary — make one secondary in Google Ads. Any other platform has no equivalent: see `site.tracking.phoneNumbers`.',\n ),\n reporting: desc(\n z.optional(z.enum(['client', 'server'])),\n 'Google Ads only: `\"client\"` (default) fires the conversion via `gtag.js` in the ' +\n 'browser at click/submit time; `\"server\"` uploads it via the Ads API from the ' +\n 'conversion endpoint instead (see `conversionActionId`). An action with no matching ' +\n '`conversionActionId` keeps firing client-side regardless of this setting, so setting ' +\n 'it before `conversionActionId` is filled in never silently drops a conversion.',\n ),\n domain: desc(\n z.optional(z.string()),\n 'Bare hostname to verify with this platform. Defaults to the host of `domains.canonical` — set it only when the ad account is verified against a different domain.',\n ),\n domainVerification: desc(\n z.optional(z.string()),\n 'The verification token from the platform UI, for the platforms that verify by DNS TXT (Meta, TikTok, Pinterest, Snapchat). `vitops ads setup` prompts for it and writes it here on first run. NOT a secret — it is published in DNS, and the platform fetching it back is the ownership proof, exactly like the IndexNow key. A value containing `=` is used as the whole record, which is the escape hatch if a platform changes its prefix.',\n ),\n category: desc(\n z.optional(z.enum(['marketing', 'analytics'])),\n 'Consent category the tag waits on (default `marketing`). An ad pixel is advertising by default; `analytics` is for a site using one purely for its own measurement.',\n ),\n enabled: desc(\n z.optional(z.boolean()),\n 'Set false to keep the property on record without emitting its tag.',\n ),\n});\n\nexport type AdPropertyConfig = z.infer<typeof AdPropertySchema>;\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\n\n/**\n * How one location's Google reviews are selected and ordered for display.\n *\n * Keyed by `organization.locations` key, mirroring `site.ads`/`site.searchConsole`\n * — `vitops reviews` needs to know which physical location a fetched review set\n * belongs to, and that record already exists.\n */\nexport const ReviewLocationSchema = z.strictObject({\n accountId: desc(\n z.optional(z.string()),\n 'GBP account resource id (`accounts/…`), overriding `site.reviews.accountId` for this location.',\n ),\n locationId: desc(z.string(), 'GBP location resource id (`locations/…`) to fetch reviews for.'),\n featured: desc(\n z.optional(z.array(z.string())),\n 'Review ids to always include, in this order, ahead of everything else automatically selected.',\n ),\n minRating: desc(\n z.optional(z.number()),\n 'Drop reviews below this star rating (default 4) from the automatic (non-featured) selection.',\n ),\n limit: desc(z.optional(z.number()), 'Keep at most this many reviews after filtering.'),\n sort: desc(\n z.optional(z.enum(['recent', 'rating'])),\n 'Ordering for the non-featured remainder (default `recent`).',\n ),\n});\n\nexport const ReviewsSchema = z.strictObject({\n accountId: desc(\n z.optional(z.string()),\n 'Default GBP account resource id (`accounts/…`) for locations that do not override it.',\n ),\n locations: desc(\n z.record(z.string(), ReviewLocationSchema),\n 'Which `organization.locations` to fetch Google reviews for, and how each one’s reviews are filtered/ordered. `vitops reviews` writes the result to `.vitops/reviews.json`.',\n ),\n schema: desc(\n z.optional(z.boolean()),\n 'Emit `Review` JSON-LD for these reviews (default false). Off by default: Google’s structured-data policy treats a business marking up reviews of itself as ineligible for review rich results, and risks a manual action.',\n ),\n avatars: desc(\n z.optional(z.boolean()),\n 'Render the reviewer’s Google-hosted avatar photo (default false). Off by default: every visitor would otherwise issue a third-party request to googleusercontent.com, and the URLs are not guaranteed to stay live.',\n ),\n});\n\nexport type ReviewsConfig = z.infer<typeof ReviewsSchema>;\nexport type ReviewLocationConfig = z.infer<typeof ReviewLocationSchema>;\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\nimport { BIGIN_DATA_CENTERS, CRM_PROVIDERS } from './providers.ts';\n\nexport const CrmProviderSchema = z.enum(CRM_PROVIDERS);\n\nexport const HubSpotConfigSchema = z.strictObject({\n portalId: desc(z.string(), 'The HubSpot portal (hub) ID the form belongs to.'),\n formGuid: desc(z.string(), 'The HubSpot form GUID to submit against.'),\n dataCenter: desc(\n z.optional(z.enum(['na', 'eu'])),\n 'Which regional submissions API to post to (default `na`).',\n ),\n});\n\nexport const BiginPipelineConfigSchema = z.strictObject({\n enabled: desc(z.boolean(), 'Also open a Pipelines record for the new/updated contact.'),\n subPipeline: desc(z.optional(z.string()), 'Sub-pipeline to file the record under.'),\n stage: desc(z.optional(z.string()), 'Starting stage for the pipeline record.'),\n});\n\nexport const BiginConfigSchema = z.strictObject({\n dataCenter: desc(\n z.enum(BIGIN_DATA_CENTERS),\n \"The self-client's data centre — determines both the OAuth accounts host and the API host (`www.zohoapis.<dc>`).\",\n ),\n duplicateCheckField: desc(\n z.optional(z.string()),\n \"Bigin field the upsert dedupes on (default `Email`, Bigin's own unique field).\",\n ),\n pipeline: desc(\n z.optional(BiginPipelineConfigSchema),\n 'Also open a Pipelines record alongside the Contact.',\n ),\n});\n\nexport const JobberNoteConfigSchema = z.strictObject({\n enabled: desc(\n z.optional(z.boolean()),\n 'Attach a best-effort note carrying the message and unmapped attribution (default true). A failure here never loses the client record itself.',\n ),\n});\n\nexport const JobberConfigSchema = z.strictObject({\n apiVersion: desc(\n z.optional(z.string()),\n \"Override the pinned `X-JOBBER-GRAPHQL-VERSION` date this integration is tested against. An escape hatch for an app pinned to a different version — Jobber's schema can change field names between versions.\",\n ),\n dedupe: desc(\n z.optional(z.boolean()),\n \"Look up an existing client by email before creating one (default false pending verification of Jobber's search API against a live schema — see AGENTS.md). Jobber's `clientCreate` has no upsert, so without this a resubmitting visitor creates a second client.\",\n ),\n note: desc(\n z.optional(JobberNoteConfigSchema),\n 'Attach a best-effort note carrying the submitted message and any attribution/extra fields with no dedicated Jobber field.',\n ),\n});\n\n/**\n * A client's CRM, wired to the same conversion route `site.notifications`\n * already reaches — see AGENTS.md's \"CRM submissions\" section for why this is\n * server-side-only and hung off `createConversionRoute()` rather than a\n * client-side embed. Credentials never live here — the toolchain has no\n * secret store, matching every other integration.\n */\nexport const CrmSchema = z.strictObject({\n provider: desc(CrmProviderSchema, 'Which CRM this site pushes form submissions into.'),\n enabled: desc(\n z.optional(z.boolean()),\n 'Set false to keep the block on record without writing to the CRM.',\n ),\n hubspot: desc(z.optional(HubSpotConfigSchema), 'Required when `provider` is `hubspot`.'),\n bigin: desc(z.optional(BiginConfigSchema), 'Required when `provider` is `bigin`.'),\n jobber: desc(z.optional(JobberConfigSchema), 'Required when `provider` is `jobber`.'),\n fieldMap: desc(\n z.optional(z.record(z.string(), z.string())),\n 'Submitted form field name → CRM property name. A field with no entry here is dropped, not guessed — an unmapped field never reaches the CRM.',\n ),\n trackingFields: desc(\n z.optional(z.record(z.string(), z.string())),\n '`_ac` attribution key (a click-ID param, a UTM param, `landingPage`, `referrer`, or `ab_variant`) → CRM property name. Same drop-if-unmapped rule as `fieldMap`; a visitor with no `_ac` cookie maps to no attribution fields at all, never an error.',\n ),\n});\n\nexport type CrmConfig = z.infer<typeof CrmSchema>;\nexport type HubSpotConfig = z.infer<typeof HubSpotConfigSchema>;\nexport type BiginConfig = z.infer<typeof BiginConfigSchema>;\nexport type BiginPipelineConfig = z.infer<typeof BiginPipelineConfigSchema>;\nexport type JobberConfig = z.infer<typeof JobberConfigSchema>;\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\nimport { AD_PROVIDERS } from './providers.ts';\n\n/** `site.notifications` */\nexport const NotificationsSchema = z.optional(\n z.strictObject({\n email: desc(\n z.optional(\n z.union([\n z.email(),\n z.strictObject({\n provider: desc(\n z.literal('cloudflare'),\n 'Delivery provider. Only Cloudflare Email Sending is implemented; the field is stated so adding another is additive.',\n ),\n to: desc(\n z.optional(z.email()),\n 'Recipient. Defaults to the primary location’s `email`, then the first location that has one.',\n ),\n from: desc(\n z.optional(z.email()),\n 'Sender. Defaults to `noreply@<domains.canonical>`. Its domain must be onboarded (`wrangler email sending enable <domain>`) or every send fails.',\n ),\n fromName: desc(\n z.optional(z.string()),\n 'Display name on the From header. Defaults to `organization.name`.',\n ),\n replyTo: desc(z.optional(z.email()), 'Reply-To header.'),\n binding: desc(\n z.optional(z.string()),\n 'Workers `send_email` binding name (default `EMAIL`). Set it when using a restricted binding.',\n ),\n verifySender: desc(\n z.optional(z.boolean()),\n \"Set false to skip `vitops email check`'s deploy-time onboarding warning for this site — for a `from` domain the scan cannot see is correct (e.g. built dynamically), which would otherwise be a false positive.\",\n ),\n }),\n ]),\n ),\n 'Where conversion notifications are e-mailed. A bare address is shorthand for `{ provider: \"cloudflare\", to }`.',\n ),\n actionUrl: desc(\n z.optional(z.string()),\n 'Optional action-link template added to the conversion email (e.g. a link into your ' +\n 'ad platform or your own CRM/portal). `{clickId}` is replaced with the ad click ID ' +\n 'captured on `_ac` (gclid, fbclid, …). Omitted from the email when the event carries ' +\n 'no click ID to interpolate, rather than rendering a broken link.',\n ),\n }),\n);\n\n/** `site.tracking` */\nexport const TrackingSchema = z.optional(\n z.strictObject({\n enabled: desc(\n z.optional(z.boolean()),\n 'Capture ad click IDs and UTMs from the landing URL into the `_ac` cookie, for attributing conversions.',\n ),\n category: desc(\n z.optional(z.enum(['marketing', 'analytics'])),\n 'Consent category the `_ac` cookie waits on (default `marketing`). It is a 90-day identifier tying a visitor to the ad that brought them, so it is only written once this category is granted — and asking for it is what raises the banner.',\n ),\n platforms: desc(\n z.optional(z.array(z.string())),\n 'Informational list of ad platforms in use. Capture recognises every known click-ID parameter regardless.',\n ),\n phoneNumbers: desc(\n z.optional(\n z.strictObject({\n number: desc(\n z.string(),\n 'The real phone number exactly as the site displays it. Server-rendered HTML always carries this one, so crawlers, structured data and visitors without JavaScript see it.',\n ),\n sources: desc(\n z.partialRecord(z.enum([...AD_PROVIDERS, 'default-paid']), z.string()),\n 'Tracking numbers keyed by ad platform (the `site.ads` provider names), each provisioned with a call-tracking provider and forwarding to `number`. `default-paid` is the fallback for a paid visit from a platform with no number of its own. Google needs no entry when `site.ads.google.callTracking` is set — Google supplies its own.',\n ),\n provider: desc(\n z.optional(z.string()),\n 'The call-tracking provider (e.g. `twilio`, `callrail`). Informational, but it records callers’ numbers, so it must be named in the privacy policy — declare it in `site.legal.privacyPolicy.processors` unless it is a known one.',\n ),\n }),\n ),\n 'Show a visitor from an ad the tracking number for that platform instead of the real one. Swapped in by the capture script from the landing URL (immediately) or the `_ac` cookie (later pages, once `marketing` is granted); the matching `tel:` links are rewritten too. An organic visitor is never swapped. Needs `enabled: true`.',\n ),\n }),\n);\n\n/** `site.security` */\nexport const SecuritySchema = z.optional(\n z.strictObject({\n turnstile: z.optional(\n z.strictObject({\n siteKey: desc(z.optional(z.string()), 'Cloudflare Turnstile site key (public).'),\n }),\n ),\n }),\n);\n\n/** `site.deployment` */\nexport const DeploymentSchema = z.optional(\n z.strictObject({\n platform: z.optional(z.string()),\n buildCommand: z.optional(z.string()),\n deployCommand: z.optional(z.string()),\n outputDirectory: z.optional(z.string()),\n type: z.optional(z.string()),\n repo: desc(\n z.optional(z.string()),\n \"`owner/name` of this site's GitHub repository. Read by the EmDash design-system-editor plugin route to auto-commit a client's admin edit back to `branch` — see AGENTS.md's \\\"EmDash plugin as the design-system editing surface\\\" section. Absent means that sync is skipped (the live override still applies); nothing else in this toolchain reads it today.\",\n ),\n branch: desc(\n z.optional(z.string()),\n 'Branch the design-system-editor sync commits to. Default `\"dev\"` — never `main`/production directly; a commit here still needs this repo\\'s own `promote` PR + admin approval to reach production, the same gate every other change goes through.',\n ),\n }),\n);\n\nexport type NotificationsConfig = NonNullable<z.infer<typeof NotificationsSchema>>;\nexport type TrackingConfig = NonNullable<z.infer<typeof TrackingSchema>>;\nexport type SecurityConfig = NonNullable<z.infer<typeof SecuritySchema>>;\nexport type DeploymentConfig = NonNullable<z.infer<typeof DeploymentSchema>>;\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\nimport { IndexingSchema } from './indexing.ts';\n\n/** A string, or a per-locale map of strings (`{ en: \"…\", fr: \"…\" }`). */\nexport const LocalizableSchema = desc(\n z.union([z.string(), z.record(z.string(), z.string())]),\n 'A string, or a per-locale map of strings (`{ en: \"…\", fr: \"…\" }`).',\n);\n\n/**\n * A share image: a bare URL, or the URL with the metadata crawlers read alongside it.\n *\n * Distinct from a content image (`alt` is required there): a site-wide fallback card often has\n * no meaningful alt text, and `<Seo />` already omits `og:image:alt` when none is given.\n */\nconst SeoImageSchema = z.union([\n z.string(),\n z.strictObject({\n src: desc(z.string(), 'Absolute, or relative to `site` (resolved at render time).'),\n alt: z.optional(z.string()),\n width: z.optional(z.number()),\n height: z.optional(z.number()),\n type: desc(z.optional(z.string()), 'MIME type, e.g. `image/png`.'),\n }),\n]);\n\n/**\n * `site.seo` — the single source of site-level SEO defaults.\n *\n * Read by `<Seo />` (through `@getvitops/astro`'s `resolveSeoDefaults`), `vitops robots` and\n * `vitops search notify`. Every display field here is consumed by `resolveSeoDefaults`;\n * `seo-defaults.test.ts` fails when a field is added to this schema and not read, which is how\n * four fields once sat here doing nothing (ADR-0002).\n */\nexport const SeoSchema = z.strictObject({\n siteName: desc(\n z.optional(LocalizableSchema),\n '`og:site_name`, and the `<title>` when a page passes none. Defaults to `organization.name`, so most sites never state it.',\n ),\n titleTemplate: desc(\n z.optional(LocalizableSchema),\n 'Page-title pattern; `%s` is the page title (e.g. \"%s · Acme\"). Applies to `<title>` only, never to `og:title`, and is skipped when the page title already equals the site name.',\n ),\n defaultTitle: desc(\n z.optional(LocalizableSchema),\n '`<title>` for pages that pass none. Used verbatim — the template is not applied. Defaults to the site name.',\n ),\n defaultDescription: desc(\n z.optional(LocalizableSchema),\n '`<meta name=\"description\">` for pages that pass none.',\n ),\n robots: desc(\n z.optional(z.string()),\n 'Site-wide robots directive (e.g. \"index,follow\" / \"noindex,nofollow\"). `site.environments.<env>.robots` wins over it.',\n ),\n googleSiteVerification: desc(\n z.optional(z.string()),\n 'Google Search Console verification token (`<meta name=\"google-site-verification\">`).',\n ),\n bingSiteVerification: desc(\n z.optional(z.string()),\n 'Bing Webmaster Tools verification token (`<meta name=\"msvalidate.01\">`).',\n ),\n openGraph: desc(\n z.optional(\n z.strictObject({\n type: desc(\n z.optional(z.enum(['website', 'article', 'profile'])),\n 'Default `og:type`. Default `website`.',\n ),\n locale: desc(z.optional(z.string()), '`og:locale`. `en-CA` and `en_CA` both emit `en_CA`.'),\n localeAlternates: desc(\n z.optional(z.array(z.string())),\n 'Other locales the same content exists in → `og:locale:alternate`.',\n ),\n image: desc(z.optional(SeoImageSchema), 'Fallback share image for pages that bring none.'),\n }),\n ),\n 'Open Graph defaults. Per-page `<Seo />` props override them.',\n ),\n twitter: desc(\n z.optional(\n z.strictObject({\n card: desc(\n z.optional(z.enum(['summary', 'summary_large_image', 'app', 'player'])),\n 'Card type when no image resolves (default `summary`).',\n ),\n site: desc(z.optional(z.string()), 'The site\\'s own handle, e.g. \"@acme\".'),\n creator: desc(z.optional(z.string()), 'Default author handle, e.g. \"@ada\".'),\n }),\n ),\n 'Twitter/X card defaults.',\n ),\n indexing: desc(\n z.optional(IndexingSchema),\n 'How `vitops search notify` tells search engines about a deploy: which sitemap, IndexNow key, Search Console property, and which pages to verify afterwards.',\n ),\n});\n\nexport type SeoConfig = z.infer<typeof SeoSchema>;\n","import * as z from '@zod/mini';\nimport { desc } from './desc.ts';\n\n/**\n * `site.chrome` — the site's page-level furniture: the **masthead** (the top bar holding brand,\n * navigation and actions) and how its navigation behaves at each width.\n *\n * Why `chrome`/`masthead` and not `header`/`banner`: `<header>` is valid inside any section or\n * article, and `banner` already means the announcement bar (`.banner`, `vitops_banner`) — and is\n * the ARIA landmark role auditors flag when written explicitly on a `<header>`.\n *\n * Read by `@getvitops/astro`'s `resolveMasthead()` / `resolveNavOptions()`; a `<Masthead>` prop\n * overrides the matching field for one layout (e.g. a transparent-over-hero landing page).\n */\n\nexport const MASTHEAD_POSITIONS = ['static', 'sticky', 'fixed'] as const;\nexport const MASTHEAD_BEHAVIORS = ['always', 'hide-on-scroll'] as const;\nexport const MASTHEAD_SURFACES = [\n 'solid',\n 'glass',\n 'transparent',\n 'transparent-until-scrolled',\n] as const;\nexport const MASTHEAD_LAYOUTS = ['full', 'inset', 'floating'] as const;\nexport const MASTHEAD_WIDTHS = ['full', 'content'] as const;\nexport const NAV_BREAKPOINTS = ['sm', 'md', 'lg', 'xl'] as const;\nexport const NAV_COMPACT_MODES = ['drawer', 'dropdown'] as const;\nexport const NAV_DRAWER_SIDES = ['start', 'end'] as const;\n\nexport type MastheadPosition = (typeof MASTHEAD_POSITIONS)[number];\nexport type MastheadBehavior = (typeof MASTHEAD_BEHAVIORS)[number];\nexport type MastheadSurface = (typeof MASTHEAD_SURFACES)[number];\nexport type MastheadLayout = (typeof MASTHEAD_LAYOUTS)[number];\nexport type MastheadWidth = (typeof MASTHEAD_WIDTHS)[number];\n\nexport const MastheadElevationSchema = z.strictObject({\n shadow: desc(\n z.optional(z.string()),\n 'A `designSystem.shadows` key (e.g. `sm`). Omit for no shadow.',\n ),\n border: desc(\n z.optional(z.boolean()),\n 'A bottom border (a full outline when `layout` is not `full`).',\n ),\n onScroll: desc(\n z.optional(z.boolean()),\n 'Apply the shadow/border only once the page has scrolled, so the masthead sits flush with the page at the top. Uses `scroll-state()` container queries; in a browser without them (Firefox today) the elevation is simply always on.',\n ),\n});\n\nexport const MastheadSchema = z.strictObject({\n position: desc(\n z.optional(z.enum(MASTHEAD_POSITIONS)),\n '`static` scrolls away; `sticky` stays in flow and pins to the top; `fixed` is out of flow and overlays the first section (for a transparent masthead over a hero). Default `static`.',\n ),\n behavior: desc(\n z.optional(z.enum(MASTHEAD_BEHAVIORS)),\n '`hide-on-scroll` slides the masthead away while scrolling down and back on scrolling up. Ignored when `position` is `static`. It never hides while a menu inside it is open or has focus. Uses `scroll-state()`; in a browser without it (Firefox today) the masthead just stays visible. Default `always`.',\n ),\n surface: desc(\n z.optional(z.enum(MASTHEAD_SURFACES)),\n '`solid`, `glass` (blurred + tinted via `--surface-glass`), `transparent`, or `transparent-until-scrolled` (clear while the page is at the top, solid once it scrolls; a browser without `scroll-state()` shows it solid throughout). Default `solid`.',\n ),\n tone: desc(\n z.optional(z.string()),\n \"A `colors.roles` key, optionally `<role>:solid`, applied as the masthead `.tone` so text contrast stays the generator's guarantee.\",\n ),\n layout: desc(\n z.optional(z.enum(MASTHEAD_LAYOUTS)),\n '`full` is flush to the viewport edges; `inset` leaves a gap to them; `floating` is inset with a panel radius. Default `full`.',\n ),\n width: desc(\n z.optional(z.enum(MASTHEAD_WIDTHS)),\n \"`content` keeps the masthead row on the page's `.centered` track; `full` lets it span the masthead. Default `content`.\",\n ),\n elevation: desc(z.optional(MastheadElevationSchema), 'Shadow and border.'),\n height: desc(\n z.optional(z.string()),\n 'CSS length for the masthead row (default `4rem`). `calc()`, `clamp()`, `min()` and `max()` are accepted, so it can be fluid: `clamp(3.5rem, 8cqi, 4.5rem)`. `cqi` resolves against the nearest size container (`body`, the same one the nav breakpoints use), `vw` against the viewport. Becomes `--masthead-height` and, when the masthead is sticky or fixed, `--header-offset` so in-page anchors land below it.',\n ),\n inset: desc(\n z.optional(z.string()),\n 'Gap between a `layout: inset` or `floating` masthead and the viewport edge (default `0.75rem`). Same length grammar as `height`, e.g. `clamp(0.5rem, 2cqi, 1.5rem)`. Ignored when `layout` is `full`. Becomes `--masthead-inset` and is counted in `--header-offset`.',\n ),\n});\n\nexport const ChromeNavSchema = z.strictObject({\n breakpoint: desc(\n z.optional(z.enum(NAV_BREAKPOINTS)),\n 'Container width where the default `primary` menu promotes from the compact control to a navbar. Default `md`.',\n ),\n mobileBreakpoint: desc(\n z.optional(z.enum(NAV_BREAKPOINTS)),\n 'Where a `primary-mobile` menu hands over to `primary`. Default = `breakpoint`.',\n ),\n megaBreakpoint: desc(\n z.optional(z.enum(NAV_BREAKPOINTS)),\n 'Where a `primary-mega` menu takes over. Default `xl`.',\n ),\n depth: desc(\n z.optional(z.int().check(z.gte(1), z.lte(3))),\n 'Levels in the default nav (1–3). Default 3.',\n ),\n megaDepth: desc(\n z.optional(z.int().check(z.gte(1), z.lte(3))),\n 'Levels in the mega panel (1–3). Default 3.',\n ),\n compact: desc(\n z.optional(z.enum(NAV_COMPACT_MODES)),\n 'What the toggle opens below its breakpoint: an edge `drawer`, or a columnar `dropdown` panel anchored under the toggle. Default `drawer`.',\n ),\n drawerSide: desc(\n z.optional(z.enum(NAV_DRAWER_SIDES)),\n 'Edge the drawer slides from when `compact` is `drawer`. Default `end`.',\n ),\n});\n\nexport const ChromeSchema = z.strictObject({\n masthead: desc(\n z.optional(MastheadSchema),\n 'The top bar: position, scroll behaviour, surface, layout, elevation.',\n ),\n nav: desc(z.optional(ChromeNavSchema), 'How the masthead navigation behaves at each width.'),\n});\n\nexport type MastheadConfig = z.infer<typeof MastheadSchema>;\nexport type ChromeNavConfig = z.infer<typeof ChromeNavSchema>;\nexport type ChromeConfig = z.infer<typeof ChromeSchema>;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvitops/config",
3
- "version": "8.3.0-alpha.3",
3
+ "version": "8.3.0-alpha.5",
4
4
  "description": "The site-config schema blocks the Vitops toolchain's provider integrations read — CRM, ads, reviews, indexing, notifications, tracking — as zod schemas and inferred types. A leaf package: it imports nothing else in the toolchain.",
5
5
  "homepage": "https://docs.vitops.ca",
6
6
  "bugs": "https://github.com/getvitops/design-system/issues",