Skip to content

Owner review 46 — lane B: clubs, sessions, venues and 대항전 on the Feed item grammar (2026-10-19) ​

Status: Reference · Date: 2026-10-19

Read-only research for owner review 46, marks M12, M13, M18, M21, M22. Paths are repo-relative; board files drop the flat-v2- prefix (docs/twomore_design_system/wireframes/); "library" = docs/design/flat-v2-component-library-2026-10-09.md.

Bottom line ​

  • Nearly every missing fact is already in the schema; the flat v2 rows just do not read it. The old app ClubCard prints composition, age band, rhythm, join policy and dues (packages/features/clubs/src/shared/club-card.tsx:187-206).
  • "Which courts" is already modelled and enterable: sessions.used_courts (00499), court_venues.used_courts (00454), club_regular_meets.used_courts (00529), entered through CourtUsagePanel (packages/app/src/presentation/components/court-usage-panel.tsx:77; used by create-session step 2 and the courts-edit sheet). The boards dropped it three times (create-session editors forms.jsx:817-927; create-club row menu forms.jsx:2033; 테니스장 관리 list-a.jsx:3063-3090), and SessionInfoModel keeps counts only (packages/app/src/domain/session-info-model/types.ts:148-151). Design plus one model field; no migration. (Checked 2026-10-19: no seeded session sets used_courts, 0 of 503 locally.)
  • 대항전 cannot fully match: it has no venue, time, format or level.
  • The library forbids the change today: "A Feed of one kind uses that kind's own row" (library :1779, :291).

Now ​

The Feed item: HubFeedItem hub.jsx:518 (library §5 :1777) — 96 thumb, overline, title, up to two facts, footer = 128 tall.

  • Club (내 클럽, 탐색): FvkMediaRow kind=club (kit-content.jsx:1595) via clubRow list-a.jsx:115 → :274, :301 (also 소속 클럽 detail.jsx:5539): 56 logo, name, region | 멤버 N명, 탐색 adds the NTRP band, Count or Tag at right; 80 tall (96 in 탐색). App: ClubCard (packages/features/clubs/src/shared/club-card.tsx:126): 3:1 cover, tier band and 모집 중 on the photo, emoji-led rows for region, composition + age band, rhythm, join policy, dues; ≈ 230–280 tall (arithmetic).
  • Session (번개 찾기, 내 경기, 저장됨, search): sesRow list-a.jsx:201 → :591, :666, :696, :717 (pickers forms.jsx:996): 56 map, venue, time range, format | NTRP | 친구 N명, n/m or one Tag; 96 / 80. App: SessionCardV2 (packages/app/src/presentation/components/sessions/session-card-v2.tsx).
  • Club sessions (모집 중, 진행 중, 지난 일정): ClubSessionsSection list-a.jsx:2542 (live :2600, past :2607): the same row; Tags 입금 확인 N건, 게스트 N명, Live. App: useClubSessionCardRow → SessionCardV2.
  • Venue (경기 › 테니스장): vnRow list-b.jsx:2545 (search :2902): photo/map/court thumb, name, district | 코트 N면 | 영업 중, 80. Club 테니스장 관리 list-a.jsx:3039. App: DirectoryVenueCard, VenueRow.
  • 대항전 (관리 › 도전 과제): chRow list-a.jsx:3282: opponent logo, "vs …", rule | dates, score, 승/패 or 받은 신청 Tag; 80 under a Strip. App: ChallengeBlock (packages/features/clubs/src/club-challenges-screen.tsx:356).

Data (exists / missing) ​

  • Clubs: member_composition NULL|male_only|female_only|mixed|any (00040, a declared policy, not measured); min_tier/max_tier (00093); preferred_format (00040); region slugs (00456); active_member_count, open_slot_count, max_members (00003, 00256); regular_meet_days (00266-67); last_activity_at, age band with k ≥ 3 (00446); dues_amount + dues_cycle (00003, 00490); join_policy (00213); focus, tagline (00255); guests_allowed (00479). Missing: a measured gender mix; the viewer's pending request in the list payload.
  • Sessions: event_type regular|pickup|tournament|league|interclub (00657); format; tier band (00500, own ?? club); composition (00500); participation_fee (00298); max_players, RSVPs + waitlist; venue + free-text location; court_count + used_courts [{number, name}] (00499); recorded vs casual = is_friendly (00047); host, rsvp_deadline. SessionInfoModel lacks usedCourts.
  • Venues: venues (00283): court_count (from venue_courts once rostered, 00529), surface, indoor/lighting, parking/shower (00370), hours, fees (00358), booking URL, rating; finder facets (00579). venue_courts stable ids (00606). Booked numbers: sessions.used_courts, matches.court_number (00162).
  • 대항전: inter_club_challenges (00098): two clubs, challenge_type total_wins|win_rate|elo_gain|matches_played, dates, status, scores. No venue, time, format, level or capacity; the score counts each club's own matches in the range, not head-to-head (00098:89-130). sessions.event_type = 'interclub' is a bare label (owner review 39 removed 교류전 for this reason).

