개발 회고

[PonsWarp] - 첫 커밋에서 세운 약속

AC 2026. 6. 24. 01:49

PonsWarp의 첫 커밋은 조용하지 않았습니다.

2025-11-20 ad322f3 feat : first commit이라는 짧은 메시지 안에 이미 제품의 거의 모든 욕심이 들어 있었습니다. 브라우저끼리 직접 파일을 보내고, 서버에는 데이터를 맡기지 않고, 10GB를 넘어 100GB와 1TB까지 상상하며, 그 모든 것을 사용자가 복잡하게 느끼지 않게 만들겠다는 약속이었습니다.

지금 돌아보면 그 약속은 절반은 제품 방향이었고, 절반은 부채였습니다. PonsWarp는 “파일을 올리고 링크를 기다리는 서비스”가 아니라 “상대가 들어오면 바로 흐르는 전송 경험”을 만들고 싶었습니다. WebRTC, 브라우저 저장소, worker, signaling, 무결성 확인, 나중의 Cloud Drop까지 모두 그 한 문장을 지키기 위해 따라왔습니다.

PonsWarp service overview
첫 커밋의 direct promise는 사라지지 않았습니다. 다만 signaling, 저장 전략, Cloud Drop이라는 현실적인 경로를 옆에 두게 됐습니다.

💡 첫 화면은 이미 제품을 정하고 있었습니다

💡 첫 커밋은 기능 목록이 아니라 제품의 계약서에 가까웠습니다. 사용자가 기대할 문장은 단순했지만, 그 문장을 실제로 지키려면 네트워크, 저장, UI 상태, 운영 경로가 함께 움직여야 했습니다.

초기 README.md의 첫 문장은 File Transfer at Warp Speed였습니다. 그 아래에는 High-performance, serverless P2P file sharing directly in your browser라는 설명이 붙어 있었습니다. serverless라고 말하는 순간 중간 저장 서버는 제품의 중심에서 밀려납니다. P2P라고 말하는 순간 WebRTC가 자연스럽게 들어옵니다. directly in your browser라고 말하는 순간 네이티브 앱이 아니라 브라우저 런타임의 제약을 받아들이게 됩니다.

첫 커밋의 App.tsx도 같은 방향이었습니다. 앱은 INTRO, SELECTION, SENDER, RECEIVER 흐름으로 나뉘고, 사용자는 SEND FILE 또는 RECEIVE FILE 중 하나를 고릅니다. 제품 플로우만 보면 단순합니다. 시작 버튼을 누르고, 보낼지 받을지 고르고, 방 키를 공유합니다.

하지만 이 단순한 선택 화면은 동시에 제품의 한계를 숨깁니다. 두 카드 사이에는 signaling 서버, WebRTC offer/answer, ICE candidate, 데이터 채널, 브라우저 저장소, worker 스레드가 들어갑니다. 첫 화면은 사용자의 언어로 말했지만, 첫 커밋의 파일 목록은 이미 시스템의 언어를 드러냈습니다.

🏗️ 첫 커밋의 구조는 네 층이었습니다

첫 커밋의 핵심은 “대용량 파일을 브라우저에서 직접 보낸다”가 아니라 “브라우저 앱 안에 제품 UI, 전송 엔진, 저장 전략, 연결 상태 모델을 동시에 둔다”였습니다.

PonsWarp를 지금 기준으로 보면 네 층으로 읽는 편이 가장 자연스럽습니다. Product and UI, Browser transfer engine, Storage and recovery, Core and backend입니다. 첫날부터 완성된 구조였다는 뜻은 아닙니다. 그래도 UI, WebRTC, worker, 저장 전략, signaling이 한 커밋 안에 같이 들어왔다는 점은 중요합니다.

UI는 App.tsx, SenderView, ReceiverView에 있었습니다. 브라우저 전송 엔진은 webRTCService와 sender/receiver worker에 있었습니다. 저장에 대한 고민은 초기 README의 OPFS 언급과 worker 기반 스트리밍 설명에 있었습니다. signaling은 services/signaling.ts로 들어왔습니다. 이후 Rust/WASM 코어와 Rust signaling 서버가 분리되지만, 씨앗은 이미 첫 커밋에 있었습니다.

⚖️ Direct transfer는 중심 약속이었습니다

초기 문서의 가장 강한 문장은 No intermediate servers store your data였습니다. 이 문장은 PonsWarp의 제품 정체성을 만들었습니다. Dropbox나 Drive처럼 일단 업로드하고 공유 링크를 만드는 방식이 아니라, 두 브라우저가 동시에 온라인일 때 바로 보내는 경험을 중심에 둔 것입니다.

Direct transfer는 “서버가 전혀 없다”는 선언이 아니라, 파일 데이터 경로에서 중간 저장을 피하겠다는 제품 약속에 가까웠습니다.

이 선택은 매력적입니다. 서버에 먼저 올리고 다시 내려받는 방식은 시간이 두 번 들고, 저장 비용도 들고, 개인 파일을 제3의 저장소에 맡긴다는 심리적 부담도 있습니다. 다만 direct transfer는 조건부 약속입니다. 두 사람이 동시에 온라인이어야 하고, 네트워크가 WebRTC 연결을 허용해야 하며, 브라우저 탭과 수신 저장이 끝까지 버텨야 합니다.

PonsWarp first commit product promise card
첫 커밋의 문장은 제품, 전송 엔진, 저장 전략, 신뢰 조건을 한 번에 묶었습니다. 이후의 복잡도는 이 약속에서 나왔습니다.

