Skip to content

Decision log — autonomous ownership pass (2026-09-17)

Status: Active · Date: 2026-09-17 · Owner brief: "Can you perform as much of the work as possible?" — the owner, fatigued, asked Claude to take over auditing, reviewing, planning, fixing and improving. Product questions that were parked as owner calls are decided here with evidence, built, shipped to preview, and left on the page QA board as one line each for veto. A reversal is an OTA; more than two reversals on one theme become a decision law in the relevant canon.

Only-owner items stay only-owner: a phone in hand (device checklists in device-checklists.md), external accounts (Kakao, PortOne, Apple, GitHub billing — external-blockers.md), and the go-live call.

How to read a line

ID · decision · why · rejected · where it shipped. The board entry for the same ID carries the same line as its reply and is marked fixed with the commit; veto by replying on the entry.

Wave 1 — small, unambiguous UX (usability audit Tier 2)

  • U-09 · "초대 코드" is a labelled header text action on the 클럽 tab (new HeaderTextAction primitive; the label reuses common.inviteCode) · the bare key icon was the only manual join entry and its label was screen-reader only; a labelled text action is what Toss and KakaoTalk use for secondary header tasks · rejected: keeping the icon with a tooltip (no hover on phones), a full-width row above the list (competes with the list) · shipped: HeaderTextAction (packages/ui), label reuses common.inviteCode. Commit: c36b5111.
  • U-10 · the empty 내 클럽 state leads with 클럽 탐색 and offers 초대 코드로 가입 as its secondary action; the pinned 클럽 만들기 band is secondary while the list is empty and primary once the user has a club · a newcomer is almost always joining, not founding; two rival primaries is the H3 failure · rejected: removing the band on the empty state (founders exist; 밴드 keeps both) · shipped: EmptyState gained secondaryActionLabel/onSecondaryAction; band weight follows isMyClubsEmpty. Commit: c36b5111.
  • U-05 · officials see a labelled "관리" header text action on club detail that opens the existing 관리 hub (ClubAdminScreen); ⋯ keeps 공유 and 나가기 · the hub already existed behind an unlabelled ⋯; a visible label copies the host-tools pattern the audit praised on session detail · rejected: a nav item (the six-tab nav is a ratified place-surface spec), a gear icon (universal but silent for this audience) · shipped: club-detail-screen.tsx 관리 text action beside ⋯ (club.detail.admin-action). Commit: c36b5111.
  • U-25 · the 조 line on member rows is hidden while the club has fewer than two groups; with two or more, one legend line sits above the list ("조는 클럽 순위로 나눈 실력 그룹이에요") · one group means the tag carries no information; a legend on first appearance is the H2 pattern · rejected: a per-row tooltip (hidden gesture) · shipped: showsGroupTag rule + resolveMemberGroupName shared derivation; legend clubList.clubGroup.legend. Commit: c36b5111.
  • U-35 · the 복식 파트너 추천 entry moves from the admin roster (/clubs/[id]/members) to the 멤버 pane every member sees on club detail · it is a personal discovery tool, so it belongs on the surface members open, not the treasurer's management list · rejected: the profile tab (loses the club context the suggester needs) · shipped: row moved to members-pane.tsx, EntryIconBubble shared under clubs/shared. Commit: c36b5111.
  • U-14 · one money vocabulary: 회비 = club dues, 참가비 = session fee, 정산 = the host's split that sets a fee; dues actions 납부 확인 / 면제 / 납부 취소, session-fee actions 입금 확인 / 면제 (the shared verb key said 납부 on session rows against the house style); the session payments screen has one title (참가비) with 입금 현황 and 정산 sections plus one sentence relating the split to the fee and its 1,000원 rounding; the dues roster adopts the session roster's inline verb-pair rows and drops the per-row ⋯ (H4: nothing only behind a menu), 메모 opens from the row itself · rejected: kebab on both (hides the money verbs), three inline actions (does not fit 390px) · shipped: DuesRow inline pills (DuesActionModal deleted), session pills use common.confirmDeposits, title common.fee, costSplitting.splitExplainer, terminology guide ruling. Commit: c36b5111.
  • U-15 · "전체 납부 확인" on the dues roster runs one atomic mark_dues_paid_bulk RPC under RLS with a single confirm naming count and total · twenty client-side mutations (the pickup approve-all shape) would be twenty requests and partial failures on a money action · rejected: per-row only · shipped: migration 00648 mark_dues_paid_bulk (SECURITY INVOKER, RLS-authorised, pgTAP), useMarkDuesPaidBulk, handleMarkAllPaid, clubDues.actions.markAllPaid. Commit: c36b5111.
  • U-18 · the 듀스 toggle is wired into the scorecard rules strip (듀스 / 노애드 badge) and its hint now says players see it in the match rules · it is real player information (advantage vs no-ad); validation cannot use it because scores are stored per set · rejected: deleting the toggle (loses a rule players ask about), inert text · shipped: RulesStrip renders 듀스/노애드 (matchRules.noAd), hint reworded, review step uses the pair. Commit: c36b5111.
  • U-33 · 형식 seeds from club.preferredFormat the way 구성 already seeds from memberComposition, still changeable; the wizard law gains the sentence that a value the same club authored is an answer, a literal default is not · the null-start law guards against fake defaults, not against the club's own choice · rejected: keeping the null start (re-picking the same format on every session) · shipped: create-session-step3.tsx seeds format from club.preferredFormat; wizards.md law amended. Commit: c36b5111.
  • U-12 · cardMeta moves from 13/18 to 14/20 in the one role definition (576 sites follow) · the most common secondary text on every row was below the 14px floor the audit set for this audience · rejected: a new intermediate token (the ladder already has 14), touching micro (numerals only) · shipped: text.tsx cardMeta $4/$4; canon/styling.md and components.md restated. Commit: c36b5111.
  • U-13 · card and row titles wrap to two lines through one shared constant; a lint keeps numberOfLines={1} off cardTitle text unless a comment justifies it · six sites hand-rolled the clip and real venue names ("대치유수지체육공원 테니스장") lost their tail · rejected: per-site edits without a gate (would drift again) · shipped: NAME_LINES (packages/ui/text-lines.ts) at 17 sites, ListRow titleLines, lint no-single-line-title (v0.38.1), AGENTS.md COMP-13. Commit: c36b5111.
  • U-21 · every tier credential is [plain 구력 line, federation line]; TierRangeSelector shows the plain line and keeps federation detail behind one 자세히 보기 · a treasurer describing a casual club met KTA/KATO/KASTA jargon first · rejected: deleting the federation lines (players who know them use them) · shipped: tierCredentials = {plain, details}, TierRangeSelector disclosure tier-range.credentials.details, common.showDetails (was ui.errorDetailsShow). Commit: c36b5111.
  • U-24 · support channels are configuration: SUPPORT_EMAIL plus optional Kakao channel URL and phone from env; the contact screen renders what is configured with Kakao leading · the address was a literal typed twice and a Kakao channel needs an account only the owner can create — the code is ready for the URL · rejected: a placeholder Kakao row (shipped-dead surface) · shipped: config/support.ts (SUPPORT_EMAIL, EXPO_PUBLIC_SUPPORT_KAKAO_CHANNEL_URL, EXPO_PUBLIC_SUPPORT_PHONE), openExternalUrl, contact rows per channel, EXT-15. Commit: c36b5111. Owner action: create the channel and set the env var.
  • U-07b · one first-encounter grammar: a caption under a wizard field the first time a term appears (구성 now; 로테이션 has no bare wizard label), a single InfoHintButton for terms with a sheet (replacing four hand-rolled triggers), plain values on session detail ("20분마다 교대"), 인터클럽 renamed to the app's own 대항전; D-day stays because it is native Korean usage · rejected: tooltips (no hover), a glossary screen (nobody opens it) · shipped: InfoHintButton (packages/ui), glossary.composition caption, sessionDetail.specRotationValue, 인터클럽→대항전 in signals, the Android channel name and the push copy map; terminology guide “First Encounter” ruling. Commit: c36b5111.
  • U-07c · trust-tier thresholds are mirrored from the SQL into a pure rules module with a parity gate against the defining migration, documented in canon, and explained in a TrustTierInfoSheet reachable from every place the tier shows · the push said the tier changed and no screen could say why · rejected: narrating the SQL by hand in copy (drifts silently) · shipped: domain/rules/trust-tier.rules.ts, scripts/check-trust-tier-parity.mjs (in yarn check, AGENTS.md DATA-25), docs/canon/trust.md, TrustTierInfoSheet on own and public profile. Commit: c36b5111.
  • U-06 · 참가비 is editable while the session is open and no payment row exists; the 00404 trigger already enforces that boundary, the form now exposes the field and explains the lock once a payment exists; 종목 / 로테이션 / 모집 마감 stay locked · a typo no longer means cancelling and recreating; the boundary is the one the database already draws · rejected: unlocking the deadline (drives derived recruitment status) and format/rotation (generated rounds depend on them) · shipped: migration 00649 (HINT on the 00404 trigger + participation_fee in the session-update signal), fee row in config-form.tsx (FeeEditModal), sessionDetail.feeLockedByPayments, lock note reworded. Commit: c36b5111.

