Files
chanora/docs/superpowers/plans/2026-05-28-server-resolution-prefetch.md

27 KiB

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<SocketAddr> 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:

#[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:

#[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<std::net::SocketAddr>,
    /// Nickname to use on the server.
    pub nickname: String,
    /// Optional server password.
    pub password: Option<String>,
    /// 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<String>,
    /// 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
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:

#[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():

fn server_socket_from_config(cfg: &ConnectConfig) -> Option<SocketAddr> {
    cfg.resolved_address
}

Find the call in connection_task that currently resolves the address, shaped like:

let server = resolve_server_socket(&cfg.address).await?;

Replace it with:

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
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:

#[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:

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<ResolutionPrefetchEntry>,
    last_failure: Option<String>,
}

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<std::net::SocketAddr> {
        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
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:

#[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:

/// Invisible server-address prefetch cache. Warmed by Flutter typing
/// but validated by Rust before Connect can reuse it.
resolution_prefetch: Arc<Mutex<ResolutionPrefetchCache>>,

Initialize in ChanoraSession::new():

resolution_prefetch: Arc::new(Mutex::new(ResolutionPrefetchCache::default())),
  • Step 4: Add connect-time cache lookup helper

Add methods inside impl ChanoraSession before connect():

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:

self.apply_prefetched_resolution(&mut cfg).await;
  • Step 5: Add prefetch scheduling API in core

Add this method in impl ChanoraSession:

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:

async fn resolve_prefetch_socket(host: &str) -> Result<std::net::SocketAddr, CoreError> {
    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::<std::net::SocketAddr>()
        .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
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:

/// 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:

flutter_rust_bridge_codegen generate

Expected: generated files update and apps/chanora_flutter/lib/src/rust/api.dart contains:

Future<void> 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
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:

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 = <String>[];
    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<void>.delayed(const Duration(milliseconds: 35));

    expect(calls, ['second.example.com']);
    scheduler.dispose();
  });

  test('skips empty hosts', () async {
    final calls = <String>[];
    final scheduler = ServerResolutionPrefetchScheduler(
      delay: const Duration(milliseconds: 10),
      prefetch: (host) async => calls.add(host),
    );

    scheduler.schedule('   ');
    await Future<void>.delayed(const Duration(milliseconds: 25));

    expect(calls, isEmpty);
    scheduler.dispose();
  });

  test('dispose cancels pending prefetch', () async {
    final calls = <String>[];
    final scheduler = ServerResolutionPrefetchScheduler(
      delay: const Duration(milliseconds: 30),
      prefetch: (host) async => calls.add(host),
    );

    scheduler.schedule('example.com');
    scheduler.dispose();
    await Future<void>.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:

import 'dart:async';

typedef ServerResolutionPrefetch = Future<void> 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
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:

import 'services/server_resolution_prefetch_scheduler.dart';

Inside _BetaHomeState, add:

late final ServerResolutionPrefetchScheduler _resolutionPrefetch;
  • Step 2: Initialize scheduler and listener

In initState(), before _eventsSub = rust.eventsStream().listen(_onEvent);, add:

_resolutionPrefetch = ServerResolutionPrefetchScheduler(
  prefetch: (host) => rust.prefetchServerResolution(host: host),
);
_hostCtl.addListener(_onHostEdited);

Add method in _BetaHomeState:

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:

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:

_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
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:

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:

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:

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.