Pet Adoption App: What the Foster-to-Forever-Home Handoff Needs — PupPal

2026-08-28

Pet Adoption App: What the Foster-to-Forever-Home Handoff Needs — PupPal

A rescue volunteer in Shanghai keeps a spreadsheet of forty dogs. Each row has a name, a breed guess, a photo link, a "temperament" cell with two or three words, and a status that moves from shelter to fostered to adopted. The spreadsheet is the single source of truth for every dog's life, and it is always behind. When a foster family sends a new photo, nobody updates the row. When an adopter asks what the dog eats, what commands the dog knows, and whether the dog is good with cats, the volunteer has to reconstruct an answer from memory and a group chat. The day a dog changes families, its whole biography has to move with it — and in a spreadsheet, a biography is just cells that never get copied correctly.

That problem is exactly what a pet adoption app exists to solve, and it is the reason PupPal's roadmap is a three-stage ladder: 宠物打卡 (pet check-in), 寄养代打卡 (foster proxy check-in), and 领养 (adoption). The first two rungs are not hypothetical — they are shipped, documented in the repository, and covered by a live Cloudflare Worker relay. This post walks the roadmap from the source code: what check-in built, what foster care added, what the adoption module inherits, and what it must build next. The thesis is simple. Adoption is not a listing feature. It is a handoff protocol — the complete, trustworthy transfer of a dog's record from one family to another — and PupPal's architecture has been preparing for that handoff since the first commit.

The Three-Stage Roadmap: Check-In, Foster Care, Adoption

The roadmap order is visible inside the app itself. The home page migration document, home_migration_prompts.md, describes the original four-corner toolbar around each pet: pet info on top, check-in history and photo wall on the left, and on the right, two entries — 寄养 (foster) and 领养 (adopt) — with the explicit instruction that the pages are not implemented yet and should show a toast with the home.comingSoon string. The i18n keys were written down before the screens existed: home.foster, home.adopt, home.comingSoon — "Foster", "Adopt", "Coming soon" in English, 寄养, 领养, 即将上线 in Chinese. That is a roadmap frozen in string tables.

What happened since is visible in the commit history and the toolbar code. The foster entry graduated. In right_toolbar.dart, the commented-out foster button (Icons.home_repair_service with t.home.foster) was replaced by a live entry — a person_2_fill icon that reads the local care session store, checks whether a session is already active, and opens either CareActivePage or CareCreatePage in a bottom sheet, gated behind the familySharing Pro feature flag. Beside it sits the second rung of the ladder in a different state: // _ToolbarButton(icon: Icons.favorite, label: t.home.adopt, onTap: widget.onAdopt) — still commented out, still "Coming soon", the exact state foster care occupied a few weeks earlier. The roadmap is not a document; it is a diff. Check-in shipped, foster care shipped, and adoption is the next diff.

Each rung escalates one thing: who is trusted with the dog's record. Check-in trusts the owner to photograph the dog daily. Foster care trusts a friend or family member to photograph the dog for a few days — without an account, without training, with tighter anomaly thresholds. Adoption trusts a stranger to own the dog for the next fifteen years — and to carry the dog's whole record into the new home. The data model had to be built in that same order, because each stage reuses what the previous stage made durable.

Stage One, Shipped: Photo Check-In as the Foundation

The first rung is the foundation every later feature leans on. When a new owner sets up PupPal, the app calls POST /puppal-init, and the profile payload is already structured like a handoff packet: name, breed, age, weight, gender, neutered status, avatar; a feeding block with brand, amount per meal, and meal times; a medications list with dosage and schedule; allergies; a behavior block with leash training, commands known, fears, and quirks; a vet block with name, phone, and address; an emergency contact; and a personality block with tone and traits. Read that payload as a biography template. Every field in it is something an adopter would want to know before saying yes, and something a caregiver needs the moment you walk out the door.

From then on, every photo is a check-in. POST /puppal-photo uploads to R2, and the Hermes Agent runs the photo pipeline from the README's skill table: V2 posture recognition, V4 expression/emotion, and V5 environment type in a single Vision call, plus C7 anomaly detection as a pure-Python rule engine. The response carries a dog_state_snapshot (happiness, energy, health_score), a trend_brief such as "energy 连续 2 天下降", and a needs_owner_attention flag. Locally, the photo lands in the Play table with MediaFile rows attached — the check-in log is a relational, queryable timeline, not a photo folder. Around the timeline, habit tracking gives the record its rhythm: 49 curated habit templates (version 2.0.0, sourced from APPA 2024 US Pet Market Research), organized into categories that include food, vet health, grooming, exercise, training, and a small special_days category with a cake icon. Each completed habit earns points and badges on the pet's profile card.

