@su-record/vibe 2.8.34 → 2.8.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,26 @@
1
+ /**
2
+ * UI 캡처 — 스크린샷, DOM 추출, computed CSS 조회
3
+ *
4
+ * CDP(Chrome DevTools Protocol)를 통해 렌더링된 결과를 정밀하게 추출.
5
+ */
6
+ import type { CaptureScreenshotOptions, ElementComputedStyle } from './types.js';
7
+ /** 페이지 스크린샷 캡처 */
8
+ export declare function captureScreenshot(page: unknown, options: CaptureScreenshotOptions): Promise<string>;
9
+ /** 요소의 computed CSS + bounding box 추출 */
10
+ export declare function getComputedStyles(page: unknown, selector: string, properties: string[]): Promise<ElementComputedStyle | null>;
11
+ /** 페이지의 모든 매칭 요소에서 computed CSS 일괄 추출 */
12
+ export declare function getComputedStylesBatch(page: unknown, selector: string, properties: string[]): Promise<ElementComputedStyle[]>;
13
+ /** 페이지의 모든 텍스트 콘텐츠 추출 */
14
+ export declare function extractTextContent(page: unknown, selector?: string): Promise<Array<{
15
+ selector: string;
16
+ text: string;
17
+ }>>;
18
+ /** 페이지의 모든 이미지 src + 로드 상태 확인 */
19
+ export declare function extractImages(page: unknown): Promise<Array<{
20
+ src: string;
21
+ alt: string;
22
+ loaded: boolean;
23
+ width: number;
24
+ height: number;
25
+ }>>;
26
+ //# sourceMappingURL=capture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../../../src/infra/lib/browser/capture.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,wBAAwB,EACxB,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAEpB,kBAAkB;AAClB,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,MAAM,CAAC,CAyBjB;AAED,yCAAyC;AACzC,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAmCtC;AAED,yCAAyC;AACzC,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAkCjC;AAED,yBAAyB;AACzB,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,OAAO,EACb,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,KAAK,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAoBpD;AAED,iCAAiC;AACjC,wBAAsB,aAAa,CACjC,IAAI,EAAE,OAAO,GACZ,OAAO,CAAC,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAiB9F"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * UI 캡처 — 스크린샷, DOM 추출, computed CSS 조회
3
+ *
4
+ * CDP(Chrome DevTools Protocol)를 통해 렌더링된 결과를 정밀하게 추출.
5
+ */
6
+ /** 페이지 스크린샷 캡처 */
7
+ export async function captureScreenshot(page, options) {
8
+ const p = page;
9
+ if (options.selector) {
10
+ const element = await p.$(options.selector);
11
+ if (!element) {
12
+ throw new Error(`Element not found: ${options.selector}`);
13
+ }
14
+ const el = element;
15
+ await el.screenshot({
16
+ path: options.outPath,
17
+ type: options.format ?? 'png',
18
+ });
19
+ }
20
+ else {
21
+ await p.screenshot({
22
+ path: options.outPath,
23
+ fullPage: options.fullPage !== false,
24
+ type: options.format ?? 'png',
25
+ });
26
+ }
27
+ return options.outPath;
28
+ }
29
+ /** 요소의 computed CSS + bounding box 추출 */
30
+ export async function getComputedStyles(page, selector, properties) {
31
+ const p = page;
32
+ const result = await p.evaluate((...args) => {
33
+ const sel = args[0];
34
+ const props = args[1];
35
+ const el = document.querySelector(sel);
36
+ if (!el)
37
+ return null;
38
+ const computed = window.getComputedStyle(el);
39
+ const styles = {};
40
+ for (const prop of props) {
41
+ styles[prop] = computed.getPropertyValue(prop);
42
+ }
43
+ const rect = el.getBoundingClientRect();
44
+ return {
45
+ selector: sel,
46
+ styles,
47
+ box: {
48
+ x: Math.round(rect.x),
49
+ y: Math.round(rect.y),
50
+ width: Math.round(rect.width),
51
+ height: Math.round(rect.height),
52
+ },
53
+ };
54
+ }, selector, properties);
55
+ return result;
56
+ }
57
+ /** 페이지의 모든 매칭 요소에서 computed CSS 일괄 추출 */
58
+ export async function getComputedStylesBatch(page, selector, properties) {
59
+ const p = page;
60
+ const results = await p.evaluate((...args) => {
61
+ const sel = args[0];
62
+ const props = args[1];
63
+ const elements = document.querySelectorAll(sel);
64
+ return Array.from(elements).map((el, idx) => {
65
+ const computed = window.getComputedStyle(el);
66
+ const styles = {};
67
+ for (const prop of props) {
68
+ styles[prop] = computed.getPropertyValue(prop);
69
+ }
70
+ const rect = el.getBoundingClientRect();
71
+ return {
72
+ selector: `${sel}:nth-child(${idx + 1})`,
73
+ styles,
74
+ box: {
75
+ x: Math.round(rect.x),
76
+ y: Math.round(rect.y),
77
+ width: Math.round(rect.width),
78
+ height: Math.round(rect.height),
79
+ },
80
+ };
81
+ });
82
+ }, selector, properties);
83
+ return results;
84
+ }
85
+ /** 페이지의 모든 텍스트 콘텐츠 추출 */
86
+ export async function extractTextContent(page, selector) {
87
+ const p = page;
88
+ const results = await p.evaluate((...args) => {
89
+ const sel = args[0];
90
+ const elements = document.querySelectorAll(sel);
91
+ return Array.from(elements)
92
+ .filter(el => el.textContent?.trim())
93
+ .map((el, idx) => ({
94
+ selector: `${sel}:nth-child(${idx + 1})`,
95
+ text: el.textContent?.trim() ?? '',
96
+ }));
97
+ }, selector ?? 'h1, h2, h3, h4, h5, h6, p, span, a, button, li, label');
98
+ return results;
99
+ }
100
+ /** 페이지의 모든 이미지 src + 로드 상태 확인 */
101
+ export async function extractImages(page) {
102
+ const p = page;
103
+ const results = await p.evaluate(() => {
104
+ const images = document.querySelectorAll('img');
105
+ return Array.from(images).map(img => ({
106
+ src: img.src,
107
+ alt: img.alt,
108
+ loaded: img.complete && img.naturalWidth > 0,
109
+ width: img.naturalWidth,
110
+ height: img.naturalHeight,
111
+ }));
112
+ });
113
+ return results;
114
+ }
115
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../../../../src/infra/lib/browser/capture.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,kBAAkB;AAClB,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAa,EACb,OAAiC;IAEjC,MAAM,CAAC,GAAG,IAGT,CAAC;IAEF,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,sBAAsB,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,MAAM,EAAE,GAAG,OAA0E,CAAC;QACtF,MAAM,EAAE,CAAC,UAAU,CAAC;YAClB,IAAI,EAAE,OAAO,CAAC,OAAO;YACrB,IAAI,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;SAC9B,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,MAAM,CAAC,CAAC,UAAU,CAAC;YACjB,IAAI,EAAE,OAAO,CAAC,OAAO;YACrB,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,KAAK;YACpC,IAAI,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;SAC9B,CAAC,CAAC;IACL,CAAC;IAED,OAAO,OAAO,CAAC,OAAO,CAAC;AACzB,CAAC;AAED,yCAAyC;AACzC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAa,EACb,QAAgB,EAChB,UAAoB;IAEpB,MAAM,CAAC,GAAG,IAET,CAAC;IAEF,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,QAAQ,CAC7B,CAAC,GAAG,IAAe,EAAE,EAAE;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAW,CAAC;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAa,CAAC;QAClC,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAErB,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QAC7C,MAAM,MAAM,GAA2B,EAAE,CAAC;QAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACjD,CAAC;QAED,MAAM,IAAI,GAAG,EAAE,CAAC,qBAAqB,EAAE,CAAC;QACxC,OAAO;YACL,QAAQ,EAAE,GAAG;YACb,MAAM;YACN,GAAG,EAAE;gBACH,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;gBACrB,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;gBACrB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;gBAC7B,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;aAChC;SACF,CAAC;IACJ,CAAC,EACD,QAAQ,EACR,UAAU,CACX,CAAC;IAEF,OAAO,MAAqC,CAAC;AAC/C,CAAC;AAED,yCAAyC;AACzC,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,IAAa,EACb,QAAgB,EAChB,UAAoB;IAEpB,MAAM,CAAC,GAAG,IAET,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,QAAQ,CAC9B,CAAC,GAAG,IAAe,EAAE,EAAE;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAW,CAAC;QAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAa,CAAC;QAClC,MAAM,QAAQ,GAAG,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAChD,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE;YAC1C,MAAM,QAAQ,GAAG,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;YAC7C,MAAM,MAAM,GAA2B,EAAE,CAAC;YAC1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACjD,CAAC;YACD,MAAM,IAAI,GAAG,EAAE,CAAC,qBAAqB,EAAE,CAAC;YACxC,OAAO;gBACL,QAAQ,EAAE,GAAG,GAAG,cAAc,GAAG,GAAG,CAAC,GAAG;gBACxC,MAAM;gBACN,GAAG,EAAE;oBACH,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;oBACrB,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;oBACrB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;oBAC7B,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;iBAChC;aACF,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,EACD,QAAQ,EACR,UAAU,CACX,CAAC;IAEF,OAAO,OAAiC,CAAC;AAC3C,CAAC;AAED,yBAAyB;AACzB,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,IAAa,EACb,QAAiB;IAEjB,MAAM,CAAC,GAAG,IAET,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,QAAQ,CAC9B,CAAC,GAAG,IAAe,EAAE,EAAE;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAW,CAAC;QAC9B,MAAM,QAAQ,GAAG,QAAQ,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAChD,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;aACxB,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;aACpC,GAAG,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;YACjB,QAAQ,EAAE,GAAG,GAAG,cAAc,GAAG,GAAG,CAAC,GAAG;YACxC,IAAI,EAAE,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE;SACnC,CAAC,CAAC,CAAC;IACR,CAAC,EACD,QAAQ,IAAI,uDAAuD,CACpE,CAAC;IAEF,OAAO,OAAoD,CAAC;AAC9D,CAAC;AAED,iCAAiC;AACjC,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAa;IAEb,MAAM,CAAC,GAAG,IAET,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE;QACpC,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;QAChD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACpC,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,MAAM,EAAE,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,YAAY,GAAG,CAAC;YAC5C,KAAK,EAAE,GAAG,CAAC,YAAY;YACvB,MAAM,EAAE,GAAG,CAAC,aAAa;SAC1B,CAAC,CAAC,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,OAAO,OAA8F,CAAC;AACxG,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * UI 비교 — 스크린샷 diff, CSS 수치 비교
3
+ *
4
+ * Figma 원본 스크린샷 vs 렌더링 결과 비교,
5
+ * Figma CSS 수치 vs computed CSS 비교.
6
+ */
7
+ import type { ElementComputedStyle, ScreenshotDiff, StyleDiff, VerificationIssue } from './types.js';
8
+ /**
9
+ * 두 PNG 파일의 픽셀 단위 비교
10
+ *
11
+ * 정밀한 비교를 위해 pixelmatch 사용 (optional dependency).
12
+ * 없으면 파일 크기 기반 근사 비교로 폴백.
13
+ */
14
+ export declare function compareScreenshots(expectedPath: string, actualPath: string, diffOutputPath?: string): Promise<ScreenshotDiff>;
15
+ /** CSS 수치 비교 — Figma 기대값 vs 실제 렌더링 값 */
16
+ export declare function compareStyles(expected: Record<string, string>, actual: ElementComputedStyle): StyleDiff[];
17
+ /** StyleDiff → VerificationIssue 변환 */
18
+ export declare function diffsToIssues(diffs: StyleDiff[]): VerificationIssue[];
19
+ //# sourceMappingURL=compare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../../../src/infra/lib/browser/compare.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,SAAS,EACT,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,CAiDzB;AAsBD,wCAAwC;AACxC,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,MAAM,EAAE,oBAAoB,GAC3B,SAAS,EAAE,CA6Bb;AA2BD,uCAAuC;AACvC,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,iBAAiB,EAAE,CAerE"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * UI 비교 — 스크린샷 diff, CSS 수치 비교
3
+ *
4
+ * Figma 원본 스크린샷 vs 렌더링 결과 비교,
5
+ * Figma CSS 수치 vs computed CSS 비교.
6
+ */
7
+ import { readFileSync } from 'node:fs';
8
+ /**
9
+ * 두 PNG 파일의 픽셀 단위 비교
10
+ *
11
+ * 정밀한 비교를 위해 pixelmatch 사용 (optional dependency).
12
+ * 없으면 파일 크기 기반 근사 비교로 폴백.
13
+ */
14
+ export async function compareScreenshots(expectedPath, actualPath, diffOutputPath) {
15
+ try {
16
+ const { default: pixelmatch } = await import('pixelmatch');
17
+ const { PNG } = await import('pngjs');
18
+ const img1 = PNG.sync.read(readFileSync(expectedPath));
19
+ const img2 = PNG.sync.read(readFileSync(actualPath));
20
+ const width = Math.max(img1.width, img2.width);
21
+ const height = Math.max(img1.height, img2.height);
22
+ // 사이즈 맞추기 (작은 쪽을 확장)
23
+ const canvas1 = createCanvas(img1, width, height);
24
+ const canvas2 = createCanvas(img2, width, height);
25
+ const diffData = new Uint8Array(width * height * 4);
26
+ const diffPixels = pixelmatch(canvas1, canvas2, diffData, width, height, { threshold: 0.1 });
27
+ const totalPixels = width * height;
28
+ if (diffOutputPath) {
29
+ const diffImg = new PNG({ width, height });
30
+ diffImg.data = Buffer.from(diffData);
31
+ const { writeFileSync } = await import('node:fs');
32
+ writeFileSync(diffOutputPath, PNG.sync.write(diffImg));
33
+ }
34
+ return {
35
+ diffRatio: diffPixels / totalPixels,
36
+ diffPixels,
37
+ diffImagePath: diffOutputPath,
38
+ totalPixels,
39
+ };
40
+ }
41
+ catch {
42
+ // pixelmatch/pngjs 미설치 시 파일 크기 기반 근사 비교
43
+ const buf1 = readFileSync(expectedPath);
44
+ const buf2 = readFileSync(actualPath);
45
+ const sizeDiff = Math.abs(buf1.length - buf2.length);
46
+ const maxSize = Math.max(buf1.length, buf2.length);
47
+ return {
48
+ diffRatio: sizeDiff / maxSize,
49
+ diffPixels: -1,
50
+ totalPixels: -1,
51
+ };
52
+ }
53
+ }
54
+ /** 캔버스 크기 통일 (투명 배경으로 확장) */
55
+ function createCanvas(img, width, height) {
56
+ const data = new Uint8Array(width * height * 4);
57
+ for (let y = 0; y < img.height; y++) {
58
+ for (let x = 0; x < img.width; x++) {
59
+ const srcIdx = (y * img.width + x) * 4;
60
+ const dstIdx = (y * width + x) * 4;
61
+ data[dstIdx] = img.data[srcIdx];
62
+ data[dstIdx + 1] = img.data[srcIdx + 1];
63
+ data[dstIdx + 2] = img.data[srcIdx + 2];
64
+ data[dstIdx + 3] = img.data[srcIdx + 3];
65
+ }
66
+ }
67
+ return data;
68
+ }
69
+ /** CSS 수치 비교 — Figma 기대값 vs 실제 렌더링 값 */
70
+ export function compareStyles(expected, actual) {
71
+ const diffs = [];
72
+ for (const [property, expectedValue] of Object.entries(expected)) {
73
+ const actualValue = actual.styles[property];
74
+ if (!actualValue)
75
+ continue;
76
+ const normalizedExpected = normalizeCSS(property, expectedValue);
77
+ const normalizedActual = normalizeCSS(property, actualValue);
78
+ if (normalizedExpected !== normalizedActual) {
79
+ const diff = {
80
+ selector: actual.selector,
81
+ property,
82
+ expected: expectedValue,
83
+ actual: actualValue,
84
+ };
85
+ const expectedNum = parseFloat(expectedValue);
86
+ const actualNum = parseFloat(actualValue);
87
+ if (!isNaN(expectedNum) && !isNaN(actualNum)) {
88
+ diff.delta = Math.abs(expectedNum - actualNum);
89
+ }
90
+ diffs.push(diff);
91
+ }
92
+ }
93
+ return diffs;
94
+ }
95
+ /** CSS 값 정규화 (비교용) */
96
+ function normalizeCSS(property, value) {
97
+ let v = value.trim().toLowerCase();
98
+ // rgb → hex 변환
99
+ const rgbMatch = v.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
100
+ if (rgbMatch) {
101
+ const [, r, g, b] = rgbMatch;
102
+ v = '#' + [r, g, b].map(c => parseInt(c, 10).toString(16).padStart(2, '0')).join('');
103
+ }
104
+ // px 값 반올림
105
+ if (v.endsWith('px')) {
106
+ const num = parseFloat(v);
107
+ if (!isNaN(num))
108
+ v = Math.round(num) + 'px';
109
+ }
110
+ // color 속성의 특수 처리
111
+ if (property === 'color' || property.includes('color')) {
112
+ if (v === 'transparent')
113
+ v = 'rgba(0, 0, 0, 0)';
114
+ }
115
+ return v;
116
+ }
117
+ /** StyleDiff → VerificationIssue 변환 */
118
+ export function diffsToIssues(diffs) {
119
+ return diffs.map(diff => {
120
+ const isPx = diff.delta !== undefined;
121
+ const isLarge = isPx && diff.delta > 4;
122
+ return {
123
+ severity: isLarge ? 'P1' : 'P2',
124
+ type: 'style-diff',
125
+ target: diff.selector,
126
+ message: `${diff.property}: expected ${diff.expected}, got ${diff.actual}` +
127
+ (diff.delta !== undefined ? ` (delta: ${diff.delta}px)` : ''),
128
+ expected: diff.expected,
129
+ actual: diff.actual,
130
+ };
131
+ });
132
+ }
133
+ //# sourceMappingURL=compare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../../src/infra/lib/browser/compare.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAQvC;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,YAAoB,EACpB,UAAkB,EAClB,cAAuB;IAEvB,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,YAAsB,CAAC,CAAC;QACrE,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,OAAiB,CAAC,CAAC;QAEhD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC;QAErD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAElD,qBAAqB;QACrB,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAClD,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC;QAEpD,MAAM,UAAU,GAAG,UAAU,CAC3B,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EACzC,EAAE,SAAS,EAAE,GAAG,EAAE,CACT,CAAC;QAEZ,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,CAAC;QAEnC,IAAI,cAAc,EAAE,CAAC;YACnB,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAqB,CAAC;YAC/D,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACrC,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC;YAClD,aAAa,CAAC,cAAc,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QACzD,CAAC;QAED,OAAO;YACL,SAAS,EAAE,UAAU,GAAG,WAAW;YACnC,UAAU;YACV,aAAa,EAAE,cAAc;YAC7B,WAAW;SACZ,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,wCAAwC;QACxC,MAAM,IAAI,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,CAAC,CAAC;QACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAEnD,OAAO;YACL,SAAS,EAAE,QAAQ,GAAG,OAAO;YAC7B,UAAU,EAAE,CAAC,CAAC;YACd,WAAW,EAAE,CAAC,CAAC;SAChB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,6BAA6B;AAC7B,SAAS,YAAY,CACnB,GAAoD,EACpD,KAAa,EACb,MAAc;IAEd,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC;IAChD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YACnC,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;YACvC,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAChC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACxC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACxC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,aAAa,CAC3B,QAAgC,EAChC,MAA4B;IAE5B,MAAM,KAAK,GAAgB,EAAE,CAAC;IAE9B,KAAK,MAAM,CAAC,QAAQ,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjE,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,WAAW;YAAE,SAAS;QAE3B,MAAM,kBAAkB,GAAG,YAAY,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QACjE,MAAM,gBAAgB,GAAG,YAAY,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAE7D,IAAI,kBAAkB,KAAK,gBAAgB,EAAE,CAAC;YAC5C,MAAM,IAAI,GAAc;gBACtB,QAAQ,EAAE,MAAM,CAAC,QAAQ;gBACzB,QAAQ;gBACR,QAAQ,EAAE,aAAa;gBACvB,MAAM,EAAE,WAAW;aACpB,CAAC;YAEF,MAAM,WAAW,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC;YAC9C,MAAM,SAAS,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC;YAC1C,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC7C,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;YACjD,CAAC;YAED,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,sBAAsB;AACtB,SAAS,YAAY,CAAC,QAAgB,EAAE,KAAa;IACnD,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAEnC,eAAe;IACf,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAC7D,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC;QAC7B,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,WAAW;IACX,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC;YAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;IAC9C,CAAC;IAED,kBAAkB;IAClB,IAAI,QAAQ,KAAK,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC,KAAK,aAAa;YAAE,CAAC,GAAG,kBAAkB,CAAC;IAClD,CAAC;IAED,OAAO,CAAC,CAAC;AACX,CAAC;AAED,uCAAuC;AACvC,MAAM,UAAU,aAAa,CAAC,KAAkB;IAC9C,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC;QACtC,MAAM,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC,KAAM,GAAG,CAAC,CAAC;QAExC,OAAO;YACL,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI;YAC/B,IAAI,EAAE,YAAqB;YAC3B,MAAM,EAAE,IAAI,CAAC,QAAQ;YACrB,OAAO,EAAE,GAAG,IAAI,CAAC,QAAQ,cAAc,IAAI,CAAC,QAAQ,SAAS,IAAI,CAAC,MAAM,EAAE;gBACxE,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/D,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { launchBrowser, openPage, closeBrowser, getBrowser } from './launch.js';
2
+ export { captureScreenshot, getComputedStyles, getComputedStylesBatch, extractTextContent, extractImages } from './capture.js';
3
+ export { compareScreenshots, compareStyles, diffsToIssues } from './compare.js';
4
+ export type { BrowserLaunchOptions, CaptureScreenshotOptions, ElementComputedStyle, StyleDiff, ScreenshotDiff, VerificationIssue, VerificationReport, } from './types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/infra/lib/browser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC/H,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChF,YAAY,EACV,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,SAAS,EACT,cAAc,EACd,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { launchBrowser, openPage, closeBrowser, getBrowser } from './launch.js';
2
+ export { captureScreenshot, getComputedStyles, getComputedStylesBatch, extractTextContent, extractImages } from './capture.js';
3
+ export { compareScreenshots, compareStyles, diffsToIssues } from './compare.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/infra/lib/browser/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC/H,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Puppeteer 브라우저 관리
3
+ *
4
+ * headless Chrome 런치, 페이지 관리, 정리.
5
+ * 싱글턴 패턴으로 세션 내 브라우저 재사용.
6
+ */
7
+ import type { BrowserLaunchOptions } from './types.js';
8
+ /** headless Chrome 브라우저 시작 */
9
+ export declare function launchBrowser(options?: BrowserLaunchOptions): Promise<unknown>;
10
+ /** 새 페이지 열고 URL 로드 */
11
+ export declare function openPage(browser: unknown, url: string, viewport?: {
12
+ width: number;
13
+ height: number;
14
+ }): Promise<unknown>;
15
+ /** 브라우저 종료 */
16
+ export declare function closeBrowser(): Promise<void>;
17
+ /** 현재 브라우저 인스턴스 반환 (없으면 null) */
18
+ export declare function getBrowser(): unknown;
19
+ //# sourceMappingURL=launch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../../../src/infra/lib/browser/launch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAoBvD,8BAA8B;AAC9B,wBAAsB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBxF;AAED,sBAAsB;AACtB,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC3C,OAAO,CAAC,OAAO,CAAC,CAWlB;AAED,cAAc;AACd,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAMlD;AAED,iCAAiC;AACjC,wBAAgB,UAAU,IAAI,OAAO,CAEpC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Puppeteer 브라우저 관리
3
+ *
4
+ * headless Chrome 런치, 페이지 관리, 정리.
5
+ * 싱글턴 패턴으로 세션 내 브라우저 재사용.
6
+ */
7
+ const DEFAULT_VIEWPORT = { width: 1920, height: 1080 };
8
+ let browserInstance = null;
9
+ /** Puppeteer 동적 import (optional dependency) */
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- puppeteer is optional peer dep
11
+ async function loadPuppeteer() {
12
+ try {
13
+ // @ts-expect-error -- puppeteer is optional peer dependency, may not have type declarations
14
+ return await import('puppeteer');
15
+ }
16
+ catch {
17
+ throw new Error('puppeteer is not installed. Run: npm install puppeteer\n' +
18
+ 'Required for UI verification (vibe.figma Phase 4, design-audit, etc.)');
19
+ }
20
+ }
21
+ /** headless Chrome 브라우저 시작 */
22
+ export async function launchBrowser(options = {}) {
23
+ if (browserInstance)
24
+ return browserInstance;
25
+ const puppeteer = await loadPuppeteer();
26
+ const browser = await puppeteer.launch({
27
+ headless: options.headless !== false,
28
+ args: [
29
+ '--no-sandbox',
30
+ '--disable-setuid-sandbox',
31
+ '--disable-dev-shm-usage',
32
+ '--disable-gpu',
33
+ ],
34
+ ...(options.executablePath ? { executablePath: options.executablePath } : {}),
35
+ });
36
+ browserInstance = browser;
37
+ return browser;
38
+ }
39
+ /** 새 페이지 열고 URL 로드 */
40
+ export async function openPage(browser, url, viewport) {
41
+ const b = browser;
42
+ const page = await b.newPage();
43
+ await page.setViewport(viewport ?? DEFAULT_VIEWPORT);
44
+ await page.goto(url, { waitUntil: 'networkidle0' });
45
+ return page;
46
+ }
47
+ /** 브라우저 종료 */
48
+ export async function closeBrowser() {
49
+ if (!browserInstance)
50
+ return;
51
+ const b = browserInstance;
52
+ await b.close();
53
+ browserInstance = null;
54
+ }
55
+ /** 현재 브라우저 인스턴스 반환 (없으면 null) */
56
+ export function getBrowser() {
57
+ return browserInstance;
58
+ }
59
+ //# sourceMappingURL=launch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch.js","sourceRoot":"","sources":["../../../../src/infra/lib/browser/launch.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,gBAAgB,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAEvD,IAAI,eAAe,GAAY,IAAI,CAAC;AAEpC,gDAAgD;AAChD,gGAAgG;AAChG,KAAK,UAAU,aAAa;IAC1B,IAAI,CAAC;QACH,4FAA4F;QAC5F,OAAO,MAAM,MAAM,CAAC,WAAW,CAAwB,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,0DAA0D;YAC1D,uEAAuE,CACxE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,8BAA8B;AAC9B,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,UAAgC,EAAE;IACpE,IAAI,eAAe;QAAE,OAAO,eAAe,CAAC;IAE5C,MAAM,SAAS,GAAG,MAAM,aAAa,EAAE,CAAC;IACxC,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC;QACrC,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,KAAK;QACpC,IAAI,EAAE;YACJ,cAAc;YACd,0BAA0B;YAC1B,yBAAyB;YACzB,eAAe;SAChB;QACD,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9E,CAAC,CAAC;IAEH,eAAe,GAAG,OAAO,CAAC;IAC1B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,sBAAsB;AACtB,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,OAAgB,EAChB,GAAW,EACX,QAA4C;IAE5C,MAAM,CAAC,GAAG,OAA8C,CAAC;IACzD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,OAAO,EAG3B,CAAC;IAEF,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,IAAI,gBAAgB,CAAC,CAAC;IACrD,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC,CAAC;IAEpD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,cAAc;AACd,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,CAAC,eAAe;QAAE,OAAO;IAE7B,MAAM,CAAC,GAAG,eAAiD,CAAC;IAC5D,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;IAChB,eAAe,GAAG,IAAI,CAAC;AACzB,CAAC;AAED,iCAAiC;AACjC,MAAM,UAAU,UAAU;IACxB,OAAO,eAAe,CAAC;AACzB,CAAC"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Browser UI 검증 인프라 타입 정의
3
+ *
4
+ * Puppeteer + CDP 기반 범용 UI 검증 도구.
5
+ * vibe.figma Phase 4, design-audit, design-polish 등에서 공통 사용.
6
+ */
7
+ /** 브라우저 런치 옵션 */
8
+ export interface BrowserLaunchOptions {
9
+ /** headless 모드 (기본: true) */
10
+ headless?: boolean;
11
+ /** 뷰포트 크기 */
12
+ viewport?: {
13
+ width: number;
14
+ height: number;
15
+ };
16
+ /** dev 서버 URL (예: http://localhost:3000) */
17
+ url?: string;
18
+ /** Chrome 실행 경로 (미지정 시 Puppeteer 번들 사용) */
19
+ executablePath?: string;
20
+ }
21
+ /** 스크린샷 옵션 */
22
+ export interface CaptureScreenshotOptions {
23
+ /** 저장 경로 */
24
+ outPath: string;
25
+ /** 특정 CSS selector만 캡처 (미지정 시 전체 페이지) */
26
+ selector?: string;
27
+ /** fullPage 스크롤 캡처 (기본: true) */
28
+ fullPage?: boolean;
29
+ /** 이미지 포맷 (기본: png) */
30
+ format?: 'png' | 'jpeg' | 'webp';
31
+ }
32
+ /** DOM 요소의 computed CSS */
33
+ export interface ElementComputedStyle {
34
+ /** CSS selector */
35
+ selector: string;
36
+ /** computed style key-value */
37
+ styles: Record<string, string>;
38
+ /** bounding box */
39
+ box: {
40
+ x: number;
41
+ y: number;
42
+ width: number;
43
+ height: number;
44
+ };
45
+ }
46
+ /** CSS 수치 비교 결과 */
47
+ export interface StyleDiff {
48
+ selector: string;
49
+ property: string;
50
+ expected: string;
51
+ actual: string;
52
+ /** px 차이 (숫자 비교 가능한 속성만) */
53
+ delta?: number;
54
+ }
55
+ /** 스크린샷 비교 결과 */
56
+ export interface ScreenshotDiff {
57
+ /** 차이 비율 (0.0 ~ 1.0) */
58
+ diffRatio: number;
59
+ /** 차이 픽셀 수 */
60
+ diffPixels: number;
61
+ /** diff 이미지 저장 경로 */
62
+ diffImagePath?: string;
63
+ /** 전체 픽셀 수 */
64
+ totalPixels: number;
65
+ }
66
+ /** UI 검증 결과 항목 */
67
+ export interface VerificationIssue {
68
+ /** P1=필수, P2=권장 */
69
+ severity: 'P1' | 'P2';
70
+ /** 이슈 유형 */
71
+ type: 'missing-image' | 'layout-mismatch' | 'style-diff' | 'text-mismatch' | 'a11y';
72
+ /** 대상 selector 또는 영역 */
73
+ target: string;
74
+ /** 이슈 설명 */
75
+ message: string;
76
+ /** 기대값 */
77
+ expected?: string;
78
+ /** 실제값 */
79
+ actual?: string;
80
+ }
81
+ /** UI 검증 리포트 */
82
+ export interface VerificationReport {
83
+ /** 검증 대상 URL */
84
+ url: string;
85
+ /** 뷰포트 */
86
+ viewport: {
87
+ width: number;
88
+ height: number;
89
+ };
90
+ /** 총 이슈 수 */
91
+ totalIssues: number;
92
+ /** P1 이슈 수 */
93
+ p1Count: number;
94
+ /** P2 이슈 수 */
95
+ p2Count: number;
96
+ /** 이슈 목록 */
97
+ issues: VerificationIssue[];
98
+ /** 스크린샷 diff (있으면) */
99
+ screenshotDiff?: ScreenshotDiff;
100
+ /** 타임스탬프 */
101
+ timestamp: string;
102
+ }
103
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/infra/lib/browser/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,iBAAiB;AACjB,MAAM,WAAW,oBAAoB;IACnC,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa;IACb,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,4CAA4C;IAC5C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,cAAc;AACd,MAAM,WAAW,wBAAwB;IACvC,YAAY;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uBAAuB;IACvB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;CAClC;AAED,2BAA2B;AAC3B,MAAM,WAAW,oBAAoB;IACnC,mBAAmB;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,mBAAmB;IACnB,GAAG,EAAE;QACH,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,mBAAmB;AACnB,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,iBAAiB;AACjB,MAAM,WAAW,cAAc;IAC7B,wBAAwB;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,qBAAqB;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc;IACd,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,kBAAkB;AAClB,MAAM,WAAW,iBAAiB;IAChC,mBAAmB;IACnB,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,YAAY;IACZ,IAAI,EAAE,eAAe,GAAG,iBAAiB,GAAG,YAAY,GAAG,eAAe,GAAG,MAAM,CAAC;IACpF,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU;IACV,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,gBAAgB;AAChB,MAAM,WAAW,kBAAkB;IACjC,gBAAgB;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU;IACV,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,aAAa;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY;IACZ,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,sBAAsB;IACtB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,YAAY;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Browser UI 검증 인프라 타입 정의
3
+ *
4
+ * Puppeteer + CDP 기반 범용 UI 검증 도구.
5
+ * vibe.figma Phase 4, design-audit, design-polish 등에서 공통 사용.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/infra/lib/browser/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@su-record/vibe",
3
- "version": "2.8.34",
3
+ "version": "2.8.36",
4
4
  "description": "AI Coding Framework for Claude Code — 49 agents, 41+ tools, multi-LLM orchestration",
5
5
  "type": "module",
6
6
  "main": "dist/cli/index.js",