Files
inkreach-agent/docs/plugins/DESIGN.md
T

175 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DESIGN.md · 插件视觉一致性规范(beautifului 审美)
> 读者是**写 UI 的 Agent/开发者**。目标:任何来源的插件,界面观感与宿主完全一致,
> 达到 [beautifului.dev](https://www.beautifului.dev/)(MIT)的质感。
> 规则分两类:**强制**(可机器校验/一眼可辨)与**默认**(无充分理由不得偏离)。
> 契约问题看 [../plugins.md](../plugins.md);本文件只管"长什么样"。
> Token 单一事实源:`apps/desktop/src/renderer/src/styles/beautifului.css`(= foundation 原版),
> 组件参考源码归档在 `docs/ui-ref/beautifului/*.json`(26 个 registry)。
## 0. 两条铁律
1. **色值只允许来自 token**。任何 `#hex`/`rgb()`/`oklch()` 硬编码(规范中列出的除外)都算违规;
语义永远用 token 名表达(`text-ink-3` 而不是 `text-gray-400`)。
2. **不发明新组件形态**。先查本文 §4 标准模式与公共组件库;确需新形态时,
用 §1 的 token 组合拼装,不引入新色/新圆角/新阴影。
## 1. Token 表(单一事实源:`peer://assets/tokens.css` = 宿主 beautifului.css)
### 表面(背景)——冷调近白,亮暗两套完整 token
| token | 意图 |
|---|---|
| `--page` | 页面最底层背景 |
| `--canvas` | 画布(primary 按钮的文字色取这里) |
| `--surface` | 卡片/面板表面(纯白) |
| `--inset` | 内嵌区域(输入框底、预览底、代码块底) |
| `--hover` / `--hover-2` | 悬停态一级/二级 |
### 文字——中性 ink ramp(深灰,不用纯黑)
| token | 意图 |
|---|---|
| `--ink` | 主文字(标题、关键值) |
| `--ink-2` | 次级文字(正文说明) |
| `--ink-3` | 弱化文字(占位、提示、元信息、路径) |
### 边线——实色 hairline,不用 alpha
| token | 意图 |
|---|---|
| `--line` | 常规边框(卡片、列表分隔) |
| `--line-strong` | 强边框(输入框、可点击描边) |
| `--line-soft` | 极弱分隔 |
### 强调与语义——蓝色 accent,语义色只作"调味料"
| token | 意图 |
|---|---|
| `--accent` / `--accent-ink` / `--accent-tint` | 焦点环/选区/选中态/链接(accent-ink 负责可交互文本,保 AA 对比) |
| `--green` / `--green-tint` | 成功/正常 |
| `--orange` / `--orange-tint` | 警示 |
| `--red` / `--red-tint` | 错误/危险 |
### 阴影——「ring + 分层柔影」,不是纯 hairline
| token | 意图 |
|---|---|
| `--shadow-btn` | ring(line-strong) + xs——secondary 按钮 |
| `--shadow-card` | ring(line) + sm——卡片 |
| `--shadow-raised` | ring(line) + md——悬浮件 |
| `--shadow-overlay` | ring(line) + lg——弹层/模态 |
| `--shadow-inset-field` | 输入框内嵌阴影 |
| `--shadow-hairline` | 纯 ring(无投影)|
暗色模式 ring 自动切换为低透明白渐进(0.10→0.15 按浮起高度),阴影加深——
**黑白主题不是简单反色**:暗色有一套专属 elevation 表达。
### 圆角体系(关键差异)
| token | 值 | 用途 |
|---|---|---|
| `--radius-chip` | 6px | chips、小徽标 |
| `--radius-control` | 8px | 输入框、菜单行、icon 按钮 |
| `--radius-card` | 10px | 卡片 |
| `--radius-window` | 14px | 窗口级容器(composer 外壳、弹窗) |
| `rounded-full` | 999px | **按钮(pill 是核心语言)**、状态点、头像 |
### 动效——强缓动曲线,内置 ease 太弱
| token | 值 | 用途 |
|---|---|---|
| `--ease-out-strong` | cubic-bezier(0.23, 1, 0.32, 1) | 大位移(滑层、展开、fade-up) |
| `--ease-in-out-strong` | cubic-bezier(0.77, 0, 0.175, 1) | 往返位移 |
| `--ease-link` | cubic-bezier(0.16, 1, 0.3, 1) | 下划线/微交互 |
| 控件默认 | 150ms ease-out | pill 按钮、悬停反馈 |
| 按压 | `active:scale-[0.96]` | 所有可点 pill |
## 2. 类名词汇(宿主 Tailwind 映射,renderer 组件直接用)
背景 `bg-page / bg-canvas / bg-surface / bg-inset / bg-hover / bg-hover-2 / bg-accent / bg-accent/10` ·
文字 `text-ink / text-ink-2 / text-ink-3 / text-accent / text-accent-ink / text-red` ·
边框 `border-line / border-line-strong / border-dashed border-line` ·
圆角 `rounded-chip`(6)`rounded-control`(8)`rounded-card`(10)`rounded-window`(14)`rounded-full`(pill)·
阴影 `shadow-btn / shadow-card / shadow-raised / shadow-overlay`。
## 3. 字号与间距
- 基准 14px/1.5,`letter-spacing -0.01em`;字号阶:`11px`(元信息/徽标)→ `12px`(chips、正文说明)→ `12.5/13px`(行标题、按钮)→ `14px`(正文)→ `16px`(页面标题)。**不使用其他字号**。
- 间距基数 4px:卡片内边距 `px-3 py-2.5`(紧凑)或 `primitive-card-pad`(12px),区块间 `space-y-1.5`,小节间 `pt-3`。
- 图标 12–16px 线条风格,`currentColor`;**一律 lucide**(宿主白名单 `plugin-icons.tsx`),禁止自绘 svg。
- 颜色只作"调味料":结构永远是中性 ink + line,彩色仅出现在状态/数值/选中。
## 4. 标准模式
> **renderer 组件优先用组件库**:`@aso/plugin-sdk/ui` 的 `Button`/`SettingRow`/`LoadingState`/
> `ErrorState`/`EmptyState`/`Badge`… 已按本规范实现(见 [L6](./07-ui-components.md))。
> 下面的 HTML 形态留给 iframe 插件页作者。
### 按钮(pill 是核心语言)
```html
<!-- primary:墨底 canvas 字 + 内高光;accent:蓝底白字;secondary:白底 ring+xs -->
<button class="inline-flex h-[27px] items-center justify-center gap-1.5 rounded-full px-3
text-[13px] font-medium leading-none transition-[transform,background-color,opacity]
duration-150 ease-out active:scale-[0.96] bg-accent text-white hover:bg-accent-ink
shadow-[inset_0_1px_0_rgba(255,255,255,0.14)]">主操作</button>
<!-- quiet:透明直到悬停(密集工具行) -->
<button class="h-7 rounded-full px-2.5 text-[12px] text-ink hover:bg-hover">次要</button>
```
### 设置行(label 左 / 控件右)
```html
<div class="flex items-center justify-between gap-3 rounded-card border border-line px-3 py-2.5">
<div class="min-w-0">
<div class="text-[12.5px] text-ink">标题</div>
<div class="mt-0.5 text-[11px] leading-4 text-ink-3">说明文字</div>
</div>
<!-- 控件 -->
</div>
```
### 卡片(window 级容器用 14px)
```html
<div class="rounded-window border border-line bg-surface shadow-card">…</div>
```
### 徽标 / 空态 / 开关
徽标 `rounded-chip bg-hover px-1.5 py-0.5 text-[10px] text-ink-3`;
空态 = 虚线卡(同旧版);开关 `h-5 w-9`(行内)/ `h-6 w-10`(表单级),
滑块 `size-4/5 rounded-full bg-white shadow`,开 `bg-accent`、关 `bg-line-strong`。
## 5. 动效范式
- 悬停反馈:150ms ease-out,只变 background-color/text(配 `transition-colors`)
- 内容进场:`fade-up 400ms var(--ease-out-strong)`(聊天消息、卡片)
- 弹层:`pop-in`(scale 0.95→1)+ `shadow-overlay`
- 滑动高亮层(菜单/标签条):220ms `cubic-bezier(0.23,1,0.32,1)`(GlideMenu)
- 流式输出:光标 2px `--ink` 常亮→结束后闪烁;`prefers-reduced-motion` 全局降级
## 6. iframe 插件页附加规则
- `<head>` 必须引入 `<link rel="stylesheet" href="peer://assets/tokens.css" />`,并用同名 token 编译自己的 Tailwind
- 主题跟随宿主推送(`useHarnessTheme`),**禁止** `prefers-color-scheme` 自测;亮暗两套 token 都要核对
- 与宿主的视觉差异应趋近于零:同 token + 同类名词汇 + 同字号阶 + 同圆角/阴影体系
## 7. renderer 组件附加规则
- 类名直接用宿主词汇(§2),无需引样式;**优先 import 公共组件库**([L6](./07-ui-components.md)),不要手写标准模式
- React 必须来自 `ctx.react`(见 [L4](./05-renderer-components.md)),否则 hooks 失效
- 禁止操作宿主 DOM(document 全局查询/改样式),一切交互限制在自己的组件子树
- 机器关卡:`pnpm plugin:check <插件目录>`(hex 色值 / React 副本 / 三态缺失等,见 [L7](./08-performance.md))
## 8. 自检清单(提发布前)
- [ ] `pnpm plugin:check <插件目录>` 0 error(机器关卡,见 [L7](./08-performance.md))
- [ ] 无硬编码色值/阴影/圆角(铁律 1)
- [ ] 圆角落在体系上:chip 6 / control 8 / card 10 / window 14 / pill 999
- [ ] 字号都在 §3 的阶上;按钮是 pill(rounded-full)
- [ ] 深浅色都检查过(暗色不是反色:ring 是低透白、阴影更深)
- [ ] 复用了 §4 模式(renderer 直接用组件库)而非新造形态
- [ ] iframe 页引了 tokens.css 且无 `prefers-color-scheme`