본문으로 건너뛰기
5분 AI.
REPO DEEP DIVE · 2026-07-29 · BERGSIDE/DESIGN-MD-CHROME · 지금 보는 웹사이트를 클릭 한 번으로 AI 에이전트용 디자인 스펙으로

bergside/design-md-chrome 딥다이브
— "보이는 대로 뽑아낸다" 웹사이트 → DESIGN.md/SKILL.md 크롬 확장

브라우저 확장

빠른 판단

무엇
활성 웹페이지의 실제 스타일을 집계해 DESIGN.md·SKILL.md 문서로 내보내는 Chrome 확장
이럴 때
참고 사이트의 색·폰트·간격을 추출해 코딩 에이전트가 재사용할 디자인 규칙 초안으로 만들 때
DESIGN.md Style Extractor는 지금 열어둔 웹페이지에 실제로 적용된 CSS 계산값(글자 크기·색·여백·둥근 정도·그림자·애니메이션 속도)을 그대로 읽어, DESIGN.md 또는 SKILL.md 형식의 "디자인 시스템 문서"로 바꿔주는 크롬 확장이다. 이 문서는 Claude Code·Codex·Cursor 같은 AI 코딩 에이전트에게 "이 사이트 느낌대로 만들어줘"라고 시킬 때 참고 자료로 물려주는 용도다.

먼저 짚어둘 사실 하나. 아이콘 줄에 Claude·Codex·Cursor 로고가 나란히 붙어 있지만, 이 확장 코드 어디에도 LLM API 호출은 없다. 색·간격·타이포그래피를 토큰으로 뽑는 것도, "이 사이트가 대시보드인지 쇼핑몰인지" 짐작하는 것도 전부 키워드 매칭과 빈도 집계로 짠 규칙(순수 휴리스틱)이다. AI가 개입하는 지점은 "이 문서를 참고자료 삼아 코드를 짜는" 다음 단계이지, 이 확장 자체가 아니다.

(저장소 bergside/design-md-chrome · 제작 Bergside(typeui.sh 운영사) · 라이선스 MIT · manifest.json 기준 버전 0.4.0 · Manifest V3 크롬 확장 · package.json 없음 — 외부 npm 의존성 0개, 순수 JS(ES 모듈) · 추적 파일 23개 · 최신 커밋 8e07614 "add: ref link to sponsors"(2026-05-25) · 별점 미인용)
목차
  1. 프로젝트 한 줄 요약
  2. 왜 지금 뜨는가
  3. 무엇을 해결하나
  4. 전체 구조
  5. 핵심 설계·동작 원리
  6. 기술 스택·의존성
  7. 설치·요구사항
  8. 실제 사용 흐름
  9. 함정·오해 방지
  10. 대안 비교
  11. 정리 — 누가 쓰면 좋은가
  12. 직접 해볼 수 있는 실습 과제
  13. 관련 기술 심화 학습 로드맵
  14. 핵심 키워드 사전

1프로젝트 한 줄 요약

한 문장으로: "지금 보는 사이트의 컴퓨티드 스타일을 긁어서 DESIGN.md/SKILL.md로 구워주는 크롬 확장"

design-md-chrome은 "웹사이트 → 디자인 토큰 문서" 변환기다. 확장 아이콘을 누르면 지금 활성 탭 페이지의 요소들에서 window.getComputedStyle로 실제 렌더링 값을 긁고, 그 값을 빈도 집계해 font.size.md, color.text.primary, space.3 같은 이름 붙은 토큰으로 정리한 뒤, 정해진 마크다운 템플릿에 끼워 넣는다.

출력은 두 모드다. DESIGN.md는 사람이 참고할 디자인 시스템 문서이고, SKILL.md는 YAML 프런트매터와 TYPEUI_SH_MANAGED_START/END 마커까지 갖춰 Claude Code(.claude/skills) 같은 에이전트가 바로 읽을 수 있게 만든 버전이다. 팝업에는 Copy·Download 외에 Quick install 버튼이 있어, 브라우저의 파일시스템 접근 API로 프로젝트 폴더에 SKILL.md를 곧장 써 넣을 수도 있다.

한 줄 비유

디자이너 대신 "줄자와 색상 스와치"를 들고 다니며 실측하는 조수

사람이 "저 사이트 느낌으로 만들어줘"라고 하면 에이전트는 색을 짐작하거나 스크린샷을 보고 어림잡는다. 이 확장은 그 대신 줄자(computed style)를 페이지에 대고 여백·글자 크기·색을 그대로 재서 숫자로 적어온다.

다만 "이 사이트가 어떤 성격인지(대시보드? 쇼핑몰?)"는 줄자로 잴 수 없는 부분이라, 그건 여전히 제목·메뉴 글자를 훑어보고 짐작하는 수준이다. 정확한 실측과 대략적인 짐작이 한 문서 안에 섞여 있다는 점이 이 도구의 정체다.

2왜 지금 뜨는가

"에이전트용 스펙 파일" 생태계 확산 · 손으로 쓰기 귀찮은 SKILL.md · 자사 서비스로의 진입로

2025~2026년 사이 Claude Code의 .claude/skills, Codex의 에이전트 규칙 파일처럼 "AI 코딩 에이전트에게 미리 읽혀 두는 스펙 문서"가 하나의 표준 관행이 됐다. 문제는 이런 문서를 사람이 처음부터 손으로 쓰기가 번거롭다는 것이다. 색상 하나, 여백 값 하나까지 다 사람이 재서 적어야 했다.

① "느낌으로 만들어줘"를 숫자로 바꿔주는 첫 단추

실무에서 흔한 요청은 "이 경쟁사 사이트 톤으로 우리 랜딩페이지 만들어줘"다. 이 확장은 그 사이트를 열어놓고 버튼 한 번으로 색상 hex 값·spacing px 값까지 뽑아주므로, 에이전트가 대략적인 느낌이 아니라 구체적인 숫자를 갖고 시작하게 해준다.

