@getvitops/generator 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,251 @@
1
1
  # @getvitops/generator
2
2
 
3
+ ## 3.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 6e68ace: **Breaking:** the site config is now a three-section `Config` — `designSystem`,
8
+ `organization`, `site`.
9
+
10
+ The flat `SiteConfig` held the company (`organization`, `contact`, `locations`) and the
11
+ deployment (`analytics`, `environments`, `seo`, `legal`) as peers, so no single noun
12
+ described it, and a second site sharing the same company had no way to say so. The three
13
+ sections split those apart: several sites can now carry one `organization` and differ only
14
+ in `site`.
15
+
16
+ **Migrating.** `designSystem` stays at the root, and the fields already under
17
+ `organization` stay where they are. Everything else moves into one of two sections:
18
+ - → `site`: `defaultLocale`, `locales`, `domains`, `dns`, `cloudflare`, `environments`,
19
+ `abTesting`, `fonts`, `tags`, `postTypes`, `galleries`, `testimonials`, `templates`,
20
+ `navigation`, `seo`, `analytics`, `notifications`, `tracking`, `security`, `legal`,
21
+ `icons`, `favicon`, `deployment`
22
+ - → `organization`: `contact`, `primaryLocation`, `locations`, `services`, `links`
23
+
24
+ You do not have to work this out from the list: `vitops validate` detects a pre-3.0 flat
25
+ config and prints every move by name, rather than reporting a dozen unknown keys.
26
+
27
+ ```jsonc
28
+ {
29
+ "designSystem": {
30
+ "themes": {
31
+ "default": {
32
+ /* … */
33
+ },
34
+ },
35
+ },
36
+ "organization": {
37
+ "name": "Acme",
38
+ "contact": "hq",
39
+ "locations": {
40
+ "hq": {
41
+ /* … */
42
+ },
43
+ },
44
+ },
45
+ "site": {
46
+ "defaultLocale": "en",
47
+ "domains": {
48
+ /* … */
49
+ },
50
+ "analytics": {
51
+ /* … */
52
+ },
53
+ },
54
+ }
55
+ ```
56
+
57
+ **Renamed exports** (`@getvitops/generator`): `SiteConfigSchema` → `ConfigSchema`,
58
+ `SiteConfig` → `Config`, `validateSite` → `validateConfig`, `resolveSiteConfig` →
59
+ `resolveConfig`, `isSiteConfig` → `isConfig`, `siteJsonSchema` → `configJsonSchema`,
60
+ `SITE_SCHEMA_URL` → `CONFIG_SCHEMA_URL`, `SiteValidationResult` →
61
+ `ConfigValidationResult`. `ResolvedInput.site` is now `ResolvedInput.config`. New:
62
+ `OrganizationConfig` and `SiteSection` for the two sections.
63
+
64
+ **Renamed schema file:** `@getvitops/generator/site.schema.json` →
65
+ `@getvitops/generator/config.schema.json`. Update the `$schema` key in your config.
66
+
67
+ Option names are unchanged — `vitops({ site: { input } })`, the Vite plugin's `site`,
68
+ and `generate({ site })` still point at the config file.
69
+
70
+ **The Astro integration is now `vitops()`, not `getvitops()`.** It is a default export,
71
+ so the name is yours and nothing breaks on upgrade, but every example now reads
72
+ `import vitops from '@getvitops/astro'` — matching the Vite plugin, which has always been
73
+ `vitops`. The `@getvitops/*` package scope and the internal `virtual:getvitops/*` module
74
+ ids are unchanged; neither is an import name.
75
+
76
+ **Added:** a generated config authoring reference — `vitops docs config`, `docs/config.md`
77
+ in the OKF bundle, and _Config reference_ on the docs site. It is walked from the published
78
+ JSON Schema by the same helper that renders the `design-system.json` reference, so it
79
+ cannot describe a field validation does not accept.
80
+
81
+ - b115993: Nav shells, a top-layer animation driver, and a pile of fixes to things that never worked
82
+
83
+ ### Breaking
84
+ - **Two custom elements renamed.** `<copy-button>` → `<wc-copy>`, `<multi-field>` →
85
+ `<wc-multifield>`. Update your markup. The Bricks element keys
86
+ (`vitops-copy-button`, `vitops-multi-field`) are **unchanged**, so elements already
87
+ placed on a Bricks page keep working; so are the `--multi-field-*` custom properties
88
+ and the `multi-field-*` events.
89
+ - **`<details>` now animates open and closed** where `prefers-reduced-motion` allows.
90
+ It was previously instant by deliberate choice — the transition used to deadlock the
91
+ disclosure shut. Re-verified on Chrome 149 with a real click; both `block-size` and
92
+ `content-visibility` must stay in the transition list, and dropping the latter
93
+ reproduces the deadlock.
94
+ - **Drawers and dialogs now animate on close.** Both drove their entry with
95
+ `animation:` on `[open]`, which by construction plays once — so the close was
96
+ instant. They now use the top-layer driver and animate both ways.
97
+ - **`.rhythm` gives every non-heading block heading-spacing before a heading.** The
98
+ pairs used to enumerate `p` (later `p, pre, blockquote, table, dl, ul, ol`), so a
99
+ heading after anything else got paragraph spacing. They are now defined by what a
100
+ heading _is_ — `h1`–`h6` plus the `font-display` / `font-title` / `font-heading` type
101
+ roles — and inverted to `:not(<heading>) + <heading>`. Expect slightly more space
102
+ above headings that follow a code block, table, figure-like `<div>` or component.
103
+ - **`popover.css` no longer pins `[popover]:popover-open { opacity: 1 }`.** It never
104
+ animated anything on its own, but it outranked `.transition` and made every
105
+ top-layer fade impossible. Removing it is what lets the driver work.
106
+ - **`nav.css`'s `.drawer-menu` timing** now follows `--animation-duration` /
107
+ `--custom-ease-out` instead of hand-set 0.4s / 0.7s.
108
+
109
+ ### Fixed
110
+ - **`.sitenav--bp-sm` dropdowns could never open.** Its desktop block had drifted onto
111
+ an older markup shape, selecting `.sitenav__disclosure > .sitenav__submenu` where the
112
+ submenu is the disclosure's _sibling_. The four "intentionally parallel" breakpoint
113
+ blocks are now one shared block behind a style query — `md`/`lg`/`xl` were
114
+ byte-identical and only `sm` had rotted, which is exactly the drift the arrangement
115
+ invited. 583 → 361 lines.
116
+ - **Hover dropdowns rendered off-screen.** A closed popover is not in the top layer but
117
+ keeps the UA's `position: fixed`, and `.dropdown--show-on-hover` reset only `inset` —
118
+ so the panel faithfully faded in ~3300px from its trigger. Now anchored to it, and it
119
+ follows on scroll. Affects `.split-link--show-on-hover` too.
120
+ - **Horizontal overflow on narrow screens.** `.centered > *` floors at
121
+ `min-inline-size: 0`; `body` gets `overflow-wrap: break-word`; `patterns/code.css`
122
+ gains the `pre` rules it never had (`max-inline-size: 100%`, `overflow-x: auto`) and
123
+ gives inline `<code>` `overflow-wrap: anywhere`. Note a scroll container does _not_
124
+ zero its min-content contribution in Chrome, which is what made a single `<pre>` hold
125
+ a 390px viewport open at 797px.
126
+ - **`.split-<a>-<b>` now publishes `--_flex-direction: row`.** Only `.flex-*` did, so
127
+ `class="split flex-col md-split-1-2"` left the variable reading `column` at every
128
+ width and a nested `.grouped` collapsed its borders on the wrong axis.
129
+ - **`.toc-layout` uses `minmax(0, 1fr)`** instead of a bare `1fr`, whose automatic
130
+ minimum is min-content.
131
+
132
+ ### Added
133
+ - **`patterns/navshell.css` + `<NavShell>` / `<NavShellToggle>`** — a navigation aside
134
+ beside content at width, collapsing to a toggle and drawer below it. **It nests**: a
135
+ site nav wrapping an on-this-page nav, each promoting at its own breakpoint, via a
136
+ style query on an inherited flag rather than four copied blocks. Its content column
137
+ is a container, so an inner shell measures the space it actually has. The toggle can
138
+ live outside the shell (a site header) with `toggle="external"`.
139
+ - **`patterns/navbar.css`** — `.navbar` extracted from `nav.css`, where it was only
140
+ half of that file's drawer⇄navbar pair, plus `--start` / `--center` / `--end`,
141
+ `__spacer` and `--sticky` (old `.navbar-sticky` aliased).
142
+ - **A top-layer animation driver.** `animation.css` gained the fourth driver alongside
143
+ `animate-view` / `animate-scroll` / `animate-trigger` / `transition`, and every effect
144
+ gained an `open-<fx>` state variant. Overlays now state _where they start_
145
+ (`--translate-x-from`) instead of owning a keyframe: `class="drawer drawer--right
146
+ open-fade-in"` composes slide and fade. Applied at zero specificity with identity
147
+ defaults, so a popover that sets no effect vars is unchanged.
148
+ - **One scrim.** `--scrim` / `--scrim-filter` tokens and `.no-scrim` in `popover.css`,
149
+ replacing 18 `::backdrop` blocks across seven partials. `.drawer--modeless` is aliased.
150
+ - **`<wc-marquee>`** — clones the content enough times to cover the track, so every gap
151
+ matches including the seam. The CSS-only `.marquee` is unchanged and still works
152
+ without it; the element only makes the spacing right. `--marquee-gap` added.
153
+ - **`--width-nav`** joins `--width-measure` / `-breakout` / `-spotlight` as the nav
154
+ column width.
155
+ - **`.skip-link`** in `patterns/anchor-link.css`, using a clip rather than the
156
+ `-100vw` idiom (which overflows in RTL and ignores the scrollbar gutter).
157
+ - **`.table-wrapper`** documented as the scroll wrapper for wide tables.
158
+
159
+ ### Notes
160
+ - `patterns/nav.css` is marked legacy. Its header promised a Lit nav component that was
161
+ never written and is not planned — the house pattern is native. Use `navbar`,
162
+ `sitenav` or `navshell`; removal is a later change.
163
+ - `scroll-target.css`'s `.is-current` no longer claims to be a JS scroll-spy fallback.
164
+ There is no such code; `:target-current` is the only working highlight, which today
165
+ means Chrome.
166
+ - `elements.js` gains one element (`wc-marquee`), so the shared bundle is slightly
167
+ larger for every consumer.
168
+ - A registered custom property's `initial-value` must be computationally independent —
169
+ `16rem` is not, so `@property` silently drops the whole rule. `navshell` uses an
170
+ inline fallback instead.
171
+
172
+ ### Patch Changes
173
+
174
+ - Updated dependencies [6e68ace]
175
+ - @getvitops/utils@3.0.0
176
+
177
+ ## 2.1.0
178
+
179
+ ### Minor Changes
180
+
181
+ - 20e518e: Add `vitops indexing` — tell search engines about a deploy, instead of opening Search Console by hand.
182
+
183
+ Configure it in your site config under `seo.indexing`, then run it after deploying:
184
+
185
+ ```jsonc
186
+ "seo": {
187
+ "indexing": {
188
+ "indexNow": { "key": "…" },
189
+ "searchConsole": { "siteUrl": "sc-domain:acme.ca" },
190
+ "priorityUrls": ["https://acme.ca/", "https://acme.ca/services"]
191
+ }
192
+ }
193
+ ```
194
+
195
+ ```
196
+ vitops indexing --dry # print the plan, make no requests
197
+ vitops indexing # submit
198
+ vitops indexing --check # a day or two later: did Google actually index them?
199
+ ```
200
+
201
+ It reads your sitemap, diffs it against the previous run, and submits only what changed —
202
+ pinging **IndexNow** and re-submitting your sitemap through the **Search Console API**.
203
+ `--check` inspects `priorityUrls` and exits non-zero on a page Google hasn't indexed, so a
204
+ scheduled CI job can catch a page that quietly fell out of the index.
205
+
206
+ **Be clear-eyed about what this can do.** Google exposes no API that requests indexing — the
207
+ button in Search Console isn't available anywhere, URL Inspection is read-only, and the sitemap
208
+ ping endpoint was removed in 2023. IndexNow reaches Bing, Yandex, Naver, Seznam and Yep; Google
209
+ doesn't participate. So this automates every sanctioned step and then verifies the outcome; it
210
+ does not make Google re-index on demand, and nothing can. Google's Indexing API is deliberately
211
+ not wired: it's scoped to job postings and livestreams, and using it for ordinary pages violates
212
+ its terms.
213
+
214
+ Also new, and worth wiring at the same time — **`gitLastmod()` for real sitemap dates**:
215
+
216
+ ```js
217
+ import vitops, { gitLastmod } from '@getvitops/astro';
218
+ vitops({ sitemap: { serialize: await gitLastmod() } });
219
+ ```
220
+
221
+ `@astrojs/sitemap` emits no `<lastmod>` by default, which means a crawler is told your pages
222
+ exist but never that one changed — and it's what lets `vitops indexing` submit a handful of URLs instead
223
+ of all of them. `gitLastmod()` derives each date from the source file's last commit, and leaves a
224
+ page alone rather than guessing (dynamic routes, ambiguous slugs, shallow clones): an inaccurate
225
+ `lastmod` is worse than none, because Google stops trusting the field site-wide.
226
+
227
+ Requires `fetch-depth: 0` in CI — the default shallow clone has no history to read, and it warns
228
+ when that's the case.
229
+
230
+ **Credentials.** IndexNow's key is public by design (it's served at `/<key>.txt` as the ownership
231
+ proof), so it lives in your config, and the Astro integration writes the file into `public/` for
232
+ you — `vitops indexing --new-key` generates one, `--write-key <dir>` writes it for non-Astro stacks.
233
+ Search Console needs a service account in `VITOPS_GSC_SERVICE_ACCOUNT` or
234
+ `GOOGLE_APPLICATION_CREDENTIALS`, added as an owner of the property; it's never read from the
235
+ config file.
236
+
237
+ Persist `.vitops/` between runs (a CI cache) — that's the changed-URL state, and without it every
238
+ run submits everything.
239
+
240
+ An environment whose `robots` policy says `noindex` is refused outright, so pointing this at
241
+ staging can't publish it to a search engine.
242
+
243
+ ### Patch Changes
244
+
245
+ - Updated dependencies [20e518e]
246
+ - Updated dependencies [9bf975a]
247
+ - @getvitops/utils@2.1.0
248
+
3
249
  ## 2.0.0
