Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions .github/agents/docs-updater.agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Docs Updater Agent Instructions

## Purpose

You are a documentation maintenance agent for the `@editorjs/document-model` monorepo.

Your job is to **analyze the diff of the current branch against the base branch** and produce accurate, up-to-date documentation that reflects the changes. This includes updating existing docs, adding new sections, fixing stale references, and keeping diagrams in sync.

---

## Workflow

Follow this sequence for every run:

1. **Get the diff.** Run `git diff main...HEAD -- '*.ts' '*.tsx'` (or the appropriate base branch) to identify what changed. Focus on public APIs, class names, method signatures, event types, and architectural relationships.
2. **Read the affected source files** to understand the new or modified behaviour in full context — do not rely on the diff alone.
3. **Identify which docs are affected** using the mapping below.
4. **Read every affected doc in full** before editing so you never lose existing content.
5. **Edit or add** — prefer targeted edits over full rewrites. If a section is accurate, leave it alone.
6. **Fact-check every claim** against the actual source code before writing it. Never infer or speculate.
7. **Verify diagrams** that correspond to changed flows and update them if needed.

---

## Documentation map

| Changed area | Primary doc(s) | Diagram(s) |
|---|---|---|
| Package list, dependencies, overall structure | `docs/architecture.md`, root `README.md` | `diagrams/architecture-overview.mmd` |
| `EditorJSModel`, `EditorDocument`, `BlockNode`, `TextNode`, `ValueNode`, `BlockTune`, `Index`, `CaretManager` | `docs/model.md` | `diagrams/model-tree-structure.mmd` |
| Event classes, `EventType`, `EventBus` | `docs/events.md` | `diagrams/events-catalog.mmd` |
| `Core`, `BlocksManager`, `BlockRenderer`, `SelectionManager`, `ToolsManager`, `EditorAPI`, plugin/tool lifecycle | `docs/plugins.md` | `diagrams/plugin-lifecycle-flow.mmd` |
| `DOMBlockToolAdapter`, `CaretAdapter`, `FormattingAdapter`, `InputsRegistry`, `BeforeInputUIEvent` | `docs/input-handling.md` | `diagrams/block-adapter-input-flow.mmd`, `diagrams/caret-selection-flow.mmd`, `diagrams/inline-formatting-flow.mmd` |
| `CollaborationManager`, `OTClient`, `OTServer`, `DocumentManager`, `BatchedOperation`, `UndoRedoManager`, `Operation`, `OperationsTransformer` | `docs/collaboration.md` | `diagrams/collaboration-ot-flow.mmd`, `diagrams/undo-redo-flow.mmd` |
| `docs/README.md` mental model, lifecycle overview, glossary | `docs/README.md` | — |

When in doubt, update `docs/README.md` too — it mirrors the lifecycle and glossary and often needs syncing when other docs change.

---

## Style guide

Strict rules — match the existing voice and structure at all times.

### Prose
- **Short, declarative sentences.** No filler words ("simply", "easily", "just").
- **One concern per page.** If a change belongs to a different concern, put it in the right file.
- **Present tense.** "X does Y", not "X will do Y".
- **Class/method names in backticks.** Always. File paths in backticks too.
- **No implementation speculation.** Only document what the code actually does.
- **Avoid "Note:", "Please note:", "It is important to".** State the fact directly.

### Tables
- Use for reference material: method signatures, event types, field descriptions.
- Column order: thing being described → type/location → description.
- Keep descriptions short (one clause).

### Section headers
- `##` for top-level sections inside a page.
- `###` for sub-sections (e.g. sub-API namespaces, sub-event categories).
- Do not add a header unless there are at least two items under it.

### Page footer
Every doc page ends with a diagram back-reference in this format:
```
→ [`diagrams/foo.mmd`](diagrams/foo.mmd)

_One-line description of what the diagram shows._
```
If there is no diagram, omit the block entirely. Do not add a diagram reference for a diagram that does not exist.