② Bergside(typeui.sh)의 무료 클라이언트 진입점

이 저장소를 만든 Bergside는 typeui.sh라는 "디자인 시스템 스킬 큐레이션" 서비스를 운영한다. README와 팝업 도움말은 계속 typeui.sh/design-md, typeui.sh/design-skills를 참조 링크로 건다. 즉 이 크롬 확장은 무료로 배포해 사용자를 typeui.sh 생태계로 끌어들이는 로컬 도구이기도 하다 — 오픈소스 확장 자체가 회사의 배포 채널인 구조다.

③ 별도 서버·설치 없이 바로 되는 가벼움

package.json조차 없는 순수 바닐라 JS라 npm install도, 빌드도 필요 없다. "압축해제 상태로 로드"만 하면 그대로 동작한다. 이런 제로 디펜던시·제로 빌드 특성이 "가볍게 훑어보고 바로 쓸 수 있는 도구"로 눈에 띄는 이유 중 하나다.

경쟁 지형
Figma 플러그인 · DevTools 수동 복사 사이에서

디자인 토큰을 뽑는 도구는 이미 많다. Figma의 Dev Mode나 Tokens Studio는 디자인 파일 원본이 있어야 하고, 브라우저 DevTools로 하나하나 복사하는 방법은 사람 손이 많이 간다. 이 확장은 "디자인 파일 없이, 이미 배포된 아무 웹사이트에서" 바로 토큰을 뽑는다는 좁지만 실용적인 틈을 파고든다.

3무엇을 해결하나

문제: 에이전트는 "느낌"을 모른다 · 해결: 브라우저가 이미 계산해 둔 진짜 값을 그대로 넘긴다

