Files
page-builder/MAIN_PLAN.md
T
jaybe 546c961a31
CI / test (push) Failing after 6m10s
CI / pr_and_merge (push) Has been skipped
섹션 레이아웃 및 템플릿 스타일 개선
2025-11-21 16:25:55 +09:00

28 KiB

메인 플랜 (Page Builder)

현재 완료된 단계

  • builder-1 ~ builder-8: 기본 에디터, 텍스트/버튼/섹션 블록, DnD, Undo/Redo, 블록 삭제/복제 등 핵심 UX 구현
  • builder-9-templates-advanced (현재 브랜치):
    • Hero / Features / CTA / FAQ / Pricing / Testimonials / Blog / Team / Footer 템플릿 섹션
    • 프리뷰 페이지 및 모바일 뷰포트 E2E
    • CI 환경에서 Prisma Client generate + API 테스트용 DATABASE_URL 설정
    • API 테스트에서 Prisma를 메모리 기반 목으로 교체하여 DB 없이 테스트 통과

다음 단계 개요

  1. builder-10-block-types

    • 더 다양한 블록 타입 추가 (예: 이미지, 아이콘이 있는 카드, 구분선, 리스트 등)
    • Zustand store에 타입/props 정의 및 액션 추가
    • 에디터 UI에 새 블록 버튼 및 속성 패널 연동
    • 새 블록 타입에 대한 유닛 테스트 + E2E 테스트 추가
  2. builder-11-editor-ux-advanced

    • 드래그앤드롭 UX 고도화 (섹션/컬럼 이동 UX 개선, 드롭 힌트 강화)
    • 키보드 숏컷 확장 및 포커스 관리 개선
    • 인라인 편집 UX 다듬기
  3. **## builder-12-theme-output 상세 플랜

    • Tailwind 기반 디자인 시스템 정리 (색상/타이포/간격 스케일)
    • Public/Preview 렌더링 품질 개선 (반응형, 여백, 타이포그래피)
  • 샘플 테마 프리셋 추가

3) 텍스트 스타일 시스템 정리 (에디터 쪽 선행 작업)

  • 에디터 텍스트 블록 속성 구조와 렌더링 규칙 정리
    • TextBlockProps 에서 정렬/폰트 크기/줄 간격/자간/색상/최대 너비/장식 등 타이포 관련 속성 확장
    • TextPropertiesPanel 에서 위 속성들을 슬라이더/프리셋/컬러 피커로 제어하도록 구현
    • SortableEditorBlock 에서 텍스트 블록 렌더링 시
      • alignpb-text-left/center/right
      • fontSizeMode/fontSizeScale/fontSizeCustompb-text-* 또는 style.fontSize
      • lineHeightMode/lineHeightScale/lineHeightCustompb-leading-* 또는 style.lineHeight
      • letterSpacingCustomstyle.letterSpacing
      • colorPalette/colorCustompb-text-color-* 또는 style.color
      • maxWidthMode/maxWidthScale/maxWidthCustompb-text-maxw-* 또는 style.maxWidth
      • underline/strike/italicpb-underline/pb-line-through/pb-italic
    • 텍스트 편집 모드가 아닐 때, 실제 텍스트 요소(<div>)에도 위 클래스와 textStyleOverrides 를 직접 적용해 시각적 일관성 확보
  • 테스트 정렬
    • tests/e2e/editor.spec.ts 에서 텍스트/구분선/리스트 블록 관련 정렬/크기 기대값을 Tailwind text-* 가 아니라 pb-text-* 유틸리티 네이밍에 맞게 조정
    • JSON 내보내기/불러오기 시나리오에서도 pb-text-left/right/sm/lg 클래스가 복원되는지 검증
  1. builder-13-forms
    • 폼 전송 구조 설계 및 구현 (internal API + Webhook/Google Sheets)
    • 폼 전송 설정(헤더/토큰/추가 파라미터) 및 다중 폼 컨트롤러 구조 설계
    • 폼 요소 블록(input/select/radio/checkbox) 추가 및 자유 레이아웃 배치 지원

builder-10-block-types 상세 플랜

