For AI agents: the complete documentation index is available at https://a3s-lab.github.io/Office/docs/0.307.0/llms.txt, the full documentation bundle is available at https://a3s-lab.github.io/Office/docs/0.307.0/llms-full.txt, and this page is available as Markdown at https://a3s-lab.github.io/Office/docs/0.307.0/browser-editor-architecture.md.

浏览器编辑器架构

A3S Office 是一套可以嵌入业务应用的完整编辑器组件。它在产品边界采用 Headless 设计:组件库负责编辑模型、命令、渲染和编辑器界面;宿主负责文件保存、身份权限、 协作、应用导航和 AI 服务。

总体结构

宿主应用
├── 文件、权限、协作、版本与 AI
└── A3S Office 公共接口
    ├── React / Vue / Web Component
    ├── 受控内容模型与宿主事件
    ├── 编辑器界面
    │   ├── Document:TipTap / ProseMirror
    │   ├── Markdown:源码 + TipTap
    │   ├── Spreadsheet:工作簿与计算模型
    │   ├── Presentation:场景图 + 按需文本编辑
    │   └── PDF:PDFium 渲染与批注
    └── 计算与文件能力
        ├── Web Worker
        ├── Rust / WebAssembly 内核
        └── DOCX、XLSX、PPTX、Markdown 与 PDF 导入导出

这条边界保证同一编辑器可以进入不同产品,而不把某一种后端、账号系统或 AI 模型 写死在组件中。

单一内容来源

每种编辑器都保留与文件格式和交互方式匹配的主模型,而不是把所有格式压成一份 HTML。

编辑器主模型UI 编辑层
Document带版本的文档树、分节、分页与页面元素TipTap / ProseMirror
MarkdownMarkdown 字符串源码编辑与 TipTap 可视化视图
Spreadsheet工作簿、工作表、单元格、公式与样式表格网格与 A3S 计算控制器
Presentation幻灯片场景图、母版、版式与对象画布交互,文本编辑时挂载 TipTap
PDFPDF 二进制与增量批注状态PDFium 页面与批注控制器

HTML 只在兼容边界使用,不是所有 Office 文件的持久化真相。这样才能保留分页、公式、 母版、批注和对象几何等格式语义。

Markdown 在桌面和平板端使用可调整的源码与预览分栏,预览正文限制为适合阅读的行宽。 手机端不会把两块内容堆叠成上下半屏,而是通过“源码 / 预览”切换让当前界面独占工作区; 这只是显示方式变化,不会创建第二份内容状态。

Spreadsheet 原生 Table/ListObject 也属于这份受控工作簿模型,不是单元格颜色旁路。创建、 上下文“表格设计”、行列结构协调、转换为区域、Yjs 协作和 XLSX 往返共同读取同一组稳定 ID、区域、列、筛选和样式字段。普通表格外观只在 Fortune 的可见单元格 Canvas 回调中解析, 条件格式仍是更高优先级的显示层;只有“转换为区域”会在有界矩阵或稀疏 celldata 路径中 把已确认外观物化为原生单元格格式。

表格公式现在还可以通过同一份受控模型解析有界的结构化引用。Rust/WASM 与 JavaScript fallback 共用表格目录和依赖图,支持表名或 displayName、工作表限定、连续列区域、 #All#Headers#Data#Totals#This Row 以及表内 [@Column] 当前行公式。 每个结构化区域最多物化 100,000 个单元格,每次请求最多 1,024 张表;整行/整列、三维、 外部工作簿、不连续或超过预算的引用会以单元格级诊断失败。一致的当前行公式会持久化为 表格元数据,并只为新插入的正文空单元格填充;手工值和冲突公式会安全保留。稠密 data 与稀疏 celldata 使用同一条路径,原生 XLSX <calculatedColumnFormula> 也会往返。原生 汇总行创作现在复用同一模型:表格设计功能区管理汇总行,并为每列保存标签、原生汇总函数或 自定义公式;原生函数映射到会忽略筛选行的 SUBTOTAL 编号;直接编辑、行列结构变化和表格 重命名都会协调已生成的单元格;totalsRowFunctiontotalsRowLabeltotalsRowFormula 可通过 XLSX 和 Yjs 往返。已有内容、合并、保护、重叠、AutoFilter、危险、外部或超预算目标 会失败关闭。切片器和查询表仍属于后续边界。

