Skip to content

Lane I — tags vs chips, top-down row hierarchy, and the attention inventory of the session and club cards (2026-10-09) ​

Status: Reference · Date: 2026-10-09 · Evidence appendix for list-surfaces-spec-2026-10-09.md. Frame, crop and script paths refer to the session scratch directory (not committed); App Store frames are re-fetchable from the store URLs in the tables.

Status: Evidence + spec for the owner rulings of 2026-10-09 (a) one passive label primitive, (b) top-down ink, (c) a compact session card without the bottom row, (d) icons are action targets. Read-only lane: nothing under /home/sungkyu08/Dev/twomore-v2 was changed. Paths are relative to that repo unless stated. Frames are under bench/{crops,b_crops,crops_c}; fetched raw sources are under bench/lane_i/.

Method notes: m3.material.io, fluent2.microsoft.design, polaris docs and Apple's HIG pages are script-rendered, so I read each system's shipped tokens or source on GitHub, Apple's developer.apple.com/tutorials/data/design/human-interface-guidelines/<page>.json mirror, and Seed's rootage JSON plus its llms text exports. Values marked [relay] came through a search summary or my own knowledge and were not read from a primary page. Peer-frame readings are mine, from marketing/store frames (±2 pt, see section 7).


0. The six findings that matter ​

  1. Our label vocabulary is split in two shapes and the same recipes are worn by labels and controls. Badge is a rounded rectangle ($2 8px / $3 12px, packages/ui/src/badge.tsx:88-96), but at least nine other sites hand-draw pills. Recipe A ($surfaceSecondary + borderRadius="$7" full pill + $textSecondary) is worn by three passive labels (club-photo-seam.tsx:228-235, club-identity-header.tsx:84-98, public-club-profile-screen.tsx:568-577) and by two pressable things (ActionPill, packages/ui/src/action-pill.tsx:27,51-71; the session chip in post-row.tsx:481-500): a tappable pill and three labels are indistinguishable. Recipe B ($primarySubtle + $primary + weight 600/700) is the selected-chip recipe (selection-chip.tsx:122-123, 58 consumer files) and is also worn by the display-only VenueLink (venue-link.tsx:88-115), MarkerPill (marker-pill.tsx:14, 4 files) and the tier label (tier-badge-utils.tsx:46-56, 13 files via two wrappers, at 8px radius).
  2. The market splits passive labels from controls almost everywhere, with named warnings. Seed Badge is "정적인 정보 전달" (static information) and not to be made clickable (https://seed-design.io/llms/components/badge.txt); GOV.UK "Do not make a tag interactive" (https://design-system.service.gov.uk/components/tag/); Carbon "Do not use tags as links" (https://carbondesignsystem.com/components/tag/usage/); Polaris keeps non-interactive chip apart from clickable-chip (https://shopify.dev/docs/api/app-home/polaris-web-components/actions/clickable-chip). Only Carbon, Fluent Tag and Atlassian Tag fold selectable/dismissible modes into one Tag; Carbon explicitly warns against multi-function tags, Fluent splits off an "Interaction tag", and Atlassian bars status from Tag. So one passive Tag + one control Choice is the position the systems support; "chips and badges are the same component" is supported only for the passive half.
  3. Passive-label systems converge on two emphasis steps, and ours has twelve variants. Seed weak/solid (+ outline), TDS fill/weak, Polaris base/strong, Atlassian subtle/bold, Fluent tint/filled (+ outline, ghost), GOV.UK tint only. Badge has 12 variants but literal call sites use four (neutral 39, warning 11, surface 5, live 3); primarySubtle and outline have no consumer at all. About 19 of the 39 variant="neutral" call sites print a fact (round-rules strip x6, amenities, payment methods, duration, distance, D-day, format), not a status, which is the T3 finding in code.
  4. Top-down is what the session peers do; the board's T2 phone is the exception. In the 11 session/listing rows I read, the first body line is the heaviest in 8 (Smash, TennisFriends, Munto, Band, Greenlight, Somoim, Spond, Heja); the three exceptions are commerce rows that promote a price (당근, Frip) and Flap (time lighter above a bold venue). Lane B counted whatever leads as "bold 15-20 pt, everything else 11-13 pt grey" (lane-b-category.md:70). The T2 "after" card puts the 18/700 time on line 2 under a 16/600 venue (docs/twomore_design_system/wireframes/visual-tone.jsx:654-705 (time at :705)), which is the middle-line promotion the owner rejected. Seed (16 → 13) and M3 (16 → 14) both step down in size between headline and supporting text; our ListRow is 14/600 over 14/500, a zero size step.
  5. Lead the row with the facet that is the list's sort key. Of the four day-grouped or date-filtered peers, three lead the text with time/date (Flap, Greenlight, Band) and one carries the date in a left date block (Spond); all four ungrouped feeds lead with the title (Smash, Munto, TennisFriends, Frip). Our lists are grouped by day (SessionDayHeader, D10), so the first line is the time at 16/600, the venue second at 14/500, and the venue photo carries the "where" identity. NN/g: users see about two words of each list item and the left edge is scanned (https://www.nngroup.com/articles/first-2-words-a-signal-for-scanning/); venue names repeat down a day, times never do.
  6. The session card carries 12-14 visual objects against a peer median of about 7; the compact row is 5. Current card (browse viewer): tile, venue, bookmark, 3 emoji + 3 texts, fee, status word, bar, count = 13 (+1 corner chip on the map tile). The compact row: photo, time, venue, one meta line, one state element (count text, or one Tag only when something outranks it) = 5; bookmark moves to the detail header (it already exists there). Height falls from about 130 px to 96-98 px (-25%). Zero inline glyphs (peers draw 0-3, median 2), zero bars (0 of 11 peers draw one), at most one accent.

Also found: the session-card-v2.tsx header says the fee left the card and the lead chip sits in the title seat (:45-47, :53, :61) but the code still renders both in the closing line (:473-485); and canon/components.md:29 (fact glyphs only for boolean facts) conflicts with canon/components.md:57 (emoji may lead any fact). See section 7.


1. Passive labels, controls and counts across design systems ​

1.1 Table ​

Column key: Passive = read-only label (name · shape · radius · height · font). Control = selectable/actionable (name · shape · selected state). Input = dismissible. Count = numeric/dot badge.

SystemPassive labelInteractive selectionDismissible inputCount badgeRule text (quoted)
Material 3 (tokens: material-web v0_192; Android docs)None named. Badge is count/dot onlyFilter chip: rect, corner-small 8px, 32px, 1px outline, label-large 14/500 (0.875rem, weight medium), icon 18; selected = secondary-container (tonal) + check. Assist, Suggestion chips: same 32px/8pxInput chip (32px, avatar 24px, trailing remove)Badge: small 6px dot, large 16px tall, corner-full, label-small 11/500, colour errorChip docs (paraphrase): chips appear dynamically as a group of interactive elements, buttons are for consistent calls to action; badges show notifications, counts or status on navigation items and icons.
Apple HIG (JSON mirror)None. No chip, no tag, no lozengeNo chip. Segmented control: one choice affecting "an object, state, or view", "no more than about five segments on iPhone", nouns; pop-up button shows the current selectionmacOS Token field (input token)Tab badge: "a red oval containing white text and either a number or an exclamation point""Reserve badges for critical information so you don't dilute their impact and meaning." "Prefer displaying text in a list or table." "Keep item text succinct."
당근 Seed (rootage JSON + llms)Badge: rect, radius 6px large / 4px medium, 24 / 20px tall, 12 / 11px, weak weight 500, solid/outline 700, 6 tones; Tag Group: inline icon+text items with separators, 12/13/14px, tones neutral-subtle/neutral/brand, regular or bold; Manner Temp Badge: pill, 20px min, 11/700, 10 level coloursChip: radius.full, 32/36/40px, label 14/500; solid selected = bg.neutral-solid (ink) + fg.on-neutral-solid; outlineWeak (recommended for selection) selected = neutral-weak fill + neutral-contrast stroke. Segmented control item: 34px, full radius, 16/700, selected label fg.neutralChip "Input" = value + Remove button in the suffix; Filter Bar shows ClearNotification Badge: dot (small) or number (large, ≤4 chars, never 0, top-right, disappears once viewed)Badge: "정적인 정보 전달" (static information); paraphrase: keep to one or two badges per object, never clickable (use Chip, Toggle Button or Action Button). Tag Group: "카드형 콘텐츠에서 메타데이터 표시" (card metadata); 6-10 chars. Tabs (paraphrase): do not combine Chip-style Tabs with Filter Chips on one screen.
Toss TDS mobileBadge only: fill / weak, sizes xsmall..large, colours blue/teal/green/red/yellow/elephant; no px published (docs index lists no chip, no tag)None. Filtering uses Segmented Control and Tabnonenone listed"Badge… 항목의 상태를 빠르게 인식할 수 있도록 강조" (emphasise status for quick recognition); fill = "higher saturation, visually striking". ListRow right slot may hold Badge.
Shopify Polaris (web components + Badge.module.css, Tag.module.css)Badge (static, system-generated status): radius --p-border-radius-200 (8px [relay]), padding 2/8, weight medium, tones info/success/warning/critical/neutral/caution, color base/strong, size base/large; Chip (non-interactive label for user-created tags)Clickable chip (interactive, color subdued/base/strong); legacy Tag: radius 8, min-height 24px (large), bg fill-tertiaryClickable chip removable; Tag removablenoneBadge: "Keep labels to 1-2 words"; paraphrase: base size in table cells, list items and badge groups, and use chip rather than badge for user-created labels. Clickable chip (paraphrase): use the non-interactive chip for labels users do not need to click or remove.
AtlassianLozenge (status/important attribute; bold by default; maxWidth 200; sentence case); Tag (descriptive metadata, 10 colours, maxWidth)Tag can link (non-interactive otherwise)Tag: removable by default, isRemovableBadge: numeric only, 6 semantics + 5 boldLozenge: "important labels that need to be easily noticed". Tag: "subtle, compact label used to categorize and organize content"; paraphrase: not for status. Badge: "For tallies or scores, use a badge." px values not on the usage/code pages.
IBM Carbon (_tag.scss, usage page)Tag read-only: pill (radius 16px; v12 flag uses a token), sm 18 / md 24 / lg 32px, label-01 (12px)Selectable: selected = $layer-selected-inverse + $text-inverse (ink); also Operational (opens popover)Dismissible: dismiss button = tag heightnone on the Tag page"Do not use tags as links that direct you to an entirely different page."; paraphrase: avoid tags with multiple functions (accidental clicks); titles under 20 characters.
Fluent 2 (useBadgeStyles, useTagStyles, usage page)Badge: circular default, rounded (medium radius) or square; 6 / 10 / 16 / 20 / 24 / 32px, caption1Strong (12) / caption2Strong; appearances filled/ghost/outline/tint; Counter Badge, Presence BadgeTag: 20/24/32px, rounded or circular; filled/outline/brand; selected = colorBrandBackground + on-brand text. Interaction Tag: primary action + dismissTag with dismiss icon (12/16/20px)Counter Badge ("numeric values such as tallies and scores")Badge: "system-generated data that people can't change." Tag: "a representation of a value that someone has picked."
GOV.UK (_mixin.scss)Tag: status only; padding 2/8/3/8, radius 1px, font 19, 9 colours as tint-80 bg + shade-50 text, regular weight, sentence casenone ("Do not make a tag interactive by making it into a link or button")nonenone"Tags are only used to indicate a status."; paraphrase: adjectives, not verbs (a verb suggests a click); dark tags were lightened because users tried to click them.
Ours todayBadge: 8px (xs/sm) / 12px (md), 18 / 26 / 28px tall, text 10 / 12 / 14, weight 600, 12 variantsSelectionChip: pill, 32 / 40px, label 12/500 → 12/700, resting $card + 1px $border, selected $primarySubtle + 1px $primaryRemovableChip (always selected look)CornerCountBadge: 18px circle, 10/700badge.tsx:2-8 calls a badge "a passive label, not a tappable control".

Sources (all fetched 2026-10-09): Seed https://seed-design.io/rootage/components/{chip,badge,tag-group,manner-temp-badge,segmented-control-item,list-item}.json, https://seed-design.io/llms/components/{badge,tag-group,notification-badge,tabs,segmented-control,manner-temp,list}.txt, https://seed-design.io/docs/components/chip, dimension and font-size tokens https://github.com/daangn/seed-design/blob/dev/packages/rootage/dimension.yaml and font-size.yaml (x1_5 = 6px, x5 = 20px, x6 = 24px; t1 = 11px, t2 = 12px, t4 = 14px). M3 https://github.com/material-components/material-web/blob/main/tokens/versions/v0_192/_md-comp-filter-chip.scss (container-height 32px :37, container-shape corner-small :38, flat-unselected-outline-width 1px :85, icon 18px :160), _md-comp-badge.scss (large-size 16px :57, size 6px :59), _md-sys-shape.scss (corner-small 8px), _md-sys-typescale.scss (label-large 0.875rem/medium), https://github.com/material-components/material-components-android/blob/master/docs/components/Chip.md and BadgeDrawable.md. Apple https://developer.apple.com/design/human-interface-guidelines/{tab-bars,segmented-controls,lists-and-tables,token-fields,pop-up-buttons}. TDS https://tossmini-docs.toss.im/tds-mobile/llms.txt, /components/badge/llm.txt, /components/ListRow/list-row-components/llm.txt. Polaris https://shopify.dev/docs/api/app-home/polaris-web-components/feedback-and-status-indicators/badge, /actions/clickable-chip, https://github.com/Shopify/polaris/blob/main/polaris-react/src/components/Badge/Badge.module.css and Tag/Tag.module.css. Atlassian https://atlassian.design/components/{lozenge,tag,badge}/usage, /lozenge/code, /tag/examples. Carbon https://carbondesignsystem.com/components/tag/usage/ and https://github.com/carbon-design-system/carbon/blob/main/packages/styles/scss/components/tag/_tag.scss. Fluent https://github.com/microsoft/fluentui/blob/master/packages/react-components/react-badge/library/src/components/Badge/useBadgeStyles.styles.ts, react-tags/.../useTagStyles.styles.ts, https://fluent2.microsoft.design/components/web/react/core/tag/usage. GOV.UK https://design-system.service.gov.uk/components/tag/ and https://github.com/alphagov/govuk-frontend/blob/main/packages/govuk-frontend/src/govuk/components/tag/_mixin.scss.

1.2 What the table supports ​

  • Passive/interactive split by name: Seed (Badge vs Chip), Polaris (Badge, Chip vs Clickable chip), Atlassian (Lozenge/Tag vs link/removable Tag), Fluent (Badge vs Tag/Interaction Tag), M3 (no passive label at all), GOV.UK (passive only). One component with modes: Carbon (read-only/dismissible/selectable/operational share one 16px pill), partly Fluent and Atlassian Tag.
  • Shape separates the two in Seed: Badge radius 4-6px (rect) against Chip radius.full. Our ledger row 12 (docs/design/research/colour-system-research-2026-10-09.md:22) already records this correction. M3 inverts it (chips 8dp rect, badge full) but still gives the two different shapes.
  • Two emphasis steps for passive labels, with a third outline only in Seed and Fluent.
  • Counts are a separate micro component in 5 of 9 (M3 Badge, Seed Notification Badge, Fluent Counter Badge, Atlassian Badge, HIG tab badge). Seed's rules map onto our CornerCountBadge: cap 4 chars, never 0, top-right, clear when seen.
  • Selected state for a chip is ink in Seed solid and Carbon, brand fill in Fluent, tonal container in M3; Seed itself recommends the low-prominence outlineWeak for selection lists because several selected values appear at once (https://seed-design.io/docs/components/chip).
  • Chip label size: Seed 14px (all three heights), M3 label-large 14px, Fluent medium Tag body1 14px; ours is 12px at the 40px height (badge role, selection-chip.tsx:107-111 via ChipLabel).
  • Navigation vs manipulation: Seed "Tabs는 탐색(Navigation)을 위한 것이고, Segmented Control은 조작(Manipulation)을 위한 것" (https://seed-design.io/llms/components/segmented-control.txt); Seed still ships a chip-variant Tabs (https://seed-design.io/llms/components/tabs.txt), so "chips stop being navigation" is the owner's call, not a market given. HIG: segmented control for "closely related choices that affect an object, state, or view"; use a tab bar for "completely separate sections".

2. Our inventory ​

2.1 Components, consumers (non-test files with a JSX use, grep -rlE "<Name\b" packages apps), specs ​

ComponentFileConsumersRadiusHeightFontFill / stateRole today
Badgepackages/ui/src/badge.tsx75 files (ui 1, app 17, clubs 19, sessions 27, profile 5, home 3, records 2, messaging 1) + 18 status maps$2 8px (xs, sm), $3 12px (md) :88-96xs 18 (10/14 + 2x2), sm 26 (12/18 + 2x4), md 28 (14/20 + 2x4)badge role, weight 600, size 10 / 12 / 14 badge-visual.ts:10-16, badge.tsx:10812 variants :58-86; literal uses: size xs 33, sm 30, md 0passive label, status + facts
status wrappers (XxxStatusBadge)packages/app/src/presentation/components/status/status-badges.tsxe.g. RsvpStatusBadge 6, MatchStatusBadge 5, SessionStatusBadge 4, WinResultBadge 4, ScoreStatusBadge 4, 15 more with 1-3via Badgevia Badgevia Badge18 variant maps status-variant-maps.ts: success 15 mentions, neutral 14, muted 14, warning 13, accent 7, live 5, info 4, primarySolid 2, premium 1domain status
TierBadgeChrome (Elo/Trust)app/.../tier-badge-utils.tsx:46-56EloTierBadge 7 files, TrustTierBadge 6$2$1 v-pad + emoji line boxbadge$primarySubtle, emoji + labelpassive level label
TierRangeBadgeapp/.../status/tier-range-badge.tsx:29,332 files + inline density on the session card$2/$322 / 26 / 30badge / cardMeta inlinesurface pill or bare textpassive fact
ReliabilityPillapp/.../reliability-pill.tsx:271BadgeBadgeBadge<Badge neutral sm>passive fact (named "pill")
MarkerPillpackages/ui/src/marker-pill.tsx:14-154full pill $7$1/$2 paddingbadge 600$primarySubtle or $cardpassive marker (a second passive label shape)
ContextChipfeatures/sessions/src/add-round/feasibility-tiles.tsx1$7$2 padcardMeta 600$card + 1px $borderSubtlepassive label drawn as a chip
CornerCountBadgepackages/ui/src/corner-count-badge.tsx:312 (app-nav.tsx, notification-bell.tsx)circle18pxmicro 10/700$badgeErrorBg / $badgeWarningBgcount
SelectionChippackages/ui/src/selection-chip.tsx:25,85,111-12358 files (≈45 wizard/settings/form option pickers: create-club*, create-session*, settings/, venue-correction/, venue-contributions/*; ≈10 scope/filter: match-history-scope-selector, venue-filter-bar, club-feed-chips, ranking-tab, sessions-pane, activity-metric-calendar, court-usage-panel, notification FilterChip, ...; 2 are DropdownChip/RangeChip themselves; split by path, approximate)$7 pill32 / 40 (+ hitSlop to 44)ChipLabel 12 badge role, 500 → 700 chip-label-visual.ts:20-26resting $card + $border; selected $primarySubtle + $primary 1pxcontrol
DropdownChippackages/ui/src/dropdown-chip.tsx11via SelectionChipvia SelectionChipvia SelectionChiptrailing chevron, ModalPanelcontrol (pop-up)
RangeChippackages/ui/src/range-chip.tsx2 (pickup-filters, discover-filters)via SelectionChipcontrol
RemovableChippackages/ui/src/removable-chip.tsx2$7$8 (40)ChipLabel selectedalways selected look + xinput chip (does not compose SelectionChip, own XStack)
PillNav / SegmentedTabspackages/ui/src/pill-nav.tsx:75-1357 direct + segmented-tabs.tsx; SegmentedTabs 8 (incl. main-tab-shell)track $5 20, pill $4 16$2 v-pad14/600, active $primaryText on $primary indicatortrack $surfaceTertiary + 1px $borderSubtlenavigation / view switch
ActionPillpackages/ui/src/action-pill.tsx:27,51-712$728badge 600 $textSecondary$surfaceSecondaryaction drawn as a label pill
TogglePillpackages/ui/src/toggle-pill.tsx:17-189$344x24 tracknone$primary / $bordera switch named "pill"
GlyphFactRow chip densitypackages/ui/src/glyph-fact-row.tsx:136-1684 (club card x2, type-hero, media-hero; session card uses inline)Badge surfaceBadge sm 26one Badge + emoji per factpassive facts as chips
MetaSegmentspackages/ui/src/meta-segments.tsx31n/an/aMETA_TEXT tiers text-grammar.ts:40-45text + MetaBreakinline facts (the T3 destination)
hand-drawn pillsclub-photo-seam.tsx:228-235, club-identity-header.tsx:84-98, public-club-profile-screen.tsx:568-577, venue-link.tsx:100-112, banner-capacity-cluster.tsx:67 (labels); post-row.tsx:481-500, arrival-picker.tsx:46-61, pickup-host-card.tsx:190-205, club-album-banner.tsx:215-229 (controls)9 sites$724-32badge / cardMeta$surfaceSecondary, $primarySubtle, $overlayBackdropboth
mis-namedTournamentContinueChip (format-chooser-sheet.tsx:403) is a full-width action banner, PlayerIdentityChip (6) is a row, FilterChip (notification-center) is a SelectionChip wrapper1 / 6 / 1name drift

Raw count: borderRadius="$7" appears 28 times in 22 files outside tests (grep -rn --include=*.tsx 'borderRadius="\$7"' features app/src/presentation ui/src); only 2 files (selection-chip.tsx, removable-chip.tsx) are in the selection-chip family.

2.2 Places a label is drawn as a chip, or the reverse ​

Labels drawn as chips or pills (should be Tag):

  • club-identity-header.tsx:84-98 emoji + label static identity "chips" (24px floor), club-photo-seam.tsx:228-235 capacity pill, public-club-profile-screen.tsx:568-577 capacity pill with chevron, venue-link.tsx:100-112 venue "link chip" (display-only per canon/components.md:18), feasibility-tiles.tsx ContextChip, MarkerPill x4.
  • GlyphFactRow default chip density: 5 mint chips on a discover card (club-card.tsx:322,329), the hero (type-hero.tsx:96, media-hero.tsx:261).
  • ~19 of 39 <Badge variant="neutral"> call sites print facts: round-rules-strip.tsx:58-64 (six Badges in a row: set format, target games, win-by, tiebreak, draw, deuce), amenities.tsx:39, payment.tsx:27, venue-confidence.tsx:242, practice-log-screen.tsx:177 (duration), directory-venue-card.tsx:262 (distance), club-schedule-rows.tsx:119 and club-session-card.tsx:142 (D-day), match-row-header.tsx:96 (format), challenge-accept-modal.tsx:57 (type), king-of-court-ladder.tsx:258. Seed's cap (paraphrase) is one or two badges per object; canon/components.md:26 says chips are a closed whitelist, one row.

Controls drawn as labels:

  • ActionPill (28px, $surfaceSecondary, $7) and the post-row session chip are tappable and look exactly like the four passive pills above.
  • TournamentContinueChip is an action with a "chip" name and a $primarySubtle + $primary border like a selected chip.
  • RemovableChip always paints the selected look (removable-chip.tsx header), so "selected" and "chosen item" are one glyph.
  • A selected SelectionChip (mint fill, brand border), VenueLink (mint fill, brand text), MarkerPill and TierBadgeChrome (mint fill) differ from each other only by border and radius.

Names that blur: "chip" (SelectionChip, MarkerPill, ContextChip, PlayerIdentityChip, TournamentContinueChip), "pill" (ActionPill, MarkerPill, TogglePill, ReliabilityPill, PillNav), "badge" (Badge, CornerCountBadge, CameraBadge, NaverBadge, 20 wrappers).


3. The unification ​

3.1 Position ​

Two primitives, one shared geometry module, one retired word.

  • Tag — the one passive label. Replaces Badge, MarkerPill, ContextChip, the tier/reliability chrome and every hand-drawn label pill. It has no press props at the type level.
  • Choice — the one selection control (renamed SelectionChip; absorbs DropdownChip, RangeChip, RemovableChip as modes menu, range, input). Wizard multi-select options and scope filters both use it. Look: outline, ink selected, check.
  • SegmentedTabs/PillNav stay as navigation / view switch (Seed: "Tabs… Navigation, Segmented Control… Manipulation"); CornerCountBadge is renamed CountBadge (Seed Notification Badge rules); ActionPill folds into Button (small tonal); TogglePill is renamed SwitchTrack (it is a switch, not a pill).
  • Retire the word "chip" from component names; keep it in prose for the control only if wanted.
  • Shared implementation: one label-chrome.ts (framework-free, like badge-visual.ts) holds heights, label font and paddings that both Tag and Choice read, so the two cannot drift, which is the "unify implementation" half of the owner's ask without making a pressable thing and a label the same shape.

Why not one Tag with selectable mode (Carbon-style): Carbon (avoid tags with multiple functions), GOV.UK ("Do not make a tag interactive…") and Seed (badges never clickable) warn against it, and ledger row 12 already cites NN/g on non-clickable items that look like buttons; the passive half is where our inconsistency lives (finding 1); and the board ruling 12 already keeps shape as the cue. If the owner insists on one name, Carbon's single Tag is the precedent, but it needs the same look split inside it.

3.2 Tag spec ​

PropertyValueSupport
Name / fileTag, packages/ui/src/tag.tsx + tag-visual.ts
Propslabel, tone, tier (default weak), size (default sm), icon? (status glyph only: check, pulse dot; never emoji), alignSelf default center (Badge's baked flex-start pins a pill to the top of a row, badge.tsx:57), shrink?. No onPress, no aria-pressed/checked/selected.GOV.UK, Seed
Tonesneutral, brand, success, warning, danger, info, levelSeed 6 tones; board VT_BADGE visual-tone.jsx:260-267
Tiersweak (tinted, repeated rows), solid (over photos), outline (neutral only, on tinted surfaces: replaces surface, 5 sites, and outline, 0 sites)6 of 7 systems have two; Seed/Fluent add outline
Sizessm 22px (12/18 + 2x2 pad), md 26px (12/18 + 2x4 pad); xs (10px text, 33 call sites) retired. Heights 22/26 sit between Seed 20/24, Fluent 20/24, Carbon 18/24.owner rejected 10px for the count on 2026-07-28 (session-compact-row.tsx count comment)
Radius$2 (8px) for both sizes (retire md's $3); level tone is a 22px circle (numeral)board ruling "rounded rectangles $2 in two tiers" (colour-system-research…:22); Seed 4-6, Polaris 8 [relay]
Fontbadge role 12/18, weight 600 weak, 700 solid; tabular numerals when the label is a numberSeed weak 500 / solid 700; ours 600 (badge.tsx:108)
Label1-2 words, ≤10 chars default, sentence case, adjectives (참석, 마감 임박, 완납), never a verbGOV.UK, Polaris, Seed ("2-3 words", >10 chars truncates)
Count of tags≤2 per object; ≥3 literal Tags in one container is a lint finding, lists of facts use inline text (MetaSegments)Seed (paraphrase: one or two badges per object); canon/components.md:26

Tone table (values from the board's VT_BADGE and the ledger palette, contrast re-computed here with WCAG 2.x):

toneweak (fill / text)solid (fill / text)used for
neutral#DFE7E1 track / $text#4F5851 / whiterole (호스트), pending, metadata
brand$primaryMuted #C8EED2 / $primary #006D37 (5.12:1)same as weak (brand solid is reserved for the one action)모집 중, invitation
success#CBEBE5 / $successText #014B40 (7.95:1)$success #00796A / white완납, 참석 확정, win
warning$warningFill #F5B301 / $text (10.32:1)same마감 임박, 미납
danger$error #B8332E / whitesamelive (+ PulseDot), cancelled, overdue
info#E8DCFB / $info #4B0082 (9.91:1)$info / whiteinformational only
level$levelChipBg #C6E84B / $levelChipText #0D100E (13.71:1) + 1px $bordersameNTRP/tier numeral

3.3 Migration map ​

TodayConsumersBecomes
Badge (12 variants)75 filesTag. neutral→neutral; muted→neutral (cancelled/expired read through the label); warning→warning; live→danger+pulse; accent→brand (모집 중 per T8 D1); success→success (teal per T8); info→info; primarySolid→brand solid (the viewer's 참석 only); surface/outline→outline; primarySubtle→brand; premium deleted. Codemod on the variant literal, 18 maps edited in one file.
<Badge variant="neutral"> printing a fact≈19 sitesnot a Tag: MetaSegments text. Round-rules strip becomes one meta line.
Badge xs33Tag sm
TierBadgeChrome (Elo/Trust)13 filesTag tone="neutral" with the tier dot (T7 "white tier chip with a tier-hue dot") on hero/profile surfaces; plain text in rows
TierRangeBadge2 + cardtext segment in rows; Tag only on the hero
ReliabilityPill, MarkerPill, ContextChip1, 4, 1Tag
hand-drawn label pills5 sitesTag
GlyphFactRow density="chip"4delete the chip density; inline without glyphs
SelectionChip58Choice (same props; look changes)
DropdownChip, RangeChip, RemovableChip11, 2, 2`Choice mode="menu"
ActionPill, hand-drawn pressable pills2 + 4Button size="sm" tone="tonal" or Choice
CornerCountBadge2CountBadge (keep; Seed rules: ≤4 chars, never 0, clear when seen)
TogglePill9SwitchTrack (rename only)

3.4 Choice (control) look ​

StateLookSupport
resting1px $border outline, $background fill, label 14/500 $text, full pill, 32 / 40px (+ hitSlop to 44, as today selection-chip.tsx:85)Seed outlineWeak/outlineStrong, M3 outlined chip
selected$text ink fill, $textInverse label 700, leading check glyph (second non-colour cue)Seed solid selected = bg.neutral-solid; Carbon $layer-selected-inverse; ledger row 10
menutrailing chevron, opens ModalPanel (no sheets)Apple pop-up button
inputtrailing x (min 44 hitSlop, as removable-chip.tsx)Seed/M3/Polaris/Fluent input chips
disabled0.5 opacitySeed

Shape cue that survives a squint: tinted fill + 8px corners = label, outlined white pill = control. Seed does the same (Badge 4-6px filled vs Chip full-radius outlined). Wizard multi-select option sets keep this look (they are form controls); big tiles stay SelectionCard.

3.5 Lint rules (modelled on no-hand-built-person-row.js, no-adhoc-status-badge.js, bounded-variant-enums.js) ​

  1. @twomore/tag-is-passive (error, features + app/src/presentation): a <Tag> carrying onPress*, role="button|tab|radio|checkbox", aria-pressed|checked|selected|expanded, or a <Pressable> whose only JSX child is a <Tag>. Message: "A pressable label is a control: use Choice or Button." Also a TS-level Omit on TagProps.
  2. @twomore/no-hand-drawn-label (error): a View/XStack/YStack that has borderRadius in {$2,$3,$7} and (backgroundColor or borderWidth) and a <Text textRole="badge"|"micro"> child and no onPress or Pressable ancestor. Exempt only tag.tsx, choice.tsx, count-badge.tsx and avatar/dot geometry (square size == height). Routes to Tag. The six sites in 2.1 are its first findings.
  3. @twomore/no-hand-rolled-pressable-pill (error): a Pressable (or onPress host) with borderRadius="$7" and a backgroundColor/borderWidth and a badge/cardMeta Text child is a hand-built chip or action pill: route to Choice or Button size="sm". First findings: post-row.tsx:481, arrival-picker.tsx:46, pickup-host-card.tsx:190, club-album-banner.tsx:215, action-pill.tsx (until folded). styling.md:93 already retires hand-rolled selectable pills in prose; this makes it a gate.
  4. @twomore/choice-needs-handler (error): <Choice> without onPress/onValueChange/onRemove is a label; <Choice> may not appear inside <Tag> or the reverse.
  5. @twomore/tags-per-container (warn → error): three or more literal <Tag> siblings in one JSX parent (Seed (one or two)).
  6. Keep and extend: no-adhoc-status-badge (route status through wrappers), bounded-variant-enums (bound Tag tone/tier), replace no-badge-weight-override with a Tag text-weight check.
  7. Canary (memory: "Gates need canaries"): a known-positive fixture under packages/eslint-plugin/__tests__/fixtures/ for each rule with an expected finding count, so a rule that matches nothing fails.

4. Part B: top-down hierarchy in rows ​

4.1 Evidence ​

SourceFindingURL
NN/g F-pattern232 users, thousands of pages: "two horizontal stripes followed by a vertical stripe"; "Front-load words"; "They'll read the third word on a line much less often than the first two words." (web content pages, desktop)https://www.nngroup.com/articles/f-shaped-pattern-reading-web-content-discovered/
NN/g first 2 words80 participants, 20 links: users "typically see about 2 words for most list items"; seeing only the first 11 characters, 35% of links gave no prediction; strong front-loading ("Gift Cards") 85%https://www.nngroup.com/articles/first-2-words-a-signal-for-scanning/
NN/g left bias120+ participants, 130,000 fixations: 80% of time on the left half (69% in 2010); 94% on SERPshttps://www.nngroup.com/articles/horizontal-attention-leans-left/
NN/g visual hierarchy"Make the most important element biggest"; "If everything is contrasted, then nothing stands out"; at most 3 sizes, at most 2 big elementshttps://www.nngroup.com/articles/visual-hierarchy-ux-definition/
NN/g cards"Card layouts are less scannable than lists"; "content at the top and on the left… is perceived as more important"https://www.nngroup.com/articles/cards-component/
NN/g photosAmazon thumbnails 0.9 fixations vs 4.4 on the description, 18% of viewing time on photos; informative images get looked at, decorative ones ignoredhttps://www.nngroup.com/articles/photos-as-web-content/
UF eyetracking (video selection)thumbnails got "almost twice as much attention" as title text and were looked at before it (no n published)https://jainlab.cise.ufl.edu/research/completed-projects/eye-tracking-for-online-user-experience/
NN/g icons"always include a visible text label"; ambiguity of hearts/stars/clocks; no claim that decorative icons slow scanninghttps://www.nngroup.com/articles/icon-usability/
M3 list item (tokens)headline body-large 16/400, supporting body-medium 14/400, trailing supporting label-small 11/500; one-line 56, two-line 72, three-line 88 px; leading/trailing 16https://github.com/material-components/material-web/blob/main/tokens/versions/v0_192/_md-comp-list.scss (:96-100, :156, :202, :220, :232, :250)
Seed list itemtitle t5 16 regular fg.neutral; detail t3 13 regular fg.neutral-subtle; suffix text 16 regular subtle; paddingY 12, body gap 2; "Detail 은 List 제목 하단에 위치" (Sub Text + Tag Group)https://seed-design.io/rootage/components/list-item.json, https://seed-design.io/llms/components/list.txt
Apple HIG"Prefer displaying text in a list or table"; "Keep item text succinct"; info button vs disclosure indicator; no sizes. subtitle style (primary body-ish / secondary subheadline) [relay]https://developer.apple.com/design/human-interface-guidelines/lists-and-tables
Toss TDSListRow left / contents / right; text types 2RowTypeA..F (top, bottom), right slot takes Texts, Switch, Badge, arrow; no px publishedhttps://tossmini-docs.toss.im/tds-mobile/components/ListRow/list-row-components/llm.txt
Toss framesheaviest = top line in every row: toss_f0 amount ~17-18 pt bold over a ~14 pt grey label, trailing tonal button; toss_f2 stock name over "4주", trailing price bold over a coloured change; toss_f5 title with blue sub, one grey status chip in 1 of 7 rowsbench/crops/toss_f0.png, toss_f2.png, toss_f5.png (visual estimates ±2 pt)
Monzo framemerchant name + grey sub left, amount right, one logo avatar, no chipsbench/crops_c/m_monzo_0.png
Ours todayListRow: title cardBody/cardMeta 14/600, sub META_TEXT.secondary 14/500 $textTertiary; zero size steppackages/ui/src/list-row-visual.ts:34-39, text-grammar.ts:42

4.2 Which line is heaviest in peer rows (my read of the frames) ​

App (frame)Line 1Heaviest lineFirst body line heaviest?Note
Smash (b_zoom1.png mid)small grey region eyebrowtitle, bold ~17yes (after eyebrow)date regular, count in grey rect tag top-right, 1 inline glyph
TennisFriends (tennisfriends_3)small grey region eyebrowtitle boldyes (after eyebrow)4 chips (복식, 3면, Lv, 마감), count + glyph
Munto (b_zoom2.png mid)category chip (+ red 마감임박)title bold ~19yes (after chips)meta one grey line, host avatar + count text, heart on photo
Flap (flapfootball_1)time "18:00" in greyvenue bold, line 2notime first but light; chips 확정임박 + AI 리포트
Band (band_3)date bold ~20dateyesevent title below, avatar stack + "28명 참석", green chip
Greenlight (greenlight_2)"1.10(수) 17:55"first lineyesregion 2nd, category 3rd, red "1자리 비었어요!" text
Somoim (somoim_5)"10/2 (수) D-2" bold, D-2 redfirst lineyestitle below
Spond (spond_5)title boldtitleyesdate block left, when 2nd, three response counts
Heja (heja_2)coloured type eyebrowtitle bold largeyes (after eyebrow)the only row with buttons
당근 item (danggeun_f1)title regular 16price bold, line 3nometa grey line with pin glyph, one orange "바로구매" tonal tag in 1 of 3 rows, counts bottom-right, kebab top-right
Frip (frip_3)small region eyebrowtitle bold, price bold lastpartlyrating, price, heart
Toss, Monzoamount / merchanttop lineyestrailing amount top-right

Tally: first body line heaviest in 9 of 12 (8 of 9 session rows); the three exceptions are commerce or a time-over-venue pair. Lane B's wider count (n = 19): time/date led 7, title led 6 (lane-b-category.md:70).

4.3 Time-first or venue-first ​

The user's question is "when and where". Peers answer it in two grammars, and the split follows the list structure:

List structurePeersLine 1
grouped or filtered by a date strip/headerFlap, Greenlight, Band lead the text with time/date (3 of 4); Spond puts the date in a left date block and leads the text with the titletime/date (3), date block + title (1)
ungrouped discovery feedSmash, Munto, TennisFriends, Frip (4 of 4)title/venue

Our lists are grouped by day (SessionDayHeader; session-card-v2.tsx:31-32: "the DATE LEAVES THE CARD"). Within a day the time is the sort key and the only thing that differs row to row; venue names repeat (a club plays at one or two courts). NN/g's "first two words" says the left edge carries the scan, so the unique, ordered datum goes there. The photo already carries the venue's identity (NN/g: informative images are fixated), so the text can lead with the time. Recommendation: line 1 = time (16/600), line 2 = venue (14/500). When a list is not grouped by day, line 1 = 토 19:00-21:00 (same style), so there is one grammar, configured by the projection, not forked. Fallback (owner prefers place-first): line 1 = venue 16/600, line 2 = time 14/500; every other rule below is unchanged. Either way the T2 "after" order (16/600 venue over 18/700 time) is out.

4.4 The ladder and the row specs ​

One ladder for all rows. Weight and size never increase down the row; colour goes ink → ink → secondary.

StepSize / lineWeightColourWhere
T1 title16/24600$text #0D100Eline 1 (cardTitle role)
T2 body14/20500$textline 2 (cardMeta role with $text)
T3 meta13/18500$textSecondary #4F5851 (7.38:1 white)line 3 and the trailing text (new meta role, ledger row 8)
Tag12/18600per toneonly on exception, never mid-row

Rules: no bold below line 1; no promoted numeral inside a row (hero numerals stay on hero blocks, 28-44/700); $textTertiary not used for row text (Lc 77/71, ledger row 8); at most one trailing element; trailing is aligned to line 1 on the right; exactly one picture or avatar at left.

RowLeftL1 (T1)L2L3Trailing (one)Moves out
Session (grouped by day)venue's own photo, else map tile, 72 x 72, radius $3, 1px $borderSubtle (map tile inset $border)19:00-21:00 (tabular)venue name, T2, one line복식 · NTRP 3.0-4.0 T3 via MetaSegments (no glyphs)6/8 T3 text, or one Tag when a state outranks it (live, the viewer's standing, 마감 임박)bookmark, bar, fee, glyphs, status word, court dot
Club (list)club logo / medallion 56, circleclub name (wraps, NAME_LINES)—서초구 · NTRP 3.0-4.0 · 멤버 42 T3; for a member, L2 = next session 토 19:00 올림픽공원 T2 and L3 = region metadiscover: 모집 중 brand Tag only when recruiting; member: CountBadge alarm only when >0cover photo window, tier corner badge, activity dot text, tagline, 5 fact chips
Duesavatar 40member name—9월 · 30,000원 T3 tabularstatus Tag (완납 success, 미납/일부 warning) or, for the admin when action is due, the one 납부 확인 button (replaces the Tag, as dues-row.tsx header already says "at most ONE visible pill")amount emphasis
Memberavatar 40name (NAME_LINES)—조 name + trust word as T3 textkebab (admins) or one exception Tag (호스트, 게스트, 예비, 차단됨), never bothTrustTierBadge chip, manual-pin chip become T3 text

Seed's and M3's numbers for scale: Seed 16 regular over 13 regular subtle; M3 16/400 over 14/400 with an 11/500 trailing. The ladder (16/600, 14/500, 13/500 secondary) is Seed's step plus a weight step, with every cell at or above the ledger floors (13/500 secondary, colour-system-research…:18).


5. Part C: attention inventory and the compact row ​

5.1 Session card today (packages/app/src/presentation/components/sessions/session-card-v2.tsx, browse viewer, no live band) ​

#ElementSize / weightColour roleShape / fillSource
1Card framepadding $3 12$card, 1px $borderSubtleradius $4 16card.tsx:28-31, session-card-v2.tsx:546
2Venue tilesquare, stretched to the 3-line column = 24 + 8 + 20 + 4 + 20 = 76map/photoradius $3, 1px $borderSubtle (+ corner surface chip on the map branch)venue-tile.tsx:92-94,124-126
3L1 venue name16/24 600$texttext:376
4Bookmark22px outline / filled$textTertiary / $primaryicon:388-395, save-button.tsx:39
5-6L2 time glyph + textemoji 12 in 20px slot; 14/20 500$textemoji (saturated, platform-dependent):408-413, glyph-fact-row.tsx:20
7-8L2 format glyph + textsame$text:414-421
9-10L3 tier glyph + band🎯/🔒 emoji; 14/500 (muted $textTertiary when locked)$text:436-447
11Lead badge (conditional)Badge xs 10/14 600, 18px tallstatus tintradius 8, filled:473, badge.tsx:88-92
12Fee (browse only)14/20 500$texttext:474-478
13Status word (when no lead chip)12/18 600$textSecondarytextsession-compact-row.tsx:124-128
14Capacity bar5px tallrecruitment hue on muted trackradius $2session-compact-row.tsx:129-136, progress-bar.tsx:70,97
15Count 6/812/18 600 tabular$texttextsession-compact-row.tsx:137-141

Typical browse viewer: items 2-4, 5-10 (six), 12-15 = 13 objects (+ the tile's corner chip) in a 130 px card (12 + 76 + 8 + 20 + 12 padding/gaps + 2 borders; arithmetic from the numbers above). A confirmed member swaps 12-13 for 11: 12 objects. Weight steps in view: 600 title, 500 facts, 600 badge, 600 count, bar fill, emoji colour: three competing accents (emoji, badge fill, bar fill) beside the photo.

Board T2 "after" card (visual-tone.jsx:654-705 (time at :705)): tile, venue 16/600, bookmark, time 18/700, format 14/500, tier 13/500 + glyphs, RSVP badge or fee, bar, count (10 objects, 6 text sizes/weights); the promoted line is the second.

5.2 Club card today (packages/features/clubs/src/shared/club-card.tsx, club-photo-seam.tsx) ​

ElementSize / weightColour / fillShapeSource
Photo window3:1, full widthphototop corners $4club-photo-seam.tsx:55,128
Tier band corner badgeBadge sm 26px, 12/600surface pill / $surfaceTertiaryradius $2:152, tier-range-badge.tsx:29
Recruitment badge (discover) / alarm circle (member)Badge / 22px circleaccent tint / $error:175,189
Flag / logo48px circle straddling the seamlogo or medallioncircle:55,193
Faces + capacity pill28px cluster; pill 12/600$surfaceSecondary$7:212-235
Name16/600$textclub-card.tsx:269
Activity indicator6px dot + 14/500$success / $textTertiary / $warningclub-card-activity-indicator.tsx:46-47
Tagline (conditional)14/500$textSecondaryclub-card.tsx:310-312
3 identity chips (📍👥📅) + 2 terms chips (🔒💳)Badge surface sm 26px, 12/600 + emoji$surfaceTertiary$2:322,329, glyph-fact-row.tsx:156-168

Discover card: photo, 2 corner badges, logo, faces, capacity pill, name, activity (2), tagline, 5 chips with 5 emoji = 16 objects + 5 glyphs. Member card: photo, tier, alarm, logo, faces, capacity, name, activity (2), then per upcoming session (up to 2) dot, date, time, venue, 24x3 meter, count (club-card.tsx:375-440) = up to 21 objects. Two pictures (the 3:1 cover and the 48px flag).

5.3 What peers put in a list row (11 session/listing rows + Toss/Monzo, my reading of bench/b_crops, bench/crops, bench/crops_c) ​

ElementPeers that show it in the rowOurs today
Identity picture7 of 11 (Smash, TennisFriends, Munto, Flap, Greenlight, Frip, 당근); one per rowtile + (club: cover + flag)
Title / lead line11 of 11yes
Time/date in the row11 of 11yes
Format (복식 etc.)3 of 11 (Smash, TennisFriends, Flap), as meta text or one chipglyph + text
Level/tier3 of 11 in the row (Smash, TennisFriends, Flap); lane B: 7 of 8 racket apps somewhere on the cardglyph + text
Fee/price3 of 11 (Flap, Frip, 당근), all commerce-likebrowse viewers
Capacity as text7 of 11 (Smash, TennisFriends, Munto, Flap, Band, Greenlight, Spond; Somoim only in its detail sheet)yes (plus bar)
Capacity bar0 of 11 (lane B reports none in 19)yes
Status/urgency6 of 11 (TennisFriends, Munto, Flap, Greenlight, Somoim, Spond), mostly one chip or one red word; TennisFriends 4 chips, Flap 2lead badge + status word
Save / heart in row4 of 11 (Munto on the photo, Flap, Frip, 당근)yes
Inline fact glyphs0-3 per row, median 2 (Smash 1, TennisFriends 3, Munto 2, Flap 2, Greenlight 3, 당근 1-2, Toss/Monzo 0)3 emoji + bookmark
Avatars of people3 of 11 (Munto host, Band stack, Greenlight ring)none
Buttons in the row1 of 11 (Heja)none
Objects per rowSmash ~6, TennisFriends ~9, Munto ~8, Flap ~7, Band ~8, Greenlight ~7, Spond ~7, 당근 ~6-7, Toss/Monzo 4 (median ≈ 7)12-14

5.4 The compact row ​

Merges and moves:

  • Price → gone from the row. Canon already says the fee left the card (session-card-v2.tsx:53-57); code disagrees (see 7). Pickup fees are settled upfront and club sessions are dues-covered; the detail's cost row keeps it. Where a fee ever decides the tap (public paid pickups), it goes into T3 as a text segment (8,000원), never its own element.
  • Capacity → the one trailing element. 6/8 as T3 text. It becomes a Tag only when a state outranks it, in this order: live (danger + pulse, "진행 중"), the viewer's standing (success "참석", info "예비", neutral "호스트"), urgency (warning "마감 임박"/"1자리"). One element, never Tag + count (the strip's lead is already one chip, session-strip-ui.tsx:130-157).
  • Status word and lead badge collapse into that Tag.
  • Bookmark → detail header (exists, see the session detail screenshot bench/ours-phone/sessions_*.png). Fallback: a 22px outline icon over the photo's top-right corner (Munto puts the heart on the photo); it is an action target, so (d) allows it. Needs the owner to overturn the 2026-09-08 "bookmark on every card" ruling (session-card-v2.tsx:67-75).
  • Capacity bar → detail (the hero's 확정 인원 stat).
  • Format + tier → one T3 line of text, no glyphs.
  • Court/surface dot, weather, fee, host/role stay off the row.
  • Live band: replace the full-width solid ribbon by the danger Tag in the trailing slot for compact lists; keep the band on the detail anchor (owner decision, the 2026-09-08 carve-out).

The single accent: the trailing Tag when it fires, in its weak tint; default rows have none. The photo is the only other colour. Day-header count anchor and the one brand action stay on the screen, outside the row.

Identity picture: sessions = the venue's own photo, else the map tile in the same 72px box (ledger row 13); clubs = the logo/medallion at 56px; people = avatar 40. One per row.

Line-by-line session row (flat on list screens; the same anatomy in a Card on hubs, as variant: 'embedded' already does):

LineContentSize / weightColourNotes
photovenue photo / map tile72 x 72, radius $31px $borderSubtlegap 12 to text
L119:00-21:0016/24 · 600 · tabular$textone line; ungrouped lists prefix the day
L1 right6/8 or one Tag13/18 · 500 · tabular$textSecondary / tonealigned to L1 baseline, flexShrink 0
L2venue name14/20 · 500$textone line, places may crop
L3복식 · NTRP 3.0-4.013/18 · 500$textSecondaryMetaSegments, no glyph
framerow padding 12 vertical, hairline $border below (flat)height 72 + 24 = 96 px (98 in a card)

Resulting element count: photo, L1, L2, L3, trailing = 5 (6 with a photo-corner bookmark) against 12-14 today and a peer median of about 7. Height 130 → 96-98 px, so a 600 pt list area shows about 6.2 rows instead of 4.6 (arithmetic, same padding). Inline glyphs 0 (today 3 emoji + bookmark), bars 0, fills other than the photo 0 by default and at most 1 on exception.

Club row: logo 56 · name 16/600 · 서초구 · NTRP 3.0-4.0 · 멤버 42 13/500 · at most one trailing Tag/count = 3-4 elements against 16-21 on the cards; Band, Spond and Meetup group rows are image + name + count with no CTA (lane-b-category.md:75).


6. Falsifiable observations ​

  1. Time-first beats venue-first in day-grouped lists. Test: 6 rows per list, 12 participants, tasks "find the 19:00 session" and "find the Olympic Park session"; metric time-to-first-correct-tap. The recommendation fails if venue-first is within 10% on the time task or time-first is more than 20% slower on the venue task.
  2. Peers lead with the heaviest line in at least 75% of session rows. Observed 8 of 9 (n small). Falsified if a 30-row sample shows below 60%.
  3. No peer draws a capacity bar in a list row. 0 of 11 here, none reported in 19 (lane B). One counter-example in the remaining 8 apps weakens "bar → detail" but does not reverse it.
  4. A size step of at least 2 px between line 1 and line 2 survives a 5-10 px blur (NN/g squint test) where today's 14/600 over 14/500 does not. Test with the board's screenshot harness.
  5. Compact row height 130 → 96-98 px. Verify on the board: render the row at 390 px width and measure; any extra gap token above 4 px between lines breaks the arithmetic.
  6. Removing the three emoji glyphs does not slow "find the doubles session". No study measures this; the NN/g icon article says only that icons need labels. If a prototype test shows a slowdown, restore one glyph (format), not three.
  7. Tag count drops. After the migration grep -c "<Tag" per container: no container above 2; <Badge variant="neutral"> fact sites 19 → 0.
  8. One recipe, two meanings today. On day one no-hand-drawn-label should report the 6 label sites (club-photo-seam, club-identity-header, public-club-profile-screen, venue-link, banner-capacity-cluster, ContextChip) and nothing in the pressable ones (ActionPill, post-row, arrival-picker, pickup-host-card, club-album-banner), which no-hand-rolled-pressable-pill catches; a different count means the rule or this inventory is wrong.
  9. Seed's outlineWeak recommendation ("several selected values may appear at once") applies to us. Wizard multi-select pages with 3+ selected options should read calmer with outline + ink + check than with 3+ mint fills; test on the day-chip section (create-club/step3/days-chips-section.tsx).
  10. A 12px chip label is a smell. Seed (14), M3 (14) and Fluent (14) all use 14px at the 32-40px chip heights; check that Choice at 14/500 fits 5-character Korean labels at 360 pt in one row.

7. Caveats ​

  • Primary sources I could not open: m3.material.io and fluent2 design pages, Polaris docs (moved to shopify.dev), Apple's own HIG pages (read through the JSON mirror), Atlassian shape/height values (not on the usage/code pages), TDS pixel values (not published). Where a number comes from tokens/source rather than the designer docs I say so; Polaris border-radius-200 = 8px and Fluent's borderRadiusMedium are [relay]. M3 badge 6/16 is from _md-comp-badge.scss, confirmed.
  • Apple subtitleCell sizes (body / subheadline) are unverified; HIG gives no numbers.
  • Peer frames are marketing or store images, 222-392 px wide, sizes ±2 pt (lane-b-category.md:8-11). "Heaviest line" and object counts are my reading of 11-12 rows, not a measured census; the grouped/ungrouped split (4 vs 4) is small and could have confounds (the grouped apps are also the ones that put date blocks in their frames).
  • NN/g evidence is web desktop (F-pattern 232 users; first-2-words 80 participants on link lists; left bias on 1920x1080 monitors). Transfer to a 390 px Korean list row is inference; Hangul reads left to right, which supports but does not prove it. No source measures chips vs badges, or inline decorative icons vs scan time; finding 6 and observation 6 are design arguments, not results.
  • UF thumbnail study gives no sample size and is for video selection; NN/g's Amazon numbers point the other way (0.9 vs 4.4 fixations); what reconciles them is information content, which is why the photo must be the venue's own.
  • Doc/code drift found, not fixed: (1) session-card-v2.tsx:53-61 says the fee left the card and the closing line is the meter alone; :473-485 renders the lead badge and, for browse viewers, the fee. (2) canon/components.md:29 limits fact glyphs on browse cards to boolean facts "never in front of a bare count"; canon/components.md:57 lets an emoji lead any fact; the card's ⏰ time glyph is neither boolean nor sanctioned by :29. (3) canon/components.md:81 still calls PillNav "chip-style tab controls". (4) Board T5's "stroke icons at $text" for fact rows is superseded for rows by ruling (d); the detail fact block's label-plus-icon rows duplicate the label (NN/g: labels are what carry meaning) and are worth the same review.
  • The bookmark move contradicts an owner ruling (2026-09-08, session-card-v2.tsx:67-75) and the deriveSessionStrip(model, saved) input; it needs an explicit decision, and the fallback overlay is given. The live-band downgrade also touches a 2026-09-08 carve-out.
  • Tag sizes 22/26 are my fit to Seed/Fluent/Carbon ranges and the existing 12px text; a 24px single size is equally defensible. The solid weight 700 is Seed's, not ours today.
  • Variant and size tallies (neutral 39, xs 33, ...) come from a single-line <Badge ...> regex and undercount multi-line tags; file counts in 2.1 are <Name JSX files; a component used only through a wrapper or a spread is undercounted (e.g. GlyphFactRow reaches the session card through session-card-v2.tsx only once). The 19-of-39 "fact" classification is a manual read of the grep listing.
  • Nothing was modified in the repo; fetched sources and scripts are in bench/lane_i/.

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