DEV Community

Cover image for I'm building a shopping app you talk to — here's the stack
Ibukun Demehin
Ibukun Demehin

Posted on

I'm building a shopping app you talk to — here's the stack

You're in the kitchen, fridge open, phone in the other hand. You hit one button and say: "a couple of bananas, milk, a dozen eggs and some chicken breast."

Your shopping list now looks like this:

[
  { "name": "Banana", "quantity": 2, "category": "Produce" },
  { "name": "Milk", "quantity": 1, "category": "Dairy" },
  { "name": "Egg", "quantity": 12, "unit": "dozen", "category": "Dairy" },
  { "name": "Chicken Breast", "quantity": 1, "category": "Meat" }
]
Enter fullscreen mode Exit fullscreen mode

That's real output from the parsing Lambda — "a couple" became 2, "a dozen" became 12, and everything landed in a sensible aisle. The app is called CannyCart, and this series is me building it in public: the stack, the features, and — mostly — the things that broke.

Why this app, and why in public

A grocery list is the most re-typed document in existence. You compose it while doing something else (cooking, staring into the fridge), which is exactly when typing is worst and talking is easiest. Voice → structured list felt like a real product, not a demo.

Why write it up? Because I've already burned evenings on bugs whose solutions live nowhere Google can find them. The fix for "my Metro bundler died with a 2017 babel package" deserves to be indexed somewhere. And honestly: writing the war story down is how I make the tuition worth it.

I'm building this solo, pair-programming with Claude. That shapes the workflow in ways I'll get into later in the series.

The stack, with one reason each

Piece Because
npm-workspaces monorepo One repo, two apps (mobile + web), one shared backend definition.
AWS Amplify Gen 2 The backend is TypeScript (CDK under the hood), the client is fully typed from the schema, and every git branch can get its own backend environment.
Expo SDK 57 + expo-router + NativeWind File-based routing and Tailwind ergonomics in React Native; EAS handles builds without me touching Xcode.
Next.js 16 + MUI The web/admin side. Boring on purpose — the mobile app is where the novelty budget goes.
Claude Haiku in a Lambda Turning "some chicken breast and a couple bananas" into structured JSON is exactly the shaped-extraction task small LLMs are great and cheap at.
A skills library Every repeatable setup (auth, soft deletes, async jobs…) lives as a written playbook in a repo I reuse across projects. The build follows the playbooks; the playbooks improve every build.

What's built so far

A fast montage — each of these gets its own post:

  • Auth + profile bootstrap — custom Cognito UI (no prebuilt Authenticator), and a post-confirmation Lambda that creates the user's profile row and group membership the instant they confirm.
  • A real theme system — light/dark/system, chosen on an Appearance page, persisted, and applied through design tokens so the tab bar, headers, and screens all flip together.
  • Branding — real icons, splash, and Nunito everywhere. (RN can't synthesize font weights; each weight is its own file. Ask me how I know.)
  • The Shopping tab — the headline feature: mic button → live on-device transcript → Claude parse → a review sheet where you fix anything it misheard → a categorised checklist with swipe-to-delete.

Shopping listRecord requestTranscribe requestTranslate to list

The voice pipeline deserves one sentence of architecture: speech-to-text happens on-device (iOS SFSpeechRecognizer / Android SpeechRecognizer via expo-speech-recognition), so audio never leaves the phone — only the final text goes to the Lambda. Free, private, fast.

What already went wrong (a preview)

Three real errors from the first two weeks, each getting a full post:

1. Every EAS build profile needs its own backend config, so I put the config file in an EAS file env var. Then:

Files can't be larger than 4 KiB. Provided value is 410 bytes above the limit.
Enter fullscreen mode Exit fullscreen mode

My config file is 4.5 KB — and the same file in a mature project of mine is 84 KB. The whole delivery mechanism was a dead end, and the redesign is post 2.

2. One evening the bundler simply stopped:

ERROR  TypeError: Cannot read properties of undefined (reading 'transformFile')
Enter fullscreen mode Exit fullscreen mode

The trail led through an icon library that reinvented itself between major versions, and ended at @babel/types@7.0.0-beta.4 — a package published in 2017 — silently hoisted to the top of my node_modules by a transitive Amplify dependency. Post 3.

3. My main branch refused to deploy while dev worked perfectly. CloudFormation said only:

CREATE_FAILED … Validation failed with 1 error(s).
ROLLBACK_COMPLETE
Enter fullscreen mode Exit fullscreen mode

The cause was a name two branches were silently fighting over. Post 4 is the forensics.

The series map

  1. This post — the what and the why
  2. One backend, three environments — the 4 KiB limit and the per-env config pattern that replaced it
  3. Every icon vanished — a Metro debugging story featuring a babel package from 2017
  4. Main won't deploy — CloudFormation forensics on Amplify Gen 2
  5. "Milk, and a dozen eggs" — the voice → Claude → checklist pipeline in depth
  6. Light/dark/system in NativeWind, properly

Where you come in

If you've shipped on this stack, tell me where it bites later — I'd rather hear it now. And if you want the war stories as they land, follow the series.

Next up (now live): how three build profiles each get the right backend when the obvious mechanism caps at 4 KiB.

Top comments (0)