Summary
The Plan tab answers "can I actually go, and what should I know first?" for one patch. It sits right after Overview on the patch detail screen and shows:
- a headline, computed on the place's own clock, that reads "Open now", "Opens at 9 AM", "Closed for the season", "Open 24 hours" or "Closed";
- today as a bar with the open hours filled in and a needle for the current time;
- up to four tiles: Entry (free, or the first price), Tickets ("Book ahead" or "Walk in"), Best time (the best months) and Time needed;
- under "The details": the full weekly hours, other venues on the site (a visitor center, a museum) with their own hours, notes, every price, a twelve-month best-time chart, a short "know before you go" list, and any live alerts from the National Park Service;
- a footnote that says when the details were last checked and against which sites, each a link to the page it came from, then "Hours change. Confirm on <official site>" and "Something wrong? Tell us".
The footnote is the point of the feature. Hours and prices go stale, so everything the tab shows was researched on a stated date from named pages, and nothing is shown that could not be verified from a fetched source: a fact the research could not confirm is left out rather than guessed.
Status
Live for the 64 National Parks. There is no feature flag: the tab appears once an app build containing it is installed and a patch's visit-info record is published. Both are true today (verified 2026-11-04):
- The tab is in every store release since v1.1.112 (
useVisibleTabs.tswires it from that tag on). - The 64 National Parks records are published in production.
GET /api/sync/contentlists 64visitInfoIds, andGET /api/sync/patch/np-zion-national-park/visit-inforeturns the record checked 2026-10-23. - Every other patch has no record, so it shows no Plan tab. Do not describe the Plan tab as covering anything beyond the national parks, or as "coming to every patch".
Planned next: the City Challenge patches, city by city. A patch with no published record shows no Plan tab at all.
User-facing surfaces
- The Plan tab on the patch detail screen (
scout://patch-modal/<patchId>), second in the tab bar after Overview. It stays once the patch is collected. It appears only when the synced catalog lists the patch as having a published record (useHasVisitInfo,mobile/src/components/patch-detail-v3/PatchDetailScreenV3.tsx:73-74). There is no feature flag. - No dedicated route or deep link: it is a tab, not a screen. Tab order is built in
mobile/src/components/patch-detail-v2/useVisibleTabs.ts:59-67. - "Something wrong? Tell us" in the footnote opens the Feedback screen for that patch (see "How it works").
How it works
Which patches show the tab. The catalog sync (GET /api/sync/content) carries visitInfoIds, the ids of patches with a published record that the public may see (backend/src/sync/sync.service.ts:222-227,293). The app keeps that list with the rest of the synced catalog (mobile/src/query/queries/content.ts:37-41,85-104) and the detail screen checks it (useHasVisitInfo, mobile/src/query/queries/visit-info.ts:30-34). The list is replaced in full on every sync, so a record that is unpublished disappears from the list on the next sync.
Loading the record. When the tab is going to show, the screen prefetches the record (PatchDetailScreenV3.tsx:90-93) from GET /api/sync/patch/:id/visit-info, so the tab is usually instant. While it loads the tab shows the house skeleton (a headline, a bar, four tiles), never a spinner (mobile/src/components/patch-detail-v3/plan/PlanBody.tsx:142-161).
The place's clock. Every "is it open" rule reads the place's time zone, never the phone's: a scout in New York at 11:30 PM opening Yellowstone sees Denver's 9:30 PM (placeNow, mobile/src/domain/visit-info.ts:185-190). Dates are handled as plain YYYY-MM-DD strings so no time zone can shift a day.
The headline (openState, mobile/src/domain/visit-info.ts:279-301), in order:
- A researched whole closure that is in effect now: "Closed", with "Closed until Nov 18" under it. Only this can turn the headline to Closed.
- The way the place opens: always open reads "Open 24 hours"; view-only places read "View any time"; a place recorded as closed reads "Closed".
- Otherwise today's hours: "Opens at 9 AM", "Open now · Until 5 PM · last entry 4 PM", "Closed for the day" with when it next opens, or "Closed for the season" with the date it reopens. A dated exception (a holiday) beats the usual week, and its name leads the line ("Thanksgiving · Opens tomorrow at 8 AM"; just "Thanksgiving" when there is no opening to name).
"When it next opens" skips every day a whole closure covers, so "Opens tomorrow at 9 AM" never contradicts a closure that starts tomorrow, and an out-of-season "Opens May 15" moves past a closure over the season's first days; a partial closure never moves it (nextOpen, visit-info.ts:215-231). It looks 62 days ahead on an ordinary closed day and, failing that, a whole year, the same horizon as out of season.
The hours that drive this are those of the grounds: the place the patch unlocks on. A visitor center, museum or tour inside the site is a venue with its own week in "The details" and never changes the headline.
Closures. A researched closure that is on now, or starts within 30 days, gets a one-line strip under the bar ("Closes Oct 26 – Nov 18 · In 3 days") and a callout in the details (activeClosure, visit-info.ts:234-269). When none is on now, the soonest coming up wins, and on the same start date a whole closure beats a partial one. A partial closure (a road, a pass, an entrance) is a heads-up only: the place stays "Open". Only a whole closure shows "Closed".
National Park Service alerts. For a National Park Service unit, the live alerts for its park code show as a quiet "NPS" strip under the headline (only "Park Closure" and "Danger" alerts earn the strip, npsStripAlerts, visit-info.ts:346-347) and in full, each linked, in "From the National Park Service" in the details. The alerts only inform. They never decide open or closed: the Park Service's alert feed has no start or end dates and a "Park Closure" is often a single trail. Only a researched, dated closure changes the headline. Alerts attach only where the researcher marked the record as the park unit's own (alertsFromNps: true) and the patch has a park code (backend/src/visit-info/visit-info.service.ts:41-58). Other patches that share the unit's park code, and its park-service id too (the Lincoln Memorial, Arlington National Cemetery and the USS Maine all carry linc), get none; their records leave the flag off. All 64 National Parks records carry it.
Tickets. The tile reads "Book ahead" only when reservations are needed today; outside a reservation window it reads "Walk in" with the window beneath ("Reservations needed May 22 – Oct 11") (ticketState, visit-info.ts:310-316). A callout with a booking link appears whenever reservations apply some time in the year.
Best time. Twelve months, each rated best / fine / avoid, are shown as the best runs ("Mar – May · Sep – Oct"; a run over New Year reads as one run) (bestRuns, visit-info.ts:319-340). A tile with nothing true to say is dropped; the last of an odd count spans the row (planTiles, visit-info.ts:394-405).
The footnote and freshness. "Checked Oct 23, 2026 against nps.gov, recreation.gov." Each site is a link to the page used; the first two are named and "+N" opens the rest. The Park Service's data API is a machine endpoint, not a page, so it is never named or linked: only pages a person can read are (sourceHosts, visit-info.ts:421-433). "Confirm on <first site>" links to the official page. Every link on the tab (the source sites, "Confirm on …", the booking link and the Park Service's alerts) opens in the in-app browser (expo-web-browser, as the patch screen's image search does), so you are one swipe from the patch; if the browser can't open, the URL is shown instead (openPlanLink, mobile/src/components/patch-detail-v3/plan/openLink.ts:11-15; PlanFootnote.tsx:38,66, PlanDetails.tsx:119,295). A record checked more than 183 days ago says so ("Last checked Mar 2, 2026 against nps.gov, over 6 months ago. Things may have changed.") with the date in the accent (PlanFootnote.tsx:58-63); it does not hide the details. Records are meant to be re-checked well before that (every 91 days for a place with hours or prices, every 182 for an always-open or view-only place, sooner if the current hours season ends first), so what you see can be up to roughly three to six months old.
"Something wrong? Tell us" opens the Feedback screen carrying the patch and the screen (screen: 'plan'), so the report reaches the support inbox already attached to the right place (PlanFootnote.tsx:71-79; see feedback-conversations).
Errors. If the record can't be read the tab shows the house error state, never a half-drawn tab:
- network or server failure, or offline: "Check your connection and try again." with Try Again; offline it recovers by itself on reconnect;
- the record was unpublished since the last catalog sync (the endpoint answers 404): "The visiting details for this place aren't available right now." with no Try Again, since a retry would 404 again until the next sync;
- a time zone the phone can't read: "We couldn't read the visiting details for this place." with no Try Again (
PlanBody.tsx:185-200).
Data model
One researched record per patch, in the patch_visit_info table (backend/prisma/schema.prisma:588-608). In the API it is flattened to (backend/src/visit-info/visit-info.types.ts:118-122):
| Field | Meaning |
|---|---|
checkedOn, recheckAfter |
When it was researched, and when it is next due |
access |
hours, always_open, seasonal, reservation_only, view_only or closed |
accessNote, timezone |
One line on how the place opens; the IANA zone its hours are in |
hours |
The grounds' weekly hours as dated periods (a season can wrap New Year), dated exceptions, last entry, a note |
venues |
Up to three places on the site with their own hours (a visitor center, a museum) |
closures |
Dated closures, each whole or partial |
cost, tickets |
Free or a list of prices and pass notes; whether reservations are needed, for what, in which window, and a booking link |
bestTime, duration |
Twelve month ratings and why; a short "time needed" |
know, notes |
Three to six "know before you go" items; labelled notes |
sources |
Every page used, with the date it was fetched |
npsAlerts |
Live Park Service alerts, for a record marked as the park unit's own (not stored in the record; the alertsFromNps mark itself is review-side and not sent) |
The app's copy of the shape is mobile/src/domain/visit-info.ts:94-119.
API surface
| Endpoint | Auth | Notes |
|---|---|---|
GET /api/sync/patch/:id/visit-info |
None | The published record plus the park's live alerts (backend/src/visit-info/visit-info.controller.ts:10-13). 404 when there is no published record, or the patch is not publicly visible (unlike GET /api/sync/patch/:id, which is unfiltered), so the endpoint reveals nothing the catalog does not. Review-only fields never leave the server (visit-info.service.ts:19-39). |
GET /api/sync/content |
None | Now also carries visitInfoIds (see above). |
Key files
mobile/src/components/patch-detail-v3/plan/PlanBody.tsx— the tab: model, headline, strips, skeleton, error states.mobile/src/components/patch-detail-v3/plan/PlanTiles.tsx,TodayBar.tsx,PlanDetails.tsx,PlanFootnote.tsx— the tiles, today bar, details and footnote.mobile/src/domain/visit-info.ts— every rule: place clock, headline, closures, tickets, best runs, tiles, bar, source hosts.mobile/src/query/queries/visit-info.ts— the record query anduseHasVisitInfo.mobile/src/components/patch-detail-v2/useVisibleTabs.ts:59-67— where Plan sits in the tab bar.backend/src/visit-info/visit-info.service.ts,visit-info.controller.ts— the endpoint.
Configuration and flags
- No feature flag. The tab needs an app build that contains it and a published record for the patch.
Edge cases and known limits
- Hours and prices go stale. Records are dated and re-checked on a schedule, but a place can change between checks. The tab says when it was last checked and links to the sources; it does not promise the hours are right today.
- Time needed is often missing. Park pages rarely say how long to stay, so many places show no "Time needed" tile. It is never guessed.
- Best months only come from a stated reason. Where a source describes weather without judging seasons, the tile is left out.
- Kings Canyon and Sequoia are one Park Service unit (
seki) with one alert feed. Both records are marked as the unit's own, but our Kings Canyon patch has no park code yet, so only Sequoia's patch shows the unit's alerts today. - A missing mark hides alerts. If a researcher leaves
alertsFromNpsoff a park unit's own record, its alerts don't show (the admin editor shows the record's alerts, so it is visible there). - "Opens …" only knows researched closures. It skips days a whole, dated closure covers; an NPS alert, or a closure nobody researched, does not move it.
- Offline. The record is fetched on demand, not carried in the synced catalog (which holds only the list of ids). A phone with no connection that has not loaded the record shows the error state.
What this feature does NOT do
- No booking. "Book ahead" tells you reservations are needed and links to the official booking page. Scout does not sell, hold or reserve anything.
- No live crowds, no live wait times, no weather forecast. "Best time" and the crowds note are researched seasonal guidance, not current conditions.
- It does not guarantee the hours are current. A record can be up to three to six months old, and past six months the footnote says so. Always confirm on the official site, which the footnote links.
- No Plan tab for a city, a state, or any other patch with no single visit to plan (a river, a long byway). Those patches have no record and no tab.
- Not beyond the first campaigns. The planned scope is National Parks, then City Challenges. Other campaigns, and patches outside them, have no Plan tab until they are researched and published.
- NPS alerts do not decide open or closed. They are shown as information. Only a dated closure confirmed by a researcher can show "Closed".
- It does not follow the phone's clock. It always uses the place's own time zone, so it can read a different time than the phone shows.
- No external browser hand-off. Source, booking and alert links open in the in-app browser, never in Safari or Chrome directly (the in-app browser has its own "open in browser" button).
- No notifications. It does not alert you when a place's hours or closures change.
Tests that cover it
mobile/src/domain/__tests__/visit-info.test.ts— the rules: before opening, open, after closing, closed today, all day, period boundaries, dated exceptions, the place's time zone differing from the phone's, a Park Closure alert never closing the place, closures on now / within 30 days / past, best-month runs including a New Year wrap, the next opening skipping a whole closure but not a partial one (in season and out), the year-ahead fallback, no dangling " · ", whole before partial on the same start date, the 183-day stale boundary, tile drop-out and the odd-count wide tile, and which source sites are named.mobile/screen-tests/patch-plan-tab.test.tsx— the tab on real-shaped records: a full record (open now, entry, best months, when checked, source links), the tab absent for an unlisted patch (paired with the listed Zion case), a partial closure beside an NPS alert, a whole closure, Yellowstone read on its own clock, dropped tiles with a paired full record, a stale record with a paired fresh one, a failed fetch with Retry, offline (paused) with Retry and recovery on reconnect, a 404 without Retry, an unknown time zone's "couldn't read", the links opening in the in-app browser, and "Something wrong?" carrying the patch and screen.mobile/src/components/patch-detail-v2/__tests__/useVisibleTabs.test.ts— Plan's position and its absence without a record.mobile/src/query/__tests__/visit-info.test.tsx,mobile/src/query/__tests__/content.test.ts— the record query (a 404 is not retried) andvisitInfoIdssurviving a delta sync.backend/src/visit-info/__tests__/visit-info.service.spec.ts— the endpoint: the flattened record, nothing review-only exposed, 404 for no row, a not-applicable row and a non-public patch, and alerts only for a record markedalertsFromNps(a sibling with the same park-service id and park code gets none).backend/src/sync/__tests__/sync.service.visit-info-ids.spec.ts—visitInfoIdson full and delta syncs, without not-applicable rows, without admin-only patches for non-admins.mobile/maestro/tests/patch-plan-tab.yaml— the end-to-end flow on Zion; it asserts the footnote by itsplan-checkedtext ("Checked … against nps.gov"), because the inline source links are nested Text, which expose no id on iOS. Written, not run: it needs a build with the tab, the endpoint deployed and the Zion record published.
Open questions
- Who re-checks records on the recurring schedule, and what triggers it, is not decided.
- Time zones on the Hermes JS engine (
Intl.DateTimeFormatwithtimeZone) were verified on the iOS simulator on 2026-10-24: with the simulator on Central time, Zion's needle sat on Denver time and Isle Royale's on Eastern. Android is not yet verified.