feat: major UI improvements + correct API parsing #5

Merged
bjoern merged 3 commits from feature/improvements into main 2026-06-28 19:15:57 +02:00
Member

Major UI improvements + correct API parsing

Six improvements based on real-world testing feedback.

1. Popup positioned near tray icon

Was appearing center-screen. Now uses QCursor.pos() at click time to position the panel near the tray icon, clamped to screen bounds so it never goes off-screen.

2. Panel closes on click-outside

The eventFilter was using underMouse() which is unreliable for Qt.Tool windows. Changed to panel_rect.contains(QCursor.pos()) — a geometry check that works correctly.

3. Fixed API client (the 0/0 tokens bug)

The old client was reverse-engineered from a VS Code extension and used the wrong field names (currentValue, usage, limit). Rewrote api_client.py based on the actual API response:

{"limits": [
  {"type": "TOKENS_LIMIT", "number": 5, "percentage": 25, "nextResetTime": 1782677318619},
  {"type": "TOKENS_LIMIT", "number": 1, "percentage": 5, "nextResetTime": 1783259354990},
  {"type": "TIME_LIMIT", ...}
], "level": "pro"}
  • TOKENS_LIMIT with number=5 → 5-hour window
  • TOKENS_LIMIT with number=1 → weekly window
  • TIME_LIMIT entries are ignored (MCP tool usage)
  • Removed the model-usage endpoint entirely (was the source of the 0/0 tokens)

4. Next refresh countdown

Footer now shows "Next refresh in Xm" with a live 1-second countdown timer.

5. Unified color scheme

Was: blue icon but green progress bars/peak banner — visually inconsistent. Now all elements use the same palette:

State Color Used for
Normal (<70%) Blue #3b82f6 Progress bars, peak banner, tray icon
Warning (70-90%) Amber #f59e0b Progress bars, peak banner, tray icon
Critical (≥90%) Red #ef4444 Progress bars, tray icon

SVG icons updated to match.

6. Weekly progress bar + reset times

Weekly section now has its own progress bar (was just text lines). Both 5h and weekly sections show:

  • Percentage used with colored bar
  • Reset time as relative + absolute: in 2h 15m (17:30)
  • Plan level badge (PRO/LITE/MAX) in the header

Tests: 47 total, all passing

  • API client tests rewritten to match real response shape (15 tests)
  • All existing peak_hours, config, and GUI import tests unchanged
## Major UI improvements + correct API parsing Six improvements based on real-world testing feedback. ### 1. Popup positioned near tray icon Was appearing center-screen. Now uses `QCursor.pos()` at click time to position the panel near the tray icon, clamped to screen bounds so it never goes off-screen. ### 2. Panel closes on click-outside The `eventFilter` was using `underMouse()` which is unreliable for `Qt.Tool` windows. Changed to `panel_rect.contains(QCursor.pos())` — a geometry check that works correctly. ### 3. Fixed API client (the 0/0 tokens bug) The old client was reverse-engineered from a VS Code extension and used the wrong field names (`currentValue`, `usage`, `limit`). Rewrote `api_client.py` based on the **actual API response**: ```json {"limits": [ {"type": "TOKENS_LIMIT", "number": 5, "percentage": 25, "nextResetTime": 1782677318619}, {"type": "TOKENS_LIMIT", "number": 1, "percentage": 5, "nextResetTime": 1783259354990}, {"type": "TIME_LIMIT", ...} ], "level": "pro"} ``` - `TOKENS_LIMIT` with `number=5` → 5-hour window - `TOKENS_LIMIT` with `number=1` → weekly window - `TIME_LIMIT` entries are ignored (MCP tool usage) - Removed the `model-usage` endpoint entirely (was the source of the 0/0 tokens) ### 4. Next refresh countdown Footer now shows "Next refresh in Xm" with a live 1-second countdown timer. ### 5. Unified color scheme Was: blue icon but green progress bars/peak banner — visually inconsistent. Now all elements use the same palette: | State | Color | Used for | |---|---|---| | Normal (<70%) | Blue `#3b82f6` | Progress bars, peak banner, tray icon | | Warning (70-90%) | Amber `#f59e0b` | Progress bars, peak banner, tray icon | | Critical (≥90%) | Red `#ef4444` | Progress bars, tray icon | SVG icons updated to match. ### 6. Weekly progress bar + reset times Weekly section now has its own progress bar (was just text lines). Both 5h and weekly sections show: - Percentage used with colored bar - Reset time as relative + absolute: `in 2h 15m (17:30)` - Plan level badge (PRO/LITE/MAX) in the header ### Tests: 47 total, all passing - API client tests rewritten to match real response shape (15 tests) - All existing peak_hours, config, and GUI import tests unchanged
feat: major UI improvements + correct API parsing
All checks were successful
CI / test (pull_request) Successful in 42s
556f8587bc
Six improvements based on real-world testing:

