디지털 가든
답장, 캡슐, 그리고 Wrapped: 이 행성은 기억하는 법을 배웠다
프롤로그: 보여진 다음에
지난 라운드에서 이 행성의 하늘에 사람이 살기 시작했다: 진짜 달, 계절마다 바뀌는 별자리, 서로의 빛의 자취. 별의 벽도 늘 그곳에 있었다 — 별을 남기면, 누군가 그것을 밝혀준다.
그런데 밝혀진 다음엔? 만 사천 개의 별에게 ‘밝히기’는 유일한 동사였다. 이번 라운드는 세 가지를 만든다. 주제는 단 하나: 기억. 답장은 하나의 별을, 캡슐은 한 마디를, 연감은 한 해를 기억한다.
하나: 별의 답장
별에 처음으로 답장이 닿는다. 관리 페이지에서 어떤 별에든 답장을 남길 수 있다 — 상한은 백마흔 자, 한 글자도 더 들어가지 않는다 — 그리고 답장이 쓰이는 순간, WebSocket이 행성 위의 모든 이에게 즉시 알린다.
프런트의 연출: 보랏빛 분홍 혜성이 꼬리를 끌고 그 별 위로 내려오고, 별의 색은 밝힌 따뜻한 빛에서 더 따뜻한 흰색으로 천천히 변한다. 별 옆에는 ↩ 표시가 생긴다. 마우스를 올리면 답장 전문이 떠오른다.
디버깅에서 조용한 함정 하나를 발견했다: Astro 빌드는 타입 검사를 하지 않아서, sealOff의 i18n 키 누락이 초록빛 빌드로 그냥 통과했다 — 이런 종류의 오류는 절대 소리치지 않는다. 출시 전에 모든 페이지를 직접 클릭해 보는 수밖에 없다. 조용한 실패의 가장 무서운 점은, 실패조차 알리지 않는다는 것이다.
둘: 타임캡슐
한 마디를 쓰고 봉인한다: 봉인하지 않기, 삼십 일, 아니면 일 년. 봉인된 별은 얼음빛 파란색으로 빛나고, 내용은 누구에게도 보이지 않으며, 투표도 할 수 없다 — 백엔드는 404를 반환한다. 비밀은 프런트의 양심이 아니라 서버가 지킨다.
시간이 되면 Durable Object의 네이티브 알람이 깨워준다: 시각이 되는 순간, 연결된 모든 클라이언트에 개봉을 알린다. 누구도 폴링할 필요가 없다. 관리 페이지에서 미리 개봉할 수도 있다.
별의 위치는 id로 결정론적으로 계산된다 — 몇 번을 새로고침해도 그 별은 제자리에 있다. 한 마디를 봉인한 별이 방문할 때마다 이사 가서는 안 된다.
통합 테스트에서 교과서적인 동시성 버그 두 개를 잡았다:
- WebSocket 브로드캐스트가 POST 응답보다 먼저 실행되고, 늦게 도착한 응답이 프런트의 또 한 번의 fetch를 유발해 같은 캡슐 별이 두 개 그려졌다. id와 문구 기준 중복 제거로 수정했다.
- 새 별의 위치를
WHERE ip=?역조회로 찾아서, 빈 IP일 때 고정된 별의 id를 잘못 가져왔다. INSERT 직후MAX(id)를 읽는 방식으로 변경 — 추측한 id는 신뢰할 수 없다. 방금 쓴 그 하나를 가져와야 한다.
셋: 행성 연감
/wrapped — 이 행성 자신의 연간 보고서. 네 언어로 제공되며, 데이터는 백엔드의 실제 통계에서 온다: 올해 얼마나 많은 별이 닿았는지, 가장 많이 밝혀진 별에는 무슨 말이 적혔는지, 몇 개의 국가와 지역에서 방문했는지(국기 포함), 총 방문 수, 그리고 아직 봉인 중인 편지의 수.
숫자는 모두 count-up 애니메이션: 뷰포트에 들어와야 시작하고, 0부터 실제 값까지 센다. IntersectionObserver와 gsap의 힘으로, 보이지 않으면 세지 않고, 다 세면 멈춘다. 커맨드 팔레트와 푸터에도 입구를 추가했다.
출시 당시 /api/wrapped의 응답: 방문 320회, 4개국. 작은 숫자지만, 진짜다.
에필로그
이 행성은 이제 답장하고, 마음을 봉인하고, 연말이면 연감을 제출한다. ‘기억’은 더 이상 브라우저 새로고침에 지워지는 것이 아니다 — 데이터베이스에, Durable Object의 알람 속에 새겨져 있다.
다음은 커맨드 팔레트 액션과 달 이스터에그. 별의 벽의 다음 장은 시간에 맡긴다.
댓글