feat: show band link icons on the public roster page#609
Merged
Conversation
Fans had to open each band's profile page just to reach their links. Each /artists card now carries up to 4 monochrome icon links in hear-first priority (bandcamp, spotify, instagram, website, then youtube/facebook/apple_music/linktree), with real 40px tap targets. The card is restructured (outer div + content Link + sibling icon cluster) so profile navigation and external links never nest anchors. /api/artists now returns `social`, sanitized server-side via safeReflectSocialLinks like the other public band endpoints; the frontend re-sanitizes with safeExternalHref/safeInstagramHref as a second layer. Clicks feed the existing social_link_click metric. Closes #607 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
The unsafe-scheme sanitization test needs a literal javascript: URL; document the eslint-disable the same way validation.test.js does. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Owner
Author
|
Layout v2 pushed per Dre's feedback: the icon cluster moved below the name/meta block — a long band name now renders in full and the card grows vertically instead of truncating ("The Time Travelling Garbage Collectors" seeded as the worst case renders cleanly in both themes). No-link cards keep their original height. All gates green: frontend 518 tests, lint/format/build clean. Auto-merge still held for Dre's visual sign-off. |
6 tasks
Owner
Author
|
Correction to my previous comment: this PR had already been merged when the v2 layout was ready, so the truncation fix ships as follow-up PR #612 (icons below the text row — screenshots with Dre). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fans had to open each band's profile page just to reach their links. Each
/artistsroster card now carries a compact icon row of the band's links — capped at 4, in "hear the band first" priority (Bandcamp → Spotify → Instagram → website → YouTube → Facebook → Apple Music → Linktree) — with the full colour-treatment link set remaining on the profile page. Cards without links look exactly as before.Closes #607
Visuals (local wrangler + seeded D1, both themes): verified in midnight-ember and daybreak — monochrome ghost icons, 40px tap targets, long names truncate cleanly against a 4-icon cluster. Screenshots shared with Dre in-session for the "looks good and practical" gate; auto-merge deliberately not armed pending his visual sign-off.
What changed
functions/api/artists.jssocialadded to the payload, sanitized server-side viasafeReflectSocialLinks(same convention as the other public band endpoints: values normalized to real http(s) URLs / Instagram handle or null; malformed JSON →{}; absent column →null); rawsocial_linksnever exposedfrontend/src/pages/ArtistsPage.jsxArtistCardrestructured: outer div + content<Link>+ sibling icon cluster (no nested anchors); module-levelSOCIAL_LINK_DESCRIPTORSpriority list;safeExternalHref/safeInstagramHrefre-sanitize client-side;trackSocialClickon click (samelink_typekeys as the profile page, allowlisted in/api/metrics); theme tokens onlyfrontend/src/pages/__tests__/ArtistsPage.test.jsxsocialnull,javascript:URLs droppedfunctions/api/__tests__/artists.test.js{}, absent →null, server-sidejavascript:nulling, raw column never exposeddocs/api-spec.yamlsocialdocumented to match actual behaviourSecurity / correctness notes
Double-layer URL sanitization: server (
safeReflectSocialLinks— nulls non-http(s) schemes, normalizes URLs) and client (safeExternalHref/safeInstagramHrefgate every href;'#'results are filtered out). External anchors carryrel="noopener noreferrer". Public read-only endpoint; no auth surface.Verification
npm run build --prefix frontend)validate:openapi: 71 documented + 1 internal = 72 route files, no driftBuilt by Sonny · Reviewed by Vera & Theo · 🤖 Claude Code