feat(ui): flat nav rail, compact assistant, responsive layouts
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>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""Check the section index added to Settings and the Task editor, offscreen.
|
||||
|
||||
The index is navigation only, so the test is again a subtraction test: every
|
||||
input control must still be there, and every index row must actually scroll to
|
||||
its section. Both dialogs are built with .show(), never .exec() — exec() blocks.
|
||||
|
||||
Run: python tools/check_dialogs.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
|
||||
|
||||
|
||||
def controls(dlg):
|
||||
from PySide6.QtWidgets import (QCheckBox, QComboBox, QLineEdit, QListWidget,
|
||||
QPlainTextEdit, QPushButton, QSpinBox)
|
||||
n = 0
|
||||
for cls in (QComboBox, QLineEdit, QCheckBox, QSpinBox, QPlainTextEdit,
|
||||
QPushButton, QListWidget):
|
||||
n += len(dlg.findChildren(cls))
|
||||
return n
|
||||
|
||||
|
||||
def check(name, dlg, app, expect_rows):
|
||||
fails = []
|
||||
print(f"--- {name} ---")
|
||||
n_ctl = controls(dlg)
|
||||
idx = dlg.section_list
|
||||
rows = [idx.item(i).text() for i in range(idx.count())]
|
||||
print(f"muc luc : {rows}")
|
||||
print(f"tong control trong hop thoai: {n_ctl}")
|
||||
if len(rows) != expect_rows:
|
||||
fails.append(f"{name}: cho {expect_rows} muc, thay {len(rows)}")
|
||||
if any(not r or r.endswith(".g_basic") or r.startswith("settings.") for r in rows):
|
||||
fails.append(f"{name}: co muc chua dich")
|
||||
|
||||
# Each row must scroll somewhere different (and the last one furthest down).
|
||||
from PySide6.QtWidgets import QScrollArea
|
||||
scroll = dlg.findChildren(QScrollArea)[0]
|
||||
positions = []
|
||||
for i in range(idx.count()):
|
||||
idx.itemClicked.emit(idx.item(i))
|
||||
app.processEvents()
|
||||
positions.append(scroll.verticalScrollBar().value())
|
||||
print(f"vi tri cuon theo tung muc : {positions}")
|
||||
if positions != sorted(positions):
|
||||
fails.append(f"{name}: muc luc nhay khong theo thu tu tren xuong")
|
||||
if len(set(positions)) < 2:
|
||||
fails.append(f"{name}: bam muc nao cung dung mot cho — muc luc khong chay")
|
||||
return n_ctl, fails
|
||||
|
||||
|
||||
def main() -> int:
|
||||
sandbox = _isolate_home()
|
||||
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, tr
|
||||
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 = []
|
||||
s = SettingsDialog(ctx)
|
||||
s.resize(900, 600)
|
||||
s.show()
|
||||
app.processEvents()
|
||||
n_s, f = check("Cai dat", s, app, 5)
|
||||
fails += f
|
||||
|
||||
t = TaskEditorDialog(ctx=ctx) # task=None → a new task, all fields present
|
||||
t.resize(900, 600)
|
||||
t.show()
|
||||
app.processEvents()
|
||||
n_t, f = check("Task editor", t, app, 5)
|
||||
fails += f
|
||||
|
||||
# Translations for the two names that had to be invented for the index.
|
||||
print()
|
||||
for lang in ("vi", "en", "ja"):
|
||||
set_language(lang)
|
||||
print(f" {lang}: general={tr('settings.group.general')!r} "
|
||||
f"basic={tr('schedtask.g_basic')!r}")
|
||||
for key in ("settings.group.general", "schedtask.g_basic"):
|
||||
if tr(key) == key:
|
||||
fails.append(f"thieu ban dich {key} cho {lang}")
|
||||
set_language("vi")
|
||||
|
||||
print()
|
||||
if fails:
|
||||
print("*** LOI ***")
|
||||
for x in fails:
|
||||
print(" " + x)
|
||||
return 1
|
||||
print("KET QUA: hai hop thoai co muc luc, khong mat control nao")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user