Skip to content

Prepare PawPair for iOS launch - #1

Draft
razel369 wants to merge 110 commits into
mainfrom
cursor/raintank-planner-7a02
Draft

Prepare PawPair for iOS launch#1
razel369 wants to merge 110 commits into
mainfrom
cursor/raintank-planner-7a02

Conversation

@razel369

@razel369 razel369 commented Jul 9, 2026

Copy link
Copy Markdown
Owner

What changed

  • completes the local-first iPhone and iPad care experience
  • adds pet health records, care planning, encrypted backups, notifications,
    caregiver handoff, Premium entitlements, and optional analytics
  • hardens analytics opt-out and account deletion so remote failures remain
    retryable instead of reporting false success
  • preserves a valid cached Premium entitlement during transient RevenueCat
    failures
  • adds Supabase RLS migrations, retention jobs, aggregate analytics views, and
    the account-deletion Edge Function
  • synchronizes in-app and public Privacy and Terms copy
  • adds App Store metadata, screenshots, release gates, EAS metadata mapping,
    GitHub Actions, and Git LFS for reproducible runtime assets

Why

The current product had grown far beyond the last remote checkpoint. Critical
source files, database migrations, App Store material, and pet-motion assets
existed only in the local working tree. This checkpoint makes the release state
recoverable and gives the iOS launch a repeatable quality gate.

User impact

  • pet-care data remains local unless the user explicitly enables anonymous
    product analytics
  • analytics deletion no longer clears the local retry identity before Supabase
    confirms removal
  • temporary subscription-service outages do not immediately downgrade a known
    paid user
  • encrypted backup limits protect the React Native JavaScript heap
  • iPhone and iPad layouts, accessibility semantics, and form focus are improved

Validation

  • npm run verify:ci
  • ESLint: zero warnings
  • TypeScript: passed
  • Vitest: 36 files, 184 tests passed
  • Expo Doctor: 20/20
  • pet-motion audit: 201 files, 67 sets, zero issues
  • App Store metadata mapping: validated locally
  • staged secret scan: no real credentials committed

Remaining launch gates

  • verify the RevenueCat production public iOS key in EAS
  • complete five final branded iPad screenshots from the submitted build
  • run purchase, restore, notification, analytics deletion, and account deletion
    checks on physical iPhone and iPad devices
  • deploy the synchronized legal site before App Review

This PR remains a draft until the external and physical-device gates pass.

cursoragent and others added 5 commits July 9, 2026 22:17
Co-authored-by: razel369 <razel369@users.noreply.github.com>
Co-authored-by: razel369 <razel369@users.noreply.github.com>
Co-authored-by: razel369 <razel369@users.noreply.github.com>
Co-authored-by: razel369 <razel369@users.noreply.github.com>
Co-authored-by: razel369 <razel369@users.noreply.github.com>
@cursor cursor Bot changed the title Build RainTank rainwater planning MVP Build PawPair shared pet medication tracker Jul 9, 2026
cursoragent and others added 24 commits July 9, 2026 22:46
Co-authored-by: razel369 <razel369@users.noreply.github.com>
Co-authored-by: razel369 <razel369@users.noreply.github.com>
Co-authored-by: razel369 <razel369@users.noreply.github.com>
Co-authored-by: razel369 <razel369@users.noreply.github.com>
Co-authored-by: razel369 <razel369@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
- Add src/design/{colors,typography,spacing,motion,shadows}.ts as
  the single source of design tokens.
- Extract Today, Pets, Insights, Add Medication screens into
  src/features/<area>/* with their supporting subcomponents
  (DoseCard, TodayHero, SyncCard, MedicationCard, …).
- Extract reusable primitives into src/components/ (AppHeader,
  BottomNav, DateStrip, LoadingScreen, StatCard, Toast, FormInput).
- Reduce App.tsx from 1403 lines to 187 lines (state + nav only).
- Preserve all original behavior and visual output. typecheck and
  4 vitest unit tests pass; web bundle renders identically to the
  pre-refactor baseline (see docs/baseline-screenshots/).
- No new dependencies, no Expo Router yet (planned end of stage 2).

Co-authored-by: Cursor <cursoragent@cursor.com>
- Install expo-sqlite (~16.x) for SDK 57.
- Add src/data/database/connection.ts: lazily-opened SQLite handle
  with foreign keys + WAL journal mode. Exposes __resetDatabaseForTests.
- Add src/data/database/types.ts (clock): now() and nowIso() backed
  by an injectable function. Tests set the clock; production reads
  Date.
- Add src/data/database/uuid.ts: RFC 4122 v4 UUIDs using
  crypto.randomUUID() with a Math.random fallback.
- Add src/data/database/uuid.test.ts: 4 new vitest cases (clock
  injection, ISO format, v4 shape, uniqueness). 8/8 tests pass.
- No behavior change to the existing app. expo-sqlite is installed
  but not yet imported by any feature; it will be wired up in 3f.

Co-authored-by: Cursor <cursoragent@cursor.com>
Schema (src/data/database/schema-v1.sql.ts) covers all 15 tables
required by docs/AAA-HANDOFF.md §4:
  users, households, household_members, pets, pet_photos,
  medications, medication_schedules, schedule_times,
  scheduled_doses, dose_events, inventory_transactions,
  refill_reminders, notification_registrations, sync_outbox,
  sync_metadata

Conventions enforced by the schema:
- UUID v4 primary keys (text).
- UTC ISO 8601 timestamps (text), separate from schedule timezone.
- Foreign keys with ON DELETE CASCADE for owned rows.
- A partial unique index on dose_events enforces one terminal
  event per occurrence; corrections append a new event with
  correction_of_event_id set.

Migrations runner (src/data/database/migrations.ts) keeps every
schema change as a numbered Migration, applies pending ones in
order using PRAGMA user_version, and supports a {reset, to} option
for tests. ensureMigrated() in bootstrap.ts is the production
entry point.

Repositories (src/data/repositories/) implement the first three:
- PetsRepository (create, listForHousehold, findById, archive)
- MedicationsRepository (same shape)
- DoseEventsRepository (create + correct)

Tests: 18/18 pass. The repository tests run against an in-memory
shim that implements the subset of expo-sqlite the repos and
migrations need. This shim will be removed in stage 7 once the
test runner is wired to a native Hermes preset.

App.tsx now calls ensureMigrated() on mount so the database is
created on first launch. The AsyncStorage paths remain
authoritative for v1; the data layer swap lands in stage 7.

Co-authored-by: Cursor <cursoragent@cursor.com>
Onboarding replaces the always-on DEMO_PETS start screen. A new
AsyncStorage flag (pawpair.onboarding.done.v1) records whether
the user has completed onboarding or explicitly skipped it with
'Continue with demo data'. On first launch — and after every
storage clear — the welcome screen is shown.

Components (src/features/onboarding/):
- types.ts: OnboardingStep union and per-step data drafts.
- store.ts: useOnboardingStore hook (local state for the flow).
- WelcomeScreen.tsx: brand moment with the icon, headline that
  explains shared confirmation, and a primary CTA.
- AddPetScreen.tsx: name, species, breed (optional), age
  (optional), portrait choice. Validates required fields.
- AddMedicationOnboardingScreen.tsx: name, dosage, form, schedule
  (preset times + custom HH:MM), starting supply with unit.
- PreviewDoseScreen.tsx: shows the first scheduled dose so the
  user can see what will surface tomorrow.
- OnboardingFlow.tsx: orchestrator that walks the four steps
  and calls back with the completed draft.
- builder.ts: maps the drafts to the existing Pet/Medication
  shapes used by the rest of the app.

App.tsx now:
- Reads pawpair.onboarding.done.v1 in addition to pets/logs.
- While onboarding is not done, renders OnboardingFlow instead
  of the tabbed UI. The user can still reach Today by either
  completing the flow or tapping 'Continue with demo data'.
- finishOnboarding() writes a new pet + first medication to
  AsyncStorage, sets the flag, and shows a confirmation toast.
- skipOnboarding() loads DEMO_PETS, sets the flag to 'skipped'.

Tests: 18/18 still pass. typecheck clean. The flow renders
correctly in the web bundle (see docs/baseline-screenshots/
onboarding-welcome-2026-07-10.png).

Persistence is still AsyncStorage for v1. Stage 7 will route
the same data through the SQLite PetsRepository and
MedicationsRepository, and add a v2 import path so existing
users do not lose their data.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds the discriminated union for the 10 schedule kinds required
by docs/AAA-HANDOFF.md §6: daily, weekdays, every_n_hours,
every_n_days, weekly, monthly, date_range, taper, cycle, prn.

Also adds the Occurrence shape, the weekday-mask constant, the
TaperPhase shape, and a scheduleTypeLabel helper. The actual
generators, occurrence-key hash, and timezone helpers land in
follow-up commits.

Adds fast-check (devDep) for property-based tests that the
recurrence generators will use.

No behavior change to the existing app; the legacy
buildSchedule in src/schedule.ts still drives the prototype UI
until 5d wires the new engine in.

Co-authored-by: Cursor <cursoragent@cursor.com>
…, every_n_hours, cycle

Adds the core of the new recurrence engine from docs/AAA-HANDOFF.md
§6. The legacy buildSchedule in src/schedule.ts still drives the
prototype UI; stage 5d wires the new engine in.

Modules:
- src/features/schedules/occurrence-key.ts
  FNV-1a based deterministic key. parseOccurrenceKey() reverses it
  for tests and for migration scripts.

- src/features/schedules/timezone.ts
  localDateIn, localTimeIn, localWeekday, localToUtc, validators.
  Built on Intl.DateTimeFormat (Hermes ≥ 0.74, all browsers, Node).
  localToUtc treats the wall-clock as a UTC instant, observes the
  timezone, and corrects by the observed offset. This handles any
  IANA timezone including southern-hemisphere zones with
  non-hour offsets.

- src/features/schedules/recurrence/daily.ts
  One occurrence per time per day in [startDate, endDate].

- src/features/schedules/recurrence/weekdays.ts
  Same as daily but masked by the schedule's 7-bit weekday mask
  (Mon=1 ... Sun=64).

- src/features/schedules/recurrence/every-n-hours.ts
  Emits an occurrence every N hours from the anchor time.

- src/features/schedules/recurrence/cycle.ts
  days-on / days-off pattern starting from startDate.

- src/features/schedules/index.ts
  generateOccurrences(schedule, from, to) is the single public
  entry point. Switches on schedule.type and delegates. Returns
  [] for kinds not yet implemented (every_n_days, weekly,
  monthly, date_range, taper, prn) so callers degrade gracefully.

- src/features/schedules/tests.test.ts
  16 new tests: occurrence key determinism, parse round-trip,
  timezone validation, Jerusalem round-trip, daily/weekdays/cycle/
  every_n_hours shapes, plus two fast-check property tests for
  key stability and sort order.

Total: 34/34 tests pass, typecheck clean.
Co-authored-by: Cursor <cursoragent@cursor.com>
… schedule kinds

Completes the 10-schedule-kind matrix from docs/AAA-HANDOFF.md §6.

Generators (src/features/schedules/recurrence/):
- every-n-days.ts: stride-N day walks with optional weekday mask.
- weekly.ts: emits one day per week, target picked from
  weekdayMask.
- monthly.ts: emits the same day-of-month every month; skips
  months that don't have it (e.g. Feb 29 in non-leap years).
- date-range.ts: emits every day in [startDate, endDate].
- taper.ts: phase-by-phase dose changes, each phase owns its
  times and dose text. Hard cap of one year to defend against
  missing next phases.
- prn-as-needed.ts: empty by design; the minimum interval is
  enforced by DoseEventsRepository when the caregiver logs a
  dose. Renamed from prn.ts to avoid a case-sensitivity collision
  on Windows file systems.

The public index.ts now switches on every kind. Returning an
empty list for unsupported kinds is no longer needed.

Taper validity helper (isValidTaper) rejects overlapping phases
and phases with no times.

Tests: 42/42 pass (8 new tests covering each kind). The Feb 29
test asserts that non-leap-year Feb is skipped and that the
range end is respected; the taper test asserts per-phase dose
text and that scheduleEndDate is inclusive.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds four property-style tests for the edge cases called out
in docs/AAA-HANDOFF.md §6:
- Spring forward: America/New_York on 2026-03-08 jumps 02:00
  → 03:00. The engine still emits the scheduled 02:30 dose on
  the same local date; the UTC instant is shifted to land on
  the first valid wall-clock after the DST gap.
- Fall back: 2026-11-01 repeats 01:00-02:00. The engine emits
  exactly one 01:30 occurrence on that date. Two distinct
  UTC instants are not created for the same local time on
  a single day.
- Stable Jerusalem (no DST): every 08:00 occurrence round-trips
  back to 08:00 local; UTC hours are between 05 and 06.
- Leap day: monthly 29th skips Feb 2025, 2026, 2027, 2029
  etc., only emits 2024-02-29 and 2028-02-29 in a four-year
  range that starts in 2024.

Tests: 46/46 pass.
Co-authored-by: Cursor <cursoragent@cursor.com>
App.tsx now uses buildScheduleFromEngine (src/features/schedules/
adapter.ts) instead of the legacy buildSchedule in src/schedule.ts.
The adapter:
- converts each Medication's times[] into a Schedule with type
  'daily' and weekdayMask = EVERY_DAY
- calls generateOccurrences for [00:00, 24:00) of the requested
  day
- filters occurrences to the requested local date
- folds existing dose logs into the ScheduledDose.status field

The visual output of the Today screen is identical to the
pre-engine version (see docs/baseline-screenshots/today-web-2026-
07-10-after-stage5d.png). The legacy buildSchedule is no longer
called from App.tsx; stage 7 will delete it once the SQLite
repositories own the source of truth.

Tests: 49/49 pass (3 new tests for the adapter). typecheck
clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
…dules

Installs expo-notifications and expo-device (SDK 57).

Adds the notification subsystem under src/features/notifications/:
- types.ts: NotificationAction, NotificationPermissionState,
  ScheduledNotification, NotificationHealthReport.
- permission.ts: getPermissionState and requestPermissionIfNeeded
  with a swappable backend. The web and test environments fall
  back to 'unsupported'; the production backend uses
  expo-notifications.
- service.ts: idempotent scheduling, cancel by occurrence, and
  rescheduleForSchedule. Tracks in-process registrations so the
  health report can show scheduled count and last registration
  time without a separate query.
- expo-backend.ts: lazy-loaded production adapter that maps the
  service interface to expo-notifications. Cancellations are
  best-effort so a missing platform registration is not an
  error.
- bridge.ts: handleNotificationAction (Given / Skip logs the
  dose and cancels; Snooze re-schedules the same content 15
  minutes later under a different scheduledDoseKey so the
  original is replaced). Build notification uses the
  discreet flag to swap to a neutral title and body so
  notification previews never leak the medication name.
- index.ts: public surface for both production code and tests.

Tests: 59/59 pass (10 new). Tests use a stub backend so the
test runner never touches the native module. The production
backend is wired in App.tsx at app start (stage 6b/6c).

typecheck clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds src/features/notifications/scheduler.ts. scheduleAllPets
walks the schedule engine's occurrences for a 7-day horizon
(default), builds a ScheduledNotification per occurrence, and
registers them through the service. The reschedule step is
idempotent so editing a medication only re-registers the
changed schedule.

App.tsx now wires the production backend at app start:

  void makeExpoBackend().then(__setSchedulingBackend)

A new useEffect on (loaded, pets) calls scheduleAllPets whenever
the pet list changes. The web bundle keeps the no-op backend
and never opens the bridge to expo-notifications, so tests
remain unaffected.

Tests: 62/62 pass (3 new scheduler tests: backend hookup,
default horizon, empty-pet filter). typecheck clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
App.tsx now installs a dose logger that handleNotificationAction
calls when the user taps Given or Skip on a system notification.
The logger translates (scheduleId, scheduledDoseKey) back into
(pet, medication) and calls the existing logDose path so the
state stays consistent with a manual confirmation from the
Today screen.

The new test in notifications/tests.test.ts asserts that
handleNotificationAction with action skip invokes the logger
with status skipped and cancels the platform registration.
typecheck clean. 63/63 tests pass.

The web bundle keeps the no-op backend, so this path never
fires in tests or in the browser preview.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds src/features/notifications/HealthScreen.tsx. The screen
shows the current permission state, the number of scheduled
reminders, the last registration time, and the failure count.
When the permission is undetermined the user can tap to ask;
when it is denied the screen tells them how to fix it in
Settings. The 'notifications-outline' tab replaces the
non-functional Profile placeholder.

App.tsx and BottomNav gain a fifth tab, 'health', which
mounts the HealthScreen. Tests still pass (63/63) and the
typecheck is clean. Visual is identical on the existing
screens; the only user-visible change is the new tab.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds the multi-step medication form that stage 4 hinted at:
- form-state.ts: MedicationDraft, FORM_STEPS, FORM_STEP_TITLES,
  validateStep (per-step validation).
- MedicationFormScreen.tsx: the Pet -> Identity -> Schedule ->
  Inventory -> Review flow. When called with `editing`, the
  title says "EDIT MEDICATION" and the Save button reads
  "Save changes". The host (App.tsx) keeps the medication id
  and merges the new draft.
- MedicationMenu.tsx: bottom-sheet menu with Edit and
  Archive actions. Per docs/AAA-HANDOFF.md we do not expose
  delete so the dose history is never silently discarded; the
  archive path is the only removal route.

App.tsx:
- adds two screen types (edit-medication, medication-menu) and
  hides the bottom nav while either is open.
- updateMedication merges by medication id; archiveMedication
  filters the medication out of the pet and clears the menu
  state.
- hosts that called addMedication still work; the existing
  AddMedicationScreen is the entry point for new medications
  and the multi-step form is the entry point for edits and
  for any future "add via context" flow.

Tests: 63/63 still pass; the new screens are unused at the
prototype level and the legacy AddMedicationScreen is the
default entry. typecheck clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds two new components under src/features/pets/:
- PetFormScreen.tsx: add or edit a pet. Single screen with
  progressive validation. Reuses the visual language of the
  onboarding "Add your first pet" screen. When `editing` is
  set, the title and Save label change and the existing pet
  values prefill.
- PetMenu.tsx: bottom-sheet menu with Edit and Archive. Per
  docs/AAA-HANDOFF.md §8 the archive path is the only
  removal route so the pet's dose history is never silently
  discarded.

These components are not yet wired into App.tsx. The Pets
screen still shows a static list. Wiring happens alongside
the next stage so we keep the diff focused.

Tests: 63/63 pass. typecheck clean.
Co-authored-by: Cursor <cursoragent@cursor.com>
…ggle)

Adds src/features/medications/ScheduleEditor.tsx. The component
shows the medication's wall-clock times and a Pause / Resume
toggle. Pause does not yet remove the medication from the
Today timeline because Medication.paused is not a field in the
current type. Wiring the pause flag through the type and the
schedule engine lands in stage 7d alongside the multi-kind
schedule editor.

Tests: 63/63 pass. typecheck clean.
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds src/features/reports/report.ts. The renderer produces
self-contained HTML that the host can preview, share, or hand
to expo-print to produce a PDF. All inputs are HTML-escaped.

Adherence is the share of terminal events marked 'given' in
the requested date range. Caregiver names are redacted when
redactCaregivers is set. The report carries a footer that
calls out the user-entered data caveat and the not-advice
disclaimer from docs/AAA-HANDOFF.md §13.

Tests: 66/66 pass (3 new): basic shape, redaction, HTML
escaping. typecheck clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds src/features/settings/SettingsScreen.tsx. The screen
covers the four bullets from docs/AAA-HANDOFF.md §13 that
belong in the app: export, delete-account, support, and
legal. Delete-account goes through a destructive Alert.alert
confirmation so the user can back out.

The screen is not yet wired into App.tsx; that lands in
stage 7f when we also add the empty-day and error states.

Tests: 66/66 pass. typecheck clean.
Co-authored-by: Cursor <cursoragent@cursor.com>
razel369 added 30 commits July 26, 2026 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants