<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tyrell</title>
    <description>The latest articles on DEV Community by Tyrell (@tyrell-snyders).</description>
    <link>https://dev.to/tyrell-snyders</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2023768%2F9fca231d-d67f-4b1e-a6da-e97f7c74b384.jpg</url>
      <title>DEV Community: Tyrell</title>
      <link>https://dev.to/tyrell-snyders</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tyrell-snyders"/>
    <language>en</language>
    <item>
      <title>Last Mile Splitter: Turning Strangers into Ride-Shares with Gemini AI</title>
      <dc:creator>Tyrell</dc:creator>
      <pubDate>Sat, 28 Feb 2026 14:29:58 +0000</pubDate>
      <link>https://dev.to/tyrell-snyders/last-mile-splitter-turning-strangers-into-ride-shares-with-gemini-ai-53h2</link>
      <guid>https://dev.to/tyrell-snyders/last-mile-splitter-turning-strangers-into-ride-shares-with-gemini-ai-53h2</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/weekend-2026-02-28"&gt;DEV Weekend Challenge: Community&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Community
&lt;/h2&gt;

&lt;p&gt;South African commuters who use public transit (Gautrain, MetroRail, MyCiti) and need to cover the &lt;strong&gt;last mile&lt;/strong&gt; from the station to their workplace. Every morning, dozens of people arrive at the same station heading to the same business parks — but each one hails a separate ride at full price. This app turns strangers at the same station into a ride-sharing community.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Last Mile Splitter&lt;/strong&gt; — a Progressive Web App that lets commuters post and join last-mile ride shares in seconds.&lt;/p&gt;

&lt;p&gt;Instead of filling out forms, you type (or voice-dictate) a single sentence like &lt;em&gt;"Arriving at Rosebank at 8:15 AM, heading to Bedfordview, looking for 2 people to split an Uber"&lt;/em&gt; — and AI (Google Gemini) parses it into structured ride data and publishes it to a real-time board.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🤖 &lt;strong&gt;Zero-Friction AI Parsing&lt;/strong&gt; — Uses the Vercel AI SDK and Google Gemini 2.5 Flash (&lt;code&gt;generateObject&lt;/code&gt; with a strict Zod schema) to instantly convert natural language into structured ride data. Includes a bulletproof regex fallback if the AI rate limits are hit.&lt;/li&gt;
&lt;li&gt;🎙️ &lt;strong&gt;Voice dictation&lt;/strong&gt; — Integrated the Web Speech API (localised to &lt;code&gt;en-ZA&lt;/code&gt;) so commuters can just speak their route while walking.&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;Auto-Syncing Real-Time Feed&lt;/strong&gt; — Powered by Supabase Realtime (&lt;code&gt;postgres_changes&lt;/code&gt;). The feed updates instantly across all connected devices—no refreshing required.&lt;/li&gt;
&lt;li&gt;🔔 &lt;strong&gt;Native Push Notifications&lt;/strong&gt; — full Web Push API implementation using VAPID keys and a custom Service Worker to deliver native OS notifications when someone joins a ride.&lt;/li&gt;
&lt;li&gt;🧠 &lt;strong&gt;Smart Database Automation&lt;/strong&gt; - Custom PostgreSQL triggers automatically decrement seat counts, mark rides as '&lt;code&gt;full'&lt;/code&gt; when capacity is reached, and auto-generate notifications without requiring frontend logic.&lt;/li&gt;
&lt;li&gt;📱 &lt;strong&gt;Installable PWA&lt;/strong&gt; — add to home screen on Android and iOS, no app store needed&lt;/li&gt;
&lt;li&gt;🔐 &lt;strong&gt;Robust Session Management&lt;/strong&gt; — Implemented a custom Next.js proxy wrapper to seamlessly refresh expired JWT tokens and sync cookies between the client and server.&lt;/li&gt;
&lt;li&gt;🪟 &lt;strong&gt;Glassmorphism UI&lt;/strong&gt; — frosted glass cards, gradient backgrounds, dark mode support&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;🔗 &lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://last-mile-splitter.vercel.app" rel="noopener noreferrer"&gt;https://last-mile-splitter.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/tsnyders" rel="noopener noreferrer"&gt;
        tsnyders
      &lt;/a&gt; / &lt;a href="https://github.com/tsnyders/last-mile-splitter" rel="noopener noreferrer"&gt;
        last-mile-splitter
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      An AI-powered, real-time PWA helping daily commuters instantly coordinate and split rides for the "last mile" of their journey.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Last Mile Splitter&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Split last-mile rides from transit stations. Post a ride in one sentence, join in one tap.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Weekend Dev Challenge&lt;/strong&gt; — a PWA built with Next.js 16, Supabase, and Gemini AI.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What it does&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Commuters arriving at a train or bus station often need to cover the "last mile" to their workplace — usually by ride-hail, at full price, alone. &lt;strong&gt;Last Mile Splitter&lt;/strong&gt; lets them:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Post a ride&lt;/strong&gt; in natural language — &lt;em&gt;"Rosebank to Bedfordview at 08:15, need 2 seats"&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browse a live feed&lt;/strong&gt; of open rides updated in real time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join in one tap&lt;/strong&gt; (with confirmation) — seat counts update instantly for everyone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get notified&lt;/strong&gt; when someone joins your ride — in-app bell + native push notifications&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;An AI endpoint (Google Gemini) parses free-text input into structured ride data. If the AI is unavailable, a regex fallback handles common patterns. A mic button lets users voice-dictate instead…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/tsnyders/last-mile-splitter" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Technology&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Framework&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Next.js 16&lt;/strong&gt; (App Router)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Tailwind CSS 4&lt;/strong&gt; + custom glassmorphism utilities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth &amp;amp; Database&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Supabase&lt;/strong&gt; (Auth, Postgres, Row Level Security, Realtime)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Vercel AI SDK&lt;/strong&gt; + &lt;strong&gt;Google Gemini 2.5 Flash&lt;/strong&gt; (&lt;code&gt;generateObject&lt;/code&gt; with Zod schema)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push Notifications&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Web Push API&lt;/strong&gt; + &lt;code&gt;web-push&lt;/code&gt; (VAPID keys)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Voice Input&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Web Speech API&lt;/strong&gt; (en-ZA locale)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PWA&lt;/td&gt;
&lt;td&gt;Web App Manifest + Service Worker (network-first caching + push handler)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploy&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Vercel&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Building this app required orchestrating several real-time services and ensuring the UX felt completely frictionless. Here is a look under the hood:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The AI Integration (Vercel AI SDK + Gemini)
&lt;/h3&gt;

