Skip to content
Open
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
76 changes: 76 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

`@editorjs/document-model` is a model-driven, collaboration-ready Editor.js engine split into focused packages. It's a **Yarn 4 (Berry) workspaces monorepo** (`packages/*` and `packages/tools/*`). Node `v24.15.0` (see `.nvmrc`; `nvm use`), Yarn `4.0.1` via Corepack (`corepack enable`).

## Commands

Run from the repo root (fan out across all workspaces via `yarn workspaces foreach`):

```bash
yarn install # install all workspace deps + set up Husky (pre-commit runs `yarn constraints`)
yarn build # build every package (tsc --build)
yarn test # run every package's Jest suite
yarn lint # lint every package (ESLint, eslint-config-codex)
yarn lint:fix # lint + auto-fix
```

Per-package (prefer this while iterating — much faster):

```bash
cd packages/model
yarn test # Jest
yarn test path/to/File.spec.ts # single file
yarn test -t "should do X" # single test by name
yarn test:coverage
yarn test:mutations # Stryker mutation testing (only some packages, e.g. model, core)
yarn lint
yarn dev # tsc --build --watch
```

Playground (manual end-to-end testing): `cd packages/playground && yarn dev`

OT server (Docker): create `.env` at repo root with `WSS_PORT=8080`, then `docker compose up`.
Comment on lines +11 to +36

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicates CONTRIBUTING.md's command list and per-package workflow almost verbatim – same commands, same example package. Only the ESM caveat isn't already there.

Anthropic's own CLAUDE.md guidance is to trim what Claude can already derive from repo docs and keep only what's non-obvious; would a pointer to @CONTRIBUTING.md + that caveat work instead of restating the full list?


**ESM caveat:** `core` runs Jest under `node --experimental-vm-modules` (baked into its `test` script). `ot-server` uses ESM (`type: module`, `ts-jest` with `useESM: true`) but its `test` script invokes `jest` directly.

## Architecture

Layered packages with a strict dependency direction. Full detail in `docs/architecture.md` — read the relevant `docs/` page before structural changes.

- **`model-types`** — foundation layer: shared low-level types, nominal brands, base event classes (`Index`, event classes, `EventBus`). No runtime deps. **Only `model` and `sdk` may depend on it directly.**
- **`sdk`** — the contract layer (interfaces, `EventBus`, `BlockTool`/`InlineTool`). Re-exports everything from `model`/`model-types` that a tool author legitimately needs. **All tools, plugins, and non-engine packages depend on `sdk`, never on `model` or `model-types` directly.**
- **`model`** — in-memory document model (`EditorJSModel`, `BlockNode`, `TextNode`, inline-fragments, caret management). The engine backing `EditorJSModel`; consumed directly only by `core` and `ot-server`. No DOM concerns. Free to change internals since it isn't the tool-facing surface.
Comment thread
ilyamore88 marked this conversation as resolved.
- **`dom-adapters`** — binds model nodes to DOM inputs (`DOMBlockToolAdapter`, `CaretAdapter`, `FormattingAdapter`). Observes/applies model changes via public APIs + events; depends only on `sdk`.
- **`collaboration-manager`** — operational transformation, batching, undo/redo, OT WebSocket client. Depends only on `sdk`.
- **`core`** — the orchestrator and single owner of service wiring: IoC container (`inversify`), plugin/tool lifecycle, `EditorAPI`, local undo/redo (`UndoRedoManager`). Entry point: services wired in constructor, `core.use(...)` registers UI plugins/tools, `initialize()` prepares tools + model + collaboration.
- **`ui`** — default UI shell (`EditorjsUI`, `BlocksUI`, `Toolbar`, `InlineToolbar`, `Toolbox`); registered as an `EditorjsPlugin` via `core.use()`. `BlocksUI` owns the `contenteditable`, normalizes browser `beforeinput` into `BeforeInputUIEvent` on the global `EventBus`.
- **`ot-server`** — standalone Node.js WebSocket OT server (`OTServer`, `DocumentManager`), one `DocumentManager` per `documentId`; transforms/applies/broadcasts operations. Server-side only.
- **`packages/tools/*`** — built-in block/inline tools (`paragraph`, `bold`, `italic`, `inline-link`), each implementing `BlockTool`/`InlineTool` from `@editorjs/sdk`.

Avoid direct cross-layer coupling: communicate through `sdk` interfaces/events and mutate via `EditorJSModel`'s public API.
Comment on lines +42 to +54

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same pattern as the Commands block: this restates docs/architecture.md's package table and dependency rules almost line-for-line.

The part that's actually load-bearing every session is the enforcement rule – sdk only, never model/model-types directly – everything else Claude can pull from docs/architecture.md on demand.

Could this trim to a pointer + the one rule?


## Testing

- Tests are **co-located** with source: `*.spec.ts` (unit) and `*.integration.spec.ts` (integration), run with Jest (`ts-jest`, ESM).
- **TDD is required** (per `openspec/config.yaml`): write the failing test first, then implement, then refactor.
- Name test cases in **should-notation**: `it('should return X when Y')`.
- CI runs lint/test/build per affected package, plus Stryker mutation tests where configured (changed files on PRs, all files on merge groups).

## Spec-driven development (OpenSpec)

This repo practices spec-driven development with [OpenSpec](https://github.com/Fission-AI/OpenSpec). Source-of-truth specs live in `openspec/specs/` (one folder per package/area); conventions are in `openspec/config.yaml`.

For any non-trivial change (new capability, breaking change, architectural shift, or anything altering behavior described in `openspec/specs/`), **start with a change proposal** under `openspec/changes/<name>/` rather than jumping into code. Small fixes/typos/behavior-preserving refactors don't need one.

Use the `/opsx:*` slash commands / skills (`explore`, `propose`, `apply`, `verify`, `sync`, `archive`, `update`) or the `openspec` CLI directly (`openspec list|show|validate|view`). A behavior change to spec-covered code without a proposal will be flagged in review.

## Conventions

- **Commits:** Conventional Commits scoped to the package, e.g. `fix(sdk): remove @/ alias from tsconfig`, `refactor(link): extract link inline tool into standalone package`.
- **Docs:** Public-API/architectural changes must update `docs/` and its Mermaid diagrams in `docs/diagrams/`. Keep diagram node/class names in sync with real TypeScript identifiers; never invent methods/properties. See `.github/agents/docs-updater.agent.md` for which doc/diagram pairs map to which code.
- **New tools/plugins:** depend on `@editorjs/sdk` only.
- ESLint (`eslint-config-codex`) is the source of truth for style; CI enforces zero warnings.