@getvitops/create 0.9.0-alpha.0 → 0.9.0-alpha.2

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.
@@ -5,12 +5,12 @@
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "astro dev",
8
- "build": "astro build && vitops emdash check-prerender",
8
+ "build": "vitops emdash build",
9
9
  "preview": "astro preview",
10
10
  "pull-content": "node scripts/pull-content.mjs",
11
11
  "prepare-local-db": "vitops emdash prepare-db",
12
- "deploy": "vitops email check && node scripts/pull-content.mjs && vitops emdash prepare-db && astro build && vitops emdash check-prerender && emdash migrate --wrangler-config wrangler.jsonc && wrangler deploy",
13
- "deploy:dev": "vitops email check && node scripts/pull-content.mjs && vitops emdash prepare-db && astro build && vitops emdash check-prerender && node scripts/build-dev-wrangler.mjs && emdash migrate --wrangler-config dist/server/wrangler.dev.json && wrangler deploy --config dist/server/wrangler.dev.json",
12
+ "deploy": "vitops email check && node scripts/pull-content.mjs && vitops emdash prepare-db && vitops emdash build && emdash migrate --wrangler-config wrangler.jsonc && wrangler deploy",
13
+ "deploy:dev": "vitops email check && node scripts/pull-content.mjs && vitops emdash prepare-db && vitops emdash build && node scripts/build-dev-wrangler.mjs && emdash migrate --wrangler-config dist/server/wrangler.dev.json && wrangler deploy --config dist/server/wrangler.dev.json",
14
14
  "init:github": "vitops setup repo",
15
15
  "init:hosting": "vitops setup hosting",
16
16
  "setup": "vitops setup all",
@@ -18,7 +18,7 @@
18
18
  "build:css": "vitops generate -i site.json -f tailwind -o src/styles",
19
19
  "lint:design": "vitops lint -i site.json -f tailwind --strict",
20
20
  "sop": "vitops generate -i site.json -f css -o .vitops/sop",
21
- "prepare": "vp config"
21
+ "prepare": "vp config && skills experimental_sync -y -a claude-code --no-frozen-lockfile"
22
22
  },
23
23
  "dependencies": {
24
24
  "@astrojs/cloudflare": "^14.0.0",
@@ -27,6 +27,7 @@
27
27
  "@getvitops/astro": "latest",
28
28
  "@getvitops/core": "latest",
29
29
  "@getvitops/emdash": "latest",
30
+ "@getvitops/skills": "latest",
30
31
  "@tailwindcss/vite": "^4.3.3",
31
32
  "astro": "^7.4.4",
32
33
  "emdash": "latest",
@@ -39,6 +40,7 @@
39
40
  "@cloudflare/workers-types": "^4.20260305.1",
40
41
  "@getvitops/cli": "latest",
41
42
  "@iconify-json/fa7-solid": "^1.2.5",
43
+ "skills": "^1.7.6",
42
44
  "typescript": "^6.0.3",
43
45
  "vite": "latest",
44
46
  "vite-plus": "latest",
@@ -1,7 +1,7 @@
1
1
  # Cloudflare static-assets response headers. They apply to files served from the
2
2
  # static asset store — the prerendered pages — and NOT to responses the Worker
3
3
  # renders itself (the EmDash admin at /_emdash/*, its API, anything left
4
- # on-demand). See AGENTS.md "Content Security Policy".
4
+ # on-demand). See .agents/skills/csp.
5
5
  #
6
6
  # REPORT-ONLY on purpose: violations are logged to the browser console and
7
7
  # nothing is blocked, so this cannot break a page. Read the console on the
@@ -6,7 +6,7 @@
6
6
  // `astro build`. The Cloudflare adapter prerenders inside a local workerd with a
7
7
  // LOCAL D1 (`.wrangler/state`), never the deployed database — so on a CI runner
8
8
  // the build would see an empty, auto-seeded database and bake "No home page yet"
9
- // into every page. Production is the truth for content (see AGENTS.md), so this
9
+ // into every page. Production is the truth for content (see .agents/skills/content-and-deploys), so this
10
10
  // copies production's D1 into the local one first.
11
11
  //
12
12
  // wrangler d1 export <db> --remote → .vitops/content.sql
@@ -15,6 +15,7 @@
15
15
  "slug": "pages",
16
16
  "label": "Pages",
17
17
  "labelSingular": "Page",
18
+ "urlPattern": "/{slug}",
18
19
  "supports": ["drafts", "revisions", "seo"],
19
20
  "fields": [
20
21
  {
@@ -81,6 +82,48 @@
81
82
  ]
82
83
  }
83
84
  ],
