Skip to content

feat: restore quota panel for OpenCode (Go Dzen) + code quality improvements - #5

Merged
weby-homelab merged 4 commits into
mainfrom
feature/quota-restoration-and-improvements
Jul 1, 2026
Merged

feat: restore quota panel for OpenCode (Go Dzen) + code quality improvements#5
weby-homelab merged 4 commits into
mainfrom
feature/quota-restoration-and-improvements

Conversation

@weby-homelab

@weby-homelab weby-homelab commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #4

Comprehensive code quality improvements, quota panel restoration for OpenCode (Go Dzen), GPU monitoring, and architectural refactoring.

Commits (4)

1. Quota Panel + Code Quality

  • Restore quota panel adapted for OpenCode with Go Dzen subscription
  • Rate limit reading from ~/.config/opencode/llmtop-rate-limits.json
  • Add GPLv3 LICENSE, proper .gitignore, cross-platform CI matrix
  • Remove dead code (~250 lines), eliminate duplication
  • Atomic config write, graceful kill (SIGTERM→SIGKILL), summary cache bound

2. GPU Monitoring + rusqlite

  • GPU monitoring via nvml-wrapper (optional nvidia feature flag)
  • GPU panel: temp, utilization, VRAM, power with gradient coloring
  • Replace sqlite3 CLI with native rusqlite (bundled) — no external binary needed
  • Fix clippy manual_flatten warnings

3. Parallel Probing + Alerts

  • Parallel port probing in auto_discover (thread::scope, chunks of 10)
  • Threshold alerts: context > 90%, rate limits > 80%, orphan ports

4. cargo-deny + Module Split

  • Add deny.toml with license/advisory/ban policies
  • Split sessions.rs (52KB) into sessions/{mod,timeline,file_audit,detail}.rs

Verification

cargo check                        ✅
cargo check --features nvidia      ✅
cargo clippy -- -D warnings        ✅ (0 warnings)
cargo test                         ✅ (88/88 passed)
cargo build --release              ✅

Files Changed (25+)

.github/workflows/ci.yml       | cross-platform matrix + cargo-deny
Cargo.toml                     | rusqlite + nvml-wrapper (optional)
deny.toml                      | NEW — cargo-deny config
LICENSE                        | GPLv3 full text
src/gpu.rs                     | NEW — GPU metrics sampler
src/ui/gpu.rs                  | NEW — GPU panel
src/ui/quota.rs                | NEW — Quota panel (OpenCode/Go Dzen)
src/ui/sessions/mod.rs         | refactored from sessions.rs
src/ui/sessions/timeline.rs    | extracted
src/ui/sessions/file_audit.rs  | extracted
src/ui/sessions/detail.rs      | extracted
src/collector/opencode.rs      | rusqlite native queries
src/collector/auto_discover.rs | parallel probing
src/app.rs                     | GPU, alerts, kill logic, quota
... and 10+ more files

Closes #4

- Restore quota panel adapted for OpenCode with Go Dzen subscription
- Add GPLv3 LICENSE file
- Add proper .gitignore for Rust project
- Add cross-platform CI matrix (ubuntu/macos/windows)
- Add README badges (CI, License, Rust)
- Remove dead code (~250 lines): DesktopRolloutScanner, codex_enabled
- Eliminate code duplication: shared truncate_at_char_boundary
- Fix auto_discover.rs effort field hack
- Atomic config write (tmp + rename pattern)
- Kill logic: SIGTERM → 3s wait → SIGKILL
- Summary cache bounded to 500 entries
- Add OpenCode rate limit reader