基于值的多关键字“自定义排序”现在由独立的 spreadsheetSort 编辑器扩展负责,不再混入行列 结构扩展。命令冻结一个连续选区,推断可覆盖的标题行,并在复制和稳定排序完整单元格行之前校验 最多 64 个互不重复的关键字。嵌套的传统 Office 兼容“排序选项”弹窗可选择以列关键字从上到下移动 完整行,或以行关键字从左到右移动完整列;后者因所有选定列都会移动而禁用标题保留。同一份规范化 选项还可选择简体中文拼音或笔画次序及是否区分大小写。每个关键字 是升序/降序值次序、显式自定义序列,或带轴向首尾位置的有效单元格颜色、字体颜色、条件格式 图标目标的封闭联合; spreadsheet-sort-custom-list 模型统一负责规范化、重复与大小守卫、七个不可变月份/星期序列和 序位匹配。通过校验的请求会复制序列项目,因此命令执行不依赖可变 UI 状态。浏览器适配层持有 有界用户序列注册表,并且不会把偏好写入受控工作簿。专用管理器以稳定序列身份暂存创建、编辑、 删除和调整顺序操作,再发布一份规范偏好集合;内置序列保持只读,无改动确认会跳过存储边界, 活动关键字会接收已编辑的替换序列,而已删除序列对应的关键字会回到值升序。未配置存储时, 注册表只属于当前挂载会话。 可选的类型化 SpreadsheetSortCustomListStore 通过宿主选择的本地后端同步加载和保存规范序列。 内置 LocalStorageSpreadsheetSortCustomListStore 接收显式 Storage 对象,使用带版本的键,并将 读取结果校验、去重和限制为 32 条。损坏读取会失败关闭;写入被拒时,完整的已修改偏好集合会 带提示地降级为当前会话序列。已保存序列优先于请求携带的同内容会话副本,因此持久身份保持稳定。独立的 spreadsheet-sort-appearance 模型会为直接原生样式与已计算条件格式输出建立快照,保留无填充 与自动字体颜色身份,并拒绝把原生图案或渐变压平为一个颜色。命令在执行前从受控工作表状态与 实时区域重新构建该快照,再校验矩阵形状与目标身份。一个方向中立的矩阵引擎只转置有界项目 视图;spreadsheet-sort-collation 边界为每个请求只编译一个本地 Intl.Collator,让数字文字保持 字符顺序,并以实际排序行为探测所选次序,而不是依赖各运行环境不同的已解析标签;运行环境确实不支持时才失败关闭。两种方向共用同一稳定比较器。相对公式引用会沿移动的行轴或列轴复用“选择性粘贴”的 有界引用平移引擎,绝对引用保持不变。未启用筛选的一次成功操作只通过一个原生区域写入跨越 Fortune 边界,并形成一条受控撤销记录。独立的结构所有者规划器可从任意内部选区识别唯一原生 表格或工作表 AutoFilter 的精确可排序区域,排除表格汇总行,固定结构标题,锁定按列排序,并携带 所有者类型及适用时的表格 ID。命令会在应用时重新推导该指纹;所有权变化会在分配或修改前失败。

筛选活动的所属区域排序改用一次受控工作簿替换。矩阵引擎会随已平移单元格返回稳定来源行置换; spreadsheet-filter-reconciliation 针对已排序的稠密/稀疏工作表重算类型化工作表和表格条件,把 Fortune 不透明值/颜色筛选的 rowhidden 所有权按该置换迁移,再保留独立手工隐藏行并重建合并 隐藏映射。Fortune 区域读取遗漏隐藏行时会回退到有界受控快照。排序区域与筛选重扫分别具有明确的 一百万单元格预算,无关筛选不会让普通区域偏离原生路径。

无法解析为唯一精确结构所有者的部分相交,以及超链接映射、导入公式元数据和边框侧车相交,仍会 失败关闭,因为这些侧车在单元格矩阵之外拥有坐标。大型排序、大型聚合/排名扫描、其余高级筛选 条件及结构侧车协调迁移到 Worker/WASM 内核仍属于后续阶段工作。 宿主直接聚焦原生网格时,现在也会进入功能区命令所用的同一套有界焦点观察器。若受控工作簿更新 替换了已聚焦的 Fortune overlay,焦点会交接给已连接的新节点;主动指针、Tab 或文字编辑焦点移动 仍会取消恢复。

工作表 AutoFilter 条件现在由独立的 A3S 自有模型和类型化 spreadsheetAutoFilter 命令扩展负责。 每个 Fortune 筛选列中的带版本条件载荷只作为网格投影;命令会在修改前重新认证受控工作表、 绝对表头列、精确筛选区域与可编辑状态。纯求值器读取稠密或稀疏单元格,为每列分别生成隐藏行集合; 这些集合的并集驱动 Fortune 行可见性,而明确的重叠标记会在替换或删除条件时分离手工隐藏行。 因此多列所有权有唯一事实来源,无需从合并后的 config.rowhidden 映射猜测来源。

同列自定义条件在封闭筛选联合中使用独立的固定双元素结构。每项都是非递归单值条件,条件对只有 一个明确的 AND/OR 判别值。求值器只组合两个匹配器;协作校验会拒绝嵌套或长度不同的条件数组; 弹窗不会把第二个条件附加到区间、空白、前几项/后几项或动态条件。