4
250
 
5
251
  ### Major Changes
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Vitops — Copy Button element (Bricks Builder).
4
4
  *
5
- * Renders the <copy-button> Lit component (src/web-components/WCCopy.ts): a
5
+ * Renders the <wc-copy> Lit component (src/web-components/WCCopy.ts): a
6
6
  * copy-to-clipboard button, hidden until connected (Clipboard API gate). The `value`
7
7
  * is copied; `label` is the button text (falls back to slotted text).
8
8
  *
@@ -21,7 +21,7 @@ class Vitops_Element_Copy extends \Bricks\Element {
21
21
  public $category = 'vitops';
22
22
  public $name = 'vitops-copy-button';
23
23
  public $icon = 'ti-clipboard';
24
- public $css_selector = 'copy-button';
24
+ public $css_selector = 'wc-copy';
25
25
 
26
26
  public function get_label() {
27
27
  return esc_html__( 'Copy Button', 'bricks' );
@@ -60,6 +60,6 @@ class Vitops_Element_Copy extends \Bricks\Element {
60
60
  $attr_str = implode( ' ', $attrs );
61
61
  $fallback = ! empty( $s['label'] ) ? esc_html( $s['label'] ) : esc_html__( 'Copy to clipboard', 'bricks' );
62
62
 
63
- echo "<copy-button {$attr_str} {$this->render_attributes( '_root' )}>{$fallback}</copy-button>";
63
+ echo "<wc-copy {$attr_str} {$this->render_attributes( '_root' )}>{$fallback}</wc-copy>";
64
64
  }
65
65
  }
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Vitops — Multi Field element (Bricks Builder).
4
4
  *
5
- * Renders the <multi-field> Lit component (src/web-components/WCMultiField.ts): a
5
+ * Renders the <wc-multifield> Lit component (src/web-components/WCMultiField.ts): a
6
6
  * form-associated repeatable input group (add / remove entries, min / max). Default
7
7
  * entries are supplied as slotted <input value="…"> children, which the component reads
8
8
  * on connect.
@@ -22,7 +22,7 @@ class Vitops_Element_Multi_Field extends \Bricks\Element {
22
22
  public $category = 'vitops';
23
23
  public $name = 'vitops-multi-field';
24
24
  public $icon = 'ti-list';
25
- public $css_selector = 'multi-field';
25
+ public $css_selector = 'wc-multifield';
26
26
 
27
27
  public function get_label() {
28
28
  return esc_html__( 'Multi Field', 'bricks' );
@@ -143,6 +143,6 @@ class Vitops_Element_Multi_Field extends \Bricks\Element {
143
143
  }
144
144
  }
145
145
 
146
- echo "<multi-field {$attr_str} {$this->render_attributes( '_root' )}>{$inputs}</multi-field>";
146
+ echo "<wc-multifield {$attr_str} {$this->render_attributes( '_root' )}>{$inputs}</wc-multifield>";
147
147
  }
148
148
  }
