Commit graph

121 commits

Author SHA1 Message Date
f951b05e0a fix(android): enable core library desugaring for flutter_local_notifications 2026-07-10 21:30:22 +02:00
d2b98af36d Merge branch 'claude/awesome-golick-007e7d': message unread badges + OS notifications
# Conflicts:
#	apps/app_seeds/lib/di/injector.dart
#	apps/app_seeds/lib/i18n/ast.i18n.json
#	apps/app_seeds/lib/i18n/en.i18n.json
#	apps/app_seeds/lib/i18n/es.i18n.json
#	apps/app_seeds/lib/i18n/pt.i18n.json
#	apps/app_seeds/lib/i18n/strings.g.dart
#	apps/app_seeds/lib/i18n/strings_ast.g.dart
#	apps/app_seeds/lib/i18n/strings_en.g.dart
#	apps/app_seeds/lib/i18n/strings_es.g.dart
#	apps/app_seeds/lib/i18n/strings_pt.g.dart
#	apps/app_seeds/lib/ui/chat_screen.dart
2026-07-10 21:15:59 +02:00
d026376993 feat(messages): unread badges + OS notification for private messages
Private messages (NIP-17) arrived in the foreground with no signal: no
unread count, no badge, no OS notification. Hook both into the single
InboxService.ingest choke point.

- UnreadService: per-peer "last read" in the keystore (timestamps+pubkey
  only, no message text), live count via a changes stream, active-peer
  suppression so the open chat never badges or notifies.
- UnreadBadge: reusable, RTL-aware (Flutter Badge) — on the home hamburger,
  the drawer "Chat" item, and per-conversation in the messages list.
- Mark-read on chat open (ChatScreen).
- NotificationService over flutter_local_notifications: generic
  "New message from <name>" (no message text, for privacy), payload = pubkey,
  tap opens /chat/:pubkey. No-op on web/Windows; Android POST_NOTIFICATIONS.
- i18n notifications.newMessageFrom (en/es/pt/ast).

Background/push stays out of scope (foreground-only by design).

Tests: UnreadService, NotificationService (mock plugin), InboxService hooks
(new peer notifies/counts; duplicate, own, and open-chat do not), and a
UnreadBadge widget test. dart analyze clean; commons_core green.
2026-07-10 21:12:00 +02:00
e715b129f6 feat(trust): full Duniter web-of-trust membership (params + referents)
Slice 4 of Block 2. The pure rule was already parameterised; this adds the
policy, cold-start and UI around it.

- commons_core: WotParams (sigQty/stepMax/sigValidity, Duniter defaults) +
  WebOfTrust.membersWith; npubToHex helper (NIP-19 decode) for adding roots.
- WotSettings (keystore): the active parameters, configurable — a young
  network loosens them and tightens as it grows, as Ğ1 did. Defaults Duniter.
- TrustReferents: the bootstrap 'seeds' membership is measured from — a
  bundled asset (empty until real founders are curated, no invented keys)
  unioned with referents the user adds by npub/QR. Honest cold-start.
- TrustCubit: computes the full membership verdict against referents+params
  alongside the personal circle, and exposes a TrustTier (networkMember >
  inYourCircle > vouched > unknown). Certifications issued with the active
  validity (they expire and renew, Duniter rule).
- UI: chat trust badge by tier; a 'Network of trust' screen (manage roots +
  advanced params) reached from the profile. i18n en/es/pt/ast.

Tests: WotParams/membersWith, npubToHex, TrustReferents, WotSettings, and
TrustCubit tiers/membership. Resolves the WoT-parameters decision
(open-decisions §B). Trust net stays empty/undetermined until seeded — by
design; users bootstrap their own roots.
2026-07-10 21:02:08 +02:00
f767ccd70e Merge branch 'claude/mystifying-meitner-3b8553': fix Asturian boot-splash locale 2026-07-10 20:23:38 +02:00
0cdeee50f8 fix(i18n): map Asturian to Spanish chrome in boot splash
The pre-init BootstrapSplash passed the raw device locale to its
MaterialApp, so booting under an Asturian (ast) device locale threw
the 'locale not supported by localization delegates' warning — the
Material/Cupertino delegates ship no ast. The main app already routes
ast to Spanish framework chrome via materialLocaleFor; apply the same
mapping in the splash. Drop @visibleForTesting since it's now shared
production code.
2026-07-10 20:23:03 +02:00
a109a84714 feat(identity): switch social identity, all from the one backup
Adds pseudonymous, switchable social identities derived from the SAME root
seed via an account index (NostrKeyDerivation.deriveFromSeed(seed, account)).
HKDF is one-way so accounts are unlinkable to the Ğ1 key; account 0 is the
original identity, byte-for-byte unchanged (no rotation for current users),
and every account regenerates from the single seed — so switching adds
nothing to back up.

