Close the listener race window by re-snapshotting tracks inside the
DisposableEffect, hoist the openDialog remember above the early returns
so the hook count stays stable when the player loses its video group,
return ImmutableList from buildQualityChoices for Compose stability,
and remember the TextButton ButtonColors copy.
Consolidate VideoQualitySheet into VideoQualityButton using the existing
PlaybackSpeedPopUpButton Popup pattern. Derives selection state from
tracks instead of duplicating it, drops the dead QualityOption.Auto
case, gates listener-driven UI behind early returns, and reuses the
existing call_settings_video_quality string. Net -168 lines.
Adds a manual override for HLS/DASH adaptive streams via a gear icon
that opens a ModalBottomSheet with Auto + per-rendition options. The
button is hidden for single-rendition videos. Overrides reset when
players return to the pool so each video starts in Auto.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Migrates imports from com.abedelazizshe.lightcompressorlibrary to
com.davotoula.lightcompressor due to package rename in the fork.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Complete Tor toggle UX:
- key(appRestartKey) wraps App — full Compose tree rebuild on toggle
- TorManager at Window level — survives rebuild, no stop/start collision
- Tor bootstrap gate at top of App — blocks ALL creation (clients, relays,
Coil, subscriptions) until Tor proxy is ready. Zero clearnet during bootstrap.
- Coil ImageLoader reinitialized after setInstance on each rebuild
- Connection pool evicted on rebuild
- DesktopTorManager retries start up to 3x (handles previous daemon stopping)
- Settings reload from prefs on rebuild (fixes stale toggle display)
- Confirmation dialog on mode change in both TorSettingsSection and Dialog
Known: 2-4 stale Coil CDN connections may persist briefly after toggle
(image loads, not relay traffic — close after OkHttp 5min keep-alive).
Relay WebSocket connections are fully through Tor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When user changes Tor settings, shows "Restart Required" confirmation
dialog. On confirm: saves prefs, triggers appRestartKey++ which forces
Compose to unmount/remount entire App tree.
Key behaviors:
- Window stays open, user stays logged in (AccountManager outside App)
- Column layout preserved (DeckState outside App)
- DisposableEffect stops old Tor daemon before new one starts
- TorSettingsSection mode change → confirmation dialog
- TorSettingsDialog Save → confirmation dialog
- Relays, subscriptions, caches all recreated fresh
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the LaunchedEffect/scope.launch reconnect code that didn't
work (relay subscriptions lost after disconnect+connect cycle).
Tor toggle will use app rebuild via key() instead (next commit).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Relay reconnect was only triggered when Tor transitioned to Active.
When toggling OFF, relays stayed on the dead SOCKS proxy and never
reconnected over clearnet — feed went blank.
Now triggers reconnect on both Active and Off transitions, so relays
switch between proxy and direct connections when toggling Tor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Port Android's OkHttpFactory pattern to desktop — creates a custom
Fetcher.Factory<Uri> that calls DesktopHttpClient.currentClient() per
image request, routing all image loads through the SOCKS proxy when
Tor is active.
Avoids the OkHttpNetworkFetcher.factory() import issue by directly
constructing NetworkFetcher with the Tor-aware Call.Factory, matching
the proven Android implementation.
This closes the last network leak — ALL egress paths now route through
Tor when enabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SECURITY: With Tor ON, all HTTP traffic now routes through SOCKS proxy.
Previously only relay WebSocket connections used Tor; 8 other egress
paths created bare OkHttpClient() instances bypassing Tor entirely.
Fail-closed behavior:
- When Tor expected but bootstrapping → dead SOCKS proxy on port 1
(requests fail instead of leaking IP)
- lateinit var crashes on misconfiguration (loud failure vs silent leak)
Leak sites fixed (all use DesktopHttpClient.currentClient()):
- AnimatedGifImage: GIF fetch
- SaveMediaAction: media downloads
- EncryptedMediaService: NIP-17 DM media
- ServerHealthCheck: Blossom server probes
- NoteActions: zap/lightning LNURL resolution
- DesktopBlossomClient: media uploads
- AccountManager: NIP-46 bunker relay connections
- Coil image loader: TODO (OkHttpNetworkFetcher import issue)
Also:
- Relay reconnect on Tor Active (prevents stale clearnet connections)
- isTorExpected() for fail-closed logic
- Tests updated for new torTypeProvider parameter
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shield was getting pushed off screen when RelayHealthIndicator ("x s ago")
appeared above it. Moved to very last position (after BunkerHeartbeat)
so it's always visible at the bottom edge.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shield icon was only in DeckSidebar (deck mode) but missing from
SinglePaneLayout's NavigationRail. Added between RelayHealthIndicator
and BunkerHeartbeatIndicator. Clicking navigates to Settings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UX improvements based on manual testing feedback:
- Default TorType changed from OFF to INTERNAL (Tor on by default)
- Default preset changed to Full Privacy (all routing via Tor)
- Shield icon always visible in sidebar (not hidden when Off)
- Shield icon clickable — opens Settings
- TorSettingsDialog Cancel/Save buttons sticky at bottom (don't scroll away)
- HorizontalDivider above buttons for visual separation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 6: Polish and wiring.
Per-relay routing:
- TorRelayEvaluation wired into DesktopHttpClient with actual settings
- shouldUseTorForRelay uses real evaluation instead of { false }
- Settings changes propagate to torTypeFlow/externalPortFlow reactively
CompositionLocal for Tor state:
- LocalTorState provides TorState (status, settings, onSettingsChanged)
- Avoids threading Tor params through every composable layer
- DeckColumnContainer reads from LocalTorState for RelaySettingsScreen
.onion relay badge:
- RelayStatusCard shows "Requires Tor" (red) when Tor is off
- Shows "via Tor" (green) when Tor is active
- Only on .onion relay URLs
Shutdown hook:
- activeTorManager.stopSync() in existing shutdown hook
- Ensures no orphaned Tor processes on app exit
- Set via volatile var from App composable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 5: Desktop settings UI for Tor.
TorStatusIndicator:
- Shield icon in sidebar footer (gray/yellow/green/red)
- Tooltip shows status (no port number for security)
- Only visible when Tor is not Off
TorSettingsSection:
- Inline in settings screen between Media Server and Dev Settings
- Mode selector (Off/Internal/External) with segmented buttons
- External SOCKS port input with validation (1-65535)
- "Advanced..." button opens full dialog
TorSettingsDialog:
- Full DialogWindow with preset selector (radio buttons)
- 4 relay routing toggles + 7 content routing toggles
- Preset auto-detection (whichPreset)
- Save/Cancel with DesktopTorPreferences persistence
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mirror TorSettingsTest and TorRelayEvaluationTest in commons/commonTest
using kotlin.test for KMP compatibility. Tests verify the shared types
directly without going through Android typealiases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New shared types in commons/commonMain/tor/:
- TorServiceStatus: clean sealed class with Off, Connecting, Active(port),
Error(message). Uses data object for stateless variants. No TorControlConnection
dependency (Android keeps its own version for now).
- ITorManager: reactive interface with status flow + dormant/active/newIdentity
- ITorSettingsPersistence: load/save interface for platform persistence
Android TorServiceStatus unchanged — will migrate to commons version when
RelayProxyClientConnector is refactored to use ITorManager signals.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move relay routing logic to shared commons module:
- TorRelaySettings data class
- TorRelayEvaluation with useTor() routing logic
Android files replaced with typealiases for backward compatibility.
All tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move core Tor data models and preset logic to shared commons module:
- TorSettings data class, TorType enum, TorPresetType enum
- parseTorType, parseTorPresetType, isPreset, whichPreset functions
- All preset constants (torOnlyWhenNeededPreset, etc.)
R.string resource IDs removed from enums in commons. Android retains
resourceId/explainerId as extension properties in ui.tor package.
15 Android files updated to import from commons.tor. All 69 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move fe_mul/fe_sqr to static inline in field.h when ASM is available
(FE_MUL_ASM=1). This allows the compiler to inline the entire field
multiply directly into gej_double and gej_add_ge, eliminating function
call boundaries.
Before: gej_double had 9 function calls (to fe_mul/fe_sqr)
After: gej_double has 2 function calls (fe_half only)
The compiler can now:
- Keep intermediate results in registers across multiply boundaries
- Schedule MULX instructions across adjacent field operations
- Eliminate push/pop register saves at call boundaries
gej_double: 738 → 1311 instructions (larger but no call overhead)
Impact:
verifyFast: 35.1µs → 31.6µs (10% faster, 1.19x vs ACINQ)
verify: 39.7µs → 38.4µs (0.98x vs ACINQ — essentially tied!)
sign: 15.2µs → 14.2µs (1.48x vs ACINQ)
batch(200): 6.2µs → 4.5µs per event (7.9x vs ACINQ)
https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
Remove fe_normalize/reduceSelf from the end of field multiply and
square. After reduceWide, the output is in [0, 2^256) which may
include values in [P, P+C) where C = 2^32+977. This is the same
"unreduced" range that lazy fe_add produces, and is safe because:
- mul/sqr: mulWide handles any 256-bit input via reduceWide ✓
- add: carry fold handles overflow past 2^256 ✓
- sub: P-add-back on underflow produces correct field element ✓
- neg/half: already normalize input via reduceSelf ✓
- isZero/cmp/toBytes: caller normalizes before use ✓
Native C-to-C results (x86_64, vs ACINQ):
verifyFast: 0.95x → 0.99x (essentially tied with ACINQ!)
sign (cached): 1.18x → 1.24x faster
ECDH: 1.05x → 1.06x faster
batch(200)/event: 7.2µs → 6.4µs
Kotlin JVM results (vs previous lazy-add-only):
Kotlin numbers stable — reduceSelf in reduceWide was already
cheap on JVM since the branch is almost never taken.
https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
Analyzed lazy fe_sub: on underflow, the unsigned-wrapped value
(a - b + 2^256) differs from (a - b + P) by C = 2^32 + 977.
When multiplied: (a-b+2^256)*x ≠ (a-b)*x mod p (off by x*C mod p).
The P-add-back on underflow is mandatory for correctness.
This is a fundamental difference from 5x52 lazy reduction where
magnitude tracking keeps values representable. With 4x64 fully-packed
limbs, sub MUST add P back, but add CAN skip reduceSelf since values
in [P, 2^256) differ from [0, C) which is handled by mulWide+reduceWide.
Also evaluated:
- WINDOW_G 12→14: only 1.2µs savings for 4x memory (128→512KB).
Not worth it on phones where L1 cache is 128-256KB.
- fe_sqrt optimization: only 831ns overhead over theoretical minimum
of 5.85µs. The addition chain is already near-optimal.
https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
Remove reduceSelf from FieldP.add, making it lazy (same approach as
the C implementation). Values may be in [0, 2^256) between additions.
Safety analysis:
- mul/sqr: reduceWide handles any 256-bit input ✓
- neg: added reduceSelf before P - a (prevents underflow) ✓
- half: added reduceSelf before conditional add P ✓
- sub: already adds P back on borrow (self-normalizing) ✓
- isZero/cmp: called on sub outputs (normalized) or after mul ✓
- verifySchnorrCore: added reduceSelf before Jacobian x-check ✓
- toBytes: only called on toAffine outputs (from mul, normalized) ✓
JVM benchmark results (ops/sec, HotSpot C2):
pubkeyCreate: 32,096 → 37,211 (+15.9%)
signXOnly: 31,149 → 34,507 (+10.8%)
sign: 16,137 → 17,822 (+10.4%)
verify: 12,733 → 14,027 (+10.2%)
verifyFast: 14,734 → 15,449 (+4.9%)
ECDH: 10,975 → 12,102 (+10.3%)
batch(200): 91,611 → 102,354 (+11.7%)
The improvement is larger than expected (10-16% vs estimated 6%)
because HotSpot's branch prediction overhead for reduceSelf
(virtual dispatch + 4 Long comparisons) is higher than the
~2.5ns estimated for native code.
https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY