$ cat ./projects/tinky.md

Tinky app icon

Tinky: What's My Dog?

Point the camera at any dog and get its breed in seconds — with a confidence score, a personality profile, and an honest answer when it isn't a dog at all.

Live on the App Store · free
Tinky camera screen: a puppy framed in the viewfinder with one shutter button
One screen, one button: frame the dog, tap once.
Tinky result screen: Golden Retriever, 96% match, breed description and trait bars
Breed, confidence, description and five trait bars.
Tinky 'Not a Dog' screen shown when no dog is detected in the photo
No dog in the frame — no invented breed.

$ cat product.md

The product

Tinky is a deliberately small bet: a consumer app that delivers its whole value in two actions — open it, take a photo. The MVP was capped at three screens: launch, camera, result. No sign-up, no feed, no history.

A result is more than a label. You get the top match with its confidence, runner-up breeds when the model is unsure — so mixed pups get an honest answer — a short breed description, trait bars for friendliness, energy, trainability, kid-friendliness and grooming, a matching breed photo, and a share button.

I built it end to end: the product spec, the React Native app, the backend, the ML service and its evaluation harness, the tinky.dog landing page, analytics and monetisation.

120 dog breeds recognised
3 screens in the whole app
1.5–3 s warm scan, end to end
$0 free, no account needed

$ ./scan --trace

How a scan travels

The phone never runs a model. It takes a good photo, hands it to one API endpoint and renders the answer — which keeps the app at 26 MB and lets me ship model changes without an App Store review.

  1. Capture

    The camera (expo-camera) or the photo library. The shot is resized to 512 px and saved as JPEG at quality 0.75 — the smallest upload that doesn't cost accuracy.

  2. Upload

    The JPEG goes to a private Supabase Storage bucket. The app then calls the API with just the object path, not the image.

  3. Edge function

    A Supabase Edge Function (Deno, TypeScript) validates the request, rate-limits by IP through a Postgres function, fetches the scan and calls the ML service with a hard timeout.

  4. Detect, crop, classify

    FastAPI on Google Cloud Run. An SSD MobileNetV2 detector decides dog / cat / other and crops the best dog box with 15% padding; a MobileNetV2 classifier then names one of 120 breeds from the crop.

  5. Enrich and respond

    The function joins the top label to a breeds table in Postgres — name, description, trait scores — deletes the uploaded photo and returns JSON. The app adds a breed portrait from the Unsplash API.

$ ls -la ./stack

Technology inside

Mobile app

One TypeScript codebase with file-based routing; native iOS builds and store submission through EAS. Haptics and a processing overlay make a two-second wait feel intentional.

React Native 0.81 Expo SDK 54 TypeScript Expo Router expo-camera expo-image-manipulator EAS Build

Backend

A single endpoint, identify. Postgres holds the breed reference and rate-limit counters; migrations revoke direct table access, so a client can reach only the function and the upload bucket.

Supabase Edge Functions · Deno Postgres Storage Row-level security

ML service

Both models are baked into the container image and loaded at startup, and one instance stays warm — nobody pays the ~15-second model load. Routing thresholds are environment variables, so tuning is a redeploy, not a retrain.

TensorFlow 2.17 Google Cloud Run Python · FastAPI TF-Hub SSD MobileNetV2 (COCO) MobileNetV2 · Stanford Dogs Docker

Quality and observability

An offline harness replays production preprocessing over four datasets before any model or threshold change ships. In production, client errors and crashes are reported back, and the edge function logs per-step timings.

Custom eval harness Stanford Dogs Oxford-IIIT Pet COCO Firebase Analytics Crashlytics

Landing, growth and monetisation

tinky.dog is a React single-page site prerendered to static HTML at build time for SEO, with structured data, a sitemap and consent-aware analytics. The app is free and ad-supported, with Apple's tracking prompt respected.

React 19 · Vite 7 Tailwind CSS 4 Framer Motion Static prerender Vercel GA4 · Consent Mode v2 AdMob App Tracking Transparency

$ python -m eval.run_eval

Measured, not guessed

The first version felt wrong on real photos, and anecdotes weren't getting me anywhere. So I built an evaluation harness that runs the exact production preprocessing over labelled data: framed dogs, off-centre dogs, cats, and scenes with no animal at all. It changed what I worked on.

  • Compression was eating the model

    The app uploaded 224 px JPEGs at quality 0.3 to save bandwidth. That alone cost 18.5 points of top-1 accuracy. Sending 512 px at quality 0.75 — a two-line client change — recovered all of it.

  • Confidence is not a dog detector

    A softmax threshold was supposed to reject non-dogs. In fact 58.6% of photos with no animal still got a confident breed. Putting an object detector in front took that to 1.0%.

  • Real photos aren't centred

    Classifying the detector's crop instead of the full frame lifted top-1 on small, off-centre dogs from 18.5% to 66.8%.

  • The trade-off, and what's next

    I accepted −1.9 points on perfectly framed dogs to get there. The known gap: 9.7% of cat photos are still shown a breed — the fix is a classifier retrained with a cat class.

Before → after eval · 2026-06

Top-1 accuracy after the client's image compression

before68.2%
after86.9%

Top-1 accuracy on small, off-centre dogs

before18.5%
after66.8%

Photos with no animal that were shown a dog breed

before58.6%
after1.0%

Off-centre dogs answered "that's a cat"

before14.0%
after2.1%

Accuracy on Stanford Dogs is an upper bound: the breed classifier saw that dataset in training, so read the deltas, not the absolute numbers. Off-centre, cat (Oxford-IIIT Pet) and no-animal (COCO) sets carry no such caveat. Breed guesses are for fun and information — not a substitute for a vet or a DNA test.

$ open https://tinky.dog

Got a dog nearby?
See what Tinky makes of it.