하루의 기록을 준비하고 있어요

잠시만 기다려 주세요.

목록으로

초록색 연결 상태는 사용자 흐름의 완료 증명이 아니었다

외부 시장 신호, 모바일 로딩과 프로젝트 트래픽을 연결하며 브라우저부터 API·원장·외부 공급자·화면까지 같은 흐름으로 검증하게 된 기록입니다.

책처럼 보기
보통
초록 표시등 옆에서 웹훅 벨, 휴대전화, 집계 차트와 캐시 카드가 하나의 검증 경로로 연결된 편집 일러스트

주제

하루스페이스 랩

초록색 연결 상태는 사용자 흐름의 완료 증명이 아니었다

외부 시장 신호, 모바일 로딩과 프로젝트 트래픽을 연결하며 브라우저부터 API·원장·외부 공급자·화면까지 같은 흐름으로 검증하게 된 기록입니다.

요약

요약

  1. 외부 시장 신호는 그룹관리자가 선택한 채팅방으로만 전달하고 허용 필드·중복 방지·AI 문맥 분리를 적용했습니다.
  2. 모바일 로딩은 컴포넌트가 아니라 동적 viewport와 safe area를 포함한 실제 화면 전체를 기준으로 고쳤습니다.
  3. 분석 연결 확인과 실제 대시보드가 다른 계약을 쓰던 문제를 찾아 전체 보고서·캐시 경로를 함께 검증하도록 보완했습니다.
12페이지
초록 표시등 옆에서 웹훅 벨, 휴대전화, 집계 차트와 캐시 카드가 하나의 검증 경로로 연결된 편집 일러스트

Summary

한눈에 보기

  • 외부 시장 신호는 그룹관리자가 선택한 채팅방으로만 전달하고 허용 필드·중복 방지·AI 문맥 분리를 적용했습니다.
  • 모바일 로딩은 컴포넌트가 아니라 동적 viewport와 safe area를 포함한 실제 화면 전체를 기준으로 고쳤습니다.
  • 분석 연결 확인과 실제 대시보드가 다른 계약을 쓰던 문제를 찾아 전체 보고서·캐시 경로를 함께 검증하도록 보완했습니다.

이 글은 2026년 8월 24–26일의 설계 문서, Git 기록과 운영 이슈를 바탕으로 다시 쓴 Haru Space 개발 회고다. 실제 웹훅 주소와 연결 키, 채팅방·프로젝트 식별자, 자격 증명, 내부 API와 공급자 응답 원문은 공개하지 않는다.

개발 화면에는 초록색 상태가 많다. 연결됨, 활성, 요청 성공, 준비 완료, 응답 200.

그 표시는 필요하다. 하지만 어느 한 계층의 성공을 말할 뿐, 사용자가 원한 일이 끝까지 됐다는 뜻은 아니다.

외부 웹훅이 200을 받았지만 채팅방에 메시지가 없을 수 있다. 로딩 카드가 화면 높이를 채웠지만 모바일 안전 영역 아래에 검은 띠가 남을 수 있다. 분석 연결 확인은 성공했지만 실제 트래픽 대시보드는 계속 확인 필요를 표시할 수 있다.

세 가지 서로 다른 이슈가 같은 교훈으로 모였다.

“연결됨”은 설정의 상태다. 완료를 말하려면 브라우저에서 시작해 API, 저장 원장과 외부 공급자를 거쳐 사용자가 보는 마지막 화면까지 같은 흐름으로 확인해야 했다.

외부 시장 신호를 어느 방에 보낼 것인가

투자 메뉴에는 외부 차트 서비스의 웹훅 신호를 받을 기능이 있었다. 초기에는 신호가 투자 알림 목록에만 저장됐다. 사용자가 원하는 것은 특정 그룹 채팅방에서 구성원과 함께 보는 것이었다.

“채팅방으로 전달”은 단순한 메시지 복사가 아니었다. 외부 요청이 어느 그룹과 방을 선택할 권한이 있는지부터 다시 설계해야 했다.

payload가 그룹 코드와 방 식별자를 보내게 하면, 연결 키를 아는 외부 호출자가 목적지를 바꿀 수 있다. 원본 JSON 전체를 메시지로 복제하면 자격 증명이나 임의 문장이 채팅에 들어올 수 있다. 일반 봇 메시지로 저장하면 외부 입력이 AI의 최근 대화 문맥에 섞여 지시처럼 작동할 수도 있다.

