106 lines
5.0 KiB
Markdown
106 lines
5.0 KiB
Markdown
# web/ 契约(给人和 AI 代理)
|
|
|
|
本目录是 WebDrop 前端。动手前先读,冲突时以此文件与 `../docs/` 为准。
|
|
|
|
## 权威顺序
|
|
|
|
1. `../docs/threat-model.md`、`../docs/protocol.md`、`../docs/api.md`(协议与接口,不得违背)
|
|
2. `../docs/ui-design.md`(视觉与交互)
|
|
3. `../docs/frontend.md`(工程约定)
|
|
4. 本文件
|
|
5. 代码
|
|
|
|
文档与代码不一致时,**以文档为准**。要改行为,先改文档再改代码。
|
|
|
|
## 范围
|
|
|
|
- 按用户要求修改项目文件,遵循对应目录的约定与测试要求。
|
|
- `@webdrop/core` 未就绪时,在 `src/core-bridge/` 按文档接口做适配与假实现,不自行发明协议。
|
|
|
|
## 命令
|
|
|
|
```
|
|
npm run dev 开发
|
|
npm run typecheck 类型检查
|
|
npm run lint:css 样式检查(颜色、圆角、阴影、blur 必须走 token)
|
|
npm test Vitest
|
|
npm run build 生产构建
|
|
```
|
|
|
|
提交前 `typecheck`、`lint:css`、`test`、`build` 必须全部通过。
|
|
|
|
## 必须遵守
|
|
|
|
### 安全
|
|
|
|
- 取出 URL fragment 后,在任何异步代码之前 `history.replaceState`。
|
|
- 密钥 K 导入为不可导出的 `CryptoKey`;不把密钥、令牌、明文写入 `console`、URL、`localStorage`、错误信息。
|
|
- 链接含 `k` 就只接受 enc 且认证通过的帧;模式与链接不符直接拒绝。
|
|
- 同一 `(item_id, frame_index)` 只能加密一次;重试发送队列里已持久化的相同字节(protocol §5)。
|
|
- 不使用 `dangerouslySetInnerHTML`;预览走沙箱 iframe;raw 内容只当文本。
|
|
- 不引入第三方脚本、统计、CDN 字体。
|
|
|
|
### 分层
|
|
|
|
- 依赖单向:`app → features → ui`;`features → store → core-bridge`;`lib` 不依赖其他层。
|
|
- `ui/` 不依赖业务;`features/` 之间不互相导入,**唯一例外**:`features/channel/`、`features/home/`、`features/share/` 是组合层,可以导入 `features/item/`、`features/scan/`(home、share 还可导入 channel 里的 ActionBar 与 ComposeSheet);组件不直接调用网络或加解密。
|
|
- 不写 barrel(`index.ts` 汇总导出)。
|
|
- 所有页面跳转经 `app/navigation.ts` 的 `navigate()`,不直接操作 `history`。页面一加载就在 `app/entry.ts` 里同步取走并清除 fragment。**唯一例外**:弹层的返回键关闭,在 `lib/useBackToClose.ts`。
|
|
|
|
### 样式
|
|
|
|
- 颜色字面量只允许出现在 `src/styles/tokens.css`。
|
|
- 圆角、阴影、`backdrop-filter` 只能取 token。
|
|
- `backdrop-filter` 仅用于顶栏和底部输入栏,同屏不超过 2 个;必须有 `prefers-reduced-transparency` 与不支持时的回退。
|
|
- 阴影只用于浮层(菜单、弹窗、Toast);列表用 1px `--line` 分隔。
|
|
- 不引入 Tailwind、CSS-in-JS、UI 组件库、图标库、动画库。
|
|
|
|
### 状态与 React
|
|
|
|
- 条目状态由日志推导,用判别联合,`switch` 必须穷尽。
|
|
- 订阅最小派生值,不订阅整个 store;高频值(进度)不触发整列表重渲染。
|
|
- 派生状态在渲染期计算,不用 effect 同步;不在组件内定义组件。
|
|
- 条件渲染用三元,不用 `&&` 渲染可能为 `0` 的值。
|
|
- 加解密、装配、哈希在 Worker,主线程不处理大文件。
|
|
|
|
### 动效
|
|
|
|
- `ViewTransition` 一律 `default="none"`,`enter` 与 `exit` 成对;只用于有空间关系的变化(层级导航、条目增删)。
|
|
- 状态变化、重连、后台同步不加动画。
|
|
- 动画 CSS 只放在 `styles/transitions.css`,取自技能配方;必须尊重 `prefers-reduced-motion`。
|
|
- 方向性 `ViewTransition` 放在页面组件里,不放布局里。
|
|
|
|
### 文案
|
|
|
|
- 只做中文,文案直接写在组件里,不做 i18n。
|
|
- 安全相关文案必须诚实(见 `ui-design.md §7.4`):`ui_restrict_copy_download` 不是安全保证;raw 模式必须明确提示服务端可见;不夸大「匿名」「不可追踪」。
|
|
|
|
## 禁止
|
|
|
|
- 自动加载有下载上限(GATED)的文件内容:缩略图、内联视频一律等用户点击,滚动列表不能消耗下载次数。
|
|
|
|
- 为了「看起来现代」加装饰性渐变、霓虹、大圆角、pill 容器、眉标签、装饰性副标题。渐变只能用 `tokens.css` 里的渐变 token,且只表达状态或层次。
|
|
- 弹层不用 portal 就渲染在带 `content-visibility` 的列表项内(`position: fixed` 会失效)。
|
|
- 新增运行时依赖而没有书面理由(解决什么、为何不能自写、体积)。
|
|
- 提前抽象。同样的代码第二次出现才抽,第三次必须抽。
|
|
- 把颜色、间距、圆角写死在组件里。
|
|
- 静默吞掉认证失败或篡改;`tampered` 必须可见。
|
|
|
|
## 约定
|
|
|
|
- 文件单一职责,组件约 150 行为上限。
|
|
- 导出面最小化,不被使用的符号不导出。
|
|
- 注释只写「为什么」,并指向文档章节,例如 `// protocol §5: 重试必须发相同字节`。
|
|
- `store/` 与 `lib/` 的模块配 Vitest;用户可见流程配 Playwright。
|
|
- 路径别名 `@/` 指向 `src/`。
|
|
- 默认中文界面;提交信息沿用仓库风格。
|
|
|
|
## 完成的定义
|
|
|
|
一项改动完成,需要:
|
|
|
|
1. 对应文档已更新(如行为有变)。
|
|
2. `typecheck`、`lint:css`、`test`、`build` 全部通过。
|
|
3. 亮暗两套、手机与桌面宽度都看过。
|
|
4. 没有违反上面的「必须遵守」与「禁止」。
|