- Set default-features = false on arti-client and tor-rtcompat
- Removed bridge-client (UI doesn't expose bridge config yet)
- Narrowed tokio features from "full" to only what the SOCKS proxy
needs: rt-multi-thread, net, io-util, time, macros
Kept: tokio, rustls, compression, onion-service-client, static-sqlite
(all required for Amethyst's .onion relay support)
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
The Guardian Project's arti-mobile-ex AAR has three problems:
1. No 16KB page-aligned binaries (required for Google Play)
2. ArtiProxy's stop()+start() causes state file lock conflicts
(lock is tied to TorClient object lifetime, released only on GC)
3. ~140MB AAR size
Replace with a custom JNI bridge built from Arti source, following
BitChat's proven approach:
Build tooling (tools/arti-build/):
- build-arti.sh: Clones official Arti, compiles with cargo-ndk
for ARM64 + x86_64, NDK 25+ for 16KB page alignment
- Cargo.toml: Minimal deps with size-optimized release profile
- src/lib.rs: Custom SOCKS5 proxy with proper lifecycle:
- initialize() creates TorClient once (holds state lock forever)
- startSocksProxy() binds port and accepts connections
- stopSocksProxy() aborts listener only (TorClient stays alive)
This cleanly separates "stop routing traffic" from "destroy client"
Kotlin side:
- ArtiNative.kt: JNI declarations + ArtiLogCallback interface
- TorService.kt: Uses ArtiNative directly, start() initializes +
starts proxy, stop() only stops proxy (no lock issues)
- TorManager.kt: Restored stop() calls for OFF/EXTERNAL modes
since our native stop is now safe
Removed: arti-mobile-ex dependency from build.gradle and version catalog
Native libraries must be built separately:
cd tools/arti-build && ./build-arti.sh
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
Arti's state file lock is released when the TorClient object is
garbage collected, not when stop() is called. Calling stop()+start()
on the same ArtiProxy creates a new internal TorClient that conflicts
with the old lock that hasn't been GC'd yet.
Solution: start ArtiProxy once, let it run for the process lifetime.
When the user turns Tor OFF or EXTERNAL, TorManager simply stops
emitting Active status — OkHttp stops routing through the proxy.
The idle proxy uses negligible resources and drops circuits when no
SOCKS connections are active.
This removes stop(), Mutex, NonCancellable, CompletableDeferred — all
the complexity that was trying to work around a fundamental Arti
design constraint.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
The root cause of file lock conflicts was creating new ArtiProxy
objects on each start. Even after stop() confirmed, build() could
race with OS-level lock release.
Now ArtiProxy is created once in the TorService constructor and
reused for the app's lifetime. start() and stop() just toggle it
on/off on the same instance. No more file lock conflicts.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
The fixed 2s delay was insufficient — Arti's native layer releases
file locks asynchronously. Now stop() waits for the actual
"state changed to Stopped" log confirmation via CompletableDeferred,
with a 10s timeout as safety net. This ensures the file lock is
truly released before start() creates a new ArtiProxy instance.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
EXTERNAL means another Tor process (e.g., Orbot) is running on the
device. No reason to keep our internal Arti alive alongside it.
Also removed the fallback that started Arti when the external port
was invalid — if the user chose EXTERNAL with a bad port, Tor should
be off, not silently falling back to internal.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
Users in restrictive countries need Tor circuits torn down when they
switch to OFF — an idle proxy still maintains detectable connections.
stop() is now called only on explicit OFF toggle (user action), not on
flow pause/resume (app lifecycle). This avoids file lock races on
resume while ensuring OFF truly disconnects from the Tor network.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
ArtiProxy holds an exclusive filesystem lock. Destroying it on OFF and
recreating on INTERNAL caused lock conflicts because the native layer
needs time to release the lock.
Instead, create ArtiProxy once and never destroy it. When Tor is OFF
or EXTERNAL, the proxy sits idle with no SOCKS connections — negligible
resource usage. This eliminates all file lock race conditions.
Also wrap start/stop in NonCancellable to prevent transformLatest
cancellation from leaking half-initialized proxy instances.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
The callbackFlow-based TorService created a new ArtiProxy on every flow
collection. When the app paused and resumed, the old instance's file lock
wasn't released before the new one started, causing "Another process has
the lock on our state files" errors.
Redesigned TorService to own a single ArtiProxy with explicit start/stop:
- ArtiProxy is created once and reused across flow re-collections
- State exposed via MutableStateFlow instead of callbackFlow
- Mutex guards start/stop to prevent races
- TorManager calls service.start() and service.stop() explicitly when
switching between INTERNAL/OFF/EXTERNAL modes
- stop() includes a 2s settle delay for native file lock release
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
Address gaps found in code review:
- Use AtomicBoolean/AtomicLong for state accessed from native Arti
log callback thread (was a data race)
- Add bootstrap timeout monitor (120s) that restarts Arti once if
bootstrapping stalls, following BitChat's inactivity pattern
- Clean up failed ArtiProxy instances before port retry
- Detect "Another process has the lock" fatal error from Arti logs
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
Replace Guardian Project's tor-android (C Tor) and jtorctl with
arti-mobile-ex, a Rust-based Tor implementation. This eliminates
the Android Service binding complexity in favor of an in-process
ArtiProxy object.
Key changes:
- TorService: Replace ServiceConnection to org.torproject.jni.TorService
with direct ArtiProxy.Builder/start/stop API. Bootstrap state detected
via log parsing (following BitChat's pattern).
- TorServiceStatus: Remove TorControlConnection field (Arti doesn't
support jtorctl control protocol).
- RelayProxyClientConnector: Remove DORMANT/ACTIVE/NEWNYM control
signals. Arti manages its own circuit lifecycle internally.
- Dependencies: Replace tor-android + jtorctl with arti-mobile-ex 1.2.3.
TorManager's external API (status/activePortOrNull StateFlows) and all
downstream consumers (DualHttpClientManager, TorSettings, UI) are
unchanged.
https://claude.ai/code/session_01BApgDd5udqBzMqysSRMpZu
Instead of replacing the new bookmark list, the migration now checks for
an existing kind 10003 event and merges old bookmarks into it, skipping
duplicates that already exist.
https://claude.ai/code/session_01U9sjQHQMVVHxYiesoXjqop
Rename the existing BookmarkListEvent (kind 30001) to OldBookmarkListEvent
and create a new BookmarkListEvent with kind 10003 as per the updated spec.
- OldBookmarkListEvent (kind 30001): Kept as-is for backward compatibility,
displayed as "Old Bookmarks" in the Bookmark Lists screen
- BookmarkListEvent (kind 10003): New replaceable event, all new bookmark
operations (save/check/remove) use this kind
- Both kinds are displayed as separate items in the Bookmark Lists screen
- Old Bookmarks screen includes a "Move All to New Bookmarks" button that
migrates public bookmarks to public and private to private
- Created PrivateReplaceableTagArrayEvent base class for replaceable events
with encrypted private tags (kind 10003 is in the 10000-19999 range)
- Updated all relay filters, search queries, and profile views to fetch
both kinds
- Updated Android and Desktop modules
https://claude.ai/code/session_01U9sjQHQMVVHxYiesoXjqop
Adds 6 preview functions covering:
- PictureCardCompose with title, without title, and multi-image
- PictureCardCaption with title, without title, and long content
- Uses ThemeComparisonColumn for light/dark theme comparison
- Uses mock PictureEvent JSON data consumed via LocalCache
https://claude.ai/code/session_01KtuzFmDZXk67tW8QxPqmMy