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:
+46
-3
@@ -10,8 +10,8 @@ from PySide6.QtGui import QGuiApplication
|
||||
from PySide6.QtWidgets import (
|
||||
QCheckBox, QComboBox, QDialog, QDialogButtonBox, QFileDialog, QFormLayout,
|
||||
QGroupBox, QHBoxLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem,
|
||||
QMessageBox, QPushButton, QScrollArea, QSpinBox, QTreeWidget, QTreeWidgetItem,
|
||||
QVBoxLayout, QWidget,
|
||||
QMessageBox, QPushButton, QScrollArea, QSizePolicy, QSpinBox, QTreeWidget,
|
||||
QTreeWidgetItem, QVBoxLayout, QWidget,
|
||||
)
|
||||
|
||||
from ..config import PROVIDER_LABELS
|
||||
@@ -42,6 +42,10 @@ class SettingsDialog(QDialog):
|
||||
outer = QVBoxLayout(self)
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
# Never sideways: the content must fit the width it is given and scroll
|
||||
# only downwards. Anything too wide has to shrink (see _model_combo and
|
||||
# _with_load), not push a second scrollbar onto the user.
|
||||
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self._content = QWidget()
|
||||
root = QVBoxLayout(self._content)
|
||||
|
||||
@@ -59,6 +63,11 @@ class SettingsDialog(QDialog):
|
||||
self.notify_chk = QCheckBox(tr("settings.tray_notify"))
|
||||
self.notify_chk.setChecked(bool(data.get("tray", {}).get("notify_on_done", True)))
|
||||
top.addRow("", self.notify_chk)
|
||||
# Zero-height anchor so the index can scroll to this section, which is a
|
||||
# bare form rather than a group box.
|
||||
self._anchor_general = QWidget()
|
||||
self._anchor_general.setFixedHeight(0)
|
||||
root.addWidget(self._anchor_general)
|
||||
root.addLayout(top)
|
||||
|
||||
self._load_workers = []
|
||||
@@ -291,10 +300,31 @@ class SettingsDialog(QDialog):
|
||||
|
||||
note = QLabel(tr("settings.tip"))
|
||||
note.setObjectName("hint")
|
||||
note.setWordWrap(True) # otherwise this one line sets the dialog's width
|
||||
root.addWidget(note)
|
||||
|
||||
scroll.setWidget(self._content)
|
||||
outer.addWidget(scroll, 1)
|
||||
# Five group boxes in one column, with no way to see what was further
|
||||
# down — the index says what is in here and jumps straight to it.
|
||||
from .widgets import section_index
|
||||
self.section_list = section_index(scroll, [
|
||||
(tr("settings.group.general"), self._anchor_general),
|
||||
(tr("settings.group.provider"), prov_group),
|
||||
(tr("settings.group.sandbox"), self.sandbox_group),
|
||||
(tr("settings.group.parameter"), param_group),
|
||||
(tr("routing.settings_group"), routing_group),
|
||||
])
|
||||
body = QHBoxLayout()
|
||||
body.setSpacing(10)
|
||||
body.addWidget(self.section_list)
|
||||
body.addWidget(scroll, 1)
|
||||
outer.addLayout(body, 1)
|
||||
# Floor the dialog at the width its own content needs AT THE CURRENT
|
||||
# FONT. On a 125%/150% display everything is wider, and without this the
|
||||
# form was simply cut off (or scrolled sideways) instead of the window
|
||||
# refusing to get that small.
|
||||
self.setMinimumWidth(self.section_list.width()
|
||||
+ self._content.sizeHint().width() + 60)
|
||||
|
||||
buttons = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel)
|
||||
buttons.accepted.connect(self._save)
|
||||
@@ -355,6 +385,13 @@ class SettingsDialog(QDialog):
|
||||
def _model_combo(value: str) -> QComboBox:
|
||||
combo = QComboBox()
|
||||
combo.setEditable(True)
|
||||
# A combo sizes itself to its longest entry by default; model ids are
|
||||
# long, so the row grew past the dialog and forced a sideways scrollbar
|
||||
# (worse at 125%/150% display scaling). Let it shrink and use a popup
|
||||
# wider than the closed box instead.
|
||||
combo.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
|
||||
combo.setMinimumContentsLength(8)
|
||||
combo.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
|
||||
if value:
|
||||
combo.addItem(value)
|
||||
combo.setCurrentText(value)
|
||||
@@ -377,6 +414,12 @@ class SettingsDialog(QDialog):
|
||||
test_btn.clicked.connect(
|
||||
lambda: self._test_connection(self.provider_combo.currentData(), status))
|
||||
lay.addWidget(test_btn)
|
||||
# The two buttons keep their natural size; the combo gives way. Without
|
||||
# this the row's minimum was combo + both buttons and nothing could
|
||||
# shrink, so the dialog scrolled sideways instead.
|
||||
for b in (btn, test_btn):
|
||||
b.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
row.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed)
|
||||
return row
|
||||
|
||||
def _stash_provider_fields(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user