기능명세서

Vidding 기능명세서

v2.6 · As-Built(현행) · 2026-09-07

가격을 부르지 않는 경매. 가장 공감받은 이야기를 쓴 사람이 낙찰받는다.

이 문서는 배포된 서비스가 실제로 하는 일을 기준으로 쓴 현행 명세다. 설계 시점 명세(v1.0)는 기능 스펙 13건이 원본이고, 구현하면서 달라진 것은 §6에 모았다.

항목내용
배포https://vidding-re.vercel.app (Vercel · 서울 icn1)
저장소https://github.com/seokachu/vidding-re
Next.js 16 (App Router · 서버 컴포넌트 · 서버 액션) · Tailwind CSS 4
백엔드Supabase — Postgres · RLS · Realtime · Auth · Storage · Vault · pg_cron · pg_net
푸시웹 푸시(VAPID) + Expo 푸시(FCM), 발송 분기 단일 파이프라인
Expo(React Native WebView) 하이브리드 — Android APK
디자인Pencil .pen 단일 파일 · 잉크 블루 #1D48B0 · Pretendard · 390px 기준 · 시안 보기

1. 권한 모델 — 관계 판정

사용자 유형(role)이 없다. 경매와 나의 관계가 화면과 권한을 정한다.

관계조건할 수 있는 것
주최자내가 등록한 경매수정 · 삭제(사연 0건일 때) · 공감(+50) · 채팅
참여자내가 사연을 쓴 경매사연 수정 · 삭제 · 추가 입찰 · 공감(+10) · 채팅(낙찰 시)
방문자로그인, 위 둘 다 아님열람 · 사연 작성(쓰는 순간 참여자로 전이) · 찜
비회원미로그인열람만. 그 외 액션은 로그인으로 유도
  • 관계는 계정 속성이 아니라 경매마다 따로 정해진다. 사용자가 선언하지 않는다
  • 같은 상세 화면이 관계에 따라 셋으로 갈린다 — 방문자·참여자·주최자 (S03 · S03b · S04)
  • 화면 진입은 프록시가 막고, 데이터 권한의 최종 판정은 RLS가 한다

상세: 00-관계-판정


2. 기능 목록

핵심 기능 (설계 F1~F12 — 전부 배포됨)

ID기능요약주요 화면
F1경매 등록사진 · 제목 · 설명 · 기간(1 · 3 · 7일). 사연이 1건이라도 있으면 삭제 불가S06 · S06b
F2경매 탐색홈 최신 경매, 목록 검색 · 정렬(최신 · 마감 임박 · 사연 많은 순)S01 · S02
F3사연 작성·입찰사연 + 포인트 입찰, 추가 입찰 가능. 마감 전 삭제는 전액 반환S05
F4공감참여자 +10 · 주최자 +50 가중치. 사연당 1회S03b · S04
F5낙찰크론이 1분 주기로 마감. 총점 = 입찰 + 공감, 동점은 먼저 쓴 사연. 포인트 정산은 원장 검증 후 커밋S09
F61:1 채팅주최자–낙찰자 실시간 대화(Realtime), 읽음 표시, 배송 정보 전송S10 · S16
F7찜하기경매 저장 · 해제, 마이페이지 찜 탭S02 · S07
F8마이페이지프로필 · 포인트 · 배송지 + 내 경매 · 내 사연 · 찜 탭S07 · S15
F9알림마감 임박 3단계 · 새 사연 · 낙찰 결과 · 새 메시지. 목록을 열면 읽음S08
F10인증구글 · 카카오 소셜 로그인만. 로그인 후 원래 위치로 복귀S13
F11온보딩로그인 전 3장 안내 — 사연 입찰 · 참여 방식 · 낙찰 규칙S12
F12배송지 관리1건 등록 · 수정. 낙찰 후 채팅으로 전송하는 용도S11

확장 기능 (설계 이후 추가 — §6)

