Skip to content

Detail pages — club-detail canon vs flat primitives (spacing audit)

Status: Accepted · Applied: 2026-09-05 · Owner ruling: "think of the spacing and alignment in the club detail page as the canonical configurations." Club detail wins on every conflict; the flat build spec (detail-pages-flat-build-spec-2026-09-05.md §1) was corrected from this audit.

All values read from live code on the 2026-09-05 tree. Tokens (packages/ui/src/tokens.ts): $1=4 $2=8 $3=12 $4=16 $5=20 $6=24 $7=32 $9=48. Text roles (packages/ui/src/text.tsx): pageTitle 24/32·700 · cardTitle 16/24·600 · cardBody 14/22·400 · cardMeta 13/18·500 · badge 12/18·600 · displaySm 20/28·700 · displayMd 28/36·700.

1. Club-detail canon (the numbers)

DimensionValueSource
Page rail$4 = 16club-identity-header.tsx, post-row.tsx, members-pane.tsx
Bannerfixed 132px visible (+ top inset), not aspect-drivenclub-album-banner.tsx BANNER_VISIBLE_HEIGHT
Banner → identity$3 = 12club-place-header.tsx
Identity → nav (the one big break)$4 = 16club-place-header.tsx (section-rhythm law)
Chrome → content12px (CHROME_CONTENT_GAP)use-collapsing-banner.ts
Pane header rhythmuniform $3 = 12home/mission-header.tsx
Identity avatar56px, 3px ring, flush below the banner (overlap retired)club-identity-header.tsx
Avatar ↔ title$3 = 12club-identity-header.tsx
Club name rolepageTitle 24/32·700club-identity-header.tsx
Name → chips · chip row gap$1 · $2club-identity-header.tsx
Section titlescardTitle 16/24 (SectionHeader variant="list")members-pane.tsx, ranking-tab.tsx
Row vertical padding$3 = 12 (PostFeedRow, MemberRow, DuesRow, LeaderboardRow)each row file
Row min height44 (TOUCH_TARGET.MIN); 36 dense (ClubScheduleRows)member-row.tsx, club-schedule-rows.tsx
Row avatar tier40 MD (member/dues) · 32 SM (post author) · 28 XS (rank)box-sizes.ts
Leading → text gap$3 = 12member-row.tsx, dues-row.tsx
Title → sub inside a row$0 (MemberRow, PostFeedRow) / $1 (DuesRow) — canon is splitrow files
Hairline ownershipmixed: 4 of 6 rows full-bleed, MemberRow rail-insetrow files vs members-pane.tsx contentContainerStyle
List bottom padding48 ($9)pane files
CTA band$3 top · $4 sides · $4 bottom · 1px top hairlinebottom-cta-band.tsx (shared byte-for-byte with flat pages)
Underline nav48px cell + 1px hairline; label 14px 800/600pill-nav.tsx

2. The diff (before this audit was applied)

DimensionCanonFlat primitive / page (before)Resolution (applied 2026-09-05)
Section → section seam$4 (one big break)FlatColumn gap $6FLAT_COLUMN_DEFAULT_GAP$4
Chrome → content12FlatColumn top $4FLAT_COLUMN_DEFAULT_TOP$3
Hero title rolepageTitle 24/32displayMd 28/36 (MediaHero, TypeHero)both heroes → pageTitle
Hero avatar56px flush, overlap retiredMediaHero avatar overlaps media by −34pxoverlap removed; avatar sits flush in the identity row (club-identity-header anatomy)
Media band heightfixed 132pxMediaHero 16:9 (≈219px at 390 wide)default → fixed 132px band; aspect becomes the explicit opt-in
TypeHero topbanner→identity 12paddingTop $2$3
Row avatar tier40 MD on member rowssession roster rows SM 32roster rows → MD
Hero avatar tier56profile XL 80, join-review LG 44, club profile 56AVATAR_SIZE.HERO = 56 everywhere
Section title rolecardTitle 16/24cardTitle 16/24 (spec text said otherwise)no code change; spec §1.4 corrected
Row padding / min height$3 / 44 (36 dense)sameno delta
Leading → text gap$3sameno delta
Title → sub gap$0 or $1 (canon split)$1keep $1 (DuesRow precedent); spec's $0.5 non-token removed
Hairline ownershipinconsistent on canonuniformly rail-insetflat pages keep rail-inset (the more disciplined reading); canon rows are a follow-up
Stat cellsonly precedent is bordered AdminSnapshotStrip (admin surface)hairline-flush StatStripkeep StatStrip (canvas law: tint/borders only as recess); admin strip is not a place surface
Doubled seamsnonematch-board round row and record scope row: paddingBottom $3 + hairline, then column gapkept: the padding is the hairline's own inner space, not a second seam; with $4 gap the stack is 28px
CTA bandshared primitiveidenticalno delta

3. Group-spread law (owner, same day)

"If we are using tabs or buttons or action buttons or any other group of components, we need them evenly spread out to fill the width (minus the canonical spacing)." Recorded in docs/canon/components.md and the build spec §1.5 item 7. Applied to ActionIconRow (was a centered cluster of 52px items with $5 gap; now every item is flex={1} across the rail). Scrollable pill rails (match-board rounds, record scope chips) and badge previews (achievement tiles) are not peer groups and stay left-anchored.

4. Follow-ups (not blocking)

  • Canon's own hairline ownership is split (full-bleed vs rail-inset); a later pass should pick one for the club-detail rows.
  • StatStrip cell minHeight 56 is an off-grid literal; keep until a device pass says otherwise.

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