Module decomposition (index) #83
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Index for module decomposition — one issue per seam, this issue for the doctrine and the map. AqomuiGui was 2,818 lines and 114 methods in one class when this was opened; the same tests apply to every module, not just the gui.
Doctrine (consolidated from the comment thread below, which carries the reasoning):
Seams:
Related decomposition outside this class: #23 (shared structure for update.py's six providers, gated on parser test coverage). Feature decisions that reshape screens before extraction is worth doing: #82 (scripts), #86 (log tab), #70 (status+server collapse), #71 (profiles).
Amendment (2026-08-20): the "gui/ subpackage only after the tab split" line had the sequencing backwards. It was aimed at not reshuffling existing files prematurely — but it would route every new extraction through widgets.py, moving each piece twice and growing a second monolith to dismantle later.
Revised rule: new extractions land directly in
aqomui/gui/, one file per widget/class, starting immediately (package cost is an__init__.pyand a setup.pypackagesentry — nothing existing moves).widgets.pymigrates piecemeal under the same pacing rule: when work touches a class there anyway, it moves out. Tests follow the same split:tests/test_gui_<widget>.pyper module rather than growingtest_aqomui_gui.py.First instance:
gui/tray.py(TrayIconwrapping QSystemTrayIcon + menu — signalsshow_window/quit_app/connect_serverout,connected(name)/disconnected()in), riding along with the #69/#102/#103 tray work. Also the natural home for #85 and #93.Refinement to the amendment's "one file per widget/class", after checking the layout of mature PyQt projects (Anki's
aqt/, Calibre'sgui2/, Picard'sui/): the unit is one module per feature/screen, named for its main widget, with the helpers only it uses riding along in the same file — not one file per QWidget subclass. Two different questions, two different tests:usable; before #106, AqomuiGui poked its internals from five handlers.) "It's consumed by the main gui file" disqualifies nothing — the main window is the composer, that's true of every feature, and it's how the class reached 2,800 lines.gui/servers.pyholds the tab plus its item delegate and filter proxy, not three files.Tests mirror the package:
tests/gui/test_<module>.py, with an__init__.pyso pytest namespaces the subdirectory (established in #106).Sharpening one thing in the doctrine above: Anki/Calibre/Picard are cited as precedent, and that reads as if they're aspirational. They're existence proofs that the gui/ layout is conventional, not exemplars of where it ends up — Anki's aqt/main.py and Calibre's gui2/ui.py are exactly the ever-growing composer class we're digging out of, behind perfectly normal directory structures. The layout alone saves nothing. The load-bearing parts of the doctrine are the ones that stand without precedent: the extraction test (interface much narrower than implementation), sole-consumer placement, and keeping CLI-backing modules Qt-free (established with gui/autostart.py in PR #112: shim logic stays in launcher.py because the CLI imports it; only the Qt-facing sliver lives in gui/).
Survey of every module (2026-08-23), filed as #125–#131. Seams considered and rejected, each with the test it fails, so they don't get re-litigated without new evidence:
dispatch_status, the twelve status handlers,kill/kill_bypass/disconnect_bypass,establish_connection, progress bars, the timeout watchdog): fails the interface test. Every handler coordinates tray + status widgets + progress bars + tab switching + notifications + service calls, so the extracted interface would be as wide as the implementation — this coordination is the composer's job. #123 already takes the policy sliver, and its own text keeps progress bars and status handling with the gui.check_other_instance,initialize_service,service_unavailable/service_recovered,restart_aqomui, the version check): PR #84 already took the mechanism; what remains is dialog policy wired into ServiceClient's callbacks. Interface ≈ implementation.ServerWidgetstays shared — the server list and the bypass app/net lists both consume it.apply_bypass,cgroup_vpn,teardown_bypass/reconcile_bypass/sweep_bypass_units): orchestration glue over settings + tunnel_state + cgroup owner — an extracted class would need all of it passed in, no narrower. The mechanics already live in bypass.py.write_config,mgmt_events, the dns watchdog): methods with already-narrow interfaces inside their sole consumer TunnelThread; mgmt.py and dns_manager.py are the extractions that made sense here and already exist.Found along the way, out of scope for this survey: the cli's two
create_server_dictcalls crash on the three-arg signature — filed as #132.Decompose AqomuiGuito Module decomposition (index)