Skip to content

Writing

Notes from building things

Build logs, architecture decisions and the occasional debugging war story. Published on dev.to and mirrored here.

6 min read

Polish pass #3: store-release edges and platform traps

The third montage, from the weeks around launch: the Android alert that silently drops its fourth button, a keyboard trap hiding in one prop, an HTTP 431 caused by cookies, sessions that survived a failed refresh badly, and the day the app got a space in its name.

reactnative
mobile
debugging
buildinpublic

5 min read

MUI vs Tailwind: the cascade-layers war story

Two CSS systems, one cascade, and two one-line changes that each silently erased every input outline in the app. How CSS cascade layers make MUI and Tailwind coexist, why 'prepend: true' inverted the layer order, and why the bug only appeared behind the login.

css
react
nextjs
debugging

6 min read

The admin console: a web app on a mobile app's Cognito pool

The web console shares the phone app's identity pool — so a valid sign-in proves nothing about belonging there. Building the front door that sends non-admins straight back out, the deletion queue with its runbook inline, tables that share one set of defaults, and the dark-mode fix that lived in a colour-maths exception.

nextjs
aws
react
buildinpublic

5 min read

The activity log: what users did, without their words

A support question — 'did my receipt save?' — needs an audit trail. This one carries no user text ever, logs one row per action rather than per write, never logs from inside a mutation, and introduced the repo's first secondary indexes along with the one screen that must never load everything.

aws
dynamodb
architecture
buildinpublic

6 min read

In-app announcements: talking to users after the build shipped

Without over-the-air updates, a shipped build can't hear you. In-app announcements are the one channel left — with version targeting against what users actually run, severity that picks the shape, a force-update wall that fails open by construction, and two surprises device testing found.

reactnative
expo
mobile
buildinpublic

5 min read

Emails in the user's language, and the frozen Cognito pool

The app spoke 19 languages while its emails stayed English, because two senders fire where no profile row exists. Cognito's standard locale attribute fixed that — and declaring it broke every branch deploy with 'Invalid AttributeDataType', because a user pool's schema is frozen at creation and the sandbox never said so.

aws
cognito
i18n
buildinpublic

6 min read

The app speaks 19 languages: tiered i18n and the AI translation pipeline

From zero i18n and 660 hard-coded strings to nineteen languages in two tiers — per-user language that roams, Arabic flipping the shell right-to-left with a font swap that needed zero call-site changes, six plural categories, and a translation pipeline whose limiter is review, not tokens.

reactnative
i18n
mobile
buildinpublic

6 min read

Account deletion for real: immediate, guest round-trip, admin-initiated

Store review turned 'file a request' into 'actually delete it'. Three deletion lanes done properly: a server-side Lambda whose order of operations is the design, a guest email round-trip that can't become an email oracle, and admin deletion that doesn't rewrite history.

aws
security
serverless
buildinpublic

5 min read

Moving a live backend to a fresh AWS account

Two weeks before launch, the whole backend moved to a brand-new AWS account. What moves, what can't (Cognito users), what a fresh account makes free (the table rename), and the two quiet gotchas — a webhook that kept building in the old account, and env vars that don't come along.

aws
devops
serverless
buildinpublic

6 min read

Shipping to both stores: the release runbook

Canny Cart went live in the App Store and Google Play on 27 August. The two things that decided the date weren't code, the highest-consequence failure was silent, and the one rejection that arrived was about a permission button — here's the runbook, honestly.

mobile
expo
startup
buildinpublic

6 min read

Scan now, link later: receipts that wait for the list

Receipt scanning felt like homework because the app asked 'which list?' before it would save anything. The fix inverts the order: save first, link later — with a pending flag, provisional matches, and a one-time enrichment that keeps the snapshot immutable.

reactnative
aws
architecture
buildinpublic

4 min read

Branded Cognito emails: the customMessage trigger in 20 minutes

Cognito's default verification and reset emails look like a phishing test. One customMessage Lambda brands all of them — and three things will silently stop your emails if you get them wrong, plus the one line that keeps the trigger out of dependency trouble.

aws
cognito
serverless
buildinpublic

6 min read

Polish pass #2: a dozen small things that make it feel real

The second polish montage, with a spine: three screens sharing one hook that wasn't actually shared, an Android crash traced through logcat to a recycled bitmap, sheets that swallowed the back button, and the account-switch leak hiding in a persisted cache.

reactnative
mobile
debugging
buildinpublic

5 min read

The Home dashboard: one screen that earns the default tab

The default tab was a 12-line placeholder for months, because Home had nothing to own. It shipped as pure composition — no backend, no schema, no rebuild — held together by two rules that stop a dashboard from becoming a dumping ground.

reactnative
mobile
ux
buildinpublic

6 min read

Your prod data, faked: one-way replication with deterministic PII masking

Dev environments run on toy data or on a GDPR incident. The third option: mirror every production write into dev through a masking Lambda that fakes PII deterministically — same user, same fake identity, forever — with push tokens nulled, never copied.

aws
dynamodb
serverless
buildinpublic

5 min read

