Skip to content

Duplication audit — components, pages, in-page information (2026-09-08)

Status: Active · Owner ask: "audit across components and pages to remove duplicate components; streamline and compact the user experience and information; reduce cognitive burden."

Method: three read-only research passes (routes/pages, components across packages/uipackages/app/presentationpackages/features, in-page information redundancy on the ten main screens), each citing file:line, then a fix batch for everything that removes a duplicate without changing what a screen is for, and a decision list for everything that reshapes a page.

1. Pages and routes

FindingEvidenceOutcome
Standalone pickup list = the 경기 tab's 찾기 pane, verbatimactivity-pickups-screen.tsx reproduced activity-screen.tsx PickupFinderTab (same hooks, controls, cards)Retired. routes.activityPickups now opens the tab on the pane (/activity?tab=pickup_finder); /activity/pickups (mobile + web) redirects there
Standalone upcoming-sessions list = the 내 경기 pane; zero inbound linksactivity-upcoming-sessions-screen.tsx; no routes.* helper, no pushRetired; /activity/sessions (web) redirects to the tab
Standalone club discover = the 클럽 tab's 탐색 pane, verbatimclubs-discover-screen.tsx vs club-list-screen.tsx DiscoverTabRetired. routes.discoverClubs = /clubs?tab=discover; /clubs/discover (mobile + web) redirects
Club-scoped venue detail = /venues/[id] with an unused clubIdclubs/[clubId]/venues/[venueId].tsx mounted the same VenueDetailScreen, one callerDeleted; club-venues-screen opens routes.venue
코트 header shortcut on the 경기 tab duplicated the adjacent 코트 paneCourtsHeaderActionroutes.courts, whose body is the pane already mounted one tap awayRemoved (component deleted)
Broken link: 기록 zero-match CTA pushed /activity/discover-pickups, a path with no route filerecords-screen.tsxFixedroutes.activityPickups
Phantom helpers with no route file and no callercourtDetail, seasonStandings, clubSeasonStandings, partnerMatching, discoverPickupsDeleted from routes.ts
/ranking, /leaderboard, /clubs/[clubId]/sessions/[sessionId] (mobile)one-line redirects for old URL shapes; web 301s the first twoKept — aliases, not surfaces
home.tsx, clubs/index.tsx, profile/index.tsx, records/index.tsx, activity/index.tsx<Redirect> gateways into the grouped tab routesKept — the alias layer routes.ts documents
s/[sessionId], lb/[clubId], m/[matchId], p/[userId]share/deep-link receivers that resolve then redirectKept (note: lb/ lands on the club page, not a leaderboard — rename candidate)
match-board / tournament-board / scorecardhost editor, bracket, spectator view — distinct roles, distinct callersKept
Pane vs full screen (club members/sessions, records period/day/history, 코트 pane vs /courts)documented "see-all" drill-downs; courts-directory-screen.tsx is explicitly one body with two mountsKept

2. Components

The 2026-09-05…08 normalization passes already hold: every sheet is ModalPanel, no raw Modal/bottom-sheet, no TouchableOpacity, one avatar chain, one meter chain, one tier-badge chrome. Six real clusters remained:

ClusterMembersConsumersOutcome
Banner re-implemented for the archived-club noticeclub-status-banner.tsx vs ui/banner.tsx1Banner tone="muted" added; wrapper collapsed
StatTile card chrome re-implementedui/stat-grid.tsx vs ui/stat-tile.tsx (StatStrip already correct)2StatTile gains delta; StatGrid composes StatTile
Sheet header re-implementedscorecard/score-sheet-header.tsx vs ui/app-header.tsx3AppHeader chrome="bare"; file deleted
Dead Korean-calendar rail duplicating DayCalendarPicker's cell rulespresentation/components/date-rail.tsx (0 renderers)0Deleted
Two hand-rolled single-thumb pan slidersui/value-slider.tsx, ui/hue-slider.tsx bar7 (unchanged)One single-band slider core; both render variants of it
Continue-chip vs SelectionChip (radius differs)format-chooser-sheet.tsx1Left — likely intentional radius

False positives worth recording so nobody re-audits them: VenueRow vs DirectoryVenueCard (different data sources, owner ruling 2026-09-04/07); SessionCompactRow vs SessionCardV2 (embed vs feed card, share RecruitmentMeter); MemberRow vs ListRow (exempted in list-row.tsx); AppHeader / CollapsingTopBar / MainTabShell (three roles); EmptyState → QueryErrorState → OfflineEmptyState (composition, not copies).

3. In-page information redundancy

Fixed in this batch (pure duplicates, no page reshaped):

  • Venue detail: share offered twice (icon row + ⋯ menu) → icon row only; reopen-report offered twice (icon row + closed banner) → banner only; the Bookmark glyph meant both "save venue" and "claim ownership" → ownership gets its own glyph.
  • Spectator scorecard: the round state word ("진행 중"/"예정") sat in the badge AND the adjacent summary string (two i18n strings for one word) → badge only.
  • Club card model computed chip groups, style chips and cadence labels the card never rendered → pruned.
  • 경기 tab: header 코트 icon duplicated the 코트 pane (above).

Owner rulings (2026-09-08, boards /wireframes/compaction-*)

#RulingFollow-through
D1YES — CollapsingTopBar on every detail page with a graphical hero; the hero must not restate a session title; the flat one-fact-per-row metadata under hero titles is rejected in favour of a compact identity pattern applied to every hero; detail-page side spacing must match club detailBuild: shell-level collapse wiring; identity-pattern research → canon → sweep; rail audit
D2KEEP the StatStrip as the one numbers row, each page choosing its key factsCanon note; no restatement elsewhere
D3Key badges live BESIDE the title, never below; header live dot staysSupersedes hero canon 2026-09-06(g); part of the identity pattern
D4NO — 정보 stays expandedClosed
D5, D6Keep the three full screens AND the 홈 schedule block (else auto-post/pin upcoming meets in the feed)Closed
D7YES merge ELO/reliability; heatmap stays OPEN; trend card = delta onlyBuild
D8YES merge PillNav + scope selectorBuild
D9YES attendance once + reliability into the ELO sheet; profile meta (클럽, 구력) become badges, with a merged club-logo+name badgeBuild after the identity pattern; reconcile the two attendance sources first
D10NO — title-left, bell-right symmetry staysClosed
D11YES rules once per session; open: a visual anchor at the top of live pagesBuilt (rules once). Anchor proposal drawn: /wireframes/live-anchor — one pinned 진행 중 band (pulse · elapsed · 라운드 N/M · 코트 N) under the header on scorecard, match board, session detail
D12NO collapse; each detail page gets its own passClosed

4. Prevention

  • Route helpers must have a route file and at least one caller; routes.test.ts now locks the tab-pane openers so a "full list" screen cannot quietly return beside its pane.
  • A pane that exists inside a MainTabShell is the surface; a standalone screen for the same list is a duplicate by definition (canon components.md, Tabs & chips).
  • canon/components.md records the new Banner muted tone, AppHeader chrome="bare", StatTile delta, and the single-band slider core as the canonical homes.

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