Owner review 10, lane U: how far can the product expand beyond tennis? (2026-10-13)
Status: Draft · Date: 2026-10-13 · Lane U of owner review 10. Inputs: the real code (domain, migrations, i18n, app and web metadata), the existing market docs (docs/architecture/roadmap.md, docs/research/market-and-revenue-research.md), and web searches run on 2026-10-13. Audience: the owner. Nothing outside this file was changed.
Owner, review 10. "Or how about something more along the line that we cater to make organizing and running tennis clubs better? Practically speaking, how much can we expand into other sports? How much generalizability should we consider as we name our product?"
0. Evidence tags and limits
| Tag | Meaning |
|---|---|
| C | Read or counted in our code today, with path:line (counts by grep, 2026-10-13). |
| W | From a web search or fetch on 2026-10-13, with link. |
| X | Not checked or not found. Said so rather than guessed. |
Not checked: Korean per-sport participation rates for 2024/2025 (the news coverage lists only walking, bodybuilding, hiking), 대한체육회 registered members per sport, Korean padel court counts, Naver Band / 소모임 / KakaoTalk open-chat usage by sport, sport-specific Korean apps beyond the two bracket apps already in docs/research/market-and-revenue-research.md. File counts below are grep counts and include tests and migrations, so read them as "how widely the idea spreads", not as work estimates.
1. Coupling inventory (the code is the truth)
1.1 Headline
The product is more generic than its copy and less generic than a racket-sport platform needs. The sport-agnostic spine (club, member, session, RSVP, dues, payments, notifications) carries no sport at all. Two places already anticipate a second sport: match_formats.discipline and the TODS-style format registry. Everything below the scoreboard is tennis-shaped.
1.2 By area
| Area | What is tennis-specific | Class |
|---|---|---|
| Club, session, RSVP, waitlist, dues, payments, notifications | No sport column on clubs (supabase/migrations/00003_clubs.sql), none on sessions (00005_sessions.sql). sessions.court_count (00005_sessions.sql:8) is the only physical assumption, and it reads as "playing spaces". | sport-agnostic |
| Match record shape | match_sets is games per side plus an optional tiebreak pair (00056_match_sets.sql:3-12). matches is singles/doubles (00001_enums.sql:9; match.entity.ts:13 adds mixed_doubles). A set holding "games" with an optional tiebreak is the tennis score tree. Rally sports fit it as one-set-equals-one-game (the points go in the games column), but the column names then lie. | racket-generic shape, tennis names |
| Scoring engine | scoring.rules.ts (469 lines) validates sets, games, win-by, tiebreak triggers, final-set variants (isSetComplete :215, isMatchComplete :269, validateSetScores :319). Format spec is a zod schema with bestOf 1/3/5, set.games 2..30, tiebreak, finalSet, noAd (match-format.entity.ts). A rally-to-21 game is set.games = 21, winBy = 2, tiebreak = null with a 30-point cap, which the schema already allows (games max 30). This is the cheapest surprise: the engine is closer to generic than it looks. | racket-generic engine, tennis-only vocabulary |
| Format registry | match_formats has discipline TEXT NOT NULL DEFAULT 'tennis' (00506_match_format_registry.sql:34) and the entity comment says "padel/pickleball are the reason the column exists" (match-format.entity.ts, ScoringFormat.discipline). Seeds are tennis: itf_tiebreak_bo3, match_tiebreak_in_lieu_bo3, fast4_bo3, and others (00506:87-135). Tennis-only noAd flag and the 6-seed TODS parser grammar (scoring.rules.ts:82, CODE_RE) cover only SET1/3/5-S:n/TBn. | already sport-keyed; seeds and parser tennis-only |
| Session match rules | MatchRulesFormat = '1_set' | '2_of_3' | '3_of_5' (match-rules.entity.ts:7), tiebreak config, deuce badge, starting score. Tennis vocabulary in a per-session setting. | tennis-only vocabulary, racket-generic idea |
| Ratings | ELO is generic and is the stated truth (rules/elo.rules.ts; server private.match_rating_compute_deltas, 00642). NTRP/UTR are inference layers on top: tier.config.ts:26-29 (ntrpMin/ntrpMax on every sub-tier), rating-region.config.ts:30-113 (per-tier NTRP and UTR bands split singles/doubles), rating-scale-format.rules.ts, rating-calibration.rules.ts. formatNtrpBand is imported in 7 files. 44 files mention NTRP, 11 UTR. | ELO and trust tier: sport-agnostic. NTRP/UTR bands: tennis-only |
| Trust tier | Computed by SQL from attendance and behaviour (canon/trust.md); no sport input. | sport-agnostic |
| Match generation | generation-policy.entity.ts (134 lines, six axes) has no sport content. rotation.rules.ts (483 lines) is "courts, singles seats 1, doubles seats 2" (:162-170); strategies include king_of_court, mexicano, americano (generation-policy.entity.ts:50), which are padel/pickleball vocabulary already. Player-count to court-count math assumes 2 or 4 players per court. | racket-generic |
| Venues | Largest tennis surface. surfaceTypeSchema = hard, clay, grass, indoor, artificial_grass, other (venue.entity.ts:146-153). The classification pipeline is tennis by construction: has_court regex 테니스장|테니스코트|...|정구장 (00563_court_evidence_guard.sql:82); blocklist rule R1 rejects "non-tennis sport" (:100, 00564); fee reparse keeps only 테니스/정구 tokens and drops 배드민턴/풋살/탁구 etc. (00348_reparse_gov_tennis_fees.sql:41-49). About 235 migrations touch venue tables; 79 mention tennis. venues.schema_type is SportsActivityLocation (00283_venue_spine.sql:60), generic by design. | venue schema: racket-generic. Venue pipeline: tennis-only (deliberately) |
| Copy | 82 occurrences of 테니스 across 7 ko files (clubs 31, venues 25, achievements 9, common 6, onboarding 4, venue-contributions 4, sessions 3) under packages/app/src/config/i18n/ko/. Tennis terms in the same catalog: 코트 130, 타이브레이크 13, 세트 11, NTRP 10, 게임 15, 랠리 4, 듀스 1 (grep across config/i18n). No per-sport layer exists. | mixed; needs a sport-keyed layer for terms |
| Brand and store | apps/web/app/layout.tsx:19,28,30,36 title "TwoMore — 테니스 클럽 관리", description "동호회 테니스의 모든 것", keywords include 테니스/tennis; apps/web/app/opengraph-image.tsx also says 테니스. apps/mobile/app.json carries only "TwoMore" (no category text). The wizard-era brand story is tennis ("두 개 더": balls, serves). Store listing copy not checked (X). | tennis-only copy, name itself neutral |
| Seeds and tests | supabase/functions/seed-scenario/** (13 files with 테니스), fixtures, tests. Cost only when the product splits by sport. | tennis-only, low risk |
1.3 What this says
- Sport-agnostic (about 60 percent of the operator surface): clubs, members, roles, sessions, RSVP, waitlist, dues and fees, PortOne rail, notifications, trust tier, faces, share pages, ELO core.
- Racket-generic: singles/doubles, court assignment, rotation and the king_of_court / mexicano / americano strategies, a "sets-like" container, bestOf, win-by, win-condition, the format registry and its
disciplinekey, venue schema. - Tennis-only: game/tiebreak/noAd vocabulary, NTRP and UTR bands, the venue ingestion and classification chain, surface list, the copy, the web metadata.
2. Effort per sport
Tiers: S = a sport key plus seeds and copy, days. M = new scoring seeds, rating mapping, a venue ingestion variant, a copy layer, two to four weeks. L = a different match model, or no matches at all, or a different venue world.
| Sport | Tier | Specific gaps |
|---|---|---|
| Soft tennis (정구) | S | Same court, same 2-or-4 player rotation. Scoring differs: 7-game or 9-game matches (a single "set" to 4 games with different tiebreak rules, 7 games or more is a match), no ad. Fits the current schema as a seed (games is 2..30). NTRP does not apply, so ratings are ELO plus Korean 정구 grades (not checked). Venue pipeline already half-knows 정구장 (00563:82). Copy shares nearly everything. |
| Padel | M | Scoring is tennis scoring, so the engine is reused unchanged. Gaps: always doubles; a "court" holds 4 with a different count math; Americano/Mexicano are already in generation-policy.entity.ts:50 (the strategies exist, the sport key does not); no NTRP (Playtomic uses its own 0-7 level; FIP ranks). Venue pipeline: new Naver keywords and a padel court catalogue (walls, glass), and surfaceTypeSchema has no padel-turf value. Korean market small (see §3). |
| Pickleball (to 11) | M | Rally scoring to 11, win by 2, bo1/bo3 is set.games 11, winBy 2, tiebreak null, so the validator already handles it, but the result UI is a set-and-games scoreboard with tiebreak entry (see review 8 tiebreak entry) and a pickleball scoreboard also wants a serving side and a server number (not modelled, X). Doubles-heavy, drop-in rotation and ladder play are the social norm (king_of_court fits). Rating: DUPR, not NTRP. Venue: courts are often tennis courts with lines, courts per site varies, new category keywords. |
| Badminton (21, bo3) | M | The largest Korean racket sport by club. Rally to 21 win-by-2 cap 30 (games max 30 allows it), bo3. Gaps: a game is a "set" (copy: 게임/세트 meaning flips), courts are 1 per 4 players but sessions often run many courts in one gym, shuttle cost on the dues side (dues already generic). No NTRP; Korean grading is letter classes (A to D, 급수) and a club-level 급수 system, which would need its own rating display (not checked). Venue pipeline: gyms and 체육관, not "courts", the hardest part. Existing clubs already run on Band and KakaoTalk (W, not quantified). |
| Table tennis (to 11) | M | Rally to 11 win by 2, bo3 to bo7: the engine allows bestOf 1/3/5 only, so bo7 is a schema change. Singles-dominated, round-robin league plus 급수 culture. Venue: 탁구장 is a commercial gym type with different data. Smaller session sizes and 1:1 match model. |
| Squash | M | Rally to 11, win by 2, bo5 (schema fits), strictly singles, one court per match, strongly commercial venues. Small Korean market (X). Rating via Squash Levels or club grades. |
| Futsal | L | A team sport: a match is team vs team with a roster, not a pairing of 2-4 players. matches has team1/team2 player arrays shaped for 1 or 2 per side; goals, no sets; rotation is "split N players into balanced teams" (a different generator and the existing GenerationPolicy axes would need new members). Ratings per player in a team context differ. Venue: 풋살장 (the fee reparse already treats 풋살 as a noise sport, 00348:41). Biggest Korean club category (W: 축구·풋살 18.4 percent of club joiners in the 2022 survey). |
| Running / hiking crews | L | No matches. Sessions, RSVP, dues, notifications, trust: reusable. Everything on the games/records/rating side is dead weight; "records" would mean distance and pace. Venue becomes route/meeting point. Competes with running apps and Band. It is a different product that happens to share the 총무 workflow. |
Summary of what generalizes in one line: a rally-scored or tennis-scored sport that is played by 1 to 2 people per side on a fixed court is M or S. A team sport or a no-match sport is L.
3. Market
3.1 Korea
- Overall participation (W): the 2025 national sports participation survey (MCST) reports 62.9 percent weekly participation (30 minutes or more), up 2.2 points; the top sports named are walking 40.5, bodybuilding 17.5, hiking 17.1 (MT, Newsseoul, MCST). The racket sports are not in the published top three, and the per-sport table was not reachable (X).
- Club (동호회) joiners by sport, 2022 survey (W, older than the 2024-2026 window, labelled as such): soccer/futsal 18.4 percent, golf 16.7, badminton 9.7, bowling 7.7, tennis 7.1 (result as quoted by a search summary, original not opened). Reading: by club joiners, badminton is bigger than tennis; futsal bigger than both.
- 대한체육회 registered members per sport (X): a search found no per-sport numbers. Regional examples exist (Ulsan: 53 sports, 2,530 clubs, 143,920 members) but not national by sport. One unverified source puts registered badminton hobbyists around 2 million in 2020 (low-quality source, do not cite in a decision).
- Pickleball in Korea (W, press estimate): a 2026 Segye article, as summarised by search, says Korea passed 10,000 pickleball hobbyists, with about 100 clubs and a 14-court dedicated site on the Han River (search result, not opened). A 2025 Korea JoongAng Daily piece describes pickleball and padel as the hybrid sports rising in Korean courts (Daum mirror). Both are early; 10,000 is about a rounding error next to badminton or tennis.
- Padel in Korea (W): Seoul social venues exist (JoongAng); no court count found (X). Asia as a whole has about 4,600 padel courts in 1,700 clubs, roughly 2.2 million amateur players, up 20 percent in 2025 (FIP).
- Incumbents (from the repo's own research,
docs/research/market-and-revenue-research.md, not re-verified here): Naver Band (free group tool with calendar, polls, attendance), KakaoTalk (distribution layer, about 48 million domestic monthly users), and two Korean bracket apps for tennis (Modo, KDK). They are sport-agnostic, which is why a sport-specific operator tool wins only on the sport-specific jobs (rotation, score, rating). Share by sport (X).
3.2 Global
| Sport | Latest number | Source |
|---|---|---|
| Tennis | 106 million players, +25.6 percent over five years (ITF Global Tennis Report, released 2024-11-28; no newer global report as of Feb 2026). Asia 35.3M, Europe 29.6M, North America 28.8M; 698,034 courts, 175,000+ coaches. | Tennis Tour Talk, ITF via search |
| Padel | 19.4 million players, 58,300 courts, 20,900+ clubs (Playtomic 2026 report, 2025 data). 2024: 50,436 courts, 15,933 clubs. The report is by Playtomic with PwC Strategy&, not Deloitte as the brief said. Federation figures are higher (FIP cites 35M+). | Playtomic, Club + Resort Business, The Padel Paper |
| Pickleball | United States only: 24.3 million players in 2025 (+22.8 percent), +171.8 percent in three years; core players (8+ times a year) 7.48 million. 2024 was 19.8 million. (SFIA 2026 topline, 2025 data.) | SFIA, Kitchen Pickle, The Dink |
Cross-read: tennis is 5 to 6 times padel by players and the only one with a 100-million base outside the US. Pickleball is a US phenomenon in the data; no global SFIA-style figure was found (X). The owner's "take over the amateur tennis market across the world" (review 6) is a 106-million-player market; no expansion is needed to make that large enough.
3.3 Where the club-organiser jobs match and differ
| Job | Same across racket sports | Differs |
|---|---|---|
| Sessions, RSVP, waitlist | Fully the same (fixed slot, capped players, late cancel, no-shows). | Number of playing spaces (courts), slot length, typical size (badminton 12 to 30 in a gym vs tennis 4 to 12). |
| Dues, fees, payments | Fully the same (venue rental split, monthly dues, transfer confirm). | Consumables (shuttles, balls), hall rental vs court rental. |
| Draws (대진) | Same core: balanced pairs, rotate partners, avoid repeats, courts. | Tennis club doubles uses ELO-balanced pairs; badminton and table tennis clubs often use 급수 grade balancing; futsal splits teams. |
| Scores and records | Same job: capture a result, settle disputes, keep a history. | Scoring tree (games/sets/tiebreak vs rally to N), best-of, cap rules. |
| Ratings | Same job: a fair number so the draw is fair. | Which external scale members recognise (NTRP vs DUPR vs 급수 vs padel level). ELO as the internal truth transfers. |
| Venues | Same job: a place to play, a map pin. | What a venue is (tennis court vs gym vs padel club), how to find it, surface list. The most sport-specific data. |
4. Recommend
4.1 Decided by evidence
- The natural boundary is "racket sports played on a court", not "any club", and tennis stays the wedge. Reasons: (a) the code's seams already sit there (
discipline, TODS-style registry, theGenerationPolicystrategies that are padel/pickleball vocabulary, schema_typeSportsActivityLocation); (b) the operator jobs in §3.3 are identical for racket sports and diverge for team and no-match sports (L tier); (c) the repo's own roadmap already says "The wedge is not 'generic sports app'" (docs/architecture/roadmap.md:36); (d) tennis is 106 million players, so there is no pressure to widen for size. - Expansion order, if and when a second sport is real: soft tennis (S), then pickleball and padel (M, same venue type or tennis-like, same scoring family, the growth sports), then badminton (M but the largest Korean racket club base; gym venues are the cost), then table tennis and squash. Futsal and running crews are not on the path: they are separate products sharing the spine.
- Counter-argument kept visible: by club joiners (2022 survey) badminton outranks tennis in Korea. If the aim were Korean volume alone, badminton before pickleball is defensible. I rank pickleball and padel first only because they reuse the tennis scoreboard and venues; the badminton data is older than the window and the venue cost is high.
- Build now as cheap insurance (each is a column or an interface, no UI):
- a
sportkey onclubs(TEXT NOT NULL DEFAULT 'tennis', aligned withmatch_formats.discipline), one migration and one entity field. This is the single piece every later split hangs on. - treat
disciplineonmatch_formatsas the sport key and add the field to the format selector query, so a second sport's formats are rows, not code. - keep new tennis-specific vocabulary behind the i18n catalog (already the rule) and do not add new literal "테니스" outside it.
- a
- YAGNI until a second sport is real: per-sport scoring UI, rally scoreboards,
bestOf7, padel and pickleball venue ingestion, per-sport rating scales (DUPR, 급수), a sport-keyed copy layer for terms (코트 vs 체육관, 세트 vs 게임), a team-sport match model, surface list changes, store-listing variants. Renamematch_sets.team1_gamesetc. only when a rally sport lands.
4.2 The owner's taste (not decided by evidence)
- Whether the public promise is "tennis" or "racket sports". Tennis focus is stronger marketing for a first audience of 40-to-60-year-old club secretaries; "racket sports" signals ambition and costs clarity.
- Whether the first non-tennis sport should be chosen for Korean volume (badminton) or product reuse (pickleball, padel, soft tennis).
4.3 What this means for the name's scope
Pick a name that says racket and court, not tennis-the-word and not any sport: names built on "두 개 더 / two more" (TwoMore) and rally/serve words (Rallia, Naishot) survive a move into padel, pickleball and soft tennis, whereas a literal "tennis" name would not and a sport-free name gains nothing for a product whose edge is the tennis-shaped 총무 workflow. The tagline can say tennis; the name should not have to.
5. Questions for the owner
- Is the one-year promise "the best app for running a tennis club", with a sport key added quietly, or "racket sports" from day one? (Recommendation: the first, no public change.)
- If a second sport comes, do you pick it for Korean volume (badminton) or for product reuse (pickleball, padel, soft tennis)? (Recommendation: reuse first.)
- May I queue the
clubs.sportmigration as the one insurance item? It changes no screen.
Implementation (checklist, only if the owner accepts 4.1 point 3)
- [ ]
supabase/migrations/NNNNN_clubs_sport_key.sql:ALTER TABLE public.clubs ADD COLUMN sport TEXT NOT NULL DEFAULT 'tennis', a CHECK or FK to a smallsportsregistry, GRANT unchanged; entity, mapper, adapter, types in one commit (entity-change checklist). - [ ]
packages/app/src/domain/entities/club.entity.ts: addsport;docs/canon/clubs.mdone line. - [ ]
packages/app/src/domain/entities/match-format.entity.ts: keepdiscipline, document it as the sport key; the format selector filters by the club'ssport. - [ ] Docs: note in
docs/architecture/roadmap.mdthat the boundary is racket sports and the order is §4.1.2. - [ ] No change: scoring engine, rotation, venue pipeline, i18n,
apps/web/app/layout.tsxmetadata, until a second sport is real.