Orbit v0.4 方案:从高质量实现到可依赖的悬浮交互 Runtime
Orbit v0.4 方案:从高质量实现到可依赖的悬浮交互 Runtime
版本定位: v0.4.0 — Runtime Hardening & Contract Alpha
方案日期: 2026-08-16
适用基线: Orbit v0.3.0 / Runtime 0.3.0-phase5
一、版本命题
Orbit v0.4 不应被定义为“增加几个新的悬浮组件”,也不应把 Music、Clock 或任何未来 Widget 当作产品本体。其唯一版本命题应当是:使 Orbit 成为第三方功能可以安全依赖的、面向静态站的悬浮交互 Runtime。
v0.3 已经证明了 Orbit 在第一方组件上的交互能力:Runtime 区分隐藏与显式销毁;Launcher 协调多 Widget 的显隐;Clock 已具备可销毁、可重挂载路径;共享手势、磁吸、停靠与展开方向模块也已形成雏形。1 2 但这些能力目前主要由第一方 Host 直接消费,且 Music 的生命周期资源未完全收束,因此仍不足以支撑“第三方 Widget 可依赖的架构”这一承诺。3 4
v0.4 的完成不是“新增功能已经演示”,而是“外部开发者可以用稳定、可测试、低样板的契约,把一个新 Widget 接入另一个采用 Orbit 的静态站主题”。
二、必须解决的产品问题
Orbit 当前的风险不是功能少,而是其价值还停留在“作者实现得比较好”。使用者可以替换 Music/Clock,开发者也能复刻拖拽、贴边和 localStorage,因此代码质量本身不会形成不可替代性。v0.4 要开始构建的不是锁定用户的数据壁垒,而是兼容性资产:稳定的 Widget Contract、统一的 Runtime Services、可移植用户 Profile,以及至少一个由非核心 Host 视角完成的接入证明。
| 当前状态 | 为什么仍可替代 | v0.4 要改变什么 |
|---|---|---|
| Music 与 Clock 是首批内置 Host | 用户可只取 UI 成果,而不采用 Orbit。 | 把 Host 私有能力抽成所有 Widget 可用的 Runtime Services。 |
registerHost 是实际生产路径 |
扩展能力仍是内部适配器约定。 | 推出可文档化、可测试的 Orbit Widget Contract Alpha。 |
registerWidget/Registry 与实际挂载未闭环 |
架构概念尚未成为真正插件路径。 | 让 Contract 定义可注册、可挂载、可显隐、可销毁。 |
| 用户状态只分散在各 Host 的 localStorage | 不存在跨 Host、跨主题的连续性。 | 定义本地优先、可导入导出的 Orbit Profile 格式。 |
| 生命周期在 Clock 与 Music 上不一致 | 第三方作者无法信赖 destroy/remount。 | 将资源所有权和清理作为发布阻断条件。 |
三、v0.4 的边界:做什么与不做什么
v0.4 必须克制。它的目标是建立“可用的架构地基”,而不是把 Orbit 变成 Widget 商城、页面助手、云同步产品或通用前端框架。以下边界是避免版本失焦的必要条件。
| v0.4 必做 | v0.4 不做 |
|---|---|
| 完整加固 Music 与 Clock 的 mount/destroy/remount 生命周期。 | 不引入账户体系、云同步、付费服务或中心化 Widget 市场。 |
| 发布 Widget Contract Alpha 和 Runtime Services。 | 不冻结一个尚未被真实使用验证的 1.0 协议。 |
建立本地 Orbit Profile 导入/导出格式。 |
不把用户行为数据上传到任何服务端。 |
| 用 3 个异质 Widget 证明 Contract 的可复用性。 | 不追求大量官方 Widget 或主题皮肤。 |
| 补齐浏览器级集成测试、CI、文档和示例主题。 | 不在这一版重写全部 CSS 或更换构建体系。 |
四、目标架构
v0.4 应保留“无框架、静态文件直接引入”的原则。运行时仍可由一个浏览器 bundle 交付,但内部需要从“Runtime 认识 Host”走向“Host 按 Contract 使用 Runtime Services”。这样既不牺牲 Hexo 的易接入性,也能让未来不同功能以一致方式获得生命周期、布局、手势与可访问性能力。
1 | 静态站主题 / 页面 |
4.1 OrbitWidgetDefinition:Contract Alpha
Contract Alpha 的目标不是创造过度抽象,而是替代目前仅供内部使用的 registerHost 适配器。它应将第三方作者真正需要的边界讲清楚:Widget 如何声明身份、如何接收 Runtime 服务、怎样报告 Root/portal、怎样响应显隐,以及何时释放一切资源。
1 | Orbit.register({ |
| Contract 部分 | 责任 | v0.4 的最低要求 |
|---|---|---|
id / version / label |
稳定标识与用户可见名称。 | id 必须命名空间化或经合法性校验;Label 不得未经转义进入 HTML。 |
capabilities |
声明希望使用的 Runtime 能力。 | 先支持 draggable、dockable、launcher、profile;未知字段必须安全忽略。 |
mount(ctx) |
创建业务 DOM 并请求服务。 | 必须返回 WidgetInstance 或抛出可捕获错误;不得把业务 id 写进 Core。 |
WidgetInstance |
由 Runtime 管理的实例边界。 | 必须有 root、setVisible(bool)、destroy();可选 portals()、snapshot()。 |
ctx.lifecycle |
所有副作用的唯一归属。 | 监听、observer、timer、rAF、媒体实例都必须可登记和逆序释放。 |
ctx.profile |
按 Widget id 隔离的持久化服务。 | 数据必须 JSON 可序列化;无 DOM、无全局键名污染。 |
ctx.portal |
body 级 sheet/menu 的所有权与显隐。 | Runtime 能在 hide/destroy 时处理受声明的 portal。 |
Contract Alpha 应明确标记为“0.4 experimental”,允许 0.5 发生一次可控的破坏性调整;但其核心生命周期语义不应含糊。隐藏不销毁,销毁必释放,遗漏配置不等于销毁是 v0.3 已建立、应被保留的语义。2
4.2 Runtime Services 的设计原则
Runtime Services 的任务是消除每个新 Widget 必须重新解决的横切问题,而不是接管 Widget 的业务状态。Music 的歌单、Clock 的时间格式、未来组件的业务数据都属于 Host;空间坐标、可见性、手势、portal 所有权、生命周期、Profile 和 Launcher 才属于 Orbit。
| Service | 应提供的能力 | 不应承担的能力 |
|---|---|---|
lifecycle |
监听、timer、rAF、observer、async cancel 的注册和幂等清理。 | Widget 的业务流程或全局单例状态。 |
layout |
位置读写、边界 clamp、展开意图、停靠侧、视口变化。 | 具体 Widget 卡片的 DOM/CSS。 |
gesture |
短按、长按、拖拽、取消与指针会话。 | “短按播放”或“长按打开某业务菜单”的业务决定。 |
visibility |
显隐过渡和 aria-hidden 协调。 |
销毁后偷偷保留业务资源。 |
profile |
分 Widget 命名空间的本地状态、导入/导出。 | 账户、云端同步和行为追踪。 |
a11y |
Launcher 聚焦、键盘关闭、reduced motion 约定。 | 强制每个 Widget 使用同一视觉风格。 |
五、v0.4 工作流与里程碑
Milestone 0:基线冻结与版本契约(1 个短迭代)
此阶段不新增用户可见功能。首先固定 v0.3 的回归矩阵,明确 Music、Clock、Launcher、ghost fallback 和动态页面恢复的当前行为。将版本号建立为单一来源,并让 package、Runtime、Demo 与文档从同一变量读取,消除目前 0.3.0、Phase 4、Phase 5 并存的口径。1 2 5
验收条件: README 不含死链;文档版本、Runtime version 和 Demo 文案一致;CI 中执行的测试集合与本地 npm test 完全一致;构建后 git diff --exit-code 为零。
Milestone 1:Lifecycle Hardening(v0.4 的发布阻断项)
首先修复 Music Host。所有 document/window 监听、setTimeout、requestAnimationFrame、MutationObserver、PJAX listener 和音频事件都必须使用具名 handler 或通过 LifecycleScope 注册。destroy() 不得只移除 DOM,而必须终止副作用、取消异步恢复路径并清除对旧 root 的引用。当前 Music Host 在 bind 阶段创建多个匿名全局 listener,而 destroy 路径没有对应清理;此项是 Contract Alpha 之前的必要修复。3
验收条件: 对 Music 与 Clock 分别执行 mount → destroy → mount 三次;每轮后监听计数不增加;单次 pointer/click 只产生一次业务反应;不存在 detached root 引用;音频 element 和自有 portal 都被释放。应在真实 DOM 测试环境中自动验证,而不是只依赖人工 Demo。
Milestone 2:Contract Alpha 的“步行骨架”
不要直接迁移全部 Music 逻辑。先用一个极小 Reference Badge 或 Status Widget 走通完整链路:register → mount → profile read/write → drag/dock → Launcher visible toggle → portal → destroy → remount。该 Widget 不追求产品价值,只用于证明 Contract 在作者不知道内部 Host 实现的条件下仍成立。
验收条件: 示例 Widget 的业务代码不直接操作 window 级全局监听、不复制 Drag/Snap/Dock 算法、不写 Orbit 私有 CSS class;其接入指南能由独立开发者在一个空静态页面中完成。
Milestone 3:迁移两个第一方 Host,并建立第三种异质 Host
Clock 是优先迁移对象:业务简单、生命周期已有基础、可作为 Contract 的参考实现。Music 第二个迁移,但不要求一次拆完全部 UI;重点是将其资源管理、Root/portal、位置持久化和手势接入 Runtime Services。第三种 Host 应选择与 Music/Clock 不同的业务形态,例如文档状态、站点公告或可折叠的页面工具。它不是“杀手功能”,而是验证 Contract 不绑定音频和时间的架构样本。
验收条件: 三个 Widget 均使用同一套 register/mount/destroy/visibility/Profile API;任意一个被隐藏或销毁不会破坏另两个;Launcher 只依赖 Contract 元数据而非硬编码 music/clock 标签。
Milestone 4:Profile Alpha 与可移植性
Profile 是 Orbit 开始产生用户连续性的最小形式,但 v0.4 只做本地优先与导入导出,绝不做云服务。Profile 需要记录 Runtime 级偏好(组件排序、可见性、布局)和每个 Widget 的隔离状态,附带 schema version、生成时间和可选站点 scope。
1 | { |
验收条件: Profile 可导出为 JSON、在同 schema 下重新导入、忽略未知 Widget、不因一个 Widget 状态损坏而阻断其他 Widget 恢复。它必须向用户说明数据留在浏览器本地,导出完全由用户主动触发。
六、测试与质量门禁
v0.4 的架构质量不能只由纯函数单测证明。现有项目已经对 LifecycleScope、Registry 和 ExpandPolicy 做了基础测试,但 CI 只调用 test:unit,没有执行本地 npm test 中的 state normalization 检查。5 6 v0.4 必须建立分层验证。
| 层级 | 测试对象 | 发布门槛 |
|---|---|---|
| Unit | Layout、Gesture、Snap、Contract schema、Profile migration。 | 关键纯逻辑分支与错误输入全覆盖。 |
| DOM integration | mount/visible/destroy/remount、portal 所有权、Launcher、焦点。 | Music、Clock、Reference Widget 至少各有一条完整生命周期用例。 |
| Regression | 三 Widget 并存、隐藏全部、ghost 恢复、窄视口、reduced motion。 | 每次发布前自动执行。 |
| Manual device | iOS/Android 长按、拖拽、Dock、横竖屏、低端性能。 | 发布 checklist 中必须记录设备与浏览器。 |
| Package | build、dist sync、README link、npm pack --dry-run。 |
CI 阻断任何产物或文档漂移。 |
CI 最小命令应收敛为一个入口,例如 npm run verify;工作流和本地开发都调用它,避免 test 集合漂移。发布前应通过 npm pack --dry-run 检查:分发包必须含 Runtime、所有首批 Contract 文档、迁移说明、许可与可运行示例。
七、文档与生态交付物
v0.4 的交付不只是 JS 产物。必须同时交付一个“让外部人成功”的文档闭环,且用非仓库作者的视角写作。
| 文件 | 面向对象 | 必须回答的问题 |
|---|---|---|
docs/CONTRACT-ALPHA.md |
Widget 作者 | 如何声明、挂载、持久化、使用 portal、处理销毁。 |
docs/HOST-MIGRATION-v0.4.md |
v0.3 Host 维护者 | registerHost 如何迁移;哪些 API 仍兼容;何时废弃。 |
docs/PROFILE.md |
用户与主题维护者 | 数据保存在何处;如何导出、导入和清除;schema 如何演进。 |
examples/reference-widget/ |
独立开发者 | 如何从零接入一个不依赖内部私有模块的 Widget。 |
docs/SECURITY.md |
部署者 | 外部音源、CSP、动态文本、第三方 Widget 的边界。 |
CHANGELOG.md |
全部使用者 | Contract Alpha 的稳定性等级、已知限制与升级动作。 |
八、v0.4 发布定义(Definition of Done)
v0.4 可以发布,必须同时满足以下条件:
- Lifecycle 可信。 Music、Clock 和 Reference Widget 都通过至少三次连续 destroy/remount 的自动 DOM 测试;无重复全局监听、无残留 portal、无已销毁 root 的异步复活。
- Contract 可用。 一个不依赖 Orbit 内部源码的 Reference Widget 能仅通过公开 Contract 接入 Runtime,并使用拖拽、显隐、Launcher、Profile 与销毁服务。
- Core 不再硬编码首批 Widget。 Launcher 标签、可见性、portal 所有权和实例列表由 Contract 元数据提供;内置 Widget 只是注册者。
- Profile 可移植。 用户可安全导入导出本地 Profile;未知 Widget 和 schema 兼容失败均有清晰、非破坏性的行为。
- 质量闭环存在。 CI 使用统一 verify 命令,覆盖 unit、DOM integration、构建产物、文档链接和 npm pack;README 与发布包无死链。
- 对外语义诚实。 版本仍标示 Contract 为 Alpha,不承诺未经真实外部使用验证的 1.0 稳定性。
九、v0.4 的北极星指标
不要用 GitHub Star、Widget 数量或代码行数评估 v0.4。真正的北极星问题是:
一个不熟悉 Orbit 内部代码的开发者,能否在不复制拖拽、吸附、移动端手势、显隐、焦点管理、生命周期和本地状态代码的情况下,完成一个可发布 Widget 的接入?
建议记录如下事实指标:外部示例 Widget 的业务代码行数;接入一个空站点的步骤数;destroy/remount 回归次数;三 Widget 并存时的手势冲突数;以及独立开发者首次接入成功率。这些指标比新增 Feature 更能说明 Orbit 是否开始拥有“被依赖”的理由。
十、风险与决策原则
最大的风险不是进度慢,而是过度抽象。Contract 若要求每个 Widget 理解太多布局和状态细节,就会重复当前 Music Host 的复杂性;若 Contract 太薄,又无法减少接入样板。因此 v0.4 应只固定生命周期、可见性、服务请求、portal 所有权与 Profile 五类横切语义,将复杂业务 UI 保留给 Host。
第二个风险是把 Profile 误做成锁定用户。Orbit 应坚持 local-first、可导出、可删除,只有在多个独立站点/主题真的使用同一 schema 后,才讨论可选同步。真正有价值的不可替代性来自互操作与兼容性网络,而不是封闭数据。
第三个风险是“官方示例伪装成生态”。v0.4 的三个 Widget 可以全部由核心仓库维护,但其中至少一个要以外部作者文档、独立目录和仅使用公开 API 的方式实现。这样才能发现 Contract 的缺口,而不是继续用内部知识绕过它。