Why does this matter for adoption? Because every rung of the ladder is only as trustworthy as the rung below it. A photo check-in taken on day one and a photo taken on day 300 are two points on the same timeline; the anomaly scores, the trend briefs, and the S1 baseline comparisons in the nightly review turn that timeline into a measurable health history. The photo check-in deep dive shows what happens inside that pipeline, and the nightly health review post shows how the review aggregates S1/S3/S4 against the 7-day baseline. A shelter or rescue that used this from intake would have, at adoption time, a longitudinal record no spreadsheet row can hold. That is the asset the second rung starts to move around.

Stage Two, Shipped: Foster Proxy Check-In with Care Codes

The second rung answers a practical question: what happens when the owner cannot be the one taking the photos? PupPal's answer is the care code, and it is the piece of the architecture that makes adoption thinkable at all.

When an owner starts a care session, POST /puppal-care-create returns a 9-digit care code formatted in 3-3-3 groups ("123 456 789", RustDesk connection-code style) plus a 4-digit PIN. The PIN is returned in plaintext exactly once, and the owner passes it to the caregiver through any channel — WeChat, iMessage, or verbally. The caregiver does not register, does not create an account, does not install a second app. They enter the code and PIN through the sitter join sheet, read the care handbook that the agent generated from the dog's profile, and start taking photos. The care codes post documents the security model in depth; the important part for the roadmap is that this is the first time in the product that a stranger touches the dog's record without becoming its owner.

The caregiver's photos flow through the same puppal-photo endpoint with source: caregiver, but they are analyzed by a different skill: care-monitor, which tightens the anomaly thresholds by roughly 40 percent. On the app side, the sitter experience is governed by an explicit constant in care_models.dart: careAnomalyScoreThreshold = 0.42. A care photo needs owner attention if the worker's JSON carries needs_owner_attention: true or an anomaly_score above 0.42 — and the app deliberately does not flag free-text summaries, only structured analysis. During a care session, a mild red ear that would pass a normal check-in can trip the tighter threshold and alert the owner immediately.

The session model itself is already built for flexibility. CareSessionInfo tracks careCode, pin, careSessionId, dogId, startTime, endTime (null means indefinite), and status (active or ended). It also carries a dogIds list, because care sessions cover multiple pets since version 1.1, and handbookAccepted plus handbookAcceptedAt, the sitter's explicit acknowledgment of the care instructions. The create form offers seven service types — pet sitting, in-home boarding, dog walking, boarding facility, doggy daycare, pet hotel, and vet boarding — and three transfer modes for photos: p2pOnly, cloudOnly, and the default p2pWithFallback, where the owner's device relays photos directly when online and falls back to cloud staging when it is not. That peer-to-peer path is real: the repository recently merged a webrtc-rs-based Rust RTC module replacing flutter_webrtc with DataChannel-only transport, in service of a smaller APK and direct device-to-device photo flow.

Most important for the roadmap: the worker exposes record-portability endpoints that did not exist in the original API contract. Beyond puppal-care-create and puppal-care-end, the app's Dio client calls GET /puppal-care-export?care_session_id=xxx to pull the session's exported data, GET /puppal-care-staged to collect photos that were buffered in the cloud during offline gaps, POST /puppal-care-photo-meta to register photo metadata back into the owner's timeline, and GET /puppal-care-photos to list the session's photos. Read those four endpoints as one sentence: a care session produces a portable, exportable snapshot of a dog's period of life. That sentence is the adoption module in miniature. The foster care monitoring post called the same thing from the product side: a foster period is adoption screening in disguise.

What a Pet Adoption App Must Carry Across the Handoff

Before designing the third rung, it is worth inventorying what the adoption module must move, because the answer determines the design. A dog changing families needs to carry, at minimum, five artifacts — and PupPal already stores every one of them.