通配符表达式是另外两个非递归自定义条件类型,因此现有等于和其他文本操作数继续保持字面量语义。 纯编译器会为每个条件把 *?~ 解析一次,把字面片段和单元格文本用 NFKC 及既有的 zh-CN 小写折叠规范化,再对 token 序列求值,不执行或插值用户文本。弹窗、模型、命令和 协作边界共用 32,767 字符的操作数上限。纯字面片段使用有序子串搜索;超长定宽候选扫描会切换到 有界 bitset 匹配器,避免对晚期失配的文本做平方级重复扫描。

前几项/后几项条件在逐单元格匹配器之前走独立聚合路径。求值器从筛选列收集有限数值,把百分比 换算为 ceil(n * percent / 100) 项,按所选方向排序,并以边界值作为比较阈值。所有等于边界的值 都会保留;文本、布尔值、空白和非有限值不进入排名域。稠密与稀疏单元格读取器共用该路径,生成 的隐藏行集合仍由一个列条件独立拥有。

动态条件使用独立纯编译器,并接收明确的本地时钟上下文。高于/低于平均值会以可迭代流消费该列, 只计算一个有限数值平均值,再返回严格匹配器,不会保留整列。日期家族会编译一个包含起点且不包含 终点的序列区间,不会预读该列。它接受已导入的 Date 对象或明确标记为 ct.t='d' 的有限数值 单元格,可跨时区恢复 SheetJS 本地日期序列,拒绝虚构的序列 60,也绝不把未格式化数字识别为日期。 相对周以周日开始;日历月份和季度家族不受年份限制。每份不可变工作表会缓存列类型画像,并把 类型化日期排除在数值排名入口之外。编辑器使用当前本地时钟,模型测试则注入确定性时钟。受控模型 持有唯一的工作簿 dateSystem;字段省略表示 1900,导入的 1904 工作簿则保留显式备用 epoch。 数值日期序列会原样通过导入、筛选、受控重挂载、协作、导出与重开,包括合法的 1904 序列 0。

同一个封闭 WorkSpreadsheetFilterCriteria 联合类型现在同时服务工作表和表格原生 OOXML。包扫描与 导入只读取工作表根节点直属的 <autoFilter>,填充受支持条件,并保留尚不支持求值的已导入类型, 而不会伪装成已经执行。导出会用原生 <filterColumn> 子节点修补生成的工作表元素。通用双元素 <customFilters and="1|0">、任意正向/否定通配符表达式、否定前缀/后缀形式以及 <top10 top="..." percent="..." val="..."/> 条件会通过共享的工作表/表格路径往返。已导入原生 动态筛选会根据当前单元格重新计算,并输出为原生 <dynamicFilter> 元素。规范的边缘星号模式仍会 归一化为包含、开头是或结尾是类型。React 适配层 只负责发现供应商菜单、可访问条件弹窗、短生命周期选区保留和焦点恢复;会绕过类型化排序与结构 守卫的供应商排序项不会对外显示。

部分区域规划是独立的受控职责。共享 spreadsheet-current-region 模型会读取稠密 data 与 稀疏 celldata,但不会物化工作表维度,因此 AutoFilter 与 Sort 不会为同一数据推导出不同 边界。命令层生成精确与扩展候选区域,并分别执行变更与结构守卫;React 适配层只负责可访问的 排序提醒/自定义弹窗转换,以及包含冻结排序请求和原选区的短生命周期授权。只有实时选区仍匹配 原选区时,命令才接受扩展目标,过期宿主状态会失败关闭。标题启发式同样只读取原始非公式文本, 不会把格式化数字或公式结果行排除在数据正文之外。

受控富文本输入法边界

Document 正文、可视化 Markdown 和 Presentation 文本共用一个内部组合输入协调器。 compositionstart 会阻止宿主发布与受控值协调;compositionend 会在当前任务之后开始 结算,让 ProseMirror 先刷新 WebKit 尚未处理的 DOM Observer 记录,并用有界的 20 毫秒 重试兼容短暂保留 view.composing 的运行时。只有完成结算的规范值会被发布,组合期间 收到的宿主权威值会在此后继续协调。

普通键盘输入不会创建定时器或额外渲染。聚焦组件测试覆盖受控回显与组合期间宿主替换; 独立 Playwright WebKit 门禁验证真实浏览器事件顺序,确认拼音预编辑阶段发布次数为零、 最终中文只发布一次,并在受控重开后保持相同内容。

文档排版管线

Document 的逻辑内容始终由同一个 TipTap 编辑器持有。排版流程读取结构化文档、页面 设置和字体清单,完成文本塑形、可视行测量、分页与页面元素定位,再把结果同时交给编辑、 只读预览和浏览器 PDF 导出。

