Swiping the public mesh chat to the right reveals an app menu page
(Wear HorizontalPagerScaffold). The menu shows mesh status and a
"Quit bitchat" button that, after confirmation, stops the mesh
foreground service and closes the task.
Swiping from the menu's left edge still falls through to system
swipe-to-dismiss. Page swipes are disabled while push-to-talk or the
full-screen image viewer owns the gesture.
A copy with a zero-width space between letters, an accent on every
letter, or math-bold or fullwidth letters looks the same to a reader
but defeated both the per-sender and cross-user repeat checks.
Normalize text for comparison by applying NFKD (which folds fullwidth
and styled letters to plain ones and splits off accents), dropping
combining marks and invisible characters, collapsing whitespace and
lowercasing. Displayed and stored text is unchanged. Input is capped
before normalizing to bound the work per message.
Tapping the QR action closed the network sheet, briefly showed the chat
screen, then opened the verify sheet; closing verify reopened the network
sheet through a reopenSidebarAfterVerification flag. Open the verify
sheet on top of the network sheet instead, the way the private chat sheet
already hosts security verification, so dismissing it (close button or
back) simply reveals the network sheet underneath. The reopen flag and the
fromSidebar parameter are no longer needed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spam filter keeps recent message text and sender keys for its
repeat checks, and panic clear left them in memory. Reset the shared
filter as part of the panic wipe.
Add a broadcast_spam Mesh Lab scenario so messaging validation covers
both admitted and filtered public messages: B must show two
near-copies from A, hide the third, and still show A's next ordinary
message. It runs phone-to-phone and phone-to-watch.
A mesh peer could skip every spam rule by choosing the nickname
"system", since the exemption for local notices checked only the
peer-controlled display name. Exempt only messages without a sender
peer ID: every message read off the wire carries one, while local
notices such as "X favorited you" do not. Applies to the phone and
watch admission paths.
The foreground geohash handler runs on the main thread, so the spam
checks could stall the UI during a relay backfill. Run them on a
background dispatcher (the signature verification one by default).
The QR action next to the network sheet's close button was the only
grey, oversized icon in any top bar, and sat 16dp away from the close
button. Align it with the conversation header's action pattern:
- tint with colorScheme.primary, like the close button beside it
- size to HeaderIconSize (19dp), like the private chat header actions
- space the close button 2dp from a preceding action; the 16dp end
inset is unchanged, so the close button does not move on any sheet
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spammers can rotate through unlimited npubs, so per-sender repeat
limits miss a swarm posting the same text once each. Keep the last 200
messages of 16+ characters from all senders and hide a message once
more than 2 near-copies from other senders appeared within the repeat
window.
Plain cross-correlation keeps only the best shift, so one inserted or
deleted character split a copy in two (a "!!" insertion scored 54%).
Similarity now combines up to 3 correlation peaks, where the extra
peaks count only runs of 3+ matching characters and no character is
used twice; the same example scores 95% while unrelated text stays
under 40%. Comparisons cover the first 280 characters and skip pairs
sharing under 25% of their trigrams, bounding the cost per message.
Both cross-user limits are tunable in the debug Spam section.
Add a Spam filter section to debug settings with sliders for every
limit (message burst and refill, characters and links per minute,
repeat tolerance, similarity and window, repeated-character threshold
and minimum length) and a reset to defaults. Tuned values persist and
apply immediately; changing them gives every sender a full budget.
A debug bypass keeps spam in the chat instead of hiding it and marks
it with a red background and the reason it would have been hidden,
in both matrix and bubble mode. Transports now ask
SpamFilter.shouldHide, which records bypassed verdicts in a flow the
message list observes.
Hide public messages from a sender who exceeds per-user token buckets
(6 messages with 1 refilled every 10 s, 600 characters per minute,
2 links per minute), posts a third near-copy of a recent message, or
sends a message that repeats a character or phrase (character
autocorrelation). Buckets run on message timestamps, clamped to the
last hour, so relay backfill and mesh sync bursts are not mistaken
for floods.
The filter applies to mesh public and channel messages at transport
admission, to geohash messages in GeohashMessageHandler, and to the
watch's mesh intake. Private messages are not filtered. All limits
live in SpamFilterConfig; a Settings toggle (on by default) disables
the filter.
Draw unplayed waveform bars in the surface text colour and played bars in the theme accent, and give the duration label an explicit colour so it stays readable in dark mode.
Public, channel and geohash timelines are capped at 1,000 messages, so once
full each arrival evicts the oldest and the list size stops changing. The
follow effect was keyed only on size, so it stopped auto-scrolling. Key it on
the newest message id and the conversation as well, and add a Robolectric
Compose regression test.
- The panic wipe announces success only when every data-at-rest stage
confirms: identity and signing-key prefs, Nostr keys, favorites, media,
bookmarks, and the conversation database. Each stage now returns
whether its wipe was committed instead of swallowing failures; any
failure shows the incomplete message.
- Add docs/PACKET_PAYLOAD_LIMITS.md for the per-type admission rules and
literal wire vectors with real DEFLATE bodies at and past the caps.
- Document the random sender ID in geohash envelopes.
- Geohash DMs and their acks embed a random sender ID per envelope
instead of the mesh peer ID, which linked geohash personas to the
device (iOS #1718). Favorites envelopes are unchanged.
- Decode enforces per-type payload caps on compressed and uncompressed
payloads, matching iOS PacketPayloadLimits; media types keep the
existing ceiling. The compression-ratio guard becomes DEFLATE's
1032:1 bound (iOS #1719, decoder part only).
- Triple-tapping the logo asks for confirmation before a panic wipe,
and the outcome (wiped, or incomplete) is posted to the chat
(iOS #1588).
- Pin that a blocked geohash sender gets no delivery ack.
Block the long-pressed message's Nostr pubkey instead of resolving a
suffix-stripped nickname, which picked an arbitrary user when nicknames
collided and failed for anon senders. Nickname lookup now honors the
#suffix and returns nothing when ambiguous. Blocking also removes the
sender's existing messages from geohash timelines.
Replace the hardcoded #00C851 and #4CAF50 greens with colorScheme.primary
and drop the extra label padding, so these primary buttons match the
other onboarding screens in color and height (40dp). In light theme this
also raises button label contrast from about 2.2:1 to 4.4:1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Apply the content column's bottom padding outside the scroll container
on the permission, background location, and battery optimization
screens. At large font sizes the clipped edge now stops 16dp above the
buttons instead of running into them. Layout at default sizes is
unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use the same 24dp outer padding on every onboarding screen. The
permission explanation screen now follows the background location and
battery optimization layout: no extra top spacer, the primary button
sits inside the screen padding instead of a separate elevated bar, and
permission rows share the privacy card's icon and text inset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>