1) 대상 블록 타입 정의

  • 이미지 블록(image)
    • props: src, alt, align, width, borderRadius
  • 구분선(divider)
    • props: style (solid/dashed), thickness, color, marginY
  • 리스트(list)
    • props: items (문자열 배열), ordered (true/false), align
  • (필요시) 카드(card)
    • props: title, description, imageSrc, align

※ 실제 구현 범위는 TDD를 진행하면서 우선순위 높은 것부터 차례대로 확장한다.

2) TDD 순서

  1. 유닛 테스트부터 작성 (실패 상태로 시작)
    • tests/unit/editorStore.spec.ts
      • addImageBlock, addDividerBlock, addListBlock 등 새 액션 테스트 추가
      • 블록이 올바른 기본 props 와 함께 blocks 배열에 추가되는지 확인
      • 섹션/컬럼 내부에 배치되는 경우 위치 정보(sectionId, columnId) 테스트
  2. 스토어 구현 (editorStore.ts)
    • Block 타입에 새 typeprops 구조 추가
    • EditorState에 새 액션 시그니처 추가
    • createEditorState 내부에 실제 로직 구현 (createId 활용, 기존 텍스트/버튼/섹션 패턴 재사용)
  3. 에디터 UI 연동 (editor/page.tsx)
    • 사이드바에 새 블록 버튼 추가 (이미지, 구분선, 리스트 등)
    • 캔버스 렌더링 분기에 새 블록 타입별 렌더링 로직 추가
    • 속성 패널에서 최소한의 필수 props 편집 가능하도록 구현
  4. E2E 테스트 추가 (Playwright)
    • tests/e2e/editor.spec.ts
      • 새 블록 타입 버튼 클릭 → 캔버스에 올바르게 렌더링되는지 확인
      • 속성 패널에서 값 수정 시 캔버스 반영 확인 (예: 이미지 src, 리스트 아이템 수정)
  5. 리팩터링 & 안정화
    • 중복되는 렌더링/props 처리 로직 정리
    • 필요시 추가 유닛 테스트/E2E로 회귀 방지

builder-11-editor-ux-advanced 상세 플랜

1) DnD UX 고도화

  • 목표

    • 블록/섹션 드래그앤드롭이 시각적으로 명확하고, 의도한 위치로 안정적으로 이동하도록 개선
    • 2컬럼 섹션 컬럼 이동, 블록 순서 변경 관련 E2E를 CI에서도 신뢰할 수 있게 만들기
  • 작업 항목 (TDD)

    1. tests/e2e/editor.spec.ts
      • "블록 드래그앤드롭으로 순서를 변경할 수 있어야 한다" 시나리오를 현재 UX에 맞게 단순/안정화
      • "2컬럼 섹션에서 블록을 다른 컬럼 영역으로 드래그하면 컬럼이 변경되어야 한다" 시나리오를 실제 드롭 영역 구조에 맞춰 조정
    2. EditorPage/SortableEditorBlock
      • 드롭 대상 컬럼/섹션의 hit-area를 넓히거나, 불필요한 overlay가 pointer events를 가로채지 않게 레이아웃 조정
      • DnD Kit 설정(sensors, collision detection 등) 필요 시 보정
    3. 필요 시 editorStore 유닛 테스트에서
      • reorderBlocks, moveBlock 조합 케이스(섹션/컬럼 간 이동)를 명시적으로 테스트해 회귀 방지

2) 키보드/포커스 UX 개선

  • 목표

    • ArrowUp/ArrowDown 으로 선택 블록이 예측 가능하게 이동
    • Cmd/Ctrl+Z / Cmd/Ctrl+Shift+Z / Cmd/Ctrl+D 와 같은 단축키가 OS에 관계없이 일관되게 동작
  • 작업 항목 (TDD)

    1. tests/e2e/editor.spec.ts
      • "ArrowUp/ArrowDown 키로 선택된 블록을 위/아래로 이동하며 순차적으로 선택할 수 있어야 한다" 테스트를 CI에서도 통과하도록 재정의
        • 초기 선택 상태, 포커스 위치, 기대되는 data-selected/aria-selected 값을 명확히 기술
    2. EditorPage의 전역 keydown 핸들러
      • ArrowUp/Down 선택 이동 로직을 단순/명확하게 정리 (현재 선택이 없을 때 첫 블록 선택 등)
      • OS별 Cmd/Ctrl 판별 로직(navigator.platform)과 E2E에서 보내는 키 조합을 일치시켜 유지

