yslow 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. yslow-0.1.0/.claude/settings.local.json +10 -0
  2. yslow-0.1.0/.gitignore +6 -0
  3. yslow-0.1.0/CLAUDE.md +22 -0
  4. yslow-0.1.0/LICENSE +21 -0
  5. yslow-0.1.0/PKG-INFO +84 -0
  6. yslow-0.1.0/README.md +57 -0
  7. yslow-0.1.0/TODO.md +41 -0
  8. yslow-0.1.0/check.sh +19 -0
  9. yslow-0.1.0/docs/dataflow.md +69 -0
  10. yslow-0.1.0/docs/metrics.md +55 -0
  11. yslow-0.1.0/logs/2026-03-21-continuous-monitoring.md +100 -0
  12. yslow-0.1.0/logs/2026-03-21-ui-overhaul.md +54 -0
  13. yslow-0.1.0/logs/2026-03-22-data-model-cleanup.md +16 -0
  14. yslow-0.1.0/logs/2026-03-22-remove-summary-cleanup-tests.md +20 -0
  15. yslow-0.1.0/logs/2026-03-22-ui-status-and-diagnosis.md +22 -0
  16. yslow-0.1.0/pyproject.toml +48 -0
  17. yslow-0.1.0/scratch.txt +22 -0
  18. yslow-0.1.0/tests/__init__.py +0 -0
  19. yslow-0.1.0/tests/conftest.py +28 -0
  20. yslow-0.1.0/tests/snapshots/all_good_watch.txt +10 -0
  21. yslow-0.1.0/tests/snapshots/everything_failed_watch.txt +14 -0
  22. yslow-0.1.0/tests/snapshots/one_issue_watch.txt +13 -0
  23. yslow-0.1.0/tests/test_common.py +18 -0
  24. yslow-0.1.0/tests/test_diagnosis.py +114 -0
  25. yslow-0.1.0/tests/test_models.py +120 -0
  26. yslow-0.1.0/tests/test_net.py +62 -0
  27. yslow-0.1.0/tests/test_probe.py +70 -0
  28. yslow-0.1.0/tests/test_snapshots.py +181 -0
  29. yslow-0.1.0/tests/test_watch.py +147 -0
  30. yslow-0.1.0/uv.lock +284 -0
  31. yslow-0.1.0/yslow/__init__.py +41 -0
  32. yslow-0.1.0/yslow/__main__.py +6 -0
  33. yslow-0.1.0/yslow/checks/__init__.py +0 -0
  34. yslow-0.1.0/yslow/checks/gateway.py +34 -0
  35. yslow-0.1.0/yslow/checks/internet.py +38 -0
  36. yslow-0.1.0/yslow/diagnosis.py +181 -0
  37. yslow-0.1.0/yslow/models.py +116 -0
  38. yslow-0.1.0/yslow/net.py +30 -0
  39. yslow-0.1.0/yslow/probe.py +62 -0
  40. yslow-0.1.0/yslow/runner.py +21 -0
  41. yslow-0.1.0/yslow/ui/__init__.py +0 -0
  42. yslow-0.1.0/yslow/ui/common.py +37 -0
  43. yslow-0.1.0/yslow/ui/watch.py +147 -0
