fix(graph): vào màn GraphRAG tự điền đường dẫn quét từ project đang chọn

GraphRenderer.auto_scan_and_fit() thoát sớm khi ô đường dẫn rỗng, trong khi
chính nút Scan lại có đường lùi (path_edit.text() or Path.cwd()). Bất đối xứng
đó nghĩa là vào màn thì không làm gì, bấm Scan thì chạy.

Cố ý KHÔNG lấy cwd() làm đường lùi như nút Scan — quét thư mục làm việc của
tiến trình là quét một cây không liên quan gì tới project và có thể rất lớn.
Điền từ project đang hoạt động, không có project thì vẫn không quét.

LƯU Ý: chưa tái hiện được ca "lần đầu khởi động không tự nạp" mà người dùng báo.
Probe với project và thư mục thật cho thấy cả hai đường (gọi thẳng
auto_scan_and_fit và bấm GraphRAG trên thanh menu) đều khởi động lượt quét. Đây
là bất đối xứng duy nhất tìm được; cần biết ô đường dẫn và combo project đang
hiện gì lúc lỗi xảy ra để đi tiếp.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-10 01:34:37 +09:00
committed by thanhnv
co-authored by Claude Opus 5
parent 8423bfc9a8
commit fd53c1cb42
2 changed files with 87 additions and 0 deletions
@@ -105,6 +105,32 @@ class StructureGraphView(QWidget):
# sau — nên panel phải bật theo, không thì lượt quét đó lại im lặng.
self.renderer.project_changed.connect(self._on_project_scan_started)
def _ensure_scan_path(self) -> None:
"""Điền ô đường dẫn từ project đang hoạt động nếu nó đang rỗng.
``GraphRenderer.auto_scan_and_fit`` thoát sớm khi ô đường dẫn rỗng,
trong khi chính nút Scan lại có đường lùi (``path_edit.text() or
Path.cwd()``). Bất đối xứng đó nghĩa là: vào màn thì không làm gì, bấm
Scan thì chạy — đúng thứ người dùng phàn nàn.
Điền từ project đang chọn chứ KHÔNG lấy ``cwd()`` làm đường lùi như nút
Scan: quét thư mục làm việc của tiến trình là quét một cây không liên
quan gì tới project, và nó có thể rất lớn.
"""
if self.renderer.path_edit.text().strip():
return
ctx = getattr(self, "ctx", None)
pid = (getattr(ctx, "active_project_id", "") or "").strip()
if not pid or pid == "default":
return
try:
from ...core.projects import load_project
project = load_project(pid)
except Exception: # noqa: BLE001
return
if project is not None:
self.renderer.path_edit.setText(str(project.workspace_dir()))
def _on_project_scan_started(self) -> None:
"""Renderer vừa đổi project. Nó chỉ quét ngay khi màn này đang mở."""
if self.renderer.isVisible():
@@ -156,6 +182,7 @@ class StructureGraphView(QWidget):
``graph_rendered`` phát — bao trọn cả lượt quét chạy ở luồng nền phía
sau, chứ không tắt ngay khi hàm này trả về.
"""
self._ensure_scan_path()
da_dung_khung = self.renderer.web is not None
self._show_busy("structure.scanning" if da_dung_khung else "structure.loading_view")
try:
+60
View File
@@ -55,6 +55,7 @@ def _dung(qapp, ViewCls, web=None, path="C:/tmp"):
v.renderer = _Renderer(qapp, web=web, path=path)
v.renderer._view = v
v._workspace_project = None
v.ctx = type("C", (), {"active_project_id": "default"})()
v.resize(800, 600)
v._build_busy_panel()
return v
@@ -199,3 +200,62 @@ def test_doi_project_khi_man_dang_an_thi_khong_bat_panel(qapp, view):
assert v._busy.isHidden() is True
finally:
v.deleteLater()
# ---- đường dẫn quét rỗng thì lấy từ project đang hoạt động ---------------
def test_o_duong_dan_rong_thi_dien_tu_project_dang_chon(qapp, view, monkeypatch, tmp_path):
"""GraphRenderer.auto_scan_and_fit thoát sớm khi ô đường dẫn rỗng, trong khi
nút Scan lại có đường lùi — vào màn không làm gì, bấm Scan thì chạy."""
import cowork_local.core.projects as projects
ws = tmp_path / "ws"
ws.mkdir()
class _P:
project_id = "p1"
def workspace_dir(self, base=None):
return ws
monkeypatch.setattr(projects, "load_project", lambda pid: _P() if pid == "p1" else None)
v = _dung(qapp, view, path="")
v.ctx = type("C", (), {"active_project_id": "p1"})()
v.renderer.path_edit = type("E", (), {
"_t": "",
"text": lambda s: s._t,
"setText": lambda s, t: setattr(s, "_t", t)})()
try:
v._ensure_scan_path()
assert v.renderer.path_edit.text() == str(ws)
finally:
v.deleteLater()
def test_da_co_duong_dan_thi_khong_ghi_de(qapp, view):
v = _dung(qapp, view, path="C:/da-chon")
v.ctx = type("C", (), {"active_project_id": "p1"})()
try:
v._ensure_scan_path()
assert v.renderer.path_edit.text() == "C:/da-chon"
finally:
v.deleteLater()
def test_chua_chon_project_thi_khong_bia_duong_dan(qapp, view):
"""Không lấy cwd() làm đường lùi: quét thư mục làm việc của tiến trình là
quét một cây không liên quan gì tới project."""
v = _dung(qapp, view, path="")
v.ctx = type("C", (), {"active_project_id": "default"})()
v.renderer.path_edit = type("E", (), {
"_t": "",
"text": lambda s: s._t,
"setText": lambda s, t: setattr(s, "_t", t)})()
try:
v._ensure_scan_path()
assert v.renderer.path_edit.text() == ""
finally:
v.deleteLater()