1. Popup positioned near tray icon (cursor position) instead of center
   - Uses QCursor.pos() at activation time
   - Clamps to screen bounds so it never goes off-screen

2. Panel now closes on click-outside
   - eventFilter uses geometry.contains(QCursor.pos()) instead of
     underMouse() which was unreliable for Qt.Tool windows

3. Fixed API client to use real response structure
   - Rewrote api_client.py based on actual API response:
     { limits: [ { type, number, percentage, nextResetTime } ], level }
   - TOKENS_LIMIT number=5 → 5-hour window
   - TOKENS_LIMIT number=1 → weekly window
   - Now correctly parses percentage + nextResetTime
   - Removed old model-usage endpoint (was returning 0/0 tokens)

4. Next refresh countdown
   - Shows 'Next refresh in Xm' in the panel footer
   - 1-second countdown timer updates the label live
   - Resets after each successful fetch

5. Unified color scheme
   - Normal state is blue (was green for progress bars, blue for icon)
   - Warning is amber, critical is red
   - Progress bars, peak banner, and tray icons all follow the same palette
   - Updated SVG icons to match (#3b82f6 blue, #f59e0b amber, #ef4444 red)

6. Weekly progress bar with reset time
   - Weekly section now has its own progress bar (was just text)
   - Shows percentage + reset time for both 5h and weekly windows
   - Reset time shown as relative + absolute: 'in 2h 15m (17:30)'
   - Plan level badge (PRO/LITE/MAX) in the header

47 tests, all passing.
docs: update README to match new API structure and features
All checks were successful
CI / test (pull_request) Successful in 21s
38c3b9ca11
- Features list: weekly quota bar, plan badge, next-refresh countdown
- Tray icon colors: blue/amber/red (was green/amber/red)
- API reference: documents the actual quota/limit response shape
  with TOKENS_LIMIT number=5 (5h) and number=1 (weekly)
- Removed reference to model-usage endpoint (no longer used)
- Project structure: adds assets/ sub-package, test_gui_imports.py, ci.yml
Author
Member

🤖 Hermes automated review: no blocking issues found

Reviewed the full diff (6 files, +444/−382) between main (e95fde0) and feature/improvements (556f858). Focused on the rewritten API client and the GUI changes.

Verdict: no blocking issues

Security scan — clean. No hardcoded secrets, shell injection, eval/exec, unsafe deserialization, or SQL string-formatting in the added lines.

Correctness checks performed:

  • No dangling references to removed/renamed APIs (fetch_all, fetch_quota, fetch_usage_stats, QuotaInfo, UsageStats, remaining_tokens). The rename to fetch_usage / LimitInfo is fully consistent across src/ and tests/.
  • ApiKeyDialog.key() removal is safe — the key is persisted via store_api_key() in _save(), and the caller (main.py:563-565) re-reads it with load_api_key() after Accepted. The removed method was dead code.
  • _parse_reset_time correctly handles None/0/negative values.
  • Panel positioning (main.py:541-559) now clamps to screen bounds — no off-screen regression.
  • Countdown timer + update_next_refresh wired correctly (_on_data_fetched resets _seconds_to_refresh).

Minor observations (non-blocking)

  • api_client.py:144,150 — limit matching uses number == 5 / number == 1. Works for the current integer response, but if the API ever returns number as a string (e.g. "5"), the == comparison would silently fail to match and both limits would come back as None. A defensive int(limit.get("number", 0)) would harden it.
  • api_client.py:141float(limit.get("percentage", 0)) raises ValueError if the API returns a non-numeric string for percentage. Low risk given the documented shape, but a try/except around the parse loop would make the client more resilient.
  • api_client.py (LimitInfo.label) — the label field is populated but not rendered anywhere in the UI (section titles are hardcoded in main.py). Harmless, but currently dead data.

Verification

CI/CD: no CI result present for head 556f858 at review time (PR opened ~2026-06-28T18:53, 0 comments). Local checks run instead:

  • python3 -m pytest tests/ -q47 passed in 0.83s (15 rewritten api_client tests + 6 config + 9 gui-imports + 17 peak-hours).

Note: this is a conversation comment, not a formal Forgejo review approval — the current MCP integration cannot create review approval states or inline diff comments.

Automated daily review. I never merge PRs.

## 🤖 Hermes automated review: no blocking issues found Reviewed the full diff (6 files, +444/−382) between `main` (`e95fde0`) and `feature/improvements` (`556f858`). Focused on the rewritten API client and the GUI changes. ### Verdict: no blocking issues **Security scan** — clean. No hardcoded secrets, shell injection, `eval`/`exec`, unsafe deserialization, or SQL string-formatting in the added lines. **Correctness checks performed:** - ✅ No dangling references to removed/renamed APIs (`fetch_all`, `fetch_quota`, `fetch_usage_stats`, `QuotaInfo`, `UsageStats`, `remaining_tokens`). The rename to `fetch_usage` / `LimitInfo` is fully consistent across `src/` and `tests/`. - ✅ `ApiKeyDialog.key()` removal is safe — the key is persisted via `store_api_key()` in `_save()`, and the caller (`main.py:563-565`) re-reads it with `load_api_key()` after `Accepted`. The removed method was dead code. - ✅ `_parse_reset_time` correctly handles `None`/`0`/negative values. - ✅ Panel positioning (`main.py:541-559`) now clamps to screen bounds — no off-screen regression. - ✅ Countdown timer + `update_next_refresh` wired correctly (`_on_data_fetched` resets `_seconds_to_refresh`). ### Minor observations (non-blocking) - **`api_client.py:144,150`** — limit matching uses `number == 5` / `number == 1`. Works for the current integer response, but if the API ever returns `number` as a string (e.g. `"5"`), the `==` comparison would silently fail to match and both limits would come back as `None`. A defensive `int(limit.get("number", 0))` would harden it. - **`api_client.py:141`** — `float(limit.get("percentage", 0))` raises `ValueError` if the API returns a non-numeric string for `percentage`. Low risk given the documented shape, but a try/except around the parse loop would make the client more resilient. - **`api_client.py` (`LimitInfo.label`)** — the `label` field is populated but not rendered anywhere in the UI (section titles are hardcoded in `main.py`). Harmless, but currently dead data. ### Verification CI/CD: no CI result present for head `556f858` at review time (PR opened ~2026-06-28T18:53, 0 comments). Local checks run instead: - `python3 -m pytest tests/ -q` → **47 passed in 0.83s** (15 rewritten api_client tests + 6 config + 9 gui-imports + 17 peak-hours). > Note: this is a conversation comment, not a formal Forgejo review approval — the current MCP integration cannot create review approval states or inline diff comments. *Automated daily review. I never merge PRs.*
fix: harden API parsing + remove dead label field
All checks were successful
CI / test (pull_request) Successful in 21s
b2cad20a2e
Address automated review feedback:
- Defensive int() on 'number' field (handles string '5' → 5)
- Defensive float() on 'percentage' (handles non-numeric → 0.0)
- Remove unused LimitInfo.label field (was never rendered in UI)
- Add 2 new tests for defensive parsing (49 total)
bjoern merged commit cca3c91647 into main 2026-06-28 19:15:57 +02:00
bjoern deleted branch feature/improvements 2026-06-28 19:15:57 +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!5
No description provided.