[PonsLink] 말만으로 부족한 순간, 기능은 계속 늘어났다
PonsLink가 위험해진 순간은 기능이 부족할 때가 아니라 기능이 너무 쉽게 붙을 때였다. 채팅, 파일, 화이트보드, CoWatch, PonsCast는 각각 좋아 보였지만, 방의 이유를 설명하지 못하면 전부 잡음이 된다. 이 판단은 ponslink-room-frontend/src/pages/Room.tsx와 ponslink-room-frontend/src/components/functions/chat/ChatPanel.tsx를 함께 볼 때 더 분명해진다. 화면에서 보이는 기능은 결과이고, 실제 제품성은 그 기능이 열리는 조건과 실패했을 때 돌아갈 길에 있다. 그래서 용어 사전 대신, 그 날 무엇을 보고 무엇을 바꿨는지부터 적는다.

기능이 늘어난 지점
PublicDeskGate와 request form은 기능 욕심을 줄이는 데 도움을 줬다. 사용자가 먼저 목적을 고르면 방은 그 목적에 맞춰 좁아질 수 있다. 상담 요청이면 질문과 시간, 결제 상태가 중요하고, 문서 검토면 파일과 기록이 중요하다. 같은 방 UI를 쓰더라도 앞에서 받은 목적이 다르면 강조해야 할 패널도 달라진다. 기능을 더하는 대신 기능의 등장 순서를 바꾸는 방식이었다.
파일도 마찬가지다. 파일 전송은 사용자가 바로 이해하는 기능이지만 용량, 만료, 다운로드 권한, 업로드 실패 안내가 함께 따라온다. R2로 직접 올리는 구조는 서버 비용을 줄여 주지만, 사용자가 실패했을 때 어떤 안내를 받을지도 같이 설계해야 한다. 지금 보면 기능을 더하는 일보다 빼는 기준을 만드는 일이 어려웠다. 기능이 늘어날수록 좋은 제품이 되는 게 아니라, 어떤 상황에서 어떤 기능을 숨길지 정해야 방의 밀도가 살아난다.
그 경계가 흔들린 기록이다. 다음에 같은 문제를 다시 푼다면 먼저 기능 목록을 쓰지 않고, 방의 목적과 닫혀야 하는 실패 경로를 표로 만들 것이다. 기능은 그 뒤에 붙여도 늦지 않다. 그래서 확장된 글은 기능 칭찬보다 기능을 거절하는 기준을 더 많이 적었다. 어떤 기능을 안 넣을지 말할 수 있을 때 제품 방향이 선명해진다. PonsLink는 많은 기능을 담을 수 있지만, 모든 방에 같은 기능을 밀어 넣지는 않아야 했다.

