# Frontend Quality Rules

本文件定义前端页面从“能用”到“可交付”的最低质量尺子。它服务于 `spec-to-ui-to-code-workflow.md`、`design-brief.md`、`image-to-frontend-spec.md`、`component-map.md`、`screen-states.md` 和 `visual-parity-review.md`，不替代项目自己的品牌规范或产品需求。

## 1. 质量分级

| 等级 | 定义 | 必备证据 | 不能声称 |
| --- | --- | --- | --- |
| P0 可用 | 主路径能完成，关键控件可操作 | 主路径 story 或 E2E，基本截图 | 生产级体验 |
| P1 完整 | default / loading / empty / error / permission / long content / mobile 有规格和验证 | 状态矩阵、story 覆盖、移动端截图 | 视觉精修完成 |
| P2 精修 | 视觉层级、响应式、动效、可访问性和文案有明确标准并通过评审 | visual parity、a11y smoke、responsive matrix | 可无限复用 |
| P3 生产级 | 架构可维护，组件边界清晰，性能预算和回归证据完整 | frontend architecture、performance budget、regression evidence | 无需后续治理 |

`feature_impl` 的前端任务如果没有写明目标等级，默认按 P1 验收；用户可见核心流程默认至少 P2。

## 2. 审美反模式

以下情况出现任意一项，Stage 1 或视觉评审应要求返工，除非 Design Brief 明确说明原因：

- 页面只由大块卡片堆叠组成，缺少信息层级。
- 全页面灰白无重点，主要操作、危险操作和状态反馈不可区分。
- 使用无意义渐变、装饰图形或营销式 hero 来包装操作型后台页面。
- 所有按钮、标签、状态、表格行的视觉权重相同。
- 表格密度失控：行高、列宽、数字、标签和操作区没有扫描节奏。
- 移动端只做横向滚动，没有重排、折叠或卡片化策略。
- loading / empty / error 只是一行临时文案，没有恢复路径。
- 图标、颜色或动画只起装饰作用，不能帮助识别状态或操作。

## 3. 动效规则

动效必须解释状态变化，不承担主要信息表达。

| 场景 | 推荐动效 | 时长 | 约束 |
| --- | --- | --- | --- |
| 弹窗 / 抽屉打开 | opacity + scale 或 translate | 120-180ms | 必须有焦点管理 |
| 筛选 chip 增删 | opacity + slight translate | 100-160ms | 不得推动整页大范围跳动 |
| 表格刷新 | skeleton / row fade | 120-220ms | 高频刷新时禁用行级动画 |
| 空态 / 错误态切换 | opacity | 120-180ms | 文案和恢复操作优先 |
| 保存成功 / dirty 清除 | button state + inline status | 120-180ms | 不得只依赖 toast |

硬约束：

- 首选 `transform` 和 `opacity`，不得用会持续触发布局的动画属性。
- 必须支持 `prefers-reduced-motion` 或等价 reduced motion 开关。
- 动效不能隐藏错误、禁用、权限不足或保存失败。
- 动效失败时页面仍必须可用。

## 4. 响应式规则

每个用户可见页面都必须写清楚 desktop / tablet / mobile 的布局策略。

| 视口 | 必须说明 | 常见策略 |
| --- | --- | --- |
| Desktop >= 1200px | 主内容宽度、侧栏、表格列、操作区位置 | 横向筛选条 + 数据表 + 分页 |
| Tablet 768-1199px | 筛选换行、列显隐、操作折叠 | 筛选条换行，次要列隐藏 |
| Mobile < 768px | 筛选入口、表格替代形态、底部操作、触控尺寸 | 筛选抽屉，表格转卡片，分页简化 |

移动端禁止只依赖横向滚动完成主要流程。若表格必须横向滚动，必须说明原因、最小宽度、冻结列或摘要卡片替代方案。

## 5. 组件复用边界

