5.0 KiB
5.0 KiB
web/ 契约(给人和 AI 代理)
本目录是 WebDrop 前端。动手前先读,冲突时以此文件与 ../docs/ 为准。
权威顺序
../docs/threat-model.md、../docs/protocol.md、../docs/api.md(协议与接口,不得违背)../docs/ui-design.md(视觉与交互)../docs/frontend.md(工程约定)- 本文件
- 代码
文档与代码不一致时,以文档为准。要改行为,先改文档再改代码。
范围
- 按用户要求修改项目文件,遵循对应目录的约定与测试要求。
@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/。 - 默认中文界面;提交信息沿用仓库风格。
完成的定义
一项改动完成,需要:
- 对应文档已更新(如行为有变)。
typecheck、lint:css、test、build全部通过。- 亮暗两套、手机与桌面宽度都看过。
- 没有违反上面的「必须遵守」与「禁止」。