Skip to content

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

DimensionIncumbent (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/screenP1, 3×
Information architecture7 stacked zones, two-tier chip strip, full-width bannersone question per line (where/when/how/join), harmony contract, worst-case-validated no-truncationP1
Canon complianceViolates 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 contractP1
Per-card runtime cost6+ 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 themOne model built per list (bulk), card renders props only; faces/friends queries removed from the card entirely (moved to detail)P1
List virtualizationAll 15 render sites are plain .map() — zero FlashList, contradicting performance-and-memory.md:234 which prescribes itRollout includes FlashList adoption per the perf docP1 (by fixing what the incumbent ignored)
Component weight1,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 themP1
Attention/urgency surfacingFull-width banners for 4 states only; the payment-hold countdown (RSVP-cancelling!) is invisibleSessionAttention: 9 audience-gated, ranked kinds incl. payment-hold, host actions, rain, waitlist — one strip per card by constructionP1 (new capability)
A11yPartial: 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 rightP1
Maturity / battle-testingShipped, QA'd, edge cases exercised (banner precedence, notes, expansion, save flows)New code — needs its own QA passIncumbent
Image identity todayPhoto-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

RiskAnswer
NCP tile volume/cost on scrolling feedsTiles 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 coordinatesLevel-14 frame ≈ 1.5km — verified tolerant on real tiles; close-zoom satellite reserved for coord-confident heroes only (the gating cascade).
Offline/failed tileRealMap pattern ships a neutral surface fallback; the card is fully legible with no image.
NAVER watermark ToSBottom-left preserved on every tile; overlays anchor top/right by contract.
Regression of loved detailsThe 정보 이동 지도 (comparison state) maps every incumbent item to its new home or to session-detail — nothing dropped silently.
New-code bugsPhased 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

  1. Domain (pure, tested): deriveSessionAttention in domain/session-attention.ts + extend SessionInfoModel with the attention field; table-driven unit tests per rule (the resolver matrix in the wireframe is the test fixture list).
  2. 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.
  3. Component: SessionCardV2 = the P1 layout (VenueLine/WhenLine/ChipsLine/StatusJoinLine + TopStrip + tile), Tamagui-only, from the wireframe spec; the wireframe is the visual acceptance criterion.
  4. Rollout: feed-by-feed across the 15 sites, starting with the 경기 tab (highest traffic, simplest data); FlashList adoption per feed as it migrates.
  5. 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.

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