@@ -529,6 +529,85 @@
529
529
  }
530
530
  }
531
531
 
532
+ /* ===========================================
533
+ * Tier 1c: Top layer (popover / dialog open-close)
534
+ * The `open-<fx>` state variants (generated, like hover-/focus-/active-) already
535
+ * supply the VALUES. What a top-layer element additionally needs is mechanism,
536
+ * and that is the same for every effect — so it lives here, once:
537
+ *
538
+ * <div popover class="drawer drawer--start transition open-slide-right">
539
+ *
540
+ * Before this existed there was no driver for the top layer, so every overlay
541
+ * pattern hand-rolled one: drawer.css drove entry with four @keyframes and
542
+ * `animation:`, which only plays on OPEN (its close was instant), while
543
+ * sitenav.css used a transition and animated both ways. Same job, two
544
+ * mechanisms, two behaviours.
545
+ * =========================================== */
546
+
547
+ /* Applied to the top layer BY DEFAULT, and at `:where()` zero specificity.
548
+ *
549
+ * Not an opt-in class, unlike the other three drivers, for two reasons. It is
550
+ * free: every endpoint below defaults to identity (opacity 1, translate 0,
551
+ * scale 1), so a popover that sets no effect vars transitions between two
552
+ * identical states and looks exactly as it did before. And it preserves the DX
553
+ * the keyframes had — `.drawer--right` animated without asking the author for a
554
+ * driver class, and it still does; it just states its start as
555
+ * `--translate-x-from: 100%` instead of owning a keyframe.
556
+ *
557
+ * Zero specificity means any pattern that wants different motion simply
558
+ * declares it. */
559
+ :where([popover], dialog) {
560
+ opacity: var(--t-opacity, var(--opacity-from, 1));
561
+ translate: var(--t-translate-x, var(--translate-x-from, 0))
562
+ var(--t-translate-y, var(--translate-y-from, 0));
563
+ scale: var(--t-scale-x, var(--scale-x-from, 1)) var(--t-scale-y, var(--scale-y-from, 1));
564
+ rotate: var(--t-rotate, var(--rotate-from, 0deg));
565
+ clip-path: var(--t-clip, var(--clip-from, none));
566
+
567
+ /* `display` and `overlay` are discrete, and BOTH must transition or the
568
+ element is yanked out of the top layer the instant it closes — you get an
569
+ entry animation and no exit, which is exactly the asymmetry drawer.css had.
570
+ `allow-discrete` is what keeps it rendered for the duration. */
571
+ transition-property: opacity, translate, scale, rotate, clip-path, display, overlay;
572
+ transition-duration: var(--animation-duration);
573
+ transition-timing-function: var(--custom-ease-out);
574
+ transition-behavior: allow-discrete;
575
+ }
576
+
577
+ /* The open flip. Mirrors what the generated `open-<fx>` variants do, so the
578
+ common case needs no effect class at all; an explicit `open-<fx>` still wins
579
+ on specificity and can pick a different effect. */
580
+ :where([popover]:popover-open, dialog[open]) {
581
+ --t-opacity: var(--opacity-to, 1);
582
+ --t-translate-x: var(--translate-x-to, 0);
583
+ --t-translate-y: var(--translate-y-to, 0);
584
+ --t-scale-x: var(--scale-x-to, 1);
585
+ --t-scale-y: var(--scale-y-to, 1);
586
+ --t-rotate: var(--rotate-to, 0deg);
587
+ --t-clip: var(--clip-to, none);
588
+ }
589
+
590
+ /* The entry half. A closed popover is `display: none`, so it has no
591
+ before-change style and the open transition would simply not run —
592
+ @starting-style supplies one.
593
+ *
594
+ * It declares the PROPERTIES rather than resetting the `--t-*` custom
595
+ * properties, which sidesteps a specificity trap: the generated
596
+ * `.open-<fx>:popover-open` rules are (0,2,0), set `--t-*`, and are imported
597
+ * after this file, so a custom-property reset here would have to outrank them.
598
+ * Setting `opacity`/`translate`/… directly at (0,2,0) instead competes only with
599
+ * the (0,0,0) base rule above, which it beats regardless of what any effect
600
+ * class does. */
601
+ @starting-style {
602
+ :is([popover], dialog):is(:popover-open, [open]) {
603
+ opacity: var(--opacity-from, 1);
604
+ translate: var(--translate-x-from, 0) var(--translate-y-from, 0);
605
+ scale: var(--scale-x-from, 1) var(--scale-y-from, 1);
606
+ rotate: var(--rotate-from, 0deg);
607
+ clip-path: var(--clip-from, none);
608
+ }
609
+ }
610
+
532
611
  /* Respect reduced-motion: drop keyframes, transitions, and continuous floats so
533
612
  elements settle at their natural resolved state. Plain selectors + !important
534
613
  so this reliably overrides the driver/float rules (a legitimate a11y override). */
