Skip to content

Comments

Production readiness: security, email, funnel sync, component refactoring#12

Merged
GraysonCAdams merged 19 commits intomainfrom
fix/lint-warnings-cleanup
Feb 22, 2026
Merged

Production readiness: security, email, funnel sync, component refactoring#12
GraysonCAdams merged 19 commits intomainfrom
fix/lint-warnings-cleanup

Conversation

@GraysonCAdams
Copy link
Contributor

Summary

  • Security hardening: CodeQL compliance, structured Pino logging, SSRF/ReDoS fixes, local security scanning scripts, DAST config
  • Email system: Modular Resend email templates (welcome, disconnect, weekly recap) with preview routes and generated assets
  • Funnel sync mode: Liked tracks can now be funneled to an existing Spotify playlist instead of always creating a new one
  • Centralized Spotify error handling: Extract handleSpotifyError into shared spotify-errors.ts module, per-circle rate limiting
  • Component refactoring: Extract hooks from PlaylistDetailClient, split HomeClient into sections, add shared UI primitives (TrackListRow, UserAvatar, SkeletonRow, Spinner)
  • Activity consolidation: Merge activity-feed-utils into activity-utils
  • Test fixes: Update all mocks for refactored routes (requireAuth, spotify-errors, circleMembers)
  • All 336 tests passing, 0 lint errors, clean TypeScript, successful build

Test plan

  • npm run lint — 0 errors (2 warnings in script file)
  • npm run type-check — clean
  • npm test — 336/336 passed
  • npm run build — successful production build

🤖 Generated with Claude Code

GraysonCAdams and others added 19 commits February 22, 2026 11:04
…av, enhance ActivitySnippet for recent activity display
…plexity

- Raise max-lines limit from 300 to 500 (more reasonable for real-world files)
- Split 9 oversized files into focused sub-modules:
  - LandingClient.tsx (2587→467 lines) → 14 landing/ sub-components
  - polling.ts (1257→179 lines) → polling-playback, polling-tracks, polling-audit, polling-periodic
  - spotify.ts (764→48 lines) → spotify-core, spotify-playlist, spotify-playback
  - PlaylistDetailClient, SwaplistsClient, ProfileClient, CircleSettingsClient, CircleSwitcher, ActivityFeed
- Reduce cognitive complexity in 18 API route handlers by extracting helper functions
- Fix complexity in MemberBadge, TrackCard, seed.ts components
- Fix no-identical-functions in concurrency.test.ts
- All 331 tests pass, build compiles successfully, 0 ESLint warnings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ontainer CVEs

- Add .gitleaks.toml to allowlist CI test secrets (POLL_SECRET, IRON_SESSION_PASSWORD)
- Add npm override for bn.js>=5.2.3 to resolve moderate vulnerability in web-push chain
- Strip npm/npx from Docker runner image to eliminate minimatch/tar CVEs
- Fix polynomial ReDoS in email validation regexes (3 routes) — use linear-time pattern
- Fix remote property injection in library route — use Map instead of plain object
- Fix biased cryptographic random in auth login — use nanoid's customAlphabet
- Fix identity replacement in tunebat — use explicit Unicode curly quote codepoints
- Remove unused eslint-disable directive in spotify-core.ts
- Add input sanitization (sanitizeForLog, validateSpotifyPath) for SSRF and log injection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The DAST container runs without DATABASE_URL, so PGlite tries to initialize
at /app/data/swapify-pg. The nextjs user couldn't create this directory.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 10044: Big Redirect — expected for OAuth login flow
- 10049: Non-Storable Content — dynamic API responses by design
- 10055: CSP unsafe-eval — required by Next.js runtime
- 90004: COEP header missing — not required for this app
- 90005: Sec-Fetch-Dest missing — client request header, not server-controlled

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace regex email validation with linear-time isValidEmail() utility
  (no regex quantifiers = no backtracking = no ReDoS)
- Use URL constructor for Spotify API fetch to satisfy CodeQL taint analysis
  (validates origin matches api.spotify.com before making request)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Convert string-interpolated log messages to Pino structured format
(dynamic values in metadata object, message as string literal) so
CodeQL's taint analysis no longer flags them as log injection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- scripts/security-scan.sh: runs same checks as CI Security workflow locally
  (~45s total). Uses SARIF driver metadata for severity mapping to match
  GitHub's alertSeverity behavior. Supports .codeql-dismissals.json for
  skipping known false positives.
- .codeql-dismissals.json: tracks dismissed CodeQL alerts locally
- CLAUDE.md: document CodeQL compliance patterns, local security scanning
  commands, and pre-push checklist
- .gitignore: exclude .codeql-db/ and .codeql-results.sarif
- spotify-core.ts: add comment explaining SSRF mitigation for CodeQL

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add likedSyncMode and likedPlaylistName columns to playlist_members for
the new funnel liked-playlist feature. Add helpBannerDismissed to users
for the swaplists help banner.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the monolithic email.ts with a modular email/ directory:
templates (welcome, disconnect, weekly-recap), shared layout, and
send utility. Add email preview routes for development and a script
to generate email logo assets. Remove the old email.ts module.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…elpers