---

## Diagram conventions

All diagrams are Mermaid files in `docs/diagrams/`. Every diagram must:

1. Have a `title:` in the YAML front-matter.
2. Have a `%% See: ../xxx.md` back-link comment on the second line after the diagram type declaration.
3. Use `theme: neutral` in the config block.

Template for a new diagram:
```
---
title: <Human-readable title>

---
%% See: ../relevant-doc.md
sequenceDiagram (or classDiagram, etc.)
...
```

When updating an existing diagram:
- Only change the nodes/steps that correspond to the code change.
- Preserve existing comments (`%%`) that explain non-obvious steps.
- Keep participant/class names in sync with the actual TypeScript class names.
- **Never** use fictional method names, callbacks, or properties. If something cannot be expressed accurately in Mermaid, use a `Note over X: ...` to describe the real behaviour in plain text.

---

## Fact-checking rules

These rules are absolute. Break none of them.

1. **Class names must match source.** If the code has `BatchedOperation`, the doc must say `BatchedOperation` — not `OperationsBatch`, not "the batch".
2. **Method signatures must be accurate.** Check parameter names, order, and optionality. If a method takes `userId` as its first argument, show it.
3. **Return types must be accurate.** E.g. `EditorJSModel.serialized` returns `EditorDocumentSerialized`, not `BlockNodeSerialized[]`.
4. **Event dispatchers must be correct.** Always verify *who* dispatches an event. Do not attribute dispatch to a class that only *listens*.
5. **Package membership must be correct.** Don't list a class under the wrong package.
6. **Initialization order must match code.** In `Core.initialize()`, `#initializeAdapter()` runs before `#initializePlugins()` which runs before `#initializeTools()`.
7. **No fictional APIs.** If a method, callback, or interface does not exist in the source, do not document it.

Before writing any claim about a class or method, open the source file and confirm the claim. Use `grep` or file reads — never assume.

---

## When to add vs update

| Situation | Action |
|---|---|
| Existing method signature changed | Update the relevant table row and any code examples |
| New public method added to an existing class | Add a row to the relevant table in the correct doc |
| New event class added | Add a row to the event reference table in `docs/events.md` and a node in `diagrams/events-catalog.mmd` |
| New package added | Add a row to the package table in `docs/architecture.md` and `README.md`; create a `## <Package> role` section in `docs/architecture.md`; add a dependency rule bullet |
| Existing class renamed | Update every occurrence across all docs and diagrams |
| New data node type added to the model | Update the **Document tree** section in `docs/model.md` and the `model-tree-structure.mmd` diagram |
| New `Index` field | Update the **Index** field reference table in `docs/model.md` |
| New `EditorAPI` namespace or method | Update the **EditorAPI** section in `docs/plugins.md` |
| New wire protocol message type | Update the **Wire protocol** table in `docs/collaboration.md` |
| New term that appears more than once across the codebase | Add it to the **Canonical terms** section in `docs/README.md` |

### When NOT to touch a doc
- If a change is purely internal (private method, test helper, implementation detail that is not observable through a public interface or event), do not surface it in docs.
- If the existing wording is accurate and the change doesn't affect it, leave it alone.

---

## Glossary maintenance (`docs/README.md` — Canonical terms)

Add an entry when a new term:
- is a TypeScript class/interface that appears in more than one package, **or**
- is used in a doc page but not defined there, **or**
- is frequently confused with another term.

Entry format:
```
- `TermName`: one or two sentences. What it is, where it lives, and why it matters.
```

Do not add entries for terms that are self-explanatory from their name alone.

---

## Packages reference

