Skip to content
Merged
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
51 changes: 40 additions & 11 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,55 @@ A document editing and rendering library for the web.

## Architecture: Rendering

SuperDoc uses its own rendering pipeline — **ProseMirror is NOT used for visual output**.
SuperDoc uses its own rendering pipeline. ProseMirror stores document state;
it is not the visual renderer.

```
PM Doc (hidden) → pm-adapter → FlowBlock[] → layout-engine → Layout[] → DomPainter → DOM
.docx
→ super-converter parses OOXML into the hidden PM doc
→ pm-adapter reads PM state and resolved styles
→ FlowBlock[]
→ layout-engine paginates
→ ResolvedLayout
→ DomPainter paints DOM
```

- `PresentationEditor` wraps a hidden ProseMirror `Editor` instance for document state and editing commands
- The hidden Editor's contenteditable DOM is never shown to the user
- **DomPainter** (`layout-engine/painters/dom/`) owns all visual rendering
- Style-resolved properties (backgrounds, fonts, borders, etc.) must flow through `pm-adapter` → DomPainter, not through PM decorations

### Where visual changes go
### Where To Put Your Change

| Change | Where |
|--------|-------|
| How something looks | `pm-adapter/` (data) + `painters/dom/` (rendering) |
| Style resolution | `style-engine/` |
| Editing behavior | `super-editor/src/editors/v1/extensions/` |
| Concern | Where | Rule |
|---------|-------|------|
| DOCX import/export | `super-editor/src/editors/v1/core/super-converter/` | Parse and preserve OOXML, style refs, and inline properties. Do not bake inherited or style-resolved formatting into direct attrs. |
| Style cascade | `layout-engine/style-engine/` | Own defaults, styles, conditional formatting, and inline override resolution. |
| Static document visuals | `pm-adapter/` data + `layout-engine/painters/dom/` rendering | Feed typed data into DomPainter. Do not style static document content with PM decorations. |
| Direction-aware properties | One layer makes the flip; pm-adapter stores logical sides LTR-default | For `w:bidiVisual` table-visual properties, DomPainter mirrors once at paint time. Pre-mirroring upstream is a double-swap. Full taxonomy: `pm-adapter/src/direction/README.md`. |
| Editing behavior | `super-editor/src/editors/v1/extensions/` | Commands, keybindings, editor plugins, and active interaction state live here. Do not duplicate cascade logic or render document visuals here. |
| Ephemeral editor UI | `PresentationEditor` post-paint pipeline + overlay components | Selections, active comments, proofing marks, search highlights, resize handles, and other overlays. PM decorations are bridged to painted DOM only for eligible plugins; comments use `CommentHighlightDecorator` (painter metadata), search is excluded from the bridge, resize handles are Vue overlay components. When adding a plugin that emits decorations, decide whether to bridge (default) or stay PM-side (add a prefix to `EXCLUDED_PLUGIN_KEY_PREFIXES` in `DecorationBridge.ts`). |
| Interaction mapping | `layout-engine/layout-bridge/` | Map clicks, selections, resize handles, and visual coordinates back to document positions. RTL-aware as an inverse mapping (visual → logical), not a pre-mirror. |
| Geometry and pagination | `layout-engine/layout-engine/` | Compute layout from `FlowBlock[]`; do not read rendered DOM to recover missing data. |
| Final DOM rendering | `layout-engine/painters/dom/` | Render `ResolvedLayout`. Paint-time visual transforms, such as the `w:bidiVisual` mirror, belong here. |
| Presentation state bridge | `PresentationEditor.ts` | Bridge editor events into layout and paint state. Do not resolve OOXML semantics here. |
| New document-API operation | `packages/document-api/src/contract/operation-definitions.ts` | Contract-first; touches 4 files — see `packages/document-api/README.md`. |
| New consumer SDK / CLI surface | `apps/cli/src/` + regenerate SDK | Run `pnpm run generate:all` after. |

**Do NOT** add ProseMirror decoration plugins for visual styling — DomPainter handles rendering.
### Quick Boundary Checks

Use these searches before adding a new visual or direction-aware path:

```bash
# Painter should not import upstream packages.
rg "@superdoc/(pm-adapter|style-engine|layout-bridge|layout-resolved)" packages/layout-engine/painters/dom/src

# pm-adapter should not do DOM work.
rg "getBoundingClientRect|clientWidth|offsetWidth|document\\.|window\\." packages/layout-engine/pm-adapter/src

# Import should not bake style cascade into direct attrs.
rg "referencedStyles|resolve.*Properties|resolve.*Style" packages/super-editor/src/editors/v1/core/super-converter
```

### State Communication

