@cmssy/cli 0.22.3 → 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/README.md +49 -650
- package/dist/cli.js +706 -328
- package/package.json +26 -73
- package/templates/block/Component.module.css.tpl +12 -0
- package/templates/block/Component.tsx.tpl +15 -0
- package/templates/block/block.ts.tpl +11 -0
- package/templates/init/app/[[...path]]/page.tsx +19 -0
- package/templates/init/app/api/draft/route.ts +5 -0
- package/templates/init/blocks/hero/Hero.module.css +131 -0
- package/templates/init/blocks/hero/Hero.tsx +100 -0
- package/templates/init/blocks/hero/block.ts +21 -0
- package/templates/init/cmssy/blocks.ts +5 -0
- package/templates/init/cmssy/editor.tsx +10 -0
- package/templates/init/cmssy.config.ts +8 -0
- package/templates/init/env.example +4 -0
- package/templates/init/next.config.mjs +8 -0
- package/templates/init/proxy.ts +32 -0
- package/config.d.ts +0 -3
- package/config.js +0 -4
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/add-source.d.ts +0 -13
- package/dist/commands/add-source.d.ts.map +0 -1
- package/dist/commands/add-source.js +0 -273
- package/dist/commands/add-source.js.map +0 -1
- package/dist/commands/build.d.ts +0 -7
- package/dist/commands/build.d.ts.map +0 -1
- package/dist/commands/build.js +0 -127
- package/dist/commands/build.js.map +0 -1
- package/dist/commands/codegen.d.ts +0 -7
- package/dist/commands/codegen.d.ts.map +0 -1
- package/dist/commands/codegen.js +0 -150
- package/dist/commands/codegen.js.map +0 -1
- package/dist/commands/configure.d.ts +0 -6
- package/dist/commands/configure.d.ts.map +0 -1
- package/dist/commands/configure.js +0 -39
- package/dist/commands/configure.js.map +0 -1
- package/dist/commands/create.d.ts +0 -18
- package/dist/commands/create.d.ts.map +0 -1
- package/dist/commands/create.js +0 -403
- package/dist/commands/create.js.map +0 -1
- package/dist/commands/dev.d.ts +0 -6
- package/dist/commands/dev.d.ts.map +0 -1
- package/dist/commands/dev.js +0 -144
- package/dist/commands/dev.js.map +0 -1
- package/dist/commands/doctor.d.ts +0 -2
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js +0 -230
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/init.d.ts +0 -6
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -414
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/lib.d.ts +0 -17
- package/dist/commands/lib.d.ts.map +0 -1
- package/dist/commands/lib.js +0 -132
- package/dist/commands/lib.js.map +0 -1
- package/dist/commands/link.d.ts +0 -8
- package/dist/commands/link.d.ts.map +0 -1
- package/dist/commands/link.js +0 -116
- package/dist/commands/link.js.map +0 -1
- package/dist/commands/migrate.d.ts +0 -2
- package/dist/commands/migrate.d.ts.map +0 -1
- package/dist/commands/migrate.js +0 -231
- package/dist/commands/migrate.js.map +0 -1
- package/dist/commands/package.d.ts +0 -14
- package/dist/commands/package.d.ts.map +0 -1
- package/dist/commands/package.js +0 -137
- package/dist/commands/package.js.map +0 -1
- package/dist/commands/publish-block-buildtime.d.ts +0 -8
- package/dist/commands/publish-block-buildtime.d.ts.map +0 -1
- package/dist/commands/publish-block-buildtime.js +0 -290
- package/dist/commands/publish-block-buildtime.js.map +0 -1
- package/dist/commands/publish-template.d.ts +0 -22
- package/dist/commands/publish-template.d.ts.map +0 -1
- package/dist/commands/publish-template.js +0 -345
- package/dist/commands/publish-template.js.map +0 -1
- package/dist/commands/publish.d.ts +0 -15
- package/dist/commands/publish.d.ts.map +0 -1
- package/dist/commands/publish.js +0 -1003
- package/dist/commands/publish.js.map +0 -1
- package/dist/commands/skills.d.ts +0 -11
- package/dist/commands/skills.d.ts.map +0 -1
- package/dist/commands/skills.js +0 -178
- package/dist/commands/skills.js.map +0 -1
- package/dist/commands/sync.d.ts +0 -6
- package/dist/commands/sync.d.ts.map +0 -1
- package/dist/commands/sync.js +0 -208
- package/dist/commands/sync.js.map +0 -1
- package/dist/commands/test.d.ts +0 -8
- package/dist/commands/test.d.ts.map +0 -1
- package/dist/commands/test.js +0 -60
- package/dist/commands/test.js.map +0 -1
- package/dist/commands/upload.d.ts +0 -11
- package/dist/commands/upload.d.ts.map +0 -1
- package/dist/commands/upload.js +0 -128
- package/dist/commands/upload.js.map +0 -1
- package/dist/commands/workspaces.d.ts +0 -2
- package/dist/commands/workspaces.d.ts.map +0 -1
- package/dist/commands/workspaces.js +0 -62
- package/dist/commands/workspaces.js.map +0 -1
- package/dist/dev-ui/app.js +0 -1284
- package/dist/dev-ui/index.html +0 -1511
- package/dist/dev-ui-react/App.tsx +0 -164
- package/dist/dev-ui-react/__tests__/previewData.test.ts +0 -193
- package/dist/dev-ui-react/components/BlocksList.tsx +0 -232
- package/dist/dev-ui-react/components/Editor.tsx +0 -469
- package/dist/dev-ui-react/components/Preview.tsx +0 -146
- package/dist/dev-ui-react/hooks/useBlocks.ts +0 -80
- package/dist/dev-ui-react/index.html +0 -13
- package/dist/dev-ui-react/main.tsx +0 -8
- package/dist/dev-ui-react/styles.css +0 -856
- package/dist/dev-ui-react/types.ts +0 -45
- package/dist/skills/claude/cmssy-block.md +0 -454
- package/dist/skills/claude/cmssy-mcp-content.md +0 -231
- package/dist/test-helpers/index.d.ts +0 -23
- package/dist/test-helpers/index.d.ts.map +0 -1
- package/dist/test-helpers/index.js +0 -50
- package/dist/test-helpers/index.js.map +0 -1
- package/dist/types/block-config.d.ts +0 -6
- package/dist/types/block-config.d.ts.map +0 -1
- package/dist/types/block-config.js +0 -12
- package/dist/types/block-config.js.map +0 -1
- package/dist/types/publish-job.d.ts +0 -61
- package/dist/types/publish-job.d.ts.map +0 -1
- package/dist/types/publish-job.js +0 -13
- package/dist/types/publish-job.js.map +0 -1
- package/dist/utils/api-error.d.ts +0 -10
- package/dist/utils/api-error.d.ts.map +0 -1
- package/dist/utils/api-error.js +0 -55
- package/dist/utils/api-error.js.map +0 -1
- package/dist/utils/block-config.d.ts +0 -87
- package/dist/utils/block-config.d.ts.map +0 -1
- package/dist/utils/block-config.js +0 -214
- package/dist/utils/block-config.js.map +0 -1
- package/dist/utils/blocks-meta-cache.d.ts +0 -28
- package/dist/utils/blocks-meta-cache.d.ts.map +0 -1
- package/dist/utils/blocks-meta-cache.js +0 -72
- package/dist/utils/blocks-meta-cache.js.map +0 -1
- package/dist/utils/builder.d.ts +0 -34
- package/dist/utils/builder.d.ts.map +0 -1
- package/dist/utils/builder.js +0 -140
- package/dist/utils/builder.js.map +0 -1
- package/dist/utils/cmssy-config.d.ts +0 -16
- package/dist/utils/cmssy-config.d.ts.map +0 -1
- package/dist/utils/cmssy-config.js +0 -19
- package/dist/utils/cmssy-config.js.map +0 -1
- package/dist/utils/config.d.ts +0 -9
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/config.js +0 -51
- package/dist/utils/config.js.map +0 -1
- package/dist/utils/dependency-check.d.ts +0 -21
- package/dist/utils/dependency-check.d.ts.map +0 -1
- package/dist/utils/dependency-check.js +0 -56
- package/dist/utils/dependency-check.js.map +0 -1
- package/dist/utils/dev-generator/api-routes/blocks.d.ts +0 -3
- package/dist/utils/dev-generator/api-routes/blocks.d.ts.map +0 -1
- package/dist/utils/dev-generator/api-routes/blocks.js +0 -194
- package/dist/utils/dev-generator/api-routes/blocks.js.map +0 -1
- package/dist/utils/dev-generator/api-routes/config.d.ts +0 -2
- package/dist/utils/dev-generator/api-routes/config.d.ts.map +0 -1
- package/dist/utils/dev-generator/api-routes/config.js +0 -107
- package/dist/utils/dev-generator/api-routes/config.js.map +0 -1
- package/dist/utils/dev-generator/api-routes/preview.d.ts +0 -2
- package/dist/utils/dev-generator/api-routes/preview.d.ts.map +0 -1
- package/dist/utils/dev-generator/api-routes/preview.js +0 -50
- package/dist/utils/dev-generator/api-routes/preview.js.map +0 -1
- package/dist/utils/dev-generator/api-routes/workspaces.d.ts +0 -2
- package/dist/utils/dev-generator/api-routes/workspaces.d.ts.map +0 -1
- package/dist/utils/dev-generator/api-routes/workspaces.js +0 -128
- package/dist/utils/dev-generator/api-routes/workspaces.js.map +0 -1
- package/dist/utils/dev-generator/helpers.d.ts +0 -23
- package/dist/utils/dev-generator/helpers.d.ts.map +0 -1
- package/dist/utils/dev-generator/helpers.js +0 -128
- package/dist/utils/dev-generator/helpers.js.map +0 -1
- package/dist/utils/dev-generator/home-page.d.ts +0 -2
- package/dist/utils/dev-generator/home-page.d.ts.map +0 -1
- package/dist/utils/dev-generator/home-page.js +0 -643
- package/dist/utils/dev-generator/home-page.js.map +0 -1
- package/dist/utils/dev-generator/index.d.ts +0 -11
- package/dist/utils/dev-generator/index.d.ts.map +0 -1
- package/dist/utils/dev-generator/index.js +0 -85
- package/dist/utils/dev-generator/index.js.map +0 -1
- package/dist/utils/dev-generator/layout.d.ts +0 -3
- package/dist/utils/dev-generator/layout.d.ts.map +0 -1
- package/dist/utils/dev-generator/layout.js +0 -63
- package/dist/utils/dev-generator/layout.js.map +0 -1
- package/dist/utils/dev-generator/next-config.d.ts +0 -3
- package/dist/utils/dev-generator/next-config.d.ts.map +0 -1
- package/dist/utils/dev-generator/next-config.js +0 -111
- package/dist/utils/dev-generator/next-config.js.map +0 -1
- package/dist/utils/dev-generator/preview-pages.d.ts +0 -3
- package/dist/utils/dev-generator/preview-pages.d.ts.map +0 -1
- package/dist/utils/dev-generator/preview-pages.js +0 -833
- package/dist/utils/dev-generator/preview-pages.js.map +0 -1
- package/dist/utils/dev-generator.d.ts +0 -12
- package/dist/utils/dev-generator.d.ts.map +0 -1
- package/dist/utils/dev-generator.js +0 -1752
- package/dist/utils/dev-generator.js.map +0 -1
- package/dist/utils/field-schema.d.ts +0 -13
- package/dist/utils/field-schema.d.ts.map +0 -1
- package/dist/utils/field-schema.js +0 -182
- package/dist/utils/field-schema.js.map +0 -1
- package/dist/utils/graphql.d.ts +0 -39
- package/dist/utils/graphql.d.ts.map +0 -1
- package/dist/utils/graphql.js +0 -138
- package/dist/utils/graphql.js.map +0 -1
- package/dist/utils/lib-manifest.d.ts +0 -7
- package/dist/utils/lib-manifest.d.ts.map +0 -1
- package/dist/utils/lib-manifest.js +0 -59
- package/dist/utils/lib-manifest.js.map +0 -1
- package/dist/utils/publish-helpers.d.ts +0 -43
- package/dist/utils/publish-helpers.d.ts.map +0 -1
- package/dist/utils/publish-helpers.js +0 -215
- package/dist/utils/publish-helpers.js.map +0 -1
- package/dist/utils/publish-helpers.test.d.ts +0 -2
- package/dist/utils/publish-helpers.test.d.ts.map +0 -1
- package/dist/utils/publish-helpers.test.js +0 -24
- package/dist/utils/publish-helpers.test.js.map +0 -1
- package/dist/utils/resolve-workspace.d.ts +0 -6
- package/dist/utils/resolve-workspace.d.ts.map +0 -1
- package/dist/utils/resolve-workspace.js +0 -32
- package/dist/utils/resolve-workspace.js.map +0 -1
- package/dist/utils/scanner.d.ts +0 -42
- package/dist/utils/scanner.d.ts.map +0 -1
- package/dist/utils/scanner.js +0 -156
- package/dist/utils/scanner.js.map +0 -1
- package/dist/utils/schema-diff.d.ts +0 -22
- package/dist/utils/schema-diff.d.ts.map +0 -1
- package/dist/utils/schema-diff.js +0 -107
- package/dist/utils/schema-diff.js.map +0 -1
- package/dist/utils/source-collector.d.ts +0 -19
- package/dist/utils/source-collector.d.ts.map +0 -1
- package/dist/utils/source-collector.js +0 -565
- package/dist/utils/source-collector.js.map +0 -1
- package/dist/utils/theme-builder.d.ts +0 -12
- package/dist/utils/theme-builder.d.ts.map +0 -1
- package/dist/utils/theme-builder.js +0 -117
- package/dist/utils/theme-builder.js.map +0 -1
- package/dist/utils/type-generator.d.ts +0 -9
- package/dist/utils/type-generator.d.ts.map +0 -1
- package/dist/utils/type-generator.js +0 -86
- package/dist/utils/type-generator.js.map +0 -1
- package/dist/utils/version.d.ts +0 -10
- package/dist/utils/version.d.ts.map +0 -1
- package/dist/utils/version.js +0 -30
- package/dist/utils/version.js.map +0 -1
- package/dist/utils/versions.d.ts.map +0 -1
- package/dist/utils/versions.js.map +0 -1
- package/src/dev-app/app/api/blocks/[name]/config/route.ts +0 -140
- package/src/dev-app/app/api/blocks/route.ts +0 -61
- package/src/dev-app/app/api/config/route.ts +0 -98
- package/src/dev-app/app/api/context/route.ts +0 -126
- package/src/dev-app/app/api/preview/[name]/route.ts +0 -137
- package/src/dev-app/app/api/workspaces/route.ts +0 -124
- package/src/dev-app/app/layout.tsx +0 -18
- package/src/dev-app/app/page.tsx +0 -1785
- package/test.d.ts +0 -1
- package/test.js +0 -2
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export interface Block {
|
|
2
|
-
type: 'block' | 'template';
|
|
3
|
-
name: string;
|
|
4
|
-
displayName: string;
|
|
5
|
-
description?: string;
|
|
6
|
-
category?: string;
|
|
7
|
-
tags?: string[];
|
|
8
|
-
version: string;
|
|
9
|
-
hasConfig?: boolean;
|
|
10
|
-
schema?: Record<string, FieldConfig>;
|
|
11
|
-
pages?: TemplatePage[];
|
|
12
|
-
layoutPositions?: LayoutPosition[];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface FieldConfig {
|
|
16
|
-
type: string;
|
|
17
|
-
label: string;
|
|
18
|
-
required?: boolean;
|
|
19
|
-
placeholder?: string;
|
|
20
|
-
defaultValue?: unknown;
|
|
21
|
-
helpText?: string;
|
|
22
|
-
options?: Array<{ label: string; value: string }>;
|
|
23
|
-
schema?: Record<string, FieldConfig>;
|
|
24
|
-
minItems?: number;
|
|
25
|
-
maxItems?: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface TemplatePage {
|
|
29
|
-
name: string;
|
|
30
|
-
slug: string;
|
|
31
|
-
blocksCount: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface LayoutPosition {
|
|
35
|
-
position: 'header' | 'footer' | 'sidebar_left' | 'sidebar_right' | 'before_content' | 'after_content';
|
|
36
|
-
type: string;
|
|
37
|
-
content?: Record<string, unknown>;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface Filters {
|
|
41
|
-
search: string;
|
|
42
|
-
type: 'all' | 'block' | 'template';
|
|
43
|
-
category: string;
|
|
44
|
-
tags: string[];
|
|
45
|
-
}
|
|
@@ -1,454 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: cmssy-block
|
|
3
|
-
description: "Run the full cmssy CLI lifecycle - init, link, scaffold/edit blocks and templates, dev/build/test, publish, sync. Trigger when the user asks to: initialize a cmssy project, link to a workspace, create/add a new block or template, add a field to a block's schema, edit config.ts, run cmssy dev/build/test/doctor, publish a block, bump a block version, sync a block from the design library, or work with defineBlock/defineTemplate. Trigger on words: cmssy, blok, block, template, publish, opublikuj, workspace, link, sync, defineBlock, defineTemplate, scaffold, zescafoldwac."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# cmssy-block
|
|
7
|
-
|
|
8
|
-
Operates the full cmssy CLI workflow for blocks and templates in a project like `cmssy-marketing`. Covers every command the CLI exposes: `init`, `link`, `workspaces`, `create`, `dev`, `test`, `build`, `publish-block`, `publish-template`, `sync`, `lib`, `skills`, `doctor`. (`configure`, `migrate`, `codegen` exist but are hidden/legacy - don't use them.)
|
|
9
|
-
|
|
10
|
-
## 0. Orientation
|
|
11
|
-
|
|
12
|
-
```bash
|
|
13
|
-
cmssy --version # CLI must be installed globally
|
|
14
|
-
cmssy doctor # all-in-one health check
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
`cmssy doctor` is the canonical precheck. It verifies:
|
|
18
|
-
|
|
19
|
-
- Node >= 18, npm, Next.js, React versions
|
|
20
|
-
- `cmssy.config.js` exists
|
|
21
|
-
- `.env` with `CMSSY_API_URL`, `CMSSY_API_TOKEN`, `CMSSY_WORKSPACE_ID`
|
|
22
|
-
- API reachable, token valid, workspace accessible
|
|
23
|
-
|
|
24
|
-
Run it before any non-trivial operation. If something fails, fix that first - don't proceed.
|
|
25
|
-
|
|
26
|
-
The project uses **pnpm**. Never run `npm install` - use `pnpm install`, `pnpm typecheck`, `pnpm lint`.
|
|
27
|
-
|
|
28
|
-
## 1. Project lifecycle (in order)
|
|
29
|
-
|
|
30
|
-
### 1.1 `cmssy init` - start a new project or bolt onto an existing one
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
cmssy init <name> # creates ./<name> via create-next-app + cmssy scaffolding
|
|
34
|
-
cmssy init -y <name> # skip prompts, use defaults
|
|
35
|
-
cmssy init # add cmssy to the current Next.js project
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
Outputs: `cmssy.config.js`, `blocks/`, `templates/`, `styles/main.css`, `components/`, example block.
|
|
39
|
-
|
|
40
|
-
### 1.2 `cmssy link` - connect the project to a workspace
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
cmssy link # interactive: pick workspace, paste token
|
|
44
|
-
cmssy link --token cs_xxx --workspace <id|slug> # non-interactive, for CI or scripted setup
|
|
45
|
-
cmssy link --api-url https://api.cmssy.io/graphql
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Writes credentials and `CMSSY_WORKSPACE_ID` to `.env`. Tokens come from https://cmssy.io/settings/tokens. List existing workspaces with `cmssy workspaces`.
|
|
49
|
-
|
|
50
|
-
### 1.3 `cmssy workspaces` - show what this machine can publish to
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
cmssy workspaces
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Returns name, slug, ID, and role for every accessible workspace. Use this when the user is ambiguous about the publish target.
|
|
57
|
-
|
|
58
|
-
### 1.4 `cmssy create` - scaffold blocks or templates
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
cmssy create block <kebab-name> [-d "desc"] [-c <category>] [-t "tag1,tag2"] [-y]
|
|
62
|
-
cmssy create template <kebab-name> [-d "desc"] [-y]
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Categories: `marketing`, `typography`, `media`, `layout`, `forms`, `navigation`, `other`.
|
|
66
|
-
|
|
67
|
-
Always use `-y` in automated flows unless the user wants to be prompted.
|
|
68
|
-
|
|
69
|
-
### 1.5 `cmssy dev` - local preview with HMR
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
cmssy dev # port 3000
|
|
73
|
-
cmssy dev -p 8080 # custom port
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
Watches `config.ts` and auto-regenerates `block.d.ts`. Use for visual verification after edits.
|
|
77
|
-
|
|
78
|
-
### 1.6 `cmssy test` - vitest runner
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
cmssy test # all blocks/templates
|
|
82
|
-
cmssy test --block hero faq # subset
|
|
83
|
-
cmssy test --watch
|
|
84
|
-
cmssy test --coverage
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Test files live at `blocks/*/src/**/*.{test,spec}.{ts,tsx}` (same for templates). If no tests exist yet, this is a no-op.
|
|
88
|
-
|
|
89
|
-
### 1.7 `cmssy build` - production bundle
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
cmssy build # everything
|
|
93
|
-
cmssy build --block hero pricing # subset
|
|
94
|
-
cmssy build --framework react # override
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Outputs `public/@<vendor>/blocks.<name>/<version>/{index.js,index.css,package.json}`. Run before `cmssy publish-block` if you want to preview the bundle locally - publishing itself bundles again in the sandbox.
|
|
98
|
-
|
|
99
|
-
### 1.8 `cmssy publish-block` - upload to workspace via the sandbox build pipeline
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
cmssy publish-block <name> # publish to workspace from .env
|
|
103
|
-
cmssy publish-block <name> -w <workspaceId> # explicit workspace
|
|
104
|
-
cmssy publish-block <name> --dry-run # collect files + print plan, no upload
|
|
105
|
-
cmssy publish-block <name> --entry src/main.tsx # override default src/index.tsx entry
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**Flow:** CLI collects the source files and POSTs them base64-encoded via the `publishBlock` GraphQL mutation. The backend packs the bundle into `tar.gz`, uploads it to Vercel Blob, and enqueues an Inngest job that builds in a Vercel Sandbox and writes artifacts back to Blob. CLI polls `publishJobStatus` until status is `completed` or `failed`.
|
|
109
|
-
|
|
110
|
-
**Limits:** 200 files / 10 MB total per block. Polling: 1.5 s interval, 10 min cap, gives up after 5 consecutive errors.
|
|
111
|
-
|
|
112
|
-
**SSR smoke test (can fail an otherwise-valid publish):** after bundling, the sandbox renders your block with `renderToString` using `defaultContent` derived from `config.ts`. When `defaultContent` is non-empty the test **demands non-empty HTML** - a block that renders nothing on its default content fails with `SSR_FAILURE: renderToString returned empty output despite realistic content`, and no artifacts are stored. The two recurring causes are an early `return null` on an empty repeater (see §4) and a repeater with no top-level `defaultValue` (see §3). Fix the block, bump the version, republish.
|
|
113
|
-
|
|
114
|
-
**Content preservation:** republishing preserves the workspace's `defaultContent` overrides by default. There is no `--overwrite-content` flag - that knob lives on `cmssy publish-template` only.
|
|
115
|
-
|
|
116
|
-
The legacy `cmssy publish` command + its flags (`--patch`, `--minor`, `--major`, `--zip`, `--with-source`, `--force`, `--all`) were removed in CMS-606. Versioning is handled by the build pipeline; there is no per-publish bump prompt anymore.
|
|
117
|
-
|
|
118
|
-
### 1.9 `cmssy sync` - pull blocks down from a workspace
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
cmssy sync @cmssy/blocks.hero # one package from the design library
|
|
122
|
-
cmssy sync --workspace <id> # everything from a workspace
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
Use when cloning a project's block set to a new repo, or when a block has drifted from its published version.
|
|
126
|
-
|
|
127
|
-
### 1.10 `cmssy lib` - manage the workspace npm dependency manifest
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
cmssy lib install <pkg...> # install locally + push manifest to workspace
|
|
131
|
-
cmssy lib install lodash zod@^4
|
|
132
|
-
cmssy lib install <pkg> --skip-install # only sync from package.json, no local install
|
|
133
|
-
cmssy lib install <pkg> --dry-run # print manifest, don't push
|
|
134
|
-
cmssy lib sync # push current package.json deps to the manifest
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
When a block imports an npm package, the sandbox build needs it in the workspace's dependency manifest. `lib install` adds the dep locally and registers it so the build pipeline can resolve it. `--package-manager <npm|pnpm|yarn|bun>` forces the PM; both subcommands take `-w` and `--dry-run`.
|
|
138
|
-
|
|
139
|
-
### 1.11 `cmssy skills` - install AI-assistant skills
|
|
140
|
-
|
|
141
|
-
```bash
|
|
142
|
-
cmssy skills list # show available skills
|
|
143
|
-
cmssy skills install block # this skill (CLI + block dev workflow)
|
|
144
|
-
cmssy skills install mcp-content # content editing via @cmssy/mcp-server
|
|
145
|
-
cmssy skills install --all # all skills
|
|
146
|
-
cmssy skills install block --local # into ./.claude/skills (default: ~/.claude/skills)
|
|
147
|
-
cmssy skills install block --force # overwrite existing
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
Positional arg is the skill name (`block`, `mcp-content`); use `--target` for the editor (default `claude`).
|
|
151
|
-
|
|
152
|
-
## 2. Block anatomy (non-negotiable)
|
|
153
|
-
|
|
154
|
-
Every block lives in `blocks/<kebab-name>/`:
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
blocks/<name>/
|
|
158
|
-
├── config.ts # defineBlock({ name, description, category, tags, schema })
|
|
159
|
-
├── package.json # { "name": "@<project>/blocks.<name>", "version": "x.y.z" }
|
|
160
|
-
├── preview.json # sample content for cmssy dev
|
|
161
|
-
└── src/
|
|
162
|
-
├── index.tsx # export { default } from "./<Pascal>"; import "./index.css";
|
|
163
|
-
├── <Pascal>.tsx # the React component
|
|
164
|
-
├── index.css # @import "../../../styles/main.css";
|
|
165
|
-
└── block.d.ts # AUTO-GENERATED from config.ts - never hand-edit
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
**Naming (hard rules):**
|
|
169
|
-
|
|
170
|
-
- Directory + package `name` suffix: `kebab-case` (`blog-post-hero`).
|
|
171
|
-
- Component file and default export: `PascalCase` matching the directory (`BlogPostHero.tsx`).
|
|
172
|
-
- Package name: `@<projectName>/blocks.<kebab-name>` where `projectName` comes from `cmssy.config.js`.
|
|
173
|
-
- New blocks start at `"version": "1.0.0"`.
|
|
174
|
-
|
|
175
|
-
## 3. Writing `config.ts`
|
|
176
|
-
|
|
177
|
-
Import from `@cmssy/cli/config`. Available field types:
|
|
178
|
-
|
|
179
|
-
| type | Use for |
|
|
180
|
-
| -------------- | ---------------------------------------------------- |
|
|
181
|
-
| `singleLine` | Short text (heading, label, badge). |
|
|
182
|
-
| `multiLine` | Paragraph without formatting. |
|
|
183
|
-
| `richText` | HTML body copy. |
|
|
184
|
-
| `link` | URL. |
|
|
185
|
-
| `media` | Image or video (returns URL string). |
|
|
186
|
-
| `boolean` | Toggle. |
|
|
187
|
-
| `numeric` | Number. |
|
|
188
|
-
| `date` | ISO date string. |
|
|
189
|
-
| `color` | Hex color picker. |
|
|
190
|
-
| `select` | Enum with `options: [{ label, value }]`. |
|
|
191
|
-
| `multiselect` | Multiple values from `options` (returns `string[]`). |
|
|
192
|
-
| `repeater` | Array of sub-objects with nested `schema`. |
|
|
193
|
-
| `form` | Reference to a form-builder form. |
|
|
194
|
-
| `pageSelector` | Pick a page (`multiple: true` for several). |
|
|
195
|
-
|
|
196
|
-
**Common field options** (all forwarded to the backend on publish): `label` (required), `required`, `placeholder`, `defaultValue`, `helperText` (helper text under the field; `helpText` is a deprecated alias), `group` (groups fields in the editor UI), `showWhen` (conditional visibility - `{ field, equals | notEquals | notEmpty | isEmpty }`), `validation` (`{ minLength, maxLength, min, max, pattern, message }`).
|
|
197
|
-
|
|
198
|
-
**Per-type options:** `options: [{ label, value }]` (select/multiselect), `minItems` / `maxItems` / `schema` (repeater), `multiple` (pageSelector). Media's `acceptedTypes` / `accept` / `maxSize` are **not** forwarded by the publish pipeline - don't rely on them.
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
import { defineBlock, field } from "@cmssy/cli/config";
|
|
202
|
-
|
|
203
|
-
export default defineBlock({
|
|
204
|
-
name: "Feature Grid",
|
|
205
|
-
description: "Grid of feature cards with icon, title, description",
|
|
206
|
-
category: "marketing",
|
|
207
|
-
tags: ["marketing", "features", "grid"],
|
|
208
|
-
|
|
209
|
-
schema: {
|
|
210
|
-
heading: field({ type: "singleLine", label: "Heading", required: true }),
|
|
211
|
-
headingHighlight: field({ type: "singleLine", label: "Highlight" }),
|
|
212
|
-
description: field({ type: "multiLine", label: "Description" }),
|
|
213
|
-
features: field({
|
|
214
|
-
type: "repeater",
|
|
215
|
-
label: "Features",
|
|
216
|
-
schema: {
|
|
217
|
-
title: field({ type: "singleLine", label: "Title", required: true }),
|
|
218
|
-
description: field({ type: "multiLine", label: "Description" }),
|
|
219
|
-
icon: field({
|
|
220
|
-
type: "select",
|
|
221
|
-
label: "Icon",
|
|
222
|
-
options: [
|
|
223
|
-
{ label: "Zap", value: "ZapIcon" },
|
|
224
|
-
{ label: "Sparkles", value: "SparklesIcon" },
|
|
225
|
-
],
|
|
226
|
-
}),
|
|
227
|
-
color: field({ type: "color", label: "Accent color" }),
|
|
228
|
-
},
|
|
229
|
-
}),
|
|
230
|
-
},
|
|
231
|
-
});
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
**Repeaters and the SSR smoke test (critical):** `defaultContent` is built by `extractDefaultContent`, which only reads a field's **top-level** `defaultValue`. For a `repeater` it does **not** synthesize a sample row from the nested fields' `defaultValue`s - a repeater without its own top-level `defaultValue` becomes an empty array `[]`. If your component renders nothing when that array is empty (e.g. `if (items.length === 0) return null`), publish fails the SSR smoke test. Seed the repeater with a top-level `defaultValue` holding at least one realistic row:
|
|
235
|
-
|
|
236
|
-
```ts
|
|
237
|
-
items: field({
|
|
238
|
-
type: "repeater",
|
|
239
|
-
label: "Items",
|
|
240
|
-
defaultValue: [{ title: "Example", description: "Sample row" }],
|
|
241
|
-
schema: {
|
|
242
|
-
title: field({ type: "singleLine", label: "Title", required: true }),
|
|
243
|
-
description: field({ type: "multiLine", label: "Description" }),
|
|
244
|
-
},
|
|
245
|
-
}),
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
After editing `config.ts`, `block.d.ts` regenerates on the next `cmssy dev` or `cmssy build`. Never hand-edit it.
|
|
249
|
-
|
|
250
|
-
### Block-level config keys (`defineBlock`)
|
|
251
|
-
|
|
252
|
-
Beyond `name`, `description`, `category`, `tags`, `schema`, `defineBlock` accepts:
|
|
253
|
-
|
|
254
|
-
| key | Type | Purpose |
|
|
255
|
-
| ----------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
256
|
-
| `longDescription` | `string` | Long copy for the design-library listing. |
|
|
257
|
-
| `layoutPosition` | `"header" \| "footer" \| "sidebar_left" \| "sidebar_right" \| "top" \| "bottom"` | Makes this a **layout block** rendered into that slot instead of the content area. |
|
|
258
|
-
| `useClient` | `boolean` (default `false`) | `false` = server-rendered HTML only (no JS shipped). `true` = hydrate on the client (hooks, browser APIs, **and client animations like framer-motion `whileInView` - which render `opacity:0` invisible without it**). |
|
|
259
|
-
| `requires` | `BlockRequires` | Capability metadata: `{ auth?, language?, workspace?, modules?, permissions?, features? }`. |
|
|
260
|
-
|
|
261
|
-
`requires` modules: `pim`, `crm`, `forms`, `analytics`, `newsletter`, `ecommerce`. `requires` features: `ai-generation`, `ai-translation`, `a-b-testing`. **`requires` is metadata** (editor/library filtering + auth-aware rendering hints) - it does **not** gate the runtime `context`, which is always populated. Always guard for missing data in the component regardless of what you declare.
|
|
262
|
-
|
|
263
|
-
(`groups` appears in some legacy block configs but is **not** forwarded by the publish pipeline - use the per-field `group` string for editor grouping instead.)
|
|
264
|
-
|
|
265
|
-
### Layout blocks (header / footer / sidebar)
|
|
266
|
-
|
|
267
|
-
A layout block is an ordinary block with a `layoutPosition`. It renders into a template slot via `LayoutPositionRenderer` and is excluded from the content-block drawer. Header/footer/nav blocks are almost always interactive, so set `useClient: true` and declare what they read:
|
|
268
|
-
|
|
269
|
-
```ts
|
|
270
|
-
export default defineBlock({
|
|
271
|
-
name: "Header Navigation",
|
|
272
|
-
description: "Responsive header with logo, nav, and CTA",
|
|
273
|
-
category: "layout",
|
|
274
|
-
layoutPosition: "header",
|
|
275
|
-
useClient: true,
|
|
276
|
-
requires: { auth: true, language: true },
|
|
277
|
-
schema: {
|
|
278
|
-
logo: field({ type: "media", label: "Logo" }),
|
|
279
|
-
links: field({
|
|
280
|
-
type: "repeater",
|
|
281
|
-
label: "Nav links",
|
|
282
|
-
defaultValue: [{ label: "Home", href: "/" }],
|
|
283
|
-
schema: {
|
|
284
|
-
label: field({ type: "singleLine", label: "Label", required: true }),
|
|
285
|
-
href: field({ type: "link", label: "URL", required: true }),
|
|
286
|
-
},
|
|
287
|
-
}),
|
|
288
|
-
},
|
|
289
|
-
});
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
The same SSR-smoke and repeater-seeding rules apply (§3 above, §1.8). Publish with `cmssy publish-block <name>` like any other block.
|
|
293
|
-
|
|
294
|
-
## 4. Writing the component
|
|
295
|
-
|
|
296
|
-
Follow the design system in the repo's `DESIGN.md`:
|
|
297
|
-
|
|
298
|
-
- Import `Container` from `../../../components/container`.
|
|
299
|
-
- Section wrapper: `<section className="py-24">` or `"py-24 bg-slate-50/50 dark:bg-slate-900/50"` for alternating tint.
|
|
300
|
-
- Destructure `content` with defaults: `const { heading = "Default" } = content;`.
|
|
301
|
-
- Gradient-clip highlight phrases: `bg-linear-to-r from-violet-600 to-purple-600 bg-clip-text text-transparent`.
|
|
302
|
-
- Primary CTAs: gradient pill button with `shadow-lg shadow-violet-500/25`. Secondary: outline with `border-input`.
|
|
303
|
-
- Icons: `lucide-react`, default stroke width.
|
|
304
|
-
- Rich text: `dangerouslySetInnerHTML` inside a `prose` wrapper.
|
|
305
|
-
|
|
306
|
-
```tsx
|
|
307
|
-
import { Container } from "../../../components/container";
|
|
308
|
-
import { BlockContent } from "./block";
|
|
309
|
-
|
|
310
|
-
export default function FeatureGrid({ content }: { content: BlockContent }) {
|
|
311
|
-
const { heading = "", features = [] } = content;
|
|
312
|
-
return (
|
|
313
|
-
<section className="py-24">
|
|
314
|
-
<Container>{/* ... */}</Container>
|
|
315
|
-
</section>
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
Blocks that read platform data accept a second `context` prop. The SSR render path populates it in full (and `requires` does **not** gate which fields you get), but type it **optional** (`context?: PlatformContext`) - that is the convention across all blocks, since non-SSR paths (editor CSR preview, fallbacks) may not pass it. Always guard every field defensively:
|
|
321
|
-
|
|
322
|
-
```tsx
|
|
323
|
-
export default function Header({ content, context }: {
|
|
324
|
-
content: BlockContent;
|
|
325
|
-
context?: PlatformContext;
|
|
326
|
-
}) { ... }
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
`PlatformContext` top-level keys: `locale` (always present - `{ current, default, enabled }`), `auth`, `workspace`, `site`, `branding`, `primaryDomain`, `pages`, `media`, `members`, `forms`, `formDefinitions`, `models`, `cart` / `canUseCart`, `isPreview`, `isDraftMode`, and `graphql(query, variables?)` for public queries. Collection sources (`pages`, `media`, `forms`, ...) are injected from the block's `dataDeclarations` and auto-detected references (e.g. a `formId` in content), so they may be empty if not declared.
|
|
330
|
-
|
|
331
|
-
**Two different "client" knobs - don't confuse them:**
|
|
332
|
-
|
|
333
|
-
- `useClient: true` in **`config.ts`** opts the block into client hydration. Without it the block is server-rendered only and ships no JS, so `useState`/`onClick`/effects won't run in production no matter what the component file says.
|
|
334
|
-
- `"use client";` at the top of the **component file** is the Next-style compile pragma for files that use hooks or browser APIs.
|
|
335
|
-
|
|
336
|
-
An interactive block needs **both**: `useClient: true` in config and `"use client";` in the component.
|
|
337
|
-
|
|
338
|
-
**Entrance/scroll animations gate VISIBILITY, not just interactivity - they REQUIRE `useClient: true`.** If your block (or any component it imports, e.g. a shared `Reveal`) uses framer-motion `whileInView` / `initial={{ opacity: 0 }}` / IntersectionObserver to fade content in, the server-rendered HTML carries `opacity:0`. With `useClient: false` there is no hydration, so the JS that animates opacity to 1 never runs and **the content is permanently invisible in production** - not merely non-interactive. This is a trap because the **editor canvas renders blocks pure-CSR**, so the animation always plays there and the block looks fine while editing; the bug only appears on the public/preview SSR path (symptom: "structure and background show but the text is missing"). Any block with reveal-on-scroll/entrance animation must set `useClient: true` and be republished. Don't "fix" it by rewriting the animation to CSS - the platform hydrates client blocks on purpose; `useClient: true` is the intended path.
|
|
339
|
-
|
|
340
|
-
**Empty-state guards vs the SSR smoke test:** an early `return null` (or rendering nothing) when a repeater is empty is a legitimate pattern, but it makes the block fail the publish-time SSR smoke test unless that repeater is seeded with a top-level `defaultValue` (see §3). Either seed the repeater, or render a non-empty wrapper (heading/section) even with zero rows.
|
|
341
|
-
|
|
342
|
-
## 5. `preview.json`
|
|
343
|
-
|
|
344
|
-
Sample content for `cmssy dev`. Keys must match `config.ts` field names. Repeater fields take arrays of objects. Make it realistic - preview is what reviewers see.
|
|
345
|
-
|
|
346
|
-
## 6. Templates
|
|
347
|
-
|
|
348
|
-
```
|
|
349
|
-
templates/<name>/
|
|
350
|
-
├── config.ts # defineTemplate({ name, description, category, tags, pages })
|
|
351
|
-
├── package.json
|
|
352
|
-
├── pages.json # layoutPositions (header/footer) + block references
|
|
353
|
-
├── preview.json
|
|
354
|
-
└── src/ # optional root component
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
```ts
|
|
358
|
-
import { defineTemplate, field } from "@cmssy/cli/config";
|
|
359
|
-
|
|
360
|
-
export default defineTemplate({
|
|
361
|
-
name: "Marketing Site",
|
|
362
|
-
description: "...",
|
|
363
|
-
category: "website",
|
|
364
|
-
tags: [...],
|
|
365
|
-
pages: [
|
|
366
|
-
{
|
|
367
|
-
name: "Home",
|
|
368
|
-
slug: "/",
|
|
369
|
-
blocks: [
|
|
370
|
-
{ type: "hero", content: { heading: "...", ... } },
|
|
371
|
-
{ type: "features", content: { ... } },
|
|
372
|
-
],
|
|
373
|
-
},
|
|
374
|
-
],
|
|
375
|
-
});
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
Publish templates via the dedicated command: `cmssy publish-template <template-name>`. Templates are declarative (no sandbox build) - the CLI accepts `--patch` / `--minor` / `--major` / `--no-bump` and `--overwrite-content` here.
|
|
379
|
-
|
|
380
|
-
## 7. Standard workflows
|
|
381
|
-
|
|
382
|
-
### Brand-new project
|
|
383
|
-
|
|
384
|
-
```bash
|
|
385
|
-
cmssy init my-blocks -y
|
|
386
|
-
cd my-blocks
|
|
387
|
-
cmssy link # interactive - pick workspace
|
|
388
|
-
cmssy doctor # confirm green
|
|
389
|
-
cmssy dev
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### Add a new block end-to-end
|
|
393
|
-
|
|
394
|
-
```bash
|
|
395
|
-
cmssy create block testimonials -c marketing -t "marketing,social-proof" -y
|
|
396
|
-
# edit blocks/testimonials/config.ts, Testimonials.tsx, preview.json
|
|
397
|
-
cmssy dev # visual check
|
|
398
|
-
pnpm typecheck && pnpm lint
|
|
399
|
-
cmssy test --block testimonials # if tests exist
|
|
400
|
-
cmssy publish-block testimonials --dry-run
|
|
401
|
-
cmssy publish-block testimonials
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
### Add a layout block (header / footer / sidebar)
|
|
405
|
-
|
|
406
|
-
```bash
|
|
407
|
-
cmssy create block site-header -c layout -y
|
|
408
|
-
# in config.ts add: layoutPosition: "header", useClient: true, requires: { auth, language }
|
|
409
|
-
# seed any repeater (nav links) with a top-level defaultValue
|
|
410
|
-
cmssy dev
|
|
411
|
-
pnpm typecheck && pnpm lint
|
|
412
|
-
cmssy publish-block site-header
|
|
413
|
-
```
|
|
414
|
-
|
|
415
|
-
`cmssy create` does not set `layoutPosition` - add it (and `useClient`) to `config.ts` by hand. See §3 "Layout blocks".
|
|
416
|
-
|
|
417
|
-
### Extend an existing block with an optional field
|
|
418
|
-
|
|
419
|
-
1. Add `field({...})` to `config.ts`. Don't set `required: true` - that's breaking.
|
|
420
|
-
2. Update the component to read and render it with a default.
|
|
421
|
-
3. Update `preview.json`.
|
|
422
|
-
4. `cmssy publish-block <block>`.
|
|
423
|
-
|
|
424
|
-
### Copy-only fix
|
|
425
|
-
|
|
426
|
-
1. Edit default strings or JSX in the component.
|
|
427
|
-
2. `cmssy publish-block <block>`.
|
|
428
|
-
|
|
429
|
-
### Pull a block from the design library
|
|
430
|
-
|
|
431
|
-
```bash
|
|
432
|
-
cmssy sync @cmssy/blocks.hero
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
## 8. Safety rules (always)
|
|
436
|
-
|
|
437
|
-
- Run `cmssy doctor` before publishing.
|
|
438
|
-
- Never fabricate a workspace ID. Use `cmssy workspaces` and confirm with the user.
|
|
439
|
-
- Never pass `cmssy publish-template --overwrite-content` without explicit user approval (wipes editor content on the affected pages).
|
|
440
|
-
- Use `--dry-run` first when unsure what will change.
|
|
441
|
-
- Confirm with the user before publishing from a branch other than `develop` or `main`.
|
|
442
|
-
- Commit local changes before publishing - publish uploads the working tree.
|
|
443
|
-
- For init and link, never write a token or workspace ID into `.env` without user confirmation.
|
|
444
|
-
|
|
445
|
-
## 9. Troubleshooting
|
|
446
|
-
|
|
447
|
-
| Symptom | Likely cause | Fix |
|
|
448
|
-
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
449
|
-
| `doctor` says token invalid | Revoked/expired token | `cmssy link` again with a fresh token from https://cmssy.io/settings/tokens |
|
|
450
|
-
| Publish says "workspace not accessible" | Wrong `CMSSY_WORKSPACE_ID` or no role | `cmssy workspaces`, update `.env` |
|
|
451
|
-
| Sandbox build fails with bundling error | Missing import or > 10 MB source tree | Inspect the polled `publishJobStatus` log; trim deps or fix the import path |
|
|
452
|
-
| `block.d.ts` shows wrong fields | Stale generation | Restart `cmssy dev`, or run `cmssy build` to regenerate |
|
|
453
|
-
| Dev server shows a blank block | `preview.json` missing keys | Match keys to `config.ts` field names |
|
|
454
|
-
| Publish fails `SSR_FAILURE` / "empty output despite realistic content" | Block renders nothing on its `defaultContent` - usually `return null` on an empty repeater that has no top-level `defaultValue` | Seed the repeater's top-level `defaultValue` with ≥1 row (§3), or render a non-empty wrapper (§4); bump version; republish |
|