@getvitops/generator 8.2.0-alpha.0 → 8.2.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 +467 -25
- package/assets/css/patterns/banner.css +20 -0
- package/assets/css/patterns/copy.css +5 -0
- package/assets/css/patterns/forms.css +5 -0
- package/assets/css/patterns/marquee.css +16 -0
- package/assets/css/patterns/splitter.css +11 -0
- package/assets/css/patterns/table.css +10 -0
- package/assets/js/consent.js +1 -1
- package/assets/js/elements.js +4 -4
- package/assets/js/polyfills/adoptedStyleSheets.js +1 -1
- package/assets/js/polyfills/css-anchor-positioning-fn.js +2 -2
- package/assets/js/polyfills/popover.js +2 -2
- package/config.schema.json +131 -110
- package/dist/config-BfcWKH-5.mjs +1743 -0
- package/dist/config-BfcWKH-5.mjs.map +1 -0
- package/dist/{config-DvbYx3Gi.mjs → config-Bhg3Xv3D.mjs} +10 -207
- package/dist/config-Bhg3Xv3D.mjs.map +1 -0
- package/dist/config.d.mts +81 -194
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +1 -1
- package/dist/{icons-sprite-Bf_KmKeX.d.mts → icons-sprite-CQ5Zj4go.d.mts} +4 -6
- package/dist/icons-sprite-CQ5Zj4go.d.mts.map +1 -0
- package/dist/icons-sprite.d.mts +1 -1
- package/dist/icons-sprite.mjs +1 -2
- package/dist/index.d.mts +19 -34
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +53 -38
- package/dist/index.mjs.map +1 -1
- package/dist/{schema-Dmq1J7S3.d.mts → schema-BV2n2phj.d.mts} +2 -3
- package/dist/{schema-Dmq1J7S3.d.mts.map → schema-BV2n2phj.d.mts.map} +1 -1
- package/dist/{tokens-8SXMDAd1.d.mts → tokens-BzBbTeTi.d.mts} +2 -6
- package/dist/tokens-BzBbTeTi.d.mts.map +1 -0
- package/dist/{tokens-zi594IZ3.mjs → tokens-C1RJwFeG.mjs} +3 -5
- package/dist/{tokens-zi594IZ3.mjs.map → tokens-C1RJwFeG.mjs.map} +1 -1
- package/dist/tokens.d.mts +1 -1
- package/dist/tokens.mjs +1 -1
- package/package.json +6 -5
- package/dist/config-DvbYx3Gi.mjs.map +0 -1
- package/dist/icons-sprite-Bf_KmKeX.d.mts.map +0 -1
- package/dist/tokens-8SXMDAd1.d.mts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,455 @@
|
|
|
1
1
|
# @getvitops/generator
|
|
2
2
|
|
|
3
|
+
## 8.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 65a13cc: **New: `site.experiments`** — runtime, per-visitor A/B content experiments for a page that
|
|
8
|
+
must stay at one URL (unlike `site.abTesting`'s existing build-time variant merge, which is
|
|
9
|
+
for a full design-system/CSS-level test via two manual deploys — `site.experiments` is the
|
|
10
|
+
opposite granularity: one page, one cookie, content branched in the page itself). Keyed by
|
|
11
|
+
an open experiment name, so more than one can run at once, each with its own consent
|
|
12
|
+
category, cookie name/lifetime, split ratio and pair of variant names.
|
|
13
|
+
|
|
14
|
+
**New `vitopsAbTesting()` middleware** (`@getvitops/astro/middleware`) — the first Astro
|
|
15
|
+
middleware this toolchain ships. Wire it into your own `src/middleware.ts`:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { vitopsAbTesting } from '@getvitops/astro/middleware';
|
|
19
|
+
import config from '../site.json' with { type: 'json' };
|
|
20
|
+
|
|
21
|
+
export const onRequest = vitopsAbTesting({
|
|
22
|
+
experiments: config.site.experiments ?? {},
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
It assigns and cookie-pins a visitor to one of an experiment's two variants on first visit,
|
|
27
|
+
by weighted random (`splitRatio`) — and never touches an already-set cookie, so a recognized
|
|
28
|
+
query parameter named after the cookie itself (`?_ab_<key>=<variant>`) reliably forces that
|
|
29
|
+
variant, even overriding an existing assignment. Combined with `splitRatio: 0`, this is a
|
|
30
|
+
blue/green-style "100% on control, but a shared link reveals the other variant" mode with no
|
|
31
|
+
special-cased logic.
|
|
32
|
+
|
|
33
|
+
A page reads its own experiment's cookie (`Astro.cookies.get(cookieName)?.value`) and
|
|
34
|
+
renders whichever of its two hand-written blocks matches — there's no config-schema
|
|
35
|
+
representation of "the variant content," which is what keeps this lightweight regardless of
|
|
36
|
+
how many experiments are configured.
|
|
37
|
+
|
|
38
|
+
**`<Analytics />` gained a reporting hook**: each configured provider's bootstrap now
|
|
39
|
+
attaches an enabled experiment's assignment to the real pageview via that provider's own
|
|
40
|
+
mechanism — Plausible via `plausible.init({customProperties})`, Matomo via
|
|
41
|
+
`setCustomDimension` (needs a new `site.analytics.matomo.abVariantDimensions` entry — the
|
|
42
|
+
dimension must exist in your Matomo instance first), GA4 via a runtime-merged
|
|
43
|
+
`gtag('config', …)` call. A no-op when `site.experiments` is unset — zero behavior change
|
|
44
|
+
for every existing site.
|
|
45
|
+
|
|
46
|
+
Also new: `experimentDimensionWarnings(site)` (`@getvitops/generator`) — a build-time warning
|
|
47
|
+
(not a hard error) when an enabled experiment's site also configures Matomo but has no
|
|
48
|
+
matching `abVariantDimensions` entry.
|
|
49
|
+
|
|
50
|
+
**Not included in this pass, deliberately**: a mechanism for a fully prerendered
|
|
51
|
+
(`output: 'static'`) page — this needs a genuinely separate Cloudflare Worker Route + two
|
|
52
|
+
pre-built static variants, real but bounded follow-up work. Everything above applies to an
|
|
53
|
+
on-demand-rendered page (the current EmDash templates' default), which needs zero new
|
|
54
|
+
deploy infrastructure.
|
|
55
|
+
|
|
56
|
+
- 67e2088: **Config validation now rejects unknown keys instead of silently stripping them.** Every
|
|
57
|
+
object in `ConfigSchema` (the `Config` type — `organization`/`site` and everything nested under
|
|
58
|
+
them) switched from `z.object` to `z.strictObject`, matching what the published JSON Schema has
|
|
59
|
+
always asserted (`additionalProperties: false` at ~95 points) but the runtime validator never
|
|
60
|
+
actually enforced. A typo'd field (`site.ntoifications`, an add-on key on the wrong nesting
|
|
61
|
+
level, …) now fails `validateConfig()`/`vitops validate` with an `unrecognized_keys` error naming
|
|
62
|
+
the bad key, instead of validating successfully and doing nothing. `site.icons` stays
|
|
63
|
+
deliberately loose (`z.looseObject`, unchanged) — its per-set keys are open-ended by design.
|
|
64
|
+
|
|
65
|
+
**Breaking for any config carrying a field that isn't in the schema** — this is the point of the
|
|
66
|
+
change, but if a real config was relying on the old silent-strip behavior (an extra key kept
|
|
67
|
+
around for another tool to read), move it under a block the schema already permits `z.unknown()`
|
|
68
|
+
on (`site.cloudflare`, `site.tags`, `site.postTypes`) or open an issue.
|
|
69
|
+
|
|
70
|
+
Also:
|
|
71
|
+
|
|
72
|
+
- Added `configVersion` (optional, currently `1`) to `Config` — the document's own SHAPE
|
|
73
|
+
version, distinct from this package's release version (which is CSS-driven and bumps on every
|
|
74
|
+
design-system change regardless of `Config`'s shape). `defaultSiteConfig()` stamps it.
|
|
75
|
+
Exported as `CONFIG_VERSION`.
|
|
76
|
+
- Re-exported several `Config`-related types/values that were defined but missing from the
|
|
77
|
+
package root: `SiteReviews`, `SiteReviewLocation`, `SiteSearchConsoleSetup`,
|
|
78
|
+
`CrmProviderConfig`, `CRM_PROVIDERS`, `BIGIN_DATA_CENTERS`.
|
|
79
|
+
- Added a `@getvitops/generator/config` subpath export — `ConfigSchema`, `Config`,
|
|
80
|
+
`validateConfig`, `resolveConfig`, `isConfig`, `resolveInput` and friends, on their own, with no
|
|
81
|
+
`node:fs`/`node:crypto`/lightningcss in the import graph (verified: `publint` clean, and the
|
|
82
|
+
built chunk's own imports are `@zod/mini` + `@getvitops/utils`'s Worker-safe `ads`/`tracking`/
|
|
83
|
+
`color` subpaths only). This is what makes `Config` bundleable in a Cloudflare Worker — the
|
|
84
|
+
EmDash plugin's own admin route being the motivating case — without pulling in code that only
|
|
85
|
+
ever runs at build time.
|
|
86
|
+
|
|
87
|
+
- 89265af: **New package: `@getvitops/config`** — the site-config provider blocks (`site.crm`, `site.ads`,
|
|
88
|
+
`site.reviews`, `site.seo.indexing`, `site.searchConsole`, `site.notifications`, `site.tracking`,
|
|
89
|
+
`site.security`, `site.deployment`) as zod schemas and inferred types, with the closed provider lists
|
|
90
|
+
(`AD_PROVIDERS`, `CRM_PROVIDERS`, `BIGIN_DATA_CENTERS`) on a zod-free `./providers` entry. It joins the
|
|
91
|
+
`fixed` group.
|
|
92
|
+
|
|
93
|
+
Why: `@getvitops/utils` holds the provider code but could not import its configuration types — the schema
|
|
94
|
+
lived in `@getvitops/generator`, a layer above — so it kept a hand-copied mirror beside each module,
|
|
95
|
+
an adapter converting into it, and a test holding the copy to the schema. The mirrors are gone.
|
|
96
|
+
|
|
97
|
+
- **`@getvitops/generator`**: `AD_PROVIDERS`, `CRM_PROVIDERS`, `BIGIN_DATA_CENTERS`, `AdProvider` and
|
|
98
|
+
`CrmProviderConfig` are re-exported unchanged. No import path changes.
|
|
99
|
+
- **`@getvitops/utils`**: the config types in `./crm`, `./ads`, `./indexing`, `./onboarding`, `./reviews`,
|
|
100
|
+
`./tracking` and `./notify` (`CrmConfig`, `HubSpotConfig`, `BiginConfig`, `JobberConfig`,
|
|
101
|
+
`IndexNowConfig`, `SearchConsoleConfig`, `ReviewsConfig`, `ReviewLocationConfig`, `TrackingConfig`,
|
|
102
|
+
`NotificationsConfig`, `EmailChannelConfig`, `DomainSetup`, `AdPropertySetup`) are now inferred from the
|
|
103
|
+
schema. Same names, same fields, but **optional fields now follow zod's inference**, so a hand-written
|
|
104
|
+
object literal typed against them may need `| undefined` handled. `AdPropertySetup` gains the fields the
|
|
105
|
+
schema always had (`conversionActionId`, `reporting`).
|
|
106
|
+
- **`@getvitops/astro`**: `GetvitopsAdProperty` and `GetvitopsTrackingOptions` are aliases of the schema
|
|
107
|
+
types. `toCrmConfig()` and `toGoogleAdsConfig()` are **deprecated** — they returned their argument's
|
|
108
|
+
field unchanged; pass `config.site.crm` / `config.site.ads?.google` directly.
|
|
109
|
+
- **`@getvitops/cli`**: `vitops ads` now passes `conversionActionId` and `reporting` through to the planner;
|
|
110
|
+
it previously copied a hand-picked subset of the block and dropped them.
|
|
111
|
+
|
|
112
|
+
Formats reached: none — no CSS change.
|
|
113
|
+
|
|
114
|
+
- 0574388: Add `site.crm` — push form submissions into HubSpot, Zoho Bigin, or Jobber.
|
|
115
|
+
|
|
116
|
+
Client sites need form submissions to land in the client's CRM. The options available before this
|
|
117
|
+
— a HubSpot/Bigin/Jobber iframe or JS embed — fail on three counts: the markup can't be
|
|
118
|
+
white-labelled against this design system, nothing can operate on the submission before it leaves
|
|
119
|
+
the page, and the submission never touched `createConversionRoute()`, so `_ac` ad-click attribution
|
|
120
|
+
was lost and no conversion notification was planned.
|
|
121
|
+
|
|
122
|
+
This adds one server-side path for all three CRMs, hung off the conversion route that already
|
|
123
|
+
exists. `createConversionRoute()` gets a new `crm` option (`{ config, credentials, fetch?,
|
|
124
|
+
timeoutMs? }`); credentials arrive as a function of `locals`, exactly like the existing `binding`
|
|
125
|
+
option, and are never config. A failed CRM write never fails the request and is folded into the
|
|
126
|
+
notification email's new "Delivery" section, so a broken CRM token is never silent.
|
|
127
|
+
|
|
128
|
+
New `@getvitops/utils/crm` subpath: `deliverToCrm()`, `mapSubmission()`, `deriveLastName()` (Bigin
|
|
129
|
+
requires a last name; this derives one from an explicit field, a full name, or an email address,
|
|
130
|
+
refusing rather than fabricating one), `submitToHubSpot()`, `submitToBigin()`, `submitToJobber()`.
|
|
131
|
+
`identifySubmitter()` was promoted out of `notify`'s private name-guessing cascade into
|
|
132
|
+
`@getvitops/utils/tracking`, since the CRM mapper needed the whole identity (first/last name,
|
|
133
|
+
email, phone, company), not just a display name.
|
|
134
|
+
|
|
135
|
+
Jobber is GraphQL, not REST, and answers `200 OK` for failures — a rejected `clientCreate` returns
|
|
136
|
+
`userErrors[]`, a malformed request returns top-level `errors[]`. `submitToJobber()` classifies on
|
|
137
|
+
the response body, not just the status. Its refresh tokens also rotate by default: **Refresh Token
|
|
138
|
+
Rotation must be turned off** in the app's Developer Center settings, since a Worker reading a
|
|
139
|
+
static `VITOPS_JOBBER_REFRESH_TOKEN` cannot persist a rotated one — a rotated token is detected at
|
|
140
|
+
exchange time and reported with the fix, rather than surfacing an hour later as an opaque 401.
|
|
141
|
+
Attribution and unmapped extra fields render into a best-effort note by default (works on every
|
|
142
|
+
Jobber plan); a resubmitting visitor creates a second Jobber client unless `crm.jobber.dedupe: true`
|
|
143
|
+
is set once its lookup query has been verified against a live account (defaults `false` pending
|
|
144
|
+
that verification — see `jobber.ts`'s docblock).
|
|
145
|
+
|
|
146
|
+
New `site.crm` config block (`provider`, `hubspot`/`bigin`/`jobber`, `fieldMap`, `trackingFields`)
|
|
147
|
+
and two CLI commands: `vitops crm check` (fetches the live HubSpot property list or Bigin field
|
|
148
|
+
list and reports every mapped name that doesn't exist there — HubSpot rejects an unknown field
|
|
149
|
+
outright, Bigin silently drops it; Jobber has nothing live to check yet, since its mapped values
|
|
150
|
+
currently render as note text) and `vitops crm setup` (creates missing HubSpot properties; Bigin
|
|
151
|
+
and Jobber have no such API, so both always print a manual reminder).
|
|
152
|
+
|
|
153
|
+
The generated privacy policy now names HubSpot, Zoho Bigin, or Jobber when `site.crm` configures
|
|
154
|
+
one. Bigin gets the same cross-border-transfer derivation Matomo Cloud already gets: a
|
|
155
|
+
`dataCenter: 'ca'` asserts no transfer outside Canada, any other data centre does. Jobber's
|
|
156
|
+
operator (Octopusapp Inc. d/b/a Jobber) is Canadian, but its own privacy policy names the United
|
|
157
|
+
States as a processing location too, so the policy always asserts a cross-border transfer for
|
|
158
|
+
Jobber regardless of config — a Canadian operator is not read as Canadian-only storage.
|
|
159
|
+
|
|
160
|
+
Credentials: `VITOPS_HUBSPOT_ACCESS_TOKEN` for HubSpot; `VITOPS_ZOHO_CLIENT_ID` /
|
|
161
|
+
`VITOPS_ZOHO_CLIENT_SECRET` / `VITOPS_ZOHO_REFRESH_TOKEN` for Bigin; `VITOPS_JOBBER_CLIENT_ID` /
|
|
162
|
+
`VITOPS_JOBBER_CLIENT_SECRET` / `VITOPS_JOBBER_REFRESH_TOKEN` for Jobber. Never config.
|
|
163
|
+
|
|
164
|
+
No consumer-facing change in `@getvitops/astro` beyond the new `crm` route option and a build
|
|
165
|
+
warning when `site.crm` is configured with no route answering `TRACKING_ENDPOINT` — CSS/Bricks/
|
|
166
|
+
tailwind outputs are unaffected.
|
|
167
|
+
|
|
168
|
+
- c3c1bcb: **New: the design-system editor, mounted inside the EmDash admin.** `vitopsEmdash({
|
|
169
|
+
designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
|
|
170
|
+
'./site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
|
|
171
|
+
`<wc-theme-editor>` — the same live token editor `@getvitops/vite`'s dev server has always
|
|
172
|
+
offered, now usable by the client directly, no dev server required. An edit is visible
|
|
173
|
+
site-wide immediately (a runtime `:root` override, served via a new plugin API route and
|
|
174
|
+
`page:fragments`) and — once `site.deployment.repo`/`branch` and a `VITOPS_GITHUB_TOKEN`
|
|
175
|
+
secret are set (see the scaffold's `SETUP.html`) — auto-commits to your `dev` branch, so it
|
|
176
|
+
reaches the git-tracked `site.json` through your repo's normal review path rather than
|
|
177
|
+
becoming a second, unreviewed source of truth. Until that's configured, the live half still
|
|
178
|
+
works; only the auto-commit half is skipped (and says so in the admin page). The save route
|
|
179
|
+
runs the exact same accessibility (APCA contrast) gate `generate()` enforces at build time,
|
|
180
|
+
synchronously, before persisting — nothing non-compliant ever goes live even momentarily.
|
|
181
|
+
|
|
182
|
+
Requires `@astrojs/react` + `react` + `react-dom` (already scaffold dependencies, for
|
|
183
|
+
EmDash's own admin) and `@getvitops/core` (added as a new scaffold dependency this release).
|
|
184
|
+
Declares two new plugin capabilities when enabled: `network:request` (for the GitHub
|
|
185
|
+
auto-commit) and `hooks.page-fragments:register` (for the live override) — reviewed once at
|
|
186
|
+
plugin-install time.
|
|
187
|
+
|
|
188
|
+
**Breaking for `@getvitops/emdash`**: `vitopsEmdash()`'s `hooks.page-fragments:register`
|
|
189
|
+
capability is now also granted whenever `designSystemEditor` is set, independent of the
|
|
190
|
+
existing `scripts: 'fragments'` option (which governs something unrelated — web-component
|
|
191
|
+
script delivery). No change if you don't use `designSystemEditor`.
|
|
192
|
+
|
|
193
|
+
Also new, supporting the above:
|
|
194
|
+
|
|
195
|
+
- `@getvitops/generator`: `validateColorContract()` and `buildReverseIndex()` (both
|
|
196
|
+
`/tokens` subpath, Worker-safe) — the monotonicity/contrast gate and the
|
|
197
|
+
`design-manifest.json` reverse-index, each extracted from `generate()`'s `build()` into
|
|
198
|
+
standalone, non-throwing functions. `patchToOverrideCss()` turns a design-system patch
|
|
199
|
+
into the `:root` override `<style>` block. `designSystemPath`/`getAt`/`setAt`/
|
|
200
|
+
`mergePatch`/`formatConfig` moved from `@getvitops/vite` into `@getvitops/generator/config`
|
|
201
|
+
(re-exported from `@getvitops/vite` unchanged) — one "patch a config file's design-system
|
|
202
|
+
subtree" implementation shared by the dev endpoint and the new plugin route. New
|
|
203
|
+
`site.deployment.repo`/`branch` config fields.
|
|
204
|
+
- `@getvitops/utils`: new `/git` subpath — `commitFile()`, a Worker-safe (fetch-only)
|
|
205
|
+
wrapper over GitHub's Contents API.
|
|
206
|
+
- `@getvitops/core`: `<wc-theme-editor>` gains a `save-endpoint` attribute (default
|
|
207
|
+
unchanged — `/__vitops/design-system`) so the same element can target the new plugin
|
|
208
|
+
route instead of the dev-only Vite endpoint.
|
|
209
|
+
|
|
210
|
+
- 0574388: Add `vitops email setup|check` — connect `site.notifications.email.from` to whether its domain is
|
|
211
|
+
actually onboarded onto an outbound email provider.
|
|
212
|
+
|
|
213
|
+
Before this, nothing checked that. `EMAIL.send()` throws `E_SENDER_NOT_VERIFIED` at runtime with no
|
|
214
|
+
build or deploy-time warning — the failure mode is silent and production-only.
|
|
215
|
+
|
|
216
|
+
`vitops email setup [domain]` onboards a sending domain (currently Cloudflare Email Sending only)
|
|
217
|
+
and polls DNS until its SPF/DKIM/DMARC records resolve or a timeout is reached; `vitops email
|
|
218
|
+
check` is the non-blocking deploy guard both `@getvitops/create` templates' `deploy`/`deploy:dev`
|
|
219
|
+
scripts now run first (`--strict` makes it a real gate; opt out per-site with
|
|
220
|
+
`site.notifications.email.verifySender: false`, or globally with `--skip` /
|
|
221
|
+
`VITOPS_SKIP_EMAIL_CHECK`).
|
|
222
|
+
|
|
223
|
+
Two things worth knowing before running it:
|
|
224
|
+
|
|
225
|
+
- **The onboarded entity is an exact name, and it is usually a subdomain**
|
|
226
|
+
(`send.yourdomain.com`), never the apex your `from` address names. When the exact domain isn't
|
|
227
|
+
onboarded but a sibling in the same zone already is, the command reports that as the fix — send
|
|
228
|
+
from the onboarded subdomain — rather than silently trying to onboard the apex, which rewrites
|
|
229
|
+
`_dmarc.<domain>` and can tighten DMARC policy on every other mail your domain sends.
|
|
230
|
+
- Candidates come from `site.notifications.email.from` (or its derived `noreply@<domains
|
|
231
|
+
.canonical>` default) first; a best-effort scan of `--src` only supplements it, and never guesses
|
|
232
|
+
at a computed `from` value.
|
|
233
|
+
|
|
234
|
+
New `@getvitops/utils/email` subpath and `site.notifications.email.verifySender` config field.
|
|
235
|
+
Needs `CLOUDFLARE_API_TOKEN` with an Email Sending scope in addition to the `Zone:Read` other
|
|
236
|
+
commands already use.
|
|
237
|
+
|
|
238
|
+
- 1a8231d: **EmDash blocks are now a tier of the component manifest, and there are four more of them.**
|
|
239
|
+
|
|
240
|
+
`@getvitops/emdash` adds `vitops.gallery`, `vitops.cards`, `vitops.accordion` and `vitops.marquee`
|
|
241
|
+
(each a thin adapter over the matching `@getvitops/astro` component), and `vitops.carousel` now edits
|
|
242
|
+
its slides as a **Slides repeater** instead of two newline-separated fields. Blocks saved before this
|
|
243
|
+
keep rendering: with no `slides`, the carousel still reads `images`/`alts`, and both legacy fields stay
|
|
244
|
+
in the editor, labelled as legacy. The editor's field type now admits `repeater` and `media_picker`.
|
|
245
|
+
|
|
246
|
+
- **New `blocks` plugin option** — `vitopsEmdash({ blocks: { include, exclude, extra } })`. `include` is
|
|
247
|
+
an allowlist, `exclude` drops blocks, `extra` adds the site's own **editor** definitions (the site
|
|
248
|
+
supplies their renderers via `<PortableText components={{ type }}>`, the same route that overrides a
|
|
249
|
+
`vitops.*` renderer). An unknown name or a colliding `type` throws at plugin construction.
|
|
250
|
+
- Link and image URLs in blocks with a scheme other than `http`, `https`, `mailto` or `tel` are
|
|
251
|
+
dropped (see the security entry).
|
|
252
|
+
|
|
253
|
+
`@getvitops/generator`: `TierEntry` gains an optional `emdash: string[]` (block `_type`s) and `Tier`
|
|
254
|
+
gains `'emdash'`, so `tierPatterns('emdash')` projects it. **Breaking only for code that exhaustively
|
|
255
|
+
switches on `Tier` or builds a `Record<Tier, …>`** — add an `emdash` case. The agent doc
|
|
256
|
+
`vitops docs components` gains an "EmDash blocks" table and the docsite a `components/emdash` page. The
|
|
257
|
+
guard that every block and every `emdash` entry agree lives in `@getvitops/emdash`'s `blocks.test.ts`,
|
|
258
|
+
because the generator cannot import the plugin.
|
|
259
|
+
|
|
260
|
+
`@getvitops/create`: the EmDash template's agent guidance explains the blocks, their limits (a block
|
|
261
|
+
cannot contain blocks; `vitops.*` blocks do not render inside EmDash's built-in `columns`) and the
|
|
262
|
+
`blocks` option.
|
|
263
|
+
|
|
264
|
+
Formats reached: none — no CSS change. Other packages in the `fixed` group: No consumer-facing change
|
|
265
|
+
in this package.
|
|
266
|
+
|
|
267
|
+
- 8d8aad7: **New: server-side Google Ads offline conversion upload.** Set
|
|
268
|
+
`site.ads.google.reporting: "server"` and a matching
|
|
269
|
+
`conversionActionId` (a numeric id or full
|
|
270
|
+
`customers/…/conversionActions/…` resource name — **not** the same value
|
|
271
|
+
as `conversionLabel`, which is the opaque label the web tag uses and
|
|
272
|
+
which the Ads API's upload endpoint doesn't accept) and
|
|
273
|
+
`createConversionRoute`'s new `googleAdsOffline` option uploads the
|
|
274
|
+
conversion via `ConversionUploadService.UploadClickConversions` instead
|
|
275
|
+
of relying on the visitor's browser to fire `gtag.js`. An action with no
|
|
276
|
+
matching `conversionActionId` keeps firing client-side regardless of
|
|
277
|
+
`reporting`, so turning it on before the id is filled in never silently
|
|
278
|
+
drops a conversion. `@getvitops/astro/routes` exports a matching
|
|
279
|
+
`toGoogleAdsConfig(config)` adapter, alongside `toCrmConfig`.
|
|
280
|
+
|
|
281
|
+
Structurally typed against Google's documented REST shape, not verified
|
|
282
|
+
against a live Ads account — no developer-token/MCC access exists
|
|
283
|
+
anywhere in this toolchain's own setup to test against. Wrapped so a
|
|
284
|
+
wrong guess degrades to a stated failure reason (folded into the
|
|
285
|
+
conversion email's "Delivery" section, same as a failed CRM write) rather
|
|
286
|
+
than breaking, the same caveat `@getvitops/utils/crm`'s Jobber executor
|
|
287
|
+
already carries for its own unverified surfaces.
|
|
288
|
+
|
|
289
|
+
`vitops ads lint` now flags a `reporting: "server"` property whose
|
|
290
|
+
configured actions have no matching `conversionActionId` — not a
|
|
291
|
+
failure, since that action keeps firing client-side, but worth surfacing
|
|
292
|
+
rather than leaving someone to wonder why nothing changed.
|
|
293
|
+
|
|
294
|
+
Not built: `vitops ads provision` (upserting conversion actions via
|
|
295
|
+
`ConversionActionService` and printing back both the label and the
|
|
296
|
+
resource id) — same credential blocker, and deliberately left unscoped
|
|
297
|
+
until there's a live account to verify the request/response shape
|
|
298
|
+
against.
|
|
299
|
+
|
|
300
|
+
- dde3df3: **`site.ads.google.conversionLabel` now accepts a per-action map** —
|
|
301
|
+
`{ call?, email?, form? }` — instead of one bare string shared by every
|
|
302
|
+
interaction. A bare string still works (back-compat, treated as `call`), but a
|
|
303
|
+
call and an email click used to report as the **same** Google Ads conversion
|
|
304
|
+
action, indistinguishable in the account; each action now gets its own
|
|
305
|
+
`send_to` target.
|
|
306
|
+
|
|
307
|
+
**New: `vitopsFormConversion(formEl)`**, exposed as `window.vitopsFormConversion`
|
|
308
|
+
by `<Tracking />`'s capture script — call it from your own fetch-based form
|
|
309
|
+
submit handler once the POST to `createConversionRoute()` succeeds, to fire the
|
|
310
|
+
`form` Google Ads conversion action client-side (mirroring the existing
|
|
311
|
+
tel:/mailto: click handler, which now fires `call`/`email` instead of sharing
|
|
312
|
+
one label). Idempotent per form element. There's deliberately no server-side
|
|
313
|
+
equivalent for a no-JS `<form method="post">` submission — that's a full-page
|
|
314
|
+
redirect with no script to fire from, and a real server-side Google Ads
|
|
315
|
+
conversion needs the Ads API's click-conversion upload (developer token + MCC
|
|
316
|
+
access), tracked separately in the vNext backlog.
|
|
317
|
+
|
|
318
|
+
**Fixed: the conversion route's rate limiter no longer trusts the client's
|
|
319
|
+
raw `event` field.** It used to key on whatever string a client sent
|
|
320
|
+
(`` `${ip}:${body.event}` ``), so varying that value on every request let a
|
|
321
|
+
form submission dodge the per-IP rate limit entirely — only `'call'`/`'email'`
|
|
322
|
+
were ever recognized, and anything else got its own fresh bucket. The key now
|
|
323
|
+
uses the server's own classification (`call` | `email` | `form`), closing that
|
|
324
|
+
bypass without changing the existing call-vs-email-don't-compete behavior.
|
|
325
|
+
|
|
326
|
+
**New: `site.notifications.actionUrl`** — an optional action-link template
|
|
327
|
+
added to the conversion-notification email as an "Actions" section.
|
|
328
|
+
`{clickId}` is replaced with the ad click ID captured on `_ac` (gclid, fbclid,
|
|
329
|
+
etc.); the section is omitted when the event has no click ID to interpolate,
|
|
330
|
+
rather than emailing a broken link.
|
|
331
|
+
|
|
332
|
+
- d9f213b: **New: `site.plan`** — records a commercial plan/add-on entitlement (`{ key, addons? }`),
|
|
333
|
+
distinct from a section's own `enabled` flag: `enabled` answers "does this site do X at all"
|
|
334
|
+
(a capability switch); `plan` answers "is this client entitled to turn X on" (a commercial
|
|
335
|
+
switch one layer above). Absence means unrestricted, matching every other opt-in block in
|
|
336
|
+
`Config`.
|
|
337
|
+
|
|
338
|
+
`PLAN_CAPABILITIES` (new export) is the small, hand-authored table of which add-on gates
|
|
339
|
+
which `Config` field — today: `ab_testing` gates `site.abTesting.enabled`, any
|
|
340
|
+
`environments.*.variant`, **and any enabled `site.experiments` entry** (that last one was
|
|
341
|
+
missed in this feature's first pass and fixed before release — found while planning this
|
|
342
|
+
toolchain's downstream adoption, not by a consumer hitting it), `server_side_tracking` gates
|
|
343
|
+
`site.tracking.enabled`. `validateConfig` now rejects a config that turns on a gated feature
|
|
344
|
+
without listing the owning add-on in `site.plan.addons`, naming the add-on in the error.
|
|
345
|
+
|
|
346
|
+
**Breaking**: an existing config using `site.abTesting.enabled`/`environments.*.variant` or
|
|
347
|
+
`site.tracking.enabled` now needs a matching `site.plan.addons` entry, or `validateConfig`
|
|
348
|
+
rejects it. Add `site.plan: { key: '<your-plan>', addons: ['ab_testing'] }` (and/or
|
|
349
|
+
`'server_side_tracking'`) to a config already using either feature.
|
|
350
|
+
|
|
351
|
+
- 7fd5f8f: **`site.seo` is now the single source of site-level SEO defaults** (ADR-0002). Until now `site.seo`
|
|
352
|
+
declared `titleTemplate`, verification tokens, `openGraph` and `twitter` that nothing read, while
|
|
353
|
+
`<Seo />` took a second, differently-shaped vocabulary through `vitops({ seo })` that no other stack
|
|
354
|
+
could see.
|
|
355
|
+
|
|
356
|
+
- `SeoSchema` moves to `@getvitops/config` and gains what only the integration had: `siteName`,
|
|
357
|
+
`defaultTitle`, `defaultDescription`, `openGraph.localeAlternates` and `openGraph.image` as
|
|
358
|
+
`{ src, alt, width, height, type }`. Text fields are localisable.
|
|
359
|
+
- `@getvitops/astro` exports `resolveSeoDefaults(config, { locale, env })`. The integration reads
|
|
360
|
+
`site.seo` from the site config it already loads; `siteName` falls back to `organization.name`, and
|
|
361
|
+
the environment's `robots` wins over `site.seo.robots`, the same fact `vitops robots` and
|
|
362
|
+
`search notify` read.
|
|
363
|
+
- `vitops({ seo })` keeps working unchanged and is now an **override** merged over the config, field by
|
|
364
|
+
field (`openGraph` and `twitter` merge key by key).
|
|
365
|
+
- **Removed from the config schema** (nothing read them): `descriptionTemplate` → use
|
|
366
|
+
`defaultDescription`; `openGraph.siteName`/`title`/`description` and
|
|
367
|
+
`twitter.title`/`description`/`image` → set per page on `<Seo />`, or the site-wide values above.
|
|
368
|
+
A config that still carries one now fails `vitops validate` with an unrecognised-key error.
|
|
369
|
+
- `openGraph.type` is now `website | article | profile`.
|
|
370
|
+
|
|
371
|
+
### Patch Changes
|
|
372
|
+
|
|
373
|
+
- 779d22e: Add a `vitops.hero` EmDash block (eyebrow, heading, text, two links, optional image; centered or split layout) built on the `.centered`/`.split` layout primitives. The EmDash template's home page now seeds one of each of hero, cards, accordion and marquee. `@getvitops/generator`: the `split` pattern lists the new block in `TIERS`.
|
|
374
|
+
- 0574388: Bump `emdash`/`@emdash-cms/cloudflare` peers to `>=1.0.0 <2` and `vite-plus` to `^1.0.0`, following
|
|
375
|
+
both projects' GA releases.
|
|
376
|
+
|
|
377
|
+
**Breaking for `@getvitops/emdash` consumers on EmDash 0.x**: the peer range no longer admits
|
|
378
|
+
`emdash@0.31.x`/`@emdash-cms/cloudflare@0.31.x`. Upgrade `emdash` and `@emdash-cms/cloudflare` to
|
|
379
|
+
`^1.0.1` first — nothing in `@getvitops/emdash`'s own plugin surface (`admin.portableTextBlocks`,
|
|
380
|
+
the `hooks.page-fragments:register` capability, `definePlugin()`) changed against EmDash 1.0, so no
|
|
381
|
+
code here needed to change, only the declared range.
|
|
382
|
+
|
|
383
|
+
The `emdash` scaffold template (`vp create @getvitops:emdash`) picks up several fixes alongside
|
|
384
|
+
the version bump:
|
|
385
|
+
|
|
386
|
+
- `tailwindcss`/`@tailwindcss/vite` are now real dependencies — the template selected
|
|
387
|
+
`css.format: 'tailwind'` without installing either, which threw on build.
|
|
388
|
+
- `site.json` now sets `site.icons.sprite: true` plus the full `semantic` list the
|
|
389
|
+
`vitops.actionLink` block's icon picker offers — previously its sprite icons pointed at a file
|
|
390
|
+
the build never generated.
|
|
391
|
+
- `astro.config.mjs` sets `migrations: { runtime: 'check', dev: 'auto' }`, and
|
|
392
|
+
`site-deploy-emdash.yml` runs EmDash 1.0's documented `build → status → apply → deploy → check`
|
|
393
|
+
migration sequence explicitly, rather than relying solely on first-request auto-migration. This
|
|
394
|
+
needs a new GitHub Environment variable, `EMDASH_TARGET_FINGERPRINT` (one per environment),
|
|
395
|
+
which only a human can set — run `emdash migrate --status` against the real deployed database,
|
|
396
|
+
review the printed identity, then `gh variable set EMDASH_TARGET_FINGERPRINT --env <env> --body
|
|
397
|
+
<value>`. Deploys fail clearly on the migration-apply step until it's set.
|
|
398
|
+
- `lint.options.typeAware`/`typeCheck` in the template's own `vite.config.ts` are commented out,
|
|
399
|
+
not deleted: vite-plus 1.0 only honours that option in a project's actual root config, and hard
|
|
400
|
+
errors on it in a nested one (which this file is, sitting inside the `design-system` monorepo).
|
|
401
|
+
Uncomment it once scaffolded into its own repo.
|
|
402
|
+
|
|
403
|
+
No config schema changes. `@getvitops/generator` also switches from the `zod/mini` subpath import
|
|
404
|
+
to the standalone `@zod/mini` package (zod 4.5+; same API) and fixes `validateConfig()` silently
|
|
405
|
+
losing the offending key name on an `unrecognized_keys` error (e.g. a typo'd `site.ads` platform
|
|
406
|
+
now names the bad key instead of reporting a bare "Invalid input").
|
|
407
|
+
|
|
408
|
+
Also fixed: `vp check`'s `tsconfig-error` for `packages/create/templates/emdash/tsconfig.json`
|
|
409
|
+
(`extends: "astro/tsconfigs/strict"`, unresolvable from inside this monorepo since the template
|
|
410
|
+
has no `node_modules` of its own — a pre-existing, structural gap, newly surfaced as a hard error
|
|
411
|
+
by oxlint 1.85's deeper config discovery, and blocking on every commit since this repo's pre-commit
|
|
412
|
+
hook runs `vp check --fix`). Fixed with a new `scripts/link-template-deps.mjs`, wired as the root
|
|
413
|
+
`postinstall` script: symlinks `astro`/`emdash`/`@emdash-cms/cloudflare` into the template's own
|
|
414
|
+
`node_modules/` (resolved fresh from sibling packages that already depend on them for real, not a
|
|
415
|
+
hardcoded `.pnpm` hash) and writes a minimal `astro:content` ambient shim at `.astro/types.d.ts`
|
|
416
|
+
(the same path `astro sync` would generate for real). Both are gitignored and only ever exist
|
|
417
|
+
inside this monorepo's copy of the template — a real scaffold installs its own real copies instead.
|
|
418
|
+
Considered and rejected first: inlining astro's strict preset (drift risk against astro's own
|
|
419
|
+
evolving default) and making the template a real pnpm workspace member (would change how its
|
|
420
|
+
`@getvitops/*: "latest"` deps resolve locally, which `templates.test.ts` exists to protect).
|
|
421
|
+
|
|
422
|
+
**Known gap, not fixed here**: `packages/astro/src/components/container.test.ts` (the
|
|
423
|
+
`AstroContainer`-based render tests) fails to even load under vite-plus 1.0's Vite 8 — direct
|
|
424
|
+
`.astro` imports in a Vitest file need the Astro Vite plugin registered, which nothing in this
|
|
425
|
+
repo's test config actually does (Astro's own docs call for `getViteConfig()`, which doesn't
|
|
426
|
+
compose with vite-plus's own `defineConfig`). A type-level shim
|
|
427
|
+
(`packages/astro/src/components/astro-modules.d.ts`) fixes `vp check`'s type-aware pass, but the
|
|
428
|
+
runtime `vp test` failure for this one file is still open.
|
|
429
|
+
|
|
430
|
+
- 1a3e811: **Fixed: `<Icon />` no longer crashes a Cloudflare Workers SSR page.** It statically imported
|
|
431
|
+
`loadIconSvg`/`spriteId` from `@getvitops/generator`'s root entry, which also re-exports
|
|
432
|
+
`generate.ts` — reaching lightningcss, whose native binary Workers cannot `require()`.
|
|
433
|
+
Rendering `<Icon>` anywhere in an SSR project 500'd every request, regardless of
|
|
434
|
+
`icons.engine`, since the import itself (not just calling the function) pulled lightningcss
|
|
435
|
+
into the bundle. Fixed by publishing a new Worker-safe `@getvitops/generator/icons-sprite`
|
|
436
|
+
subpath (`buildIconSprite`/`loadIconSvg`/`spriteId`, no lightningcss in its import graph) and
|
|
437
|
+
switching `<Icon />` to import from there.
|
|
438
|
+
- Updated dependencies [89265af]
|
|
439
|
+
- Updated dependencies [0574388]
|
|
440
|
+
- Updated dependencies [c3c1bcb]
|
|
441
|
+
- Updated dependencies [0574388]
|
|
442
|
+
- Updated dependencies [0574388]
|
|
443
|
+
- Updated dependencies [8d8aad7]
|
|
444
|
+
- Updated dependencies [0574388]
|
|
445
|
+
- Updated dependencies [0574388]
|
|
446
|
+
- Updated dependencies [dde3df3]
|
|
447
|
+
- Updated dependencies [7f4f50e]
|
|
448
|
+
- Updated dependencies [7fd5f8f]
|
|
449
|
+
- Updated dependencies [ceb8310]
|
|
450
|
+
- @getvitops/config@8.2.0
|
|
451
|
+
- @getvitops/utils@8.2.0
|
|
452
|
+
|
|
3
453
|
## 8.2.0-alpha.0
|
|
4
454
|
|
|
5
455
|
### Minor Changes
|
|
@@ -93,7 +543,7 @@
|
|
|
93
543
|
|
|
94
544
|
This adds one server-side path for all three CRMs, hung off the conversion route that already
|
|
95
545
|
exists. `createConversionRoute()` gets a new `crm` option (`{ config, credentials, fetch?,
|
|
96
|
-
timeoutMs? }`); credentials arrive as a function of `locals`, exactly like the existing `binding`
|
|
546
|
+
timeoutMs? }`); credentials arrive as a function of `locals`, exactly like the existing `binding`
|
|
97
547
|
option, and are never config. A failed CRM write never fails the request and is folded into the
|
|
98
548
|
notification email's new "Delivery" section, so a broken CRM token is never silent.
|
|
99
549
|
|
|
@@ -138,8 +588,8 @@ timeoutMs? }`); credentials arrive as a function of `locals`, exactly like the e
|
|
|
138
588
|
tailwind outputs are unaffected.
|
|
139
589
|
|
|
140
590
|
- c3c1bcb: **New: the design-system editor, mounted inside the EmDash admin.** `vitopsEmdash({
|
|
141
|
-
designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
|
|
142
|
-
'./site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
|
|
591
|
+
designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
|
|
592
|
+
'./site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
|
|
143
593
|
`<wc-theme-editor>` — the same live token editor `@getvitops/vite`'s dev server has always
|
|
144
594
|
offered, now usable by the client directly, no dev server required. An edit is visible
|
|
145
595
|
site-wide immediately (a runtime `:root` override, served via a new plugin API route and
|
|
@@ -186,7 +636,7 @@ designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import c
|
|
|
186
636
|
|
|
187
637
|
`vitops email setup [domain]` onboards a sending domain (currently Cloudflare Email Sending only)
|
|
188
638
|
and polls DNS until its SPF/DKIM/DMARC records resolve or a timeout is reached; `vitops email
|
|
189
|
-
check` is the non-blocking deploy guard both `@getvitops/create` templates' `deploy`/`deploy:dev`
|
|
639
|
+
check` is the non-blocking deploy guard both `@getvitops/create` templates' `deploy`/`deploy:dev`
|
|
190
640
|
scripts now run first (`--strict` makes it a real gate; opt out per-site with
|
|
191
641
|
`site.notifications.email.verifySender: false`, or globally with `--skip` /
|
|
192
642
|
`VITOPS_SKIP_EMAIL_CHECK`).
|
|
@@ -198,7 +648,7 @@ check` is the non-blocking deploy guard both `@getvitops/create` templates' `dep
|
|
|
198
648
|
from the onboarded subdomain — rather than silently trying to onboard the apex, which rewrites
|
|
199
649
|
`_dmarc.<domain>` and can tighten DMARC policy on every other mail your domain sends.
|
|
200
650
|
- Candidates come from `site.notifications.email.from` (or its derived `noreply@<domains
|
|
201
|
-
.canonical>` default) first; a best-effort scan of `--src` only supplements it, and never guesses
|
|
651
|
+
.canonical>` default) first; a best-effort scan of `--src` only supplements it, and never guesses
|
|
202
652
|
at a computed `from` value.
|
|
203
653
|
|
|
204
654
|
New `@getvitops/utils/email` subpath and `site.notifications.email.verifySender` config field.
|
|
@@ -280,7 +730,7 @@ check` is the non-blocking deploy guard both `@getvitops/create` templates' `dep
|
|
|
280
730
|
needs a new GitHub Environment variable, `EMDASH_TARGET_FINGERPRINT` (one per environment),
|
|
281
731
|
which only a human can set — run `emdash migrate --status` against the real deployed database,
|
|
282
732
|
review the printed identity, then `gh variable set EMDASH_TARGET_FINGERPRINT --env <env> --body
|
|
283
|
-
<value>`. Deploys fail clearly on the migration-apply step until it's set.
|
|
733
|
+
<value>`. Deploys fail clearly on the migration-apply step until it's set.
|
|
284
734
|
- `lint.options.typeAware`/`typeCheck` in the template's own `vite.config.ts` are commented out,
|
|
285
735
|
not deleted: vite-plus 1.0 only honours that option in a project's actual root config, and hard
|
|
286
736
|
errors on it in a nested one (which this file is, sitting inside the `design-system` monorepo).
|
|
@@ -623,7 +1073,7 @@ check` is the non-blocking deploy guard both `@getvitops/create` templates' `dep
|
|
|
623
1073
|
It is **required for a user credential and wrong for a service account**, which is why it is not
|
|
624
1074
|
sent unconditionally. A user credential authenticates through a shared OAuth client that owns no
|
|
625
1075
|
project, so Google refuses the call outright — measured: `403 "requires a quota project, which is
|
|
626
|
-
not set by default"`. A service account already belongs to a project, and pointing it at a
|
|
1076
|
+
not set by default"`. A service account already belongs to a project, and pointing it at a
|
|
627
1077
|
different one would newly require `serviceusage.services.use` there, turning a working CI
|
|
628
1078
|
credential into a 403 for no benefit.
|
|
629
1079
|
|
|
@@ -648,7 +1098,7 @@ not set by default"`. A service account already belongs to a project, and pointi
|
|
|
648
1098
|
**Fixed: an ADC file in `GOOGLE_APPLICATION_CREDENTIALS` no longer hard-exits.** That variable is
|
|
649
1099
|
Google's own convention _and_ where gcloud writes ADC, so the same variable carries two kinds of
|
|
650
1100
|
credential. Parsing an `authorized_user` file as a service account failed with `service account
|
|
651
|
-
JSON is missing client_email or private_key` — while holding a credential the command already knew
|
|
1101
|
+
JSON is missing client_email or private_key` — while holding a credential the command already knew
|
|
652
1102
|
how to use. It is now discriminated on `type`, the field Google's own libraries switch on.
|
|
653
1103
|
|
|
654
1104
|
**Also fixed — `search setup --dry` and `--check` are not offline.** The docs implied otherwise. The
|
|
@@ -729,7 +1179,7 @@ JSON is missing client_email or private_key` — while holding a credential the
|
|
|
729
1179
|
created, with `patterns/counter.css`'s own reduced-motion query as a second belt.
|
|
730
1180
|
- The accessible name stays the final value for the whole animation: `.counter__value` is toggled
|
|
731
1181
|
to the framework's `.sr-only` utility while running, never hidden — `display: none`/`visibility:
|
|
732
|
-
hidden` would pull it out of the accessible-name computation along with the visual hiding, and a
|
|
1182
|
+
hidden` would pull it out of the accessible-name computation along with the visual hiding, and a
|
|
733
1183
|
screen reader must never be read ~60 intermediate numbers. The animated text lives in a separate
|
|
734
1184
|
`aria-hidden` presentation span it never reaches.
|
|
735
1185
|
- `data-from` (default `0`), `data-duration` (ms, default `1200`), `data-easing` (`linear` |
|
|
@@ -1488,28 +1938,20 @@ hidden` would pull it out of the accessible-name computation along with the visu
|
|
|
1488
1938
|
{
|
|
1489
1939
|
"designSystem": {
|
|
1490
1940
|
"themes": {
|
|
1491
|
-
"default": {
|
|
1492
|
-
/* … */
|
|
1493
|
-
},
|
|
1941
|
+
"default": {/* … */},
|
|
1494
1942
|
},
|
|
1495
1943
|
},
|
|
1496
1944
|
"organization": {
|
|
1497
1945
|
"name": "Acme",
|
|
1498
1946
|
"contact": "hq",
|
|
1499
1947
|
"locations": {
|
|
1500
|
-
"hq": {
|
|
1501
|
-
/* … */
|
|
1502
|
-
},
|
|
1948
|
+
"hq": {/* … */},
|
|
1503
1949
|
},
|
|
1504
1950
|
},
|
|
1505
1951
|
"site": {
|
|
1506
1952
|
"defaultLocale": "en",
|
|
1507
|
-
"domains": {
|
|
1508
|
-
|
|
1509
|
-
},
|
|
1510
|
-
"analytics": {
|
|
1511
|
-
/* … */
|
|
1512
|
-
},
|
|
1953
|
+
"domains": {/* … */},
|
|
1954
|
+
"analytics": {/* … */},
|
|
1513
1955
|
},
|
|
1514
1956
|
}
|
|
1515
1957
|
```
|
|
@@ -1603,7 +2045,7 @@ hidden` would pull it out of the accessible-name computation along with the visu
|
|
|
1603
2045
|
`animate-view` / `animate-scroll` / `animate-trigger` / `transition`, and every effect
|
|
1604
2046
|
gained an `open-<fx>` state variant. Overlays now state _where they start_
|
|
1605
2047
|
(`--translate-x-from`) instead of owning a keyframe: `class="drawer drawer--right
|
|
1606
|
-
open-fade-in"` composes slide and fade. Applied at zero specificity with identity
|
|
2048
|
+
open-fade-in"` composes slide and fade. Applied at zero specificity with identity
|
|
1607
2049
|
defaults, so a popover that sets no effect vars is unchanged.
|
|
1608
2050
|
- **One scrim.** `--scrim` / `--scrim-filter` tokens and `.no-scrim` in `popover.css`,
|
|
1609
2051
|
replacing 18 `::backdrop` blocks across seven partials. `.drawer--modeless` is aliased.
|
|
@@ -1918,8 +2360,8 @@ open-fade-in"` composes slide and fade. Applied at zero specificity with identit
|
|
|
1918
2360
|
replace it with `--anim-range`. `--stagger-range-step` is now a viewport length (`5vh`) to match.
|
|
1919
2361
|
|
|
1920
2362
|
**Fixed: every journey ran on a truncated range.** The generator emitted `animation-range: entry
|
|
1921
|
-
exit`; lightningcss parsed the end of that shorthand as `exit 0%` rather than the spec's `exit
|
|
1922
|
-
100%`, so the bundle shipped `entry exit 0%`. Journeys reach their `100%` keyframe — the hidden
|
|
2363
|
+
exit`; lightningcss parsed the end of that shorthand as `exit 0%` rather than the spec's `exit
|
|
2364
|
+
100%`, so the bundle shipped `entry exit 0%`. Journeys reach their `100%` keyframe — the hidden
|
|
1923
2365
|
`from` state — as the element hits the top of the viewport, which is to say they faded out while
|
|
1924
2366
|
still fully on screen. Journeys now run `entry calc(50% + 10vh) → exit 100%`: they start on the same
|
|
1925
2367
|
midpoint pivot as everything else and keep the full entry → hold → exit arc, so the hold sits in the
|
|
@@ -1932,7 +2374,7 @@ exit`; lightningcss parsed the end of that shorthand as `exit 0%` rather than th
|
|
|
1932
2374
|
so one knob tunes both. If your own config has an empty `slide` base, add the same line.
|
|
1933
2375
|
|
|
1934
2376
|
**Fixed: `reveal-*` on hover was unreachable.** A `hover-reveal-left` element rests at `clip-path:
|
|
1935
|
-
inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting — so it had zero
|
|
2377
|
+
inset(0 100% 0 0)`, and `clip-path` clips **hit-testing** as well as painting — so it had zero
|
|
1936
2378
|
hittable area and could never receive the hover that would reveal it. The state variants now match
|
|
1937
2379
|
the element **or its direct parent**, mirroring what `animation.css` already did for the trigger
|
|
1938
2380
|
driver (`:is(.is-active, [data-active]) > .animate-trigger`):
|
|
@@ -69,3 +69,23 @@
|
|
|
69
69
|
.banner[hidden] {
|
|
70
70
|
display: none;
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* `<wc-dismissable>` wraps a banner or notification — block content — and animates its own
|
|
75
|
+
* opacity/scale on dismiss, so it needs a box of its own: `display: contents` would drop
|
|
76
|
+
* that animation. Pre-upgrade it would otherwise be an inline box around a block child.
|
|
77
|
+
*/
|
|
78
|
+
:where(wc-dismissable) {
|
|
79
|
+
display: block;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/*
|
|
83
|
+
* A dismiss button does nothing until `<wc-dismissable>` upgrades — an inert control that
|
|
84
|
+
* looks live is worse than none. `:not(:defined)` is true with no JS (and until elements.js
|
|
85
|
+
* has run), so the close affordance only appears once something can act on it. The banner
|
|
86
|
+
* itself stays fully readable. Markup that puts a `[data-dismiss]` button inside the element
|
|
87
|
+
* gets the same treatment, whatever its class.
|
|
88
|
+
*/
|
|
89
|
+
wc-dismissable:not(:defined) :is(.banner__close, .notification__close, [data-dismiss]) {
|
|
90
|
+
display: none;
|
|
91
|
+
}
|