Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
3872b02
admin report list item carries thumbnailUrl
theobong Sep 16, 2026
2d59670
feed ranking contract: scored cursor, counts endpoint, realtime topic…
theobong Sep 16, 2026
f44415a
shared 0.49.0
theobong Sep 16, 2026
8b062c3
feed live updates: new-posts pill, count patching, ranked cursor adop…
theobong Sep 16, 2026
5ea06d9
donation card moved above event sign-ups
theobong Sep 16, 2026
d2b15d2
resolve the iOS API base URL from the install source at runtime
theobong Sep 16, 2026
76c1250
install-source probe hardening and env-scoped storage
theobong Sep 16, 2026
02c4cba
render the shell below a pushed route from its own nav stack
theobong Sep 16, 2026
bf241fc
cut the work that runs while a swipe transition is in flight
theobong Sep 16, 2026
cf34264
pressed hover and focus affordances across ui pressables
theobong Sep 16, 2026
5252119
44pt search field family across list bodies
theobong Sep 16, 2026
c425c9b
load-more pills consolidated onto sectionStyles
theobong Sep 16, 2026
a0b443b
text clamps and token substitutions in ui
theobong Sep 16, 2026
6d13d11
mobile overlay header ink and camera affordances
theobong Sep 16, 2026
b13f311
mobile auth affordances and dangerInk error rows
theobong Sep 16, 2026
37b50c2
web dark-mode token fixes: ring collision, hue ramps through scheme v…
theobong Sep 16, 2026
b897fd3
guest cancel view localized in all four locales
theobong Sep 16, 2026
1072e05
web transition, radius, type and a11y polish
theobong Sep 16, 2026
74bfa3c
native swipe capture reads the real touch-down x, not the pre-grant zero
theobong Sep 16, 2026
ede88b9
web token layer: canonical light palette, shadcn var collision, durat…
theobong Sep 16, 2026
e224af2
feed realtime: one cache pass per counts batch, overflow re-queued, p…
theobong Sep 16, 2026
d725982
event donation card hidden on cancelled and host views; receipt wraps…
theobong Sep 16, 2026
a26af45
a detail route with no entry snapshots the whole shell stack instead …
theobong Sep 16, 2026
bdbc4a5
mobile storage environment marker purges another environment's legacy…
theobong Sep 16, 2026
f285327
inbox row swipe guard pins the recorded touch-down x
theobong Sep 16, 2026
c9dc475
drop the explanatory comments added with these fixes
theobong Sep 16, 2026
391d01c
event announcements + consolidated analytics contract
theobong Sep 16, 2026
6bf64ab
shared 0.50.0
theobong Sep 16, 2026
31b3ebe
enums: announcement broadcast kind label in all four locales
Sep 16, 2026
350c50f
event announcements: read surfaces, deep-linkable detail, data hooks
Sep 16, 2026
bb97299
host comms: group chat + announcements replace the two message button…
Sep 16, 2026
b101102
event analytics in the app: chart primitives, a carousel card and a f…
Sep 16, 2026
f4e1e01
in-app organization management at /orgs/:slug/manage
Sep 16, 2026
e21719b
horizontal BarChart sizes itself from its parent rather than taking a…
Sep 16, 2026
4b59294
chat keeps the local photo when the server ack drops the attachment
theobong Sep 16, 2026
6bbc8ab
quote composer shows the post being quoted
theobong Sep 16, 2026
ea63c1c
social links render as brand icons on every profile surface
theobong Sep 16, 2026
9a2a768
review fixes: analytics gating, chat overlay, last-admin pre-disable,…
theobong Sep 16, 2026
0e004cc
web org page social links as icons
theobong Sep 17, 2026
4559605
address resolution contract: resolve-address endpoint, precision ladd…
theobong Sep 17, 2026
8965d4f
shared 0.51.0
theobong Sep 17, 2026
111159f
list cards clear their corner arc
theobong Sep 17, 2026
c784b5a
event dashboard drops the donation link row
theobong Sep 17, 2026
22daad0
event detail leads with what the event is
theobong Sep 17, 2026
b1f69cc
hosts can put a cover image on an event
theobong Sep 17, 2026
4c755f6
address resolution has a data hook
theobong Sep 17, 2026
4967715
addresses get a row you can tap, copy and navigate to
theobong Sep 17, 2026
388d1b6
hosts confirm the meeting address before an event goes live
theobong Sep 17, 2026
b550374
detail pages and tickets share one address row
theobong Sep 17, 2026
68b7f75
the report composer offers the address it already knows
theobong Sep 17, 2026
1750bc9
a missing resolve endpoint falls back instead of failing
theobong Sep 17, 2026
b0b1359
picking a place no longer doubles up the address
theobong Sep 17, 2026
0d214e7
editing an event keeps the address you already saved
theobong Sep 17, 2026
0274b45
a ticket and an ended event both keep their address and roster usable
theobong Sep 17, 2026
6cfb7cf
a report only sends an address the reporter actually wrote
theobong Sep 17, 2026
cd8f369
Open maps on Android via the https Google URL instead of a refused ge…
theobong Sep 17, 2026
b976925
org page: both event runs stay open with a real Load more, share move…
theobong Sep 17, 2026
5546f97
event dashboard: invitations get their own card, the attention nudges…
theobong Sep 17, 2026
3a8bcff
event detail drops the guest contact list
theobong Sep 17, 2026
6772791
invitations move from the event dashboard to your own profile
theobong Sep 17, 2026
118f568
the analytics carousel becomes graph-first and pages on a measured width
theobong Sep 17, 2026
ad9156c
merge origin/main
theobong Sep 22, 2026
b82209f
feed ranking: jitter knob, location-first default (0.54.0)
theobong Sep 22, 2026
7c310ba
push teardown unregister, 409 terminal, aps-environment mode
theobong Sep 22, 2026
bdd839f
feed batch cleanup
theobong Sep 22, 2026
a3fd6dd
storage marker write policy fix
theobong Sep 22, 2026
8480fc1
sign-out revokes the server session; lapsed push captures its bearer …
theobong Sep 22, 2026
4aec4e2
review fixes: og cover gate, thread routes, about api host, chart and…
theobong Sep 22, 2026
27d905e
sign-out releases the push token before revoking the session
theobong Sep 22, 2026
ca2df09
analytics labels, plurals, announcement emphasis
theobong Sep 22, 2026
c8c8f7e
host analytics summary contract (0.55.0)
theobong Sep 22, 2026
1ef64af
host analytics: 30-day all-events carousel and event+range analytics …
theobong Sep 22, 2026
a0352d7
analytics by-event rows keyed by event id
theobong Sep 22, 2026
947f0fd
analytics: whole-event signups tile fix
theobong Sep 22, 2026
cf89c61
analytics carousel: inert panels, quiet footer press, wider gutter
theobong Sep 22, 2026
2749250
carousel chart budgets for its label row
theobong Sep 22, 2026
e5d2893
profile donation card reuses the org card, row variant removed
theobong Sep 22, 2026
bf5a0b4
bar chart edge labels clamp inside the chart width
theobong Sep 22, 2026
702f71c
carousel panel drops its corner clip
theobong Sep 22, 2026
649a10d
event donation card shows for every viewer
theobong Sep 22, 2026
728902c
donate card spacing on profiles, org donate above events
theobong Sep 22, 2026
f7d2979
own profile: event dashboard row sits under the affiliation
theobong Sep 22, 2026
d3a55d6
Revert "own profile: event dashboard row sits under the affiliation"
theobong Sep 22, 2026
c764230
link previews accept reference codes and handles, and preview posts
theobong Sep 22, 2026
ff04585
link previews in the X card format
theobong Sep 23, 2026
624ee75
web map flies to the fix after a granted prompt
theobong Sep 22, 2026
85c94ee
mobile: wait for the first fix after a prompted grant
theobong Sep 22, 2026
60c175a
mobile: a map gesture cancels the late first-fix flight
theobong Sep 23, 2026
c3c7f73
web page stack: retained layers with slide transitions
theobong Sep 23, 2026
354a173
compact web opens details as full pages
theobong Sep 23, 2026
1641916
web shell safe areas, banner offset and input zoom floor
theobong Sep 23, 2026
de5fa37
web app manifest
theobong Sep 23, 2026
02e477b
link previews: unlisted events keep the plain card, code-point clamp,…
theobong Sep 23, 2026
caaaf23
event detail drops the mini-map hero
theobong Sep 22, 2026
5a2f1d1
map focus eases again when the same pin is re-published
theobong Sep 22, 2026
978b130
address row: labelled Show on map action
theobong Sep 22, 2026
39e5f39
map focus handoff survives the detail page
theobong Sep 23, 2026
655b795
show on map switches to the map tab on full pages
theobong Sep 23, 2026
1b47d96
deep-linked detail dismisses to the root shell on a view switch
theobong Sep 23, 2026
46a72f4
web map: a user gesture beats the late prompt fix; stricter prompt si…
theobong Sep 23, 2026
fc0f17f
web-only post thread route, side safe areas, inert retained pages
theobong Sep 23, 2026
9ced29d
show on map: one-shot fly-to request keeps every pin rendered
theobong Sep 23, 2026
166d023
event cover hint no longer mentions the map
theobong Sep 23, 2026
465a801
show on map draws the target when it is not on the map
theobong Sep 23, 2026
bfc4e95
Merge remote-tracking branch 'origin/main' into feat/feed-and-polish-…
theobong Sep 23, 2026
7ff07c2
shared 0.56.0
theobong Sep 23, 2026
8954911
web safe-area insets applied once; post hrefs open the thread; mobile…
theobong Sep 23, 2026
d30c38c
Merge remote-tracking branch 'origin/feat/feed-and-polish-batch' into…
theobong Sep 23, 2026
1166837
Merge remote-tracking branch 'origin/main' into feat/mobile-web-parit…
theobong Sep 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/guest-readable-public-posts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@civfix/shared": patch
---

