Relaxes the view-center clamp from ±80° to ±89°. Previously opening or
tapping a geohash above 80°N/S clamped the fly-in and syncSelection()
encoded the clamped center, silently returning a different cell than
the user picked. Verified with p2 (87°N) and p6 (85°S) cells: the view
now centers on the requested cell and selects it correctly.
Adds public-domain Natural Earth vector overlays in the established
terminal style, bundled as compact assets (works fully off-grid):
- world_borders.geojson (56KB): admin-0 boundary lines, stroked as
front-facing horizon-clipped runs in a muted theme color
- world_cities.geojson (99KB, 1251 populated places): zoom-tiered by
scale rank - capitals as accent dots, other cities as muted dots;
name labels in Geist Mono fade in from regional zoom levels to keep
the whole-globe view uncluttered
Reworks the geohash picker as a fully offline, native Compose globe:
- Orthographic 3D Earth rendered on a Canvas: starfield, atmosphere,
shaded ocean, graticule and vector continents from bundled public-domain
Natural Earth 110m data (world_land.geojson, 81KB)
- Geohash cells projected onto the sphere with theme-aware dark styling,
Geist Mono labels, level + coverage readout and pulsing crosshair
- Gestures: drag to spin with inertial fling, pinch to zoom (precision
follows zoom), tap to focus, double-tap zoom, cinematic fly-in intro,
haptic tick on cell change
- Removes the WebView/Leaflet/CDN dependency; picker now works off-grid
and matches the app theme in dark and light mode
Rendering notes: horizon-clipped polygon fills (front-run splitting with
limb arcs) and viewport clipping of all path geometry to avoid Skia
precision loss for coordinates beyond 32767px at high zoom.
- Extract shared PeerAvatar (initial circle + lower-right transport badge)
from the conversation row and use it in the mesh peer list and the
geohash/Nostr people list
- Remove the favorite toggle button from the peer list; favorite state is
now a small star badge on the avatar (filled = we favorited, outline =
they favorited us), so favoriting only happens from the private chat
- Show the unread-count badge on peer rows, matching conversation rows
A bare `test -n` does not abort in a shell without set -e, so a failed
mktemp would let `--out ""` resolve to the repository root and write
private evidence there. Guard with an explicit exit instead.
The previous recheck narrowed the race without closing it. Testing
hotspotHold outside lifecycleLock left this interleaving:
1. the recheck passes, hold not yet set
2. holdForHotspot() sets the flag and calls stop(), which takes the
lock, finds nothing published, and returns having stopped nothing
3. this block takes the lock and publishes the service anyway
Aware ends up running while the hotspot believes it owns the radio,
which is the original failure.
The hold is now tested inside the same synchronized block that
publishes, so the check and the publication are one step against
stop(). Ordering holds because holdForHotspot() sets the flag before
calling stop(): either the publisher observes the flag and abandons,
or stop() observes the published service and tears it down. There is
no interleaving that leaves a service published with the hold set.
stopServices() stays outside the lock since it can block.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>