一个 Host,多种 Provider:Host 适配计划
状态:仅为规划,不具规范性。 本文不会改变
spec/protocol.md 或安全边界。本文记录的是一套策略:
在不放弃纯数据模型和多 Provider 组合能力的前提下,缩小 AFUI 渲染界面和手写前端之间的质量差距。
适用对象包括邮件分诊、CMS 管理后台、服务器监控、游戏等差异很大的 Provider。
问题
一个可信 Host 必须渲染任意 Provider,而现在它几乎用同一种方式渲染所有 Provider: 每个 unit 都变成一个网格 tile,每个记录列表都变成同一种内联 master/detail, 每个 capability 结果都变成一行状态文本。结果是安全、通用,也通用地平庸。 邮件界面需要焦点和阅读流;管理后台需要高密度表单和批量编辑;监控界面需要一眼可读的实时面板; 游戏需要棋盘和回合。无论 CSS 打磨得多好,单一的统一排列都不可能让所有场景都感觉正确。
差距不在协议。Provider 已经发布了好界面所需的语义信息:salience、layout role / relation、
summary、actor 状态选择等;参考 Host 几乎没有消费这些信息。真正的问题是:
剩下的设计判断应该放在哪里。
设计判断可以放在哪里?
手写前端好用,是因为设计者做了成百上千个小决策:什么内容应该一眼看完,什么内容值得单独成页, 什么应该并排,按 Enter 会发生什么。每一个决策都必须有一个归属。候选位置只有四个:
- Provider,通过更丰富的协议 hint。 速度快,也最了解领域,因为 Provider 知道自己的业务。 但把具体表现形式放进 wire format,会破坏协议存在的三个关键性质:两个 Provider 的像素级计划无法组合成一个界面; 面向 Web 的 hint 对终端或语音 viewer 没有意义;展示权威跨过信任边界,正是规范禁止的夹带。 详细的语义 hint 可以接受;详细的视觉 hint 是有害的。
- Host,以代码实现。 安全且可组合,但单一通用 renderer 最多停在“还可以的 dashboard”。 Host 代码要跨 Provider 扩展,只能编码模式,不能编码每个 app 的专用布局。
- 第三种 artifact:viewer 拥有、声明式、按 (provider, host) 匹配。 这是一份具体展示决策文件, Host 在用户策略下应用它。它可以由任何人编写:Provider 团队、agent、用户; 因为是否应用永远是 viewer 的选择。协议已经勾勒了机制:用户 override 放在用户自己的 section; Host 有 init-assets 逃生口。只是它还没有成为一等 artifact。
- 运行时 agent。 适应性最强,但协议自己的约束 #3 仍然成立:好默认值不能依赖 AI。 运行时 AI 布局昂贵、不可复现,而且今天大多数用户并不能使用。
计划是:模式放在 Host 中 (2),具体决策放在声明式 artifact 中 (3),agent 作为 artifact 的一种作者 (4), 协议 hint 只做少量、严格语义化的补充 (1)。 没有任何单一位置可以包打天下; 每个位置只承担它在结构上最适合承担的决策。
平台类比
这种形态可行的证据,就是每一个原生平台。iOS 从未发布一个读取 app 像素计划的布局引擎; 它发布的是一小组优秀的交互模式:navigation stack、split view、list、sheet, 再加上 human interface guidelines。成千上万个 app 由这些模式组装而成,同时仍然感觉原生且好用。 App 声明意图;平台提供交互模式;用户天然获得一致性。
AFUI Host 应该在这个意义上成为平台,而不是网站生成器。它的价值来自把少数交互 archetype 做到足够精致, 让每个匹配某个 archetype 的 Provider 都继承这种精致,包括键盘模型、焦点行为、空状态、响应式折叠等。 这些能力不是任何 per-app hint 能跨媒介承载的。
四层适配
每个 Provider 都落在能满足需求的最低层;更高层只做细化,不能替换低层。 一个 space 永远能在 Tier 0 渲染,这是安全地板,任何上层都不能破坏它。
| Tier | 内容 | 作者 | 覆盖范围 |
|---|---|---|---|
| 0 | Canonical kind + 默认 shell | Host 代码 | 一切,且安全 |
| 1 | Genre shell:特定 archetype 的排列方式 | Host 代码 | 约 90% 的操作型界面 |
| 2 | Arrangement pack:声明式 per-provider 调优 | Provider 团队、agent 或用户 | 最后一公里的打磨 |
| 3 | 可信 renderer module:用户安装的 viewer 代码 | 用户信任的人 | 长尾场景,例如实时 canvas |
Tier 0:地板(今天已存在,但需要密度修复)
当前参考 Host 需要升级到真正消费它收到的语义信息:salience 映射到密度
(subtle facts unit 应该是 badge strip,而不是半屏 tile);master_detail 映射到带全高 reader 的 focus stack;
overlay 映射到被召出的 drawer;request 结果获得通用 report surface。
这些修复是所有上层的前提,即使不做后续计划,也值得独立完成。
地板层可以遵守一个有用纪律:每个 unit 必须能以三种 zoom level 渲染:
glance(由 summary 生成的 badge 或一行文本)、compact(一行或小卡片)、
full(unit 的完整界面)。Shell 和 pack 之后只选择 zoom level 和位置;它们永远不发明新的渲染方式。
Tier 1:Genre shell
Genre 是 Host 实现的完整、有主张的交互 archetype: slot 结构、导航模型、键盘语法、实时行为、空状态。初始词表按我们实际看到的界面选择:
| Genre | 交互模式 | 典型 Provider |
|---|---|---|
triage | 队列 -> 聚焦 reader -> 每项一个动词,键盘优先 | 邮件、工单、review 队列 |
collection_admin | 树/过滤导航 -> 表格 -> 记录编辑器,批量操作 | CMS、库存、商店后台 |
monitor | 实时 tile 总览 -> 下钻到详情和历史 | 服务器状态、流水线、集群健康 |
reader | 文档优先,目录,宽正文 | 文档、报告、长笔记 |
conversation | 时间线 + composer,最新内容在底部 | 聊天、评论线程、agent session |
board | facts 的空间网格 + 受回合限制的 move capability | 围棋、卡牌、tile-based 管理游戏 |
console | 终端优先,周围有辅助状态 | 运维盒子、REPL、agent terminal |
Provider 在自己的 section 中用一个 advisory fact 声明 genre,例如
"surface": { "genre": "triage" }。这个声明和其他 hint 一样只是建议:
不支持该 shell 的 Host 会忽略它并回退到 Tier 0;错误的 genre 只会让界面没那么方便,不会变得不安全。
如果没有声明 genre,Host 可以从已发布的信息推断(role / relation / kind:
一个主要由 log + facts + attention summary 组成的 section 看起来就是 monitor),
因此现有 Provider 不改动也能改善。
Genre shell 也是多 Provider shell 的归属位置:每个 owner 都是一个 app,
由自己的 genre shell 渲染成 workspace;dock 用来切换 app;一个全局 attention rail 聚合所有 Provider 的
summary.attention_binding badge,并跳转到所属 unit。混合 Provider 是这个方向的核心承诺,
也正是 per-provider 视觉 hint 做不到的地方:两个手工布局无法合并,
但两个 genre shell 可以在同一个 attention rail 和同一套交互语法下并排存在。
Tier 2:Arrangement pack
Arrangement pack 是一个小型声明式文件,记录某个 Provider 在某个 Host 家族上的具体展示决策, 也就是通用 shell 无法知道的最后一公里判断:
{
"afui_pack": "0.1",
"match": { "name": "afmail" }, // 按 advisory section name/genre 匹配
"genre": "triage", // 确认或覆盖推断结果
"slots": {
"queue": { "unit": "inbox_triage" },
"reader": { "unit": "message_reader", "measure": "reading" },
"glance": { "units": ["mailbox_status"], "zoom": "glance" },
"drawer": { "units": ["archived_cases", "notifications"] }
},
"zoom": { "push_queue": "compact" },
"order": ["inbox_triage", "active_cases"],
"accent": "host_token:indigo" // 命名 Host theme token,绝不携带具体值
}
使它保持在信任模型内的规则:
- 只声明,只管展示。 Pack 命名 unit、slot、zoom level、排序和 Host 定义的主题 token。 它不携带代码、URL、capability grant、risk 变化;未知字段是惰性的。Pack 可以让界面更丑,但不能让界面不安全。
- 在用户策略下应用。 安装 pack 是用户行为,类似选择主题;即使它由 Provider 随 spore 作为推荐一起发布也是如此。 Host 的层级是:owner hint < genre shell < pack < 用户自己的 override。用户永远优先。
- 限定在 Host 家族内,对协议不可见。 Pack 是 Host artifact;wire format 永远不携带它。 终端 Host 可以读取同一个 pack 中媒介无关的部分(zoom、order),忽略其余部分。
Pack 是路线图的关键,因为每个作者时代产出的都是同一种 artifact。 今天,人类手写 afmail 的 pack。明天,本地 agent 加入一个新 space,读取 facts 和 hints, 草拟 pack,headless 截图验证,迭代,然后询问用户是否保留。 再往后,用户对运行时 copilot 说“把 log 放大一点”,它修改同一个 pack (或把等价的 override fact 写入用户自己的 section,这正是协议已存在的原生运行时通道)。 手写时代建设的东西不会在 agent 到来后被丢弃;agent 能力只改变谁来写这个文件,以及写得多频繁。
Tier 3:可信 renderer module
有些界面永远无法只靠数据 hint 渲染出原生质量:实时游戏 canvas、地图、节点图编辑器。
对此,协议已经给出了答案:由用户安装的可信 viewer 定制。把它具体化:
renderer module 是 用户安装的 viewer 侧代码(永远不能由被观察数据选择或交付),
注册到某个 kind 或 section,从同一批 facts 渲染,并且只能通过同一条 capability/request 路径行动,
受同样的 risk gate 约束。board genre 的围棋游戏不需要 module;
60 fps 动作游戏需要 module,这很诚实。AFUI 以数据承载它的状态和 move,
module 只是用户选择信任的 viewer 的一部分。
“来自 Provider 的非常详细布局 hint” 怎么办?
一个过渡想法是:让 Provider 发布丰富的布局细节,这样一个笨 Host 今天也能渲染得好。 这个想法对了一半。对的部分是:Provider 应该 能发布详细的展示判断, 短期质量也不应该 等到 agent 成熟。错的只是通道: 如果通过协议作为 hint 推送,这些细节会变成 Web 形状、不可组合、跨越信任边界, 而且之后还必须再拆掉。
Pack 就是 这个过渡层,只是绕过 wire format,而不是穿过 wire format: Provider 发布它原本想发布的详细主张,放在 space 旁边,作为 viewer 在用户策略下应用的推荐。 协议因此仍然对终端 viewer、语音 viewer、组合的多 Provider 界面保持干净。 同样的细节、同样的作者、同样的短期收益;没有长期损害,也不会在 agent 成熟后需要迁移走。
这个计划需要的协议相邻补充只有很小一部分,而且严格保持语义化,继续符合 View Hint Profile 的性质:
advisory surface.genre 声明,以及在实践证明需要时增加 per-unit 默认 zoom hint。
没有 panel,没有 pixel,没有 column。
Agent:两个角色,但都不是渲染前提
- 设计时(近期): agent 是 pack 作者。它读取一个 space,草拟 pack,用 headless screenshot 验证, 迭代,然后由用户保留结果。这是批处理工作:每个 Provider 只需要一次有能力的 agent run, 不需要每个用户机器上都有 agent;输出随后服务该 Host 的所有用户。 因此 agent 稀缺不是阻碍:生态可以现在为旗舰 Provider 手写 pack,随着 agent 普及再让它们接管作者工作。
- 运行时(更晚): agent 是 copilot,通过编辑用户 pack 或把展示 override fact 写入用户自己的 section 来调整界面。 这两个通道都是 Host 已经会从人类那里接受并尊重的通道。Agent 提议;确定性的 Host 渲染。
在两个角色里,agent 产出的都是人类也会产出的同一种声明式 artifact。 Host 从不要求 AI 才能渲染,因此协议的“好默认值不能依赖 AI”仍然成立。
这个计划不追求什么
- 与定制品牌前端和营销网站做像素级一致。目标是操作型界面的原生平台质量: 达到优秀内部工具或优秀原生 app 的标准,并通过共享交互模式实现。
- 运行时 AI 布局合成,或任何每次运行结果都不同的渲染路径。
- Provider 交付代码或样式权威。任何 tier 都不允许,永远不允许。
路线图
- Phase 0:地板。 Salience->density 映射、三种 zoom level、focus-stack reader、 summoned overlay、通用 request-result surface、基于增量 emit 的重新渲染。 (独立成立;修复今天 afmail 体验中最糟糕的部分。)
- Phase 1:genre。 在 View Hint Profile 中加入
surface.genre作为 advisory field; 先做triage和monitorshell(afmail 和服务器监控是诚实且相反的测试对象); 为未声明 space 做 genre 推断;实现 owner-as-app workspace shell,带 dock 和全局 attention rail。 - Phase 2:pack。 Pack format
0.1、layering 和 consent 规则、手写 afmail reference pack、 spore 发布 pack 的约定,以及由真实 CMS 形状 Provider 驱动的collection_adminshell。 - Phase 3:agent 作者。 Pack 草拟 agent workflow(space -> draft -> headless screenshot -> iterate -> user approval); 通过 user-section override fact 提供运行时 copilot 通道。