Feature/fsg gamma team ui fix #3
@@ -1074,7 +1074,14 @@ class MainWindow(QMainWindow):
|
|||||||
self.workspace.refresh() # re-list projects + threads on entry
|
self.workspace.refresh() # re-list projects + threads on entry
|
||||||
widget = self._page_widgets[page]
|
widget = self._page_widgets[page]
|
||||||
if sub is not None and hasattr(widget, "select_subtab"):
|
if sub is not None and hasattr(widget, "select_subtab"):
|
||||||
widget.select_subtab(sub)
|
# Enforce the project gate here rather than at each entry point. A
|
||||||
|
# greyed rail row cannot be clicked, but _goto is also reached from
|
||||||
|
# RECENTS and from startup restore, and it used to open a sub-tab
|
||||||
|
# the gate was holding shut — page shown, tab strip still hiding it.
|
||||||
|
if hasattr(widget, "subtab_available") and not widget.subtab_available(sub):
|
||||||
|
self.statusBar().showMessage(tr("app.nav.needs_project"), 4000)
|
||||||
|
else:
|
||||||
|
widget.select_subtab(sub)
|
||||||
# Move the highlight with the content, however navigation was triggered —
|
# Move the highlight with the content, however navigation was triggered —
|
||||||
# a programmatic _goto used to leave it on whatever was clicked last.
|
# a programmatic _goto used to leave it on whatever was clicked last.
|
||||||
if not self._nav_building:
|
if not self._nav_building:
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
"""With no project chosen, the gated screens must stay shut on every route.
|
||||||
|
|
||||||
|
The redesign shows Cowork and GraphRAG at all times instead of making them
|
||||||
|
appear and disappear. That is a presentation change only — the gate is the same
|
||||||
|
`isTabVisible` state as before — so this asserts the gate still actually holds,
|
||||||
|
and holds for programmatic jumps too, not just for the greyed rail rows.
|
||||||
|
|
||||||
|
Runs against an EMPTY home, not a copy of the real one: with any project on
|
||||||
|
disk the gate is open and the test proves nothing.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.insert(0, str(REPO.parent))
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||||
|
|
||||||
|
sandbox = Path(tempfile.mkdtemp(prefix="cowork-gate-"))
|
||||||
|
(sandbox / ".cowork_local").mkdir(parents=True, exist_ok=True)
|
||||||
|
for _var in ("USERPROFILE", "HOME"):
|
||||||
|
os.environ[_var] = str(sandbox)
|
||||||
|
os.environ.pop("HOMEDRIVE", None)
|
||||||
|
os.environ.pop("HOMEPATH", None)
|
||||||
|
|
||||||
|
from capture_screens import _apply_theme, _freeze_schedulers, _load_fonts # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
from PySide6.QtWidgets import QApplication
|
||||||
|
|
||||||
|
app = QApplication([])
|
||||||
|
_load_fonts()
|
||||||
|
_freeze_schedulers()
|
||||||
|
_apply_theme(app)
|
||||||
|
|
||||||
|
from cowork_local.config import CONFIG_DIR
|
||||||
|
assert str(sandbox) in str(CONFIG_DIR), f"isolation failed: {CONFIG_DIR}"
|
||||||
|
|
||||||
|
from cowork_local.app import MainWindow
|
||||||
|
from cowork_local.config import AppConfig
|
||||||
|
from cowork_local.i18n import set_language
|
||||||
|
from cowork_local.state import AppContext
|
||||||
|
|
||||||
|
set_language("vi")
|
||||||
|
win = MainWindow(AppContext(AppConfig.load()), user_name="local")
|
||||||
|
win.resize(1400, 900)
|
||||||
|
win.show()
|
||||||
|
app.processEvents()
|
||||||
|
|
||||||
|
fails = []
|
||||||
|
n_projects = len(win.workspace.project_choices())
|
||||||
|
print(f"project tren dia: {n_projects}")
|
||||||
|
if n_projects:
|
||||||
|
print("FAIL home khong rong — phep thu vo nghia")
|
||||||
|
sys.stdout.flush()
|
||||||
|
os._exit(1)
|
||||||
|
|
||||||
|
tree = win.nav
|
||||||
|
rows = [(i, tree.topLevelItem(i)) for i in range(tree.topLevelItemCount())]
|
||||||
|
locked = [(i, it) for i, it in rows if it.isDisabled()]
|
||||||
|
print("dong bi khoa :", [it.text(0) for _i, it in locked])
|
||||||
|
print("dong mo binh thuong:", [it.text(0) for _i, it in rows if not it.isDisabled()])
|
||||||
|
if not locked:
|
||||||
|
fails.append("khong project nao ma khong dong nao bi khoa")
|
||||||
|
|
||||||
|
for _i, it in locked:
|
||||||
|
if not it.toolTip(0):
|
||||||
|
fails.append(f"dong khoa '{it.text(0)}' khong noi ly do")
|
||||||
|
|
||||||
|
for i, it in locked:
|
||||||
|
label = it.text(0)
|
||||||
|
data = it.data(0, Qt.UserRole) or {}
|
||||||
|
before = win.workspace.current_subtab()
|
||||||
|
|
||||||
|
tree.setCurrentItem(it)
|
||||||
|
app.processEvents()
|
||||||
|
if win.workspace.current_subtab() != before:
|
||||||
|
fails.append(f"bam duoc vao '{label}' du dang khoa")
|
||||||
|
|
||||||
|
# the route a greyed row does not guard: a jump from code
|
||||||
|
win._goto(data.get("page", 0), data.get("sub"))
|
||||||
|
app.processEvents()
|
||||||
|
landed = win.workspace.current_subtab()
|
||||||
|
if landed == data.get("sub"):
|
||||||
|
fails.append(f"_goto mo duoc '{label}' trong khi cong dang dong")
|
||||||
|
print(f" '{label}': bam -> {before}, _goto -> {landed} "
|
||||||
|
f"(tab hien = {win.workspace.tabs.isTabVisible(data.get('sub'))})")
|
||||||
|
|
||||||
|
# controls that would act on a project must be off too
|
||||||
|
for name, widget in (("+ chat moi", win.nav_new_chat),
|
||||||
|
("chon project", win.nav_project_btn)):
|
||||||
|
if widget.isEnabled():
|
||||||
|
fails.append(f"'{name}' van bam duoc khi chua co project")
|
||||||
|
print(f"+ chat moi bat={win.nav_new_chat.isEnabled()} "
|
||||||
|
f"tooltip={win.nav_new_chat.toolTip()!r}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
for f in fails:
|
||||||
|
print("FAIL " + f)
|
||||||
|
print("PASS cong project van giu, ca khi bam lan khi goi tu code" if not fails
|
||||||
|
else f"{len(fails)} problem(s)")
|
||||||
|
sys.stdout.flush()
|
||||||
|
os._exit(1 if fails else 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -67,6 +67,14 @@ class WorkspaceTab(QWidget):
|
|||||||
self.tabs.isTabVisible(i))
|
self.tabs.isTabVisible(i))
|
||||||
for i in range(self.tabs.count())]
|
for i in range(self.tabs.count())]
|
||||||
|
|
||||||
|
def subtab_available(self, index: int) -> bool:
|
||||||
|
"""False while the project gate is holding this sub-tab shut.
|
||||||
|
|
||||||
|
The rail greys those rows out, but that only guards the rail. This lets
|
||||||
|
every other route ask the same question of the same state.
|
||||||
|
"""
|
||||||
|
return bool(0 <= index < self.tabs.count() and self.tabs.isTabVisible(index))
|
||||||
|
|
||||||
def select_subtab(self, index: int) -> None:
|
def select_subtab(self, index: int) -> None:
|
||||||
if 0 <= index < self.tabs.count():
|
if 0 <= index < self.tabs.count():
|
||||||
self.tabs.setCurrentIndex(index)
|
self.tabs.setCurrentIndex(index)
|
||||||
|
|||||||
Reference in New Issue
Block a user