CI / test (push) Canceled after 0s
fix các bug theo yêu cầu https://fptsoftware362-my.sharepoint.com/❌/g/personal/nampdt_fpt_com/IQAHBJ4A9xqDTLgvt2bhukJEAdRB5LRz2hbJpTivvIiBSYM?wdExp=TEAMS-TREATMENT&web=1&isSPOFile=1&ovuser=f01e930a-b52e-42b1-b70f-a8882b5d043b%2CAnhTNM1%40fpt.com&clickparams=eyJBcHBOYW1lIjoiVGVhbXMtRGVza3RvcCIsIkFwcFZlcnNpb24iOiI0OS8yNjA4MTMxOTMxNyIsIkhhc0ZlZGVyYXRlZFVzZXIiOmZhbHNlfQ%3D%3D --------- Co-authored-by: Duy Le Huu <duylh19@fpt.com> Reviewed-on: #10 Co-authored-by: Anh Tran Nguyen Minh <anhtnm1@fpt.com>
142 lines
7.8 KiB
Markdown
142 lines
7.8 KiB
Markdown
# Nguyên nhân gốc hay gặp của bug UI PySide6
|
|
|
|
Danh mục để **chẩn đoán**, không phải để đoán bừa. Mỗi mục: triệu chứng người dùng mô tả →
|
|
nguyên nhân → cách xác minh → hướng sửa.
|
|
|
|
---
|
|
|
|
## Nhóm A — Layout & kích thước
|
|
|
|
### P01. Widget bị bóp/giãn sai khi resize
|
|
**Triệu chứng:** "kéo cửa sổ to ra thì bảng bên phải nuốt hết chỗ", "panel trái biến mất".
|
|
**Nguyên nhân:** thiếu `stretch` factor, hoặc `QSizePolicy` sai (`Preferred` vs `Expanding`).
|
|
**Xác minh:** đọc `addWidget(w, stretch)` / `setStretchFactor` / `setSizePolicy` quanh chỗ dựng.
|
|
**Sửa:** đặt stretch tường minh trên `QSplitter`/`QBoxLayout`. Không sửa bằng `setFixedWidth`.
|
|
|
|
### P02. Chữ bị cắt / hiện `...` ở một số ngôn ngữ hoặc scale
|
|
**Triệu chứng:** "nút bị mất chữ", "tên project chỉ hiện một nửa".
|
|
**Nguyên nhân:** `setFixedWidth`/`setFixedSize` tính theo chuỗi tiếng Anh ở 100% scale.
|
|
**Xác minh:** `grep -n "setFixedWidth\|setFixedSize\|setMaximumWidth" <file>`; thử với `vi`/`ja`.
|
|
**Sửa:** dùng `minimumWidth` + `sizeHint`, hoặc `QFontMetrics.horizontalAdvance` cho chuỗi
|
|
dài nhất trong 3 ngôn ngữ. Xem `i18n_rules.md` §4.
|
|
|
|
### P03. Nội dung trong `QScrollArea` không cuộn được / bị nén
|
|
**Nguyên nhân:** quên `setWidgetResizable(True)`, hoặc đặt widget con vào scroll area
|
|
**sau** khi đã `setWidget`.
|
|
**Sửa:** `setWidgetResizable(True)` và dựng xong nội dung rồi mới `setWidget`.
|
|
|
|
### P04. Khoảng trắng thừa quanh panel
|
|
**Nguyên nhân:** `setContentsMargins`/`setSpacing` mặc định của layout lồng nhau cộng dồn.
|
|
**Xác minh:** đếm số layout lồng; repo dùng `setContentsMargins(10,10,10,10)` +
|
|
`setSpacing(10)` ở shell (`main_window.py:145`), layout con thường phải là `(0,0,0,0)`.
|
|
|
|
### P05. Bug chỉ xảy ra trên màn hình scale 125%/150%
|
|
**Triệu chứng:** "máy em bình thường, máy sếp bị lệch".
|
|
**Nguyên nhân:** hằng số pixel cứng, icon raster không có bản @2x, `QPixmap` không set
|
|
`devicePixelRatio`.
|
|
**Xác minh:** hỏi người dùng độ phân giải + mức scale Windows; test lại bằng biến môi trường
|
|
`QT_SCALE_FACTOR=1.5`.
|
|
**Sửa:** dùng đơn vị theo `QFontMetrics`, icon SVG hoặc `icon()` từ `ui/icons.py`.
|
|
|
|
---
|
|
|
|
## Nhóm B — Stylesheet & theme
|
|
|
|
### P06. `setStyleSheet` cục bộ đè mất style toàn app
|
|
**Triệu chứng:** "một chỗ nhìn khác hẳn phần còn lại", "combo box mất mũi tên".
|
|
**Nguyên nhân:** gọi `widget.setStyleSheet(...)` — QSS con **thay thế** chứ không merge với
|
|
QSS ứng dụng cho subcontrol đó. Riêng `::drop-down` bị style là Qt ngừng vẽ mũi tên mặc
|
|
định (xem `theme_tokens.md` §5).
|
|
**Sửa:** gỡ stylesheet cục bộ, gán `objectName`, style trong `theme/qss.py`.
|
|
|
|
### P07. Widget dựng lười không nhận theme / ngôn ngữ mới
|
|
**Triệu chứng:** "đổi sang giao diện sáng rồi mà màn Giám sát vẫn tối", "chỉ màn đó bị".
|
|
**Nguyên nhân:** Dashboard / Schedule / Monitoring chỉ được dựng ở lần mở đầu tiên
|
|
(`presentation/shell/page_registry.py::_ensure_page`). Chúng **bỏ lỡ** sự kiện đổi theme
|
|
hoặc đổi ngôn ngữ đã phát trước đó.
|
|
**Xác minh:** mở app → đổi theme → *rồi mới* bấm vào màn đó. Nếu lỗi tái hiện thì đúng P07.
|
|
**Sửa:** áp lại stylesheet/`tr()` trong `_ensure_page` sau khi dựng, hoặc để widget tự đăng ký
|
|
listener ngay trong `__init__`. Không sửa trong từng widget con.
|
|
|
|
### P08. Style không áp lại sau khi đổi property động
|
|
**Triệu chứng:** "nút vẫn xám sau khi đã chọn xong".
|
|
**Nguyên nhân:** QSS selector dạng `[state="active"]` chỉ được đánh giá lại khi ép polish.
|
|
**Sửa:** `w.style().unpolish(w); w.style().polish(w)` sau khi `setProperty`.
|
|
|
|
### P09. Bug chỉ có ở một theme
|
|
**Xác minh bắt buộc:** đối chiếu `docs/screens/<slug>-dark.png` và `<slug>-light.png`.
|
|
**Nguyên nhân thường gặp:** dùng `accent` ở chỗ cần `accent_solid`, hoặc token bề mặt sai bậc
|
|
(`surface` thay vì `surface_raised`).
|
|
|
|
---
|
|
|
|
## Nhóm C — Signal, slot, luồng
|
|
|
|
### P10. Bấm một lần chạy hai lần
|
|
**Triệu chứng:** "gửi 1 tin mà hiện 2", "tạo trùng task".
|
|
**Nguyên nhân:** `connect()` được gọi lại mỗi lần refresh/rebuild mà không `disconnect()`.
|
|
**Xác minh:** `grep -n "\.connect(" <file>` và tìm xem có nằm trong hàm được gọi nhiều lần không.
|
|
**Sửa:** connect một lần trong `__init__`, hoặc `Qt.UniqueConnection`.
|
|
|
|
### P11. UI đứng khi chạy tác vụ dài
|
|
**Triệu chứng:** "app treo khi bấm Phân tích", "vòng xoay không quay".
|
|
**Nguyên nhân:** gọi LLM / đọc file lớn / gọi MCP ngay trong GUI thread.
|
|
**Sửa:** đẩy xuống service của `application/` chạy async/worker; GUI chỉ nhận signal.
|
|
Đây cũng là vi phạm kiến trúc (`guardrail.md` G3), không chỉ là bug hiệu năng.
|
|
|
|
### P12. Widget biến mất không lý do
|
|
**Nguyên nhân:** không có parent, bị Python GC thu hồi; hoặc bị `deleteLater` sớm.
|
|
**Sửa:** truyền `parent` khi khởi tạo, hoặc giữ tham chiếu trên `self`.
|
|
|
|
### P13. Truy cập widget đã bị xoá → crash
|
|
**Triệu chứng:** "đóng dialog xong app tắt luôn".
|
|
**Nguyên nhân:** slot vẫn chạy sau khi C++ object đã destroy (`RuntimeError: Internal C++ object already deleted`).
|
|
**Sửa:** `disconnect` trong `closeEvent`, hoặc dùng `QPointer`/kiểm tra `shiboken6.isValid`.
|
|
|
|
### P14. Dữ liệu cũ hiện lại sau khi đã cập nhật
|
|
**Nguyên nhân:** view đọc từ cache/model không được `beginResetModel`/`endResetModel`,
|
|
hoặc widget được `hide()` chứ không rebuild.
|
|
|
|
---
|
|
|
|
## Nhóm D — Vẽ tay & hiệu năng
|
|
|
|
### P15. Nhấp nháy khi chuyển màn hoặc khi cuộn
|
|
**Nguyên nhân:** `repaint()` gọi tay trong vòng lặp, hoặc `paintEvent` đọc file/config.
|
|
**Sửa:** dùng `update()` (gộp lần vẽ), và đọc màu qua `current_palette()` — đã được cache
|
|
sẵn chính vì lý do này (`theme_tokens.md` §2).
|
|
|
|
### P16. Chart / canvas vẽ đè, để lại vệt
|
|
**Nguyên nhân:** không xoá nền trong `paintEvent`, hoặc `QPainter` không `end()`.
|
|
|
|
### P17. Icon mờ hoặc sai màu ở dark/light
|
|
**Nguyên nhân:** icon raster một màu cố định.
|
|
**Sửa:** lấy qua `ui/icons.py::icon`, không load PNG trực tiếp.
|
|
|
|
---
|
|
|
|
## Nhóm E — Vòng đời & dữ liệu
|
|
|
|
### P18. Trạng thái rỗng/đang tải/lỗi không có giao diện riêng
|
|
**Triệu chứng:** "màn hình trắng trơn, không biết đang chạy hay hỏng".
|
|
Đây là **bug UX**, không phải bug kỹ thuật → route sang `3_ux_flow_fixer.md`.
|
|
|
|
### P19. Người dùng mất dữ liệu khi đóng nhầm
|
|
**Triệu chứng:** "gõ instruction xong đóng tab, mất hết".
|
|
**Nguyên nhân:** không có dirty-state, không chặn `closeEvent`.
|
|
Đây là bug UX mức nghiêm trọng, ưu tiên cao hơn phần lớn bug hiển thị.
|
|
|
|
### P20. Dialog mở sau lưng cửa sổ chính / mở lệch màn hình
|
|
**Nguyên nhân:** dialog không truyền `parent`, hoặc set vị trí bằng toạ độ tuyệt đối.
|
|
**Sửa:** luôn truyền parent; căn giữa theo `parent.geometry()`, không theo `screen(0)`.
|
|
|
|
---
|
|
|
|
## Cách dùng danh mục này
|
|
|
|
1. Ánh xạ triệu chứng người dùng → 1-3 mục khả dĩ.
|
|
2. Với mỗi mục, chạy đúng bước **Xác minh** — đọc code hoặc tái hiện.
|
|
3. Loại trừ cho tới khi còn một nguyên nhân có `file:line` cụ thể.
|
|
4. Nếu không mục nào khớp: ghi giả thuyết mới vào `fix_plan.md`, và **bổ sung mục mới vào
|
|
file này** khi đã xác nhận. Danh mục phải lớn dần theo bug thật của sản phẩm.
|