ArtiNative is a process-global singleton over a single native Tor client, and
initialize/destroy are blocking JNI calls that ignore coroutine cancellation.
TorService guarded it with only two AtomicBooleans and no mutual exclusion
across start/stop/reset/resetWithCleanState. A self-heal reset() (stuck-
Connecting watchdog or onNetworkChange) could therefore call destroy() while a
start() was mid-initialize(): the freshly bootstrapped client was destroyed
~0.5s after coming up, leaving the SOCKS listener bound with no live client
behind it. Every Tor dial then timed out at the exit (ExitTimeout barrage) and
status was left at a stale Active.
Serialize all four native lifecycle transitions behind a single lifecycleMutex
so destroy() can never overlap initialize() — a reset now waits for an
in-flight bootstrap to finish before tearing it down cleanly. reset() and
resetWithCleanState() share a private resetLocked() helper (Mutex is not
reentrant). Also gate the "Sufficiently bootstrapped" -> Active callback on
proxyRunning so a late callback from a torn-down client can't resurrect a stale
Active status.
Verified on device: forced reset+start now fully serialize (destroy completes
before create begins), and a clean start bootstraps to Active in one cycle with
no churn.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A relay that accepts the WebSocket handshake and then immediately resets
the connection (e.g. essayist.decentnewsroom.com) defeated the exponential
reconnect backoff.
UrlParser.parseValidUrls filtered every detected URL through
isValidTopLevelDomain(), which requires the TLD's first character to be
an ASCII letter. IPv6 literal hosts are bracketed (e.g. [2001:db8::1])
and have no dotted TLD, so the whole bracketed host became the candidate
"TLD", starting with '[' and failing the check. As a result, valid
IPv6 URLs like http://[302:68d0:f0d5:b88d::bdb]/<hash> were dropped and
rendered as plain text instead of links.
The UrlDetector already validates the bracketed address as syntactically
correct IPv6, so accept bracketed hosts directly in isValidTopLevelDomain.
The "Updates all dependencies" commit (ece719445) bumped Kotlin to 2.4.0,
which removed the `DisableCacheInKotlinVersion.2_3_21` enum value. Rather
than bumping the guard, the whole `disableUiKitPrebuiltCache()` workaround
was deleted — which reintroduced the iOS test link failure on CI
Drop three lower-impact items from the Highlights list (still documented
in the body): audio visualizer (author notes minimal visibility), LaTeX
math (niche), and pinned DMs (minor QoL). Keeps the headline payment,
privacy, and new-feed features front and center.
https://claude.ai/code/session_01Y8cDkDRV48GYdQd3CR4kuV
Reviewed all merged PRs since the v1.11.0 release. Fixed the misleading
'new money operations' Tor line (the toggle already existed; what changed
is relay sockets now honor it). Added gaps the PR titles surfaced: NIP-14
group DM subjects (#3163), removal of wallet reordering drag-and-drop
(#3114), and diagnosable relay failure logs (#3197).
https://claude.ai/code/session_01Y8cDkDRV48GYdQd3CR4kuV
A file-level audit (not just commit subjects) surfaced major features hidden
under misleading commit messages: the new NIP-60 Cashu ecash wallet, music
tracks/playlists, the NIP-82 Software Apps directory, the live audio
visualizer, and multi-wallet management (NWC/CLINK-debit/Cashu).
https://claude.ai/code/session_01Y8cDkDRV48GYdQd3CR4kuV
Adds Hidden Words unblock, Share to DM, reply-to-zaps from notifications,
the Selected/Global notification split, the podcasts subsystem, desktop
unhealthy-relay review, the live zap-popup rails, and the commons KMP
migration scope.
https://claude.ai/code/session_01Y8cDkDRV48GYdQd3CR4kuV
Summarizes the 506 commits / 131 PRs merged since the v1.11.0 release:
CLINK payments, private posts/reactions via NIP-17, NIP-101e workouts,
LaTeX math rendering, NIP-89 app recommendations, pinned DMs, activity
cards, desktop image compression + new media player, and the Quartz
relay-server toolkit. User-facing sections are written for end users;
build/quartz/cli sections keep developer-level language.
https://claude.ai/code/session_01Y8cDkDRV48GYdQd3CR4kuV
OkHttp 5.4.0 expanded Interceptor.Chain from ~10 to ~40 members, breaking
the test's hand-rolled `object : Interceptor.Chain` fake. Replace it with a
java.lang.reflect.Proxy that implements only request()/proceed() and throws
for the rest, so it survives future interface growth without dozens of empty
stubs. A real OkHttpClient can't be used here because Platform init fails
outside Robolectric (android.util.Log.isLoggable is unavailable).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the Namecoin diagnostics card that Android renders below the
per-server test results to the desktop settings panel, so support
requests carry the same information on both platforms.
- last test timestamp + pass/fail tally
- host OS (name/version/arch) — desktop equivalent of Android's
Build.MANUFACTURER/MODEL row
- JVM name + version — desktop equivalent of Android's API level row
- distinct TLS versions observed during the test run
Also adds a 'Testing next server...' inline progress row matching the
Android section's behaviour when the test loop is mid-run.
Pure UI addition; no model, preference, or callback changes.
The single CHANGELOG.md had grown to ~7.9k lines. Move it to
docs/changelog/ with one file per release, named with zero-padded
minor/patch versions (e.g. v1.11.00.md) so plain file browsers sort
them correctly. Add a README.md index (newest first) and a TEMPLATE.md
for cutting new releases. The root CHANGELOG.md now points to the new
location.
The RelayStat.lastConnectAt / lastIncomingAt fields added in #3186 were
written on every connect and every incoming relay message (a TimeUtils.now()
call plus a volatile store on the Android hot path) but never read.
The notifications "Global" mode was split into a raw Global (every event
that p-tags the user) and a curated Selected mode. Existing users who had
selected the old curated-Global kept a persisted value that now
deserializes to the much-more-permissive raw Global, so they suddenly saw
everything.
Add a one-shot, per-account migration: on load, if the account hasn't been
stamped yet and its notification filter is Global, rewrite it to Selected
and stamp the account. saveToEncryptedStorage always stamps the account as
migrated, so brand-new accounts (and any deliberate raw-Global choice made
after the split) are never reverted.
Only the notifications key is touched; Global keeps its original meaning
for every other feed.