feat(sw): prepare reusable AI-friendly component package
- Ship an AI guide in the package (ai/: usage rules, component catalog, design rules, composition patterns) via package.json files. - Add examples A-D that import only public exports: default header, viewer header, feature header, and a consumer-side search extension that reuses SWHeaderInput and the public SW tokens. - Add SW-only checks: docs/API consistency, tarball consumer project (React 19 + TS + Vite typecheck/build) and a browser check (27/27). - No runtime source or public API change; README gains a pointer section. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No files matched your search
@@ -0,0 +1,116 @@
|
||||
# SW AI 재사용 템플릿 — 조사·구현·검증 기록 (2026-10-19)
|
||||
|
||||
> 범위: `@hm-design/sw`만. `@hm-design/ui`·조직도·Center Graphic·DWG/DXF Viewer는 변경하지 않았습니다. 작업 Worktree `D:\디자인시스템\HMDesign-SW-AI-Template`, 브랜치 `feature/sw-ai-template`, 시작 HEAD `50bf5bda8e956a4973b923d64131f2f2285bd38c`(`feature/sw-package`).
|
||||
>
|
||||
> AI용 사용 규칙·카탈로그·디자인 규칙·조합 패턴의 **원본은 패키지 안 [`packages/sw/ai/`](../../../packages/sw/ai/AI_USAGE.md)** 이며 npm 패키지에 포함됩니다. 이 문서는 조사 결과와 검증 기록만 담고 원본 내용을 반복하지 않습니다.
|
||||
|
||||
## 1. 결론
|
||||
|
||||
- 기존 공개 Export만으로 재사용(예제 A)·조합(예제 B·C)·확장(예제 D)이 모두 가능했습니다. **런타임 소스(`packages/sw/src`)와 공개 API는 변경하지 않았습니다.** 그래서 기존 SW Header의 디자인·기능·Props·Callback은 그대로입니다.
|
||||
- 변경은 패키지 배포 범위(`files`에 `ai` 추가), AI 가이드 문서 4종, 공개 API만 쓰는 예제, README 안내 1절, SW 전용 검증 스크립트 3개입니다.
|
||||
- 공개 API가 부족한 부분(More, 범용 필드 면, Header 면 레이아웃 등)은 내부 Import로 우회하지 않았습니다. §3 EXPORT CANDIDATE / MISSING으로 보고합니다.
|
||||
|
||||
## 2. 기존 구성 분석 요약
|
||||
|
||||
- 진입점: `src/index.ts` → `components/sw-header/index.ts`, `SWTheme`. 배포는 `exports` `"."`(ESM `dist/index.js` + `dist/index.d.ts`)와 `"./styles.css"` 두 개입니다. `main`·`types`가 있고 `module` 필드는 없습니다(ESM 전용 `"type": "module"`, `exports`가 우선). Peer는 `react`/`react-dom` `^19.0.0`, 런타임 dependency는 없습니다.
|
||||
- 자산: SVG·PNG 48개는 Vite library build에서 data URI로 JS에 포함됩니다(소비 프로젝트 33개 이미지 디코드 확인). 글꼴은 포함하지 않습니다.
|
||||
- CSS: 토큰은 `.hm-sw-header`와 `.hm-sw-theme`에만 정의됩니다. 컴포넌트 규칙은 대부분 `hm-sw-header__*` class 단위라서 `SWTheme` 안에서 단독 렌더링됩니다. 반응형은 `@container hm-sw-header`(12개 규칙)라 `SWHeader` 안에서만 적용됩니다. 전역 선택자는 없습니다.
|
||||
- Overlay: 필드 Dropdown은 absolute(필드 아래, `z-index` 30)이고 `closest(".hm-sw-header") ?? documentElement` 기준으로 좌우를 보정하므로 Header 밖에서도 동작합니다. 속성창·K 가이드는 `position: fixed`(`useSWHeaderOverlay`)이며 Portal·Dim은 없습니다.
|
||||
- `dist`에는 내부 모듈 `.d.ts`도 생성되지만 `exports`가 deep import를 막습니다.
|
||||
|
||||
## 3. 분류
|
||||
|
||||
### PUBLIC READY (현재 외부 재사용 가능)
|
||||
|
||||
| 컴포넌트 | 근거 |
|
||||
|---|---|
|
||||
| `SWHeader` | 공식 기준 템플릿. 예제 A에서 1920 좌표가 Figma와 일치(0·80 / 65·310 / 377·1301 / 1680·240, 49px) |
|
||||
| `SWTheme` | 단독 사용 토큰 범위. 소비 프로젝트에서 토큰·글꼴 확인 |
|
||||
| `SWHeaderHome`, `SWHeaderDocumentTitle` | 예제 B·C 렌더링, 문서 Dropdown `+ New` Callback |
|
||||
| `SWHeaderLayerMenu`, `SWHeaderColorMenu`, `SWHeaderLineStyleMenu`, `SWHeaderTextStyleMenu`, `SWHeaderDimensionStyleMenu` | 예제 B·C·D에서 열기·선택 Callback·Escape Focus 복귀 |
|
||||
| `SWHeaderActions` | `showPrint` / `showPopupGuide`로 K만·인쇄만. K 가이드 Hover·Click |
|
||||
| `SWHeaderInput`, `parseSWHeaderColor`, `parseSWHeaderNumber` | Input은 예제 D에서 Enter 확정·Escape 복원 확인. 파서는 이번 미검증 |
|
||||
| `SWHeaderLayerControl` 외 필드 버튼 4종, `SWHeaderWindowControls`, `SWHeaderProperties`, `SWHeaderPropertyBar`, `SWHeaderPropertyPanel`, `SWHeaderLinePreview`, `swHeaderLinePatterns`, 공개 타입 | 공개·문서화 상태(기존 사용 가이드 §4·§8). 이번 소비 검증 범위 밖 |
|
||||
|
||||
### EXPORT CANDIDATE (내부 구현 있음, 공개 API 정비 필요)
|
||||
|
||||
| 내부 구현 | 재사용 가치 | 공개 전 필요한 정비 |
|
||||
|---|---|---|
|
||||
| `SWHeaderMore` | 조합 Header의 Overflow | tier·`data-visible`·container 규칙이 `SWHeader` 구조에 결합. 항목 visibility API 설계 필요 |
|
||||
| `SWHeaderField` / `SWHeaderFieldText` | 새 필드형 컨트롤이 같은 면을 재사용(예제 D 표면 CSS 대체) | `variant`가 5개 고정 폭. 범용 폭·`children` 계약 필요 |
|
||||
| `SWHeaderKGuide` | pill 없는 K 단독 버튼 | 현재 `SWHeaderActions showPrint={false}`로 대체 가능. 수요 확인 후 |
|
||||
| `useSWHeaderDropdown`, `SWHeaderMenuDetail`, `SWHeaderColorChip` | 새 Dropdown 동작·`… 상세` pill·색상 칩 | 반환값 계약(ref·style·shifted)과 CSS 의존 정리 |
|
||||
| Header 면(객체 수정 / 창 영역 / 막대) | 조합 Header 배치 | 현재 `SWHeader` 내부 DOM. 예제는 소비 CSS + 공개 토큰으로 구성 |
|
||||
|
||||
### INTERNAL ONLY
|
||||
|
||||
`SWHeaderLayerRows`, `SWHeaderColorPanelBody`, `SWHeaderLineStyleColumns`(헤더 Dropdown과 속성창 Picker의 공유 본문), `SWHeaderPropertyPickerField`, `propertyPicker`, `propertyLineTypes`, `SWHeaderLayerIconArt`, `SWHeaderLayerToggle`, `layerIconOn`, `SWHeaderCommitPresence`, `useSWHeaderOverlay`, `useSWHeaderColorMode`, `SWHeaderKGuidePreview`. 상위 공개 컴포넌트의 상태·Focus 계약에 묶여 있어 단독 공개 시 중복 상태 위험이 있습니다.
|
||||
|
||||
### MISSING (SW 패키지에 없음)
|
||||
|
||||
검색 컨트롤(예제 D에서 소비 프로젝트 확장으로 작성), 범용 SW Button / IconButton, 범용 Tooltip 컴포넌트(창 이동 Tooltip은 CSS 전용), 조합 Header용 반응형 More, Header 면 레이아웃 컴포넌트.
|
||||
|
||||
### 발견한 재사용 제약 (코드 미변경, 문서화)
|
||||
|
||||
1. `SWHeaderDocumentTitle`은 앞의 로고와 겹치는 전제로 왼쪽 테두리가 없습니다 → 첫 칸이면 소비 wrapper에 테두리(예제 C).
|
||||
2. `SWHeaderDocumentTitle` 폭은 `100cqw` 기반이라 Header 밖에서는 뷰포트 폭을 기준으로 계산합니다.
|
||||
3. 창 영역 배경·테두리는 `SWHeader` DOM에 있어 `SWHeaderActions`/`SWHeaderWindowControls` 단독 사용 시 소비 면이 필요합니다.
|
||||
4. `body` margin 등 페이지 기본값은 소비 앱 책임입니다(첫 소비 실행에서 8px 오프셋으로 확인 → 테스트 앱에서 지정).
|
||||
|
||||
## 4. 변경 파일
|
||||
|
||||
| 파일 | 내용 |
|
||||
|---|---|
|
||||
| `packages/sw/package.json` | `files`: `["dist"]` → `["dist", "ai"]` |
|
||||
| `packages/sw/README.md` | 끝에 "AI 코딩 도구용 가이드" 절 추가(기존 본문 무변경) |
|
||||
| `packages/sw/ai/AI_USAGE.md`, `COMPONENT_CATALOG.md`, `DESIGN_RULES.md`, `COMPOSITION_PATTERNS.md` | AI 가이드(배포 포함) |
|
||||
| `packages/sw/ai/examples/*` | 예제 A~D, `SWSearchControl`(소비 확장), 소비 배치 CSS, 샘플 데이터 |
|
||||
| `packages/sw/tests/ai-docs.check.mjs` | 문서·예제와 공개 Export·링크 일치 검사 |
|
||||
| `packages/sw/tests/ai-template-consumer.mjs` | `npm pack` → 저장소 밖 독립 React 19 + TS 7 + Vite 8 프로젝트 생성, Tarball 설치, 설치된 `ai/examples` 복사, `tsc`·`vite build` |
|
||||
| `packages/sw/tests/ai-template-consumer.browser.mjs` | 소비 빌드 브라우저 검증(Chrome, Playwright) |
|
||||
| `docs/sw/ai-template/` | 이 기록과 `evidence/`(스크린샷 7장, `report.json`) |
|
||||
|
||||
## 5. 이번에 실행한 검증 (2026-10-19)
|
||||
|
||||
| 항목 | 결과 |
|
||||
|---|---|
|
||||
| `npm run typecheck -w @hm-design/sw` | PASS |
|
||||
| `npm run build -w @hm-design/sw` | PASS(`dist/index.js` 712.25 kB, `styles.css` 102.25 kB) |
|
||||
| `npm pack`(실제 tarball, Publish 없음) | PASS — 40개 파일, `ai/` 문서 4 + 예제 7, `dist`, `README.md`, `package.json` |
|
||||
| `node packages/sw/tests/ai-docs.check.mjs` | 19/19 PASS — 문서에 쓴 SW 이름 전부 공개 Export, "공개하지 않은 것" 목록에 Export 없음, 예제 Import 전부 공개·deep import 없음, 패키지 상대 링크 정상 |
|
||||
| 독립 소비 프로젝트 설치(`react@19.3.0`, `typescript@7.0.2`, `vite@8.3.0`, `@vitejs/plugin-react@6.1.1`) | PASS |
|
||||
| 소비 프로젝트 `tsc`(strict, `noUnusedLocals`, `skipLibCheck: false`) | PASS — 설치된 패키지의 예제를 그대로 컴파일 |
|
||||
| 소비 프로젝트 Production Build | PASS(번들 500 kB 경고만, 오류 없음) |
|
||||
| 소비 빌드 브라우저(Chrome 1920 × 1080) | 27/27 PASS([report.json](./evidence/report.json)) |
|
||||
|
||||
브라우저 27개 항목: 자산 33개 data URI 디코드 · CSS/토큰 로딩 · A 기본 Header 좌표 = Figma · A 레이어 선택 / 종료 Callback · B 순서(DOM) · B 크기(49 / 80 / 파일 x65 / 234·192 × 38) · B 문서 `+ New` · B 레이어 열림 · Escape 닫힘 + Focus 복귀 · B 색상 선택(열림 유지) · B K 가이드 Hover · B K Click · C 순서 · C 파일 왼쪽 테두리 · C 선·문자·치수 선택과 필드 값 반영 · C 인쇄 · D 검색 필드 표면 = 레이어 필드(높이·위치·테두리·radius·배경·그림자·라벨 12px) · D Hover 링(테두리만 노랑, 내부 픽셀 불변) · D Enter → 검색 · D 버튼 검색 · D Escape 복원 · D 색상 선택 · B Tab 순서 = 표시 순서 · 페이지/콘솔 오류 0.
|
||||
|
||||
중간 실패와 수정: 첫 실행 24/27(소비 앱 `body` 8px margin, 예제 막대 `box-sizing`, favicon 404) → 소비 앱·예제 CSS 수정. 육안 확인 중 D의 Hover 링이 필드 전체를 덮는 문제(표준 `mask` 뒤에 `-webkit-mask` 선언) 발견 → 선언 순서 수정, 테스트에 픽셀 검사 추가 후 재실행 PASS.
|
||||
|
||||
증거: [A](./evidence/A-default-1920.png) · [B](./evidence/B-viewer-1920.png) · [B 레이어](./evidence/B-layer-open.png) · [B K](./evidence/B-k-guide.png) · [C](./evidence/C-feature-1920.png) · [D](./evidence/D-search-1920.png) · [D Hover](./evidence/D-search-hover.png).
|
||||
|
||||
재현:
|
||||
|
||||
```bash
|
||||
npm run build -w @hm-design/sw
|
||||
node packages/sw/tests/ai-docs.check.mjs
|
||||
node packages/sw/tests/ai-template-consumer.mjs <저장소 밖 빈 폴더>
|
||||
node packages/sw/tests/ai-template-consumer.browser.mjs <같은 폴더> <증거 폴더>
|
||||
```
|
||||
|
||||
## 6. NOT VERIFIED
|
||||
|
||||
- Firefox·Safari에서 소비 빌드(이번에는 Chrome만). 예제 D의 mask는 표준 + prefix 둘 다 선언했으나 Firefox 미실행.
|
||||
- 이번 소비 검증에서 직접 렌더링하지 않은 공개 컴포넌트: 필드 버튼 5종 단독, `SWHeaderWindowControls` 단독(창 이동 Motion), `SWHeaderProperties`, `SWHeaderPropertyBar`, `SWHeaderPropertyPanel`(13개 필드·4상태), `SWHeaderLinePreview`, 파서 함수. 기존 Storybook 검증 기록만 있습니다.
|
||||
- `SWHeader`의 반응형 More·10개 폭, Colorize, 게이지바, 속성창 연결: 런타임 코드를 바꾸지 않았으므로 이번에 다시 실행하지 않았습니다(과거 PASS 기록은 기존 문서).
|
||||
- Noto Sans KR 실제 로드(소비 테스트는 시스템 대체 글꼴).
|
||||
- 저장소 루트 전체 `npm run typecheck`·Storybook build(SW 패키지 단위만 실행).
|
||||
- `README.md`의 기존 `../../docs/...` 링크는 Tarball 안에서 열리지 않습니다(기존 상태, `ai/` 문서는 패키지 내부 링크만 사용).
|
||||
|
||||
## 7. 추가 개선 후보
|
||||
|
||||
1. `SWHeaderField` 범용 폭 공개 → 새 필드형 컨트롤이 표면 CSS를 다시 쓰지 않도록.
|
||||
2. 조합 Header용 More(또는 `SWHeaderMore` 공개 API) 설계.
|
||||
3. `SWHeaderDocumentTitle`에 단독 첫 칸용 왼쪽 테두리 옵션 검토(기본 Header 픽셀 영향 확인 필요).
|
||||
4. Header 면(막대·객체 수정·창 영역) 레이아웃 컴포넌트 검토.
|
||||
5. 소비 검증을 Firefox와 속성창 단독 사용까지 확장.
|
||||
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,276 @@
|
||||
{
|
||||
"checks": [
|
||||
{
|
||||
"name": "1 all SVG/PNG assets decode (data URI, no external path)",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"count": 33,
|
||||
"broken": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "1 styles.css loaded; SWTheme provides tokens",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"height": "49px",
|
||||
"font": "\"Noto Sans KR\", \"Malgun Gothic\", \"Apple SD Gothic Neo\", sans-serif"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2 A default header 1920 geometry = Figma (49 / 0·80 / 65·310 / 377·1301 / 1680·240)",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"header": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 1920,
|
||||
"h": 49
|
||||
},
|
||||
"home": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 80,
|
||||
"h": 48
|
||||
},
|
||||
"document": {
|
||||
"x": 65,
|
||||
"y": 0,
|
||||
"w": 310,
|
||||
"h": 48
|
||||
},
|
||||
"object": {
|
||||
"x": 377,
|
||||
"y": 0,
|
||||
"w": 1301,
|
||||
"h": 48
|
||||
},
|
||||
"window": {
|
||||
"x": 1680,
|
||||
"y": 0,
|
||||
"w": 240,
|
||||
"h": 48
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2 A layer dropdown select callback",
|
||||
"status": "PASS",
|
||||
"data": "layer:center"
|
||||
},
|
||||
{
|
||||
"name": "2 A close callback",
|
||||
"status": "PASS",
|
||||
"data": "close"
|
||||
},
|
||||
{
|
||||
"name": "3 B order Logo → File → Layer → Color → K (DOM)",
|
||||
"status": "PASS",
|
||||
"data": [
|
||||
"뷰어 홈",
|
||||
"hm-sw-header__document",
|
||||
"레이어",
|
||||
"색상",
|
||||
"팝업 가이드"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "3 B sizes keep SW rules (bar 49, home 80 overlapping file 15px, fields 234/192 x 38)",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"bar": {
|
||||
"x": 0,
|
||||
"y": 409,
|
||||
"w": 1920,
|
||||
"h": 49
|
||||
},
|
||||
"home": {
|
||||
"x": 0,
|
||||
"y": 409,
|
||||
"w": 80,
|
||||
"h": 48
|
||||
},
|
||||
"document": {
|
||||
"x": 65,
|
||||
"y": 409,
|
||||
"w": 310,
|
||||
"h": 48
|
||||
},
|
||||
"layer": {
|
||||
"x": 382,
|
||||
"y": 414,
|
||||
"w": 234,
|
||||
"h": 38
|
||||
},
|
||||
"color": {
|
||||
"x": 620,
|
||||
"y": 414,
|
||||
"w": 192,
|
||||
"h": 38
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "3 B document dropdown New callback",
|
||||
"status": "PASS",
|
||||
"data": "document:new"
|
||||
},
|
||||
{
|
||||
"name": "3 B layer dropdown opens under the field",
|
||||
"status": "PASS",
|
||||
"data": "true"
|
||||
},
|
||||
{
|
||||
"name": "3 B Escape closes and returns focus to the field",
|
||||
"status": "PASS",
|
||||
"data": null
|
||||
},
|
||||
{
|
||||
"name": "3 B color chip callback (dropdown stays open)",
|
||||
"status": "PASS",
|
||||
"data": "color:red"
|
||||
},
|
||||
{
|
||||
"name": "3 B K guide preview on hover (animation frame advances)",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"guide": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "3 B K click callback",
|
||||
"status": "PASS",
|
||||
"data": "popupGuide"
|
||||
},
|
||||
{
|
||||
"name": "4 C order File → 선 → 문자 → 치수 → 인쇄",
|
||||
"status": "PASS",
|
||||
"data": [
|
||||
"file",
|
||||
"선스타일",
|
||||
"문자스타일",
|
||||
"치수스타일",
|
||||
"인쇄"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "4 C first file well has a left edge (consumer wrapper)",
|
||||
"status": "PASS",
|
||||
"data": "1px"
|
||||
},
|
||||
{
|
||||
"name": "4 C line type callback + field value",
|
||||
"status": "PASS",
|
||||
"data": "lineType:center"
|
||||
},
|
||||
{
|
||||
"name": "4 C text style callback + field value",
|
||||
"status": "PASS",
|
||||
"data": "textStyle:dotum"
|
||||
},
|
||||
{
|
||||
"name": "4 C dimension style callback",
|
||||
"status": "PASS",
|
||||
"data": "dimensionStyle:iso"
|
||||
},
|
||||
{
|
||||
"name": "4 C print callback",
|
||||
"status": "PASS",
|
||||
"data": "print"
|
||||
},
|
||||
{
|
||||
"name": "5 D new search field matches the SW field surface (height/top/border/radius/background/shadow/label)",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"layer": {
|
||||
"h": 38,
|
||||
"top": 1232,
|
||||
"border": "1px rgb(8, 37, 28)",
|
||||
"radius": "3px",
|
||||
"bg": "linear-gradient(0deg, rgba(0, 0, 0, 0.45",
|
||||
"shadow": "rgba(9, 39, 30, 0.7) 0px 1px 4px 0px"
|
||||
},
|
||||
"search": {
|
||||
"h": 38,
|
||||
"top": 1232,
|
||||
"border": "1px rgb(8, 37, 28)",
|
||||
"radius": "3px",
|
||||
"bg": "linear-gradient(0deg, rgba(0, 0, 0, 0.45",
|
||||
"shadow": "rgba(9, 39, 30, 0.7) 0px 1px 4px 0px"
|
||||
},
|
||||
"label": "12px",
|
||||
"fieldLabel": "12px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "5 D hover ring like existing fields (edge turns yellow, inside unchanged)",
|
||||
"status": "PASS",
|
||||
"data": {
|
||||
"ring": "1",
|
||||
"before": {
|
||||
"inside": [
|
||||
18,
|
||||
50,
|
||||
39
|
||||
],
|
||||
"edge": [
|
||||
8,
|
||||
37,
|
||||
28
|
||||
]
|
||||
},
|
||||
"after": {
|
||||
"inside": [
|
||||
18,
|
||||
50,
|
||||
39
|
||||
],
|
||||
"edge": [
|
||||
172,
|
||||
131,
|
||||
3
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "5 D Enter commits through SWHeaderInput → search callback",
|
||||
"status": "PASS",
|
||||
"data": "search:WALL-01"
|
||||
},
|
||||
{
|
||||
"name": "5 D search button callback uses committed value",
|
||||
"status": "PASS",
|
||||
"data": "search:WALL-01"
|
||||
},
|
||||
{
|
||||
"name": "5 D Escape restores the committed value",
|
||||
"status": "PASS",
|
||||
"data": "WALL-01"
|
||||
},
|
||||
{
|
||||
"name": "5 D existing color dropdown in the same bar",
|
||||
"status": "PASS",
|
||||
"data": "color:blue"
|
||||
},
|
||||
{
|
||||
"name": "6 B Tab order = display order",
|
||||
"status": "PASS",
|
||||
"data": [
|
||||
"문서: C0020101-006-평면및종단면도(06)",
|
||||
"레이어",
|
||||
"색상",
|
||||
"팝업 가이드"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "7 no page/console errors",
|
||||
"status": "PASS",
|
||||
"data": []
|
||||
}
|
||||
],
|
||||
"errors": [],
|
||||
"warnings": [
|
||||
"Canvas2D: Multiple readback operations using getImageData are faster with the willReadFrequently attribute set to true. See: https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-will-read-frequently",
|
||||
"Canvas2D: Multiple readback operations using getImageData are faster with the willReadFrequently attribute set to true. See: https://html.spec.whatwg.org/multipage/canvas.html#concept-canvas-will-read-frequently"
|
||||
]
|
||||
}
|
||||