@comet/agent-features 9.0.0-beta.5

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 (35) hide show
  1. package/LICENSE +24 -0
  2. package/package.json +19 -0
  3. package/rules/coding-guidelines/api-nestjs.instructions.md +107 -0
  4. package/rules/coding-guidelines/cdn.instructions.md +24 -0
  5. package/rules/coding-guidelines/general.instructions.md +30 -0
  6. package/rules/coding-guidelines/git.instructions.md +37 -0
  7. package/rules/coding-guidelines/kubernetes.instructions.md +59 -0
  8. package/rules/coding-guidelines/libraries.instructions.md +34 -0
  9. package/rules/coding-guidelines/naming.instructions.md +39 -0
  10. package/rules/coding-guidelines/postgresql.instructions.md +40 -0
  11. package/rules/coding-guidelines/react.instructions.md +102 -0
  12. package/rules/coding-guidelines/security.instructions.md +44 -0
  13. package/rules/coding-guidelines/styling.instructions.md +50 -0
  14. package/rules/coding-guidelines/typescript.instructions.md +50 -0
  15. package/skills/.gitkeep +0 -0
  16. package/skills/comet-block/SKILL.md +246 -0
  17. package/skills/comet-block/references/admin-patterns.md +192 -0
  18. package/skills/comet-block/references/api-patterns.md +183 -0
  19. package/skills/comet-block/references/block-loader.md +368 -0
  20. package/skills/comet-block/references/block-types.md +210 -0
  21. package/skills/comet-block/references/custom-block-field.md +266 -0
  22. package/skills/comet-block/references/fixtures.md +436 -0
  23. package/skills/comet-block/references/image.md +341 -0
  24. package/skills/comet-block/references/migration.md +597 -0
  25. package/skills/comet-block/references/registration.md +167 -0
  26. package/skills/comet-block/references/response-summary.md +102 -0
  27. package/skills/comet-block/references/rich-text.md +309 -0
  28. package/skills/comet-block/references/select.md +176 -0
  29. package/skills/comet-block/references/site-patterns.md +202 -0
  30. package/skills/comet-mail-react/SKILL.md +541 -0
  31. package/skills/comet-mail-react/references/components-and-theme.md +441 -0
  32. package/skills/comet-mail-react/references/layout-patterns.md +315 -0
  33. package/skills/comet-mail-react/references/styling-and-customization.md +306 -0
  34. package/skills/comet-minor-update/SKILL.md +191 -0
  35. package/skills/dev-pm/SKILL.md +100 -0
