Back to catalog

phased-development

Fast and concise JavaScript, TypeScript utilities for React.

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • GitHub stars: 1
  • Updated: 2025-11-21

Download the latest version (v1.0.0) from this page.

SKILL.md

# Phased Development Skill

## 역할

당신은 @canard/schema-form 플러그인의 단계별 개발 프로세스 전문가입니다.

## 핵심 책임

1. **5단계 개발 절차**: 설계 → 인프라 → 핵심 → 고급 → 최적화
2. **우선순위 결정**: 컴포넌트 구현 순서 제시
3. **체크리스트 제공**: 각 단계별 완료 조건
4. **마일스톤 관리**: 개발 진행 상황 추적
5. **품질 검증**: 각 단계별 검증 기준

## 작동 방식

### 1. 개발 단계 가이드

**knowledge/development-phases.md**를 통해:

- Phase 1-5 상세 절차
- 각 단계 완료 조건
- 다음 단계 전환 기준

### 2. 우선순위 관리

**knowledge/priority-guide.md**로:

- P1-P4 우선순위 기준
- 컴포넌트 구현 순서
- MVP vs Full Feature 구분

### 3. 최적화 검증

**knowledge/optimization-checklist.md**로:

- 성능 최적화 체크리스트
- 코드 품질 검증
- 배포 전 최종 점검

## 제공하는 정보

### 5단계 개발 프로세스

#### Phase 1: 설계 및 검증 (1-2일)

**목표**: 호환성 분석 및 설계 완료

**작업**:

1. UI 라이브러리 컴포넌트 매핑 분석
2. Context 타입 및 인터페이스 설계
3. 구현 우선순위 결정
4. package.json 초안 작성

**완료 조건**:

- [ ] 호환성 매트릭스 작성 완료
- [ ] Context 타입 정의 완료
- [ ] 우선순위 결정 완료

#### Phase 2: 기본 인프라 (2-3일)

**목표**: 프로젝트 기본 구조 구축

**작업**:

1. 프로젝트 설정 및 의존성 설치
2. 타입 정의 (`src/type.ts`)
3. 기본 렌더러 구현 (FormGroup, FormLabel, FormInput, FormError, formatError)
4. 빌드 및 테스트 환경 구성

**완료 조건**:

- [ ] 빌드 성공 (`yarn build`)
- [ ] 타입 체크 통과 (`yarn type-check`)
- [ ] 기본 렌더러 5개 구현 완료

#### Phase 3: 핵심 컴포넌트 (3-5일)

**목표**: 필수 FormTypeInput 구현

**우선순위 순서**:

1. **Priority 1: 기본 Input** (1-2일)
   - FormTypeInputString
   - FormTypeInputNumber
   - FormTypeInputBoolean
   - **formTypeInputDefinitions 배열 마지막에 배치**

2. **Priority 2: 특수 Format/FormType** (1-2일)
   - FormTypeInputTextarea (format: textarea)
   - FormTypeInputPassword (format: password)
   - FormTypeInputDate (format: date)
   - FormTypeInputTime (format: time)
   - FormTypeInputSlider (formType: slider)
   - **formTypeInputDefinitions 배열 앞쪽에 배치**

3. **Priority 3: Enum 및 구조** (1-2일)
   - FormTypeInputStringEnum (함수 조건)
   - FormTypeInputArray (ChildNodeComponents)

**완료 조건**:

- [ ] P1 컴포넌트 모두 구현
- [ ] P2 컴포넌트 선택 구현
- [ ] formTypeInputDefinitions 우선순위 순서 정렬
- [ ] 단위 테스트 작성

#### Phase 4: 고급 기능 및 문서화 (2-3일)

**목표**: 추가 컴포넌트 및 문서

**작업**:

1. 선택적 컴포넌트 (Radio, Slider, 등)
2. README.md 및 README-ko_kr.md 작성
3. Storybook stories 작성
4. package.json 의존성 최종 확인

**완료 조건**:

- [ ] README 작성 완료
- [ ] Storybook stories 작성
- [ ] 예제 코드 작성

