Scout — Full Product Context → feature documentation

On This Day

A weekly "N years ago this week" notification from the photos on your phone, a full-screen Story of that trip, and a postcard you can share.

Summary

Once a week, on Saturday at 10:00, Scout can remind you of a trip from a past year: "3 years ago this week · Sunset Crater Volcano National Monument and 4 more places". It is built from the dated photos already on your phone, the same ones the camera-roll import filed into your Album. A tap opens a Story of that trip: full screen, photo by photo, the place's patch large beside its name, ending on a postcard you can share. The Album shows the same memory as a card at the top during that week, so a missed notification is not lost. See the trip opens that trip's page.

It was first built for a paid tier that is not live, and made free for everyone on 2026-11-04 as a reason to come back each week (the 2026-11-04 analytics read: 88% of installs finish onboarding, but only 48% come back on a later day).

Status

User-facing surfaces

How it works

  1. Chapters, from the phone only. useDeviceAlbumChapters (mobile/src/hooks/useAlbumChapters.ts:89) builds chapters (runs of dated photos, no gap over 48 h, at least two photos; see domain/albumChapters.ts) from visitPhotos, the camera-roll photos the import filed. It never asks the cloud album, for anyone.

  2. Scheduling. useOnThisDaySync (mobile/src/hooks/useOnThisDaySync.ts:29-31), mounted at the app root, is enabled for a signed-in user with the switch on. weeklyMemories (onThisDay.ts:76) picks the next 4 Saturdays that have a memory; syncOnThisDay (mobile/src/services/onThisDayNotifications.ts:34) cancels every pending On This Day notification and schedules those, only when the OS permission is already granted. It never asks. It re-syncs on launch, on return to the app and when the chapters change.

  3. The rules (unchanged from the paid-tier build): a week is Saturday to Friday, matched by month/day in each day's own year; the most recent past year wins; a Feb 29 photo counts as Feb 28 in a non-leap year; a week with no memory sends nothing.

  4. This week's card. memoryThisWeek (onThisDay.ts:126) finds the memory of the week we are in (the one the notification already announced), and memoryStory (onThisDay.ts:184) turns its chapter into the Story's content: the lead place, the cover first, the rest spread evenly across the trip.

  5. A 7-day link (mobile/src/hooks/useShareMemoryLink.ts). The sheet captures the postcard, then: POST /api/shared-memories (the lead place by patch id, the "years ago" phrase, the trip's first and last calendar day (YYYY-MM-DD) and counts) returns a code and presigned PUT urls; each Story photo still on the phone is shrunk to 1080 px and re-encoded as JPEG (shrinkCameraRollPhotoSized; the re-encode carries no EXIF, so no location) and PUT straight to private S3 (presignedPut), one at a time; a photo no longer on the phone, or whose upload fails, is skipped; the postcard is PUT (if it fails, the link fails and says so); POST /api/shared-memories/:code/commit names the slots that landed; the server checks each object (there, at most 3 MB, image/jpeg), copies it to a live/ key that no upload url can reach, and deletes the upload, before the link goes live. Then the OS share sheet opens with the url.

  6. On the server (backend/src/shared-memories/): the place name and patch art come from the catalog by patchId, and only for a patch the app can show (VISIBLE_PATCH_WHERE, the content sync's own rule); the server writes the date line itself from the two days (formatDateRange: "Oct 27 – 28, 2023"), so any phone language works and no free text reaches a page on our domain; the one phrase the phone sends must be the app's own ("3 years ago this week", isValidOver). At most 10 links per person per rolling 24 h (counted in the database, SHARE_DAILY_LIMIT), at most 8 photo slots (SHARE_MAX_PHOTOS). Commit refuses any upload over 3 MB or not image/jpeg (a presigned PUT cannot cap size); the page signs only the checked live/ copies, for an hour, forced to be served as image/jpeg, so a PUT that lands after commit (the url lives 10 minutes) changes nothing anyone sees. Objects live under the owner: shared-memories/<userId>/<code>/. A daily job at 03:30 CT (shared-memory-sweep) deletes the S3 objects of every link past its 7 days and stamps deleted_at (or removes the row, once its account is gone). Deleting an account (AccountService, deleteAllForUser) deletes everything under the owner's prefix at once (rows or not) and expires every link that moment, so the page says "expired" immediately; the next sweep clears the prefix again (catching an upload that was still in flight) and removes the rows.

Data model

API surface

Key files

Configuration and flags

Edge cases and known limits

What this feature does NOT do

Tests that cover it

Open questions