Portfolio
iOSEnd-to-End UXAI-Assistedv1 — In Progress

QuickStop

Building a Convenience Store App That Actually Delivers on Convenience

Role

Product Designer + iOS Developer

Stack

Claude AI · Figma · Figma MCP · SwiftUI · UIKit · ConvexMobile · Stripe

Convenience stores fail at the one thing in their name.

Lines behind lottery-ticket buyers. Mobile orders left on an open shelf anyone can grab. Fresh food with no prep timestamp. A drive across town only to find the item is out of stock. The apps that exist are loyalty cards with a receipt feature bolted on — they don't touch the actual friction.

The real problem isn't speed. It's certainty. Every customer arriving at a convenience store is making a bet: will the item be there, will the line be short, will this take 90 seconds or nine minutes? The opportunity was to eliminate the bet entirely — and let speed follow naturally once certainty was built in.

That single reframe — from fast to certain — drove every design and architecture decision that followed.


This project ran as a deliberate end-to-end test of an AI-assisted product workflow, using Claude throughout: not as an autocomplete, but as a thinking partner that could hold the full problem space and move between strategy, design, and code in the same session.

The honest result: the gap between having a judgment and seeing it rendered essentially closed. What previously took days of iteration — user research synthesis, IA scaffolding, component spec, Swift boilerplate — compressed into hours. That freed up the real bottleneck: the design decisions that actually require a human.


Part I — Design

Before wireframes, the question was: who is actually using a convenience store, and what does “fast” mean to each of them?

Standard market segmentation produces demographics. What the app needed was jobs to be done — specific goals people arrive with at different times of day. Using Claude to synthesize behavioral patterns from real convenience store use cases, five personas emerged:

User Research

QuickStop Personas

#NameArchetypeTheir version of "fast"
01Marcus Hale, 32The Commuter90 seconds in-and-out, same order every day
02Priya Ramanathan, 28The Night ShifterCurbside at 3am, fresh food she can trust
03Danny Reyes, 41The Bulk BuyerFour coffees, four tacos, one transaction
04Zoe Kapoor, 19The Impulse LocalScan-and-go with room to browse; three taps max
05Sarah Mitchell, 38The Emergency ErrandDelivery in under 15 minutes, verified stock

Each persona produced a priority weighting across dimensions — Speed, Freshness, Reliability, Discovery — that became design decision criteria throughout the project. When a structural choice had to be made, the question was: which persona does this hurt, and can they afford to be hurt?

The key insight, visible across all five:they don't all want the same speed — they want certainty. Certainty that the item is there, that the pickup method will work, that the trip will take exactly as long as expected. Build for certainty and speed becomes a byproduct.


Most convenience store apps exist to capture loyalty points, not to solve the trip. The design difference here is that every screen is designed around a specific moment of uncertainty and resolves it before anything else:

  • The home screen answers "is my store open and stocked?" before surfacing any action
  • The shop screen surfaces real-time stock status, not a catalogue that might be fiction
  • The orders screen is a live status board, not a receipt archive
  • Age-gated categories tell the user about the ID check before checkout, not when the driver arrives

This is a different product philosophy than “make the app look nice.” Every UX decision is load-bearing.


The IA started with five obvious tabs: Home, Shop, Orders, Store, You.

Two structural decisions changed the outcome:

Decision 1 — Scan & Go gets its own tab.

Scan & Go was originally nested inside Shop as a mode toggle. It was promoted to the top-level tab bar because it's not a shopping mode — it's a mode of being in the store. The input model is fundamentally different (camera-first vs. browse/search), and Zoe's constraint — three taps or she abandons — is only satisfiable from the tab bar, not from inside a nested flow.

Decision 2 — Store merges into Home.

Promoting Scan meant the tab bar was now at six. Rather than a sixth tab, Store (hours, live inventory health, location) was dissolved into Home's smart header. This was actually a better outcome: instead of certainty living on a separate screen people have to navigate to, it became the first thing visible on the app's primary screen.

[ Home ]  [ Shop ]  [ Scan ]  [ Orders ]  [ You ]
TabCore jobServes
HomeSmart entry — reorder, store status, quick actionsMarcus, Sarah
ShopBrowse + order-ahead + deliveryZoe, Priya, Sarah
ScanCamera-first in-store checkoutZoe, Marcus
OrdersActive tracking + pickup/delivery methodsAll
YouProfile, payment, loyaltyAll

The home screen is Marcus's primary surface — but it has to work for Sarah's emergency errand moments too. The “The Usual” module is the first reorderable content after the live store header, keeping his 90-second exit achievable. Below it, the quick-action tiles give everyone a direct path to their preferred mode without browsing.

The store status card at the bottom — wait time, in-stock percentage, delivery ETA — is the certainty layer made visible. It answers the question every user has before they even leave the house.

QuickStop home screen with store status, The Usual module, and quick actions

