- Add AppTab enum to ChatState to centralize tab state management
- Move selectedTab state flow from shell composition to ChatState for global access
- Update ChannelManager to automatically switch to Public tab when joining channels
- Add showTab() method to ChatViewModel to expose tab selection to all navigation points
- Integrate tab switching into back navigation handler for consistent unwind order
- Update selectLocationChannel() to switch to Public tab on geohash notification taps
- Remove BackHandler and local tab state from MainScaffold composition
- Clarify navigation behavior with detailed code comments explaining tab switching rules
- Add ChannelTabRoutingTest to verify tab routing on channel selection
- Ensure all navigation paths (notifications, channel joins, password prompts) consistently manage tab state from a single location
- Remove AnimatedVisibility wrapper from navigation bar and replace with conditional if statement
- Add comment explaining why animations are deliberately avoided on screen swaps to prevent layout thrashing
- Update MessageArrivalTracker.arrivals() to filter out backfilled messages from arrival detection
- Add lastKnownIndex tracking to distinguish between backfilled history and new messages
- Update filter logic to only report messages newer than the last known message as arrivals
- Add comprehensive test coverage for backfill scenarios: messages loaded behind latest and new arrivals alongside backfill
- Prevents transcript flicker on short conversations by not animating backfilled history rows
- Change theme chip selected state from saturated primary fill to tinted primaryContainer with primary border stroke
- Update theme chip label color to use onPrimaryContainer for selected state
- Increase theme chip border radius from 10dp to 12dp and adjust padding to 11dp
- Add animated border stroke that appears only when chip is selected
- Separate chat transcript layout into its own dedicated card section with new "Chat style" label
- Remove unused chatUiMode state variable from theme section
- Reduces visual competition between multiple selected theme chips by using softer container color instead of solid primary fill
- Improves information hierarchy by giving transcript layout its own labeled section instead of mixing it with theme settings
- Extract MainScaffold as new top-level container component for improved UI organization
- Add TimeFormat.kt module with TimeFormatPreferenceManager for 12/24-hour and seconds display preferences
- Initialize TimeFormatPreferenceManager in BitchatApplication startup sequence
- Simplify notification handling by consolidating showMeshPeerList() and showPrivateChatSheet() into openPrivateChat()
- Optimize QR code generation performance by replacing per-pixel setPixel calls with batch setPixels operation
- Update QrCodeGenerator.generateQrBitmap() from private to public API for broader accessibility
- Refactor state management methods: rename getPrivateChatSheetPeerValue() to getOpenPrivateChatPeerValue() and setPrivateChatSheetPeer() to setOpenPrivateChatPeer()
- Remove unused androidx.core.graphics.set import from QrCodeGenerator
- Improve documentation and code clarity across UI components and services
- Update test coverage in ChatUIUtilsTest to reflect API changes
If SQLite clearAll fails mid-panic, in-memory state was already cleared
but a process restart could reload encrypted history from disk (#699).
Fall back to deleting the database files before reporting failure.
A relay that fails is retried on an exponential backoff, then abandoned
for the lifetime of the process. Nothing brings it back: the relay layer
registers no connectivity callback, the periodic subscription validator
only repairs subscriptions on sockets that are already open (and returns
immediately when connectedRelayCount is 0, which is exactly the state
after an outage), and connect() runs once from NostrClient.initialize().
The remaining paths that reset reconnectAttempts are a manual retry, a
Tor state change, and a successful open.
Two ways a relay died permanently:
- Any error whose message mentioned DNS returned before scheduling
anything at all. "Unable to resolve host" is what this device reports
when it simply has no network, so a moment in a tunnel killed every
relay at once, with no retry ever.
- Otherwise the schedule stopped at MAX_RECONNECT_ATTEMPTS. With
INITIAL=1s and MULTIPLIER=2 that is nine waits totalling about eight
and a half minutes, after which the relay was dead. MAX_BACKOFF_INTERVAL
was unreachable: attempt 9 asks for 256s and attempt 10 gave up, so the
five-minute ceiling the constant defines never applied to anything.
Let the backoff saturate at MAX_BACKOFF_INTERVAL and keep retrying there.
A name-resolution failure now backs off like any other error. Steady state
costs one connection attempt per relay per five minutes; the previous
behaviour cost the user every internet DM, delivery receipt and geohash
channel until they noticed and restarted the app.
The schedule moves into RelayReconnectPolicy so it is unit-testable
without OkHttp or a Context.
The collision case fails on main: two packets sharing a 64-byte prefix and
a timestamp, where the second was silently dropped.
The other two pass before and after on purpose. Replay of an identical
packet must still be caught, and the same packet arriving from two
different peers must still be tracked separately — strengthening the
identity must not quietly weaken either.