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:
NamPDT
2026-08-17 11:40:13 +09:00
co-authored by Claude Opus 5
parent 291a611737
commit 0fa61b6a95
27 changed files with 4346 additions and 384 deletions
+31 -18
View File
@@ -92,39 +92,52 @@ class DashboardTab(QWidget):
self.refresh_btn.setIcon(icon("refresh"))
self.refresh_btn.setFixedWidth(34)
self.refresh_btn.clicked.connect(self.refresh)
# Two rows, grouped by what the controls do, instead of nine widgets
# strung across one line where the title, a date pager, two chart
# selectors, a currency picker and Refresh all read as one undifferentiated
# strip. Row 1 is "where am I"; row 2 is "what am I looking at".
head.addWidget(self._title, 1)
head.addWidget(self.chart_prev_btn)
head.addWidget(self._chart_period_lbl)
head.addWidget(self.chart_next_btn)
head.addWidget(self.gran_combo)
head.addWidget(self.metric_combo)
head.addWidget(self.currency_lbl)
head.addWidget(self.currency_combo)
head.addWidget(self.refresh_btn)
root.addLayout(head)
controls = QHBoxLayout()
controls.setSpacing(6)
controls.addWidget(self.chart_prev_btn) # period pager
controls.addWidget(self._chart_period_lbl)
controls.addWidget(self.chart_next_btn)
controls.addSpacing(12)
controls.addWidget(self.gran_combo) # what the chart plots
controls.addWidget(self.metric_combo)
controls.addStretch(1)
controls.addWidget(self.currency_lbl) # how money is displayed
controls.addWidget(self.currency_combo)
root.addLayout(controls)
# ---- stat cards ---------------------------------------------------
# Single row, 5 equal-width cards (same layout as Monitoring Overview)
# Cost is the headline this screen exists for, so it gets a card twice
# the height of the rest instead of being the fifth of five identical
# tiles — with six equal cards nothing said which number mattered.
cards_grid = QGridLayout()
cards_grid.setSpacing(8)
self.card_total = _StatCard()
self.card_in = _StatCard()
self.card_out = _StatCard()
self.card_cache = _StatCard()
self.card_cost = _StatCard()
for i, card in enumerate((self.card_total, self.card_in, self.card_out,
self.card_cache, self.card_cost)):
cards_grid.addWidget(card, 0, i)
self.card_cost = _StatCard().as_hero()
# Hero on the left, spanning both rows; the four supporting figures fill
# a 2×2 block beside it.
cards_grid.addWidget(self.card_cost, 0, 0, 2, 1)
for i, card in enumerate((self.card_total, self.card_in,
self.card_out, self.card_cache)):
cards_grid.addWidget(card, i // 2, 1 + i % 2)
# Budget: remaining/budget, direct entry, auto-warns red past 85% used.
self.budget_card = _BudgetCard()
self.budget_card.apply_btn.setIcon(icon("check"))
self.budget_card.apply_btn.clicked.connect(self._apply_budget)
cards_grid.addWidget(self.budget_card, 0, 5)
# Equal stretch on every column — otherwise the grid sizes each column
# to its widest cell's natural content (Budget's longer "$X / $Y" value
# + entry row made its column ~25% wider than the plain stat cards).
for col in range(6):
cards_grid.setColumnStretch(col, 1)
cards_grid.addWidget(self.budget_card, 0, 3, 2, 1)
# The hero and Budget columns get more room than the small tiles.
for col, stretch in ((0, 3), (1, 2), (2, 2), (3, 3)):
cards_grid.setColumnStretch(col, stretch)
root.addLayout(cards_grid)
# ---- token/cost within the selected period (spline): WEEK → 7 days