From f45ae34cfb42890035475c63c8adc04fd78a66f6 Mon Sep 17 00:00:00 2001 From: David <60177543+davd-gzl@users.noreply.github.com> Date: Wed, 22 Jul 2026 20:01:22 +0000 Subject: [PATCH] Simplify: apply a batch of safe, behavior-preserving cleanups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A curated set from an app-wide analysis — each verified behavior-preserving and guarded by the type-checker + tests: - Shared helpers replace copies: `stampNow` (schema/helpers, was duplicated in all three stores), `formatPercentFloor` (lib/format, was inlined 4× across the stats screens), `isOffline` (GuideButton, was repeated 3×). - Reuse existing constants/values: MapScreen imports `IN_VIEW_CAP` from viewport (was a second copy); MapView's trip-arcs reset reuses `EMPTY_FC`; TripComposer maps over the shared `MODE_ORDER` instead of its own mode list. - Smaller reads: Backup reuses its `hasData` flag; SyncSection drops a needless `Promise.all([import])` wrapper. - Tighter surface: drop `export` from types only used in their own module (PosterStats, PhotoRef, MapMode, CityStatus). No behavior change. Gate: tsc clean, 459 unit tests, e2e green (smoke, a11y, trip-reconstruction, filter-panel, offline). --- apps/postcards/src/features/backup/Backup.tsx | 2 +- .../src/features/guides/GuideButton.tsx | 9 ++++-- apps/postcards/src/features/map/MapScreen.tsx | 3 +- apps/postcards/src/features/map/MapView.tsx | 6 ++-- apps/postcards/src/features/map/viewport.ts | 2 +- .../postcards/src/features/passport/poster.ts | 2 +- .../src/features/settings/SyncSection.tsx | 2 +- .../src/features/stats/StatStrip.tsx | 9 ++---- .../src/features/stats/StatsView.tsx | 28 ++++++------------- .../src/features/travel/TripComposer.tsx | 4 +-- apps/postcards/src/lib/format/format.ts | 7 +++++ apps/postcards/src/lib/image/photoBlobs.ts | 2 +- apps/postcards/src/lib/schema/helpers.ts | 4 +++ apps/postcards/src/lib/store/useStories.ts | 3 +- apps/postcards/src/lib/store/useTrips.ts | 3 +- apps/postcards/src/lib/store/useVisits.ts | 3 +- 16 files changed, 40 insertions(+), 49 deletions(-) diff --git a/apps/postcards/src/features/backup/Backup.tsx b/apps/postcards/src/features/backup/Backup.tsx index fa4ea30..1278fe6 100644 --- a/apps/postcards/src/features/backup/Backup.tsx +++ b/apps/postcards/src/features/backup/Backup.tsx @@ -197,7 +197,7 @@ export function Backup() { setMessage({ kind: "err", text: result.error }); return; } - if (visits.length > 0 || trips.length > 0 || stories.length > 0) { + if (hasData) { const ok = window.confirm( t("backup.confirm.replace", { curPlaces: visits.length, diff --git a/apps/postcards/src/features/guides/GuideButton.tsx b/apps/postcards/src/features/guides/GuideButton.tsx index 149709d..889cc7b 100644 --- a/apps/postcards/src/features/guides/GuideButton.tsx +++ b/apps/postcards/src/features/guides/GuideButton.tsx @@ -24,6 +24,9 @@ const KIND_GROUP: Record = { }; const GROUP_ORDER = ["explore", "understand", "phrasebook"] as const; +/** Whether the device is offline right now (guides are online-only, opt-in). */ +const isOffline = () => typeof navigator !== "undefined" && !navigator.onLine; + /** Resolve the names a place's guides are built from (common country name — * the real Wikivoyage article title, e.g. "Russia", not "Russian Federation"). */ function guideNames(place: PlaceRef) { @@ -203,7 +206,7 @@ function GuideContent({ placeName, names }: { placeName: string; names: GuideNam const tried = useRef(false); useEffect(() => { if (tried.current || summary || wpSummary || !autoLoad) return; - if (typeof navigator !== "undefined" && !navigator.onLine) return; + if (isOffline()) return; tried.current = true; void loadOverview(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -271,7 +274,7 @@ function GuideContent({ placeName, names }: { placeName: string; names: GuideNam )} {!overview && state === "empty" && (

- {typeof navigator !== "undefined" && !navigator.onLine + {isOffline() ? t("guide.emptyOffline") : t("guide.emptyOnline")}{" "}