mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 22:34:56 +00:00
12 KiB
12 KiB
Refactoring Quick Reference Guide
TL;DR Summary
Problem: 8 React components exceed 150 LOC limit (violates MetaBuilder architecture) Solution: Decompose into smaller components, extract logic to hooks and Lua Timeline: 3 weeks, 103 hours of work Impact: Improved modularity, testability, and data persistence
| Violation | Current | Target | Reduction |
|---|---|---|---|
| GitHubActionsFetcher | 887 LOC | 95 LOC | 89% ↓ |
| NerdModeIDE | 733 LOC | 110 LOC | 85% ↓ |
| LuaEditor | 686 LOC | 120 LOC | 82% ↓ |
Key Architectural Changes
1. Component Decomposition Pattern
Before:
MonolithicComponent.tsx (887 LOC)
- API fetching
- State management
- UI rendering
- Business logic
- Error handling
After:
MonolithicComponent.tsx (95 LOC)
├── SubComponent1.tsx (65 LOC) - Handle one thing
├── SubComponent2.tsx (80 LOC) - Handle one thing
├── SubComponent3.tsx (75 LOC) - Handle one thing
└── Hooks & Lua for business logic
2. State Management Shift
Before:
const [data, setData] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
// ... 8+ more useState
// Lost on page reload, no sharing
After:
// Database (Prisma) - Persistent
model WorkflowRun {
id: String
name: String
status: String
// ... shared across sessions and users
}
// React State - Ephemeral UI state
const [selectedId, setSelectedId] = useState(null) // UI only
const { runs } = useWorkflowActions() // From database
3. Logic Extraction Pattern
Before:
// In component, 200+ LOC of async operations
const fetchGitHubActions = async () => {
// Set loading, call API, handle errors, cache...
}
After:
// Custom hook - 50 LOC, testable, reusable
export function useGitHubActions() {
// All API logic, caching, error handling
return { runs, isLoading, error, refetch }
}
// In component - 5 LOC
const { runs, isLoading } = useGitHubActions()
4. Configuration as Data
Before:
const getStatusColor = (status: string) => {
if (status === 'completed') {
if (conclusion === 'success') return 'text-green-600'
// ... 50 lines of hardcoded logic
}
}
After (JSON Config):
{
"statusColors": {
"completed-success": "text-green-600",
"completed-failure": "text-red-600"
}
}
Usage:
const config = useUIConfig('GitHubActions')
const color = config.statusColors[status]
Implementation Roadmap
Week 1: Violation #1 (GitHubActionsFetcher)
Day 1-2: Database models + migrations
Day 3-4: useGitHubActions hook
Day 5: useWorkflowLogs + useWorkflowAnalysis hooks
Day 6: Sub-components creation
Day 7: Main component refactor + tests
Week 2: Violation #2 (NerdModeIDE)
Day 1: Database models (projects, files, git config)
Day 2-4: useIDEFileTree (3 hours) + other hooks
Day 5: File tree components
Day 6: Editor + toolbar components
Day 7: Main component refactor + tests
Week 3: Violation #3 (LuaEditor)
Day 1-2: Extract Monaco config + create hooks
Day 3-4: Parameter + execution components
Day 5: Security + snippet components
Day 6: Main component refactor
Day 7: Testing + documentation
File Structure After Refactoring
src/
├── components/
│ ├── GitHubActionsFetcher.tsx (95 LOC) ← Refactored
│ │ ├── GitHubActionsToolbar.tsx (65 LOC)
│ │ ├── GitHubActionsRunsList.tsx (80 LOC)
│ │ ├── GitHubActionsRunRow.tsx (45 LOC)
│ │ ├── GitHubActionsJobsPanel.tsx (100 LOC)
│ │ ├── GitHubActionsJobStep.tsx (50 LOC)
│ │ └── GitHubActionsAnalysisPanel.tsx (70 LOC)
│ │
│ ├── NerdModeIDE.tsx (110 LOC) ← Refactored
│ │ ├── IDEFileTreePanel.tsx (90 LOC)
│ │ ├── IDEFileTreeNode.tsx (60 LOC)
│ │ ├── IDEEditorPanel.tsx (85 LOC)
│ │ ├── IDEToolbar.tsx (65 LOC)
│ │ ├── IDEConsolePanel.tsx (70 LOC)
│ │ ├── IDEGitDialog.tsx (75 LOC)
│ │ └── IDENewFileDialog.tsx (65 LOC)
│ │
│ ├── LuaEditor.tsx (120 LOC) ← Refactored
│ │ ├── LuaScriptSelector.tsx (70 LOC)
│ │ ├── LuaCodeEditor.tsx (110 LOC)
│ │ ├── LuaMonacoConfig.tsx (120 LOC)
│ │ ├── LuaParametersPanel.tsx (95 LOC)
│ │ ├── LuaParameterItem.tsx (60 LOC)
│ │ ├── LuaExecutionPanel.tsx (100 LOC)
│ │ ├── LuaExecutionResult.tsx (80 LOC)
│ │ ├── LuaSecurityWarningDialog.tsx (75 LOC)
│ │ └── LuaSnippetLibraryPanel.tsx (85 LOC)
│ │
│ └── (existing components...)
│
├── hooks/
│ ├── useGitHubActions.ts (60 LOC)
│ ├── useWorkflowLogs.ts (50 LOC)
│ ├── useWorkflowAnalysis.ts (40 LOC)
│ │
│ ├── useIDEProject.ts (40 LOC)
│ ├── useIDEFileTree.ts (100 LOC)
│ ├── useIDEEditor.ts (40 LOC)
│ ├── useIDEGit.ts (60 LOC)
│ ├── useIDETest.ts (50 LOC)
│ │
│ ├── useLuaScripts.ts (40 LOC)
│ ├── useLuaEditor.ts (40 LOC)
│ ├── useLuaExecution.ts (60 LOC)
│ ├── useLuaSecurity.ts (30 LOC)
│ │
│ └── (existing hooks...)
│
├── lib/
│ ├── github-ui-config.ts (30 LOC)
│ └── (existing libs...)
│
└── seed-data/
└── lua-scripts/
├── filter-workflow-runs.lua
├── aggregate-logs.lua
└── (more scripts...)
Database Schema Summary
New Models (15 total)
GitHub Feature:
GitHubWorkflowRun- Workflow execution dataGitHubWorkflowJob- Job detailsGitHubConfig- API config
IDE Feature:
IDEProject- Project metadataIDEFile- File tree with contentGitConfig- Git credentials (update)IDETestRun- Test results
Lua Feature:
LuaScriptLibrary- Reusable snippetsLuaScriptParameter- Parameter definitionsLuaTestRun- Execution history
Updated Models:
LuaScript- Add description fieldUser- Track changes
Hook API Patterns
Pattern 1: Data Fetching Hook
export function useGitHubActions() {
const [runs, setRuns] = useState<WorkflowRun[]>([])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const refetch = useCallback(async () => {
setIsLoading(true)
try {
const data = await fetchFromAPI()
setRuns(data)
} catch (e) {
setError(e.message)
} finally {
setIsLoading(false)
}
}, [])
useEffect(() => {
refetch()
}, [])
return { runs, isLoading, error, refetch }
}
Pattern 2: State Management Hook
export function useIDEFileTree(projectId: string) {
const [files, setFiles] = useState<IDEFile[]>([])
const createFile = useCallback(async (parentId: string, name: string) => {
const newFile = await db.file.create({ projectId, parentId, name })
setFiles([...files, newFile])
}, [files])
return { files, createFile, deleteFile, updateFile }
}
Pattern 3: Derived State Hook
export function useLuaEditor(scripts: LuaScript[]) {
const [selectedId, setSelectedId] = useState<string | null>(
scripts.length > 0 ? scripts[0].id : null
)
const currentScript = useMemo(
() => scripts.find(s => s.id === selectedId) || null,
[scripts, selectedId]
)
return { selectedId, setSelectedId, currentScript }
}
Testing Strategy
Unit Test Template
describe('useGitHubActions', () => {
it('should fetch and cache workflow runs', async () => {
const { result } = renderHook(() => useGitHubActions(), {
wrapper: MockProviders
})
expect(result.current.isLoading).toBe(true)
await waitFor(() => {
expect(result.current.runs.length).toBeGreaterThan(0)
expect(result.current.isLoading).toBe(false)
})
})
})
Component Test Template
describe('GitHubActionsRunRow', () => {
it('should render workflow run data', () => {
const mockRun = { id: 1, name: 'Test', status: 'completed' }
render(
<GitHubActionsRunRow
run={mockRun}
isSelected={false}
onSelect={jest.fn()}
/>
)
expect(screen.getByText('Test')).toBeInTheDocument()
})
})
Migration Path (No Breaking Changes)
Step 1: Create New Infrastructure
1. Add new Prisma models
2. Create hooks (side-by-side with old code)
3. Create new sub-components
4. Create wrapper for backward compatibility
Step 2: Gradual Adoption
1. Tests use new hooks/components
2. Documentation shows new patterns
3. New features use new patterns
4. Old code still works
Step 3: Cleanup (Optional)
1. Remove old component after 1 release
2. Move any users to new version
3. Clean up deprecated code
Common Pitfalls to Avoid
❌ Don't: Props Drilling
// BAD - passing through 5 levels
<Component1 data={data} onChange={onChange}>
<Component2 data={data} onChange={onChange}>
<Component3 data={data} onChange={onChange}>
<Component4 data={data} onChange={onChange}>
<Component5 data={data} onChange={onChange} />
✅ Do: Use Context or Hooks
// GOOD - hook provides data
const { data, onChange } = useWorkflowContext()
<Component5 />
❌ Don't: Mix Data and UI
// BAD - API calls in component
export function MyComponent() {
const [data, setData] = useState(null)
useEffect(() => {
fetch('/api/data').then(setData) // Should be in hook!
}, [])
}
✅ Do: Separate Concerns
// GOOD - hook handles data
export function useMyData() {
const [data, setData] = useState(null)
useEffect(() => {
fetch('/api/data').then(setData)
}, [])
return data
}
export function MyComponent() {
const data = useMyData()
}
❌ Don't: Hardcode UI Configuration
// BAD - colors hardcoded
const statusColors = {
success: '#16a34a',
failure: '#dc2626'
}
✅ Do: Load from Database
// GOOD - config in database
const config = useUIConfig('GitHubActions')
const color = config.statusColors[status]
Success Metrics
Before Refactoring
- Average component size: 569 LOC
- Components > 150 LOC: 8
- Custom hooks: ~5
- Lua scripts: ~10
After Refactoring
- Average component size: 73 LOC
- Components > 150 LOC: 0 ✅
- Custom hooks: ~20 ✅
- Lua scripts: ~15 ✅
Next Steps
- Review this strategy with team
- Adjust timeline based on capacity
- Start with Phase 1 (GitHubActionsFetcher)
- Track progress with checklist
- Celebrate each milestone
- Repeat for Violations #2 and #3
Questions to Review
- Database schema approved?
- Hook API contracts clear?
- Component responsibility boundaries defined?
- Testing approach agreed?
- Timeline realistic?
- Team capacity sufficient?
- Risk mitigation plans ready?
Contact / Escalation
- Questions about architecture? → Review copilot-instructions.md
- Database schema issues? → Check prisma/schema.prisma
- Hook design help? → Look at existing hooks in src/hooks/
- Component composition questions? → Review /packages/ structure
Document Version: 1.0
Last Updated: 2025-12-25
Status: Ready for implementation
Approval: Pending ⏳