Commit Graph
14 Commits
Author SHA1 Message Date
Nam Pham Dinh ThanhandClaude Opus 5 a0cef768c9 Restore the two gating rules the redesign dropped, and put the dot back in its corner
A1 — hiding the assistant from the dot. The old build kept a chevron button
beside the dot wired to _hide_to_edge; the redesign moved that action into the
panel's ⋯ menu, so hiding went from one click to open-panel → ⋯ → Ẩn. The
drawing has no button there (26px, no text, no chevron), so the reach comes
back as a right-click on the dot, with the tooltip saying so.

A2 — per-part cost. Folding Input/Output/Cache into the total tile's sub-line
carried their token counts but not their prices, which live only on those three
cards, so the cost breakdown left the screen entirely. The wireframe asks for
the tiles anyway — "$0.31 Tổng chi phí · 57 lượt / 395.4K Tổng token / 292.8K
Input / 102.7K Output / 108.9K Cache" — so they are back as tiles, each with its
price, and the turn count rides on the cost tile's label as drawn.

The dot on Cowork. _update_dock_guard lifted it whenever a composer existed,
testing only the vertical axis. On a wide window the composer ends at the chat
column's right edge — x=1688 against a dot at x=1892 — so the dot rose 156px to
clear something that was never under it, and Cowork became the one screen where
it was not in the corner. It now lifts only when the two actually overlap:
18px from the bottom at 1936px wide, still lifted at 1200 and 900 where the
composer does reach under it.

Also: open_tooltip and show_tooltip still said "App Assistant" after the rename.

check_dock_corner covers the corner rule at three widths and fails if the lift
comes back unconditionally.

18/18 checkers pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 12:30:50 +09:00
Nam Pham Dinh ThanhandClaude Opus 5 16406f863f Enforce the project gate in _goto, not only on the rail rows
Showing Cowork and GraphRAG always, greyed, instead of letting them appear and
vanish, moved the gate from "the row is absent" to "the row is disabled". That
guards the rail — Qt will not select a disabled row — but it guards only the
rail, and _goto is also reached from a RECENTS click and from startup restore.
Called with a gated sub-tab it opened the page while the workspace's own tab
strip still had it hidden.

_goto now asks WorkspaceTab.subtab_available() — the same isTabVisible state
that greys the row — and shows "Chọn project trước" in the status bar instead
of navigating. One gate, one place, whatever triggered the jump.

check_project_gate runs against an EMPTY home, because with any project on disk
the gate is open and the test proves nothing. It asserts the gated rows are
disabled and say why, that clicking them does nothing, that _goto refuses them,
and that + new chat and the project picker are off. Dropping the _goto guard
fails it with "_goto mo duoc 'Cowork' trong khi cong dang dong".

17/17 checkers pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 12:06:40 +09:00
Nam Pham Dinh ThanhandClaude Opus 5 d37a0c1578 Let the splitter handle beside the rail actually resize it
The rail was setFixedWidth, so the handle QSplitter draws between it and the
content was inert — it looked draggable and did nothing when dragged.

The expanded rail is now a range (132–360px) instead of one number, so the
handle has something to give. Collapsing still pins it at 54px exactly, and a
width dragged to is remembered across the fold rather than snapping back to the
150px default. Long project names and RECENTS threads are what a wider rail
buys.

check_rail_resize drives the splitter the way the handle does and asserts the
rail follows, clamps at both ends, folds to 54px and comes back to the chosen
width. Restoring setFixedWidth fails it — the first attempt at that mutation
passed, because setMaximumWidth on the next line quietly undid it.

16/16 checkers pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 11:55:35 +09:00
Nam Pham Dinh ThanhandClaude Opus 5 bcdfe43c7c Keep the project picker reachable while the rail is collapsed
The picker was hidden outright at 54px, on the grounds that a combo box there
could only show a stub of a name. True, but it left the collapsed rail with no
way to change project at all — and the audit page's own inventory of the 11
collapsible controls says the rail's fold "giữ nguyên toàn bộ".

A folder QToolButton stands in for it: same items, and choosing one moves the
combo, so _on_rail_project_pick stays the single code path. Its tooltip carries
the current project name, which the combo could not have shown anyway. It
matches the + button's width and drops its menu arrow — at 42px the arrow
would take a third of the row to say nothing.

check_nav now collapses the rail and requires a reachable picker whose menu
matches the combo item for item, then picks a row and asserts the project
actually changed. Hiding the button fails it.

15/15 checkers pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 11:41:03 +09:00
Nam Pham Dinh ThanhandClaude Opus 5 ac29f519cc Stop the rail destinations sliding to the middle when it collapses
The scroll body holds [nav, RECENTS header, RECENTS], and RECENTS carries the
only stretch factor. Collapsing hides it, which leaves a fixed-height nav tree
(app.py sets one per row count) and nothing that can expand — so the box layout
centred what was left and the destinations dropped 297px down a 1048px window,
with the same gap opening below them.

A stretch spacer at the foot of the body takes the slack instead. It takes none
of it while RECENTS is visible, because RECENTS carries stretch 1 against its 0,
so the open rail is byte-for-byte what it was.