Ten top-level categories. Two age-gated. Each with subcategories, special filters, and cross-cutting feature logic tied to specific personas. The structural rule was two levels maximum — Category → Subcategory → Product list — with plain-language names that match real convenience store terminology.

The Shop landing is the first screen where Zoe's browse instinct and Marcus's extract instinct have to coexist. The mode selector (Order Ahead / Delivery) sits at the top so intent is declared early. “The Usual” reorder card surfaces immediately below — Marcus can be done in two taps without touching the category grid at all. Zoe scrolls past it to browse.

QuickStop shop landing with category grid and mode selector

The four cross-cutting strips that tie personas to the taxonomy

FeaturePersonaLogic
"The Usual"MarcusHistory-driven one-tap reorder at top of Shop
"New This Week"ZoeDiscovery strip; excludes age-gated categories
"Need It Now"SarahEmergency shortcut preset; excludes age-gated
"Running Low?"AllCadence-based reorder prompt

Age-gating model (v1 — trust-and-confirm)

The decision was a deliberate scope constraint. In-app ID scanning, ID.me, biometric verification — all deferred to v2. v1 uses a four-step model: DOB stored at account level → one-time-per-session cart acknowledgement → persistent ID-check banner on the Orders tab → manual visual ID check by driver or associate at handoff. The IA was deliberately designed so v2 can insert a real verification step between steps 3 and 4 without restructuring anything around it.


The category page is where the certainty layer has to be most granular. A “79 items” count means nothing if 55 of them are warm. The “24 cold now” metadata next to the item count is the design doing its core job: answering the question before it's asked.

Stock status badges on individual cards (green “Cold”, amber “Low”, red “Out”) give Zoe the information she needs to make a decision in the aisle without asking staff. The featured banner surfaces the week's deal without pushing it — it's the first content block after the subcategory chips, not a full-screen takeover.

QuickStop cold drinks category with stock status badges

The onboarding flow has one job: get the user to a first order fast, and set the habit that brings them back. Six screens:

  1. Value prop— “4 minutes. Your order. Ready. Guaranteed.” — leading with the promise, not a feature list
  2. Location — map-first, showing real nearby stores with wait times before asking for permission
  3. Category preferences— 12-tile grid; pick what you usually buy; used to personalize “New this week”
  4. Sign in— deferred until after the user has seen the product's value
  5. First order — a pre-populated order (most popular combo) with a first-order discount applied; change it if you want
  6. Success + habit prompt— order placed, 4-minute ETA visible, “Make this your usual?” prompt to set a recurring schedule

The habit prompt on the final screen is the most important moment in the flow. If Marcus sets “Mon · Wed · Fri · 8:15am,” the app has already won retention before he's picked up his first order.

QuickStop onboarding value proposition screen
QuickStop onboarding success and habit prompt screen

Active order tracking is Priya's screen — she's in the car at 3am watching the ETA count down before she walks in. The progress bar (Placed → Preparing → Ready → Picked up) keeps the state legible at a glance. The “I'm here” button for curbside triggers the car-side handoff without her needing to go inside.

The persistent ID-check banner (not shown here, added at checkout for age-gated items) runs across active orders that include alcohol or tobacco — visible in Orders so there are no surprises at the door.

QuickStop orders tab with active order tracking

Scan & Go is Zoe's exit strategy and Marcus's shortcut when he's already in the store. The flow is deliberately minimal: camera fills the screen, item count and cart total are always visible, checkout is one tap from anywhere. There's no browse mode inside Scan — you're in the store, you're scanning, you're leaving.

The cart total bar at the bottom keeps the running price visible through the whole session, which also handles the common “wait, how much is this?” moment without breaking the scanning flow.

QuickStop Scan and Go scanning screen

Part II — Development

The Swift project is organized around a principle that mirrors the Figma file structure: atomic design, strictly applied. The goal was to make the boundary between design and code mechanical rather than interpretive — a molecule in Figma maps to a molecule in Swift, no translation required.

Quickstop/
├── Sources/
│   ├── App/                        # Entry points
│   ├── Components/
│   │   ├── Atoms/                  # Primitive, single-purpose
│   │   │   ├── DSBadge
│   │   │   ├── DSButton
│   │   │   ├── DSLabel
│   │   │   ├── DSTextField
│   │   │   └── DSToggle
│   │   ├── Molecules/              # Composed from atoms
│   │   │   ├── ProductCard
│   │   │   ├── CartItemRow
│   │   │   ├── StockBadge
│   │   │   └── SearchBar
│   │   └── Organisms/              # Full UI sections
│   │       ├── ProductGrid
│   │       ├── CartSummary
│   │       └── PromoBannerCarousel
│   ├── DesignSystem/
│   │   └── Tokens/
│   │       ├── DSColors
│   │       ├── DSTypography
│   │       ├── DSSpacing
│   │       ├── DSRadius
│   │       └── DSShadow
│   ├── Features/
│   │   ├── Home/
│   │   ├── Cart/
│   │   ├── Orders/
│   │   ├── Auth/
│   │   └── Search/
│   └── ViewModels/

