@getvitops/generator 0.8.0 → 1.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 +459 -0
- package/README.md +16 -4
- package/assets/bricks/load.php +46 -1
- package/assets/css/animation.css +12 -2
- package/assets/css/patterns/carousel.css +3 -3
- package/assets/css/patterns/code.css +2 -2
- package/assets/css/patterns/combobox.css +3 -3
- package/assets/css/patterns/comment.css +1 -1
- package/assets/css/patterns/copy.css +3 -3
- package/assets/css/patterns/details.css +27 -8
- package/assets/css/patterns/dialog.css +2 -2
- package/assets/css/patterns/drawer.css +17 -2
- package/assets/css/patterns/forms.css +11 -11
- package/assets/css/patterns/grouped.css +1 -1
- package/assets/css/patterns/icon.css +2 -2
- package/assets/css/patterns/list.css +5 -5
- package/assets/css/patterns/media.css +1 -1
- package/assets/css/patterns/nav.css +5 -5
- package/assets/css/patterns/notification.css +3 -3
- package/assets/css/patterns/scroll-based.css +1 -1
- package/assets/css/patterns/scroll-target.css +3 -3
- package/assets/css/patterns/sitenav.css +14 -14
- package/assets/css/patterns/split-link.css +4 -4
- package/assets/css/patterns/subgrid.css +62 -16
- package/assets/css/patterns/tag.css +1 -1
- package/assets/css/patterns/text-effects.css +1 -1
- package/assets/css/patterns/theme-editor.css +14 -6
- package/assets/css/patterns/tooltip.css +2 -2
- package/assets/css/patterns/tree.css +3 -3
- package/assets/css/utilities.css +5 -3
- package/assets/js/editor.js +2 -2
- package/dist/index.d.mts +565 -50
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3337 -1301
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/schema.json +51 -9
- package/site.schema.json +149 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,464 @@
|
|
|
1
1
|
# @getvitops/generator
|
|
2
2
|
|
|
3
|
+
## 1.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- bb92a14: **The colour system is rebuilt on a target-prefixed grammar and a shared lightness ladder.**
|
|
8
|
+
This is a breaking change to every colour token and utility class.
|
|
9
|
+
|
|
10
|
+
## Why
|
|
11
|
+
|
|
12
|
+
Two axes shared one class namespace — functional _planes_ (`--<role>-bg-muted`) and
|
|
13
|
+
appearance-relative _stops_ (`--color-<role>-muted`) — arbitrated by a "plane wins" rule. The
|
|
14
|
+
result was not a scale. On the shipped palette, light mode:
|
|
15
|
+
|
|
16
|
+
| class | resolved to | step |
|
|
17
|
+
| ---------------------------------- | ------------ | --------------------------------------------- |
|
|
18
|
+
| `bg-ui-accent-x-muted` | stop | 100 |
|
|
19
|
+
| `bg-ui-accent-muted` | plane | 100 — identical, so `x-muted` was a dead rung |
|
|
20
|
+
| `bg-ui-accent` | plane | 50 — _lighter_ than both its "muted" rungs |
|
|
21
|
+
| `border-ui-accent-muted` / `-bold` | stop / plane | 300 / 300 — identical |
|
|
22
|
+
| `text-ui-accent-bold` | stop | 700 — _lighter_ than `text-…-muted` (800) |
|
|
23
|
+
|
|
24
|
+
Every family was non-monotonic, two had duplicate rungs, and `--color-<role>-muted` was
|
|
25
|
+
unreachable through any `bg-` class.
|
|
26
|
+
|
|
27
|
+
## The new grammar
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
--color-<target>-<role>[-<variant>] target ∈ bg | text | icon | border
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The target is **inside** the name, so `bg-danger-muted` and `text-danger-muted` are different
|
|
34
|
+
tokens and there is nothing left to arbitrate. **The class name is the token name minus
|
|
35
|
+
`--color-`** — one vocabulary instead of two.
|
|
36
|
+
|
|
37
|
+
Variants are ordinal (`xx-muted` < `x-muted` < `muted` < bare < `bold` < `x-bold`) and the
|
|
38
|
+
tables are sparse: only cells that hold their contrast target exist.
|
|
39
|
+
|
|
40
|
+
## Role kinds
|
|
41
|
+
|
|
42
|
+
`colors.roles` values may now be `{ hue, kind }` as well as a bare hue string:
|
|
43
|
+
|
|
44
|
+
```jsonc
|
|
45
|
+
"roles": {
|
|
46
|
+
"danger": "rust", // shorthand => chromatic
|
|
47
|
+
"surface": { "hue": "navy", "kind": "surface" }
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- **`surface`** — a page/panel colour: `bg-<role>` is the card, `bg-<role>-muted` the page
|
|
52
|
+
behind it, `bg-<role>-x-muted` a well, `bg-<role>-bold` the inverse surface. Full text scale.
|
|
53
|
+
- **`chromatic`** (default) — a signal colour: tints (`bg-<role>-x-muted`/`-muted`) and solids
|
|
54
|
+
(`bg-<role>-solid[-bold|-x-bold]`), with **no bare `bg-<role>`** — say how loud you mean.
|
|
55
|
+
`text-on-<role>` is the guaranteed foreground for the solid family.
|
|
56
|
+
|
|
57
|
+
## Palette generation
|
|
58
|
+
|
|
59
|
+
Every ramp now sits on one **fixed lightness ladder** (50 → L 0.98 … 950 → L 0.21); only chroma
|
|
60
|
+
and hue vary. That is what makes a step mean the same lightness in every hue. Previously each
|
|
61
|
+
seed transposed the curve, so relative luminance at step 300 ranged from 0.253 to 0.384 across
|
|
62
|
+
the shipped palette — the same variant read differently depending on the role.
|
|
63
|
+
|
|
64
|
+
Authored colours are still reproduced **exactly**. A `seed`, an `anchors` entry or a `tones`
|
|
65
|
+
value is pinned verbatim at its nearest step; every other step takes the ladder. Snapping brand
|
|
66
|
+
colours to the ladder was measured and rejected: 11 of 18 real brand hexes moved beyond a
|
|
67
|
+
just-noticeable difference (worst ΔE-OK 0.050 — Facebook's `#1877F2` → `#0067e1`) and six left
|
|
68
|
+
sRGB gamut. Deviation is now bounded and local to pinned steps, and warns past ~0.03 L.
|
|
69
|
+
|
|
70
|
+
Two tones that claim the same step now **error** instead of one silently overwriting the other;
|
|
71
|
+
the record form (`tones: { "600": "…", "700": "…" }`) is how you resolve it.
|
|
72
|
+
|
|
73
|
+
## New
|
|
74
|
+
- **`icon-<role>`** — a non-text tier, so a glyph may run more vivid than text. `icon` is now a
|
|
75
|
+
default utility family alongside `bg`/`text`/`border`.
|
|
76
|
+
- **`--color-border-focus`** — the focus-ring tone, taken from `ui-primary`'s solid.
|
|
77
|
+
- **Contrast is enforced at build time**, not only in tests: text ≥ APCA Lc 75 on its primary
|
|
78
|
+
background, ≥ 60 on secondary planes, icons and surface boundaries ≥ 45, both appearances.
|
|
79
|
+
A violation now fails `generate`. Chromatic text is checked against the _surface_ planes it
|
|
80
|
+
actually sits on, not only its own tints. `text-<role>-x-muted` (placeholder) and `-xx-muted`
|
|
81
|
+
(disabled) are explicitly exempt; nothing else is.
|
|
82
|
+
|
|
83
|
+
## Migrating
|
|
84
|
+
|
|
85
|
+
Rename `--<role>-<suffix>` → `--color-<target>-<role>[-<variant>]`, and the same for classes:
|
|
86
|
+
|
|
87
|
+
| before | after |
|
|
88
|
+
| --------------------------------------- | ---------------------------------------------------------------- |
|
|
89
|
+
| `--<role>-bg` / `bg-<role>` (chromatic) | `--color-bg-<role>-x-muted` / `bg-<role>-x-muted` |
|
|
90
|
+
| `--<role>-bg-muted` | `--color-bg-<role>-muted` |
|
|
91
|
+
| `--<role>-solid` / `-solid-bold` | `--color-bg-<role>-solid` / `-solid-bold` |
|
|
92
|
+
| `--<role>-on-solid` / `text-on-<role>` | `--color-text-on-<role>` (class unchanged) |
|
|
93
|
+
| `--<role>-text` / `-text-muted` | `--color-text-<role>` / `-muted` |
|
|
94
|
+
| `--<role>-border` / `-border-bold` | `--color-border-<role>` / `-bold` |
|
|
95
|
+
| `--color-<role>-muted` (stop) | judge by use: `--color-bg-<role>-muted` or `--color-text-<role>` |
|
|
96
|
+
|
|
97
|
+
**`surface` background names rotate**, value-preserving: what was `--surface-bg` (the page) is
|
|
98
|
+
now `--color-bg-surface-muted`; what was `--surface-bg-bold` (raised) is now
|
|
99
|
+
`--color-bg-surface`. Elevation is expressed by which token you reach for — page `bg-muted`,
|
|
100
|
+
card `bg` — rather than a raised/sunken pair, which is what lets a future surfaces axis flatten
|
|
101
|
+
it without touching markup.
|
|
102
|
+
|
|
103
|
+
`vitops lint` reports role classes that no longer resolve, and now derives its suggestions from
|
|
104
|
+
what the generator actually emits rather than a hand-maintained list.
|
|
105
|
+
|
|
106
|
+
### Minor Changes
|
|
107
|
+
|
|
108
|
+
- bb92a14: **Base page typography can now be bound to a type role, and the live editor's Body controls work.**
|
|
109
|
+
|
|
110
|
+
`typography.headings` was documented as an h1…h6 map, but the key has always been used verbatim as
|
|
111
|
+
a selector. Mapping `"body"` to your prose role now binds base page typography to that role:
|
|
112
|
+
|
|
113
|
+
```jsonc
|
|
114
|
+
"typography": {
|
|
115
|
+
"headings": { "body": "body", "h1": "display", "h2": "heading" }
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The generator emits `body { font-family: var(--body-ff, …); line-height: var(--body-lh, …); … }`,
|
|
120
|
+
so prose inherits the role and the role's tokens become the single place it is edited.
|
|
121
|
+
`defaultConfig()` includes the binding; existing configs are unaffected until they add it.
|
|
122
|
+
|
|
123
|
+
This is what made `<wc-theme-editor>`'s **Typography → Body** controls appear dead. They set
|
|
124
|
+
`--body-lh` and friends on `:root`, but only `.font-body` read them — page prose was styled by
|
|
125
|
+
hand-written `body { line-height: 1.55 }` rules that consumers had to write precisely _because_ the
|
|
126
|
+
framework offered no binding. If you keep such a block, drop the properties the role now owns:
|
|
127
|
+
restating them shadows the role's tokens, and because that CSS is typically unlayered it wins.
|
|
128
|
+
|
|
129
|
+
**Fixed: typography edits that previewed live and were dropped on save.** The design manifest's
|
|
130
|
+
`reverseIndex` only mapped hooks a role explicitly declared, while the editor renders a control for
|
|
131
|
+
every hook. Editing a property the role omitted (`--body-ls`, `--body-tt`, …) updated the page and
|
|
132
|
+
then silently vanished from the `design-system.json` patch. Every hook of every role is now indexed.
|
|
133
|
+
|
|
134
|
+
**Added: `validate()` warns when a shadow value can't survive `drop-shadow()`.** A `--shadow-<name>`
|
|
135
|
+
token feeds both `box-shadow` (pattern geometry) and `filter: drop-shadow(…)` (the
|
|
136
|
+
`.drop-shadow-<name>` utilities and the `shadow:` state shortcut). `drop-shadow()` accepts a single
|
|
137
|
+
layer of at most three lengths — a spread radius, a second comma-separated layer or `inset`
|
|
138
|
+
invalidates the whole `filter`, so the utility renders **no** shadow while the token still looks
|
|
139
|
+
correct everywhere it is authored. Keep shadow values in that intersection.
|
|
140
|
+
|
|
141
|
+
- bb92a14: Add a fourth output format, `design`, that emits `DESIGN.md` — the agent-facing brief in
|
|
142
|
+
[google-labs-code/design.md](https://github.com/google-labs-code/design.md) format.
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
vitops generate --format design --out . # DESIGN.md, and nothing else
|
|
146
|
+
vitops generate --format css,design # compose it with a stylesheet
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
The file is YAML front matter carrying the tokens (`colors`, `typography`, `rounded`,
|
|
150
|
+
`spacing`, `components`, cross-referenced with `{group.token}`) followed by a prose body
|
|
151
|
+
carrying the rationale — colour model, fluid scales, layout vocabulary, elevation, shape
|
|
152
|
+
cascade, component tiers, do's and don'ts. Every section is rendered from your config, so
|
|
153
|
+
it cannot describe a system the other formats don't build. Point a coding agent, a Figma
|
|
154
|
+
import, or a designer at this one file when they don't have the toolchain; `vitops docs`
|
|
155
|
+
remains the richer reference for those who do.
|
|
156
|
+
|
|
157
|
+
It is emitted with `--out .` in mind: DESIGN.md conventionally lives at a repo root beside
|
|
158
|
+
`AGENTS.md`, not in a build directory.
|
|
159
|
+
|
|
160
|
+
Three things the spec cannot express, handled the same way every time and explained in the
|
|
161
|
+
emitted prose so the file is self-describing:
|
|
162
|
+
- **Fluid `clamp()` sizes** → the maximum (desktop) value, since a spec `Dimension` is a
|
|
163
|
+
bare number plus px/em/rem.
|
|
164
|
+
- **Dark mode** → light values only, with the automatic functional flip explained. Role
|
|
165
|
+
tokens are emitted as `{colors.<hue>-<step>}` references into the raw ramps rather than
|
|
166
|
+
flattened hexes, so the role → ramp lineage survives the export — flattening them is
|
|
167
|
+
exactly what breaks dark mode downstream.
|
|
168
|
+
- **A `50%` radius** → dropped from `rounded` (it is not a `Dimension`) and named in the
|
|
169
|
+
Shapes prose instead, so nothing is silently lost.
|
|
170
|
+
|
|
171
|
+
**New:** an optional `meta` key in `design-system.json` (`{ name, description }`) supplies
|
|
172
|
+
the brand name and the Overview paragraph. It affects no other format.
|
|
173
|
+
|
|
174
|
+
**New:** `StylesheetFormat` (`Exclude<Format, 'design'>`), exported from
|
|
175
|
+
`@getvitops/generator`. `@getvitops/astro`'s `css.format` now takes that narrower type —
|
|
176
|
+
`design` produces no stylesheet to inject, so passing it there is a type error rather than
|
|
177
|
+
a missing-file failure at build time. `vitops lint --format` is likewise restricted to the
|
|
178
|
+
three CSS formats. No change for anyone already passing `tailwind`, `css` or `bricks`.
|
|
179
|
+
|
|
180
|
+
- bb92a14: Generate legal documents from your site config
|
|
181
|
+
|
|
182
|
+
`vitops legal` renders a privacy policy, terms of service and cookie notice from a site
|
|
183
|
+
config, in markdown, HTML or EmDash Portable Text:
|
|
184
|
+
|
|
185
|
+
```sh
|
|
186
|
+
vitops legal --out ./content # every enabled document, as markdown
|
|
187
|
+
vitops legal --doc privacy --format html # one document, as an HTML fragment
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The documents are **derived from your config**, not filled into a form. The analytics
|
|
191
|
+
provider they name is the one whose ID you set; the personal information they list is what
|
|
192
|
+
your configured forms actually collect; the countries they name come from the providers you
|
|
193
|
+
use. So a provider swap updates the policy on the next build, and the fix for a wrong policy
|
|
194
|
+
is a corrected config — hand-editing the output is overwritten.
|
|
195
|
+
|
|
196
|
+
Enable documents under `legal`, which gains the facts the prose asserts:
|
|
197
|
+
|
|
198
|
+
```jsonc
|
|
199
|
+
{
|
|
200
|
+
"legal": {
|
|
201
|
+
"jurisdiction": "ca", // only 'ca' (PIPEDA) ships today
|
|
202
|
+
"privacyPolicy": {
|
|
203
|
+
"enabled": true,
|
|
204
|
+
"lastUpdated": "2026-08-01",
|
|
205
|
+
"retention": "24 months after our last contact with you",
|
|
206
|
+
// Third parties the config cannot imply. Analytics, Turnstile and your
|
|
207
|
+
// deploy platform are detected automatically — list only the rest.
|
|
208
|
+
"processors": [
|
|
209
|
+
{
|
|
210
|
+
"name": "Stripe",
|
|
211
|
+
"purpose": "payment processing",
|
|
212
|
+
"country": "the United States",
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
"termsOfService": { "enabled": true },
|
|
217
|
+
"cookieConsent": {
|
|
218
|
+
"enabled": true,
|
|
219
|
+
"type": "opt-in",
|
|
220
|
+
"categories": ["Essential", "Analytics"],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Delivery, by stack:
|
|
227
|
+
- **Any stack** — `vitops legal`. No integration code; prints to stdout without `--out`.
|
|
228
|
+
- **WordPress/Bricks** — `vitops generate --site <path>` also writes `dist/legal/*.html`, and
|
|
229
|
+
the theme loader now registers `[vitops_legal doc="privacy"]` to render one in a page. The
|
|
230
|
+
document updates on the next deploy with no action in WordPress.
|
|
231
|
+
- **Astro** — `getvitops({ legal: { input: 'site.json', out: 'src/content/legal' } })` writes
|
|
232
|
+
markdown into a content collection and re-renders when the site config changes. It needs a
|
|
233
|
+
`css` config (that is what registers the Vite plugin); without one, use the CLI.
|
|
234
|
+
- **EmDash** — `--format portable-text`, pasted into the admin.
|
|
235
|
+
|
|
236
|
+
Also new on the public API: `generateLegal()`, `renderMarkdown()`, `renderNodes()`,
|
|
237
|
+
`derivePolicyVars()`, `parseMarkdown()` / `toHtmlFragment()` / `toPortableText()`, and
|
|
238
|
+
`resolvePrivacyContact()`.
|
|
239
|
+
|
|
240
|
+
Two things to know before you publish anything this produces:
|
|
241
|
+
- **It is not legal advice.** Every document opens with a review banner saying so. The
|
|
242
|
+
bundled terms-of-service prose in particular is generic website boilerplate and
|
|
243
|
+
deliberately does not cover sales, refunds, subscriptions, accounts or user-generated
|
|
244
|
+
content — a site doing any of those needs clauses drafted for it.
|
|
245
|
+
- **It is only as true as your config.** A policy asserting things your site does not do is
|
|
246
|
+
worse than no policy. Check that the config describes reality before you ship the output.
|
|
247
|
+
|
|
248
|
+
`validateSite` now rejects a config that enables a privacy policy without a contact for
|
|
249
|
+
privacy requests or a `domains.canonical`, since both are interpolated into sentences that
|
|
250
|
+
would otherwise render blank.
|
|
251
|
+
|
|
252
|
+
- eeb059f: **A pattern's fill can now be undone — flat, border-only cards.**
|
|
253
|
+
|
|
254
|
+
`background` was the only `base` property with no per-pattern override hook. Every other one had
|
|
255
|
+
`--p-card`, `--br-card`, `--b-card`, `--ds-card`; the background was hard-wired, so a flat card
|
|
256
|
+
meant an inline `style="background: transparent"` rather than the documented mechanism.
|
|
257
|
+
|
|
258
|
+
Two additions:
|
|
259
|
+
- **`background` and `background-color` join `BASE_HOOK`**, both mapping to `bg`, since patterns
|
|
260
|
+
author either spelling. `.card` now emits
|
|
261
|
+
`background: var(--bg-card, var(--surface-bg))`. This also covers the fill the generator
|
|
262
|
+
_injects_ for a `default_role` pattern, so `.cta` gets `--bg-cta`.
|
|
263
|
+
|
|
264
|
+
Role variants (`.card-danger`) are emitted as separate rules and stay unwrapped, so tuning
|
|
265
|
+
`--bg-card` adjusts the default fill without silently defeating them.
|
|
266
|
+
|
|
267
|
+
- **`bg-transparent` and `bg-inherit` utilities** in the `css` and `bricks` formats. Neither is
|
|
268
|
+
derived from the palette, so neither the generated scale nor Bricks' palette import produced
|
|
269
|
+
them. The `tailwind` format deliberately emits neither — Tailwind ships both as built-ins, the
|
|
270
|
+
same deferral it makes for the `TW_CLASH` names.
|
|
271
|
+
|
|
272
|
+
```html
|
|
273
|
+
<!-- via the hook — set it anywhere, including :root for every card -->
|
|
274
|
+
<div class="card" style="--bg-card: transparent; --ds-card: none">…</div>
|
|
275
|
+
|
|
276
|
+
<!-- or compose the utility, which is what most authors will reach for -->
|
|
277
|
+
<div class="card bg-transparent" style="--ds-card: none">…</div>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
There is no utility for the shadow, so `--ds-card: none` is how you drop it in every format.
|
|
281
|
+
|
|
282
|
+
These hooks also reach the live theme editor: `--bg-card`, `--bg-btn` and `--bg-status` now
|
|
283
|
+
appear in `design-manifest.json`'s reverse index, so pattern backgrounds are tunable in the
|
|
284
|
+
browser alongside their geometry.
|
|
285
|
+
|
|
286
|
+
### Patch Changes
|
|
287
|
+
|
|
288
|
+
- Updated dependencies [bb92a14]
|
|
289
|
+
- @getvitops/utils@1.0.0
|
|
290
|
+
|
|
291
|
+
## 0.9.0
|
|
292
|
+
|
|
293
|
+
### Minor Changes
|
|
294
|
+
|
|
295
|
+
- **The `tailwind` format now emits the full role colour vocabulary.** It previously emitted only
|
|
296
|
+
the functional role utilities, so 87 classes — `bg-<role>-x-muted`, `bg-<role>-bold`,
|
|
297
|
+
`bg-<role>-x-bold`, `text-<role>-bold`, `text-<role>-x-bold`,
|
|
298
|
+
`border-<role>-{muted,x-muted,x-bold}`, for every role — existed in the `css` and `bricks`
|
|
299
|
+
outputs and silently did nothing in `tailwind`. Nothing in the test suite built the tailwind
|
|
300
|
+
format, so the divergence was invisible; it was found by a consumer who hand-forked four
|
|
301
|
+
background planes into their own stylesheet to work around the absence.
|
|
302
|
+
|
|
303
|
+
All three formats now render from one emitter (`roleColorUtilities()`), which resolves the
|
|
304
|
+
plane-vs-stop namespace collision explicitly instead of relying on the CSS minifier dropping a
|
|
305
|
+
shadowed rule. No class changes meaning, and the `css`/`bricks` output is unchanged.
|
|
306
|
+
|
|
307
|
+
Also fixed in the tailwind format:
|
|
308
|
+
- `colors.utilities` is now honoured. It was hardcoded to `bg`/`text`/`border`, so enabling
|
|
309
|
+
`outline`/`fill`/`stroke` worked in `css`/`bricks` and was ignored here. (For raw hue scales
|
|
310
|
+
it remains a floor rather than a ceiling — those are `@theme` colours, and Tailwind derives
|
|
311
|
+
every colour family from them on demand.)
|
|
312
|
+
- **Component container queries are no longer stripped.** The pass that drops the framework's
|
|
313
|
+
pre-expanded `md-*` breakpoint utilities (Tailwind regenerates those as `@md:`) matched every
|
|
314
|
+
`@container (min-width: …)` block, including component behaviour — most visibly the
|
|
315
|
+
sitenav's, so `.sitenav--bp-{sm,md,lg,xl}` were removed and the nav stayed in its mobile
|
|
316
|
+
layout at every width.
|
|
317
|
+
|
|
318
|
+
New: **`vitops lint`** reports framework classes in your source that resolve to nothing — the
|
|
319
|
+
failure mode where an unknown utility looks exactly like a working one. It is format-aware
|
|
320
|
+
(`md-flex-row` is a real class in `css`/`bricks` and inert in `tailwind`) and only judges
|
|
321
|
+
classes anchored to your own config, so it stays quiet on Tailwind's utilities and your own.
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
vitops lint --format tailwind --src src
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
- **The `css` and `bricks` bundles now ship cascade layers, so a utility can override a pattern.**
|
|
328
|
+
|
|
329
|
+
`class="card bg-danger-muted"` previously tinted the card in the `tailwind` format and silently
|
|
330
|
+
did nothing in `css`/`bricks`: every colour utility was emitted before every pattern and both sat
|
|
331
|
+
at `0-1-0`, so the pattern won on source order. The bundle now emits
|
|
332
|
+
|
|
333
|
+
```css
|
|
334
|
+
@layer vitops.base, vitops.components, vitops.utilities;
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
- `vitops.base` — the UA reset and the pure `:root` token blocks
|
|
338
|
+
- `vitops.components` — the animation engine, structural layout, and every pattern
|
|
339
|
+
- `vitops.utilities` — `bg-*`, `text-*`, `border-*`, `drop-shadow-*`, `font-*`, animation
|
|
340
|
+
effects, and the display/`sr-only` families
|
|
341
|
+
|
|
342
|
+
No rule changed — 1482 rules before, 1482 after — only precedence.
|
|
343
|
+
|
|
344
|
+
**What this changes for you.** Unlayered CSS beats every cascade layer regardless of specificity,
|
|
345
|
+
so your own stylesheet, an Astro scoped `<style>`, or a Bricks-authored class now overrides the
|
|
346
|
+
framework with no `!important` and no specificity escalation. That is the intended override story
|
|
347
|
+
and the reason to layer at all.
|
|
348
|
+
|
|
349
|
+
**Migration — only if you ship a reset.** An unlayered reset will now beat the framework
|
|
350
|
+
component rules it used to lose to; a bare `p { margin: 0 }` defeats `.rhythm`. Put it in a layer
|
|
351
|
+
and declare the order **before** the stylesheet loads:
|
|
352
|
+
|
|
353
|
+
```html
|
|
354
|
+
<style>
|
|
355
|
+
@layer my.reset, vitops.base, vitops.components, vitops.utilities;
|
|
356
|
+
</style>
|
|
357
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Declaring it _after_ the link makes `my.reset` a name introduced later, which sorts **last** —
|
|
361
|
+
highest priority — and the reset wins anyway. That ordering rule is the one non-obvious step.
|
|
362
|
+
|
|
363
|
+
The `tailwind` format is byte-identical. Its utilities are `@utility` definitions, which Tailwind
|
|
364
|
+
already places in a layer above `components` and which are what make `hover:`/`@md:` variants
|
|
365
|
+
work; wrapping them ourselves would win the cascade and lose the variants.
|
|
366
|
+
|
|
367
|
+
Known gap: `layout.css` mixes structural rules (`.rhythm`, `.centered`) with utilities (`.m-*`,
|
|
368
|
+
`.flex`, `.split-*`) in one partial, so it sits in `vitops.components` whole and its utility half
|
|
369
|
+
cannot yet override a pattern. Splitting it is tracked separately.
|
|
370
|
+
|
|
371
|
+
### Patch Changes
|
|
372
|
+
|
|
373
|
+
- **Fixed: `vitops init` and `vp create` scaffolded configs referencing tokens that no longer
|
|
374
|
+
exist.** `defaultConfig()` and the EmDash template still pointed at `--color-surface-xl` and
|
|
375
|
+
`--color-surface-xxl`, aliases from the named-step colour scale removed in 0.6. A scaffolded
|
|
376
|
+
project therefore got a `card` with no background and an invalid default border colour, with
|
|
377
|
+
nothing reporting it. Both now use `--surface-bg` / `--surface-bg-muted`. The EmDash template
|
|
378
|
+
also dropped its `patterns.radii.card` key, which collided with the `card` pattern on
|
|
379
|
+
`--br-card` — the collision `validate()` has been warning about (the `panel` group already
|
|
380
|
+
supplies the same radius, so nothing changes visually).
|
|
381
|
+
|
|
382
|
+
**Fixed: `.text-reveal` rendered invisible text.** Its gradient consumed
|
|
383
|
+
`--text-reveal-color-from`/`-to` with no defaults, so unless a consumer set both, the `var()`
|
|
384
|
+
substitution failed, `background` became invalid at computed-value time, and the accompanying
|
|
385
|
+
`color: transparent` left the text with no way to render. Both tones now default to the
|
|
386
|
+
functional text tokens.
|
|
387
|
+
|
|
388
|
+
**Fixed: `.bordered` fell back to `currentColor`** via a reference to `--color-surface-xl`,
|
|
389
|
+
which the generator never emitted. It now resolves `--surface-border`.
|
|
390
|
+
|
|
391
|
+
**New: `validate()` warns when a required role is missing.** `colors.roles` is an open map —
|
|
392
|
+
any name works and generates a full token set — but the shipped component CSS references
|
|
393
|
+
`brand-primary`, `danger`, `neutral`, `surface`, `ui-primary` and `warning` with no fallback,
|
|
394
|
+
so omitting one leaves those components uncoloured. This is a warning, not an error; the
|
|
395
|
+
required list is re-derived from the CSS partials by a test so it cannot drift.
|
|
396
|
+
|
|
397
|
+
`vitops docs` and `vitops agents` now surface config warnings (on stderr, so piping `docs` is
|
|
398
|
+
unaffected). They previously discarded them, unlike `generate` and `validate`.
|
|
399
|
+
|
|
400
|
+
Docs corrections, all in the generated bundle:
|
|
401
|
+
- **Raw scale classes are frozen and do not remap in dark mode** — stated explicitly for the
|
|
402
|
+
first time, with a migration table to the role equivalents. The dark-mode guarantee only ever
|
|
403
|
+
applied to functional role tokens, and nothing said so.
|
|
404
|
+
- **Roles are extensible over a required core** — the schema description and class reference
|
|
405
|
+
read as a closed enumeration, which is why a consumer forked their own colour layer rather
|
|
406
|
+
than adding a role.
|
|
407
|
+
- **The `md:` / `@md:` / `md-` distinction** in the tailwind format: `@md:` uses the framework's
|
|
408
|
+
breakpoints, `md:` works but uses Tailwind's (which differ — `sm:` is 40rem, `@sm:` is 30rem),
|
|
409
|
+
and `md-` is silently inert. Plus a note that registering `--container-*` also re-points
|
|
410
|
+
Tailwind's `max-w-*` scale.
|
|
411
|
+
- The css and bricks bundles now carry a `/*!` banner pointing at `npx vitops docs classes`
|
|
412
|
+
(the previous plain comment was stripped by the minifier, so it never reached the file).
|
|
413
|
+
|
|
414
|
+
Contrast checking now covers **every** background plane a role emits (`bg`, `bg-muted`, and
|
|
415
|
+
`bg-bold`), not just `bg` — body text on a `card` was previously unguaranteed.
|
|
416
|
+
|
|
417
|
+
- Docs now name both dark-mode attributes. The generated colour docs still described the
|
|
418
|
+
dark flip as hanging off `:root[data-brx-theme="dark"]` alone, from before the selector
|
|
419
|
+
also matched `:root[data-theme="dark"]` (the attribute `<color-scheme-toggle>` writes) —
|
|
420
|
+
so a non-Bricks consumer reading `vitops docs color` would wire up an attribute that
|
|
421
|
+
nothing matches. The selector moved into `shared.ts` as `DARK_SEL` (also exported from
|
|
422
|
+
the package index) and the docs interpolate it, so the two can't drift again.
|
|
423
|
+
- c949cae: `vitops init` no longer scaffolds a config that warns on its own output.
|
|
424
|
+
|
|
425
|
+
`defaultConfig()` declared `patterns.radii.card`, a key named after the `card` pattern — the exact
|
|
426
|
+
collision `validate()` started reporting in 0.8.0. The primitive won `--br-card`, shadowing the
|
|
427
|
+
pattern's override hook and leaving its `--br-card-group` alias unreachable, so a scaffolded project
|
|
428
|
+
was warned about a config it had just been given.
|
|
429
|
+
|
|
430
|
+
The key is dropped. This is **value-preserving**: `card` belongs to the `panel` group, which already
|
|
431
|
+
carries the same `0.5rem`, so `--br-card-group` → `--br-panel` → `0.5rem` renders identically.
|
|
432
|
+
|
|
433
|
+
**Migration:** none for existing configs — your `design-system.json` is not touched. If you copied
|
|
434
|
+
the scaffold and reference `var(--br-card)` directly in hand-written CSS, note it is now an
|
|
435
|
+
_override hook_ (undefined until you set it) rather than a defined primitive; read `--br-card-group`
|
|
436
|
+
instead, or keep your own `radii` key under a name no pattern uses.
|
|
437
|
+
|
|
438
|
+
- **The theme editor no longer dims the page it's editing.**
|
|
439
|
+
|
|
440
|
+
The panel used `.drawer`'s scrim and `popover="auto"`, which fought the whole purpose of a live
|
|
441
|
+
editor: the page you were tuning sat behind a 4px blur, and the first click on it dismissed the
|
|
442
|
+
panel. It is now modeless — no scrim, and clicking through to the page keeps it open. Escape still
|
|
443
|
+
closes it (wired explicitly, since `popover="manual"` doesn't provide it), as does the × button.
|
|
444
|
+
|
|
445
|
+
**New: `.drawer--modeless`.** The same treatment is available to any drawer — a side panel you work
|
|
446
|
+
_alongside_ rather than through (an inspector, a live editor, a filter rail). Pair it with
|
|
447
|
+
`popover="manual"`:
|
|
448
|
+
|
|
449
|
+
```html
|
|
450
|
+
<div class="drawer drawer--right drawer--modeless" popover="manual">…</div>
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
An `auto` popover light-dismisses on the first outside click, which is exactly the interaction a
|
|
454
|
+
modeless panel exists to permit — so `manual` is part of the pattern, not an afterthought. It also
|
|
455
|
+
drops Escape, so give the panel a visible close control.
|
|
456
|
+
|
|
457
|
+
**Added:** a drift guard tying `<wc-theme-editor>`'s dark-override selector to the generator's
|
|
458
|
+
`DARK_SEL`. The two must match or the editor's dark-mode edits land on a rule the page never
|
|
459
|
+
matches — a failure that is invisible in light mode, so nothing would have caught it.
|
|
460
|
+
- @getvitops/utils@0.9.0
|
|
461
|
+
|
|
3
462
|
## 0.8.0
|
|
4
463
|
|
|
5
464
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @getvitops/generator
|
|
2
2
|
|
|
3
|
-
Generate design-system outputs — **
|
|
4
|
-
|
|
3
|
+
Generate design-system outputs — **Tailwind v4** (Astro/EmDash), **Bricks** (WordPress), an
|
|
4
|
+
agent-facing **`DESIGN.md`** brief, and standalone **CSS** — from a single `design-system.json`.
|
|
5
|
+
This is the library that powers
|
|
5
6
|
[`@getvitops/cli`](https://www.npmjs.com/package/@getvitops/cli) and
|
|
6
7
|
[`@getvitops/vite`](https://www.npmjs.com/package/@getvitops/vite).
|
|
7
8
|
|
|
@@ -10,7 +11,7 @@ import { generate, validate, defaultConfig } from '@getvitops/generator';
|
|
|
10
11
|
|
|
11
12
|
await generate({
|
|
12
13
|
input: './design-system.json', // path or a config object
|
|
13
|
-
format: 'tailwind', // 'bricks' | '
|
|
14
|
+
format: 'tailwind', // 'tailwind' | 'bricks' | 'design' | 'css'
|
|
14
15
|
outDir: './src/styles',
|
|
15
16
|
});
|
|
16
17
|
```
|
|
@@ -29,8 +30,19 @@ await generate({
|
|
|
29
30
|
| format | writes |
|
|
30
31
|
| ---------- | -------------------------------------------------------------------------------------------------------- |
|
|
31
32
|
| `tailwind` | `tailwind.css` (self-contained, `@import "tailwindcss"`) + `tokens.json` |
|
|
32
|
-
| `css` | `styles.css` (bundled, standalone) + `tokens.json` + `design-manifest.json` |
|
|
33
33
|
| `bricks` | `styles.min.css`, `bricks-colors-*.json`, `bricks-variables.json`, `tokens.json`, JS, `bricks/`, `docs/` |
|
|
34
|
+
| `design` | `DESIGN.md` — and nothing else |
|
|
35
|
+
| `css` | `styles.css` (bundled, standalone) + `tokens.json` + `design-manifest.json` |
|
|
36
|
+
|
|
37
|
+
`design` is the odd one out: it emits **no CSS**, just the agent-facing brief in
|
|
38
|
+
[design.md](https://github.com/google-labs-code/design.md) format — YAML token front matter plus a
|
|
39
|
+
prose body, meant to sit at a repo root beside `AGENTS.md` (so run it with `outDir: '.'`). It's what
|
|
40
|
+
you hand to a tool that has never heard of Vitops; the stylesheet formats are what the browser gets.
|
|
41
|
+
`Format` covers all four, and `StylesheetFormat` (`Exclude<Format, 'design'>`) is the narrower type
|
|
42
|
+
anything expecting an importable stylesheet uses.
|
|
43
|
+
|
|
44
|
+
`generate()` takes one format per call, so pair the brief with a stylesheet by calling it twice
|
|
45
|
+
(`@getvitops/cli`'s `--format` accepts a comma-separated list and does exactly that).
|
|
34
46
|
|
|
35
47
|
See the [`design-system.json` schema](./schema.json) for the config shape.
|
|
36
48
|
|
package/assets/bricks/load.php
CHANGED
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
*
|
|
7
7
|
* require_once get_stylesheet_directory() . '/dist/bricks/load.php';
|
|
8
8
|
*
|
|
9
|
-
* It does
|
|
9
|
+
* It does the following:
|
|
10
10
|
* 1. Registers every repo-owned Bricks element under dist/bricks/elements/.
|
|
11
11
|
* 2. Enqueues the framework CSS + JS bundles — polyfills.js and elements.js as
|
|
12
12
|
* ES modules (so the Lit custom elements upgrade), deferred.js with `defer`.
|
|
13
13
|
* The enqueue runs on `wp_enqueue_scripts`, which the Bricks builder canvas
|
|
14
14
|
* iframe also fires, so the elements upgrade while editing too.
|
|
15
15
|
* 3. Registers a "Vitops" builder category so the elements group together.
|
|
16
|
+
* 4. Defaults the design system to dark mode.
|
|
17
|
+
* 5. Registers [vitops_legal] for the generated legal documents.
|
|
16
18
|
*
|
|
17
19
|
* Owned by the framework repo (vitops: bricks/); copied into the theme's
|
|
18
20
|
* dist/bricks/ on build — do not hand-edit in the theme.
|
|
@@ -127,3 +129,46 @@ add_filter(
|
|
|
127
129
|
},
|
|
128
130
|
99
|
|
129
131
|
);
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 5. [vitops_legal doc="privacy"] — render a generated legal document.
|
|
135
|
+
*
|
|
136
|
+
* The build emits dist/legal/*.html from the site config, so the document updates
|
|
137
|
+
* on the next deploy with no action in WordPress. Drop this shortcode into a
|
|
138
|
+
* Bricks page (or any content) and the fragment renders there, styled by the
|
|
139
|
+
* framework CSS the theme already loads.
|
|
140
|
+
*
|
|
141
|
+
* `doc` is matched against a fixed allowlist rather than interpolated into the
|
|
142
|
+
* path. The attribute is author-supplied and lands in a filesystem read, so
|
|
143
|
+
* anything less would be a traversal waiting to happen — an unknown value
|
|
144
|
+
* renders nothing.
|
|
145
|
+
*
|
|
146
|
+
* The fragment is generated by our own build from a closed markdown subset, and
|
|
147
|
+
* is already HTML-escaped at render time. It is echoed as-is deliberately:
|
|
148
|
+
* running it through wp_kses would strip the markup the document depends on.
|
|
149
|
+
*/
|
|
150
|
+
add_shortcode(
|
|
151
|
+
'vitops_legal',
|
|
152
|
+
function ( $atts ) {
|
|
153
|
+
$allowed = array(
|
|
154
|
+
'privacy' => 'privacy-policy',
|
|
155
|
+
'terms' => 'terms-of-service',
|
|
156
|
+
'cookies' => 'cookie-notice',
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
$atts = shortcode_atts( array( 'doc' => 'privacy' ), $atts, 'vitops_legal' );
|
|
160
|
+
$key = is_string( $atts['doc'] ) ? strtolower( trim( $atts['doc'] ) ) : '';
|
|
161
|
+
|
|
162
|
+
if ( ! isset( $allowed[ $key ] ) ) {
|
|
163
|
+
return '';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
$path = get_stylesheet_directory() . '/dist/legal/' . $allowed[ $key ] . '.html';
|
|
167
|
+
|
|
168
|
+
if ( ! file_exists( $path ) ) {
|
|
169
|
+
return '';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return file_get_contents( $path );
|
|
173
|
+
}
|
|
174
|
+
);
|
package/assets/css/animation.css
CHANGED
|
@@ -303,6 +303,16 @@
|
|
|
303
303
|
failure leaves readable text rather than blank space. Scroll-timeline is also
|
|
304
304
|
guarded — without it the text simply shows the final (to) color, still legible. */
|
|
305
305
|
.text-reveal {
|
|
306
|
+
/* Both tones MUST default, and NOT to currentColor. They are consumed by the
|
|
307
|
+
gradient below, where a var() substitution failure makes `background`
|
|
308
|
+
invalid at computed-value time — which, with the `color: transparent` in
|
|
309
|
+
that same block, renders the text completely invisible rather than
|
|
310
|
+
degrading. currentColor is unusable there for the same reason: inside the
|
|
311
|
+
@supports block it resolves to that transparent value. So default to the
|
|
312
|
+
functional text tokens (they flip with the appearance) with a literal
|
|
313
|
+
backstop, since this is precisely the declaration that must never fail. */
|
|
314
|
+
--_reveal-from: var(--text-reveal-color-from, var(--color-text-neutral-x-muted, #9099a8));
|
|
315
|
+
--_reveal-to: var(--text-reveal-color-to, var(--color-text-neutral, #05070d));
|
|
306
316
|
/* Fallback baseline: ordinary visible text in the start color. */
|
|
307
317
|
color: var(--text-reveal-color-from, currentColor);
|
|
308
318
|
}
|
|
@@ -313,8 +323,8 @@
|
|
|
313
323
|
--text-reveal-background,
|
|
314
324
|
linear-gradient(
|
|
315
325
|
var(--text-reveal-slant, 90deg),
|
|
316
|
-
var(--
|
|
317
|
-
var(--
|
|
326
|
+
var(--_reveal-to) 50%,
|
|
327
|
+
var(--_reveal-from) 50%
|
|
318
328
|
)
|
|
319
329
|
);
|
|
320
330
|
background-size: 200% 100%;
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
border: none;
|
|
65
65
|
border-radius: 50%;
|
|
66
66
|
color: var(--carousel-button-color, currentColor);
|
|
67
|
-
background: var(--
|
|
67
|
+
background: var(--color-bg-surface-muted);
|
|
68
68
|
opacity: 0.9;
|
|
69
69
|
transition:
|
|
70
70
|
opacity 0.2s ease,
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
|
|
74
74
|
&::scroll-button(*):hover {
|
|
75
75
|
opacity: 1;
|
|
76
|
-
background: var(--color-surface-muted);
|
|
76
|
+
background: var(--color-bg-surface-muted);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
&::scroll-button(*):focus-visible {
|
|
@@ -211,7 +211,7 @@
|
|
|
211
211
|
block-size: 2rem;
|
|
212
212
|
border: none;
|
|
213
213
|
border-radius: 50%;
|
|
214
|
-
background: var(--
|
|
214
|
+
background: var(--color-bg-surface-muted);
|
|
215
215
|
color: var(--carousel-button-color, currentColor);
|
|
216
216
|
opacity: 0.7;
|
|
217
217
|
cursor: pointer;
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
:where(:not(pre) > code) {
|
|
11
11
|
font-family: var(--font-mono);
|
|
12
12
|
font-size: 0.85em;
|
|
13
|
-
background: var(--
|
|
14
|
-
color: var(--
|
|
13
|
+
background: var(--color-border-neutral);
|
|
14
|
+
color: var(--color-text-neutral);
|
|
15
15
|
padding: 0.1em 0.4em;
|
|
16
16
|
border-radius: var(--br-default);
|
|
17
17
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
border-radius: var(--_br);
|
|
21
21
|
|
|
22
22
|
&:focus {
|
|
23
|
-
outline: 2px solid var(--combobox-focus-color, var(--color-
|
|
23
|
+
outline: 2px solid var(--combobox-focus-color, var(--color-border-focus));
|
|
24
24
|
outline-offset: 2px;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
border: var(--_b);
|
|
36
36
|
border-radius: var(--_br);
|
|
37
37
|
box-shadow: var(--_ds);
|
|
38
|
-
background: var(--
|
|
38
|
+
background: var(--color-bg-surface);
|
|
39
39
|
max-block-size: 15rem;
|
|
40
40
|
overflow-y: auto;
|
|
41
41
|
list-style: none;
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
|
|
56
56
|
&:hover,
|
|
57
57
|
&[aria-selected='true'] {
|
|
58
|
-
background: var(--color-surface-muted);
|
|
58
|
+
background: var(--color-bg-surface-muted);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
&[aria-disabled='true'] {
|