- extract the headless search out of `easy` into an engine that
measures the baseline, resolves the target, and drives the
producer/consumer loop, so the CLI and web share one search
- make `State` fully observable — structured discovery feed, run phase,
baseline/target, and final outcome — and render the same snapshots two
ways: the in-place terminal panel and an SSE stream
- serve a small embedded page (HTML/JS/Pico baked in via include_str!)
on a hand-rolled hyper service: POST /api/run runs one search at a
time, GET /api/events streams live snapshots as JSON until it settles
- open the UI on a bare invocation (double-clicked binary); `web` with
--host/--port/--no-open controls it explicitly
- bump to 0.2.0
- compute the IP column width from the widest address instead of a
fixed 39 (which left a big gap for IPv4-only output)
- left-align the latency and speed columns so the header labels line up
with their values, instead of right-aligning into a wide field
- keep the colored speed as the last column so it needs no width padding
- drop the *8 bit conversion so throughput is MB/s, matching what
clients like v2rayN display
- rename speed_mbps -> speed_mbs across speed/report/commands
- narrow the ranking bucket to 0.1 MB/s so slow (~0.1-0.3 MB/s) nodes
still separate by speed instead of collapsing into one bucket
- update table/CSV/alias labels, color thresholds, --min-speed help,
and README
- lat-top now defaults to 0 so every IP within --lat-max feeds the speed
stage, instead of capping at the fastest 30
- speed-top now defaults to 0 so every IP passing --min-speed is written
to result.txt, instead of capping at 10
- downstream truncation already treats 0 as keep-all; update README
- sample each CF segment in proportion to its Laplace-smoothed valid-hit
rate (hits+1)/(attempts+2), so segments that keep failing fade out and
the probe budget flows to ones actually yielding IPs
- switch to a FuturesUnordered scheduler that refills each freed slot
using the weights learned so far, giving a live feedback loop instead
of the prefetch-decoupled buffer_unordered
- untried segments start at 0.5 for fair exploration; the smoothing floor
keeps a starved segment from being permanently killed off
- classify each result as ok / too slow / failed(reason) instead of a
bare pass-or-fail, mirroring the ping stage's live feed
- surface a concise reason (timeout, tcp connect, ws handshake, http
status, ...) from the error's outermost context
- label the inner TLS handshake so its failure reads cleanly instead of
dumping a raw rustls error
- time several round-trips on the warm connection and report the
fastest, shedding transient network jitter
- time the warm-up request too and fall back to it if the node won't
honour keep-alive, instead of failing the IP outright
- stop at the first reuse failure and keep the samples already gathered
- restore the auto speed timeout to 10s as a hard cap
- measure throughput over 500ms windows from the first body byte and
return as soon as a window stops climbing (TCP slow start is over),
so the result is the real steady-state speed, not a slow-start-dragged
average
- fall back to the overall average if the transfer ends before settling
- exclude the one-time tunnel + inner TLS setup from the timing, which
was inflating latency ~5x over the real per-request RTT
- send a throwaway warm-up request, then time a second request on the
warm keep-alive connection (matches what clients like v2rayN report)
- extract request_once helper for the keep-alive GET + response parse
- replace the two-phase availability pre-scan with a single-stage
continuous stream that round-robins segments and keeps concurrency
probes in flight, so a slow unreachable IP only blocks its own slot
- drop the now-unused --probe-sample knob
- show per-probe reasons (unreachable / too fast / too slow / loss) in
the live feed across all stages
- cut the auto speed timeout to 5s