#### Phase 5: 최적화 및 검증 (1-2일)

**목표**: 성능 최적화 및 배포 준비

**작업**:

1. 성능 최적화 체크리스트 적용
2. 접근성 검증 (axe-core)
3. canard-form 통합 테스트
4. 빌드 및 타입 체크

**완료 조건**:

- [ ] 성능 최적화 체크리스트 100% 완료
- [ ] 접근성 테스트 통과
- [ ] 통합 테스트 통과
- [ ] 빌드 크기 확인

## 우선순위 가이드

### Priority 1 (P1) - MVP 필수

**기준**: 기본 폼 동작에 필수

**컴포넌트**:

- String, Number, Boolean 입력
- 기본 렌더러 5개

**목표**: 2-3일 내 구현

### Priority 2 (P2) - 초기 버전 포함

**기준**: 일반적으로 자주 사용

**컴포넌트**:

- Textarea, Password, Email
- Date, Time (UI 라이브러리 지원 시)
- Enum/Select

**목표**: Phase 3-4에서 구현

### Priority 3 (P3) - 추가 기능

**기준**: 특수한 경우 사용

**컴포넌트**:

- Radio Group, Slider
- Color Picker
- 커스텀 Format

**목표**: Phase 4 또는 2차 버전

### Priority 4 (P4) - 선택적

**기준**: 외부 라이브러리 필요 또는 비표준

**컴포넌트**:

- Rich Text Editor
- File Upload (고급)

**목표**: 3차 버전 또는 별도 패키지

## 체크리스트 템플릿

### Phase 완료 체크리스트

```markdown
## Phase {N}: {Phase Name}

**시작일**: YYYY-MM-DD
**목표 완료일**: YYYY-MM-DD
**실제 완료일**:

### 작업 목록

- [ ] 작업 1
- [ ] 작업 2
- [ ] 작업 3

### 완료 조건

- [ ] 조건 1
- [ ] 조건 2

### 이슈 및 결정사항

- [날짜] 이슈 또는 결정

### 다음 단계

- Phase {N+1} 준비사항
```

## 마일스톤

### M1: MVP (Phase 1-3)

- 기본 렌더러 5개
- P1 컴포넌트 (String, Number, Boolean)
- 빌드 및 테스트 환경

**목표**: 1주일

### M2: 초기 버전 (Phase 4)

- P2 컴포넌트 추가
- 문서화 완료
- Storybook stories

**목표**: 2주일

### M3: 정식 버전 (Phase 5)

- 성능 최적화
- 접근성 검증
- 통합 테스트

**목표**: 3주일

## 제약 조건

- Phase 순서는 지키되, 일정은 유연하게
- P1 완료 전 P2 시작 금지
- 각 Phase 완료 조건 충족 필수
- 코드 리뷰는 Phase 단위로

## 출력 형식

### 개발 계획 제공

```markdown
## {UI Library} 플러그인 개발 계획

### 일정 요약

- Phase 1: {날짜}
- Phase 2: {날짜}
- Phase 3: {날짜}
- Phase 4: {날짜}
- Phase 5: {날짜}

### Phase 1: 설계 및 검증

[상세 내용]

### Phase 2: 기본 인프라

[상세 내용]

...
```

## 사용 시나리오

### 시나리오 1: MUI 플러그인 Phase 1 - 설계 및 검증

**상황**: 새로운 MUI (Material-UI) v6 플러그인 개발 시작, Phase 1 설계 단계

**단계**:

#### Step 1: 호환성 매트릭스 작성

