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
When a zap push notification is tapped, the app now navigates to the
Notifications screen and scrolls to the specific zap event, highlighting
it briefly to draw the user's attention.
- Change Route.Notification from object to data class with optional scrollToEventId
- Include zap/reaction event ID in notification URI (scrollTo param)
- Parse scrollTo from URI in uriToRoute() and pass through navigation
- Scroll to matching card in the feed and animate a highlight that fades
https://claude.ai/code/session_01Dk9vVKQKDtLLgvJPUCfKSs
- Add themed blockquote gutter using BarGutter with primary color at 45% opacity
- Add table border styling using theme-consistent subtle borders
- Set table cell padding to 10sp for comfortable reading
https://claude.ai/code/session_01PPExHNAdLqddiYsGZi1BgW
- Increase body text line height from 1.30em to 1.50em for better readability
- Increase paragraph spacing from 18sp to 20sp for more breathing room
- Add explicit fontWeight to headings: Bold for H1-H2, SemiBold for H3-H4, Medium for H5-H6
- Add negative letterSpacing to H1-H2 for tighter display-style headings
- Widen heading size range (32sp-15sp vs 32sp-18sp) for clearer visual hierarchy
- Add internal padding to code blocks (16dp horizontal, 12dp vertical) so text isn't flush against borders
- Add vertical margin (4dp) around code blocks for separation from surrounding text
- Add line height (1.45em) to code block text for better readability
- Add letter spacing (0.3sp) to inline code for better monospace readability
- Fix light theme code block using DarkColorPalette instead of LightColorPalette for background
https://claude.ai/code/session_01PPExHNAdLqddiYsGZi1BgW
- MainActivity: use applicationContext instead of Activity ref in GlobalScope
- ChoreographerHelper: add stop() method and running flag to allow unregistering the perpetual FrameCallback
- MediaSessionPool: replace GlobalScope with class-scoped CoroutineScope, cancel on destroy
- ExoPlayerPool: cancel scope after destroy completes
- PlaybackServiceClient: add shutdown() for the ExecutorService thread pool
- LocalCache: replace GlobalScope with app-scoped coroutine for payment callbacks
- TextToSpeechEngine: clear all listener lambdas in destroy() to prevent capturing UI scope
- NotificationReplyReceiver: scope CoroutineScope to each onReceive call and cancel after use
- NotificationUtils: use SingletonImageLoader instead of creating new ImageLoader per notification
- AppModules: clean up BackgroundMedia and PlaybackServiceClient on terminate
https://claude.ai/code/session_01RR2kqsV23JKKdNGQVj7YaQ
Add expandable FAB buttons to create new content from each feed screen:
- Polls: composite FAB with Regular Poll and Zap Poll options via new
dedicated routes (Route.NewPoll, Route.NewZapPoll) and PollPostScreen
- Pictures: expandable FAB with camera and gallery options
- Shorts: expandable FAB with video recording and gallery options
- Longs: expandable FAB with video recording and gallery options
The poll creation screens reuse ShortNotePostViewModel with the
appropriate poll type pre-enabled. Media screens use the existing
NewMediaModel/NewMediaView upload flow.
https://claude.ai/code/session_01NGMh9KM9yTtdBP6vW8MU1q
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