fix(market): search your zone by a coarse prefix, not the exact cell

Discovery searched the full 5-char geohash (~2.4 km) with an exact Nostr tag
match, so two devices in adjacent cells never found each other even though
publish emits the full NIP-52 prefix ladder. Search now coarsens the area to a
configurable precision (SocialSettings.searchPrecision, default 4 ~20 km) via
searchPrefix(), with a human-worded 'how far' selector (Very close / Around
here / My region -> 5/4/3) in the market setup sheet.

Also: surface a clear 'couldn't reach the servers' message when no relay
accepts a share (instead of 'shared 0'), and add wss://relay.comunes.org as the
first default relay (reliable community home; public relays are backup).

Tests: settings precision (default/clamp/roundtrip), searchPrefix helper,
neighbour-cell discovery regression (offers_cubit + commons_core nostr
transports), and the range-selector widget test.
This commit is contained in:
vjrj 2026-07-10 16:12:38 +02:00
parent 1fb8e37536
commit 97b9223cb2
16 changed files with 268 additions and 10 deletions

View file

@ -16,11 +16,24 @@ class SocialSettings {
static const _areaKey = 'tane.social.area_geohash';
static const _relaysKey = 'tane.social.relays';
static const _searchPrecisionKey = 'tane.social.search_precision';
/// How wide "your zone" searches, as a geohash prefix length: 5 ±2.4 km
/// ("very close"), 4 ±20 km ("around here"), 3 ±78 km ("my region").
/// Publishing always emits the full prefix ladder, so a coarser search still
/// matches finer offers the default is deliberately wide so a still-sparse
/// network shows something.
static const int minSearchPrecision = 3;
static const int maxSearchPrecision = 5;
static const int defaultSearchPrecision = 4;
/// Community servers used automatically so sharing works from the first
/// launch. The relay pool skips any that are unreachable, so a dead one never
/// breaks the market; the user never has to know these exist.
/// breaks the market; the user never has to know these exist. The Comunes
/// relay comes first as the reliable, non-commercial home; the public ones
/// are backup.
static const List<String> defaultRelays = [
'wss://relay.comunes.org',
'wss://nos.lol',
'wss://relay.damus.io',
'wss://relay.primal.net',
@ -47,6 +60,23 @@ class SocialSettings {
urls.map((u) => u.trim()).where((u) => u.isNotEmpty).join('\n'),
);
/// How wide to search a geohash prefix length in [minSearchPrecision,
/// maxSearchPrecision]. Defaults (and falls back on any garbage) to
/// [defaultSearchPrecision].
Future<int> searchPrecision() async {
final raw = await _store.read(_searchPrecisionKey);
final value = int.tryParse(raw ?? '');
if (value == null) return defaultSearchPrecision;
return _clampPrecision(value);
}
Future<void> setSearchPrecision(int precision) =>
_store.write(_searchPrecisionKey, '${_clampPrecision(precision)}');
int _clampPrecision(int p) => p < minSearchPrecision
? minSearchPrecision
: (p > maxSearchPrecision ? maxSearchPrecision : p);
/// Whether the social layer has enough config to attempt going online.
Future<bool> get isConfigured async =>
(await relayUrls()).isNotEmpty && (await areaGeohash()).isNotEmpty;