Identity. The Pet model is built around a stable identity: remoteId is a unique index, key is a unique index, masterId names the current owner, and familyGroupId is an indexed field that already anticipates a pet belonging to a family unit rather than a single person. There is also a soft-delete pair — isDeleted and deletedAt — which means identity survives even when a record is removed from view. For adoption, the crucial property is that dog_id is a namespace in the worker and the agent memory, not a phone. The dog's record is keyed to the dog, so it can be re-pointed from one human to another.

Health and behavior history. The photo wall, the Play rows, the dog_state_snapshot values, the anomaly scores, the trend briefs, and the vet block from puppal-init — the entire timeline the first rung built. The rescue-dog story post on this blog (rescue dog recovery) walks the first two weeks of exactly this data being collected under foster-mode thresholds, and the 3-3-3 rule rescuers use to describe adjustment periods.

Habits and streaks. Habit instances are personal the way photos are: the template daily_feeding row plus the owner's own custom "afternoon sniff walk". Streaks, completion counts, and points live with the pet. Strikingly, the template library already contains the first habit an adopted dog should have. In habit_templates.json, activity id 47 is adoption_anniversary: category special_days, tag memorial, house icon, streakFrequency: yearly, streakGoal: 1, completionsPerDay: 1, a 60-minute completion window, a reminder at 18:00, four reward points — and active: false. Its description key resolves to "Gotcha Day" in English and 领养周年 in Chinese. The adoption habit is defined, indexed, and dormant, waiting for an adoption date that does not exist yet. The Pet model has birthDate but no adoptionDate; the module's smallest meaningful feature is adding that field and flipping the habit on.

Behavior and care knowledge. The behavior block from puppal-init (commands known, leash training, fears, quirks, friendliness with strangers) and the care handbook that care-handbook generates — feeding, medications, behavior notes, vet contact, plus a closing message of gratitude. A foster family accumulates this knowledge over weeks; the handbook is the mechanism that compresses it into something a stranger can follow on day one. Adoption is the ultimate handbook transfer: the foster family's evergreen handbook becomes the adopted family's starter kit.

The dog's story. Since commit 5464405, the app keeps a pet story — a single long text, edited by double-tap, with an initialization guide — that is injected directly into the agent's chat memory. This is the "about me" artifact: the dog's backstory as the family tells it, in the dog's own voice when the daily-voice skill writes it at 09:00. That is also, in product terms, the first draft of an adoption profile listing, written by the people who know the dog best rather than by a shelter staffer filling cells in a spreadsheet.

Design Sketch: The Adoption Module on Existing Primitives

With the handoff inventory in hand, the adoption module stops being a mystery and becomes an exercise in composing existing primitives. The following sketch is a roadmap design, not shipped code — every element is labeled by what exists today and what the module adds, because PupPal's discipline so far has been to build each rung on the rung below it.

Add the adoption date. The smallest change, already anticipated by the dormant adoption_anniversary template: a gotchaDate (or the Chinese 领养周年 equivalent) on the Pet model, next to birthDate. When a handoff completes, this field is set, the adoption_anniversary habit activates with its yearly streak, the 18:00 reminder starts standing guard, and the family inherits a Gotcha Day celebration almost no pet app models. Reptile keepers get hatch-day cards; dogs should get a gotcha-day habit.

Reuse the care codes as the family-creation mechanism. Adoption is, structurally, a care session that never ends. The owner of the record — the rescue coordinator, the foster family — starts an adoption handoff the same way they start a care session, and the adopting family claims it with the code and PIN, no account required. The worker already has the vocabulary: puppal-care-end takes a reason field (manual today); an adoption end reason plus a distinct puppal-adopt-handoff event would terminate foster monitoring and re-point the dog_id namespace to the adopter. The no-account philosophy — the thing that made foster care shareable with a grandmother — carries straight over.

Move the record through the export pipeline it already has. The care-export and care-photos endpoints were built to make a foster week portable. The adoption module generalizes them: profile, photo wall, habit streaks, handbook, pet story, anomaly history, and trend briefs serialize into a handoff bundle that the adopter's install imports on first launch. Import is the inverse of onboarding — the onboarding post walks the first ten minutes of a fresh setup; the adoption import makes the first ten minutes in the new home start with a full biography instead of a blank profile.