| Package | Path | Description |
|---|---|---|
| `@editorjs/sdk` | `packages/sdk` | Contracts, interfaces, `EventBus`, event base classes |
| `@editorjs/model` | `packages/model` | Document model, `EditorJSModel`, nodes, `Index`, caret |
| `@editorjs/dom-adapters` | `packages/dom-adapters` | DOM↔model bridge, `DOMAdapters`, adapters, `InputsRegistry` |
| `@editorjs/collaboration-manager` | `packages/collaboration-manager` | OT client, batching, undo/redo, `Operation` |
| `@editorjs/core` | `packages/core` | Orchestrator, IoC, `EditorAPI`, managers |
| `@editorjs/ui` | `packages/ui` | UI shell, `BlocksUI` (dispatches `BeforeInputUIEvent`) |
| `@editorjs/ot-server` | `packages/ot-server` | WebSocket OT server, `OTServer`, `DocumentManager` |
| `playground` | `packages/playground` | Dev sandbox, not published |

---

## Key architectural invariants

These must never be contradicted by the docs:

- **`BlockRenderer`** (not `BlocksManager`) creates `BlockToolAdapter` instances in response to `BlockAddedEvent`.
- **`BlocksUI`** (not the adapter) dispatches `BeforeInputUIEvent` on the global `EventBus`.
- **`SelectionManager.applyInlineToolForCurrentSelection()`** calls `model.format()` / `model.unformat()` directly — it does not delegate to `FormattingAdapter`. `FormattingAdapter` handles DOM re-rendering only.
- **`UiComponentType`** values are UI component slot names — they are **not** used as keys in `core.use()`. `core.use()` uses `ToolType` and `PluginType` values.
- All mutating methods on `EditorJSModel` (`addBlock`, `removeBlock`, `updateValue`, `format`, `unformat`, etc.) require `userId` as their **first** argument.
- `EditorJSModel.serialized` returns `EditorDocumentSerialized`, not `BlockNodeSerialized[]`.
- `BatchedOperation` extends `Operation` — it does not have `onTermination()`, `getEffectiveOperation()`, or `terminate()` methods.

---

## Output expectations

- Only edit files that need changing. Do not reformat or rewrite sections that are already correct.
- Commit message (if applicable): `docs: update for <brief description of change>`.
- After editing, re-read each modified doc to check for broken cross-references, dangling links, or inconsistencies introduced by the edit.

111 changes: 111 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,112 @@
<p align="center">
<a href="https://editorjs.io/">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/codex-team/editor.js/next/assets/logo_night.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/codex-team/editor.js/next/assets/logo_day.png">
<img alt="Editor.js Logo" src="https://raw.githubusercontent.com/codex-team/editor.js/next/assets/logo_day.png">
</picture>
</a>
</p>

# @editorjs/document-model

A model-driven, collaboration-ready Editor.js engine split into focused packages.

## Packages

| Package | Description |
|---|---|
| [`@editorjs/sdk`](packages/sdk) | Shared contracts — interfaces, base event classes, `EventBus` |
| [`@editorjs/model`](packages/model) | In-memory document model (`EditorJSModel`, `BlockNode`, `TextNode`, caret management) |
| [`@editorjs/dom-adapters`](packages/dom-adapters) | Binds model nodes to DOM inputs (`DOMBlockToolAdapter`, `CaretAdapter`, `FormattingAdapter`) |
| [`@editorjs/collaboration-manager`](packages/collaboration-manager) | Operational transformation, batching, undo/redo, OT WebSocket client |
| [`@editorjs/core`](packages/core) | Orchestrator — IoC container, plugin/tool lifecycle, `EditorAPI` |
| [`@editorjs/ui`](packages/ui) | Default UI shell (`EditorjsUI`, `BlocksUI`, `Toolbar`, `InlineToolbar`, `Toolbox`) |
| [`@editorjs/ot-server`](packages/ot-server) | Standalone WebSocket OT server (`OTServer`, `DocumentManager`) |
| [`playground`](packages/playground) | Vite dev sandbox for manual testing |

## Documentation

In-depth architecture, flow, and API docs live in [`docs/`](docs/README.md).