受控文档
  → TipTap 事务与结构化模型
  → 字体解析、回退与文本塑形
  → 可视行测量
  → 分节、分页、表格续排与浮动对象约束
  → 页面栈
      ├── 编辑界面
      ├── 只读预览
      └── PDF 导出

耗时且可取消的计算放进 Worker。Rust / WebAssembly 内核负责需要确定性和跨界面一致的 部分;DOM 测量只用于浏览器特有或暂未进入内核的内容。新的计算结果带版本,过期任务 不会覆盖更新后的文档。

目录使用独立的块级原子节点,因为原生 DOCX TOC 域会跨越多段缓存结果。语义标题和原生 大纲级别段落只读取共享大纲收集器,页码只读取分页域解析器,并保存有界选项和最多 512 个 缓存项。缺少原生段落身份的入选项会在插入或修改目录的同一事务中获得身份,因此插入目录 不会让自身链接失效。“引用” 功能区中的同一弹窗负责插入和修改当前选中目录,显式更新会在一个事务内刷新全部目录。 DOCX 导出写入真实 w:sdt/TOC 域,修补原生前导符和页码对齐,并为稳定标题身份补充 内部书签。导入只接受可以无损表示的常见开关;在 Mammoth 转换前整体移除内容控件,待 段落身份恢复后再放回一个类型化目录块,因此不会把跨段目录误判成非法行内域。每个导入 目录独立匹配缓存项,因此多个目录可以重复指向同一个标题。

原生索引分别使用行内 documentIndexEntry 原子节点和块级 documentIndex 原子节点。行内 节点保存稳定身份、主索引项、次索引项、交叉引用和页码强调意图;块节点只保存有界选项、 生成行和截断状态。唯一收集器遍历规范 TipTap 文档,从现有域上下文解析器取得页码,按规范 化索引项分组,并在合并同页标记时保留全部目标身份。标记或编辑索引项,以及插入、自定义或 更新索引块,都分别只派发一个事务。DOCX 导出把行内节点映射为原生 w:fldSimple XE 域, 通过独立的标记收集器和补丁器写入 INDEX 内容控件,并生成 Index1/Index2 缓存行。导入 会在 Mammoth 转换前标记支持的 XE 边界并移除支持的 INDEX 控件,转换后重建类型化节点并把 缓存页码重新连接到匹配的标记身份。不支持的开关和损坏结构继续进入字段诊断路径。

原生隐藏文字会在判断排版资格之前解析。规范 TipTap Mark 保留继承、隐藏和显式可见三种 状态;DOM 默认隐藏 data-office-hidden-text="true",仅可编辑界面的“视图”模式会用点状 下划线揭示它,只读预览和独立 PDF 快照不会继承该显示类。包含隐藏文字的段落使用浏览器 权威测量,因为不可见运行不能贡献 Worker/WASM 字宽。这是明确的逐段回退,不会创建第二棵 文档树或第二套分页模型。

原生空心、阴影、阳文与阴文会在 DOM 序列化和 DOCX 导出前,通过四个可空 TextStyle 属性解析。空心与阴影可以共存;阳文和阴文分别与其他启用效果互斥。导入会在绘制前拒绝 重复、错位、命名空间伪造、带文本、带子节点、多余属性、未知值和冲突属性组。导出使用 规范 WordprocessingML 运行属性顺序及防碰撞的嵌套 w:rStyle 标记。文字效果位于内层, 隐藏文字位于外层;先修补隐藏文字,再修补文字效果,而且只提交真正到达生成文字运行的 标记。这样既能保留原字符样式,也不会让空嵌套容器留下孤立标记。这四种效果只改变绘制, 不会改变字宽,所以符合条件的段落仍走 Worker/WASM,浏览器与 PDF 的 CSS 投影则明确保持 为有界近似。

原生字符边框会在判断排版资格、DOM 序列化和 DOCX 导出之前,解析为一个经过校验的 TextStyle 值。封闭模型接受 25 种可见 w:bdr 线型以及 nilnone,并保留直接颜色或 解析后的主题颜色与 tint/shade 身份、有界八分之一磅宽度、磅间距、阴影和框架。重复、 错位、艺术边框、命名空间伪造、带子节点或文本、多余属性、越界以及无法解析主题颜色的 叶节点会归一化为显式 nil 重置并生成兼容性诊断,不会启用不可信绘制或恢复过期继承格式。

规范 DOM Mark 在 data-office-run-border 中保存语义 JSON;CSS 投影使用四边边框、 内边距、阴影与 box-decoration-break: clone。可见边框会改变行内几何,所以所在段落会 明确使用浏览器权威测量;显式 nilnone 不增加几何,仍可走 Worker/WASM 塑形。 DOCX 生成器只在生成运行中分配经过颜色碰撞检查的临时边框标记,然后在写入格式修订旧值 之前,把正文、页眉页脚和脚注尾注部件修补为准确 w:bdr 属性。这样可以准确保留直接格式、 样式继承和 w:rPrChange 旧值,同时明确把浏览器与 PDF 线型绘制视为有界视觉近似。

