feat(ui): close the last gaps against the audit design (31/31)

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>
This commit is contained in:
NamPDT
2026-08-17 12:25:25 +09:00
co-authored by Claude Opus 5
parent 0fa61b6a95
commit cb68f87b35
15 changed files with 454 additions and 93 deletions
+43 -26
View File
@@ -19,7 +19,7 @@ from PySide6.QtWidgets import (
QCheckBox, QComboBox, QDateTimeEdit, QDialog, QDialogButtonBox, QFileDialog,
QFormLayout, QGroupBox, QHBoxLayout, QInputDialog, QLabel, QLineEdit,
QListWidget, QListWidgetItem, QMessageBox, QPlainTextEdit, QPushButton,
QScrollArea, QSpinBox, QVBoxLayout, QWidget,
QScrollArea, QSpinBox, QTabWidget, QVBoxLayout, QWidget,
)
from ..config import PROVIDER_LABELS
@@ -93,12 +93,11 @@ class TaskEditorDialog(QDialog):
root = QVBoxLayout(content)
# ---- basics ----------------------------------------------------
# Zero-height anchor: this block is a bare form, so the index needs
# something to scroll to.
self._anchor_basic = QWidget()
self._anchor_basic.setFixedHeight(0)
root.addWidget(self._anchor_basic)
form = QFormLayout()
# Held in its own widget so the whole block can be moved into a step
# tab below; it is a bare form, not a group box.
self._basic_box = QWidget()
form = QFormLayout(self._basic_box)
form.setContentsMargins(0, 0, 0, 0)
self.title_edit = QLineEdit(self.task.get("title", ""))
# Description is the source of truth. Its ✨ button GENERATES the Prompt
# (Input) FROM the description — the title is just the task's label and
@@ -211,7 +210,7 @@ class TaskEditorDialog(QDialog):
form.addRow(tr("schedtask.f_skill"), self.skill_combo)
form.addRow(tr("schedtask.f_priority"), self.priority_combo)
form.addRow(tr("schedtask.f_status"), self.status_combo)
root.addLayout(form)
root.addWidget(self._basic_box)
self._main_form = form
self._model_box = model_box
self._on_run_kind_changed() # apply agent/flow row visibility
@@ -433,25 +432,38 @@ class TaskEditorDialog(QDialog):
eform.addRow("", self.approval_chk)
root.addWidget(eg)
# Five groups in one long scroll — same problem, same fix as Settings.
from .widgets import section_index
self.section_list = section_index(scroll, [
(tr("schedtask.g_basic"), self._anchor_basic),
(tr("schedtask.g_schedule"), sg),
(tr("schedtask.g_input"), ig),
(tr("schedtask.g_dependency"), dg),
(tr("schedtask.g_execution"), eg),
])
# Three steps, as tabs: Nội dung → Lịch chạy → Liên kết. The five group
# boxes are re-parented into three pages — none is dropped, they are
# grouped by the question being answered rather than stacked in one
# scroll where the later ones are out of sight.
self.step_tabs = QTabWidget()
self.step_tabs.setObjectName("taskSteps")
pages = [
("schedtask.step_content", [self._basic_box, ig]),
("schedtask.step_schedule", [sg]),
("schedtask.step_link", [dg, eg]),
]
self._step_keys = [key for key, _ in pages]
for _key, groups in pages:
page = QWidget()
pv = QVBoxLayout(page)
pv.setContentsMargins(4, 8, 4, 4)
for g in groups:
root.removeWidget(g)
pv.addWidget(g)
pv.addStretch(1)
wrap = QScrollArea()
wrap.setWidgetResizable(True)
wrap.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
wrap.setWidget(page)
self.step_tabs.addTab(wrap, "")
outer.removeWidget(scroll)
body = QHBoxLayout()
body.setSpacing(10)
body.addWidget(self.section_list)
body.addWidget(scroll, 1)
outer.insertLayout(0, body, 1)
# Same floor as Settings: the dialog may not be narrower than its own
# content at the font in use, so nothing is ever cut off sideways.
self.setMinimumWidth(self.section_list.width()
+ content.sizeHint().width() + 60)
scroll.setParent(None)
outer.insertWidget(0, self.step_tabs, 1)
self._retranslate_steps()
# Floor the width at what the widest page needs, at the font in use.
self.setMinimumWidth(max(p.widget().sizeHint().width()
for p in self.step_tabs.findChildren(QScrollArea)) + 60)
buttons = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel)
buttons.button(QDialogButtonBox.Save).setIcon(icon("save"))
@@ -465,6 +477,11 @@ class TaskEditorDialog(QDialog):
from .widgets import guard_wheel
guard_wheel(self)
def _retranslate_steps(self) -> None:
"""Name the three step tabs for the current language."""
for i, key in enumerate(self._step_keys):
self.step_tabs.setTabText(i, f"{i + 1}. {tr(key)}")
def _apply_hints(self) -> None:
"""Tooltip hints on every non-obvious control, so each option explains
itself on hover."""