85
+ "widgetAreas": [
86
+ {
87
+ "name": "announcement",
88
+ "label": "Announcement bar",
89
+ "description": "Shown above the header on every page. Empty until you add a widget.",
90
+ "widgets": []
91
+ },
92
+ {
93
+ "name": "pre-footer",
94
+ "label": "Above the footer",
95
+ "description": "A call to action shown above the footer on every page (Widgets in the admin).",
96
+ "widgets": [
97
+ {
98
+ "type": "content",
99
+ "title": "Ready to talk?",
100
+ "content": [
101
+ {
102
+ "_type": "block",
103
+ "_key": "pf-text",
104
+ "style": "normal",
105
+ "markDefs": [],
106
+ "children": [
107
+ {
108
+ "_type": "span",
109
+ "_key": "pf-text-s",
110
+ "text": "Tell us what you need and we will reply within one business day.",
111
+ "marks": []
112
+ }
113
+ ]
114
+ },
115
+ {
116
+ "_type": "vitops_actionLink",
117
+ "_key": "pf-cta",
118
+ "label": "Contact us",
119
+ "href": "/contact",
120
+ "variant": "cta"
121
+ }
122
+ ]
123
+ }
124
+ ]
125
+ }
126
+ ],
84
127
  "content": {
85
128
  "pages": [
86
129
  {
@@ -119,10 +162,11 @@
119
162
  ]
120
163
  },
121
164
  {
122
- "_type": "vitops.hero",
165
+ "_type": "vitops_hero",
123
166
  "_key": "demo-hero",
124
167
  "eyebrow": "Vitops blocks",
125
168
  "heading": "Build pages from design-system patterns",
169
+ "level": "2",
126
170
  "body": "Every block below renders a framework pattern, so it follows your tokens and works without JavaScript.",
127
171
  "primaryLabel": "Open the admin",
128
172
  "primaryHref": "/_emdash/admin",
@@ -131,7 +175,7 @@
131
175
  "layout": "centered"
132
176
  },
133
177
  {
134
- "_type": "vitops.cards",
178
+ "_type": "vitops_cards",
135
179
  "_key": "demo-cards",
136
180
  "columns": "3",
137
181
  "cards": [
@@ -155,7 +199,7 @@
155
199
  ]
156
200
  },
157
201
  {
158
- "_type": "vitops.accordion",
202
+ "_type": "vitops_accordion",
159
203
  "_key": "demo-accordion",
160
204
  "exclusive": true,
161
205
  "items": [
@@ -173,7 +217,7 @@
173
217
  ]
174
218
  },
175
219
  {
176
- "_type": "vitops.marquee",
220
+ "_type": "vitops_marquee",
177
221
  "_key": "demo-marquee",
178
222
  "pauseOnHover": true,
179
223
  "reverse": false,
@@ -197,14 +241,14 @@
197
241
  ]
198
242
  },
199
243
  {
200
- "_type": "vitops.banner",
244
+ "_type": "vitops_banner",
201
245
  "_key": "welcome-banner",
202
246
  "message": "This banner is a Vitops block — dismiss it, then edit this page in the EmDash admin.",
203
247
  "tone": "info",
204
248
  "dismissible": true
205
249
  },