References (fetched 2026-10-19 unless noted) ​

Pattern: level and gender are structured on racket-match cards; capacity is n/m; no source read lists court numbers as a fact (Seoul: in the title; Playtomic: after assignment).

Proposal ​

Keep the slots and "nothing on the thumb, no Tag, no control". Add a second facts row ("who") for objects with eligibility. Each facts row costs 24: one = 128, two = 144, three = 168. Recommend two, one 144 item app-wide (thumb 112; a post gets a third text line) so the pitch stays even; fallback 128 in the mixed 소식 Feed and 144 in single-kind lists. A wrapping name adds 24.

  • Club (144). Thumb: logo tile (탐색: featured photo, else logo). Overline 강남구 | 친목형. Facts A 혼성 | 복식 | NTRP 3.0–4.0. Facts B 주 2회 저녁 | 월 3만원 | the deciding fact (신청 중/게스트, else 승인제/초대 전용, else 모집 중 n자리; in 내 클럽: 다음 일정 토 19:00 | 참석). Footer 2시간 전 활동 … 42/60. Top three: level, composition + format, rhythm.
  • Session (144). Thumb: venue map. Overline 일정|번개|대회|리그|대항전 | club or host. Facts A time | the viewer's state (참석, 호스트; 예비 n번, 입금 전 in warn), else D-n deadline, else n자리 남음 (two or fewer). Facts B 복식 | 혼성 | NTRP 3.0–4.0 | 3~4번 코트 (a segment equal to an active filter drops). Footer posted time … 친구 2, 6/8. Live = 진행 중 + round n/m; full = 마감 ink3; past = date, then the 결과 grammar when recorded; 취소됨. Club sessions: the same item under the three Tabs; officer attention (입금 확인 3건) takes the deciding slot in warn.
  • Venue (144). Thumb: photo, map or court tile. Overline 테니스장 | 송파구. Facts A 코트 12면 | 하드 | 실내. Facts B 영업 중 | 12,000원/시간 (club venues: 예약 오픈 매월 1일 10:00). Footer 1.2km … 4.3. 테니스장 관리 adds 우리 클럽 3~5번 코트.
  • 대항전 (144). Thumb: two club logos. Overline 대항전 | 총 승수. Title "vs 반포 한강 테니스". Facts A 9.14~9.28 | D-5 종료. Facts B the standing 2-1, final 승 3-2 (success), 받은 신청 (warn). A real two-club event (venue, time, format) needs a migration and a ruling.
  • Booked courts. 일정 만들기 gets a 코트 Cell under 장소 opening a Sheet of multi-select Choices over the venue's court numbers (venue_courts when rostered, else 1..court_count; a stepper above 12, as CourtUsagePanel does), preselected from the club's used_courts or the 정기 일정; it writes used_courts and court_count. Shown as 3~4번 코트 (consecutive ~, else 1, 3번 코트; scattered beyond three = 코트 5면) in facts B, the session detail and the club venue row; a count only when numbers are unknown, never invented. Model: add where.usedCourts.

Migrations: none for the grammar. Optional: a measured gender mix beside the age band (as 00446), the viewer's pending request in the list payload, a 대항전 session link.

Tensions: peers use one scarcity chip but the Feed forbids Tag pills (review 35), so urgency is warn text; 144 everywhere reopens review 36's 128; 대항전 is either the challenge page or sessions labelled interclub.

Not verified: Playtomic, Strava, TeamSnap, 문토, 당근 모임, 네이버 예약 card layouts; 플랩풋볼 cards (filters only); app heights (estimates); venue_courts population in the live DB.

Markdown remains the source of truth. Run yarn docs:check before handoff.