# Server Resolution Prefetch Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add invisible server-address resolution prefetch so the active host field can warm Rust resolver state before Connect without changing connection semantics. **Architecture:** Flutter schedules debounced prefetch calls for the active host field only. Rust owns resolution prefetch state, TTL, exact-match validation, and connect-time reuse. The protocol layer accepts an optional already-resolved socket address so Connect can skip resolver work only when the core cache says it is safe. **Tech Stack:** Flutter/Dart, flutter_rust_bridge generated bindings, Rust async Tokio, `chanora_core`, `chanora_protocol`, `chanora_resolver`, Flutter widget/service tests, Rust unit tests. --- ## File Structure - Modify `core/chanora_core/src/lib.rs`: add a session-owned prefetch cache, prefetch API, connect-time cache lookup, and unit tests for TTL/exact-match/generation behavior. - Modify `crates/chanora_protocol/src/adapter.rs`: add `resolved_address: Option` to `ConnectConfig` and skip `resolve_server_socket()` when present. - Modify `crates/chanora_bridge/src/api.rs`: add `prefetch_server_resolution(host: String)` bridge function and pass cache-aware connects through `ChanoraSession`. - Regenerate `crates/chanora_bridge/src/frb_generated.rs`: generated Rust bridge bindings. - Regenerate `apps/chanora_flutter/lib/src/rust/api.dart`, `api.freezed.dart`, and `frb_generated.dart`: generated Dart bridge bindings. - Create `apps/chanora_flutter/lib/services/server_resolution_prefetch_scheduler.dart`: small testable debounce helper for host-field prefetch scheduling. - Modify `apps/chanora_flutter/lib/main.dart`: wire `_hostCtl` listener, settings-loaded prefetch, and disposal. - Create `apps/chanora_flutter/test/services/server_resolution_prefetch_scheduler_test.dart`: Flutter/Dart tests for debounce and empty-host behavior. Keep all prefetch UI invisible. Do not prefetch bookmarks in bulk. --- ### Task 1: Protocol Config Accepts Prefetched Socket **Files:** - Modify: `crates/chanora_protocol/src/adapter.rs` - [ ] **Step 1: Add a failing protocol config test** Add this test inside the existing `#[cfg(test)] mod tests` in `crates/chanora_protocol/src/adapter.rs`: ```rust #[test] fn connect_config_can_carry_prefetched_socket_address() { let addr: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap(); let cfg = ConnectConfig { address: "example.com".to_string(), nickname: "Tester".to_string(), password: None, identity: None, ready_timeout: Duration::from_secs(1), resolved_address: Some(addr), }; assert_eq!(cfg.resolved_address, Some(addr)); } ``` - [ ] **Step 2: Run the test to verify it fails** Run: `cargo test -p chanora_protocol connect_config_can_carry_prefetched_socket_address` Expected: FAIL to compile with a message like `struct ConnectConfig has no field named resolved_address`. - [ ] **Step 3: Add the minimal config field** Update `ConnectConfig` in `crates/chanora_protocol/src/adapter.rs`: ```rust #[derive(Debug, Clone)] pub struct ConnectConfig { /// Server address: `hostname[:port]` or TSDNS name. pub address: String, /// Optional already-resolved socket address from core's invisible /// prefetch cache. When present, the protocol layer skips address /// resolution but still opens a normal TS3 connection only after /// the user requested Connect. pub resolved_address: Option, /// Nickname to use on the server. pub nickname: String, /// Optional server password. pub password: Option, /// Optional pre-existing identity (base64 string accepted by /// `tsclientlib::Identity::new_from_str`). If `None`, a fresh /// identity is generated and **not persisted** — production callers /// should provide one from secure identity storage. pub identity: Option, /// How long to wait for the initial state snapshot before /// returning `ProtocolError::Timeout`. pub ready_timeout: Duration, } impl Default for ConnectConfig { fn default() -> Self { Self { address: String::new(), resolved_address: None, nickname: "Chanora".to_string(), password: None, identity: None, ready_timeout: Duration::from_secs(10), } } } ``` - [ ] **Step 4: Run the focused test** Run: `cargo test -p chanora_protocol connect_config_can_carry_prefetched_socket_address` Expected: PASS. - [ ] **Step 5: Commit** ```bash git add crates/chanora_protocol/src/adapter.rs git commit -m "feat(protocol): accept prefetched server address" ``` --- ### Task 2: Protocol Connect Skips Resolver On Prefetch Hit **Files:** - Modify: `crates/chanora_protocol/src/adapter.rs` - [ ] **Step 1: Add a failing helper test** Add a private helper next to `resolve_server_socket()` only after this failing test is added. First add this test in `#[cfg(test)] mod tests`: ```rust #[test] fn server_socket_from_config_prefers_prefetched_address() { let addr: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap(); let cfg = ConnectConfig { address: "example.com".to_string(), resolved_address: Some(addr), nickname: "Tester".to_string(), password: None, identity: None, ready_timeout: Duration::from_secs(1), }; assert_eq!(server_socket_from_config(&cfg), Some(addr)); } ``` - [ ] **Step 2: Run the test to verify it fails** Run: `cargo test -p chanora_protocol server_socket_from_config_prefers_prefetched_address` Expected: FAIL to compile with `cannot find function server_socket_from_config`. - [ ] **Step 3: Add the helper and wire connection task** Add this helper near `resolve_server_socket()`: ```rust fn server_socket_from_config(cfg: &ConnectConfig) -> Option { cfg.resolved_address } ``` Find the call in `connection_task` that currently resolves the address, shaped like: ```rust let server = resolve_server_socket(&cfg.address).await?; ``` Replace it with: ```rust let server = match server_socket_from_config(&cfg) { Some(addr) => { info!( target: "chanora_protocol", input = %cfg.address, resolved = %addr, "using prefetched server address" ); addr } None => resolve_server_socket(&cfg.address).await?, }; ``` - [ ] **Step 4: Run protocol tests** Run: `cargo test -p chanora_protocol server_socket_from_config_prefers_prefetched_address` Expected: PASS. - [ ] **Step 5: Run broader protocol tests** Run: `cargo test -p chanora_protocol` Expected: PASS. - [ ] **Step 6: Commit** ```bash git add crates/chanora_protocol/src/adapter.rs git commit -m "fix(protocol): reuse prefetched server address" ``` --- ### Task 3: Core Prefetch Cache Data Model **Files:** - Modify: `core/chanora_core/src/lib.rs` - [ ] **Step 1: Write failing cache tests** Add these tests inside `#[cfg(test)] mod tests` in `core/chanora_core/src/lib.rs`: ```rust #[test] fn resolution_prefetch_cache_returns_fresh_exact_match() { let mut cache = ResolutionPrefetchCache::default(); let now = std::time::Instant::now(); let addr: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap(); let generation = cache.begin(" Example.COM "); cache.store_success(generation, " Example.COM ", addr, now); assert_eq!(cache.fresh_match("example.com", now), Some(addr)); } #[test] fn resolution_prefetch_cache_ignores_stale_entries() { let mut cache = ResolutionPrefetchCache::default(); let now = std::time::Instant::now(); let addr: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap(); let generation = cache.begin("example.com"); cache.store_success(generation, "example.com", addr, now - RESOLUTION_PREFETCH_TTL - std::time::Duration::from_secs(1)); assert_eq!(cache.fresh_match("example.com", now), None); } #[test] fn resolution_prefetch_cache_ignores_different_hosts() { let mut cache = ResolutionPrefetchCache::default(); let now = std::time::Instant::now(); let addr: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap(); let generation = cache.begin("example.com"); cache.store_success(generation, "example.com", addr, now); assert_eq!(cache.fresh_match("other.example.com", now), None); } #[test] fn resolution_prefetch_cache_ignores_stale_generation_completion() { let mut cache = ResolutionPrefetchCache::default(); let now = std::time::Instant::now(); let first: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap(); let second: std::net::SocketAddr = "127.0.0.2:9987".parse().unwrap(); let old_generation = cache.begin("example.com"); let new_generation = cache.begin("example.com"); cache.store_success(new_generation, "example.com", second, now); cache.store_success(old_generation, "example.com", first, now); assert_eq!(cache.fresh_match("example.com", now), Some(second)); } ``` - [ ] **Step 2: Run tests to verify they fail** Run: `cargo test -p chanora_core resolution_prefetch_cache --lib` Expected: FAIL to compile with missing `ResolutionPrefetchCache` and `RESOLUTION_PREFETCH_TTL`. - [ ] **Step 3: Add the cache model** Add near `NetworkDiagnostics` in `core/chanora_core/src/lib.rs`: ```rust const RESOLUTION_PREFETCH_TTL: Duration = Duration::from_secs(120); #[derive(Debug, Clone)] struct ResolutionPrefetchEntry { normalized_host: String, resolved_address: std::net::SocketAddr, completed_at: std::time::Instant, generation: u64, } #[derive(Debug, Default)] struct ResolutionPrefetchCache { latest_generation: u64, entry: Option, last_failure: Option, } impl ResolutionPrefetchCache { fn begin(&mut self, host: &str) -> u64 { self.latest_generation = self.latest_generation.saturating_add(1); self.last_failure = None; let _ = normalize_prefetch_host(host); self.latest_generation } fn store_success( &mut self, generation: u64, host: &str, resolved_address: std::net::SocketAddr, completed_at: std::time::Instant, ) { if generation != self.latest_generation { return; } self.entry = Some(ResolutionPrefetchEntry { normalized_host: normalize_prefetch_host(host), resolved_address, completed_at, generation, }); self.last_failure = None; } fn store_failure(&mut self, generation: u64, error: String) { if generation != self.latest_generation { return; } self.last_failure = Some(error); } fn fresh_match(&self, host: &str, now: std::time::Instant) -> Option { let normalized = normalize_prefetch_host(host); let entry = self.entry.as_ref()?; if entry.normalized_host != normalized { return None; } if now.duration_since(entry.completed_at) > RESOLUTION_PREFETCH_TTL { return None; } Some(entry.resolved_address) } } fn normalize_prefetch_host(host: &str) -> String { host.trim().to_lowercase() } ``` - [ ] **Step 4: Run focused core tests** Run: `cargo test -p chanora_core resolution_prefetch_cache --lib` Expected: all four tests PASS. - [ ] **Step 5: Commit** ```bash git add core/chanora_core/src/lib.rs git commit -m "feat(core): add resolution prefetch cache" ``` --- ### Task 4: Core Prefetch API And Connect Cache Lookup **Files:** - Modify: `core/chanora_core/src/lib.rs` - [ ] **Step 1: Write failing session tests** Add this test in `#[cfg(test)] mod tests` in `core/chanora_core/src/lib.rs`: ```rust #[tokio::test] async fn session_uses_fresh_prefetched_resolution_for_connect_config() { let session = ChanoraSession::new(); let addr: std::net::SocketAddr = "127.0.0.1:9987".parse().unwrap(); { let mut cache = session.resolution_prefetch.lock().await; let generation = cache.begin("example.com"); cache.store_success(generation, "example.com", addr, std::time::Instant::now()); } let mut cfg = ConnectConfig::default(); cfg.address = " example.com ".to_string(); session.apply_prefetched_resolution(&mut cfg).await; assert_eq!(cfg.resolved_address, Some(addr)); } ``` - [ ] **Step 2: Run the test to verify it fails** Run: `cargo test -p chanora_core session_uses_fresh_prefetched_resolution_for_connect_config --lib` Expected: FAIL to compile with missing `resolution_prefetch` and `apply_prefetched_resolution`. - [ ] **Step 3: Add session field and constructor initialization** Add to `ChanoraSession`: ```rust /// Invisible server-address prefetch cache. Warmed by Flutter typing /// but validated by Rust before Connect can reuse it. resolution_prefetch: Arc>, ``` Initialize in `ChanoraSession::new()`: ```rust resolution_prefetch: Arc::new(Mutex::new(ResolutionPrefetchCache::default())), ``` - [ ] **Step 4: Add connect-time cache lookup helper** Add methods inside `impl ChanoraSession` before `connect()`: ```rust async fn apply_prefetched_resolution(&self, cfg: &mut ConnectConfig) { let host = cfg.address.trim(); if host.is_empty() { return; } let resolved = { let cache = self.resolution_prefetch.lock().await; cache.fresh_match(host, std::time::Instant::now()) }; match resolved { Some(addr) => { info!( target: "chanora_core", host = %host, resolved = %addr, "connect using prefetched resolution" ); cfg.resolved_address = Some(addr); } None => { info!(target: "chanora_core", host = %host, "connect prefetch miss or stale"); } } } ``` In `connect()`, after identity resolution and before `ProtocolClient::connect(cfg.clone())`, add: ```rust self.apply_prefetched_resolution(&mut cfg).await; ``` - [ ] **Step 5: Add prefetch scheduling API in core** Add this method in `impl ChanoraSession`: ```rust pub async fn prefetch_server_resolution(&self, host: String) -> Result<(), CoreError> { let normalized = normalize_prefetch_host(&host); if normalized.is_empty() { return Ok(()); } let generation = { let mut cache = self.resolution_prefetch.lock().await; cache.begin(&normalized) }; let cache = self.resolution_prefetch.clone(); tokio::spawn(async move { info!(target: "chanora_core", host = %normalized, "resolution prefetch started"); let result = resolve_prefetch_socket(&normalized).await; let mut guard = cache.lock().await; match result { Ok(addr) => { info!( target: "chanora_core", host = %normalized, resolved = %addr, "resolution prefetch result" ); guard.store_success(generation, &normalized, addr, std::time::Instant::now()); } Err(err) => { warn!( target: "chanora_core", host = %normalized, error = %err, "resolution prefetch failed" ); guard.store_failure(generation, err.to_string()); } } }); Ok(()) } ``` Add this helper outside `impl ChanoraSession`: ```rust async fn resolve_prefetch_socket(host: &str) -> Result { let resolver = chanora_resolver::ChanoraResolver::new() .map_err(|err| CoreError::Protocol(chanora_protocol::ProtocolError::DnsFailed { host: host.to_string(), reason: format!("resolver initialization failed: {err}"), }))?; let resolved = resolver .resolve_client_address(host) .await .map_err(|err| CoreError::Protocol(chanora_protocol::ProtocolError::DnsFailed { host: host.to_string(), reason: err.to_string(), }))?; resolved .parse::() .map_err(|err| CoreError::Protocol(chanora_protocol::ProtocolError::DnsFailed { host: host.to_string(), reason: format!("resolver returned invalid socket address '{resolved}': {err}"), })) } ``` - [ ] **Step 6: Run focused core tests** Run: `cargo test -p chanora_core session_uses_fresh_prefetched_resolution_for_connect_config --lib` Expected: PASS. - [ ] **Step 7: Run core tests** Run: `cargo test -p chanora_core --lib` Expected: PASS. - [ ] **Step 8: Commit** ```bash git add core/chanora_core/src/lib.rs git commit -m "feat(core): prefetch server resolution" ``` --- ### Task 5: Bridge API Exposes Prefetch **Files:** - Modify: `crates/chanora_bridge/src/api.rs` - Regenerate: `crates/chanora_bridge/src/frb_generated.rs` - Regenerate: `apps/chanora_flutter/lib/src/rust/api.dart` - Regenerate: `apps/chanora_flutter/lib/src/rust/api.freezed.dart` - Regenerate: `apps/chanora_flutter/lib/src/rust/frb_generated.dart` - [ ] **Step 1: Add bridge function** Add after `connect()` in `crates/chanora_bridge/src/api.rs`: ```rust /// Warm server address resolution for the active host field. This is /// intentionally fire-and-forget from the UI perspective: it schedules /// Rust-side prefetch work and never opens a TS3 session. pub async fn prefetch_server_resolution(host: String) -> Result<(), BridgeError> { runtime() .spawn(async move { session().prefetch_server_resolution(host).await }) .await .map_err(|e| task_join_error("prefetch_server_resolution", e))??; Ok(()) } ``` - [ ] **Step 2: Regenerate flutter_rust_bridge bindings** Run from repo root: ```bash flutter_rust_bridge_codegen generate ``` Expected: generated files update and `apps/chanora_flutter/lib/src/rust/api.dart` contains: ```dart Future prefetchServerResolution({required String host}) => RustLib .instance .api .crateApiPrefetchServerResolution(host: host); ``` - [ ] **Step 3: Run bridge/core compile check** Run: `cargo test -p chanora_bridge --lib` Expected: PASS. - [ ] **Step 4: Run Flutter analyzer smoke check** Run: `cd apps/chanora_flutter && flutter analyze` Expected: no new errors related to generated bindings. - [ ] **Step 5: Commit** ```bash git add crates/chanora_bridge/src/api.rs crates/chanora_bridge/src/frb_generated.rs apps/chanora_flutter/lib/src/rust/api.dart apps/chanora_flutter/lib/src/rust/api.freezed.dart apps/chanora_flutter/lib/src/rust/frb_generated.dart git commit -m "feat(bridge): expose resolution prefetch" ``` --- ### Task 6: Flutter Prefetch Scheduler Helper **Files:** - Create: `apps/chanora_flutter/lib/services/server_resolution_prefetch_scheduler.dart` - Create: `apps/chanora_flutter/test/services/server_resolution_prefetch_scheduler_test.dart` - [ ] **Step 1: Write failing scheduler tests** Create `apps/chanora_flutter/test/services/server_resolution_prefetch_scheduler_test.dart`: ```dart import 'package:flutter_test/flutter_test.dart'; import 'package:chanora_flutter/services/server_resolution_prefetch_scheduler.dart'; void main() { test('debounces host edits and prefetches latest trimmed host', () async { final calls = []; final scheduler = ServerResolutionPrefetchScheduler( delay: const Duration(milliseconds: 20), prefetch: (host) async => calls.add(host), ); scheduler.schedule(' first.example.com '); scheduler.schedule(' second.example.com '); await Future.delayed(const Duration(milliseconds: 35)); expect(calls, ['second.example.com']); scheduler.dispose(); }); test('skips empty hosts', () async { final calls = []; final scheduler = ServerResolutionPrefetchScheduler( delay: const Duration(milliseconds: 10), prefetch: (host) async => calls.add(host), ); scheduler.schedule(' '); await Future.delayed(const Duration(milliseconds: 25)); expect(calls, isEmpty); scheduler.dispose(); }); test('dispose cancels pending prefetch', () async { final calls = []; final scheduler = ServerResolutionPrefetchScheduler( delay: const Duration(milliseconds: 30), prefetch: (host) async => calls.add(host), ); scheduler.schedule('example.com'); scheduler.dispose(); await Future.delayed(const Duration(milliseconds: 45)); expect(calls, isEmpty); }); } ``` - [ ] **Step 2: Run tests to verify they fail** Run: `cd apps/chanora_flutter && flutter test test/services/server_resolution_prefetch_scheduler_test.dart` Expected: FAIL to compile with missing `server_resolution_prefetch_scheduler.dart`. - [ ] **Step 3: Implement scheduler** Create `apps/chanora_flutter/lib/services/server_resolution_prefetch_scheduler.dart`: ```dart import 'dart:async'; typedef ServerResolutionPrefetch = Future Function(String host); class ServerResolutionPrefetchScheduler { ServerResolutionPrefetchScheduler({ required this.prefetch, this.delay = const Duration(milliseconds: 700), }); final ServerResolutionPrefetch prefetch; final Duration delay; Timer? _timer; bool _disposed = false; void schedule(String rawHost) { if (_disposed) return; _timer?.cancel(); final host = rawHost.trim(); if (host.isEmpty) return; _timer = Timer(delay, () { if (_disposed) return; unawaited(prefetch(host)); }); } void dispose() { _disposed = true; _timer?.cancel(); _timer = null; } } ``` - [ ] **Step 4: Run scheduler tests** Run: `cd apps/chanora_flutter && flutter test test/services/server_resolution_prefetch_scheduler_test.dart` Expected: PASS. - [ ] **Step 5: Format Dart files** Run: `cd apps/chanora_flutter && dart format lib/services/server_resolution_prefetch_scheduler.dart test/services/server_resolution_prefetch_scheduler_test.dart` Expected: files formatted, no errors. - [ ] **Step 6: Commit** ```bash git add apps/chanora_flutter/lib/services/server_resolution_prefetch_scheduler.dart apps/chanora_flutter/test/services/server_resolution_prefetch_scheduler_test.dart git commit -m "feat(ui): add resolution prefetch scheduler" ``` --- ### Task 7: Wire Prefetch Scheduler Into Home UI **Files:** - Modify: `apps/chanora_flutter/lib/main.dart` - [ ] **Step 1: Add imports and state fields** In `apps/chanora_flutter/lib/main.dart`, add: ```dart import 'services/server_resolution_prefetch_scheduler.dart'; ``` Inside `_BetaHomeState`, add: ```dart late final ServerResolutionPrefetchScheduler _resolutionPrefetch; ``` - [ ] **Step 2: Initialize scheduler and listener** In `initState()`, before `_eventsSub = rust.eventsStream().listen(_onEvent);`, add: ```dart _resolutionPrefetch = ServerResolutionPrefetchScheduler( prefetch: (host) => rust.prefetchServerResolution(host: host), ); _hostCtl.addListener(_onHostEdited); ``` Add method in `_BetaHomeState`: ```dart void _onHostEdited() { _resolutionPrefetch.schedule(_hostCtl.text); } ``` - [ ] **Step 3: Schedule prefetch after settings load** In `_loadUiSettings()`, after setting host/nickname from settings and before the `catch`, add: ```dart if (settings.host.isNotEmpty) { _resolutionPrefetch.schedule(settings.host); } ``` Ensure this is not duplicated if an existing `_hostCtl.text = settings.host;` listener already schedules it. If both paths fire, keep only the explicit `_resolutionPrefetch.schedule(settings.host);` and temporarily remove/re-add the listener around `_hostCtl.text = settings.host`, or accept the duplicate because scheduler debounce collapses it. Prefer accepting the duplicate for minimal change. - [ ] **Step 4: Dispose scheduler and listener** In `dispose()`, before `_hostCtl.dispose();`, add: ```dart _hostCtl.removeListener(_onHostEdited); _resolutionPrefetch.dispose(); ``` - [ ] **Step 5: Run focused Flutter tests** Run: `cd apps/chanora_flutter && flutter test test/services/server_resolution_prefetch_scheduler_test.dart test/services/connection_phase_state_test.dart` Expected: PASS. - [ ] **Step 6: Run analyzer** Run: `cd apps/chanora_flutter && flutter analyze` Expected: no new analyzer errors. - [ ] **Step 7: Commit** ```bash git add apps/chanora_flutter/lib/main.dart git commit -m "feat(ui): prefetch active server address" ``` --- ### Task 8: End-To-End Verification On Android **Files:** - No source changes expected unless verification finds a bug. - [ ] **Step 1: Run focused Rust tests** Run: `cargo test -p chanora_resolver -p chanora_protocol -p chanora_core --lib` Expected: PASS. - [ ] **Step 2: Run focused Flutter tests** Run: `cd apps/chanora_flutter && flutter test test/services/server_resolution_prefetch_scheduler_test.dart test/services/connection_phase_state_test.dart test/services/ts3_server_link_test.dart` Expected: PASS. - [ ] **Step 3: Build Android debug APK** Run: `cd apps/chanora_flutter && flutter build apk --debug` Expected: `✓ Built build/app/outputs/flutter-apk/app-debug.apk`. - [ ] **Step 4: Install and launch through ADB** Run from `apps/chanora_flutter`: ```bash adb install -r "build/app/outputs/flutter-apk/app-debug.apk" adb shell am force-stop app.chanora.chanora_flutter adb logcat -c adb shell monkey -p app.chanora.chanora_flutter -c android.intent.category.LAUNCHER 1 ``` Expected: install success and app launches. - [ ] **Step 5: Allow prefetch, connect, and inspect logs** Run from repo root: ```bash sleep 2 adb shell input tap 354 1363 sleep 8 adb logcat -d -v time | rg "resolution prefetch|prefetched|client request resolution|server address resolved|FATAL|AndroidRuntime" ``` Expected: - No app `FATAL` lines. - Logs include `resolution prefetch started` and either `resolution prefetch result` or safe failure. - If prefetch completed before tap, logs include `connect using prefetched resolution` and protocol logs include `using prefetched server address`. - If prefetch did not complete before tap, connect still succeeds through normal resolver path. - [ ] **Step 6: Inspect UI hierarchy** Run: ```bash adb shell uiautomator dump /sdcard/window.xml adb exec-out cat /sdcard/window.xml ``` Expected: connected server view with channel count, not stuck on `Synchronizing...`. - [ ] **Step 7: Final status check** Run: `git status --short` Expected: no unintended source changes. Unrelated existing `config.json` may remain untracked and must not be committed. --- ## Self-Review Spec coverage: - Active-field-only prefetch: Tasks 6 and 7. - Rust-owned resolver cache and exact-match validation: Tasks 3 and 4. - 2-minute TTL: Task 3. - Invisible UI: Tasks 6 and 7 avoid any UI status changes. - No bookmark fan-out: Task 7 wires only `_hostCtl` and settings-loaded host. - No TS3 session before Connect: Task 4 only resolves address; Task 2 only uses resolved address during actual protocol connect. - Diagnostics: Tasks 2 and 4 add logs for prefetch and cache hit/miss. - Testing and Android smoke: Task 8. Completeness scan: no incomplete markers are used. Each code-changing task includes concrete code snippets and commands. Type consistency: `resolved_address` is added to protocol `ConnectConfig`, core uses `cfg.resolved_address`, bridge exposes `prefetch_server_resolution`, and Dart uses generated `prefetchServerResolution`.