状态:已采纳(MVP 基线)
apps/web/src/
app/ 路由、主题、全局 Provider
design-system/
tokens/ CSS variables、主题和尺寸
primitives/ Button、Dialog、Input 等
patterns/ 组合型通用模式
components/
pattern/ PatternGrid、BeadPalette、MaterialSummary
preview/ PreviewToolbar、IroningControl
project/ ProjectHeader、SaveStatus
features/
import/ 导入流程
conversion/ 转换流程
editor/ 图纸编辑流程
preview-3d/ 3D 预览流程
export/ 导出流程
state/ Zustand slices 和 selector
workers/ 图像转换 Worker
lib/ API、IndexedDB、埋点和工具
- Tailwind CSS:布局和设计令牌映射,不允许业务页面自行发明颜色体系。
- Radix Primitives:Dialog、Tooltip、Select、Tabs、Slider 等需要焦点管理和键盘交互的无样式基础组件。
- Storybook:维护组件的默认、悬停、禁用、加载、错误、空数据和极端内容状态。
- Vitest + Testing Library:组件行为测试。
- Playwright:桌面/移动页面流程和截图回归。
Radix 提供的是交互基础而不是视觉主题,所有样式仍由项目令牌控制。官方说明其组件遵循 WAI-ARIA 模式,并处理常见的焦点管理和键盘导航:Radix Accessibility。
- 业务组件只能使用
design-system/tokens中的语义变量。 - 禁止在 feature 文件中新增全局 CSS 选择器。
- 禁止复制按钮、弹窗、表单控件的局部变体;新变体先补到设计系统。
- 组件的视觉状态使用
data-state或明确的 props,不通过父级选择器猜测。 - 2D/3D Canvas 的尺寸由布局容器提供,渲染器不负责页面布局。
- 任何组件都不能直接修改另一个 feature 的 Zustand 状态;通过领域事件或明确的 action 交互。
projectSlice 项目元信息、保存状态和版本
patternSlice Pattern 数据、选区、历史记录
conversionSlice 转换参数、进度、错误和结果
previewSlice 2D/3D 模式、相机、熨烫和材质
exportSlice 导出格式、任务状态和下载地址
uiSlice 面板开关、快捷键和 Toast
组件通过 selector 读取最小状态,避免修改一颗豆导致整个工作台重渲染。Pattern 大数组更新必须使用区域变更或批处理。
- Props 表达输入和事件,不把业务服务对象传进基础组件。
- 基础组件不依赖路由、项目 API 或 Pattern 算法。
- 组合组件只负责布局和交互编排,业务计算放在 hooks 或 packages。
- 每个产品组件同时提供默认、禁用、加载、错误和空数据 Story。
- 需要异步操作的组件暴露
status、error和retry,不自行吞掉异常。
Pattern 是唯一事实来源。编辑动作产生领域事件,例如:
type PatternCommand =
| { type: 'set-cell'; index: number; colorId: number }
| { type: 'fill-region'; indices: number[]; colorId: number }
| { type: 'clear-region'; indices: number[] }2D 网格和 3D 预览都订阅同一变更流。3D 只消费变更索引,不重新推导图案;统计模块也消费同一事件,防止三处结果不一致。
- 基础组件测试键盘、焦点、禁用和错误状态。
- 产品组件测试真实 Pattern、长色号和空数据。
- 关键工作台页面进行桌面和移动截图回归。
- 3D 页面固定测试场景:小图、2 万颗豆、大色彩数量、特殊材质和熨烫切换。
- 新增颜色或间距 token 必须说明使用场景,避免 token 泛滥。
Storybook 用于组件级状态目录;Playwright 用于完整页面和画布布局。Playwright 的截图基线必须在固定浏览器、字体和运行环境中生成,避免不同平台渲染造成无意义差异:Playwright Visual Comparisons。
提交 UI 代码前必须回答:
- 是否复用了已有基础组件和语义 token?
- 是否定义了加载、空、错误和恢复状态?
- 是否在桌面和移动布局都可完成核心动作?
- 是否会因为状态更新导致整个 Canvas 或 3D 场景重渲染?
- 是否需要新增组件、变体或 token?为什么?