The compliance sprint nobody blogs about

Before v1 could ship, both stores handed over the same unglamorous list: a real website, real legal pages, and a real account-deletion path. What review actually requires, and the design decisions hiding inside the boring parts.

webdev
mobile
startup
buildinpublic

5 min read

Push notifications and the weekly shopping reminder

Notifications are where good apps become annoying, so every one had to pass a test: what would I thank the app for telling me? Local-first scheduling, token hygiene on shared devices, cold-start deep links, and broadcasts that can't drift from their source.

reactnative
expo
mobile
buildinpublic

5 min read

Tablet-worthy without a redesign: the content-column pass

iPad support in seven small phases, built on one decision: a 640pt centered content column that no phone is wide enough to trigger — so every change was tablet-only by construction. Plus the App Store rejection code the plan caught before submission day.

reactnative
expo
mobile
buildinpublic

5 min read

The EAS build that demanded four packages that never existed

npm ci failed on Missing: @opentelemetry/core@2.0.0 — four times, for a package version nothing asked for. A debugging war story about bundled dependencies, lockfile dialects, and published tarballs that are internally incoherent.

npm
expo
debugging
buildinpublic

6 min read

Photograph a paper list: the third input modality

Point the camera at the handwritten list on the fridge and get editable items. Almost pure assembly — the interesting parts are which pipeline it reuses, why the photo gets deleted, and two Android traps that had nothing to do with vision.

reactnative
aws
mobile
buildinpublic

6 min read

Budgets live on months, not lists: the period-first Expenses rework

The per-list budget from the MVP was measuring the wrong thing. Moving budgets onto a monthly map — with a carry-forward rule that stops editing the past from rewriting the present — and a headline that survives clearing your list.

reactnative
aws
architecture
buildinpublic

6 min read

The purchase ledger: when did you actually buy that?

Clearing the Done section felt like losing your history — but the record already existed as soft-deleted rows nothing read. One timestamp, three time semantics, and a documented exception to a house convention turned clearing into archiving.

aws
dynamodb
reactnative
buildinpublic

6 min read

Your app hardcodes GBP: currency as a first-class preference

A currency symbol is a user property masquerading as a constant. Unwinding one hardcoded £ touched state architecture (two kinds of readers, two stores, one setter), onboarding (guess, then confirm), and product honesty (relabel is never convert).

reactnative
i18n
mobile
buildinpublic

7 min read

What your receipts know: price history, "last paid", trends, savings

No price API exists, but after weeks of receipt scanning the user owns a dataset nobody sells. Three screens built on it — and the honesty gates, living in selectors not UI, that stop derived numbers from lying.

reactnative
datavis
mobile
buildinpublic

7 min read

Barcode lookup: a provider chain with an LLM as the normaliser

A barcode is an exact input — so the AI's job flips from interpreting to normalising, the lookup becomes a chain of fallbacks with 3-second fuses, and the cache becomes a shared global model that grows coverage exactly where the world's product databases are blank.

aws
ai
architecture
buildinpublic

8 min read

Point your camera at a receipt and Claude reads it

The headline money feature: photograph a crumpled receipt, extract priced line items with Claude vision, match them to your list, keep the receipt as an immutable record. Plus two war stories: an IAM AccessDenied that looked impossible, and the save that failed silently for a whole session.

reactnative
aws
ai
buildinpublic

6 min read

The Expenses tab MVP: prices ride on the items you already have

The money feature didn't need a money model. One new schema field, prices as an attribute of shopping items, sums in pennies, a green/amber/red budget bar — and the offline architecture from Part 7 paying rent.

reactnative
aws
mobile
buildinpublic

7 min read

A template is just a list with a flag: reusable shopping lists

Reusable 'Weekly essentials' lists shipped with one new schema field and zero new tables. The design fork (new model vs status vs flag), the copy-both-ways helper, and the three ripple effects one boolean forced.

reactnative
aws
architecture
buildinpublic

6 min read

You can't GROUP BY in DynamoDB — so the client does the counting

The feature was one line of SQL: my most-added grocery items, ranked. DynamoDB doesn't have the clause. Here's the client-side aggregation that shipped instead — with explicit scan budgets, honest counts, and a documented exit ramp.

aws
dynamodb
reactnative
buildinpublic

5 min read

I deleted the drag-and-drop library and shipped four buttons instead

Drag-to-reorder on a grouped React Native list set off a cascade of Android gesture bugs — crash, refresh freeze, header overlap, janky scroll. The fix wasn't debugging harder. It was re-reading the requirement.

reactnative
expo
android
buildinpublic

3 min read

Set up React Query on an Expo Project

There is a good chance that you will need to interact with an external API to receive and send data,...

mobile
react
reactnative
tutorial

1 min read

Introduction to the project (Frontend)

In this series, I will begin a journey into creating a platform that will allow anyone willing, to...

javascript
opensource
typescript
nextjs

2 min read

2D Array - HackerRank JavaScript Solution

Given a 6 X 6 Array arr: 1 1 1 0 0 0 0 1 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0...

javascript
tutorial
programming
algorithms