```markdown
## MUI v6 호환성 분석

### 기본 컴포넌트 매핑

| FormTypeInput | MUI Component | 우선순위 | 난이도 | 비고 |
|---------------|---------------|----------|--------|------|
| String | TextField | P1 | 쉬움 | 기본 |
| Number | TextField (type="number") | P1 | 쉬움 | 기본 |
| Boolean | Checkbox / Switch | P1 | 쉬움 | 기본 |
| Textarea | TextField (multiline) | P2 | 쉬움 | format: textarea |
| Password | TextField (type="password") | P2 | 쉬움 | format: password |
| Email | TextField (type="email") | P2 | 쉬움 | format: email |
| Date | DatePicker (@mui/x) | P2 | 중간 | 별도 패키지, ISO 변환 |
| Time | TimePicker (@mui/x) | P2 | 중간 | 별도 패키지, ISO 변환 |
| StringEnum | Select / RadioGroup | P2 | 중간 | 개수 기반 선택 |
| NumberEnum | Select | P2 | 중간 | - |
| Slider | Slider | P3 | 중간 | formType: slider |
| Array | Box + Button | P2 | 어려움 | ChildNodeComponents |
| Object | Box | P2 | 쉬움 | ChildNodeComponents |

### 특수 고려사항

**DatePicker/TimePicker**:
- ✅ 장점: @mui/x-date-pickers 공식 지원
- ⚠️ 주의: 별도 패키지 필요, date-fns 어댑터 설치
- 🔧 구현: ISO 8601 ↔ Date 객체 변환 필수
- 📦 의존성: `@mui/x-date-pickers`, `date-fns`

**Slider**:
- ✅ 장점: MUI Slider 컴포넌트 기본 제공
- 🔧 구현: formType: 'slider' 조건 추가
- 📋 Schema 예시: `{ type: 'number', formType: 'slider', minimum: 0, maximum: 100 }`

**Array/Object**:
- ✅ 장점: Box, Paper 컴포넌트로 깔끔한 레이아웃 가능
- 🔧 구현: ChildNodeComponents 활용, IconButton으로 삭제
- 🎨 스타일: Paper + sx prop으로 depth 표현
```

#### Step 2: Context 타입 설계

```typescript
// src/type.ts - 초안
/**
 * MUI 플러그인 Context 타입
 * SchemaForm의 context prop으로 전달되는 설정
 */
export interface MuiFormContext {
  /**
   * 전역 컴포넌트 크기
   * @default 'medium'
   */
  size?: 'small' | 'medium' | 'large';

  /**
   * 전역 TextField variant
   * @default 'outlined'
   */
  variant?: 'standard' | 'outlined' | 'filled';

  /**
   * 전역 색상 테마
   * @default 'primary'
   */
  color?: 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning';

  /**
   * 파일 업로드 콜백 (Phase 4 또는 2차 버전)
   * @param file - 업로드할 파일
   * @returns 업로드된 파일 URL
   */
  onFileAttach?: (file: File) => Promise<string>;

  /**
   * 날짜 포맷 로케일 (Phase 3)
   * @default 'ko' (한국어)
   */
  dateLocale?: string;
}

/**
 * SchemaForm 사용 예시:
 *
 * <SchemaForm
 *   schema={schema}
 *   plugin={muiPlugin}
 *   context={{
 *     size: 'medium',
 *     variant: 'outlined',
 *     color: 'primary',
 *     dateLocale: 'en-US',
 *   }}
 * />
 */
```

#### Step 3: 우선순위 결정 및 일정 계획