ID기능요약상태
X1웹 푸시notifications INSERT 트리거 → pg_net 웹훅 → VAPID 발송. 구독 배너 · 해제배포됨
X2하이브리드 앱 푸시WebView 앱 셸이 Expo 토큰을 페이지에 주입, 같은 파이프라인에서 FCM으로 분기APK 검증 완료
X3실시간 알림 배지notifications INSERT 를 Realtime 구독 → 새로고침 없이 하단 탭 점이 켜진다배포됨
X4채팅 목록/chat — 상대 · 마지막 메시지 · 미읽음 수. 전 탭 헤더에 진입 아이콘배포됨
X5PWA 설치매니페스트 + 설치 유도 배너 + 스플래시 — 웹을 홈 화면에 앱처럼 설치배포됨
X6에러 처리 · 오프라인전역 404 · 화면 오류(500) 화면 + 서비스 워커 오프라인 폴백 + 앱 셸의 오프라인 화면(WebView 내장 에러 페이지 차단)배포됨 · 앱 v1.3.1
X7경매 자동 활성화GitHub Actions 크론이 하루 1건 새 경매를 올리고, 진행중 경매에 사연 · 입찰 · 공감을 며칠에 걸쳐 나눠 채운다배포됨
X8뒤로가기 정책화면을 두 층으로 본다 — 탭 루트(1층)는 히스토리에 쌓지 않고, 2층은 히스토리 back. 앱 셸은 1층에서 두 번 눌러 종료배포됨 · 앱 v1.4.0

3. 기능별 명세

각 기능의 원본 스펙은 기능명 · 목적 · 동작 방식 · 예외 처리 · 완료 조건 5항목으로 작성돼 있다. 여기서는 현행 기준의 핵심만 요약한다.

F1. 경매 등록

  • 사진(필수 1장) · 제목 · 설명 · 기간(1 · 3 · 7일)으로 등록한다. 마감 시각은 등록 시점 + 기간
  • 수정은 주최자만, 사연이 1건이라도 있으면 삭제할 수 없다 — 남의 입찰을 증발시키지 않는다
  • 이미지는 Storage 버킷에 사용자별 폴더로 올린다 (RLS로 남의 폴더 접근 차단)

F2. 경매 탐색

  • 홈(S01)은 최신 경매를, 목록(S02)은 검색과 정렬(최신 · 마감 임박 · 사연 많은 순)을 제공한다
  • 마감 임박(1시간 이내)은 배지로 구분한다. 상태 배지는 진행중 · 마감 임박 · 마감됨 · 낙찰됨 · 유찰

F3. 사연 작성·입찰

  • 사연(텍스트)과 함께 포인트를 걸어 입찰한다. 경매당 사연 1건, 포인트는 추가 입찰로 올릴 수 있다
  • 마감 전 삭제는 전액 반환 + 재작성 허용. 마감 후에는 낙찰 사연만 삭제 불가
  • 가입 시 5,000 P 지급. 추가 획득 경로는 경매를 열어 낙찰시키는 것

F4. 공감

  • 사연에 공감을 남기면 가중치가 총점에 더해진다 — 참여자 +10, 주최자 +50
  • 사연당 1회, 자기 사연에는 불가. 마감 뒤에는 공감 버튼이 잠긴다

F5. 낙찰

  • pg_cron이 1분 주기로 마감을 처리한다. 총점 = 건 포인트 + 받은 공감 가중치, 동점이면 먼저 작성한 사연
  • 정산: 낙찰자 차감 확정 → 주최자에게 이전, 미낙찰자는 전액 반환. 사연 0건이면 유찰
  • 정산은 포인트 원장으로 기록되고, "반환 + 이전 = 차감 총액"이 깨지면 롤백된다

F6. 1:1 채팅

  • 낙찰이 확정되면 주최자–낙찰자 채팅방이 열린다. Supabase Realtime으로 실시간 수신, 읽음 표시
  • 낙찰자는 저장된 배송지를 카드 형태 메시지로 전송할 수 있다 (보내기 전 확인 다이얼로그)
  • 연결이 끊기면 상태 줄로 알리고, 재연결 시 놓친 메시지를 다시 불러온다. 전송 실패는 재전송 제공

F7. 찜하기

  • 목록 · 상세에서 하트로 저장하고, 마이페이지 찜 탭에서 모아 본다. 비회원은 로그인 유도