- `ui primitives`: 只放按钮、输入、弹窗、菜单、表格基础结构等无业务语义组件。
- `data-table`: 只放排序、筛选、分页、列显隐、空态容器等表格通用行为。
- `features/<domain>`: 放业务组件、业务 hooks、字段映射、权限逻辑和页面组合。
- `api-client`: 放契约生成或手写 API 调用，不直接依赖 UI。
- `model/schema`: 放类型、枚举、表单校验和 URL query 解析。

复用准入：

- 新通用组件必须至少有两个明确使用场景，或是基础 primitive。
- 业务语义不稳定时留在 feature 内，不提前抽到 `packages/ui`。
- 通用组件必须有 props 契约、状态矩阵、story 和可访问性说明。
- generated types 可以作为数据输入类型，不能决定 UI primitive 的命名和职责。

## 6. 交互与文案规则

每个关键操作必须定义：

- 触发条件、禁用条件、加载反馈、成功反馈、失败反馈。
- inline error、toast、modal confirm 的使用边界。
- dirty 状态如何产生、如何保存、何时清除。
- 重试、取消、关闭、返回是否保留用户输入。
- 空态文案、错误文案、权限文案、按钮文案和长文本截断规则。

空态必须区分“系统没有数据”和“筛选后没有结果”。错误态必须包含用户可执行的恢复路径。

状态不是给评审看的页面说明。`loading / empty / error / disabled / permission / mobile` 等状态必须由真实状态源驱动，例如 API 请求状态、权限结果、空数据集合、表单 dirty/valid 状态、路由或 query 参数、feature flag、响应式断点、story controls 或可控 mock。页面可以有用户可见文案，但不能出现“这里是 loading 状态”“权限态展示如下”等占位说明来冒充实现。

每个 P0/P1 状态都必须能在代码和证据中追溯到：

- 状态源：store、query、hook、props、fixture、mock response、viewport 或权限判断。
- 渲染分支：组件条件分支、状态组件、disabled 属性、error boundary、empty renderer 或 responsive layout。
- 触发路径：用户操作、API 响应、测试 seed、story control、query 参数、mock scenario 或 viewport。
- 复验证据：story、interaction test、E2E 步骤、截图矩阵或 visual parity finding。

## 7. 可访问性规则

前端规格至少覆盖：

- 键盘路径和焦点顺序。
- 弹窗、抽屉、菜单的 focus trap 和关闭方式。
- 表单错误与输入控件的关联。
- 加载、保存成功、失败的屏幕阅读器提示。
- 色彩不能作为唯一信息来源。
- 移动端触控目标不小于 44x44px，除非平台规范另有说明。

## 8. 性能预算

前端设计阶段必须说明是否存在以下风险：

- 首屏依赖过多组件、图标、图表或动画库。
- 表格超过 100 行、复杂筛选、虚拟滚动或服务端分页需求。
- 图片、头像、缩略图或远程媒体影响布局稳定。
- 动效或重排可能影响滚动和输入响应。

如果页面包含复杂表格、图表或列表，必须在 `frontend-architecture.md` 或 DEV-PLAN 中写明 client/server 分界和性能验证方式。

## 9. 证据要求

前端任务完成声明至少包含：

- story / interaction / E2E 中的对应状态覆盖。
- desktop 和 mobile 的真实浏览器截图。
- visual parity 结论。
- 可访问性 smoke 结论或明确豁免。
- 性能风险检查结论；复杂页面需附性能预算验证。

## 10. State Surface Gate

前端任务进入实现前，必须把状态暴露方式写进 plan 或当前 task 的 `state_surface`。可接受的状态表面包括：

- Storybook / story 文件：适合组件、页面片段、状态矩阵和视觉回归。
- 真实 route + query / fixture：适合页面级状态、权限态和深链路复现。
- Mock scenario / MSW / API fixture：适合 loading、empty、error、权限和异常返回。
- E2E seed / test data：适合跨页面流程、保存、删除、批量操作和副作用验证。
- Viewport matrix：适合响应式、移动端重排和触控行为。

每个 P0/P1 状态至少要有一个可运行入口，并写明评审看哪里、回归跑什么、失败证据放哪里。状态只存在于设计稿、截图或聊天说明中时，Stage 1 应要求补状态表面，而不是让实现任务继续猜。