```markdown
## 구현 우선순위 및 일정

### Phase 1: 설계 및 검증 (2일)
**날짜**: 2025-01-20 ~ 2025-01-21

- [x] 호환성 매트릭스 작성
- [x] Context 타입 설계
- [ ] 우선순위 결정
- [ ] package.json 초안

**결정사항**:
- DatePicker는 Phase 3에 구현 (date-fns 학습 필요)
- Slider는 Phase 4로 미룸 (P3 우선순위)
- Array/Object는 Phase 3에 포함 (MVP에 필요)

### Phase 2: 기본 인프라 (3일)
**예상 날짜**: 2025-01-22 ~ 2025-01-24

**Priority 1: 프로젝트 설정**
- [ ] yarn create 및 패키지 초기화
- [ ] tsconfig.json, rollup.config.js 설정
- [ ] @canard/schema-form peerDependencies 설정
- [ ] MUI 의존성 설치

**Priority 2: 타입 및 렌더러**
- [ ] src/type.ts 완성
- [ ] FormGroup, FormLabel, FormInput, FormError 구현
- [ ] formatError 헬퍼 함수

### Phase 3: 핵심 컴포넌트 (5일)
**예상 날짜**: 2025-01-25 ~ 2025-01-29

**Day 1-2: P1 기본 Input**
- [ ] FormTypeInputString
- [ ] FormTypeInputNumber
- [ ] FormTypeInputBoolean (Checkbox)

**Day 3-4: P2 특수 Format**
- [ ] FormTypeInputTextarea
- [ ] FormTypeInputPassword
- [ ] FormTypeInputEmail

**Day 5: Enum 및 구조**
- [ ] FormTypeInputStringEnum (Select 또는 RadioGroup)
- [ ] FormTypeInputArray (간단 버전)
- [ ] formTypeInputDefinitions 우선순위 정렬

### Phase 4: 고급 기능 (3일)
**예상 날짜**: 2025-01-30 ~ 2025-02-01

- [ ] FormTypeInputDate (DatePicker)
- [ ] FormTypeInputTime (TimePicker)
- [ ] README.md 작성
- [ ] Storybook stories 3-5개

### Phase 5: 최적화 (2일)
**예상 날짜**: 2025-02-02 ~ 2025-02-03

- [ ] 성능 최적화 체크리스트
- [ ] 접근성 테스트 (axe-core)
- [ ] 통합 테스트
- [ ] 빌드 및 배포

**총 예상 기간**: 15일 (3주)
```

#### Step 4: package.json 초안

```json
{
  "name": "@canard/schema-form-mui-plugin",
  "version": "0.1.0",
  "description": "Material-UI (MUI) v6 plugin for @canard/schema-form",
  "keywords": ["canard", "schema-form", "mui", "material-ui", "react", "form"],
  "author": "Your Name",
  "license": "MIT",
  "main": "dist/index.cjs.js",
  "module": "dist/index.esm.js",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.esm.js",
      "require": "./dist/index.cjs.js",
      "types": "./dist/index.d.ts"
    }
  },
  "files": ["dist", "README.md"],
  "scripts": {
    "build": "rollup -c",
    "typecheck": "tsc --noEmit",
    "lint": "eslint src --ext .ts,.tsx",
    "test": "vitest run",
    "test:watch": "vitest"
  },
  "peerDependencies": {
    "@canard/schema-form": "*",
    "@mui/material": "^6.0.0",
    "react": ">=18.0.0",
    "react-dom": ">=18.0.0"
  },
  "dependencies": {
    "@winglet/react-utils": "*"
  },
  "devDependencies": {
    "@canard/schema-form": "*",
    "@mui/material": "^6.0.0",
    "@types/react": "^18.0.0",
    "react": "^18.0.0",
    "rollup": "^4.0.0",
    "typescript": "^5.0.0"
  }
}
```

**Phase 1 완료 조건**:
- ✅ 호환성 매트릭스 완성
- ✅ Context 타입 설계 완료
- ✅ 우선순위 및 일정 결정
- ✅ package.json 초안 작성

**다음 단계**: Phase 2로 진행, 프로젝트 설정 시작

---

### 시나리오 2: Ant Design 플러그인 Phase 2 - 기본 인프라 구축

**상황**: Ant Design v5 플러그인, Phase 1 완료 후 Phase 2 진행

**단계**:

#### Step 1: 프로젝트 초기화

```bash
# 1. 패키지 디렉토리 생성
mkdir packages/canard-schema-form-antd-plugin
cd packages/canard-schema-form-antd-plugin

# 2. package.json 생성 (Phase 1 초안 기반)
yarn init -y

# 3. TypeScript 및 빌드 도구 설치
yarn add -D typescript rollup @rollup/plugin-typescript rollup-plugin-dts

# 4. Ant Design 및 canard-form 설치 (dev)
yarn add -D @canard/schema-form antd react react-dom @types/react

# 5. winglet utils 의존성 추가
yarn add @winglet/react-utils

# 6. tsconfig.json 생성
```