This is what "thu gọn menu lại ra giữa" meant, reported three times. I read it
as horizontal each time and measured x — which was 4px and correct throughout.
The report was about the vertical axis and I never checked it.

check_rail_align now measures the gap between the + button and the first
destination in both states: 6px/6px now, 6px/223px with the spacer removed.

15/15 checkers pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 11:11:55 +09:00
Nam Pham Dinh ThanhandClaude Opus 5 cb03bc1494 Pin rail row icons to the left edge instead of letting Qt centre them
QStyledItemDelegate hands the style decorationAlignment = AlignHCenter |
AlignVCenter. For a row with a label that is invisible — the icon sits left of
the text either way — but every rail row loses its label when the rail
collapses to 54px, and a centred decoration then lands wherever the column
happens to leave room. _NavItemDelegate overrides it to AlignLeft, so the icon
is on the row's left padding regardless of column width, style or platform.

Honest limit: this was reported from the running app and I could not reproduce
it offscreen — sweeping the column width leaves the icon at a fixed x with or
without the delegate. What is verifiable is the instruction, and centring is
the only mechanism by which Qt can put a label-less row's icon anywhere but
the left. check_rail_align now asserts the flag itself, which fails (132) the
moment the delegate is removed, alongside the column sweep.

Settings follows the rows to their new inset (padding-left 7px → 4px). Its
label still sat 3px left of Dashboard's because a button leaves less air
between icon and label than a tree row does; a 19px icon box closes that to
1px, with the 16px glyph left-aligned inside it.

15/15 checkers pass, no flakes this run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 11:01:56 +09:00
Nam Pham Dinh ThanhandClaude Opus 5 d060d5679a Align rail Settings, translate the help transcript, style the checkers
Settings sat 7px further in than the Dashboard/Giám sát rows above it — its
QSS gave it a 6px side margin where those rows start at the rail edge. At the
collapsed 54px width that put its icon near the middle of the rail, which is
what "thu gọn menu lại ra giữa" was describing. Icon and label now start on
the same x as the rows, open and collapsed, in both themes.

The help panel's transcript is rendered HTML, so switching language re-labelled
the chrome but left the greeting — and the "AI Assistant" speaker label — in
whatever language the panel was built in. retranslate() now rewrites the
greeting (matched by identity, so a real reply is never touched) and re-renders.

