Open KnowledgeOpen Knowledge
Internals

Validation Results

Summary of the V1--V7 spike validations that proved the foundation architecture.

Historical record

These validation results are from the init spike (March 2026). They confirmed the foundation architecture decisions that the project is built on. The codebase has evolved since -- see Architecture and Service Topology for current state.

Seven targeted validations (V1--V7) were run during the init spike, plus three addendum validations (A1--A3) for the agent write path and three-way merge.

Summary

ValidationResultKey finding
V1aGround truth3 semantic losses without fixes: frontmatter, images, task checkboxes
V1bPASSZero semantic loss after ~80 LOC fixes. Convergence confirmed.
V2PASSHocuspocus embeds in Vite via standalone WebSocketServer
V3PASSDirectConnection agent writes via HTTP API
V4PASS (V4b)Serialize-on-toggle via updateYFragment (not prosemirrorJSONToYDoc)
V5PASSThree-tier pipeline: CRDT to markdown to git plumbing
V6PASSVoid node renders React component, survives markdown round-trip
V7FAILYjs v14 delta protocol not viable -- ecosystem pins to v13
A1PASSAgent markdown write path + source mode live injection
A2PASSThree-way merge on toggle-back preserves agent writes
A3PASSCombined A1+A2: agent writes visible in source, preserved on toggle-back

V1a: Markdown round-trip (raw)

Baseline measurement with no fixes applied. Total line differences: 27 from 1292 byte input. No convergence -- frontmatter corruption cascades each cycle.

Three semantic losses identified:

  • Frontmatter -- --- interpreted as horizontal rule, title: becomes H2 via setext heading
  • Images -- ![alt](url) collapses to plain alt text
  • Task list checkboxes -- - [x] becomes - (checkbox stripped)

All other patterns (headings, inline formatting, links, fenced code, GFM tables, blockquotes, ordered/nested lists) preserved.

V1b: Markdown round-trip (with fixes)

All 14 patterns preserved. Convergence confirmed: cycle 2 output byte-identical to cycle 1. 54 line differences from original -- all cosmetic (blank line positioning, table column padding).

Fixes applied (~80 LOC total):

  1. Frontmatter strip/prepend -- regex strip before parse, re-prepend after serialize
  2. Image extension -- @tiptap/extension-image built-in markdown support
  3. Task lists -- TaskList + TaskItem from @tiptap/extension-list
  4. JsxComponent extension -- custom parseMarkdown intercepts code tokens with lang === 'jsx-component'

V2: Hocuspocus in Vite

Hocuspocus embedded via Vite configureServer() plugin hook. Standalone ws.WebSocketServer({ noServer: true }) intercepts upgrades on /collab. Hocuspocus handleConnection(ws, req) called without listen(). Vite HMR continues working on its own WebSocket with no conflict.

V3: DirectConnection writes

HTTP API (POST /api/agent-write) uses hocuspocus.openDirectConnection() to write into the CRDT. Node structure matches y-prosemirror conventions: Y.XmlFragment -> Y.XmlElement('paragraph') -> Y.XmlText with applyDelta(). CLI simulator supports single writes and rapid bursts.

V4: Source toggle (V4b -- serialize-on-toggle)

After V7's failure, V4b was the expected fallback. Toggle to source serializes via MarkdownManager. Toggle back parses and applies via updateYFragment() (diff-based). Critical constraint: never use prosemirrorJSONToYDoc() which destroys collaboration state. Frontmatter preserved via ref across toggle cycles. CodeMirror 6 with basicSetup + @codemirror/lang-markdown.

V5: Git auto-persistence pipeline

Hocuspocus onStoreDocument extension with two layers:

  • Layer 1 (CRDT to disk): yXmlFragmentToProsemirrorJSON() -> MarkdownManager.serialize() -> writeFileSync(), 2s quiet / 10s max debounce
  • Layer 2 (disk to git): simple-git plumbing commands (git add -> write-tree -> commit-tree -> update-ref refs/wip/main), 30s debounce

Server-side serialization is pure Yjs/JSON -- no DOM, no schema needed.

V6: Void node with React component preview

TipTap node extension with atom: true, group: 'block'. Intercepts code tokens with lang === 'jsx-component' at priority 60 (above codeBlock's 50). ReactNodeViewRenderer renders the component preview. The raw JSX string survives the round-trip unchanged. Known components (e.g., Callout) get visual styling; unknown components get a code display.

V7: Yjs v14 delta protocol

Result: FAIL -- confirming the expected fallback to V4b.

  • yjs@14.0.0-16 installs but y-prosemirror@2.0.0-2 does not exist on npm
  • Yjs v14 does not have unified YType -- XmlFragment and Text remain separate classes
  • toDeltaDeep() does not exist; applyDelta() exists but toDelta() does not
  • Dual Yjs import (v13 from y-prosemirror + v14 from root) triggers constructor check errors
  • The ecosystem (y-protocols, Hocuspocus, @tiptap/y-tiptap) all pin to yjs@^13

The foundation remains sound with Yjs v13.

A1: Agent markdown write path

POST /api/agent-write-md accepts { markdown, position? } and applies via serialize, splice, parse, updateYFragment. This unifies agent writes with the toggle-back code path -- both go through markdown parse then updateYFragment. The agent-sim.ts --markdown flag uses the new endpoint.

Source mode live injection: when source mode is active, a Y.XmlFragment observer fires on agent writes. The serialized markdown is pushed to CodeMirror via React state. The observer is unsubscribed before toggle-back to avoid interference with the three-way merge.

A2: Three-way merge on toggle-back

three-way-merge.ts implements three-way merge for source mode toggle-back. Three versions are compared: the snapshot (when entering source mode), the user's edits, and the current Y.Doc. Agent-added paragraphs (beyond the snapshot's block count) are appended to the user's edits. Conflicts (both user and agent modified the same paragraph) are resolved with user-wins and logged to the console.

A3: Combined (A1 + A2)

Combined test: source mode active, agent writes paragraph C via markdown path, it appears in Y.Doc and CodeMirror, user edits paragraph A in source, toggle back, three-way merge preserves both. User edit to paragraph A present, untouched paragraph B present, agent's paragraph C present. Both approaches work independently and together.