Implements the redesign from docs/ui-audit.html. Rearrangement only — no
feature was removed; every control that moved kept its handler, and the
gates that used to hide things now grey them out instead.
Navigation
* The rail is one flat list: the five Workspace sub-views sit at the top
level instead of behind an accordion, with Dashboard/Monitoring pinned
at the foot and Settings below them.
* Cowork and GraphRAG stay listed and greyed while no project is
selected, rather than vanishing and resizing the menu under the user.
* Monitoring keeps its eight sub-views in its own tab strip (unhidden)
instead of doubling the rail's length.
* _goto now moves the highlight itself, fixing a long-standing bug where
programmatic navigation left the rail pointing at the previous screen.
* Rail header gained the project picker and "New chat"; RECENTS lists the
active project's threads. Both are second views of existing state — the
Cowork toolbar button and the full History panel are untouched.
* Provider / language / theme moved from the top bar to an account row at
the foot of the rail (same widgets, same signals).
Screens
* Co4E: the flow tab strip is gone (per the design); Flow Status became a
toolbar toggle with its own way back, and the three icon-only tabs became
four labelled, foldable sections in one column. One flow open at a time
is the one capability this costs; background runs are unaffected.
* Dashboard: header split into two rows; cost promoted to a hero card.
* Monitoring Overview: one scrolling column of titled sections; the model
price table got its own full-width section instead of sharing a row with
the CPU meters.
* Settings and Task editor gained a section index down the left.
* Help dock: 84x64 launcher + chevron became one 26px dot that expands to
a labelled pill on hover; "hide to the edge" moved into the panel's menu.
Layout
* The window's minimum width dropped from 1453px to 768px. The main cause
was a QTabWidget taking its minimum from the widest page even when that
page is hidden, so Co4E was forcing Project and Cowork wide.
* Secondary panes fold themselves on a narrow window and restore when it
grows, never overriding a fold the user made.
* The long dialogs no longer scroll sideways at any font size.
Verification: tools/check_*.py build a real MainWindow offscreen against a
copy of ~/.cowork_local with the schedulers no-oped. check_design_parity.py
reads its checklist straight from the audit page's own proposals.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
95 lines
3.2 KiB
Python
95 lines
3.2 KiB
Python
"""Prove the long dialogs never scroll sideways — including at large fonts.
|
||
|
||
The report that started this came from a display at 125–150% scaling, where
|
||
every label is wider than on a 100% screen. Rather than trusting one font size,
|
||
this runs each dialog at several point sizes and several widths and fails if any
|
||
horizontal scrollbar turns up, in the scroll area or in the section index.
|
||
|
||
Run: python tools/check_no_hscroll.py
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import sys
|
||
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")
|
||
|
||
from capture_screens import _isolate_home, _load_fonts # noqa: E402
|
||
|
||
WIDTHS = (1100, 964, 820, 700)
|
||
POINTS = (9, 11, 14) # ~100%, ~125%, ~150% display scaling
|
||
|
||
|
||
def hscroll(dlg):
|
||
"""(scroll-area overflow, index overflow) — each True means a bar appears."""
|
||
from PySide6.QtWidgets import QListWidget, QScrollArea
|
||
sa = dlg.findChildren(QScrollArea)[0]
|
||
over_area = sa.widget().sizeHint().width() > sa.viewport().width()
|
||
idx = dlg.findChild(QListWidget, "sectionIndex")
|
||
over_idx = False
|
||
if idx is not None:
|
||
over_idx = idx.sizeHintForColumn(0) > idx.viewport().width()
|
||
return over_area, over_idx
|
||
|
||
|
||
def main() -> int:
|
||
sandbox = _isolate_home()
|
||
from PySide6.QtGui import QFont
|
||
from PySide6.QtWidgets import QApplication
|
||
|
||
app = QApplication([])
|
||
_load_fonts()
|
||
|
||
from cowork_local.config import AppConfig, CONFIG_DIR
|
||
assert str(sandbox) in str(CONFIG_DIR), f"isolation failed: {CONFIG_DIR}"
|
||
|
||
from cowork_local.i18n import set_language
|
||
from cowork_local.state import AppContext
|
||
from cowork_local.ui.settings_dialog import SettingsDialog
|
||
from cowork_local.ui.task_editor_dialog import TaskEditorDialog
|
||
|
||
set_language("vi")
|
||
ctx = AppContext(AppConfig.load())
|
||
fails: list[str] = []
|
||
|
||
for pt in POINTS:
|
||
f = QFont(app.font())
|
||
f.setPointSize(pt)
|
||
app.setFont(f)
|
||
for name, make in (("Cai dat", lambda: SettingsDialog(ctx)),
|
||
("Task editor", lambda: TaskEditorDialog(ctx=ctx))):
|
||
dlg = make()
|
||
dlg.show()
|
||
row = []
|
||
for w in WIDTHS:
|
||
dlg.resize(w, 900)
|
||
for _ in range(4):
|
||
app.processEvents()
|
||
over_area, over_idx = hscroll(dlg)
|
||
row.append(f"{w}:{'A' if over_area else '.'}{'I' if over_idx else '.'}")
|
||
if over_area:
|
||
fails.append(f"{name} @ {pt}pt {w}px — vung cuon tran ngang")
|
||
if over_idx:
|
||
fails.append(f"{name} @ {pt}pt {w}px — muc luc tran ngang")
|
||
print(f" {pt:>2}pt {name:12} {' '.join(row)}")
|
||
dlg.close()
|
||
print()
|
||
|
||
print("A = vung cuon tran · I = muc luc tran · . = khong tran")
|
||
print()
|
||
if fails:
|
||
print("*** LOI ***")
|
||
for x in fails:
|
||
print(" " + x)
|
||
return 1
|
||
print("KET QUA: khong hop thoai nao cuon ngang, o moi co chu va be rong da thu")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|