SessionCard v2 — Implementation Decision Packet
Status: Accepted Gate: owner go received 2026-07-27 — implemented same day Date: 2026-07-27 Design spec: the P1 card + SessionAttention model at /wireframes/densityIncumbent audit: engineering audit of session-card.tsx subtree, evidence cited below by file:lineOutcome: Implemented d21f1fba (P1 compact card live on all 10 session-list sites), preview OTA group 59fd4d4e, 2026-07-27. The old session-card.tsx subtree is retained in the tree for rollback, pending on-device QA.
1. The question
The new compact card (P1: square satellite tile · titleless WHERE/WHEN/HOW/JOIN grammar · conditional TopStrip driven by a SessionAttention resolver · tap-through) is fully specified after 20+ design iterations. Is it better or worse than the shipped SessionCard — and should we implement it?
2. Scorecard
| Dimension | Incumbent (shipped) | P1 (new design) | Verdict |
|---|---|---|---|
| Density | ~400px/card measured from code (hero 132 + WHEN 44 + chips ~56 + participation ~40 + ~70 gaps + 32 padding) → ~1.2/screen | ~130px calm, ~155 with strip → ~3.5/screen | P1, 3× |
| Information architecture | 7 stacked zones, two-tier chip strip, full-width banners | one question per line (where/when/how/join), harmony contract, worst-case-validated no-truncation | P1 |
| Canon compliance | Violates the model canon — zero calls to buildSessionInfoModel in the subtree; re-derives gates ad-hoc (sessionStatusDateTone, sessionRecruitmentState, fee/type labels, banner precedence: session-card.tsx:28-31, 156-175, 280-341) | Specified as a pure projection of buildSessionInfoModel + deriveSessionAttention — zero ad-hoc derivation by contract | P1 |
| Per-card runtime cost | 6+ hooks fire per card in production: useSessionWeather, useMyRsvp, useMySessionPayment, useVenue (no bulk sibling exists), useSessionRsvps, host-gated useSessionApplications, plus useFriends + useProfilesByIds inside the meta section (session-card.tsx:181-295, session-meta-section.tsx:152,172). Bulk props exist (Protocol A) but zero of 15 consumers pass them | One model built per list (bulk), card renders props only; faces/friends queries removed from the card entirely (moved to detail) | P1 |
| List virtualization | All 15 render sites are plain .map() — zero FlashList, contradicting performance-and-memory.md:234 which prescribes it | Rollout includes FlashList adoption per the perf doc | P1 (by fixing what the incumbent ignored) |
| Component weight | 1,871 LOC subtree, ~9 levels deep, SessionHeader takes 31 props; dead code in production (size="compact", layout="default" — no live caller) | Est. ~450–550 LOC: 5 shared line components + strip + shell; zero new primitives beyond them | P1 |
| Attention/urgency surfacing | Full-width banners for 4 states only; the payment-hold countdown (RSVP-cancelling!) is invisible | SessionAttention: 9 audience-gated, ranked kinds incl. payment-hold, host actions, rain, waitlist — one strip per card by construction | P1 (new capability) |
| A11y | Partial: card/WHEN/save labeled; VenueHero image, HostBadge, SessionTopBanner, SessionWeatherGlance unlabeled (venue-hero.tsx:84 et al.) | Spec'd fresh; fewer interactive elements (whole card = one Pressable) — smaller a11y surface to get right | P1 |
| Maturity / battle-testing | Shipped, QA'd, edge cases exercised (banner precedence, notes, expansion, save flows) | New code — needs its own QA pass | Incumbent |
| Image identity today | Photo-first cascade already live (moderated venue_media → map → glyph) | Same cascade, satellite inserted (coord-confident only); smaller tiles (64px vs 132px full-width) | Tie (same cascade, cheaper crop) |
3. Risks of switching — and their answers
| Risk | Answer |
|---|---|
| NCP tile volume/cost on scrolling feeds | Tiles are keyed per venue (not per session) at fixed params → high cache-hit; the edge fn already serves Cache-Control: 24h + s-maxage 7d (static-map/index.ts:122); RN Image caches by URL. Net: fewer image bytes than today's 132px hero per card. NCP quota worth a monthly glance. |
| Centroid-grade coordinates | Level-14 frame ≈ 1.5km — verified tolerant on real tiles; close-zoom satellite reserved for coord-confident heroes only (the gating cascade). |
| Offline/failed tile | RealMap pattern ships a neutral surface fallback; the card is fully legible with no image. |
| NAVER watermark ToS | Bottom-left preserved on every tile; overlays anchor top/right by contract. |
| Regression of loved details | The 정보 이동 지도 (comparison state) maps every incumbent item to its new home or to session-detail — nothing dropped silently. |
| New-code bugs | Phased per-feed rollout (below) + the existing wireframe as the visual acceptance spec. |
4. Verdict
Implement. This is not a marginal restyle: the incumbent independently needs surgery on three counts the audit proved — canon violation (no SessionInfoModel), a per-card hook storm that Protocol A was supposed to prevent (bulk props exist, no consumer uses them), and missing virtualization the perf doc already prescribes. Implementing P1 as SessionCardV2 is the single vehicle that fixes all three while shipping the density/IA win the owner has now design-approved. The alternative — patching the incumbent to canon + bulk + FlashList — costs most of the same work and keeps the 400px card.
5. Migration plan
- Domain (pure, tested):
deriveSessionAttentionindomain/session-attention.ts+ extendSessionInfoModelwith the attention field; table-driven unit tests per rule (the resolver matrix in the wireframe is the test fixture list). - Data layer: bulk venue query (the missing Protocol A sibling), feeds build
SessionInfoModel[]once per list via existing bulk hooks; card receives the model — zero hooks inside the card. - Component:
SessionCardV2= the P1 layout (VenueLine/WhenLine/ChipsLine/StatusJoinLine + TopStrip + tile), Tamagui-only, from the wireframe spec; the wireframe is the visual acceptance criterion. - Rollout: feed-by-feed across the 15 sites, starting with the 경기 tab (highest traffic, simplest data); FlashList adoption per feed as it migrates.
- Deletion: retire the old subtree (~1,871 → ~500 LOC) once all 15 sites migrate; club cards follow on the same harmony contract (owner sequencing).
6. Effort
Domain + tests ≈ small; data-layer bulk plumbing ≈ the real work (per-feed); component ≈ mechanical from spec. Suggested slicing: (a) domain+model, (b) card+first feed (경기), (c) remaining feeds + FlashList, (d) deletion + docs. Each slice independently shippable via OTA.