릴리즈는 기능이 아니라 약속이다

도형 리사이즈, 모자이크 버그, 그리고 N 배지 하나가 알려준 운영 부채

최근 AnnotateShot 릴리즈는 겉으로 보면 작은 기능들의 묶음이었다. 원을 타원으로 그릴 수 있게 하고, 선택한 도형의 크기를 조절하고, 모자이크 채우기가 회색으로 보이는 문제를 고쳤다. 하지만 실제로 남은 감각은 기능보다 운영이었다. 기능은 잘 만들면 끝나는 것처럼 보이지만, 릴리즈는 사용자가 오늘 무엇을 받았는지 정확히 알게 만드는 약속이다.

이번 작업에서 가장 선명했던 장면은 N 배지였다. Changelog 날짜를 오늘로 바꿨는데도 전역 내비게이션에는 새 릴리즈 표시가 뜨지 않았다. 이유는 간단했다. nav-freshness.js 안에 최신 날짜가 하드코딩되어 있었다. 릴리즈 노트의 날짜와 별개로, 또 하나의 날짜를 사람이 기억해서 고쳐야 했다.

수동 절차는 결국 빠진다

하드코딩된 최신 날짜는 처음에는 합리적으로 보였을 수 있다. 정적 사이트에서 최근 콘텐츠 배지를 띄우려면 어디엔가 기준일이 있어야 하니까. 하지만 릴리즈를 반복하면 그 기준일은 곧 부채가 된다. 기능 구현, QA, changelog, 스크린샷, 커밋, push까지 이어지는 흐름에서 별도의 날짜 상수를 기억해야 한다면 언젠가는 빠진다.

그래서 이번 릴리즈에서는 기준을 바꿨다. nav-freshness.js가 더 이상 자기 안의 날짜를 믿지 않는다. 대신 changelog.html의 첫 번째 .version-tagblog/index.html의 첫 번째 .post-date를 읽는다. 릴리즈 노트와 블로그 목록이 source of truth가 되고, 배지는 그 결과를 따라간다.

도형 기능도 같은 문제였다

도형 리사이즈와 원/타원 기능도 같은 종류의 정리였다. 처음에는 "원을 찌그러뜨릴 수 있으면 좋겠다"는 사용성 요청이었다. 구현으로 들어가면 질문이 생긴다. 원과 타원을 별도 도구로 나눌 것인가, 하나의 도구로 둘 것인가. 기존 디자인 도구의 관습을 빌려 기본은 자유 타원, Shift는 정원 유지로 정했다. 그리고 힌트를 노출했다. 숨은 단축키가 아니라, 보이는 기본 기능과 정밀 제어의 조합으로 만들었다.

모자이크 버그도 비슷했다. 기존 코드는 getImageData()로 픽셀을 읽어 작은 블록으로 다시 칠했다. 실패하면 회색 채우기처럼 보이는 fallback으로 떨어졌다. 사용자는 내부 사정을 모른다. 그냥 "모자이크가 안 된다"고 느낀다. 이번 수정은 픽셀을 직접 읽는 대신, 오프스크린 캔버스에 영역을 작게 그렸다가 smoothing 없이 키우는 방식으로 바꿨다. 더 단순하고, 더 안정적이다.

GSD의 핵심은 끝까지 가는 기준이다

이번 과정에서 GSD는 "빨리 고친다"가 아니었다. 이슈를 만들고, 구현하고, 테스트하고, 브라우저에서 보고, 릴리즈 노트와 QA 로그를 남기고, push 권한은 분리했다. 특히 push는 명시적 승인 후에만 한다는 규칙을 다시 확인했다. 빠르게 가되, 어디서 멈춰야 하는지도 알아야 한다.

작은 릴리즈가 쌓이면 서비스의 성격이 드러난다. AnnotateShot은 무거운 협업 도구가 아니라, 이미지를 열고 바로 표시하고 저장하는 도구다. 그래서 기능의 크기보다 흐름의 정확성이 중요하다. 도형은 선택하면 편집 가능해야 하고, 모자이크는 실제로 모자이크처럼 보여야 하고, 새 릴리즈가 있으면 사용자가 알 수 있어야 한다. 당연한 것들이 당연하게 동작해야 한다.

이번 릴리즈의 교훈은 단순했다. 사람이 매번 챙겨야 하는 절차는 자동화하거나, source of truth를 하나로 줄여야 한다.