gbrain 设计系统解析:从 Voice 规则、设计 Token 到服务端 SVG 图表的完整设计规范
【免费下载链接】gbrainGarry's Opinionated OpenClaw/Hermes Agent Brain项目地址: https://gitcode.com/gh_mirrors/gb/gbrain
gbrain 的DESIGN.md是管理后台(admin SPA)与校准(Calibration)体系的设计系统事实来源(source of truth),它诞生于 v0.26.0 admin SPA 工作中沉淀下来的事实 Token,并在 v0.36.1.0 Hindsight 校准波次的设计评审中被正式化。本文以该文档为骨架,结合仓库源码与测试,完整拆解 gbrain 的语音(Voice)规则、颜色/字体/间距 Token、布局约束、服务端渲染 SVG 图表的 XSS 防护姿态,以及如何在新增 UI 时正确使用这份规范。
文档定位:一切 UI 问题的校准目标
DESIGN.md在仓库中的作用非常明确:它是/plan-design-review与/design-review流程的校准目标。当出现"这个 UI 是否符合系统风格"的疑问时,答案就在这里,而不是散落在各个页面组件里。文档开篇即说明其来源:
- v0.26.0 期间 admin SPA 工作时,设计 Token 以事实(de facto)形态落在 admin/src/index.css 的
:root变量中; - v0.36.1.0 Hindsight 校准波次的设计评审(design review)中,这些事实 Token 被正式整理为文档;
- 此后该文档成为所有 UI 变更评审的"靶子"(calibration target)。
这一"先有事实代码、后有规范文档"的演进路径意味着:文档中的每一个 Token 都可以在源码中找到对应实现,两者必须保持同步。
Voice:像懂你过去的聪明朋友,而不是临床评分系统
gbrain 对用户可见文案有一个统一过滤器:所有用户可见字符串都必须以"懂你的聪明朋友"的口吻表达,而不是临床式的评分系统。这体现在五个具体规则上:
- 第二人称,允许缩写——用 "you / your",允许 "you're / it's";
- 落地于用户可验证的具体数据——"2 of 3 missed" 胜过 "Brier 0.31";
- 绝不居高临下——禁止 "we recommend"、"according to your data" 这类措辞;
- 短——叙述性文字 25 词以内,状态类文字一行以内;
- 数字必须翻译成真实结果——不能抛出未经翻译的抽象指标。
五个使用该 Voice 的 UI 表面(v0.36.1.0+)
| 表面(Surface) | 用途 |
|---|---|
pattern_statement | 校准画像中的模式陈述,复盘你在某领域的历史表现 |
nudge | 同步提交 take 后触发的实时轻推 |
forecast_blurb | 新 take 上的内联预测短句 |
dashboard_caption | 仪表盘图表标题 |
morning_pulse | 每日早间脉搏一行 |
gateVoice():单一入口、五表面共用
所有五个表面都通过 src/core/calibration/voice-gate.ts 中的gateVoice()单一函数把关。设计决策(源码注释中标注为 D24)是:五个表面共享一个 gate,模式相关的微调放在 gate 下发给评判模型的 rubric 里,而不是 fork 出多个 gate 实现——fork 会让 voice 规范漂移(改了一个表面,漏了另外四个)。
gateVoice()的核心接口如下:
export interface VoiceGateOpts<S> { mode: VoiceGateMode; // 表面类型,驱动 rubric 微调 generate: VoiceGateGenerator; // 每次生成一个候选字符串 templateFallback: { fn: VoiceGateTemplate<S>; slots: S }; // 兜底模板 maxAttempts?: number; // 默认 2(D11 决策) judge?: VoiceGateJudge; // 测试注入桩;生产用 Haiku rubric?: string; // 按模式覆盖 rubric(很少用) }执行流程(voice-gate.ts):
- 调用
generate()生成一个候选字符串; - 由 Haiku 评判模型根据该模式的 rubric 判定
conversational(通过)或academic(驳回),并附上不超过 80 字符的理由; - 若驳回,最多重试
maxAttempts(默认 2)次,每次把上一条驳回理由作为feedback传给生成器,引导下一次生成避开失败模式; - 若两次均失败(或生成器抛异常、输出为空),回退到 src/core/calibration/templates.ts 中的手写模板,绝不静默隐藏表面——那样会让 voice 质量无声劣化。
每个模式的 rubric 也定义在 voice-gate.ts 中(DEFAULT_RUBRICS),例如nudge模式要求"像朋友拍你肩膀,而不是警报系统""始终以具体下一步收尾(CLI 命令或提问)""30 词以内,绝不居高临下";dashboard_caption则要求"每个标题一句、只陈述一个具体事实、禁止营销腔"。
手写模板回退:可预测输出胜过语音质量轮盘
当两个候选都被驳回时,templates.ts 提供确定性回退。源码注释(D11,CEO review 决策)说明:预测性输出胜过语音质量轮盘——回退模板刻意听起来"有点机械"(可接受,因为用户在双次重生成失败时看到的是相同的形状,而不是随机的质量退化)。真实会话感来自 LLM 路径,模板只是安全网。
例如patternStatementTemplate:
export function patternStatementTemplate(s: PatternStatementSlots): string { const total = s.nRight + s.nWrong; if (total === 0) { return `Not enough resolved ${s.domain} calls yet to spot a pattern.`; } const direction = s.direction ?? (s.nWrong > s.nRight ? 'mixed' : 'mostly right'); return `Your ${s.domain} calls have a ${direction} record — ${s.nRight} of ${total} held up.`; }注意它遵守了 Voice 规则——"nRight of total held up" 是可验证的具体数字,而不是抽象 Brier 值。nudgeTemplate还有一个重要的历史修正(issue #3697):曾引导用户执行不存在的gbrain takes nudge --hush <pattern>子命令,现已改为说明 14 天自动静默机制(fireNudge写入take_nudge_log,冷却探针按(take_id, pattern)抑制重复)。
模板与模式之间存在契约约束:VOICE_GATE_MODES常量(templates.ts)固定了五种模式,测试套件以此校验"每个 Mode 必须有对应模板条目"(模式对等性)。
评判模型与测试接缝
生产环境的评判走gatewayChat调用,使用TIER_DEFAULTS.utility档位模型(Haiku)、maxTokens: 100;判定输出是 JSON:{"verdict":"conversational"|"academic","reason":"…"}。parseJudgeOutput()对结果做了健壮性处理:容忍代码围栏包裹与前置散文,解析失败一律按academic处理(reason=parse_failed),从而走模板回退而不是放行劣质 voice。
测试接缝是opts.judge——测试注入一个返回 verdict + reason 的桩函数,使 gate 可以完全在密闭环境(hermetically)运行,无需真实模型调用。这也是gateVoice的通用性体现:生成与评判分离,调用方只需提供generate与templateFallback。
颜色 Token:暗色主题是唯一主题
颜色 Token 定义在 admin/src/index.css 的:root中,SVG 渲染器在 src/core/calibration/svg-renderer.ts 中内联了与之一致的字面量(必须同步)。
| Token | 值 | 用途 |
|---|---|---|
--bg-primary | #0a0a0f | 页面背景 |
--bg-secondary | #14141f | 侧边栏、卡片 |
--bg-tertiary | #1e1e2e | 次级表面、边框 |
--text-primary | #e0e0e0 | 正文 |
--text-secondary | #888 | 标题、标签 |
--text-muted | #777 | 三级文本——TD2 从#555提升而来,为达 WCAG AA 对比度(约 5.5:1) |
--accent | #3b82f6 | 激活态、链接、主 CTA |
--success | #22c55e | 健康 / 正常状态 |
--warning | #f59e0b | Doctor 警告 |
--error | #ef4444 | 失败、破坏性确认 |
暗色主题与 WCAG 对比度
admin 是仅暗色主题,无亮色模式切换计划——因为 admin 是运维工具而非营销页面,用户本就长期在终端暗色环境中工作。源码注释记录了一次关键的可达性修复(v0.36.1.0 TD2):--text-muted从#555提升到#777,对比度从 4.0(低于 WCAG AA 对正文要求的 4.5)提升到约 5.5,通过 AA。该改动全局作用于 Dashboard、Agents、RequestLog 与新增的 Calibration 页。
文档给出的完整对比度核算:
- 正文
#e0e0e0在#0a0a0f上 → 约 14:1,达 AAA; - 三级文本
#777在#0a0a0f上 → 约 5.5:1,达 AA(修复前 4.0 / 不达标); - 强调链接
#3b82f6在#0a0a0f上 → 约 5.7:1,达 AA。
Token 的实际使用方式
index.css 中 Token 被大量用于语义化类:.nav-item.active用border-left-color: var(--accent)标记激活项;.metric-value使用var(--font-mono)渲染数字;.badge-read/.badge-write/.badge-admin/.badge-success/.badge-error用 15% 透明度的同色系底色加前景色构成徽章;.status-active/.status-warning/.status-inactive用状态点表达健康状态;.warning-bar以rgba(245,158,11,0.15)底色 +var(--warning)边框呈现告警条。这些模式共同构成了"暗色 + 语义色"的视觉语言。
排版:Inter 负责 UI,JetBrains Mono 负责数字
| 变量 | 值 | 用途 |
|---|---|---|
--font-sans | Inter, system-ui, sans-serif | UI 文本、标题、正文 |
--font-mono | JetBrains Mono, monospace | 数字、slug、代码、终端风格数据 |
类型字号刻度目前仍是事实标准(de facto,尚未正式化):
- 18px:侧边栏 Logo / 页面标题;
- 14px:正文;
- 13px:导航项;
- 12px:图表标题、次级标签;
- 11px:高密度图表中的三级标签。
关键规则:表格与指标中的数字必须用 JetBrains Mono,这样列对齐是机械性的(等宽字体天然对齐);同时避免在同一行内混排 Inter 与 JetBrains Mono。在 index.css 中可以看到具体落地——.metric-value以 28px 等宽字体渲染核心指标,表格th用 11px 大写字母 + 0.5px 字距的 muted 色。
间距与密度:4 / 8 / 16 / 24 / 32 刻度
间距采用 4 的倍数刻度:4 / 8 / 16 / 24 / 32px。密度风格是 Linear-app 式:主区块之间 24–32px,行组之间 16px,行内 8px。文档指定Calibration 页(已批准的 variant-B 线框)是这一密度的典范示例——从 Calibration.tsx 的实现看,页面用padding: 32、章节间marginBottom: 32、标题下方marginBottom: 8~24,与刻度严格对应。
布局:侧边栏 + 内容区 + 内容宽度上限
- 左侧 200px 侧边栏;激活项以
--accent色 3px 左描边标记(见 index.css 的.nav-item.active); - 主内容区占据剩余宽度;
- 文本密集型页面(Calibration)最大内容宽度 720px,数据表格页面(Request Log)最大 960px;
- 禁止三列特性网格、彩色圆圈图标、装饰性 blob;
- 卡片必须"配得上存在"——大多数场景下"标题 + 内容"直接呈现即可,无需卡片边框包装。
侧边栏结构可在 App.tsx 中直接看到:sidebar-logo(GBrain)+sidebar-nav(Dashboard / Agents / Request Log / Calibration / Jobs Watch 五个导航项)+ 底部 "Sign out everywhere" 按钮,主区根据 hash 路由渲染对应页面。移动端(≤768px)侧边栏隐藏、主区 padding 收窄,这是布局规则对响应式的补充。
图表:服务端渲染 SVG,纯函数,零图表库
gbrain 的图表架构是本文档最具技术特色的部分:由服务端渲染 SVG,通过 src/core/calibration/svg-renderer.ts 中的纯函数实现(数据 → SVG 字符串),无 DOM、无 React 组件、无图表库。选择该架构的理由(设计文档中标注为 D23):
- 图表逻辑贴近数据计算(chart logic stays close to the data math);
- 零新增客户端图表库依赖;
- SVG 可访问(含文本标签)、可缩放、可直接复制粘贴进 PR 描述与文档;
- 为未来的 admin 图表(contradictions trend、takes scorecard 等)确立了先例。
四个图表渲染器(v0.36.1.0)
| 渲染器 | 输出 |
|---|---|
renderBrierTrend({ series }) | 迷你趋势线 + 0.25 基线参考线(Brier 0.25 是"始终 50% 下注"的基准) |
renderDomainBars({ bars }) | 按领域横向准确率条 |
renderAbandonedThreadsCard(threads) | 文本行 + "revisit now" 链接 |
renderPatternStatementsCard(statements) | 可点击下钻锚点 |
各渲染器的实现细节值得注意:
renderBrierTrend(svg-renderer.ts):默认 600×180,y 轴固定 [0, 0.4](0 = 完美,0.25 = 50% 基线),0.25 处以虚线stroke-dasharray="2,3"绘制基线,折线用--accent色;空数据时渲染svgEmpty()提示文案("No Brier-trend data yet (need 5+ resolved takes)");renderDomainBars(L137-L165):每行 28px 高,条宽按accuracy钳制在 [0,1],右侧标注accuracy% · n=样本量;renderAbandonedThreadsCard(L180-L207):声明文本超过 70 字符截断加省略号(服务端无法测量文本宽度),每行带 conviction、静默月数及默认指向/admin/calibration/revisit/<takeId>的 "revisit now" 链接;renderPatternStatementsCard(L217-L238):90 字符截断,锚点默认指向/admin/calibration/pattern/<index>支持点击下钻。
XSS 防护姿态
由于 SVG 通过dangerouslySetInnerHTML注入 admin SPA,安全姿态是三层防御(svg-renderer.ts 与 Calibration.tsx 均有注释说明):
- 服务端转义:所有调用方可控字符串经
escapeXml()(转义& < > " ')后才进入 SVG; - 数值强转:数字输入统一
.toFixed()强制转换; - 端点鉴权:SVG 端点由
requireAdmin中间件门控,admin SPA 通过TrustedSVG包装器渲染。
admin 端ChartSvg组件通过api.calibrationChart(type)拉取image/svg+xml,加载中显示ariaLabel + "loading...",出错则渲染role="alert"的错误块——这正好呼应下文"加载态/错误态"的交互规范。
Calibration 页的空状态即特性
Calibration.tsx 是空状态设计的教科书实现:当还没有校准画像时,页面不是显示 "no data available",而是给出具体的下一步——"No calibration profile yet. Builds after 5+ resolved takes.",并直接内联一段可复制的命令gbrain dream --phase calibration_profile,告诉用户如何构建画像。这正是 DESIGN.md 中"空状态 IS 特性:温暖 + 主行动 + 上下文"规则的最佳示例。
交互模式
- 键盘导航是 CLI 交互表面的硬性要求:propose-queue 评审使用 J/K/空格/u/q 快捷键(gmail 风格);
- 加载态:显示 "Loading...";200ms 以内的操作不显示 spinner;
- 错误态:说出失败对象 + 给出下一步,绝不写 "an error occurred — please try again"。
路线图:v0.37+ 还未落地的部分
文档如实列出了尚未完成的部分,避免将现状当作最终形态:
- 类型字号刻度的正式化(当前值为事实标准,未强制);
- 动画 Token(admin SPA 目前刻意零动画,v0.37 可能增加细微的进度/加载过渡);
- 打印样式表;
- 亮色模式(未计划——见上文"暗色主题是唯一主题");
- 组件库抽取(React 组件目前内联在
admin/src/pages/下,尚无<Button>/<Card>抽象层)。
如何使用这份文档:新增 UI 的四步检查
当为 gbrain 新增 UI 表面时,文档给出明确的四条纪律:
- 先复用现有 Token,再谈新 Token——新 Token 必须走
/plan-design-review评审; - 匹配 Voice 规则——校准表面的任何用户可见字符串上线前都要过
gateVoice(); - 匹配间距刻度与密度——"Linear 式的平静清晰"优先于"仪表盘卡片马赛克";
- 匹配排版——Inter 用于 UI,JetBrains Mono 用于数字。
文档本身也强调它是一个活的目标(living target)而非冻结规格:重大变更需要经过/plan-design-review以保持系统整体一致。这与仓库中 docs/designs 目录下的设计提案文档互为表里——前者沉淀已落地的设计决策,后者承载进行中的设计探讨。
小结
gbrain 的DESIGN.md用一份精炼文档同时锁定了"怎么说"(Voice 规则与gateVoice()流水线)、"长什么样"(颜色/字体/间距/布局 Token)、"怎么画图"(服务端 SVG 纯函数 + XSS 三层防护)和"怎么演进"(评审流程 + 路线图)。它不是抽象的视觉规范,而是与 admin/src/index.css、src/core/calibration/voice-gate.ts、src/core/calibration/svg-renderer.ts、src/core/calibration/templates.ts、admin/src/pages/Calibration.tsx 一一对应的可执行规范——任何"这 UI 是否符合系统"的疑问,都能在这份文档及其源码落地点中找到答案。
【免费下载链接】gbrainGarry's Opinionated OpenClaw/Hermes Agent Brain项目地址: https://gitcode.com/gh_mirrors/gb/gbrain
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考