- SocialAccountStore: keystore-backed active account + max created.
- Per-identity stores (chats, profile, name cache) namespaced by account
  scope (account 0 = legacy keys, no migration) so identities never mix.
- switchSocialAccount() re-derives the identity, re-scopes the stores and
  restarts the inbox listener; RestartWidget rebuilds the tree to pick up
  the new social singletons. DB/inventory untouched.
- Profile 'Your identities' switcher: list, create, switch (with a note
  that messages/contacts are kept separate per identity). i18n en/es/pt/ast.

Tests: account-indexed derivation (legacy 0 unchanged, accounts distinct
yet deterministic, negatives rejected); SocialAccountStore; per-identity
store scope isolation. Resolves the flagged 'change identity' decision
(open-decisions §B).

Known: switching resets navigation to home (full tree rebuild).
2026-07-10 20:22:24 +02:00
4b71859416 feat(labels): choose copies per label, prefilled from container count
Each label in the print sheet gets an editable copy count, suggested from
the lot's latest condition-check container count (3 jars → 3 labels), and
falling back to 1. Type or step the count; the total updates live and Save
is disabled at zero. On save each seed expands into that many identical
labels.

- SeedLabelEntry.suggestedCopies; labelRows reads the latest container count
- label print sheet: per-row −/number/+ stepper, live total, expand on save
- tests: repository suggestedCopies + print-sheet copy chooser
2026-07-10 20:12:36 +02:00
4d110e8082 Merge branch 'worktree-print-labels': print seed labels with QR
# Conflicts:
#	apps/app_seeds/lib/i18n/strings.g.dart
#	apps/app_seeds/lib/ui/inventory_list_screen.dart
2026-07-10 19:20:33 +02:00
3ea1bfbf55 Fix test 2026-07-10 19:14:29 +02:00
33c45bcab1 Add Windows plugin 2026-07-10 19:14:21 +02:00
efb369eb81 feat(labels): print seed labels with QR from an inventory selection
Add multi-select to the inventory and a "Print labels" flow that renders a
PDF sheet of labels (small stickers or large cards), each carrying the seed's
common name, variety, scientific name, year/origin and a tanemaki://seed QR
that holds the seed's data offline (future scan-to-import).

- SeedLabelCodec: compact, versioned tanemaki://seed URI (encode/decode)
- LabelSheetService: pure-Dart PDF grid (pdf + barcode + DejaVu), RTL-aware
- VarietyRepository.labelRows: one row per lot, locale-picked common name
- InventoryCubit selection mode; contextual app bar + label print sheet
- i18n en/es/pt (printLabels.*)

Tests: codec, PDF service (both formats + RTL), labelRows, selection cubit,
inventory widget selection.
2026-07-10 19:14:04 +02:00
a71493bfb1 Merge branch 'claude/intelligent-bardeen-0d5ee9': market detail page, filters, publish-dedup
# Conflicts:
#	apps/app_seeds/lib/i18n/strings.g.dart
#	apps/app_seeds/lib/i18n/strings_ast.g.dart
2026-07-10 19:11:59 +02:00
0213152bfb feat(market): fix publish-duplicates, add offer detail page + filters
The vecino/market view had three gaps:

- Publishing inventory duplicated the listing. discover() appended every
  offer the relay streamed, but relays legitimately resend an addressable
  NIP-99 event (stored copy + live echo on publish). Merge by
  (authorPubkeyHex, id) so each offer appears once; two authors reusing a
  lot id stay distinct.
- No product detail. Tapping a card jumped straight to chat. Add a
  read-only "product page" (MarketOfferDetailScreen) with title, mode,
  category, eco badge, coarse location, price, a "Shared by" seller
  section (profile fetched by pubkey) and the message button. Reached via
  a new /market/offer route; cards are now fully tappable. Shows only what
  the network already carries — no photos/description added to the wire.
- No filters. Add a filter bar (type, category, eco) mirroring inventory,
  each chip group shown only when a discovered offer can match it. To make
  the eco filter real, publish the organic flag on the wire: Offer.isOrganic
  -> NIP-99 'organic' tag -> OfferMapper -> ShareableLot.

