Commit graph

17 commits

Author SHA1 Message Date
4217e88338 v0.6.3: hide the paid city lots from users without a ParkSmarter account
All checks were successful
build-apk / build (push) Successful in 9m54s
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>
2026-08-13 06:02:23 +00:00
7fc92d24e4 v0.6.1: stop Anonymous Mode bouncing to sign-in on every foreground
All checks were successful
build-apk / build (push) Successful in 9m55s
Parking without an account showed "Your session expired — please sign in
again." repeatedly and kicked the user back to the login screen.

The 401 hook on the API client is global: it fires on every unauthorized
response whether or not the caller caught the error. Anonymous Mode has no
token, so any ParkSmarter call 401s, and two of them run automatically —
discoverPaidSession() on open and every foreground, and the map's
lastSessionLot() on every mount. Each one flipped the status to signedOut;
re-entering anonymous mode ran them again, hence "over and over".

Both call sites already caught their errors, which is why this hid: the
bounce came from the client hook, not from the catch.

- A 401 in Anonymous Mode is no longer treated as a session expiry. There is
  no session to expire, and bouncing to sign-in makes the mode pointless.
  This also covers the Sessions/Favorites/Scan tabs, which 401 the same way.
- authBus carries the mode to the non-React modules that need it.
- discoverPaidSession() and lastSessionLot() are skipped entirely when
  anonymous rather than fired and discarded.
- "Last lot" now says it needs an account instead of silently doing nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 04:19:12 +00:00
148e1635d3 v0.6.0: city parking-map overlay + local time tracking, no IPS API
All checks were successful
build-apk / build (push) Successful in 10m38s
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>
2026-08-13 03:28:18 +00:00
4a5660e7e1 v0.4.0: Anonymous Mode + zone-location mirror; free check-in on any zone
Some checks failed
build-apk / build (push) Failing after 1h56m52s
Anonymous Mode — "Park without signing in" on the login screen (with a popup of
what works vs needs a login). Anonymous users browse parking areas from our
mirror, see labels, and start free check-in timers; paying, sessions, and
account screens prompt to sign in. AuthContext gains an 'anonymous' status +
enterAnonymous/requireLogin.

Zone mirror — server gains a `zones` table + public GET /api/zones and admin
POST /api/zones/sync. Signed-in admins push the zones they pull (authed) from
ParkSmarter after each map search, so anonymous users can read areas without a
ParkSmarter login. Map/Scan read the mirror when anonymous.

Also: the free "Check in" button is now always available with a 2h/3h/4h picker
(no longer gated on a prior label) — fixes "couldn't start a timer on a free
zone". CORS probe confirmed ParkSmarter allows any origin but only Content-Type,
so a future PWA can't auth to it — the mirror is what makes anonymous browsing
(and a PWA) possible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-03 21:38:32 +00:00
eeaf09ea0e app: zone-labels client + admin labeling UX (Phase B) + default map = downtown Sandpoint
- zoneLabels client (cached fetch wrapper) hitting bigbrainparking.mowden.top;
  adminStore keeps the admin password in secure-store.
- Admin screen (verify + save password) reachable from Account → Admin.
- MeterDetail shows a label badge and, for admins, 2h/3h/4h/Pay-now/Clear
  labeling controls that PUT/DELETE to the API.
- app.json extra.zoneLabelsApiUrl.
- Map default view is now downtown Sandpoint (48.2766,-116.5533 @ z14) instead
  of the continental-US fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-24 18:18:35 +00:00