F8. 마이페이지

  • 프로필(닉네임 · 아바타는 마지막 로그인 제공자를 따른다) · 보유 포인트 · 배송지
  • 탭 3개: 내 경매 / 내 사연(참여중 · 낙찰 · 미낙찰 배지) / 찜
  • 포인트 내역(S15)은 원장 그대로 — 가입 축하 · 입찰 · 반환 · 낙찰 수령

F9. 알림 (+ X1 · X2 · X3)

  • 종류: 마감 임박(기간에 비례한 3단계 — 3일 · 1일 · 1시간 전) · 새 사연(주최자) · 낙찰 결과(전원) · 새 메시지
  • 하단 탭에 미읽음 점, 목록을 열면 읽음 처리. 알림을 누르면 관련 경매 · 채팅방으로 이동
  • 현행 확장: 웹 · 앱 푸시로도 발송되고(X1 · X2), 화면을 보고 있으면 새로고침 없이 점이 켜진다(X3)
  • 새 메시지 알림은 내용 미리보기 40자를 싣고 방마다 최신 1건만 남는다. 배송 정보는 개인정보라 미리보기하지 않는다

F10. 인증

  • 구글 · 카카오 소셜 로그인만 제공한다 (이메일 · 비밀번호 없음)
  • 로그인이 필요한 곳에서 진입하면 왜 왔는지 알리고, 로그인 후 원래 위치로 돌려보낸다
  • 접근 판정은 실제로 있는 화면에만 한다. 프록시가 아는 경로 목록(routes.tsKNOWN_ROUTES)에 없으면 판정을 건너뛰고 404(S17)로 흘려보낸다 — 없는 주소는 로그인해도 여전히 없어서, 막아 봐야 사용자를 두 번 헛걸음시킬 뿐이다
  • 목록 안에서는 막는 쪽으로 판정한다 — 어느 공개 규칙에도 안 걸리면 열지 않는다. 새 화면을 목록에 빠뜨리면 조용히 뚫리는 게 아니라 비회원에게 404 로 보인다(눈에 띄게 실패한다)
  • 목록은 손으로 적지만 어긋난 채로 오래 가지 않는다pnpm check:routessrc/app 의 실제 라우트와 양방향으로 대조하고(빠진 것 · 지웠는데 남은 것) CI 가 매 PR 에서 돌린다. 자동 생성하지 않는 이유는, 이 목록이 공개/보호 규칙 옆에서 사람이 읽고 판단하는 값이기 때문이다

F11. 온보딩

  • 로그인 전 3장: 사연으로 입찰한다 → 참여 방식 → 낙찰 규칙. 건너뛸 수 있다

F12. 배송지 관리

  • 1건만 등록 · 수정한다. 우편번호 검색 포함
  • 용도는 낙찰 후 배송 정보 전송(F6) 하나다. 서버가 저장된 값을 다시 읽어 보내므로 클라이언트 위조가 불가능하다

X1 · X2. 푸시 (웹 + 앱)

  • notifications INSERT 트리거가 pg_net으로 /api/push 웹훅을 부른다. 발송 주소 · 시크릿은 Vault에 보관
  • 웹은 VAPID 웹 푸시, 앱 셸(WebView)은 Push API가 없어 Expo 토큰을 페이지에 주입해 같은 테이블에 저장 — 발송 시 endpoint 모양으로 웹 푸시 / FCM을 가른다
  • 구독은 알림 화면 배너에서 켜고 끈다. 계정 전환 시 이전 계정의 구독 행은 회수된다

X3. 실시간 알림 배지

  • notifications INSERT를 Realtime으로 구독해 서버 트리를 갱신한다 — 미읽음 수의 진실은 서버에 있으므로 클라이언트에서 따로 세지 않는다
  • 알림 목록을 보는 중에 온 알림은 목록에 바로 나타나고 자동으로 읽음 처리된다
  • 연결이 끊겼다 붙으면(절전 등) 한 번 갱신해 놓친 알림을 따라잡는다

X4. 채팅 목록

  • /chat — 상대 · 경매 제목 · 마지막 메시지 · 미읽음 수. 참여자 판정은 RLS 조회가 한다
  • 탭 4곳 헤더 오른쪽에 진입 아이콘(+ 미읽음 점). 배송 정보 메시지는 목록에서도 미리보기하지 않는다

