Files
metabuilder/docs/implementation/COMPONENT_MIGRATION_GUIDE.md
johndoe6345789 e717a1916b chore: Organize docs folder into logical subfolders
Grouped 100+ docs into categories:
- architecture/ - System design, DBAL, component architecture
- analysis/ - Status reports, assessments, migration analysis
- guides/ - Quick references, how-tos, integration guides
- implementation/ - Implementation details, migration guides
- packages/ - Package-specific docs (forum, notifications, etc)
- phases/ - Phase completion summaries and deliverables
- testing/ - E2E tests, Playwright, test architecture
- workflow/ - Workflow engine documentation

Root level retains: README, ROADMAP, AGENTS, CONTRACT, CLAUDE, PROMPT

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 17:55:28 +00:00

16 KiB

Component Migration Guide

Guide for adding declarative JSON components to existing packages.

Quick Start

Step 1: Create Component Folder

mkdir -p packages/my_package/component

Step 2: Create Metadata File

cat > packages/my_package/component/metadata.json << 'EOF'
{
  "entityType": "component",
  "description": "My package components",
  "components": [
    { "id": "comp_my_button", "name": "My Button" },
    { "id": "comp_my_card", "name": "My Card" }
  ]
}
EOF

Step 3: Define Components

cat > packages/my_package/component/components.json << 'EOF'
[
  {
    "id": "comp_my_button",
    "name": "My Button",
    "category": "form",
    "template": {
      "type": "Button",
      "props": { "variant": "{{variant}}" },
      "children": "{{label}}"
    },
    "props": { "label": "Click", "variant": "primary" }
  },
  {
    "id": "comp_my_card",
    "name": "My Card",
    "category": "layout",
    "template": {
      "type": "Card",
      "children": { "type": "CardContent", "children": "{{content}}" }
    },
    "props": { "content": "Card content" }
  }
]
EOF

Step 4: Bootstrap

POST http://localhost:3000/api/bootstrap

Components now available in database and can be used in pages.

Detailed Examples

Example 1: Basic Button Component

Scenario: You have a custom button style and want to make it customizable.

Before (Hardcoded):

// packages/my_package/src/components/MyButton.tsx
export function MyButton({ label }: { label: string }) {
  return (
    <Button variant="contained" color="primary">
      {label}
    </Button>
  )
}

// Usage in page.tsx
return <MyButton label="Click Me" />

After (Declarative):

// packages/my_package/component/components.json
[
  {
    "id": "comp_my_button",
    "name": "My Button",
    "description": "Custom branded button",
    "category": "form",
    "version": "1.0.0",
    "isPublished": true,
    "schema": {
      "type": "object",
      "properties": {
        "label": { "type": "string", "default": "Click" },
        "variant": {
          "type": "string",
          "enum": ["contained", "outlined", "text"],
          "default": "contained"
        },
        "color": {
          "type": "string",
          "enum": ["primary", "secondary", "error"],
          "default": "primary"
        },
        "disabled": { "type": "boolean", "default": false }
      }
    },
    "template": {
      "type": "Button",
      "props": {
        "variant": "{{variant}}",
        "color": "{{color}}",
        "disabled": "{{disabled}}"
      },
      "children": "{{label}}"
    },
    "props": {
      "label": "Click Me",
      "variant": "contained",
      "color": "primary",
      "disabled": false
    },
    "events": [
      { "name": "onClick", "description": "Fired when button clicked" }
    ]
  }
]

Usage in page:

// packages/my_package/page-config/page.json
[
  {
    "id": "page_example",
    "path": "/example",
    "title": "Example",
    "component": "example_root",
    "componentTree": {
      "type": "Box",
      "children": {
        "type": "comp_my_button",
        "props": {
          "label": "Save Changes",
          "variant": "contained",
          "color": "primary"
        }
      }
    }
  }
]

Benefits:

  • Customizable from admin panel
  • No code changes to update UI
  • Consistent styling across app
  • Can be used in multiple pages

Example 2: Complex Card Component

Before (Hardcoded):

// packages/dashboard/src/components/DashboardCard.tsx
export function DashboardCard({
  title,
  icon,
  content,
  footer
}: {
  title: string
  icon: React.ReactNode
  content: string
  footer?: string
}) {
  return (
    <Card>
      <CardHeader
        avatar={<Avatar>{icon}</Avatar>}
        title={title}
      />
      <CardContent>
        <Typography>{content}</Typography>
      </CardContent>
      {footer && (
        <CardActions>
          <Typography variant="caption">{footer}</Typography>
        </CardActions>
      )}
    </Card>
  )
}

After (Declarative):

