fix(graphrag): crash on project rename/path change (DF-009)

session_events.py::_on_projects_changed (runs on every project
create/rename/delete) called self.structure._refresh_project_combo() —
but that method only ever existed on GraphRenderer, not on
StructureGraphView itself (self.structure). Renaming a project or
changing its working-directory path raised AttributeError and crashed
the app.

Add StructureGraphView.refresh_project_list(), matching the existing
schedule_rescan/set_project/prewarm forwarding pattern, and call that
instead. Add a regression test
(test_refresh_project_list_forwards_to_renderer).

Logged as DF-009 in Task Tracking Template.xlsx — root cause, fix and
regression test recorded (row was empty in this local copy despite
being reported as already logged there).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 09:46:33 +09:00
co-authored by Claude Sonnet 5
parent a20c7a2d69
commit f6c196ca87
4 changed files with 26 additions and 1 deletions
Binary file not shown.
@@ -219,6 +219,12 @@ class StructureGraphView(QWidget):
"""Dựng sẵn khung đồ thị trước khi người dùng bấm vào, để lần mở đầu không giật.""" """Dựng sẵn khung đồ thị trước khi người dùng bấm vào, để lần mở đầu không giật."""
self.renderer.prewarm() self.renderer.prewarm()
def refresh_project_list(self) -> None:
"""Project khác vừa được tạo/sửa/xoá/đổi tên — làm mới danh sách trong
bộ chọn project của renderer (bộ chọn KHÔNG tự nạp lại khi project
thay đổi ở màn khác, chỉ khi ``set_project`` được gọi)."""
self.renderer._refresh_project_combo()
def hideEvent(self, e): # noqa: N802 def hideEvent(self, e): # noqa: N802
# Leaving the GraphRAG tab → drop the temporary extracted info. # Leaving the GraphRAG tab → drop the temporary extracted info.
"""Rời màn GraphRAG thì xoá phần trích xuất tạm của khung hỏi-đáp.""" """Rời màn GraphRAG thì xoá phần trích xuất tạm của khung hỏi-đáp."""
+1 -1
View File
@@ -106,4 +106,4 @@ class SessionEventsMixin:
"""Project được tạo/sửa/xoá: gom nhóm lại cột lịch sử và cập nhật nhãn thư mục.""" """Project được tạo/sửa/xoá: gom nhóm lại cột lịch sử và cập nhật nhãn thư mục."""
self.sidebar.refresh() # History regroups by project self.sidebar.refresh() # History regroups by project
self.cowork._apply_output_folder_label() # project may have been renamed self.cowork._apply_output_folder_label() # project may have been renamed
self.structure._refresh_project_combo() # GraphRAG's project lock list follows too self.structure.refresh_project_list() # GraphRAG's project lock list follows too
@@ -55,6 +55,25 @@ def test_structure_graph_view_builds(ctx):
assert view.qa is not None assert view.qa is not None
def test_refresh_project_list_forwards_to_renderer(ctx):
"""Crash bug: ``presentation/shell/session_events.py::_on_projects_changed``
called ``self.structure._refresh_project_combo()`` — a method that only
ever existed on ``GraphRenderer``, not on ``StructureGraphView`` itself —
so creating/renaming/deleting a project (anywhere in the app) raised
``AttributeError`` and crashed. ``refresh_project_list()`` is the public
forwarding method callers must use instead (matching ``schedule_rescan``/
``set_project``/``prewarm``'s existing forwarding pattern)."""
from cowork_local.presentation.graph.structure_graph_view import StructureGraphView
view = StructureGraphView(ctx)
assert not hasattr(view, "_refresh_project_combo")
combo = view.renderer.project_combo
before = combo.count()
view.refresh_project_list() # must not raise
assert combo.count() == before # re-populated from the same project list, same size
def test_render_populates_the_scene_and_emits_graph_rendered(ctx, tmp_path): def test_render_populates_the_scene_and_emits_graph_rendered(ctx, tmp_path):
from cowork_local.presentation.graph.graph_renderer import GraphRenderer from cowork_local.presentation.graph.graph_renderer import GraphRenderer