Verification:
- cargo check ✅
- cargo clippy -- -D warnings ✅
- cargo test ✅ (88/88 passed)
- cargo build --release ✅

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new Quota panel to track OpenCode rate limits, refactors string truncation logic, removes the unused desktop rollout scanner, and updates the session kill sequence to gracefully terminate processes. The feedback highlights several critical improvements: running the blocking kill-and-sleep sequence in a background thread to prevent TUI freezes, resolving OpenCode config paths on Windows for cross-platform compatibility, optimizing the summary cache eviction loop to avoid performance degradation, and scaling token rate calculations proportionally during startup when history is limited.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/app.rs
Comment on lines 738 to +750
let _ = std::process::Command::new("kill")
.args(["-9", &pid.to_string()])
.args(["-15", &pid.to_string()])
.output();
std::thread::sleep(std::time::Duration::from_secs(3));
if std::process::Command::new("kill")
.args(["-0", &pid.to_string()])
.status()
.is_ok_and(|s| s.success())
{
let _ = std::process::Command::new("kill")
.args(["-9", &pid.to_string()])
.output();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Blocking the main thread with std::thread::sleep(std::time::Duration::from_secs(3)) in a TUI application will freeze the entire user interface for 3 seconds whenever a session is killed.

To keep the TUI responsive, the graceful kill sequence (sending SIGTERM, waiting, and then sending SIGKILL if still alive) should be executed in a background thread.

Suggested change
let _ = std::process::Command::new("kill")
.args(["-9", &pid.to_string()])
.args(["-15", &pid.to_string()])
.output();
std::thread::sleep(std::time::Duration::from_secs(3));
if std::process::Command::new("kill")
.args(["-0", &pid.to_string()])
.status()
.is_ok_and(|s| s.success())
{
let _ = std::process::Command::new("kill")
.args(["-9", &pid.to_string()])
.output();
}
std::thread::spawn(move || {
let _ = std::process::Command::new("kill")
.args(["-15", &pid.to_string()])
.output();
std::thread::sleep(std::time::Duration::from_secs(3));
if std::process::Command::new("kill")
.args(["-0", &pid.to_string()])
.status()
.is_ok_and(|s| s.success())
{
let _ = std::process::Command::new("kill")
.args(["-9", &pid.to_string()])
.output();
}
});

Comment on lines +63 to +73
pub fn read_opencode_rate_limits() -> Vec<RateLimitInfo> {
let mut results = Vec::new();
let Some(home) = dirs::home_dir() else {
return results;
};
let path = home.join(".config").join("opencode").join(OPENCODE_RATE_FILE);
if let Some(info) = read_rate_file(&path, "opencode") {
results.push(info);
}
results
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

On Windows, OpenCode configurations and rate limit files are typically stored in %LOCALAPPDATA%/opencode or %APPDATA%/opencode rather than ~/.config/opencode. The current implementation only checks the Unix-style ~/.config path, which will cause the quota panel to show "no data" on Windows.

To ensure cross-platform compatibility, we should check %LOCALAPPDATA% and %APPDATA% on Windows, matching the path resolution logic used in opencode.rs.

pub fn read_opencode_rate_limits() -> Vec<RateLimitInfo> {
    let mut results = Vec::new();
    let Some(home) = dirs::home_dir() else {
        return results;
    };
    let mut path = home.join(".config").join("opencode").join(OPENCODE_RATE_FILE);

    #[cfg(target_os = "windows")]
    if !path.exists() {
        for var in ["LOCALAPPDATA", "APPDATA"] {
            if let Ok(base) = std::env::var(var) {
                if !base.is_empty() {
                    let candidate = std::path::PathBuf::from(base).join("opencode").join(OPENCODE_RATE_FILE);
                    if candidate.exists() {
                        path = candidate;
                        break;
                    }
                }
            }
        }
    }

    if let Some(info) = read_rate_file(&path, "opencode") {
        results.push(info);
    }
    results
}

Comment thread src/app.rs
Comment on lines +979 to +985
while bounded.len() > MAX_CACHED_SUMMARIES {
if let Some(oldest_key) = bounded.keys().next().cloned() {
bounded.remove(&oldest_key);
} else {
break;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Calling bounded.keys().next() repeatedly in a loop to evict elements is highly inefficient. Each call to .keys() creates a new iterator that must scan the internal bucket array of the HashMap from the beginning to find the first occupied bucket. If many elements need to be evicted, this results in $O(K \times B)$ complexity (where $K$ is the number of evicted elements and $B$ is the bucket count), which can block the main TUI thread and cause noticeable stuttering.

Additionally, HashMap has no defined order, so keys().next() does not return the "oldest" key but rather an arbitrary one.

We can optimize this to $O(B)$ by collecting the keys to remove in a single pass.

    if bounded.len() > MAX_CACHED_SUMMARIES {
        let keys_to_remove: Vec<String> = bounded.keys()
            .take(bounded.len() - MAX_CACHED_SUMMARIES)
            .cloned()
            .collect();
        for key in keys_to_remove {
            bounded.remove(&key);
        }
    }

Comment thread src/ui/quota.rs
Comment on lines +41 to +43
let rates = &app.token_rates;
let ticks_per_min: usize = 30;
let tokens_per_min: f64 = rates.iter().rev().take(ticks_per_min).sum();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Right after application startup, app.token_rates will have fewer than 30 elements (ticks). Summing them directly as tokens_per_min will result in an artificially low rate that slowly climbs over the first minute, even if the agent is actively processing tokens at a constant rate.

To provide an accurate rate immediately, we should scale the sum proportionally when there are fewer than 30 ticks of history available.

    let rates = &app.token_rates;
    let ticks_per_min: usize = 30;
    let rates_taken = rates.iter().rev().take(ticks_per_min).collect::<Vec<_>>();
    let sum_tokens: f64 = rates_taken.iter().copied().copied().sum();
    let tokens_per_min = if !rates_taken.is_empty() {
        sum_tokens * (ticks_per_min as f64 / rates_taken.len() as f64)
    } else {
        0.0
    };

- Add GPU monitoring via nvml-wrapper (optional 'nvidia' feature flag)
- New src/gpu.rs: GpuMetrics struct + GpuSampler with conditional compilation
- New src/ui/gpu.rs: GPU panel with temp/utilization/VRAM/power display
- Replace sqlite3 CLI subprocess with native rusqlite (bundled feature)
- Remove sqlite3 availability checks and Windows warnings
- Fix clippy manual_flatten warnings in opencode.rs queries
- Add GPU panel toggle (key 8) in view menu and config overlay
- Full i18n support for GPU panel (EN + ZH)

Verification:
- cargo check ✅
- cargo check --features nvidia ✅
- cargo clippy -- -D warnings ✅
- cargo test ✅ (88/88 passed)
- Parallel port probing in auto_discover.rs using std::thread::scope
  (chunks of 10 concurrent threads, avoids sequential 300ms timeouts)
- Threshold alerts: context > 90%, rate limits > 80%, orphan ports
- Alerts shown as transient status messages in tick_no_summaries()

Verification:
- cargo clippy -- -D warnings ✅
- cargo test ✅ (88/88 passed)
- Add deny.toml with license/advisory/ban/source policies
- Add cargo-deny CI step (EmbarkStudios/cargo-deny-action@v2)
- Split src/ui/sessions.rs (52KB monolith) into:
  - sessions/mod.rs: main draw functions + tests
  - sessions/timeline.rs: timeline rendering
  - sessions/file_audit.rs: file audit rendering
  - sessions/detail.rs: chat history rendering

Verification:
- cargo clippy -- -D warnings ✅
- cargo test ✅ (88/88 passed)
@weby-homelab
weby-homelab merged commit d492b1f into main Jul 1, 2026
5 of 8 checks passed
@weby-homelab
weby-homelab deleted the feature/quota-restoration-and-improvements branch July 2, 2026 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: restore quota panel for OpenCode + code quality improvements

1 participant