Skip to content

Loved One test plan

This is the step-by-step spec for testing the Loved One flow end to end: Thomas and Lisa sign in to the web app, add new content to the senior's memory bank, and confirm it is saved and ready for the senior's UI.

Content added here reaches the senior's phone: Stories fetches the active senior's bank questions and photos before each session (see Multiple seniors for how the switch selects the session). The final section walks through seeing it on the phone.

1. Start the local services

pnpm install
pnpm dev:api        # Worker on http://localhost:8787 (local D1 + R2)
pnpm dev:web        # Loved One app on http://localhost:5173

On a fresh checkout, or after wiping local state, create and seed the database first:

pnpm --filter @senior-health/api migrate   # apply migrations to local D1
pnpm seed:api                              # create the dev senior and bank

The seed creates the two roster seniors, Tom (bank dev-bank-tom) and Nyna (bank dev-bank-nyna), and one Loved One account, loved.one@example.com / daybreak-dev, who manages both. These exist only in the local database.

2. Create the Thomas and Lisa accounts

Thomas and Lisa are personal Loved One accounts that live only in local D1 state (.wrangler/state/, gitignored). They are never part of the committed seed. If the state was wiped, recreate them from apps/api/:

npx wrangler d1 execute senior-health --local --command "
INSERT OR REPLACE INTO users (id, role, name, email, password_hash, created_at) VALUES
  ('thomas-loved-one', 'admin', 'Thomas', 'thomas@example.com',
   'pbkdf2\$100000\$3b85cd9e703e087691601a0cc06cf3fb\$697ea9a3394995c42d8fb7d37b5f8b902bc81466a4e2ff534f54370070d7c980',
   '2026-07-30T00:00:00.000Z'),
  ('lisa-loved-one', 'admin', 'Lisa', 'lisa@example.com',
   'pbkdf2\$100000\$3b85cd9e703e087691601a0cc06cf3fb\$697ea9a3394995c42d8fb7d37b5f8b902bc81466a4e2ff534f54370070d7c980',
   '2026-07-30T00:00:00.000Z');
INSERT OR REPLACE INTO bank_members (bank_id, user_id, access, created_at) VALUES
  ('dev-bank-tom', 'thomas-loved-one', 'manage', '2026-07-30T00:00:00.000Z'),
  ('dev-bank-tom', 'lisa-loved-one', 'manage', '2026-07-30T00:00:00.000Z'),
  ('dev-bank-nyna', 'thomas-loved-one', 'manage', '2026-07-30T00:00:00.000Z'),
  ('dev-bank-nyna', 'lisa-loved-one', 'manage', '2026-07-30T00:00:00.000Z');
"

Notes on this snippet:

  • Swap in the real emails you each sign in with.
  • The hash is the seed's PBKDF2 hash of daybreak-dev, so both accounts start with that password. A different password needs a new hash in the same pbkdf2$iterations$salt$hash format that apps/api/src/auth.ts produces.
  • role: admin makes them Loved Ones; the manage membership row is what actually grants access to Poppy's bank. Without it, sign-in works but no bank appears.

3. Sign in

  1. Open http://localhost:5173.
  2. Enter the email and password. The header should show your name with the "Loved One" label and a Sign out button.
  3. If more than one bank is linked to the account, pick the senior under "Whose memories". With the dev seed there is only Poppy's bank, chosen automatically.
  4. If the page says no memory bank is linked, run pnpm seed:api and the snippet in step 2, then sign in again.

Thomas and Lisa can be signed in at the same time in separate browsers. Each gets an independent session; both write to the same bank.

4. Add content to the senior's bank

Work through the three sections in the header. Everything saved here goes to local D1 (records) and local R2 (files).

Photos

  1. Open Photos and choose a file.
  2. Fill in the title and the description. The description is required; it becomes the alt text a screen reader speaks. The API refuses an upload without one, so an undescribed photo cannot exist.
  3. Save, and confirm the photo appears in the list with its thumbnail.