📦 Cloud Drop은 약속의 배신이 아니라 분기였습니다

현재 README는 PonsWarp를 두 경로로 설명합니다. SEND / RECEIVE는 두 사람이 같이 온라인일 때 쓰는 최대 크기의 direct P2P 전송입니다. CLOUD Drop은 한 번 업로드하고 다운로드 전용 링크를 공유하는 경로입니다. 무료 경로는 10GB와 24시간, 유료 경로는 더 큰 용량과 긴 보관 기간을 맡습니다.

처음의 순수한 문장만 보면 Cloud Drop은 모순처럼 보일 수 있습니다. 서버에 저장하지 않겠다고 해놓고 Cloudflare R2에 올리는 경로가 생겼기 때문입니다. 하지만 제품 관점에서는 이게 더 정직한 진화였습니다. direct transfer는 “지금 둘 다 여기 있다”는 조건을 풀고, Cloud Drop은 “상대가 지금 없거나, 모바일이 오래 버티기 어렵거나, 재시도 가능한 링크가 필요하다”는 조건을 풉니다.

경로 제품 가치 현실 비용
Direct WebRTC 서버 저장 최소화, 즉시성 NAT, 모바일, 탭 생명주기 영향
TURN relay 연결 성공률 보강 대역폭 비용과 운영 책임
Cloud Drop 비동기 링크 공유와 재시도 저장 비용, 정책, 과금 설계

⚙️ 디자인 선택은 화려함과 신뢰 사이에 있었습니다

첫 커밋에는 WarpBackground와 3D 배경이 들어갔습니다. README도 Sci-Fi UI를 주요 기능으로 적었습니다. PonsWarp라는 이름, warp speed라는 문장, 큰 파일이 빛처럼 이동한다는 감각은 시각적으로 강한 방향을 요구합니다.

하지만 파일 전송 제품에서 디자인은 속도감만 보여주면 안 됩니다. 사용자가 정말로 보고 싶은 것은 현재 상태입니다. 연결됐는지, 상대가 들어왔는지, 얼마나 보냈는지, 저장이 끝났는지, 실패하면 어디서 실패했는지입니다. 현재 코드가 direct send와 Cloud Drop을 한 화면에서 나누고, 보안 배지와 상태 오버레이, 진행률 표시를 붙이는 것은 “멋진 전송”에서 “믿고 기다릴 수 있는 전송”으로 이동한 흔적입니다.

🧩 첫날부터 서버 없는 서비스는 아니었습니다

serverless P2P라는 문장은 조심해서 읽어야 합니다. 데이터 파일을 중간 서버에 저장하지 않는다는 뜻으로는 맞습니다. 하지만 서비스 전체가 서버 없이 돌아간다는 뜻은 아닙니다. 첫 커밋에도 signaling service가 있었고, 같은 날 이후 커밋에서는 프로덕션 로깅과 STUN 보강이 바로 등장합니다.

이 지점에서 PonsWarp의 첫 약속은 조금 더 정확해져야 했습니다. “서버가 없다”가 아니라 “파일 데이터 경로에서 중간 저장을 피한다”가 더 정확합니다. 제품 메시지는 사용자를 데려오지만, 시스템 설계는 실패 조건을 감당해야 합니다.

📚 지금 돌아보는 판단

첫 커밋의 장점은 방향을 미루지 않았다는 데 있습니다. 파일 전송 앱을 만들면서 “일단 업로드 기반으로 만들고 나중에 P2P를 붙이자”가 아니라, 가장 어려운 direct transfer를 중심에 놓았습니다. 덕분에 PonsWarp의 정체성은 초반부터 분명했습니다.

아쉬운 점은 약속의 범위를 너무 크게 잡았다는 데 있습니다. unlimited size, 10GB, 100GB, 1TB+, No intermediate servers, Reliable Delivery는 모두 강한 문장입니다. 그런데 이 문장들은 각각 별도의 실패 조건을 가집니다. 큰 파일은 메모리를 터뜨립니다. 서버 없는 전송은 signaling과 TURN에서 현실을 만납니다. reliable delivery는 저장 완료와 무결성 확인 없이는 말하기 어렵습니다.

첫 커밋은 과장된 출발이었지만, 빈말은 아니었습니다. 이후의 고생이 그 증거입니다. 정말 빈말이었다면 메모리 폭발, TURN, 완료 핸드셰이크, Cloud Drop, Rust/WASM 코어까지 가지 않았을 것입니다. PonsWarp는 첫날 던진 문장을 계속 갚아 나간 프로젝트였습니다.

📚 읽은 코드
PonsWarp/README.md:first commit — File Transfer at Warp Speed, serverless P2P, WebRTC, OPFS, 10TB+ 지원을 제품 약속으로 선언
PonsWarp/App.tsx:first commit — INTRO → SELECTION → SENDER / RECEIVER 흐름으로 기본 사용자 플로우 고정
PonsWarp/package.json:first commit — React, simple-peer, socket.io-client, streamsaver, fflate, Three.js 계열 의존성이 한꺼번에 출발
PonsWarp/src/App.tsx:224-260 — 현재 direct send와 Cloud Drop을 같은 SEND 영역 안에서 분기
PonsWarp/src/components/CloudSenderView.tsx:59-120 — 무료 10GB/24시간, 100GB/500GB/1TB Drop Pass, Pro 플랜 정책
LIST