Scout — Full Product Context → feature documentation

What's new

After an update, a returning user's first calm moment in the app brings up a full-screen What's new sheet (over the ember backdrop) listing every release since the last one they saw, with Take me there links. Never shown to a brand-new install. The drawer's What's new page shows recent releases any time.

Summary

When someone who already uses Scout opens an updated app, a full-screen What's new sheet appears once, at the first calm moment. It sits over the ember backdrop from the unlock celebration and lists what changed, grouped under New, Improved and Fixed. Items can carry a Take me there link straight to the feature. A person who skipped versions sees every release they missed, newest first. It is never shown to someone installing Scout for the first time. The drawer's What's new row shows the recent releases at any time.

This replaces the release-notes post that used to be pinned to the community board. Those old posts stay where they are, but no new ones are made.

Status

Built, not yet released (2026-09-27). It ships in the app release after 1.1.95 (1.1.95 was already in review when it was built). The backend endpoint ships with the next backend deploy, and the new table is created by that deploy's prisma migrate deploy. No feature flag. Update this line when the build is live.

User-facing surfaces

How it works

  1. Publishing. The deploy-apps skill drafts structured notes and publishes them once the release is installable with npm run release:notes -- --file <json> (backend/scripts/release-notes-publish.ts). The script validates locally, then POSTs to POST /api/admin/release-notes through ProdAdmin. Every write goes through validateReleaseNotes (backend/src/release-notes/release-notes.validate.ts):

    • the version looks like 1.2.3
    • sections are titled New / Improved / Fixed
    • each item has 1–3 bullets and a known icon
    • links must be scout:// deep links, never the web
    • republishing a version replaces it, which is how a typo gets fixed
  2. Reading. GET /api/release-notes?upTo=<installed>[&after=<last seen>] is public (release-notes.controller.ts:10). selectReleaseNotes (release-notes.service.ts:32) compares versions numerically (1.1.10 > 1.1.9). With after it returns every version in (after, upTo]; without it, the latest 3 ≤ upTo. It never returns notes newer than the installed app.

  3. Remembering what was seen. The app persists lastSeenReleaseVersion on the device, next to tutorialSeen.

    • Finishing onboarding records the installed version (store.ts, setTutorialSeen), which is what keeps a brand-new user from ever getting the sheet.
    • Presenting the sheet records it (presentWhatsNew). It is recorded when the sheet opens rather than when it closes, so no way out (X, Got it, a link, the Android back button) can bring it back next launch. The same call freezes the notes the sheet shows (presentedReleaseNotes, not persisted), so the content cannot change while the sheet animates away.
  4. Deciding at launch (mobile/src/utils/whatsNewGate.ts):

    • whatsNewQuery (:34):
      • nothing while onboarding is unfinished, or when this version was already seen (or the app was downgraded)
      • "everything since last seen" for an update
      • "latest only" for a device with no record yet, which is an existing user on their first update after this feature shipped
    • pickLaunchNotes (:42) then keeps, for that last case, only the installed version's notes, never the back catalogue. A version with no notes (a hotfix) shows nothing.
  5. Waiting its turn. WhatsNewWatcher (mounted beside CelebrationWatcher, _layout.tsx:452) presents the sheet only when canPresentWhatsNew (:56) agrees:

    • the user is on an ordinary drawer screen (Home and its siblings), not onboarding, a celebration, or a pushed screen
    • no unlock celebration is owed
    • there are notes
    • it has not already shown this session

    The calm moment must also last WHATS_NEW_SETTLE_MS (1.5 s) and still hold when it is re-checked. CelebrationWatcher dequeues the last owed patch a moment before the celebration route opens, and without the settle the sheet would slip in on top. Celebrations are blocked over the sheet in turn (celebrationGate.ts:33), so the two never stack.

  6. Take me there dismisses the sheet and then navigates (router.dismiss() then router.navigate(path)), the same as the celebration's "view patch". A router.replace from this root modal would stack a second drawer navigator over Home for a drawer route.

  7. Dates are formatted in UTC (formatReleaseDate), so a release published for Sep 27 never reads "Sep 26" in the US.

  8. Malformed data never crashes the sheet. sanitizeReleaseNotes drops any item or section the app cannot render. The server validates every write; this is the second line of defence against a hand-edited row.

Data model

release_notes (migration 20260927120000_release_notes), one row per version: version (PK text), released_at, sections JSONB, created_at, updated_at. sections = [{ title: New|Improved|Fixed, items?: [{ icon, heading, bullets[1-3], link?: { label, url: scout://… } }], fixes?: string[] }]. On the device: lastSeenReleaseVersion in the persisted store.

API surface

Method Path Auth Purpose
GET /api/release-notes?upTo=1.1.96&after=1.1.93 none Releases in (after, upTo], newest first
GET /api/release-notes?upTo=1.1.96 none Latest 3 releases ≤ upTo
POST /api/admin/release-notes AdminGuard Validate and upsert one release's notes

Key files

Configuration and flags

No feature flag. DRAWER_LIMIT = 3 (release-notes.service.ts:7). The notes are cached for 5 minutes on the device.

Edge cases and known limits

What this feature does NOT do

Tests that cover it

Open questions