Người dùng phản ánh dán nội dung vào chat cho câu trả lời tốt hơn đính kèm cùng
nội dung đó. Nội dung KHÔNG bị cắt (giới hạn 2 triệu ký tự) — nguyên nhân là
loãng và trùng: _augment luôn quét thêm [Workspace files] và [Project files],
và tệp đính kèm nếu nằm trong thư mục workspace sẽ đi vào prompt HAI lần. Với
tài liệu dài, bản thứ hai vừa nhân đôi ngữ cảnh vừa khiến model không biết bản
nào là bản được hỏi.
Lọc trùng theo đường dẫn đã giải quyết, và đổi nhãn để nói rõ tệp đính kèm là
CHỦ THỂ CHÍNH còn tệp thư mục chỉ là ngữ cảnh phụ. Dòng cảnh báo "N tệp không
nạp được" đếm TRƯỚC khi lọc trùng, nếu không nó báo sai.
Cùng lượt, hai chỗ bỏ thông tin không cần thiết:
- gỡ dòng "provider · model" ngay sau chữ "Cowork" — nó lặp lại thứ bộ chọn
provider ở thanh trên đang hiển thị, mà chiếm chỗ đắt nhất trên thanh công cụ;
- không báo "đang dùng <provider>" ở thanh trạng thái khi đổi provider — bộ
chọn nằm ngay trên màn hình và đã hiện thứ người dùng vừa tự chọn.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Prompt cũ chỉ liệt kê TÊN các màn hình, nên model không có cách nào biết trên
mỗi màn có gì và nó lấp khoảng trống bằng thứ nghe hợp lý: người dùng thật đã
được hướng dẫn vào "Dashboard → Add Project" và "Settings → Project Settings →
New Project". Không một thứ nào trong đó tồn tại. Người dùng đi tìm rồi mới
phát hiện ra — câu trả lời trôi chảy mà sai còn tệ hơn câu "tôi không biết".
Ba thứ ghép thêm vào prompt:
- docs/help/app_guide.md — sổ tay viết tay, bám mã nguồn thật, có test chốt
rằng nó nhắc đủ mọi màn trong docs/screens/manifest.json;
- luật chống bịa, kèm ví dụ few-shot nêu đúng câu trả lời sai đã xảy ra cạnh
câu đúng, và một ví dụ dạy nó NÓI KHÔNG BIẾT;
- ngữ cảnh sống: màn hình đang mở và nhãn các nút/tab ĐANG hiện.
Ngữ cảnh sống đọc từ cây widget thật, KHÔNG từ docs/screens/controls.json: file
đó trích tự động nhưng đã cũ — 5/41 file trong đó không còn tồn tại và nó không
có file nào trong presentation/ (chưa sinh lại sau refactor R08). Nạp nó vào
prompt là dạy trợ lý về nút của những file đã bị xoá.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cùng lớp lỗi với tab Thư mục: _bind_project gọi _structure.set_project(pid) vô
điều kiện, và chuỗi _goto -> refresh -> _load_current -> _bind_project chạy lại
mỗi lần vào lại màn Workspace, nên bộ chọn project của chính màn GraphRAG bị
kéo về giá trị của Workspace ở mỗi cú chuyển tab. set_workspace_project() bỏ
qua khi project không đổi (dùng None làm giá trị khởi tạo, không phải "", để
lần gọi đầu không bị bỏ qua khi chưa chọn project nào).
Chọn project xong phải tự quét: _on_project_changed cố ý hoãn lượt quét sang
"lần ghé tiếp", nhưng combo nằm ngay trên màn đó nên người dùng đang đứng ở đấy
và không có lần ghé tiếp nào — họ phải tự bấm Scan. Giờ quét ngay khi màn đang
mở; hoãn vẫn giữ cho trường hợp đổi project từ màn Workspace khi GraphRAG ẩn.
Panel "đang tải": prewarm() chỉ chạy 3 giây sau khi cửa sổ hiện, nên bấm
GraphRAG trong 3 giây đầu gặp _ensure_web() dựng QWebEngineView ĐỒNG BỘ trên
GUI thread — đóng băng 1-2 giây mà không có gì báo. Vì GUI thread bị chặn, panel
phải repaint() ngay chứ không update(): update() chỉ xếp hàng một lượt vẽ cho
vòng lặp sự kiện, mà vòng lặp đó sắp bị chặn, nên panel sẽ chỉ hiện ra sau khi
hết đóng băng — đúng lúc không còn cần tới nó nữa.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Hai lỗi riêng biệt cùng gây ra "chọn folder khác, sang tab khác rồi quay lại
thì về folder cũ":
1. WorkspaceFileTree.root_changed KHÔNG có ai lắng nghe trong toàn bộ repo.
Người dùng chọn thư mục trong cây thì chỉ cái cây đổi gốc; _root, khung xem
và terminal ở lại thư mục cũ — ba widget con lệch nhau ngay từ lúc bấm chọn.
2. _load_current gọi set_root(project.workspace_dir()) vô điều kiện, và _goto
gọi workspace.refresh() mỗi lần vào lại màn Workspace, nên mỗi cú chuyển tab
kéo thư mục về workspace của project.
set_project_root() bỏ qua khi project KHÔNG đổi. Đổi sang project khác thì vẫn
re-root — thư mục của màn này thuộc về project; chỉ lần refresh trong CÙNG một
project là không được đụng.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Lịch sử hội thoại KHÔNG nằm chung một chỗ: _bind_project đặt
config._project_history_dir thành <workspace của project>/.cowork_history mỗi
lần chọn project khác — cố ý, để chia sẻ thư mục project là chia sẻ cả lịch sử.
Nhưng hai chỗ đọc lại chỉ đọc MỘT thư mục, gây hai triệu chứng cùng gốc:
- ui/sidebar.py đọc history_dir() (thư mục của project ĐANG mở), nên khung
"Tất cả project…" dựng đủ tiêu đề nhóm mà mọi nhóm trừ một đều rỗng;
- _project_counts gọi list_conversations() KHÔNG tham số, tức đọc HISTORY_DIR
toàn cục nơi không có hội thoại nào của project, nên mọi dòng project đều
đếm "0 đoạn chat".
Thêm history_dirs() + list_conversations_by_project(), giữ đúng thứ tự cũ (ghim
trước, mới nhất trước) và chống trùng. Thư mục là chủ sở hữu có thẩm quyền: hội
thoại nằm trong workspace của project nào thì thuộc project đó, kể cả khi trường
project_id trong file đã cũ vì project bị đổi thư mục.
HISTORY_SUBDIR + project_history_dir() gom đường dẫn về một định nghĩa duy nhất
— chuỗi ".cowork_history" từng nằm rải trong ui/workspace_tab.py.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Landing được chốt SAU _restore_sessions(): hội thoại lần trước vẫn được nạp
lại (phục hồi sau khi thoát đột ngột là mục đích của nó), nhưng khung nhìn đầu
tiên là danh sách chứ không phải cuộc trò chuyện vừa đóng.
Phải đi qua _goto, không gọi thẳng show_history_pane(): _goto là chỗ duy nhất
dời vệt sáng trên thanh menu theo nội dung. Gọi tắt thì nội dung sang Cowork
còn vệt sáng ở lại Project — và vì QTreeWidget không phát currentItemChanged
khi bấm lại đúng dòng đang chọn, bấm "Project" sẽ KHÔNG có tác dụng gì cho tới
khi người dùng bấm sang mục khác rồi bấm về. Gom hai đường vào (link "Tất cả
project…" và màn mặc định) về một hàm để chúng không lệch nhau nữa.
Chưa chọn project thì cổng project đang giữ sub-tab Cowork đóng; ép mở bằng cửa
sau sẽ để nội dung ở Cowork trong khi cả cổng lẫn vệt sáng đều nói là Project.
Màn đúng cho người chưa có project chính là màn quản lý project.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bốn thay đổi trên cùng một khối, gom vào presentation/workspace/project_editing.py:
- Project đã có mở ra ở dạng CHỈ ĐỌC; phải bấm "Sửa project" (nút vàng) mới gõ
được, và nút Lưu chuyển xanh lá. Trước đây form luôn mở nên chỉ cần lỡ tay là
sửa mất nội dung của một project đang dùng mà không có gì cảnh báo.
- Chặn TRÙNG TÊN khi tạo và khi lưu (không phân biệt hoa thường, bỏ khoảng
trắng hai đầu). Danh sách bên trái chỉ hiện tên, nên trùng tên là không phân
biệt được nữa. Chặn TRƯỚC khi ghi đĩa.
- Chuột phải một project: Mở / Sửa / Xoá. Trước đây bấm phải không làm gì cả.
- workspace.counts được format một lần lúc dựng dòng nên đổi ngôn ngữ xong nó
vẫn nằm ở ngôn ngữ cũ, kể cả khi chọn tiếng Anh. ProjectRow giờ giữ SỐ và tự
format lại — KHÔNG gọi refresh(), vì refresh() kết thúc bằng _load_current()
và sẽ xoá mất nội dung người dùng đang gõ dở.
Ba nút về chung một hàng với "Project mới", và cùng theo một luật hiện/ẩn: chỉ
hiện trên sub-tab Project và khi có project đang mở. Hàng tiêu đề vắt ngang cả
màn Workspace nên thứ gì đặt lên đó cũng lọt sang Cowork/Co4E/Thư mục/GraphRAG
nếu không tự ẩn.
Màu vàng/xanh lá đi qua token warning/success. Hai nút dùng nền *_soft với chữ
và viền là token, KHÔNG phải nền đặc: hai token đó được chỉnh làm màu CHỮ (cùng
lý do accent và accent_solid là hai token riêng) — chữ trắng trên warning dark
#CCA700 chỉ đạt ~2.3:1, trượt AA.
ui/workspace_tab.py đang ở đúng trần bánh cóc nên phần mã mới bắt buộc ra module
riêng; chuyển _ProjectRow và CRUD sang đó làm file co từ 566 xuống 523 dòng mã,
và bánh cóc được siết theo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Kéo thanh chia sang trái hết cỡ thì thanh menu biến mất hẳn, và khi đã mất
thì không còn gì đủ rộng để nhận ra, nói gì tới bắt lại mà kéo ra.
_nav_wrap.setMinimumWidth(132) chỉ chi phối việc BỐ TRÍ, không chi phối thao
tác kéo: QSplitter mặc định cho phép người dùng kéo một ngăn vượt qua chính
minimum của nó rồi đóng sập về 0. Tắt bằng setChildrenCollapsible(False).
Thu gọn vẫn là việc của nút MENU, và nó dừng ở 54px chứ không về 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dòng "Made by QuanDH14" là widget thường trực ở góc dưới phải: chiếm một góc
màn hình trên MỌI màn, suốt cả phiên, cho một thông tin chỉ cần đọc một lần.
Chuyển vào Cài đặt ▸ Giới thiệu — vẫn tra được, không còn đứng thường trực.
Mục Giới thiệu đứng CUỐI danh sách: nó không có thiết lập nào để đổi, nên đặt
trước các mục thao tác được sẽ đẩy chúng xuống mà không được gì.
Đặc tả SettingsDialog đổi từ 5 lên 6 mục — thay đổi CÓ CHỦ Ý, không phải tách nhầm.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
run.bat chỉ hỏi junction "có tồn tại không", không hỏi nó trỏ vào đâu. Một
junction còn lại từ checkout KHÁC vẫn được dùng lại im lặng: run.bat nằm
trong thư mục này nhưng ứng dụng chạy từ thư mục kia. Triệu chứng là "sửa
code xong chạy vẫn y nguyên", và không có gì báo lỗi.
- junction được tạo lại mỗi lần chạy, không chỉ khi thiếu;
- mỗi thư mục mã nguồn có junction RIÊNG (khoá SHA1 từ đường dẫn tuyệt đối).
Bản trước dùng chung một đường dẫn cho cả máy, nên hai checkout tranh nhau:
cái chạy sau trỏ junction về mình, và tiến trình con của cái chạy trước
(máy chủ MCP MS365, sinh ra sau khi app đã mở) import mã nguồn của cái kia;
- install.bat dọn junction dùng chung của bản cũ — để lại là một cái bẫy;
- venv cũ phải CHẠY ĐƯỢC, không chỉ tồn tại file python.exe: venv dựng bằng
bản Python đã bị nâng cấp hoặc xoá vẫn còn nguyên file đó;
- smoke test kiểm DANH TÍNH, không chỉ kiểm import được — có một "cowork_local"
khác chen trên sys.path thì lệnh import vẫn chạy tốt và bước kiểm vẫn xanh
trong khi ứng dụng đọc mã nguồn khác;
- run.bat chốt cowork_local/__main__.py thật sự nhìn thấy được trước khi khởi
động, thay cho lỗi Python khó hiểu "'cowork_local' is a package and cannot
be directly executed";
- thông báo lỗi rõ cho ổ mạng và ổ không phải NTFS — junction không trỏ sang
được, ca hay gặp nhất khi mang sang máy khác.
Đã kiểm bằng cách chạy thật cả hai script.
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>
Bộ 7 role chuyên biệt (triage → specialist → implementer → reviewer) cùng
lớp dùng chung: guardrail, tri thức về repo, checklist, và contract đầu ra.
Vì sao có: bug UI/UX được báo bằng lời kể triệu chứng, và người sửa hay bỏ
qua ba thứ mà repo này rất dễ vi phạm — luật "không file nào ngoài theme/
được đặt tên một màu", trần LOC theo bánh cóc, và việc ui/ với presentation/
cùng tồn tại nên sửa nhầm file là "đã fix mà vẫn thấy lỗi".
knowledge/qt_pitfalls.md chép lại 20 nguyên nhân gốc hay gặp của bug PySide6;
examples/bad_fix.md có hai ca CÓ THẬT, gồm ca chính bản vá trong nhánh này
từng mắc (compare_digest trên str ngoài ASCII) và lọt qua vòng review đầu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copy of sections VI-IX from Feature_Architecture_Proposal.md
(roadmap, team assignment/KPI, anti-patterns, function migration map).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>