우리는 목적지를 외부 payload에서 제거했다.

  • 그룹관리자가 Haru Space 안에서 자신이 참여하고 쓸 수 있는 일반 채팅방을 선택한다.
  • 서버가 그 선택과 공개 연결 참조를 묶어 저장한다.
  • 외부 요청은 서버가 발급한 해당 연결의 비밀을 증명할 뿐 그룹이나 방을 지정하지 못한다.
  • 신호 종류, 종목, 시각과 가격·거래량 같은 허용 필드만 파싱한다.
  • 임의 메시지와 알 수 없는 필드는 저장하거나 채팅으로 복제하지 않는다.
  • 결과는 세 언어의 정형 알림 이벤트로 만들고 AI 대화 문맥에서 제외한다.

한 요청이 재전송될 때 같은 메시지가 여러 번 생기지 않도록 정규화한 신호 지문으로 전달 원장을 만들었다. 원장과 채팅 메시지는 같은 트랜잭션에서 확정했다. 실시간 화면 갱신은 기존 경로를 재사용했고, 푸시는 부가적인 최선 전달로 분리했다. 푸시가 실패해도 웹훅 본 작업을 다시 실행해 채팅을 중복 생성하지 않았다.

웹훅의 성공 기준은 HTTP 응답이 아니었다. 올바른 연결이 올바른 방에 정확히 한 건의 안전한 이벤트를 남기고, 그 이벤트가 AI 지시로 재사용되지 않는 것까지였다.

복사 가능한 템플릿도 입력 검증의 일부였다

초기 신호 템플릿에는 문법 오류가 있었고, 이벤트 필드를 실제 분류에 사용하지 않아 서로 다른 신호가 같은 일반 알림으로 보일 수 있었다.

서버 파서를 고치는 것만으로는 사용자가 TradingView에 붙여 넣는 경험이 좋아지지 않는다. 골든·데드 신호별 완전한 JSON을 앱에서 복사할 수 있게 하고, 서버가 event와 상태 플래그의 일관성을 다시 확인했다. 숫자가 아니거나 허용하지 않은 이벤트는 조용히 보정하지 않고 거부했다.

UI가 제공한 템플릿과 서버 계약을 같은 테스트에서 확인했다. 사용자가 복사한 예시가 실제 입력에서 실패하지 않아야 했고, 손으로 변형한 잘못된 요청은 안전하게 닫혀야 했다.

로딩 화면은 화면 전체가 아니었다

같은 주에 모바일 Safari에서 밝은 로딩 화면 아래 검은 영역이 보였다. 로딩 컴포넌트 자체는 밝은 배경이고 높이도 전체 화면으로 설정돼 있었다. PC와 일반 모바일 미리보기에서는 문제가 잘 드러나지 않았다.

원인은 컴포넌트 밖에 있었다.

문서 루트의 기본 배경은 어두웠고, 모바일 브라우저의 동적 viewport와 하단 안전 영역이 고정 오버레이 바깥의 문서 배경을 순간적으로 보여 줄 수 있었다. 기기 상단·하단 영역까지 사용하도록 viewport 설정도 충분하지 않았다.

해결은 로딩 카드에 패딩을 더하는 정도가 아니었다.

  • 로딩 중 문서 루트와 화면의 배경색을 같은 테마로 동기화했다.
  • 고정 viewport, 작은 viewport와 동적 viewport 높이를 순서대로 지원했다.
  • 상하좌우 safe area를 로딩 화면의 실제 영역에 포함했다.
  • overscroll에서 다른 루트 배경이 드러나지 않게 했다.
  • 로딩이 끝나면 임시 루트 상태를 정리했다.

검증 기준도 컴포넌트 bounding box가 아니라 실제 모바일 화면의 모든 픽셀 영역으로 바꿨다. 밝은색과 어두운색, 서로 다른 모바일 높이에서 로딩 화면·문서 루트·body 배경이 일치하는지 확인했다.

사용자가 보는 오류는 컴포넌트 경계 밖에서 생길 수 있었다. 종단 검증에는 DOM 계층뿐 아니라 브라우저가 그리는 viewport까지 포함됐다.

그룹별 트래픽을 직접 수집하지 않기로 했다

Haru Space는 그룹 프로젝트를 관리하므로, 그룹원도 자기 프로젝트가 실제로 얼마나 방문되는지 보고 싶었다. 별도 Cloudflare 프록시나 원시 로그 수집기를 추가하는 방법도 있었지만 첫 단계에는 운영 지점과 비용이 너무 많이 늘었다.

