Files
landing-builder/MAIN_PLAN.md
jaybe d08f050bfd
Auto PR / open-pr (push) Successful in 22s
Auto Label PR / add-automerge-label (pull_request) Successful in 7s
CI / test (pull_request) Failing after 1m5s
캔버스 키보드 너지: 멀티선택 델타 적용 및 축별 스냅 보정, 수용 테스트 추가
2025-11-17 18:55:40 +09:00

49 KiB
Raw Permalink Blame History

메인플랜: 랜딩 페이지 빌더

[정렬] WYSIWYG 빌더 목표/요구사항/수용기준 (2025-11-17)

  • 목표

    • /[locale]/wysiwyg 에서 비개발자도 랜딩을 시각적으로 제작(Add→편집→Export)할 수 있다.
    • Export 산출물은 ZIP(index.html/css/js/assets/*, site.webmanifest, robots.txt, 무결성 파일)을 포함한다.
  • 확정 요구사항

    • R1: 텍스트/이미지/버튼 추가 및 속성 편집(X/Y/W/H/Rotate + 타입별 속성)
    • R2: 조작 UX(드래그/리사이즈/회전, 8px/15° 스냅, 멀티선택·마퀴, 키보드 너지, 가이드 표시, a11y 라이브 리전)
    • R3: 레이어 관리(순서 이동, lock, visible 토글)
    • R4: Export ZIP 생성(무결성 파일 포함) 및 verify ok
    • R5: 경로 고정(/[locale]/wysiwyg)
  • 수용 기준(DoD)

    • A1: /wysiwyg에서 Add Text → 속성 수정 → Export 실행 시 ZIP 생성, HTML/CSS에 수정 반영
    • A2: 드래그/리사이즈/회전 스냅 동작 및 가이드 표시 스모크 그린
    • A3: 라이브 리전 문구가 선택/가이드/너지에 맞춰 갱신
    • A4: 생성된 ZIP에 대해 verifyIntegrity 결과 ok=true
    • A5: 레이어 순서 변경이 렌더에 반영
  • TDD 매핑

    • R1 → PropsPanel.test(기존) + WysiwygBuilderPage 추가/수정 반영 스모크(신규)
    • R2 → Canvas.*.test(기존) + 빌더 멀티선택/마퀴 스모크(신규)
    • R3 → LayersPanel.test(기존) + 빌더 순서 반영(신규)
    • R4 → exportFrame/buildZip/무결성(기존) + 빌더 Export 스모크(신규)
    • R5 → 라우트 스모크(기존)
  • 순차 작업 계획

    1. 수용 테스트 추가(빌더 Add→Edit→Export, 레이어 순서 반영, 멀티선택/마퀴 스모크)
    2. /wysiwyg Export에 무결성 파일 포함 연동
    3. 최소 UI 토글(가이드/그리드) 노출 및 문서 업데이트
    4. 전체 그린 확인 후 브랜치 범위 점검 → 푸쉬

Guides 토글 안정화 + 리사이즈 UX 2차(방향 핸들) (2025-11-17)

  • TDD
    • components/wysiwyg/WysiwygBuilder.guides.acceptance.test.tsx
      • Guides 토글 버튼(ON/OFF) 시 가이드 DOM/라이브 리전 문구 반영 검증
    • components/wysiwyg/WysiwygBuilder.resize.acceptance.test.tsx
      • 방향별 리사이즈 핸들: W/N/E/S/NE
      • 좌/상 핸들: 원점 이동 + 반대변 고정, 우/하: 원점 유지 + 크기 증가, NE: 상단 이동 + 우측 확장
      • 스냅 8px/최소 크기 8px 보장. 스냅으로 인한 픽셀 변화는 snap8 기대값으로 검증
  • 구현
    • components/wysiwyg/Canvas.tsx
      • enableGuides에 따라 드래그 중에만 가이드 노출, 종료 시 해제
      • 커스텀 리사이즈 핸들 5종(W/N/E/S/NE) 추가 및 방향성 로직 구현
      • 스냅(8px)·최소 크기(8px) 적용, 좌/상은 원점 이동 처리
      • 리사이즈 중 Moveable 드래그 이벤트 무시(간섭 방지)
  • 오류 원인/디버깅
    • React 경고: "다른 컴포넌트 렌더 중 setState" → 드래그/리사이즈 라이프사이클로 이동해 해결
    • Moveable 드래그와 리사이즈 동시 발생으로 좌표 튐/기대 불일치 → resizingRef 활성 시 Moveable onDragStart/onDrag 조기리턴 처리
    • 테스트 불안정: 선택 직후 의도치 않은 드래그 → 선택 후 mouseUp(canvas)로 상호작용 종료 보장, 스냅 기대값으로 단정
  • 결과: 위 2개 수용 테스트 포함 전체 그린 유지

가이드라인 고도화 진행(2025-11-16)

  • 목표: 객체-객체 스냅 가이드(8px 임계)
    • 수직 중심 정렬 가이드/스냅
    • 수평 중심 정렬 가이드/스냅
    • 좌/우/상/하 가장자리 정렬 가이드/스냅
  • TDD
    • components/wysiwyg/Canvas.guidelines.advanced.test.tsx
      • 수직 중심 스냅 가이드(8px) 검증. Moveable 모킹, act/waitFor 적용
    • components/wysiwyg/Canvas.guidelines.horizontal.test.tsx
      • 수평 중심 스냅 가이드(8px) 검증
    • components/wysiwyg/Canvas.guidelines.edges.test.tsx
      • 좌/우 에지 스냅 가이드(8px) 1차 케이스 검증(T→L 등 에지 페어)
  • 구현
    • components/wysiwyg/Canvas.tsx
      • onDrag: 그리드(8px) → 객체 중심 스냅(수직/수평) → 객체 에지 스냅(좌/우/상/하) 우선순위 적용
      • 가이드 좌표: 축별로 에지 > 중심 > 그리드 순서로 표시
      • 드래그 시작 시 가이드 표시, 종료 시 가이드 해제 유지
  • 결과: 전체 테스트 그린(131/131)

오류 원인/디버깅(메모)

  • 실패: 수직 중심 스냅 테스트에서 선택 대상이 기본 선택되지 않아 좌표 미변경 → 레이어 objects 순서를 조정(o1을 첫 요소로)
  • 실패: 비동기 상태 반영 전 단정 → Moveable 이벤트 호출을 act로 감싸고 결과 검증을 waitFor/findByTestId로 보강하여 해결
  • 실패: 가장자리 스냅 미구현으로 208px(그리드) 유지 → 에지 스냅(LL/LR/RL/RR, TT/TB/BT/BB) 페어 계산 로직과 8px 임계 우선순위 적용으로 해결

다음 단계

  • 키보드 nudge(Shift/Alt 미세/가속)와 가이드 반영 TDD/구현
  • 다중 선택 및 그룹 이동/정렬 스펙 초안 TDD
  • 접근성: 키보드 조작 시 라이브 리전 안내/가이드라인 ARIA 노출 정책 검토

키보드 nudge 진행(2025-11-16)

  • 정책: 기본 화살표 1px 이동 → 8px 그리드 스냅, Shift=1px 비스냅, Alt=16px 이동 후 스냅
  • TDD: components/wysiwyg/Canvas.nudge.test.tsx에 Shift/Alt 케이스 추가
  • 구현: components/wysiwyg/Canvas.tsx onKeyDown 로직 보강(step/snap 정책)
  • 결과: 전체 테스트 그린(220/220)

멀티선택/그룹 이동·정렬 진행(2025-11-16~17)

  • 목표: Shift+클릭 멀티선택, 그룹 드래그 시 동시 이동, 그룹 스냅/가이드(그룹 bbox 기준), 마퀴(드래그 박스) 선택
  • TDD
    • components/wysiwyg/Canvas.multiselect.test.tsx
      • Shift+클릭으로 다중 선택 토글, 선택 아웃라인 표시, 그룹 드래그 시 동시 이동
    • components/wysiwyg/Canvas.multigroup.guides.test.tsx
      • 그룹 bbox 기준 중심/에지 스냅, 혼합 케이스(에지>중심), 다수 후보 중 최근접 우선 검증
      • 그리드 스냅 근사치(±8px)로 인한 픽셀 오차는 상대 delta로 검증(안정성 강화)
    • components/wysiwyg/Canvas.marquee.test.tsx
      • 빈 캔버스 드래그로 마퀴(드래그 박스) 선택. 교차하는 객체 자동 선택. 빈 클릭 시 선택 해제
  • 구현: components/wysiwyg/Canvas.tsx
    • 상태: selectedIds(배열)로 전환, 마지막 선택을 Moveable target으로 사용
    • Shift+클릭 토글 선택. 선택된 모든 객체에 아웃라인/핸들 표시
    • 그룹 드래그: 마지막 선택에서 제안된 좌표 → 그룹 bbox로 변환해 스냅 후보(중심/에지) 계산 → 스냅된 그룹 delta를 모든 선택 객체에 적용
    • 가이드: 그룹 컨텍스트에서 에지>중심>그리드 우선 표시(축별)
    • 마퀴: 캔버스 mousedown→mousemove→mouseup으로 사각형 추적, 교차 판정으로 선택 세트 갱신. 빈 캔버스 클릭 시 선택 해제
    • 테스트 환경 폴리필: document.elementFromPoint 안전 폴리필 추가
    • 린트: 폴리필 미사용 인자 경고 해결(인자 소비), useCallback 의존성 보강(마퀴 핸들러)
  • 결과: 전체 테스트 그린(135/135)

오류 원인/디버깅(메모)

  • 그룹 중심/에지 스냅과 그리드 스냅의 상호작용으로 픽셀 기대치가 환경에 따라 한두 픽셀 차이 발생 → 절대 좌표 단정 대신 상대 delta/허용 집합(88/90/96 등)으로 안정화
  • 마퀴 선택 실시간 갱신 시 훅 의존성 누락 경고 → useCallback deps에 marquee.active/x0/y0model.layers 반영
  • jsdom 환경의 elementFromPoint 부재로 이벤트 경로 해석 이슈 → 폴리필로 무해화(() => null)

다음 단계

  • 멀티선택 혼합/최근접 보강 케이스 수평축/복합 시나리오 추가 점검
  • 접근성 보강: 선택/스냅/가이드 상태에 대한 라이브 리전 안내 및 ARIA 속성 방안 설계

WYSIWYG 빌더 Export/이미지/Props 동기화 진행(2025-11-17)

  • 목표: /wysiwyg에서 실제 랜딩에 가까운 HTML/CSS Export와 로컬 이미지 업로드, 속성 패널과 Canvas의 일관된 동기화 확보
  • TDD
    • lib/wysiwyg/export.test.ts
      • 버튼 오브젝트를 <a id="..." href="...">label</a>로 내보내는지 검증
      • 숨김 레이어(visible: false)가 HTML/CSS에 포함되지 않는지 검증
      • exportFrame 결과 HTML이 <!doctype html>, <link rel="stylesheet" href="styles.css" />, <script src="app.js"></script>를 포함하는 전체 문서 구조인지 검증
    • components/wysiwyg/WysiwygBuilder.acceptance.test.tsx
      • 로컬 이미지 업로드 시 Canvas에 <img src="/assets/...">가 렌더되는지 검증
      • PropsPanel에서 Width/Height/Rotate 변경이 Canvas 스타일(width/height/transform)에 반영되는지 검증
      • PropsPanel에서 Text/Label 변경이 Canvas 텍스트/버튼 콘텐츠에 반영되는지 검증
  • 구현
    • lib/wysiwyg/export.ts
      • frame 내용을 <div class="frame-scale"><div class="frame">...</div></div>로 감싼 뒤, <!doctype html><html lang="en"><head>...styles.css...</head><body>...app.js...</body></html> 전체 문서로 래핑
      • 숨김 레이어는 HTML/CSS 생성 시 스킵, 버튼은 <a>로 내보내되 위치/크기/회전은 CSS에서 절대 좌표로 표현
    • components/wysiwyg/WysiwygBuilderPage.tsx
      • 페이지 수명 주기 동안 유지되는 AssetManageruseRef로 보관
      • 파일 업로드 input(data-testid="wysiwyg-image-input") 추가 후, 업로드된 이미지를 AssetManager.add로 수집하고 props.src="/assets/..."인 image WObject를 Base 레이어에 추가
      • Export 시 assetManager.toZipStructure()buildZipassets로 전달하고, 기존 무결성 파일(assets.json, assets.index.json, assets.integrity.index.json)을 extras에 포함
    • components/wysiwyg/PropsPanel.tsx
      • 내부 modelobject prop과 useEffect로 동기화하여 선택 변경 시 최신 값 반영
      • Text용 Text 필드(aria-label="Text"props.text), Button용 Label 필드(aria-label="Label"props.label) 추가
  • 결과
    • /wysiwyg에서 Add Text/Image/Button, PropsPanel 편집, 로컬 이미지 업로드, Export ZIP(index.html/styles.css/app.js/assets/* + 무결성 파일)까지 한 플로우가 acceptance 테스트로 검증되는 상태
    • npm test -s 기준 145 파일 / 246 테스트 전체 그린

접근성 라이브 리전(2025-11-17)

  • 목표: 캔버스에서 선택 개수 변화와 가이드(스냅 라인) 좌표 변화를 스크린리더에 폴라이트로 안내
  • TDD
    • components/wysiwyg/Canvas.a11y.guides.test.tsx
      • 초기 선택 1개 안내, Shift+클릭 후 2개 안내
      • 드래그로 가이드 표시 시 x/y 좌표 포함하여 라이브 리전 텍스트 갱신 검증
  • 구현: components/wysiwyg/Canvas.tsx
    • role="status" aria-live="polite" 라이브 리전 요소 추가(data-testid="aria-live")
    • 텍스트는 useMemo로 파생 계산(선택 수, 가이드 표시 여부, 가이드 좌표)
    • 상태(setState) 기반 이펙트 대신 파생 텍스트로 전환하여 렌더 루프/린트 경고 회피
  • 결과: 테스트 그린(전체 232/232)
  • 디버깅 메모: useEffect 내 setState 경고 발생 → useMemo로 파생 텍스트 계산으로 해결

폼 요소 이관 + Export a11y + Help + Validation (2025-11-17)

  • TDD
    • components/wysiwyg/
      • WysiwygBuilder.form-input.acceptance.test.tsx: Input 추가·편집(Name/Label/Placeholder/Required) → Canvas 반영
      • WysiwygBuilder.form-select.acceptance.test.tsx: Select 추가·편집(Name/Label/Options/Default) → Canvas 반영
      • WysiwygBuilder.form-textarea.acceptance.test.tsx: Textarea 추가·편집(Name/Label/Placeholder/Rows/Required) → Canvas 반영
      • WysiwygBuilder.form-radio.acceptance.test.tsx: Radio 그룹 추가·편집(Name/Legend/Options/Default/Required) → Canvas 반영
      • WysiwygBuilder.form-checkbox.acceptance.test.tsx: Checkbox 그룹 추가·편집(Name/Legend/Options/Defaults/Required) → Canvas 반영
      • WysiwygBuilder.form-help.acceptance.test.tsx: Help 편집 → Canvas help 텍스트 및 aria-describedby 연결 검증
    • lib/wysiwyg/
      • export.form.test.ts: 폼 5종 a11y(label/for, fieldset/legend, selected 등) HTML/CSS 출력 검증
      • export.form.help.test.ts: Help → aria-describedby(입력)/그룹 Help(라디오/체크박스) 검증
      • export.form.validation.test.ts: pattern/minLength/maxLength 속성 내보내기 검증
  • 구현
    • lib/wysiwyg/schema.ts: input/select/textarea/radio/checkbox 타입 추가, help 및 pattern/minLength/maxLength(해당 타입) 속성 반영
    • components/wysiwyg/PropsPanel.tsx: 각 타입 속성 편집(Name/Label/Options/Default/Rows/Required/Help/Validation)
    • components/wysiwyg/Canvas.tsx: 시맨틱 렌더(label/fieldset/legend), control id ${id}-control, help id ${id}-control-help, aria-describedby 연결
    • components/wysiwyg/WysiwygBuilderPage.tsx: Add 버튼 및 오브젝트 생성 로직 추가(5종)
    • lib/wysiwyg/export.ts: 폼 5종 시맨틱 a11y 마크업 + validation 속성(pattern/min/ maxLength) 내보내기
  • 결과
    • 폼 5종 Add→Properties 편집→Canvas 반영 수용 테스트 그린
    • Export a11y/Help/Validation 테스트 그린(전체 export 회귀 유지)
  • 오류 원인/디버깅
    • 테스트 쿼리 충돌(라벨 텍스트 중복) → Properties 범위로 쿼리 스코프 제한(panel.querySelector)
    • 라디오/체크박스 초기 checked 불일치 → Canvas에서 제어형으로 렌더(checked, onChange no-op)
    • 타입/린트: any 캐스트 제거(스키마 확장), Partial 타입에 help 키 추가로 해결

스타일 토큰화 + 상태/테마 (2025-11-17)

  • TDD
    • lib/wysiwyg/export.style.tokens.test.ts: :root 토큰 존재(--bg, --text, --border, --primary), .frame/anchor 토큰 참조
    • lib/wysiwyg/export.style.tokens.controls.test.ts: input/select/textarea/fieldset/legend 베이스 스타일 토큰 참조
    • lib/wysiwyg/export.style.tokens.states.test.ts: :focus, :invalid 상태 스타일이 토큰(--primary/--danger) 참조
    • lib/wysiwyg/export.style.tokens.theme.test.ts: [data-theme="light"|"dark"] 프리셋 토큰 존재 및 .frame이 var(--bg) 사용
  • 구현
    • lib/wysiwyg/export.ts
      • :root 토큰: --bg, --text, --border, --primary, --danger
      • 베이스 규칙: .frame{background:var(--bg)}, a{color:var(--primary)}
      • 컨트롤 베이스: input,select,textarea{color:var(--text);border:1px solid var(--border)} + fieldset/legend
      • 상태: :focus{outline:2px solid var(--primary)} / :invalid{border-color:var(--danger)}
      • 테마 프리셋: [data-theme="light"], [data-theme="dark"] 각각 토큰 값 제공
  • 결과: 모든 관련 테스트 그린 유지
  • 오류 원인/디버깅
    • 없음(스냅샷은 정규식 매칭으로 안정화)

스타일 토큰 회귀 강화(링/비활성) (2025-11-17)

  • TDD
    • lib/wysiwyg/export.style.tokens.focusVisible.test.ts: --ring, --ring-offset 토큰 존재 및 :focus-visible에서 outline/offset 참조 검증
    • lib/wysiwyg/export.style.tokens.disabled.test.ts: --disabled-bg, --disabled-fg 토큰 존재 및 :disabled/[readonly]에서 참조 검증
  • 구현: lib/wysiwyg/export.ts
    • :root/테마 토큰에 --ring, --ring-offset, --disabled-bg, --disabled-fg 추가
    • 규칙 추가: :focus-visible{outline:2px solid var(--ring);outline-offset:var(--ring-offset)}
    • 규칙 추가: input,select,textarea:disabled{background:var(--disabled-bg);color:var(--disabled-fg)} / [readonly]{color:var(--disabled-fg)}
  • 결과: 전체 테스트 그린(166 파일 / 291 테스트)

Guides/그룹 액션 완성(드래그/리사이즈/회전 + 스냅) (2025-11-17)

  • TDD
    • components/wysiwyg/WysiwygBuilder.guides.acceptance.test.tsx: Guides 토글 ON/OFF 표시 확인
    • components/wysiwyg/WysiwygBuilder.group-actions.acceptance.test.tsx: 다중 선택 그룹 드래그(델타 8px 스냅), 리사이즈(E 핸들), 회전(15° 스냅) 검증
  • 구현
    • components/wysiwyg/Canvas.tsx
      • 다중 선택 드래그: 델타 기반 8px 방향 스냅(+13→+16, -13→-16), 선택된 전체 객체에 동일 delta 적용
      • 그룹 리사이즈: 방향별 로직 유지, 그룹 원점/크기 변화 반영
      • 그룹 회전: 15° 스냅 적용(Shift 없이)
      • Moveable 간섭 방지: 다중 선택 시 Moveable의 draggable/rotatable 비활성화(단일 선택에서만 활성)
      • 선택 보존: 이미 선택된 객체를 다시 클릭해 드래그해도 멀티 선택 유지
  • 결과: 수용 테스트 2종 그린, 기존 회귀 그린 유지
  • 오류 원인/디버깅
    • 경고: "다른 컴포넌트 렌더 중 setState" → 다중 선택 드래그에서 Moveable와 커스텀 드래그가 중복 적용되던 이슈를 해결(멀티 시 Moveable drag/rotate 비활성화)
    • 테스트 불안정: 버튼 라벨 모호 매칭(Add Text vs Add Textarea) → 정규식 고정(/^Add Text$/)

린트/테스트 안정화 배치 (2025-11-17)

  • 조치
    • 테스트 쿼리 모호성 제거: 모든 Add Text 버튼 매칭을 name: /^Add Text$/i로 통일
    • 훅 의존성 배열 보강: Canvas 마퀴/가이드/드래그 관련 useCallback/useEffect deps 누락 보완
    • 미사용 유틸/헬퍼 제거 및 경고 해소
    • ts-ignore 대체: URL/DOM 모듈 모킹을 vi.spyOn(...)으로 치환하여 타입 우회 제거
    • 멀티선택 시 Moveable 간섭 방지 재확인: draggable/rotatable 비활성화 유지로 중복 업데이트 차단
    • 스냅 우선순위 보존 검증: 에지 > 중심 > 그리드, 방향성 스냅(+13→+16, -13→-16) 회귀 그린 확인
  • 결과
    • npm test -s: 164 파일, 289 테스트 전부 그린
    • React 경고(setState in render) 제거, 테스트 플래키성 해소

키보드 nudge × 멀티선택 복합 (2025-11-17)

  • TDD
    • components/wysiwyg/Canvas.nudge.multiselect.acceptance.test.tsx
      • ArrowRight: 1px 이동 후 그리드 스냅 적용(이동 축만 스냅)
      • Shift+ArrowUp: 스냅 없이 정확히 -1px 이동(미세 이동)
      • Alt+ArrowRight: +16px 이동 후 그리드 스냅(가속 이동)
  • 구현: components/wysiwyg/Canvas.tsx
    • onKeyDown: 앵커(마지막 선택) 기준 목표 좌표(tx, ty) → 스냅 적용(nx, ny) → 델타(ddx, ddy) 산출 → 선택 집합 전체에 동일 델타 적용
    • 이동하지 않은 축은 기존 좌표 유지하여 비의도 축 보정 방지
  • 결과: 전체 테스트 그린(168 파일 / 295 테스트)

목표

  • 비개발자도 사용 가능한 단일 페이지 랜딩 페이지 빌더.
  • 결과물: 정적 ZIP(HTML/CSS/JS) 다운로드. 이미지: 외부 URL 또는 로컬 업로드(서버 저장 없음, ZIP에 패키징). 폰트는 외부 CDN 허용.
  • 폼: 다양한 필드 타입(파일 업로드 제외), 커스텀 name, action URL 설정. action 미지정 시 Google Sheets 저장.
  • 다국어 지원(i18n), 접근성 준수, SEO/OG 메타, 분석 스니펫 삽입.

확정 요구사항

  • 단일 페이지 기준.
  • 이미지: 외부 URL 또는 로컬 업로드 허용(서버 저장 없음, ZIP 패키징). CDN 허용(예: Google Fonts 등).
  • 폼 필드 타입: 텍스트/이메일/전화/셀렉트/라디오/체크박스/텍스트에어리어/날짜 등(파일 업로드는 추후).
  • 검증 규칙 필요. 스팸 방지: 기본(honeypot, 제출지연 타임스탬프).
  • 제출 경로: 1) 사용자가 action URL 지정 → 2) 미지정 시 Google Sheets.
    • 기본: Google Apps Script 웹앱 템플릿 가이드 제공(사용자 배포 URL 사용)。
    • 대안: 서비스계정 + Google Sheets API(선택).
  • 다국어 지원 필요. 접근성 준수. SEO/OG 포함. 분석 스니펫 삽입 기능 필요.
  • 스택: React 기반. Next.js(App Router) + TS + Tailwind + shadcn/ui 권장.

아키텍처 개요

  • 빌더 앱: Next.js(App Router, TS), Tailwind, shadcn/ui, dnd-kit, Zustand.
  • 도메인 스키마: Page → Section[] → Block[] → Form(fields, validation, actionUrl) → Theme.
  • 에셋 매니저: 사용자가 업로드한 이미지를 메모리/브라우저에서 관리, 파일명 해싱 후 assets/ 경로로 참조 재작성.
  • HTML Exporter: JSON 스키마 → 정적 HTML/CSS/JS 생성. 로컬 업로드 이미지는 assets/로 참조, 외부 URL은 그대로 유지.
  • 제출 경로: action 사용 또는 Google Sheets(기본 Apps Script 템플릿 제공).
  • i18n: next-intl. a11y: Testing Library + axe-core 스모크. SEO/OG 메타 생성.
  • 내보내기: ZIP(브라우저 JSZip 우선). 필요 시 서버(API Route) 대안 제공. 로컬 업로드 이미지는 ZIP 내 assets/ 폴더에 파일로 포함.

UI/UX 리디자인 계획(합의안)

  • 정보 구조(3패널)
    • 좌: 컴포넌트 패널(Hero/FAQ/CTA + Form 프리셋)
    • 중: 캔버스(dnd-kit, 섹션/필드 배치·정렬·삭제)
    • 우: 인스펙터(선택 요소 속성/검증/스타일 편집)
  • 상단 툴바: 언어, 테마, 프리셋, Export ZIP
  • 프리뷰: 실시간 렌더, 반응형 뷰포트 토글
  • 계약 고정: 스키마(섹션/폼/검증), 상태 인터페이스, Exporter 입·출력

단계별 TDD 진행

  1. 레이아웃 스캐폴딩(shadcn/ui) + 접근성 라벨링 테스트
  2. dnd 캔버스 1차: 섹션 드래그 정렬/삭제 테스트 → 최소 구현
  3. 인스펙터 1차: 선택 섹션/필드 기본 속성 편집 테스트 → 연동
  4. 컴포넌트 패널: 카드 추가(섹션/필드 프리셋) 테스트 → 드래그 추가
  5. 프리뷰 강화: 실시간 반영/뷰포트 토글 스모크 테스트

현재 상태(MVP)

  • 섹션 추가/삭제, 폼 필드 구성(옵션/필수/패턴/최대길이), i18n, ZIP Export, 로컬 에셋 포함 동작 확인
  • Next.js 16: middlewareproxy 마이그레이션 완료
  • dnd 상호작용 테스트 및 구현 안정화(정렬/삭제 버튼 폴백 포함)
  • 섹션 프리셋 확장: Features/Testimonials 카드 추가 및 프리뷰/Exporter 스텁 반영 완료
  • SEO/Analytics/OG 인스펙터 추가 및 프리뷰 반영
  • Exporter: og:image 메타 태그 반영 및 테스트 추가
  • a11y: jest-axe 스모크 테스트 통과(FormBuilderPanel), dnd-kit 라이브 리전/안내 존재 검증
  • 라벨-id 매핑 강화(중앙 입력/인스펙터/FAQ/CTA/Features/Testimonials)
  • 프리뷰 뷰포트 토글(Desktop/Mobile) + localStorage 영속화
  • Features/Testimonials 인스펙터 편집 UI(리스트 추가/삭제/편집) + 테스트
  • Exporter 타입 보강(any 제거) 및 CSS 보강(Features/Testimonials)
  • E2E(Playwright):
    • 기본 플로우(섹션 추가→편집→프리뷰→Export 클릭)
    • dnd 키보드 폴백(Up/Down) 재정렬
    • SEO/Analytics/OG 프리뷰 반영 검증
    • Export ZIP 메타(SEO/OG/Analytics) 검증(테스트 훅 사용)
    • 프리뷰 뷰포트 토글(aria-pressed/키보드 접근) 검증

a11y/반응형 정책(Exporter)

  • 문서 내비게이션: skip link, <main id="main" role="main"> 적용
  • 섹션 식별: aria-labelledby + 섹션 헤딩 id(features/testimonials/faq)
  • 이미지 대체텍스트: hero img alt = heading 파생
  • 포커스 표시: 포커스 가능한 요소 outline 표시
  • 반응형: 모바일 컨테이너 패딩 축소, grid 자동 칼럼, prefers-reduced-motion 준수

최근 보강 내역(접근성/테스트)

  • 인스펙터 입력에 aria-describedby 연결(SEO/Analytics/OG/Hero/CTA)
  • SEO/Analytics/OG/Hero/CTA/FAQ 헬프텍스트 추가
  • Exporter 구조 스냅샷 강화: <main> 내부 섹션/폼 포함 검증
  • Exporter CSS 토큰 스냅샷: skip-link/focus outline/반응형/모션 감소 토큰 존재 검증
  • Features/Testimonials aria-labelledby/헤딩 id 일치 검증 추가
  • Exporter 헤딩 레벨 일관성 전수 테스트(hero=h1, others=h2, FAQ=h3) 추가
  • Exporter 색상/타이포그래피 토큰 스모크 테스트 추가
  • Exporter(Form): helptext ↔ aria-describedby 연결 TDD 완료
    • 각 필드 id=field-<name><label for> 연결
    • help가 있을 경우 id=field-<name>-help 요소 생성 및 컨트롤에 aria-describedby 부여
    • 라디오 그룹은 fieldsetaria-describedby 적용, 옵션별 id/htmlFor 연결
  • next-intl 기본 timeZone 설정: Asia/Seoul로 ENVIRONMENT_FALLBACK 경고 제거
  • a11y 색/폰트 최소 기준 스모크(본문 컬러/폰트 패밀리/모바일 h1 폰트 크기) 추가
  • CTA 섹션에 aria-labelledby + <h2 id="cta-heading"> 추가(헤딩 레벨 일관성)
  • 전체 테스트 그린(유닛/컴포넌트/E2E)
  • Inspector a11y 스모크: SEO/Analytics/OG 입력과 헬프텍스트의 aria-describedby 연결 검증
  • Exporter CSS 스모크: 본문 텍스트 컬러/포커스 outline/모바일 h1 폰트 크기/MQ 토큰 존재 검증
  • FAQ 인스펙터 Q/A 입력 aria-describedby(ins-faq-help) 연결 보강
  • Inspector 키보드 UX 스모크: Features/Testimonials 항목 추가( Space/Enter ) 검증 추가
  • Inspector 키보드 UX 스모크: Features/Testimonials 항목 삭제(Remove) Enter/Space 검증 추가
  • 인스펙터 Remove 버튼 키보드 처리(onKeyDown/onKeyUp) 반영
  • Exporter 회귀 스냅샷: heading/ARIA/반응형 토큰 통합 검증 추가
  • Preview 토글 ARIA 회귀 스냅샷(Desktop/Mobile: aria-pressed/controls/describedby)
  • Exporter CSS 토큰 회귀 확대(:root --primary, body font, skip-link base/focus, focus outline, reduced-motion)
  • 컴포넌트 i18n 회귀 스냅샷(en/ko 주요 라벨/버튼 키 존재)
  • 섹션 a11y 자동 검증 템플릿(testUtils/sectionA11y) 도입 및 템플릿 테스트 추가
  • 이미지 최적화 옵션 1차(TDD): hero 이미지에 fetchpriority/srcset/sizes 조건부 렌더링
  • 테스트: lib/exporter/image.responsive.options.test.ts (속성 존재 검증)
  • 구현: lib/exporter/html.ts(ExportOptions에 옵션 추가 및 <img> 렌더 반영)
  • 브랜치: feat/image-optimization (로컬 커밋, 푸쉬 보류)
  • 이미지 최적화 옵션 2차(TDD): srcset만 제공 시 기본 sizes 자동 적용(imageAutoSizes)
  • 기본값: (max-width: 640px) 100vw, 600px
  • 테스트: image.responsive.options.test.ts (auto on/off 케이스)
  • 구현: ExportOptions.imageAutoSizes 추가 및 renderHero 적용
  • 신규 섹션 추가: Benefits
  • 스키마 확장(benefits): props.items:string[]
  • Exporter 렌더 추가: <section class="benefits" aria-labelledby>, <h2 id="benefits-heading">, 리스트 렌더
  • 테스트: sections.benefits.test.ts (h2/aria-labelledby/리스트 아이템 검증)
  • 섹션 회귀 스냅샷(benefits 포함) 보강: sections.regression.benefits.test.ts 추가(ARIA/헤딩/리스트 구조 전수 검증)
  • 섹션 회귀 스냅샷(CTA/Testimonials): sections.regression.cta-testimonials.test.ts 추가(figure/blockquote/figcaption, CTA 링크 클래스/URL 검증)
  • 섹션 회귀 스냅샷(Features/FAQ): sections.regression.features-faq.test.ts 추가(리스트/클래스/아이템 수 검증)
  • Exporter 이스케이프 회귀: sections.escape.regression.test.ts 추가(특수문자 <, >, &, ") 안전 이스케이프 검증)
  • 인스펙터 a11y 자동 체크 헬퍼 도입: components/testUtils/inspectorA11y.ts + 템플릿 테스트 2종(InspectorA11y.template/sections)
  • i18n 회귀 확장: Sidebar Add 버튼군(en/ko) 라벨 스모크(i18n.sidebar.actions.test.tsx)
  • E2E 스모크: 프리뷰/Export 마커(heading/ARIA/반응형 CSS) 존재 검증 추가
  • SEO description 기본값 설정으로 Export 스키마 안정화
  • 프리뷰 토글 버튼에 aria-controls="preview-container" 추가로 제어 영역 명시
  • 프리뷰 토글에 sr-only 설명 추가 및 두 토글 버튼 aria-describedby 연결
  • SectionCanvas 제어 버튼 aria-label 스모크(Select/Up/Down/Remove) 추가
  • CSS focus-visible 토큰 스모크(3px solid + HEX 컬러) 추가
  • 인스펙터 Add 버튼 title 보조 힌트 추가(Features/Testimonials/FAQ) 및 일관성 테스트
  • SectionCanvas 버튼 title/aria-label 일관성 스모크
  • 전역 버튼 네이밍 일관성 보강: Inspector Add/Remove 및 SectionCanvas Select/Up/Down/Remove의 title/aria-label 일치 확인 및 스모크 완료
  • a11y 커버리지 최종 점검: 전 섹션 인스펙터 입력 aria-describedby 연결 확인(체크리스트 충족)

다음 작업(계획)

  • Exporter 헤딩 레벨 일관성 테스트: hero는 h1, 나머지 섹션은 h2, FAQ 항목은 h3 검증
  • a11y 색/폰트 최소 기준 스모크: 대비/폰트크기 관련 CSS 토큰/규칙 존재 여부 스냅샷

Inspector a11y 커버리지 체크리스트

  • Hero: Hero Heading 입력 aria-describedbyins-hero-heading-help
  • CTA: CTA Text/CTA Href 입력 aria-describedbyins-cta-text-help/ins-cta-href-help
  • FAQ: FAQ Q n/FAQ A n 입력 aria-describedbyins-faq-help
  • Features: Feature n 입력 aria-describedbyins-features-help
  • Testimonials: Author n/Quote n 입력 aria-describedbyins-testimonials-help
  • Preview 토글: sr-only 설명(preview-toggle-help)과 버튼 aria-describedby 연결, aria-controls="preview-container"

CI/테스트 실행

  • 유닛/컴포넌트: npm test (Vitest)
  • E2E: npm run test:e2e (Playwright)
  • 로컬 개발 서버는 Playwright 설정에서 자동 기동(webServer: npm run dev)
  • GitHub Actions 워크플로: .github/workflows/ci.yml
    • Node 20 + npm ci
    • Vitest 실행 → Playwright 브라우저 설치(npx playwright install --with-deps) → E2E 실행

CI 최종 점검

  • main/master 브랜치 푸시/PR 시 워크플로 자동 실행 확인
  • 새로 추가된 테스트(헤딩 레벨 전수, 색/폰트 스모크) CI 통합 확인
  • 작업 브랜치 푸시 완료: chore/ci-verify-a11y-headings (원격: origin)
  • PR 생성 및 Actions 결과 기록 예정(base: main)
  • CI verification note: 러너 온라인 이후, 사소 변경으로 CI 트리거 검증 예정
    • CI rerun: 워크플로 재실행용 ping 커밋

PR/CI 결과(2025-11-14)

  • PR: feat/ci-verify-run → main 머지 완료
  • CI: Vitest + Playwright 전부 그린
  • E2E 추가: Exported page skip-link focus 표시 회귀 테스트 포함
  • 워크플로: self-hosted runner [macos, host], Playwright 보고서 업로드 아티팩트 활성화
  • 제안: 다음 릴리스 태그(v0.2.0)로 회귀 커버리지/Benefits 섹션 추가 반영

결과: CI 통과 (self-hosted Gitea Actions)

  • PR: #2
  • 러너: self-hosted, labels: [macos, host]
  • 테스트: Unit/Component + E2E 총 7/7 green
  • 수정 요약(E2E 포커스):
    • 초기 포커스 선점 방지: body 클릭으로 포커스 부여
    • 스킵링크 등 선행 포커서블 회피: Desktop 버튼에 명시적 focus()
    • 키보드 시나리오: Tab → Mobile 포커스 확인 → Space로 토글 → Shift+Tab → Enter
추가 CI 결과(2025-11-14)
  • E2E a11y 스모크 추가: 포커스 아웃라인(3px solid, 브랜드 컬러), 스킵링크 탭 순서(main 포커스 이동)
  • Exporter JS에 스킵링크 클릭 시 #maintabindex="-1" 부여 후 focus() 처리 → CI 그린 통과

머지 상태

  • 머지 방식: Squash and merge
  • 브랜치 정리: feat/ci-verify-run 삭제 완료

다음 스프린트 후보 과제

  • Exporter 회귀 스냅샷 보강(폼 토큰/검증 토큰 범위 확대)
  • 인스펙터 신규 섹션 추가 시 a11y 베이스라인 재사용 가이드 마련
  • 버튼 네이밍 일관성 전수 스캔 스모크 범위 확대
    • node 20 + npm ci
    • Vitest 실행 → Playwright 브라우저 설치 → E2E 실행

마일스톤

  1. 스캐폴딩 및 테스트 환경 구성
  2. 스키마 정의(Zod) 및 HTML Exporter TDD
  3. 컴포넌트/상태관리(Zustand) 및 인스펙터 기본 기능
  4. 접근성/키보드 네비게이션 강화 및 테스트 안정화
  5. CI/CD(Self-hosted Gitea) 연동 및 그린 검증

릴리스

v0.1.0

  • CI 검증 완료(Self-hosted Gitea runner, labels: [macos, host])
  • E2E 안정화: 초기 포커스 이슈 제거(Desktop 명시 focus), 스킵링크 고려
  • Exporter(Form) a11y: helptext ↔ aria-describedby, id/htmlFor 연결, 라디오 fieldset 설명 연결
  • i18n: en/ko 중첩 구조, 누락 키 보강(builder.action., builder.form.)
  • next-intl: 기본 timeZone=Asia/Seoul 설정으로 ENVIRONMENT_FALLBACK 경고 제거
  • 태그: v0.1.0

접근성 베이스라인 가이드(신규 섹션/인스펙터 적용 규칙)

  • 레이블 연결: 모든 입력은 id=ins-... 또는 id=field-... 부여, <label for>로 연결
  • 헬프텍스트 연결: 헬프 요소 id=...-help 생성, 입력에 aria-describedby로 연결
  • 라디오 그룹: 개별 <input> 대신 <fieldset>aria-describedby 적용, 옵션은 id/htmlFor로 연결
  • 버튼 네이밍: 보이는 텍스트 또는 aria-label로 안정적 접근 가능한 이름 제공
  • 키보드 활성화: Enter/Space 활성(onClick와 동일 동작), focus-visible 스타일 유지
  • 스킵링크: <a class="skip-link"> 제공, 포커스 시 화면 내 표시
  • 헤딩 레벨: hero=h1, 1뎁스 섹션=h2, FAQ 항목=h3 일관성 유지
  • 미리보기 토글: 토글 버튼에 aria-pressed, aria-controls, aria-describedby 연결
  • i18n 키: 중첩 객체 구조, 신규 키 추가 시 en/ko 동시 보강
  • 테스트 원칙: TDD로 유닛/컴포넌트/E2E 및 스냅샷 추가. 포커스/ARIA/토큰 스모크 포함

적용 가이드(신규 섹션 추가 시)

  • 1) 섹션 구현: <section class="<name>" aria-labelledby="<name>-heading"><h2 id="<name>-heading"> 구성
  • 2) 인스펙터 입력: id=ins-..., <label for>, 헬프텍스트 ...-help + aria-describedby 연결
  • 3) Exporter 반영: 섹션 렌더 함수에 위 구조/속성 부여
  • 4) 테스트 추가:
    • 공통 템플릿 사용: lib/exporter/testUtils/sectionA11y.tsassertSectionA11y(html)
    • 샘플: lib/exporter/sectionA11y.template.test.ts 참고하여 새 섹션 추가해 검증
    • 필요 시 개별 스냅샷/스모크: 헤딩/ARIA/키보드/토큰 추가 검증

다음 단계(E2E 초안)

  • 시나리오: 섹션 추가(Hero/FAQ/Features/Testimonials/CTA) → 인스펙터 편집 → Export ZIP 생성 → HTML 메타/섹션/폼 렌더 확인
  • 환경: Playwright
  • 체크리스트:
    • 섹션 추가/정렬/삭제 동작
    • 인스펙터 편집 반영(미리보기/Export HTML)
    • i18n 라벨 안정성(data-testid 사용)
    • a11y 스모크 무결성 유지

TDD 전략

  • 각 기능 단위로: 실패 테스트 작성 → 최소 구현 → 리팩터 → 커버리지 확인.
  • 단위: Vitest + Testing Library + jsdom. E2E: Playwright.
  • 접근성: axe-core 스모크 테스트.

작업 보드(요약)

  • 스키마/도메인(Zod)
  • HTML Exporter
  • 에셋 매니저
  • 폼 엔진
  • Sheets 연동
  • 빌더 UI
  • 내보내기 ZIP
  • i18n
  • a11y/SEO/OG/Analytics

오류/버그 기록

  • [2025-11-16 22:32] jsdom 환경에서 Moveable 내부 document.elementFromPoint 미구현으로 예외 발생 → Canvas.tsx에 테스트 전용 폴리필 추가(elementFromPoint: () => null)로 해결. 린트 any 제거 위해 타입(DocWithEFP) 선언 및 미사용 인자 경고 최소화.

  • [2025-11-13 22:55] BuilderClientPage 렌더 중 ref 접근 경고 / useRef.current를 렌더 중 참조하여 ESLint 경고 발생 / FormBuilderPanel 통합 시 useRef(createFormStore()) 사용 / useMemo(() => createFormStore(), [])로 스토어 인스턴스 고정하고 useSyncExternalStore에 직접 주입하여 해결 / 렌더 중 ref 접근 금지 원칙 재확인

  • [2025-11-14 09:35] Vitest 로그에서 i18n 누락 메시지 경고 확인(en: builder.action.addSelect/addRadio, builder.form.actionUrl/honeypot/minSeconds) → messages/en.json에 키 존재 여부 점검 및 누락 보완 후 재실행, 모든 테스트 그린 유지

  • [2025-11-14 09:45] 프리뷰 텍스트 매칭 불안정으로 테스트 실패 → data-testid 기반 안정화(preview-seo-title 등) 및 텍스트 매칭 지점 Title:Locale:로 조정, 전면 그린

  • [2025-11-14 20:30] E2E 실패: 스킵링크 활성화 후 main 포커스 미이동 → Exporter JS에 스킵링크 클릭 핸들러 추가(tabindex=-1 + focus()) → 로컬/CI 모두 그린

  • [2025-11-14 23:45] Gitea 라벨 중복 생성/페이로드 오류 → 워크플로 수정

    • auto-label.yml/auto-pr.yml에서 라벨 생성 로직 제거(기존 레이블만 사용)
    • 라벨 지정 시 JSON 페이로드를 {"labels":[id]} 형태로 고정
    • 다중 동일 라벨 존재 시 가장 작은 ID를 선택하도록 정렬 적용
    • ci.yml 자동 머지 가드: PR 라벨 조회 후 automerge 존재시에만 머지

CI 파이프라인 자동화 결과(automerge)

  • 브랜치 푸시 → auto-pr로 PR 생성 → auto-label로 automerge 부여(기존 레이블만) → ci.yml에서 테스트 그린 시 자동 머지
  • 안전장치: automerge 라벨 없으면 머지 스킵, PR 닫힘/드래프트 시 머지 스킵
  • 진단 로그: API status/라벨 JSON 일부/머지 응답 코드 출력으로 디버그 용이성 확보

충돌 해결 기록

  • [2025-11-14 23:50] .gitea/workflows/auto-pr.yml main 병합 충돌 해결
    • 충돌 마커 제거, 메시지 통일: "기존 'automerge' 라벨만, 생성 금지"
    • PR 번호 해석 재시도 루프 유지 및 라벨 ID 해석 로직 안정화

ZIP 검증 TDD(자산/메타)

  • 통합/메타 테스트 추가: zip.validation.integrated.test.ts, zip.validation.meta.test.ts
    • exportPage + buildZip 통합: 자산 경로 재작성 및 포함, 하위 폴더 포함 검증

ZIP 검증 TDD Phase4(루트 파일/manifest)

  • 테스트: zip.validation.phase4.test.ts
    • ZIP 루트에 favicon.ico / site.webmanifest / robots.txt 포함 검증
    • site.webmanifest JSON 파싱 유효성 검증(잘못된 JSON 시 에러)
  • 구현: lib/exporter/zip.ts
    • BuildZipInput.extras 추가(루트 파일 수집)
    • manifest JSON 파싱 검증 로직 추가
  • 브랜치/PR: feat/manifest-robots-auto → CI 트리거 및 자동 머지 대상(automerge 라벨 조건)

SEO/Perf 개선(TDD): theme-color, hero width/height

  • 테스트: lib/exporter/seoPerf.themeColor.heroDims.test.ts
    • <meta name="theme-color" content="#0ea5e9" /> 헤드 포함 검증(page.theme.primaryColor 사용)
    • hero 이미지에 width/height 속성 존재 검증(CLS 저감)
  • 구현: lib/exporter/html.ts
    • buildHead에 theme-color 메타 추가
    • renderHero 이미지 태그에 width/height 기본값 추가(1200x675)
  • 브랜치/PR: feat/seo-perf-themecolor-hero-dims → CI 트리거 및 자동 머지 대상

Export 옵션 기반(TDD): extras 오버라이드(robots/manifest)

  • 테스트: lib/exporter/extras.overrides.test.ts
    • robots.txt 내용 오버라이드(Disallow 경로) 반영 검증
    • manifest 필드(name/short_name/start_url/display/theme_color) 오버라이드 검증
  • 구현: lib/exporter/extras.ts
    • buildExtrasForPage(page, overrides?) 시그니처로 확장
    • overrides.robotsText/overrides.manifest 병합 적용, 기본값 유지
  • 브랜치/PR: feat/export-options-overrides → CI 트리거 및 자동 머지 대상

ZIP 검증 TDD Phase5(extras 내용 검증)

  • 테스트: lib/exporter/zip.validation.phase5.test.ts
    • robots.txt 텍스트 베이스라인 확인(User-agent: *)
    • site.webmanifest JSON 파싱 및 필수 키(start_url, display) 확인
  • 구현: 기존 buildExtrasForPage/buildZip 경로 사용(추가 구현 불필요)
  • 브랜치/PR: feat/zip-validation-phase5 → CI 트리거 및 자동 머지 대상

Export 옵션 UI 폴리시(TDD): display 드롭다운 + theme_color HEX 검증

  • 테스트: components/ExportOptions.polish.ui.test.tsx
    • Manifest display 입력이 드롭다운(select)으로 제공되고 옵션 값(standalone/minimal-ui/browser) 중에서만 선택됨을 검증
    • theme_color#RGB/#RRGGBB 형식의 HEX만 허용하며, 잘못된 값은 오버라이드에 반영되지 않음을 검증
  • 구현:
    • components/BuilderClientPage.tsx
      • display 입력을 <select>로 변경하고 옵션 추가
      • theme_color는 정규식 ^#([0-9a-fA-F]{3}){1,2}$ 통과 시에만 manifestOverrideBase.theme_color에 반영
  • 결과: 전체 테스트 그린(유닛/컴포넌트/E2E)
  • 브랜치/PR 제안: feat/export-ui-polish → CI 트리거 및 자동 머지 대상(automerge 라벨 조건)

폰트 최적화(TDD)

  • 테스트: lib/exporter/font.optimization.test.ts
    • head에 <link rel="preconnect" href="https://fonts.googleapis.com">, <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 존재 검증
    • CSS에 system-ui 폴백 포함 검증
  • 구현: lib/exporter/html.ts
    • head에 googleapis/gstatic preconnect 추가
    • CSS body font-family에 system-ui 폴백 유지
  • 브랜치/PR: feat/font-optimization → CI 트리거 및 자동 머지 대상(automerge 라벨 조건)

폼 제출: Google Sheets 템플릿 Export 포함(TDD)

  • 테스트: lib/exporter/sheets.template.test.ts
    • actionUrl 미지정 시 ZIP extras에 sheets/Code.gs, sheets/README.txt 포함 검증
    • actionUrl 지정 시 extras에 포함되지 않음 검증
  • 구현: lib/exporter/extras.ts
    • buildExtrasForPage(page)로 extras 생성
    • Apps Script Code.gs/README.txt 기본 템플릿 제공
  • 브랜치/PR: feat/form-sheets-template → CI 트리거 및 자동 머지 대상(automerge 라벨 조건)

site.webmanifest/robots.txt 자동 생성(TDD)

  • 테스트: lib/exporter/manifest.robots.auto.test.ts
    • extras에 site.webmanifest/robots.txt 자동 포함 검증
    • manifest 파생 키 검사(name/short_name/start_url/display/theme_color)
  • 구현: lib/exporter/extras.ts
    • buildExtrasForPage(page)에서 robots.txt/manifest 자동 생성 및 포함
  • 브랜치/PR: feat/manifest-robots-auto → CI 트리거 및 자동 머지 대상(automerge 라벨 조건)

의사결정 기록(ADR)

  • [YYYY-MM-DD] Next.js(App Router) 채택: 생태계/배포/서버리스/SEO 편의성.

  • [YYYY-MM-DD] Sheets 기본 방안: Apps Script 웹앱 템플릿(사용자 편의/트래픽 분산).

  • [YYYY-MM-DD] 로컬 이미지 업로드 허용: 서버 저장 없이 ZIP에 assets/로 패키징(브라우저 JSZip 우선).

  • [ci] automerge 재검증용 ping (2025-11-14 21:36:38)

  • [ci] auto-pr 라벨 자동부여 재검증 ping (2025-11-14 21:45:14)

  • [ci] auto-label 재트리거 ping (2025-11-14 21:56:22)

  • [ci] automerge 라벨 수동부여 후 재트리거 ping (2025-11-14 22:01:41)

  • [ci] full auto pipeline 재검증 ping (2025-11-14 22:09:02)

  • [ci] auto-label payload fix 재검증 ping (2025-11-14 22:15:14)

  • [ci] auto pipeline 최종 재검증 최종 ping (2025-11-14 22:26:59)

Export ZIP 무결성 강화(Option2) 진행 현황

목표

  • Export ZIP 무결성 강화: 자산 무결성 지표와 번들 검증 체계 구축
  • CI/CD(Gitea)에서 PR 자동화, 라벨링, 조건부 머지 적용
  • 모든 개발은 TDD로 진행하고, 기능 단위 커밋/대브랜치 단위 푸쉬

브랜치 전략

  • 대범위 기능 묶음 브랜치: feat/export-bundle-integrity
  • 관련 기능 완료까지 로컬 커밋만 진행, 완료 시점에 일괄 푸쉬

진행 현황

  • SHA-256(base64) 무결성 구현 및 assets.integrity.index.json 생성
  • assets.index.json 그룹 정렬/경로 전략(grouped/flat) 커버리지 확장
  • referencedAt 추적(HTML 내 rewriteUrl('local:...')) 매트릭스 테스트
  • verifyIntegrity 유틸 구현 및 TDD(정상/변조/누락/번들해시 불일치) 통과
  • assets.integrity.index.jsonsize(byte) 추가, verifyIntegrity에서 크기 불일치 검증
  • verifyIntegrity 결과에 summary(total/missing/integrityMismatches/sizeMismatches/bundleHashMismatch) 추가
  • Export Options(폰트 프리커넥트/프리로드, 이미지 로딩/디코딩, robots 메타) UI/문서화
  • 브랜치 푸쉬 완료: feat/export-bundle-integrity
  • 이미지 최적화 옵션(1차): fetchpriority/srcset/sizes 추가 및 테스트 그린
  • 이미지 최적화 옵션(2차): imageAutoSizes(srcset만 있을 때 기본 sizes 자동 적용)
  • 이미지 최적화 옵션(3차): imageSizesPreset(mobile-first/desktop-fixed) + 우선순위(imageSizes > preset > auto > none)
  • 브랜치/PR: feat/image-optimization 푸쉬 완료
  • verifyIntegrity CLI 래퍼(TDD) 추가: lib/exporter/verify.cli.ts + 테스트
  • 폴더 기반 직렬화 유틸(TDD) 추가: lib/exporter/verify.serialize.ts + 테스트
  • CI 통합(권장 플로우): .gitea/workflows/ci.yml./artifacts/export 존재 시 검증 스텝 자동 실행
  • 스크립트: scripts/make-verify-json.js, scripts/verify.js + npm run verify:make-json/verify:run
  • 브랜치/PR: feat/verify-integrity-cli 푸쉬 완료

하드닝

  • ZIP 경로 가드: ../, 절대경로(/), 역슬래시(\) 차단 (테스트 포함)

TDD 계획 및 상태

  • 테스트 선작성 → 최소 구현 → 리팩터/문서화
  • 현재 테스트: 110 files / 173 tests 모두 통과
  • 무결성 인덱스/검증에 대한 정상/오류 케이스 커버 완료

오류/디버깅 로그 요약

  • 그룹 경로(other) 정규식 기대값 수정으로 테스트 안정화
  • SHA-256 재계산 불일치 회피: 테스트에서 중복 해시 구현 제거, 산출물 기준 비교
  • Node/브라우저 호환 btoa/atob 제거, 안전한 변환 로직 적용

다음 작업

  1. PR 생성/머지 순서 관리: feat/image-optimization, feat/verify-integrity-cli → main
  2. CI 검증 리포트 형식 보강: 요약 필드(total/missing/...)를 워크플로 로그에 하이라이트
  3. PR 라벨 자동화 및 조건부 머지(Gitea Actions/웹훅) 유지
  4. 변환 파이프라인(AVIF/WebP) 설계 초안(TDD 스텁)
  5. README/개발자 가이드에 ./artifacts/export 폴더 정책 명시(로컬/CI 공통)

산출물 경로 정책(CI)

  • 기본 폴더: ./artifacts/export/ (ZIP 해제 또는 직접 생성)
  • 검증 입력: npm run verify:make-json./artifacts/extras.json/./artifacts/assets.json
  • 검증 실행: npm run verify:run (종료코드 0/1/2)

CI/CD 계획(초안)

  • PR 오픈 시: 라벨 자동 지정, 테스트/빌드, 무결성 검증 스텝 실행
  • main 머지 조건: 테스트/빌드 통과 + 무결성 검증 통과 + 최소 라벨 셋 충족

커밋/푸쉬 원칙

  • 기능 단위 커밋은 한국어 메시지
  • 대범위 브랜치 완료 전에 푸쉬 금지(현 단계는 완료되어 푸쉬 수행)

WYSIWYG 캔버스(드림위버 스타일) 계획

목표

  • 컨텐츠 영역에서 오브젝트를 자유 배치(드래그/리사이즈/회전)하여 랜딩을 구성
  • 웹표준/접근성/모바일·PC 반응형을 모두 충족하는 Export 산출

핵심 설계 원칙

  • 의미론적 HTML Export: Text→h1~h6/p, Button→a/button, Image→img/picture
  • 접근성
    • 레이어(탭) 순서 ↔ DOM 순서 매핑, 필요 시 탭 순서 수동 설정
    • 이미지 alt/버튼 aria-label 필수, 헤딩 레벨 강제, 키보드 포커스/스킵링크 유지
    • 색 대비/타겟 크기 가이드 및 경고
  • 반응형
    • 1차: 고정 비율 스케일(MVP)
    • 2차: 브레이크포인트 재배치(선택 그룹)
    • 3차: 유동 단위/컨테이너 쿼리, clamp 폰트 확장
  • 품질 가드: 8px 격자 + 가장자리/중심 스냅, 오버랩/가독성 경고, 역할/속성 프리셋

기술/구현 전략

  • 모델: Frame/Layer/Object(Text/Image/Button) + 의미/접근성 필드(zod 스키마)
  • 편집: Moveable로 드래그/리사이즈/회전 + 스냅/가이드/키보드 nudge
  • Export: 오브젝트→의미론 태그 매핑, 반응형(스케일→브레이크포인트), 이미지 최적화 옵션 반영

마일스톤

  • M1: 캔버스 조작(드/리/회+스냅) TDD → 최소 구현
  • M2: 레이어/속성 패널 TDD → UI 연결(순서/잠금/숨김, 위치/크기/회전/폰트/색/링크/이미지)
  • M3: Export(의미론+스케일) TDD → ZIP 생성 연동
  • M4: a11y/반응형 회귀 스모크 확대 + 품질 가드/경고

진행 현황

  • 브랜치: feat/wysiwyg-canvas 생성

  • 스키마 TDD 완료: lib/wysiwyg/schema.ts(+test)

  • 다음 작업: Moveable 도입 캔버스 조작 TDD(8px 격자/가장자리 스냅/키보드 nudge)

  • 스냅 유틸 공통화 완료: lib/wysiwyg/snap.ts (snap8, snapAngle) + 테스트 lib/wysiwyg/snap.test.ts

    • 정책: 위치 8px 스냅, 회전 15° 스냅(Shift시 1° 미세제어)
    • 경계 보정: 0<|deg|<15 구간은 15°/15°로 스냅되도록 조정
  • Moveable 통합 완료(TDD→구현→그린)

    • 설치: react-moveable, moveable
    • 스모크: Moveable가 드래그/리사이즈/회전 + 8px 스냅 그리드 props로 렌더
    • 이벤트 연동: onDrag/onResize/onRotate에서 snap8/snapAngle 적용, 가이드라인 표시/해제
    • Shift 1° 미세 제어 타입가드(isShiftEvent) 도입
    • target 바인딩: 선택 객체의 data-testid 셀렉터([data-testid="obj-<id>"])를 Moveable target으로 전달

다음 작업(캔버스 Moveable 통합)

  • 패키지 추가: react-moveable, moveable
  • TDD: Moveable 기반 드래그/리사이즈/회전 일원화 테스트 + 8px/15° 스냅 및 가이드라인 표시/해제 검증
  • 구현: components/wysiwyg/Canvas.tsx에 Moveable 적용, 기존 조작 로직 치환, 공통 스냅 유틸 사용
  • 회귀: 기존 키보드 nudge/가이드라인 테스트 유지 그린