3) 인라인 편집 UX 정리

  • 목표

    • 텍스트 블록 더블클릭 → 인라인 편집 진입
    • Enter → 커밋, Esc → 취소, 포커스 아웃 시 안전하게 커밋
  • 작업 항목 (TDD)

    1. tests/e2e/editor.spec.ts
      • "텍스트 블록을 더블클릭해서 내용을 수정할 수 있어야 한다" 시나리오를
        • 더블클릭 → 편집 모드 진입
        • Enter 커밋 / Esc 취소 흐름까지 포함하도록 보완
    2. EditorPage의 인라인 편집 상태(editingBlockId, editingText)
      • 더블클릭/포커스 아웃/Enter/Esc 이벤트에 대한 상태 전이와 updateBlock 호출 타이밍을 명확히 정의

이 모든 작업도 builder-10과 동일하게 실패하는 테스트 추가 → 최소 구현 → 리팩터링 → CI/E2E 확인 순서로 진행한다.


builder-13-forms 상세 플랜

1) 폼 전송 구조 (v1: 단일 폼 + 필드 배열)

  • 목표

    • SPA 스타일로 페이지 리로드 없이 폼을 전송하고, CORS 문제 없이 외부 API/Google Sheets 로 연동
  • 현재 구현 상태 요약

    • FormBlockProps 에 전송 설정 추가
      • submitTarget: "internal" | "webhook"
      • destinationUrl, method, headers, extraParams
      • successMessage, errorMessage
    • 퍼블릭 렌더러: 폼 블록을 <form> 으로 렌더링하고 /api/forms/submit 으로 FormData 전송
    • /api/forms/submit 라우트:
      • __config hidden 필드로 넘어온 FormBlockProps 를 파싱
      • submitTarget === "internal" 일 때 서버 내부 처리 (향후 DB 저장/메일 발송용)
      • submitTarget === "webhook" 일 때 destinationUrl 로 서버사이드 POST (x-www-form-urlencoded)
        • headers/extraParams 를 함께 전송하여 토큰/고정 파라미터 전달
    • 폼 필드 정의:
      • FormFieldConfig (id, name, label, type, required)
      • FormBlockProps.fields?: FormFieldConfig[] 로 필드 배열 관리
      • 에디터 우측 패널에서 필드 추가/삭제/순서/라벨/name/type/필수 여부 편집 UI 제공
  • TDD 순서 (v1)

    1. tests/unit/editorStore.spec.ts
      • FormBlockProps/FormFieldConfig 초기값, addFormBlock 동작 테스트
    2. editorStore.ts
      • FormFieldConfig, FormBlockProps 정의 및 addFormBlock 구현
    3. PublicPageRenderer.tsx
      • fields 기반 폼 렌더링 및 /api/forms/submit 호출 로직 구현
    4. src/app/api/forms/submit/route.ts
      • internal/webhook 분기 및 Webhook/Google Sheets 전송 로직 구현
    5. tests/e2e/editor.spec.ts/tests/e2e/preview.spec.ts
      • 폼 추가 → 필드 수정 → 프리뷰에서 폼 전송 → 성공/에러 메시지 확인 시나리오 추가