Tests: commons_core organic round-trip; app-side dedup, two-author
separation, filter logic, mapper passthrough; detail-screen widget tests.
i18n keys added to en/es/pt/ast and slang regenerated.
2026-07-10 19:10:40 +02:00
09b7575df3 perf(startup): paint a splash immediately, run init off the first frame
main() awaited all of configureDependencies() (open the encrypted DB,
derive the social key, seed/scan the species catalog) before the first
runApp, so nothing painted until init finished — startup jank visible as
skipped frames. Now main() runs runApp immediately with a Bootstrap
widget that shows a native-splash-matching green splash while the heavy
work runs, then swaps in TaneApp. On failure it shows a localized error
with a retry (configureDependencies is idempotent, so retry is safe).

Adds a bootstrap i18n group (en/es/pt/ast) and a widget test for the
splash/error frame.
2026-07-10 17:34:08 +02:00
21ec836bd8 fix(chat): dedupe messages on display, not just in the store
Duplicated bubbles came from the cubit emitting every inbox event to the
screen regardless of whether it was already shown. On opening a chat the
saved message loads from history AND the live subscription hands back the
same stored gift wrap the relay re-delivers — two bubbles. (The store was
already idempotent, but the cubit ignored that for display.)

MessagesCubit now keeps a seen-set (sender+timestamp+text): a re-delivered
or already-loaded message is skipped, and pre-existing duplicates in old
saved history collapse on load too. Arrival order preserved (no resort).
Tests: re-delivered wrap shown once; history-raced-by-live shown once.
2026-07-10 17:02:40 +02:00
3dca732da9 feat(chat): app-wide inbox listener so messages arrive without opening the chat
Reception only ran inside an open ChatScreen for a known peer, so a first
message from a new peer was invisible: nothing in local storage -> nothing
in the inbox list -> you never opened the chat -> you never subscribed.

Add InboxService: one long-lived NIP-17 inbox subscription for the whole
app (foreground), persisting every incoming message to MessageStore and
firing a 'changes' signal the inbox list live-reloads on. Reconnects when
the network returns; degrades to nothing offline. Started from main when a
social identity exists.

Make MessageStore.append idempotent (dedup by sender+timestamp+text) and
serialized behind a write lock — the global listener and an open chat's own
subscription now write the same conversation concurrently and relays
re-deliver stored gift wraps on every resubscribe. Tests for both.

Known trade-offs (follow-ups): foreground-only (no push yet); each of
InboxService/ChatScreen/MarketScreen opens its own RelayPool (a shared
connection is a later optimization).
2026-07-10 16:53:03 +02:00
138027a8ef fix(nav): inventory is a spoke off the home hub — back arrow, no duplicate drawer
The inventory screen carried its own const AppDrawer() (no marketEnabled),
so opening the menu from inventory greyed out market/chat/profile — the
recurring 'phase 1' look. It was also reached via go() (route replace),
which is why it showed a hamburger instead of a back arrow like the market.

