@getvitops/emdash 1.0.0-alpha.0 → 2.0.0-alpha.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +279 -3
  2. package/README.md +66 -21
  3. package/dist/index.d.mts +82 -18
  4. package/dist/index.d.mts.map +1 -1
  5. package/dist/index.mjs +1626 -152
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +16 -13
  8. package/src/admin/DesignSystemEditorPage.tsx +15 -42
  9. package/src/astro/blocks/Accordion.astro +39 -0
  10. package/src/astro/blocks/ActionLink.astro +4 -2
  11. package/src/astro/blocks/Callout.astro +73 -0
  12. package/src/astro/blocks/Cards.astro +96 -0
  13. package/src/astro/blocks/Carousel.astro +25 -8
  14. package/src/astro/blocks/Embed.astro +67 -0
  15. package/src/astro/blocks/Entries.astro +30 -0
  16. package/src/astro/blocks/Form.astro +194 -0
  17. package/src/astro/blocks/Gallery.astro +40 -0
  18. package/src/astro/blocks/Hero.astro +115 -0
  19. package/src/astro/blocks/ImageCompare.astro +5 -2
  20. package/src/astro/blocks/Logos.astro +48 -0
  21. package/src/astro/blocks/Marquee.astro +38 -0
  22. package/src/astro/blocks/Nav.astro +85 -0
  23. package/src/astro/blocks/NavTree.astro +48 -0
  24. package/src/astro/blocks/Pricing.astro +66 -0
  25. package/src/astro/blocks/Reviews.astro +122 -0
  26. package/src/astro/blocks/RichText.astro +14 -0
  27. package/src/astro/blocks/Section.astro +84 -0
  28. package/src/astro/blocks/Stats.astro +59 -0
  29. package/src/astro/blocks/Steps.astro +48 -0
  30. package/src/astro/blocks/embed.test.ts +43 -0
  31. package/src/astro/blocks/embed.ts +84 -0
  32. package/src/astro/blocks/form.sample.ts +33 -0
  33. package/src/astro/blocks/form.test.ts +55 -0
  34. package/src/astro/blocks/form.ts +158 -0
  35. package/src/astro/blocks/head.stub.ts +2 -0
  36. package/src/astro/blocks/nav.test.ts +51 -0
  37. package/src/astro/blocks/nav.ts +65 -0
  38. package/src/astro/blocks/render.container.test.ts +570 -0
  39. package/src/astro/blocks/tone.test.ts +53 -0
  40. package/src/astro/blocks/tone.ts +122 -0
  41. package/src/astro/blocks/value.test.ts +56 -0
  42. package/src/astro/blocks/value.ts +103 -1
  43. package/src/astro/index.ts +40 -0
  44. package/src/admin/core-editor.d.ts +0 -7
package/CHANGELOG.md CHANGED
@@ -1,12 +1,288 @@
1
1
  # @getvitops/emdash
2
2
 
