Skip to content

feat(supporter): render chip on /discover/profiles listing#148

Open
ntatschner wants to merge 1 commit into
nextfrom
feat/supporter-chip-discover
Open

feat(supporter): render chip on /discover/profiles listing#148
ntatschner wants to merge 1 commit into
nextfrom
feat/supporter-chip-discover

Conversation

@ntatschner
Copy link
Copy Markdown
Collaborator

Summary

Final piece of the supporter chip rollout: #143 (self profile) → #144 (topbar) → #145 (public/friend profile) → #146 (this PR — discover bulk). Every public-profile surface now carries supporter recognition.

Server changes

  • SupporterStore gains get_many_public_by_handle(handles) — single SQL round-trip with WHERE LOWER(claimed_handle) = ANY($1) returning a HashMap<lowercased_handle, SupporterStatus>. The critical bit is one query for N profiles instead of N individual lookups; with DEFAULT_LIMIT=50 profiles per page that's a 50× round-trip reduction. Filters to active/lapsed server-side so non-supporters simply don't appear in the map.
  • DiscoverProfile gains supporter: Option<PublicSupporterInfo>, reusing the projection type added in feat(supporter): expose chip on public + friend profile views #145.
  • PublicSupporterInfo gets Clone derived (one-char change).
  • list_discover_profiles handler takes Extension(Arc<dyn SupporterStore>) — the Extension layer was wired in feat(supporter): expose chip on public + friend profile views #145, no main.rs change needed. Bulk-fetches AFTER the listing SQL completes and merges into each row. Fail-soft on store error (warn log; chips absent on the listing but discover page still renders).

Web changes

  • discover/page.tsx (server, first page) — renders <SupporterChip status={p.supporter} size="sm"> between display-name and "Active X ago". Full tier-specific palette via the shared component.
  • DiscoverLoadMore.tsx (client, subsequent pages) — builds an imperative-DOM chip with the standard accent palette. Tier-specific colours only on the first page; load-more uses accent uniformly. Deliberate trade-off — the alternative is replicating the full tier-palette mapping in two places (TS + imperative DOM strings) which would drift.

Tests

  • 3 new SupporterStore tests: empty-input shortcut, skips unknown/unbound handles in bulk, filters none-state rows.
  • All 11 supporter + 8 discover_routes + 35 sharing_routes tests pass.
  • cargo fmt + clippy clean.
  • pnpm vitest (40) + typecheck + lint clean.

Test plan

  • cargo test -p starstats-server --bin starstats-server supporters — 11 tests pass (3 new)
  • cargo test -p starstats-server --bin starstats-server discover — 8 tests pass (existing)
  • cargo clippy -p starstats-server --bin starstats-server -- -D warnings clean
  • pnpm --filter web run typecheck clean
  • pnpm --filter web run test:run — 40 tests pass
  • Smoke after platform promote: visit /discover signed-out, supporter chips appear on profile cards for any user with active/lapsed state. Click "Load more" past 50 profiles; subsequent cards also show a (simpler-palette) chip.

This completes the four-PR chip rollout. No further follow-ups planned.

Final piece of the chip rollout: #143 (self profile) → #144 (topbar)
→ #145 (public/friend profile) → **#146 (this PR — discover bulk)**.
Every public-profile surface now carries supporter recognition.

Server changes:

  - `SupporterStore` gains `get_many_public_by_handle(handles)` —
    single SQL round-trip with `WHERE LOWER(claimed_handle) = ANY($1)`
    returning a `HashMap<lowercased_handle, SupporterStatus>`. The
    critical bit is that this is ONE query for N profiles instead
    of N individual lookups; with `DEFAULT_LIMIT=50` profiles per
    page that's the difference between 1 round-trip and 50. Filters
    to `active`/`lapsed` states server-side so non-supporters
    simply don't have a map entry.

  - `DiscoverProfile` gains `supporter: Option<PublicSupporterInfo>`,
    reusing the projection type added in PR #145 (sharing_routes).
    `PublicSupporterInfo` gets `Clone` derived (was Debug+Serde
    only) so `DiscoverProfile`'s derive(Clone) propagates.

  - `list_discover_profiles` handler takes `Extension(Arc<dyn
    SupporterStore>)` (the layer was wired in #145; no main.rs
    change needed), bulk-fetches supporter info AFTER the listing
    SQL completes, then merges results into each row. Fail-soft:
    a supporter-store error logs a `warn` and leaves chips absent
    on the listing — the discover page still renders.

Web changes:

  - `apps/web/src/app/discover/page.tsx` (server component, first
    page) renders `<SupporterChip status={p.supporter} size="sm">`
    between the display-name line and the "Active X ago" timestamp.
    Full tier-specific palette via the shared component.

  - `apps/web/src/app/discover/_components/DiscoverLoadMore.tsx`
    (client component, subsequent pages) builds an imperative-DOM
    chip with the standard accent palette. Tier-specific colours
    only apply on the first page; the load-more path uses the
    accent palette uniformly. This is a deliberate trade-off — the
    alternative is replicating the full tier-palette mapping
    twice (TS + imperative DOM string-building), which would drift.

Tests:

  - 3 new SupporterStore tests: empty-input shortcut, skips
    unknown/unbound handles in the bulk path, filters none-state
    rows. All 11 supporter tests + 8 discover_routes tests + 35
    sharing_routes tests pass.

  - cargo fmt + clippy clean on starstats-server.
  - pnpm vitest + typecheck + lint clean on web.

Smoke after platform promote:
  - Visit /discover signed-out; supporter chips appear on profile
    cards for any user with active/lapsed state.
  - Click "Load more" past 50 profiles; subsequent cards also show
    a chip (simpler palette as documented).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant