Make peer lists accessible and actionable; block mesh peers by stable identity (#1360)

* Make peer lists accessible and actionable; block by stable identity

Who you can reach — the app's most important fact — was encoded in
unlabeled 10pt icons with macOS-only tooltips, and the mesh list had no
actions (block/favorite/verify were slash-command-only).

- Both peer lists become real accessibility citizens: each row is one
  element announcing name, reachability, and favorite/unread/blocked
  state, with a button trait and custom actions for the gesture-only
  interactions. Neither file previously had a single accessibility
  modifier. Reachability icons gain tooltips reusing existing strings;
  teleported vs in-area pins are explained.
- Mesh rows gain the context menu the geohash list already had: direct
  message, favorite, show fingerprint, block/unblock. The fingerprint
  double-tap, previously shadowed by the single tap, is reordered so it
  fires.
- The DM header's offline state (previously EmptyView — absence of a
  glyph as the only signal) becomes a dimmed "offline" tag, and a
  geohash DM — always Nostr-routed — no longer mislabels itself
  "offline".
- App Info gains a SYMBOLS legend defining every glyph the lists and
  headers use; nothing defined them before.
- Mesh block/unblock now resolve by the peer's stable Noise identity
  instead of a `/block <displayName>` string, so the exact tapped row is
  affected and offline peers can be unblocked (with covering tests).

New strings are added source-language (en) only.

* Surface block/unblock feedback in the conversation where it was triggered

setMeshPeerBlocked silently returned when the peer's identity could not
be resolved (e.g. long-press-blocking an old public message from a
sender who left and was never a favorite), where the /block command
printed "cannot block X: not found or unable to verify identity" — post
that same message from the guard branch.

Both the failure and confirmation messages now route through
addCommandOutput instead of addSystemMessage, so blocking from inside a
private chat prints into that chat rather than invisibly into the
public timeline (same routing #1363 applied to command output).

The confirmation also reuses the /block wording ("blocked X. you will
no longer receive messages from them") for parity with the command.

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

* Remove dead accessibility label and unreachable /unblock fallback

The favorite button's .accessibilityLabel in MeshPeerList is
unreachable: the row-level .accessibilityElement(children: .ignore)
swallows child elements, and the row's custom accessibility action
already covers favoriting.

ConversationUIModel.unblock is only called from the mesh peer list with
a non-optional mesh peerID, so the "/unblock <name>" fallback branch
could never run — take PeerID directly and drop the branch.

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

---------

Co-authored-by: jack <212554440+jackjackbits@users.noreply.github.com>
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Hot Pixel Group
2026-07-06 10:12:53 +02:00
committed by GitHub
co-authored by jack jack Claude Fable 5
parent 7a0c821807
commit c74e212ea3
10 changed files with 617 additions and 5 deletions
+312
View File
@@ -5015,6 +5015,162 @@
}
}
},
"app_info.legend.blocked" : {
"comment" : "Legend entry for the nosign glyph",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "blocked"
}
}
}
},
"app_info.legend.encrypted" : {
"comment" : "Legend entry for the lock glyph",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "end-to-end encrypted session"
}
}
}
},
"app_info.legend.encryption_failed" : {
"comment" : "Legend entry for the failed-encryption lock-slash glyph",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "encryption failed — messages not secured"
}
}
}
},
"app_info.legend.favorite" : {
"comment" : "Legend entry for the star glyph",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "favorite — enables offline messages via nostr when mutual"
}
}
}
},
"app_info.legend.location_nearby" : {
"comment" : "Legend entry for the map pin glyph",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "physically in this location channel's area"
}
}
}
},
"app_info.legend.mesh_connected" : {
"comment" : "Legend entry for the antenna glyph",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "connected directly over bluetooth"
}
}
}
},
"app_info.legend.mesh_relayed" : {
"comment" : "Legend entry for the relayed-mesh glyph",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "reachable through the mesh, relayed by others"
}
}
}
},
"app_info.legend.nostr" : {
"comment" : "Legend entry for the globe glyph",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "reachable over the internet (nostr) — mutual favorites only"
}
}
}
},
"app_info.legend.offline" : {
"comment" : "Legend entry for the offline person glyph",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "offline — not currently reachable"
}
}
}
},
"app_info.legend.teleported" : {
"comment" : "Legend entry for the teleported glyph",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "teleported — joined the channel from somewhere else"
}
}
}
},
"app_info.legend.title" : {
"comment" : "Section header for the symbols legend in app info",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "symbols"
}
}
}
},
"app_info.legend.unread" : {
"comment" : "Legend entry for the envelope glyph",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "unread private messages"
}
}
}
},
"app_info.legend.verified" : {
"comment" : "Legend entry for the verified seal glyph",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "identity verified"
}
}
}
},
"app_info.privacy.ephemeral.description" : {
"extractionState" : "manual",
"localizations" : {
@@ -23742,6 +23898,42 @@
}
}
},
"geohash_people.state.nearby" : {
"comment" : "State label for someone physically in the location channel's area",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "in this area"
}
}
}
},
"geohash_people.state.teleported" : {
"comment" : "State label for someone who joined the location channel from elsewhere",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "teleported from elsewhere"
}
}
}
},
"geohash_people.state.you" : {
"comment" : "State label marking your own row in the people list",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "you"
}
}
}
},
"geohash_people.tooltip.blocked" : {
"extractionState" : "manual",
"localizations" : {
@@ -31492,6 +31684,78 @@
}
}
},
"mesh_peers.accessibility.open_dm_hint" : {
"comment" : "Accessibility hint on a peer row explaining activation opens a private chat",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "opens a private chat"
}
}
}
},
"mesh_peers.action.fingerprint" : {
"comment" : "Context menu action that shows a peer's fingerprint/verification screen",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "show fingerprint"
}
}
}
},
"mesh_peers.state.blocked" : {
"comment" : "State label for a blocked peer",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "blocked"
}
}
}
},
"mesh_peers.state.favorite" : {
"comment" : "State label for a favorited peer",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "favorite"
}
}
}
},
"mesh_peers.state.offline" : {
"comment" : "State label for a peer that is not currently reachable",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "offline"
}
}
}
},
"mesh_peers.state.unread" : {
"comment" : "State label for a peer with unread private messages",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "new messages"
}
}
}
},
"mesh_peers.tooltip.new_messages" : {
"extractionState" : "manual",
"localizations" : {
@@ -33819,6 +34083,54 @@
}
}
},
"system.mesh.block_failed" : {
"comment" : "System message shown when a mesh peer cannot be blocked",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "cannot block %@: not found or unable to verify identity"
}
}
}
},
"system.mesh.blocked" : {
"comment" : "System message shown when a mesh peer is blocked",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "blocked %@. you will no longer receive messages from them"
}
}
}
},
"system.mesh.unblock_failed" : {
"comment" : "System message shown when a mesh peer cannot be unblocked",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "cannot unblock %@: not found"
}
}
}
},
"system.mesh.unblocked" : {
"comment" : "System message shown when a mesh peer is unblocked",
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "unblocked %@"
}
}
}
},
"system.tor.dev_bypass" : {
"extractionState" : "manual",
"localizations" : {