#### Step 2: tsconfig.json 설정

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "lib": ["ES2020", "DOM"],
    "jsx": "react-jsx",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "removeComments": true,
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": false
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]
}
```

#### Step 3: src/type.ts 구현

```typescript
// src/type.ts
/**
 * Ant Design 플러그인 Context 타입
 */
export interface AntdFormContext {
  /**
   * 전역 컴포넌트 크기
   * @default 'middle'
   */
  size?: 'small' | 'middle' | 'large';

  /**
   * 전역 Input variant (Ant Design은 variant 없음, bordered로 제어)
   * @default true
   */
  bordered?: boolean;

  /**
   * ConfigProvider의 componentSize 연동
   */
  componentSize?: 'small' | 'middle' | 'large';

  /**
   * 파일 업로드 콜백 (Phase 4)
   */
  onFileAttach?: (file: File) => Promise<string>;

  /**
   * 날짜 포맷 로케일
   * @default 'ko_KR'
   */
  locale?: string;
}

/**
 * 재사용 타입
 */
export type { FormTypeInputPropsWithSchema } from '@canard/schema-form';
```

#### Step 4: 기본 렌더러 구현 (FormGroup)

```typescript
// src/components/FormGroup.tsx
import type { FormTypeRendererProps } from '@canard/schema-form';
import { Box } from 'antd'; // ⚠️ Ant Design에는 Box가 없음, div 사용

/**
 * FormGroup: 폼 그룹 렌더러
 * depth에 따라 들여쓰기 적용
 */
export const FormGroup = ({ depth, children }: FormTypeRendererProps) => {
  return (
    <div
      style={{
        paddingLeft: depth * 16,
        borderLeft: depth > 0 ? '2px solid #f0f0f0' : undefined,
        marginBottom: 16,
      }}
    >
      {children}
    </div>
  );
};
```

#### Step 5: FormLabel 구현

```typescript
// src/components/FormLabel.tsx
import type { FormTypeRendererProps } from '@canard/schema-form';

/**
 * FormLabel: 폼 라벨 렌더러
 * Ant Design은 Form.Item이 라벨을 처리하므로 여기서는 간단히 구현
 */
export const FormLabel = ({ children, htmlFor, required }: FormTypeRendererProps) => {
  return (
    <label
      htmlFor={htmlFor}
      style={{
        display: 'block',
        marginBottom: 4,
        fontSize: 14,
        fontWeight: 500,
      }}
    >
      {children}
      {required && <span style={{ color: 'red', marginLeft: 4 }}>*</span>}
    </label>
  );
};
```

#### Step 6: FormInput, FormError, formatError 구현

```typescript
// src/components/FormInput.tsx
import type { FormTypeRendererProps } from '@canard/schema-form';

/**
 * FormInput: 입력 필드 래퍼
 * Ant Design은 Form.Item을 사용하지 않으므로 단순 wrapper
 */
export const FormInput = ({ children }: FormTypeRendererProps) => {
  return <div style={{ marginBottom: 8 }}>{children}</div>;
};

// src/components/FormError.tsx
import type { FormTypeRendererProps } from '@canard/schema-form';

/**
 * FormError: 에러 메시지 렌더러
 */
export const FormError = ({ children }: FormTypeRendererProps) => {
  if (!children) return null;

  return (
    <div
      style={{
        color: '#ff4d4f',
        fontSize: 12,
        marginTop: 4,
      }}
    >
      {children}
    </div>
  );
};

// src/components/formatError.ts
import type { FormTypeError } from '@canard/schema-form';

/**
 * formatError: 에러 객체를 문자열로 변환
 */
