Files
cowork-local/agent/knowledge/theme_tokens.md
T
3c3ec748f9 docs(agent): bổ sung role fix-dispatcher và siết lại bộ tài liệu agent
- Thêm agent/roles/0_fix_dispatcher.md: phân tier/lane cho từng defect trước
  khi các agent khác chạy, kèm agent/commands/fix.md và hợp đồng đầu ra
  agent/output/dispatch_plan.md.
- Cập nhật system/guardrail, response_policy, security và các checklist
  ui/ux/pr_readiness cho khớp luồng mới.
- Mở rộng knowledge: i18n_rules, screen_map, theme_tokens,
  secrets_and_config; cập nhật workflow intake_to_fix và handoff_contract.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 01:35:47 +09:00

12 KiB
Raw Blame History

Theme & Design Tokens — Luật màu sắc của Cowork Local

Knowledge module dành cho các agent xử lý UI Visual / Theme / QSS của Cowork Local.

Nguồn chính

  • theme/__init__.py — docstring và API theme
  • theme/palettes.py — định nghĩa Palette/token
  • theme/qss.py — _TEMPLATE và stylesheet
  • theme/qss_controls.py — style cho các Qt controls

1. Luật quan trọng nhất

Ngoài thư mục theme/, không file nào được tự định nghĩa màu.

Luồng màu chuẩn của Cowork Local:

Palette
   ↓
token ngữ nghĩa
   ↓
_TEMPL​ATE
   ↓
stylesheet(theme)
   ↓
QApplication.setStyleSheet(...)

Nói đơn giản:

Widget không tự chọn màu. Theme quyết định màu.


2. Hai cách hợp lệ để widget có màu

Cách 1 — Style bằng QSS

Đây là cách mặc định.

Widget đặt objectName, sau đó style được định nghĩa trong:

theme/qss.py

Ví dụ:

widget.setObjectName("my_widget")

và style tương ứng nằm trong _TEMPLATE.


Cách 2 — Widget tự vẽ bằng QPainter

Dùng cho các thành phần như:

  • chart;
  • canvas;
  • syntax highlighter;
  • custom painting.

Code phải lấy màu từ:

current_palette()

Ví dụ:

palette = current_palette()

Sau đó dùng token từ palette.


3. Những cách KHÔNG được phép

Không được tự đặt màu trong UI code.

❌ Hardcode HEX

self.label.setStyleSheet("color: #dc2626;")

❌ Hardcode tên màu

pen.setColor(QColor("red"))

❌ Hardcode RGBA

self.card.setStyleSheet(
    "background: rgba(0,0,0,.1)"
)

Các trường hợp này phải bị reject khi review.

Rule ngắn gọn

Không có màu literal ngoài theme/

Không chỉ tránh #hex, mà cả:

  • tên màu;
  • RGB;
  • RGBA;
  • stylesheet cục bộ chứa màu.

4. API Theme cần nhớ

API Dùng để
theme.stylesheet(theme) Tạo QSS cho toàn app
theme.set_active_theme(theme) Ghi nhận theme hiện đang active
theme.current_theme() Lấy theme hiện tại: dark / light
theme.current_palette() Lấy Palette của theme hiện tại
theme.palette(theme) Lấy Palette của một theme cụ thể
theme.resolve_theme("system") Xác định dark/light theo OS
theme.role_colors(theme) Lấy màu theo role: user/assistant/tool/result/error

Khi đổi theme

Hai lệnh này phải đi cùng nhau:

theme.set_active_theme(theme)
app.setStyleSheet(theme.stylesheet(theme))

Không được chỉ gọi setStyleSheet() mà quên cập nhật active theme.


5. current_palette() dùng để làm gì?

Code vẽ bằng QPainter phải dùng:

current_palette()

Không được mỗi lần paintEvent() lại đọc:

config.json

Lý do:

paintEvent()
   ↓
repaint
   ↓
đọc config
   ↓
lặp lại rất nhiều lần

Điều này từng gây vấn đề hiệu năng thực tế.

Vì vậy:

current_palette() tồn tại để custom painting lấy màu nhanh từ theme hiện tại.


