Files
metabuilder/docs/refactoring/reference/REFACTORING_QUICK_REFERENCE.md

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 data
  • GitHubWorkflowJob - Job details
  • GitHubConfig - API config

IDE Feature:

  • IDEProject - Project metadata
  • IDEFile - File tree with content
  • GitConfig - Git credentials (update)
  • IDETestRun - Test results

Lua Feature:

  • LuaScriptLibrary - Reusable snippets
  • LuaScriptParameter - Parameter definitions
  • LuaTestRun - Execution history

Updated Models:

  • LuaScript - Add description field
  • User - 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

  1. Review this strategy with team
  2. Adjust timeline based on capacity
  3. Start with Phase 1 (GitHubActionsFetcher)
  4. Track progress with checklist
  5. Celebrate each milestone
  6. 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