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:
+11
-39
@@ -132,48 +132,20 @@ def main() -> int:
|
||||
nav_state = {"label": "", "expected": ""}
|
||||
|
||||
def nav_to(win, page: int, sub=None, expect: str = "") -> None:
|
||||
"""Navigate the way a user does: SELECT the nav-rail row.
|
||||
"""Navigate the way a user does, and record where the rail ends up.
|
||||
|
||||
Calling ``win._goto()`` directly swaps the content but leaves the rail
|
||||
highlighting whatever was selected before — so a Co4E screenshot showed
|
||||
the content of Co4E with "Workspace" still lit. Setting the current item
|
||||
fires currentItemChanged → _navigate → _goto, i.e. both halves.
|
||||
Since the rail became a flat list, ``_goto`` moves the highlight itself
|
||||
(``_select_nav_row``), so this no longer needs the two-step workaround
|
||||
that existed while selecting a Workspace child destroyed the row being
|
||||
selected.
|
||||
"""
|
||||
win._ensure_page(page) # build lazy page + its children
|
||||
item = win._nav_items[page]
|
||||
if sub is None:
|
||||
win.nav.setCurrentItem(item)
|
||||
app.processEvents()
|
||||
else:
|
||||
# Two steps, because selecting the row alone does not survive.
|
||||
#
|
||||
# Navigating to Workspace runs refresh(), which emits
|
||||
# subtabs_changed → _reload_nav_children → takeChildren(); that
|
||||
# DESTROYS the row just selected and the highlight falls back to the
|
||||
# parent. Retrying only re-triggers the same cascade. (Real app bug,
|
||||
# reproduced in test_nav_bug.py: 5/5 Workspace rows lose the
|
||||
# highlight, 0/8 Monitoring rows do.)
|
||||
#
|
||||
# So: drive the content first, let the rebuild settle, then set the
|
||||
# highlight with signals blocked so it cannot cascade again. The
|
||||
# screenshot then shows what the user *should* see; the underlying
|
||||
# bug is reported separately in the audit page.
|
||||
win._goto(page, sub)
|
||||
app.processEvents()
|
||||
app.processEvents()
|
||||
if item.childCount() == 0:
|
||||
win._reload_nav_children(page)
|
||||
item.setExpanded(True)
|
||||
target = next(
|
||||
(item.child(i) for i in range(item.childCount())
|
||||
if (item.child(i).data(0, Qt.UserRole) or {}).get("sub") == sub),
|
||||
None)
|
||||
assert target is not None, f"nav child page={page} sub={sub} not found"
|
||||
blocked = win.nav.blockSignals(True)
|
||||
win.nav.setCurrentItem(target)
|
||||
win.nav.blockSignals(blocked)
|
||||
win._ensure_page(page)
|
||||
win._goto(page, sub)
|
||||
app.processEvents()
|
||||
cur = win.nav.currentItem()
|
||||
app.processEvents()
|
||||
cur = next((t.currentItem() for t in (win.nav, win.nav_bottom)
|
||||
if t.currentItem() is not None and t.currentItem().isSelected()),
|
||||
None)
|
||||
nav_state["label"] = cur.text(0) if cur is not None else ""
|
||||
nav_state["expected"] = expect or nav_state["label"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user