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.
$ cat ./projects/tinky.md
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.
$ cat product.md
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.
$ ./scan --trace
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.
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.
The JPEG goes to a private Supabase Storage bucket. The app then calls the API with just the object path, not the image.
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.
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.
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
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.
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.
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.
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.
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.
$ python -m eval.run_eval
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.
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.
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%.
Classifying the detector's crop instead of the full frame lifted top-1 on small, off-centre dogs from 18.5% to 66.8%.
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.
Top-1 accuracy after the client's image compression
Top-1 accuracy on small, off-centre dogs
Photos with no animal that were shown a dog breed
Off-centre dogs answered "that's a cat"
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