fix: tray icons + QScreen import + CI system deps #3

Merged
bjoern merged 2 commits from fix/assets-and-ci into main 2026-06-28 18:23:07 +02:00
Member

Fix: tray icons, QScreen import, and CI system deps

Three bugs in one PR — the first two you hit directly, the third was the CI failure.

1. Tray icons not loading (blue square)

Root cause: ASSETS_DIR pointed to src/assets/ which doesn't exist — the SVGs were at repo-root assets/. Even if the path had been correct, the assets wouldn't be packaged with pip install because they were outside the Python package.

Fix:

  • Moved SVGs into src/zai_tray_checker/assets/ (inside the package)
  • Added __init__.py so it's a proper sub-package
  • Replaced hardcoded ASSETS_DIR with importlib.resources.files("zai_tray_checker.assets") — works from both source checkout and pip install
  • Added logger.warning() when fallback is used (was silently failing with no log message)
  • Fixed pyproject.toml package-data from ../assets/*.svg to assets/*.svg

2. QScreen ImportError (from unmerged PR #2)

Still on main — QScreen imported from QtWidgets (wrong module), and QSvgRenderer was an inline import inside _load_svg_icon.

Fix:

  • QScreen, QCursor → module-level import from PySide6.QtGui
  • QSvgRenderer → module-level import from PySide6.QtSvg
  • Removed all inline from PySide6... import from method bodies

3. CI fails on headless runner

The Debian runner has no libGL.so.1 / libglib-2.0.so.0, so import PySide6 itself fails.

Fix:

  • CI workflow now apt-get install system GL/runtime libs before installing
  • GUI import tests use @pytest.mark.skipif to skip gracefully when PySide6 can't import
  • The AST-based inline-import test is static (parses source code) — runs without PySide6

Tests: 45 total, all passing

Test file Tests Notes
test_api_client.py 13 Unchanged
test_config.py 6 Unchanged
test_peak_hours.py 16 Unchanged
test_gui_imports.py 10 7 skip on headless, 3 run everywhere

New TestAssetBundling class verifies SVGs exist inside the package and __init__.py is present.

## Fix: tray icons, QScreen import, and CI system deps Three bugs in one PR — the first two you hit directly, the third was the CI failure. ### 1. Tray icons not loading (blue square) **Root cause:** `ASSETS_DIR` pointed to `src/assets/` which doesn't exist — the SVGs were at repo-root `assets/`. Even if the path had been correct, the assets wouldn't be packaged with `pip install` because they were outside the Python package. **Fix:** - Moved SVGs into `src/zai_tray_checker/assets/` (inside the package) - Added `__init__.py` so it's a proper sub-package - Replaced hardcoded `ASSETS_DIR` with `importlib.resources.files("zai_tray_checker.assets")` — works from both source checkout and pip install - Added `logger.warning()` when fallback is used (was silently failing with no log message) - Fixed `pyproject.toml` package-data from `../assets/*.svg` to `assets/*.svg` ### 2. QScreen ImportError (from unmerged PR #2) Still on main — `QScreen` imported from `QtWidgets` (wrong module), and `QSvgRenderer` was an inline import inside `_load_svg_icon`. **Fix:** - `QScreen`, `QCursor` → module-level import from `PySide6.QtGui` - `QSvgRenderer` → module-level import from `PySide6.QtSvg` - Removed all inline `from PySide6... import` from method bodies ### 3. CI fails on headless runner The Debian runner has no `libGL.so.1` / `libglib-2.0.so.0`, so `import PySide6` itself fails. **Fix:** - CI workflow now `apt-get install` system GL/runtime libs before installing - GUI import tests use `@pytest.mark.skipif` to skip gracefully when PySide6 can't import - The AST-based inline-import test is static (parses source code) — runs without PySide6 ### Tests: 45 total, all passing | Test file | Tests | Notes | |---|---|---| | `test_api_client.py` | 13 | Unchanged | | `test_config.py` | 6 | Unchanged | | `test_peak_hours.py` | 16 | Unchanged | | `test_gui_imports.py` | 10 | 7 skip on headless, 3 run everywhere | New `TestAssetBundling` class verifies SVGs exist inside the package and `__init__.py` is present.
fix: tray icons not loading + QScreen import + CI system deps
Some checks failed
CI / test (pull_request) Failing after 20s
707c16b1f5
Three bugs fixed in one PR:

1. Tray icons not loading (blue square fallback):
   - Assets were at repo-root assets/ but ASSETS_DIR pointed to src/assets/
   - Even if path was right, assets wouldn't be packaged with pip install
   - Moved SVGs into src/zai_tray_checker/assets/ (inside the package)
   - Replaced hardcoded ASSETS_DIR with importlib.resources
   - Added logging when fallback is used (was silently failing)
   - Fixed pyproject.toml package-data path

2. QScreen ImportError (from unmerged PR #2):
   - QScreen imported from QtWidgets (wrong — it's in QtGui)
   - QCursor and QSvgRenderer were inline imports in method bodies
   - All moved to module-level imports from correct modules
   - Removed all inline PySide6 imports from method bodies

3. CI fails on headless runner (missing libGL/libglib):
   - Added apt-get install for system GL/runtime libs in ci.yml
   - GUI import tests now skip gracefully when PySide6 can't import
   - AST-based inline-import test runs without PySide6 (static analysis)

Tests: 45 total, all passing
- 7 GUI import tests (skip on headless CI without system libs)
- 2 new asset bundling tests (verify SVGs exist in package)
- 36 existing logic tests unchanged
fix: PySide6 availability check must test C++ extensions, not just the wrapper
All checks were successful
CI / test (pull_request) Successful in 21s
82ae173407
'import PySide6' succeeds even without system libs like libxkbcommon.
The failure only surfaces when importing C++ extension modules
(QtCore, QtGui, QtWidgets). Updated _pyside6_available() to actually
import from those submodules. Also added libxkbcommon0 to CI deps.
Author
Member

🤖 Hermes automated review: no blocking issues found

Reviewed base 50efb8c → head 82ae173 (8 files, +279/−26). CI/CD: CI / test (21s) passed for head 82ae173 — local test run skipped per CI policy.

This PR cleanly fixes three real bugs. All three root causes are correctly diagnosed and the fixes are sound:

1. Tray icons not loading (asset packaging)

The original ASSETS_DIR = Path(__file__).parent.parent / "assets" pointed outside the package, so (a) it was wrong in source checkout and (b) the SVGs would never ship in a pip install / wheel. The fix is the correct modern approach:

  • SVGs relocated under src/zai_tray_checker/assets/ with an __init__.py making it a proper sub-package ✓
  • _load_svg_icon (main.py:66-86) now uses importlib.resources.files("zai_tray_checker.assets").joinpath(name).read_bytes() — the canonical Python 3.9+ idiom that works identically from a source tree and an installed package ✓
  • Explicit renderer.isValid() check before rendering, with a logged ValueError if the renderer rejects the bytes ✓
  • Fallback path fills a solid-color QPixmap and logger.warning(...)s — previously this failed silently with no log line ✓
  • pyproject.toml package-data corrected from the non-functional "../assets/*.svg" to the in-package "assets/*.svg"

The bundled SVGs are minimal static files (only <rect>/<text>/<circle>, no href/xlink/<script>/external refs) — no SVG-injection or XXE surface.

2. QScreen ImportError

QScreen is in PySide6.QtGui, not QtWidgets — the old import would raise ImportError at runtime when the panel was first toggled. Fix is correct:

  • QScreen, QCursor → module-level import from PySide6.QtGui (main.py:14-21) ✓
  • QSvgRenderer → module-level import from PySide6.QtSvg (main.py:22) ✓
  • The two inline from PySide6... import statements in _toggle_panel are removed (main.py:474-480) ✓
  • Panel positioning now works because self._app.primaryScreen() returns the real QScreen

The new AST-based test test_no_inline_pyside6_imports_in_methods (test_gui_imports.py:131-163) statically enforces that no from PySide6... import appears inside a function body — a good guard that prevents the same class of bug (hidden import that only fails on a specific code path) from regressing.

3. CI on headless runner

  • ci.yml now apt-get installs libgl1 libglib2.0-0 libegl1 libfontconfig1 libdbus-1-3 libxkbcommon0 before installing — these are the actual runtime libs import PySide6.QtCore/QtGui/QtWidgets needs ✓
  • The 2>/dev/null || true on the apt step means a stale package index won't hard-fail the job, which is a reasonable trade-off for a self-hosted runner ✓
  • GUI import tests use @pytest.mark.skipif(not _pyside6_available(), ...) and skip gracefully when the C++ extensions can't load ✓
  • Commit 2's refinement is important and correct: _pyside6_available() now actually imports QtCore/QtGui/QtWidgets rather than just import PySide6, because the top-level wrapper imports successfully even when the underlying .so files are missing libs. This prevents the skipif guard from incorrectly returning True. Good catch.

Minor / non-blocking

  • test_qscreen_not_in_qtwidgets (test_gui_imports.py) asserts that QScreen is not importable from QtWidgets. If a future PySide release re-exports it there, the test's failure message already notes the import in main.py should still use QtGui — so the guard remains valid. Non-issue, just noting the intent is encoded.
  • _load_svg_icon's fallback branch (main.py:88-90) duplicates the pm.fill(QColor("#2563eb")) color that's also the "ok" icon background. Harmless; could be a module constant if more states are added later.
  • The || true on the CI apt step means a genuinely broken package mirror would let the job proceed and then fail with a confusing PySide import error rather than a clear apt error. Acceptable for a single-runner setup; worth revisiting if CI flakiness appears.

Verdict: Three well-targeted fixes with correct root-cause analysis and good regression coverage (the AST inline-import test and the asset-bundling test would both catch their respective original bugs). CI green. No blocking issues.


Automated daily review. I never merge PRs. Note: this is a PR conversation comment with file:line references, not a formal Forgejo review approval — the MCP integration cannot create inline review comments or approval states.

## 🤖 Hermes automated review: no blocking issues found Reviewed base `50efb8c` → head `82ae173` (8 files, +279/−26). CI/CD: **CI / test** ✅ (21s) passed for head `82ae173` — local test run skipped per CI policy. This PR cleanly fixes three real bugs. All three root causes are correctly diagnosed and the fixes are sound: ### ✅ 1. Tray icons not loading (asset packaging) The original `ASSETS_DIR = Path(__file__).parent.parent / "assets"` pointed outside the package, so (a) it was wrong in source checkout and (b) the SVGs would never ship in a `pip install` / wheel. The fix is the correct modern approach: - SVGs relocated under `src/zai_tray_checker/assets/` with an `__init__.py` making it a proper sub-package ✓ - `_load_svg_icon` (`main.py:66-86`) now uses `importlib.resources.files("zai_tray_checker.assets").joinpath(name).read_bytes()` — the canonical Python 3.9+ idiom that works identically from a source tree and an installed package ✓ - Explicit `renderer.isValid()` check before rendering, with a logged `ValueError` if the renderer rejects the bytes ✓ - Fallback path fills a solid-color `QPixmap` and `logger.warning(...)`s — previously this failed silently with no log line ✓ - `pyproject.toml` `package-data` corrected from the non-functional `"../assets/*.svg"` to the in-package `"assets/*.svg"` ✓ The bundled SVGs are minimal static files (only `<rect>`/`<text>`/`<circle>`, no `href`/`xlink`/`<script>`/external refs) — no SVG-injection or XXE surface. ### ✅ 2. QScreen ImportError `QScreen` is in `PySide6.QtGui`, not `QtWidgets` — the old import would raise `ImportError` at runtime when the panel was first toggled. Fix is correct: - `QScreen`, `QCursor` → module-level import from `PySide6.QtGui` (`main.py:14-21`) ✓ - `QSvgRenderer` → module-level import from `PySide6.QtSvg` (`main.py:22`) ✓ - The two inline `from PySide6... import` statements in `_toggle_panel` are removed (`main.py:474-480`) ✓ - Panel positioning now works because `self._app.primaryScreen()` returns the real `QScreen` ✓ The new AST-based test `test_no_inline_pyside6_imports_in_methods` (`test_gui_imports.py:131-163`) statically enforces that no `from PySide6... import` appears inside a function body — a good guard that prevents the same class of bug (hidden import that only fails on a specific code path) from regressing. ### ✅ 3. CI on headless runner - `ci.yml` now `apt-get install`s `libgl1 libglib2.0-0 libegl1 libfontconfig1 libdbus-1-3 libxkbcommon0` before installing — these are the actual runtime libs `import PySide6.QtCore/QtGui/QtWidgets` needs ✓ - The `2>/dev/null || true` on the apt step means a stale package index won't hard-fail the job, which is a reasonable trade-off for a self-hosted runner ✓ - GUI import tests use `@pytest.mark.skipif(not _pyside6_available(), ...)` and skip gracefully when the C++ extensions can't load ✓ - Commit 2's refinement is important and correct: `_pyside6_available()` now actually imports `QtCore`/`QtGui`/`QtWidgets` rather than just `import PySide6`, because the top-level wrapper imports successfully even when the underlying `.so` files are missing libs. This prevents the skipif guard from incorrectly returning `True`. Good catch. ### Minor / non-blocking - `test_qscreen_not_in_qtwidgets` (`test_gui_imports.py`) asserts that `QScreen` is **not** importable from `QtWidgets`. If a future PySide release re-exports it there, the test's failure message already notes the import in `main.py` should still use `QtGui` — so the guard remains valid. Non-issue, just noting the intent is encoded. - `_load_svg_icon`'s fallback branch (`main.py:88-90`) duplicates the `pm.fill(QColor("#2563eb"))` color that's also the "ok" icon background. Harmless; could be a module constant if more states are added later. - The `|| true` on the CI apt step means a genuinely broken package mirror would let the job proceed and then fail with a confusing PySide import error rather than a clear apt error. Acceptable for a single-runner setup; worth revisiting if CI flakiness appears. **Verdict:** Three well-targeted fixes with correct root-cause analysis and good regression coverage (the AST inline-import test and the asset-bundling test would both catch their respective original bugs). CI green. No blocking issues. --- *Automated daily review. I never merge PRs. Note: this is a PR conversation comment with file:line references, not a formal Forgejo review approval — the MCP integration cannot create inline review comments or approval states.*
bjoern merged commit 1ef78c0d54 into main 2026-06-28 18:23:07 +02:00
bjoern deleted branch fix/assets-and-ci 2026-06-28 18:23:07 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/zai-tray-checker!3
No description provided.