Hyperframes Studio 组合(Composition)编辑可靠性验收:解析 composition-reliability 最小测试工程
本篇文章以 Hyperframes Studio 仓库内 packages/studio/tests/e2e/fixtures/composition-reliability/ 目录中的验收夹具说明为主线,剖析这份"无媒体、纯 HTML"的 Studio 工程如何一次性覆盖组合复用、嵌套组合、透明遮罩标题、剪辑碰撞与新轨道放置、跨轨叠层等编辑链路。读完你可以理解 composition-reliability 夹具的验收清单、每个文件的设计意图,以及仓库中对应集成测试是如何以"不改动检入源"的方式守住这些行为的。
一、夹具定位:一段视频工程验证"一整条编辑栈"
在 Hyperframes 的世界里,Studio 的工程本质是 HTML:画布上的每一个剪辑、每一条时间线都是带 data-* 属性的 DOM 节点,编辑操作最终写回 HTML 源码。而"组合编辑是否可靠"正是整条产品栈的地基——组合(composition)可以被实例化为剪辑、可以被嵌套、可以被移动/剪切/撤销,且每一步都必须产生可预期、可复现的源码结果。
composition-reliability 夹具正是为此设计的验收基准工程。README 给出了它的设计约束与验收范围:
Compact, media-free Studio project for validating composition editing as one stack(紧凑、无媒体文件的 Studio 工程,用于把组合编辑作为一整条技术栈来验证)
核心设计目标有五条:
- 两个**根级宿主(root host)**在不同时间点复用同一个
title-card.html; nested-shell.html在更深一层托管同一个标题卡,构造嵌套组合场景;- 标题卡使用透明 overflow 遮罩包裹可编辑的标题文字;
- 同一轨道上的相邻剪辑提供干净的碰撞(collision)/"落至新轨道"拖放目标;
- 一次跨轨道重叠用于验证常规的视觉分层行为。
同时它明确了一种工程纪律:进行浏览器验收前须将整个目录复制到 scratch(临时工作区),验收动作(打开工程、组合插入、单选/多选移动、碰撞放置、重叠分层、剪切、标题颜色/字号、单步撤销)都在副本上进行,仓库内检入的夹具源文件必须保持不变。
二、夹具目录解剖:一个可发布的迷你 Studio 工程
该夹具不是一个零散 HTML 文件,而是一个结构完整的工程根目录(对应一个可被 Studio 打开的组合):
packages/studio/tests/e2e/fixtures/composition-reliability/
├── README.md
├── package.json
├── hyperframes.json
├── index.html # 顶层"根组合",1280×720 @ 30fps,时长 12s
└── compositions/
├── title-card.html # 可复用的标题卡组合(480×220)
└── nested-shell.html # 嵌套一层托管 title-card 的组合
工程声明文件有两个:
package.json仅含"name": "composition-reliability-fixture"与"private": true,避免被误发布;- hyperframes.json 声明了工程格式与资源映射(该文件头部还包含
$schema与registry键,指向项目官方 schema 与上游 registry;其paths字段将组合 HTML 目录映射为资源类别,格式与仓库 schema/hyperframes.json 描述的工程结构对应):
{
"paths": {
"blocks": "compositions",
"components": "compositions/components",
"assets": "assets"
}
}
也就是说,compositions/ 下的每个 HTML 被当作组合块(block)源来解析——这正是"打开工程"验收步骤的前提。
三、顶层 index.html:把"验收场景"排版成一条可编辑时间线
index.html 是整个夹具的舞台。根节点是一个组合容器,用 data-* 属性声明画布元信息:
<main id="composition-reliability"
data-composition-id="composition-reliability"
data-width="1280" data-height="720"
data-start="0" data-duration="12" data-fps="30"
data-no-timeline>
随后六个带 class="clip" 的子节点构成 4 条轨道。把它们的属性汇总如下(这正是验收场景的"排版"):
| 剪辑 | data-composition-src | start | duration | track | 画布位置 | 场景作用 |
|---|---|---|---|---|---|---|
| title-host-a | compositions/title-card.html |
0 | 4 | 0 | 左上 (48,48) | 标题卡复用 #1 |
| title-host-b | compositions/title-card.html |
4 | 4 | 0 | 右上 (600,48) | 同一组合再次实例化 #2 |
| nested-host | compositions/nested-shell.html |
2 | 6 | 1 | 左下 (48,330) | 嵌套一层引用标题卡 |
| collision-a | — | 1 | 2 | 2 | (600,580) #7357ff | 碰撞目标 A |
| collision-b | — | 3 | 4 | 2 | (740,580) #ff5c7a | 与 A 相邻的碰撞目标 B |
| layer-overlap | — | 3 | 4 | 3 | (880,580) 半透明绿 | 跨轨时间重叠对象 |
从这份布局可以读出三个刻意的验证维度:
- 组合复用:
title-host-a(0–4s)与title-host-b(4–8s)在轨道 0 上首尾相接、各自引用compositions/title-card.html。同一个组合文件被实例化两次且互不干扰,任何编辑都不得在宿主间串扰。 - 碰撞/落轨目标:
collision-a与collision-b位于轨道 2,collision-a结束的时刻正好是collision-b开始(end = start+duration = 3),两者在画布上水平相邻(600–820 与 740–960 存在视觉邻接),为"拖到旁边触发碰撞吸附/落至新轨道"提供了干净的几何目标。 - 跨轨重叠分层:
layer-overlap与collision-b起点同为 3s,但落在轨道 3,且画布上两者水平范围重叠(880–1100 与 740–960 在 880–960 相交),用于验证"不同轨道同时刻素材如何按轨道上下关系正确分层渲染"。
四、title-card.html:透明 overflow 遮罩包裹可编辑标题
组合被复用的关键前提是"内容可被独立编辑"。标题卡组合 title-card.html 展示了 Hyperframes 组合的标准形态:以 <template> 声明一个 480×220、无内置时间线的组合片段,DOM 结构为"块 → 遮罩 → 标题文字"三层:
<template id="title-card-template">
<section id="title-card-root" data-composition-id="title-card"
data-width="480" data-height="220" data-start="0" data-duration="4" data-no-timeline>
<style>
#title-card-root { padding: 32px; overflow: hidden; background: #202736; ... }
.hl-block { width: 100%; }
.hl-mask { overflow: hidden; background: transparent; }
.hl-text { color: #f4f7ff; font-size: 52px; line-height: 1.05; }
</style>
<div class="hl-block" data-hf-id="title-block">
<div class="hl-mask" data-hf-id="title-mask">
<h1 class="hl-text" data-hf-id="title-text">Reliable compositions</h1>
</div>
</div>
</section>
</template>
设计要点在于 .hl-mask:它的 background 是透明的(因此不会在标题后画出矩形色块),而 overflow: hidden 让超长标题文字在盒子边界被裁剪。换句话说,它是一个"看不见但会裁剪"的容器——这正是"透明 overflow 遮罩"的含义,也使得标题文字的尺寸变化、换行、出格行为都可被稳定观察与测量。文字层则是一个真实的 <h1>(data-hf-id="title-text"),承载颜色/字号编辑。
五、nested-shell.html:组合里再放组合
真正的"可靠性"挑战来自嵌套。工程里的第二个组合 nested-shell.html 只有 22 行,却在内部又实例化了一次标题卡:
<template id="nested-shell-template">
<section id="nested-shell-root" data-composition-id="nested-shell"
data-width="480" data-height="220" data-start="0" data-duration="6" data-no-timeline">
<div id="nested-title-host" class="clip"
data-hf-id="nested-title-host"
data-composition-id="nested-title-card"
data-composition-src="title-card.html"
data-start="1" data-duration="4" data-track-index="0"
style="position: absolute; inset: 0; ..."></div>
</section>
</template>
注意它的 data-composition-src="title-card.html" 是相对本文件的路径(同一目录下),从而在仓库中形成了一条完整的引用链:顶层宿主 → nested-shell 组合 → title-card 组合。运行时需要正确解析"宿主目录级"的相对引用,编辑器则需要保证:在嵌套组合内部发生的编辑落在嵌套组合自己的源码文件里,而不是漏到根组合。这一点与嵌套组合的时间线语义一致——关于嵌套组合内剪辑的分组等编辑规则,可参考 docs/studio/audio-groups.mdx 中对"嵌套组合内剪辑"的相关约束说明。
六、仓库内的自动化守门:集成测试如何验证"编辑分层正确"
除了浏览器人工验收,仓库已经为这份夹具配了一个自动化守门测试:compositionReliabilityFixture.integration.test.ts(// @vitest-environment jsdom,运行于 jsdom + DOMParser 环境)。
测试第一步就通过固定路径引用夹具目录:
const fixtureDir = join(
dirname(fileURLToPath(import.meta.url)),
"../../../tests/e2e/fixtures/composition-reliability",
);
随后用两个用例把 README 的核心主张一一转成断言:
用例一:夹具结构自身的"静态契约"
index.html中引用compositions/title-card.html的宿主恰好有两个,且data-start依次为["0","4"](复用 + 时间错开);nested-shell.html内部的嵌套宿主存在,且其data-composition-src="title-card.html"指向的依赖文件确实存在于compositions/目录(existsSync校验,堵住引用悬空);- 标题模板里
.hl-mask文本包含 "Reliable compositions",其<style>匹配/\.hl-mask\s*\{[^}]*overflow:\s*hidden;[^}]*background:\s*transparent;/,且文字节点是H1(透明遮罩拓扑得到机器可读的保证); collision-a与collision-b同轨道,且 A 的start+duration恰好等于 B 的start(相邻不重叠);layer-overlap与collision-b起点相同但轨道不同(跨轨时间重叠成立)。
用例二:编辑会落到"正确的源码层"
这是整个夹具最有价值的守门点。测试通过 @hyperframes/studio-server/source-mutation 导出的 patchElementInHtml,以及同目录 domEditing 模块导出的 buildDomEditStylePatchOperation,模拟两类真实编辑:
- 移动时间线上的宿主:对
index.html中的title-host-a打补丁,把start改为 5、duration改为 2。断言结果是:修改只出现在根工程源码里(data-hf-id="title-host-a"、data-start="5"、data-duration="2"均被命中),根源码不出现data-hf-id="title-text",标题模板源文件也不含新颜色——时间线宿主编辑不会泄漏进被复用的组合模板。 - 给标题文字换色:对模板里的
title-text打一个 style 补丁(color: #12b886)。断言结果是:补丁命中,颜色只写到title-text元素自身的style上,title-mask的style属性保持为空,且重着色后的 HTML 不含根工程宿主标记、根源码也不含该颜色——内容级编辑只落在组合模板层,绝不外溢。
把两个用例合起来看,它验证的正是 README 所说的 "composition editing as one stack":时间线型编辑与内容型编辑虽然走同一条 DOM 补丁管线(patchElementInHtml 按 data-hf-id 精确寻址),却因为 data-hf-id 归属的文件不同而天然分层,编辑结果永远落在声明它的那份源码里。这与仓库中 packages/studio/src/utils/blockInstaller.ts、timelineElement.ts、timelineElementHelpers.ts 等模块持续读写 data-composition-src/宿主属性的事实相互印证:data-* 属性就是 Studio 把 HTML 转成可编辑时间线的那份共享契约。
七、验收操作矩阵:复制到 scratch 后逐项演练
README 给出的浏览器验收操作清单与夹具元素的对应关系如下,可直接作为手工验收脚本:
| 验收操作 | 在夹具中如何发生 |
|---|---|
| 打开(open) | 打开工程根 index.html,校验 1280×720 画布与 4 条轨道、6 个剪辑正确物化 |
| 组合插入(composition insert) | 从组合源插入/复用 title-card,观察新宿主出现在预期时间与轨道 |
| 单/多移动(single/multi move) | 单选拖动 title-host-a 或框选两个标题宿主一起移动,校验源码 data-start 同步 |
| 碰撞放置(collision placement) | 把一个剪辑拖向 collision-a/collision-b 邻接处,验证碰撞吸附或"落至新轨道" |
| 重叠分层(overlap layering) | 观察 collision-b(轨道 2)与 layer-overlap(轨道 3)同时刻重叠时的视觉层序 |
| 剪切(cut) | 对标题宿主执行剪切,校验剩余片段与模板不受影响 |
| 标题颜色/字号 | 修改 title-text 的颜色与 font-size,验证只落在模板文件对应元素上 |
| 单步撤销(one-step undo) | 执行一次撤销,校验所有文件回到操作前状态 |
每条操作都应遵循同一条前提:先复制目录到 scratch 再动手——因为浏览器验收会实际改写工程文件,检入的夹具源必须保持原样,才能让集成测试与后续验收永远在同一份"标准答案"上运行。
八、小结:一份夹具如何撑起一整套可靠性主张
回看整个 fixture,它的巧妙之处在于用极少的素材完成了极强的覆盖:一个可复用的标题卡、一个嵌套壳、两条纯色碰撞条与一块半透明叠层,就构造出"复用 / 嵌套 / 透明遮罩 / 碰撞落轨 / 跨轨分层"五个正交的编辑风险面;而 compositionReliabilityFixture.integration.test.ts 则把其中"可自动化断言"的部分(结构契约、编辑归属分层)变成了 CI 里每次都跑的守门测试。对于想要为 Studio 贡献组合相关功能或新增验收夹具的开发者,这份 fixture 就是现成的模板:一个能被搜索、被引用、被复制到 scratch 反复演练的最小工程,胜过一段随时可能过期的口头验收清单。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00