CLAUDE.md
core-web/libs/new-block-editor/CLAUDE.mdCLAUDE.md
Quality
61/100
Scores the file, not the repository.Length
2,134 words
20 headings · 1 code blocksRepository
949
— · pushed 0 days agoLast changed
3 days ago
First indexed 3 days ago.1## Interaction Preferences23Act with constructive skepticism. You are a collaborator with strong reasoning ability.45Make decisions based on evidence. Do not assume you must agree with me.67You should:89- Question weak premises10- Point out flaws in reasoning11- Propose new approaches or mental models1213If I am approaching a problem from the wrong perspective or with incorrect assumptions, explain it clearly and suggest a better starting point.1415Be direct.16Avoid unnecessary validation language, emojis, or marketing tone.1718## Expected Response Format1920Your responses should focus on:2122- **Core insight**23- **Key tradeoffs**24- **Major risks**25- **Recommended next move**2627## TipTap Node Names Are Immutable2829TipTap serializes editor content to JSON using the node's `name` as the `type` key:3031```json32{ "type": "dotImage", "attrs": { ... } }33{ "type": "dotContent", "attrs": { ... } }34```3536dotCMS customers store this JSON in their database. **If a node name changes, TipTap will not recognize stored content and will silently drop those blocks on load — permanently destroying customer data.**3738### Rule3940**Never rename an existing node's `name` field without explicit approval from the developer.** This applies to any `.extension.ts` or node file where `name:` is set.4142If asked to rename a node, you must:431. Refuse and explain the data-loss risk442. Present the trade-off: renaming requires a database migration to rewrite every stored document that contains that node type — not just a code change453. Wait for explicit developer confirmation before proceeding4647### Creating new nodes4849When creating a new node, you may choose any name — but choose carefully, because **that name can never be changed** once real content has been written with it. Prefer descriptive, namespaced names (e.g. `dotVideo`, `dotContent`) over generic ones.5051### Current node name registry5253| Node | Name | File |54|------|------|------|55| Image | `dotImage` | `extensions/nodes/image.extension.ts` |56| Video | `dotVideo` | `extensions/nodes/video.extension.ts` |57| Audio | `dotAudio` | `extensions/nodes/audio.extension.ts` |58| Contentlet | `dotContent` | `extensions/nodes/contentlet/contentlet.extension.ts` |59| Grid block | `gridBlock` | `extensions/nodes/grid.extension.ts` |60| Grid column | `gridColumn` | `extensions/nodes/grid.extension.ts` |61| AI content | `aiContent` | `extensions/nodes/ai-content.extension.ts` |6263Standard TipTap/StarterKit names (`paragraph`, `heading`, `bulletList`, `orderedList`, `blockquote`, `codeBlock`, `horizontalRule`, `table`, etc.) are owned by TipTap upstream and must not be changed either.6465---6667## Service Architecture6869The lib follows a strict split: **data fetching** delegates to `@dotcms/data-access`; **state and orchestration** stays local. The legacy block-editor sets the precedent — use the data-access services directly rather than re-implementing them.7071### Data services — consume from `@dotcms/data-access`7273| Service | Used for |74|---|---|75| `DotContentTypeService` | Content type filtering for the slash-menu's content-type sub-picker (`filterContentTypes`) and per-type metadata reads (`getContentType`, used by `ContentletEditUrlService`). |76| `DotContentSearchService` | Lucene search behind the slash-menu's contentlet drill-down (`/api/content/_search`). The editor-flavoured query string (`+contentType:X +languageId:Y +deleted:false +working:true +catchall:** title:''^15`) is built inline at the call site (`buildContentletByTypeQuery` in `slash-menu-catalog.ts`); the service itself stays generic. |77| `DotLanguagesService` | Language metadata for the editor store (`getById`). |78| `DotAiService` | AI text generation, AI image generation + publish, plugin status check. Identical surface to legacy block-editor usage. |79| `DotUploadFileService` | Wrapped by the lib's local `DotUploadService` adapter (see below). |80| `DotMessageService` | i18n. Used everywhere. |8182Do **not** create custom HTTP services in this lib for any of the above. If you need a method that doesn't exist on a data-access service, extend the data-access service rather than rolling a new one here.8384### Local services — editor-specific8586| Service | Why it stays local |87|---|---|88| `EditorPopoverService` | Caret-anchored popover state (active id, anchor rect, per-popover payloads). Editor-only concern. |89| `EditorModalService` | Lifecycle for centered `DialogService.open()` modals (AI content, AI image, image / video pickers). Editor-only concern. |90| `EditorToolbarStore` | Signal mirror of TipTap mark/block/alignment state for the toolbar. Editor-only concern. |91| `SlashMenuService` | Slash-menu catalog, filtering, sub-menu loading. Editor-only concern. |92| `ContentletEditUrlService` | Resolves the legacy-vs-new content editor URL via per-content-type feature-flag cache. Caches the metadata read so repeated contentlet edits within one session don't re-hit the network. The wrapper exists *for* the cache; without it, every "Edit contentlet" click would re-fetch. |93| `DotUploadService` (adapter) | Promise/async-await adapter around `DotUploadFileService.publishContent()`. Two responsibilities: bridge the async model for `handleMediaDrop` (which is `async` linear code) and unwrap the workflow PUBLISH endpoint's `Record<contentTypeKey, contentlet>` shape into the editor's narrower `UploadedImage` / `UploadedVideo` types. |9495When in doubt: **state & orchestration → local. HTTP → data-access.**9697---9899## Overlay System Architecture100101The editor uses two distinct overlay primitives. Pick by interaction model, not content type — the difference is whether the overlay anchors to the caret/trigger (popover) or sits centered over the page (modal dialog).102103### Pick the right overlay primitive104105| Primitive | Use | Anchored to | Modality | Examples |106|-----------|-----|-------------|----------|----------|107| `<dot-editor-popover>` shell | Compact, caret-anchored, single form | Caret / trigger rect via `@floating-ui/dom` | Non-modal — no backdrop, no focus trap, click-outside dismisses | link, table, image-properties, emoji |108| PrimeNG `DialogService.open()` | Centered modal — large content, multi-pane, embeddable, or external library component | Viewport center | Modal — backdrop, focus trap, explicit close | AI content, AI image, image picker, video picker |109110When an overlay has both an input area AND a result/preview area, default to a centered modal — caret-anchored popovers get cramped.111112### Caret-anchored popover shell (`<dot-editor-popover>`)113114All compact popovers (link, table, image-properties, emoji) share a single `EditorPopoverService` and an `<editor-popover>` shell component:115116- `EditorPopoverService` (`services/editor-popover.service.ts`) — central state: which popover is open, its anchor rect, and per-popover payloads (`imagePropertiesPayload`, `linkPayload`).117- `EditorPopoverComponent` (`components/editor-popover.component.ts`) — shell wrapper: absolute positioning via `@floating-ui/dom`, `display:none` toggle, Escape + click-outside dismiss (whitelisting body-portaled PrimeNG `.p-overlay` / `.p-select-overlay` so embedded `<p-select>` stays alive), `<ng-content>` projection, auto-focus on the first form control after first paint.118119Each popover content component:120- Takes `editor = input.required<Editor>()` and calls editor commands directly.121- Wraps its form in `<dot-editor-popover popoverId="...">`.122- Injects `EditorPopoverService` for open/close state and payloads.123124### Centered modals via `DialogService.open()` (`EditorModalService`)125126Every centered modal in the editor — AI content, AI image, image picker, video picker — is opened through PrimeNG's `DialogService.open()`, surfaced by `EditorModalService` (`services/editor-modal.service.ts`). One pattern, one teardown story:127128- The editor component provides `DialogService` at the component scope so each editor instance gets its own dynamic-dialog factory. Provided in `editor.component.ts`.129- `EditorModalService` keeps one private `DynamicDialogRef` per modal kind, set to `null` between opens.130- Each `openX(editor)` method calls `dialogService.open(Component, config)` with the right `data` and subscribes to `dialogRef.onClose` to apply the result (insert nodes, mutate state) into the editor.131- Modal components inject `DynamicDialogRef` and signal a result by calling `this.dialogRef.close(result)`. Cancel/Escape/X close with no value, which the `onClose` subscriber treats as "no-op".132- `ngOnDestroy()` on the service closes every live ref so an editor unmount mid-dialog doesn't orphan an overlay.133134When adding a new centered modal:1351. Build the component as a normal standalone Angular component; inject `DynamicDialogRef` and call `this.dialogRef.close(result)` on confirm.1362. Add `openYourModal(editor)` to `EditorModalService` mirroring the existing methods (private ref + idempotent guard + `onClose` subscription).1373. Add a teardown line to `ngOnDestroy()`.138139Do not embed `<p-dialog>` directly inside `editor.component.ts`. The pattern above gives consistent lifecycle, focus behavior, and per-editor isolation for free.140141---142143## Node + action inventory144145What actions are available on each node type. **Slash** = appears in `/` menu (`slash-menu-catalog.ts`). **Toolbar** = button in `toolbar.component.ts`. **Marks** = inline marks that can be applied to text inside this node. **Commands** = TipTap commands declared on the node's extension. **Node-scoped** = appears only when the node is selected/active.146147### Block-level nodes148149| Node (`type`) | Source | Slash | Toolbar | Allowed-block key |150|---------------|--------|-------|---------|-------------------|151| `paragraph` | StarterKit | Text | Block-type select | always allowed |152| `heading` (levels 1–6) | StarterKit | Heading 1 / 2 / 3 | Block-type select (1–3) | `heading1`…`heading6` |153| `bulletList` | StarterKit | Bullet List | Bullet List | `bulletList` |154| `orderedList` | StarterKit | Ordered List | Ordered List | `orderedList` |155| `listItem` | StarterKit | — | indent / outdent | inherits list parent |156| `blockquote` | StarterKit | Blockquote | Blockquote | `blockquote` |157| `codeBlock` | StarterKit | Code Block | Code Block | `codeBlock` |158| `horizontalRule` | StarterKit | — | Horizontal rule | `horizontalRule` |159| `table` | TableKit | Table (popover) | Insert table + table sub-toolbar | `table` |160| `dotImage` | `image.extension.ts` | Image (modal picker) | Insert image, wrap-left/right (node-scoped), align, image properties popover (node-scoped) | `image` |161| `dotVideo` | `video.extension.ts` | Video (modal picker) | Insert video | `video` |162| `dotAudio` | `audio.extension.ts` | Audio (modal picker) | Insert audio | `audio` |163| `youtube` | `@tiptap/extension-youtube` | — (legacy slash entry) | — | `youtube` |164| `dotContent` | `contentlet/contentlet.extension.ts` | Content type → submenu | Edit contentlet (node-scoped) | `dotContent` |165| `gridBlock` | `grid.extension.ts` | Grid (2 columns) | — | `gridBlock` |166| `gridColumn` | `grid.extension.ts` | — (created by `insertGrid`) | — | inherits gridBlock |167| `aiContent` | `ai-content.extension.ts` | Ask AI (centered modal) | — | `aiContent` |168| `uploadPlaceholder` | `upload-placeholder.extension.ts` | — (transient) | — | always (transient) |169170### Marks171172| Mark | Source | Toolbar | Applies to |173|------|--------|---------|------------|174| `bold` | StarterKit | Bold | any text |175| `italic` | StarterKit | Italic | any text |176| `underline` | StarterKit | Underline | any text |177| `strike` | StarterKit | Strike | any text |178| `code` | StarterKit | Inline code | any text |179| `superscript` | `@tiptap/extension-superscript` | Sup | any text |180| `subscript` | `@tiptap/extension-subscript` | Sub | any text |181| `link` | `@tiptap/extension-link` | Link popover | any text (gated by `link` allowed-block) |182| `textAlign` | `@tiptap/extension-text-align` | Align L/C/R/Justify | configured for `paragraph` + `heading` only |183184### Special / node-scoped commands185186| Command | Owner | What it does |187|---------|-------|--------------|188| `setImageTextWrap('left' \| 'right')` | `dotImage` | Toggles `image-wrap-left/right` class on the wrapping `<figure>`. Mutually exclusive with `setImageTextAlign`. |189| `setImageTextAlign('left' \| 'center' \| 'right')` | `dotImage` | Sets `image-align-*` class on the wrapping `<figure>`. Clears `textWrap`. |190| `insertGrid()` | `gridBlock` | Inserts a 2-column grid block at the selection. Equal default widths. |191| `setGridColumns(columns: number[])` | `gridBlock` | Updates column-fraction widths for the active grid block. Used by the grid resize plugin. |192193> **AI Content note:** the `aiContent` node has no custom commands. AI-generated HTML is inserted with the standard `commands.insertContent(html)` so each block becomes a normal editable node. The node registration only exists so legacy stored content (from the old block editor, which DID wrap in `aiContent`) still parses and renders — removing it would silently drop those blocks (see "TipTap Node Names Are Immutable").194195### Slash-only "actions" (don't insert a single node)196197These slash entries do not map 1:1 to a node — they trigger flows that mutate the editor:198199| Slash entry | Trigger |200|-------------|---------|201| AI Image | Opens `DotAIImagePromptComponent` via `DialogService.open()` (centered modal). On accept, inserts a `dotImage` node. |202| AI Content | Opens `AiContentDialogComponent` via `DialogService.open()` (centered modal). On insert, the generated HTML is parsed against the editor schema so each block becomes a normal editable node (paragraphs / headings / lists). Does NOT wrap in an `aiContent` block. |203| Content type | Opens an in-place sub-menu of allowed content types, then a contentlet picker. Inserts a `dotContent` node. |204| Image / Video | Opens `DotBrowserSelectorComponent` via `DialogService.open()` (centered modal picker). Inserts the corresponding `dotImage` / `dotVideo` node. |205| Table / Link / Emoji | Opens a caret-anchored `<dot-editor-popover>`. Insert / mutate the corresponding node. |206207### Customer-supplied remote commands (`customBlocks` field variable)208209Each declared `Action` becomes a slash entry that calls `editor.commands[action.command]()` on selection. The TipTap extensions resolved from the remote URLs determine which commands actually exist; missing commands log a warning instead of throwing. See `extensions/remote-extensions.loader.ts`.210211### Toolbar groups (visual order)2122131. History — Undo, Redo2142. Block type — paragraph / heading 1–3 select2153. Inline format — Bold, Italic, Underline, Strike, Code, Superscript, Subscript2164. Alignment — Left, Center, Right, Justify (heading + paragraph only)2175. Image-only (visible when an image is selected) — Wrap L/R, Image properties2186. Contentlet-only (visible when a contentlet is selected) — Edit2197. Block formats — Bullet List, Ordered List, Blockquote, Code Block2208. Indent / Outdent / Clear formatting2219. Horizontal rule22210. Insert dialogs — Link, Image, Video, Table22311. Table sub-toolbar (when inside a table) — Insert row/col, Merge/Split, Toggle row/col header, Delete row/col/table22412. Emoji22513. Markdown copy / paste22614. Fullscreen toggle227228The `showInsertGroup`/`showBlockFormatsGroup` computeds and the `@if (allow*)` guards collapse dividers when a group is empty. See `toolbar.component.ts`.
Also in dotCMS/core
Diff this repo’s formatsOne repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| dotCMS/core.github/copilot-instructions.md · 949 | Copilot instructions | setupbuildtestlint-format+11 | 84/100 | today | |
| dotCMS/corecore-web/apps/dotcms-ui-e2e/AGENTS.md · 949 | AGENTS.md | setupstylearchtesting-strategy+2 | 78/100 | 3 days ago | |
| dotCMS/corecore-web/apps/mcp-server/CLAUDE.md · 949 | CLAUDE.md | setupbuildtestlint-format+5 | 89/100 | 3 days ago | |
| dotCMS/core.cursor/rules/doc-updates.mdc · 949 | Cursor rules | docs | 30/100 | 3 days ago | |
| dotCMS/core.cursor/rules/dotcms-guide.mdc · 949 | Cursor rules | archdo-notdocs | 69/100 | 3 days ago | |
| dotCMS/core.cursor/rules/e2e-rules.mdc · 949 | Cursor rules | setupteststylearch+5 | 89/100 | 3 days ago | |
| dotCMS/core.cursor/rules/frontend-context.mdc · 949 | Cursor rules | teststyledocs | 78/100 | 3 days ago | |
| dotCMS/core.cursor/rules/java-context.mdc · 949 | Cursor rules | buildstyle | 44/100 | 3 days ago | |
| dotCMS/core.cursor/rules/test-context.mdc · 949 | Cursor rules | testtesting-strategy | 54/100 | 3 days ago | |
| dotCMS/core.github/instructions/frontend.instructions.md · 949 | Copilot instructions | testlint-formatstylearch+3 | 69/100 | 3 days ago | |
| dotCMS/coreCLAUDE.md · 949 | CLAUDE.md | setupbuildteststyle+7 | 99/100 | today | |
| dotCMS/corecore-web/AGENTS.md · 949 | AGENTS.md | style | 63/100 | 3 days ago | |
| dotCMS/corecore-web/CLAUDE.md · 949 | CLAUDE.md | teststylearchtesting-strategy+3 | 100/100 | 3 days ago | |
| dotCMS/corecore-web/apps/dotcms-ui/AGENTS.md · 949 | AGENTS.md | buildteststyledependencies+3 | 94/100 | 3 days ago | |
| dotCMS/corecore-web/libs/block-editor/CLAUDE.md · 949 | CLAUDE.md | archdo-not | 69/100 | 3 days ago | |
| dotCMS/corecore-web/libs/portlets/CLAUDE.md · 949 | CLAUDE.md | setupteststyleui+1 | 77/100 | 3 days ago | |
| dotCMS/corecore-web/libs/portlets/edit-ema/portlet/src/lib/store/CLAUDE.md · 949 | CLAUDE.md | teststylearchtypes+2 | 65/100 | 3 days ago | |
| dotCMS/corecore-web/libs/sdk/client/CLAUDE.md · 949 | CLAUDE.md | setupbuildtestlint-format+9 | 97/100 | 3 days ago | |
| dotCMS/corecore-web/libs/sdk/react/CLAUDE.md · 949 | CLAUDE.md | setupbuildtestlint-format+9 | 97/100 | 3 days ago | |
| dotCMS/coredotCMS/src/main/java/com/dotcms/rest/CLAUDE.md · 949 | CLAUDE.md | typesdatabaseapido-not+1 | 57/100 | 3 days ago |
Diff against .github/copilot-instructions.md Diff against core-web/apps/dotcms-ui-e2e/AGENTS.md Diff against core-web/apps/mcp-server/CLAUDE.md Diff against .cursor/rules/doc-updates.mdc Diff against .cursor/rules/dotcms-guide.mdc Diff against .cursor/rules/e2e-rules.mdc Diff against .cursor/rules/frontend-context.mdc Diff against .cursor/rules/java-context.mdc Diff against .cursor/rules/test-context.mdc Diff against .github/instructions/frontend.instructions.md Diff against CLAUDE.md Diff against core-web/AGENTS.md Diff against core-web/CLAUDE.md Diff against core-web/apps/dotcms-ui/AGENTS.md Diff against core-web/libs/block-editor/CLAUDE.md Diff against core-web/libs/portlets/CLAUDE.md Diff against core-web/libs/portlets/edit-ema/portlet/src/lib/store/CLAUDE.md Diff against core-web/libs/sdk/client/CLAUDE.md Diff against core-web/libs/sdk/react/CLAUDE.md Diff against dotCMS/src/main/java/com/dotcms/rest/CLAUDE.md
Similar configs
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| Adit-Jain-srm/NightmareNetCLAUDE.md · 45 | CLAUDE.md | buildtestlint-formatstyle+6 | 100/100 | 3 days ago | |
| nimbalyst/nimbalystpackages/android/CLAUDE.md · 1.4k | CLAUDE.md | setupbuildstylearch+2 | 100/100 | 3 days ago | |
| dotCMS/corecore-web/CLAUDE.md · 949 | CLAUDE.md | teststylearchtesting-strategy+3 | 100/100 | 3 days ago | |
| microsoft/playwrightCLAUDE.md · 94k | CLAUDE.md | buildtestlint-formatstyle+7 | 100/100 | 3 days ago | |
| filamentphp/filamentCLAUDE.md · 32k | CLAUDE.md | buildtestlint-formatstyle+7 | 100/100 | 3 days ago | |
| bagisto/bagistoCLAUDE.md · 28k | CLAUDE.md | setupbuildteststyle+5 | 100/100 | 3 days ago | |
| dotCMS/coreCLAUDE.md · 949 | CLAUDE.md | setupbuildteststyle+7 | 99/100 | today | |
| lollipopkit/flutter_server_boxCLAUDE.md · 8.3k | CLAUDE.md | buildteststylearch+2 | 98/100 | 3 days ago |
