Whiskerville
Whiskerville was designed around a simple observation: The hardest part of rescue work is often coordination, not compassion.
Overview
Many foster-based rescues run on a patchwork of Slack threads, spreadsheets, shared drives, text chains, and institutional memory — patterns I became deeply familiar with while volunteering in animal rescue. Foster placement, transport, clinic days, sitter coverage, supply requests, adoption applications, and medical tracking each live in a different tool, and nobody has a single view of what is happening.
Whiskerville is my answer: a multi-tenant operations platform for rescues and foster networks. It started as a front-end exploration of calm, workflow-driven UX and has grown into a full product — a React and TypeScript front end on Postgres with row-level security, roles and permissions, notifications, reporting, mapping, and grounded AI writing for adoption listings.
I designed and built all of it: product scope, data model, interaction design, visual system, front end, and backend schema. The through-line never changed:
“Make operational work feel calm, flexible, and humane, even as the organization gets more complex.”
What I Focused On
Real rescue operations are dynamic: animals move between foster homes, clinic schedules shift, transport changes daily, volunteers cover for each other, and a supply run serves a whole foster household. The system had to support that without turning volunteers into ticket-filers.
- Modeling placements, adoptions, and clinic visits as first-class objects with history, not fields overwritten on an animal
- Timeline-based operational visibility for every animal
- Lightweight request-and-claim workflows for transport, sitter coverage, and supplies
- Grounded AI that writes summaries and adoption listings from the rescue's own data
- Roles and permissions so fosters, volunteers, and coordinators can share one system safely
- Per-organization configuration instead of baked-in assumptions
- An interface that stays calm at ten thousand animals, not just ten
Product & UX Decisions
Foster placements as first-class operational objects
Animals move between foster homes for medical needs, behavior, availability, emergency overflow, and temporary coverage. Instead of overwriting a single “current foster” field, each placement is its own record with a start, an end, a reason, and a temporary flag. Reassignment closes one and opens the next, so foster history reads as a coherent timeline rather than a series of anonymous edits.
Guardrails grew out of that model: placing an animal moves it into care automatically, the form warns when a foster is at capacity, and a foster parent can request a reassignment themselves rather than waiting on a coordinator.
Normalize where it helps the humans
A recurring challenge was resisting structure that looked clean technically but created friction operationally. Supply requests serve a foster household, not one animal. A sitter request covers everything in someone's care. A transport may carry animals, supplies, or both. A bonded pair moves through a single adoption application. A litter is entered once, with only the per-kitten differences filled in. Each of those cost something in relational purity and paid it back in speed for the person filling out the form.
Timeline-driven operational visibility
The timeline is the backbone of the animal record. Intake, placements, medical records, clinic visits, notes, adoption progress, and transport are organized chronologically and filterable, so anyone opening the page can answer the two questions that matter:
What happened to this animal, and what happens next?
“What happens next” is its own object: an animal has at most one open action item, and any priority above normal requires one. The banner is a forcing function, not a reminder.
Grounded AI, not generative filler
Rescues write the same things over and over: a summary for a foster handoff, a Petfinder blurb, a longer profile for their own site. Whiskerville generates these with one hard constraint: the model may only use what the rescue has actually recorded. It is fed the animal's traits, medical history, notes, and placement context, and told to omit anything not stated rather than invent a friendly detail.
Two details make that trustworthy in practice. A Summary Inputs card shows which sources the summary drew on, so a thin summary reads as a data-quality prompt rather than a model failure. And every generated piece is fingerprinted against its inputs, so the UI flags it as stale when the record changes.
For adoption listings, the organization owns a template of fixed disclaimers, fees, and merge fields; AI fills only the animal-specific sections. Tone and length are org settings — a short Petfinder blurb or a detailed website profile — so the output sounds like the rescue, not like a model.
From record to adoption
Adoption is a pipeline, not a checkbox. Applications move from inquiry through meet-and-greet, paperwork, and placement. Unsuccessful closes are recorded distinctly (declined, withdrawn, duplicate) so reports do not compress every lost application into an org-sounding “cancelled,” and returns bring the animal back into care with a required reason. Because rescues post the same animal in several places, each animal also tracks its external listings per platform — Petfinder, Adopt-a-Pet, the org's site, social — with a draft, published, or removed status. That is groundwork for automated syncing without pretending it exists yet.
Rescue sites on a map
Trap-neuter-return work is geographic: a colony behind a restaurant, a feeder who checks it daily, a trapper who covers that neighborhood. Whiskerville models these as Rescue Sites — a named location with an address-autocompleted map pin, a site lead, a point of contact, volunteers with their roles, and the animals that came from it. Closing a site keeps its history. The same address and mapping layer powers saved locations for clinics and transport pickups, so “where” is entered once and reused everywhere.
Coordination without tickets
Transport, sitter coverage, and supplies share one request-and-claim pattern: anyone can raise a request, a volunteer claims it or a coordinator assigns it, and everyone involved is told. Transports carry animals and supplies with separate pickup and drop-off locations; a sitter request temporarily hands off placement responsibility; a supply request can be submitted on behalf of a foster and warns about duplicates.
Notifications are what let this replace Slack rather than duplicate it. Events are created by database triggers, never the client, and fan out per recipient. An hourly job sends proactive reminders in the organization's own timezone: a clinic appointment tomorrow, a transport still needing a volunteer, a foster placement ending in three days.
Clinic days as an operational system
A clinic event is a coordination hub: a date, a location, a vet, a coordinator, limited slots, and the transport needed to fill them. Each slot is one animal, and each animal usually gets several procedures in a visit, so procedures are tracked individually. A three-step completion flow (attendance, records, review) records who showed up and what was done, captures what was learned on the table (a previously unknown sex, for example), and writes medical records back to each animal, guarding against duplicates of records that already exist.
One system, many kinds of users
Owners and admins configure the organization; members get granular permissions (animals, medical, transport, adoptions, sites, listings); foster parents see and edit only what concerns the animals in their care, and contact details can be hidden from members who do not need them. Access is invite-based with Google, email, and passkey sign-in, and support staff can be granted temporary, audited access when something needs a second pair of eyes.
Configuration instead of assumptions
Every rescue is opinionated in a different way. Rather than encode one org's rules, each organization enables its own species and breeds, defines its own traits (split into characteristics of the animal versus requirements of the home), sets a name-reuse policy from “duplicates are fine” to “wait two years,” edits its adoption template, and hides entire workflows it does not use — a shelter without a foster program never sees foster or sitting features at all.
Visual & Interaction Design
The visual system is meant to feel calm, trustworthy, and operationally clear. Many operational tools overwhelm because every workflow competes equally for attention. Whiskerville prioritizes strong hierarchy, restrained color, soft surfaces, generous spacing, and lightweight status signaling: lifecycle status, priority, and care flags are three separate axes and are styled as three separate things, and an opportunity flag like “recommended for transfer” is deliberately green, not amber.
Principles that held up across dozens of screens:
- Progressive disclosure: hero actions collapse, help lives in a drawer, and first-time setup is a dismissable checklist rather than clutter on the working screen
- Search-driven pickers wherever an entity is referenced, never dropdowns, because every list eventually gets long
- Validation that explains itself — “Luna can't be reused until Aug 2028, previously used by Luna (Adopted)” — with a link to the conflict
- Forcing functions over nags: an adoptable animal without a listing, or an urgent animal without a next step, prompts in place
- The same layouts on a phone, because fosters and trappers are rarely at a desk
Engineering Notes
Whiskerville is React, TypeScript, Vite, and Tailwind on Supabase (Postgres, row-level security, storage, auth), with edge functions for AI generation and email. A few decisions I would defend in a design review:
- Tenancy is enforced in the database. Every table carries an organization id and a row-level policy. The client never filters by org itself, so a UI bug cannot leak another rescue's data.
- Built for scale before it was needed. A TNR-heavy org can take in a thousand cats a year. Animals and contacts load an operational subset up front plus a slim index of every record for search; historical rows are fetched on demand, and long lists are virtualized.
- Optimistic, centralized writes. Every mutation goes through one context layer with optimistic updates and reconciliation on failure. Failures surface once, centrally: a calm toast, a logged error, and a one-click path to report a bug.
- Documents are private, photos are public. Adoption applications and vet records live in a private bucket behind short-lived signed URLs; photos live in a public one. Same path convention, different threat model.
- Analytics with a privacy budget. Explicit events only, ids and enums only, never names or note text. The audit trail stays in Postgres.
Trade-offs
The public demo linked above runs entirely in the browser against seeded data, so anyone can explore it without an account; the production build is the same interface on the live backend. A few things are deliberately not built:
- External listings are tracked, not synced. Pushing to Petfinder automatically is a future step, and the data model was shaped so it will not need a rewrite.
- Notifications poll rather than stream. The hook is shaped so swapping in realtime is a drop-in.
- Uploaded documents are not fed into AI summaries. Grounding in structured data was the point; PDFs would make the provenance murkier.
Why this approach
Building fewer workflows with strong operational realism, then hardening them into a real multi-tenant product, was a more meaningful exercise than a generic animal database with every feature half-built.
Result
Whiskerville grew from a UX exploration into a working platform, and the original thesis held: operational complexity becomes understandable instead of overwhelming when the workflows mirror how the work actually happens.
The project reflects the kind of product engineering I like best, where
- UX and interaction design
- front-end engineering
- data modeling and permissions
- workflow and systems design
- grounded, trustworthy AI
…all reinforce each other instead of existing separately.
Rather than treating rescue work as a collection of forms and records, Whiskerville approaches it as a living operational system centered around coordination, trust, and care.
— Dan Thoreson