X5. PWA 설치

  • 웹 매니페스트로 "설치할 수 있는 앱" 요건을 갖추고, 탭 4화면에서 하단 탭 위에 설치 유도 배너를 띄운다
  • 설치 흐름은 플랫폼별로 갈린다 — 크로미움(안드로이드 · 데스크톱 크롬/엣지)은 네이티브 설치 프롬프트, iOS는 "공유 → 홈 화면에 추가" 가이드 바텀시트
  • 도울 수 없을 때는 나타나지 않는다 — 앱 셸 안, 이미 설치됨, 인앱 브라우저, 최근 닫은 사용자(7일)
  • 스플래시는 잉크 블루 배경 + 흰 로고 + 태그라인 — iOS는 기기별 이미지 17장(pnpm splash로 생성), 안드로이드는 매니페스트에서 자동 생성
  • APK 앱 셸의 스플래시는 2단계다 — 프로세스가 뜨는 순간은 네이티브가 배경색만 보이고(vidding-app v1.3.2), RN 오버레이가 S00(로고 + 태그라인)을 웹 로드가 끝날 때까지 잇는다. 네이티브에 아이콘을 두면 아이콘은 정중앙 · RN 은 덩어리 중앙이라 인계 순간 로고가 23dp 튀었다 — 아이콘을 지우면 "튀는" 것이 "나타나는" 것이 된다
  • APK 배포는 /download 페이지가 맡는다 — 인앱 브라우저(카카오톡 등)는 APK 설치가 막히므로 감지해서 외부 브라우저로 자동 전환한다. README 의 QR 도 이 페이지를 가리킨다
  • APK 는 arm64 전용이다 (앱 v1.4.1, 약 27 MB). CPU 4종 네이티브 라이브러리가 56 MB 였는데 폰에 필요한 것은 arm64 하나다 — expo-build-propertiesandroid.buildArchs. 68 MB 이던 다운로드가 몇 초로 줄었다. 32비트 전용 기기(2015년 이전)와 x86 에뮬레이터는 제외된다
  • 다운로드는 페이지를 떠나지 않는다. 카메라가 여는 크롬 커스텀 탭에서는 파일 주소로의 이동이 크롬의 다운로드 화면("다운로드 중…")으로 페이지를 통째로 덮었고, 그 화면은 용량을 다 채우고도 표시가 안 바뀌는 경우가 있어 — 파일은 이미 받아져 있는데 — 멈춘 것처럼 보였다 (에뮬레이터의 일반 탭 · 커스텀 탭 두 흐름 모두 파일은 온전히 저장되는 것을 확인했다). 숨긴 iframe 으로 받아 페이지를 남기고, "알림창에서 vidding.apk 를 열라"는 안내와 버전 · 용량(릴리스 API)을 보여준다. iframe 이 막히는 환경을 위해 직접 링크도 둔다

