feat(workspace): mỗi thư mục làm việc chỉ thuộc về một project
CI / test (pull_request) Canceled after 0s

Thư mục làm việc vừa là sandbox (agent chỉ đọc/ghi bên trong nó) vừa là kho
kiến thức chung của project (file ở gốc được mọi đoạn chat tự đọc). Hai project
trỏ vào cùng một thư mục là đọc lẫn dữ liệu của nhau và ghi đè lên nhau — đúng
điều docstring đầu core/projects.py nói sandbox sinh ra để ngăn, nhưng không có
chỗ nào thực thi: cả ba nơi đặt thư mục đều gán thẳng output_dir rồi lưu.

Thêm core.projects.folder_conflict: so theo workspace_dir() chứ không theo
output_dir (project chưa đặt thư mục riêng vẫn đang chiếm thư mục quản lý sẵn),
chuẩn hoá đường dẫn bằng expanduser + abspath + normcase, và coi thư mục lồng
nhau là trùng — đứng ở thư mục cha vẫn với tới được file của project con.

Chặn tại cả ba nơi ghi output_dir: nút Đổi ở màn Project, thư mục mirror cloud,
và nút chọn thư mục trong tab Cowork. Chặn lúc CHỌN chứ không lúc Lưu, vì nút
Lưu chỉ ghi tên/mô tả/chỉ dẫn — chặn ở đó sẽ khoá luôn việc đổi tên một project
lỡ đang trùng thư mục.

Dữ liệu đã trùng sẵn không bị tự sửa: có nhãn cảnh báo ngay dưới ô Thư mục làm
việc, nói rõ trùng với project nào, để người dùng tự quyết.

Luật ở module riêng (presentation/workspace/project_folder_rules.py) vì nhét
vào project_editing.py sẽ đẩy file đó lên 435 dòng, vượt trần 400 của
scripts/check_loc.py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-17 15:36:16 +09:00
co-authored by Claude Opus 5
parent bbdf146d2c
commit b7a41b3658
7 changed files with 408 additions and 1 deletions
+11
View File
@@ -57,6 +57,17 @@ STRINGS: Dict[str, Dict[str, str]] = {
"en": "Another project is already called \"{name}\". Project names must be unique — the list shows nothing but the name, so two of them cannot be told apart.",
"ja": "「{name}」という名前のプロジェクトが既にあります。一覧には名前しか出ないため、同じ名前が二つあると区別できません。",
"vi": "Đã có project khác tên \"{name}\". Tên project phải khác nhau — danh sách chỉ hiện tên, trùng tên là không phân biệt được."},
"workspace.folder_taken_title": {
"en": "Folder already used", "ja": "フォルダーが重複しています",
"vi": "Thư mục đã được dùng"},
"workspace.folder_taken_body": {
"en": "Project \"{name}\" already works in {folder}. One folder belongs to one project only — the folder is that project's sandbox and shared knowledge, so sharing it lets two projects read and overwrite each other's files. Pick another folder.",
"ja": "プロジェクト「{name}」が既に {folder} を使用しています。フォルダーは 1 つのプロジェクト専用です — フォルダーはそのプロジェクトのサンドボックス兼共有ナレッジなので、共有すると互いのファイルを読み書きしてしまいます。別のフォルダーを選んでください。",
"vi": "Project \"{name}\" đang làm việc trong {folder}. Mỗi thư mục chỉ thuộc về một project — thư mục vừa là sandbox vừa là kho kiến thức chung của project đó, dùng chung là hai project đọc và ghi đè file của nhau. Hãy chọn thư mục khác."},
"workspace.folder_shared_warning": {
"en": "⚠ This folder is also used by project \"{name}\". One folder belongs to one project only — pick another folder for one of them.",
"ja": "⚠ このフォルダーはプロジェクト「{name}」でも使われています。フォルダーは 1 つのプロジェクト専用です — どちらかに別のフォルダーを指定してください。",
"vi": "⚠ Thư mục này đang được project \"{name}\" dùng chung. Mỗi thư mục chỉ thuộc về một project — hãy đổi thư mục cho một trong hai."},
"workspace.instructions_placeholder": {
"en": "e.g. \"All answers in Vietnamese. We are building the X reporting tool; always follow the naming rules …\"",
"ja": "例:「回答はすべて日本語で。X レポートツールを開発中。命名規則に従うこと …」",