처음에는 말로 설명되면 충분할 줄 알았다
운영 관점에서는 ponslink-room-frontend/src/components/functions/fileStreaming/FileStreamingPanel.tsx가 글의 방어선이다. 사용자는 방을 하나의 경험으로 보지만 시스템은 요청, 세션, 미디어, 파일, 결제, 기록처럼 여러 책임으로 나뉜다. 이 책임을 나눠 적으면 글은 조금 덜 매끈해지지만, 실패했을 때 어디서 멈춰야 하는지 설명할 수 있다. 이번 배치에서 모든 글에 실패나 한계를 넣은 것도 이 때문이다. 이어 읽으면 좋은 글은 [과 [이다.
앞 글은 왜 이 문제가 생겼는지 보여 주고, 다음 글은 같은 기준이 다른 기능이나 운영 경계로 어떻게 넘어갔는지 보여 준다. 한계는 사용자가 기능 목록을 읽지 않는다는 데 있었다. 호스트는 지금 이 방이 상담인지, 자료 검토인지, 짧은 확인인지 먼저 알아야 했다. 게스트는 어떤 버튼을 눌러야 하는지보다 이 방에서 무엇을 해도 되는지 알고 싶어 했다.
기능 수가 늘수록 이 질문은 더 흐려졌다. 이 부분은 실제로 실패와 trade-off가 함께 있는 지점이었다. 기능을 열어 두면 데모는 풍성해지지만, 사용자에게 필요한 설명은 줄어들지 않는다. 반대로 권한을 좁게 잡으면 초반 경험은 답답해질 수 있지만 운영자는 사고 범위를 예측할 수 있다. 사용자와 이야기하다 보면 필요한 기능은 끝없이 나온다.
회의 중 파일을 보내고 싶고, 그림을 같이 그리고 싶고, 영상을 같이 보고 싶고, 놓친 내용을 채팅으로 남기고 싶다. 문제는 이 요구가 모두 사실이어도 한 방에 동시에 넣으면 제품의 중심이 흐려진다는 점이었다. 그래서 기능 소개가 아니라 그때 내가 어떤 기준을 세우지 못했고, 어떤 실패를 보고 나서 경계를 다시 잡았는지를 따라간다.
PonsLink는 회의 앱을 흉내 낸 프로젝트가 아니라 요청에서 방, 방에서 기록으로 이어지는 흐름을 제품으로 만들려는 시도였다. 마지막으로 ponslink-api-infra/src/providers/mediaPolicy.ts를 함께 읽으면 다음 작업의 기준이 보인다. PonsLink의 각 글은 독립된 기능 홍보가 아니라 앞뒤 글과 이어지는 판단 기록이어야 한다. 그래서 내부 링크를 두 개씩 남겼고, 이미지도 cover가 아니라 본문 근거로 다시 만들었다.
글 하나를 읽고 끝나는 것이 아니라 독자가 다음 방, 다음 권한, 다음 운영 문제로 넘어갈 수 있게 하는 것이 이번 확장의 목적이다.

기능 분산이 선명해진 순간
기능 sprawl은 코드 양보다 판단 피로에서 먼저 온다. 새 패널을 하나 열 때마다 사용자는 그 기능을 써야 하는지, 호스트는 그 기능을 허용해야 하는지, 운영자는 그 기능의 실패를 어디서 볼지 정해야 한다. 이 세 질문에 답하지 못하면 기능은 곧 부채가 된다. ChatPanel.tsx를 열어 보면 채팅은 가장 쉬운 기능처럼 보인다. 메시지를 입력하고, 상대가 받고, 기록이 쌓이면 된다.
그런데 상담 방에서는 채팅이 단순 보조 수단이 아니었다. 말로 설명하기 어려운 링크, 파일 이름, 결정 문장을 남기는 장소였다. 그렇다면 채팅은 항상 켜야 하는가, 아니면 요청 목적이 문서 검토일 때 더 강하게 드러내야 하는가. 기능이 있다는 사실보다 어느 상황에서 앞에 세울지가 더 중요했다. 이 글에서 남긴 기준은 단순하다. 기능이 늘어났다는 이유만으로 글을 쓰지 않는다.
그 기능이 없으면 어떤 사용자 흐름이 깨졌는지, 넣었을 때 어떤 운영 비용이 생겼는지, 그리고 숨겼을 때 무엇을 보호하는지를 같이 적어야 한다. 이 기준이 없으면 다음 번에도 다시 기능 목록을 늘리는 쪽으로 도망가게 된다. 처음에는 패널을 늘리는 쪽으로 반응했다. Room.tsx 안에 기능 패널을 붙이고 ChatPanel, FileStreamingPanel, whiteboard, media layout을 나란히 열 수 있게 만들면 다양한 상황을 커버한다고 생각했다.
이 선택은 빠르게 확인하기 좋았다. 사용자는 들어오고, 장치는 켜지고, 메시지는 오간다. 하지만 빠른 확인이 곧 운영 가능한 구조라는 뜻은 아니었다. 권한이 화면 안에 섞이면 나중에 기능을 줄이기도 어렵고, 문제가 생겼을 때 어디를 막아야 하는지도 흐려진다. 말만으로 부족한 순간, 기능은 계속 늘어났다에서 먼저 확인한 파일은 ponslink-room-frontend/src/pages/Room.tsx이다.
이 근거는 글의 출발점이 추상적인 제품 감상이 아니라 실제 화면과 상태를 가진 코드였음을 보여 준다. 화면 컴포넌트가 어떤 이름으로 나뉘어 있는지 따라가면 사용자가 보는 흐름과 운영자가 책임지는 흐름이 어디서 갈라지는지 보인다. 그 갈라짐을 문장으로 남기지 않으면 글은 다시 기능 소개로 돌아간다. 이 글에서 feature grant를 강조한 이유는 운영자가 기능을 끄기 위해서가 아니다.
기능을 제대로 켤 수 있는 조건을 만들기 위해서다. 상담방, 그룹방, 오디오방이 같은 기능 목록을 가질 필요는 없다. 방의 목적이 다르면 켜지는 패널도 달라야 한다.
기능 경계 나누기
FileStreamingPanel.tsx 쪽은 더 노골적인 trade-off가 있었다. 파일 공유는 사용자가 좋아하는 기능이지만, 큰 파일을 API 서버로 모두 통과시키면 작은 VPS는 바로 병목이 된다. 그래서 R2 direct upload나 proxy fallback 같은 경계가 필요했다. 파일 기능을 붙이는 일은 버튼 하나가 아니라 저장 비용, 업로드 실패, 만료 정책, CORS, 다운로드 권한을 함께 붙이는 일이었다. mediaPolicy.ts를 함께 보면 이 판단은 UI 취향이 아니라 운영 정책이다.
active publisher 수, room profile, media limit은 사용자가 직접 읽지 않아도 방의 크기와 비용을 결정한다. 아무 기능이나 켜는 것은 결국 아무 비용이나 허용하는 것과 같다. PonsLink는 기능을 파는 제품이 아니라 방의 조건을 파는 제품에 가까웠고, 그래서 feature grant가 기능 욕심을 막는 안전장치가 됐다. 다시 설계한 기준은 기능을 목적 뒤에 세우는 것이었다. PublicDeskGate와 desk request form에서 요청의 목적을 먼저 받고, 방에 들어와서는 목적에 맞는 패널만 강조한다.
기능은 늘릴 수 있지만, 사용자가 들어온 이유를 중심에 두지 않으면 제품은 도구 상자가 된다. 이때부터 PonsLink의 중심은 단순한 room component가 아니라 상태와 권한의 연결이 되었다. ponslink-room-frontend/src/components/functions/fileStreaming/FileStreamingPanel.tsx와 ponslink-api-infra/src/providers/mediaPolicy.ts를 보면 이 판단이 프론트엔드 취향이 아니라 API/BFF 경계와 맞물린 결정이라는 점이 보인다. 운영자는 feature flag와 grant를 통해 방마다 가능한 기능을 조절한다. API README의 media policy와 room lifecycle은 이 판단을 뒷받침한다.
프론트엔드는 가능 여부를 추측하지 않고 grant를 받아 패널을 열며, 큰 파일은 R2 upload URL을 받아 직접 전송한다. 사용자는 이 복잡도를 몰라도 된다. 대신 제품은 사용자가 보지 않는 곳에서 무엇을 허용하고 무엇을 미룰지 결정해야 한다. 그 결정을 숨기면 당장은 매끄러워 보여도 장애나 결제 지연, 네트워크 실패가 왔을 때 설명할 말이 없어진다.
흐름 제어 디테일
화이트보드는 좋은 예였다. 화면에 띄우면 제품이 풍성해 보이지만, 실제 상담에서 화이트보드가 필요한 순간은 생각보다 좁다. 문서 검토나 설명 중심 세션에는 도움이 되지만 빠른 일정 조율에는 과하다. 그래서 기능 자체의 완성도보다 목적과의 거리가 더 중요했다. 로컬 frontend의 whiteboard/file 기능 이미지를 증거 이미지로 재가공했다. PublicDeskGate와 request form은 기능 욕심을 줄이는 데 도움을 줬다.
사용자가 먼저 목적을 고르면 방은 그 목적에 맞춰 좁아질 수 있다. 상담 요청이면 질문과 시간, 결제 상태가 중요하고, 문서 검토면 파일과 기록이 중요하다. 같은 방 UI를 쓰더라도 앞에서 받은 목적이 다르면 강조해야 할 패널도 달라진다. 기능을 더하는 대신 기능의 등장 순서를 바꾸는 방식이었다. 파일도 마찬가지다. 파일 전송은 사용자가 바로 이해하는 기능이지만 용량, 만료, 다운로드 권한, 업로드 실패 안내가 함께 따라온다.
R2로 직접 올리는 구조는 서버 비용을 줄여 주지만, 사용자가 실패했을 때 어떤 안내를 받을지도 같이 설계해야 한다. 지금 보면 기능을 더하는 일보다 빼는 기준을 만드는 일이 어려웠다. 기능이 늘어날수록 좋은 제품이 되는 게 아니라, 어떤 상황에서 어떤 기능을 숨길지 정해야 방의 밀도가 살아난다. 그 경계가 흔들린 기록이다. 다음에 같은 문제를 다시 푼다면 먼저 기능 목록을 쓰지 않고, 방의 목적과 닫혀야 하는 실패 경로를 표로 만들 것이다.
기능은 그 뒤에 붙여도 늦지 않다. '[PonsLink 말만으로 부족한 순간, 기능은 계속 늘어났다'을 관통하는 질문은 이 섹션에서도 같았고, 답만 더 구체적이 됐다.
마치며
이 글에서 확인한 경계는 다음 변경의 기준으로 남겼다.