AI가 생성한 코드가 빌드를 통과하고 CI가 초록불을 켰다고 해서 사용자 경험이 완성된 건 아니다. 프로덕트에는 자동화 도구가 기본값으로 보지 않는 영역이 두 군데 있다. 에러 메시지와 접근성이다. 이 둘은 묘하게 닮아있다—평소엔 아무도 보지 않고, 망가져도 배포는 막히지 않고, 피해는 사용자가 조용히 혼자 떠안는다. 그리고 AI는 둘 다 고쳐줄 수 없다.
에러 메시지는 설계의 마지막 줄이 아니라 별도의 설계 면이다
개발자라면 한 번쯤 이런 식으로 에러 메시지를 썼을 것이다. 기능 구현을 끝내고, 마감이 코앞에 있고, 예외 처리를 붙여야 하는데 손가락은 이미 다음 함수 위에 올라가 있다. 그 순간에 나오는 게 "Something went wrong. Please try again." 같은 문장이다. 기술적으로 틀리지 않고, 빌드는 통과하고, 아무도 PR에서 지적하지 않는다.
dev.to에 공개된 RAXXO 개발자의 회고는 그 문장이 실제로 어떤 비용을 만들어내는지를 솔직하게 기록하고 있다. 요지는 단순하다. 에러가 발생하는 것 자체는 문제가 아니다. 에러 메시지가 사용자를 막다른 골목에 세워두는 것이 문제다. 막다른 골목에 선 사용자는 다음 행동을 찾지 못하고 지원 메시지를 보내거나 그냥 이탈한다. 지원 메시지는 개발자의 시간을, 이탈은 사용자의 신뢰를 각각 소비한다. 그리고 그 손실은 대부분 에러 자체보다 에러 메시지에서 발생한다.
그가 정착한 구조는 세 파트다. 무슨 일이 일어났는가(What happened), 왜 일어났는가(Why, 사용자가 행동을 바꿀 수 있을 때만), 다음에 무엇을 해야 하는가(What to try next). 세 번째 파트가 핵심이다. 메시지가 문제 기술에서 끝나면 그건 사용자에게 벽을 보여주는 것과 같다. 벽 앞에 선 사람은 문을 찾지 못하면 돌아선다. 다음 행동을 명시하는 것만으로 지원 문의는 눈에 띄게 줄었다고 그는 기록한다.
여기서 흥미로운 운영 규칙이 하나 나온다. 같은 내용의 지원 질문이 두 번 반복되면 그건 문서화 문제가 아니라 에러 카피의 버그로 분류하고 인터페이스를 직접 수정한다는 것. 더 친절한 답변 메일을 쓰는 게 아니다. 사용자가 혼란스러워한 바로 그 문자열을 찾아서 고친다. 지원 메시지를 제품 개선의 신호로 읽는 관점 전환이다. AI가 코드를 아무리 빠르게 생성해줘도, 이 신호를 읽고 어느 메시지를 고쳐야 할지 판단하는 건 여전히 개발자의 몫이다.
접근성 자동화 도구의 초록불은 완성이 아니다
에러 메시지가 '기능은 작동하는데 경험이 망가지는' 사례라면, 접근성은 '테스트까지 통과했는데 경험이 망가지는' 사례다. 후자가 더 위험하다. 자동화 도구가 확인해줬다는 신뢰감이 문제를 더 오래 숨기기 때문이다.
dev.to에 공개된 또 다른 사례가 이 지점을 정확하게 짚는다. 개발자가 다크 모드에서 텍스트와 배경이 완전히 같은 색으로 렌더링되는 버튼을 배포했다. rgb(108, 196, 154) 위에 rgb(108, 196, 154) 텍스트—1:1 대비, 사실상 보이지 않는 버튼이다. 그런데 axe-core 기반의 CI 파이프라인은 초록불을 켰다.
이유는 두 가지였다. 첫 번째는 잘 알려진 것이다. 헤드리스 브라우저가 라이트 모드로 부팅되어 다크 모드 팔레트를 아예 렌더링하지 않았다. colorScheme: 'dark' 한 줄로 해결할 수 있는 문제다. 그런데 두 번째가 더 근본적이다. 다크 모드로 재실행해도 axe는 이것을 위반(violation)으로 분류하지 않았다. incomplete 버킷에 넣었다.
왜냐하면 axe는 1:1 대비가 의도적인 숨김 기법인지, 버그로 인한 결과인지 구분할 수 없기 때문이다. 시각적으로 숨겨진 레이블, 페이드인 중인 텍스트, 장식 요소—모두 동일한 패턴을 가질 수 있다. axe는 증명할 수 있는 것만 위반으로 보고하고 나머지는 사람에게 넘긴다. 이건 linter로서 올바른 결정이다. 문제는 대부분의 파이프라인이 violations 버킷만 보고 incomplete는 버린다는 것이다. 그리고 대비 결함이 심각할수록—1:1에 가까울수록—위반보다 incomplete로 분류될 가능성이 높다. 가장 나쁜 결함이 가장 쉽게 통과하는 구조다.
이 개발자가 내린 결론은 두 가지다. incomplete를 버리지 말고, 베이스라인 대비 새로 등장한 항목을 추적하라. 단순한 카운트는 아무도 안 읽는 숫자지만, "다크 모드에서 새로 발생한 3개의 항목"은 원인이 붙은 버그 리포트가 된다. 나아가 그는 다크 모드, 강제 색상, 320px 리플로우, 모션 감소 등 각 상태를 한 번에 하나씩 변수로 바꾸며 고유하게 발생하는 접근성 결함만 추출하는 a11y-matrix 도구를 직접 만들었다.
재밌는 건 그 도구를 자기 페이지에 돌렸을 때다. 320px에서 비교 테이블이 세 번째 컬럼을 가리는 문제가 발견됐다. 권장 방식대로 overflow-x: auto에 tabindex="0", role="region", aria-label을 붙였다. 재실행했더니 동일한 여섯 개의 결함이 그대로였다. 이유는 axe가 "오버레이에 가려짐"과 "스크롤 컨테이너 안에서 뷰 밖으로 밀려남"을 같은 메시지로 처리하기 때문이다. 전자는 버그고 후자는 WCAG가 권장하는 정확한 처리 방식인데 axe는 구분하지 못한다. 올바르게 고친 페이지가 도구에게 여전히 결함으로 보이는 상황이다.
눈에 보이지 않는 UX 품질을 설계한다는 것
두 사례가 공유하는 구조적 문제가 있다. 자동화 도구는 렌더링된 상태에서 측정 가능한 것만 잡아낸다. 에러 메시지가 사용자를 막다른 곳에 세우는지, 접근성 결함이 다크 모드에서만 나타나는지, 올바른 수정이 도구의 오탐으로 실패 처리되는지—이런 것들은 자동화의 사각지대에 존재한다.
AI 생성 코드가 빨라질수록 이 사각지대는 상대적으로 더 넓어진다. 코드가 더 빠르게 쌓이는데 이 영역을 보는 사람의 수는 늘지 않기 때문이다. 낯선 사람이 에러 메시지를 읽고 다음 행동을 찾을 수 있는지 테스트하는 것, 다크 모드와 320px와 강제 색상 각각에서 접근성 도구가 뭐라고 하는지 직접 확인하는 것—이건 AI에게 위임할 수 없는 판단이다.
프로덕트가 사용자에게 진짜 도움이 되는가를 묻는 질문의 답은, CI 초록불과 빌드 성공 메시지 바깥에 있다. 에러가 발생하는 최악의 순간에 사용자가 혼자 길을 찾을 수 있는지, 특정 환경에서 버튼이 사라지진 않는지—이 두 가지를 직접 확인하는 것이 AI 시대 프론트엔드 개발자의 가장 인간적인 역할로 남는다.