Commit Graph
3 Commits
Author SHA1 Message Date
jackandGitHub 820c933958 Harden PTT audio and the 1.7.1 release (#1423)
Centralize PTT and voice audio-session ownership, harden courier/bridge/outbox delivery and recovery, correct location and delivery-state races, add privacy/release metadata, and ship reproducible universal Arti slices with Release CI coverage.

Validated by the full iOS suite, repeated audio/fragment/performance regressions, BitFoundation tests, strict lint and dead-code analysis, universal iOS Release builds, and iOS/macOS archives.
2026-07-10 14:04:09 +02:00
304460ee83 Nearby notes: tap-to-reveal (consent-gate the location subscription) + subscription hygiene (#1422)
* Nearby notes: tap-to-reveal before any relay REQ, plus shared subscription pool

The nearby-notes counter used to open a live building-precision (precision-8)
geohash REQ to the closest geo relays whenever the mesh public timeline was
visible — a passive location side-channel with no opt-in. Now nothing
subscribes until one explicit act reveals the counter for the session: tapping
the new "check for notes left here" line on the empty mesh timeline (static,
no network), opening the notices sheet's geo tab, or a successful /drop. The
app-info setting stays the default-ON kill switch and still gates /drop.

Subscription hygiene alongside:
- LocationNotesManager.deinit now unsubscribes its live REQ (hopping to the
  main actor like the timer teardown) instead of only invalidating timers.
- New refcounted LocationNotesPool dedupes the counter's and the notices
  sheet's identical 9-cell kind-1 REQs into one shared manager per geohash;
  both callers release-and-reacquire instead of retargeting in place, and the
  sheet releases its ref on dismissal.

The reveal affordance is localized across all 29 catalog locales, and new
NearbyNotesCounterTests cover the no-REQ-before-reveal contract, the 9-cell
filter, NIP-40 expiry handling, single unsubscribe on deactivate, and pool
refcounting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Review fixes: permission-gate the hint, exclude building cell from pre-reveal sampling, explicit-act reveal only

Fixes the verified review findings on the tap-to-reveal PR:

- Permission dead-end: the "check for notes left here" hint now renders
  only when location permission is already authorized (it never prompts).
  Previously a location-denied install could tap it, flip the sticky
  revealed flag, and get nothing for the rest of the session because
  retarget() guards on authorization. Predicate lives on
  NearbyNotesCounter.offersRevealHint(permissionState:) and is reactive
  to the published permission state.

- Building-cell sampling: background geohash sampling subscribed
  geo-sample-<gh> for every regional level including the precision-8
  building cell, pre-reveal — contradicting the claim that nothing
  building-precision hits a relay before the explicit act.
  GeoChannelCoordinator now excludes the building level until
  NearbyNotesCounter.revealed (injectable publisher for tests); coarser
  levels keep the nearby-conversation hint and participant counts
  working, and bookmarks stay exempt (bookmarking is explicit).

- Implicit reveal: opening the notices sheet no longer reveals — the
  sheet auto-lands on the geo tab whenever a location channel is
  selected, so browsing a remote geohash and opening notices revealed
  the LOCAL building subscription. reveal() now fires only on the
  person actively picking the geo segment, and only when the sheet has
  a geo scope (the empty-mesh hint tap and /drop stay as before).

- Subscription hygiene: switching the sheet geo → mesh releases the
  pooled notes manager (the REQ was left streaming behind the mesh
  board); switching back re-acquires from the pool. The dismissal
  release stays balanced — liveGeoManager is nil after the tab-switch
  release.

- VoiceOver: the hint button exposes the plain localized action text
  instead of the decorated "* 📍 … *" label.

- Removed LocationNotesManager.setGeohash: zero callers, and calling it
  on a pooled instance would corrupt the pool's keying and refcounts.

New tests: hint permission gate, explicit-geo-tab reveal contract,
building-cell sampling exclusion before/after reveal, pool
release/re-acquire round trip. Full suite (1467) green; iOS simulator
build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Deflake peer-snapshot binding waits: longTimeout for the multi-hop pipeline

CI failed once on initialization_bindsPeerSnapshotsIntoAllPeers: the
snapshot -> allPeers binding crosses the mock transport's unstructured
Task, UnifiedPeerService.updatePeers, a receive(on: main), and another
Task { @MainActor } in bindPeerService — all contending with every
parallel worker. On the failing runner the whole suite took 10.1s
(usually ~4.4s locally), so the positive 5s defaultTimeout wait lost
the race. That's exactly the case TestConstants.longTimeout documents;
passing runs return as soon as the condition holds and never pay it.

Not caused by the tap-to-reveal changes: nothing on that pipeline was
touched, and 20 full parallel-suite loops each on the branch and on
origin/main reproduce zero failures locally. The two sibling waits on
the same pipeline in this file get the same timeout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 16:47:08 +02:00
74b8a47d02 Empty-mesh liveliness: nearby conversations, echoes, wave, dead drops, radar (#1409)
* Empty-mesh liveliness: nearby conversations, echoes, wave action, dead drops, radar

The empty mesh timeline was a dead end: a grey zero and "nobody in range
yet". This turns it into a live surface and gives the app pull when the
mesh wakes up:

- Nearest conversation: background geohash sampling now tracks actual
  chat messages (not just presence) per regional channel; the empty state
  surfaces the busiest nearby conversation with a preview, one tap to
  join (GeohashChatActivityTracker, fed from GeoPresenceTracker).
- Echoes: the carried 6h store-and-forward window renders as dimmed
  "heard here earlier" rows at launch (new Transport
  collectArchivedPublicMessages -> GossipSyncManager snapshot, decoded
  with signature-derived nicknames; content-identity dedup guards
  against re-synced duplicates).
- Wave: the "bitchatters nearby" notification gains a "wave" quick
  action that broadcasts a mesh 👋 straight from the notification, even
  backgrounded (first UNNotificationCategory in the app).
- Dead drops: /drop pins a note to the current building geohash as a
  kind-1 location note with a 24h NIP-40 expiry; expired notes are now
  dropped client-side at ingest; the notices sheet shows "fades in Xh";
  a "location notes" toggle plus location-permission controls live in
  app info (also fixes the duplicated Voice section).
- Radar: an ambient sonar animation shows the radio scanning, with a
  privacy-safe daily tally ("N devices passed within range today" via
  salted per-day hashes) and a "notes left here" hint that opens the
  notices geo tab.

All new user-facing strings ship in all 29 locales. 1403 tests green,
including new suites for the activity tracker, sightings tally, and
note expiry/drop publishing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Review round: app-info polish, urgent/expiry parity, centered radar, pin fill, Codex P2s

- App info: LOCATION header uppercased like sibling sections; location
  notes and live voice descriptions shortened (29 locales); redundant
  "location access granted" line removed.
- Notices parity: urgent + expiry controls now show on the geo tab too;
  the bridged Nostr note carries ["t","urgent"] and NIP-40 so relay-side
  readers see both; urgent parsed back from incoming notes.
- Radar moved from the top of the empty state to the center of the chat
  area, below the help text (empty state fills the visible height).
- Header pin fills whenever the scope has notices (was: only unseen),
  and Nostr-only nearby notes now light it too.
- Codex P2 fixes: notification completion deferred until the wave action
  is handled (background suspension dropped the send); NIP-40 notes now
  prune on a timer when they expire while displayed; the location-notes
  kill switch retargets the nearby-notes counter immediately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Hide macOS segmented picker's built-in label in notices composer (duplicate 'expires in')

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Geo notes: permanent (∞) expiry default, urgent stays mesh-only

- Geo expiry picker gains ∞ as the default: a permanent note posts as a
  pure relay note (no NIP-40 tag, no mesh-board copy — a board copy must
  fade within days, contradicting the ∞ the user picked). 1/3/7d keep
  the board + bridged-note path with NIP-40.
- The notes manager is now owned by the notices sheet (not the list) so
  the composer local-echoes ∞ notes into the list; it revives via
  refresh() after a tab-switch cancel, and its expiry-prune timer
  survives cancel (weak self, dies with the instance).
- Urgent toggle returns to mesh-only per review — notes are ambient;
  the read-side urgent-tag parse stays so tagged notes still render.
- macOS: hide the segmented picker's built-in label (duplicate
  "expires in").

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Clearing the mesh timeline dismisses echoes for good; tighter divider copy

Triple-tap /clear emptied the timeline but the next launch re-seeded
"heard here earlier" from the persisted archive. A MeshEchoSettings
watermark now records the clear; only messages heard after it come back
(the archive itself still carries everything for peers' sync). The
echo dedup keys reset with it, and panic wipe drops the watermark.

Divider copy tightened to "heard here earlier · last 6h" (29 locales).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Echoes visual polish: tinted history block, radar-captioned tally, ambient footer

Device-test feedback on the echoes screen:
- Archived echoes now sit on a subtle tinted background (secondary at
  8%) in addition to the dim, so "heard here earlier" reads as one
  distinct block; the divider carries the echo ID prefix to join it.
- "N devices passed within range today" moves out of the narration
  lines to sit centered under the radar as its caption.
- When the timeline holds only echoes/system lines, a compact ambient
  footer (small radar + tally + live hints) renders below the history
  instead of the whole ambient layer vanishing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Notes strip persists above the mesh chat; leaner empty-state narration

- The 📍 "notes left here" line was empty-state-only, so starting a
  conversation hid it. It is now a tappable strip pinned above the mesh
  timeline whenever unexpired notes exist at this place (opens the
  notices geo tab); the nearby-notes counter runs for the whole mesh
  timeline, not just the empty state.
- Empty state narration drops "nobody in range yet..." (the radar and
  the sightings caption already say it) and the nearby-conversation
  hint moves below the help line instead of splitting the narration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Radar means searching: hide the sweep once mesh peers are connected or reachable

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 13:24:44 +02:00