@@ -0,0 +1,167 @@
1
+ # Block Registration Patterns
2
+
3
+ "Registering a block" means adding it to a **blocks block** (`createBlocksBlock`) so editors can select and use it in a content area. A newly created block is not available in the admin UI until it is registered in at least one blocks block. Registration must happen consistently across all three layers (API, Admin, Site).
4
+
5
+ ---
6
+
7
+ ## Registration Targets
8
+
9
+ Most Comet projects have a hierarchy of blocks blocks:
10
+
11
+ | Target | Purpose | Typical Includes |
12
+ | ------------------------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
13
+ | **PageContentBlock** | Top-level page content area. Editors build page content by adding blocks here. | All general-purpose blocks, including `columns`, `contentGroup`, `pageTreeIndex`, and any full-width page-level blocks. |
14
+ | **ContentGroupBlock** | A grouped content section with a shared background color or visual wrapper. Used inside `PageContentBlock`. | Same as `PageContentBlock`, minus `contentGroup` (no self-nesting), `pageTreeIndex`, and page-level-only blocks. |
15
+ | **ColumnsContentBlock** | Content inside a single column of a `ColumnsBlock`. More restricted due to limited width. | Reduced set: `accordion`, `anchor`, `richText`, `space`, `heading`, `callToActionList`, `media`. |
16
+ | **AccordionContentBlock** | Content inside an accordion item. Very restricted. | Minimal set: `richText`, `heading`, `space`, `callToActionList`. |
17
+
18
+ Not all projects have all of these. Discover the project's blocks blocks by searching for `createBlocksBlock` usages.
19
+
20
+ ---
21
+
22
+ ## Default Registration Target
23
+
24
+ When the user does not specify where to register a block:
25
+
26
+ 1. **Always add to `PageContentBlock`** -- the main content area.
27
+ 2. **Also add to `ContentGroupBlock`** if it exists and the block is a general-purpose content block.
28
+ 3. **Do not add to `ColumnsContentBlock`** or `AccordionContentBlock` unless explicitly requested.
29
+
30
+ Blocks that should typically **not** be added to `ContentGroupBlock`:
31
+
32
+ - `contentGroup` -- would cause self-nesting
33
+ - `pageTreeIndex` -- page-level navigation block
34
+ - Any full-width or page-level-only block (e.g., a hero block)
35
+ - Any block that is a layout container wrapping other blocks blocks
36
+
37
+ Blocks that should typically **not** be added to `ColumnsContentBlock`:
38
+
39
+ - `teaser`, `featureList`, `columns` -- too wide or complex for column layouts
40
+ - `contentGroup`, `pageTreeIndex` -- page-level blocks
41
+
42
+ ---
43
+
44
+ ## Consistent Keys Across Layers
45
+
46
+ The key used to register a block in `supportedBlocks` must be **identical across API, Admin, and Site**. The key is **camelCase** and typically matches the block's purpose or name (without "Block").
47
+
48
+ ```ts
49
+ // API
50
+ supportedBlocks: { featureList: FeatureListBlock }
51
+
52
+ // Admin
53
+ supportedBlocks: { featureList: FeatureListBlock }
54
+
55
+ // Site
56
+ const supportedBlocks: SupportedBlocks = {
57
+ featureList: (props) => <FeatureListBlock data={props} />,
58
+ };
59
+ ```
60
+
61
+ A mismatch in keys causes data loss or runtime errors.
62
+
63
+ **Common key conventions:**
64
+
65
+ | Block | Typical Key |
66
+ | --------------------------------- | ------------------ |
67
+ | `StandaloneRichTextBlock` | `richText` |
68
+ | `StandaloneHeadingBlock` | `heading` |
69
+ | `StandaloneMediaBlock` | `media` |
70
+ | `StandaloneCallToActionListBlock` | `callToActionList` |
71
+ | `ColumnsBlock` | `columns` |
72
+ | `ContentGroupBlock` | `contentGroup` |
73
+ | `FeatureListBlock` | `featureList` |
74
+ | `TeaserBlock` | `teaser` |
75
+ | `SpaceBlock` | `space` |
76
+ | `AccordionBlock` | `accordion` |
77
+ | `AnchorBlock` | `anchor` |
78
+ | `ProductListBlock` | `productList` |
79
+
80
+ For new blocks, derive the key from the block name in camelCase without "Block" (e.g., `ProductListBlock` -> `productList`).
81
+
82
+ ---
83
+
84
+ ## Step-by-Step: Registering a Block
85
+
86
+ To register `FeatureListBlock` in `PageContentBlock`:
87
+
88
+ **1. API layer** -- add import and key to `supportedBlocks`:
89
+
90
+ ```ts
91
+ import { FeatureListBlock } from "@src/documents/pages/blocks/feature-list.block";
92
+
93
+ export const PageContentBlock = createBlocksBlock(
94
+ {
95
+ supportedBlocks: {
96
+ // ... existing blocks ...
97
+ featureList: FeatureListBlock,
98
+ },
99
+ },
100
+ "PageContent",
101
+ );
102
+ ```
103
+
104
+ **2. Admin layer** -- same key:
105
+
106
+ ```tsx
107
+ import { FeatureListBlock } from "@src/documents/pages/blocks/FeatureListBlock";
108
+
109
+ export const PageContentBlock = createBlocksBlock({
110
+ name: "PageContent",
111
+ supportedBlocks: {
112
+ // ... existing blocks ...
113
+ featureList: FeatureListBlock,
114
+ },
115
+ });
116
+ ```
117
+
118
+ **3. Site layer** -- same key, **render function** syntax:
119
+
120
+ ```tsx
121
+ import { FeatureListBlock } from "@src/documents/pages/blocks/FeatureListBlock";
122
+
123
+ const supportedBlocks: SupportedBlocks = {
124
+ // ... existing blocks ...
125
+ featureList: (props) => <FeatureListBlock data={props} />,
126
+ };
127
+ ```
128
+
129
+ In the Site layer, the value is a **render function** `(props) => <Component data={props} />`. Some blocks use a `PageContent`-prefixed wrapper for layout purposes (e.g., `heading: (props) => <PageContentStandaloneHeadingBlock data={props} />`). Use the wrapper variant if it exists.
130
+
131
+ ---
132
+
133
+ ## Registering in Multiple Targets
134
+
135
+ When a block should be in both `PageContentBlock` and `ContentGroupBlock`, add it to both. The `ContentGroupBlock` has its own inner blocks block (typically `ContentGroupContentBlock` or `ContentBlock`) defined locally inside the `ContentGroupBlock` file.
136
+
137
+ Repeat the same key in all targets. Repeat the pattern in Admin and Site layers for each target.
138
+
139
+ ---
140
+
141
+ ## ContentGroupBlock Structure
142
+
143
+ `ContentGroupBlock` is a **composite block** wrapping a blocks block as a child. It is itself registered inside `PageContentBlock`:
144
+
145
+ ```
146
+ PageContentBlock (blocks block)
147
+ ├── contentGroup: ContentGroupBlock (composite block)
148
+ │ ├── backgroundColor (select field)
149
+ │ └── content: ContentGroupContentBlock (blocks block)
150
+ │ ├── accordion, anchor, space, teaser, richText, heading, ...
151
+ │ └── (same as PageContentBlock minus contentGroup, pageTreeIndex, and page-level-only blocks)
152
+ ├── columns: ColumnsBlock (composite block)
153
+ │ └── columns[].content: ColumnsContentBlock (blocks block)
154
+ │ └── accordion, anchor, richText, space, heading, callToActionList, media
155
+ ├── accordion, richText, heading, media, featureList, teaser, ...
156
+ └── pageTreeIndex, and any other page-level-only blocks
157
+ ```
158
+
159
+ ---
160
+
161
+ ## Discovering Registration Targets
162
+
163
+ 1. **Search for `createBlocksBlock`** across the API directory to find all blocks blocks.
164
+ 2. **Check `PageContentBlock`** first -- it is the primary registration target.
165
+ 3. **Check `ContentGroupBlock`** -- if it exists and has similar blocks, also register there.
166
+ 4. **Inspect `ColumnsBlock`** and `AccordionBlock` -- only add blocks there if they suit constrained-width contexts.
167
+ 5. **Use the same key** in all targets.
@@ -0,0 +1,102 @@
1
+ # Response Template
2
+
3
+ Use this template for the summary section at the end of **every** block creation or editing response. Include only the sections that apply — omit sections marked "conditional" when they are not relevant.
4
+
5
+ ---
6
+
7
+ ## Template
8
+
9
+ ```
10
+ ## Summary
11
+
12
+ ### Blocks created ← omit entire section for pure edit tasks
13
+ - **`BlockName`** — `Layer/path/to/BlockName.block.ts` (API), `Layer/path/to/BlockNameBlock.tsx` (Admin/Site)
14
+ Registered in: `PageContentBlock`, `ContentGroupBlock`
15
+ - **`ListItemBlockName`** — registered in `ListBlockName`
16
+ - **`ListBlockName`** — registered in `PageContentBlock`
17
+
18
+ ### Blocks edited ← omit entire section for pure creation tasks
19
+ - **`BlockName`** — added `fieldName` (string), removed `oldField`, changed `image` from `PixelImageBlock` → `DamImageBlock`
20
+
21
+ ### Migrations created ← CONDITIONAL: only include if a migration was created
22
+ - **`BlockNameV2Migration`** — `api/src/documents/pages/blocks/migrations/block-name.migration.ts`
23
+ Reason: [brief reason, e.g., "renamed `headline` → `title`", "added required `variant` field"]
24
+
25
+ ### Fixtures
26
+ - **`BlockNameFixtureService`** — `api/src/db/fixtures/blocks/block-name-fixture.service.ts`
27
+ - **`ListItemBlockNameFixtureService`** — `api/src/db/fixtures/blocks/list-item-block-name-fixture.service.ts`
28
+ ← omit this section if no fixture services were created or modified
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Rules
34
+
35
+ **Created blocks section:**
36
+
37
+ - List every new block file created. Include both the list block and its item block as separate entries.
38
+ - For each block, show its registration target(s). If registered in multiple parents, list all: `registered in \`PageContentBlock\` and \`ContentGroupBlock\``.
39
+ - If the item block is "registered in" a list block, make that explicit: `registered in \`TeaserListBlock\``.
40
+
41
+ **Edited blocks section:**
42
+
43
+ - Describe each change concisely: `added \`fieldName\` (type)`, `removed \`fieldName\``, `changed \`fieldName\` from X → Y`.
44
+ - List each changed block as a separate bullet.
45
+
46
+ **Migrations section (conditional):**
47
+
48
+ - Include only when one or more migration classes were created.
49
+ - State the migration class name and file path.
50
+ - Give a one-line reason (what data shape changed and why).
51
+
52
+ **Fixtures section:**
53
+
54
+ - Include when fixture services were created or meaningfully updated.
55
+ - Omit entirely when no fixture work was done.
56
+ - List each fixture service separately with its file path.
57
+ - If an existing fixture was only wired (not created), note it as "updated `ParentFixtureService` to include `BlockNameFixtureService`".
58
+
59
+ ---
60
+
61
+ ## Example — Block creation with list block and migration
62
+
63
+ ```
64
+ ## Summary
65
+
66
+ ### Blocks created
67
+ - **`TeaserItemBlock`** — registered in `TeaserListBlock`
68
+ - **`TeaserListBlock`** — registered in `PageContentBlock`, `ContentGroupBlock`
69
+
70
+ ### Migrations created
71
+ - **`TeaserListBlockV2Migration`** — `api/src/documents/pages/blocks/migrations/teaser-list-block.migration.ts`
72
+ Reason: added required `variant` field (existing data defaults to `"primary"`)
73
+
74
+ ### Fixtures
75
+ - **`TeaserItemBlockFixtureService`** — `api/src/db/fixtures/blocks/teaser-item-block-fixture.service.ts`
76
+ - **`TeaserListBlockFixtureService`** — `api/src/db/fixtures/blocks/teaser-list-block-fixture.service.ts`
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Example — Editing an existing block, no migration
82
+
83
+ ```
84
+ ## Summary
85
+
86
+ ### Blocks edited
87
+ - **`HeroBlock`** — added `subtitle` (string, optional), changed `image` from `PixelImageBlock` → `DamImageBlock`
88
+
89
+ ### Fixtures
90
+ - Updated **`HeroBlockFixtureService`** — added `subtitle`, updated `image` field to `DamImageBlock` input format
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Example — Pure creation, no fixtures
96
+
97
+ ```
98
+ ## Summary
99
+
100
+ ### Blocks created
101
+ - **`QuoteBlock`** — registered in `PageContentBlock`
102
+ ```
@@ -0,0 +1,309 @@
1
+ # RichText Block Rules
2
+
3
+ Detailed rules for creating and configuring RichText blocks in Comet. Load this file when a block contains a rich text field.
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ Two usage modes:
10
+
11
+ 1. **Shared project-wide `RichTextBlock`** — defined once in `common/blocks/` and reused wherever full rich text is needed.
12
+ 2. **Scoped `RichTextBlock`** — a locally defined `createRichTextBlock(...)` call inside the composite block file, configured for a specific purpose (single-line eyebrow, heading-only field, etc.).
13
+
14
+ The API block is always the shared one. Per-field configuration is **Admin-only**.
15
+
16
+ ---
17
+
18
+ ## API Side
19
+
20
+ The API block is project-wide regardless of how many Admin-side scoped variants exist.
21
+
22
+ ```ts
23
+ // api/src/common/blocks/rich-text.block.ts
24
+ import { createRichTextBlock } from "@comet/cms-api";
25
+ import { LinkBlock } from "@src/common/blocks/link.block";
26
+
27
+ export const RichTextBlock = createRichTextBlock({ link: LinkBlock });
28
+ ```
29
+
30
+ Options: `link` (required — the inline link block) and `indexSearchText` (optional boolean, defaults to `true`).
31
+
32
+ Use as a child block in composite blocks:
33
+
34
+ ```ts
35
+ import { RichTextBlock } from "@src/common/blocks/rich-text.block";
36
+
37
+ class MyBlockData extends BlockData {
38
+ @ChildBlock(RichTextBlock)
39
+ description: BlockDataInterface;
40
+ }
41
+
42
+ class MyBlockInput extends BlockInput {
43
+ @ChildBlockInput(RichTextBlock)
44
+ description: ExtractBlockInput<typeof RichTextBlock>;
45
+
46
+ transformToBlockData(): MyBlockData {
47
+ return blockInputToData(MyBlockData, this);
48
+ }
49
+ }
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Admin Side
55
+
56
+ ```ts
57
+ import { createRichTextBlock } from "@comet/cms-admin";
58
+ ```
59
+
60
+ Admin options: `link` (required), `rte` (partial `IRteOptions` — spread over defaults), `minHeight` (pixels, default ~150px), `tags`.
61
+
62
+ ### Key `rte` options
63
+
64
+ | Option | Purpose |
65
+ | ------------------- | ----------------------------------------------------------------- |
66
+ | `supports` | Array of toolbar features to enable (see values below) |
67
+ | `maxBlocks` | Max paragraphs — set to `1` for single-line fields |
68
+ | `standardBlockType` | Default block type, e.g. `"header-one"` or `"paragraph-standard"` |
69
+ | `blocktypeMap` | Add custom block types or relabel existing ones |
70
+
71
+ **Available `supports` values:** `"bold"`, `"italic"`, `"underline"`, `"strikethrough"`, `"sub"`, `"sup"`, `"header-one"` through `"header-six"`, `"ordered-list"`, `"unordered-list"`, `"blockquote"`, `"history"`, `"link"`, `"links-remove"`, `"non-breaking-space"`, `"soft-hyphen"`.
72
+
73
+ **Default set** includes all of the above except `"underline"` and `"blockquote"`.
74
+
75
+ **Link rule:** when a `link` block is configured, always include `"link"` and `"links-remove"` in `supports` unless the field is intentionally link-free.
76
+
77
+ ---
78
+
79
+ ## Configuration Patterns
80
+
81
+ ### Pattern 1: Shared Full RichTextBlock
82
+
83
+ Defined once per project in `common/blocks/`. No `rte` overrides needed for a standard full-featured editor.
84
+
85
+ ```tsx
86
+ // admin/src/common/blocks/RichTextBlock.tsx
87
+ import { createRichTextBlock } from "@comet/cms-admin";
88
+ import { LinkBlock } from "./LinkBlock";
89
+
90
+ export const RichTextBlock = createRichTextBlock({ link: LinkBlock });
91
+ ```
92
+
93
+ When projects need custom paragraph variants, configure `standardBlockType` and `blocktypeMap`:
94
+
95
+ > **Note:** `"paragraph-standard"` and `"paragraph-small"` below are **examples only**. The actual block type keys are entirely project-defined. Always check the project's `RichTextBlock` definition to see which custom block types are in use — or whether custom block types are used at all.
96
+
97
+ ```tsx
98
+ export const RichTextBlock = createRichTextBlock({
99
+ link: LinkBlock,
100
+ rte: {
101
+ standardBlockType: "paragraph-standard",
102
+ blocktypeMap: {
103
+ "paragraph-standard": {
104
+ label: <FormattedMessage id="richTextBlock.paragraphStandard" defaultMessage="Paragraph Standard" />,
105
+ },
106
+ "paragraph-small": {
107
+ label: <FormattedMessage id="richTextBlock.paragraphSmall" defaultMessage="Paragraph Small" />,
108
+ renderConfig: {
109
+ element: (props) => <Typography paragraph variant="body2" {...props} />,
110
+ },
111
+ },
112
+ },
113
+ },
114
+ });
115
+ ```
116
+
117
+ ### Pattern 2: Single-Line Inline Field
118
+
119
+ For eyebrows, subtitles, captions — any field that should be a single line of formatted text.
120
+
121
+ Key settings:
122
+
123
+ - `maxBlocks: 1` — prevents Enter from creating new paragraphs
124
+ - `minHeight: 0` — compact editor height
125
+
126
+ ```tsx
127
+ const DescriptionRichTextBlock = createRichTextBlock({
128
+ link: LinkBlock,
129
+ rte: {
130
+ maxBlocks: 1,
131
+ supports: ["bold", "italic", "sub", "sup", "non-breaking-space", "soft-hyphen", "link", "links-remove"],
132
+ },
133
+ minHeight: 0,
134
+ });
135
+ ```
136
+
137
+ ### Pattern 3: Heading-Only Field
138
+
139
+ For single-line heading/title fields where the user selects a heading level from a dropdown.
140
+
141
+ Key settings:
142
+
143
+ - `maxBlocks: 1` + `minHeight: 0` — single compact line
144
+ - `standardBlockType: "header-one"` — new content defaults to heading 1
145
+ - `blocktypeMap` — only add if the project uses custom heading label names. **Check existing RTE blocks in the project** to see how heading levels are labelled and match that convention. If no custom labels are used, omit `blocktypeMap`.
146
+
147
+ ```tsx
148
+ const HeadlineRichTextBlock = createRichTextBlock({
149
+ link: LinkBlock,
150
+ rte: {
151
+ maxBlocks: 1,
152
+ supports: [
153
+ "header-one",
154
+ "header-two",
155
+ "header-three",
156
+ "header-four",
157
+ "header-five",
158
+ "header-six",
159
+ "bold",
160
+ "italic",
161
+ "sub",
162
+ "sup",
163
+ "non-breaking-space",
164
+ "soft-hyphen",
165
+ ],
166
+ standardBlockType: "header-one",
167
+ },
168
+ minHeight: 0,
169
+ });
170
+ ```
171
+
172
+ ### Pattern 4: Limited Feature Set
173
+
174
+ When the full RTE is too powerful for the context but multiline content is still needed.
175
+
176
+ ```tsx
177
+ const BaseRichTextBlock = createRichTextBlock({
178
+ link: ExternalLinkBlock,
179
+ rte: {
180
+ supports: ["bold", "italic", "header-one", "header-two", "header-three", "link", "links-remove"],
181
+ },
182
+ });
183
+ ```
184
+
185
+ ---
186
+
187
+ ## `blocktypeMap` Patterns
188
+
189
+ ### Adding Custom Block Types
190
+
191
+ Custom block types are arbitrary strings outside Draft.js defaults. Define them in `blocktypeMap` and set `standardBlockType` if the custom type should be the default.
192
+
193
+ > **Note:** `"paragraph-standard"` and `"paragraph-small"` below are **examples only**. Block type keys are entirely project-defined — always look at the project's `RichTextBlock` definition to see what custom types are actually configured.
194
+
195
+ ```tsx
196
+ blocktypeMap: {
197
+ "paragraph-standard": {
198
+ label: <FormattedMessage id="richTextBlock.paragraphStandard" defaultMessage="Paragraph Standard" />,
199
+ },
200
+ "paragraph-small": {
201
+ label: <FormattedMessage id="richTextBlock.paragraphSmall" defaultMessage="Paragraph Small" />,
202
+ renderConfig: {
203
+ element: (props) => <Typography paragraph variant="body2" {...props} />,
204
+ },
205
+ },
206
+ },
207
+ ```
208
+
209
+ When `standardBlockType` is set to something other than `"unstyled"`, the `"unstyled"` block type is hidden from the dropdown.
210
+
211
+ ### Relabeling Existing Block Types
212
+
213
+ Override just `label` to rename existing types without changing behavior:
214
+
215
+ ```tsx
216
+ blocktypeMap: {
217
+ "header-one": {
218
+ label: <FormattedMessage id="headingBlock.size600" defaultMessage="Size 600" />,
219
+ },
220
+ },
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Naming Conventions
226
+
227
+ **Shared RichTextBlock:**
228
+
229
+ - API: `api/src/common/blocks/rich-text.block.ts`
230
+ - Admin: `admin/src/common/blocks/RichTextBlock.tsx`
231
+ - Site: `site/src/common/blocks/RichTextBlock.tsx`
232
+ - Export name: `RichTextBlock`
233
+
234
+ **Scoped RichTextBlock:** name descriptively as `{Purpose}RichTextBlock` (e.g., `EyebrowRichTextBlock`, `HeadlineRichTextBlock`, `DescriptionRichTextBlock`). Define as a non-exported `const` inside the composite block file:
235
+
236
+ ```tsx
237
+ // Inside admin/src/documents/pages/blocks/TeaserItemBlock.tsx
238
+
239
+ const DescriptionRichTextBlock = createRichTextBlock({
240
+ link: LinkBlock,
241
+ rte: { maxBlocks: 1, supports: ["bold", "italic", "sub", "sup", "non-breaking-space", "soft-hyphen", "link", "links-remove"] },
242
+ minHeight: 0,
243
+ });
244
+
245
+ export const TeaserItemBlock = createCompositeBlock({
246
+ name: "TeaserItem",
247
+ blocks: {
248
+ description: {
249
+ block: DescriptionRichTextBlock,
250
+ title: <FormattedMessage id="teaserItemBlock.description" defaultMessage="Description" />,
251
+ },
252
+ },
253
+ });
254
+ ```
255
+
256
+ ---
257
+
258
+ ## Site Side
259
+
260
+ ```tsx
261
+ import { hasRichTextBlockContent, type PropsWithData, withPreview } from "@comet/site-nextjs";
262
+ import { type RichTextBlockData } from "@src/blocks.generated";
263
+ ```
264
+
265
+ Use `hasRichTextBlockContent` to conditionally render:
266
+
267
+ ```tsx
268
+ {
269
+ hasRichTextBlockContent(description) && <RichTextBlock data={description} />;
270
+ }
271
+ ```
272
+
273
+ When custom block types are added in the Admin, add matching entries in the site's `blocks` renderer map. The keys must exactly match what is defined in the project's Admin-side `RichTextBlock` configuration — `"paragraph-standard"` and `"paragraph-small"` below are **examples only**:
274
+
275
+ ```tsx
276
+ blocks: {
277
+ "paragraph-standard": createTextBlockRenderFn({ bottomSpacing: true }),
278
+ "paragraph-small": createTextBlockRenderFn({ variant: "paragraph200", bottomSpacing: true }),
279
+ },
280
+ ```
281
+
282
+ Do **not** wrap `RichTextBlock` in a `Typography` component — the block already defines its own typography through its renderers; wrapping produces conflicting styles.
283
+
284
+ ---
285
+
286
+ ## Decision Guide
287
+
288
+ | Scenario | Use Shared | Create Scoped |
289
+ | ------------------------------------------------------------- | ---------- | -------------------------- |
290
+ | General content area with full formatting | Yes | No |
291
+ | Short description or subtitle (single-line, basic formatting) | No | Yes — single-line pattern |
292
+ | Heading with size dropdown | No | Yes — heading-only pattern |
293
+ | Eyebrow text | No | Yes — single-line pattern |
294
+ | Email campaign with different link block | No | Yes — different link block |
295
+ | Content area with fewer features than default | No | Yes — limited feature set |
296
+
297
+ **Rule of thumb:** if the field needs `maxBlocks`, a custom `supports` set, a custom `standardBlockType`, or a different `link` block — create a scoped RichTextBlock.
298
+
299
+ ---
300
+
301
+ ## Common Pitfalls
302
+
303
+ 1. **Forgetting `minHeight: 0`** with `maxBlocks: 1` — editor area is unnecessarily tall for a single-line field.
304
+ 2. **Adding custom block types in Admin without updating the site** — site renderers must include all custom block type keys or content using those types renders as nothing.
305
+ 3. **Setting `standardBlockType` to a custom type without defining it in `blocktypeMap`** — the dropdown label renders incorrectly.
306
+ 4. **Trying to customize the API block per-field** — the API block is shared; all per-field customization is Admin-only.
307
+ 5. **Exporting scoped RichTextBlocks** — they must remain private (`const` without `export`) in the composite block file.
308
+ 6. **Omitting `"link"` and `"links-remove"` when a link block is configured** — editors cannot add or remove links without these in `supports`.
309
+ 7. **Wrapping `RichTextBlock` in `Typography` on the site** — produces double-wrapped, incorrectly styled output.