@@ -0,0 +1,10 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(uv run:*)",
5
+ "Bash(./check.sh 2>&1)",
6
+ "Bash(./check.sh)",
7
+ "WebSearch"
8
+ ]
9
+ }
10
+ }
yslow-0.1.0/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ *.egg-info/
5
+ .pytest_cache/
6
+ .ruff_cache/
yslow-0.1.0/CLAUDE.md ADDED
@@ -0,0 +1,22 @@
1
+ # Claude context
2
+
3
+ ## Quick reference
4
+
5
+ - `./check.sh` — run all checks (format, lint, typecheck, tests)
6
+ - `uv run yslow` — run the tool
7
+ - `uv run pytest` — tests only
8
+ - `uv run ty check` — type check only
9
+ - `uv run ruff check --fix` — lint
10
+ - `uv run ruff format` — format
11
+ - `uv run pytest tests/test_snapshots.py --snapshot-update` — regenerate UI snapshots after output changes
12
+
13
+ ## Key files
14
+
15
+ - `TODO.md` — feature backlog and progress tracking
16
+ - `docs/` — design and reference documentation (metrics, etc.)
17
+ - `logs/` — session logs recording what was changed and why
18
+
19
+ ## Guidelines
20
+
21
+ - Update or add docs in `docs/` when changing metrics, thresholds, or diagnostic logic
22
+ - At the end of a session, write a log entry in `logs/` (format: `YYYY-MM-DD-short-description.md`) summarizing what changed and any open questions
yslow-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wesley Ellis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
yslow-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.4
2
+ Name: yslow
3
+ Version: 0.1.0
4
+ Summary: Diagnose why your network connection is slow
5
+ Project-URL: Homepage, https://codeberg.org/tahnok/yslow
6
+ Project-URL: Repository, https://codeberg.org/tahnok/yslow
7
+ Project-URL: Issues, https://codeberg.org/tahnok/yslow/issues
8
+ Author-email: Wesley Ellis <wesley@tahnok.ca>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: diagnostics,latency,network,troubleshooting,wifi
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: System Administrators
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: System :: Networking :: Monitoring
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: rich>=14.3.3
26
+ Description-Content-Type: text/markdown
27
+
28
+ # yslow
29
+
30
+ A command-line tool that diagnoses why your network connection is slow (or feels slow).
31
+
32
+ Instead of just reporting numbers, yslow isolates *where* the problem is — your local network (wifi/router), your ISP, or the broader internet — and tells you in plain language.
33
+
34
+ ## What it checks
35
+
36
+ - **Gateway latency** — how fast your machine can talk to your router. High latency or jitter here points to wifi congestion, a bad cable, or an overloaded router.
37
+ - **Internet latency** — TCP handshake time to multiple well-known servers (Cloudflare, Google, OpenDNS).
38
+ - **Packet loss** — at both the local and internet level.
39
+ - **Jitter** — variance in latency, which causes buffering, choppy calls, and inconsistent page loads.
40
+
41
+ By comparing gateway vs internet results, yslow isolates the problem:
42
+
43
+ | Gateway | Internet | Diagnosis |
44
+ |---------|----------|-----------|
45
+ | Fast | Fast | Connection is healthy |
46
+ | Slow | Slow | Local network is the bottleneck (router/wifi) |
47
+ | Fast | Slow | ISP or internet routing issue |
48
+ | Unreachable | Slow | Unusual setup, but internet works |
49
+
50
+ ## Usage
51
+
52
+ ```
53
+ uv run yslow
54
+ ```
55
+
56
+ Requires Python 3.10+ and Linux. No dependencies beyond the standard library.
57
+
58
+ ## How it works
59
+
60
+ yslow uses **TCP handshake timing** rather than ICMP ping. A TCP connect to port 443 measures the same network round-trip as ping, but works without root privileges and inside containers where ICMP is often blocked.
61
+
62
+ For the gateway, it tries common TCP ports (80, 443, 53) and falls back to ICMP ping if needed.
63
+
64
+ ## Development
65
+
66
+ ```
67
+ uv sync # install deps
68
+ ./check.sh # run all checks (format, lint, typecheck, tests)
69
+ uv run pytest # run tests only
70
+ uv run ty check # type check only
71
+ uv run ruff check --fix # lint
72
+ uv run ruff format # format
73
+ ```
74
+
75
+ ## Documentation
76
+
77
+ See [`docs/metrics.md`](docs/metrics.md) for details on what yslow measures (RTT, packet loss, jitter), how each metric is calculated, and the thresholds used for diagnosis.
78
+
79
+ ## Planned
80
+
81
+ - DNS resolution timing
82
+ - Bandwidth / throughput estimation
83
+ - Bufferbloat detection (latency under load)
84
+ - Route analysis (where along the path is the bottleneck)
yslow-0.1.0/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # yslow
2
+
3
+ A command-line tool that diagnoses why your network connection is slow (or feels slow).
4
+
5
+ Instead of just reporting numbers, yslow isolates *where* the problem is — your local network (wifi/router), your ISP, or the broader internet — and tells you in plain language.
6
+
7
+ ## What it checks
8
+
9
+ - **Gateway latency** — how fast your machine can talk to your router. High latency or jitter here points to wifi congestion, a bad cable, or an overloaded router.
10
+ - **Internet latency** — TCP handshake time to multiple well-known servers (Cloudflare, Google, OpenDNS).
11
+ - **Packet loss** — at both the local and internet level.
12
+ - **Jitter** — variance in latency, which causes buffering, choppy calls, and inconsistent page loads.
13
+
14
+ By comparing gateway vs internet results, yslow isolates the problem:
15
+
16
+ | Gateway | Internet | Diagnosis |
17
+ |---------|----------|-----------|
18
+ | Fast | Fast | Connection is healthy |
19
+ | Slow | Slow | Local network is the bottleneck (router/wifi) |
20
+ | Fast | Slow | ISP or internet routing issue |
21
+ | Unreachable | Slow | Unusual setup, but internet works |
22
+
23
+ ## Usage
24
+
25
+ ```
26
+ uv run yslow
27
+ ```
28
+
29
+ Requires Python 3.10+ and Linux. No dependencies beyond the standard library.
30
+
31
+ ## How it works
32
+
33
+ yslow uses **TCP handshake timing** rather than ICMP ping. A TCP connect to port 443 measures the same network round-trip as ping, but works without root privileges and inside containers where ICMP is often blocked.
34
+
35
+ For the gateway, it tries common TCP ports (80, 443, 53) and falls back to ICMP ping if needed.
36
+
37
+ ## Development
38
+
39
+ ```
40
+ uv sync # install deps
41
+ ./check.sh # run all checks (format, lint, typecheck, tests)
42
+ uv run pytest # run tests only
43
+ uv run ty check # type check only
44
+ uv run ruff check --fix # lint
45
+ uv run ruff format # format
46
+ ```
47
+
48
+ ## Documentation
49
+
50
+ See [`docs/metrics.md`](docs/metrics.md) for details on what yslow measures (RTT, packet loss, jitter), how each metric is calculated, and the thresholds used for diagnosis.
51
+
52
+ ## Planned
53
+
54
+ - DNS resolution timing
55
+ - Bandwidth / throughput estimation
56
+ - Bufferbloat detection (latency under load)
57
+ - Route analysis (where along the path is the bottleneck)
yslow-0.1.0/TODO.md ADDED
@@ -0,0 +1,41 @@
1
+ # TODO
2
+
3
+ ## Testing
4
+ - [x] Add test suite (unit tests for parsing, result building; mock-based tests for probing)
5
+
6
+ ## New diagnostics
7
+ - [ ] DNS resolution timing — measure lookup time for common domains, detect slow/broken resolvers
8
+ - [ ] Bufferbloat detection — measure latency while simultaneously loading the connection
9
+ - [ ] Speed test — download/upload throughput estimation
10
+ - [ ] IPv6 health — check if IPv6 is configured but broken/slower than IPv4 (common cause of "feels slow" when apps try IPv6 first and fall back)
11
+ - [ ] Wifi signal strength — read signal/noise from iw/iwconfig, flag weak signal or high noise
12
+ - [ ] Route analysis — traceroute-style hop-by-hop latency to find where the bottleneck is
13
+ - [ ] MTU / fragmentation issues — detect path MTU problems
14
+ - [ ] DNS-over-HTTPS vs plain DNS comparison — detect if DoH config is hurting perceived speed
15
+ - [ ] TLS handshake timing — separate TCP connect from TLS negotiation to isolate slow TLS
16
+ - [ ] Bandwidth asymmetry — flag upload vs download disparity (kills video calls and syncing)
17
+ - [ ] Probe a user-specified target — "why is github.com slow?" mode
18
+ - [ ] Network interface health — read TX/RX errors, drops, retransmits from /proc/net/dev and /proc/net/snmp
19
+ - [ ] Competing traffic detection — check if something else on the machine is saturating the connection
20
+ - [ ] VPN detection, so we can account for extra latency, problems with the gateway
21
+ - [ ] Specific site probe, we can ask the user if a particular site or service is feeling slow
22
+
23
+ ## Longer-running / monitoring mode
24
+ - [x] Continuous monitoring mode — `--watch` / `-w` flag, configurable interval, live dashboard
25
+ - [x] Session summary on exit (Ctrl+C) — total checks, healthy %, most common issues
26
+ - [x] Terminal bell + OSC 9 desktop notification on problem detection
27
+ - [ ] Stateful check modules — checks that accumulate data, control their own run frequency, produce smarter summaries over time
28
+ - [ ] Summary statistics over a monitoring session (percentiles, worst periods)
29
+ - [ ] Latency-under-load test — run probes while generating traffic to detect congestion/bufferbloat
30
+ - [ ] Historical comparison — save results to a local log, compare "is it worse than usual?"
31
+ - [ ] Time-of-day correlation — in monitoring mode, flag patterns like "always slow at 7pm"
32
+ - [ ] Network condition simulation for testing — Docker/tc/netem, network namespaces, or VMs
33
+
34
+ ## Performance / UX
35
+ - [ ] Parallel probing — probe all targets concurrently instead of sequentially
36
+ - [ ] Colored output (green/yellow/red for OK/warning/problem)
37
+ - [ ] Summary verdict — single top-line sentence like "Your wifi is the bottleneck"
38
+ - [ ] Suggested fixes — actionable next steps based on findings (e.g., "Try switching DNS to 1.1.1.1")
39
+
40
+ ## Platform
41
+ - [ ] macOS support (gateway detection, wifi tools differ)
yslow-0.1.0/check.sh ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+
4
+ run() {
5
+ local label="$1"
6
+ shift
7
+ if ! output=$("$@" 2>&1); then
8
+ echo "=== $label FAILED ==="
9
+ echo "$output"
10
+ exit 1
11
+ fi
12
+ }
13
+
14
+ run "ruff format" uv run ruff format --check .
15
+ run "ruff check" uv run ruff check .
16
+ run "ty" uv run ty check
17
+ run "pytest" uv run pytest -q "$@"
18
+
19
+ echo "All checks passed."
@@ -0,0 +1,69 @@
1
+ # Data flow
2
+
3
+ How a network check becomes a displayed result.
4
+
5
+ ## Models
6
+
7
+ ```
8
+ ProbeResult Raw ping data for one host (RTTs, loss, jitter)
9
+ Issue A single diagnosed problem from one check (kind, severity, description)
10
+ CheckResult Output of one check cycle: timestamp + ProbeResults + Issues
11
+ IssueRecord Aggregation of Issues across multiple CheckResults, grouped by kind
12
+ ```
13
+
14
+ ## Pipeline
15
+
16
+ ```
17
+ ┌─────────────────────────────────────────────────────────────┐
18
+ │ Probes (checks/) │
19
+ │ │
20
+ │ gateway.run() ──→ ProbeResult (1 host) │
21
+ │ internet.run() ──→ list[ProbeResult] (multiple hosts) │
22
+ └──────────────────────────┬──────────────────────────────────┘
23
+
24
+
25
+ ┌─────────────────────────────────────────────────────────────┐
26
+ │ Diagnosis (diagnosis.py) │
27
+ │ │
28
+ │ analyze(gateway, internet) ──→ list[Issue] │
29
+ │ │
30
+ │ Compares probe results against thresholds (see metrics.md)│
31
+ │ to produce issues like "gateway-loss", "isp-latency", etc.│
32
+ └──────────────────────────┬──────────────────────────────────┘
33
+
34
+
35
+ ┌─────────────────────────────────────────────────────────────┐
36
+ │ Runner (runner.py) │
37
+ │ │
38
+ │ run_once() ──→ CheckResult │
39
+ │ │
40
+ │ Bundles timestamp + ProbeResults + Issues into one result.│
41
+ └──────────────────────────┬──────────────────────────────────┘
42
+
43
+
44
+ ┌─────────────────────────────────────────────────────────────┐
45
+ │ Aggregation (models.py) │
46
+ │ │
47
+ │ IssueRecord.from_results(list[CheckResult]) │
48
+ │ ──→ list[IssueRecord] │
49
+ │ │
50
+ │ Groups Issues by kind across all check results. │
51
+ │ Tracks count, last_seen, whether currently active, │
52
+ │ and last observed value. │
53
+ └──────────────────────────┬──────────────────────────────────┘
54
+
55
+
56
+ ┌─────────────────────────────────────────────────────────────┐
57
+ │ UI (ui/) │
58
+ │ │
59
+ │ WatchState holds a rolling buffer of CheckResults. │
60
+ │ On each cycle: │
61
+ │ - Runs a check (run_once) │
62
+ │ - Appends the CheckResult │
63
+ │ - Renders dashboard: │
64
+ │ Status indicator (stable / unstable / problem) │
65
+ │ Issue history (from IssueRecords) │
66
+ │ Probe summary (gateway + internet one-liners) │
67
+ │ - Sends desktop notification on healthy→problem change │
68
+ └─────────────────────────────────────────────────────────────┘
69
+ ```
@@ -0,0 +1,55 @@
1
+ # Metrics
2
+
3
+ yslow measures three things about your network connection. Each is collected by sending a series of probes (currently 5) to each target.
4
+
5
+ ## RTT (round-trip time)
6
+
7
+ The time for a single probe to reach a target and get a response, in milliseconds.
8
+
9
+ We measure RTT using TCP handshakes (SYN → SYN-ACK) on port 443 for internet targets, and TCP or ICMP ping for the gateway. TCP handshake is preferred because it works without elevated privileges and isn't blocked by most firewalls.
10
+
11
+ We report the **average RTT** across all successful probes in a run.
12
+
13
+ ### Thresholds
14
+
15
+ | Target | Elevated | High |
16
+ |--------|----------|------|
17
+ | Gateway (LAN) | > 3 ms | > 10 ms |
18
+ | ISP (internet avg minus gateway avg) | > 50 ms | > 150 ms |
19
+ | Internet (when no gateway data) | > 50 ms | > 150 ms |
20
+
21
+ "Elevated" is a warning; "high" is an error.
22
+
23
+ The ISP threshold uses the difference between internet RTT and gateway RTT to isolate how much latency is added beyond your local network.
24
+
25
+ ## Packet loss
26
+
27
+ The percentage of probes that received no response within the timeout (3 seconds).
28
+
29
+ Any packet loss is flagged as an error. When both gateway and internet loss are measured, we compare them to determine the source:
30
+
31
+ - Internet loss > gateway loss → problem is beyond your local network (ISP/routing)
32
+ - Internet loss ≤ gateway loss → problem is likely your local network (wifi/router)
33
+
34
+ ## Jitter
35
+
36
+ The sample standard deviation of RTTs within a single probe run, in milliseconds. It measures how **consistent** the connection is — a low average RTT with high jitter means some packets are fast and some are slow.
37
+
38
+ High jitter degrades real-time applications (video calls, gaming, VoIP) even when average latency looks acceptable, because individual packets arrive with unpredictable timing.
39
+
40
+ ### How it's calculated
41
+
42
+ Given RTTs `[r1, r2, ..., rn]` with mean `m`:
43
+
44
+ jitter = sqrt(sum((ri - m)^2) / (n - 1))
45
+
46
+ This is the standard sample standard deviation (using Bessel's correction with `n-1`).
47
+
48
+ ### Thresholds
49
+
50
+ | Target | High |
51
+ |--------|------|
52
+ | Gateway (LAN) | > 5 ms |
53
+ | Internet (beyond LAN) | > 20 ms above gateway jitter |
54
+
55
+ Internet jitter subtracts gateway jitter before comparing to the threshold, so local network jitter isn't double-counted. If your gateway has 15 ms jitter and your internet targets have 18 ms jitter, the ISP is only adding ~3 ms of jitter — the local network is the problem, not the ISP.
@@ -0,0 +1,100 @@
1
+ # Session: Continuous monitoring mode
2
+
3
+ Date: 2026-03-21
4
+
5
+ ## What we built
6
+
7
+ Added `--watch` / `-w` flag for continuous monitoring mode. Runs checks on a configurable interval (default 30s), displays a live dashboard, and notifies on problems via terminal bell + OSC 9 desktop notifications.
8
+
9
+ Also unified the one-shot and monitoring output paths so both modes share the same rendering logic.
10
+
11
+ ## Key decisions
12
+
13
+ ### No TUI toolkit
14
+
15
+ Decided against adding a terminal UI library (e.g., rich, textual). The dashboard is a single screen redrawn periodically — `\033[2J\033[H` (clear + home) plus `print()` is sufficient. This preserves the zero-runtime-dependency constraint. If we ever need interactive controls or complex layouts, we can revisit.
16
+
17
+ ### Structured error model: severity + kind, no category enum
18
+
19
+ Initially considered a `Category` enum (LOCAL, ISP, INTERNET, CONNECTIVITY) alongside `Severity`. Decided against it because:
20
+ - As we add more checks (DNS, wifi signal, bufferbloat, etc.), a fixed category enum would need constant expansion
21
+ - The "where" context is better captured in the description string, which is already descriptive
22
+ - Severity is what drives behavior (notifications, healthy/unhealthy state)
23
+
24
+ Each issue has a `kind` string (e.g., `"gateway-latency"`, `"isp-loss"`) for grouping, and `description` + `value` for display. The `message` property combines them: `"LAN: high latency to gateway (71 ms) — wifi congestion"`.
25
+
26
+ ### Issue model: description + value, not a single message string
27
+
28
+ Split the human-readable message into `description` (stable across occurrences) and `value` (the specific measurement). This solved two problems:
29
+ - In monitoring mode, issues need to be grouped by kind — identical descriptions group naturally
30
+ - One-shot mode wants specific values, monitoring history wants the description without values cluttering the grouping
31
+
32
+ The `message` property reconstructs the full string by inserting the value before the em dash.
33
+
34
+ ### Three monitoring states: stable / unstable / problem
35
+
36
+ Replaced the original OK/PROBLEM binary with three states:
37
+ - **Stable**: every check has been healthy, nothing to report
38
+ - **Problem**: current check has issues
39
+ - **Unstable**: current check is healthy, but problems were seen recently — the connection is flaky
40
+
41
+ This better matches the user's mental model. "It's fine now but it was bad 2 minutes ago" is meaningfully different from "it's always been fine."
42
+
43
+ ### Persistent issue history
44
+
45
+ The dashboard shows every issue kind ever seen (within the rolling buffer), with count, last value, and timing. Active issues show `!!` prefix and "ongoing"; resolved ones show ` ` prefix and "Xm ago". This means transitioning from unstable back to problem doesn't lose the history of past issues.
46
+
47
+ ### Notifications only on transition
48
+
49
+ Terminal bell + OSC 9 fire only on healthy-to-problem transitions. Repeating the bell every check cycle during a persistent problem would be unusable.
50
+
51
+ ### Consistent output between modes
52
+
53
+ Both modes now use the same rendering path:
54
+ - Check modules (`gateway`, `internet`) own their `summary()` function for one-line status
55
+ - `_render_probes()` formats the summary lines
56
+ - Issue display uses the same formatting
57
+ - Status line appears in both modes
58
+ - Startup message in both modes ("checking..." / "running initial check...")
59
+
60
+ This eliminated `diagnose()`, `output.py`, and the `quiet` parameter on check modules. Checks are now pure data — they run probes and return results, with no print side effects.
61
+
62
+ ### Labels: LAN and ISP/internet
63
+
64
+ Settled on "LAN:" and "ISP/internet:" as consistent labels across summary lines and issue descriptions. Initially had "Local network:" in issues vs "LAN:" in summaries vs "Internet:" in some places. Unified everything.
65
+
66
+ ### Duration formatting
67
+
68
+ Two formatters: `_format_duration` for countdowns and session summaries (e.g., "2m 05s"), and `_format_ago` for issue history (seconds when under 1 minute, just minutes after — no one cares that an error was "2m 37s ago").
69
+
70
+ ## Architectural direction
71
+
72
+ ### Toward stateful check modules
73
+
74
+ The current check modules are stateless functions. The user expressed interest in making them stateful for monitoring mode — each check could:
75
+ - Decide its own run frequency (`should_run()`)
76
+ - Accumulate data across cycles
77
+ - Produce smarter summaries based on history
78
+
79
+ This would let future checks (DNS timing, wifi signal) run on different schedules and build up richer analysis without the monitor needing to understand each check's internals.
80
+
81
+ ### Documentation as we go
82
+
83
+ Created `docs/metrics.md` covering RTT, packet loss, and jitter — what they are, how we measure them, and thresholds. Added a guideline to CLAUDE.md to update docs when changing metrics or diagnostic logic.
84
+
85
+ ### Network condition simulation (TODO)
86
+
87
+ Noted the need for simulating network conditions in tests (packet loss, high latency, jitter) — possibly via Docker containers with `tc`/`netem`, network namespaces, or lightweight VMs. Currently all tests use mocking.
88
+
89
+ ## Files changed
90
+
91
+ - `yslow/models.py` — added `Severity`, `Issue` (with kind/description/value/message), `CheckResult`
92
+ - `yslow/diagnosis.py` — refactored to return `list[Issue]`, removed `diagnose()`
93
+ - `yslow/monitor.py` — new: monitoring loop, state tracking, dashboard rendering, notifications
94
+ - `yslow/checks/gateway.py` — simplified to pure data, added `summary()`
95
+ - `yslow/checks/internet.py` — simplified to pure data, added `summary()`
96
+ - `yslow/__init__.py` — added argparse, unified one-shot and watch mode
97
+ - `yslow/output.py` — removed (dead code after consolidation)
98
+ - `docs/metrics.md` — new: measurement documentation
99
+ - `tests/test_diagnosis.py` — updated for `Issue` return type
100
+ - `tests/test_monitor.py` — new: monitor state, notification, and render tests
@@ -0,0 +1,54 @@
1
+ # UI overhaul session — 2026-03-21
2
+
3
+ ## What we did
4
+
5
+ ### ASCII banner
6
+ - Added a figlet-style "yslow" ASCII art banner displayed at startup in both modes.
7
+
8
+ ### Restructured into `ui/` package
9
+ - Moved one-shot mode out of `__init__.py` into its own file.
10
+ - Created `ui/` package with:
11
+ - `common.py` — shared console, banner, formatting helpers, probe rendering
12
+ - `check.py` — one-shot mode (`check()`)
13
+ - `watch.py` — continuous mode (`watch()`, `WatchState`, issue history)
14
+ - Created `runner.py` — `run_once()` extracted from UI code where it didn't belong.
15
+ - Renamed `MonitorState` → `WatchState`, `monitor()` → `watch()`, `oneshot()` → `check()` to match CLI verbs (`yslow` = check, `yslow -w` = watch).
16
+
17
+ ### Snapshot tests
18
+ - Added `tests/test_snapshots.py` with `--snapshot-update` flag for regeneration.
19
+ - One snapshot file per scenario per mode (6 files in `tests/snapshots/`).
20
+ - Tests call the real `check()` and `render()` functions, capturing stdout — no knowledge of internal rendering structure.
21
+ - Uses `Console(color_system=None)` for plain-text snapshots (also tests the pipe-to-file path).
22
+ - Three scenarios: all good, one issue, everything failed.
23
+
24
+ ### Swapped issue/probe ordering
25
+ - Both modes now show issues/status before probe detail stats.
26
+
27
+ ### Migrated to rich
28
+ - Replaced hand-rolled ANSI escape codes with `rich.Console`.
29
+ - Colors: green checkmark for healthy, red warning for issues, yellow for unstable, dim for secondary info (banner, probes).
30
+ - `console.clear()` replaces raw `\033[2J\033[H`.
31
+ - Auto-strips formatting when piped to a file (rich's TTY detection).
32
+
33
+ ### Spinners
34
+ - Replaced the countdown timer (`Next check in 30s`) with rich `console.status()` spinners.
35
+ - `checking...` spinner while probes run (both modes).
36
+ - `rechecking in {interval}s...` spinner while waiting (watch mode).
37
+
38
+ ### Structured Issue model
39
+ - Split `Issue.description` into `location`, `problem`, `diagnosis` fields.
40
+ - Updated `diagnosis.py` with structured fields for all issue types.
41
+ - `Issue.message` property still produces a readable single-line string from the fields.
42
+
43
+ ### Issue table (rich Table)
44
+ - Built `render_issues()` using `rich.Table` with columns: location, problem, diagnosis, value.
45
+ - Watch mode history table adds count + timing columns.
46
+
47
+ ## Open question / next steps
48
+
49
+ The issue table and probe stats sections feel redundant — they show overlapping information (e.g., "LAN: high latency to gateway, 45ms" in the issue table vs "LAN: 45.0 ms rtt" in the probe stats). Unifying them into a single view would be cleaner but the right design isn't obvious yet. The table stuff may get reverted while this is figured out.
50
+
51
+ Possible directions:
52
+ - Single table that shows probes with inline issue annotations
53
+ - Issues as the primary view, probes only shown in a `--verbose` mode
54
+ - Probes as the primary table with issue indicators (color/icons) on problem rows
@@ -0,0 +1,16 @@
1
+ # Data model cleanup — 2026-03-22
2
+
3
+ ## What we did
4
+
5
+ ### Promoted IssueRecord to a proper model
6
+ - Moved `IssueRecord` from `ui/watch.py` to `models.py` — it's a domain concept (aggregation of issues across check results), not a UI concern.
7
+ - Added `IssueRecord.from_results(list[CheckResult])` static method, moving the aggregation logic out of `WatchState`.
8
+ - `WatchState.issue_history()` now delegates to `IssueRecord.from_results()`.
9
+
10
+ ### Added dataflow documentation
11
+ - Created `docs/dataflow.md` documenting the full pipeline: probes → diagnosis → CheckResult → IssueRecord → UI.
12
+ - Covers all four model types and how they relate.
13
+
14
+ ## Context
15
+
16
+ Working toward unifying the redundant display sections (issue history, probe summary, session summary). Understanding and cleaning up the data model is the first step — the display redundancy noted in the 2026-03-21 session is still open.
@@ -0,0 +1,20 @@
1
+ # Remove session summary & reorganize tests — 2026-03-22
2
+
3
+ ## What we did
4
+
5
+ ### Removed redundant session summary
6
+ - Deleted `print_summary()` from `ui/watch.py` — it duplicated the dashboard's issue history display.
7
+ - Removed `format_duration()` from `ui/common.py` (sole caller was `print_summary`).
8
+ - Removed `started_at` and `elapsed` from `WatchState` (only used by `print_summary`).
9
+ - Updated `docs/dataflow.md` to remove the "on exit" line.
10
+
11
+ ### Reorganized tests to match module structure
12
+ - Deleted `test_monitor.py` — was a grab bag testing `models`, `ui/common`, and `ui/watch`.
13
+ - Created `test_watch.py` — `WatchState`, `notify`, `_render` tests.
14
+ - Created `test_common.py` — `format_ago` tests.
15
+ - Moved `TestCheckResultHealthy` and `TestIssueHistory` into `test_models.py`.
16
+ - Added shared `make_check` helper and `T0` constant to `conftest.py`.
17
+
18
+ ## Context
19
+
20
+ Continues the display redundancy cleanup noted in the 2026-03-21 session and the data model work from earlier today. The dashboard is now the single place issues are displayed in watch mode.
@@ -0,0 +1,22 @@
1
+ # 2026-03-22 — UI status header rework and diagnosis cleanup
2
+
3
+ ## What changed
4
+
5
+ ### UI status header
6
+ - Replaced the old `problem` / `unstable` / `stable` header with a conversational format:
7
+ - `Is it slow?` on its own line (dim)
8
+ - Answer on next line: `● YES!` (red) / `● Sorta?` (yellow) / `● Nope!` (green)
9
+ - Added section headers: `Why?` before issue list, `Details` before probe results
10
+ - Removed gateway address from the LAN details line (was showing `(gateway 192.168.1.1)`)
11
+
12
+ ### Diagnosis: combined gateway-latency and gateway-jitter
13
+ - Old: separate `gateway-latency` and `gateway-jitter` issues with different descriptions
14
+ - New: single `gateway-quality` issue ("LAN: unstable wifi/LAN connection") that includes whichever details are relevant (rtt, jitter, or both) in the value string
15
+ - `gateway-loss` remains separate since packet loss is a distinct symptom
16
+
17
+ ### Snapshot tests
18
+ - Fixed `--snapshot-update` flag: comparison tests now skip when updating instead of failing before the update test runs
19
+ - Added snapshot update command to CLAUDE.md quick reference
20
+
21
+ ## Open questions
22
+ - None
@@ -0,0 +1,48 @@
1
+ [project]
2
+ name = "yslow"
3
+ version = "0.1.0"
4
+ description = "Diagnose why your network connection is slow"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.10"
8
+ authors = [
9
+ { name = "Wesley Ellis", email = "wesley@tahnok.ca" },
10
+ ]
11
+ classifiers = [
12
+ "Development Status :: 3 - Alpha",
13
+ "Environment :: Console",
14
+ "Intended Audience :: System Administrators",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Operating System :: POSIX :: Linux",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: System :: Networking :: Monitoring",
24
+ ]
25
+ keywords = ["network", "diagnostics", "latency", "wifi", "troubleshooting"]
26
+ dependencies = [
27
+ "rich>=14.3.3",
28
+ ]
29
+
30
+ [project.urls]
31
+ Homepage = "https://codeberg.org/tahnok/yslow"
32
+ Repository = "https://codeberg.org/tahnok/yslow"
33
+ Issues = "https://codeberg.org/tahnok/yslow/issues"
34
+
35
+ [project.scripts]
36
+ yslow = "yslow:main"
37
+
38
+ [build-system]
39
+ requires = ["hatchling"]
40
+ build-backend = "hatchling.build"
41
+
42
+ [dependency-groups]
43
+ dev = [
44
+ "freezegun>=1.5.5",
45
+ "pytest>=9.0.2",
46
+ "ruff>=0.15.7",
47
+ "ty>=0.0.24",
48
+ ]