0a6ca53c04 v0.2.0: never auto-send GPS; "Last lot" uses last session; About page rework
All checks were successful
build-apk / build (push) Successful in 11m47s
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>
2026-07-13 19:29:13 -07:00
a566f41d66 Remove [CAM] debug logging; default HTTP request logging off
Camera recenter-on-search confirmed fixed (view-lock). Strip the temporary trace
logs; keep viewport tracking (needed for the fix). HTTP request logging now defaults
off — still toggleable in Account > Log requests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:27:14 -07:00
7321453337 Map: pin current view before 'Search this area' to defeat camera stop-revert
The native MapLibre camera re-applies its last setCamera() stop on the search
re-render (confirmed via trace: revert to prior target with userInteraction=false).
Track the live viewport and, before searching the current area, setCamera() to the
current center (0ms) so the re-applied stop is a no-op instead of a jump-back.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:22:47 -07:00
a4150555ae Fix map recenter (root cause), login logo/theming, policy pricing display
- ROOT CAUSE: MapLibre Camera re-applies its last setCamera() stop on every
  re-render, so a search (setZones) snapped the map back to the last programmatic
  target. Memoize the Camera + UserLocation elements so search re-renders never
  touch them; only the marker ShapeSource updates. (Confirmed via [CAM] trace.)
- Login: brain-car logo + full theming so text is readable in light/dark
- Meter policies: negative Rate is a sentinel (RateType No Parking=-1 / Free=-3),
  not a price; show DisplayString + colored type dot, real $/hr only when positive.
  Fix zone rate "$ 4.00" formatting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:14:23 -07:00
648acb95cb Meter screen: theme it + fix rate/max-time/policy display; add camera trace logs
- MeterDetail now themed (fixes unreadable black headings in dark mode)
- Rate no longer double-'$'; MaxTime 0 -> "Unlimited"; policies show a clean
  title + time window and hide sentinel (<=0) rates
- Temporary [CAM] logging on the map to trace the persistent recenter-on-search

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:05:42 -07:00
3837200d89 Map: render meters as a GL CircleLayer instead of MarkerView
MarkerView (React overlays repositioned per frame) caused the pins to float and
the camera to snap back to the user on pan/zoom/tap. Meters are now a ShapeSource
+ CircleLayer (GPU-drawn, coordinate-anchored); tapping a circle opens its parking
flow via the source's native onPress. No React overlays = no floating, no camera thrash.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:46:10 -07:00
b0c343ab20 Map: pin markers -> parking flow; stop native user-location camera tracking
- Markers are now compact colored pins (no long label bubbles); tapping one opens
  the meter/parking flow for that zone
- UserLocation renderMode "native" -> "normal": the native location component was
  tracking the camera back to the user on re-render / marker tap. Normal mode draws
  the dot in the JS layer with no camera coupling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:38:39 -07:00
f28bc4dd93 Map: disable rotation gesture
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:33:15 -07:00
fb3d1cdf3a Detect failed logins; fix map camera recenter, marker contrast, dark map tiles
- Client: server returns 200 + Status:"Error" on bad credentials; loginWith* now
  throw LoginError with the server message instead of faking a signed-in state
- Map: uncontrolled camera positioned once on load + explicit actions only, so
  Search/marker-tap/re-renders no longer snap back to the user's location
- Markers: always-dark high-contrast bubble + zone-colored dot (some zones report
  a white BackgroundColor -> was white-on-white)
- Dark mode now switches map tiles to CARTO dark-matter

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:27:41 -07:00
65e9118806 401 auto re-login, My Location via native GPS, edge-to-edge, brain-car icon
- Client: onUnauthorized hook; 401 clears the token and (in-app) bounces to sign-in
- Map: "My location" uses the native UserLocation fix + flyTo; safe-area insets on
  the floating status bar / controls
- Enable edgeToEdgeEnabled (Android 15 forces it) so headers/back-arrow sit correctly
- App icon: brain-car launcher + adaptive icon

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:58:23 -07:00
b31cb78592 Map: search the viewport, add My Location + status feedback; tab bar icons
- "Search this area" now searches the map's current center (getCenter), so it
  works with no GPS fix — pan anywhere and search. Fixes "search here does nothing".
- "My location" recenters the camera on the GPS fix and searches there.
- Status bar shows real feedback (N meters / none found / errors) instead of silent.
- Add bottom tab icons via @expo/vector-icons (map/qr/star/time).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:35:46 -07:00
1dede50995 Initial commit: parksmarter-client + BigBrainParking app
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>
2026-07-06 08:33:47 -07:00