Publish the dog's case file without a social graph. The listing problem — how does an adopter find the dog? — is the one place the no-account model pinches, and the worker already points at the answer: KV stores subscription and rate-limit state, and the app ships a care quota service. Adoption listings can be generated records, not user posts: the agent composes the profile page from the photo wall, the trend history, the pet story, and the daily-voice messages, and the worker serves it as a read-only page gated by rate limits. The algorithm is "the case file builds itself from check-ins," which is the honest version of PupPal's core claim — photo = check-in, share = care — applied to the last mile.

Keep foster-mode monitoring for the first two weeks in the new home. The single best predictor of an adoption's success is the first fortnight, and PupPal already has the instrument for it: care-monitor with its tighter thresholds and instant owner alerts. The adoption module's "new home mode" is foster care mode re-labeled — the adopter starts as a monitored caregiver, thresholds relax as the dog's baseline stabilizes in the new environment, and the daily review quietly accumulates the evidence that the dog has settled. Rescuers already do this by intuition; the product can do it by curve.

What Adoption Demands That Foster Care Does Not

Honesty requires naming what is genuinely new in the third rung, because adoption is the one stage that differs in kind, not just degree. Four things, specifically.

Permanence and idempotence. A foster session ends and life goes on; an adoption handoff must be practically irreversible and safe to retry. The worker's endpoints are webhook-driven and the relay post on this blog (the Cloudflare relay) explains how KV and Durable Objects keep that state consistent; the handoff endpoint inherits the same discipline, but it needs explicit one-way semantics — an adoption claim that cannot accidentally roll the record back to the previous family, and an import that is idempotent so a failed phone migration does not duplicate a dog's history.

Identity, beyond a code and PIN. For a weekend of sitting, "whoever holds the code and PIN" is a fine definition of authority. For a legal re-homing of a living animal, the human behind the code matters. This does not mean abandoning the no-account philosophy — it means the adoption handoff needs a stronger, still account-free ceremony: verified contact plus human review, the way RustDesk-style sharing pairs a connection code with an out-of-band identity check. The product does not need a social graph; it needs the adoption moment to be a moment, not a background task.

Abuse resistance. Adoption listings are a magnet for bad actors in every country where pet adoption is a market. The worker's existing toolbelt — KV rate limits and the quota service — has to be deployed aggressively on listing endpoints, and the staged-photo fallback channel has to be closed at handoff, because an adopter's record should not be able to pass through a buffer the foster system left open.

Longevity of the record. Foster monitoring covers days; adoption covers years. The adoption_anniversary habit is yearly, the dog's streak should live as long as the dog does, and the record has to outlive any single agent session, any single device, and any software version — which is exactly the argument for the i18n discipline this blog covered: template habits stored as raw keys translate at the display boundary, so a record written in Chinese by a foster family is displayed in English to an adopter in Toronto without a single data migration.

The Forever-Home Handoff Is the Whole Point

Step back and the ladder reads as one continuous argument. Photo check-in makes the record exist. Foster care makes the record portable and proves that strangers can safely add to it. Adoption completes the arc: the record changes families without losing a day of its history, and the yearly Gotcha Day habit turns the anniversary into a checked-in moment for the rest of the dog's life.

That is what separates a pet adoption app from an adoption website. A website lists dogs; a record inherits them. The spreadsheet in the Shanghai rescue is the before-picture: a biography that degrades every time it is copied by hand. PupPal's roadmap is the after-picture: a biography that is written once, in photos and structured fields and first-person voice messages, and that moves intact from shelter to foster home to forever home. The volunteer's rows — name, breed, temperament, status — become the pet's photo wall, its trend history, its care handbook, its story, and finally the four-pointed star on a Gotcha Day habit that repeats once a year at 18:00.

The adoption module is the next diff in the toolbar, the next line of code to replace a comment. And when it ships, the dog on the other side of the handoff will arrive with everything: the first photo from the shelter, the foster family's notes, the vet's phone number, the meds schedule, the fear of vacuum cleaners, the favorite quirks, and a standard greeting written in its own voice by the daily-voice skill. The new family will not start from zero. They will start from the whole dog — because in PupPal, care is a record, and the record is what moves.

So the call to action is the same one every rung of this roadmap has earned: if you have a dog, start the photo check-in today, because the first photo is the first line of a record you will want to hand over someday. And if you are fostering — or adopting — the foster-care monitoring post on this blog is the field guide to the stage right before the one that comes next.