export const formatError = (error: FormTypeError): string => {
  // JSON Schema validation 에러
  if (error.keyword) {
    const keywordMessages: Record<string, string> = {
      required: '필수 입력 항목입니다',
      minLength: `최소 ${error.params?.limit}자 이상 입력해야 합니다`,
      maxLength: `최대 ${error.params?.limit}자까지 입력 가능합니다`,
      minimum: `최소값은 ${error.params?.limit}입니다`,
      maximum: `최대값은 ${error.params?.limit}입니다`,
      pattern: '형식이 올바르지 않습니다',
      format: '형식이 올바르지 않습니다',
      enum: '허용되지 않는 값입니다',
      type: '타입이 올바르지 않습니다',
    };

    return keywordMessages[error.keyword] || error.message || '유효하지 않은 값입니다';
  }

  // 일반 에러
  return error.message || '유효하지 않은 값입니다';
};
```

#### Step 7: src/index.ts 구현

```typescript
// src/index.ts
import type { SchemaFormPlugin } from '@canard/schema-form';

import { FormError } from './components/FormError';
import { FormGroup } from './components/FormGroup';
import { FormInput } from './components/FormInput';
import { FormLabel } from './components/FormLabel';
import { formatError } from './components/formatError';

/**
 * Ant Design v5 플러그인
 * Phase 2: 기본 인프라만 구현, formTypeInputDefinitions는 Phase 3에서 추가
 */
export const plugin = {
  FormGroup,
  FormLabel,
  FormInput,
  FormError,
  formatError,
  formTypeInputDefinitions: [], // ⚠️ Phase 3에서 구현
} satisfies SchemaFormPlugin;

export type * from './type';
```

#### Step 8: rollup.config.js 설정

```javascript
// rollup.config.js
import typescript from '@rollup/plugin-typescript';
import dts from 'rollup-plugin-dts';

export default [
  // ESM 및 CJS 빌드
  {
    input: 'src/index.ts',
    output: [
      {
        file: 'dist/index.esm.js',
        format: 'esm',
        sourcemap: true,
      },
      {
        file: 'dist/index.cjs.js',
        format: 'cjs',
        sourcemap: true,
      },
    ],
    external: [
      'react',
      'react-dom',
      'antd',
      '@canard/schema-form',
      '@winglet/react-utils',
    ],
    plugins: [
      typescript({
        tsconfig: './tsconfig.json',
        declaration: false, // dts 플러그인이 처리
      }),
    ],
  },
  // 타입 선언 파일 번들
  {
    input: 'src/index.ts',
    output: {
      file: 'dist/index.d.ts',
      format: 'esm',
    },
    external: [/\.css$/],
    plugins: [dts()],
  },
];
```

#### Step 9: 빌드 및 검증

```bash
# 1. TypeScript 타입 체크
yarn typecheck
# ✅ 에러 없이 통과해야 함

# 2. 빌드
yarn build
# ✅ dist/index.esm.js, dist/index.cjs.js, dist/index.d.ts 생성 확인

# 3. 생성된 파일 확인
ls -la dist/
# 출력:
# index.esm.js
# index.esm.js.map
# index.cjs.js
# index.cjs.js.map
# index.d.ts
```

**Phase 2 완료 조건**:
- ✅ 빌드 성공 (`yarn build`)
- ✅ 타입 체크 통과 (`yarn typecheck`)
- ✅ 기본 렌더러 5개 구현 완료 (FormGroup, FormLabel, FormInput, FormError, formatError)
- ✅ plugin 객체 export 성공

**다음 단계**: Phase 3로 진행, FormTypeInput 컴포넌트 구현 시작

---

### 시나리오 3: Chakra UI 플러그인 Phase 3 - 핵심 컴포넌트 구현

**상황**: Chakra UI v2 플러그인, Phase 2 완료 후 Phase 3 핵심 컴포넌트 구현

**단계**:

#### Day 1-2: Priority 1 기본 Input 구현

```typescript
// src/formTypeInputs/FormTypeInputString.tsx
import { useMemo } from 'react';
import { useHandle } from '@winglet/react-utils';
import type { FormTypeInputPropsWithSchema, FormTypeInputDefinition } from '@canard/schema-form';
import {
  FormControl,
  FormLabel,
  Input,
  FormErrorMessage,
  FormHelperText,
} from '@chakra-ui/react';
import type { ChakraFormContext } from '../type';

interface FormTypeInputStringProps
  extends FormTypeInputPropsWithSchema<string, never, ChakraFormContext>,
          ChakraFormContext {}