原生字符底纹使用一条独立且只影响绘制的 TextStyle 路径。封闭的 w:shd 模型接受全部 规范图案、彼此独立的直接色、自动色或已解析主题前景/背景通道、tint、shade 和显式 nil。属性源按原生优先级依次解析文档默认值、段落和字符样式、条件表格样式与直接运行 属性。无效直接叶节点会归一化为 nil,不会让过期继承底纹重新出现。

规范 Mark 在 data-office-run-shading 中保存有界 JSON;CSS 用克隆片段投影实心、线条、 交叉和百分比遮罩。这些声明不改变字宽,因此底纹仍可走 Worker/WASM。原生高亮作为独立 Mark 保留并绘制在底纹之上。导出只在实际生成的文字运行中分配防碰撞临时填充色,随后把 正文、页眉页脚和脚注尾注修补为准确 w:shd 属性,最后再向 w:rPrChange 写入准确旧值。

原生校对元数据使用另一条语义 TextStyle 路径。封闭的 w:lang 模型会在样式解析和全部可编辑 story 中 保留拉丁 w:val、东亚 w:eastAsia、双向文字 w:bidi 槽,以及显式 w:noProof true 或 false 状态。规范 DOM Mark 在 data-office-proofing-languages 中保存有界 JSON, 并在 data-office-no-proof 中保存显式布尔值;派生的 langspellcheck 只是显示和 浏览器校对投影,不会替换源状态。重复、错位、嵌套、带文本、多余属性、命名空间伪造、 无效语言标记或无效开关值会安全拒绝并生成兼容性诊断。

文字排版收集器会为每个 DOM 运行解析有效继承 lang,使用同一有界语法再次校验,并把语言 加入运行样式合并键。这个可选协议字段保持向后兼容:缺失语言时沿用原塑形行为,有效值则在 Rust 中再次校验,并在推断分段属性与塑形前写入 RustyBuzz UnicodeBuffer。因此与语言相关 的字形选择可以保持确定性,同时分页不依赖浏览器词典或宿主语法服务。

表格几何不会再被压缩成一个“窗口 / 内容 / 固定”状态。排版算法、自动 / 百分比 / 像素 首选宽度、左中右对齐、缩进、表级四边单元格边距和单元格级边距覆盖分别进入类型化模型, 编辑、预览、打印和 DOCX 往返共享同一份数据。导入与导出分别保留 w:tblLayoutw:tblWw:jcw:tblIndw:tblCellMarw:tcMarw:tblGridw:tcW; OOXML 中以五十分之一百分比或百分数字符串表示的宽度都会按真实比例渲染,不再把 50% 错误放大为 100%。表格布局功能区通过统一的“表格属性”弹窗编辑表格、行、列和单元格四类属性。 弹窗同时管理首选宽度、位置、缩进、当前行高与分页、当前列宽、单元格垂直对齐和边距, 校验完整草稿后用一次 TipTap 事务和一条撤销记录提交。弹窗中的厘米数字只是显示投影; 未修改的像素值和单元格部分边距继承直接从类型化源模型复制,不会经过两位小数回显再次量化。 取消或 Escape 不修改文档,并把焦点还给原触发按钮。紧凑布局中的四个页签、选项、数字步进和 底部操作均保持视口内可用的触控区域。真实 DOCX 样本及 A3S Test 会同时检查 62.5% 居中表格、 四个属性页签、原子提交与取消安全、导入边距的精确保留,以及编辑和预览几何一致性。

图片节点分别保存宽度、高度、是否锁定纵横比、文字环绕、对齐、文字距离、替代文字和有限变换。 选中图片后,“图片”功能区保留常用快捷操作,并通过一个响应式“图片属性”弹窗统一编辑 这些字段以及 90° 旋转、水平/垂直翻转。确定时只把真正变化的字段写入一次独立 TipTap 历史记录;取消和 Escape 不修改 文档,并把焦点精确还给原触发按钮。导入尺寸只是按厘米投影到输入框,没有修改的像素值会 原样保留,不会因两位小数回显发生量化。纵横比锁定属于每张图片自身的属性,因此弹窗和 后续拖拽缩放使用同一规则。390 像素宽的 A3S Test 流程覆盖尺寸、解锁比例、四周环绕、 右对齐、文字距离、替代文字、图片选区保持、焦点归还以及零控制台和页面错误。

90° 的旋转和水平/垂直翻转会在编辑、预览和 PDF 捕获中使用同一组 CSS 投影,并在 DOCX 中写入 DrawingML a:xfrmrotflipHflipV。任意角度或格式错误的变换会进入 兼容性诊断并归一化为有限模型,不会生成不可靠的精确编辑状态。

