Design pattern & component consistency governance across 120 files: ESLint governance (eslint.config.mjs): - Enforce ui/ components over native HTML controls (warn) - Ban Lucide imports (error), require Phosphor via ui/icon.tsx (warn) - Component file size limit 500 lines (warn, skip blanks+comments) - Patterns layer isolation (error: no hooks/lib imports) - Exclude 资料/ from lint scope Icon unification (ui/icon.tsx): - Unified barrel export with 86+ Phosphor icons - Decoupled types/index.ts from icon library (generic IconComponent type) - Separated command data (lib/constants/commands.ts) from icons (command-icons.ts) Color token system (globals.css): - Added semantic status tokens: success/warning/error/info with muted/foreground/border variants - Migrated 39 files from raw green/red/orange/amber/blue to semantic tokens - grep-based lint:colors script covers green/emerald/red/orange/yellow/amber/blue Component splits (all under 500 lines): - MessageInput 1659→444: MessageInputParts, SlashCommandPopover, 7 hooks, 2 libs - ChatView ~850→419: useStreamSubscription, useAssistantTrigger - ProviderManager 1081→406: provider-presets, PresetConnectDialog, useProviderModels - AppShell 527→470: useUpdateChecker - AssistantWorkspaceSection 908→499: WorkspaceTabPanels/ConfirmDialogs/StatusCards - ChatListPanel 677→484: SessionListItem, ProjectGroupHeader, chat-list-utils Other: - Migrated 80 native HTML controls to ui/ components across 38 files - Fixed 22 hook dependency / set-state-in-effect issues - Cleaned 7 dead code warnings - Rebuilt better-sqlite3 for ARM architecture - Added handover doc: docs/handover/ui-governance.md Lint status: 0 errors 0 warnings on touched files; typecheck pass; 374/374 tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6.5 KiB
6.5 KiB
设计模式与组件一致性治理
完成时间:2026-03-12 | 涉及 92 个文件 | 净减 ~2,900 行
治理目标
在保留现有视觉语言的前提下,建立统一的组件分层规则、消灭跨层重复模式、拆分超大组件、通过 ESLint 静态约束防止退化。
分层架构
src/components/ui/ → 纯 primitives(Button, Input, Select, Dialog…)
src/components/patterns/ → 复用展示模式(SettingsCard, FieldRow, StatusBanner…),零副作用
src/components/{feature}/ → 组合 hooks + patterns + 业务状态
src/app/ → 页面装配
src/hooks/ → 数据获取、状态管理
src/lib/constants/ → 纯数据常量,不引用 UI 层
已完成的治理项
1. ESLint 治理规则 (eslint.config.mjs)
| 规则 | 级别 | 作用域 | 说明 |
|---|---|---|---|
| 禁止原生 HTML 控件 | warn | 业务组件 + app 层 | <button/input/select/textarea> → 使用 ui/ 组件 |
| 禁止 Lucide 导入 | error | src/** |
统一使用 Phosphor,通过 ui/icon.tsx 入口 |
| 禁止直接引用 Phosphor | warn | 业务组件 + hooks | 应从 @/components/ui/icon 导入 |
| 组件文件行数上限 500 | warn | src/components/**(排除 ui/、ai-elements/) |
skipBlankLines + skipComments |
| Patterns 层隔离 | error | src/components/patterns/** |
禁止导入 @/hooks/* 和 @/lib/*(允许 @/lib/utils) |
| 外部资料目录排除 | — | 资料/** |
不参与主仓 lint |
2. 图标统一 (src/components/ui/icon.tsx)
- 所有业务组件通过
@/components/ui/icon统一入口导入 Phosphor 图标 - 目前导出 86+ 个图标 +
ICON_SIZE常量 +Icon/IconProps类型 types/index.ts中的IconComponent类型与 Phosphor 解耦,使用泛型 SVG 签名- 图标与命令数据分离:
lib/constants/commands.ts(纯数据)+lib/constants/command-icons.ts(图标映射),在hooks/useSlashCommands.ts中合并
3. 颜色 token 治理 (globals.css + lint:colors)
新增语义 token(light + dark):
--status-success-{muted,foreground,border} (原 green-*)
--status-warning-{muted,foreground,border} (原 orange/yellow/amber-*)
--status-error-{muted,foreground,border} (原 red-*)
--status-info-{muted,foreground,border} (原 blue-*)
- Tailwind v4 通过
@theme inline中的--color-status-*变量注册 npm run lint:colors检查green/emerald/red/orange/yellow/amber/blue-{400-700}用法- 例外行标注
// lint-allow-raw-color(如 diff 语法高亮、终端提示符)
4. Patterns 层 (src/components/patterns/)
| 组件 | 用途 |
|---|---|
SettingsCard |
统一的设置卡片容器(title + description + children) |
FieldRow |
设置项行布局(label + description + 右侧控件) |
StatusBanner |
success/warning/error/info 状态横幅 |
EmptyState |
空状态占位(icon + title + description + action) |
CommandList |
斜杠命令列表弹窗 |
5. 超大组件拆分
| 组件 | 原始行数 | 当前行数 | 抽出的子模块 |
|---|---|---|---|
| MessageInput | 1,659 | 444 | MessageInputParts, SlashCommandPopover, hooks: useChatCommands, usePopoverState, useSlashCommands, useCliToolsFetch, useCommandBadge, libs: message-input-logic, file-utils |
| ChatView | ~850 | 419 | useStreamSubscription, useAssistantTrigger |
| ProviderManager | 1,081 | ~406 | provider-presets, PresetConnectDialog, useProviderModels |
| AppShell | 527 | 470 | useUpdateChecker |
| AssistantWorkspaceSection | 908 | 499 | WorkspaceTabPanels, WorkspaceConfirmDialogs, WorkspaceStatusCards, workspace-types |
| ChatListPanel | 677 | 484 | SessionListItem, ProjectGroupHeader, chat-list-utils |
6. 原生 HTML 控件迁移
38 个文件中 80 处原生 <button/input/textarea/select> 替换为 ui/Button、ui/Input、ui/Textarea、ui/Select。
7. Hook 依赖与死代码清理
- 修复
react-hooks/exhaustive-deps和react-hooks/set-state-in-effect共 22 处 - 清除 unused imports/variables 共 7 处
- AppShell 中 5 个
set-state-in-effect错误通过 lazy initializer、derived state、eslint-disable-next-line组合解决
新增文件清单
组件
src/components/patterns/CommandList.tsxsrc/components/chat/MessageInputParts.tsxsrc/components/chat/SlashCommandPopover.tsx(重构)src/components/layout/SessionListItem.tsxsrc/components/layout/ProjectGroupHeader.tsxsrc/components/layout/chat-list-utils.tssrc/components/settings/provider-presets.tsxsrc/components/settings/PresetConnectDialog.tsxsrc/components/settings/WorkspaceTabPanels.tsxsrc/components/settings/WorkspaceConfirmDialogs.tsxsrc/components/settings/WorkspaceStatusCards.tsxsrc/components/settings/workspace-types.ts
Hooks
src/hooks/useAssistantTrigger.tssrc/hooks/useChatCommands.tssrc/hooks/useCliToolsFetch.tssrc/hooks/useCommandBadge.tssrc/hooks/usePopoverState.tssrc/hooks/useProviderModels.tssrc/hooks/useSlashCommands.tssrc/hooks/useStreamSubscription.tssrc/hooks/useUpdateChecker.ts
Lib
src/lib/constants/commands.tssrc/lib/constants/command-icons.tssrc/lib/file-utils.tssrc/lib/message-input-logic.ts
当前 Lint 状态
- 治理涉及文件:0 errors, 0 warnings
- 全仓:0 errors, 80 warnings(均在未涉及的文件中:tests、api routes、lib、ai-elements)
- Typecheck:通过
- Unit tests:374 pass, 0 fail
已知遗留(不在本轮治理范围)
- ai-elements 层:
prompt-input.tsx(1,345L)仍含 30+ thin re-export wrappers;file-tree.tsx有可访问性 warning - 全仓 80 warnings:主要是 tests 中的死代码、bridge adapters 中的 unused vars、
stream-session-manager.ts中的 unused imports - CodeBlock 两套并存:
chat/CodeBlock.tsx(Prism)和ai-elements/code-block.tsx(Shiki),计划统一为 Shiki catch { // ignore }模式:50+ 处静默错误,需逐步加 toast 或 console.warn
开发者须知
- 新建业务组件时,从
@/components/ui/icon导入图标,不要直接引@phosphor-icons/react - 颜色使用语义 token(
text-status-success-foreground),不用裸色值(text-green-500) - 新 pattern 组件放
patterns/,不引 hooks/lib(除@/lib/utils的cn()) - 组件超过 500 行时考虑拆分
- 提交前运行
npm run test+npm run lint:colors