fix(share): terminal access for late joiners, auto-scroll, inventory scroll
release / release (push) Successful in 14s
release / release (push) Successful in 14s
- 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>
This commit is contained in:
@@ -5,6 +5,15 @@ All notable changes to RigDoctor are recorded here. Format follows
|
|||||||
(`MAJOR.MINOR.PATCH`, pre-1.0). `__version__` and `pyproject.toml` must match the git
|
(`MAJOR.MINOR.PATCH`, pre-1.0). `__version__` and `pyproject.toml` must match the git
|
||||||
release tag (so the auto-updater, D18, can compare versions).
|
release tag (so the auto-updater, D18, can compare versions).
|
||||||
|
|
||||||
|
## [0.7.1] - 2026-05-21
|
||||||
|
### Fixed
|
||||||
|
- Shared terminal: a guest who joined **after** the host enabled the terminal stayed read-only.
|
||||||
|
The host now re-sends the terminal state when a guest joins, so the terminal is available.
|
||||||
|
- Inventory page no longer jumps back to the top when it refreshes (e.g. when elevated data
|
||||||
|
arrives) — scroll position is preserved and unchanged data isn't re-rendered.
|
||||||
|
- Shared terminal now follows the cursor to the bottom as output arrives (e.g. `ls -la`),
|
||||||
|
instead of staying scrolled up.
|
||||||
|
|
||||||
## [0.7.0] - 2026-05-21
|
## [0.7.0] - 2026-05-21
|
||||||
### Added
|
### Added
|
||||||
- **Shared terminal (M12, Tier 3)**: when the host enables it, the session shares a real **PTY**
|
- **Shared terminal (M12, Tier 3)**: when the host enables it, the session shares a real **PTY**
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "rigdoctor"
|
name = "rigdoctor"
|
||||||
version = "0.7.0"
|
version = "0.7.1"
|
||||||
description = "Modular hardware monitoring & crash diagnostics for Linux gamers."
|
description = "Modular hardware monitoring & crash diagnostics for Linux gamers."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""RigDoctor — modular hardware monitoring & crash diagnostics for Linux gamers."""
|
"""RigDoctor — modular hardware monitoring & crash diagnostics for Linux gamers."""
|
||||||
|
|
||||||
__version__ = "0.7.0"
|
__version__ = "0.7.1"
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class InventoryPage(QWidget):
|
|||||||
header.addWidget(self._refresh_btn)
|
header.addWidget(self._refresh_btn)
|
||||||
root.addLayout(header)
|
root.addLayout(header)
|
||||||
|
|
||||||
scroll = QScrollArea()
|
self._scroll = scroll = QScrollArea()
|
||||||
scroll.setWidgetResizable(True)
|
scroll.setWidgetResizable(True)
|
||||||
scroll.setFrameShape(QFrame.Shape.NoFrame)
|
scroll.setFrameShape(QFrame.Shape.NoFrame)
|
||||||
scroll.setStyleSheet("background: transparent;")
|
scroll.setStyleSheet("background: transparent;")
|
||||||
@@ -118,7 +118,11 @@ class InventoryPage(QWidget):
|
|||||||
if sections is None: # collection failed — keep current
|
if sections is None: # collection failed — keep current
|
||||||
self._status.setText("collection failed")
|
self._status.setText("collection failed")
|
||||||
return
|
return
|
||||||
|
if sections == self._sections: # unchanged — don't rebuild (would jump scroll)
|
||||||
|
self._status.setText("")
|
||||||
|
return
|
||||||
|
|
||||||
|
scroll_pos = self._scroll.verticalScrollBar().value()
|
||||||
self._sections = sections
|
self._sections = sections
|
||||||
while self._list.count():
|
while self._list.count():
|
||||||
item = self._list.takeAt(0)
|
item = self._list.takeAt(0)
|
||||||
@@ -129,6 +133,8 @@ class InventoryPage(QWidget):
|
|||||||
self._list.addWidget(_section_card(section))
|
self._list.addWidget(_section_card(section))
|
||||||
self._list.addStretch(1)
|
self._list.addStretch(1)
|
||||||
self._status.setText("")
|
self._status.setText("")
|
||||||
|
# restore scroll after the layout settles so re-renders don't yank to the top
|
||||||
|
QTimer.singleShot(0, lambda: self._scroll.verticalScrollBar().setValue(scroll_pos))
|
||||||
|
|
||||||
def _copy(self) -> None:
|
def _copy(self) -> None:
|
||||||
if self._sections:
|
if self._sections:
|
||||||
|
|||||||
@@ -147,7 +147,10 @@ class SharePage(QWidget):
|
|||||||
return
|
return
|
||||||
kind = data.get("type") # frames forwarded from a guest
|
kind = data.get("type") # frames forwarded from a guest
|
||||||
if kind == "req_full":
|
if kind == "req_full":
|
||||||
|
# A guest just joined — send a full frame AND the current terminal state, so a
|
||||||
|
# guest that joins *after* the host enabled the terminal still gets access.
|
||||||
self._host_ws.sendTextMessage(share.host_full_frame(self._sampler))
|
self._host_ws.sendTextMessage(share.host_full_frame(self._sampler))
|
||||||
|
self._send_terminal_state()
|
||||||
elif kind == "pty_in" and self._pty:
|
elif kind == "pty_in" and self._pty:
|
||||||
self._pty.write(base64.b64decode(data["data"]))
|
self._pty.write(base64.b64decode(data["data"]))
|
||||||
elif kind == "pty_resize" and self._pty:
|
elif kind == "pty_resize" and self._pty:
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import pyte
|
import pyte
|
||||||
from PySide6.QtCore import Qt, Signal
|
from PySide6.QtCore import Qt, Signal
|
||||||
from PySide6.QtGui import QFontDatabase, QFontMetrics
|
from PySide6.QtGui import QFontDatabase, QFontMetrics, QTextCursor
|
||||||
from PySide6.QtWidgets import QPlainTextEdit
|
from PySide6.QtWidgets import QPlainTextEdit
|
||||||
|
|
||||||
|
|
||||||
@@ -39,9 +39,13 @@ class TerminalView(QPlainTextEdit):
|
|||||||
self._render()
|
self._render()
|
||||||
|
|
||||||
def _render(self) -> None:
|
def _render(self) -> None:
|
||||||
bar = self.verticalScrollBar().value()
|
|
||||||
self.setPlainText("\n".join(self._screen.display))
|
self.setPlainText("\n".join(self._screen.display))
|
||||||
self.verticalScrollBar().setValue(bar)
|
# Follow the terminal cursor so output (e.g. `ls -la`) stays in view.
|
||||||
|
cursor = self.textCursor()
|
||||||
|
cursor.movePosition(QTextCursor.MoveOperation.Start)
|
||||||
|
cursor.movePosition(QTextCursor.MoveOperation.Down, QTextCursor.MoveMode.MoveAnchor, self._screen.cursor.y)
|
||||||
|
self.setTextCursor(cursor)
|
||||||
|
self.ensureCursorVisible()
|
||||||
|
|
||||||
def resizeEvent(self, event): # noqa: N802 (Qt override)
|
def resizeEvent(self, event): # noqa: N802 (Qt override)
|
||||||
super().resizeEvent(event)
|
super().resizeEvent(event)
|
||||||
|
|||||||
Reference in New Issue
Block a user