The green lots are the map's only paid category ("City lots - Paid hourly or
permit"); paying for them goes through ParkSmarter, so they are no use to
someone browsing without an account. Every other category is free street
parking with a posted time limit and needs nothing.
Gated by category rather than by an id list. Two lots were named (off Oak St
and off N 3rd Ave) and then the beach ones, which together is every green lot
on the map — and an id list would silently break the next time the map is
regenerated from a new PDF, since ids are positional.
- Paid lots are filtered out of the overlay when signed out, so they are
neither drawn nor tappable.
- "Park here" still detects them, so standing in one explains that it needs an
account instead of reporting no parking nearby.
- The area screen guards too, in case one is reached with a stale nav param.
- Server carries an optional per-area requiresAccount override for a lot that
turns out to take payment another way. Null means "use the category
default", so an unset value can't be confused with an explicit false.
The new column needs a real migration: CREATE TABLE IF NOT EXISTS does not add
a column to a table that already exists, so an already-deployed server would
have kept the old schema. Covered by a test that builds the pre-migration
table and then opens it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A city-map timer started without signing in did not appear under Sessions.
The tab returned "Sign in to see your sessions" before rendering anything, so
a local session could never show; and even signed in it only ever listed
ParkSmarter's sessions. A local timer is the one kind that has no server copy,
which made it the one kind the screen could not display.
- Sessions now leads with "Tracking on this phone": the live countdown with
+1 hour and End, working with no account and no network, because that is the
only place the session exists.
- Recent local sessions are kept in a small on-device history (50 max). Without
it a local session vanished the instant it ended — there is no server to ask.
Paid ParkSmarter sessions are excluded so they don't appear twice.
- The ParkSmarter half is layered on top when signed in and can fail
independently: offline it reports that and keeps the local half visible,
rather than the whole tab going blank. It also no longer leaves an unhandled
rejection when the fetch throws (it had try/finally but no catch).
- The card's second button follows the notification's rule: +1 hour for a local
timer, Extend -> purchase screen for a bought session, since only one of those
can honestly add time.
History is written in endActiveParking() before the record is dropped, which
also covers expiry — syncActiveParking() routes a lapsed session through the
same call.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the City of Sandpoint's printed "Downtown & Waterfront Public Parking"
map as a georeferenced overlay, and lets you track your time on any of its
areas without ever touching the ParkSmarter/IPS API.
Georeferencing (tools/citymap/)
- The PDF carries no geo metadata, so the page->WebMercator affine is
recovered by fitting the drawing to OSM street centrelines.
- pdftocairo writes stroked street segments with per-path matrix()
transforms in local coords while filled lots are absolute; both are
handled. The five legend swatches share the real geometry's colours and
are identified by stroke-width and position, then dropped.
- 49 areas, fitted to RMS 4.1 m (X) / 3.5 m (Y). On-street segments land a
mean 4.0 m from the nearest OSM road. sp-039/040 sit further out because
they are angled bays along the old rail corridor, on no named road at all.
- Sandpoint's grid jogs 38 m between N 2nd Ave and S 2nd Ave; the page shows
the same jog at the fitted scale, which independently confirms the fit.
App
- Map tab: "City map" layer in the legend's colours, tappable.
- "Park here" pins the car from GPS and auto-detects the containing area
(40 m snap). With no fix it asks you to tap the spot instead, so the pin
never depends on GPS working.
- The pin lives in its own storage key, not inside the session: pinning the
car without starting a timer must survive backing out of the screen.
- Durations cap at the posted limit — a 2-hour space is not offered a
4-hour timer. Lots and no-limit spots get the long options.
- Reuses the existing foreground-service countdown. The second notification
button reads "+1 hr" for a city area rather than "Extend": there is
nothing to buy, so it edits the local timer and says so.
- Account -> Align city map: nudge/scale/rotate the whole overlay against a
live GPS fix. Save-on-phone needs no admin token, since the person who can
see the misalignment is the one standing on the street.
Server
- parking_areas + map_overlay tables, public read, admin replace-all. The
areas come from one source document, so replacement is wholesale rather
than an upsert.
Dropped geometryCenter from the geo module: on the real data it returns a
point in the water for the crescent City Beach lot and mid-block for
L-shaped runs. Nothing used it.
Tests: 8 geometry tests in app/, 5 area/overlay tests in server/.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ongoing countdown now appears whenever a car is parked, paid or free,
and sticks around the way ntfy's does on GrapheneOS.
Why it wasn't showing at all on a paid park: StartSessionScreen carried its
own stricter copy of parseApiTime that demanded MM-DD-YYYY + AM/PM. The
dual-format fix from deb78bf only landed in sessionStatus.ts, so when the
API returned the other format the parser returned null and the notification
was simply never posted. There is now one parser (api/parseTime.ts), and the
failure logs instead of going silent.
Persistence: a plain notify() was never enough — Android 14+ lets the user
swipe an ongoing notification away, and nothing brought it back after a
reboot. BbpSessionService is a real foreground service (type specialUse) that
owns the notification, plus a BOOT_COMPLETED receiver to restore it; specialUse
is one of the types Android 14/15 still allow to start from BOOT_COMPLETED.
The service's life is exactly the session's life: it stops itself, removing
the notification, on End, at expiry, or when there's no session to show.
Buttons: paid sessions previously got no actions at all (only free check-ins
did). Both now get End and Extend/Pay. End stops tracking — honest about the
fact that ParkSmarter has no stop-session endpoint, so bought time keeps
running at the meter. Extend opens the purchase screen for that exact zone.
The active session (with its Zone) is persisted locally, so the countdown and
Extend survive reboot, offline, and Anonymous Mode instead of depending on a
round-trip. sessionStatus.ts + features/checkin collapse into one owner,
features/session/activeParking — one record, one notification.
Verified: :bbp-notify:compileDebugKotlin passes, and the merged app manifest
carries the service (specialUse + FGS subtype property), both receivers, and
the FOREGROUND_SERVICE/SPECIAL_USE/RECEIVE_BOOT_COMPLETED permissions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/OFFICIAL_APP_PRIVACY.md — readable-by-anyone comparison based on the official
app's manifest + decompiled bundle (v4.4.0): auto-sends GPS to IPS on map open;
bundles Segment/Amplitude/Firebase/Sentry + ad-ID + install-referrer; can send
IMEI; but NO background location. Contrasts with BigBrainParking (explicit-only
location, zero analytics). Confidence levels labeled. Linked from README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Location privacy (hard invariant): the device GPS is sent to the API ONLY when
the user taps "My location" and searches.
- Map opens on your LAST session's parking lot (the meter's coordinate from
history), not your GPS.
- "Last" -> "Last lot": centers on the last session's lot and searches there
(~couple-mile view) — no GPS.
- "Search this area" falls back to the tracked viewport center, never the GPS fix.
About page: lead with a BigBrainParking open-source blurb + repo link (was showing
ParkSmarter's own About text first); privacy wording updated to match; drop the
now-unused getAbout fetch.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- docs/PARKSMARTER_API.md: narrative guides (bootstrap/auth, finding parking,
estimates, initiating a transaction, sessions/receipts, account) + per-endpoint
reference + a behaviors/gotchas table (200-on-error, stale-token empty body,
multi-estimate fallback, free windows, flat-rate zones, no free check-in).
- client README: link the guide; promote PastSession/ParkingReceipt to CONFIRMED
(ActiveSession still pending — no active session at capture time).
- Main README: mark "Start a paid session" as working (confirmed live end-to-end).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Session-expiry reminders are purely on-device (AlarmManager); added an Android
notification channel and a "Send a test reminder" button (Account > Notifications)
to verify delivery without a real session
- Strip all FCM/bridge talk from unifiedPush.ts + README; UnifiedPush(ntfy) is now
an optional no-op stub for hypothetical future server-initiated messages only
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Forgejo keeps obtainium:// URLs in markdown links (verified via the render API) but
not as bare autolinks — so use [text](obtainium://…) syntax in INSTALL.md and README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Fix withReleaseSigning: RN template's unconditional debug signingConfig was
overriding the release key. Now uses the release key when BBP_UPLOAD_* is set
(verified: APK signed CN=BigBrainParking), else debug.
- INSTALL.md: dead-simple Obtainium setup (one-tap obtainium:// link + manual
Forgejo-source steps). README links it.
- scripts/publish-release.sh: create a Forgejo release + upload the signed APK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reverse-engineered ParkSmarter API client (TypeScript, live-verified) plus a
de-Googled Expo/React Native app for GrapheneOS: MapLibre meter map with GPS,
VisionCamera QR kiosk scanning with save/share, local session-expiry reminders,
UnifiedPush wiring, and Gitea CI to publish signed APKs for Obtainium.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>