X6. 에러 처리 · 오프라인

  • 404 (S17) — 어느 라우트에도 걸리지 않은 주소와 notFound()app/not-found.tsx 가 함께 받는다. 경매 상세처럼 자기 문맥이 있는 곳은 자기 not-found 를 그대로 쓴다
  • 안내만 하고 끝내지 않고 갈 곳을 함께 준다 — 홈 · 경매 목록 버튼과 빠른 이동 칩(채팅 · 알림 · 마이). 찾지 못한 주소를 그대로 보여줘 오타인지 고장인지 구분시킨다
  • 앱(WebView)은 이 404 를 그대로 본다. 앱 셸이 웹을 띄우는 구조라 앱용 화면을 따로 만들지 않았다 — 위에 네이티브 상태바만 얹힌다
  • 오프라인 (S18) — 서비스 워커가 설치 시점에 /offline.html 한 장만 미리 받아 두고, 화면 이동이 네트워크 실패로 끊길 때 대신 돌려준다. 연결이 돌아오면(online) 자동으로 새로고침한다
  • 페이지는 캐시하지 않는다. 캐시가 늘수록 "고쳤는데 옛 화면이 뜬다"는 갱신 실패만 늘어난다. 화면 안의 조회 실패는 지금처럼 ErrorState 가 제 자리에서 다룬다
  • 서비스 워커 등록을 알림 배너에서 앱 시작 지점으로 옮겼다 — 푸시를 켠 사람만 오프라인 화면을 갖는 것은 이상하다. 등록은 권한을 묻지 않는다
  • 앱 셸 (vidding-app v1.3.1) — 지금까지는 로드가 실패하면 안드로이드 웹뷰의 내장 에러 페이지("웹페이지를 사용할 수 없음")가 그대로 떠서, 앱 안에서 브라우저가 튀어나온 것처럼 보였다. renderError 로 그 자리를 S18 화면이 대신 채운다
  • renderError 는 웹뷰를 대체하지 않는다. react-native-webview 는 웹뷰와 renderError() 결과를 형제로 나란히 그리므로, 그냥 두면 내장 에러 페이지가 위에 남고 우리 화면이 그 아래로 밀려 화면 밖으로 나간다 (v1.3.0 의 버그). 오프라인 화면을 불투명 배경으로 컨테이너 전체를 덮게 해 가린다 — 에뮬레이터에서 네이티브 경로 · 서비스 워커 경로 둘 다 확인했다
  • CTA 는 [다시 시도 = reload()] + [홈으로 = 웹뷰 재마운트]. reload() 는 실패한 주소를 다시 부를 뿐이라, 그 주소 자체가 문제일 때 빠져나갈 길이 하나 더 필요하다
  • 연결 복구는 NetInfo 가 아니라 도달 확인으로 잡는다. 네이티브 모듈이 하나 늘어나는데 알려주는 것은 "링크가 붙었는가"뿐이라, Wi-Fi 에 붙었지만 밖으로 못 나가는 상태를 못 거른다. 오프라인 화면이 떠 있는 동안에만 /offline.html 을 4초 주기로 찔러보고(앱을 다시 켤 때도 한 번), 응답이 오면 자동으로 다시 불러온다. 새 네이티브 의존성은 없다
  • 아이콘은 벡터 라이브러리를 새로 들이는 대신 lucide 글리프를 .pen 색으로 구워 assets/ 에 넣었다
  • 비회원도 404 를 본다. 예전에는 프록시가 모르는 경로를 전부 로그인으로 보내 이 화면이 비회원에게 닿지 않았다. 이제 프록시가 아는 경로 목록에 없으면 판정을 건너뛴다 (F10 참고) — 보호 경로의 로그인 유도는 그대로다
  • 화면 오류 (S19) — 404 는 "없는 주소", 이건 있는 화면인데 못 그린 경우다. 조회가 예외를 던지거나 렌더가 실패하면 app/error.tsx 가, 루트 레이아웃까지 깨지면 app/global-error.tsx 가 대신 선다. 없을 때는 Next 기본 오류 화면이 그대로 나왔다
  • 화면 *일부*의 조회 실패는 여기까지 오지 않는다 — 그건 그 자리에서 ErrorState · InlineRetry 가 받는다 (F8 4 · F9 4). 빈 상태 · 조회 실패 · 화면 오류가 서로 다른 화면이다
  • CTA 는 [다시 시도] + [홈으로 가기]. Next 16 은 reset 이 아니라 unstable_retryreset 은 오류 상태만 지우고 같은 데이터로 다시 그려서, 서버 조회가 원인이면 대개 그대로 또 터진다
  • 서버에서 난 오류는 내용이 클라이언트로 넘어오지 않는다. 서버 로그와 맞춰볼 수 있는 digest 해시를 칩으로 보여줘 제보에 실어 보낼 수 있게 한다
  • global-error 는 루트 레이아웃을 대신하므로 html · body · 전역 스타일 · 390 셸을 직접 그린다. 폰트는 따라오지 않아 시스템 서체로 떨어진다 — 오류 화면 한 장을 위해 폰트 로딩을 다시 세우지 않는다
  • 다만 루트 레이아웃이 서버에서 터지면 첫 응답은 Next 의 내부 오류 문서(<html id="__next_error__">)다. global-error 는 그 뒤 하이드레이션에서 그려진다 — Next 의 동작이고, 상태 코드는 500 으로 정확히 나간다 (로컬에서 확인)

