Audit 2 — the data contract per page (2026-11-01)
Status: Reference · Date: 2026-11-01 · The table behind owner-review-52-audits-2026-11-01.md § 2. Read-only, at HEAD 191e628bd.
Source: board spec rows (Reads, Taps, Stack, Rulings) of each page section in docs/twomore_design_system/wireframes/*.jsx, joined to page-levels.json and page-code-map.md; the data layer was checked by grep over ports, hooks, supabase/migrations (latest definition), generated.types.ts Tables and supabase/functions. Nothing was run against a database; only the repo at read time.
Summary
- Pages: 82. covered 54 · partial 23 · missing 5.
- covered: L0-02, L0-05, L1-03, L1-03c, L1-04, L2-03, L3-03, L3-05, L3-06, L3-07, L3-09, L3-13, L3-14, L3-15, L3-16, L3-18, L3-19, L3-20, L3-21, L3-22, L3-23, L3-24, L3-27, L3-28, L3-29, L3-30, L3-31, L3-33, L3-34, L3-37, L3-41, L3-42, L3-43, L3-44, L3-45, L4-01, L4-02, L4-03, L4-04, L4-05, L4-06, L4-07, L4-08, L4-09, L4-10, L4-11, L4-12, L4-13, L4-21, L4-15, L4-17, L4-18, L4-19, L4-20
- partial: L0-01, L0-04, L0-06, L0-07, L1-01, L1-02, L1-05, L2-01, L2-02, L2-04, L2-06, L3-01, L3-04, L3-10, L3-11, L3-17, L3-25, L3-32, L3-35, L3-36, L3-38, L3-39, L3-47
- missing: L0-03, L3-08, L4-14, L4-16, L3-46
- Method note: board hook names are not always the shipped names (useConsents/useUserConsents, useAddThreadMembers/useAddThreadParticipants, useSessionRules, deriveMatchResultState/deriveMatchResult, useClubByInviteCode); such mismatches are noted under "unsure" and are not counted as gaps when an equivalent exists. Board "data owed" lines that the repo already satisfies (player_matchups_by_format for 선수별 전적 and 기록) are marked as such.
Distinct gaps by kind
New table
- identity verification store (CI hash unique, real name, birth date, phone, foreigner flag, verified_at); profiles has only phone, birth_year, nationality_code. Pages: L0-04
- per-user hidden 소식 item ids (숨기기/되돌리기) and per-club 소식 mute (이 클럽 소식 끄기): nothing stores them (signal_preferences is per signals category). Pages: L1-01
- club_dues_plans (name, amount, cadence incl. one_time/every_n_months/per_session, due rule, grace, proration, applies-to role/조);
dueshas only month/year, no plan_id. Pages: L2-01, L3-11, L4-14 - attendance excuse request (one per attendance_record, reason <=200 chars, answered by host/총무, 거절됨 state): only excuse_attendance_record(p_record_id), an official one-way mark, exists. Pages: L3-01
- match_notes (match_id, author_id, mood good|ok|bad, body <=500; own-row RLS): no table, port or hook exists (practice_logs dropped in 00702). Pages: L3-08, L3-38
- club ledger (opening balance, dated income/expense entries with 분류 + 영수증; a confirmed 회비 = one income entry) with an aggregate read (balance, month spend) and a CSV export: no table, port or hook. Pages: L3-11, L3-46
- club join-vote store for the 멤버 투표 admission rule (votes, deadline, objection) with a cast-vote RPC and a read hook (club_join_request has no vote data). Pages: L3-32
New column
- rsvps.leaves_at, available_until_round, checked_in_at, checked_out_at (rsvps has arrives_at and available_from_round only; no migration adds them). Pages: L2-02, L3-01, L3-36
- profiles 주 종목 and 선호 시간 (no column on profiles or user_preferences; no editor page among the 82 sets them). Pages: L2-06
- dues.plan_id, dues.period_start, dues.period_end (rows generated per period). Pages: L3-11, L4-14
- reminder log for 마지막 알림 (who/when) on dues and on session_payments (no column or table). Pages: L3-11
- matches.stage and group (tournament matches live in tournaments.state JSONB today), matches.scheduled_start (09:30 시작 queue). Pages: L3-38, L3-39
- saved generation preset 소셜/실력별/고정/랜덤 on sessions (board: only the three score-driven generation_style_id values persist; GenerationPolicy is spine-only, no persisted axes). Pages: L3-47
- persisted GenerationPolicy axes the page offers beyond recording (host-selected pool 선택한 사람만, 만드는 시점, 확정 시점): generation-policy.entity.ts derives an effective policy and persists no policy column. Pages: L3-47
New RPC / RPC extension
- get_public_share_preview extension: session fee, approval mode, isPickup, venue district; match venue/format/day; profile NTRP + 신뢰 점수 (it returns totalSessionsConfirmed and the raw trust tier key the board forbids); club region as words. Pages: L0-06, L0-07
- anonymous, minimal, rate-limited invite reader for the /join-club card (lookup_club_by_invite_code returns an id only; the whole clubs row must not leave). Pages: L0-07
- trust record read (신뢰 점수 = 50 + 12-month points held 0..100, none before 5 recorded sessions; per-record points, rule applied, venue/day, streak, praise count): no trust_score column, RPC or domain function exists (profiles has trust_tier only). Pages: L1-05, L2-06, L3-01, L3-25
- visibility-filtered reads of ANOTHER person (match history, achievements, play facts, last-20 attendance + praise for shared-context viewers); useMatchHistory and useAchievements read the viewer's own. Pages: L2-06, L3-04
- attendance record query joined to venue + the cancel-time rule applied (board: "data owed: the venue joins the record query"). Pages: L3-01
- send reminder for one member / all 미납 members (dues) and for a session fee (참석자 관리), logged by who/when: no port method or RPC (generate_dues_reminders was dropped in 00405). Pages: L3-11, L3-35
- period (전체/이번 주/이번 달) narrowing on the leaderboard reads (get_club_rating_ranks, get_regional_ranking, get_public_profile_leaderboard, findTopByRating take none). Pages: L3-17
- submit_match_score expected-revision parameter and the typed refusal score_changed (the RPC takes no revision; matches.score_revision exists). Pages: L3-38
- move tournament matches into
matches(stage/group) and the group-table/bracket/advance reads; retire tournaments.state JSONB + useTournament/useAdvanceTournament/useCancelTournament. Pages: L3-38, L3-39, L3-47
New hook / port method
- signUp(email, password) on AuthServicePort (board: only if the OAuth route slips). Pages: L0-03
- useHomeNewsFeed: the 소식 feed of 일정/결과/게시글 (+사진) across the viewer's clubs with a hide filter (HomeFeedScreen uses useHomeData, period recaps, nearby/discovery cards; no feed query or RPC exists). Pages: L1-01
- bulk pending join requests for the 클럽 list (only per-club useMyJoinRequest / findMyJoinRequest(clubId) exist). Pages: L1-02
- useTrustRecord(userId[, clubId]) over the trust read (existing: useMemberStrikeRecords, useAttendanceSummary, useExcuseAttendance). Pages: L1-05, L3-01
- arrival/exit stamp mutation (도착했어요, 먼저 갈게요) writing rsvps.checked_in_at/checked_out_at (only check_in_attendance_gps RPC from 00318 and useArrivalAvailability exist). Pages: L2-02, L3-36
- a venue's public 매치 (in play + planned), clubs whose home_court_venue_id is the venue, and the owner's waiting-suggestion count (useOpenPickups has no venue filter; no hook lists them). Pages: L2-04
- useMatchNotes(userId, 50) and a save/upsert mutation for a 경기 노트 (no hook, no port). Pages: L3-08, L3-38
- useDeclineChallenge (InterClubChallengeRepositoryPort.decline exists; only useAcceptChallenge is a hook). Pages: L3-10
- 정산 나누기 cost-split write for a session with no fixed fee (sessions.cost_total exists; no hook confirmed). Pages: L3-35
- getUserIdentities / linkIdentity / unlinkIdentity on AuthServicePort + hooks (port has signInWithOAuth, signInWithEmail, signOut, changePassword only). Pages: L4-16
New edge function
- verify-portone-identity edge function (PortOne v2 identity check, age/CI/duplicate; only verify-portone-payment and portone-webhook exist). Pages: L0-04
- AI 요약 job/edge function for 테니스 여정 (supabase/functions has none), plus an entitlement key for the player plan gate (getEffectivePlan/useFeatureGate exist; PLAN player exists). Pages: L3-08
Config
- supabase/config.toml configures only [auth.external.apple]; Kakao, Naver (custom) and Google providers are not configured locally; enable_manual_linking = false (needed by 연결된 계정). Pages: L0-01, L4-16
Candidates the lead named
- match note: no
match_notestable, port or hook (verified: not in generated.types.ts Tables, no hook; migration 00702 drops practice_logs). Needed by L3-08 and L3-38. - 테니스 여정 AI 요약: no edge function (18 functions plus _shared, none AI), no storage; plan gate pieces exist (subscription.entity.ts PlanType free/player/basic/pro/club_plus, getEffectivePlan, useFeatureGate) but no AI entitlement key was found.
- 공개 프로필 플레이 facts (주 종목, 선호 시간): profiles holds neither (columns listed in generated.types.ts); clubs has primary_day/primary_time_slot, profiles does not.
- 대진 짜기 planned policy axes: generation-policy.entity.ts is spine-only (no persisted policy column); sessions.generation_style_id persists only the score-driven styles.
- 참가비 내역: covered by useMySessionPayments (session_payments has refund_state, submitted_at). The route is new, the data is not.
- 장부: no ledger table (see L3-46). 입금 계좌: club_bank_accounts + useClubBankAccount exist (L4-21 covered). 연결된 계정: no linkIdentity/getUserIdentities anywhere and enable_manual_linking = false. 신뢰 기록 meter: no trust score anywhere (profiles.trust_tier only). 행사: club_events + useCreateClubEvent/useClubEvents exist. Owed pages 정기 일정 / 조 / 앨범 관리: hooks exist (useClubRegularMeets, useClubGroups, useManageClubMedia), no page code.
Per page
Columns: Page | Reads | Writes | Provided by | Gaps | Status. where the board spec is silent or a name did not resolve, the Gaps cell ends with "(unsure: ...)".
| Page | Reads | Writes | Provided by | Gaps | Status |
|---|---|---|---|---|---|
| L0-01 시작 | signed-out session; provider list (Kakao, Naver, Google, Apple) | signInWithOAuth(provider); signInWithEmail | AuthServicePort.signInWithOAuth/signInWithEmail; useAuth; getAuthErrorMessage | [config] supabase/config.toml configures only [auth.external.apple]; Kakao, Naver (custom) and Google providers are not configured locally; enable_manual_linking = false (needed by 연결된 계정) (unsure: Hosted Supabase providers not checked; only supabase/config.toml read) | partial |
| L0-02 이메일 로그인 | email + password | signInWithEmail | AuthServicePort.signInWithEmail; useAuth().signInWithEmail; getAuthErrorMessage | none | covered |
| L0-03 이메일 가입 | client signUp + email confirmation; resend mail; password reset | useAuth (sign-in only) | [hook] signUp(email, password) on AuthServicePort (board: only if the OAuth route slips) (unsure: Board marks this flow "only if the OAuth route slips"; profile row is created at first sign-in) | missing | |
| L0-04 온보딩 | required consents for KR (getRequiredConsentsForJurisdiction, showAtSignup); pending identityVerificationId | PortOne identity verification result (ok/under_14/duplicate/failed); useUpdateProfile; useGrantConsent per buildConsentGrants; completeOnboarding | useUpdateProfile; useGrantConsent; consent rules (areRequiredConsentsGranted, buildConsentGrants); resolveOnboardingDestination | [edge] verify-portone-identity edge function (PortOne v2 identity check, age/CI/duplicate; only verify-portone-payment and portone-webhook exist); [table] identity verification store (CI hash unique, real name, birth date, phone, foreigner flag, verified_at); profiles has only phone, birth_year, nationality_code (unsure: completeOnboarding/handleFinish names did not resolve as exports; step 2 and 3 appear to be existing screen code) | partial |
| L0-05 업데이트가 필요해요 | runtime version vs app version policy; store URL | RPC get_app_version_policy; useAppVersionGate; app-version-policy.service.port.ts | none (unsure: Store URL is empty until the stores list the app (EXT-8), external to the data layer) | covered | |
| L0-06 공유 링크 | club, venue, date, times, format, tier band, deadline, counts (no names); fee, approval mode, pickup flag, venue district; match venue/format/day/score tag | apply (via 매치 미리보기) | RPC get_public_share_preview(kind,id); usePublicSharePreview; public-share-preview.repository.port; useSessionQuery | [rpc] get_public_share_preview extension: session fee, approval mode, isPickup, venue district; match venue/format/day; profile NTRP + 신뢰 점수 (it returns totalSessionsConfirmed and the raw trust tier key the board forbids); club region as words | partial |
| L0-07 웹 공유 페이지 | same preview fields for /s /m /p /lb; invite card (/join-club): name, mark, region, level band, member count, join method, monthly dues | RPC get_public_share_preview; RPC lookup_club_by_invite_code (id only) | [rpc] get_public_share_preview extension: session fee, approval mode, isPickup, venue district; match venue/format/day; profile NTRP + 신뢰 점수 (it returns totalSessionsConfirmed and the raw trust tier key the board forbids); club region as words; [rpc] anonymous, minimal, rate-limited invite reader for the /join-club card (lookup_club_by_invite_code returns an id only; the whole clubs row must not leave) (unsure: generateMetadata, opengraph-image, robots noindex and og dimensions are web-app work, not data-layer; not counted as gaps) | partial | |
| L1-01 홈 | next joined game + state (cancelled/check-in/fee due/waitlist/confirmed); match in play and result; 할 일 (HomeTask kind,count,deadline); 소식 feed (일정, 결과, 게시글, 사진); hidden item ids; bell unread | hide / unhide a 소식 item; mute a club's 소식 | get_home_snapshot RPC + HomeSnapshotRepositoryPort; useHomeData; useHomeTasks / deriveHomeTasks; useNotificationBellState / useUnreadSignalCount; useMySessionPayment + sessionPaymentGateState | [hook] useHomeNewsFeed: the 소식 feed of 일정/결과/게시글 (+사진) across the viewer's clubs with a hide filter (HomeFeedScreen uses useHomeData, period recaps, nearby/discovery cards; no feed query or RPC exists); [table] per-user hidden 소식 item ids (숨기기/되돌리기) and per-club 소식 mute (이 클럽 소식 끄기): nothing stores them (signal_preferences is per signals category) | partial |
| L1-02 클럽 | 내 클럽 rows (buildClubCardModel + focus, member_composition, preferred_format, regular_meet_days, dues_amount/dues_cycle, join_policy, last_activity_at); guest + archived clubs; 승인 대기 requests; 클럽 찾기 with facets | none on the page (FAB opens create/join) | useMyClubs; useMyGuestClubs; useViewerDiscoverClubs; RPC get_club_discovery_summaries (has last_activity_at); clubs columns focus/member_composition/preferred_format/dues_amount/dues_cycle/join_policy; club_regular_meets / clubs.primary_day | [hook] bulk pending join requests for the 클럽 list (only per-club useMyJoinRequest / findMyJoinRequest(clubId) exist) (unsure: last_activity_at is returned by get_club_discovery_summaries (discovery); not verified for the 내 클럽 payload) | partial |
| L1-03 경기 | open pickups (filters: date, region, event, format, court type, NTRP, fee); 예정 sessions; saved sessions; friendsGoing, distanceKm, used courts | save toggle | useOpenPickups / useOpenPickupsForMe; applyPickupFilters; useMyParticipatingSessions; useSavedSessionIds + useSessionsByIds + useToggleSaveSession; useSessionCardModels (friendsGoing, distanceKm); sessions.used_courts (00499) | none (unsure: where.usedCourts is a model mapping change over an existing column, not a data gap) | covered |
| L1-03c 경기 · 테니스장 | venue directory with kind/region/facility facets; saved venues; distance when location granted; open-now, court count | save toggle | useVenueFinderData; useVenueDirectory / useVenueViewCounts; useSavedVenueIds + useToggleSaveVenue; RPC count_venues_by_view | none | covered |
| L1-04 기록 | rating + 30-day history + next tier; club rank neighbours; 최고의 파트너 / 천적 (>=5 matches); activity grid per day; newest 5 matches | share record card | usePlayerProfileData; useClubRatingRanks (RPC get_club_rating_ranks); usePlayerFormatRecords (player_matchups_by_format, player_partner_records via findMatchupsByFormat/findPartnerRecords); computePartnerStats; useMatchHistory; usePlayerStatsDetail; useShareStatCard | none (unsure: Board text still says player_matchups needs a format column; player_matchups_by_format (discipline) and findMatchupsByFormat already exist) | covered |
| L1-05 프로필 | profile; trust record (last 20 counted records, streak, strikes, praise count, 신뢰 점수, hidden <5 sessions); friends preview + count; achievements | useProfile; usePlayerProfileData; useAttendanceSummary; useMannerTagCounts (manner_tags); useFriendsSummary / useFriends; useAchievements; useDmUnreadCount | [rpc] trust record read (신뢰 점수 = 50 + 12-month points held 0..100, none before 5 recorded sessions; per-record points, rule applied, venue/day, streak, praise count): no trust_score column, RPC or domain function exists (profiles has trust_tier only); [hook] useTrustRecord(userId[, clubId]) over the trust read (existing: useMemberStrikeRecords, useAttendanceSummary, useExcuseAttendance) (unsure: manner_tags findCountsByUser counts tags; "distinct people who shared a match" praise count not confirmed) | partial | |
| L2-01 클럽 홈 | club, role, unread count; album + cover; home court (clubs.home_court_venue_id); 홈 feed: sessions, posts, album uploads, closed records; chat rooms; 기록 pane: season, standings, member records, 최고의 파트너; members by role; 회비 pane: own dues rows, session_payments, bank account, 납부 내역; non-member public sessions + photos as counts | join by policy (바로/신청/초대 코드); leave club; reactions; 입금했다고 알리기; copy account number; role/조/내보내기 (member Sheet) | useClub; useClubDetailBundle (RPC get_club_detail_bundle); useClubRole / useClubContext; useUnreadPostCount; useClubMedia / useApprovedPublicClubMedia; useClubPosts; useClubSessions / useCompletedClubSessions; useClubEvents; useClubRatingRanks, season_records; useClubMembers; dm_threads(club_id) via useVisibleDmThreads; useClubBankAccount; dues port findByMember + attestDuesPayment; club.repository joinOpenClub/applyToClub/joinByInviteCode; usePartnerCandidates | [table] club_dues_plans (name, amount, cadence incl. one_time/every_n_months/per_session, due rule, grace, proration, applies-to role/조); dues has only month/year, no plan_id (unsure: 회비 pane "by the club's plan" depends on club_dues_plans; today dues are month/year rows with clubs.dues_amount/dues_cycle/dues_day) | partial |
| L2-02 일정 상세 | session detail bundle + model (hero, facts, NTRP, who); roster (confirmed + waitlist); first 3 matches; my payment gate + countdown; weather; capabilities; pending applications; my planned hours and arrival stamp | RSVP / cancel; arrival stamp (도착했어요); exit time (먼저 가기); planned hours (참석 시간); save / report | useSessionDetailBundle; useSessionDetailModel + buildSessionInfoModel; useSessionMatchSets; useMySessionPayment + sessionPaymentGateState + useDeadlineCountdown; useSessionWeather; useSessionCapabilities; useLiveSessionData; useSessionApplications; useToggleSaveSession; rsvps.arrives_at | [column] rsvps.leaves_at, available_until_round, checked_in_at, checked_out_at (rsvps has arrives_at and available_from_round only; no migration adds them); [hook] arrival/exit stamp mutation (도착했어요, 먼저 갈게요) writing rsvps.checked_in_at/checked_out_at (only check_in_attendance_gps RPC from 00318 and useArrivalAvailability exist) | partial |
| L2-03 매치 미리보기 | session; venue; public attendance counts; my application; host profile; club (public club session) | apply (참석 신청/바로 참석); withdraw application; DM to host | useSessionQuery; useVenue; usePublicSessionAttendance; useMyApplicationForSession; useProfile; useClubRole; useClub; useApplyToSession; useUpdateApplicationStatus; useInitiateDmThread | none | covered |
| L2-04 장소 상세 | venue facts + hero + map; source values / 확인됨; pending + my corrections; saved flag; distance; edit approver + owner scope; booking alert; public 매치 at the court (live + planned); clubs with this home court; owner's waiting suggestion count | save; booking alert switch; 코트 수 바꾸기; remove from club list; propose / claim (links) | useVenue; useDirectoryVenue / useDirectoryVenueDetail / useVenueCourts; useVenueHeroImage; useVenueSourceValues; useVenuePendingCorrections / useMyVenueCorrections; useSavedVenueIds + useToggleSaveVenue; useUserLocation; useIsVenueEditApprover / useVenueOwnerScope; useBookingAlert; useVenueContributions({scope,venueId}); columns sessions.court_venue_id, clubs.home_court_venue_id, court_venues.venue_id | [hook] a venue's public 매치 (in play + planned), clubs whose home_court_venue_id is the venue, and the owner's waiting-suggestion count (useOpenPickups has no venue filter; no hook lists them) (unsure: useVenueContributions with scope + venueId may already give the owner's suggestion count) | partial |
| L2-06 공개 프로필 | identity, face (can_view_face), level, 구력, 신뢰 점수 (none before 5 sessions), rating (singles + doubles); friendship state; 총 경기/승률 + last five results; primary club(s); earned badges; 주 종목, 선호 시간; last-20 attendance + praise (shared-context only); 나와의 전적 (상대 + 같은 편) | friend request / cancel / accept / decline; block / unfriend; report; DM request | usePublicProfile (profiles.findPublicView, can_view_face); useFriendship + friend mutations; useHeadToHead; computePartnerStats; useClub; useBlockUser; useInitiateDmThread; player_ratings per discipline; profiles.profile_visibility + patch_profile_visibility | [rpc] visibility-filtered reads of ANOTHER person (match history, achievements, play facts, last-20 attendance + praise for shared-context viewers); useMatchHistory and useAchievements read the viewer's own; [column] profiles 주 종목 and 선호 시간 (no column on profiles or user_preferences; no editor page among the 82 sets them); [rpc] trust record read (신뢰 점수 = 50 + 12-month points held 0..100, none before 5 recorded sessions; per-record points, rule applied, venue/day, streak, praise count): no trust_score column, RPC or domain function exists (profiles has trust_tier only) | partial |
| L3-01 신뢰 기록 | attendance_records of the person (final_status, strike_value, cancel time vs start, created_at), last 20 counted in 12 months; streak; points per record; 신뢰 점수 + evidence line; praise count; excusal requests + answers; per-record session club + venue + map thumbnail; official: dues + session_payments states, lateness vs own start | 사유 인정 요청 (reason <=200); official 인정 / 거절 | attendance-record.repository (findByMember, findStrikesByMember, getSummaryForMember, findByClub, excuseRecord); useMemberStrikeRecords; useAttendanceSummary; useExcuseAttendance (RPC excuse_attendance_record); useMannerTagCounts; dues + session_payments ports | [rpc] trust record read (신뢰 점수 = 50 + 12-month points held 0..100, none before 5 recorded sessions; per-record points, rule applied, venue/day, streak, praise count): no trust_score column, RPC or domain function exists (profiles has trust_tier only); [hook] useTrustRecord(userId[, clubId]) over the trust read (existing: useMemberStrikeRecords, useAttendanceSummary, useExcuseAttendance); [table] attendance excuse request (one per attendance_record, reason <=200 chars, answered by host/총무, 거절됨 state): only excuse_attendance_record(p_record_id), an official one-way mark, exists; [column] rsvps.leaves_at, available_until_round, checked_in_at, checked_out_at (rsvps has arrives_at and available_from_round only; no migration adds them); [rpc] attendance record query joined to venue + the cancel-time rule applied (board: "data owed: the venue joins the record query") | partial |
| L3-03 성장 분석 | 30-day club analytics vs prior window; entitlement; role; 모집 계획 (maxMembers, requiredCapacity, weeklyTotalAttendance, planningAttendanceRate) | none (read-only) | useClubAnalytics (club-analytics.repository); useFeatureGate; useClubContext; useClubMembers / useClubGroups / useClubRegularMeets; club-groups.rules (requiredCapacity, weeklyTotalAttendance) | none | covered |
| L3-04 기간 기록 | period matches, days played, opponents, scope filters; another person's record (rp-other) limited by profile_visibility | useRecordDetailData; usePullToRefresh; useMatchHistory (viewer's own) | [rpc] visibility-filtered reads of ANOTHER person (match history, achievements, play facts, last-20 attendance + praise for shared-context viewers); useMatchHistory and useAchievements read the viewer's own (unsure: rp-other (공개 프로필 전체 보기 door) is a variant of this page; own-record variant is covered) | partial | |
| L3-05 하루 기록 | matches of one date | useMatchHistory (date filter); usePullToRefresh | none | covered | |
| L3-06 선수별 전적 | every opponent (record, form); every partner; format split 단식/복식; with one person: 상대 record, 같은 편 record, matches together | useHeadToHead; computePartnerStats; usePlayerFormatRecords (player_matchups_by_format, player_partner_records); useMatchHistory (partnerId/opponentIds); usePublicProfile | none (unsure: Board "Data owed" (format column) is satisfied by player_matchups_by_format; hook wiring of the format Choice not verified) | covered | |
| L3-07 출석 현황 | per member attendance rate, isFlagged (weighted absences >=3) | useClubAttendanceSummary; club-attendance-since-join.repository | none | covered | |
| L3-08 테니스 여정 | match notes (matchId, sessionTitle, venueName, mood, body, createdAt) newest first; month's matches + notes count; account plan; AI 요약 | save / edit a 경기 노트; AI 요약 request | PlanType player + getEffectivePlan + useFeatureGate (gate only); useMatchHistory (month count) | [table] match_notes (match_id, author_id, mood good|ok|bad, body <=500; own-row RLS): no table, port or hook exists (practice_logs dropped in 00702); [hook] useMatchNotes(userId, 50) and a save/upsert mutation for a 경기 노트 (no hook, no port); [edge] AI 요약 job/edge function for 테니스 여정 (supabase/functions has none), plus an entitlement key for the player plan gate (getEffectivePlan/useFeatureGate exist; PLAN player exists) | missing |
| L3-09 업적 | held achievements; catalog filtered by isAchievementShown; progress for quantitative types | useAchievements; ACHIEVEMENT_TYPES + isAchievementShown; usePlayerStatsDetail; achievement.repository.findByUser | none (unsure: getProgress is named in the board but did not resolve as an export) | covered | |
| L3-10 클럽 대항전 | challenges of the club (대기/진행/지난); both clubs; state word + 승/패 stats | accept (수락); decline (거절) | useInterClubChallenges; useClubsByIds; deriveChallengeDisplayStatus; computeChallengeStats; useAcceptChallenge; InterClubChallengeRepositoryPort.decline | [hook] useDeclineChallenge (InterClubChallengeRepositoryPort.decline exists; only useAcceptChallenge is a hook) | partial |
| L3-11 회비 관리 | dues by month in sections 확인 대기/미납/완납; club balance for 장부 Cell; last reminder per member; dues plan for 회비 설정 | 입금 확인; 되돌리기 (unmark); waive; 알림 (one / 모두 알리기); 메모 남기기; 납부 취소 | useClubDuesData; useClubDuesActions; dues.repository (findByClub, confirmDuesPayment, unmarkPaid, markPaidBulk, updateStatus, getClubSummary); RPCs mark_dues_paid_bulk, unmark_dues_paid; dues.note | [table] club_dues_plans (name, amount, cadence incl. one_time/every_n_months/per_session, due rule, grace, proration, applies-to role/조); dues has only month/year, no plan_id; [column] dues.plan_id, dues.period_start, dues.period_end (rows generated per period); [rpc] send reminder for one member / all 미납 members (dues) and for a session fee (참석자 관리), logged by who/when: no port method or RPC (generate_dues_reminders was dropped in 00405); [column] reminder log for 마지막 알림 (who/when) on dues and on session_payments (no column or table); [table] club ledger (opening balance, dated income/expense entries with 분류 + 영수증; a confirmed 회비 = one income entry) with an aggregate read (balance, month spend) and a CSV export: no table, port or hook | partial |
| L3-13 알림 | signals of last 30 days not dismissed, grouped per key with actor + count; family per FVK_NOTIF_FAMILIES; 처리할 일 (open requests); bell unread | acknowledge; dismiss + undismiss; 친구 신청 수락/거절 in row | useNotificationCenter; useActiveSignalsFeed; useAcknowledgeSignals; useDismissSignals / useUndismissSignals (RPC signals_dismiss_batch, signals_undismiss_batch); useOpenRequestSignalIds; useSignalVisibleCounts; signals.dismissed_at/expires_at/resolved_at/acknowledged_at | none (unsure: "one row per group key" has no group_key column on signals (dedup_key, context exist); grouping presumed client-side; board says Lane E §5 lists migrations) | covered |
| L3-14 메시지 | accepted threads; unread by thread; pending count (cap 99) | useDmThreadsByStatus; useDmUnreadByThread; dm_threads.request_status | none (unsure: /messages/requests route is new; data is the same pending read) | covered | |
| L3-15 메시지 요청 | pending threads (requests) | 수락 / 거절 | useDmThreadsByStatus(pending); useDecideDmRequest (RPC decide_dm_request) | none | covered |
| L3-16 참가비 내역 | my session_payments (amount, status, submittedAt, refundState) with session day/format/location and club name | useMySessionPayments; useSessionsByIds; useClubsByIds; sessionPaymentGateState; session_payments.refund_state/submitted_at | none (unsure: Route /profile/fees and MyFeesScreen are new; board says nothing else (due time, method, receipt) exists to read) | covered | |
| L3-17 순위표 | club ranks; friends ranked; regional ranking; national top 50; record value, 7-day ELO change, tier tag; 기간 narrowing | share top five | useClubRatingRanks (get_club_rating_ranks) + useClubProfiles; useFriends; useRegionalRanking / useRegionalLeaderboard (get_regional_ranking); profiles.findTopByRating; getTierFromElo; useShareLeaderboard; elo_history | [rpc] period (전체/이번 주/이번 달) narrowing on the leaderboard reads (get_club_rating_ranks, get_regional_ranking, get_public_profile_leaderboard, findTopByRating take none) (unsure: recordValue/eloDelta7d did not resolve as exports; 7-day change presumably from elo_history) | partial |
| L3-18 친구 목록 | friends; incoming + outgoing requests | accept; decline / cancel; 친구 끊기 | useFriends; useIncomingFriendRequests; useOutgoingFriendRequests; friendships table | none | covered |
| L3-19 친구 찾기 | profile search (20); friendship state per row | send friend request | useProfileSearch; useFriendship; useSendFriendRequest | none | covered |
| L3-20 차단한 사용자 | blocked ids + profiles | unblock | useBlockedUsers; useProfilesByIds; useUnblockUser; user_blocks | none | covered |
| L3-21 동의 이력 | consent history (<=200): purpose, state, createdAt, method, expiresAt | useConsentHistory; user_consent; purpose_catalog | none | covered | |
| L3-22 공지사항 | announcement list from app copy (id, title, date, body, important); opened state | mark opened | AnnouncementsScreen (static in-app copy, local useState) | none (unsure: Board says the list is the app's copy and empty until real; persistence of the opened state and the notice push (/legal/notices/[noticeId]) are not specified) | covered |
| L3-23 앨범 | approved club photos grouped by origin (session / post); pending photos (approval) | none (tiles display-only) | useClubMedia (approvalStatus); club_media (session_id, approval_status, visibility); useClubMediaSignedUrls | none (unsure: club_media has session_id but no post id; the 게시글 door grouping source was not confirmed) | covered |
| L3-24 복식 파트너 추천 | partner candidates with score breakdown (eloProximity, chemistry, trust, attendance); partner records | usePartnerCandidates; usePartnerRecords (player_partner_records); strongestPartnerFactor; profiles.global_attendance_rate | none | covered | |
| L3-25 멤버 관리 | roster by role with effective 조, pin mark, trust score, 출석 주의; join requests (applicants) with prospect basis; recommendable guests | 승인 / 거절 applicant; 역할 바꾸기; 조 정하기; 내보내기; guest 초대; 클럽 단톡방 만들기 | useClubMembersData; useProspectAttendanceSummary; useClubAttendanceSummary; useMemberKebabActions; useUpdateMemberRole; RPCs assign_member_group, decide_join_request; join-review.repository.getRecommendableGuests/nominateForMembership; useCreateGroupThread | [rpc] trust record read (신뢰 점수 = 50 + 12-month points held 0..100, none before 5 recorded sessions; per-record points, rule applied, venue/day, streak, praise count): no trust_score column, RPC or domain function exists (profiles has trust_tier only) (unsure: Roster trust score (신뢰 84점) has no source until the trust read exists) | partial |
| L3-27 일정 관리 | club sessions upcoming (open, locked, in_progress) by day; completed sessions paged; attention facts (입금 확인 N건, 게스트 N명) | useClubSessions; useCompletedClubSessions; useDaySectionedSessionList; useClubSessionCardRow; canManageSchedule | none | covered | |
| L3-28 신고 관리 | reports of the club (member, date, category, reason; reporter hidden) | resolve 확정 / 기각 | useClubReports (member-report.repository.findByClub); useResolveMemberReport (resolve); useProfilesByIds | none | covered |
| L3-29 테니스장 관리 | club venues | add / update / delete venue; 코트 수 / 메모 | useClubVenues; useCreateVenue / useUpdateVenue / useDeleteVenue; VenueSearchTakeover + useNaverAdoptCorrelation; parseClubVenueCourtCount | none | covered |
| L3-30 장소 정보 제안 내역 | my contributions / contributions to review (kind, status, venue, createdAt) | useVenueContributions({scope,venueId}); RPC get_venue_contributions; useAuth | none | covered | |
| L3-31 소유권 신청 내역 | my claims / 운영팀 검토 claims | useVenueOwnerClaims({scope,venueId,status}); OwnerFrame / useVenueOwnerScope; venue-owner-claim.repository.list | none | covered | |
| L3-32 가입 심사 | join request message + day; prospect attendance summary, record, ELO, played-with; profile; club join method; votes + time left (멤버 투표) | 승인 / 거절 (decide); 찬성 / 반대 vote | useMyJoinRequest; useProspectAttendanceSummary; useProfile / useProfilesByIds; useClubContext; useDecideJoinRequest (RPC decide_join_request) | [table] club join-vote store for the 멤버 투표 admission rule (votes, deadline, objection) with a cast-vote RPC and a read hook (club_join_request has no vote data) | partial |
| L3-33 제안 상세 | contribution receipt, evidence, decisions | withdraw; review (승인/거절/추가 정보 필요 + explanation); resubmit | useVenueContribution; useWithdrawVenueContribution; useReviewVenueContribution (RPC review_venue_contribution); useAuth | none | covered |
| L3-34 장소 소유권 | owner claim, history, scope | withdraw; agree to transfer; supersede / resubmit; relinquish (소유권 끝내기) | useVenueOwnerClaim; useVenueOwnerClaimHistory; useVenueOwnerScope; useVenueOwnerClaimMutation (submit, withdraw, setTransferConsent, relinquish, review, revoke) | none | covered |
| L3-35 참석자 관리 | rsvps by status; session_payments joined; applications; capabilities; 첫 참석 flag | 입금 확인 (per row) + 되돌리기; 참가비 면제; 참석 확정으로 올리기; 내보내기; 승인 / 거절 application; 알림 / 모두 알리기 reminder; 정산 나누기 | useLiveSessionData(includeManagementProfiles); useSessionPayments; usePaymentActions (session-payment port: attest, markPaid, markWaived); useSessionCapabilities; useSessionApplications + useDecideGuestApplication; profiles.total_sessions_confirmed | [rpc] send reminder for one member / all 미납 members (dues) and for a session fee (참석자 관리), logged by who/when: no port method or RPC (generate_dues_reminders was dropped in 00405); [hook] 정산 나누기 cost-split write for a session with no fixed fee (sessions.cost_total exists; no hook confirmed) (unsure: Reads names a new bulk-confirm RPC; Taps says the page has no bulk command, so it is not counted) | partial |
| L3-36 참석 확인 | confirmed rsvps with window + arrival/exit stamps; lateness vs own start (10 min); 확인할 사람 / 도착 groups; unresolved count for 관리 sheet | 도착 확인 / 모두 확인 (attended); 불참 (no_show); 시간 바꾸기; 되돌리기 | useLiveSessionData; useConfirmAttendance (RPC confirm_attendance); useSessionAttendance; computeUnresolvedAttendanceCount; rsvps.arrives_at | [column] rsvps.leaves_at, available_until_round, checked_in_at, checked_out_at (rsvps has arrives_at and available_from_round only; no migration adds them); [hook] arrival/exit stamp mutation (도착했어요, 먼저 갈게요) writing rsvps.checked_in_at/checked_out_at (only check_in_attendance_gps RPC from 00318 and useArrivalAvailability exist) | partial |
| L3-37 호스트 관리 | pending applications with names + relative time | 승인 / 거절 | useLiveSessionData; useSessionApplications; useProfilesByIds; useDecideGuestApplication (RPC decide_guest_application) | none | covered |
| L3-38 스코어카드 | rounds, courts, sets, result state per match; viewer's own matches (team first); session rules + per-match override; tab set by session type; tournament groups / table / bracket; planned start / minutes in play; recap (settled matches + rating change); match note | submit score (expected revision, idempotency key); 점수 검토 요청; official rule override; 경기 노트; 결과 공유 | useLiveSessionData; useSessionMatchSets; useSessionMatchScoreCalls; deriveMatchResult (domain); useSubmitScore (RPC submit_match_score); useProposeScoreCorrection (RPC propose_score_correction); useUpdateMatchRulesSnapshot (matches.rules_snapshot); useShareMatch; matches.score_revision, outcome | [rpc] submit_match_score expected-revision parameter and the typed refusal score_changed (the RPC takes no revision; matches.score_revision exists); [rpc] move tournament matches into matches (stage/group) and the group-table/bracket/advance reads; retire tournaments.state JSONB + useTournament/useAdvanceTournament/useCancelTournament; [column] matches.stage and group (tournament matches live in tournaments.state JSONB today), matches.scheduled_start (09:30 시작 queue); [table] match_notes (match_id, author_id, mood good|ok|bad, body <=500; own-row RLS): no table, port or hook exists (practice_logs dropped in 00702); [hook] useMatchNotes(userId, 50) and a save/upsert mutation for a 경기 노트 (no hook, no port) (unsure: deriveMatchResultState and useSessionRules (named by the board) did not resolve as exports; closest are deriveMatchResult and matches.rules_snapshot) | partial |
| L3-39 스코어카드 운영 | scorecard reads + useMatchBoardData; inbox exceptions derived from matches + unsettled payments; queue (matches without a court); minutes from start time; groups -> 본선 draw | 대신 입력; 검토하기 (keep / change score); 기록에서 제외 / 다시 기록에 넣기; 라운드 추가; 늦게 온 선수 추가 (available_from_round); 팀 바꾸기 / 선수 교체; 대진 다시 짜기; start match on a free court; outcomes walkover/retired/etc. | useMatchBoardData; useLiveSessionData; useSessionMatchSets; useSubmitScore (p_outcome accepts walkover, retired, defaulted, suspended, abandoned); useProposeScoreCorrection; rsvps.available_from_round; matches.court_number, started_at, status; useConfirm | [rpc] move tournament matches into matches (stage/group) and the group-table/bracket/advance reads; retire tournaments.state JSONB + useTournament/useAdvanceTournament/useCancelTournament; [column] matches.stage and group (tournament matches live in tournaments.state JSONB today), matches.scheduled_start (09:30 시작 queue) (unsure: Board lists 팀 바꾸기 / 대진 다시 짜기 as not in code as board rows (UI, not data)) | partial |
| L3-41 게시글 | post, comments (infinite), reaction summary, author profiles, club | reaction toggle; comment / reply; edit / delete; report; mark read | usePostDetail; usePostCommentsInfinite; usePostReactionSummary; useProfilesByIds; useClub; useMarkPostRead; useMarkThreadRead | none | covered |
| L3-42 DM 스레드 | thread, messages, chat items, composer | send; react; delete (60 s); report / block; accept / decline request; leave group | useDmThread / useDmThreadData; useDmMessages / useDmLatestMessages; useDmChatItems; useDmComposer; useSendDmMessage; useDmThreadActions; useMarkThreadRead; useToggleDmReaction; useLeaveThread | none | covered |
| L3-43 문의하기 | FAQ copy; support channels; app version / device / account for the mail draft | open mail draft | supportChannels(); ContactScreen static copy | none | covered |
| L3-44 이용약관 | versioned terms document + history | config/legal/documents/terms.ko.ts; config/legal/policy-versions.ts; policy_versions table | none (unsure: Board draws two versions; shipped document has one) | covered | |
| L3-45 개인정보 처리방침 | versioned privacy document + history | 내 데이터 받기 (JSON export) | config/legal/documents/privacy.ko.ts; policy_versions; useExportUserData + edge function export-user-data | none | covered |
| L4-01 일정 만들기 | draft (seeded by regular meet / past session); club preferences; club bank account for fee gate | create session (club or pickup) | create-session-draft; useRegularMeetPrefill / usePastSessionPrefill; useClub; buildSessionRecordRows / recordFromWizardDraft / wizardFeeAmount; useClubBankAccount; session.repository.create | none | covered |
| L4-02 클럽 만들기 | live draft; profile region; generated logo; creation receipt | create club (+logo, region, venues, dues, recruitment) | usePersistedWizardDraft (create-club-draft); club-logo; club-setup-receipt; club.repository.create; useClubSetupMutations | none | covered |
| L4-03 행사 만들기 | role (club_events insert policy) | create club event (title, date, time, location, link, max attendees, description); club_event_created notification | useClubRole; useCreateClubEvent; useClubEvents / useClubEvent; club_events (incl. cost_per_person, linked_session_id); signal club_event_created (00514) | none (unsure: Route and screen are new; the data layer exists) | covered |
| L4-04 초대 코드로 가입 | club for an invite code (id only); auth state | resolve code then open 클럽 홈 | RPC lookup_club_by_invite_code; ClubRepositoryPort.findByInviteCode / joinByInviteCode; useJoinClub; useAuth | none (unsure: useClubByInviteCode (named by the board) does not exist as a hook; JoinClubScreen uses useJoinClub) | covered |
| L4-05 글쓰기 | role (categories clamp, audience); categories | create post + photos | useClubRole; POST_CATEGORIES; useCreatePost; useUploadClubPostMedia / useRemoveClubPostMedia; usePickImages | none | covered |
| L4-06 경기 규칙 | session; capability canEditMatchRules; six scoring formats | save match rules; reset to defaults | useSessionQuery; useSessionCapabilities; useScoringFormats; MatchRulesSchema; KOREAN_DEFAULT_MATCH_RULES; useUpdateMatchRulesSnapshot; sessions.match_rules | none | covered |
| L4-07 장소 정보 제안 | contribution draft; venue snapshot for a correction; earlier proposal | submit contribution (new venue, facts, court operations, lifecycle, report) | useContributionDraft; useSubmitVenueContribution (RPC submit_venue_contribution); useVenueContributionContext; EvidenceFields | none | covered |
| L4-08 소유권 신청 | claim attempt; venue snapshot | submit owner claim | useOwnerClaimAttempt; useVenueOwnerClaimMutation; RPC submit_venue_owner_claim; claim-evidence | none | covered |
| L4-09 단톡방 만들기 | friends or seeded profiles; selection | create group thread | useFriends; useProfilesByIds; useMemberSelection; useCreateGroupThread (RPC create_group_thread) | none | covered |
| L4-10 멤버 초대 | friends + club guests minus thread participants | add thread members | useFriends; useDmThreadParticipants; useAddThreadParticipants; useMemberSelection | none (unsure: Board names useThreadParticipants / useAddThreadMembers; the existing hooks are useDmThreadParticipants / useAddThreadParticipants) | covered |
| L4-11 프로필 수정 | profile; region keys | update profile (name, gender, birth year, start year, region); upload / remove avatar | useProfile; useUpdateProfile; useUploadAvatar / useRemoveAvatar; usePickImages; SIDO_KEYS | none | covered |
| L4-12 비밀번호 변경 | change password | useChangePassword; AuthServicePort.changePassword | none | covered | |
| L4-13 회원 탈퇴 | my clubs with role (blocked page: owners); confirm phrase | delete account (soft delete, release seats, withdraw consents, clear tokens); sign out | useDeleteAccount (profiles.deleteAccountAtomic / softDeleteProfile); useMyClubs; deleteAccount confirm phrase | none | covered |
| L4-14 회비 설정 | dues plan (납부 주기, 금액, 납부 기한 일, 대상); first-plan setup | save dues plan | clubs.dues_amount / dues_cycle / dues_day (single club-level setting); useUpdateClub | [table] club_dues_plans (name, amount, cadence incl. one_time/every_n_months/per_session, due rule, grace, proration, applies-to role/조); dues has only month/year, no plan_id; [column] dues.plan_id, dues.period_start, dues.period_end (rows generated per period) (unsure: Board also lists 6 cadences and 대상 (role or 조); only a club-level amount/cycle/day exists) | missing |
| L4-21 입금 계좌 | club bank account (bank, number, holder) | upsert account | club_bank_accounts; club-bank-account.repository; useClubBankAccount + its upsert mutation | none (unsure: Route /clubs/[clubId]/settings/account is new; board lists a bank-picker list of banks (static)) | covered |
| L4-15 설정 | profile; color scheme (local); OTA update check; developer mode | sign out; theme mode; rerun tour | useProfile; useThemeStore / useColorScheme; checkForAndApplyOtaUpdate; useDeveloperModeStore; AuthServicePort.signOut / useAuth | none (unsure: useColorSchemeStore and useSignOut (board names) do not exist by those names) | covered |
| L4-16 연결된 계정 | connected identities (provider, nickname/email) | link provider; unlink provider (server refuses the last) | (none) | [hook] getUserIdentities / linkIdentity / unlinkIdentity on AuthServicePort + hooks (port has signInWithOAuth, signInWithEmail, signOut, changePassword only); [config] supabase/config.toml configures only [auth.external.apple]; Kakao, Naver (custom) and Google providers are not configured locally; enable_manual_linking = false (needed by 연결된 계정) | missing |
| L4-17 공개 범위 설정 | profile_visibility merged over DEFAULT_VISIBILITY; preview per audience (device) | patch one visibility field | useProfile; usePatchProfileVisibility (RPC patch_profile_visibility) | none | covered |
| L4-18 알림 설정 | signal preferences per family (quiet hours); master push + message preview; per-club level | batch update signal preferences; update preferences | useSignalPreferences / useUpdateSignalPreferences (signal_preferences.quiet_hours); usePreferences / useUpdatePreferences (user_preferences); FVK_NOTIF_FAMILIES | none (unsure: Per-club level (전체/공지만/끔) storage not verified: signal_preferences is keyed by category, not club) | covered |
| L4-19 동의 관리 | consents by purpose, jurisdiction channel; purpose labels | grant; revoke | useUserConsents; useGrantConsent; useRevokeConsent; user_consent / purpose_catalog | none (unsure: Board names useConsents; the existing hook is useUserConsents) | covered |
| L4-20 클럽 설정 | club, role, bank account; regular meets + groups for planning facts; club media | update one club field; upload banner / logo; transfer ownership; archive / unarchive; invite code share | useClub; useClubRole; useUpdateClub; useClubBankAccount; useClubRegularMeets (+ create/update/delete); useClubGroups (+ CRUD); useManageClubMedia / useUpdateClubMedia / useArchiveClubMedia; useTransferClubOwnership / useArchiveClub / useUnarchiveClub; club_play_regions | none (unsure: The owed editors 정기 일정, 조, 앨범 관리 have no page code but their hooks exist; the 회비 설정 door depends on dues_plans (see L4-14)) | covered |
| L3-46 장부 | ledger entries by month (infinite); balance over all entries; month spend | add / edit / delete expense; CSV export | (none) | [table] club ledger (opening balance, dated income/expense entries with 분류 + 영수증; a confirmed 회비 = one income entry) with an aggregate read (balance, month spend) and a CSV export: no table, port or hook | missing |
| L3-47 대진 짜기 | session, format, court count; confirmed RSVPs less unsettled fee payers; arrival rounds and times; attendance viability; round preview; session rules; generation style; tournament strategy inputs | generate round (저장); create tournament; arrival marks (available_from_round, arrives_at); saved preset | useGenerateRoundInputs; useRoundPreview; useGenerateRound (RPC generate_session_round); computeAttendanceViability; partitionSettledMatchPool; useCreateTournament; rsvps.available_from_round / arrives_at; sessions.generation_style_id; GenerationPolicy entity (spine-only) | [column] saved generation preset 소셜/실력별/고정/랜덤 on sessions (board: only the three score-driven generation_style_id values persist; GenerationPolicy is spine-only, no persisted axes); [rpc] move tournament matches into matches (stage/group) and the group-table/bracket/advance reads; retire tournaments.state JSONB + useTournament/useAdvanceTournament/useCancelTournament; [column] persisted GenerationPolicy axes the page offers beyond recording (host-selected pool 선택한 사람만, 만드는 시점, 확정 시점): generation-policy.entity.ts derives an effective policy and persists no policy column (unsure: useSessionRules (named by the board) did not resolve; 대진 짜기 reads rules through the session) | partial |