From f7fa433bdd928ffa0fc489e697324abfa9dd26a4 Mon Sep 17 00:00:00 2001 From: Jaybe Date: Mon, 17 Nov 2025 19:26:32 +0900 Subject: [PATCH 1/9] =?UTF-8?q?=EB=AC=B8=EC=84=9C:=20MAIN=5FPLAN=20?= =?UTF-8?q?=EC=B5=9C=EC=8B=A0=20=EB=B0=B0=EC=B9=98=20=EA=B8=B0=EB=A1=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80(=EB=A7=88=ED=80=B4=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=EA=B7=B8=EB=A3=B9=20=EB=93=9C=EB=9E=98=EA=B7=B8/=EB=A6=AC?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=EC=A6=88=C2=B7=ED=9A=8C=EC=A0=84=20E2E,=20?= =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=98=B5=EC=85=98=202=EC=B0=A8,?= =?UTF-8?q?=20CSS=20=ED=86=A0=ED=81=B0=20=EB=94=A5=20=ED=9A=8C=EA=B7=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MAIN_PLAN.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/MAIN_PLAN.md b/MAIN_PLAN.md index a0c1f3c..c76c65b 100644 --- a/MAIN_PLAN.md +++ b/MAIN_PLAN.md @@ -248,6 +248,36 @@ - 이동하지 않은 축은 기존 좌표 유지하여 비의도 축 보정 방지 - 결과: 전체 테스트 그린(168 파일 / 295 테스트) +### CI E2E 수정: 마퀴 시작 판정 완화 (2025-11-17) +- 원인: 빈 영역 드래그 시에도 마퀴가 시작해야 하나, 캔버스 data-testid 직접 비교로 인해 실제 히트박스에서 시작 누락 → aria-live "선택 0개" 유지 +- 조치: `components/wysiwyg/Canvas.tsx`의 `onMouseDown`에서 객체/핸들 근접 여부 검사(`closest`)로 판정 완화, 객체/핸들이 아닌 영역이면 항상 마퀴 시작 +- 결과: E2E 라이브리전 멀티선택 문구 안정화, 전체 테스트 그린 유지 + +### E2E 스모크: 그룹 드래그 스냅 델타 동일 적용 (2025-11-17) +- 테스트: `tests/e2e/wysiwyg-group-drag.e2e.ts` + - 마퀴로 2개 선택 후, 선택 중 하나를 +16px 드래그 → 8px 스냅 반영 동일 델타로 양 객체 이동 검증 +- 기대/결과: 스냅된 델타(dx)가 선택 전체에 동일하게 적용됨 확인 + +### E2E 스모크 확장: 그룹 리사이즈(E)/회전(15°) + 가이드 라이브리전 (2025-11-17) +- 테스트: `tests/e2e/wysiwyg-group-resize-rotate.e2e.ts` + - 그룹 E 핸들 리사이즈: 좌표 고정, width만 스냅 증가, 두 객체 동일 증분 + - 회전 핸들: 15° 단위 스냅, 드래그 중 라이브리전에 가이드 좌표 문구 포함 +- 결과: 그룹 액션 스냅/가이드 UX 안정화 스모크 통과 + +### 이미지 최적화 옵션 2차 TDD (2025-11-17) +- 테스트: `lib/exporter/image.responsive.options.matrix.test.ts` + - loading/decoding/fetchpriority 적용 + - srcset + autoSizes + preset 동시 제공 시 preset 우선 + - explicit sizes가 있으면 preset/autoSizes보다 우선 +- 구현 상태: `lib/exporter/html.ts`의 기존 로직이 요구사항 충족(우선순위: explicit > preset > default(autoSizes)) + +### Export CSS 토큰 딥 회귀 (폼/테마) (2025-11-17) +- 테스트: `lib/wysiwyg/export.style.tokens.form.deep.test.ts` + - 토큰 존재: `--ring`, `--ring-offset`, `--disabled-bg`, `--disabled-fg` + - 사용 검증: `:focus-visible` 링/오프셋, `:disabled` BG, `[readonly]` FG + - 테마 프리셋: `[data-theme="light|dark"]`에서 `--bg` 제공, `.frame{background:var(--bg)}` 유지 +- 결과: 토큰/참조/테마 오버라이드 회귀 보강, 전체 테스트 그린(170 파일/300 테스트) + ## 목표 - 비개발자도 사용 가능한 단일 페이지 랜딩 페이지 빌더. - 결과물: 정적 ZIP(HTML/CSS/JS) 다운로드. 이미지: 외부 URL 또는 로컬 업로드(서버 저장 없음, ZIP에 패키징). 폰트는 외부 CDN 허용. -- 2.52.0 From 28f9548f16f67ea9e193d30e34e932b84208bfe5 Mon Sep 17 00:00:00 2001 From: Jaybe Date: Mon, 17 Nov 2025 19:45:31 +0900 Subject: [PATCH 2/9] =?UTF-8?q?Export=20A11y=20=ED=9A=8C=EA=B7=80=20?= =?UTF-8?q?=EB=A7=A4=ED=8A=B8=EB=A6=AD=EC=8A=A4:=20input/select/textarea?= =?UTF-8?q?=20label/for=C2=B7describedby,=20radio/checkbox=20fieldset?= =?UTF-8?q?=C2=B7legend=C2=B7help=20=EA=B2=80=EC=A6=9D=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/wysiwyg/export.form.a11y.matrix.test.ts | 65 +++++++++++++++++++ .../export.style.tokens.form.deep.test.ts | 35 ++++++++++ 2 files changed, 100 insertions(+) create mode 100644 lib/wysiwyg/export.form.a11y.matrix.test.ts create mode 100644 lib/wysiwyg/export.style.tokens.form.deep.test.ts diff --git a/lib/wysiwyg/export.form.a11y.matrix.test.ts b/lib/wysiwyg/export.form.a11y.matrix.test.ts new file mode 100644 index 0000000..54a2a72 --- /dev/null +++ b/lib/wysiwyg/export.form.a11y.matrix.test.ts @@ -0,0 +1,65 @@ +import { describe, it, expect } from 'vitest' +import { exportFrame } from '@/lib/wysiwyg/export' +import type { Frame } from '@/lib/wysiwyg/schema' + +function frameWithForm(): Frame { + return { + id: 'f', width: 600, height: 400, background: '#ffffff', + layers: [ + { + id: 'base', name: 'Base', visible: true, locked: false, objects: [ + { id: 't1', type: 'input', x: 40, y: 40, width: 200, height: 32, rotate: 0, zIndex: 0, props: { name: 'email', label: 'Email', placeholder: '', required: true, help: 'We never share your email.' } }, + { id: 't2', type: 'select', x: 40, y: 100, width: 200, height: 32, rotate: 0, zIndex: 0, props: { name: 'country', label: 'Country', options: ['KR','US'], defaultValue: 'KR', help: 'Pick one' } }, + { id: 't3', type: 'textarea', x: 40, y: 160, width: 280, height: 80, rotate: 0, zIndex: 0, props: { name: 'msg', label: 'Message', placeholder: '', rows: 3, required: false, help: 'Optional' } }, + { id: 't4', type: 'radio', x: 320, y: 40, width: 200, height: 90, rotate: 0, zIndex: 0, props: { name: 'plan', legend: 'Plan', options: ['Basic','Pro'], defaultValue: 'Basic', required: false, help: 'Choose your plan' } }, + { id: 't5', type: 'checkbox', x: 320, y: 150, width: 200, height: 60, rotate: 0, zIndex: 0, props: { name: 'agree', legend: 'Agree to terms', options: ['Yes'], defaultValues: [], required: false, help: 'You must agree' } }, + ] + } + ], + } +} + +describe('wysiwyg export - a11y matrix for form controls', () => { + it('label/for + aria-describedby for input/select/textarea', () => { + const out = exportFrame(frameWithForm()) + const html = out.html + // input + expect(html).toMatch(/