X7. 경매 자동 활성화

  • 비어 있는 목록은 서비스가 아니다. 포트폴리오로 열어 둔 서비스라 실사용자 유입이 없어, 며칠만 지나면 홈이 마감된 경매만 남은 화면이 된다. GitHub Actions 크론(auction-daily.yml, 하루 4회 — 09:20 · 12:20 · 16:20 · 20:20 KST)이 그 자리를 채운다
  • 하는 일은 둘이다. ① 그날(KST) 새 경매가 없으면 1건 등록 — 풀(scripts/data/auction-daily-pool.mjs, 35건)에 안 올린 품목이 있으면 그것을, 없으면 마감된 지 7일 지난 경매를 사진째 재등록한다. 그래서 풀이 비어도 자동화는 멈추지 않는다
  • ② 진행중 경매에 예정 시각이 지난 사연 · 입찰 · 공감을 그때그때 등록한다 — 한 경매가 올라온 직후엔 조용하다가 며칠에 걸쳐 반응이 쌓이는 것처럼 보인다. 하루 4회로 나눈 이유가 이것이다
  • 테이블에 직접 쓰지 않는다. 입찰 · 공감을 직접 넣으면 원장과 잔액이 어긋나 close_auction() 이 롤백된다. 매직링크로 로그인해 anon 키로 place_bid() · toggle_episode_like() 를 부르는 실제 사용자 흐름을 그대로 밟는다 — RLS 를 통과하므로 정책이 바뀌면 이 스크립트도 사용자처럼 같이 막힌다 (seed.mjs 와 같은 원칙)
  • 시드 계정 포인트가 모자라면 그 입찰만 건너뛰고 다음 실행에 재시도한다. 진행중 경매가 마감되면 반환 · 이전이 일어나므로 시간이 지나면 저절로 풀린다
  • 마감 처리는 여기서 하지 않는다 — Supabase pg_cron 이 1분 주기로 이미 하고 있다 (F5)
  • 의존성 없는 스크립트라 Actions 에서 pnpm install 없이 바로 돈다. pnpm auction:daily --dry-run 으로 계획만 볼 수 있다

X8. 뒤로가기 정책

  • 뒤로가기가 끝이 없었다. 하단 탭이 Link push 라 홈 → 탐색 → 상세 → 탐색 → 마이 → … 를 오간 만큼 히스토리가 쌓였고, 앱의 물리 뒤로가기는 그 길을 전부 되밟아야 "첫 화면"에 닿아 종료할 수 있었다. 몇 분 쓰고 나면 열 번을 눌러도 앱이 안 꺼졌다
  • 화면을 두 층으로 본다. 하단 탭 넷(홈 · 탐색 · 알림 · 마이)이 1층, 그 밖의 전부(상세 · 작성 · 수정 · 채팅 · 포인트 · 배송지)가 2층이다. 목록은 src/lib/routes.tsTAB_ROOTS 한 곳이고 하단 탭의 활성 판정도 이걸 쓴다
  • 탭 전환은 히스토리에 쌓지 않는다 (Link replace). 탭 넷은 나란한 1층이지 위아래가 아니다. 홈에서만 그려지는 헤더 로고도 같다. 그래서 어디서 시작했든 2층에서 back 을 되밟으면 한두 번 안에 1층에 닿는다
  • 2층의 뒤로가기는 언제나 히스토리 back 이다 — 상단 바 버튼도, 물리 뒤로가기도. 홈 경유 같은 우회 규칙을 두지 않는다. 작성 · 수정 저장 뒤에도 back 이라 상세가 두 번 남지 않고, 등록 성공은 서버가 RedirectType.replace 로 상세를 보내 폼이 히스토리에서 사라진다 (기존 동작)
  • 돌아갈 히스토리가 없으면 그 화면이 속한 탭으로 replace 한다 (hasHistory · tabRootOf). 공유 링크 · 푸시 알림으로 상세에 바로 들어온 사람에게 router.back() 은 아무 일도 하지 않아 버튼이 죽은 것처럼 보였다. /auctions/… 는 탐색, /mypage/… 는 마이, 나머지는 홈
  • 앱 셸 (vidding-app v1.4.0) — 물리 뒤로가기는 웹뷰의 canGoBack 이 아니라 현재 경로로 판단한다. 1층이면 히스토리가 남아 있어도 더 물러나지 않고 2초 안에 두 번 눌러 종료한다 (토스트 "한 번 더 누르면 종료됩니다"). 2층이면 goBack(), 돌아갈 곳이 없으면 종료 대신 홈을 연다. 로그인 전 첫 화면(/ · 온보딩)도 1층으로 친다 — 거기서 물러나는 것은 둘러보기로 들어가겠다는 뜻이 아니라 나가겠다는 뜻이다
  • 셸의 EXIT_ROOTS 는 웹의 TAB_ROOTS 와 같은 목록이다. 탭을 늘리면 양쪽을 함께 고친다
  • 홈 경유(비홈 탭에서 back → 홈 → back → 종료) 안도 검토했다. 종료까지 한 번 더 걸리는 대신 얻는 것이 없고, "왜 갑자기 홈이지"가 생긴다. 1층은 전부 같은 층이라는 단순한 규칙이 설명하기도 맞추기도 쉽다