const FormTypeInputString = ({
  jsonSchema,
  path,
  name,
  required,
  disabled,
  readOnly,
  errors,
  defaultValue,
  onChange,
  context,
  size: sizeProp,
}: FormTypeInputStringProps) => {
  const [size, label] = useMemo(() => {
    return [
      sizeProp ?? context.size ?? 'md',
      jsonSchema.label ?? jsonSchema.title ?? name,
    ];
  }, [sizeProp, context.size, jsonSchema.label, jsonSchema.title, name]);

  const hasError = errors.length > 0;
  const errorMessage = hasError ? errors[0].message : undefined;

  const handleChange = useHandle((event: React.ChangeEvent<HTMLInputElement>) => {
    onChange(event.target.value);
  });

  // ✅ Chakra UI는 FormControl로 감싸야 자동 접근성 처리
  return (
    <FormControl isRequired={required} isInvalid={hasError} isDisabled={disabled || readOnly}>
      <FormLabel htmlFor={path}>{label}</FormLabel>
      <Input
        id={path}
        name={name}
        defaultValue={defaultValue ?? ''}
        onChange={handleChange}
        size={size}
        placeholder={jsonSchema.placeholder}
      />
      {hasError && <FormErrorMessage>{errorMessage}</FormErrorMessage>}
      {!hasError && jsonSchema.description && (
        <FormHelperText>{jsonSchema.description}</FormHelperText>
      )}
    </FormControl>
  );
};

export const FormTypeInputStringDefinition = {
  Component: FormTypeInputString,
  test: { type: 'string' },
} satisfies FormTypeInputDefinition;
```

```typescript
// src/formTypeInputs/FormTypeInputNumber.tsx
const FormTypeInputNumber = ({ /* ...props */ }: FormTypeInputNumberProps) => {
  // FormTypeInputString과 유사, Input에 type="number" 추가
  return (
    <FormControl isRequired={required} isInvalid={hasError}>
      <FormLabel htmlFor={path}>{label}</FormLabel>
      <Input
        id={path}
        name={name}
        type="number"  // ✅ 차이점
        defaultValue={defaultValue ?? ''}
        onChange={(e) => onChange(parseFloat(e.target.value) || 0)}
        size={size}
      />
      {/* ... */}
    </FormControl>
  );
};

export const FormTypeInputNumberDefinition = {
  Component: FormTypeInputNumber,
  test: (params) => {
    return params.jsonSchema.type === 'number' || params.jsonSchema.type === 'integer';
  },
} satisfies FormTypeInputDefinition;
```

```typescript
// src/formTypeInputs/FormTypeInputBoolean.tsx
import { Checkbox } from '@chakra-ui/react';

const FormTypeInputBoolean = ({ /* ...props */ }: FormTypeInputBooleanProps) => {
  const handleChange = useHandle((event: React.ChangeEvent<HTMLInputElement>) => {
    onChange(event.target.checked);
  });

  return (
    <FormControl isDisabled={disabled || readOnly}>
      <Checkbox
        id={path}
        name={name}
        defaultChecked={defaultValue ?? false}
        onChange={handleChange}
        size={size}
      >
        {label}
      </Checkbox>
      {jsonSchema.description && (
        <FormHelperText>{jsonSchema.description}</FormHelperText>
      )}
    </FormControl>
  );
};

export const FormTypeInputBooleanDefinition = {
  Component: FormTypeInputBoolean,
  test: { type: 'boolean' },
} satisfies FormTypeInputDefinition;
```

**Day 1-2 완료**:
- ✅ FormTypeInputString
- ✅ FormTypeInputNumber
- ✅ FormTypeInputBoolean

#### Day 3-4: Priority 2 특수 Format 구현

```typescript
// src/formTypeInputs/FormTypeInputTextarea.tsx
import { Textarea } from '@chakra-ui/react';

const FormTypeInputTextarea = ({ /* ...props */ }: FormTypeInputTextareaProps) => {
  return (
    <FormControl isRequired={required} isInvalid={hasError}>
      <FormLabel htm