Make it coherent with the market: reach inventory via push() (from the
home card and the drawer), and drop its drawer so the AppBar shows a back
arrow returning to the home hub. The drawer now lives only on the home
screen, which always passes marketEnabled — the wrong-drawer bug class is
gone. Update the home_screen test to assert the spoke has a back arrow and
no hamburger.
2026-07-10 16:24:54 +02:00
5ab505bc94 fix(di): robust all-or-nothing init so the social layer never greys out
The idempotency guard used SocialService as its sentinel, but that was
registered mid-cascade, so a run that crashed part-way could leave a
half-built container the next run would reuse (or hit 'already
registered' on) — intermittently falling back to the inventory-only look
with market/chat/profile greyed out.

- Guard on a dedicated _DepsReady marker registered LAST, so the sentinel
  means the WHOLE container is wired, never a partial one.
- Reset a half-wired container (crashed prior run) before rebuilding,
  instead of reusing it. Safe: main aborts before runApp on a partial run.
- Make the social key derivation non-fatal: on failure the app opens on
  inventory (social hidden) rather than blanking. main tolerates an
  absent SocialService.
2026-07-10 16:14:10 +02:00
1af0282e00 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.
2026-07-10 16:12:38 +02:00
5dd0f243ce Merge branch 'main' into spike/block2-derisking
# Conflicts:
#	apps/app_seeds/lib/i18n/strings.g.dart
2026-07-10 15:48:36 +02:00
ea5ab0342d perf(species): seed the bundled catalog only once per version
Skip the ~1.5 MB catalog parse and the table scan on every launch — the
main startup cost. `seedBundledIfNeeded` re-seeds only when the recorded
version differs from `speciesCatalogVersion`, writing the version after
the seed commits so an interrupted seed retries next launch.

Adds `parseSpeciesCatalogVersion` and a test keeping the constant in sync
with the asset's `version` field.
2026-07-10 15:46:04 +02:00
c9c764e624 feat(i18n): add Asturian (asturianu) as a language
Adds Asturianu, translated in full (345 strings), plus the wiring to
make a minority language work end to end — the OS language picker is an
unreliable way to reach it, so the in-app choice must be explicit and
remembered.

- Model Asturian as the real `ast` locale (honest, international-by-design),
  not a fake `es-AST` region — slang rejects a 3-letter region anyway.
- Flutter ships no Material/Cupertino/intl localizations for `ast`, so map
  the framework chrome to Spanish (`materialLocaleFor`); the app's own text
  stays Asturian via slang. Fixes an "Invalid locale ast" crash when the
  auto-backup date was formatted with intl.
- Persist the picked language in the keystore (LocaleStore) and restore it
  at startup so it survives restarts and wins over the device locale.
- Settings: add the Asturianu tile; compare the full AppLocale so `es` and
  `ast` aren't both marked selected.

Tests: LocaleStore round-trip, locale switch + Spanish-chrome fallback,
and a regression for the intl date crash under Asturian.
2026-07-10 15:43:33 +02:00
8d1a66e7b8 feat(market): search + pull-to-refresh in the market list
Match the inventory patterns the market was missing:
- Search: OffersState gains a `query` + `visibleOffers` getter and
  OffersCubit.search() filters the already-discovered offers locally by
  summary (case-insensitive), never re-hitting the transport. The filter
  survives a refresh. Rounded search field mirrors inventory's.
- Pull-to-refresh: RefreshIndicator re-runs discovery for the current
  area (empty state included, so a swipe re-scans). Text filter kept.
- i18n: market.searchHint / market.noMatches (en/es/pt).

Note: the shared slang output (strings*.g.dart, incl. the generated
Asturian locale) is regenerated wholesale, so it also carries the
in-progress `ast` locale keys already present in the working tree.
2026-07-10 15:40:13 +02:00
5cb1c33d1b chore(i18n): regenerate strings after merging main 2026-07-10 15:27:36 +02:00
a14065db3f Merge branch 'main' into spike/block2-derisking 2026-07-10 15:27:01 +02:00
3e84e9bb3f fix(block2): make DI idempotent — stop the app falling back to 'inventory only'
Feedback: at times the social items showed greyed ('like phase 1'), only
inventory active. Cause: a hot restart / Android Activity restart re-runs main
while GetIt still holds the singletons; re-registration threw, main aborted
before rebuilding TaneApp with the social layer, leaving the pre-Block-2 tree.
configureDependencies now returns early if already configured, so re-entry
succeeds and TaneApp is always built with the social layer wired.
2026-07-10 15:24:23 +02:00
3fe5397675 feat(block2): global 'you're offline' banner (network state)
Feedback: offline, the social layer stops working with no clear reason. A slim
amber strip now shows above every screen when there's no network (connectivity_
plus), so it's obvious WHY sharing is paused — the inventory keeps working.
Wrapped once via the MaterialApp builder; connectivity check is guarded so it
no-ops where the plugin is absent (tests/unsupported platforms). i18n en/es/pt.
Analyzer clean.
2026-07-10 15:18:16 +02:00
c6b91b7666 Merge branch 'spike/block2-derisking' 2026-07-10 13:40:07 +02:00
a447149a74 fix(block2): clearer market states — 'can't reach servers' (retry) vs 'set your area'
Feedback: the market sometimes looked 'disabled'. Now the offline/empty states
are distinct and honest:
- No working relay (defaults exist, so it's a reachability issue) → 'Can't reach
  the community servers right now' + a Retry.
- Online but no area set → 'Set your area' prompt (you'd otherwise see a bare
  'nothing nearby').
- Area set + nothing found → the existing empty state.

i18n en/es/pt; offline tests updated; analyzer clean.
2026-07-10 13:36:01 +02:00
5b52298160 fix(block2): sharing config UX + discovery + labels (raw feedback batch)
From on-device feedback:
- Area is no longer shown as a raw geohash 'name'. 'Use my location' is the
  primary action; a human status line says whether your area is set (coarse,
  never exact). The geohash code + the relay servers moved under 'Advanced'
  (code field labelled 'a code like sp3e9 — not a place name'), which also
  brings back node/relay selection that had been fully hidden.
- Location denial no longer yanks you into system settings — returns quietly
  with an actionable inline message.
- Discovery no longer spins forever when nobody's sharing nearby: a timeout
  resolves 'searching' to the empty state.
- Home card 'Open market' -> 'Market' + 'Discover and share seeds nearby'
  (the word 'open' confused).

i18n en/es/pt; analyzer clean; the use-my-location test asserts the status flip.
2026-07-10 13:32:11 +02:00
a9c6030dde feat(block2): Ğ1 — publish a Ğ1 address + 'Pay in Ğ1' in chat (levels 1-2)
The optional Ğ1 (free currency) integration, per g1-integration.md.
- Profile: an optional 'Ğ1 address' field, persisted locally and published in
  your NIP-01 kind:0 metadata (separate from the Nostr key).
- commons_core: UserProfile/ProfileTransport carry a 'g1' field (round-trip
  tested).
- Chat: when the peer published a Ğ1 address, a 'Pay in Ğ1' app-bar action opens
  their wallet (url_launcher) so value moves in the wallet, not the app — copies
  the address as a fallback. No payment rails in-app (sharing-model §4.3).

NOTE (vjrj): the wallet deep-link uses the Cesium web wallet as a universal
fallback; swap the URI in chat_screen._payG1 for the Ğ1nkgo/Ğecko scheme you
prefer. Analyzer clean (both packages); profile transport test covers g1.
2026-07-10 13:06:38 +02:00
267896430f Merge branch 'spike/block2-derisking' 2026-07-10 13:04:05 +02:00
3067d9ca7f feat(block2): QR of your npub in the profile (scan to share at a fair)
Adds a QrView (painted from the pure-Dart 'barcode' matrix — already transitive
via pdf, no native plugin) and shows a QR of your npub on the profile identity
card, so you can hand your identity over with a scan. Analyzer clean.
2026-07-10 13:00:47 +02:00
e32fdc9d38 Merge branch 'spike/block2-derisking' 2026-07-10 12:57:02 +02:00
48228c0bc6 feat(block2): show contact names (resolve peers' published profiles)
Closes the profile loop — peers now appear by name, not a raw key.
- ProfileCache: keystore-backed cache of peers' published display names (works
  offline) + shortPubkey() fallback.
- Chat: the app bar shows the peer's name (cached first, then freshened from
  their kind:0 over the session); falls back to a short key.
