← Cockpit Wireframes
Headers · 헤더 아이콘
Approved — build queue
Venue Finder · 코트 찾기
ArchiveStrip Map · 세션 스트립 전체 시나리오Headers · 헤더 아이콘Compaction — 일정 · 코트 · 스코어카드Compaction — 클럽 상세 · 홈Compaction — 기록 · 프로필Live anchor — 진행 중 밴드Density · 카드 밀도토스트 — 리디자인 제안위저드 내비게이션 — 결정 보드드래프트 복원 — 무음 복원클럽 조 편성 — 모델 결정 보드Session Detail — Redesign · 일정Venue Detail — Redesign · 테니스장Profile Detail — Redesign · 프로필Tournament Detail — Redesign · 대항전Applicant Detail — Redesign · 가입 심사Canvas Migration — 회비 · 멤버 · 규칙Record Detail — Redesign · 기록Venue Filters · 코트 필터 재구성
Decided 2026-09-17 — shipped; kept as the decision record
Interactive kit · grounded in code

Headers · 헤더 아이콘

헤더 아이콘 재정비 — CURRENT vs PROPOSED
Every screen's top-right icon(s), rendered from the real lucide path data at the real props each screen passes today. All 24px icons below share ONE root cause: icon.tsx exposes no strokeWidth override, so every icon in the app renders at lucide's default (2) — never the app's own intended visual weight.
PROPOSED — 5 rules
1. Size — every header icon 24px (the xl token). Never 28/2xl (today's home bell) or any other ad-hoc size.
2. Stroke — 1.8, not lucide's default 2. Every header icon, no exceptions.
3. Color$textSecondary (#455D49) at rest, not $text (#131A14). Reads as quiet chrome, not "black solid filled."
4. One wrapperHeaderIconButton everywhere (bare icon, invisible 44pt hitSlop). Never Button variant="ghost" (a visible chunky 36px frame) and never a raw Pressable with a text-character glyph.
5. Two-glyph cap — top-right holds at most TWO glyphs. Slot 1 = the screen's single signature action (stateful/badged icons get priority). Slot 2 = (MoreHorizontal) overflow for everything else — the ONLY overflow glyph, except a gear when the menu genuinely IS settings. Slot 1 may instead be ONE labelled HeaderTextAction (초대 코드, 관리 — decided 2026-09-17, U-05/U-09): 44pt, cardBody 600, $primary ink as its one affordance cue, $textInverse over a hero; a secondary navigation task only, never the page's primary action. Never three. Stateful/badged icons never collapse and keep their semantic color: bookmark saved = filled $primary green; bell/DM badges keep their amber/red tone.
bookmark→ saved
overflowvs settings
홈 · HomeMainTabShell headerRight → NotificationBell
PROPOSED
3

24px (xl), $textSecondary, stroke 1.8 — matches the spec everywhere. Badge tone (amber = obligation / red = unread, deriveBellState) never resizes to hide its own signal.

일정 상세 · Session detailDetailShell headerRight → SessionSaveButton(bookmark) + Share2 + MessageCircle, gap $3
PROPOSED
양재 클레이코트토 7/26 · 오전 10:00

Two-glyph cap: Bookmark (stateful, slot 1 — never collapses) + ⋯ (slot 2). The ⋯ overflow holds 공유 and 일정 단톡방 만들기 (use-session-detail-data.ts's headerMenuActions — group chat, not a 1:1 host DM).

프로필 · Own profileMainTabShell headerRight → MessageSquare(dot) + Share2 + Settings, gap $3
PROPOSED
프로필

Two-glyph cap: MessageSquare (badged, slot 1 — never collapses) + Settings (slot 2 — genuinely settings-adjacent, so gear stays gear, not ⋯). Stat-card share relocates into the screen's own content/설정, not the bar.

클럽 · Club list tabMainTabShell headerRight → ClubJoinHeaderAction (HeaderTextAction label=t().common.inviteCode)
SHIPPED
클럽
초대 코드

Decided 2026-09-17 (U-09): the manual-join entry is a labelled 초대 코드 text action, not a bare key glyph whose meaning lived only in its accessibilityLabel. Same primitive as 관리 on club detail — one text-action grammar.

클럽 · Club detailDetailShell headerRight → HeaderActionsMenu (Button variant=ghost size=sm trigger, default Settings gear)
SHIPPED
클럽
관리

Decided 2026-09-17 (U-05): officials get a labelled 관리 HeaderTextAction in slot 1 (→ ClubAdminScreen); ⋯ (MoreHorizontal, HeaderIconButton 24px/1.8/secondary) keeps 공유하기 (any member) and 나가기 (destructive, non-owner members). A plain member sees only ⋯. The unlabelled admin entry inside the menu was the H5 failure the usability audit named.

코트 · Venue detailDetailShell headerRight → HeaderActionsMenu (same trigger pattern as club-detail)
SHIPPED
코트

venueActions (venue-detail-screen.tsx): 길찾기 / 지도 보기 / 예약 사이트 / 예약 전화 — navigation + map + booking, NOT settings, 2-4 items depending on whether the venue has a booking URL/phone. Trigger is ⋯ (MoreHorizontal default), tight, 24px/1.8/secondary — never a gear. Court finder's own header action (courts-header-action.tsx) separately overrides this trigger to a MapPin glyph — a still-open third inconsistent overflow glyph, not shown as its own row here.

알림 · Notification centerDetailShell headerRight → ActionButton(모두 읽음, tertiary) + HeaderIconButton(Settings)
PROPOSED
알림
새 소식모두 읽음

모두 읽음 moves off the bar entirely — the bar is gear-only (Toss-minimal, one glyph). The action relocates to sit next to what it acts on: a quiet right-aligned link on the 새 소식 band header, in the list content below.

대화 · DM threadDetailShell headerRight → raw Pressable, Text role=cardTitle literal '⋯' (both 1:1 and group)
PROPOSED
김민준

A real MoreHorizontal icon at 24px/1.8/secondary via HeaderIconButton, replacing the typed '⋯' character — same color, now with real touch/hitSlop semantics. Opens the participants sheet (group) or the report/block menu (1:1).

오버플로우 메뉴 · Overflow menu — open stateheader-actions-menu.tsx's Popover.Content — what pushing ⋯ / the gear actually opens.
Overflow-menu surface rules
Surface — a compact ModalPanel action list (RN native Modal — always renders above content, never z-fights or flickers). Tamagui's JS-portal Popover is BANNED from product overflow menus — the anchored dropdown (HeaderActionsMenu) is retired entirely, not just re-skinned.
Contents — secondary actions ONLY. The primary CTA stays in BottomCtaBand; stateful toggles (bookmark) stay in the bar — neither ever moves into this menu.
Destructive — last, $error, separated from the rest by ONE divider (not a divider per row).
Row anatomy — 20px icon ($textSecondary; $error when destructive) + cardBody label, 44px min row height. NO per-row dividers.
Chrome — a centered card (no anchoring, so no popover arrow), radius $4, $card bg over a dimmed scrim, ~208–224px wide, close affordance optional for a menu this small. Max ~5 rows.
ModalPanel is the app's proven native-Modal overlay (STYLE-3 canon), replacing the JS-portal Popover that used to flicker, z-fight, and intermittently lose interactivity in production. header-actions-menu.tsx is already exactly the ModalPanel spec above (20px $textSecondary icons, 44px rows, ONE divider before the first destructive row, centered scrim card, default MoreHorizontal trigger).
세션 ⋯
양재 클레이코트토 7/26 · 오전 10:00
공유
일정 단톡방 만들기

Session-detail's own proposal, now shipped as HeaderActionsMenu (a centered ModalPanel): the 2 actions the two-glyph cap moves off the bar are 공유 and 일정 단톡방 만들기 (use-session-detail-data.ts — a group chat, not a 1:1 host DM; label text below updated to match).

코트 ⋯
코트
길찾기
지도 보기
예약 사이트
예약 전화

venueActions (venue-detail-screen.tsx) in a ModalPanel: 20px icons, 44px rows. None of these 4 are destructive, so — per rule — no divider at all.

클럽 ⋯ · SHIPPED
클럽
공유하기
관리
나가기

clubActions is 3 rows — 공유하기 (Share2, any member) → 관리 (Settings icon, admin-only) → 나가기 (LogOut, destructive, non-owner members) — surfaced via header-actions-menu.tsx: a centered ModalPanel (no arrow), 20px $textSecondary icons, 44px rows, ONE divider directly before 나가기. The trigger is the component's default MoreHorizontal (⋯) — no call site overrides triggerIcon.

대화 1:1 ⋯
김민준
신고하기
차단하기

Today's 1:1 ⋯ already opens a ModalPanel — just a bespoke one (차단 first/$error, 신고 second/$text, both icon-less plain rows, no scrim-centered card chrome). PROPOSED converts it to the SAME canonical ModalPanel menu as every other screen, preserving today's tone assignment: 신고하기 stays default, 차단하기 stays the destructive row.

DM 그룹 채팅 예외 — the group thread's ⋯ keeps opening the participants ModalPanel (roster + leave), unchanged. That's a CONTENT surface, not an action menu, so it's exempt from every rule on this panel.

Spec (grounding, anatomy)
grounded in 18 source file(s)
packages/ui/src/app-header.tsxpackages/ui/src/header-icon-button.tsxpackages/ui/src/header-actions-menu.tsxpackages/ui/src/icon.tsxpackages/ui/src/main-tab-shell.tsxpackages/ui/src/button.tsxpackages/ui/src/box-sizes.tspackages/app/src/presentation/components/sessions/session-save-button.tsxpackages/features/home/src/cards/notification-bell.tsxpackages/features/home/src/notification-center-screen.tsxpackages/features/sessions/src/session-detail-screen.tsxpackages/features/sessions/src/venue-detail-screen.tsxpackages/features/profile/src/profile-screen.tsxpackages/features/clubs/src/club-detail-screen.tsxpackages/features/clubs/src/shared/club-join-header-action.tsxpackages/ui/src/header-text-action.tsxpackages/features/messaging/src/dm-thread-screen.tsxpackages/app/src/config/themes/classic-grass.theme.ts