Skip to content

main - #19

Merged
AndyH0ng merged 12 commits into
mainfrom
feat/suspensive-numberflow-ui-fixes
Sep 16, 2026
Merged

main#19
AndyH0ng merged 12 commits into
mainfrom
feat/suspensive-numberflow-ui-fixes

Conversation

@AndyH0ng

Copy link
Copy Markdown
Member

No description provided.

AndyH0ng and others added 11 commits September 14, 2026 14:39
- useQuery를 전부 useSuspenseQuery로 전환하고, 공통 SuspenseQueryBoundary(Suspense
  + ErrorBoundary)로 로딩/에러를 통일. 에러는 seed Snackbar로 안내하고
  다시 시도 액션을 제공
- Next.js 서버 액션을 초기 렌더 중 동기 호출하지 못하는 제약 때문에
  SuspenseQueryBoundary는 기본 clientOnly로 SSR을 건너뜀
- 리더보드/팀 쇼케이스/관리자 대시보드/투자 금액 입력에 @number-flow/react
  애니메이션 적용
- ScrollFog에 전역 반전 색상(라이트=검정, 다크=흰색) 적용, 팀 상세 헤더의
  중복 그림자 제거, 팀 목록 페이지에도 동일한 스크롤 레이아웃 적용
- 투자 바텀시트 레이아웃 시프트 방지(고정 높이 슬롯) 및 남은 투자금 실시간 반영
mask-image의 alpha/luminance 해석과 mask-composite 처리가 Chromium과
WebKit에서 달라서, 색상 스트립을 마스크로 드러내는 방식은 크롬에서 각진
사각형으로, 사파리에서는 흰색 그림자로 깨졌음. 대신 위에 그냥
linear-gradient 오버레이를 올려서 브라우저 무관하게 동일하게 렌더링되게
바꿈.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Vitest 대신 bun test를 사용해요 — 프로젝트가 이미 vite 없이 bun으로만
동작하고, bun test가 tsconfig paths도 네이티브로 해석해서 별도
번들러/플러그인 없이 바로 붙일 수 있어요. DOM 테스트는 happy-dom을
preload로 등록하고(등록 스크립트를 분리해 @testing-library/dom의
`screen`이 document 없이 먼저 평가되는 걸 방지), jest-dom 매처는
bun:test의 Matchers에 타입 병합해요.

순수 로직(booth/score/investment pure 함수, zod 스키마)과 이번 브랜치의
UI 변경분(BoothFloorPlan, TeamMembersField)을 우선 커버하고, Supabase를
직접 호출하는 서버 액션 중 실제 계산 로직이 있는 두 곳(점수 리더보드
가중합산, 투자 거래 가드/RPC 위임)만 mock.module로 의존성을 모킹해서
테스트했어요. CI 워크플로도 추가해 PR마다 typecheck/lint/test가 돌아요.

bun test는 기본적으로 여러 테스트 파일을 동시에 실행하면서
mock.module 레지스트리를 프로세스 전역으로 공유해서, 서로 다른
파일이 같은 모듈을 다른 모양으로 모킹하면 충돌할 수 있어요 —
`bun test --isolate`(파일마다 독립된 global)로 해결했어요.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
로컬은 next dev가 만들어둔 .next/types가 이미 있어서 tsc --noEmit만으로도
통과했지만, CI는 클린 체크아웃이라 이 전역 타입이 없어서 실패했어요.
Next.js 공식 문서가 안내하는 `next typegen && tsc --noEmit` 패턴을 그대로
따랐어요.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- ci.yml: actions/checkout·oven-sh/setup-bun을 전체 커밋 SHA로 고정(공급망
  위험 완화)하고 persist-credentials: false 추가
- pure.test.ts: "50% 반올림" 테스트가 기준 하나에 자기 maxScore를 초과하는
  점수를 넣고 있었음 — 각 기준에 자기 maxScore의 절반씩 배분하도록 수정
