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
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,42 @@ jobs:
- name: Test
run: npm run test:once

browser-compatibility:
name: Browser compatibility
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 'lts/*'
cache: npm

- name: Install dependencies
run: npm ci

- name: Install Playwright browsers
run: npm exec -- playwright install --with-deps chromium firefox webkit

- name: Test browser compatibility
run: npm run test:browser

- name: Upload browser failure artifacts
if: ${{ failure() && !cancelled() }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: browser-compatibility-${{ github.run_attempt }}
path: |
playwright-report/
test-results/
if-no-files-found: ignore
retention-days: 14

build-js:
name: Build JS
runs-on: ubuntu-latest
Expand All @@ -58,9 +94,21 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Install package-gate browser
run: npm exec -- playwright install --with-deps chromium

- name: Build
run: npm run build

- name: Verify Lexical package graph
run: npm run test:lexical-versions

- name: Verify packed React consumers
run: npm run test:package

- name: Verify cross-version Markdown
run: npm run test:cross-version

build-docs:
name: Build Docs
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ tsdoc-metadata.json
.vscode
.env
.vs
playwright-report/
test-results/
blob-report/
173 changes: 173 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# AGENTS.md

This file provides guidance to coding agents working in this repository.

## Project Overview

MDXEditor is an open-source React component for rich-text markdown editing built on top of Lexical. It provides WYSIWYG markdown editing with support for MDX, tables, images, code blocks, JSX components, and more.

**Key technologies:**

- React 18/19 with TypeScript
- Lexical (Facebook's text editor framework)
- Gurx (reactive state management library)
- Vite (build tool)
- Vitest (testing)
- Ladle (component development)
- MDAST (markdown abstract syntax tree)

## Development Commands

**Build and development:**

```bash
npm run build # Build the library with Vite
npm run dev # Start Ladle dev server (component explorer)
npm start # Alias for npm run dev
```

**Code quality:**

```bash
npm run typecheck # Run TypeScript type checking (no emit)
npm run lint # Lint source files with ESLint
```

**Testing:**

```bash
npm test # Run Vitest in watch mode
npm run test:once # Run Vitest once (CI mode)
npm run test:compat # Run focused jsdom and three-browser compatibility tests
npm run test:browser # Run the complete Playwright browser suite
npm run test:package # Verify packed React 18 and React 19 consumers
npm run test:cross-version # Verify Markdown replay through the legacy package
npm run test:lexical-versions # Assert the installed Lexical graph is lockstep
```

**Tests are located in:** `src/test/**/*.test.{ts,tsx}`

### Browser Tests in Codex on macOS

When running under Codex's managed sandbox on macOS, request elevated execution before the first invocation of any command that launches a native browser. Do not attempt it sandboxed first.

This includes:

- `npm run test:browser`
- `npm run test:package`
- `npm run test:cross-version`
- direct Playwright commands
- any other command launching Chromium, Firefox, or WebKit

Sandboxed browser launches can fail during macOS Mach-port or GUI application registration, producing OS crash reports with signatures such as:

- `MachPortRendezvousServer`
- `bootstrap_check_in ... Permission denied (1100)`
- `_RegisterApplication`
- `SIGTRAP` or startup-time `SIGABRT`

These failures are environmental only when they occur before page creation. Assertion failures, page errors, or crashes after navigation are genuine test failures and must not be automatically retried as environmental failures.

Keep lint, typecheck, Vitest, builds, and other non-browser commands sandboxed. After browser tests, verify that owned servers and port `61000` were cleaned up.

**Other utilities:**

```bash
npm run build:docs:api # Generate API docs with typedoc
npm run image-upload-backend # Start example file upload server on port 65432
npm run export-icons # Export icons from Figma
```

## Architecture

### Plugin System

The editor is built around a **plugin architecture** using Gurx (reactive state management):

- **RealmPlugin**: Core plugin interface with `init`, `postInit`, and `update` lifecycle methods
- **realmPlugin()**: Factory function to create plugins that accept parameters
- Plugins are initialized in `RealmWithPlugins` component which creates a Gurx Realm

**Plugin structure:**

```typescript
export const myPlugin = realmPlugin({
init: (realm, params) => {
/* register nodes, visitors, cells */
},
postInit: (realm, params) => {
/* access other plugins' state */
},
update: (realm, params) => {
/* handle prop updates */
}
})
```

### State Management with Gurx

Gurx primitives are exported with `$` suffix:

- **Cells** (state): `markdown$`, `rootEditor$`, `activeEditor$`, `readOnly$`, etc.
- **Signals** (actions): `insertMarkdown$`, `setMarkdown$`, `applyBlockType$`, etc.

Use React hooks to interact with Gurx:

```typescript
const [markdown, rootEditor] = useCellValues(markdown$, rootEditor$)
const applyBlockType = usePublisher(applyBlockType$)
```

### Lexical Integration

- **Root editor** (`rootEditor$`): Main Lexical editor instance
- **Active editor** (`activeEditor$`): Can be root or nested editor (e.g., code blocks)
- **Custom Lexical nodes**: Each feature typically registers custom LexicalNode subclasses
- **Dollar-prefixed functions** (e.g., `$isCodeBlockNode`): Follow Lexical conventions, used within editor read/update cycles

### Markdown <-> Lexical Conversion

The editor maintains bidirectional conversion between markdown and Lexical's internal state:

**Import (Markdown → Lexical):**

- `importMarkdownToLexical.ts`: Parses markdown to MDAST, then converts to Lexical nodes
- **MdastImportVisitor**: Interface for converting MDAST nodes to Lexical nodes
- Uses micromark + mdast-util libraries for parsing

**Export (Lexical → Markdown):**

- `exportMarkdownFromLexical.ts`: Converts Lexical nodes to MDAST, then serializes to markdown
- **LexicalExportVisitor**: Interface for converting Lexical nodes to MDAST
- Uses mdast-util-to-markdown for serialization

Each plugin typically registers both import and export visitors for its node types.

### Key Source Directories

- **src/plugins/**: Plugin implementations (core, toolbar, headings, lists, table, image, codeblock, jsx, directives, etc.)
- **src/plugins/core/**: Core plugin with fundamental functionality, state management, and base visitors
- **src/plugins/toolbar/**: Toolbar UI components and primitives
- **src/examples/**: Ladle stories (component examples/demos)
- **src/jsx-editors/**: Editors for JSX components
- **src/directive-editors/**: Editors for directives (e.g., admonitions)
- **src/styles/**: CSS modules and theming
- **src/utils/**: Utility functions
- **src/test/**: Test files

### Build Configuration

- **Vite**: Library mode with preserveModules for tree-shaking
- **CSS Modules**: Scoped with camelCaseOnly convention
- **TypeScript**: Strict mode, path alias `@/*` → `src/*`
- **SVG**: Loaded as React components via vite-plugin-svgr (replaces black with currentColor)
- **External packages**: All dependencies and peerDependencies are externalized in the build

## Conventions

- Path alias: Use `@/` for imports from `src/` directory
- CSS Modules: Use camelCase for class names
- Gurx exports: Suffix with `$` (e.g., `markdown$`, `applyBlockType$`)
- Lexical functions: Prefix with `$` for functions used in editor read/update cycles (e.g., `$isCodeBlockNode`)
- Plugin nodes: Custom Lexical nodes typically stored in plugin directories
- Visitors: Separate files for MDAST and Lexical visitors per feature
129 changes: 0 additions & 129 deletions CLAUDE.md

This file was deleted.

1 change: 1 addition & 0 deletions CLAUDE.md
Loading