表格边框使用每个单元格独立的上、右、下、左属性,而不是一条统一 CSS 边框。表格设计 功能区维护可复用的颜色与线型笔,并通过所有、外侧、内部、单边、内部横向和内部纵向 目标生成一次 TipTap 事务。编辑和只读预览直接渲染同一组属性;DOCX 导入把 w:tblBorders 的外侧与内部语义解析到单元格边,把显式 w:tcBorders 作为覆盖,导出则 逐边写回 w:tcBorders。默认或显式 w:type="table" 样式会经过有界且可处理循环的 w:basedOn 链解析;w:tblLook、横纵带状尺寸、跨列和行网格偏移共同选择整表、带状、 首末行列和四角条件。条件填充、逐边边框、文字强调、字体和颜色,以及段落对齐、方向、 缩进、间距与行距规则、分页规则和制表位,都会在段落样式之后、直接段落和文字格式之前 进入同一编辑模型;表格和单元格直接格式仍优先于样式中的单元格效果。主题派生颜色会 转换为稳定 RGB,保证编辑、预览和重新导出一致。支持范围之外的少见条件段落属性、导出 后保留语义主题引用、嵌套表格和复杂合并单元格流尚未宣称完整保真。

编辑发生在长文档尾部时,分页只回退到安全页面边界。稳定前缀不仅复用内核页面,也复用 已经解析的首页、默认页和偶数页页眉页脚、页码、页面摘要与导航位置;回退边界及其后的 页面才重新派生。运行时会报告复用和重新派生的页饰数量,让确定性浏览器测试直接约束 增量预算,而不是依赖不稳定的耗时判断。

实时 DOM 测量按 32 毫秒上限进行协作式分片,只在 ProseMirror 顶层块之间让出事件循环, 因此单个块及其渲染元素始终作为一个原子测量单元。文档失效会在旧任务替换可复用快照前 将其中止,现有单任务协调器只合并一次新的运行;同步测量路径继续供确定性调用和一致性 测试使用。

符合窄格式条件的超长 DOCX 会按模型层 NodeView 窗口工作。屏幕外叶子不挂载后代 DOM, 可见叶子从结构缓存取得只读语义预览;指针选区、搜索、模型边界导航或真实编辑只水合目标块。 占位高度、累计几何索引和二分查找保留完整滚动距离,Ctrl+HomeCtrl+End 依据模型位置 运行,不依赖已经挂载的 DOM。

生产环境不会把 CSS content-visibility: auto 当作这条边界。它只能跳过浏览器布局和绘制, 不能移除 ProseMirror 节点、后代 DOM、JavaScript 投影、选区和 Mutation bookkeeping 或可访问树。 真正的 NodeView 窗口会省略屏幕外后代 DOM 与页面 DOM,Spreadsheet 则只绘制可见 Canvas 行列。content-visibility 仍可作为受测基准模式,但不能替代模型窗口或可见区域绘制。

分页缩略图与 PDF 捕获

Word 导航窗格和浏览器 PDF 导出复用同一套物理页面捕获边界。缩略图不会再根据正文 片段拼出近似卡片,而是克隆已经完成测量的实时分页页面,移除编辑状态,再按精确页面 偏移裁剪为图片。当前页和相邻页优先进入串行队列,其余页面通过可视区域窗口按需捕获; 页面内容变化会触发防抖刷新,离开窗口的图片会释放。

分页完成就是这里的布局就绪依据。捕获过程不会等待与当前页面无关的整份字体集合,也 不依赖后台智能体标签页可能永久暂停的动画帧。正文摘要只在加载或失败时作为降级内容。 超过 48 页时,导航窗格只挂载最多 24 个连续页面按钮;当前页和键盘游标页位于窗口外 时会作为稀疏锚点保留。可变高度占位块保持原生滚动距离,Home 和 End 可以直接挂载并 聚焦首尾页,不依赖浏览器动画帧。基于真实 120 页 DOCX 的 A3S Test 已验证挂载上限、 首尾页键盘跳转、选择、焦点保持和占位几何。图片捕获与页面按钮 DOM 均已有边界。

页面列表、标题树和全文搜索结果复用同一套导航窗口模型。标题与搜索结果超过 48 条后, 最多挂载 32 个连续条目,并仅在窗口外稀疏保留当前项、选中项或键盘游标项。前置、中间和 后置物理占位块保持原生滚动距离;即使大部分条目没有挂载,aria-posinsetaria-setsize 仍反映完整集合。Home 和 End 无需依赖动画帧即可挂载首尾目标。真实 120 页 DOCX 同时包含 120 个内置标题和 120 个正文匹配,确定性流程已验证标题与搜索 结果的挂载上限、首尾键盘访问、精确结果选择,以及无控制台和页面错误。长距离程序化 选择会把文档滚动临时切为即时模式并保持到下一次绘制,再恢复原有平滑滚动样式,因此 正文目标可以在同一帧准确显示。

修订审阅在超过 48 条修订时复用同一窗口模型。界面最多挂载 32 张连续修订卡片,键盘 游标所在卡片作为稀疏锚点保留,省略范围由物理占位块表示。方向键、PageUp/PageDown、 Home 和 End 因此可以访问完整审阅队列,而无需一次挂载全部条目。接受或拒绝一条修订 后,焦点会移动到相邻保留条目的同类操作。确定性 A3S Test 会导入包含 120 条原生 OOXML 插入修订的真实 DOCX,并验证挂载上限、首尾焦点、120 到 119 的决策变化、占位 几何,以及零控制台和页面错误。

聚焦移动夹具还会验证源/目标配对处理、只定位目标侧、不可变协作审计,以及不泄露浏览器 标记的原生 DOCX 重开。

段落格式属于节点级审核语义,不使用行内 Mark。开启修订后,一次格式事务会比较每个 受影响段落或标题前后的完整规范属性集合。发生变化的节点获得同一个 paragraph-formatting 身份与第一次修改前的完整快照;后续格式修改仍保留这个原始审核 起点。接受只清除修订属性,拒绝在不修改子内容的情况下恢复经过校验的快照。同一身份下 的所有节点会原子处理,接受与拒绝各自拥有独立撤销边界。DOCX 导入导出把该模型映射到 严格或过渡 w:pPrChange,Yjs 则直接保存相同节点属性和不可变决定记录,不建立第二套 传输模型。

纯文字移动修订属于成对的行内审核语义,而不是两张无关的插入/删除卡。同一个 move 身份会保留明确的 fromto 角色;审阅定位指向目标侧,接受或拒绝则先校验两侧文字 一致,再在一个事务中处理完整配对。原生 DOCX 导入导出会把该配对映射到严格或过渡 w:moveFrom/w:moveTo,Yjs 边车只为完整意图保存一条不可变决定记录。 该记录的类型为 changeKind: "move"

文档比较在编辑器事务之前形成纯规划边界。当前与导入的 TipTap/ProseMirror 树保持 不可变,确定性的加权块对齐先匹配相关段落和标题,再由 LCS 词元差异生成插入与删除 Mark;准确的 Mark 与段落属性快照则生成字符格式和段落格式修订。如果一个词法范围在删除块和 插入块中各只出现一次,且文字格式与可携带的分隔空白一致,有界移动推断会保留稳定的段落范围, 并可在同一分节内对齐的简单文字块之间配对,而不会压平文档树;跨分节、富文本块和结构对象不 进入这条推断路径。配对结果带有源/目标角色并共享一个 move 身份。稳定语义签名用于派生修订 身份,因此相同输入和选项会得到相同顺序与身份。只有完全通过预检的计划才会替换已挂载文档, 而且整个替换只产生一个事务、一次受控发布和一条撤销记录。重复、格式不一致、跨分节、富文本和 超出上限的候选不会被猜测成移动,而会保留为普通修订或诊断;已经存在的原生成对移动仍可正常 进入审阅。

合并只核验,不猜测。它先在不可变审阅快照上拒绝全部导入修订,再把结果语义签名与 当前基线比较。只有准确匹配时,才会迁移审阅副本的行内与格式修订,同时保留当前段落 和分节身份。当前文档已有修订、审阅快照损坏、结构块修订、布局不匹配或复杂对象变化 都会保持已挂载文档不变。比较限制为每个版本 1,024 个块、两份文档合计 1,000,000 个 文字字符,并且块对齐或任一次行内矩阵最多约 110 万个单元。该规划器目前在主线程运行, 不宣称使用 Worker 或 WASM 计算差异;在量化证据证明需要新的执行边界前,有界预算与 原子提交会让同步成本保持明确。

公开格式 Fixture 还会在普通审核队列中同时创建独立的字符与段落格式卡。两套确定性 A3S Test 分别拒绝一种修订,验证正文和另一条修订不受影响,并证明段落旧对齐、缩进、 间距与行距完整恢复,且控制台和页面错误为空。

10 万规模性能证据

以下本地冷启动数据于 2026-08-19 在 Apple M2 Pro、16 GB 内存、Playwright 1.61.1、 headless Chrome 149 和 1,440 × 1,000 视口下记录。Document 取三个新浏览器进程中位数, Spreadsheet 取五个。它们描述当前实现和测试夹具,不是所有设备的通用承诺。

夹具编辑器可见分页完成连续滚动p95 帧间隔
100,000 个文字段落,450 万字符0.397 秒1.460 秒,3,125 页120.0 FPS10.7 ms
100,000 行 × 3 列 DOCX 表格0.813 秒1.747 秒,2,381 页120.6 FPS10.1 ms
100,000 行 × 10 列 XLSX,100 万个已填充单元格0.480 秒不适用120.1 FPS12.9 ms