2) 폼 요소 블록 + 폼 컨트롤러 구조 (v2: 자유 레이아웃)

  • 목표

    • 폼 요소(input/select/radio/checkbox)를 텍스트 블록처럼 섹션/컬럼 안에 자유롭게 배치
    • 폼 블록은 전송 설정 + 어떤 요소(id)들을 묶어서 어디로 보낼지 제어하는 컨트롤러 역할만 수행
    • 페이지 내에 여러 폼 블록이 있어도 각기 다른 필드/submit 버튼/전송 설정을 사용 가능
  • 설계 개요

    • 새 블록 타입 추가:
      • formInput, formSelect, formRadio, formCheckbox
      • 각 props 예시:
        • FormInputBlockProps: label, placeholder, formFieldName?, formFieldType, required?
        • FormSelectBlockProps: label, formFieldName?, options[], required?
        • FormRadioBlockProps: groupLabel, formFieldName?, options[], required?
        • FormCheckboxBlockProps: label, formFieldName?, required?
    • FormBlockProps 확장:
      • fieldIds: string[] (이 폼이 담당하는 폼 요소 블록들의 id)
      • submitButtonId?: string (이 버튼이 눌리면 이 폼을 전송)
    • 좌측 사이드바에 "폼 요소" 섹션 추가:
      • 텍스트 입력/셀렉트/라디오 그룹/체크박스 블록 추가 버튼
      • editorStoreaddFormInputBlock/addFormSelectBlock 등 액션 추가
    • 런타임 동작:
      • 버튼 클릭 → 해당 버튼 id 를 submitButtonId 로 가진 FormBlock 검색
      • FormBlock.fieldIds 에 포함된 블록들 중 formFieldName 이 설정된 요소만 수집
      • 현재 값(value)을 모아 FormData 또는 JSON 으로 /api/forms/submit 에 전송
  • TDD 순서 (v2)

    1. tests/unit/editorStore.spec.ts
      • 새 폼 요소 블록 타입 추가 액션(addFormInputBlock 등) 테스트
      • FormBlock 의 fieldIds/submitButtonId 기본값 및 업데이트 동작 테스트
    2. editorStore.ts
      • BlockType 에 폼 요소 타입 추가, 각 props/interface 정의
      • addFormInputBlock 등 구현 (섹션/컬럼 내 자유 배치)
    3. src/app/editor/forms/elements/*.tsx
      • InputElement/SelectElement/RadioGroupElement/CheckboxElement 컴포넌트 구현 및 에디터/퍼블릭 렌더링 연계
    4. src/app/editor/page.tsx
      • 좌측 사이드바에 "폼 요소" 섹션 추가 및 액션 연결
      • 블록 렌더링 분기에 새 폼 요소 블록 타입 처리 추가
      • 우측 폼 패널에 FormBlock 의 fieldIds/submitButtonId 매핑 UI 추가
    5. tests/e2e/editor.spec.ts/tests/e2e/preview.spec.ts
      • 섹션/컬럼 안에 폼 요소들을 자유 배치 → FormBlock 에 id 매핑 → 버튼 클릭 시 해당 요소 값만 전송되는지 검증

폼 기능 역시 다른 단계와 동일하게 실패하는 테스트 → 최소 구현 → 리팩터링 순서를 유지하며, Webhook/Google Sheets 연동 시 CORS 문제를 피하기 위해 항상 /api/forms/submit 경유 구조를 사용한다.

3) 폼 컨트롤러 + 프리뷰 레이아웃 동기화 (브랜치: feature/builder-13-forms-style-sync)

  • 목표

    • 에디터에서 배치한 폼 요소 블록(formInput/formSelect/formRadio/formCheckbox)이 프리뷰에서도 가능한 한 같은 레이아웃으로 보이도록 정리한다.
    • FormBlock(폼 블록)은 레이아웃을 직접 렌더링하지 않고, 전송 설정 + 어떤 요소/버튼을 묶어 전송할지 제어하는 컨트롤러 역할만 수행한다.
    • 기존 v1 fallback(contact 폼: 이름/이메일/메시지 3필드)은 가능하면 제거하거나, v2 컨트롤러가 전혀 설정되지 않았을 때에만 선택적으로 사용한다.
  • 현재 문제 인식

    • 에디터 캔버스에서는 formInput/formSelect/formRadio/formCheckbox 블록이 각각 파란 박스 형태로 자유 레이아웃에 배치되어 보이지만,
    • 프리뷰에서는 FormBlockProps.fields/fieldIds/fallback 에 따라 별도의 수직 폼 레이아웃(name/email/message) 이 생성되어, 에디터에 없는 블록이 새로 생긴 것처럼 보인다.
    • 사용 의도는 "폼 요소 블록 = 레이아웃, 폼 블록 = 전송 컨트롤러" 인데, 현재는 폼 블록이 레이아웃까지 재구성하는 상태.
  • 작업 항목 (TDD)

    1. 프리뷰 E2E 보강 (tests/e2e/preview.spec.ts)

      • 단순한 시나리오부터 추가:
        • 에디터에서 formInput/button/form 블록을 추가하고, 폼 컨트롤러에서 해당 입력/버튼을 매핑한 뒤 /preview 로 이동했을 때,
        • 프리뷰 화면에 기본 name/email/message 폼이 아니라, 에디터에서 만든 입력 필드(레이아웃 기반)가 보이는지 검증.
      • fallback contact 폼은 fieldIds/fields 가 완전히 비어 있을 때에만 보이는지, 또는 사용하지 않을 경우 어떤 동작을 기대할지 테스트로 먼저 정의.
    2. PublicPageRenderer 리팩터링 (폼 컨트롤러 역할 명확화)

      • PublicPageRenderer 에서 form 블록을 렌더링할 때:
        • 가능하면 <form> 요소 안에서 레이아웃을 새로 만들지 않고, 섹션/컬럼/루트 레이아웃 안에 이미 렌더된 폼 요소 블록들을 기준으로 전송만 담당하도록 구조 재검토.
        • v2 컨트롤러(fieldIds/submitButtonId) 우선 사용, v1 fields/기본 name/email/message 폼은 옵션/백워드 호환 용도로만 유지하거나 점진적 제거.
      • submit 시에는 여전히 /api/forms/submitFormData 를 전송하되, 수집 대상 필드를 v2 컨트롤러 기준으로 한정.
    3. 에디터/프리뷰 시각적 차이 최소화

      • 텍스트/버튼과 동일하게, 폼 요소 블록의 타이포/색상/레이아웃 스타일이 에디터와 프리뷰에서 크게 다르지 않도록 정리.
      • 필요시 PublicPageRenderer 에서 폼 요소 렌더링에 재사용 가능한 프레젠테이션 컴포넌트 도입(에디터용 렌더링과 일정 부분 공유).
    4. 회귀 방지

      • 기존 폼 관련 E2E(preview.spec.ts의 v2 컨트롤러 시나리오)와 API 테스트(formsSubmit.spec.ts)가 모두 통과하는지 수시로 확인.
      • fallback contact 폼 동작을 유지할지/제거할지 결정되면, 해당 경로도 테스트로 명시해 회귀 방지.

3) 진행 현황 (2025-11-20 ~ 2025-11-21)

  • v2 TDD 1단계(editorStore)

    • tests/unit/editorStore.spec.ts
      • formInput 블록 추가 액션(addFormInputBlock)에 대한 유닛 테스트 작성
      • FormBlock 의 fieldIds/submitButtonId 기본값 및 업데이트 동작 테스트
    • src/features/editor/state/editorStore.ts
      • BlockType"formInput" 타입 추가
      • FormInputBlockProps 정의 및 공통 Block.props 유니온에 포함
      • FormBlockPropsfieldIds?: string[], submitButtonId?: string | null 추가
      • addFormBlock에서 fieldIds: [], submitButtonId: null 기본값 설정
      • addFormInputBlock 액션 구현 (루트에 기본 label/formFieldName 으로 추가, 선택 상태 업데이트)
    • 결과: editorStore.spec.ts 전체 25개 테스트 모두 통과
  • 텍스트 스타일/프리뷰 1차 정리 (별도 브랜치에서 수행 후 병합 예정)

    • 에디터 텍스트 블록(TextBlockProps)의 정렬/폰트 크기/줄 간격/자간/색상/최대 너비/장식 속성을 SortableEditorBlock 렌더링에 전부 반영
    • 에디터 캔버스에서 텍스트 요소에 pb-text-*/pb-leading-*/pb-font-*/pb-text-color-* 및 커스텀 스타일이 적용되도록 정리
    • 퍼블릭 렌더러(PublicPageRenderer)에서 텍스트 블록의 정렬/크기/색상 일부를 에디터와 의미적으로 일치하도록 개선
    • 관련 E2E(editor.spec.ts) 기대값을 text-*pb-text-* 네이밍에 맞게 조정
  • 폼 컨트롤러 + 프리뷰 레이아웃 동기화 브랜치 시작 (feature/builder-13-forms-style-sync)

    • MAIN_PLAN 에 "3) 폼 컨트롤러 + 프리뷰 레이아웃 동기화" 섹션 추가
    • v2 폼 컨트롤러 시나리오에서 프리뷰에 기본 contact fallback 폼(name/email/message)이 보이지 않아야 한다는 E2E 기대를 명시
    • 다음 단계에서 PublicPageRenderer 의 FormBlock 렌더링을 컨트롤러 중심으로 리팩터링하고, 컨트롤러가 없는 FormBlock 의 fallback 정책을 정리할 예정
  • v2 TDD 2단계(editorStore + Editor UI)

    • tests/unit/editorStore.spec.ts
      • formSelect/formRadio/formCheckbox 블록 추가 액션 및 FormBlock fieldIds/submitButtonId 업데이트 테스트 추가
    • src/features/editor/state/editorStore.ts
      • BlockType"formSelect", "formRadio", "formCheckbox" 추가
      • FormSelectBlockProps/FormRadioBlockProps/FormCheckboxBlockProps 정의 및 Block.props/updateBlock에 반영
      • addFormSelectBlock/addFormRadioBlock/addFormCheckboxBlock 구현 (루트에 기본 label/options 로 추가)
    • src/app/editor/page.tsx
      • 좌측 사이드바에 "폼 요소" 섹션 추가
      • 우측 속성 패널에서 FormBlock 선택 시 "폼 컨트롤러" 섹션 추가
      • 체크박스/셀렉트 변경 시 updateBlock 으로 FormBlock 의 fieldIds/submitButtonId 업데이트
    • tests/e2e/editor.spec.ts
      • "폼 요소 사이드바에서 폼 입력/셀렉트/라디오/체크박스 블록을 추가할 수 있어야 한다" 시나리오 추가
      • "폼 블록을 선택하면 우측 속성 패널에서 폼 필드/버튼 매핑 UI가 보여야 한다" 시나리오 추가
    • 결과: 관련 유닛/E2E 테스트 모두 통과
  • v2 TDD 3단계(Preview/Public 렌더러 폼 제출 v2)

    • tests/e2e/preview.spec.ts
      • "폼 컨트롤러에 매핑한 폼 요소와 버튼이 프리뷰에서 함께 동작해야 한다" 시나리오 추가
        • 에디터에서 formInput/button/form 블록을 추가
        • FormBlock 의 fieldIds/submitButtonId 매핑 설정 후 /preview 이동
        • 프리뷰에서 입력값을 채우고 "버튼" 클릭 시 성공 메시지("성공적으로 전송되었습니다.")가 표시되는지 검증
    • src/features/editor/components/PublicPageRenderer.tsx
      • FormBlock 렌더링 시 v2 컨트롤러 우선 사용
        • fieldIds 가 설정된 경우 해당 id 를 가진 formInput/formSelect/formCheckbox/formRadio 블록들만 수집
        • 각 폼 요소 블록의 formFieldName/label/options 를 사용해 <input>/<select>/<textarea>/checkbox/radio 필드 렌더링
        • fieldIds 가 없거나 매핑된 요소가 없으면 기존 v1 FormBlockProps.fields 또는 기본 name/email/message 폼을 fallback 으로 사용
      • submitButtonId 가 설정된 경우 해당 버튼 블록의 label 을 폼의 submit 버튼 라벨로 사용
      • submit 시 /api/forms/submit 으로 FormData 를 전송하고, 응답이 ok 이면 successMessage (기본값 "성공적으로 전송되었습니다.") 를 표시
    • 결과: 프리뷰 폼 컨트롤러 v2 E2E 테스트 통과
  • v2 TDD 4단계(radio/checkbox 라벨 구조 정리)

    • 목표: 라디오/체크박스 필드에서 groupLabel 은 항상 그룹 타이틀(제목 텍스트)로만 사용하고, 각 옵션의 label 이 실제 라벨 역할을 하도록 분리
    • 에디터 속성 패널(EditorPage)
      • 폼 요소 선택 시 라디오/체크박스의 상단 라벨 인풋을 그룹 타이틀 로 표기
      • 라벨 모드 셀렉트를 라디오/체크박스의 경우 옵션 라벨 타입 으로 표기하여 그룹 제목과 옵션 라벨 역할을 명확히 분리
    • 에디터 캔버스 렌더링(EditorPage)
      • formRadio / formCheckbox 블록에서 상단 헤더는 항상 groupLabel 텍스트로만 렌더링
      • labelMode === "image" 인 경우, 라디오/체크박스 옆 옵션 라벨 위치에 labelImageUrl/labelImageAlt 를 사용해 이미지 라벨을 표시하고, 기본 모드에서는 옵션의 label 텍스트를 그대로 사용
    • E2E 테스트(tests/e2e/editor.spec.ts)
      • "폼 셀렉트/라디오/체크박스 블록도 우측 패널에서 기본 필드 속성을 편집할 수 있어야 한다" 시나리오를 groupLabel/옵션 라벨 타입 분리에 맞게 업데이트
        • 라디오/체크박스 선택 시 라벨 입력 필드 이름을 그룹 타이틀 로 기대
        • 라디오/체크박스 선택 시 옵션 라벨 타입 콤보박스 존재 여부를 검증
  • 디버깅 기록

    • tests/unit/editorStore.spec.ts 하단에 템플릿/undo/redo/remove/duplicate 관련 테스트 블록이 중복 삽입되어 중괄호 정렬이 깨짐
      • 증상: Vitest 실행 시 Unexpected "}" 파싱 에러 발생
      • 조치: 중복된 it 블록들 제거, 기존 describe 내부의 removeBlock/duplicateBlock 테스트를 정리하고 그 뒤에 폼 관련 2개 테스트만 배치하여 정상 종료
    • tests/unit/PropertyControls.spec.ts 에는 별도의 TS/JSX 문법 및 타입 오류(onChange, label 등)가 존재하지만, 현재 폼 기능 흐름과 직접 관련 없으므로 후속 단계에서 별도 TDD 리팩터링 대상으로 남겨둠