- Inbox: each conversation shows the cached name, and missing names are resolved
  over one session and cached.
- Wired via DI + TaneApp(profileCache).

Analyzer clean; ProfileCache covered by a plain test.
2026-07-10 12:47:48 +02:00
856736c8c5 Merge branch 'spike/block2-derisking' 2026-07-10 12:42:33 +02:00
458502b12b fix(block2): back buttons on social screens + clear 'Share my seeds' button
Two UX reports:
- Market/Messages (and Profile/Chat) had no back button — they were opened with
  context.go (replaces the stack). Switched the drawer entries, home Market card,
  offer 'Message' link and inbox taps to context.push, so the AppBar shows a back
  arrow to return.
- The market's upload icon (tooltip-only) was unclear. Replaced it with a labelled
  FloatingActionButton.extended 'Share my seeds' (campaign icon); the config gear
  stays in the AppBar.

Analyzer clean.
2026-07-10 12:40:08 +02:00
30246f4292 feat(block2): profiles — your identity card + name/about (NIP-01 kind:0)
Drawer 'Profile' is now live.
- commons_core: ProfileTransport + UserProfile + NostrProfileTransport (kind:0
  publish/fetch), exposed as SocialSession.profile. Round-trip tested against the
  in-process relay (fast dart test).
- app: ProfileStore (local name/about, keystore) + ProfileScreen — shows your
  shareable npub with copy, edits display name + 'about', saves locally and (when
  online) publishes kind:0 so peers can recognise you instead of a raw key.
- Drawer 'Profile' -> /profile (gated like Market/Chat).
- i18n en/es/pt. ProfileStore + profile transport covered by plain/dart tests.