Videos

  1. Open Videos and choose a file.
  2. Fill in the title and the description. For a video the description doubles as the audio description.
  3. Save, and confirm the video appears in the list. Videos load on request, so click one to check playback.

Quiz questions

  1. Open Quiz and author one question by hand: pick a topic and difficulty, write the prompt and the correct answer, then use Suggest wrong answers to fill the three wrong options. Edit any suggestion that reads oddly, then save.
  2. Build a set with the story wizard: choose Build questions from a story, paste a few paragraphs (an interview, a letter, a written-down story), and generate. Tap at least one candidate from the first five, take what you want from the ranked second round, and save.
  3. The batch saves as a question set named after the current date and time. Rename it to something meaningful, for example "Lake house summers".

5. Verify what was saved

  • Each section lists what you added immediately after saving; that list is read back from the API, not from local page state.
  • Sign out and back in (or check from the other person's browser): the content must still be there. This proves it round-tripped through D1 and R2.
  • Optional API spot check with the dev senior token:
curl -H "Authorization: Bearer dev-tom-token" http://localhost:8787/banks/dev-bank-tom/questions

6. See it in the senior's UI

Quiz questions and photos now flow to the phone: Stories from the Past fetches both from the Worker before each session. Questions join the quiz draw; photos appear as reminiscence moments, the photo above its title with a Continue button, placed at the end so a session closes on warmth. Videos do not reach the senior's UI yet, and the Music and Video panes still play only bundled content.

One-time device setup:

  1. Give the phone its dev sessions by mapping each roster senior to their seeded token in apps/mobile/.env:
EXPO_PUBLIC_DEV_AUTO_LOGIN=1:dev-tom-token,2:dev-nyna-token,3:dev-thomas-token

Settings > Who is using the app now switches banks too: as Tom the app reads dev-bank-tom, as Nyna dev-bank-nyna. A senior left out of the mapping plays bundled content only. With the variable unset the app behaves exactly as before the accounts work. It is never set for EAS preview or production builds, so a release cannot ship auto-logged-in.

  1. Keep pnpm dev:api running, and start the app with pnpm start (restart Expo after editing .env so the value is picked up).

The test loop:

  1. Thomas or Lisa saves a question, a question set, or a photo in the web app (step 4).
  2. On the senior's phone or simulator, go Home and open Stories. The pool is fetched fresh every time the screen opens, so a brief "Getting your stories ready" may show first.
  3. The new questions are now in the random draw, and photos can appear as the closing moment of a session. A session asks only three items, so play a couple of passes ("Start again") to catch a specific new one; a wrong answer reveals the source quote saved with it.

Removing a question works from either side:

  • Web: the Remove button on a question, or Delete on a whole set, in the Quiz section.
  • Phone: Settings > Memory bank > Manage Quiz Questions lists everything the Loved Ones added; Remove asks for confirmation, then deletes it for everyone. The starter questions bundled with the app are not listed and cannot be removed.

If the Worker is unreachable, Stories falls back within a few seconds to the last successfully fetched questions (cached on the device), and to bundled content only if nothing was ever fetched.

Simulators reach the Worker automatically. A physical phone needs the Worker reachable from the network: run pnpm --filter @senior-health/api exec wrangler dev --ip 0.0.0.0 and set EXPO_PUBLIC_DATA_API_URL=http://<laptop-ip>:8787 in apps/mobile/.env.

7. Resetting

Wiping apps/api/.wrangler/state/ deletes the local database and every uploaded file. After a wipe, repeat step 1's migrate and seed, then step 2 to recreate Thomas and Lisa.

Troubleshooting

  • "Email or password is incorrect": the accounts are missing (state was wiped) or the password does not match the hash. Re-run step 2.
  • "Your session ended. Please sign in again.": sessions expire after 30 days, and a wiped database also invalidates them. Sign back in.
  • Browser errors calling the API: the Worker only allows http://localhost:5173 and http://127.0.0.1:5173 in dev CORS. Open the web app from one of those exact origins, and check that pnpm dev:api is still running on port 8787.