Joye Dev

Back

feat(cover): render covers for DOC pages

feat(cover): render covers for DOC pages (PR #4815)


正文来源:飞书学习文档。以下为通过个人 Feishu API 获取并转换后的完整 Markdown 正文。

今日选择#

feat(cover): render covers for DOC pages(PR #4815)

  • 作者:zuomeng wang(GitHub: alt1o)
  • 合入时间:2026-07-16 02:45:55 UTC / 2026-07-16 12:45:55 AEST
  • 变更规模:35 个文件,957 insertions / 135 deletions
  • 链接:GitHub PR #4815

为什么值得学#

  1. 它把现有 DESIGN 封面管线扩展成按 pageType 分发的跨 Go 后端与 export worker 契约,而不是在 DOC 旁边再造一条孤立任务链;SHEET 仍在调度边界被排除。
  2. DOC 渲染直接消费持久化的 ProseMirror content 和 node registry,在独立 doc.html 中构造一次性 DOM;表格节点从 registry 物化成真正的 HTML table,避免依赖编辑器运行时。
  3. 输出几何被固定为 880×1140,并只序列化前 72 个顶层块;这样封面与编辑器页面换行保持一致,同时把浏览器截图和资源加载成本限制在可控范围。
  4. 资源按文档引用的 asset/font ID 在渲染时通过 workspace-scoped worker token 获取,已分配但无法解码的媒体会使任务失败并进入重试,而不是生成看似成功的坏封面。

关键代码#

1. 在调度层定义可封面化页面类型backend/go/internal/cover/scheduler.go:33-50

func isCoverable(t api.PageType) bool {
    return t == api.DESIGN || t == api.DOC
}

if pg.Scope != scope.Workspace || !isCoverable(api.PageType(pg.Type)) {
    return nil
}
return s.prod.Enqueue(ctx, Payload{PageID: pageID})
plaintext

设计点:把“是否应该产生封面任务”收敛在 scheduler 的业务边界;加入 DOC 的同时明确 SHEET 和 system-scope page 不会误入队列。

2. 用 discriminated union 固化 worker 输入契约backend/workers/export/contracts/exportCover.ts:33-62

export const coverRenderSchema =
  z.discriminatedUnion("pageType", [
    z.object({ pageType: z.literal("DESIGN"), value: designValueSchema }),
    z.object({ pageType: z.literal("DOC"), value: docValueSchema }),
  ]);
plaintext

设计点:pageType 同时决定 payload 的 value 形状和 render page 路由;非法的旧字段或缺失 pageType 在 HTTP 入口被拒绝,避免渲染页自行猜测格式。

3. 处理文档表格的“节点 + registry 值”双层模型backend/workers/export/render/src/doc/renderDocCover.ts:56-74

const nodeId: unknown = node.attrs[UniqueIdAttributeType];
const def = typeof nodeId === "string" ? docNodes[nodeId] : undefined;
const table =
  def && isSheetValue(def.value) ? sheetValueToTable(def.value) : null;
if (!table) return defaultTableToDom(node);
const dom = document.createElement("div");
dom.innerHTML = serializeTableToHtml(table);
plaintext

设计点:ProseMirror 节点本身只带引用,真正的 sheet 内容在 node registry;渲染器在静态 HTML 生成阶段补齐这一边界,并在数据不完整时保留默认占位行为。

4. 把浏览器渲染失败变成可观察的任务失败backend/workers/export/render/src/doc/doc-main.ts:25-40

try {
  await renderDocCover(root, data);
} catch (error) {
  window.__EXPORT_ERROR = error instanceof Error ? error.message : String(error);
}
window.__EXPORT_READY = true;
plaintext

设计点:先设置错误再翻转 ready,让 screenshot worker 走统一的 waitForCaptureReady 检查并快速报错,而不是等到超时或上传一张坏图。

和最近学习记录的关系#

  • 与最近的 #4720 有直接的可靠性主题关系:#4720 只持久化 assetId、在 replay 时重新解析短期 URL;本 PR 同样不把 URL 写进 DOC 内容,而是在 cover render 时用 workspace token 批量解析,并选择 thumbnail rendition。
  • 与 #4598 有边界上的延续:#4598 将 iframe 内的临时 DOM 编辑与可持久化 HtmlEdit 分开;本 PR 也把持久化的 DOC envelope 与一次性截图 DOM 分开,渲染过程不回写文档内容。
  • 上一轮学习的 #4637 是 agent mode/profile 状态机,没有同一模块的直接前后关系;这里没有强行把两者当成一条链路。更直接的产品邻接是前一天合入的 #4746:它在 site 端实现 DocumentThumbnail,使用同样的 880px 页面、表格物化和媒体 rendition 思路;本 PR 刻意重复部分序列化逻辑,并把抽取留给后续重构。

我会怎么吸收#

  1. 跨运行时扩展能力时,先把“类型/数据形状/路由”放进显式契约,再让各实现按 discriminator 分支;不要让下游通过字段存在性猜测模式。
  2. 对持久化内容做预览或物化时,只传稳定 ID 和原始 envelope,在目标运行时解析短期资源;同时明确缺失资源是软降级还是任务失败。
  3. 对有界的预览产物,用与真实编辑器一致的几何参数和合理的内容上限换取确定性;把上限、资源批量大小和失败语义写成代码常量与测试。

边界 / 风险#

  • PR 明确没有抽取 site 端 DocumentThumbnail 的表格/媒体序列化逻辑,当前存在两份实现;编辑器 schema 或媒体属性变化时可能发生预览漂移。
  • MAX_COVER_BLOCKS=72 是“足以填满 1140px 页面”的经验上限,不是按实际布局计算;极端大块内容可能被截断,字体加载失败也可能退回 fallback 字体而不阻止产物生成。
  • Playwright snapshot 覆盖普通文本、列表、图片和损坏媒体,但 fixture 没有真正的 table node registry 数据,也没有直接覆盖 72-block 截断与字体批量失败;这些是后续值得补的回归边界。

候选说明#

按 Australia/Melbourne 日期统计:今天(2026-07-16)有 11 个已合入 origin/main 且未记录的候选,昨天(2026-07-15)有 20 个。今天的 11 个 merge commit 均验证为 origin/main 的祖先,因此优先在今天窗口选取 #4815。昨天的 #4637 已在学习日志中记录并去重跳过;今天没有已记录 PR 被跳过。#4815 在今天候选中同时具备跨 Go/export worker 的完整链路、明确的数据边界和多层测试,学习价值高于纯 UI/文档/小修复候选。

学习元数据#

模块:backend/go/internal/cover;backend/go/cmd/backfill-covers;backend/workers/export/contracts;backend/workers/export/render/src/doc;backend/workers/export/worker;backend/workers/export/tests

学习标签:cover-pipeline、page-type-dispatch、document-rendering、prosemirror-serialization、workspace-scoped-worker-token、asset-font-resolution、snapshot-test、backfill

主结论:把 DOC 封面看作持久化页面内容到渲染 artifact 的跨边界物化任务:后端只负责按类型和版本取稳定 envelope、签发短期 workspace 能力;worker 负责按 schema 选择渲染页、解析节点引用、等待资源并将失败暴露给重试系统。

🗂️ 这是知识库中的🔬 研究。

内容可能仍在补充或修订中。

← Back