문제. AI 코딩 에이전트에게 "이 사이트처럼 만들어줘"라고 말해도, 에이전트는 스크린샷을 보고 색을 추측하거나(부정확), 아예 아무 근거 없이 흔한 값(#111827, 16px 같은 "관용적으로 자주 쓰이는" 숫자)을 골라 쓴다. 결과물은 "비슷하지만 미묘하게 다른" 디자인이 되기 쉽다.

해결. 브라우저는 페이지를 그릴 때 이미 모든 요소의 정확한 색·크기·간격을 계산해 갖고 있다(getComputedStyle). design-md-chrome은 이 값을 그대로 읽어와 빈도순으로 집계하고, color.text.primary, space.3 같은 의미 있는 이름을 붙여 마크다운 표로 정리한다. 에이전트는 이제 "대략 짙은 회색"이 아니라 #111827이라는 정확한 값을 받는다.

단, 절반만 해결된다
"브랜드가 무엇을 하는 곳인지"는 여전히 짐작

색·크기·간격은 실측값이라 정확하다. 반면 문서의 Brand·Audience·Product surface 항목(예: "대시보드 웹앱", "개발자와 기술팀")은 lib/normalize.mjs의 inferSiteProfile()이 제목·메뉴 텍스트·폼 개수 같은 신호를 키워드로 스코어링해 추측한 결과다.

즉 이 문서를 곧이곧대로 믿으면 "색상표는 정확한데 브랜드 설명은 헛다리 짚은" 결과물이 나올 수 있다. 실측 부분과 추측 부분을 구분해서 읽는 게 이 도구를 제대로 쓰는 첫걸음이다.

4전체 구조

popup → service-worker(백그라운드) → content-script(페이지 안) → lib/ 4개 순수 함수 파이프라인

코드는 4개의 층으로 깔끔히 나뉜다. 팝업(사용자 화면), 백그라운드 서비스 워커(조율자), 콘텐츠 스크립트(페이지에 실제로 들어가는 코드), 그리고 lib/ 아래 부작용 없는 순수 함수 4개(정규화·마크다운 생성 2종·검증)다.

┌────────────────────────────────────────────────────────────────┐ │ DESIGN-MD-CHROME 4계층 구조 │ └────────────────────────────────────────────────────────────────┘ [1] popup/popup.js (팝업 UI, 사용자가 보는 화면) · 모드 전환(DESIGN.md / SKILL.md) · Copy · Download · Quick install │ chrome.runtime.sendMessage({type:"RUN_EXTRACTION", mode}) ▼ [2] service-worker.js (백그라운드, MV3 서비스 워커) handleExtraction() ├─ getActiveTab() 현재 탭 확인 (chrome:// 페이지는 거부) ├─ injectExtractor() chrome.scripting.executeScript로 주입 ├─ requestExtractionPayload() content-script에 메시지 전송 └─ lib/* 순수 함수 호출 정규화 → 마크다운 생성 → 검증 │ chrome.tabs.sendMessage(tabId, {type:"TYPEUI_EXTRACT_STYLES"}) ▼ [3] content-script.js (페이지 안에 주입되어 실행) extractStylesFromPage() · 최대 280개 요소 샘플링 → getComputedStyle 로 실측 · 타이포그래피·색·간격·radius·shadow·motion + 사이트 신호 수집 │ payload(JSON)를 sendResponse로 되돌려줌 ▼ [4] lib/ (순수 함수 4개, DOM/브라우저 API 없음) normalize.mjs → 원값을 빈도 집계해 이름 붙은 토큰으로 generate-design-md.mjs / generate-skill-md.mjs → 마크다운 조립 validate.mjs → 생성된 마크다운을 문자열 검사로 점검

이 구조가 주는 교훈은 "브라우저 API를 만지는 코드(주입·메시징)"와 "순수 데이터 변환 코드(lib/)"를 확실히 분리했다는 점이다. lib/ 안 함수들은 전부 입력을 받아 값을 반환할 뿐 chrome.* API를 전혀 부르지 않는다 — 그래서 tests/run-tests.mjs가 크롬 없이 node 하나로 그대로 테스트할 수 있다.

5핵심 설계·동작 원리

온디맨드 주입 → 우선순위 샘플링 → Map 빈도 집계 → 키워드 스코어링 추론 → 템플릿 조립 → 문자열 검증

설계 패턴 ① — 콘텐츠 스크립트를 "그때그때" 주입한다

보통 확장은 manifest.json의 content_scripts에 페이지를 등록해 놓고 자동 주입시키지만, 이 저장소는 그렇게 하지 않는다. manifest.json에는 content_scripts 항목 자체가 없다. 대신 service-worker.js의 injectExtractor()가 확장 아이콘을 누른 그 순간에만 chrome.scripting.executeScript({ target:{tabId}, files:["content-script.js"] })로 코드를 밀어 넣는다.

content-script.js 맨 위의 if (window.__typeuiStyleExtractorInstalled) return;는 같은 탭에서 여러 번 눌러도 리스너가 중복 등록되지 않게 막는 가드다. "모든 페이지에 항상 살아있는 코드" 대신 "필요할 때만 잠깐 들어갔다 나오는 코드"로 설계해 권한 범위와 성능 부담을 줄인 것이다.

비유

일반적인 콘텐츠 스크립트는 모든 방에 상주하는 경비원과 같다. 반면 이 확장은 호출할 때만 잠깐 들어오는 검침원이다. 버튼을 누르는 순간에만 방(탭)에 들어와 계량기(스타일)를 확인하고, 이미 들어와 있으면("설치됨" 표시를 보고) 중복으로 들어오지 않는다.

설계 패턴 ② — 우선순위 셀렉터로 "280개 한도" 안에서 훑기

content-script.js의 collectSampledElements(280)는 body → 제목(h1~h6) → p → a → button → 입력 요소 → label → nav/header/footer/main/section/article/aside → 리스트 항목 → 표 → [role='button'] → 클래스에 card/btn이 들어간 요소 → [tabindex] 순서로 셀렉터를 훑으며, isVisible()로 화면에 실제로 보이는 요소만 골라 최대 280개까지 채운다.

요소마다 window.getComputedStyle을 호출해 fontFamily/fontSize/lineHeight/fontWeight, color/backgroundColor/borderColor/outlineColor, 8방향 margin·padding, borderRadius, boxShadow, transitionDuration 등을 그대로 배열에 쌓는다. 동시에 collectSiteSignals()가 메타 태그(description·keywords·og:type)와 제목·내비게이션·CTA 문구, 그리고 document.body.innerText 앞부분 14,000자까지를 함께 담아 반환한다.

설계상 한계
셀렉터 목록과 280개 한도 밖은 안 보인다

웹 컴포넌트(Shadow DOM)나 위 셀렉터 목록에 없는 커스텀 태그, 또는 페이지에 요소가 아주 많아 280개를 넘기는 경우 뒤쪽 디자인 토큰은 아예 표본에서 빠진다. "정확한 실측"이라는 이 도구의 강점은 표본이 닿는 범위 안에서만 성립한다.

설계 패턴 ③ — Map 빈도 집계로 "가장 자주 쓰인 값"을 토큰으로 승격

lib/normalize.mjs의 normalizeExtractedStyles()는 원시 값을 그대로 쓰지 않는다. increment(map, key) 헬퍼로 각 값이 몇 번 등장했는지 Map에 누적하고, topEntries()로 등장 빈도가 높은 순서대로만 골라 SIZE_NAMES = ["xs","sm","md","lg","xl","2xl","3xl","4xl"], RADIUS_NAMES, TEXT_NAMES = ["primary","secondary","tertiary","inverse"] 같은 고정 이름 배열을 순서대로 붙인다. 즉 color.text.primary는 "가장 자주 쓰인 글자색"이라는 뜻이지, 실제 CSS의 --text-primary 변수와는 무관하다.

색상은 toCanonicalColor() → parseRgbLike() → rgbToHex() 경로로 rgb(17, 24, 39) 같은 값을 #111827 형태의 hex로 통일한다. 여백·radius는 parsePx()로, 트랜지션 시간은 parseDuration()으로 150ms/0.15s 표기를 하나의 숫자(ms)로 맞춘다. "많이 쓰인 값일수록 진짜 토큰일 가능성이 높다"는 빈도 = 신뢰도 가정이 이 모듈 전체를 관통한다.

설계 패턴 ④ — 사이트 성격 추론은 100% 규칙 기반 키워드 스코어링

inferSiteProfile()은 documentation/dashboard/marketing/content/ecommerce/webapp 6개 "제품 성격" 후보와 developer/operator/business/consumer/reader/general 6개 "대상 독자" 후보에 각각 점수를 매긴다. 점수 계산은 세 함수뿐이다.

pickTop()이 가장 높은 점수의 후보를 고르고, confidenceFromScores()가 "점수 ≥ 5이고 2등과 차이 ≥ 1.4면 high, 점수 ≥ 2.4이고 차이 ≥ 0.5면 medium, 아니면 low"라는 하드코딩된 문턱값으로 신뢰도 라벨을 매긴다. 여기엔 임베딩도, 분류 모델도, LLM 호출도 없다 — if/else급 점수표다.

용어
휴리스틱 (heuristic)
정답을 통계·학습으로 구하는 대신, 사람이 미리 정해둔 "대략 맞을 법한 규칙"으로 빠르게 근사치를 내는 방법. 이 확장의 사이트 성격 추론은 전부 사람이 하드코딩한 키워드 목록과 가중치로 이뤄진 휴리스틱이다.

설계 패턴 ⑤ — 저장소 루트의 DESIGN.md 자체가 "생성기의 설계도"

저장소 루트에는 DESIGN.md라는 파일이 하나 더 있는데, 이건 확장이 만들어내는 결과물이 아니라 "SKILL.md를 어떻게 써야 하는지"를 규정한 메타 문서다. 여기 적힌 Mission/Brand/Style Foundations/Accessibility/Writing Tone/Rules: Do/Rules: Don't/Guideline Authoring Workflow/Required Output Structure/Component Rule Expectations/Quality Gates 11개 섹션 이름이, lib/generate-design-md.mjs·lib/generate-skill-md.mjs가 실제로 채워 넣는 섹션과 lib/validate.mjs의 REQUIRED_HEADINGS 배열에 토씨 하나 안 틀리고 그대로 등장한다. 즉 "설계도(DESIGN.md) → 생성 코드 → 검증 코드"가 같은 스펙을 세 번 반복하는 자기참조 구조다.

설계 패턴 ⑥ — 검증은 마크다운을 "파싱"하지 않고 그냥 문자열 검색한다

lib/validate.mjs의 validateMarkdownOutput()은 진짜 마크다운 파서를 쓰지 않는다. markdown.includes("## Mission")처럼 부분 문자열이 포함돼 있는지만 검사한다. REQUIRED_STATES = ["default","hover","focus-visible","active","disabled","loading","error"] 각 단어가 본문 어딘가에 등장하는지, "WCAG 2.2 AA" 문구가 있는지, "must"/"should" 단어가 쓰였는지도 같은 방식으로 확인한다.

함정
검증 통과 ≠ 내용이 맞다는 뜻

순서가 뒤바뀌어 있어도, 섹션 제목만 어딘가에 존재하면 runCheck()는 통과로 친다. "필요한 단어가 문서 안에 있는지"만 볼 뿐 문맥이나 값의 타당성은 전혀 검사하지 않는다. 팝업의 경고 목록(issuesEl)이 비어 있다고 해서 생성된 토큰 값이 실제로 쓸만하다는 보장은 없다.

6기술 스택·의존성

진실원 = manifest.json · npm 의존성 0개 · 빌드 단계 없음

이 저장소에서 버전의 진실원(source of truth)은 manifest.json의 "version": "0.4.0"이다. 팝업 배지에 보이는 "v0.4.0" 텍스트는 popup/popup.html에 별도로 손으로 박아 넣은 문자열이라, 둘을 늘 맞춰줘야 한다.

구성 요소내용
패키지 매니저없음. 저장소 어디에도 package.json이 없다. npm/yarn/pnpm install 자체가 필요 없는 순수 정적 JS 확장
확장 형식Manifest V3(manifest_version: 3). 백그라운드는 service_worker + "type": "module"로 ES 모듈 임포트 지원
권한activeTab·scripting·storage·downloads 4개뿐. 항상 켜져 있는 백그라운드 권한(tabs 전체 등)은 요구하지 않음
프런트엔드팝업은 프레임워크 없는 바닐라 HTML/CSS/JS(popup/popup.html·popup.css·popup.js). 번들러·트랜스파일러 없음 — 브라우저가 파일을 그대로 로드
핵심 로직lib/normalize.mjs·lib/generate-design-md.mjs·lib/generate-skill-md.mjs·lib/validate.mjs — 전부 브라우저 API에 의존하지 않는 순수 ES 모듈
테스트tests/run-tests.mjs가 node:assert/strict만으로 작성됨. 테스트 프레임워크(Jest/Vitest 등) 없이 node tests/run-tests.mjs로 직접 실행
CI.github/workflows 자체가 저장소에 없음 — GitHub Actions 등 자동 테스트 파이프라인 없음(로컬 실행 의존)
아이콘 빌드scripts/generate-icons.sh가 macOS 전용 sips 명령으로 assets/logo.png를 16/32/48/128px 아이콘으로 리사이즈. 크로스플랫폼 빌드 스크립트가 아님
외부 서비스 연동런타임 코드에는 없음. 다만 팝업 도움말·README가 typeui.sh/design-md를 참고 링크로 반복 인용(직접 fetch는 하지 않음)
용어
Manifest V3
크롬 확장의 최신 규격. 오래 떠 있던 백그라운드 페이지 대신 필요할 때만 깨어나는 "서비스 워커"를 쓰게 하고, 권한을 더 세밀하게 요구한다. 이 확장은 service_worker + type: module 조합으로 ES 모듈을 백그라운드에서 바로 import한다.
용어
.mjs 파일
Node.js/브라우저에 "이 파일은 ES 모듈(import/export 문법)이다"라고 알려주는 확장자. .js와 달리 별도 설정 없이 import 구문을 바로 쓸 수 있다.

7설치·요구사항

npm install 불필요 · 크로미움 계열 브라우저 · Quick install은 파일시스템 접근 API 필요
항목내용
설치 방법chrome://extensions 접속 → 개발자 모드 켜기 → 압축해제된 확장 프로그램을 로드 → 이 저장소 폴더 선택. 이게 전부다(빌드·설치 명령 없음)
브라우저Manifest V3를 지원하는 크로미움 계열(Chrome·Edge·Brave 등). Firefox는 MV3 구조가 달라 그대로는 동작하지 않음
Node.js확장 실행 자체엔 불필요. 로컬 테스트(node tests/run-tests.mjs)를 돌릴 때만 Node가 필요
Quick install 버튼window.showDirectoryPicker(File System Access API)로 프로젝트 폴더에 직접 SKILL.md를 쓴다. 이 API가 없는 환경에서는 클립보드 복사 + 다운로드로 자동 대체(fallback)
사용 불가 페이지service-worker.js의 getActiveTab()이 chrome:// 페이지에서는 명시적으로 에러를 던짐 — 브라우저 내부 설정 화면에서는 추출이 안 됨
가벼움이 곧 요구사항
설치 장벽이 사실상 없다

npm 의존성이 0개이므로 "버전 충돌", "취약점 스캔에 걸리는 패키지" 같은 걱정이 원천적으로 없다. 코드를 그대로 읽고 바로 로드해 쓸 수 있다는 점은 이 저장소의 가장 큰 실무적 장점이다.

8실제 사용 흐름

아이콘 클릭 → 모드 선택 → 추출·검증 → Copy/Download/Quick install
단계 1

사이트를 열고 확장 아이콘 클릭

토큰을 뽑고 싶은 페이지를 열어둔 채 확장 아이콘을 누르면 popup.js의 init()이 실행된다. chrome.storage.local에서 지난번에 쓰던 모드(outputMode)를 불러와 그대로 이어서 runExtraction()을 자동 실행한다.

단계 2

DESIGN.md / SKILL.md 모드 전환

팝업 상단의 두 버튼으로 사람이 읽을 DESIGN.md와 에이전트가 바로 읽을 SKILL.md를 오갈 수 있다. 모드를 바꾸면 state.mode가 갱신되고 즉시 runExtraction()이 다시 돌아간다.

단계 3

백그라운드가 추출·정규화·조립·검증을 순서대로 실행

service-worker.js가 injectExtractor()로 content-script.js를 주입하고, requestExtractionPayload()로 원시 스타일 데이터를 받아, normalizeExtractedStyles() → generateDesignMarkdown()/generateSkillMarkdown() → validateMarkdownOutput() 순으로 처리한 뒤 팝업으로 되돌려준다.

단계 4

미리보기·검증 경고 확인

결과 마크다운이 <textarea id="preview">에 그대로 채워진다(실제 파일에는 이스케이프된 형태로 존재). validation.errors/warnings가 있으면 목록(issuesEl)으로 뜬다. 앞서 s5에서 봤듯 이 검증은 문자열 포함 여부만 확인한다는 점을 기억해 둘 것.

단계 5

Copy / Download / Quick install 중 선택

Copy는 클립보드에, Download는 data:text/markdown URL로 파일 저장 대화상자를 띄운다. Quick install은 Claude Code/Codex/Cursor 버튼 중 하나를 누르면 폴더 선택창을 띄워 .claude/skills/design-system/SKILL.md(또는 .agents/…, .cursor/…) 경로에 바로 파일을 써 넣는다.

단계 6

"?" 도움말로 이번 추출의 근거 확인

물음표 버튼을 누르면 renderGenerationExplanation()이 "이번 실행에서 샘플링된 요소 수", "토큰 개수", "브랜드 추론 신뢰도(high/medium/low)와 근거 키워드"를 실제 숫자로 보여준다. 결과를 그냥 믿기 전에 이 패널로 신뢰도를 먼저 확인하는 습관을 들이면 좋다.

9함정·오해 방지

이름은 "AI 확장"이지만 내부는 규칙 기반 · 문서와 실제가 어긋나는 지점들
함정 1
Claude·Codex·Cursor 로고가 있어도 LLM 호출은 0회

팝업 UI에 세 도구 로고가 나란히 붙어 있어 "AI가 이 사이트를 분석해준다"고 오해하기 쉽다. 실제로는 fetch로 외부 API를 부르는 코드가 단 한 줄도 없다. 색·간격 추출은 getComputedStyle 실측, 브랜드 추론은 lib/normalize.mjs의 키워드 스코어링이 전부다. "AI 확장"이 아니라 "AI에게 넘겨줄 스펙을 만드는 확장"이다.

함정 2
팝업 안 GitHub 링크가 실제 저장소와 다르다

popup/popup.html의 GitHub 배지는 https://github.com/bergside/design-md-extractor-chrome("-extractor"가 붙은 이름)로 연결되지만, 실제로 이 문서가 분석한 저장소 주소는 bergside/design-md-chrome이다. 저장소 이름이 바뀌었거나 리네이밍되면서 팝업 안 링크가 갱신되지 않은 문서-코드 불일치 사례다.

함정 3
검증 통과는 "단어가 있다"는 뜻일 뿐

lib/validate.mjs는 마크다운을 파싱하지 않고 markdown.includes(...)로만 검사한다. 섹션 순서가 뒤죽박죽이어도, 심지어 본문 어딘가 엉뚱한 문맥에 "disabled" 같은 단어만 있어도 검사는 통과로 뜬다. 경고 목록이 비어 있다고 해서 토큰 값 자체의 품질까지 보장되는 건 아니다.

함정 4
샘플은 최대 280개, 셀렉터 목록 밖은 안 보인다

collectSampledElements(280)의 한도와 고정 셀렉터 목록 밖에 있는 요소(예: Shadow DOM 안의 커스텀 컴포넌트)는 추출 대상에서 빠진다. 복잡한 페이지일수록 "실제로 뽑힌 토큰"이 그 사이트 디자인의 일부만 대표할 수 있다.

함정 5
버전은 두 곳에 따로 적혀 있다 · 아이콘 스크립트는 macOS 전용

버전의 진실원은 manifest.json이지만 popup/popup.html의 "v0.4.0" 배지 텍스트는 손으로 따로 박아둔 값이라 둘이 어긋날 수 있다. 또한 scripts/generate-icons.sh는 macOS 전용 sips 명령을 쓰므로 Linux/Windows 개발 환경에서는 그대로 실행되지 않는다.

10대안 비교

DevTools 수동 복사 · Figma 토큰 도구 · 스크린샷 기반 AI 추측과 무엇이 다른가
방법이 확장과 비교
브라우저 DevTools 수동 복사정확하지만 요소 하나하나 값을 손으로 옮겨 적어야 한다. 이 확장은 280개 요소를 자동으로 훑고 빈도순으로 정리해 시간을 크게 절약하지만, 사람의 판단(어떤 값이 "진짜 토큰"인지)을 빈도 집계로 대신한다.
Figma Dev Mode / Tokens Studio디자이너가 만든 원본 디자인 파일이 있어야 동작한다. 이 확장은 디자인 파일 없이 이미 배포된 실제 웹사이트에서 바로 토큰을 뽑을 수 있어, 경쟁사 사이트 분석이나 레퍼런스 벤치마킹에 더 적합하다.
스크린샷을 AI에게 보여주고 "느낌 설명해줘"빠르지만 색상 hex·정확한 px 값은 매번 다르게 추측되는(hallucination) 문제가 있다. 이 확장은 getComputedStyle 실측값을 쓰므로 색·간격 부분만큼은 결정적(deterministic)이다 — 다만 브랜드 성격 추론은 여전히 휴리스틱 추측이라는 점은 동일한 한계.
SKILL.md를 손으로 처음부터 작성정확하지만 시작이 느리다. 이 확장은 실제 사이트에서 뽑은 초안을 자동으로 만들어주므로, 빈 문서가 아니라 "고쳐 쓸 초안"에서 시작하게 해준다.
typeui.sh 웹서비스(같은 회사)이 확장이 참조하는 typeui.sh/design-md·typeui.sh/design-skills는 Bergside의 별도 웹 서비스다. 확장은 그 스펙을 로컬에서 무료로 실행하는 클라이언트 역할이며, 웹사이트에 접속하지 않고도 같은 규격의 문서를 만들어낸다.

11정리 — 누가 쓰면 좋은가

"AI 코딩 에이전트에게 물려줄 디자인 스펙 초안"이 급할 때 5분 안에 쓰는 도구

design-md-chrome은 크지 않은 저장소지만(package.json도 없는 23개 파일) 목적이 뚜렷하다. 임의의 웹사이트에서 실제 CSS 값을 실측해, AI 코딩 에이전트가 곧바로 참고할 수 있는 마크다운 스펙으로 정리해 준다. 색상·간격 같은 "잴 수 있는 값"은 정확하지만, 브랜드 성격 같은 "해석이 필요한 값"은 여전히 키워드 기반 추측이라는 이중적 성격을 정확히 이해하고 쓰는 게 핵심이다.

이런 사람에게 맞다
레퍼런스 사이트를 빠르게 스펙 문서로 바꾸고 싶은 실무자

· "이 사이트 느낌으로 만들어줘"를 자주 요청받는 프런트엔드 개발자·1인 개발자
· Claude Code/Codex/Cursor에 물려줄 SKILL.md 초안을 빠르게 확보하고 싶은 사람
· npm 의존성 없는 가벼운 크롬 확장의 실제 구조(온디맨드 주입·메시지 패싱·순수 함수 분리)를 공부하고 싶은 초보 개발자

이런 용도로는 주의
추론된 브랜드 설명을 그대로 최종본으로 쓰는 것

Audience·Product surface 항목은 키워드 스코어링 결과일 뿐이다. 실제 서비스 문서로 쓸 거라면 색상·간격 등 실측 토큰 표만 신뢰하고, 브랜드 설명 문단은 반드시 사람이 다시 검토·수정해야 한다.

12직접 해볼 수 있는 실습 과제

기존 테스트에서 출발해 토큰 정규화·문서 생성·검증의 한계까지 직접 확인한다

아래 명령은 manifest.json이 있는 레포 소스 루트에서 실행한다. Node.js가 필요하며, 예제는 입력 데이터를 메모리에서 만들고 결과를 터미널에 출력한다. 패키지 설치나 원본 파일 수정은 필요 없다.

과제 1 · 입문

기존 테스트를 실행하고 데이터 흐름 따라가기

목표: 브라우저에서 실행되는 부분과 Node.js만으로 확인할 수 있는 부분을 구분한다.

  1. manifest.json에서 팝업 진입점과 백그라운드 모듈을 찾는다. 이어 service-worker.js의 handleExtraction()을 읽는다.
  2. 아래 명령으로 실행 환경과 기존 테스트를 확인한다.
node --version
node tests/run-tests.mjs
  1. tests/run-tests.mjs의 mockPayload에서 출발해 정규화 → 두 문서 생성기 → 검증기로 이어지는 호출을 찾는다.
  2. 서비스 워커의 같은 호출 흐름과 대조해, 테스트가 실제 탭 주입과 메시지 전송까지 확인하는지 판단한다.

완료 기준: All tests passed.를 확인하고, 기존 테스트는 가짜 입력으로 lib/의 변환 결과를 검사하며 chrome.scripting.executeScript의 실제 동작은 다루지 않는다고 설명할 수 있다.

과제 2 · 중급

색 표기 통일과 여백 토큰 만들기

목표: 같은 색의 서로 다른 표기가 어떻게 묶이는지, 여백 값이 어떻게 토큰으로 바뀌는지 확인한다.

  1. lib/normalize.mjs에서 toCanonicalColor(), buildColorTokens(), sortNumericKeys()를 읽는다.
  2. 아래 명령을 실행해 색과 여백의 정규화 결과를 각각 확인한다.
node --input-type=module -e 'import assert from "node:assert/strict";
import { normalizeExtractedStyles } from "./lib/normalize.mjs";

const normalized = normalizeExtractedStyles({
  source: { title: "로컬 실습" },
  sampledElements: 3,
  totalElements: 3,
  colors: ["rgb(17, 24, 39)", "#111827", "rgb(75, 85, 99)"]
    .map(textColor => ({ textColor })),
  spacing: [{
    paddingTop: "16px", paddingRight: "8px",
    paddingBottom: "16px", paddingLeft: "0px"
  }]
});
assert.equal(normalized.colorPalette[0].value, "#111827");
assert.equal(normalized.colorPalette[0].usage, 2);
assert.deepEqual(
  normalized.spacingScale.map(row => row.value),
  ["8px", "16px"]
);
console.table(normalized.colorPalette);
console.table(normalized.spacingScale);'

완료 기준: 색 표의 color.text.primary 값이 #111827, 사용 횟수가 2이고, 여백 표에는 space.1=8px와 space.2=16px가 나온다. 색의 사용 빈도와 여백의 숫자 정렬을 구분하고, 이 입력의 0px가 여백 토큰에서 제외되는 이유를 코드에서 찾는다.

과제 3 · 중급

하나의 입력을 DESIGN.md와 SKILL.md로 바꾸기

목표: 두 생성기의 입력 계약과 SKILL.md의 프런트매터를 이해한다.

  1. 루트의 DESIGN.md를 설계도로 읽고, lib/generate-design-md.mjs와 lib/generate-skill-md.mjs의 내보낸 함수를 비교한다.
  2. 아래 명령으로 같은 normalized와 metadata를 두 생성기에 전달한다. 스타일 배열을 넣지 않은 입력이라는 점도 함께 관찰한다.
node --input-type=module -e 'import assert from "node:assert/strict";
import { normalizeExtractedStyles } from "./lib/normalize.mjs";
import { generateDesignMarkdown } from "./lib/generate-design-md.mjs";
import { generateSkillMarkdown } from "./lib/generate-skill-md.mjs";
import { validateMarkdownOutput } from "./lib/validate.mjs";

const normalized = normalizeExtractedStyles({
  source: { title: "로컬 실습" },
  sampledElements: 0,
  totalElements: 0
});
const context = {
  normalized,
  metadata: {
    systemName: "로컬 실습", brand: "실습", scope: "local-lab",
    audience: "학습자", productSurface: "학습용 화면"
  }
};
const design = generateDesignMarkdown(context);
const skill = generateSkillMarkdown(context);
assert.equal(validateMarkdownOutput("design", design).isValid, true);
assert.equal(validateMarkdownOutput("skill", skill).isValid, true);
assert.ok(skill.startsWith("---\n"));
assert.ok(skill.includes("name: design-system-local-lab"));
console.log(design.split("\n").slice(0, 13).join("\n"));
console.log(skill.split("\n").slice(0, 4).join("\n"));
console.log(normalized.diagnostics);'

완료 기준: 두 모드의 isValid 검사가 통과하고, SKILL.md 앞부분에 name: design-system-local-lab이 나온다. 동시에 비어 있지 않은 diagnostics를 확인해, 문서 형식 통과와 추출 근거의 충분함이 별개임을 설명한다.

과제 4 · 심화

검증기의 오류·경고·잘못된 통과 재현하기

목표: 문서의 단어 존재 여부를 검사하는 방식과 실제 구조 검증의 차이를 확인한다.

  1. lib/validate.mjs에서 REQUIRED_HEADINGS, REQUIRED_STATES, isValid의 계산식을 읽는다.
  2. 아래 명령으로 정상 문서와 메모리에서만 바꾼 세 문서를 비교한다. 실행 전에 각 경우의 통과 여부를 예상해 본다.
node --input-type=module -e 'import assert from "node:assert/strict";
import { normalizeExtractedStyles } from "./lib/normalize.mjs";
import { generateDesignMarkdown } from "./lib/generate-design-md.mjs";
import { validateMarkdownOutput } from "./lib/validate.mjs";

const base = generateDesignMarkdown({
  normalized: normalizeExtractedStyles({
    source: { title: "로컬 실습" },
    sampledElements: 0, totalElements: 0
  })
});
const check = text => validateMarkdownOutput("design", text);
const normal = check(base);
const missing = check(base.replace("## Brand\n", ""));
const renamed = check(base.replace("## Brand\n", "## BrandExtra\n"));
const noState = check(base.replaceAll("disabled", ""));

assert.equal(normal.isValid, true);
assert.equal(missing.isValid, false);
assert.equal(renamed.isValid, true);
assert.equal(noState.isValid, true);
assert.ok(noState.warnings.some(text => text.includes("disabled")));
console.table([
  ["정상", normal], ["제목 삭제", missing],
  ["제목 변형", renamed], ["상태 누락", noState]
].map(([name, result]) => ({
  name, isValid: result.isValid,
  errors: result.errors.length, warnings: result.warnings.length
})));'

완료 기준: 제목 삭제만 isValid: false가 되고, BrandExtra로 바꾼 제목은 통과하며, disabled 삭제는 경고만 남긴다. 그 이유를 includes()와 errors.length에서 찾고, 개선안으로 제목 줄의 정확한 일치 검사와 필수 상태 누락의 처리 기준을 제안한다.

13관련 기술 심화 학습 로드맵

5주 동안 실행 경계 → 스타일 수집 → 추론 → 문서 계약 → 품질 검증으로 넓힌다

매주 개념을 읽은 뒤, 해당 코드에서 근거를 찾고 작은 결과물로 정리한다. 앞의 실습 결과를 재사용하되, 브라우저에서 수집한 값과 코드가 덧붙인 해석을 구분하는 습관을 유지한다.

주차배울 개념이 레포에서 읽을 파일결과물
1주차 ES 모듈, Manifest V3, 실행 환경 분리, 비동기 메시지 전달 manifest.json
service-worker.js
popup/popup.js
tests/run-tests.mjs
과제 1의 실행 결과와 호출 흐름도. RUN_EXTRACTION과 TYPEUI_EXTRACT_STYLES가 오가는 주체, 각 응답에 담기는 데이터를 표시한다.
2주차 DOM 탐색, CSS 계산값, 표본 선택과 가시성 판정 content-script.js의 collectSampledElements(), isVisible(), extractStylesFromPage() 수집 항목과 CSS 속성의 대응표. 셀렉터 우선순위·중복 제거·표본 한도가 결과에 미치는 영향을 적고, 크기가 없는 요소와 Shadow DOM 내부 요소가 빠지는 경로를 설명한다.
3주차 Map 집계, 값 정규화, 숫자 정렬, 휴리스틱과 신뢰도 lib/normalize.mjs
tests/run-tests.mjs의 mockPayload.siteSignals
과제 2의 토큰 표와 사이트 추론 비교표. 별도 메모리 입력에서 siteSignals만 바꿔 siteProfile.scores와 evidence를 비교하고, 규칙 점수를 정답 확률로 읽을 수 없는 이유를 적는다.
4주차 템플릿 기반 생성, 입력 계약, 메타데이터 우선순위, YAML 프런트매터 DESIGN.md
lib/generate-design-md.mjs
lib/generate-skill-md.mjs
과제 3의 두 문서 비교표. 공통 필수 섹션과 SKILL.md의 추가 형식을 정리하고, metadata가 추론된 독자·제품 성격보다 우선하는 위치를 찾는다.
5주차 오류와 경고, 문자열 검사의 한계, 파일 저장과 대체 경로 lib/validate.mjs
popup/popup.js의 installQuick(), writeFileToProject(), fallbackQuickInstall()
과제 4의 통과 여부 비교표와 검증 개선안. Quick install의 성공·취소·대체 경로를 코드로 추적하고, QUICK_INSTALL_PROVIDERS의 도구별 저장 위치를 함께 정리한다.
학습 점검
출력 문장에서 근거 코드까지 거슬러 올라가기

색 토큰 하나는 수집 속성과 정규화 규칙으로, 독자 설명은 사이트 신호와 점수 규칙으로, 검증 결과는 검사 조건으로 연결해 설명해 보자. 이 연결을 할 수 있으면 다른 확장이나 문서 생성기에서도 실측·추론·검증의 경계를 스스로 찾을 수 있다.

14핵심 키워드 사전

브라우저가 읽은 값부터 에이전트에게 전달할 문서까지, 코드와 연결해서 익히는 용어
DOM
브라우저가 페이지의 요소를 나무 모양으로 표현한 구조다. 이 레포의 content-script.js는 document.querySelectorAll()로 요소를 찾아 스타일 수집과 구성 요소 개수 집계에 사용한다.
계산된 스타일
여러 CSS 규칙과 상속을 적용한 뒤 브라우저에서 조회할 수 있는 스타일 값이다. 이 확장은 window.getComputedStyle()로 선택한 요소의 글자색·크기·여백 등을 읽으며, 원래 CSS 변수 이름을 복원하는 것은 아니다.
디자인 토큰
색이나 간격처럼 반복해서 쓸 디자인 값에 붙인 이름이다. 이 레포는 추출한 값으로 color.text.primary, space.1 같은 이름을 만들며, 이름이 원본 사이트의 설계 의도까지 보장하지는 않는다.
샘플링
전체 대신 일부를 골라 관찰하는 방법이다. collectSampledElements()는 정해진 셀렉터 순서와 가시성 조건으로 표본을 모으므로, 추출 결과를 모든 요소의 완전한 목록으로 읽으면 안 된다.
정규화
같은 뜻의 서로 다른 표기를 비교하고 처리하기 쉬운 형태로 맞추는 작업이다. lib/normalize.mjs는 RGB 색을 hex로 바꾸고 시간 단위를 맞추는 한편, 값을 묶어 토큰 목록을 만든다.
Manifest V3
확장의 권한과 실행 구성을 선언하는 크롬 확장 규격이다. 이 레포의 manifest.json에는 팝업, 모듈형 서비스 워커, activeTab·scripting·storage·downloads 권한이 선언되어 있다.
서비스 워커
확장에서 화면 없이 이벤트를 처리하는 백그라운드 실행 주체다. 이 레포의 service-worker.js는 팝업 요청을 받아 탭에 추출기를 주입하고, 받은 데이터를 정규화·문서 생성·검증으로 넘긴다.
온디맨드 주입
코드를 미리 모든 페이지에 실행시키는 대신 필요한 순간에 대상 탭으로 넣는 방식이다. 여기서는 injectExtractor()가 chrome.scripting.executeScript로 content-script.js를 실행하고, 설치 여부 가드가 리스너의 중복 등록을 막는다.
메시지 전달
서로 다른 실행 환경이 요청과 응답을 주고받는 방법이다. 팝업은 RUN_EXTRACTION을 서비스 워커에 보내고, 서비스 워커는 TYPEUI_EXTRACT_STYLES를 콘텐츠 스크립트에 보내 원시 데이터를 받는다.
순수 함수
외부 상태를 바꾸지 않고 입력을 결과로 변환하는 함수다. 이 레포의 lib/는 브라우저에서 값을 읽는 작업과 분리되어 있어, 가짜 입력을 넣고 Node.js에서 변환 결과를 확인할 수 있다.
휴리스틱
미리 정한 규칙으로 그럴듯한 답을 빠르게 추정하는 방법이다. inferSiteProfile()은 제목·경로·본문 키워드와 요소 개수에 점수를 매겨 독자와 제품 성격을 추론하며, 신뢰도 라벨은 규칙의 점수 차이에서 나온다.
프런트매터
문서 맨 앞의 구분선 사이에 이름이나 설명 같은 메타데이터를 적는 영역이다. generateSkillMarkdown()은 YAML 형식의 name과 description을 만들고, 스킬 모드 검증기는 관련 문자열이 있는지 확인한다.
문자열 검증
문서 구조를 해석하는 대신 특정 글자가 들어 있는지 확인하는 검사다. validateMarkdownOutput()은 필수 제목과 상태 표현 등을 찾지만, 제목의 정확한 일치나 실제 접근성 충족 여부까지 검사하지는 않는다.
File System Access API
사용자가 고른 폴더와 파일에 브라우저가 접근하도록 하는 기능이다. Quick install은 showDirectoryPicker()로 폴더를 선택받아 도구별 경로에 SKILL.md를 쓰며, API가 없거나 취소가 아닌 저장 오류가 나면 클립보드 복사와 다운로드를 대체 경로로 시도한다.