`getPost` is `auth: "optional"`: a public post is guest-readable (share-link previews).
4 changes: 2 additions & 2 deletions apps/community-mobile/APP-STORE-SUBMISSION-AUDIT.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ Permission↔usage parity is clean and there is **no tracking/ads/analytics SDK*
Identity and asset pipeline are largely submission-ready (real reverse-DNS ids; marketing icon has **no alpha**; encryption flag accurate; OSM attribution present on the *main* map). The blocking work is operational + a couple of review/ToS risks. Note: `android/` and `ios/` are gitignored (regenerated by `expo prebuild`); **no `ios/` has ever been generated on this box**, so the iOS native build is unproven.

- 🟠 **H9 — iPad supported while portrait-locked + phone-first.** `app.config.ts:43` `orientation:"portrait"` + `:55` `ios.supportsTablet:true`. With tablet support on, App Store Connect demands a full iPad screenshot set and reviewers test on iPad; a stretched iPhone layout draws 4.x design rejections. The shared UI adapts by width (≥840px sidebar shell) so it *may* pass, but must be verified on real iPad hardware. *Fix:* either commit to iPad (test + upload iPad screenshots) or set `supportsTablet:false`.
- 🟠 **H10 — LocationPicker map disables OSM/CARTO attribution.** `LocationPicker.native.tsx:96-98` sets both `logo={false}` and `attribution={false}` and renders no text credit (only a "Tap the map to place the pin" hint), while drawing the same CARTO Voyager (OSM data) tiles as the home map. This violates the OSM Tile Usage Policy / CARTO basemap ToS on that screen. *(Compliant comparators: `MiniMap.native.tsx:56-58` draws a faint "CARTO / OSM" text credit; `Map.native.tsx:245` keeps the native (i) button.)* *Fix:* remove `attribution={false}` or add a static "© OpenStreetMap / CARTO" credit.
- 🟠 **H10 — LocationPicker map disables OSM/CARTO attribution.** `LocationPicker.native.tsx:96-98` sets both `logo={false}` and `attribution={false}` and renders no text credit (only a "Tap the map to place the pin" hint), while drawing the same CARTO Voyager (OSM data) tiles as the home map. This violates the OSM Tile Usage Policy / CARTO basemap ToS on that screen. *(Compliant comparator: the home map, `Map.native.tsx`, keeps the native attribution control — the (i) button placed by `attributionPosition`.)* *Fix:* remove `attribution={false}` or add a static "© OpenStreetMap / CARTO" credit.
- 🟡 **M12 — Release build signed with the debug keystore.** `android/app/build.gradle:118-121` `release` uses `signingConfigs.debug` (prebuild default; comment warns against it). Fine for local APKs, not for store upload. Informational since `android/` is regenerated. *Fix:* let EAS manage the upload keystore (`eas credentials`).
- 🟡 **M14 — No `ios/` project + Google iOS URL scheme placeholder.** `app.config.ts:26` falls back to `com.googleusercontent.apps.PLACEHOLDER` when the env var is unset; an iOS build without the real reversed-client-id ships a non-functional Google sign-in scheme. Overlaps **B8**. *Fix:* set `EXPO_PUBLIC_GOOGLE_IOS_*` in the EAS env; run a macOS/EAS iOS build to validate.
- 🟡 **M15 — New Architecture on + native libs pinned for compatibility.** `app.config.ts:46` `newArchEnabled:true`; README notes `react-native-mmkv` pinned to v3 and `vision-camera` to v4 to avoid Nitro, `react-native-compressor` excluded from the doctor check. Any module misbehaving under Fabric is a crash-on-launch risk. Not a config error — a **test obligation**. *Fix:* run the Play pre-launch report + a TestFlight pass on real devices (camera, MapLibre, video).
Expand Down Expand Up @@ -193,7 +193,7 @@ Expected: `ACCESS_COARSE/FINE_LOCATION`, `CAMERA`, `RECORD_AUDIO`, `POST_NOTIFIC
- **Bundle id / package** are clean reverse-DNS (`org.civfix.community`), consistent iOS↔Android, no `com.example`/`com.anonymous`/`host.exp.exponent` leakage.
- **Marketing icon** has no alpha; **fonts** (Baloo 2, Bricolage Grotesque, JetBrains Mono, Manrope) are OFL/Apache and Lucide is ISC — all redistributable, no restrictive assets.
- **Prior `["threads"]` cache-collision crash** is now guarded with split query keys + tests (`packages/ui/src/data/__tests__/discussion-cache.test.ts`); low residual risk. Push-token and Google-sign-in failures are both wrapped non-fatal (dead-feature, not crash).
- **Main map (home) shows OSM attribution** (`Map.native.tsx:245`) and `MiniMap` shows a text credit — only `LocationPicker` is non-compliant (H10).
- **Main map (home) shows OSM attribution** (`Map.native.tsx`, the native attribution control at `attributionPosition`) — only `LocationPicker` is non-compliant (H10).

---

Expand Down
32 changes: 25 additions & 7 deletions apps/community-mobile/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
useLayoutMode,
Map as SharedMap,
MapControls,
useMapFlyTo,
useMapFocus,
useMapViewport,
useReportFilterStore,
enabledCategoriesArray,
Expand Down Expand Up @@ -273,9 +275,16 @@ export default function MapHomeScreen() {
}, [])

const resolveLocation = location.resolve
const awaitFirstFix = location.awaitFirstFix

const recalledViewport = recallMapViewport() !== null
const initialCenterOwnedRef = useRef(recalledViewport)

const onUserCameraMove = useCallback(() => {
initialCenterOwnedRef.current = true
beginCameraRequest()
}, [beginCameraRequest])

const [rememberedCenter] = useState<RememberedCenter | null>(readLastCenter)

const centerPlan = useMemo(
Expand Down Expand Up @@ -303,19 +312,25 @@ export default function MapHomeScreen() {
const onLocate = useCallback(() => {
const requestGeneration = beginCameraRequest()
void (async () => {
const precise = await resolveLocation()
const { coords, prompted } = await resolveLocation()
const fallback = approximatePointRef.current
initialCenterOwnedRef.current = true
if (precise) {
if (coords) {
adoptedSourceRef.current = "precise"
centerOnTarget({ ...precise, zoom: PRECISE_ZOOM }, requestGeneration)
centerOnTarget({ ...coords, zoom: PRECISE_ZOOM }, requestGeneration)
return
}
if (!fallback) return
adoptedSourceRef.current = "approximate"
centerOnTarget({ ...fallback, zoom: APPROX_ZOOM }, requestGeneration)
if (fallback) {
adoptedSourceRef.current = "approximate"
centerOnTarget({ ...fallback, zoom: APPROX_ZOOM }, requestGeneration)
}
if (!prompted) return
const firstFix = await awaitFirstFix()
if (!firstFix) return
adoptedSourceRef.current = "precise"
centerOnTarget({ ...firstFix, zoom: PRECISE_ZOOM }, requestGeneration)
})()
}, [beginCameraRequest, centerOnTarget, resolveLocation])
}, [awaitFirstFix, beginCameraRequest, centerOnTarget, resolveLocation])

const onboardingDone = useOnboardingStore((s) => s.completedVersion >= ONBOARDING_VERSION)
const tourPresenting = useOnboardingStore((s) => s.presenting)
Expand Down Expand Up @@ -358,6 +373,7 @@ export default function MapHomeScreen() {
if (!shouldAdoptCenter(adoptedSourceRef.current, source)) return
adoptedSourceRef.current = source
initialCenterOwnedRef.current = true
if (useMapFocus.getState().focus || useMapFlyTo.getState().highlight) return
centerOnTarget(center)
}, [centerPlan, seedCenter, centerOnTarget])

Expand Down Expand Up @@ -483,6 +499,7 @@ export default function MapHomeScreen() {
<ManagedMap
onMapHandle={setMapHandle}
onInstanceRegionChange={onRegionChange}
onUserCameraMove={onUserCameraMove}
initialCenter={mapSeed}
reports={pins}
reportAggregates={reportAggregates}
Expand All @@ -503,6 +520,7 @@ export default function MapHomeScreen() {
seedCenter,
setMapHandle,
onRegionChange,
onUserCameraMove,
location.coords,
location.permission,
pins,
Expand Down
26 changes: 18 additions & 8 deletions apps/community-mobile/src/components/DetailRouteHost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ import {
shellStackBelow,
useNestedShellStore,
} from "@/lib/nestedShellSignal"
import { secondaryShellBackAction } from "@/lib/secondaryShellBack"
import {
secondaryShellBackAction,
secondaryShellLeaveAction,
type SecondaryShellLeaveAction,
} from "@/lib/secondaryShellBack"

export interface DetailRouteHostProps {
entry: DetailEntry | null
Expand Down Expand Up @@ -98,19 +102,25 @@ export default function DetailRouteHost({ entry }: DetailRouteHostProps): React.
}, [hostId, navigationRef]),
)

const leave = useCallback(() => {
leftRef.current = true
if (router.canGoBack()) router.back()
else goHome(router)
}, [router])
const leave = useCallback(
(action: SecondaryShellLeaveAction) => {
leftRef.current = true
if (action === "home" || !router.canGoBack()) {
goHome(router)
return
}
router.back()
},
[router],
)

const armedRef = useRef(false)
useFocusEffect(
useCallback(() => {
if (useNavStore.getState().active === null && entryRef.current) seedEntry(entryRef.current)
if (useNavStore.getState().active === null) {
armedRef.current = false
leave()
leave("back")
return
}
armedRef.current = true
Expand All @@ -121,7 +131,7 @@ export default function DetailRouteHost({ entry }: DetailRouteHostProps): React.
}
if (!armedRef.current) return
armedRef.current = false
leave()
leave(secondaryShellLeaveAction(state.lastTransition))
})
}, [leave]),
)
Expand Down
29 changes: 20 additions & 9 deletions apps/community-mobile/src/hooks/useUserLocation.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import { useEffect, useMemo, useState, useCallback } from "react"
import * as Location from "expo-location"
import type { LatLng } from "@civfix/shared/geocode"
import { GPS_TIMEOUT_MS, LAST_KNOWN_MAX_AGE_MS, withTimeout } from "@/lib/withTimeout"
import { FIRST_FIX_TIMEOUT_MS, GPS_TIMEOUT_MS, LAST_KNOWN_MAX_AGE_MS, withTimeout } from "@/lib/withTimeout"

export type LocationPermission = "undetermined" | "granted" | "denied"

export interface LocationRefreshResult {
coords: LatLng | null
prompted: boolean
}

export interface UserLocationState {
permission: LocationPermission
permissionResolved: boolean
coords: LatLng | null
resolve: () => Promise<LatLng | null>
refresh: () => Promise<LatLng | null>
resolve: () => Promise<LocationRefreshResult>
refresh: () => Promise<LocationRefreshResult>
awaitFirstFix: () => Promise<LatLng | null>
}

export function useUserLocation(): UserLocationState {
const [permission, setPermission] = useState<LocationPermission>("undetermined")
const [permissionResolved, setPermissionResolved] = useState(false)
const [coords, setCoords] = useState<LatLng | null>(null)

const readFix = useCallback(async (): Promise<LatLng | null> => {
const readFix = useCallback(async (timeoutMs: number = GPS_TIMEOUT_MS): Promise<LatLng | null> => {
try {
const pos =
(await Location.getLastKnownPositionAsync({ maxAge: LAST_KNOWN_MAX_AGE_MS })) ??
(await withTimeout(
Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.Balanced }),
GPS_TIMEOUT_MS,
timeoutMs,
))
if (!pos) return null
const next = { lat: pos.coords.latitude, lng: pos.coords.longitude }
Expand All @@ -35,17 +41,21 @@ export function useUserLocation(): UserLocationState {
}
}, [])

const refresh = useCallback(async (): Promise<LatLng | null> => {
const refresh = useCallback(async (): Promise<LocationRefreshResult> => {
const before = await Location.getForegroundPermissionsAsync().catch(() => null)
const { status } = await Location.requestForegroundPermissionsAsync()
setPermissionResolved(true)
if (status !== Location.PermissionStatus.GRANTED) {
setPermission("denied")
return null
return { coords: null, prompted: false }
}
setPermission("granted")
return readFix()
const prompted = before !== null && before.status !== Location.PermissionStatus.GRANTED
return { coords: await readFix(), prompted }
}, [readFix])

const awaitFirstFix = useCallback(() => readFix(FIRST_FIX_TIMEOUT_MS), [readFix])

useEffect(() => {
let active = true
void (async () => {
Expand All @@ -71,7 +81,8 @@ export function useUserLocation(): UserLocationState {
coords,
resolve: refresh,
refresh,
awaitFirstFix,
}),
[permission, permissionResolved, coords, refresh],
[permission, permissionResolved, coords, refresh, awaitFirstFix],
)
}
9 changes: 9 additions & 0 deletions apps/community-mobile/src/lib/detailRouteRestore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,3 +421,12 @@ test("DetailRouteHost takes its snapshot through detailShellSnapshot, not a raw
assert.match(host, /detailShellSnapshot\(useNavStore\.getState\(\)\.stack, seedKey, entryIdentity\)/)
assert.doesNotMatch(host, /stack\.filter\(\(e\) => entryIdentity\(e\) !== seedKey\)/)
})

test("DetailRouteHost dismisses to the root shell when a view switch empties the store", () => {
const host = readFileSync(
join(dirname(fileURLToPath(import.meta.url)), "..", "components", "DetailRouteHost.tsx"),
"utf8",
)
assert.ok(host.includes("leave(secondaryShellLeaveAction(state.lastTransition))"))
assert.ok(host.includes('if (action === "home" || !router.canGoBack())'))
})
17 changes: 16 additions & 1 deletion apps/community-mobile/src/lib/secondaryShellBack.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from "node:assert/strict"
import { test } from "node:test"
import { secondaryShellBackAction } from "./secondaryShellBack.ts"
import { secondaryShellBackAction, secondaryShellLeaveAction } from "./secondaryShellBack.ts"

test("hardware back pops the store stack while a parent page remains beneath the top one", () => {
assert.equal(secondaryShellBackAction(2, true), "pop-detail")
Expand All @@ -20,3 +20,18 @@ test("an empty store stack has nothing to pop, so the press falls through to the
assert.equal(secondaryShellBackAction(0, true), "system")
assert.equal(secondaryShellBackAction(0, false), "system")
})

test("a leave with no transition, or one caused by a pop, push, replace or restore, goes back to the route beneath", () => {
assert.equal(secondaryShellLeaveAction(null), "back")
assert.equal(secondaryShellLeaveAction({ type: "pop", count: 1 }), "back")
assert.equal(secondaryShellLeaveAction({ type: "push" }), "back")
assert.equal(secondaryShellLeaveAction({ type: "replace" }), "back")
assert.equal(secondaryShellLeaveAction({ type: "restore" }), "back")
})

test("a leave caused by a view switch dismisses to the root shell", () => {
assert.equal(secondaryShellLeaveAction({ type: "select" }), "home")
assert.equal(secondaryShellLeaveAction({ type: "select", consumes: true }), "home")
assert.equal(secondaryShellLeaveAction({ type: "seed" }), "home")
assert.equal(secondaryShellLeaveAction({ type: "reset" }), "home")
})
10 changes: 10 additions & 0 deletions apps/community-mobile/src/lib/secondaryShellBack.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import type { NavTransition } from "@civfix/ui"

export type SecondaryShellBackAction = "pop-detail" | "system"

export function secondaryShellBackAction(stackLength: number, canPopRoute: boolean): SecondaryShellBackAction {
if (stackLength > 1) return "pop-detail"
if (stackLength === 1 && !canPopRoute) return "pop-detail"
return "system"
}

export type SecondaryShellLeaveAction = "back" | "home"

const VIEW_SWITCH_TRANSITIONS: ReadonlySet<NavTransition["type"]> = new Set(["select", "seed", "reset"])

export function secondaryShellLeaveAction(transition: NavTransition | null): SecondaryShellLeaveAction {
return transition && VIEW_SWITCH_TRANSITIONS.has(transition.type) ? "home" : "back"
}
2 changes: 2 additions & 0 deletions apps/community-mobile/src/lib/withTimeout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
*/
export const GPS_TIMEOUT_MS = 4000

export const FIRST_FIX_TIMEOUT_MS = 15_000

/**
* Cap how OLD a CACHED device fix may be before we stop treating it as "where the user is".
*
Expand Down
Loading
Loading