AI가 결과를 뱉는 순간, 많은 팀이 그걸 '완료'로 받아들인다. 자막은 99% 정확도를 찍었고, 커버 이미지는 멋지게 생성됐고, 컴포넌트 스타일은 번들에 올라갔다. 체크리스트 세 줄이 동시에 지워진다. 그런데 사용자 앞에 도착한 결과물은 전혀 다른 이야기를 하고 있다. 자막은 한 단어가 조용히 의미를 바꿔놨고, 이미지는 썸네일 크기에서 그냥 검은 사각형이 됐고, 버튼 스타일은 누군가의 !important 때문에 6주째 멈춰 있다.
이 세 가지는 표면적으로 완전히 다른 문제처럼 보인다. 하나는 ASR 모델 출력이고, 하나는 이미지 생성 프롬프트 실패고, 하나는 CSS 명시도 충돌이다. 하지만 한 층 아래를 보면 구조가 같다. AI 혹은 자동화된 시스템이 결과를 냈고, 그 결과를 그대로 쓰면 안 됐는데, 개입 지점을 아무도 설계하지 않았다. 이 글은 그 세 층위를 하나씩 해체하고, 프론트엔드 설계자가 어디서 손을 다시 얹어야 하는지를 이야기한다.
첫 번째 층위: 정확도 99%는 워크플로우가 아니다
자막 편집기를 직접 만든 한 개발자의 경험담이 출발점이 됐다(dev.to). ASR 모델이 돌아오는 건 WER(Word Error Rate) 기준으로 거의 완벽한 수준이었다. 그런데 테스트 클립 하나에서 HP가 gp로 표기됐다. 한 글자, 하나의 토큰. 그 단어는 주변의 깔끔한 텍스트와 똑같이 생겼고, 아무 위험 신호도 없었다.
여기서 핵심 문제가 드러난다. 모델은 불확실성을 갖고 있었다. 인터페이스가 그걸 숨겼다. ASR 시스템은 단어 수준의 타이밍 데이터와 컨피던스 값을 이미 반환하고 있었다. 그런데 UI는 그 값을 죄다 동등하게 렌더링했다. 결과적으로 확신도가 낮은 단어와 높은 단어가 화면에서 구분되지 않았다.
더 근본적인 문제는 WER이라는 숫자 자체의 한계다. and가 an이 되는 건 거의 무해하다. 하지만 not이 빠지면 문장의 의미가 반전된다. 15가 50이 되면 내용이 완전히 달라진다. WER은 오류를 센다. 그 오류의 결과(consequence)는 세지 않는다. 제품 레벨에서 "99% 정확도"를 약속으로 내거는 순간, 이 간극은 사용자에게 고스란히 전달된다.
이 개발자가 찾은 해법은 컨피던스 값을 UI에 뿌리는 게 아니었다. 그건 하나의 haystack을 다른 haystack으로 바꾸는 일에 불과하다. 대신 리뷰 큐(review queue)를 설계했다. 컨피던스가 낮은 토큰을 순위화하고, 검토 대상을 유한하게 바운딩하고, 사용자가 각 항목을 accept 또는 edit 하면 완료 상태가 기록되는 구조다. "All clear"는 '모델이 완벽하다'는 뜻이 아니라 '이 큐의 모든 항목을 사람이 확인했다'는 뜻으로 재정의된다. 워크플로우 스테이트이지 품질 인증서가 아니다.
설계할 게 또 하나 있다. 컨피던스 값이 아예 없을 때다. 이 케이스를 '높은 신뢰'로 처리하는 건 조용한 재앙이다. 부재를 가장 낮은 리뷰 스코어 0으로 정규화하고, 그 토큰을 큐에 올리는 것이 안전한 기본값이다. 없는 증거를 확실성으로 변환하지 말 것.
두 번째 층위: 이미지는 당신이 승인한 크기로 존재하지 않는다
RealFeedApp을 만든 개발자는 토픽별 커버 이미지를 AI로 생성해 앱에 뱉었다(dev.to). 생성 결과는 프리뷰 그리드에서 진짜 좋았다. 어둡고 절제된 분위기, 검은 배경에 오브젝트가 떠 있는 에디토리얼한 느낌. 승인하고 배포했다.
그런데 실제 피드에서 카드는 400픽셀 이하의 타일로 렌더링됐다. 넓은 검은 공간 한가운데 작은 오브젝트가 박혀 있는 이미지가 그 크기로 줄어들면 어떻게 보일까. 검은 사각형. 이미지가 아니라 부재(absence). 모델은 정확히 요청한 대로 결과를 냈다. 문제는 그 요청이 잘못된 크기 기준으로 작성됐다는 점이다. 대형 프리뷰로 승인하고, 소형 타일로 배포한 것이다.
두 번째 실패는 더 흥미롭다. 다크 무드를 버리고 다큐멘터리 사진 스타일로 재프롬프트를 짰더니, 15장 중 3장에 뉴스 그래픽 하단 바와 헤드라인이 생성됐다. TROOLDOGS NEWS 같은 완전히 조작된 텍스트. 랜덤 오류가 아니라 패턴이었다.
원인을 추적하니 세 단어의 결합이었다. "editorial look" + "news features" + "accent colour in the frame". 이미지 모델은 이 세 가지 요건을 동시에 가장 저렴하게 만족하는 해를 찾았다. 그 해가 뉴스 그래픽 with 컬러 배너였다. 모델이 오작동한 게 아니라, 요건을 정직하게 연산한 결과였다. 세 단어가 각각 무해해 보였지만, 합쳐지면 원치 않는 것을 가장 효율적으로 묘사하는 명세가 됐다.
그리고 하나 더. 스타일을 바꾸는 건 렌더링 방식을 바꾸는 게 아니다. "어두운 포털로 흘러드는 빛나는 토큰 스트림"은 다큐멘터리 사진으로 찍을 수 없다. 그런 사진은 존재하지 않는다. 스타일을 전환하면 묘사하는 피사체 자체를 다시 설계해야 한다. 스타일은 고정된 아이디어 위에 얹는 필터가 아니다.
실용적인 교훈은 간단하고 다시 잊기 쉽다. 이미지가 실제로 보일 크기로 프롬프트를 쓸 것. 승인은 배포 컨텍스트에서 할 것. 그리고 상업적 용도라면 모델 라이선스를 생성 전에 확인할 것. 나중에 발견하면 이미 늦다.
세 번째 층위: 스타일 충돌은 설계의 실패다
컴포넌트 라이브러리를 쓰는 팀이라면 누구나 한 번쯤 겪는 장면이 있다. 버튼 배경색을 바꾸려고 클래스 하나 추가했는데 적용이 안 된다. 클래스명도 맞고, CSS도 로딩되고, 심지어 번들 순서도 뒤다. 그래서 !important를 쓴다. 6주 뒤 팀원이 같은 문제를 만나고, 그도 !important를 쓴다. 이제 두 !important가 하나의 버튼을 두고 싸우고 있다. 마지막에 배포한 사람이 이긴다. 설계가 아니라 우연으로(dev.to).
이 문제의 원인은 명시도(specificity)다. .card .button은 2개의 클래스 선택자, 명시도 0-2-0. 소비자가 쓰는 .button은 0-1-0. 번들 순서가 뒤여도 진다. CSS의 "나중에 오면 이긴다"는 규칙은 명시도가 동일할 때만 적용되는 타이브레이커다. 많은 사람이 이걸 첫 번째 규칙으로 기억한다.
네스팅이 깊어질수록 문제는 커진다. .dashboard .card .card-header .button은 0-4-0. 소비자가 이기려면 클래스를 4개 쌓아야 한다. 그 4개를 쌓으면 이번엔 다른 누군가가 5개를 쌓는다. 이게 명시도 전쟁이다.
CSS :where()는 이 문제를 위해 만들어진 선택자다. :where() 안에 무엇을 넣든 그 선택자들의 명시도 기여는 0이 된다. 0-1-0도 아니고, 낮은 것도 아니고, 정확히 0. :where(.dashboard .card .card-header) .button으로 작성하면 전체 명시도는 .button 하나의 0-1-0이 된다. 소비자의 .button { background: teal }이 드디어 공정한 싸움을 할 수 있다. 번들 순서가 뒤니까 이긴다.
:is()와 혼동하면 안 된다. :is()는 내부 선택자 중 가장 높은 명시도를 가져온다. :is(#sidebar, .card) .button은 .card가 매칭되더라도 #sidebar 때문에 ID 레벨 명시도 1-0-0을 갖는다. 명시도를 평탄화하고 싶다면 :is()가 아니라 :where()다.
컴포넌트 라이브러리를 만드는 입장이라면 :where()로 스코핑 선택자를 감싸는 게 소비자에게 문을 여는 행위다. 벽 대신 문을 만드는 것. !important는 이 구조 안에서 다시 올바른 용도—"이건 무조건 이겨야 해"라는 명시적 선언—로 돌아온다.
AI 결과물의 공통된 실패 패턴
세 사례는 각각 다른 도메인에 있지만, 실패의 구조는 같다. AI 또는 자동화된 시스템이 산출물을 낸다. 그 산출물은 주어진 기준 안에서는 정확하다. 문제는 그 기준이 실제 사용 맥락을 담고 있지 않다는 점이다.
99% WER은 단어의 결과 가중치를 모른다. 이미지 모델은 타일 크기를 모른다. 컴포넌트 라이브러리는 소비자의 선택자 깊이를 모른다. 이 간극을 채우는 건 AI가 아니라 설계자의 몫이다. 워크플로우를 설계하고, 검토 맥락을 설정하고, 개입 지점을 만드는 것. 생성이 끝난 지점에서 진짜 설계가 시작된다.
결국 이건 AI를 불신하라는 이야기가 아니다. ASR 모델은 강력하고, 이미지 생성은 쓸 수 있고, 번들러는 동작한다. 문제는 그 출력을 '완료'로 받는 순간 우리가 설계의 책임을 내려놓는다는 점이다. AI가 결과를 뽑는 속도가 빨라질수록, 그 결과를 실제 사용 맥락에 맞게 재설계하는 능력이 프론트엔드 설계자의 핵심 역량이 된다.