Skip to content

Information-Flow Audit — Right Information, Right Timing

Status: Active

Date: 2026-07-31 · Remediation: not started · Owner directive: "thorough research on the types of information and the information flow to ensure we are delivering all the relevant information at the right timing."

Three research lenses, all code-grounded (files:lines verified against what actually runs): the session lifecycle (participant + host), the club/social lifecycle, and the delivery channels themselves (signals, push, bell, home feed, polling, cron). Verdicts per information item: RIGHT-TIME / TOO-LATE / TOO-EARLY / NEVER-DELIVERED / WRONG-SURFACE / DUPLICATED.

1. What works (validated, no action)

The event-driven core is healthy: session created/updated/cancelled, 대진 published, match started/completed, score-confirm requests, waitlist promotion, payment hold created/submitted/confirmed, application received/decided, dues due-soon → overdue → severe escalation, refund obligations (hourly), DM (5s focused poll + push), ELO/tier changes — all fire in real time or on adequate cadences, with correct audiences. Host trust surfaces at the RSVP decision moment (pickup-host-card.tsx). The admin join-review dossier is exemplary (evidence card at decision time). Quiet hours + payment-category 24/7 exemption follow Toss/KakaoPay convention.

2. Confirmed bugs (fix; not design questions)

#BugEvidence
B1Board-post notifications silently stop after the first post, forever, per club-member pair. post_new's dedup key has no post id/time bucket (00109:463), and signals_emit's ON CONFLICT DO UPDATE (00107:464-474) never refreshes payload (stale first-post title) nor push_sent_at (permanently excluded from push by 00114:81). The identical bug was found and fixed for DMs in 00226 — the fix was never applied to post_new.server, high confidence
B2rsvp_deadline_soon runs once daily (05:30 KST, next-24h window) — any deadline set after the tick and expiring before the next one gets zero warning through any channel. The client closing strip renders only if the user happens to open the app. 00385:616-690.server cron
B3Push-invalidation busts the wrong RSVP query key. use-notification-handler.ts:136-146 invalidates rsvpKeys.bySession (detail) but never rsvpKeys.bySessionIds (the list/card key, STALE_TIME.frequent = 2min) — "1 spot left" cards sit stale during active push traffic for that very session. The owner-named "last seat" scenario, live today.client
B4Host gets no push reminders for their own session unless they self-RSVP'd. Both reminder crons loop rsvps.status='confirmed' only (00421:98-119,173-184); the client attention model explicitly special-cases viewer.isHost (session-attention.ts:327-337) — the push path never honors that intent.server cron
B5Bell understates urgency for signal-class obligations. openObligationCount counts only admin-attention items (notification-bell.tsx:15-23); an urgent unread obligation signal (e.g. session cancelled) renders the red unread tone, not amber attention — the two-state bell doesn't implement the AlertClass split it's built on.client

3. Silent information — needed, no channel at all

  • Trust consequences: trust_tier_changed, late_cancel_penalty_applied, no_show_reported — declared, zero emitters. A system that gates eligibility by trust tier takes silent action against users (fairness/appeal problem, not just UX).
  • Money: dues_refund_issued — never emitted (flagged "reserved" in 00108:15). A member owed money learns nothing.
  • Host ops: session_underfilled_alert, session_late_cancel_alert — never emitted; a host has no proactive warning their session is at risk.
  • Payment-hold mid-countdown: only hold_created and hold_expired exist — no "5 min left" warning on a 10-minute clock; hold pushes are severity high (not critical) so quiet hours suppress them entirely for late-night fee RSVPs; the feed-level countdown is a static snapshot, not a ticking clock (session-strip-ui.tsx:182-211).
  • Social: friend_rsvped_to_my_session — TypeScript-only type, zero server emitters; friendsGoing is computed on detail but used solely for roster sort order — never rendered as a count anywhere.
  • Attendance review (host, post-session): unresolved no-show count computed only inside the attendance screen; the NavRow to it carries no badge (unlike the parallel applications row), no signal exists.

4. Wrong surface / wrong moment

  • Dues cost vanishes at the club decision moment: shown on the discover card (club-card.tsx:274), absent from public-club-profile-screen.tsx — the screen with the apply CTA. Deep-link arrivals never see the cost before applying.
  • Cancellation/refund policy sits below a persistently-tappable RSVP CTA: the pinned BottomCtaBand is actionable from mount; the fee/cancellation terms live mid-scroll in SessionIdentityCard. Commit precedes disclosure.
  • Head-to-head record: queryable per opponent (use-head-to-head.ts) but reachable only by self-navigating to 기록 — never linked from a matchup/session surface where it matters.
  • Distance: WhereInfo.distanceKm is a declared model gate never populated by either builder; club discovery has no distance concept at all — for an LBS-flavored Korean sports app this is a first-class decision fact missing at both discovery surfaces.
  • Host identity on session cards: a browsing user learns who hosts only after opening the detail page.
  • Club activity recency: schedule intent (next session + cadence pattern) substitutes for proof of actual play — a dormant club with one future session reads as active.
  • Admin aggregate: individual admin events push correctly, but the aggregate (bell attention tone) is poll-only (STALE_TIME.frequent), and the home admin strip was retired 2026-07-31 → closed-app admins rely on individual pushes alone. Growth analytics are pull-only behind 관리 → 분석 with no spike/drop alerting.

5. Missing flows (nothing exists)

  • Club onboarding: a new member's first render of the club is identical to a veteran's; club_member_joined notifies everyone except the newcomer. No welcome, no dues-setup nudge, no board/intro pointer.
  • Waitlist odds / queue position: joining a waitlist is blind (no position, no movement updates to non-promoted members); applying to a club is blind (no typical-response-time, no competition signal).
  • Progression/gamification signal family (8 types): expected — Phase 6 not shipped.

6. Channel timing parameters (reference)

Push cron _/2min (batch 100) · receipts _/20min · hold expiry _/5min · auto-advance statuses hourly:15 · starting-soon _/15min (2h window, matches client) · rsvp-deadline daily 05:30 (B2) · dues daily 09:00 · refunds hourly · reminders daily 06:00 · weather ~15min staggered/AQI hourly. Client tiers: realtime=60s, frequent=2min, standard=5min, stable=15min; all Postgres realtime channels retired 2026-06-01 — "realtime" labels mean 60s polls. Stale doc comments in use-rsvps.ts/use-sessions.ts still describe the retired channel.

  1. Wave 1 — bugs (B1–B5): post_new dedup fix (per-post key or DM-style DELETE pattern), event-driven or hourly rsvp-deadline tick, add bySessionIds to push-bust, host-inclusive reminder crons, bell obligation count from signal class.
  2. Wave 2 — silent money/trust/host-ops emitters: dues_refund_issued, trust trio, underfilled/late-cancel alerts, hold-expiry warning (+ decide quiet-hours severity for holds), attendance-review badge.
  3. Wave 3 — decision-moment surfacing: dues on the public profile CTA band (sublabel recap already in the hub wireframe), cancellation terms adjacent to the CTA, distance + friends-going on cards (model slots already exist), head-to-head link from matchups, host identity on cards.
  4. Wave 4 — flows: club onboarding card stack, waitlist position, activity-recency proof (pairs with the club-hub redesign already in wireframe review).
  5. Hygiene: delete or emit the dead signal types (match_assigned_to_court, match_result_posted, rsvp_* trio…), fix stale realtime doc comments, consider a mechanical guard asserting every SignalType is either emitted or explicitly listed as planned.

Full per-stage evidence tables live in the session transcripts of the three research agents (2026-07-31); this file is the durable synthesis.

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