6. Palette và Design Token

Palette là:

@dataclass(frozen=True)

Token phải mang ý nghĩa, không phải tên màu.

❌ Không đặt token kiểu:

blue
grey2
dark_blue
light_grey

✅ Đặt theo vai trò:

accent
danger
text
text_muted
surface
surface_raised

Lợi ích:

Thêm theme mới = thêm một Palette, không phải viết lại stylesheet.


7. Các nhóm token chính

7.1. Surface — các mức bề mặt

Token Dùng cho
bg Nền chính của cửa sổ/canvas
surface Panel, card, group box
surface_raised Input, list, tree — nơi người dùng nhập/chọn
overlay Menu, tooltip, popup
sunken Log, code, terminal — vùng chủ yếu để đọc
hover Trạng thái hover
active Trạng thái đang active/pressed

Lưu ý

surface không có nghĩa là nav rail.

Nav rail có chủ đích riêng về độ sáng/tối.


7.2. Text

Các token chính:

text
text_muted
...

Dùng token theo vai trò thay vì tự chọn màu.


7.3. Accent

Có hai token:

accent
accent_solid

Hai token này khác nhau có chủ đích.

accent

Dùng cho accent thông thường, ví dụ:

  • trạng thái;
  • thành phần UI;
  • điểm nhấn.

accent_solid

Dùng khi accent trở thành nền đặc và bên trên có chữ.

Lý do:

Một màu accent có thể đủ sáng để đọc khi dùng như chữ trên nền tối, nhưng lại quá sáng khi dùng làm nền cho chữ trắng.

Vì vậy:

Chữ trên nền accent đặc
        ↓
accent_solid

Không tự lấy accent chỉ vì nó có vẻ "cùng màu".


7.4. State

Ví dụ:

danger
...

Các state token cũng phải mang ý nghĩa, không đặt theo tên màu.


7.5. Conversation roles

Có các token:

role_user
role_assistant
role_tool
role_result
role_error

Dùng để phân biệt các role trong giao diện hội thoại.


7.6. Code / Syntax

Ví dụ:

code_string
...

Dùng cho syntax highlighting.


8. Các nguyên tắc thiết kế — đừng nhầm thành bug

Một số đặc điểm nhìn "khác mắt" nhưng có chủ đích.

Không được tự ý sửa chỉ vì người dùng nói "trông hơi tối" hoặc "không giống app hiện đại".


8.1. Không gradient, không glow

Thiết kế lấy cảm hứng từ:

VS Code Dark Modern
VS Code Light Modern

Phong cách chính:

  • surface phẳng;
  • góc gần vuông;
  • không gradient;
  • không glow;
  • một accent chính;
  • accent dành cho thứ người dùng tương tác.

8.2. Độ sâu đến từ surface và border

Không tạo chiều sâu bằng cách:

đổi màu quá mạnh

Thay vào đó dùng:

surface hierarchy
+
border mảnh

9. Nav rail tối hơn là thiết kế có chủ đích

Silhouette của Cowork Local lấy theo VS Code:

NAV RAIL
    ↓
tối hơn
    ↓
CONTENT AREA

Không phải:

nav rail sáng hơn content

Vì vậy nếu user báo:

"Menu bên trái tối quá."

thì chưa được kết luận ngay là visual bug.

Đây có thể là design intent.

Xem thêm:

examples/bad_fix.md

để tránh sửa nhầm.


10. Contrast — WCAG AA

Body text và chữ trên button nền đặc phải đạt:

Contrast ratio ≥ 4.5:1

Đây là yêu cầu tối thiểu.

Khi thay token/màu:

Dark theme
+
Light theme
+
text/background

đều phải được kiểm tra.


Không khôi phục màu VS Code cũ nếu màu đó không đạt AA

Một số màu gốc của VS Code không đạt yêu cầu AA.

Các giá trị đã được Cowork Local điều chỉnh vừa đủ, ví dụ:

Trường hợp Contrast cũ
Dark line 3.59:1
Chữ mờ trên sidebar sáng 4.28:1
Xanh lá sáng 4.33:1
Hổ phách sáng 3.12:1