4. 화면 목록

.pen 시안의 화면 코드와 배포 라우트. 상세 화면은 관계에 따라 같은 라우트가 다르게 그려진다. 전 화면 이미지는 디자인 시안에서 디자인 시스템과 함께 볼 수 있다.

코드화면라우트
S00스플래시(설치된 앱 실행 시)
S01/main
S01b홈 · 설치 배너/main (미설치 브라우저)
S02경매 탐색/auctions
S03 · S03b · S04경매 상세 — 방문자 · 참여자 · 주최자/auctions/[id]
S03c경매 상세 · 유찰/auctions/[id] (마감 후)
S09낙찰 결과/auctions/[id] (마감 후)
S05사연 작성 · 입찰/auctions/[id]/episodes/write
S06 · S06b경매 등록 · 수정/auctions/write · /auctions/[id]/edit
S07 · S07b · S07c마이페이지 (기본 · 로그아웃 확인 · 내 사연)/mypage
S14 · S14b마이페이지 빈 상태 · 내 사연 빈 상태/mypage
S15포인트 내역/mypage/points
S11배송지 관리/mypage/address
S08 · S08b알림 · 빈 상태/notifications
S10 · S10b채팅 · 전송 실패/chat/[id]
S16 · S16b채팅 목록 · 빈 상태/chat
S12 · S12-1~3온보딩 3장/onboarding
S13로그인/
S17 · S17b404 · 웹 셸 배치걸리지 않는 모든 주소
S18 · S18b오프라인 — 앱 · 웹/offline.html (서비스 워커 폴백)
S19화면 오류 (500)렌더·조회가 실패한 모든 화면

5. 데이터 · 인프라 요약

구성내용
테이블users · auctions · episodes · likes · favorites · points(원장) · notifications · chat_rooms · messages · addresses · push_subscriptions
RLS전 테이블 활성. 판정 헬퍼(is_chat_participant 등)로 중복 제거. 알 수 없는 것은 막는다
RPC입찰 · 공감 · 마감(close_auction) · 읽음 처리 등 — 원장 정합이 깨지면 롤백
크론pg_cron 1분 주기 — 마감 처리 · 마감 임박 알림
Realtimemessages · notifications 발행. RLS가 그대로 적용돼 본인 것만 수신
발송notifications INSERT 트리거 → pg_net → /api/push (시크릿 헤더 인증, Vault 보관)

상세: 데이터 모델 명세


6. 설계에서 달라진 것

v1.0 설계 문서는 수정하지 않고 그대로 보존한다. 아래가 구현하면서 달라진 전부다.

