Trước đây rất nhiều widget gọi setText(tr(...)) một lần lúc dựng, nên sau khi
đổi ngôn ngữ một nửa màn hình vẫn giữ tiếng cũ. Thêm họ bind_text/bind_tip/
bind_placeholder/bind_items/bind_dynamic trong i18n: khoá dịch được gắn thẳng
vào widget (giữ tham chiếu yếu) và tự áp lại mỗi lần set_language.
- co4e sidebar, node property panel, run control, routing toggle, nav rail,
top bar, filter scaffold, usage chart: chuyển sang binding hoặc tự đăng ký
on_language_changed thay vì trông chờ nơi nhúng.
- RoutingToggle/AutoRunToggle tự đăng ký retranslate và dùng
AdjustToContents để bản dịch dài không bị cắt.
- BusyOverlay mới: che cửa sổ trong lúc set_language chạy đồng bộ trên GUI
thread, tránh cảm giác treo app.
- co4e sidebar chỉ đọc thư viện skill một lần (_skill_prefix_lookup) thay vì
quét đĩa cho từng skill — trước đó mỗi lần reload tốn ~3.8s đứng GUI.
- Bổ sung bản dịch ja/vi còn để nguyên tiếng Anh; sửa chiều cao hàng
Settings ở nav rail.
- Thêm 4 bộ test UI cho các thay đổi trên.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DEFAULT_CONFIG ship agent_security.sandbox_pw = "" kể từ commit 3827552, và
cấu hình đưa tới dialog luôn được deep-merge với defaults đó. Nghĩa là trên
mọi bản cài không đặt COWORK_SANDBOX_PASSWORD, mật khẩu đã lưu là chuỗi rỗng
— và phép so `pw == self._sandbox_pw` nhận luôn ô nhập trống. Bấm Unlock với
ô trống là mở được toàn bộ nhóm Sandbox Security.
Commit 3827552 chỉ sửa config.py nên bỏ sót bản sao thứ hai của literal nằm
trong ui/settings_dialog.py, và chính nó tạo ra lỗ hổng rỗng này.
Sửa:
- gỡ literal credential khỏi mã nguồn (nó vốn là code chết: deep-merge làm
tham số mặc định của .get() không bao giờ chạy);
- chặn rỗng trước khi so, theo đúng mẫu mà MS365 unlock đã dùng;
- so sánh timing-safe trên BYTES, không trên str — secrets.compare_digest
ném TypeError với str ngoài ASCII, mà app mặc định tiếng Việt và phục vụ
khách Nhật nên mật khẩu có dấu là input bình thường;
- chưa đặt mật khẩu thì báo đúng trạng thái đó, không báo "sai mật khẩu" —
người dùng sẽ gõ lại mãi một thứ không tồn tại.
Root cause: ui/settings_dialog.py:104 (bản cũ)
Test: tests/ui/test_sandbox_unlock_security.py
LƯU Ý CHO REVIEWER: literal cũ vẫn nằm trong 6 commit của Git history. Gỡ ở
đây không gỡ khỏi lịch sử — cần xoay credential trên các máy còn giá trị đó
trong config.json. Không rewrite history (SECURITY.md).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>