Expand Down Expand Up @@ -76,7 +105,7 @@ tests/visual/ Visual regression tests (Playwright + R2 baselines)

**The importer stores raw OOXML properties. The style-engine resolves them at render time.**

- The converter (`super-converter/`) should only parse and store what is explicitly in the XML (inline properties, style references). It must NOT resolve style cascades, conditional formatting, or inherited properties.
- The converter (`super-converter/`) should only parse and store what is explicitly in the XML (inline properties, style references). It must not resolve style cascades, conditional formatting, or inherited properties.
- The style-engine (`layout-engine/style-engine/`) is the single source of truth for cascade logic. All style resolution (defaults → table style → conditional formatting → inline overrides) happens here.
- Both rendering systems call the style-engine to compute final visual properties.

Expand All @@ -99,7 +128,7 @@ The `packages/document-api/` package uses a contract-first pattern with a single

Adding a new operation touches 4 files: `operation-definitions.ts`, `operation-registry.ts`, `invoke.ts` (dispatch table), and the implementation. See `packages/document-api/README.md` for the full guide.

Do NOT hand-edit `COMMAND_CATALOG`, `OPERATION_MEMBER_PATH_MAP`, `OPERATION_REFERENCE_DOC_PATH_MAP`, or `REFERENCE_OPERATION_GROUPS` — they are derived from `OPERATION_DEFINITIONS`.
Do not hand-edit `COMMAND_CATALOG`, `OPERATION_MEMBER_PATH_MAP`, `OPERATION_REFERENCE_DOC_PATH_MAP`, or `REFERENCE_OPERATION_GROUPS` — they are derived from `OPERATION_DEFINITIONS`.

## JSDoc types

Expand Down
39 changes: 25 additions & 14 deletions packages/layout-engine/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ ProseMirror Doc → pm-adapter → FlowBlock[] → layout-engine → Layout[]
| `style-engine/` | OOXML style resolution | `src/index.ts` |
| `geometry-utils/` | Math utilities for layout | `src/index.ts` |

## Key Insight: DomPainter is "Dumb"
## Key Insight: DomPainter Receives Paint-Ready Data

DomPainter receives a single paint-ready input — `ResolvedLayout` — and
renders the result to DOM. It does NOT do layout logic, measurement, or
PM-adapter conversion (that's upstream in `layout-engine/` /
`layout-resolved/` / `pm-adapter/`).
renders it to DOM. It does not do layout logic, measurement, or pm-adapter
conversion. Those decisions happen upstream in `layout-engine/`,
`layout-resolved/`, and `pm-adapter/`.

This is enforced as two hard invariants, not aspirational language:

Expand Down Expand Up @@ -74,7 +74,7 @@ When adding style resolution for a new property type (e.g., `tableCellProperties
3. Cascade using `combineProperties()` (low → high priority)
4. Inline properties always win last

See root CLAUDE.md "Style Resolution Boundary" for why this must NOT be done in the importer.
See root CLAUDE.md "Style Resolution Boundary" for why this must not be done in the importer.

## Important Patterns

Expand Down Expand Up @@ -138,12 +138,23 @@ Rendering logic for specific OOXML features is extracted into **feature modules*
- `layout-bridge/src/incrementalLayout.ts` - Layout orchestration (called by PresentationEditor)
- `pm-adapter/src/internal.ts` - PM → FlowBlock conversion

## Rendering Ownership

**DomPainter owns ALL visual rendering.** ProseMirror is hidden — its DOM is never shown to the user.

- Style-resolved properties flow through: `style-engine` → `pm-adapter` (sets attrs on FlowBlocks) → `DomPainter` (renders to DOM)
- Do NOT add ProseMirror decoration plugins for visual styling — that bypasses the rendering pipeline
- Editing behavior (commands, keybindings) stays in `super-editor/src/editors/v1/extensions/`

See root CLAUDE.md for full architecture.
## Layer Ownership

See root `CLAUDE.md` for the full placement map. This package owns the
layout and rendering pipeline.

- Style-resolved properties flow through `style-engine` → `pm-adapter` →
DomPainter.
- Static document visuals belong in layout data plus DomPainter rendering, not
ProseMirror decorations.
- Editing behavior, including commands and keybindings, stays in
`super-editor/src/editors/v1/extensions/`.
- `PresentationEditor` bridges editor state into layout and paint state. It
should not resolve OOXML semantics.
- Direction work keeps OOXML axes separate. `style-engine` resolves cascades,
`pm-adapter` writes typed direction/table attrs, and DomPainter owns
paint-time visual mirroring. For `w:bidiVisual`, upstream layers keep table
sides in LTR-default form and DomPainter mirrors once.

For the full direction taxonomy, see
`pm-adapter/src/direction/README.md`.
Loading