- Extract handleSpotifyError into src/lib/spotify-errors.ts for reuse
  across API routes (was duplicated in playlists/route.ts)
- Consolidate activity-feed-utils.ts into activity-utils.ts (single
  source of truth for activity event types and helpers)
- Add getActiveOrDefaultCircleId to auth.ts for circle-aware routes
- Add _resetAllCircleState test helper to spotify-budget.ts
- Remove deprecated global budget functions from spotify-config.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-avatar

Reusable UI building blocks extracted from component refactoring:
- SkeletonRow: animated loading placeholder rows
- Spinner: simple loading spinner
- TrackListRow: standardized track display with album art, actions
- UserAvatar: consistent avatar with fallback initials

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cle rate limits

- Replace inline handleSpotifyError in routes with shared module import
- Add funnel sync mode to liked-playlist route (created vs funnel)
- Use per-circle rate limiting (isCircleRateLimited) instead of global
- Add circle membership verification to playlist creation
- Improve GET /api/playlists with active track counts and stats
- Add circle-aware liked-playlist sync to polling-audit
- Use getActiveOrDefaultCircleId for circle-scoped API routes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…imitives

HomeClient: extract ActivityFeedSection and ReactionsSection into
separate files, derive reauth/greeting state with useMemo.

PlaylistDetailClient: extract use-current-track, use-playlist-actions,
and use-sorted-tracks hooks to reduce file size.

LikedTracksView: add funnel sync mode UI, playlist picker for
funneling liked tracks to existing playlists.

TrackCard, OutcastTracksView, UnplayedTrackRow: refactor to use
shared TrackListRow and UserAvatar components.

SpotlightTour: convert hasMounted ref to state for React 19 compat.

Add SwaplistsHelpBanner dismissible onboarding component.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Activity test: mock requireAuth (route switched from getCurrentUser)
- Playlist route tests: add circleMembers.findFirst mock, spotify-budget
  mock, and handleSpotifyError mock for centralized error handling
- Join route test: mock @/lib/spotify-core isRateLimited and
  @/lib/spotify-errors handleSpotifyError
- Liked-playlist test: add funnel sync mode tests, loop prevention,
  backfill population
- Tracks route test: add spotify-errors mock for TokenInvalidError
- Spotify-budget test: add beforeEach state reset, fix import

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- README: update architecture docs for new email system, funnel sync,
  and extracted UI components
- globals.css: add funnel mode pill styles, adjust glass-card padding
- layout.tsx: update meta description

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…anup

# Conflicts:
#	drizzle/meta/_journal.json
#	package-lock.json
#	package.json
#	src/app/api/activity/__tests__/route.test.ts
#	src/app/api/activity/route.ts
#	src/app/api/player/current/route.ts
#	src/app/api/player/play-all/route.ts
#	src/app/api/player/play-track/route.ts
#	src/app/api/playlists/[playlistId]/__tests__/route.test.ts
#	src/app/api/playlists/[playlistId]/join/__tests__/route.test.ts
#	src/app/api/playlists/[playlistId]/join/route.ts
#	src/app/api/playlists/[playlistId]/liked-playlist/__tests__/route.test.ts
#	src/app/api/playlists/[playlistId]/liked-playlist/route.ts
#	src/app/api/playlists/[playlistId]/route.ts
#	src/app/api/playlists/[playlistId]/tracks/route.ts
#	src/app/api/playlists/[playlistId]/tracks/sync/route.ts
#	src/app/api/playlists/route.ts
#	src/app/api/profile/preferences/route.ts
#	src/app/api/spotify/playlists/route.ts
#	src/app/api/unplayed-tracks/route.ts
#	src/app/dashboard/HomeClient.tsx
#	src/app/globals.css
#	src/app/playlist/[playlistId]/PlaylistDetailClient.tsx
#	src/app/playlist/[playlistId]/types.ts
#	src/app/profile/ProfileClient.tsx
#	src/app/swaplists/SwaplistsClient.tsx
#	src/app/swaplists/page.tsx
#	src/app/swaplists/swaplists-types.ts
#	src/components/ActivityEventCard.tsx
#	src/components/ActivityFeed.tsx
#	src/components/ActivitySnippet.tsx
#	src/components/AllActivityModal.tsx
#	src/components/CircleCard.tsx
#	src/components/LikedTracksView.tsx
#	src/components/OutcastTracksView.tsx
#	src/components/PlaylistCard.tsx
#	src/components/TrackCard.tsx
#	src/components/UnplayedTrackRow.tsx
#	src/components/UnplayedTracksModal.tsx
#	src/components/UnplayedTracksWidget.tsx
#	src/db/schema.ts
#	src/lib/__tests__/spotify-budget.test.ts
#	src/lib/activity-utils.ts
#	src/lib/circle-health.ts
#	src/lib/polling-audit.ts
#	src/lib/spotify-budget.ts
#	src/lib/spotify-config.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@GraysonCAdams GraysonCAdams merged commit 54d50aa into main Feb 22, 2026
11 checks passed
@GraysonCAdams GraysonCAdams deleted the fix/lint-warnings-cleanup branch February 22, 2026 19:14
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