[
  {
    "id": "comp_dashboard_card",
    "name": "Dashboard Card",
    "description": "Card for dashboard widgets",
    "category": "layout",
    "schema": {
      "type": "object",
      "properties": {
        "title": { "type": "string" },
        "icon": { "type": "string" },
        "content": { "type": "string" },
        "footer": { "type": ["string", "null"] },
        "showIcon": { "type": "boolean", "default": true }
      },
      "required": ["title", "content"]
    },
    "template": {
      "type": "Card",
      "children": [
        {
          "type": "CardHeader",
          "props": { "title": "{{title}}" },
          "children": {
            "type": "conditional",
            "condition": "{{showIcon}}",
            "then": {
              "type": "Avatar",
              "children": "{{icon}}"
            }
          }
        },
        {
          "type": "CardContent",
          "children": {
            "type": "Typography",
            "children": "{{content}}"
          }
        },
        {
          "type": "CardActions",
          "children": {
            "type": "conditional",
            "condition": "{{footer}}",
            "then": {
              "type": "Typography",
              "props": { "variant": "caption" },
              "children": "{{footer}}"
            }
          }
        }
      ]
    },
    "props": {
      "title": "Widget",
      "icon": "Star",
      "content": "Widget content",
      "footer": null,
      "showIcon": true
    }
  }
]

Example 3: Form Component

Before (Hardcoded):

export function LoginForm() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')

  return (
    <Box sx={{ maxWidth: 400 }}>
      <TextField
        label="Email"
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        fullWidth
      />
      <TextField
        label="Password"
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        fullWidth
        sx={{ mt: 2 }}
      />
      <Button variant="contained" fullWidth sx={{ mt: 3 }}>
        Sign In
      </Button>
    </Box>
  )
}

After (Declarative):

[
  {
    "id": "comp_login_form",
    "name": "Login Form",
    "category": "form",
    "template": {
      "type": "Box",
      "style": { "maxWidth": "400px", "margin": "0 auto" },
      "children": [
        {
          "type": "FormField",
          "props": { "label": "Email" },
          "children": {
            "type": "TextField",
            "props": {
              "type": "email",
              "placeholder": "{{emailPlaceholder}}",
              "fullWidth": true
            }
          }
        },
        {
          "type": "FormField",
          "props": { "label": "Password" },
          "children": {
            "type": "TextField",
            "props": {
              "type": "password",
              "placeholder": "{{passwordPlaceholder}}",
              "fullWidth": true
            }
          }
        },
        {
          "type": "Button",
          "props": {
            "variant": "contained",
            "fullWidth": true,
            "disabled": "{{isLoading}}"
          },
          "children": "{{isLoading ? 'Signing in...' : 'Sign In'}}"
        }
      ]
    },
    "props": {
      "emailPlaceholder": "you@example.com",
      "passwordPlaceholder": "••••••••",
      "isLoading": false
    }
  }
]

Example 4: Data Table Component

Before (Hardcoded):

