Files
tuimble/reports/code-review-2026-02-24-response.md

6.6 KiB

Code Review Response

Original review: code-review-2026-02-24.md Date applied: 2026-02-24 Commits: 14 functional + 4 tasklist updates (be6574a..d4a8f34) Test suite: 83 tests, all passing


Summary

All 17 items in the priority matrix were evaluated. 15 were applied across five phases (A--E). Two were deferred to the backlog with rationale.

Disposition by severity

Severity Total Applied Deferred Notes
Critical 4 3 1 Widget tests deferred (backlog)
Important 9 9 0
Nice 4 3 1 Frozen config deferred

Phase A -- Safety

# Item Resolution
2 Replace regex HTML stripping Replaced with html.parser.HTMLParser subclass. Handles malformed tags, comments, nested markup, entities. 13 edge-case tests added.
3 Validate TOML config keys Added _load_section() that filters unknown keys via dataclasses.fields() before construction. Logs warnings for typos. 5 tests added.
10 Guard join_channel/send_text join_channel uses .get() + ValueError. send_text wraps lookup in try/except (KeyError, AttributeError).

Commits:

8be475f app: replace regex html stripping with stdlib parser
44da57d config: filter unknown toml keys before dataclass init
897c5b1 client: guard join_channel and send_text against stale ids

Phase B -- Performance

# Item Resolution
5 Use numpy for _apply_gain Deviation: numpy is NOT a transitive dep of sounddevice (only cffi). Used stdlib array.array("h") instead -- eliminates struct pack/unpack overhead without adding a dependency.
6 Replace polling sleep with queue.get(timeout=) get_capture_frame() now accepts timeout param. Send loop uses timeout=0.02. No polling, instant wake on data.
15 Drain audio queues on stop() Both capture and playback queues drained after stream close. Prevents stale frames leaking on restart. Test added.

Commits:

7a2c8e3 audio: replace struct pack/unpack with array module in _apply_gain
bfa79ea app: replace audio send polling with blocking queue.get

Phase C -- Architecture

# Item Resolution
1 Extract reconnect manager Extracted to src/tuimble/reconnect.py (93 lines). Uses threading.Event for cancellation (item #7 resolved simultaneously). Instant cancel, no polling.
7 threading.Event for reconnect Resolved as part of item #1 above. Event.wait(timeout=delay) replaces the 0.5s sleep loop.
8 Extract _make_client() factory Single factory method replaces 4 duplicate 7-line instantiation blocks.
9 Compile _strip_html regex Superseded by item #2 (HTMLParser replacement). No regex remains.
13 Extract InputHistory class Extracted to standalone class in app.py (29 lines). Clean push/up/down interface. 11 tests added.
-- Cache _find_root() ChannelTree.set_state() now caches _root_id. Eliminates redundant traversal during render.

Commits:

216a4be app: extract InputHistory, _make_client factory, cache _find_root
0cf3702 app: extract ReconnectManager to reconnect.py

Phase D -- Test Coverage

# Item Resolution
4 Widget/app tests Partially addressed. Extracted components (ReconnectManager, InputHistory, _strip_html, config loading) are now fully tested without the TUI harness. Widget-level tests (StatusBar breakpoints, ChannelTree navigation) deferred to backlog -- requires Textual App.run_test().
-- Refactor test_audio.py Replaced private attribute access with public interface calls (_capture_callback + get_capture_frame instead of direct queue manipulation).

Test additions:

  • test_reconnect.py -- 8 tests (backoff formula, cancellation, exhaustion, non-retryable abort)
  • test_history.py -- 11 tests (navigation, draft preservation, push reset, full cycle)
  • test_strip_html.py -- 13 tests (nested tags, entities, comments, malformed HTML)
  • test_config.py -- 5 new tests (_load_section filtering, missing file, unknown keys)
  • test_audio.py -- 1 new test (stop drains queues), existing tests refactored

Commits:

4c1a545 test: add ReconnectManager unit tests
7c57e03 test: add InputHistory unit tests
65de741 test: add _strip_html edge cases and config validation tests
d117576 test: refactor audio tests to use public interfaces

Phase E -- Polish

# Item Resolution
12 Deduplicate config change detection _detect_config_changes and _apply_restart_changes now use dataclasses.asdict() for comparison. New config fields are automatically picked up.
11 Suppress PTT chatlog spam _on_ptt_change skips chatlog writes when ptt.mode == "hold". Status bar indicator still updates in real time.
16 Add -> None return annotations Added to 21 methods across client.py and audio.py.
-- Cache users/channels properties Properties now cache with dirty flags. Invalidated on connect, disconnect, and user/channel event callbacks.

Commits:

0ae0e77 app: deduplicate config detection, suppress hold-mode PTT spam
bbd28e2 audio: add -> None return annotations
aa17159 client: add return annotations and cache users/channels properties

Deferred Items

# Item Reason
4 Widget tests (StatusBar, ChannelTree) Requires Textual async test harness (App.run_test()). Extracted logic is tested; widget integration tests are in the backlog.
14 Inline callback wrappers with lambdas Low priority. Current named methods aid debugging (stack traces show _cb_connected vs <lambda>).
17 Frozen dataclasses for Config Requires refactoring _apply_safe_changes which mutates self._config.ptt. Scope exceeds incremental polish.

Metrics

Before After Delta
Source LOC ~1,750 1,866 +116
Test LOC ~480 818 +338
Test count 45 83 +38
Modules 7 8 +1
app.py LOC ~999 965 -34

New module: src/tuimble/reconnect.py (93 lines). Net source growth is primarily from the extracted reconnect module and cached property logic in client.py. app.py shrank despite gaining the InputHistory class and dataclasses import.