206
250
  {
207
- "_type": "vitops.details",
251
+ "_type": "vitops_details",
208
252
  "_key": "next-steps",
209
253
  "summary": "Next steps",
210
254
  "body": "Adjust site.json (under designSystem.themes.default) to your brand (colors, type, patterns), replace src/assets/logo.svg, then deploy with `pnpm deploy`.",
@@ -245,7 +289,7 @@
245
289
  "title": "Terms & Conditions",
246
290
  "content": [
247
291
  {
248
- "_type": "vitops.banner",
292
+ "_type": "vitops_banner",
249
293
  "_key": "terms-placeholder",
250
294
  "message": "Placeholder text — replace with your own terms before launch. This is not legal advice.",
251
295
  "tone": "warning",
@@ -346,7 +390,7 @@
346
390
  "title": "Privacy Policy",
347
391
  "content": [
348
392
  {
349
- "_type": "vitops.banner",
393
+ "_type": "vitops_banner",
350
394
  "_key": "privacy-placeholder",
351
395
  "message": "Placeholder text — replace with your own privacy policy before launch. This is not legal advice.",
352
396
  "tone": "warning",
@@ -461,7 +505,7 @@
461
505
  "title": "Contact",
462
506
  "content": [
463
507
  {
464
- "_type": "vitops.banner",
508
+ "_type": "vitops_banner",
465
509
  "_key": "contact-placeholder",
466
510
  "message": "Placeholder text — replace with your contact details before launch.",
467
511
  "tone": "warning",
@@ -482,15 +526,35 @@
482
526
  ]
483
527
  },
484
528
  {
485
- "_type": "vitops.form",
529
+ "_type": "vitops_form",
486
530
  "_key": "contact-form",
487
531
  "heading": "Send us a message",
488
532
  "level": "2",
489
533
  "fields": [
490
- { "label": "Your name", "name": "name", "type": "text", "required": true },
491
- { "label": "Email", "name": "email", "type": "email", "required": true },
492
- { "label": "Phone", "name": "phone", "type": "tel", "required": false },
493
- { "label": "Message", "name": "message", "type": "textarea", "required": true }
534
+ {
535
+ "label": "Your name",
536
+ "name": "name",
537
+ "type": "text",
538
+ "required": true
539
+ },
540
+ {
541
+ "label": "Email",
542
+ "name": "email",
543
+ "type": "email",
544
+ "required": true
545
+ },
546
+ {
547
+ "label": "Phone",
548
+ "name": "phone",
549
+ "type": "tel",
550
+ "required": false
551
+ },
552
+ {
553
+ "label": "Message",
554
+ "name": "message",
555
+ "type": "textarea",
556
+ "required": true
557
+ }
494
558
  ],
495
559
  "submitLabel": "Send message",
496
560
  "successMessage": "Thanks. Your message has been sent and we will reply soon."
@@ -506,7 +570,7 @@
506
570
  "title": "Cookie Notice",
507
571
  "content": [
508
572
  {
509
- "_type": "vitops.banner",
573
+ "_type": "vitops_banner",
510
574
  "_key": "cookies-placeholder",
511
575
  "message": "Placeholder text — replace with your own cookie notice before launch. This is not legal advice. `vitops legal` can generate a draft from site.json.",
512
576
  "tone": "warning",
@@ -23,8 +23,14 @@
23
23
  }
24
24
  },
25
25
  "roles": {
26
- "neutral": { "hue": "ink", "kind": "surface" },
27
- "surface": { "hue": "ink", "kind": "surface" },
26
+ "neutral": {
27
+ "hue": "ink",
28
+ "kind": "surface"
29
+ },
30
+ "surface": {
31
+ "hue": "ink",
32
+ "kind": "surface"
33
+ },
28
34
  "ui-primary": "brand",
29
35
  "brand-primary": "brand",
30
36
  "info": "sky",
@@ -221,6 +227,46 @@
221
227
  "size": "var(--text-m)",
222
228
  "weight": 400,
223
229
  "line-height": "1.55"
230
+ },
231
+ "title": {
232
+ "family": "display",
233
+ "size": "var(--text-2xl)",
234
+ "weight": 600,
235
+ "line-height": "1.15",
236
+ "tracking": "-0.025em",
237
+ "text-wrap": "balance"
238
+ },
239
+ "lead": {
240
+ "family": "sans",
241
+ "size": "var(--text-l)",
242
+ "weight": 400,
243
+ "line-height": "1.5",
244
+ "text-wrap": "pretty"
245
+ },
246
+ "caption": {
247
+ "family": "sans",
248
+ "size": "var(--text-s)",
249
+ "weight": 400,
250
+ "line-height": "1.3",
251
+ "text-wrap": "pretty",
252
+ "color": "var(--color-text-neutral-muted)"
253
+ },
254
+ "eyebrow": {
255
+ "family": "sans",
256
+ "size": "var(--text-s)",
257
+ "weight": 600,
258
+ "line-height": "1.3",
259
+ "tracking": "0.08em",
260
+ "text-transform": "uppercase",
261
+ "text-wrap": "balance"
262
+ },
263
+ "footnote": {
264
+ "family": "sans",
265
+ "size": "var(--text-xs)",
266
+ "weight": 400,
267
+ "line-height": "1.4",
268
+ "text-wrap": "pretty",
269
+ "color": "var(--color-text-neutral-muted)"
224
270
  }
225
271
  },
226
272
  "headings": {
@@ -263,6 +309,17 @@
263
309
  },
264
310
  "site": {
265
311
  "defaultLocale": "en",
312
+ "templates": {
313
+ "contact": {
314
+ "type": "form",
315
+ "fields": [
316
+ { "name": "name", "type": "text", "required": true },
317
+ { "name": "email", "type": "email", "required": true },
318
+ { "name": "tel", "type": "tel" },
319
+ { "name": "message", "type": "textarea", "required": true }
320
+ ]
321
+ }
322
+ },
266
323
  "locales": {
267
324
  "en": {
268
325
  "name": "English"
@@ -7,6 +7,7 @@ import SiteFooter from '@getvitops/astro/components/SiteFooter.astro';
7
7
  import SiteNav from '@getvitops/astro/components/SiteNav.astro';
8
8
  import { loadNavMenus } from '@getvitops/astro/components/sitenav.ts';
9
9
  import { EmDashHead } from 'emdash/ui';
10
+ import { VitopsWidgetArea } from '@getvitops/emdash/astro';
10
11
  import config from '../../site.json' with { type: 'json' };
11
12
  // Design-system CSS, layout-scoped (vitops css.inject: false): only pages
12
13
  // rendered through this layout get it — keeps it out of /_emdash/admin, while
@@ -72,6 +73,8 @@ const orgName = config.organization?.name || siteTitle;
72
73
  <VitopsHead />
73
74
  </head>
74
75
  <body>
76
+ <!-- Widget areas (Widgets in the admin): the same content wherever they appear. -->
77
+ <VitopsWidgetArea name="announcement" class="centered" />
75
78
  <header class="navbar-sticky centered">
76
79
  <div class="navbar">
77
80
  <a class="brand flex items-center" href="/" aria-label={`${siteTitle} home`}>
@@ -89,6 +92,8 @@ const orgName = config.organization?.name || siteTitle;
89
92
  <slot />
90
93
  </main>
91
94
 
95
+ <VitopsWidgetArea name="pre-footer" class="centered" />
96
+
92
97
  <SiteFooter class="centered" sitemap={footerSitemap} legal={footerLegal} name={orgName} />
93
98
 
94
99
  <style>
@@ -8,7 +8,7 @@ import Layout from '../layouts/Layout.astro';
8
8
  // the content database — `scripts/pull-content.mjs` loads production's into the
9
9
  // local build database first (it is part of `deploy`/`deploy:dev`) — so an edit
10
10
  // in the admin goes live on the next rebuild, which a publish webhook triggers
11
- // (see AGENTS.md "Rendering and freshness").
11
+ // (see .agents/skills/content-and-deploys).
12
12
  //
13
13
  // There is no `Astro.rewrite('/404')` here and no on-demand fallback: a URL that
14
14
  // isn't in the list below simply has no route, so Astro serves the prerendered
@@ -1,8 +1,8 @@
1
1
  /**
2
- * The site's one on-demand endpoint: contact-form posts (the `vitops.form` block) and the
2
+ * The site's one on-demand endpoint: contact-form posts (the `vitops_form` block) and the
3
3
  * conversion beacons (`<Tracking />`, `tel:`/`mailto:` clicks) all arrive here.
4
4
  *
5
- * Every public page is prerendered (see AGENTS.md, "Rendering and freshness"); this route sets
5
+ * Every public page is prerendered (see .agents/skills/content-and-deploys); this route sets
6
6
  * `prerender = false` because it has to run on each request. It is the one exception, and the
7
7
  * Cloudflare adapter handles it in the Worker beside the EmDash admin.
8
8
  *
@@ -5,8 +5,8 @@ import Layout from '../layouts/Layout.astro';
5
5
 
6
6
  // Prerendered, like every page in this template: written to static HTML at build
7
7
  // time from the `home` entry (seed/seed.json → pages/home). Edit it in the admin;
8
- // a publish triggers a rebuild that bakes the change in (AGENTS.md "Rendering and
9
- // freshness"). The Vitops blocks from @getvitops/emdash render automatically —
8
+ // a publish triggers a rebuild that bakes the change in (.agents/skills/content-and-deploys,
9
+ // "Rendering and freshness"). The Vitops blocks from @getvitops/emdash render automatically —
10
10
  // EmDash merges the plugin's block components into <PortableText>.
11
11
  //
12
12
  // Anything on a page that must be fresh per request (a stock count, a signed-in
@@ -70,6 +70,13 @@ activates to `script-src` / `connect-src`, any embed to `frame-src`, and hash an
70
70
  inline script you add yourself. Hashes rather than nonces because a static page has no
71
71
  per-request response to carry one.
72
72
 
73
+ ## Skills
74
+
75
+ `@getvitops/skills` ships agent skills that update with the package. `skills experimental_sync` (run
76
+ by `prepare`, so by `vp install`) links them into `.agents/skills/` and `.claude/skills/`; they are
77
+ gitignored. The set is written for EmDash sites, so `csp` is the one that applies here. A skill you
78
+ write yourself with the same name is never replaced. `setup` is this site's own.
79
+
73
80
  ## Commands
74
81
 
75
82
  This is a **Vite+** project: use `vp` (and its `vpr` shorthand), not `pnpm`/`npm`/`npx`.
@@ -25,3 +25,13 @@ public/vitops/
25
25
 
26
26
  # os / editor
27
27
  .DS_Store
28
+
29
+ # agent skills linked from @getvitops/skills by `skills experimental_sync` (see skills-lock.json)
30
+ .agents/skills/custom-blocks
31
+ .agents/skills/content-and-deploys
32
+ .agents/skills/contact-form
33
+ .agents/skills/csp
34
+ .claude/skills/custom-blocks
35
+ .claude/skills/content-and-deploys
36
+ .claude/skills/contact-form
37
+ .claude/skills/csp
@@ -16,15 +16,17 @@
16
16
  "build:css": "vitops generate -i site.json -f css -o src/styles",
17
17
  "lint:design": "vitops lint -i site.json -f css --strict",
18
18
  "sop": "vitops generate -i site.json -f css -o .vitops/sop",
19
- "prepare": "vp config"
19
+ "prepare": "vp config && skills experimental_sync -y -a claude-code --no-frozen-lockfile"
20
20
  },
21
21
  "dependencies": {
22
22
  "@getvitops/astro": "latest",
23
+ "@getvitops/skills": "latest",
23
24
  "astro": "^7.4.4"
24
25
  },
25
26
  "devDependencies": {
26
27
  "@astrojs/check": "^0.9.12",
27
28
  "@getvitops/cli": "latest",
29
+ "skills": "^1.7.6",
28
30
  "typescript": "^6.0.3",
29
31
  "vite": "latest",
30
32
  "vite-plus": "latest",
@@ -1,104 +0,0 @@
1
- ---
2
- name: custom-blocks
3
- description: Decide whether a page section needs a new EmDash block type, and if so how to name, build, version and retire it. Use before creating, renaming or deleting any block type, any `src/components/blocks/*` renderer, or when asked to "add a block", "make an editable section" or "add a component the editors can place".
4
- ---
5
-
6
- # Custom blocks
7
-
8
- Pages are composed from **blocks** so non-technical editors can rearrange and edit
9
- them in the CMS. A new block type is the most expensive way to get a section onto a
10
- page: it adds a content-model entry, a renderer, a field schema and a migration
11
- burden, and **a block type cannot be renamed or deleted** once pages use it (see
12
- "Lifecycle"). So work down this list and stop at the first rung that fits.
13
-
14
- ## 1. Decision rule
15
-
16
- 1. **Compose what exists.** The `vitops.*` blocks from `@getvitops/emdash` (cards,
17
- carousel, gallery, accordion, details, banner, hero, marquee, image-compare,
18
- copy button, action link, …) resolve inside any block type's `portableText` field,
19
- so "a section with a heading, intro and then a row of cards" is a section block
20
- whose body holds `vitops.cards` — not a new "cards section" type. Check what is
21
- installed first: `vp exec vitops docs components` lists every block the toolchain
22
- ships and which tier provides it.
23
- 2. **Extend an existing block** if one is close. An extra option (a `variant`
24
- select, an optional slot) on a block that already exists is a version bump on that
25
- block, not a new type. Prefer this when the new thing is a variation of the same
26
- content shape.
27
- 3. **Site-local block type** only when the content shape is specific to this site
28
- (a bespoke dashboard mock, an A-versus-B argument with hard-coded figures). Keep
29
- the renderer in `src/components/blocks/` and the type name prefixed for the site
30
- (see "Naming"). Code-owned widgets expose only a caption field; the numbers live in
31
- code, not in the CMS.
32
- 4. **Upstream it** when a second site would plausibly want it. Don't build a
33
- generic block here and quietly fork it: raise it against the toolchain
34
- (`getvitops/design-system`) with the shape you used, so every site gets it. Until
35
- it ships, keep the local version and mark it `local (watch)` in a comment, so it
36
- is easy to retire when the real one lands.
37
-
38
- A form, a section container, a stats row, a logo grid, a CTA band and a steps list
39
- are all _generic_ — expect to find them upstream or to upstream them, not to keep
40
- them local.
41
-
42
- ## 2. Naming: check the framework's vocabulary first
43
-
44
- Block types are permanent, so the name has to be right the first time. Before you
45
- name one:
46
-
47
- - Run `vp exec vitops docs components` and `vp exec vitops docs classes`. If the
48
- framework already has a pattern by that name, **use its meaning**: `badge` is a
49
- _static_ label; `tag` is a label that may be static or dynamic (editable,
50
- dismissable). Never `chip` or `pill`. A block called `chips` that renders badges
51
- cost one site a new type, a content migration and a permanently retired type.
52
- - Prefix site-local types with a short site code (`acme_section`), never `vitops.`
53
- — that namespace belongs to the toolchain.
54
- - Name the thing, not its look: `section`, `steps`, `logos`; not `green-box`.
55
-
56
- ## 3. Field and renderer conventions
57
-
58
- - **Colour: `tone` plus optional `color`.** `tone` takes a **role** name from
59
- `site.json`'s `colors.roles` (preferred), falling back to a semantic name
60
- (`info` `success` `warning` `danger`); `color` is the escape hatch for a palette
61
- hue/step and should be rare — a site that sets it on every item is telling you a
62
- role is missing. A select field can only list static options, so either list the
63
- roles you actually use or take a validated string. Resolve it in one shared helper
64
- (`src/components/blocks/tone.ts`) rather than per block.
65
- - **Class names cannot be built from field values.** Tailwind only emits classes it
66
- can read in source, so `` `bg-${tone}` `` produces nothing. Resolve a tone to CSS
67
- custom properties (or a lookup table of whole class names) instead.
68
- - **Links are `url` fields**, not `string`: EmDash's `url` type accepts site-relative
69
- paths. (`string` is a mistake that needs a breaking version to undo.)
70
- - **Repeater sub-fields are scalar-only** (no `portableText`, no links inside text),
71
- so a checklist is one newline-separated `text` field. Don't fight it; rich item
72
- bodies need EmDash to lift that limit.
73
- - **Blocks don't nest.** A "section containing stats" is the section's own body
74
- holding `vitops.*` blocks, or a flat sequence of sibling blocks; per-section
75
- backdrops don't carry across siblings.
76
- - **Reach for framework classes** (`centered`, `region`, `rhythm`, `subgrid`,
77
- `cluster`, `font-*`, `card`, `badge`, `tag`) in the renderer; run
78
- `vpr lint:design` — hand-written layout CSS in a block is the signal that the
79
- framework is missing a pattern worth upstreaming.
80
- - A card title is a heading: render a real `<h2>`–`<h4>` with a level prop, not a
81
- styled `<p>`.
82
-
83
- ## 4. Lifecycle
84
-
85
- - **Create** with the schema tools (the `schema_*` actions on the site's MCP server,
86
- `/_emdash/api/mcp`) or the admin; commit the matching renderer and the
87
- `emdash export-seed` output so a fresh environment gets the same model.
88
- - **A breaking change is a new version**, not an edit: add the version, migrate the
89
- entries that use the old one, then activate it. Two things the tooling does not say:
90
- `schema_activate_block_type_version`'s `expectedFingerprint` is the fingerprint of
91
- the **currently active** version, not the one you are activating (a mismatch is a
92
- `CONFLICT`), and `schema_update_block_type` **replaces the whole `fields` list**, so
93
- send every field, not just the changed one.
94
- - **Migrating content:** `emdash content update` has no `--migrate-blocks` flag; the
95
- REST route `PUT /_emdash/api/content/<collection>/<id>` with
96
- `{ data, _rev, migrateBlocks: true }` does (it takes Portable Text arrays; the CLI
97
- and MCP take Markdown). Updating a published entry stages a **draft** — a separate
98
- `content publish` is needed before the live page changes, and (because pages are
99
- prerendered) a rebuild after that.
100
- - **Retiring is not deleting.** A type that was renamed or superseded stays in the
101
- database and the seed permanently; keep its renderer mapping old values so existing
102
- content still renders, mark it retired, and stop offering it to editors.
103
- - **Typed fields:** `emdash types` does not type `blocks` fields (they come out as
104
- `unknown[]`); hand-write a value type per renderer until EmDash generates it.