BACK_TO_BLOG
·3 min read·Building / Engineering / AI

46 commits into Switchback: partners, push, and a real Community tab

A heavy week on kickstand-web — partner referral foundation, push notifications, background GPS recording, and a dedicated Community tab built on the existing backend.

Forty-six PRs landed on kickstand-web this week. Most of them stack into three threads worth talking about.

Partnerships and referral, end to end

Switchback finally has a real partner program. Five waves, all merged:

  • Wave 1 (#99, #101) — referral foundation. partners, referral_codes, referral_attributions, referral_accruals tables; a pure 25%-net / 12-month / 30-day-hold accrual calculator; ?ref=CODE capture at signup. I regenerated types.ts against the live schema and dropped the untyped cast — it had been stale since June 13 and twelve other tables had drifted.
  • Wave 2 (#102, #103) — partner badges. New partner_badges table, a partner-logos storage bucket, an SVG engine that renders logo/monogram medals, and the IRONSIDE seed. Partner badges show in the profile Badges tab and are pinnable to the showcase.
  • Wave 3a (#106, #107, #108, #109) — application intake and admin console. Become-a-Partner form, schema + validator + route, best-effort support@ email on submission, and a two-pane admin console that matches the comp (sidebar list + selected partner detail, with the review queue inside the main pane). The split list/detail layout was wrong; one screen is the comp.
  • Wave 4 (#111, #112) — public surfaces. /partners/[slug], /events/[slug], the /partners directory, and a site-header "More ▾" dropdown so the program is reachable from every page (it wasn't).
  • #104 — when a paid account is deleted, cancel the Polar subscription first (cancel-at-period-end). A delete should never strand a rider with a renewing bill.

Push notifications, background GPS, and a Community tab

The mobile side got the build-out it needed to be a real riding app.

  • #127 — background GPS recording on a dev/standalone build. expo-task-manager, iOS UIBackgroundModes:[location] with the always-permission string, Android ACCESS_BACKGROUND_LOCATION + FOREGROUND_SERVICE_LOCATION, and lib/ride-location-task.ts. Recording now survives a locked screen. This is the unlock.
  • #119, #120, #121 — push notifications for badge_earned, record_set, record_beaten, plus rendering the same three in the in-app notification centers. The pushes were firing but the clients were dropping unknown types.
  • #131–#139 — a dedicated Community tab. The old /feed screen is gone; the bottom tab (renamed from Group) is now the social home, built on the existing backend (lib/feed.ts, activity_kudos). No new tables. New activity_comments (#133) with a SECURITY DEFINER RPC that resolves the post owner server-side so the recipient can't be forged. Nearby + Global feed scopes (#135) use a shared assembleFeed() and distance-gate against POI lat/lng. The story rail always renders now, leading with a "Post" tile — sparse follow lists used to hide it entirely.

The bug worth remembering

#130. Every planner request on iOS dev builds was stalling for the full 30s and the Track-a-ride "Curves" button was crashing the app to home. The apex switchback-moto.com 307-redirects to www; RN's iOS fetch stalls following a 307 on a POST because the body doesn't get resent, and the dying request was taking down native networking with it. Hitting www directly returned in ~1s. An hour of staring at the planner code before checking the host.

The AI angle

Almost every PR above is co-authored with Claude Opus 4.8. That's not a curiosity anymore — it's the workflow. The leverage shows up in the wave-sized batches: Wave 1, Wave 2, Wave 3a, Wave 4 each shipped in one week because the spec, migration, validator, route, and UI move together instead of sequentially.

The judgment is still mine. The throughput isn't.