The full installer experience as a GUI wizard (gui/setup_wizard.py): environment
summary → pick dependency bundles (from the catalog, grouped) → install missing
apt packages → choose recording trigger → readiness summary.
- Shown on first launch (config setup_done) and via `rigdoctor-gui --setup`;
re-runnable from Settings → Run setup wizard.
- install.sh launches it after a fresh install when a desktop session is present.
- catalog.by_bundle() groups components; config gains setup_done.
- Tests: by_bundle grouping + wizard construction smoke.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scope M12 down to a single shared-terminal mode (D23, amends D16):
- Share page rewritten terminal-only: host shares their PTY/shell; guest watches
and may type only if the host ticks "Allow the guest to type" (read-only
otherwise — the D9 consent exception). Terminal is larger; either side can pop
it full-screen (Esc to exit).
- Removed the read-only stats view + HTTP server (core/share.py) and the
`rigdoctor share serve` CLI; deleted their tests.
- Docs: D23 added; SPEC/MODULES/ROADMAP updated (M12 → done, terminal-only).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The terminal view rendered monochrome (QPlainTextEdit.setPlainText), dropping
pyte's per-cell attributes. Rewritten as a QTextEdit that renders fg/bg/bold/
reverse per cell (block cursor = inverted cell), preserving scrollback. The
session already runs the host's $SHELL + config with TERM=xterm-256color, so
fish/ls/git/prompts now look the same as locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
M6 leftovers (the watcher defers to M9's trigger-mode work):
- gameenv: check_gpu_powermizer (NVIDIA, X; degrades when the gpu target won't
resolve), check_wine (wine --version), check_steam_client (dpkg package version);
steam.client_version() helper.
- core/launchers.py: detect Lutris (read-only SQLite pga.db) and Heroic (Epic
legendary + GOG JSON) installed games; Game gained a `launcher` field.
- Games page + `rigdoctor games` list non-Steam games alongside Steam, tagged by
launcher; Run Diagnostic works on them (auto-launch stays Steam-only).
- Tests for launchers (synthetic Lutris db + Heroic json).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Upgrade `rigdoctor monitor` from a basic redraw to a stdlib curses dashboard
(tui.py): current / session-min / session-max per sensor, grouped by subsystem,
with temperature & utilization color bands (GPU-lost flagged red). q quits,
r resets min/max. Plain full-screen redraw fallback on a non-TTY (--plain forces
it). Pure track()/band() helpers are unit-tested; curses path verified in a pty.
Completes the Monitoring bundle (M2 + M8).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reshape the IA so it reads by intent instead of a flat pile of pages.
- Grouped sidebar: Monitor / Diagnose / System / App (section headers).
- Renames: Health → System Health, Environment → Tuning, Logs → Recordings,
Setup → Settings.
- Settings absorbs Notifications (alerts) as a section; Notifications dropped as a
separate page (notifications_page.py removed; SetupPage gains the alerts card +
`changed` signal wired to the live alert monitor).
- Recordings is now a hub: a source dropdown to view any captured log (always-on /
last diagnostic / preserved crash) + Analyze-crash in place, plus the recorder
controls; status line now shows the captured game.
- main_window nav is data-driven (_NAV groups → _PAGES order → stack); show_page,
badges, and tray flows updated. GUI smoke test asserts the new page set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
QSystemTrayIcon applet (gui/tray.py, D13): menu with live CPU/GPU temp + memory
used/total, a status line, a Run Diagnostic submenu per detected game, plus Open
dashboard / Start-Stop recording / Snapshot-copy / Quit. Reuses the dashboard's
sample stream; drives existing MainWindow flows.
- MainWindow creates the tray when one is available; closing the window hides to
tray (Quit exits); setQuitOnLastWindowClosed(False) so dialogs don't quit it.
- app: `--tray` starts hidden for autostart.
- tests/test_gui_smoke.py: construct MainWindow headless + exercise the tray, so
a startup crash (like the 0.18.0 import bug) fails the build. Skips if no PySide6.
- docs: M10/M11 marked done in MODULES/ROADMAP.
Completes the Desktop UI bundle (M10 + M11).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The recording indicator (0.18.0) used `from .core import diagnostic`, which
resolves to the non-existent rigdoctor.gui.core and crashed MainWindow on launch.
Fixed to `from ..core import diagnostic`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The live sample count wasn't useful at a glance. The sidebar badge now shows
just ● Recording + the game, plus a ⚠ GPU-lost line when detected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
While a capture runs, the sidebar shows a red "● Recording" badge on every page
with the game and live sample count (+ GPU-lost flag). A 1.5s poll of the
recorder status reflects captures started any way — manual record, a guided
diagnostic, or the Steam launch wrapper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restore the GUI Inventory page (removed in 0.7.2 for the CLI). Sidebar Inventory
→ System/CPU/Firmware/Memory/GPU/Storage/Display cards, Copy Markdown / Save… /
Refresh; root-only dmidecode details (motherboard/BIOS/RAM) fill in after launch
elevation. Reuses the existing M5 core/inventory.py; CLI unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
D12 "build first" wrapper: `rigdoctor wrap %command%` (Steam launch option /
Lutris/Heroic wrapper field) auto-brackets a focused diagnostic around a game —
start a game-tagged capture on launch, clean stop on exit; a hard freeze leaves
it unterminated → flagged as a crash next launch.
- core/wrap.py: game name from SteamAppId, PATH-proof launch_option(), run()
that doesn't disturb an existing capture and returns the game's exit code.
- diagnostic.start() preserves an unanalyzed crash to diagnostic-crash.jsonl
before clearing, so auto-relaunch can't wipe an unseen crash; pending_crash/
analyze_crash check the archive first.
- GUI: "Auto-capture…" helper dialog (copyable launch-option string).
- Tests for wrap (name resolution, exit-code passthrough, no-double-start).
- docs: fix stale MODULES.md status column (M1/M3/M4/M5/M8/M10/M13 → done),
update ROADMAP/MODULES for the wrapper + crash detection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A focused capture that ends without a clean stop (no session-stop, no live
recorder) is treated as a likely hard freeze.
- core/diagnostic.py: pending_crash() detects the unterminated session;
acknowledge_crash() dismisses it; analyze_crash() combines the captured window
(final readings + GPU-lost) with a focused scan of the PREVIOUS (crashed) boot
+ SMART/driver/persistence/temps.
- health.check_previous_boot() scans `journalctl -k -b -1`; run_health_checks
gained include_journal to avoid double-scanning for the crash path.
- GUI: Games page shows a warning banner on launch for an interrupted diagnostic
with Analyze crash / Dismiss → results dialog.
- Tests for crash detection / clean-stop / acknowledge / in-progress.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the four headline gauges (GPU temp, GPU load, CPU temp, memory) with
HistoryGraph trend tiles: each plots its session history with the current value,
window min/max, a dashed warn-threshold line, and a kind-colored line (temp band
/ usage / accent). QPainter-drawn, no new dependency. Seeing changes over time is
more useful than the live-only snapshot.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The recording banner gave no guidance, so it wasn't clear what to do after
clicking Run Diagnostic.
- Start dialog now spells out the flow: play the game, reproduce the crash, then
Finish & analyze (data survives a hard freeze + reboot), with "Launch game &
start" (steam.launch_game via steam:// appid URL) or "Start without launching".
- Recording banner now states the next step, not just a sample count.
- steam.launch_game(appid): best-effort Steam launch (steam / xdg-open).
- Fix: escape "&" in button labels (Qt mnemonic) so "Finish & analyze" shows
correctly instead of "Finish _analyze".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the guided diagnostic (0.11.0 core/CLI) into the GUI:
- Each game row gets a "Run Diagnostic" button → starts a focused, game-tagged
capture and shows a recording banner (live sample count + GPU-lost indicator)
with Finish & analyze / Discard.
- Finishing runs core.diagnostic.finish() off the UI thread and opens a results
dialog (gui/diagnostic_dialog.py): window-scoped capture summary + findings
cards (reusing render_summary + finding_card).
- Banner restores on showEvent if a capture is still running (navigate away/back).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The seed use case end to end, orchestrating M3 + M4 (ARCHITECTURE §7.1).
- core/diagnostic.py: start(game) runs a focused, game-tagged capture into a
dedicated diagnostic log (window-scoped report, separate from the always-on
crash log); finish() stops it and combines the capture summary (M3) with the
health findings (M4). Game recorded as a log event so it survives crash+reboot.
- CLI: rigdoctor diagnose start --game/--appid | status | finish.
- recorder/record run gained an optional --game tag; reccontrol passes it through.
- Tests for game recovery + the finish() combination.
GUI/tray "Run Diagnostic" button and auto start/stop (D12 wrapper) come next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Thread the command output through to the status line and classify it: cancelled
at the password prompt vs. the system rejecting the change (e.g. a BIOS/kernel-
locked PCIe ASPM policy), instead of a vague "cancelled, or needs privileges".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Style the QComboBox popup (QAbstractItemView) — it's a separate widget the
theme didn't cover, so the drop-down list rendered light-on-light.
- Install/Apply finding buttons used PrimaryButton (accent fill + dark text),
whose fill didn't paint reliably inside the finding cards, leaving dim
dark-on-dark text. New outlined ActionButton style: bright accent text on the
dark card, fills accent on hover, with a min-height so the row can't crush it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Make the environment report actionable, not just advisory.
Install (reuses M9 installer):
- Add GameMode, MangoHud, cpupower to the component catalog (so they also show
on the Setup page); catalog.by_id() lookup.
- "tool not installed" findings (GameMode/MangoHud) get an Install button.
Apply runtime-reversible tunables (D22, realizing the D9 consent-gated milestone):
- core/fixes.py: dropdown of live options + Apply for CPU governor, NVIDIA
persistence, PCIe ASPM policy, vm.swappiness, THP. One pkexec command each,
no reboot, reverts on reboot; chosen value validated against live options;
writes go to sysfs/procfs/nvidia-smi, never GRUB. GRUB/mitigations stay
suggestion-only.
- Finding gained optional action (install) + fix (apply) ids; shared
finding_card renders the matching control; Environment page wires both and
re-checks after a change.
Tests for fixes (parse, command builders, value validation, gameenv wiring).
Docs: D22 added (amends D9); SPEC/MODULES/ROADMAP updated. 0.9.0 -> 0.10.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The evaluate-and-suggest half of M6: a read-only findings report (D9) over
system settings that affect gaming stability/performance, each with the exact
fix command.
- core/gameenv.py: PCIe ASPM, NVIDIA persistence mode, CPU governor (the three
seed-case contributors to GPU bus-drop / Xid 79), GameMode, MangoHud,
vm.swappiness, shader disk cache, THP, CPU mitigations, Proton versions.
Pure evaluate_* helpers split from IO for testing; reuses the M4 Finding model.
- steam.proton_versions(): surfaces installed Proton builds for the report.
- CLI: rigdoctor gameenv (text / --json); render_health() gained a title arg.
- GUI: new Environment page; extracted a shared finding_card widget and switched
the Health page to it.
- Tests for the pure evaluators + aggregate.
Also fix: desktop notifications now use the RigDoctor icon (installed theme copy
-> bundled asset -> stock fallback) instead of a generic stock icon, matching
the app/dock icon.
Docs (MODULES/ROADMAP) updated; version 0.8.0 -> 0.9.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first slice of M6 (gaming-environment checks): detect a user's Steam
libraries and the games installed in each — also the D12 "pick a game"
foundation.
- core/steam.py: multi-install/library discovery (libraryfolders.vdf, symlink
dedupe, native/Flatpak/Snap), appmanifest_*.acf scan with runtime/Proton/
redist filtering, scan cache + new-game diff. Stdlib only. VDF keys read
case-insensitively (e.g. lastupdated vs SizeOnDisk).
- Libraries are opt-in (config steam_libraries); the flat TOML writer now
emits list/array values.
- GUI Games page: library checkboxes with per-library counts, game list,
background rescan on every launch, NEW badge + sidebar count for games
installed since the last scan (acknowledged when viewed).
- CLI: rigdoctor games / games libraries [--enable|--disable|--all|--json]
(headless-complete, D17).
- Tests for VDF parse, scan, tool filter, cache diff, config list round-trip.
- Docs (MODULES/ROADMAP) updated; version 0.7.3 -> 0.8.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Render with pyte.HistoryScreen and show scrollback + screen, so large output
(ls -la, cat, etc.) can be scrolled up to read. Auto-scroll to the bottom only
when already at the bottom; preserve position when the user has scrolled up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- The shared terminal caret now sits at the real cursor (row and column) instead
of the start of the line.
- Remove the GUI Inventory tab; `rigdoctor inventory` (CLI) covers it. Inventory is
still collected for the relay guest view (so a remote helper sees the host's
hardware) and via launch elevation. Deletes gui/inventory_page.py.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- A guest who joined after the host enabled the terminal stayed read-only; the
host now re-sends the terminal state on join (req_full), so the terminal works.
- The shared terminal follows the cursor to the bottom as output arrives (ls -la)
instead of staying scrolled up.
- The Inventory page preserves scroll position on refresh (and skips re-rendering
unchanged data), so it no longer jumps to the top while you're reading.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- feat(share): host-consented interactive terminal over the relay. The host shares
a real PTY shell (core/pty_session.py); the guest renders it with pyte and sends
keystrokes (gui/terminal_widget.py) — vim/top/tab-completion/Ctrl-C work. Runs as
the host's user (never root). The host reads along live and can type too, e.g. a
sudo password, which stays local and is never sent to the guest. Off by default.
Guest also pulls inventory on join (req_full).
- fix(gui): style all form controls (QLineEdit/QPlainTextEdit/spin boxes/combo/
terminals) dark-on-light-text — Fusion defaulted them to unreadable light-on-light.
- replaces the command/response shell with the full PTY; adds pyte to the gui extra.
Verified end-to-end against the deployed relay (guest keystroke ran on host PTY).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a Share tab that hosts or joins a read-only live session through the
rigdoctor-relay over WebSocket (QtWebSockets), gated by the Gitea access token.
- gui/share_page.py: Start shared session (host: get a code, stream snapshot +
health + inventory) and Enter share code (guest: view a host's data read-only)
- core/share.py: host_full_frame / host_snapshot_frame + guest_html renderer
- config: relay_url (default wss://rigdoctor.jesseyvanofferen.com)
- setup: token now powers updates AND sharing — hint asks for read:user +
read:repository scopes (relay validates the account via Gitea)
- main_window: Share nav tab + socket cleanup on close
- tests for the relay frame builders and guest HTML
Verified end-to-end against the deployed relay (host code -> guest frame).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A checked checkbox was hard to distinguish from unchecked on the dark theme.
Style QCheckBox::indicator: accent-filled box with a checkmark when checked,
dark box with a visible border when not.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>