Analyzer clean (both packages).
2026-07-10 12:37:07 +02:00
432acded29 Merge branch 'spike/block2-derisking' 2026-07-10 12:31:01 +02:00
abc2d37402 feat(block2): messages inbox + drawer 'Chat' link (was unreachable)
Chat was only reachable via an offer's 'Message' button, and the drawer 'Chat'
sat on 'coming soon' — so messaging/trust were effectively hidden.
- MessageStore keeps a conversation index (the keystore has no key enumeration)
  and exposes conversations() — peers newest-active-first with their last line.
- ChatListScreen: the messages inbox (tap a conversation → /chat/:pubkey).
- Drawer 'Chat' is now a live destination -> /messages (gated like Market).
- i18n en/es/pt. conversations() covered by a plain test (no widget hang).

Analyzer clean.
2026-07-10 12:26:33 +02:00
867bf38c5a Merge branch 'spike/block2-derisking' 2026-07-10 12:20:52 +02:00
7bf80d2031 feat(block2): persist chat history (messages survive leaving the chat)
Messages were in-memory only — gone on leaving the conversation.
- MessageStore: keystore-backed (no plaintext), a capped per-peer JSON list
  (last 200). Simple; the encrypted Drift DB is the eventual home at scale.
- MessagesCubit: loads saved history on start (subscribes FIRST, then loads, so
  a message arriving during the load isn't dropped) and persists every sent and
  received message. Wired via DI + TaneApp(messageStore).

Tests (plain 'test', no hang risk): MessageStore round-trip/per-peer/cap, and a
cubit test that reopens a fresh cubit and sees the saved conversation. Analyzer
clean; run 'flutter test' locally to confirm.
2026-07-10 12:20:25 +02:00
6d8c6cf7e1 test(block2): drop the market discover widget tests that hang the runner
Even with fixed pumps, driving discovery through testWidgets hangs here (the
transient 'searching' spinner never lets pumpAndSettle settle). Discovery logic
is already covered by offers_cubit_test (plain test + pumpEventQueue). Keep only
the offline/setup widget tests, which have no live stream. Reported by the user.
2026-07-10 12:14:53 +02:00
f3a7ad198d feat(block2): 'in your circle' trust — friend-of-a-friend, not a raw count
A network-wide 'N people vouch' count invites spam and means little. Now the
trust banner computes whether the peer is within YOUR circle — you vouch for
them, or someone you vouch for does (friend-of-a-friend) — via the pure
WebOfTrust rule (seeds={you}, threshold 1, distance 2). When known, the banner
turns green with 'In your circle' (else it falls back to the count).

The full member policy (threshold/distance/bootstrap) stays open (network-trust
§2); this is a deliberately loose 'people near you' heuristic.

Analyzer clean; trust cubit test adds a friend-of-a-friend vs stranger case
(run flutter test locally to confirm — the widget suite is too slow to run here).
2026-07-10 12:10:06 +02:00
320b9b6dba Merge branch 'spike/block2-derisking' 2026-07-10 12:07:11 +02:00
46b5537a11 test(block2): stop market discover tests hanging on the searching spinner
The 'searching' state shows a CircularProgressIndicator, so pumpAndSettle never
settled and the widget test hung ~10 min. Pump a couple of fixed frames instead
(the buffered offers deliver on a microtask), and close the fake discover stream
so it's finite. Reported by the user.
2026-07-10 12:06:19 +02:00
c18314d8c5 Merge branch 'spike/block2-derisking' 2026-07-10 11:56:56 +02:00
c513a6ce4b fix(block2): hide server config, enable drawer Market, badge my own offers
Three feedback fixes:
- Servers are fully hidden now — relays are managed automatically (default
  community servers; the pool skips dead ones). Sharing setup shows only 'your
  area'; no jargon-y URL field. (3 default relays for redundancy.)
- Drawer 'Market' is a live destination (was stuck on 'coming soon'), gated on
  the social layer being wired like the home card.
- My own listings in the market carry a 'You' badge (and no 'message yourself'
  button), so it's clear which offers are mine.

Analyzer clean. Note: could not run the flutter widget suite here (10+ min
compile after the geolocator native dep in this sandbox) — run 'flutter test'
locally to confirm; the You-badge test was simplified to be robust.
2026-07-10 11:56:55 +02:00
49a43722eb Merge branch 'spike/block2-derisking' 2026-07-10 11:24:40 +02:00