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>
152 lines
4.6 KiB
JavaScript
152 lines
4.6 KiB
JavaScript
import { defineConfig, globalIgnores } from "eslint/config";
|
|
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
import nextTs from "eslint-config-next/typescript";
|
|
|
|
const eslintConfig = defineConfig([
|
|
...nextVitals,
|
|
...nextTs,
|
|
// Override default ignores of eslint-config-next.
|
|
globalIgnores([
|
|
// Default ignores of eslint-config-next:
|
|
".next/**",
|
|
"out/**",
|
|
"build/**",
|
|
"release/**",
|
|
"dist-electron/**",
|
|
"next-env.d.ts",
|
|
// apps/site generated artifacts
|
|
"apps/site/.next/**",
|
|
"apps/site/.source/**",
|
|
// External reference materials / vendored packages (not part of the main codebase)
|
|
"资料/**",
|
|
]),
|
|
|
|
// ── Governance rules for business components ──
|
|
{
|
|
files: [
|
|
"src/components/settings/**/*.{ts,tsx}",
|
|
"src/components/bridge/**/*.{ts,tsx}",
|
|
"src/components/chat/**/*.{ts,tsx}",
|
|
"src/components/gallery/**/*.{ts,tsx}",
|
|
"src/components/plugins/**/*.{ts,tsx}",
|
|
"src/components/skills/**/*.{ts,tsx}",
|
|
"src/components/project/**/*.{ts,tsx}",
|
|
"src/components/layout/**/*.{ts,tsx}",
|
|
"src/components/cli-tools/**/*.{ts,tsx}",
|
|
"src/app/**/*.{ts,tsx}",
|
|
],
|
|
rules: {
|
|
// Discourage native HTML controls — use ui/ components instead
|
|
"no-restricted-syntax": [
|
|
"warn",
|
|
{
|
|
selector: "JSXOpeningElement[name.name='button']",
|
|
message: "Use <Button> from @/components/ui/button instead of native <button>.",
|
|
},
|
|
{
|
|
selector: "JSXOpeningElement[name.name='input']",
|
|
message: "Use <Input> from @/components/ui/input instead of native <input>.",
|
|
},
|
|
{
|
|
selector: "JSXOpeningElement[name.name='select']",
|
|
message: "Use <Select> from @/components/ui/select instead of native <select>.",
|
|
},
|
|
{
|
|
selector: "JSXOpeningElement[name.name='textarea']",
|
|
message: "Use <Textarea> from @/components/ui/textarea instead of native <textarea>.",
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
// ── Discourage Lucide imports project-wide ──
|
|
{
|
|
files: ["src/**/*.{ts,tsx}"],
|
|
rules: {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
patterns: [
|
|
{
|
|
group: ["lucide-react"],
|
|
message: "Use @phosphor-icons/react instead. See docs/ui-governance.md for mapping.",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
// ── Discourage direct Phosphor imports outside ui/ and ai-elements/ — use ui/icon.tsx ──
|
|
{
|
|
files: [
|
|
"src/components/settings/**/*.{ts,tsx}",
|
|
"src/components/bridge/**/*.{ts,tsx}",
|
|
"src/components/chat/**/*.{ts,tsx}",
|
|
"src/components/gallery/**/*.{ts,tsx}",
|
|
"src/components/plugins/**/*.{ts,tsx}",
|
|
"src/components/skills/**/*.{ts,tsx}",
|
|
"src/components/project/**/*.{ts,tsx}",
|
|
"src/components/layout/**/*.{ts,tsx}",
|
|
"src/components/cli-tools/**/*.{ts,tsx}",
|
|
"src/app/**/*.{ts,tsx}",
|
|
"src/hooks/**/*.{ts,tsx}",
|
|
],
|
|
rules: {
|
|
"no-restricted-imports": [
|
|
"warn",
|
|
{
|
|
patterns: [
|
|
{
|
|
group: ["@phosphor-icons/react"],
|
|
message: "Import icons from @/components/ui/icon instead. See docs/ui-governance.md.",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
// ── Raw status colors ──
|
|
// ESLint cannot lint inside className strings. Use `npm run lint:colors` (grep-based)
|
|
// to check for raw green/red/yellow/orange/blue-{400-700} usage in business components.
|
|
// Add `// lint-allow-raw-color` on lines where raw colors are intentional (e.g. diff syntax).
|
|
|
|
// ── Component file size limit ──
|
|
{
|
|
files: ["src/components/**/*.{ts,tsx}"],
|
|
ignores: [
|
|
"src/components/ui/**",
|
|
"src/components/ai-elements/**",
|
|
],
|
|
rules: {
|
|
"max-lines": ["warn", { max: 500, skipBlankLines: true, skipComments: true }],
|
|
},
|
|
},
|
|
|
|
// ── Patterns layer: no data logic imports ──
|
|
{
|
|
files: ["src/components/patterns/**/*.{ts,tsx}"],
|
|
rules: {
|
|
"no-restricted-imports": [
|
|
"error",
|
|
{
|
|
patterns: [
|
|
{
|
|
group: ["@/hooks/*", "@/hooks"],
|
|
message: "Pattern components must be pure presentation — no hooks imports.",
|
|
},
|
|
{
|
|
group: ["@/lib/*", "!@/lib/utils"],
|
|
message: "Pattern components must be pure presentation — no lib imports. Use @/lib/utils for cn() only.",
|
|
},
|
|
],
|
|
paths: [],
|
|
},
|
|
],
|
|
},
|
|
},
|
|
]);
|
|
|
|
export default eslintConfig;
|