Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,113 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

WingDex

A photo-first bird identification and life list tracker. Upload your bird photos, identify the species on your device, and build your personal WingDex over time.

Try it ->

image

What is WingDex?

WingDex is for reverse birding: people who take photos first and identify species later. Instead of checklists and field guides, you upload photos you already took, and the app identifies the birds. You just confirm with a tap.

Photos never leave your device. Identification runs entirely in the browser, or natively on iOS. There is no inference server, so there is nothing to upload a photo to.

Drop a whole day's photos at once and the batch wizard clusters them into outings by time and location, identifies each bird, and lets you confirm the lot in one pass. Species link back to the outings you saw them on, and outings link forward to the species, so you can cross-reference either direction.

Features

  • On-device species ID - ~11,000 eBird species, reranked by where and when the photo was taken
  • Private by construction - photos are never uploaded or stored; all bird imagery comes from Wikipedia
  • Batch upload - photos auto-grouped into outings by time and GPS, merged with existing sessions, deduplicated by hash
  • Life list - first and last seen, sighting counts, searchable and sortable, with per-species detail and history
  • eBird integration - import and export checklists and life lists in eBird Record Format
  • Works offline - after the one-time model download, identification needs no network
  • Accounts - anonymous sessions, passkeys, and GitHub / Google / Apple sign-in
  • iOS app - native companion app in ios/

How it works

  1. Upload photos from your device
  2. EXIF GPS and timestamps are read in the browser and photos are clustered into outings
  3. Review the outing: date, auto-geocoded location, notes
  4. Identify on-device, ranked by visual similarity and then by how likely each species is at that place and time of year
  5. Confirm the top suggestion, pick an alternative, or skip
  6. Saved to your WingDex

The first identification downloads about 53 MB of model files, once. After that everything is local and works offline.

Identification

WingCLIP-0.3 is a 39M-parameter image encoder distilled from BioCLIP-2 via WingCLIP-0.1. Small enough to download once and run on a phone.

Vision alone struggles to separate lookalike species, so candidates are reranked against an empirical prior over what has actually been reported in that map cell in that month. Some pairs are hard to tell apart from pixels and trivial to tell apart from geography. With that rerank the pipeline reaches 95.0 percent top-1 on a 3,322-photo held-out split.

The same model, priors, and preprocessing ship on both platforms, and a golden-vector suite pins the two implementations to each other.

Tech stack

Layer Technology
Platform Cloudflare Workers, D1 (SQLite)
Frontend React 19, TypeScript, Vite 8
Styling Tailwind CSS 4, Radix UI primitives, Phosphor Icons
Auth better-auth (anonymous, passkeys, GitHub/Google/Apple OAuth)
Identification WingCLIP-0.3 via ONNX Runtime Web (browser) and Core ML (iOS)
Geocoding WingDex proxy backed by Geoapify (results may incorporate OpenStreetMap data)
Bird imagery Wikipedia REST API
Testing Vitest (unit), Playwright (e2e), XCTest (iOS)
iOS Swift, XcodeGen

Development

Requires Node 24+. No API keys: identification runs on the device, and anonymous auth works out of the box.

git clone https://github.com/jlian/wingdex.git
cd wingdex
npm install
npm run db:migrate
npm run dev

npm run dev serves Vite on :5000 with Wrangler behind /api/* on :8787, and creates .dev.vars from the example on first run. npm stop clears stale ports. Local D1 state lives in ~/.cache/wingdex/wrangler-state.

Run npm run check (lint, typecheck, unit) before pushing, and npm run check:all (adds e2e and a production build) when the change touches functions/, e2e/, routing, auth, or data flow. Everything runnable is in package.json under scripts.

Path Purpose
src/components/ React components: ui/ primitives, pages/, flows/
src/lib/ Client-side logic, including identification and ranking
src/__tests__/ Vitest unit and integration tests
functions/ Cloudflare Workers API routes and shared server logic
migrations/ D1 SQL migrations
e2e/ Playwright specs
ios/ Native iOS app (XcodeGen)
ml/ Model conversion and cross-platform parity harnesses

Releases

PR titles follow Conventional Commits (feat: add outing merge UX, fix: handle wiki 404 fallback). On merge to main, Release Please works out the next version, updates CHANGELOG.md, and tags it.

Contributing

See CONTRIBUTING.md.

License

MIT

About

For reverse birders who take pictures first and then ID in bulk.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages

Generated from github/spark-template