Wave 3 — design-queue items

  • SD-IA (session-detail IA order, parked 2026-09-01) · the shipped order is ratified as canon: hero → StatStrip → live participant bar → refund banners → notice → payment gate → participants → matches → info → host callout → host tools → danger zone, one pinned CTA by state precedence · it already puts identity, the viewer's one action and money before everything else, and no evidence favours a reorder; canon/sessions.md §116 still described a two-surface layout that no longer exists and is rewritten to the shipped order · rejected: info before participants (the hero already carries venue/date/time; the roster is the social proof that drives RSVP) · fix shipped alongside: MatchesSection gated at the call site like its siblings (cancelled sessions no longer double a hairline) · Commit: 747cc80f.
  • LIVE-ANCHOR (duplication audit D11 open item; wireframe live-anchor proposal) · one SessionLiveAnchor band (진행 중 · elapsed · 라운드 N/M · 코트 N) fed by one shared useSessionLiveFacts(sessionId) derivation on the spectator scorecard, the match board and session detail; pinned where the shell already pins (scorecard: above the tabs), first flow child under the header/hero on the other two · the three surfaces signalled "live" three different ways and none stated elapsed, round or courts; the formatter written for it had zero call sites · rejected: the two stacked half-height bands (two seams for one fact), a new DetailShell pinned slot (a shared-primitive change for one band; revisit if the owner wants it truly fixed on match board) · Commit: 747cc80f.
  • U-32 (create-club quick start) · step 1 gains a "기본 설정으로 빠르게 만들기" toggle (off by default) that seeds real values — 구성 혼합, 형식 복식, 실력 무관, 균형, 게스트 허용, recruit target from the schedule-derived suggestion — and the wizard's step sequence skips 스타일/인원/회비/소개 (name → where → when → review, four screens); the review keeps every seeded row editable with a 기본값 badge; ClubPlanningSection gains the missing maxMembers editor so "fill in later from 클럽 설정" is true for all three axes · nine screens and two arithmetic receipts were the H8/H12 failure, and every skipped field already had a submit-time default (create-club-submit.ts) plus a settings editor except 인원 · rejected: a separate short wizard (two wizards to keep in sync), quick as the default (item #13 wants card-critical answers deliberate; the toggle is the deliberate act) · Commit: 747cc80f.
  • REGIONS (multi-region settings entry, courts-derived vs explicit) · courts stay the single source of truth (owner ruling 2026-08-08) and the derivation moves server-side: a trigger on court_venues recomputes the club's primary base and club_play_regions (first distinct district = primary, up to three additional) whenever a club's courts change; the wizard's submit no longer overwrites each created court's own district with the club's; club settings shows a read-only 활동 지역 line with a link to 코트 관리 (a manual district field remains only for a club with no courts) · the junction was populated once at creation and never again, a club that moved courts kept stale regions, and an explicit region picker would reintroduce the 활동 지역 UI the owner removed · rejected: an explicit picker (two truths), client-side re-derivation on every venue edit (misses edits made elsewhere) · Commit: 747cc80f.
  • PHOTO-ROW (사진 업로드 auto feed row product definition) · one row per member per day: "OO님이 사진 N장을 올렸어요" from that member's approved club_media of the day, thumbnails on the shared FeedPhotoStrip, tap opens the album, under 전체 only, capped at three recent like the match row, supplied by the club-detail bundle RPC (no post row, no trigger) · the codebase already names MediaUploadFeedRow and the 전체-only rule for auto rows; grouping by day matches how albums are browsed and avoids five rows for five photos; the gallery picker gains multi-select so a batch is one action · rejected: a system post per upload (five rows per outing; contradicts the stated design), a trigger-batched post (time-window upserts in SQL for a display concern) · Commit: 747cc80f.
  • VENUE-RESIDUALS · the 경기 tab's 테니스장 찾기 pane is the explore entry point; /courts stays as the web and deep-link address of the same content; the 미확인 pick-mode question is closed by the 2026-09-06 canon (no trust filter, no 미확인 badge) — the vestigial verification: 'all' parameter is deleted and canon states that the inclusive policy covers pick mode · rejected: a second in-app entry to the same list, an intent-conditional verified-first default (would reverse the owner's own ruling) · Commit: 747cc80f.

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