连续滚动三组夹具都没有产生 ≥50 ms 的 Long Task。保留状态分别约为 70.4 MiB 与 2,867 个 DOM 节点、145.8 MiB 与 3,106 个 DOM 节点、38.41 MiB 与 903 个 DOM 节点。 Spreadsheet 的 Ctrl+EndJ100000 中位耗时为 77.3 ms。

夹具第一次编辑墙钟延迟第二次编辑墙钟延迟
100,000 个文字段落70.3 ms53.9 ms
100,000 行 × 3 列 DOCX 表格102.3 ms85.1 ms
100,000 行 × 10 列 XLSX52.5 ms45.9 ms

墙钟延迟从页面内 beforeinput 开始,在宿主完整受控值发布计数变化时结束,因此包含输入 事务、React、分页调度与完整发布,但排除了 Playwright 传输和轮询退避。详细范围、CPU 分解、内存和复现实验命令见 English 完整工程参考

这里的百万单元格 XLSX 是矩形数据夹具,不是语义 WorkSpreadsheetTable/OOXML ListObject 性能测试。它证明普通工作簿导入和可见区域 Canvas 路径,不能用于声称结构化引用、计算列、 汇总、表格筛选、转换或导出具有同样指标。计算列自动填充受插入行操作约束并已有正确性 覆盖,但尚未发布大型 ListObject 的延迟或内存数据;发布表格专项预算前仍需独立的矩阵。

PDF 页面组织架构

PDF 页面组织使用独立且按需加载的 Web Worker,不与 PDFium 渲染 Worker 混用。界面只把 有界源字节和类型化的插入、删除、旋转、重排、合并、抽取或拆分计划交给 pdf-lib,不会 跨线程传递 PDFDocument 或页面对象。修改操作返回一份完整字节数组,替换查看器当前 Blob,用 PDFium 重新打开,并把修改前后的两个 Blob 作为一条撤销记录。抽取和拆分只 返回类型化文件,不修改源文件或历史。工具栏先消费 PDFium 批注/表单历史,再消费页面历史, 因此一次用户意图不会横跨两套历史栈。

主文件最多 256 MiB,合并文件最多 128 MiB,每个结果最多 4,096 页。格式损坏、加密或签名 源文件无法修改;删除、重排和合并还会拒绝包含表单、大纲或标签结构的文件,因为当前不能 安全重写其目录引用。抽取和拆分允许复制页面范围,同时返回未复制文档级对象的明确诊断。 协作和证据覆盖层会隐藏页面组织,因为它们把稳定记录绑定到不可变源页身份。 onPageExport 是衍生文件的类型化宿主接口,onSave 仍是修改后源文件的唯一持久化接口。

Extension 边界

Document 与 Markdown 可以接收 TipTap Extension。宿主应保持 Extension 数组引用 稳定,并确保名称唯一。自定义 Node 或 Mark 如果需要经过 DOCX 或 Markdown 往返后 继续存在,还必须提供对应的解析和序列化语义。

Spreadsheet、Presentation 与 PDF 目前通过 fileActionsonAgentRequestloadSourceonSaveonPageExport 和放映事件等类型化接口扩展。内部命令上下文尚未承诺兼容性, 因此不会作为公共 API 暴露。

文件与运行时资源

编辑器实现按类型拆包。打开某一种文件时才加载对应组件、Worker、字体或 PDFium 资源; 宿主也可以在可预期的导航前调用预加载 API。组件不要求 A3S 后端,资源地址可由宿主 覆盖,以适配 CDN、离线环境和内容安全策略。

正确性原则

  • 受控内容是宿主与编辑器之间的唯一状态协议。
  • 编辑、预览与导出尽量复用同一排版结果,避免三套视觉逻辑。
  • 不支持的文件语义要明确报告,不能静默丢弃或伪装成高保真结果。
  • 异步命令必须检测过期选区和过期文档版本。
  • 快捷键、弹窗、菜单与焦点行为属于编辑器公共质量,不由宿主重复实现。
  • 右键菜单在桌面端跟随鼠标或键盘选区定位,在手机端使用带 44 px 操作项和内部滚动的 底部操作面板;两种布局保持同一命令顺序、快捷键和焦点归还行为。
  • 大文档计算必须有边界、可取消,并避免随页数无上限挂载 DOM。

当前边界

当前架构已经覆盖五类编辑器的公共组件、受控模型、按需加载和主要文件流程,但仍需继续 提高 DOCX 字体替代、段落与分页、复杂表格、浮动对象、母版继承、公式兼容性和可搜索 矢量 PDF 等能力。对 传统 Office / Word 的视觉一致性必须用真实 OOXML 文件、固定字体与 逐页截图证据验证,不能只用简单 HTML 示例判断。

需要逐模块实现清单、协议字段和性能门槛时,请切换到 English 完整工程参考