이미 프로젝트가 배포된 Vercel의 Web Analytics 집계 API를 사용하기로 했다.

연결 설정은 그룹관리의 기존 Vercel 카드 안에 두고, 실제 지표는 별도 프로젝트 트래픽 그룹 메뉴에서 보게 했다. 설정과 일상 조회의 사용자를 분리한 것이다.

대시보드는 사람 방문의 집계값만 제공했다.

  • 기간별 페이지뷰와 방문자
  • 일별 추이
  • 상위 route와 유입 hostname
  • 국가와 기기 유형 범주

원본 IP, User-Agent, query string, 개별 요청 경로와 원시 방문 이벤트는 Haru Space에 저장하지 않았다. 봇과 AI crawler는 사람 방문 지표에 섞지 않고 공급자의 별도 관찰 화면으로 안내했다.

대시보드는 결제 화면이나 보안 로그의 대체품도 아니었다. 월 페이지뷰 기준은 그룹의 자문 알림일 뿐 실제 Vercel 과금 상한으로 표시하지 않았다.

같은 외부 프로젝트를 두 그룹의 숫자로 보여 주지 않았다

Vercel의 공개 집계 API는 당시 한 프로젝트 안의 여러 hostname을 원하는 방식으로 그룹별 분리하는 계약을 제공하지 않았다. 하나의 Vercel 프로젝트가 여러 그룹 도메인을 함께 제공하면, 그 합계를 각 그룹의 트래픽이라고 보여 줄 수 없다.

편의를 위해 도메인 문자열로 추정하는 대신 외부 프로젝트 하나를 한 그룹에만 연결하도록 했다. 플랫폼 전체에서 같은 프로젝트의 중복 연결을 막고, 소유권을 옮길 때는 기존 그룹의 원장과 과거 집계를 보존한 별도 승인 절차가 필요하다고 정했다.

플랫폼 최고관리자도 비공개 그룹 트래픽을 자동으로 볼 수 없게 했다. 인프라 연결을 관리하는 권한과 그룹 상세 지표를 읽는 권한을 분리하고, 현재 그룹의 활성 멤버십을 다시 확인했다.

숫자는 개인 대화보다 덜 민감해 보이지만, 그룹의 성장과 활동을 드러내는 운영 데이터다. 집계값도 그룹 주권 경계 안에 있어야 했다.

외부 분석 장애를 그룹 작업 장애로 만들지 않았다

트래픽 화면을 열 때마다 공급자 API를 새로 호출하면 느리고 비싸며, 외부 장애가 그대로 사용자 경험에 전파된다.

기간별 집계에는 짧은 cache를 두고, 여러 서버 인스턴스가 동시에 갱신하지 않도록 DB 임대를 사용했다. 공급자 호출이 실패하면 마지막 정상 집계를 stale로 표시해 제공했다. 정상 데이터가 한 번도 없을 때만 상태별 빈 화면을 보였다.

오류도 인증 필요, 권한 부족, 분석 리소스 확인 필요, 요청 제한과 일시 장애로 나눴다. 공급자 응답 본문과 요청 URL은 저장하지 않고 복구에 필요한 제한된 코드만 남겼다.

외부 분석이 멈춰도 채팅, 프로젝트 작업과 배포는 계속 동작해야 했다. 관찰 기능의 장애가 본 작업의 실패로 오인되지 않게 했다.

연결 확인은 성공했는데 대시보드는 실패했다

운영에서 가장 중요한 오류는 사용자가 설정을 끝낸 뒤 나타났다.

개발 연결 화면의 연결 확인은 성공했다. API도 정상 응답했고 저장된 연결과 최소 범위의 분석 자격 증명도 활성 상태였다. 그런데 같은 그룹의 프로젝트 트래픽 페이지는 계속 확인 필요를 표시했다.

원인은 연결 확인과 실제 대시보드가 다른 계약을 검증한 데 있었다.

연결 확인은 짧은 기간의 전체 방문 수 한 건만 조회했다. 실제 대시보드는 방문 수에 더해 일별, route, 유입처, 국가와 기기 집계를 함께 파싱했다. 외부 공급자는 직접 유입이나 분류 불가처럼 범주 이름이 비어 있는 정상 행을 보낼 수 있었다. Haru Space 파서는 그 한 행 때문에 전체 보고서를 잘못된 응답으로 폐기했다.