영역설계 (v1.0)현행 (v2.0)이유
푸시만들지 않는다 (F9)웹 푸시 + 앱 푸시 도입 (X1 · X2)서비스 밖에서도 마감 · 낙찰을 놓치지 않게. Vault · pg_net 트리거로 우려했던 관리 비용을 줄였다
알림 갱신화면 진입 시 조회Realtime 실시간 배지 (X3)열어 둔 화면에서도 점이 켜져야 알림이 알림답다
채팅 진입낙찰 알림에서만채팅 목록 /chat + 전 탭 헤더 아이콘 (X4)알림이 밀리면 대화로 돌아갈 길이 없었다
채팅 알림메시지마다 1건방마다 최신 1건 + 내용 미리보기 40자같은 방 알림이 목록을 도배했다. 배송 정보는 미리보기 제외
내 사연 배지탈락미낙찰사연을 꺼낸 사람에게 탈락은 시험 낙방처럼 읽힌다. 포인트 내역의 어휘와 통일
배송지경매 등록의 요건요건 아님 — 낙찰 후 전송 용도만등록 장벽을 없앤다. 배송지가 실제로 쓰이는 곳은 전송뿐이다
프로필가입 시점 고정마지막 로그인 제공자를 따라간다구글 ↔ 카카오를 오가도 아바타 · 닉네임이 어색하지 않게
범위 밖Expo WebView 하이브리드 + FCM 푸시웹 푸시가 닿지 않는 안드로이드 웹뷰를 앱 셸이 메운다
설치APK 배포뿐PWA 설치 배너 + 스플래시 (X5)APK 를 받지 않아도 웹에서 바로 홈 화면에 앱처럼 설치되게
에러 화면경매 상세의 없는 경매만 (F3 4.1)전역 404 · 오프라인 · 화면 오류 (X6)없는 주소는 Next 기본 404, 앱은 웹뷰 내장 에러 페이지가 떴다 — 서비스 밖으로 튕겨 나가는 두 자리였다
뒤로가기규정 없음 — 브라우저 기본두 층 규칙 (X8) — 탭은 쌓지 않고, 앱은 탭에서 두 번 눌러 종료탭을 오간 만큼 뒤로가기가 되밟아 끝이 없었고, 앱은 종료에 영영 못 닿았다

7. 버전 기록

버전일자상태요약
v1.02026-07-31설계PRD · 데이터 모델 · 기능 스펙 13건 확정. 미결 정책 0건으로 구현 시작
v1.12026-08-01구현전 화면 구현 · Vercel 배포. 백엔드(RLS · RPC · 크론) 검증 — 등록→사연→입찰→공감→마감→낙찰→채팅→알림 전 구간 · 포인트 총량 보존 확인
v1.22026-08-02검증.pen 시안 21화면을 배포본과 1:1 대조 완료
v2.02026-08-03확장웹 푸시 (VAPID · Vault · INSERT 트리거)
v2.12026-08-04확장하이브리드 앱 푸시(Expo · FCM) · 실시간 알림 배지 · 채팅 목록 · 채팅 알림 정리(미리보기 · 방마다 1건) · 미낙찰 문구
v2.22026-08-05문서As-Built 기능명세서(이 문서) 작성 · /docs 공개 · 디자인 시안 페이지
v2.32026-08-14확장PWA 설치(X5 — 매니페스트 · 설치 배너 · 스플래시) · 스플래시 잉크 블루 리디자인 · 로그인 버튼 bfcache 잠김 수정
v2.42026-08-27확장에러 처리 · 오프라인(X6 — 전역 404 · 서비스 워커 오프라인 폴백 · 워커 등록 상시화 · 앱 셸 오프라인 화면 v1.3.1 · 500 화면) · 접근 판정을 아는 경로에만 적용해 없는 주소는 비회원도 404 를 본다. .pen 에 S17 · S18 · S19 시안 5장 추가. 문서 누락분(X7 경매 자동 활성화) 반영
v2.52026-09-06확장뒤로가기 정책(X8 — 탭 루트는 히스토리에 쌓지 않고, 앱 셸은 탭에서 두 번 눌러 종료 · 히스토리 없는 진입은 탭으로 대체) · 앱 셸 v1.4.0
v2.62026-09-07현행앱 다운로드 안정화(X5) — APK arm64 전용(68 → 약 27 MB) · 다운로드 페이지가 페이지를 떠나지 않고 받으며 버전 · 용량과 설치 안내를 보여준다 · 앱 셸 v1.4.1

관련 문서