The sparkle is #FDBE59, sampled from the audit page's own render. Its CSS says
.spark{color:#0F9B8A}, but the glyph is the ✨ emoji and a colour emoji ignores
CSS colour, so the page has always drawn a gold star.

Behind all three: MainWindow does not style itself — run() calls
app.setStyleSheet — so 12 of 13 checkers were measuring a window with no
padding, margins or borders. Every QSS-driven layout bug was invisible to them,
and an unstyled window reported an icon drift that does not exist. Added
_apply_theme() and wired it through.

Two checker repairs that followed:
  · check_no_hscroll flagged the 9pt dialogs on sizeHintForColumn(0), which
    returns 182px at 9pt, 11pt and 14pt alike. Nothing was clipped. It now
    compares the painted text against the width actually on screen, and fails
    on a squeezed list (24 combos) where the old test passed.
  · the checkers print Vietnamese and died mid-report on a cp932 console.

New: check_rail_align (icons hold one line, both themes, both states) and
check_help_i18n (transcript follows the language). Both verified to fail
without their fix.

15/15 checkers pass. check_nav and check_design_parity segfault in Qt teardown
roughly one run in three — pre-existing, after the verdict prints, and it
happens with or without the theme change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 10:48:11 +09:00
NamPDTandClaude Opus 5 f15557483f fix(ui): Monitoring Overview now reads like the wireframe, and the rail stops overlapping
Two problems, both only visible at the window size this machine actually opens
(1024×571 on a 1280×672 work area):

  * The rail drew the "GẦN ĐÂY" heading on top of the last nav row. nav and the
    bottom group have fixed heights, so a short window put the entire squeeze on
    RECENTS; once that hit zero the layout had nowhere left to give. The
    destinations and RECENTS now scroll together inside their own area, while
    the bottom group, Settings and the account row stay pinned.

  * Overview did not look like the drawing. Fixed, in the order the page draws
    them: sections are quiet caps headings with flat content instead of six
    bordered boxes; the four resource metrics sit ACROSS one row rather than
    stacked four deep; and "Hoạt động gần đây" moved from second place to the
    bottom beside the audit log, so the reading order is cost → machine →
    permissions → prices → what happened. At 1280×760 the page now reaches the
    price table on first screen; before it showed one and a half sections.

Two deliberate differences from the drawing, both because matching it would
remove information: the wireframe shows four KPI tiles where the app has five
real metrics plus the budget entry card, and it writes resources as one text
line where the app keeps the CPU/RAM gauges.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 14:25:18 +09:00
NamPDTandClaude Opus 5 b6dee044c9 feat(ui): adapt to the screen and its scaling, not to fixed pixels
Two things differ between machines and only one of them is width: a 4K panel
has more pixels, while a 125%/150% display has the same logical pixels holding
LESS, because every label and margin is taller. Breakpoints written as raw
pixels only hold on the machine they were tuned on.

  * ui_scale() derives a factor from font height (1.0 at the 15px line the
    layouts were measured against) and every narrow-guard threshold is
    multiplied by it, so panes fold when the content is cramped rather than
    when a number is crossed.
  * The window takes a share of the available screen (80% × 85%) with the old
    1180×760 as the floor, instead of opening at that size on any monitor.
  * Moving the window to another screen re-pins the assistant and re-decides
    the fold, since the new screen's work area and scaling may differ.

Found by tools/check_multi_screen.py, which walks 5 window sizes × 3 font
scales:

  * At 150%, Schedule was clipped on 1280 and 1366 screens and the window's
    own minimum grew to 1459px — wider than a 1280 laptop, so the app could
    not fit at all. The cause was not the lanes: the one-line lane-count
    summary in the header reported a sizeHint wide enough to set the minimum
    width of the entire window. It now yields first (its text stays in the
    tooltip); the window minimum drops 1459 → 752 and holds there at every
    scale.

Also: these checkers exited 0xC0000409 from a Qt teardown crash AFTER printing
their verdict. check_probes_bite decides whether a probe caught its mutation by
reading exit codes, so a crash would have counted as "caught" — the round could
have passed while proving nothing. They now flush and os._exit with the real
verdict, and round 5 still catches all six mutations.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 14:10:19 +09:00
NamPDTandClaude Opus 5 f381fd298a test(ui): five verification rounds, and two things they found
Five rounds, each looking from an angle the previous one cannot:

  1. text     — every "Thay đổi" bullet on the page vs a probe (existing)
  2. geometry — check_layout_geometry.py: reading order of the rail, section
                order down Monitoring, which side each pane is on, and the size
                relationships the design names (hero card, 26px dot)
  3. inventory— check_controls_alive.py: every control in controls.json still
                present in the built app, attributed to its owning CLASS via the
                baseline commit (a file holds several classes, so a per-file
                check reported eight dialog controls as missing)
  4. eyes     — rendered screens read against the wireframes
  5. adversarial — check_probes_bite.py: break one feature at a time and fail if
                the matching check still passes

What rounds 4 and 5 caught, which 1-3 could not:

  * Schedule showed six of seven lanes; the seventh needed a horizontal
    scroll. The design says "giữ đủ 7 lane, thu hẹp cho vừa một màn". Lane
    minimum width 190 → 150, so 7 × 150 + gaps fits a 1280 window. Round 2 now
    measures this instead of relying on someone noticing.

  * check_design_parity ALWAYS returned 0. It was a report, not a check: every
    probe in it was incapable of failing, so a regression would print on screen
    and still exit green. It now exits non-zero when anything is CHUA — which
    is what let round 5 detect the two mutations it had been sleeping through.

Also fixed in the harness itself: it rewrote line endings while restoring
mutated files (read_text/write_text translate both ways), and it compared the
tree against "clean" rather than against its own starting state.

All ten checkers green by exit code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 13:55:47 +09:00
NamPDTandClaude Opus 5 62bec1f984 fix(ui): sections 17 and 18 were checked against the wrong text
check_design_parity read its checklist from build_audit_page.ANALYSIS. Eight
sections of the audit page are hand-written, and for those the generator's
text is NOT what the page says — so Settings and the Task editor were reported
as matching a design they had never been compared against. The Task editor was
in fact built backwards.

  * The checker now reads the "Thay đổi" bullets straight out of
    docs/ui-audit.html, and reports how many bullets on the page still have no
    probe (57 on the page, 32 probed) instead of implying full coverage.

  * Task editor: reverted from three step tabs to a left list + right panel,
    five rows matching the five real group boxes — which is what the page asks
    for, in as many words ("thay vì chia tab"), for consistency with Settings.

  * Settings now switches panels rather than scrolling, so both dialogs are
    navigated identically and neither is a long scroll any more.

  * Settings field presentation, as the page's second bullet asks: the six
    checkboxes became switches, and Language/Theme became segmented controls.
    ToggleSwitch subclasses QCheckBox and SegmentedControl exposes the slice
    of the QComboBox API this dialog uses, so no save/load path changed —
    verified by round-tripping language/theme/tray through _save().

All 22 task-editor fields and 14 settings fields verified present after the
move; 7 suites green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 12:49:52 +09:00
NamPDTandClaude Opus 5 cb68f87b35 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>
2026-08-17 12:25:25 +09:00
NamPDTandClaude Opus 5 0fa61b6a95 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>
2026-08-17 11:40:13 +09:00
NamPDTandClaude Opus 5 291a611737 chore: sync local working copy as of 2026-08-15
The Gitea repo was initialised from an earlier snapshot, so main and the
machine this runs on had drifted apart in 153 files before any UI work
started. This commit brings the branch up to the local tree as it stood
on 2026-08-15 21:31 (from cowork_local.7z), so the redesign that follows
shows up as its own reviewable diff instead of being mixed in with the
pre-existing divergence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 11:38:18 +09:00