Files
7418andClaude Opus 4.6 5d7850a180 refactor: complete UI governance — lint rules, icon/color unification, component splits
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>
2026-03-12 01:02:28 +08:00

140 lines
6.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 设计模式与组件一致性治理
> 完成时间: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.tsx`
- `src/components/chat/MessageInputParts.tsx`
- `src/components/chat/SlashCommandPopover.tsx`(重构)
- `src/components/layout/SessionListItem.tsx`
- `src/components/layout/ProjectGroupHeader.tsx`
- `src/components/layout/chat-list-utils.ts`
- `src/components/settings/provider-presets.tsx`
- `src/components/settings/PresetConnectDialog.tsx`
- `src/components/settings/WorkspaceTabPanels.tsx`
- `src/components/settings/WorkspaceConfirmDialogs.tsx`
- `src/components/settings/WorkspaceStatusCards.tsx`
- `src/components/settings/workspace-types.ts`
### Hooks
- `src/hooks/useAssistantTrigger.ts`
- `src/hooks/useChatCommands.ts`
- `src/hooks/useCliToolsFetch.ts`
- `src/hooks/useCommandBadge.ts`
- `src/hooks/usePopoverState.ts`
- `src/hooks/useProviderModels.ts`
- `src/hooks/useSlashCommands.ts`
- `src/hooks/useStreamSubscription.ts`
- `src/hooks/useUpdateChecker.ts`
### Lib
- `src/lib/constants/commands.ts`
- `src/lib/constants/command-icons.ts`
- `src/lib/file-utils.ts`
- `src/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
## 已知遗留(不在本轮治理范围)
1. **ai-elements 层**:`prompt-input.tsx`(1,345L)仍含 30+ thin re-export wrappers;`file-tree.tsx` 有可访问性 warning
2. **全仓 80 warnings**:主要是 tests 中的死代码、bridge adapters 中的 unused vars、`stream-session-manager.ts` 中的 unused imports
3. **CodeBlock 两套并存**:`chat/CodeBlock.tsx`(Prism)和 `ai-elements/code-block.tsx`(Shiki),计划统一为 Shiki
4. **`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`