Files

5.0 KiB

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. 没有违反上面的「必须遵守」与「禁止」。