Các chỗ này có comment ghi lại giá trị gốc.

Rule

Không đưa chúng trở lại giá trị VS Code ban đầu.

Mục tiêu của Cowork Local là:

VS Code silhouette
+
WCAG AA

không phải copy nguyên xi mọi giá trị màu của VS Code.


11. ⚠️ Combo Box và _chevron_asset

Một lỗi dễ gặp:

Combo box mất mũi tên.

Nguyên nhân liên quan đến cách Qt xử lý QSS.


11.1. image: trong QSS không nhận QPixmap

QSS:

image:

chỉ nhận đường dẫn tới:

  • file;
  • resource.

Không nhận trực tiếp:

QPixmap

11.2. Style ::drop-down sẽ làm Qt ngừng vẽ arrow mặc định

Khi style các selector như:

::drop-down
::up-button
::down-button

Qt có thể ngừng vẽ mũi tên mặc định.


11.3. Cowork Local dùng _chevron_asset

Trong:

theme/palettes.py

_chevron_asset:

  1. render chevron thành PNG;
  2. lưu vào thư mục tạm;
  3. cache theo:
(direction, color)

Khi debug combo box

Nếu thấy:

Combo box mất mũi tên.

Hãy kiểm tra trước:

stylesheet cục bộ
        ↓
::drop-down

Đây thường là nguyên nhân.

Cache nằm tại:

%TEMP%/cowork_local_theme/chevron_*.png

Nếu đang test màu mới, có thể xóa cache để buộc render lại.


12. Checklist sửa bug màu sắc/theme

Trước khi hoàn thành visual fix, kiểm tra:

Theme coverage

  • Bug đã được kiểm tra trên Dark chưa?

  • Bug đã được kiểm tra trên Light chưa?

  • Có thể dùng screenshot:

    • docs/screens/*-dark.png
    • docs/screens/*-light.png

Token

  • Patch dùng semantic token thay vì hex literal?
  • Không có setStyleSheet() cục bộ để thay màu?
  • Không có QColor("red"), QColor("blue"), v.v.?
  • Nếu thêm token mới, đã thêm cho cả DARK và LIGHT?
  • Token mới có tên theo ý nghĩa, không theo màu?

Accent

  • Chữ trên nền accent đặc đã dùng accent_solid?
  • Không dùng accent chỉ vì hai token có vẻ giống nhau?

Accessibility

  • Contrast đạt ≥ 4.5:1?
  • Đã kiểm tra cả text và button có nền đặc?

Theme lifecycle

  • Widget tạo sau khi đổi theme có nhận đúng stylesheet?
  • Đã kiểm tra vấn đề lazy screen theo qt_pitfalls.md P07?

Design intent

  • Không vô tình thêm gradient?
  • Không thêm glow?
  • Không làm nav rail sáng hơn content?
  • Không khôi phục các màu VS Code cũ đã bị loại vì không đạt WCAG AA?

13. Quy tắc review nhanh

Khi gặp một defect liên quan màu sắc, đi theo thứ tự:

1. Xác định widget
       ↓
2. Kiểm tra objectName
       ↓
3. Tìm rule trong theme/qss.py
       ↓
4. Kiểm tra token trong palettes.py
       ↓
5. Kiểm tra DARK + LIGHT
       ↓
6. Kiểm tra contrast
       ↓
7. Kiểm tra local setStyleSheet()
       ↓
8. Kiểm tra lazy theme lifecycle (P07)
       ↓
9. Xác định đây là bug thật hay design intent
       ↓
10. Chỉ sau đó mới tạo fix_plan

Nguyên tắc cuối

UI code
  ↓
không tự chọn màu
  ↓
semantic token
  ↓
Palette
  ↓
_TEMPL​ATE / current_palette()
  ↓
theme

Nếu một màu mới cần xuất hiện, trước tiên hỏi:

"Màu này đang đại diện cho vai trò gì?"

Sau đó tạo hoặc dùng semantic token phù hợp.

Không hỏi:

"Mình muốn màu xanh nào?"

Vì trong Cowork Local, ý nghĩa của màu quan trọng hơn bản thân màu.