@@ -543,6 +622,17 @@
543
622
  .transition {
544
623
  transition: none !important;
545
624
  }
625
+ /* The top-layer driver is applied by default rather than by class, so it needs
626
+ naming here explicitly — `.transition` above does not cover it. `display`
627
+ and `overlay` must still change, they just must not be animated, and
628
+ `transition: none` is exactly that. */
629
+ [popover],
630
+ dialog {
631
+ transition: none !important;
632
+ }
633
+ :where([popover], dialog)::backdrop {
634
+ transition: none !important;
635
+ }
546
636
  }
547
637
 
548
638
  /* ===========================================
@@ -32,6 +32,16 @@
32
32
  around them. */
33
33
  body {
34
34
  margin: 0;
35
+
36
+ /* Long unbreakable strings are the single most common source of horizontal
37
+ page scroll, and prose is full of them — URLs, file paths, CSS selectors.
38
+ `break-word` rather than `anywhere` on purpose: it only takes effect when a
39
+ word would otherwise overflow, and it does NOT contribute to min-content
40
+ sizing, so no existing layout's intrinsic measurements move. (`anywhere`
41
+ does affect min-content, which would silently resize every intrinsically
42
+ sized box in a consumer's site.) Inline <code> gets the stronger `anywhere`
43
+ in patterns/code.css, where a break mid-token is the correct trade. */
44
+ overflow-wrap: break-word;
35
45
  }
36
46
 
37
47
  @media (prefers-reduced-motion: no-preference) {
@@ -33,7 +33,12 @@
33
33
  @import './patterns/tooltip.css';
34
34
  @import './patterns/dropdown.css';
35
35
  @import './patterns/split-link.css';
36
+ /* navbar before sitenav: sitenav's wide state composes it. */
37
+ @import './patterns/navbar.css';
36
38
  @import './patterns/sitenav.css';
39
+ /* After sitenav (the nav family stays contiguous) and after drawer.css, whose
40
+ panel surface navshell's desktop block has to undo. */
41
+ @import './patterns/navshell.css';
37
42
  @import './patterns/notification.css';
38
43
  @import './patterns/lightbox.css';
39
44
  @import './patterns/overlay.css';