Before a single screen was built, the token layer was established. The brand palette was extracted directly from the Figma file via Figma MCP — variable values pulled programmatically, not transcribed by hand — and exported as DSColors.swift.

Brand palette

brandPrimary

Teal · #30C5FF

brandAccent1

Forest · #5C946E

brandAccent12

Sage · #80C2AF

brandAccent13

Sky · #A0DDE6

The file covers 70+ tokens across brand colors, system backgrounds (primary/secondary/tertiary with elevated variants), grouped backgrounds, labels, and vibrant labels — all with Light/Dark mode values, all accessible via Color.brandPrimary or UIColor.backgroundSecondary in view code.

The result: no hardcoded hex values anywhere in component or feature code. Light/Dark mode is handled entirely at the token layer — no conditional appearance checks in view logic.

// Every color decision in the codebase looks like this:
Text("Welcome")
    .foregroundColor(.labelPrimary)
    .background(Color.backgroundSecondary)

Button("Order Now") { }
    .tint(.brandPrimary)

Atoms

Atoms wrap iOS system controls with design system tokens applied. DSButton applies brand colors and the correct type scale. DSLabel applies semantic label colors. No business logic lives here.

Molecules

Molecules compose atoms into product concepts. ProductCard combines DSLabel, PriceLabel, StockBadge, and DSButton into one reusable unit. CartItemRow composes quantity, price, and image into a list-ready component.

Organisms

Organisms assemble molecules into full UI sections. ProductGrid handles layout and state for a list of ProductCards. PromoBannerCarousel manages scroll state across multiple banners.

The division is enforced: no business logic in atoms or molecules, no token lookups in organisms (they consume components, not token values directly).


ConvexMobile 0.7.0

Real-time backend sync for live inventory, order status, and stock levels. This is the technical choice that directly delivers the product promise. Certainty about what's in stock is only possible with a real-time data layer; polling doesn't cut it for a user who's already in the car.

Stripe 23.32.0

Payment processing. Apple Pay integration via the Stripe SDK keeps checkout friction minimal for Marcus's 90-second exit.


The Swift implementation was built using Cursor with DSColors.swift, DSTypography, and the project structure as persistent context. The workflow that changed most:

  • Component scaffolding

    Cursor generated atom and molecule boilerplate from a description of the Figma equivalent, with correct tokens applied from the design system files already in context. Starting a new component meant describing it, not writing it from scratch.

  • Token referencing

    Rather than looking up hex values or sizing constants, Cursor resolved the right token for each use case from the existing token files. No design-to-code translation step.

  • Preview generation

    PreviewMocks stubs were generated alongside each component so SwiftUI previews worked immediately, without a live backend. No waiting to see what a component looks like.

  • Bulk refactoring

    When token names changed during design system iteration, Cursor propagated the rename across all component files in a single pass.


This project was a deliberate test of how far an AI-assisted workflow could go end-to-end. The honest accounting:

What compressed significantly

TaskBeforeWith AI
Persona synthesisDays of research consolidationOne focused session
IA iterationMultiple async review roundsPropose, critique, revise in the same session
Shop taxonomy + edge casesMultiple doc draftsSingle iterative session with immediate stress-testing
DSColors.swiftManual Figma → Swift transcriptionExtracted programmatically via Figma MCP
Figma screen constructionHand-placed every elementPlugin code written and executed via MCP
Swift component boilerplateWritten from scratchScaffolded from Figma description + token context

What still required human judgment

  • Deciding which structural option was right — Scan & Go promotion, merging Store into Home
  • Visual taste — verifying that designs looked right, not just structurally correct
  • Scoping — what goes in v1, what waits for v2, and what never ships
  • Architecture decisions — the atomic design hierarchy, token layer separation, ConvexMobile choice

The net effect: AI removed the time between having a judgment and seeing it rendered. It didn't produce the judgments.


ArtifactFormatDescription
personas.htmlHTMLFive user personas with pain points, needs, priority bars
quickstop-ia.mdMarkdownFull IA v0.4, 5-tab nav, age-gating model
quickstop-shop-categories.mdMarkdownShop taxonomy v0.4, 10 categories, age-gated flows
DSColors.swiftSwiftFull color token file, SwiftUI + UIKit, 70+ tokens
Figma design fileFigmaShop Landing, Cold Drinks, Product Detail, Onboarding (6 screens)
iOS implementationSwiftNative app using atomic design + DSColors token bridge

  • Age-gated checkpoint sheet (the ID-required modal at cart level)
  • Orders tab — active order tracking, pickup method screens
  • Scan & Go flow — scanner UI, cart drawer, exit & pay
  • v2 scoping: crew/group ordering, business accounts, in-app ID verification (drop-in to existing flow)