export function UserTable({ users }: { users: User[] }) {
  return (
    <TableContainer>
      <Table>
        <TableHead>
          <TableRow>
            <TableCell>Name</TableCell>
            <TableCell>Email</TableCell>
            <TableCell>Role</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {users.map((user) => (
            <TableRow key={user.id}>
              <TableCell>{user.name}</TableCell>
              <TableCell>{user.email}</TableCell>
              <TableCell>{user.role}</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  )
}

After (Declarative):

[
  {
    "id": "comp_user_table",
    "name": "User Table",
    "category": "table",
    "template": {
      "type": "TableContainer",
      "children": {
        "type": "Table",
        "children": [
          {
            "type": "TableHead",
            "children": {
              "type": "TableRow",
              "children": [
                { "type": "TableCell", "children": "Name" },
                { "type": "TableCell", "children": "Email" },
                { "type": "TableCell", "children": "Role" }
              ]
            }
          },
          {
            "type": "TableBody",
            "children": [
              {
                "type": "TableRow",
                "children": [
                  { "type": "TableCell", "children": "{{users[0].name}}" },
                  { "type": "TableCell", "children": "{{users[0].email}}" },
                  { "type": "TableCell", "children": "{{users[0].role}}" }
                ]
              }
            ]
          }
        ]
      }
    }
  }
]

Note: Full iteration support would require template loops (future enhancement).

Example 5: Feature Grid Component

Before (Hardcoded):

export function FeatureGrid() {
  const features = [
    { icon: '⚡', title: 'Fast', desc: 'Lightning quick' },
    { icon: '🔒', title: 'Secure', desc: 'Enterprise security' },
    { icon: '📈', title: 'Scalable', desc: 'Grow without limits' },
  ]

  return (
    <Grid container spacing={2}>
      {features.map((feature) => (
        <Grid item xs={12} md={4} key={feature.title}>
          <Card>
            <CardContent>
              <div style={{ fontSize: '2rem' }}>{feature.icon}</div>
              <Typography variant="h6">{feature.title}</Typography>
              <Typography variant="body2">{feature.desc}</Typography>
            </CardContent>
          </Card>
        </Grid>
      ))}
    </Grid>
  )
}

After (Declarative):

[
  {
    "id": "comp_feature_grid",
    "name": "Feature Grid",
    "category": "layout",
    "template": {
      "type": "Grid",
      "props": { "container": true, "spacing": 2 },
      "children": [
        {
          "type": "Grid",
          "props": { "item": true, "xs": 12, "md": 4 },
          "children": {
            "type": "Card",
            "children": {
              "type": "CardContent",
              "children": [
                {
                  "type": "Box",
                  "style": { "fontSize": "2rem", "marginBottom": "1rem" },
                  "children": "⚡"
                },
                {
                  "type": "Typography",
                  "props": { "variant": "h6" },
                  "children": "Fast"
                },
                {
                  "type": "Typography",
                  "props": { "variant": "body2" },
                  "children": "Lightning quick"
                }
              ]
            }
          }
        },
        {
          "type": "Grid",
          "props": { "item": true, "xs": 12, "md": 4 },
          "children": {
            "type": "Card",
            "children": {
              "type": "CardContent",
              "children": [
                {
                  "type": "Box",
                  "style": { "fontSize": "2rem", "marginBottom": "1rem" },
                  "children": "🔒"
                },
                {
                  "type": "Typography",
                  "props": { "variant": "h6" },
                  "children": "Secure"
                },
                {
                  "type": "Typography",
                  "props": { "variant": "body2" },
                  "children": "Enterprise security"
                }
              ]
            }
          }
        },
        {
          "type": "Grid",
          "props": { "item": true, "xs": 12, "md": 4 },
          "children": {
            "type": "Card",
            "children": {
              "type": "CardContent",
              "children": [
                {
                  "type": "Box",
                  "style": { "fontSize": "2rem", "marginBottom": "1rem" },
                  "children": "📈"
                },
                {
                  "type": "Typography",
                  "props": { "variant": "h6" },
                  "children": "Scalable"
                },
                {
                  "type": "Typography",
                  "props": { "variant": "body2" },
                  "children": "Grow without limits"
                }
              ]
            }
          }
        }
      ]
    }
  }
]

Migration Checklist

  • Create /packages/[package]/component/ folder
  • Create metadata.json with entity type and component list
  • Create components.json with all component definitions
  • Define schema for each component (props validation)
  • Define template (component tree structure)
  • Set default props
  • Document events (if component emits events)
  • Test with POST /api/bootstrap
  • Verify components load in database: db.components.list()
  • Test rendering in a page
  • Update page definitions to reference new components
  • Remove hardcoded components from TypeScript
  • Commit changes

Common Patterns

Pattern 1: Conditional Display

{
  "type": "conditional",
  "condition": "{{isLoading}}",
  "then": {
    "type": "Spinner"
  },
  "else": {
    "type": "Button",
    "children": "Save"
  }
}

Pattern 2: Styled Box

{
  "type": "Box",
  "style": {
    "padding": "2rem",
    "backgroundColor": "{{backgroundColor}}",
    "borderRadius": "8px"
  },
  "children": "{{children}}"
}

Pattern 3: Form Field Wrapper

{
  "type": "FormField",
  "props": {
    "label": "{{label}}",
    "required": "{{required}}"
  },
  "children": {
    "type": "TextField",
    "props": {
      "placeholder": "{{placeholder}}",
      "value": "{{value}}"
    }
  }
}

Pattern 4: Card with Actions

{
  "type": "Card",
  "children": [
    {
      "type": "CardContent",
      "children": "{{content}}"
    },
    {
      "type": "CardActions",
      "children": [
        {
          "type": "Button",
          "props": { "variant": "text" },
          "children": "Cancel"
        },
        {
          "type": "Button",
          "props": { "variant": "contained" },
          "children": "Save"
        }
      ]
    }
  ]
}

Pattern 5: Responsive Grid

{
  "type": "Grid",
  "props": { "container": true, "spacing": 2 },
  "children": [
    {
      "type": "Grid",
      "props": {
        "item": true,
        "xs": 12,
        "sm": 6,
        "md": 4,
        "lg": 3
      },
      "children": "{{item}}"
    }
  ]
}

Troubleshooting

Components Not Loading After Bootstrap

  1. Check metadata.json exists:

    ls packages/my_package/component/metadata.json
    
  2. Check components.json exists:

    ls packages/my_package/component/components.json
    
  3. Check JSON is valid:

    npx json-schema-validator packages/my_package/component/components.json \
      schemas/package-schemas/component.schema.json
    
  4. Check database:

    npm --prefix dbal/development run db:studio
    # Query ComponentConfig table
    

Template Expressions Not Evaluating

  1. Check component receives props:

    {
      "template": {
        "type": "Button",
        "props": {
          "label": "{{label}}"  // Props passed to renderJSONComponent
        }
      },
      "props": {
        "label": "Default Label"  // Optional default
      }
    }
    
  2. Check expression syntax:

    // ✅ CORRECT
    "{{props.title}}"
    "{{isLoading ? 'Saving...' : 'Save'}}"
    "{{!disabled}}"
    
    // ❌ WRONG
    "{title}"
    "{{props.title + ' extra'}}"
    "{{doSomething()}}"
    

Component Not Found in Registry

# List all available components
node -e "console.log(require('@/lib/fakemui-registry').FAKEMUI_REGISTRY)"

# Check if fakemui import works
npm --prefix frontends/nextjs run typecheck

Last Updated: 2026-01-16 Version: 1.0.0 Status: Production Ready