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>
This commit is contained in:
Nam Pham Dinh Thanh
2026-08-18 10:48:11 +09:00
co-authored by Claude Opus 5
parent a358a20556
commit d060d5679a
18 changed files with 1193 additions and 836 deletions
+23 -9
View File
@@ -46,7 +46,12 @@ _PANEL_W, _PANEL_H = 340, 460 # expanded chat panel size
# assistant is teal, not the app accent, and the same in both themes —
# it is one recognisable object floating over every screen.
_TEAL_BG, _TEAL_LINE = "#E6F6F4", "#7FD0C4"
_TEAL_TEXT, _TEAL_SPARK = "#0F6E62", "#0F9B8A"
_TEAL_TEXT = "#0F6E62"
# The page's CSS says .spark{color:#0F9B8A}, but the glyph is the ✨ EMOJI and a
# colour emoji ignores CSS colour — so what the page actually renders is the
# gold star. Sampled from the page's own render of section 27 (1055 pixels of
# the star, averaged): #FDBE59.
_SPARK_GOLD = "#FDBE59"
# The three states the floating assistant cycles through.
_HIDDEN, _LAUNCHER_ST, _PANEL = "hidden", "launcher", "panel"
@@ -120,9 +125,12 @@ class HelpAgentWidget(QWidget):
self._worker: Optional[AgentWorker] = None
# Conversation history (excludes the system prompt, prepended per call).
# Seeded with the greeting so the panel always opens on a friendly hello.
self._history: List[Dict[str, str]] = [
{"role": "assistant", "content": self._greeting()}
]
# Kept by identity so retranslate() can rewrite it without having to
# guess which language the visible text is in — and without touching a
# real reply that happens to look like a greeting.
self._greet_msg: Dict[str, str] = {
"role": "assistant", "content": self._greeting()}
self._history: List[Dict[str, str]] = [self._greet_msg]
self.setAttribute(Qt.WA_StyledBackground, True)
self._pal = self._compute_palette()
self._build_edge_tab()
@@ -147,7 +155,7 @@ class HelpAgentWidget(QWidget):
self._apply_style()
muted = self._pal.text_muted
self.edge_tab.setIcon(icon("chevron-left", color=_TEAL_TEXT))
self.launcher.setIcon(icon("sparkle", size=_DOT_ICON, color=_TEAL_SPARK))
self.launcher.setIcon(icon("sparkle", size=_DOT_ICON, color=_SPARK_GOLD))
self.min_btn.setIcon(icon("minus", color=muted))
self._render()
@@ -164,8 +172,8 @@ class HelpAgentWidget(QWidget):
border-radius: {_DOT // 2}px; color: {_TEAL_TEXT}; font-weight: 700;
font-size: 12px; padding: 0; text-align: center; }}
#helpLauncher[pill="true"] {{ text-align: left; padding-left: 6px; }}
#helpLauncher:hover {{ background: #D5EFEA; border-color: {_TEAL_SPARK}; }}
#helpLauncher:focus {{ border: 1px solid {_TEAL_SPARK}; }}
#helpLauncher:hover {{ background: #D5EFEA; border-color: {_TEAL_LINE}; }}
#helpLauncher:focus {{ border: 1px solid {_TEAL_TEXT}; }}
#helpEdgeTab {{ background: {_TEAL_BG}; border: 1px solid {_TEAL_LINE};
border-right: none; border-top-left-radius: {r}px;
border-bottom-left-radius: {r}px; }}
@@ -213,7 +221,7 @@ class HelpAgentWidget(QWidget):
# is gone — hiding to the edge is now a line in the panel's ⋯ menu.
self.launcher = _HoverPill(self)
self.launcher.setObjectName("helpLauncher")
self.launcher.setIcon(icon("sparkle", size=_DOT_ICON, color=_TEAL_SPARK))
self.launcher.setIcon(icon("sparkle", size=_DOT_ICON, color=_SPARK_GOLD))
self.launcher.setCursor(Qt.PointingHandCursor)
self.launcher.setToolTip(tr("help_agent.open_tooltip"))
self.launcher.clicked.connect(self._expand)
@@ -233,7 +241,7 @@ class HelpAgentWidget(QWidget):
hb.setContentsMargins(12, 8, 8, 8)
self.title_icon = QLabel(header)
self.title_icon.setPixmap(
icon("sparkle", size=16, color=_TEAL_SPARK).pixmap(16, 16))
icon("sparkle", size=16, color=_SPARK_GOLD).pixmap(16, 16))
hb.addWidget(self.title_icon)
self.title = QLabel(tr("help_agent.title"), header)
self.title.setObjectName("helpTitle")
@@ -444,6 +452,12 @@ class HelpAgentWidget(QWidget):
self.send_btn.setEnabled(not busy)
def retranslate(self) -> None:
# The transcript is rendered HTML, so switching language left the
# greeting — and every "AI Assistant" speaker label — in the language
# the panel was built in.
if self._history and self._history[0] is self._greet_msg:
self._greet_msg["content"] = self._greeting()
self._render()
self.title.setText(tr("help_agent.title"))
self.input.setPlaceholderText(tr("help_agent.placeholder"))
self.launcher.setToolTip(tr("help_agent.open_tooltip"))