@getvitops/generator 4.0.0 → 5.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 +384 -0
- package/assets/bricks/elements/sitenav.php +4 -1
- package/assets/css/index.css +3 -0
- package/assets/css/patterns/anchor-link.css +67 -0
- package/assets/css/patterns/card.css +35 -0
- package/assets/css/patterns/list.css +8 -0
- package/assets/css/patterns/nav.css +8 -0
- package/assets/css/patterns/subgrid.css +16 -1
- package/assets/css/utilities.css +12 -0
- package/assets/js/consent.js +1 -1
- package/assets/js/elements.js +19 -19
- package/config.schema.json +88 -4
- package/dist/index.d.mts +261 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2250 -1499
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,389 @@
|
|
|
1
1
|
# @getvitops/generator
|
|
2
2
|
|
|
3
|
+
## 5.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- **Dangling token references are now a build failure.** `validate()` resolves every `var(--…)` a
|
|
8
|
+
config authors — in `patterns.defaults` / `radii` / `groups` / `items.*.{base,overrides,states}`,
|
|
9
|
+
`typography.roles` and `shadows` — against the tokens that config actually emits. A reference to a
|
|
10
|
+
token that does not exist used to validate, generate, minify and ship, then resolve to nothing in
|
|
11
|
+
the browser: one downstream config shipped a `.cta` whose `color` fell back to `inherit` on a brand
|
|
12
|
+
fill (unreadable text on every filled button), and two of its four dead references had been dead for
|
|
13
|
+
several releases with nothing ever reporting them.
|
|
14
|
+
|
|
15
|
+
The check is deliberately anchored to the namespaces the generator owns — `--color-*`, `--shadow-*`,
|
|
16
|
+
`--z-tier-*`, `--surface-glass`, `--overlay` — the same discipline `vitops lint` follows. A pattern
|
|
17
|
+
may legitimately reference a framework token from `@getvitops/core` or a hook of your own, and
|
|
18
|
+
enumerating "every valid token" would make those false positives. A reference carrying a fallback
|
|
19
|
+
(`var(--x, 0.25rem)`) is never flagged: that is you saying what happens when the token is absent.
|
|
20
|
+
Errors name the config path and suggest the nearest token that does exist.
|
|
21
|
+
|
|
22
|
+
Two related diagnostics ship with it:
|
|
23
|
+
- **Pre-1.0 colour-grammar references are reported as one rename table**, not as a dozen separate
|
|
24
|
+
"no such token" errors — the shape the 3.0 flat-config detector uses, applied to the 1.0 colour
|
|
25
|
+
change. It is built from your own role names, and it says to apply the renames _simultaneously_,
|
|
26
|
+
because several of them rotate (`--surface-bg` → `--color-bg-surface-muted`,
|
|
27
|
+
`--surface-bg-bold` → `--color-bg-surface`) and sequential replacement compounds them.
|
|
28
|
+
- **A surface-shaped role declared chromatic now warns.** A bare hue string is shorthand for
|
|
29
|
+
`kind: "chromatic"`, which emits no bare `--color-bg-<role>` and no `.bg-<role>` — so writing
|
|
30
|
+
`"surface": "ink"` silently removed `--color-bg-surface` and the 36 references the framework's own
|
|
31
|
+
CSS makes to it. Relatedly, `--surface-glass`, `--overlay` and `.glass` are no longer emitted when
|
|
32
|
+
`surface` is chromatic: they read `--color-bg-surface`, so emitting them pointed at nothing.
|
|
33
|
+
|
|
34
|
+
**`vitops lint --fix` is now the migration tool.** It reads `var(--…)` out of CSS and `<style>`
|
|
35
|
+
blocks (not just `class` attributes) and rewrites pre-1.0 token references in a **single pass**, so
|
|
36
|
+
the rotating renames cannot compound. Nothing else is rewritten — every other finding it reports is
|
|
37
|
+
a judgement call. Run it after a major bump.
|
|
38
|
+
|
|
39
|
+
**`--help` works on every subcommand.** `vitops lint --help` used to exit non-zero with
|
|
40
|
+
`Unknown option '--help'`; every leaf subcommand parsed its own argv strictly and none declared the
|
|
41
|
+
option. It is now answered before dispatch, so no command can forget it, and a drift guard fails the
|
|
42
|
+
build if a command has no documented options. (`validate`'s options were genuinely undocumented and
|
|
43
|
+
now are.)
|
|
44
|
+
|
|
45
|
+
**The Astro integration reads `site.tracking` and `site.legal.cookieConsent`.** These drive the
|
|
46
|
+
generated cookie notice, while `vitops({ consent, tracking })` drove the runtime — two hand-synced
|
|
47
|
+
declarations of the same fact with nothing comparing them, so a site could ship a notice naming
|
|
48
|
+
categories its banner never offered, or disclosing an `_ac` cookie no capture script ever wrote, and
|
|
49
|
+
the build was clean. The config blocks are now defaults (as `css` / `fonts` / `favicon` / `ads`
|
|
50
|
+
already were) and a genuine contradiction — `true` against `false` on the same fact — **fails the
|
|
51
|
+
build**, naming both sides. An absent option is not a contradiction; it just takes the config's
|
|
52
|
+
value.
|
|
53
|
+
|
|
54
|
+
Also fixed, each from a downstream report:
|
|
55
|
+
- **`@getvitops/astro/tracking`** re-exports `@getvitops/utils/tracking` (plus `TRACKING_ENDPOINT`),
|
|
56
|
+
so the documented conversion flow works with one install. Under strict pnpm app code cannot
|
|
57
|
+
resolve a transitive dependency, and the obvious workaround — importing the same symbols from the
|
|
58
|
+
package index — is the one that drags the integration's Node builtins toward a Worker bundle. This
|
|
59
|
+
entry and `./routes` are both clean.
|
|
60
|
+
- **`createConversionRoute`'s example was broken on every supported Astro.** It read the binding off
|
|
61
|
+
`locals.runtime.env`, removed in Astro v6, while the package peers on `>= 7` — and because the
|
|
62
|
+
throw precedes the response, Astro re-reads the request and reports a misleading "Body has already
|
|
63
|
+
been used". It also called a `toNotifyContext()` helper that does not exist. The example now uses
|
|
64
|
+
`import { env } from 'cloudflare:workers'` and a plain `NotifyContext`.
|
|
65
|
+
- **`/api/track` is exported as `TRACKING_ENDPOINT`**, and the integration warns at build when
|
|
66
|
+
tracking is on and no route answers it. It was a bare literal inside the inlined capture script:
|
|
67
|
+
name the route file anything else and every `tel:` beacon 404s, conversions vanish, and nothing
|
|
68
|
+
errors anywhere.
|
|
69
|
+
- **`describeEvent` no longer reports "from Unknown"** for forms that don't have a field literally
|
|
70
|
+
named `name`. It now falls back through `first_name`/`last_name`, `full_name`, and finally the
|
|
71
|
+
email address.
|
|
72
|
+
- **`sendEmail` takes `timeoutMs` (default 10s).** `binding.send()` had no deadline, so one hung
|
|
73
|
+
attempt hung the request that produced it — indefinitely, in the one module otherwise built on
|
|
74
|
+
always saying why. A timeout is retried, like any other transient failure.
|
|
75
|
+
- **`vitops search notify` warns when the sitemap's URLs aren't on the configured origin.** A 404
|
|
76
|
+
fails loudly; a well-formed sitemap belonging to someone else did not — a route collision served a
|
|
77
|
+
valid document listing another site's pages and the run reported a healthy submission. Surfaces at
|
|
78
|
+
`--dry`.
|
|
79
|
+
- **`require()` warns when the page has no `<wc-consent>`.** The demand can never be granted, so
|
|
80
|
+
whatever waited on it never runs — silently. Hit by rendering `<Tracking />` on pages where
|
|
81
|
+
`<CookieConsent />` wasn't.
|
|
82
|
+
- **`vitops agents` no longer writes a path it guessed.** It used to warn and then write "tokens
|
|
83
|
+
live in `design-system.json`" into a project whose tokens are in `company.json`, with the four
|
|
84
|
+
emitted `vitops generate` commands hard-coded to match — copy-paste-broken in exactly the projects
|
|
85
|
+
that most need the block to be right. It now finds the config by shape, interpolates the resolved
|
|
86
|
+
path into every emitted command, and fails rather than guessing when there is nothing to name.
|
|
87
|
+
- **`vitops search setup --dry` and `vitops ads setup --dry` run without credentials.** A dry run
|
|
88
|
+
that mutates nothing should not demand the token to mutate; with none set it plans from scratch
|
|
89
|
+
and says so. (`--check` still needs them — drift is a comparison against live state.) Both now
|
|
90
|
+
also mention that the Cloudflare token needs `Zone:Read` alongside `Zone:DNS:Edit` — the
|
|
91
|
+
zone-by-name lookup uses it, and its absence read as "the zone isn't in this account".
|
|
92
|
+
- **`vitops legal` names the files it wrote and flags new ones.** Enabling `cookieConsent` silently
|
|
93
|
+
produced a `cookie-notice` document that then needs a page, a route, a link and a `policyUrl`; the
|
|
94
|
+
command said only "3 files". The review reminder is also printed on the stdout path, where it was
|
|
95
|
+
missing entirely.
|
|
96
|
+
- **`vitops init` writes a versioned `$schema`** — `./node_modules/@getvitops/generator/schema.json`,
|
|
97
|
+
following wrangler — instead of an unpinned unpkg URL that resolved to whatever was newest, so an
|
|
98
|
+
editor validated a pinned project against a schema it wasn't built with.
|
|
99
|
+
- **`favicon.backgroundColor` and `favicon.name` are described accurately.** `backgroundColor` is
|
|
100
|
+
not manifest-only: it fills the opaque `apple-touch-icon.png` and `icon-mask.png`, which are
|
|
101
|
+
written whether or not a manifest is. `name` documents that it and `themeColor` **together** are
|
|
102
|
+
what make a site installable. The missing-background warning now also fires for a dark opaque mark,
|
|
103
|
+
not only a transparent source — `apple-touch-icon` insets the logo and fills the surround either
|
|
104
|
+
way.
|
|
105
|
+
|
|
106
|
+
- **The framework now says out loud which patterns are foundational, and reports it when you
|
|
107
|
+
reach past them.** From repeated downstream reports: sites were inventing a `.wrap` class and
|
|
108
|
+
using it everywhere instead of `.centered`, and `.subgrid` — the class for any set of cards —
|
|
109
|
+
was going essentially unused. Neither failure is visible in a build: every class involved is
|
|
110
|
+
real, nothing errors, and the page renders.
|
|
111
|
+
|
|
112
|
+
**`.subgrid` was missing from the generated class reference entirely.** So was `.cluster`, and
|
|
113
|
+
so was `.region`. `vitops docs classes` is the doc an agent is told to fetch for "which class
|
|
114
|
+
do I apply", and the framework's answer for a set of cards was not in it — which is most of why
|
|
115
|
+
it never got used. All three are now documented, along with `grid-auto`, and the reference opens
|
|
116
|
+
with a **Foundations** section that states the six substitutions as
|
|
117
|
+
_temptation → what to write instead_ rather than as a vocabulary list. The same table is now in
|
|
118
|
+
the shipped agent skill and in the `@getvitops/create` template's `AGENTS.md`, because a pointer
|
|
119
|
+
to a doc only helps someone who already suspects there is something to look up. (`css`/`bricks`
|
|
120
|
+
and `tailwind` alike — this is documentation and the `TIERS` manifest, not emitted CSS.)
|
|
121
|
+
|
|
122
|
+
**`vitops lint` gained three reuse rules and a markup pass.** The existing `.centered` rule only
|
|
123
|
+
fired when the hand-written CSS already referenced `--width-measure`, which is precisely the
|
|
124
|
+
author who was never going to hand-roll a container. It now also catches a page-scale
|
|
125
|
+
`max-width` (≥ 48rem, or a `ch` reading measure) with auto margins, and a
|
|
126
|
+
container-shaped class name — `wrap`, `wrapper`, `container`, `inner`, `shell`, … — carrying any
|
|
127
|
+
width cap at all. Reading the cap out of `min()` / `clamp()` too. New rules report a
|
|
128
|
+
hand-written `repeat()` grid as `.subgrid` (or `.grid-auto`), and a new **markup** pass reports a
|
|
129
|
+
repeated card set laid out without either — a loop that renders a card, or three or more cards
|
|
130
|
+
written out. That last one is the only check that can see this drift at all, since a card list
|
|
131
|
+
built from utility classes contains no bad class and no hand-written CSS.
|
|
132
|
+
|
|
133
|
+
All of these are `suggestion` severity, so they do not fail your build unless you pass
|
|
134
|
+
`--strict`. They found five real instances in this repo's own docs site.
|
|
135
|
+
|
|
136
|
+
**`vitops lint [files...]`** now takes explicit paths instead of scanning `--src`, so it can be
|
|
137
|
+
wired into a pre-commit hook — `vp`'s `staged` key appends the staged files to whatever it runs,
|
|
138
|
+
and a command that refused positionals could not be put in the one place the feedback lands at
|
|
139
|
+
the moment the code is written. Unreadable and non-source paths are skipped rather than fatal. The
|
|
140
|
+
`@getvitops/create` emdash template wires it up with `--strict` and adds a `lint:design` script.
|
|
141
|
+
|
|
142
|
+
**Two answers for "the whole card is a link", because one cannot exist.** Reported downstream:
|
|
143
|
+
agents dislike the `<li>` wrapper when a card is a link and write `<li><a class="card">` instead.
|
|
144
|
+
That shape is **wrong in a way that renders fine**, which is why it survives review — the `<li>`
|
|
145
|
+
is the grid item, so the anchor is an ordinary block inside it and the tranches within the anchor
|
|
146
|
+
never reach the parent's shared row lines. The alignment `.subgrid` exists for silently does not
|
|
147
|
+
happen, and the anchor does not fill the cell either. Putting the anchor in the grid's place
|
|
148
|
+
(`<ul><a></ul>`) is invalid HTML, so there was genuinely no correct shape to reach for.
|
|
149
|
+
|
|
150
|
+
There are now two, and they trade against each other because **no CSS-only technique can make a
|
|
151
|
+
whole card clickable and leave its text selectable** — a transparent overlay necessarily receives
|
|
152
|
+
the pointer-drag:
|
|
153
|
+
|
|
154
|
+
| Want | Use |
|
|
155
|
+
| ---------------------------------------- | ------------------------------------------- |
|
|
156
|
+
| zero JS, whole card clickable | `.stretched-link` — **text not selectable** |
|
|
157
|
+
| selectable text **and** a clickable card | `<Cards>` / `<wc-cards>` — needs JS |
|
|
158
|
+
|
|
159
|
+
`.stretched-link` goes on a link inside the card; its `::after` covers the card.
|
|
160
|
+
**`<wc-cards>` is a new tier-2 element** that adds no overlay and instead tells a click apart from
|
|
161
|
+
the end of a drag, so text selection survives. Its fallback is the card's own link, fully usable
|
|
162
|
+
with no JS, and the pointer cursor is applied by the element — so the affordance never appears
|
|
163
|
+
without the behaviour. `<Cards>` emits it for you. They are alternatives, never layered: with an
|
|
164
|
+
overlay present the JS has nothing to do, and `vitops lint` reports the combination. (All formats
|
|
165
|
+
— pattern partials, deliberately not `utilities.css`, which the tailwind format skips on the
|
|
166
|
+
invariant that everything left in it is a name Tailwind ships itself.)
|
|
167
|
+
|
|
168
|
+
### Breaking
|
|
169
|
+
- **`<Subgrid />` renders its slot verbatim — author the `<li>` items yourself.**
|
|
170
|
+
|
|
171
|
+
```diff
|
|
172
|
+
<Subgrid>
|
|
173
|
+
- <article class="card">…</article>
|
|
174
|
+
+ <li class="card subgrid-card">…</li>
|
|
175
|
+
</Subgrid>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
It used to parse the slotted HTML and rebuild each child as an `<li>`, carrying over only
|
|
179
|
+
`class` and `style`. That existed solely because a `<ul>` may contain nothing else, and it cost
|
|
180
|
+
more than it bought: the child's tag was discarded, `id`/`data-*`/`aria-*` were silently
|
|
181
|
+
dropped, and `href` could not survive at all. Nothing is copied now, so nothing is lost. `as`
|
|
182
|
+
picks the container (`ul` by default, `ol`, `div`), and all other props are forwarded — they
|
|
183
|
+
were previously discarded, including `role`.
|
|
184
|
+
|
|
185
|
+
- **`<Subgrid />` and `<Tree />` now emit `role="list"`,** as does the Bricks `sitenav` element.
|
|
186
|
+
`list-style: none` stops Safari + VoiceOver announcing a `<ul>` as a list, so every marker-less
|
|
187
|
+
framework list was silently losing the semantics its `<ul>` was chosen for. If you hand-write
|
|
188
|
+
`.subgrid`, `.list`, `.facet-list`, `.nav-items`, `.collapse-menu` or `.tree` markup, add
|
|
189
|
+
`role="list"` yourself — the framework cannot add it to markup it does not render, and each
|
|
190
|
+
partial now says so at the `list-style` reset.
|
|
191
|
+
- **`<Cards />` no longer adds `class="card"` to slotted children,** because it no longer parses
|
|
192
|
+
them. Write the class on the item. It now emits `<wc-cards>` around a `<Subgrid>`, which is what
|
|
193
|
+
makes the whole card clickable — and like `<Tree />`, **it emits its own element, so do not wrap
|
|
194
|
+
it in `<wc-cards>` yourself.** It also previously passed a `role="list"` that `<Subgrid>` silently
|
|
195
|
+
discarded, so that never took effect.
|
|
196
|
+
- **`.subgrid-card` now sets `position: relative`**, so `stretched-link` inside it needs no extra
|
|
197
|
+
class. If you were absolutely positioning a descendant of a `.subgrid-card` against an ancestor
|
|
198
|
+
_outside_ that card, it will now resolve against the card. Move the positioning context
|
|
199
|
+
explicitly. (`css`/`bricks`/`tailwind` — the pattern partial is inlined into all three.) Not
|
|
200
|
+
reachable by `vitops lint --fix`: the fix depends on which ancestor you meant, which the
|
|
201
|
+
linter cannot know.
|
|
202
|
+
|
|
203
|
+
### Fixed
|
|
204
|
+
- **`packages/generator/src/docs.ts` contained a literal NUL byte** (`CODE_SLOT`, written as the
|
|
205
|
+
raw character rather than an escape). libmagic classified the file as `data` rather than text, so
|
|
206
|
+
grep and ripgrep treated the largest doc emitter in the repo as **binary and silently skipped
|
|
207
|
+
it** — every search for a string in it returned nothing, with no error. It is now written as the `\u0000` escape.
|
|
208
|
+
Behaviour is identical; the file is searchable.
|
|
209
|
+
- **`.grid-auto` is documented.** It was a real framework class for auto-fit card grids with no
|
|
210
|
+
entry in the class reference, so the honest alternative to `subgrid` was as invisible as
|
|
211
|
+
`subgrid` itself.
|
|
212
|
+
- **`.raised` replaces the advice to use `.relative` above a `stretched-link` overlay**, which
|
|
213
|
+
could not work: a positioned element at `z-index: auto` does not rise above an explicit
|
|
214
|
+
`z-index` regardless of DOM order, so a second link or button in the card stayed underneath and
|
|
215
|
+
unclickable. `.raised` sets both, against the same `--z-tier-raised` token the overlay now uses
|
|
216
|
+
instead of a hard-coded `1`.
|
|
217
|
+
|
|
218
|
+
### Patch Changes
|
|
219
|
+
|
|
220
|
+
- **Astro and Bricks are documented as the same tier, because they are.** Both are platform
|
|
221
|
+
wrappers that generate HTML from the classes and elements of tiers 1 and 2 — siblings chosen by
|
|
222
|
+
which platform you are on, not steps in a ladder. No project uses both, and neither outranks the
|
|
223
|
+
other.
|
|
224
|
+
|
|
225
|
+
They were published as tier 3 and **tier 4**, in `vitops docs components`, the shipped agent
|
|
226
|
+
skill and the docs site. The cause was a projection axis being read as a hierarchy: `Tier` has
|
|
227
|
+
four keys (`css` `wc` `astro` `bricks`) because there are four tables to render, and the tier
|
|
228
|
+
number was being derived from that key order — so the last key became "tier 4". The advice next
|
|
229
|
+
to it inherited the error, telling readers to "use the highest-**numbered** tier your stack has",
|
|
230
|
+
which reads as Bricks outranking Astro.
|
|
231
|
+
|
|
232
|
+
Nothing about which patterns each tier provides has changed; this is the numbering and the prose
|
|
233
|
+
around it. `Tier` keeps its four keys — that part was never wrong — and now says in its own type
|
|
234
|
+
docs why four keys are three levels, so the next renderer cannot repeat the derivation.
|
|
235
|
+
|
|
236
|
+
- Updated dependencies
|
|
237
|
+
- @getvitops/utils@5.0.0
|
|
238
|
+
|
|
239
|
+
## 4.1.0
|
|
240
|
+
|
|
241
|
+
### Minor Changes
|
|
242
|
+
|
|
243
|
+
- 2c890c0: Link a site to its ad properties: `site.ads`, `vitops ads`, and `<Ads />`.
|
|
244
|
+
|
|
245
|
+
`vitops search` covers the whole Search Console relationship. Nothing covered the equivalent one
|
|
246
|
+
with ad platforms, and a site's ad accounts had nowhere to live — so a Meta pixel pasted into a
|
|
247
|
+
template was invisible to the rest of the toolchain. It set `_fbp` on a site whose generated cookie
|
|
248
|
+
notice never mentioned it, whose consent gate never cleared it on revoke, and — for LinkedIn and
|
|
249
|
+
Pinterest — whose attribution never captured the click ID at all, so every conversion from those
|
|
250
|
+
platforms arrived indistinguishable from organic traffic.
|
|
251
|
+
|
|
252
|
+
**New `site.ads` block**, keyed by platform (`google`, `meta`, `linkedin`, `reddit`, `tiktok`,
|
|
253
|
+
`microsoft`, `pinterest`, `snapchat`):
|
|
254
|
+
|
|
255
|
+
```jsonc
|
|
256
|
+
{
|
|
257
|
+
"site": {
|
|
258
|
+
"ads": {
|
|
259
|
+
"meta": { "pixelId": "123456789", "domainVerification": "abc123" },
|
|
260
|
+
"google": {
|
|
261
|
+
"accountId": "123-456-7890",
|
|
262
|
+
"pixelId": "AW-987654321",
|
|
263
|
+
"conversionLabel": "xyz",
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**`vitops ads setup`** ensures each platform's domain-verification DNS record. Only four platforms
|
|
271
|
+
verify a domain at all — Meta, TikTok, Pinterest and Snapchat, by apex DNS TXT — and that record is
|
|
272
|
+
the one thing created for you (in Cloudflare, via `CLOUDFLARE_API_TOKEN`; created only, never edited
|
|
273
|
+
or deleted). Google Ads, LinkedIn, Reddit and Microsoft Ads have no domain verification: linking
|
|
274
|
+
there is the tag and the account id, and the run says so rather than skipping in silence. No
|
|
275
|
+
platform Marketing API is called — Meta's needs a system-user token and Google's an approved
|
|
276
|
+
developer token with your own account on the line — so the final "Verify" click is surfaced as a
|
|
277
|
+
reminder. `--dry` prints the plan, `--check` reports drift and exits non-zero.
|
|
278
|
+
|
|
279
|
+
**It asks for what the config is missing.** A verification token does not exist until someone
|
|
280
|
+
fetches it from the platform UI, so a first run prompts for it, naming the exact UI path, folds the
|
|
281
|
+
answer into the plan and writes it to your config. The token is not a secret — it is published in
|
|
282
|
+
DNS, and the platform fetching it back is the ownership proof, exactly like the IndexNow key.
|
|
283
|
+
Prompting requires a TTY: with `--dry`, `--check`, `--no-prompt`, or in CI, you get a named error
|
|
284
|
+
instead and the run never hangs. `--no-write` keeps the answer out of the config.
|
|
285
|
+
|
|
286
|
+
**`vitops ads tags`** prints each pixel as an inert, consent-gated `<script>` — `type="text/plain"`
|
|
287
|
+
with the library URL on `data-src`, so an undecided visitor's page issues no third-party request.
|
|
288
|
+
For Bricks, WordPress, Eleventy: any stack without the Astro integration.
|
|
289
|
+
|
|
290
|
+
**`vitops ads lint`** reports the gaps that are invisible at runtime: a click ID the platform stamps
|
|
291
|
+
that attribution does not capture, a pixel while `site.tracking` is off, a property with no tag id.
|
|
292
|
+
|
|
293
|
+
**`<Ads />` in `@getvitops/astro`**, a sibling of `<Analytics />` rather than part of it — ad
|
|
294
|
+
properties come from the site config, state their own consent category (`marketing` by default,
|
|
295
|
+
rather than being derived), and switch per environment on their own
|
|
296
|
+
(`environments.<env>.ads`, defaulting to `analytics`, then true, so a preview deployment can send
|
|
297
|
+
pageviews without firing conversion pixels). Both components now render through one `<GatedTags />`,
|
|
298
|
+
so the inert markup has a single implementation.
|
|
299
|
+
|
|
300
|
+
**Your cookie notice now discloses every configured pixel** — name, cookies and opt-out — from the
|
|
301
|
+
same table that writes `data-consent-cookies`, so the notice and the revoke cannot disagree. If you
|
|
302
|
+
add `site.ads` to a config whose privacy policy or cookie notice you have already published,
|
|
303
|
+
re-generate and re-read them.
|
|
304
|
+
|
|
305
|
+
`li_fat_id` (LinkedIn) and `epik` (Pinterest) join the click-ID capture vocabulary; a site running
|
|
306
|
+
either platform starts attributing conversions it previously recorded as organic.
|
|
307
|
+
|
|
308
|
+
- e764090: Separate where a processor stores information from whose law can reach it.
|
|
309
|
+
|
|
310
|
+
A processor carried one optional `country`, and the privacy policy flat-deduped it into a single
|
|
311
|
+
sentence. That conflated two facts a disclosure has to keep apart — **where the data rests** and
|
|
312
|
+
**which jurisdiction can compel access** — and privacy law turns on the second: the OPC's concern
|
|
313
|
+
is foreign _access_, not merely foreign storage.
|
|
314
|
+
|
|
315
|
+
Ordinary arrangements were inexpressible. Azure is data-resident in Canada and US-controlled;
|
|
316
|
+
Zoho can serve mail from a Canadian datacentre and telemetry from the US while being headquartered
|
|
317
|
+
in India; a Microsoft 365 tenant can be Canadian with its identity data in the US. For all of
|
|
318
|
+
them every available option was wrong: `country: "Canada"` rendered the incoherent _"outside of
|
|
319
|
+
Canada, including Canada"_, naming the operator's country asserted storage that wasn't happening,
|
|
320
|
+
and omitting it dropped the processor from the disclosure **silently**.
|
|
321
|
+
|
|
322
|
+
**New on a processor:**
|
|
323
|
+
|
|
324
|
+
```jsonc
|
|
325
|
+
{
|
|
326
|
+
"name": "Zoho",
|
|
327
|
+
"purpose": "receiving and storing email sent to us",
|
|
328
|
+
"storage": [
|
|
329
|
+
{ "country": "Canada", "scope": "mail and productivity data" },
|
|
330
|
+
{ "country": "the United States", "scope": "analytics and telemetry" },
|
|
331
|
+
],
|
|
332
|
+
"operatorCountry": "India",
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
- `storage[]` — where information rests, each entry optionally `scope`d to a category. The scope
|
|
337
|
+
is what makes "Canadian tenant, identity data in the US" sayable.
|
|
338
|
+
- `operatorCountry` — the jurisdiction that can compel the provider to produce it. Gets its own
|
|
339
|
+
sentence: _"…established in, or controlled from, jurisdictions outside of Canada, including
|
|
340
|
+
India. Personal information those providers handle on our behalf may be subject to the laws of
|
|
341
|
+
those jurisdictions even when it is stored in Canada."_
|
|
342
|
+
- `country` still works, as shorthand asserting **both** facts — which is what it always
|
|
343
|
+
asserted, since the sentence it fed claimed storage and legal reach in one breath.
|
|
344
|
+
|
|
345
|
+
**Combining `country` with either explicit field is rejected** by `vitops validate`. Whether the
|
|
346
|
+
shorthand narrows the explicit fact or adds to it are two readings that make contradictory legal
|
|
347
|
+
claims, so neither is guessed.
|
|
348
|
+
|
|
349
|
+
A country in the policy's own jurisdiction is no longer treated as a transfer, which is the
|
|
350
|
+
`country: "Canada"` fix. Comparison ignores case and a leading "the".
|
|
351
|
+
|
|
352
|
+
### This changes text you have already published
|
|
353
|
+
|
|
354
|
+
**Cloudflare, Cloudflare Turnstile, Vercel and Netlify now assert operator jurisdiction and no
|
|
355
|
+
storage.** They previously claimed `the United States` into a sentence saying "stored or processed
|
|
356
|
+
in" — a claim about the wrong fact. Cloudflare is anycast: a request from Toronto is answered from
|
|
357
|
+
a Toronto PoP, and Workers/R2 have residency controls the config cannot see.
|
|
358
|
+
|
|
359
|
+
So a site whose only foreign element is its host or Turnstile **stops claiming foreign storage**
|
|
360
|
+
and instead discloses foreign legal reach. That is a retraction in the direction of accuracy, and
|
|
361
|
+
it deserves a re-read. **If you pin a US region, say so** — declare a processor with
|
|
362
|
+
`storage: [{ country: "the United States" }]`. This generalises the rule the provider table
|
|
363
|
+
already applied to Matomo: "we don't know" is a fact.
|
|
364
|
+
|
|
365
|
+
**Everything else renders identically.** Google Analytics, Tag Manager, Clarity, Plausible,
|
|
366
|
+
Matomo Cloud and any processor you declared with `country` produce the same words as before,
|
|
367
|
+
because the shorthand expands to both facts and the operator sentence is suppressed when it would
|
|
368
|
+
only restate a country already disclosed as storage. The one cosmetic change: the closing
|
|
369
|
+
"subject to access requests…" sentence is now its own paragraph, since it is shared by both
|
|
370
|
+
clauses.
|
|
371
|
+
|
|
372
|
+
**A processor with no location at all is now reported** on stderr by `vitops legal`, naming the
|
|
373
|
+
document it will be missing from. It cannot appear in a transfer disclosure — there is nothing
|
|
374
|
+
true to say — but it used to vanish in silence, which is the failure that looks tidy. It is not a
|
|
375
|
+
validation error: a bare `{ name, purpose }` is still valid.
|
|
376
|
+
|
|
377
|
+
New exports from `@getvitops/generator`: `processorsMissingLocation`, `JURISDICTION_COUNTRIES`,
|
|
378
|
+
and the `ProcessorStorage` type. `PolicyVars` gains `jurisdictionCountry`, `storageCountries`,
|
|
379
|
+
`scopedStorage` and `operatorCountries`; `countries` is **deprecated but retained**, and still
|
|
380
|
+
names every country it ever named, so a custom template keeps rendering what it rendered.
|
|
381
|
+
|
|
382
|
+
### Patch Changes
|
|
383
|
+
|
|
384
|
+
- Updated dependencies [2c890c0]
|
|
385
|
+
- @getvitops/utils@4.1.0
|
|
386
|
+
|
|
3
387
|
## 4.0.0
|
|
4
388
|
|
|
5
389
|
### Major Changes
|
|
@@ -203,7 +203,10 @@ class Vitops_Element_Sitenav extends \Bricks\Element {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
$list_class = $top ? 'sitenav__list' : 'sitenav__submenu';
|
|
206
|
-
|
|
206
|
+
// `role="list"` restores what `list-style: none` takes away: Safari + VoiceOver
|
|
207
|
+
// stop announcing a marker-less <ul> as a list, so the reset silently costs the
|
|
208
|
+
// semantics the <ul> was chosen for. Both classes set it (sitenav.css).
|
|
209
|
+
$html = '<ul class="' . $list_class . '" role="list">';
|
|
207
210
|
|
|
208
211
|
foreach ( $by_parent[ $parent_id ] as $item ) {
|
|
209
212
|
$id = (int) $item->ID;
|
package/assets/css/index.css
CHANGED
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
@import './patterns/cluster.css';
|
|
17
17
|
@import './patterns/grouped.css';
|
|
18
18
|
@import './patterns/subgrid.css';
|
|
19
|
+
/* After subgrid: `.subgrid-card` is the usual card and this sets the `<wc-cards>`
|
|
20
|
+
host plus the upgraded-card affordance. */
|
|
21
|
+
@import './patterns/card.css';
|
|
19
22
|
@import './patterns/masonry.css';
|
|
20
23
|
@import './patterns/media.css';
|
|
21
24
|
@import './patterns/separator.css';
|
|
@@ -100,3 +100,70 @@ a[target='_blank'].link--external::after {
|
|
|
100
100
|
color: var(--color-text-surface);
|
|
101
101
|
border-radius: var(--br-control, 0.375rem);
|
|
102
102
|
}
|
|
103
|
+
|
|
104
|
+
/* ── stretched-link — the whole card is a link ──
|
|
105
|
+
One link inside a container covers it, so the entire card is clickable while the
|
|
106
|
+
accessible name stays on real text. `.subgrid-card` already carries the
|
|
107
|
+
`position: relative` this resolves against; on a bare `.card`, add `relative`.
|
|
108
|
+
|
|
109
|
+
<li class="card subgrid-card">
|
|
110
|
+
<p class="font-eyebrow">Service</p>
|
|
111
|
+
<p class="font-heading"><a class="link stretched-link" href="/x">Title</a></p>
|
|
112
|
+
<p>Blurb…</p>
|
|
113
|
+
</li>
|
|
114
|
+
|
|
115
|
+
WHY THIS RATHER THAN MAKING THE ANCHOR THE CARD. `<li><a class="card">` is the
|
|
116
|
+
shape that gets reached for, and it is wrong in a way that renders fine: the LI is
|
|
117
|
+
the grid item, so the anchor is an ordinary block inside it — its tranches never
|
|
118
|
+
land on the parent's shared row lines, and the alignment `.subgrid` exists for
|
|
119
|
+
silently does not happen. (Nor does an inline anchor fill the cell.) Putting the
|
|
120
|
+
anchor in the grid's place instead means `<ul><a></ul>`, which is invalid.
|
|
121
|
+
|
|
122
|
+
THE COST, AND THE ALTERNATIVE. The overlay receives the pointer-drag over
|
|
123
|
+
everything it covers, so **the card's text can no longer be selected**. That is
|
|
124
|
+
inherent to the technique, not a bug to fix here — any transparent layer over text
|
|
125
|
+
takes the drag. Where selectable text matters, use `<wc-cards>`
|
|
126
|
+
(src/web-components/WCCards.ts) instead: no overlay, and JS tells a click apart
|
|
127
|
+
from the end of a drag. The two are alternatives, never layered — with this
|
|
128
|
+
overlay present, that element has nothing left to do.
|
|
129
|
+
|
|
130
|
+
It lives HERE, in a pattern partial, and not in `utilities.css`: that file is in
|
|
131
|
+
the generator's TAILWIND_SKIP on the stated invariant that everything left in it
|
|
132
|
+
is a TW_CLASH name Tailwind ships itself. `stretched-link` is ours, so putting it
|
|
133
|
+
there would emit it in css/bricks and silently not in tailwind — the exact
|
|
134
|
+
cross-format hole `format-parity.test.ts` exists to catch.
|
|
135
|
+
|
|
136
|
+
Another interactive element inside the same card must sit above the overlay: give
|
|
137
|
+
it `.raised`, NOT `.relative`. A positioned element at `z-index: auto` does not
|
|
138
|
+
rise above an explicit z-index no matter its DOM order, so `.relative` alone left
|
|
139
|
+
the control underneath and unclickable. ── */
|
|
140
|
+
.stretched-link::after {
|
|
141
|
+
content: '';
|
|
142
|
+
position: absolute;
|
|
143
|
+
/* Covers the nearest positioned ancestor, which is the card. */
|
|
144
|
+
inset: 0;
|
|
145
|
+
/* The shared z-scale rather than a bare 1, so `.raised` can be defined in terms of
|
|
146
|
+
the same token instead of guessing a number that beats it. */
|
|
147
|
+
z-index: var(--z-tier-raised);
|
|
148
|
+
border-radius: inherit;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* ── raised — lift content above a `.stretched-link` overlay ──
|
|
152
|
+
For the second link, the button, or the text you want selectable inside a card
|
|
153
|
+
whose whole area is otherwise clickable.
|
|
154
|
+
|
|
155
|
+
It carries `position: relative` AND a z-index, and both are required: z-index has
|
|
156
|
+
no effect on a static element, and a positioned element at `z-index: auto` does not
|
|
157
|
+
rise above an explicit z-index regardless of DOM order. That second half is why
|
|
158
|
+
telling people to use `.relative` here did not work.
|
|
159
|
+
|
|
160
|
+
Defined against the same token as the overlay, so the two cannot drift apart the
|
|
161
|
+
way two hand-picked numbers would.
|
|
162
|
+
|
|
163
|
+
Here rather than in `utilities.css` for the reason given above `.stretched-link`:
|
|
164
|
+
that file is in TAILWIND_SKIP on the invariant that everything left in it is a name
|
|
165
|
+
Tailwind ships itself, and Tailwind has no `raised`. ── */
|
|
166
|
+
.raised {
|
|
167
|
+
position: relative;
|
|
168
|
+
z-index: calc(var(--z-tier-raised) + 1);
|
|
169
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Card — the `<wc-cards>` host and the marker its enhancement applies.
|
|
3
|
+
*
|
|
4
|
+
* The `.card` pattern itself is CONFIG-GENERATED (`patterns.items.card` in
|
|
5
|
+
* design-system.json), so its border, padding, background and shadow are not here.
|
|
6
|
+
* This partial holds only what the framework owns: the custom element's box, and the
|
|
7
|
+
* affordance for a card the element has taken over.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* ── wc-cards — the whole-card click enhancement (src/web-components/WCCards.ts) ──
|
|
11
|
+
A custom element defaults to `display: inline`, and this one wraps a block
|
|
12
|
+
container — a `<ul class="subgrid">` in the documented shape — so an inline box
|
|
13
|
+
around a block child is exactly the KNOWN GAP recorded in `js/elements.ts`.
|
|
14
|
+
|
|
15
|
+
`block`, matched to the content it wraps, following the one precedent:
|
|
16
|
+
`patterns/tree.css` declares `wc-tree { display: block }` around a
|
|
17
|
+
`<ul class="tree">`. Deliberately NOT `display: contents`, whose support has been
|
|
18
|
+
uneven — the accessibility tree in particular — and this is not a place to depend
|
|
19
|
+
on it.
|
|
20
|
+
|
|
21
|
+
The cost of a real box: where the grid is nested in a parent flex/grid, `wc-cards`
|
|
22
|
+
rather than the `<ul>` becomes the item. Restyle the element in that case; it
|
|
23
|
+
carries no other styling to lose. ── */
|
|
24
|
+
wc-cards {
|
|
25
|
+
display: block;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ── The card the element has taken over ──
|
|
29
|
+
Written by `WCCards` at upgrade, and only onto cards it found a link in — so the
|
|
30
|
+
pointer cursor cannot appear on a card with nothing to click, and cannot appear at
|
|
31
|
+
all when `elements.js` never loads. That is the point of stamping it from JS
|
|
32
|
+
rather than authoring it: the affordance and the behaviour arrive together. ── */
|
|
33
|
+
[data-card-link] {
|
|
34
|
+
cursor: pointer;
|
|
35
|
+
}
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
--_ds: var(--ds-list, var(--ds-list-group, var(--ds-default)));
|
|
12
12
|
--_fs: var(--fs-list, var(--fs-list-group, var(--fs-default)));
|
|
13
13
|
|
|
14
|
+
/* Markerless — so ADD `role="list"` to the element. Safari + VoiceOver stop
|
|
15
|
+
announcing a marker-less <ul> as a list, so this reset silently costs the
|
|
16
|
+
semantics the <ul> was chosen for. Nothing in the framework emits this markup,
|
|
17
|
+
so only the author can restore it. */
|
|
14
18
|
list-style: none;
|
|
15
19
|
margin: 0;
|
|
16
20
|
padding: var(--_p);
|
|
@@ -75,6 +79,10 @@
|
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
.facet-list {
|
|
82
|
+
/* Markerless — so ADD `role="list"` to the element. Safari + VoiceOver stop
|
|
83
|
+
announcing a marker-less <ul> as a list, so this reset silently costs the
|
|
84
|
+
semantics the <ul> was chosen for. Nothing in the framework emits this markup,
|
|
85
|
+
so only the author can restore it. */
|
|
78
86
|
list-style: none;
|
|
79
87
|
margin: 0;
|
|
80
88
|
padding: 0;
|
|
@@ -30,6 +30,10 @@ nav:has(> .nav-items) {
|
|
|
30
30
|
display: flex;
|
|
31
31
|
flex-direction: column;
|
|
32
32
|
gap: 1.5rem;
|
|
33
|
+
/* Markerless — so ADD `role="list"` to the element. Safari + VoiceOver stop
|
|
34
|
+
announcing a marker-less <ul> as a list, so this reset silently costs the
|
|
35
|
+
semantics the <ul> was chosen for. Nothing in the framework emits this markup,
|
|
36
|
+
so only the author can restore it. */
|
|
33
37
|
list-style: none;
|
|
34
38
|
padding: 0;
|
|
35
39
|
margin: 0;
|
|
@@ -99,6 +103,10 @@ nav:has(> .nav-items) {
|
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
.collapse-menu {
|
|
106
|
+
/* Markerless — so ADD `role="list"` to the element. Safari + VoiceOver stop
|
|
107
|
+
announcing a marker-less <ul> as a list, so this reset silently costs the
|
|
108
|
+
semantics the <ul> was chosen for. Nothing in the framework emits this markup,
|
|
109
|
+
so only the author can restore it. */
|
|
102
110
|
list-style: none;
|
|
103
111
|
padding-inline-start: 1.5rem;
|
|
104
112
|
margin-block-start: 0.5rem;
|
|
@@ -63,7 +63,12 @@
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
/* Markerless when it's a list — a subgrid of cards is a list of things, and
|
|
66
|
-
the markers would sit in the gutter.
|
|
66
|
+
the markers would sit in the gutter.
|
|
67
|
+
|
|
68
|
+
ADD `role="list"` to the element. Safari + VoiceOver stop announcing a
|
|
69
|
+
marker-less <ul> as a list, so this reset silently costs the semantics the <ul>
|
|
70
|
+
was chosen for — "3 items" becomes nothing at all. `<Subgrid>` does it for you;
|
|
71
|
+
hand-written markup must say it. */
|
|
67
72
|
&:is(ul, ol) {
|
|
68
73
|
list-style: none;
|
|
69
74
|
margin: 0;
|
|
@@ -111,6 +116,16 @@
|
|
|
111
116
|
display: grid;
|
|
112
117
|
grid-template-rows: subgrid;
|
|
113
118
|
grid-row: span var(--_card-rows);
|
|
119
|
+
/* The containing block for a `.stretched-link` inside it (utilities.css), so a
|
|
120
|
+
whole-card link needs no extra class here. Costs nothing otherwise.
|
|
121
|
+
|
|
122
|
+
This is the framework's answer to "the card IS a link", and the shape it
|
|
123
|
+
replaces is wrong in a way that looks fine: `<li><a class="card">` makes the
|
|
124
|
+
LI the grid item, so the anchor is an ordinary block and the tranches inside
|
|
125
|
+
it never land on the shared row lines — the alignment subgrid exists for
|
|
126
|
+
silently does not happen, and the anchor doesn't fill the cell either. Keep
|
|
127
|
+
the card as the grid item and stretch a link inside it. */
|
|
128
|
+
position: relative;
|
|
114
129
|
padding: var(--_card-padding);
|
|
115
130
|
border: var(--b-card, var(--b-card-group, var(--b-default)));
|
|
116
131
|
border-radius: var(--br-card, var(--br-card-group, var(--br-default)));
|
package/assets/css/utilities.css
CHANGED
|
@@ -272,3 +272,15 @@
|
|
|
272
272
|
.text-pretty {
|
|
273
273
|
text-wrap: pretty;
|
|
274
274
|
}
|
|
275
|
+
|
|
276
|
+
/* ── position — a containing block for anything absolutely positioned inside, e.g.
|
|
277
|
+
a `.stretched-link` overlay on a bare `.card` (`.subgrid-card` already has one).
|
|
278
|
+
`relative` is in TW_CLASH: Tailwind ships it natively, so the tailwind format
|
|
279
|
+
drops this copy.
|
|
280
|
+
|
|
281
|
+
To lift content ABOVE a stretched-link overlay this is not enough — see `.raised`
|
|
282
|
+
in patterns/anchor-link.css, which has to live there because Tailwind has no
|
|
283
|
+
equivalent name and this file is in TAILWIND_SKIP. ── */
|
|
284
|
+
.relative {
|
|
285
|
+
position: relative;
|
|
286
|
+
}
|