&lt;p&gt;The core differentiator of this app is skipping the traditional multi-step form. I used the Vercel AI SDK connected to Google Gemini 2.5 Flash. When a user types "Looking for 2 people to split an Uber to Bedfordview," the system prompt is smart enough to infer implicit data (e.g., needing 2 people means a total of 3 seats including the creator) and returns a perfectly formatted JSON object. If the AI is ever unavailable, the client gracefully falls back to a regex parser so the app never breaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Database &amp;amp; Automation (Supabase)
&lt;/h3&gt;

&lt;p&gt;I wanted the database to handle the heavy lifting to keep the client fast. The Postgres database uses 5 highly-normalized tables (&lt;code&gt;profiles&lt;/code&gt;, &lt;code&gt;rides&lt;/code&gt;, &lt;code&gt;ride_participants&lt;/code&gt;, &lt;code&gt;notifications&lt;/code&gt;, &lt;code&gt;push_subscriptions&lt;/code&gt;) secured by strict Row Level Security (RLS) policies.&lt;/p&gt;

&lt;p&gt;Instead of relying on the frontend to manage ride capacity, I wrote &lt;strong&gt;PostgreSQL Triggers&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Seat Syncing&lt;/strong&gt;: When a user joins or leaves a ride, a &lt;code&gt;SECURITY DEFINER&lt;/code&gt; trigger automatically decrements or increments the &lt;code&gt;available_seats&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auto-Full Status&lt;/strong&gt;: If seats hit zero, a trigger instantly updates the ride status to &lt;code&gt;'full'&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Auto-Profiles&lt;/strong&gt;: A trigger automatically generates a public profile row whenever a new user signs up via Supabase Auth.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Real-Time &amp;amp; Push Architecture
&lt;/h3&gt;

&lt;p&gt;Getting push notifications to work on a PWA without an app store was the biggest weekend hurdle. I built a reactive chain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A user taps "Join Ride" (featuring a two-tap confirmation to prevent accidental joins).&lt;/li&gt;
&lt;li&gt;A database trigger automatically inserts a row into the &lt;code&gt;notifications&lt;/code&gt;table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase Realtime&lt;/strong&gt; instantly broadcasts this &lt;code&gt;INSERT&lt;/code&gt;to the creator's browser.&lt;/li&gt;
&lt;li&gt;The client updates the in-app bell badge and triggers a &lt;code&gt;POST&lt;/code&gt;to my &lt;code&gt;/api/push/send endpoint&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The Next.js backend looks up the user's VAPID keys in the &lt;code&gt;push_subscriptions&lt;/code&gt; table and uses &lt;code&gt;web-push&lt;/code&gt; to trigger the Service Worker (&lt;code&gt;sw.js&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The user gets a native OS notification on their phone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Frontend &amp;amp; UX (Next.js 16)
&lt;/h3&gt;

&lt;p&gt;The UI was built mobile-first using the Next.js App Router and Tailwind CSS v4. To give it that premium, native-app feel, I designed a custom Glassmorphism UI with frosted glass cards and smooth gradient backgrounds. I also implemented global error boundaries (&lt;code&gt;global-error.tsx&lt;/code&gt;) and a custom toast notification context to ensure that if anything fails while a commuter is rushing, the app recovers gracefully and provides immediate, clear feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Login&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3e744sbav7daxcuz2o2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3e744sbav7daxcuz2o2.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sign Up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbleyt6l4c5f88efswic4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbleyt6l4c5f88efswic4.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgot Password&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwznyvs90l8kmp8yzzz0u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwznyvs90l8kmp8yzzz0u.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Home Screen&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi26ghomzq3qvhz2cfjg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi26ghomzq3qvhz2cfjg1.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notifications&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk840gdbq7umcmmph1n00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk840gdbq7umcmmph1n00.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Built solo over one weekend. ☕&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
