fix(ui): sections 17 and 18 were checked against the wrong text

check_design_parity read its checklist from build_audit_page.ANALYSIS. Eight
sections of the audit page are hand-written, and for those the generator's
text is NOT what the page says — so Settings and the Task editor were reported
as matching a design they had never been compared against. The Task editor was
in fact built backwards.

  * The checker now reads the "Thay đổi" bullets straight out of
    docs/ui-audit.html, and reports how many bullets on the page still have no
    probe (57 on the page, 32 probed) instead of implying full coverage.

  * Task editor: reverted from three step tabs to a left list + right panel,
    five rows matching the five real group boxes — which is what the page asks
    for, in as many words ("thay vì chia tab"), for consistency with Settings.

  * Settings now switches panels rather than scrolling, so both dialogs are
    navigated identically and neither is a long scroll any more.

  * Settings field presentation, as the page's second bullet asks: the six
    checkboxes became switches, and Language/Theme became segmented controls.
    ToggleSwitch subclasses QCheckBox and SegmentedControl exposes the slice
    of the QComboBox API this dialog uses, so no save/load path changed —
    verified by round-tripping language/theme/tray through _save().

All 22 task-editor fields and 14 settings fields verified present after the
move; 7 suites green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
NamPDT
2026-08-17 12:49:52 +09:00
co-authored by Claude Opus 5
parent cb68f87b35
commit 62bec1f984
7 changed files with 365 additions and 132 deletions
+68 -7
View File
@@ -26,6 +26,29 @@ os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
from capture_screens import _freeze_schedulers, _isolate_home, _load_fonts # noqa: E402
def page_proposals():
"""The 'Thay đổi' bullets as they appear ON THE PAGE, per section.
Read from docs/ui-audit.html rather than build_audit_page.ANALYSIS: eight
sections are hand-written, and for those two the generator's text is NOT
what the page shows. Checking against ANALYSIS reported Settings and the
Task editor as matching the design when the page asked for something else
(and, for the Task editor, the opposite).
"""
import re
html = (REPO / "docs" / "ui-audit.html").read_text(encoding="utf-8")
html = re.sub(r"data:image/png;base64,[A-Za-z0-9+/=]+", "", html)
out: dict[str, list[str]] = {}
for m in re.finditer(r'<section class="sec" id="([^"]+)">(.*?)</section>', html, re.S):
block = re.search(r'Thay đổi</div><ul class="pr">(.*?)</ul>', m.group(2), re.S)
if not block:
continue
out[m.group(1)] = [
re.sub(r"\s+", " ", re.sub(r"<[^>]+>", "", li)).strip()
for li in re.findall(r"<li>(.*?)</li>", block.group(1), re.S)]
return out
def build():
sandbox = _isolate_home()
from PySide6.QtWidgets import QApplication
@@ -213,6 +236,7 @@ def main() -> int:
# --- 9/15 Monitoring ---
from PySide6.QtWidgets import QHBoxLayout, QScrollArea
from PySide6.QtWidgets import QSpinBox as QSpinBoxT
ov = mon.findChildren(QScrollArea)[0].widget()
one_col = not isinstance(ov.layout(), QHBoxLayout)
add("monitoring-tổng-quan", "Một cột chính, mục có tiêu đề", one_col,
@@ -232,15 +256,31 @@ def main() -> int:
s = SettingsDialog(win.ctx)
add("dialog-settings", "Thêm cột mục lục bên trái",
s.section_list.count() == 5, f"{s.section_list.count()} mục")
have = [n for n in ("provider_combo", "language_combo", "theme_combo")
if getattr(s, n, None) is not None]
add("dialog-settings", "Gom Provider/Ngôn ngữ/Giao diện vào Settings",
len(have) == 3, f"{have} (cũng có ở hàng tài khoản trên rail)")
# The page asks for Ngôn ngữ/Giao diện in the Chung group, with AI Provider
# left as its own group — not everything merged together.
from cowork_local.ui.widgets import SegmentedControl, ToggleSwitch
in_general = s._general_box.isAncestorOf(s.language_combo) and \
s._general_box.isAncestorOf(s.theme_combo)
prov_apart = not s._general_box.isAncestorOf(s.provider_combo)
add("dialog-settings", "Gom Ngôn ngữ/Giao diện vào nhóm Chung; AI Provider vẫn riêng",
in_general and prov_apart,
f"ngôn ngữ+giao diện trong Chung={in_general} · provider tách riêng={prov_apart}")
n_switch = len(s.findChildren(ToggleSwitch))
n_seg = len(s.findChildren(SegmentedControl))
steppers = [w for w in s.findChildren(QSpinBoxT) if w.buttonSymbols() != 2]
add("dialog-settings",
"Field theo đúng loại: checkbox→toggle, dropdown 2–4 giá trị→segmented, số→stepper",
n_switch >= 6 and n_seg >= 2 and len(steppers) >= 1,
f"{n_switch} toggle · {n_seg} segmented · {len(steppers)} stepper")
s.close()
t = TaskEditorDialog(ctx=win.ctx)
steps = [t.step_tabs.tabText(i) for i in range(t.step_tabs.count())]
add("dialog-task-editor", "Chia 3 bước có tab: Nội dung → Lịch chạy → Liên kết",
len(steps) == 3, " · ".join(steps))
rows = [t.section_list.item(i).text() for i in range(t.section_list.count())]
like_settings = (t.section_list.count() == 5
and t.section_stack.count() == 5
and not hasattr(t, "step_tabs"))
add("dialog-task-editor",
"Danh sách trái + panel phải giống Cài đặt (không dùng tab), 5 mục = 5 group",
like_settings, " · ".join(rows))
t.close()
# --- 27 help dock ---
@@ -269,6 +309,19 @@ def main() -> int:
dock_top + dock.height() <= comp_top,
f"chấm đáy y={dock_top + dock.height()} · ô nhập đỉnh y={comp_top}")
# --- coverage: is every bullet ON THE PAGE actually probed? -------------
proposals = page_proposals()
probed = {}
for slug, text, _v, _e in R:
probed.setdefault(slug, 0)
probed[slug] += 1
gaps = []
for slug, bullets in proposals.items():
n_probe = probed.get(slug, 0)
if len(bullets) > n_probe:
for extra in bullets[n_probe:]:
gaps.append((slug, extra))
# --- report ---
order = ["OK", "KHAC", "CHUA", "TAY"]
counts = {k: 0 for k in order}
@@ -280,7 +333,15 @@ def main() -> int:
cur = slug
print(f" [{verdict:4}] {text}")
print(f" {ev}")
if gaps:
print()
print(f"*** DE XUAT TREN TRANG CHUA CO PHEP DO: {len(gaps)} ***")
for slug, text in gaps:
print(f" {slug}")
print(f" {text[:160]}")
print()
print(f"de xuat tren trang : {sum(len(v) for v in proposals.values())}"
f" · da co phep do : {len(R)}")
print("TONG KET: " + " · ".join(f"{k}={counts.get(k, 0)}" for k in order))
print(f" OK = dung thiet ke moi ({counts['OK']}/{len(R)})")
print(f" KHAC = co y lam khac, da ghi ly do")