- jest-dom.d.ts: node_modules 상대경로 대신 공개 서브패스
  `@testing-library/jest-dom/matchers`를 사용하도록 단순화 (exports map에
  등록된 경로라 bundler 해석도 정상 동작함을 확인)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- teams/page.tsx: 페이지네이션은 page 검색 파라미터만 바꿀 뿐이라 내부
  스크롤 컨테이너의 scrollTop이 유지돼 다음 페이지가 이전 스크롤 위치에서
  보이는 문제가 있었음 — 스크롤 컨테이너에 key={page}를 줘서 페이지
  전환마다 리마운트되게 함
- invest-button.tsx: getTradeContextAction은 InvestButton이 investor로
  확인된 사용자에게만 렌더링될 때 호출되므로, 이 시점의 null은 세션이
  끊긴 에러 상태이지 정상적인 빈 상태가 아님. 기존엔 null을 0원 예산으로
  조용히 처리해 매수 버튼이 계속 활성화돼 있다가 제출 시점에야 에러가
  났음 — 쿼리 직후 throw해서 기존 에러 폴백이 뜨게 하고, 이제 항상
  non-null임이 보장되는 context에 남아있던 옵셔널 체이닝을 정리함

CodeRabbit 리뷰 반영.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
useSuspenseQuery는 미해결 쿼리를 만나면 promise를 throw해서 같은 렌더의
다음 훅이 실행되지 않아요. 한 컴포넌트에서 서로 무관한 useSuspenseQuery를
여러 번 연달아 부르면 각 조회가 순서대로(직렬로) 끝나야 다음 조회가
시작되는 워터폴이 생겨요 — admin-score-table(5개), leaderboard(2개),
admin-booth-grid(4개), booth-floor-plan-dialog(4개), 그리고
admin-participant-table은 팀 조회 후에야 AdminCrudTable 내부의 참가자
조회가 시작되는 구조였어요.

- use-suspense-query.ts: 기존 useSuspenseQuery의 queryFn 지연 로직을
  deferQueryFn으로 뽑아내고, 같은 지연을 각 쿼리에 적용하는
  useSuspenseQueries 어댑터를 추가
- admin-score-table/leaderboard/admin-booth-grid/booth-floor-plan-dialog:
  개별 useSuspenseQuery 호출들을 useSuspenseQueries 하나로 병렬화
- admin-crud-table.tsx: 내부에서 직접 조회하던 걸 상위에서 미리 조회한
  `items`를 주입받는 방식으로 바꿔서, admin-participant-table이 팀 조회와
  참가자 조회를 한 useSuspenseQueries로 같이 준비할 수 있게 함 (기존
  단일 조회만 쓰던 admin-team-table/admin-investor-table도 같은
  계약으로 맞춤)

CodeRabbit 리뷰 반영 (Heavy lift 항목).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
배열/Record 인덱스 접근(arr[i], record[key])이 실제로는 undefined일 수
있는데 지금까지는 타입이 항상 값이 있다고 가정했어요. 켜고 나서 드러난
7곳을 수정:

- dashboard-overview-panel.tsx: scoreLeaderboard[0]를 두 번 인덱싱하는
  곳 — 첫 번째 체크가 두 번째 접근까지 타입을 좁혀주지 않아서 지역
  변수로 한 번만 인덱싱하도록 정리
- application/model/schema.ts: 정규식으로 이미 형식이 보장된
  value.split("-").map(Number) 구조분해에 ?? NaN 기본값(실제로 트리거될
  일은 없음, 타입만 맞춤)
- booth-floor-plan.tsx: zones[zoneIndex]를 매 셀마다 다시 인덱싱하던 걸
  구역당 한 번만 꺼내 쓰도록 정리
- team-showcase.tsx: Map.values()를 배열로 편 뒤 2개 원소를 구조분해하는
  핀치줌 로직 2곳에 이미 있던 size===2 불변조건을 타입에도 반영하는
  가드 추가