3
+ ## 2.0.0-alpha.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 161c7f3: A contact form that works end to end: `vitops setup turnstile`, `<wc-turnstile>`, the `vitops.form` EmDash block, and the route the emdash template ships to receive it.
8
+
9
+ **CSS, all formats** (no existing class changes): `forms.css` gains `.form-notice--target:not(:target)` (a message revealed only when the URL fragment names it, which is how a prerendered page shows a no-JS form's result) and `:where(wc-turnstile) { display: block }`.
10
+
11
+ **`@getvitops/core`:** new `<wc-turnstile sitekey="…">` in `elements.js`. It loads Cloudflare Turnstile on the first interaction with its form (no request to `challenges.cloudflare.com` for a visitor who only reads the page), holds an early submit until the token arrives, and leaves its slotted no-JS explanation in place until JS takes over. Turnstile is category `necessary`, so it is not behind the consent banner.
12
+
13
+ **`@getvitops/utils` / `@getvitops/cli`:** new `@getvitops/utils/turnstile` and `vitops setup turnstile [--dry] [--check] [--no-write] [--no-secret]`. It plans from the site's hostnames, creates a managed widget unless one already covers them (an existing widget is adopted, never edited, rotated or deleted), writes the public key to `site.security.turnstile.siteKey`, and pipes the one-time secret to `wrangler secret put VITOPS_TURNSTILE_SECRET` without writing it to any file. Needs `CLOUDFLARE_API_TOKEN` with **Account > Turnstile > Edit** and `CLOUDFLARE_ACCOUNT_ID`.
14
+
15
+ **`@getvitops/astro`:** `virtual:getvitops/head` gains `turnstileSiteKey` (read from `site.security.turnstile.siteKey`) so blocks can render the widget. No other change.
16
+
17
+ **`@getvitops/generator`:** `TIERS` gains a `turnstile` entry (wc-turnstile, claimed by `vitops.form`); `wc-multi-field`'s `emdashExempt` reason is reworded now the form block exists.
18
+
19
+ **`@getvitops/emdash`:** new `vitops.form` block: a field repeater (text, email, phone, long text, choose one, tick box), required flags, help text, success/error messages, optional Turnstile. It renders a semantic `<form method="post">` that works with no JavaScript, with a honeypot named per `createConversionRoute`'s defaults. A field named `phone` submits as `tel`, because the route reserves `phone` and `event`.
20
+
21
+ **`@getvitops/create`:** the emdash template ships `src/pages/api/track.ts` (`prerender = false`, `createConversionRoute()`), a seeded contact form, and `challenges.cloudflare.com` in both templates' Report-Only CSP (`script-src`, `frame-src`). The route refuses a post with nowhere to deliver and fails closed when a site key exists without `VITOPS_TURNSTILE_SECRET`. SETUP.html gains the Turnstile step. Existing sites: copy the route and the CSP change, then run `vitops setup turnstile`.
22
+
23
+ - 1e36b83: Seven new blocks, a section block type, and a shared `tone`.
24
+
25
+ **New Portable Text blocks:** `vitops.logos` (a still grid; `marquee` only scrolls), `vitops.steps`, `vitops.stats` (each figure is a `<Counter>`; a `tiles` style adds group, change and good/bad state), `vitops.callout` (a call-to-action band, not the dismissible `banner`), `vitops.entries` (wraps `<wc-entries>`), `vitops.pricing` (aligned plan cards) and `vitops.embed` (a video or map that loads only after consent: the iframe ships with no `src`, the address rides on `data-consent-src`, and a `title` is required).
26
+
27
+ **New block TYPE `vitops_section`** (kicker, heading with a level, lead-in, tone, an optional heading-left `split` layout, and a Portable Text body that accepts every `vitops.*` block). It is a schema-level block type, not a Portable Text block, because Block Kit fields cannot hold rich text. Add `...vitopsBlockTypes()` to `seed.json`'s `blockTypes`, allow `vitops_section` on the collection's `blocks` field, and map it with `blockTypeComponents` from `@getvitops/emdash/astro` (`defineBlockComponents`).
28
+
29
+ **Extended, non-breaking:** `vitops.hero` gains accent marks (`*words*` in the heading), a heading `level`, `tone`/`color` and an optional row of cards. `vitops.cards` gains a `panel` style (tone, checklist, button), a heading `level`, `tone`/`color`, and `[text](/url)` inline links in the body. **Cards fix:** the card title is now a real heading element (default `h3`, `font-heading`) instead of `<p class="font-heading">`; saved content is unchanged, but the markup and the page outline change. Set `level` to taste.
30
+
31
+ **Shared `tone` + `color`** on every block that colours anything: a role from `site.json`, falling back through its semantic relatives to `neutral`, or a palette hue/step. Renderers emit `--tone-*` custom properties (never a class built from a value, which Tailwind cannot see). New option `vitopsEmdash({ tones })` (or the roles are read from `designSystemEditor.config`) fills each block's `tone` select with the site's own roles through a `tones/list` plugin route; with neither, the select offers the semantic names. `vitops.banner.tone` and `vitops.actionLink.role` keep their existing values.
32
+
33
+ **Render tests:** `pnpm --filter @getvitops/emdash run test:render` renders the block adapters through Astro's container API in a separate vitest config.
34
+
35
+ - 175d16e: **Added: `vitops.reviews` and `vitops.nav` EmDash blocks.** Non-breaking.
36
+
37
+ - **`vitops.reviews`** renders Google reviews from the committed `.vitops/reviews.json` cache (`vitops reviews`) as a grid, carousel or marquee of `<ReviewCard>`. Fields: location key, layout, columns, limit, lowest rating, order, featured ids, reviewer photos (off by default), tone/colour. Which reviews show is `selectReviews()`, the one shared decision. Review JSON-LD is not offered (Google treats self-marked-up reviews as ineligible for rich results). A new review reaches the page after `vitops reviews` is re-run, committed and the site rebuilt, not when an editor saves; with no cache file the block renders nothing (a note in `astro dev`).
38
+ - **`vitops.nav`** renders a named EmDash menu inside page content (site map, section links, footer-style columns), never the header. Fields: menu (picked from the site's menus via a new `menus/list` plugin route, with `footer-sitemap`/`footer-legal`/`primary` as the static fallback), accessible name, visible heading and level, layout `list | inline | columns | sitenav`, depth 1-3, tone/colour. It is a `<nav>` with a name that is never `Primary`, `Site map` or `Legal` (the layout's landmarks), `aria-current="page"`, no runtime JS; a missing or empty menu renders nothing.
39
+ - `@getvitops/astro`: **`virtual:getvitops/reviews`**, served by `vitops()`, is `.vitops/reviews.json` as a build-time import (an empty cache when absent or unreadable), because a Worker cannot read the file. `<ReviewCard>` now renders the review's source link (`.review__source`, `rel="nofollow noopener noreferrer"`), which the pattern already styled. **Fix:** `src/json-ld.ts` was missing from the published `files`, so any consumer importing `<ReviewCard>` (which composes `schemas/Review.astro`) failed to build with an unresolved import. `@getvitops/emdash` now needs this version of `@getvitops/astro` for the `vitops.reviews` block.
40
+ - `@getvitops/utils`: new Worker-safe subpath **`@getvitops/utils/reviews/select`** (`selectReviews` and the review types, no `node:fs`). `selectReviews` now takes only the display fields (`featured`, `minRating`, `limit`, `sort`), so a caller with no configured `locationId` can use it; existing calls are unchanged.
41
+ - `@getvitops/core`: new `.navlist` pattern in `patterns/sitenav.css` (`.navlist`, `__item`, `__link`, `__label`, `__sub`, `--inline`, `--columns`). **All formats** (css, bricks, tailwind). No existing class changes.
42
+ - `@getvitops/generator`: `TIERS.review` lists `vitops.reviews` and `TIERS.sitenav` lists `vitops.nav` (and `.navlist`), so `vitops docs components` and the docsite show them.
43
+
44
+ - 06d6112: Fix the EmDash `/design-system` admin page erroring on a tailwind site. It fetches `/vitops/design-manifest.json`, which the `tailwind` format never emitted and the integration only mirrored under `editor: true` (which also loads `editor.js` on every public page).
45
+
46
+ - **generator** (`tailwind` format only): `design-manifest.json` is now emitted alongside `tailwind.css`. No change to the CSS.
47
+ - **astro**: new `vitops({ editorManifest: true })` writes everything an outside host needs into `public/vitops/` (`design-manifest.json`, `editor.js`, a standalone `editor.css`, and `design-system-editor.html`) without loading the editor on public pages. `editor: true` still loads it from `<Head />`.
48
+ - **vite**: `editorManifestDir` also writes `vitops/editor.css`, from a throwaway `css`-format pass on tailwind sites.
49
+ - **emdash**: the `/design-system` admin page was unstyled and overflowed the admin, because the editor borrows the framework CSS and the admin never loads it. It now embeds `/vitops/design-system-editor.html` in an iframe, so the design system can't leak into the admin's styles. **Requires `editorManifest: true`** in `vitops({ … })`.
50
+ - **create**: the EmDash template sets `editorManifest: true`. Existing sites: add it to `vitops({ … })` in `astro.config.mjs`.
51
+
52
+ No consumer-facing change in the other toolchain packages.
53
+
54
+ ### Patch Changes
55
+
56
+ - Updated dependencies [efd4e72]
57
+ - Updated dependencies [93d2067]
58
+ - Updated dependencies [161c7f3]
59
+ - Updated dependencies [175d16e]
60
+ - Updated dependencies [83d6f3c]
61
+ - Updated dependencies [892b204]
62
+ - Updated dependencies [06d6112]
63
+ - Updated dependencies [6297337]
64
+ - Updated dependencies [224a731]
65
+ - Updated dependencies [55061e2]
66
+ - Updated dependencies [b47df4d]
67
+ - @getvitops/astro@8.3.0-alpha.0
68
+ - @getvitops/core@8.3.0-alpha.0
69
+ - @getvitops/generator@8.3.0-alpha.0
70
+ - @getvitops/utils@8.3.0-alpha.0
71
+
72
+ ## 1.0.0
73
+
74
+ ### Major Changes
75
+
76
+ - c3c1bcb: **New: the design-system editor, mounted inside the EmDash admin.** `vitopsEmdash({
77
+ designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
78
+ './site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
79
+ `<wc-theme-editor>` — the same live token editor `@getvitops/vite`'s dev server has always
80
+ offered, now usable by the client directly, no dev server required. An edit is visible
81
+ site-wide immediately (a runtime `:root` override, served via a new plugin API route and
82
+ `page:fragments`) and — once `site.deployment.repo`/`branch` and a `VITOPS_GITHUB_TOKEN`
83
+ secret are set (see the scaffold's `SETUP.html`) — auto-commits to your `dev` branch, so it
84
+ reaches the git-tracked `site.json` through your repo's normal review path rather than
85
+ becoming a second, unreviewed source of truth. Until that's configured, the live half still
86
+ works; only the auto-commit half is skipped (and says so in the admin page). The save route
87
+ runs the exact same accessibility (APCA contrast) gate `generate()` enforces at build time,
88
+ synchronously, before persisting — nothing non-compliant ever goes live even momentarily.
89
+
90
+ Requires `@astrojs/react` + `react` + `react-dom` (already scaffold dependencies, for
91
+ EmDash's own admin) and `@getvitops/core` (added as a new scaffold dependency this release).
92
+ Declares two new plugin capabilities when enabled: `network:request` (for the GitHub
93
+ auto-commit) and `hooks.page-fragments:register` (for the live override) — reviewed once at
94
+ plugin-install time.
95
+
96
+ **Breaking for `@getvitops/emdash`**: `vitopsEmdash()`'s `hooks.page-fragments:register`
97
+ capability is now also granted whenever `designSystemEditor` is set, independent of the
98
+ existing `scripts: 'fragments'` option (which governs something unrelated — web-component
99
+ script delivery). No change if you don't use `designSystemEditor`.
100
+
101
+ Also new, supporting the above:
102
+
103
+ - `@getvitops/generator`: `validateColorContract()` and `buildReverseIndex()` (both
104
+ `/tokens` subpath, Worker-safe) — the monotonicity/contrast gate and the
105
+ `design-manifest.json` reverse-index, each extracted from `generate()`'s `build()` into
106
+ standalone, non-throwing functions. `patchToOverrideCss()` turns a design-system patch
107
+ into the `:root` override `<style>` block. `designSystemPath`/`getAt`/`setAt`/
108
+ `mergePatch`/`formatConfig` moved from `@getvitops/vite` into `@getvitops/generator/config`
109
+ (re-exported from `@getvitops/vite` unchanged) — one "patch a config file's design-system
110
+ subtree" implementation shared by the dev endpoint and the new plugin route. New
111
+ `site.deployment.repo`/`branch` config fields.
112
+ - `@getvitops/utils`: new `/git` subpath — `commitFile()`, a Worker-safe (fetch-only)
113
+ wrapper over GitHub's Contents API.
114
+ - `@getvitops/core`: `<wc-theme-editor>` gains a `save-endpoint` attribute (default
115
+ unchanged — `/__vitops/design-system`) so the same element can target the new plugin
116
+ route instead of the dev-only Vite endpoint.
117
+
118
+ - 0574388: Bump `emdash`/`@emdash-cms/cloudflare` peers to `>=1.0.0 <2` and `vite-plus` to `^1.0.0`, following
119
+ both projects' GA releases.
120
+
121
+ **Breaking for `@getvitops/emdash` consumers on EmDash 0.x**: the peer range no longer admits
122
+ `emdash@0.31.x`/`@emdash-cms/cloudflare@0.31.x`. Upgrade `emdash` and `@emdash-cms/cloudflare` to
123
+ `^1.0.1` first — nothing in `@getvitops/emdash`'s own plugin surface (`admin.portableTextBlocks`,
124
+ the `hooks.page-fragments:register` capability, `definePlugin()`) changed against EmDash 1.0, so no
125
+ code here needed to change, only the declared range.
126
+
127
+ The `emdash` scaffold template (`vp create @getvitops:emdash`) picks up several fixes alongside
128
+ the version bump:
129
+
130
+ - `tailwindcss`/`@tailwindcss/vite` are now real dependencies — the template selected
131
+ `css.format: 'tailwind'` without installing either, which threw on build.
132
+ - `site.json` now sets `site.icons.sprite: true` plus the full `semantic` list the
133
+ `vitops.actionLink` block's icon picker offers — previously its sprite icons pointed at a file
134
+ the build never generated.
135
+ - `astro.config.mjs` sets `migrations: { runtime: 'check', dev: 'auto' }`, and
136
+ `site-deploy-emdash.yml` runs EmDash 1.0's documented `build → status → apply → deploy → check`
137
+ migration sequence explicitly, rather than relying solely on first-request auto-migration. This
138
+ needs a new GitHub Environment variable, `EMDASH_TARGET_FINGERPRINT` (one per environment),
139
+ which only a human can set — run `emdash migrate --status` against the real deployed database,
140
+ review the printed identity, then `gh variable set EMDASH_TARGET_FINGERPRINT --env <env> --body
141
+ <value>`. Deploys fail clearly on the migration-apply step until it's set.
142
+ - `lint.options.typeAware`/`typeCheck` in the template's own `vite.config.ts` are commented out,
143
+ not deleted: vite-plus 1.0 only honours that option in a project's actual root config, and hard
144
+ errors on it in a nested one (which this file is, sitting inside the `design-system` monorepo).
145
+ Uncomment it once scaffolded into its own repo.
146
+
147
+ No config schema changes. `@getvitops/generator` also switches from the `zod/mini` subpath import
148
+ to the standalone `@zod/mini` package (zod 4.5+; same API) and fixes `validateConfig()` silently
149
+ losing the offending key name on an `unrecognized_keys` error (e.g. a typo'd `site.ads` platform
150
+ now names the bad key instead of reporting a bare "Invalid input").
151
+
152
+ Also fixed: `vp check`'s `tsconfig-error` for `packages/create/templates/emdash/tsconfig.json`
153
+ (`extends: "astro/tsconfigs/strict"`, unresolvable from inside this monorepo since the template
154
+ has no `node_modules` of its own — a pre-existing, structural gap, newly surfaced as a hard error
155
+ by oxlint 1.85's deeper config discovery, and blocking on every commit since this repo's pre-commit
156
+ hook runs `vp check --fix`). Fixed with a new `scripts/link-template-deps.mjs`, wired as the root
157
+ `postinstall` script: symlinks `astro`/`emdash`/`@emdash-cms/cloudflare` into the template's own
158
+ `node_modules/` (resolved fresh from sibling packages that already depend on them for real, not a
159
+ hardcoded `.pnpm` hash) and writes a minimal `astro:content` ambient shim at `.astro/types.d.ts`
160
+ (the same path `astro sync` would generate for real). Both are gitignored and only ever exist
161
+ inside this monorepo's copy of the template — a real scaffold installs its own real copies instead.
162
+ Considered and rejected first: inlining astro's strict preset (drift risk against astro's own
163
+ evolving default) and making the template a real pnpm workspace member (would change how its
164
+ `@getvitops/*: "latest"` deps resolve locally, which `templates.test.ts` exists to protect).
165
+
166
+ **Known gap, not fixed here**: `packages/astro/src/components/container.test.ts` (the
167
+ `AstroContainer`-based render tests) fails to even load under vite-plus 1.0's Vite 8 — direct
168
+ `.astro` imports in a Vitest file need the Astro Vite plugin registered, which nothing in this
169
+ repo's test config actually does (Astro's own docs call for `getViteConfig()`, which doesn't
170
+ compose with vite-plus's own `defineConfig`). A type-level shim
171
+ (`packages/astro/src/components/astro-modules.d.ts`) fixes `vp check`'s type-aware pass, but the
172
+ runtime `vp test` failure for this one file is still open.
173
+
174
+ ### Minor Changes
175
+
176
+ - b3d9c07: `vitops docs blocks` prints every EmDash block type and its fields, so an agent can write valid Portable Text without reading the plugin source. It reads `@getvitops/emdash` from the project (and says so if it isn't installed); `@getvitops/emdash` exports `renderBlocksDoc()` for it.
177
+ - 2ef2d41: **Tested against EmDash 1.4.** `emdash` and `@emdash-cms/cloudflare` are now `^1.4.0` in the
178
+ `emdash` template, and `@getvitops/emdash`'s peer ranges for both moved from `>=1.0.0 <2` to
179
+ `>=1.3.0 <2` — **breaking for a site still on EmDash 1.0–1.2**: upgrade `emdash` and
180
+ `@emdash-cms/cloudflare` together (EmDash warns at build when the two differ) before upgrading
181
+ this package. 1.4 fixes a stored XSS in the visual-editing toolbar (an author's published content
182
+ could run script for a signed-in editor), so an existing client site should take it promptly.
183
+
184
+ - **`vitopsHosting()` coalesces D1 reads by default** — new `cloudflare.coalesce` option
185
+ (default `true`, passed to `d1({ coalesce })`). With `session: 'auto'`, EmDash 1.3 stops
186
+ preloading settings/menus/widgets for logged-out visitors unless reads are coalesced, so this
187
+ restores one round trip instead of several. It is experimental upstream: a read and a write
188
+ started together may run write-first, so `await` a read before a write that must not affect it.
189
+ Set `coalesce: false` to opt out. No effect without a session.
190
+ - **Template:** `AGENTS.md` gains an "EmDash 1.x behaviours" section (passkeys are bound to a
191
+ hostname so use _Change domain_ at cutover; admin-authored HTML blocks run JS and conflict with
192
+ Astro CSP; link by `entry.data.slug`, not `entry.id`; submit `/sitemap.xml`; clearing a stuck D1
193
+ lock with `emdash migrate --take-over-lock`).
194
+ - **Upstream behaviour changes to know about (no code change here):** `cacheHint.lastModified` is
195
+ no longer auto-populated and pages lose the implicit `Cache-Control: no-cache`; collection
196
+ sitemaps paginate at 2,000 entries; `entry.id` is always locale-prefixed on multilingual sites.
197
+
198
+ No consumer-facing change in `@getvitops/core`, `@getvitops/generator`, `@getvitops/utils`,
199
+ `@getvitops/cli` or `@getvitops/vite`. `@getvitops/astro` has one — see the scaffold changeset.
200
+
201
+ - 1a8231d: **EmDash blocks are now a tier of the component manifest, and there are four more of them.**
202
+
203
+ `@getvitops/emdash` adds `vitops.gallery`, `vitops.cards`, `vitops.accordion` and `vitops.marquee`
204
+ (each a thin adapter over the matching `@getvitops/astro` component), and `vitops.carousel` now edits
205
+ its slides as a **Slides repeater** instead of two newline-separated fields. Blocks saved before this
206
+ keep rendering: with no `slides`, the carousel still reads `images`/`alts`, and both legacy fields stay
207
+ in the editor, labelled as legacy. The editor's field type now admits `repeater` and `media_picker`.
208
+
209
+ - **New `blocks` plugin option** — `vitopsEmdash({ blocks: { include, exclude, extra } })`. `include` is
210
+ an allowlist, `exclude` drops blocks, `extra` adds the site's own **editor** definitions (the site
211
+ supplies their renderers via `<PortableText components={{ type }}>`, the same route that overrides a
212
+ `vitops.*` renderer). An unknown name or a colliding `type` throws at plugin construction.
213
+ - Link and image URLs in blocks with a scheme other than `http`, `https`, `mailto` or `tel` are
214
+ dropped (see the security entry).
215
+
216
+ `@getvitops/generator`: `TierEntry` gains an optional `emdash: string[]` (block `_type`s) and `Tier`
217
+ gains `'emdash'`, so `tierPatterns('emdash')` projects it. **Breaking only for code that exhaustively
218
+ switches on `Tier` or builds a `Record<Tier, …>`** — add an `emdash` case. The agent doc
219
+ `vitops docs components` gains an "EmDash blocks" table and the docsite a `components/emdash` page. The
220
+ guard that every block and every `emdash` entry agree lives in `@getvitops/emdash`'s `blocks.test.ts`,
221
+ because the generator cannot import the plugin.
222
+
223
+ `@getvitops/create`: the EmDash template's agent guidance explains the blocks, their limits (a block
224
+ cannot contain blocks; `vitops.*` blocks do not render inside EmDash's built-in `columns`) and the
225
+ `blocks` option.
226
+
227
+ Formats reached: none — no CSS change. Other packages in the `fixed` group: No consumer-facing change
228
+ in this package.
229
+
230
+ - 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`.
231
+
232
+ ### Patch Changes
233
+
234
+ - 734ab60: EmDash gallery and carousel blocks now derive their element id from the block's `_key`, so two blocks with the same label on one page no longer open each other's dialog. `<wc-entries>` column navigation now honours `prefers-reduced-motion`.
235
+ - 7f4f50e: `@getvitops/utils/url` exports `safeHref()` (a Worker-safe scheme allowlist: http, https, mailto, tel and relative), shared by the EmDash blocks and the Astro components. It now also guards the EmDash image-compare block, the review avatar and the cookie-notice policy link. `nodesToHtml`/`toHtml` throw on invalid tag or attribute names and on `on*` event-handler attributes, and drop `href`/`src`-style values that fail `safeHref`. EmDash cards link text now names the card for screen readers.
236
+ - e5bc937: **Security: JSON-LD can no longer break out of its `<script>` tag.** All 24 `schemas/*.astro`
237
+ components inlined `JSON.stringify(jsonLd)` as-is, so a review, FAQ answer or forum post containing
238
+ `</script><script>…` ran as script on the page. They now go through `jsonLdScript()`, which escapes
239
+ `<`, `>`, `&`, U+2028 and U+2029 (still valid JSON — consumers and search engines parse the same
240
+ document). Sites rendering any user-supplied text through a schema component should upgrade promptly.
241
+
242
+ **`createConversionRoute()` hardening** — behaviour changes:
243
+
244
+ - The body-size cap is now enforced on the stream itself, not only on `Content-Length`; a chunked
245
+ request with no header is refused with 413 once it passes `maxBodyBytes`.
246
+ - **Breaking for non-Cloudflare hosts:** `X-Forwarded-For` is no longer used as the rate-limit key by
247
+ default, since off Cloudflare a client can set it freely and mint a new bucket per request. Pass
248
+ `trustForwardedFor: true` when running behind a proxy that overwrites it. Without
249
+ `CF-Connecting-IP` or that opt-in there is no IP and rate limiting is skipped. No change on
250
+ Cloudflare Workers.
251
+
252
+ `@getvitops/emdash`: the link/button block (`vitops.actionLink`) drops hrefs with a scheme other
253
+ than `http`, `https`, `mailto` or `tel` (e.g. `javascript:`, `data:`) and renders nothing for them.
254
+
255
+ Formats reached: none — no CSS change. Other packages in the `fixed` group: No consumer-facing
256
+ change in this package.
257
+
258
+ - Updated dependencies [65a13cc]
259
+ - Updated dependencies [67e2088]
260
+ - Updated dependencies [89265af]
261
+ - Updated dependencies [0574388]
262
+ - Updated dependencies [c3c1bcb]
263
+ - Updated dependencies [0574388]
264
+ - Updated dependencies [1a8231d]
265
+ - Updated dependencies [779d22e]
266
+ - Updated dependencies [0574388]
267
+ - Updated dependencies [8d8aad7]
268
+ - Updated dependencies [0574388]
269
+ - Updated dependencies [1a3e811]
270
+ - Updated dependencies [0574388]
271
+ - Updated dependencies [dde3df3]
272
+ - Updated dependencies [d9f213b]
273
+ - Updated dependencies [7f4f50e]
274
+ - Updated dependencies [7fd5f8f]
275
+ - Updated dependencies [ceb8310]
276
+ - @getvitops/generator@8.2.0
277
+ - @getvitops/utils@8.2.0
278
+
3
279
  ## 1.0.0-alpha.0
4
280
 
5
281
  ### Major Changes
6
282
 
7
283
  - c3c1bcb: **New: the design-system editor, mounted inside the EmDash admin.** `vitopsEmdash({
8
- designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
9
- './site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
284
+ designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import config from
285
+ './site.json' with { type: 'json' }`) adds a `/design-system` admin page mounting
10
286
  `<wc-theme-editor>` — the same live token editor `@getvitops/vite`'s dev server has always
11
287
  offered, now usable by the client directly, no dev server required. An edit is visible
12
288
  site-wide immediately (a runtime `:root` override, served via a new plugin API route and
@@ -67,7 +343,7 @@ designSystemEditor: { config } })` (`config` is your `site.json`, e.g. `import c
67
343
  needs a new GitHub Environment variable, `EMDASH_TARGET_FINGERPRINT` (one per environment),
68
344
  which only a human can set — run `emdash migrate --status` against the real deployed database,
69
345
  review the printed identity, then `gh variable set EMDASH_TARGET_FINGERPRINT --env <env> --body
70
- <value>`. Deploys fail clearly on the migration-apply step until it's set.
346
+ <value>`. Deploys fail clearly on the migration-apply step until it's set.
71
347
  - `lint.options.typeAware`/`typeCheck` in the template's own `vite.config.ts` are commented out,
72
348
  not deleted: vite-plus 1.0 only honours that option in a project's actual root config, and hard
73
349
  errors on it in a nested one (which this file is, sitting inside the `design-system` monorepo).
package/README.md CHANGED
@@ -36,7 +36,7 @@ export default defineConfig({
36
36
  });
37
37
  ```
38
38
 
39
- Tested against `emdash@1.0.x`.
39
+ Tested against `emdash@1.4.x`.
40
40
 
41
41
  ## Rendering mode
42
42
 
@@ -125,19 +125,60 @@ export default defineConfig({
125
125
  in the database (D1 export / EmDash seed round-trip), media in the storage
126
126
  backend (bucket/directory copy). Both directions work.
127
127
 
128
- ## Editor blocks (v1)
129
-
130
- | Slash-menu entry | `_type` | Renders |
131
- | ---------------- | --------------------- | ----------------------------------------------- |
132
- | Link or button | `vitops.actionLink` | sprite-icon `<a>`, styled `.link`/`.btn`/`.cta` |
133
- | Image compare | `vitops.imageCompare` | `<wc-image-compare>` before/after slider |
134
- | Copy snippet | `vitops.copyButton` | `<wc-copy>` with code/inline fallback |
135
- | Banner | `vitops.banner` | `<wc-dismissable>` banner with tone colours |
136
- | Disclosure | `vitops.details` | native `<details>`/`<summary>` |
137
- | Carousel | `vitops.carousel` | `@getvitops/astro`'s `<Carousel />` |
128
+ ## Editor blocks
129
+
130
+ | Slash-menu entry | `_type` | Renders |
131
+ | ---------------- | --------------------- | ---------------------------------------------------------- |
132
+ | Link or button | `vitops.actionLink` | sprite-icon `<a>`, styled `.link`/`.btn`/`.cta` |
133
+ | Image compare | `vitops.imageCompare` | `<wc-image-compare>` before/after slider |
134
+ | Copy snippet | `vitops.copyButton` | `<wc-copy>` with code/inline fallback |
135
+ | Banner | `vitops.banner` | `<wc-dismissable>` banner with tone colours |
136
+ | Disclosure | `vitops.details` | native `<details>`/`<summary>` |
137
+ | Accordion | `vitops.accordion` | a group of `<details>`; optionally one-open-at-a-time |
138
+ | Carousel | `vitops.carousel` | `@getvitops/astro`'s `<Carousel />` over a Slides repeater |
139
+ | Gallery | `vitops.gallery` | `<Gallery />` — thumbnail grid opening a lightbox carousel |
140
+ | Cards | `vitops.cards` | `<Cards />` — a grid of `.card`s, whole-card click with JS |
141
+ | Marquee | `vitops.marquee` | `<Marquee />` — a continuously scrolling row |
138
142
 
139
143
  All blocks render accessible markup that works without JavaScript; the
140
- web components progressively enhance it.
144
+ web components progressively enhance it. Each is a thin adapter over the matching
145
+ `@getvitops/astro` component, so the markup exists once. `@getvitops/generator`'s `TIERS`
146
+ manifest lists which block renders which pattern (its `emdash` key, the `vitops docs components`
147
+ "EmDash blocks" table), and `blocks.test.ts` fails if a block and the manifest disagree.
148
+
149
+ Repeating data (cards, gallery images, accordion items, marquee items, carousel slides) is a
150
+ **repeater** field; images can be a media-library URL or any `http(s)`/relative URL. Link and image
151
+ URLs with any other scheme (`javascript:`, `data:`) are dropped at render time.
152
+
153
+ ### Choosing and extending the blocks
154
+
155
+ ```js
156
+ vitopsEmdash({
157
+ blocks: {
158
+ exclude: ['vitops.marquee'], // or `include: [...]` as an allowlist
159
+ extra: [
160
+ {
161
+ type: 'acme.pricing',
162
+ label: 'Pricing table',
163
+ fields: [/* Block Kit */],
164
+ },
165
+ ],
166
+ },
167
+ });
168
+ ```
169
+
170
+ `extra` adds **editor** definitions only. This plugin cannot render a block it has never heard of,
171
+ so the site maps each of its own types to a component, and can replace a `vitops.*` renderer the same way:
172
+
173
+ ```astro
174
+ <PortableText
175
+ value={page.data.content}
176
+ components={{ type: { 'acme.pricing': PricingTable, 'vitops.cards': MyCards } }}
177
+ />
178
+ ```
179
+
180
+ EmDash merges renderers as built-ins, then plugin `blockComponents`, then this `components` prop, so
181
+ the site's entry wins.
141
182
 
142
183
  ## Script delivery
143
184
 
@@ -154,17 +195,21 @@ loaded on pages that render these blocks. Pick **one**:
154
195
  Enabling both would load the scripts twice (harmless — element registration is
155
196
  guarded — but wasteful).
156
197
 
157
- ## Repeating structured patterns
198
+ ## Patterns that contain other blocks
199
+
200
+ A Portable Text block cannot hold other blocks — a repeater's rows are scalar fields. So a section
201
+ with columns, a hero, or a card holding a button is not a `vitops.*` block. Two routes:
202
+
203
+ - A **`blocks` field** (EmDash's typed page-composition field): define block types such as `hero` or
204
+ `split` in the seed, and map each to an Astro component built from `@getvitops/astro`
205
+ (`Subgrid`, `Cards`, the `.split` classes) with `defineBlockComponents`.
206
+ - **Sections** (`/section`): compose the pattern once as reusable content and let editors insert copies.
158
207
 
159
- EmDash's Portable Text block fields are flat, so patterns with repeating data
160
- (card grids, FAQ lists, spec tables via `wc-entries`, forms) are deliberately
161
- **not** v1 blocks. Use instead:
208
+ EmDash's built-in `columns` block renders its children with marks only, so a `vitops.*` block placed
209
+ inside one does not render. Use a `blocks` field for layout instead.
162
210
 
163
- - **Sections** (`/section`): compose the pattern once as reusable content and
164
- let editors insert copies.
165
- - **Field Kit `list` widgets** on `json` collection fields: model the repeating
166
- rows in the collection schema and render them at the template level with
167
- `Cards.astro` / `NodeRenderer.astro` from `@getvitops/astro`.
211
+ Not yet blocks: tree, entries, reviews (`ReviewCard` reads `.vitops/reviews.json`, which needs a
212
+ build-time import rather than a filesystem read on a Worker), dialog and tabs (no component yet).
168
213
 
169
214
  ## Changelog
170
215
 
package/dist/index.d.mts CHANGED
@@ -1,6 +1,34 @@
1
- import { PluginDescriptor, PortableTextBlockConfig } from "emdash";
1
+ import { ApplySeedBlockTypeInput, PluginDescriptor, PortableTextBlockConfig } from "emdash";
2
2
  import { AstroIntegration } from "astro";
3
- //#region src/design-system-route.d.ts
3
+ /** The subset of EmDash Block Kit field elements these blocks use. */
4
+ type BlockKitFieldDef = Extract<NonNullable<PortableTextBlockConfig['fields']>[number], {
5
+ type: 'text_input' | 'number_input' | 'select' | 'toggle' | 'combobox' | 'repeater' | 'media_picker';
6
+ }>;
7
+ type PortableTextBlockDef = Omit<PortableTextBlockConfig, 'fields'> & {
8
+ fields?: BlockKitFieldDef[];
9
+ };
10
+ export declare const BLOCKS: PortableTextBlockDef[];
11
+ /** Which Vitops blocks a site's editor offers, and any it adds of its own. */
12
+ interface BlockSelection {
13
+ /** Offer only these `vitops.*` types. Unknown names throw — a typo must not silently offer nothing. */
14
+ include?: string[];
15
+ /** Drop these `vitops.*` types. Applied after `include`. */
16
+ exclude?: string[];
17
+ /**
18
+ * The site's own blocks. These are EDITOR definitions only: the plugin cannot render a
19
+ * block it has never heard of, so the site maps each `type` to a component with
20
+ * `<PortableText components={{ type: { … } }} />`. A type that collides with an existing
21
+ * one throws, since the later definition would silently replace the earlier one's fields.
22
+ */
23
+ extra?: PortableTextBlockDef[];
24
+ }
25
+ /**
26
+ * Resolve {@link BlockSelection} against {@link BLOCKS}.
27
+ *
28
+ * Pure so a misconfiguration fails at plugin construction, with the offending name in the
29
+ * message, rather than as an editor menu that is quietly missing something.
30
+ */
31
+ export declare function selectBlocks(selection?: BlockSelection): PortableTextBlockDef[];
4
32
  interface DesignSystemEditorOptions {
5
33
  /**
6
34
  * The site's own config, typically `import config from '../../site.json'
@@ -13,18 +41,37 @@ interface DesignSystemEditorOptions {
13
41
  /** Which `designSystem.themes` entry this edits. Default: `'default'`. */
14
42
  theme?: string;
15
43
  }
16
- //#endregion
17
- //#region src/blocks.d.ts
18
- /** The v1 subset of EmDash Block Kit field elements used by these blocks. */
19
- type BlockKitFieldDef = Extract<NonNullable<PortableTextBlockConfig['fields']>[number], {
20
- type: 'text_input' | 'number_input' | 'select' | 'toggle' | 'combobox';
21
- }>;
22
- type PortableTextBlockDef = Omit<PortableTextBlockConfig, 'fields'> & {
23
- fields?: BlockKitFieldDef[];
24
- };
25
- export declare const BLOCKS: PortableTextBlockDef[];
26
- //#endregion
27
- //#region src/hosting.d.ts
44
+ /**
45
+ * The block reference an agent reads via `vitops docs blocks`: every Portable Text `_type`,
46
+ * its fields and what each saves. Rendered from the same `BLOCKS` the editor menu uses, so
47
+ * it cannot describe a field the editor doesn't offer.
48
+ */
49
+ export declare function renderBlocksDoc(blocks?: readonly PortableTextBlockDef[]): string;
50
+ /** Block-type slugs may not contain a dot, so `vitops.section` is stored as `vitops_section`. */
51
+ export declare const SECTION_SLUG = "vitops_section";
52
+ /** `vitops.section`: kicker, heading, lead-in, tone/backdrop, optional heading-left layout, and a body of blocks. */
53
+ export declare const SECTION_BLOCK_TYPE: ApplySeedBlockTypeInput;
54
+ /**
55
+ * Every block type this plugin ships, in the shape `seed.json`'s `blockTypes` takes. Spread
56
+ * it into the seed (and allow the slugs on the collection's `blocks` field), e.g.
57
+ * `blockTypes: [...vitopsBlockTypes()]`.
58
+ */
59
+ export declare function vitopsBlockTypes(): ApplySeedBlockTypeInput[];
60
+ /** The role chain for a tone: itself, its relatives, then `neutral`. Pure and exported for tests. */
61
+ export declare function toneChain(tone: unknown): string[];
62
+ interface ResolvedTone {
63
+ /** Always `tone`; a block adds `tone--solid` itself when it fills rather than tints. */
64
+ className: string;
65
+ /** Inline `style` value of `--tone-*` properties (no trailing semicolon). Empty when unset. */
66
+ style: string;
67
+ }
68
+ /**
69
+ * Resolve a block's `tone` / `color` fields.
70
+ *
71
+ * Returns an empty style when neither is set, so an unconfigured block carries no tone at
72
+ * all (rather than a neutral one) and stays transparent.
73
+ */
74
+ export declare function resolveTone(tone?: unknown, color?: unknown): ResolvedTone;
28
75
  type HostingTarget = 'cloudflare' | 'node';
