从“想做一个 Craft”到 ArkBlocks:一次 AI 协作开发原生 Block Editor 的心路历程

从“想做一个 Craft”到 ArkBlocks:一次 AI 协作开发原生 Block Editor 的心路历程

最开始,我只是想做一个类似 Craft.do 的笔记编辑器。

准确一点说,我想在 HarmonyOS NEXT 手机上,用 ArkTS 原生开发一个真正好用的 Block Editor。不是 WebView,不是简单套一个网页编辑器,也不是把 React 生态里的东西硬搬到鸿蒙上,而是尽量用 HarmonyOS 自己的能力,做一个适合手机端使用的原生编辑器。

刚开始的时候,这个目标听起来其实很大,也很模糊。

Craft 很好看,交互也很舒服。它的文档、卡片、折叠、拖拽、内联样式、图片排版都很迷人。但如果真的要自己做,问题马上就来了:

一个 Block Editor 到底应该怎么设计?

它的 Document Model 是什么?

Block 和 Inline 应该怎么组织?

Undo / Redo 怎么做?

拖拽怎么做?

图片、卡片、Callout 这些能力,是写死在编辑器里,还是做成插件?

AI 能不能帮我把现有的开源编辑器,比如 BlockNote,迁移到 ArkTS?

这就是整个 ArkBlocks 项目的起点。


第一阶段:从“移植 BlockNote”到“理解编辑器”

一开始,我的想法很直接:BlockNote 是开源的,能不能让 AI 去读它的源码,然后把它迁移到 ArkTS?

这个想法很自然,但后来我们慢慢意识到,直接“迁移”不是最好的路。

BlockNote 的实现依赖 React、Tiptap、ProseMirror、DOM、Selection API、contenteditable 等一整套 Web 体系。HarmonyOS NEXT 是另一套世界。ArkUI 有自己的声明式 UI,RichEditor 有自己的输入机制,鸿蒙的手势、键盘、剪贴板、生命周期也都和浏览器不一样。

所以我们逐渐把目标从:

迁移 BlockNote

调整成:

学习 BlockNote 的架构思想,然后为 HarmonyOS 重新设计一个原生 Block Editor Framework。

这一步非常关键。

它让项目不再是“翻译代码”,而是进入了“抽象设计”的阶段。

我们先让 AI 阅读 BlockNote,输出了几份研究文档:

  • Editor Architecture
  • Document Model
  • Editor Design Principles

这些文档不是为了抄代码,而是为了回答几个根本问题:

  • 为什么现代编辑器要用 Block?
  • Document、Block、Inline、Style、Selection 之间是什么关系?
  • 哪些是平台无关的核心能力?
  • 哪些是浏览器环境下的实现细节?
  • 如果换成 ArkTS,哪些东西应该保留,哪些东西应该重新设计?

这一步之后,我开始意识到:真正值得做的不是一个“鸿蒙版 Craft”,而是一个 HarmonyOS 原生 Block Editor Framework。

后来这个项目就有了名字:

ArkBlocks


第二阶段:先做架构,而不是急着写代码

很多 AI Coding 项目容易犯一个错误:刚想清楚一点方向,就立刻让 AI 写代码。

我们这次反而刻意踩了刹车。

先建立了 AGENTS.md,让所有 AI 知道这个项目是什么,不是什么。ArkBlocks 不是一个普通笔记 App,而是一个可复用的编辑器框架。

然后建立了 docs/research、docs/design、docs/rfc、docs/spec、docs/tests 等文档体系。

当时我们做了几件现在回头看非常有价值的事情:

1. 架构设计

我们让 AI 设计了 ArkBlocks 的总体架构,明确了:

  • Document
  • Block
  • Inline
  • Selection
  • Transaction
  • History
  • Command
  • Renderer
  • Adapter
  • Runtime

这些模块之间的关系。

2. RFC

我们建立了 RFC-0001:Document Canonical Model。

这份 RFC 冻结了整个项目最重要的决策:

Document Block Tree 是 ArkBlocks 的唯一真相来源。

也就是说:

  • Renderer 不拥有文档状态
  • Selection 不复制文档内容
  • History 不直接持有活动 Block 对象
  • 所有修改都必须通过 Transaction
  • Document Tree 是整个编辑器的核心数据结构

3. SPEC

后来我们又做了 SPEC-0001:Document Operations Specification。

这份文档详细定义了:

  • InsertBlock
  • DeleteBlock
  • MoveBlock
  • DuplicateBlock
  • SplitBlock
  • MergeBlock
  • ReplaceBlock
  • InsertInline
  • DeleteInline
  • UpdateProps
  • Indent
  • Outdent

每一个操作都要说明前置条件、输入输出、状态变化、Selection 行为、History 行为、失败条件和复杂度。

4. SPEC Test

之后我们又写了 SPEC-0001-Test-Suite,把行为规范转成测试用例。

这一步很重要。因为它让项目不只是“文档说得漂亮”,而是开始有了可验证的契约。

从现在看,这套流程非常像大型框架项目:

Research ↓ Architecture ↓ RFC ↓ SPEC ↓ SPEC Test ↓ Implementation ↓ Code Review

这和普通的“让 AI 写功能”完全不同。


第三阶段:从骨架工程到真正可运行

有了架构之后,我们开始让 AI 搭建 Framework Skeleton。

这个阶段不是为了实现功能,而是为了把架构变成真实的代码结构。

AI 创建了大量模块:

  • core/types
  • schema
  • document
  • transaction
  • history
  • command
  • renderer
  • adapter
  • plugin
  • editor

一开始这些模块很多只是接口、空实现、TODO,但它们的意义很大:

AI 后面写代码时,不再凭空发明架构,而是在既有框架里补实现。

之后我们开始真正实现 Document Index System,也就是 PR-0001。

这个 PR 解决了一个非常关键的问题:

Block 查找必须是 O(1)。

如果每次编辑都递归遍历整个 Block Tree,编辑器以后一定会在大文档里崩掉。所以 Document 维护了:

  • blockIndex
  • parentIndex

这一步看起来很底层,但它奠定了整个编辑器的性能基础。

PR-0001 之后我们还做了 Code Review,发现了 Transaction 原子性、索引外部可变等问题,于是又做了 PR-0001A 修复。