check_design_parity.py reads its checklist from the audit page's own
proposals; it now reports every one of the 31 as implemented, with no
deliberate divergences left.
* Schedule: the Kanban/Calendar drop-list became a pair of tabs, and the
Running lane is outlined while it holds anything — dropping a card there
starts the task for real, so it should not look like the other six.
* Cowork: agent / routing / usage / folder moved out of the typing box into
their own status strip beneath it, styled as status rather than a second
toolbar. All of them stay interactive; the design's read-only strip would
have cost features.
* Folder: the path is written as the screen's title instead of sitting in a
read-only text box that looked editable and cost a row.
* GraphRAG: the second toolbar row is gone (Export joined the first), and
the one button that relabelled itself became Đồ thị | Tin nhắn tabs, so
the view you are NOT in is named too.
* Settings gained the theme picker, so language / provider / theme are all
reachable there as well as on the rail's account row.
* Task editor: the five group boxes are grouped into three step tabs
(Nội dung → Lịch chạy → Liên kết). All 22 fields verified present after
the move; only the old section index is gone, replaced by the tabs.
* The assistant dot now clears a screen's own bottom bar (Cowork's
composer), measured from the composer's top edge in window coordinates.
Also adds .gitattributes: without it a Windows checkout records CRLF and
every file reads as fully rewritten to a Linux CI runner.
Verification: 7 check_*.py suites green, no screen clipped at 1920/1366/1280,
and no dialog scrolls sideways at 9/11/14pt.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
114 lines
3.9 KiB
Python
114 lines
3.9 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, app):
|
||
"""(scroll-area overflow, index overflow) — each True means content is
|
||
wider than the space it is given.
|
||
|
||
A dialog built from step tabs has one scroll area per page, and a page that
|
||
is not current has stale geometry — so each tab is brought to the front
|
||
before its page is measured.
|
||
"""
|
||
from PySide6.QtWidgets import QListWidget, QScrollArea
|
||
over_area = False
|
||
tabs = getattr(dlg, "step_tabs", None)
|
||
if tabs is not None:
|
||
keep = tabs.currentIndex()
|
||
for i in range(tabs.count()):
|
||
tabs.setCurrentIndex(i)
|
||
for _ in range(3):
|
||
app.processEvents()
|
||
sa = tabs.widget(i)
|
||
if sa.widget().sizeHint().width() > sa.viewport().width():
|
||
over_area = True
|
||
tabs.setCurrentIndex(keep)
|
||
else:
|
||
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, app)
|
||
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())
|