29
76
  interface VitopsHostingOptions {
30
77
  /** Hosting target. Overridden by the `HOSTING` env var. Default 'cloudflare'. */
@@ -36,6 +83,13 @@ interface VitopsHostingOptions {
36
83
  mediaBinding?: string;
37
84
  /** D1 Sessions API read-replication mode (default 'auto'). */
38
85
  session?: string;
86
+ /**
87
+ * Batch reads issued together into one D1 round trip (default true).
88
+ * Experimental in emdash: a read and a write started together may run
89
+ * write-first, so await a read before a write that must not affect it.
90
+ * No effect without a session.
91
+ */
92
+ coalesce?: boolean;
39
93
  };
40
94
  node?: {
41
95
  /** SQLite URL (default `DATABASE_PATH` env or 'file:./data/emdash.db'). */
@@ -57,8 +111,6 @@ interface VitopsHosting {
57
111
  storage: unknown;
58
112
  }
59
113
  export declare function vitopsHosting(options?: VitopsHostingOptions): Promise<VitopsHosting>;
60
- //#endregion
61
- //#region src/index.d.ts
62
114
  export interface VitopsEmdashOptions {
63
115
  /**
64
116
  * How the web-component runtime scripts reach the page:
@@ -82,6 +134,19 @@ export interface VitopsEmdashOptions {
82
134
  * reviewed once at plugin-install time, same as any other capability.
83
135
  */
84
136
  designSystemEditor?: DesignSystemEditorOptions | false;
137
+ /**
138
+ * Which blocks the editor offers. All of the `vitops.*` blocks by default. Use `exclude` to
139
+ * drop one a client should not see, `include` for an allowlist, and `extra` to add the
140
+ * site's own — whose renderers the site supplies, see {@link BlockSelection}.
141
+ */
142
+ blocks?: BlockSelection;
143
+ /**
144
+ * The site's colour role names (`Object.keys(site.designSystem.colors.roles)`), offered in
145
+ * every block's `tone` select next to the semantic names. When omitted and
146
+ * `designSystemEditor.config` is set, they are read from that config. With neither, the
147
+ * select offers only the semantic names — an editor can still type a palette colour.
148
+ */
149
+ tones?: string[];
85
150
  }
86
151
  /**
87
152
  * Descriptor factory — import in `astro.config.mjs` and pass to the emdash()
@@ -90,6 +155,5 @@ export interface VitopsEmdashOptions {
90
155
  export declare function vitopsEmdash(options?: VitopsEmdashOptions): PluginDescriptor;
91
156
  /** Runtime side — EmDash imports and calls this from the descriptor's entrypoint. */
92
157
  export declare function createPlugin(options?: VitopsEmdashOptions): import("emdash").ResolvedPlugin<import("emdash").PluginStorageConfig>;
93
- //#endregion
94
- export { type BlockKitFieldDef, type DesignSystemEditorOptions, type HostingTarget, type PortableTextBlockDef, type VitopsHosting, type VitopsHostingOptions, createPlugin as default };
158
+ export { type BlockKitFieldDef, type BlockSelection, type DesignSystemEditorOptions, type HostingTarget, type PortableTextBlockDef, type VitopsHosting, type VitopsHostingOptions, createPlugin as default };
95
159
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/design-system-route.ts","../src/blocks.ts","../src/hosting.ts","../src/index.ts"],"mappings":";;;UAuDiB;;;;;;;;EAQf;;EAEA;;;;;KCjDU,mBAAmB,QAC7B,YAAY;EACV;;KAGQ,uBAAuB,KAAK;EACtC,SAAS;;qBAGE,QAAQ;;;KCOT;UAIK;;EAEf,SAAS;EACT;;IAEE;;IAEA;;IAEA;;EAEF;;IAEE;;IAEA;;IAEA;;IAEA;;;UAIa;EACf,QAAQ;EACR,SAAS;;EAET;;EAEA;;wBAoBoB,cAAc,UAAS,uBAA4B,QAAQ;;;iBCxBhE;;;;;;;;;;;EAWf;;EAEA;;;;;;;;;EASA,qBAAqB;;;;;;wBAOP,aAAa,UAAS,sBAA2B;;wBAmBjD,aAAa,UAAS,uCAAwB,gCAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/blocks.ts","../src/design-system-route.ts","../src/blocks-doc.ts","../src/block-types.ts","../src/astro/blocks/tone.ts","../src/hosting.ts","../src/index.ts"],"mappings":";;;KAuBY,mBAAmB,QAC7B,YAAY;EAEV;;KAWQ,uBAAuB,KAAK;EACtC,SAAS;;qBAmDE,QAAQ;;UA62BJ;;EAEf;;EAEA;;;;;;;EAOA,QAAQ;;;;;;;;wBASM,aAAa,YAAW,iBAAsB;UCn6B7C;;;;;;;;EAQf;;EAEA;;;;;;;wBC9Bc,gBAAgB,kBAAiB;;qBCZpC;;qBAqCA,oBAAoB;;;;;;wBAejB,oBAAoB;;wBCzBpB,UAAU;UAcT;;EAEf;;EAEA;;;;;;;;wBASc,YAAY,gBAAgB,kBAAkB;KC7ClD;UAIK;;EAEf,SAAS;EACT;;IAEE;;IAEA;;IAEA;;;;;;;IAOA;;EAEF;;IAEE;;IAEA;;IAEA;;IAEA;;;UAIa;EACf,QAAQ;EACR,SAAS;;EAET;;EAEA;;wBAoBoB,cAAc,UAAS,uBAA4B,QAAQ;iBCzBhE;;;;;;;;;;;EAWf;;EAEA;;;;;;;;;EASA,qBAAqB;;;;;;EAMrB,SAAS;;;;;;;EAOT;;;;;;wBAOc,aAAa,UAAS,sBAA2B;;wBAmBjD,aAAa,UAAS,uCAAwB,gCAAA"}