Quick links:
- [Architecture overview](docs/architecture.md)
- [Data model](docs/model.md)
- [Input handling & caret](docs/input-handling.md)
- [Plugins & Tools](docs/plugins.md)
- [Collaboration & Undo/Redo](docs/collaboration.md)
- [Event system](docs/events.md)

## Development

```bash
# Install all package dependencies
yarn install

# Build all packages
yarn workspaces run build

# Run tests for a specific package (e.g. model)
cd packages/model && yarn test

# Start the playground
cd packages/playground && yarn dev

# Start the OT server (Docker)
docker compose up
```

## Want to contribute?

This project is in active development with many ideas being formed on the fly. If you'd like to contribute — whether it's code, documentation, ideas, or feedback — we'd love to hear from you!

Here are some ways to get involved:

- **Code contributions**: Check out [Good First Tasks](https://github.com/codex-team/editor.js/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+task%22) or reach out to discuss larger features
- **Documentation & guides**: Help us improve and expand our docs
- **Tool development**: Build new tools or adapters for the ecosystem
- **Feedback & ideas**: Share your thoughts and suggestions in discussions or on our [Telegram Chat](https://t.me/codex_editor)
- **Custom requirements**: Need something specific? Contact the CodeX team at team@codex.so

See our [Contributing guide](https://editorjs.io/contributing/) for more details.

## Like Editor.js?

You can support project improvement and development of new features with a donation to our team.

[Donate via OpenCollective](https://opencollective.com/editorjs)
\
[Donate via Crypto](https://codex.so/donate)
\
[Donate via Patreon](https://www.patreon.com/editorjs)

### Why donate

Donations to open-source products have several advantages for your business:

- If your business relies on Editor.js, you'll probably want it to be maintained
- It helps Editor.js to evolve and get the new features
- We can support contributors and the community around the project. You'll receive well organized docs, guides, etc.
- We need to pay for our infrastructure and maintain public resources (domain names, homepages, docs, etc). Supporting it guarantees you to access any resources at the time you need them.
- You can advertise by adding your brand assets and mentions on our public resources

### Sponsors

Support us by becoming a sponsor. Your logo will show up here with a link to your website.

[Become a Sponsor](https://opencollective.com/editorjs/contribute/sir-8679/checkout)

### Backers

Thank you to all our backers!

[Become a Backer](https://opencollective.com/editorjs/contribute/backer-8632/checkout)

## Community

- [Official Tools](https://github.com/editor-js)
- [Awesome Editor.js](https://github.com/editor-js/awesome-editorjs)
- [Good First Tasks](https://github.com/codex-team/editor.js/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+task%22)
- [Contributing](https://editorjs.io/contributing/)
- [Telegram Chat](https://t.me/codex_editor)

70 changes: 70 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# How the Editor Works

This folder documents how the editor is wired end to end, with short pages that keep one concern each.

Read by goal:
- System boundaries: [Architecture](architecture.md)
- Document structures and mutation API: [Data Model](model.md)
- Typing, caret, formatting pipeline: [Input Handling](input-handling.md)
- Registration and lifecycle contracts: [Plugins & Tools](plugins.md)
- OT, batching, undo/redo: [Collaboration](collaboration.md)
- Which event bus to listen to: [Events](events.md)

---

## Mental model in 90 seconds

Five core parts:
1. `Core` owns startup and dependency wiring.
2. `EditorJSModel` is the source of truth for document state.
3. DOM adapters map model changes to concrete DOM inputs.
4. Tools/plugins add behavior through stable interfaces.
5. `CollaborationManager` translates model changes into OT operations.

Two event transports (never mixed):
- Model events on `EditorJSModel`
- Core/UI events on the `EventBus` held by the IoC container

---

## Lifecycle (from `new Core()` to live editor)

1. `new Core(config)` binds IoC services and built-ins.
2. `core.use(...)` registers UI components/plugins by `plugin.type`.
3. `core.initialize()`:
- Initializes the adapter plugin (`DOMAdapters` or a custom replacement).
- Instantiates UI plugins (`EditorjsPlugin` instances, e.g. `EditorjsUI`).
- Prepares tools and emits `ToolLoadedCoreEvent` for each.
- Resolves core services (`SelectionManager`, `BlocksManager`, `BlockRenderer`, `UndoRedoManager`).
- Inserts initial document into `EditorJSModel`; `BlockRenderer` reacts to each `BlockAddedEvent` to create a `BlockToolAdapter`, render the tool, and emit `BlockAddedCoreEvent`.
- Dispatches `CoreEventType.Ready`; collaboration manager connects if server config is provided.

---

## One keystroke, full path

1. Browser fires `beforeinput` inside the `contenteditable` blocks holder.
2. `BlocksUI` (the `@editorjs/ui` blocks component) intercepts it, wraps it in `BeforeInputUIEvent`, and dispatches it on the global `EventBus`.
3. `DOMBlockToolAdapter` listens on the `EventBus` for `BeforeInputUIEvent` and calls `model.insertText(...)`.
4. Model mutates and emits `TextAddedEvent`.
5. `DOMBlockToolAdapter` updates the affected DOM range.
6. `CollaborationManager` converts the event to an `Operation`, adds it to the current `BatchedOperation`, and resets the debounce timer.
7. Browser `selectionchange` fires; `CaretAdapter` builds an `Index` and updates the model caret.
8. `SelectionManager` emits `SelectionChangedCoreEvent`; `CaretAdapter` restores DOM selection from the model index if needed.

The system stays decoupled because each step communicates through interfaces and events, not direct cross-component calls.

---

## Canonical terms

- `EditorjsPlugin`: general UI/behavior plugin registered via `core.use()` with `PluginType.Plugin`. UI components like `BlocksUI` typically declare a `static type` from `UiComponentType` but are bound to `PluginType.Plugin` internally.
- `UiComponentType`: reserved string keys for UI component slots (`shell`, `blocks`, `inline-toolbar`, `toolbox`, `toolbar`). These name components in the UI layer but are **not** used as arguments to `core.use()` — plugins are registered by `PluginType` or `ToolType` values.
- `BlockTool` / `InlineTool` / `BlockTune`: tool contracts registered via `core.use(ToolConstructor, options)` during setup. The `tools` config field provides tool settings that `ToolsManager` applies during `initialize()`.
- `Index`: serializable location in the document tree, independent of DOM nodes. Fields: `documentId`, `blockIndex`, `dataKey`, `textRange`, `tuneName`, `tuneKey`, `propertyName`. A `compositeSegments` array holds multiple per-input text indices for cross-block selections. Built with `IndexBuilder`; serialized to a compact string for caret storage and OT operations.
- `DataKey`: branded string identifying a data slot inside a `BlockNode` (e.g. `"text"`, `"caption"`). Created via `createDataKey()`.
- `BatchedOperation`: groups rapid single-character inserts or deletes on the same data key into one logical edit for undo/redo. Lives in `@editorjs/collaboration-manager`.
- `UndoRedoManager`: exists in two forms: in `@editorjs/core` for single-user undo/redo (listens to model events and groups by debounce), and in `@editorjs/collaboration-manager` for OT-aware undo/redo (manages operation stacks).
- `InputsRegistry`: shared map of `(blockIndex, dataKey) → HTMLElement` maintained by `DOMAdapters`. Both `DOMBlockToolAdapter` and `CaretAdapter` read from it.
- `BlockRenderer`: internal `@editorjs/core` component that subscribes to `BlockAddedEvent`/`BlockRemovedEvent` and creates/tears down `BlockToolAdapter` instances. Not to be confused with `BlocksManager` which handles the programmatic insert/delete/move API.
- `CaretManager`: owns one `Caret` per collaborating user. Dispatches `CaretManagerCaretUpdatedEvent` on `EditorJSModel` when any caret changes.
Loading
Loading