두 가지를 함께 고쳤다.

  1. 범주 이름이 없는 행은 해당 분류표에서만 제외하고 전체 요약과 다른 유효 행은 유지한다.
  2. 연결 확인도 실제 대시보드와 같은 전체 보고서를 조회하고 cache까지 준비해야 성공하게 한다.

관대한 파싱으로 모든 값을 받아들인 것은 아니었다. 일별 시각과 페이지뷰·방문자 수는 계속 안전한 정수 계약으로 엄격히 검증했다. 숫자가 잘못되면 0으로 꾸미지 않았다. 실패 로그에는 어느 집계 차원이 맞지 않았는지만 남기고 자격 증명, 필터와 응답 값은 넣지 않았다.

작은 probe의 초록불을 전체 기능의 초록불로 사용한 것이 문제였다.

자격 증명도 “Vercel 연결” 하나가 아니었다

초기에는 배포와 도메인 연결에 사용하던 Vercel OAuth 자격 증명을 분석 API에도 재사용하려 했다. 프로젝트가 정확하고 대시보드에 방문 데이터가 있어도 분석 조회는 원하는 결과를 주지 않았다.

조사 결과 두 기능의 공식 권한 범위가 달랐다. 배포 연결 자격 증명에 분석 권한이 포함된다고 가정할 수 없었다.

분석은 현재 팀과 단일 프로젝트만 허용한 별도 최소 범위 자격 증명을 사용하도록 분리했다. 저장 전 실제 분석 capability를 확인하고, 서버에서 암호화해 보관했다. 클라이언트에는 등록 여부와 갱신 시각만 반환했으며 일부 문자열도 다시 보여 주지 않았다. 연결 프로젝트가 바뀌면 자격 증명을 자동 이관하지 않았다.

“같은 공급자”는 “같은 권한”이 아니었다. 배포, 도메인과 분석을 하나의 연결 스위치로 보는 순간 최소 권한이 무너졌다.

종단 검증 체크리스트

이 주의 세 사례에서 공통으로 남은 점검 순서는 다음과 같다.

1. 사용자가 시작한 입력이 정확히 무엇인가

복사 템플릿, 선택한 채팅방, 모바일 테마와 분석 프로젝트를 실제 UI 값으로 확인한다.

2. 서버가 목적지와 권한을 다시 결정하는가

외부 payload와 URL의 임의 식별자를 믿지 않는다. 현재 그룹, 방, 프로젝트 소유권을 서버에서 검증한다.

3. 원장과 cache가 중복·실패를 어떻게 표현하는가

동시 재전송은 한 번만 저장하고, 실패를 성공으로 덮지 않으며, 외부 장애에는 마지막 정상 집계를 구분해 보여 준다.

4. 외부 공급자의 실제 계약을 끝까지 사용했는가

연결용 probe만 보지 않는다. 사용자가 보게 될 전체 보고서와 같은 인증·파싱 경로를 검증한다.

5. 마지막 화면을 실제 환경에서 보았는가

컴포넌트 단위 테스트뿐 아니라 모바일 viewport, 선택 채팅의 실시간 메시지와 트래픽 빈·오류·stale 상태를 확인한다.

6. 부가 기능 실패가 본 작업을 재실행하지 않는가

푸시, 분석과 진행 이벤트 실패가 웹훅 메시지나 프로젝트 작업을 중복 만들지 않게 한다.

아직 남아 있는 확인을 완료라고 쓰지 않는다

이 기간 안에 외부 신호 전달, 모바일 로딩 보완과 트래픽 대시보드 코드는 검증과 병합 단계까지 진행됐다. 트래픽 연결 확인과 전체 보고서의 불일치 원인도 찾아 계약을 보정했다.

그러나 저장소 기록상 일부 최종 운영 실사용·실기기 확인은 후속 관찰 항목으로 남아 있었다. 그래서 이 글도 “모든 흐름을 완전히 검증했다”고 쓰지 않는다. 자동 테스트와 빌드, 운영 API 상태가 통과한 것과 실제 사용자가 선택한 방·기기·프로젝트에서 끝까지 관찰한 것은 구분한다.

그 구분 자체가 이번 주의 가장 중요한 결과였다.

초록색 상태를 더 많이 만든 것이 아니라, 각 초록불이 무엇을 증명하고 무엇을 아직 증명하지 못하는지 말할 수 있게 됐을 때 운영 검증이 시작됐다.

이어 읽기

이전 글 · 다음 글

이전 글자유로운 대화가 코드가 되는 순간, 어디에 문을 둘 것인가