디버깅/CI 관련 기록 (요약)

  • CI에서 prisma generate 단계가 DATABASE_URL 없음으로 실패 → 워크플로에서 더미 DATABASE_URL 주입으로 해결

  • 유닛 테스트에서 API 테스트가 실제 DB 접속 시도 →

    • CI: Run unit tests 스텝에도 더미 DATABASE_URL 추가
    • 테스트 코드: PrismaClient를 메모리 기반 목으로 교체, 라우트를 동적 import 하도록 수정
  • Playwright E2E (에디터):

    • 레이아웃 상 사이드바/속성 패널이 캔버스 블록 클릭을 가로채면서 locator.click 타임아웃이 발생하는 이슈 확인
    • divider/list 블록용 E2E는 블록 추가 직후 자동 선택 상태와 속성 패널 조작만 검증하도록 작성해 부분적으로 우회
    • 기존 테스트들(블록 삭제, 복제, 키보드 이동 등)은 동일한 클릭 간섭 문제 영향 범위에 있어 후속 단계에서 레이아웃/테스트 전략 리팩터링 필요
  • 리스트 블록 선택/드래그 버그 (2025-11-21)

    • 증상: 에디터에서 리스트 블록만 회색 박스/드래그 핸들이 보이지 않고, 클릭/드래그가 전혀 되지 않음. 다른 텍스트/버튼 블록은 정상 동작.
    • 원인: src/app/editor/page.tsxSortableEditorBlock 내부에서 block.type === "list" 인 경우, 리스트 렌더링 JSX 를 return 으로 즉시 반환하면서 공통 래퍼(테두리 + "블록 드래그 핸들" 버튼 + 선택 onClick)를 건너뜀. 결과적으로 리스트 블록은 DnD 컨텍스트에는 포함되지만, 실제 화면에는 핸들이 없는 ul/li 만 노출되어 선택/드래그 불가 상태가 됨.
    • 조치:
      • 리스트 분기에서 JSX 를 직접 반환하는 코드를 제거하고, alignClass 만 설정하도록 수정하여 리스트도 다른 블록과 동일하게 SortableEditorBlock 공통 래퍼를 타도록 변경.
      • tests/e2e/editor.spec.ts 에 "리스트 블록에도 드래그 핸들이 있고 선택/드래그가 가능해야 한다" E2E 테스트 추가.
        • 텍스트/리스트 블록을 추가 → 리스트 블록 안의 "블록 드래그 핸들" 존재 여부, 클릭 시 aria-selected 전환, 드래그 후 리스트 블록이 첫 번째 위치로 이동하는지 검증.
  • 리스트 중첩 편집 1차 뼈대 정리 (현재 브랜치)

    • 구현 완료 범위
      • 리스트 블록 추가/선택/드래그
      • 리스트 스타일(정렬/폰트/색상/불릿/간격) 에디터 ↔ 프리뷰 동기화
      • itemsTree 기반 중첩 리스트 데이터 구조 및 유틸(indent/outdent/moveUp/moveDown)
      • selectedListItemId 상태 및 li 클릭 하이라이트
      • 패널 버튼으로 기본적인 들여쓰기/내어쓰기/위/아래 이동 1차 동작 구현
      • 리스트 관련 유닛 테스트 + 기본 흐름 E2E 테스트 통과
    • 한계/제약
      • textarea 내용 ↔ itemsTree 동기화가 단순해서, 여러 번 섞어가며 편집할 때 완전한 “리치 리스트 편집기” 수준의 UX 는 아님
      • Tab/Shift+Tab 조작이 선택/상태 조합에 따라 체감상 불안정한 구간이 존재
  • 리스트 에디터 v2 설계 개요 (향후 별도 TDD 태스크)

    • 핵심 컨셉
      • 단일 소스 = textarea
      • 각 줄의 Tab/스페이스 수를 depth 로 해석하여 중첩 구조를 표현
      • depth + textitemsTree 로 변환하는 유틸 도입
      • itemsTreedepth + texttextarea 내용을 재구성하는 유틸 도입
    • 공통 조작 모델 (키보드/패널 공통)
      • Enter: 현재 줄 기준 새 줄 추가
      • Tab/Shift+Tab: 현재 줄의 depth ±1 (Tab = +1, Shift+Tab = -1, 최소 0)
      • 패널 버튼(들여쓰기/내어쓰기)도 동일한 depth 조정 로직을 재사용
    • v2 작업 단위(TDD 단계 제안)
      • v2-1: depth + text → itemsTree 유틸
        • 입력: 줄 단위 텍스트 배열 + 각 줄의 depth(정수)
        • 출력: 현재 리스트 블록에서 사용하는 itemsTree 구조
        • TDD: 단일 레벨/간단 중첩/잘못된 depth(건너뛴 depth 등) 케이스를 포함한 유닛 테스트부터 작성
      • v2-2: itemsTree → depth + text → textarea 유틸
        • 입력: itemsTree
        • 출력: 각 아이템의 depth + text 로 구성된 라인 목록 (textarea 직렬화 가능 형태)
        • TDD: v2-1 에서 사용한 입력/기대 구조를 역으로 검증하는 round-trip 유닛 테스트 작성
      • v2-3: textarea + 키보드/패널 통합 조작 모델
        • 내용: textarea 내 현재 커서가 위치한 줄 기준으로 Enter/Tab/Shift+Tab/패널 버튼이 동일한 depth 조정 규칙을 사용하도록 통합
        • TDD: 키 이벤트에 따른 라인/depth 변화에 대한 작은 단위 유틸 테스트 → 이후 Playwright E2E 로 실제 textarea ↔ 프리뷰 동기화 시나리오 추가
    • 기대 효과
      • textarea 에서 보이는 탭/들여쓰기가 곧 중첩 구조가 되어 UX 가 직관적
      • 라인 + depth 기반 단일 모델 위에서 키보드/버튼 조작 로직을 통합해 유지보수성 향상
  • 이후 단계(builder-11-editor-ux-advanced, builder-12-theme-output)에서도 모든 기능 추가는 TDD(실패 테스트 → 구현 → 리팩터링) 순서를 유지한다.