- 테스트 파일 2곳(pure.test.ts, score.test.ts): 인덱스/구조분해 결과에
  옵셔널 체이닝 추가

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
TS 유니언 타입은 컴파일 타임에만 존재해서, 이 타입들을 곧이곧대로 믿는
두 경계에 실제로는 구멍이 있었어요.

서버 액션 입력 (쓰기 경로):
Server Action은 함수가 아니라 콜러블 엔드포인트라 클라이언트의 타입을
거치지 않고도 호출될 수 있어요. submitApplicationAction은 이미
zod로 재검증하고 있었지만 같은 원칙이 다른 액션엔 없었어요:
- updateApplicationStatusAction(status: ApplicationStatus)
- setBoothMarkerAction(kind: BoothMarkerKind | null)
- placeTradeAction(type: TransactionType)
셋 다 requireAdmin()으로 "누가"는 막지만 "뭘 보냈는지"는 안 막았어요.

DB row 매핑 (읽기 경로):
database.types.ts를 보면 role/status/kind/type 컬럼이 전부 plain
`text`예요(Postgres enum/CHECK 제약이 없어서) — 그래서 매핑 함수들이
검증 없이 `as ApplicationRole` 식으로 캐스팅만 하고 있었어요. DB에
잘못된 값이 들어가면(마이그레이션 실수 등) 조용히 넘어가거나 원인 모를
오동작으로 이어질 수 있었어요.

- shared/lib/parse-known-value.ts: zod 스키마로 파싱하고 실패하면
  라벨이 붙은 에러를 던지는 공용 헬퍼 추가
- 각 엔티티의 model/pure.ts(또는 schema.ts)에 해당 유니언 타입 바로
  옆에 zod 스키마를 두어 단일 소스로 유지 (BoothMarkerKind ·
  TransactionType · StaffRole · ApplicationRole/Type/Status)
- application.ts/booth.ts/investment.ts/staff.ts/session.ts의 매핑
  함수들이 이 스키마로 파싱하도록 교체
- feature 레이어의 액션 3곳이 같은 스키마를 가져다 입력을 재검증하도록
  수정 (schema.ts 파일들은 엔티티의 스키마를 재노출만 함)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: 하단 고정 버튼 있는 화면들에 SnackbarAvoidOverlap 누락 보완

SidePanelFooter/BottomSheetFooter나 하단 고정 액션 바가 있는데
SnackbarAvoidOverlap으로 감싸지 않아서, 스낵바가 그 버튼 위에 겹쳐
뜨던 곳들:

- admin-application-table.tsx: 지원서 상세 SidePanel의 "삭제" 버튼
- admin-booth-grid.tsx: 부스 상세 SidePanel의 "이 부스 삭제" 버튼
- team-showcase.tsx: 페이지 하단 고정 액션 바(투자/공유/로그인 유도) —
  이 바 안(공유 버튼)과 스크롤 영역의 공유 섹션 양쪽에서 뜨는 스낵바가
  전부 이 바를 피하게 됨

이미 admin-crud-table.tsx / invest-button.tsx / judge-team-list.tsx는
같은 패턴을 쓰고 있었음 — 나머지 두 곳만 놓쳐서 맞췄어요.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix: 다크모드 ScrollFog 그림자를 흰색 대신 검은색 반투명으로 통일

ScrollFog가 스크롤 가장자리에 깔던 색(--seed-color-bg-scroll-fog)이
다크모드에서만 흰색으로 반전돼 있었어요(라이트 모드는 검정). 다크모드도
같은 검정으로 통일하고, 겸사겸사 불투명 검정 대신 이 디자인 시스템의
기본 스크림 색(--seed-color-bg-overlay)과 같은 반투명 검정을 써서
그라데이션 시작 지점이 단단한 각으로 잘리지 않고 자연스럽게 페이드되게
했어요.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/suspensive-numberflow-ui-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@AndyH0ng
AndyH0ng merged commit aa30694 into main Sep 16, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant