Prepare PawPair for iOS launch - #1
Draft
razel369 wants to merge 110 commits into
Draft
Conversation
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: 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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
caregiver handoff, Premium entitlements, and optional analytics
retryable instead of reporting false success
failures
the account-deletion Edge Function
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
product analytics
confirms removal
paid user
Validation
npm run verify:ciRemaining launch gates
checks on physical iPhone and iPad devices
This PR remains a draft until the external and physical-device gates pass.