# Tiptap Editor

> Tiptap editor API patterns for vmark WYSIWYG development. Use when working with editor commands, node traversal, selection handling, or format operations.

- Skill: `xiaolai/tiptap-editor` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add xiaolai/tiptap-editor`
- Raw SKILL.md: https://api.skillmd.com/api/skills/xiaolai/tiptap-editor/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: xiaolai (https://skillmd.com/u/xiaolai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/xiaolai/tiptap-editor

---


# Tiptap Editor API Patterns

## Overview

This skill documents proper Tiptap API usage patterns for vmark development. It helps distinguish when to use Tiptap's high-level API vs direct ProseMirror access.

## When to Use Tiptap API

**Always prefer Tiptap API for:**
- Format commands (bold, italic, underline, etc.)
- Block type changes (heading, paragraph, code block)
- List operations (bullet, ordered, toggle, indent/outdent)
- Table operations via Tiptap table extension
- Content insertion and replacement
- Editor state queries (`isActive`, `getAttributes`)

**Tiptap patterns to use:**
```typescript
// Direct commands
editor.commands.toggleBold()
editor.commands.setHeading({ level: 2 })
editor.commands.setContent(doc, { emitUpdate: false })

// Chained commands (for multiple operations)
editor.chain().focus().setHeading({ level: 2 }).run()
editor.chain().focus().toggleMark("underline").run()

// State queries
editor.isActive("blockquote")
editor.isActive("heading", { level: 2 })
editor.getAttributes("link")
```

## When Direct ProseMirror is Appropriate

**Use ProseMirror directly for:**
- Markdown conversion layer (`proseMirrorToMdast.ts`, `mdastToProseMirror.ts`)
- Multi-cursor/selection subclassing (`MultiSelection.ts`)
- Custom node views
- Low-level transaction manipulation
- Schema-level operations

## Known Issues in vmark

### 1. cursorHandlers.ts Block Boundary Issue
`src/hooks/mcpBridge/cursorHandlers.ts` uses `doc.textContent` which flattens the document and loses block boundaries. The correct approach is to use `$pos` helpers:

```typescript
// WRONG - loses block structure
const text = doc.textContent;

// RIGHT - respects block boundaries
const $pos = doc.resolve(from);
const currentNode = $pos.parent;
const blockStart = $pos.before($pos.depth);
const blockEnd = $pos.after($pos.depth);
```

### 2. Cursor Sync Drift After WYSIWYG Edits
`sourceLine` attributes are only set on initial parse. After WYSIWYG edits that add/remove blocks, line numbers no longer match the source. This is a known limitation.

### 3. HtmlNodeView.ts Store Issue
`src/plugins/markdownArtifacts/HtmlNodeView.ts` writes cursor info to wrong store.

## References

- `references/patterns.md` - Detailed API patterns and $pos usage
- `references/examples.md` - Real code examples from vmark codebase

## Workflow

1. Identify operation type (format, block, selection, traversal)
2. Check if Tiptap has a built-in command for it
3. Use `editor.commands.xxx()` for single operations
4. Use `editor.chain().focus().xxx().run()` when focus is needed or chaining
5. For node traversal, use `doc.resolve(pos)` to get `$pos` helpers
6. For state queries, use `editor.isActive()` or `editor.getAttributes()`

