Skip to content

feat: add Yjs-based rich markdown file type to yjsfs - #19

Open
K-Mistele wants to merge 18 commits into
mainfrom
rich-markdown
Open

feat: add Yjs-based rich markdown file type to yjsfs#19
K-Mistele wants to merge 18 commits into
mainfrom
rich-markdown

Conversation

@K-Mistele

@K-Mistele K-Mistele commented May 23, 2026

Copy link
Copy Markdown
Contributor

PR Description

Ticket | Artifacts | Task

What problems was I solving

This PR enables AI agents to collaboratively edit rich markdown documents using TipTap/Yjs. Previously, agents could only work with plain text files via string-based edits. Now agents can:

  • Edit rich text documents with proper structural awareness (headings, lists, code blocks, etc.)
  • Make semantic edits using a sandboxed QuickJS runtime that mutates the Yjs document directly
  • Collaborate with humans in real-time via Durable Streams with proper presence/cursor isolation

Success is measured by: agents successfully editing rich artifacts without corrupting document structure, and multiple TipTap editors syncing correctly via one Yjs document.

What user-facing changes did I ship

How I implemented it

Core Package: packages/yjs-rich-markdown

  • src/artifact-store.ts - Path-keyed artifact registry backed by Y.Map<ArtifactMetadata> in one Y.Doc
  • src/yxml-proxy.ts - YXmlProxyBindings providing a DSL for agents to navigate and mutate YXml trees (root, children, get, append, prepend, insertBefore, insertAfter, insertText, deleteText, splitText, wrapTextRange, wrap, remove, setAttribute, etc.)
  • src/yxml-proxy-prompt.ts - YXML_PROXY_AGENT_PROMPT with console.log diagnostic guidance and the full bindings DSL contract
  • src/markdown.ts - Markdown bridge via TipTap's MarkdownManager for readArtifactMarkdown/writeArtifactMarkdown
  • src/tiptap-bindings.ts - TipTap collaboration binding helpers for both fragment and field modes

Agent Tools

CLI Agent

  • src/cli/rich-markdown-agent.ts - Full CLI agent with list_artifacts, read_artifact, write_artifact, edit, and inspect_artifact tools, connects to Durable Streams, supports --interactive, --prompt, --verbose, --stream flags

QuickJS Sandbox: packages/quickjs-exec

  • src/index.ts - QuickJsSyncExec and QuickJsAsyncExec with runWithConsole API, structured error capture via QuickJsExecutionError with code frame rendering

Demo App: examples/yjs-rich-markdown-demo

  • TanStack Router React app with routes: /, /single, /compare, /same-doc, /awareness
  • Local Durable Streams server at server/index.ts
  • TipTap v3 (3.22.5) with collaboration extensions

Deviations from the plan

Implemented as planned

  • Console capture via runWithConsole with globalThis.__quickjsConsoleLogs pattern
  • YXmlProxyBindings full binding set matching the DSL specification
  • YXML_PROXY_AGENT_PROMPT with diagnostic guidance
  • Apply sub-agent architecture with QuickJS code-mode tool
  • RichMarkdownArtifactStore path-keyed registry
  • Markdown bridge via MarkdownManager
  • CLI agent with full tool set
  • Demo app with TanStack Router
  • yjs-fs kept unaware of rich artifacts per design decision

Deviations/surprises

  • QuickJsRunWithConsoleResult is a discriminated union: Plan specified { value, console } but implementation uses { ok: true, value, console } | { ok: false, error, console } - an improvement that bakes in structured error capture
  • Errors return structured result instead of rethrowing: Callers get { ok: false, error, console } instead of thrown exceptions
  • QuickJsExecutionError with code frame added: Not in plan; adds source code context rendering for debugging generated code failures
  • TipTap v3 used instead of v2: Upgraded to 3.22.5 for @tiptap/markdown support
  • Demo uses @tiptap/extension-collaboration-caret: The TipTap v3 rename of @tiptap/extension-collaboration-cursor

Additions not in plan

  • QuickJsExecutionError class with codeFrame source context rendering
  • readArtifact for raw XML output (supports inspect_artifact tool)
  • test/yxml-proxy-prompt-examples.test.ts - validates prompt examples are executable
  • test/yxml-invalid-structures.test.ts - documents lossy Markdown serialization
  • done and i_am_stuck tools in apply sub-agent
  • awaitSubAgent integration for streaming sub-agent output

Items planned but not implemented

  • Phases 8-11 (comment mark anchoring, durable comment threads, yjs-fs integration direction) - deferred
  • /comments demo route - not created
  • Durable Streams connected integration tests - require live server infrastructure
  • awareness-scope.test.ts, durable-streams-sync.test.ts - test files not created (functionality verified via demo app)

How to verify it

Manual Testing

# Set up worktree
git worktree add -b rich-markdown ~/wt/agentlayer/rich-markdown origin/rich-markdown
cd ~/wt/agentlayer/rich-markdown
bun install

# Run demo app
cd examples/yjs-rich-markdown-demo
bun run dev
# Open http://localhost:5173 in browser
# Test routes: /, /single, /compare, /same-doc, /awareness
  • Create/edit artifacts in demo app
  • Open multiple browser tabs and verify real-time sync
  • Verify cursor presence isolation across different artifacts
  • Test markdown export panel

CLI Agent Testing

cd packages/yjs-rich-markdown
export ANTHROPIC_API_KEY=your_key
bun run agent --interactive
  • Use list_artifacts to see artifacts
  • Use read_artifact to read content
  • Use edit to make semantic edits
  • Verify edits sync to demo app

Automated Tests

bun run typecheck
bun run test

Description for the changelog

Add Yjs-based rich markdown file type with TipTap collaboration, sandboxed QuickJS agent editing via YXml proxy bindings, and CLI agent for rich artifact management.

K-Mistele and others added 18 commits May 1, 2026 22:07
Adds a new package for rich markdown artifacts using TipTap/Yjs:

- `packages/yjs-rich-markdown`: Core library with artifact store,
  path-keyed registry, and TipTap collaboration bindings
- `examples/yjs-rich-markdown-demo`: TanStack Router demo app for
  testing collaboration, presence, and content isolation

Learning tests confirm:
- Multiple TipTap fragments in one Y.Doc remain isolated
- One Durable Streams connection syncs all artifacts
- Cursor presence is natively scoped per fragment (no adapter needed)
- Both `fragment` and `field` binding modes work

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant