Skip to content

feat: support todo workspace - #1989

Draft
qdaxb wants to merge 1 commit into
mainfrom
todo_workspace
Draft

feat: support todo workspace#1989
qdaxb wants to merge 1 commit into
mainfrom
todo_workspace

Conversation

@qdaxb

@qdaxb qdaxb commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added a unified TODO workflow with Inbox, Ready, In Progress, Review, and Completed stages.
    • Added quick item creation, drag-and-drop movement, subtasks, blockers, next actions, assignments, and “My Work” views.
    • Added configurable project workflows, reusable templates, and completion confirmation.
    • Added shared workspaces for browsing, uploading, renaming, deleting, and revealing files.
    • Added collaboration mode support when running TODO items.
  • Bug Fixes

    • Corrected the build script help text.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a unified TODO work-item and workflow model with local and Tauri-backed persistence, adds a Rust-based todo workspace filesystem store with security guards, reworks the TODO UI (board, detail panel, my-work, workflow dialog), and wires a collaborationMode option through project runtime task creation. A trailing typo was added to build_image.sh help text.

Changes

Todo Workflow and Workspace

Layer / File(s) Summary
Todo data model and persistence
wework/src/features/todo/todoModel.ts, wework/src/features/todo/todoModel.test.ts
Defines LocalWorkItem, workflow types, default workflow/templates, legacy draft migration, local persistence, and normalization; adds unit tests for migration and workflow config storage.
Tauri todo store and workspace filesystem
wework/src-tauri/src/lib.rs, wework/src-tauri/src/todo_store.rs, wework/src/features/todo/todoModel.ts
Adds Rust Tauri commands for JSON store load/save, workspace directory creation, protected file write/rename/delete, symlink-safe path validation, recursive listing, unit tests, and matching frontend wrapper functions.
Unified work-item orchestration and runtime execution
wework/src/features/todo/TodoWorkspace.tsx, wework/src/features/workbench/useWorkbenchRuntimeMessaging.ts, wework/src/features/workbench/workbenchContextTypes.ts, wework/src/components/layout/DesktopWorkbenchLayout.tsx
Replaces draft-based composition with hydrated local work items, adds quick-create/workflow child execution and dependency checks, and threads a new collaborationMode option through createProjectRuntimeTask/sendPreparedRuntimeMessage.
Workflow, board, detail, and personal-work UI
wework/src/features/todo/TodoWorkflowDialog.tsx, TodoWorkItems.tsx, TodoDetailPanel.tsx, TodoMyWork.tsx, TodoNavigation.tsx, wework/src/i18n/locales/*/common.json
Adds a workflow configuration dialog, inbox state and quick-create/drag-drop board flows, workflow/workspace editing in the detail panel, a “my work” grouped view, a workflow-settings nav entry, and related localization strings.
End-to-end workflow validation
wework/src/features/todo/TodoWorkspace.test.tsx
Reworks tests to use inline quick-create flows and adds coverage for workflow configuration, application, dependency editing, and completion confirmation.

Estimated code review effort: 4 (Complex) | ~75 minutes

Build Help-Text Correction

Layer / File(s) Summary
Build script help output
build_image.sh
The help banner echo line gains an extraneous trailing token.

Sequence Diagram(s)

sequenceDiagram
  participant TodoWorkspace
  participant todoModel
  participant localStorage
  participant TauriTodoStore
  TodoWorkspace->>todoModel: hydrateLocalWorkItems(userId)
  todoModel->>TauriTodoStore: load_todo_store(scope)
  TauriTodoStore-->>todoModel: serialized work items
  todoModel->>localStorage: cache normalized work items
  TodoWorkspace->>todoModel: saveLocalWorkItems(userId, items)
  todoModel->>localStorage: persist work items
  todoModel->>TauriTodoStore: save_todo_store(scope, contents)
Loading
sequenceDiagram
  participant TodoWorkspace
  participant TodoDetailPanel
  participant createProjectRuntimeTask
  participant sendPreparedRuntimeMessage
  TodoWorkspace->>TodoDetailPanel: run local work item
  TodoDetailPanel->>TodoWorkspace: onRunTodo(request)
  TodoWorkspace->>createProjectRuntimeTask: create task with collaborationMode
  createProjectRuntimeTask->>sendPreparedRuntimeMessage: forward collaborationMode
  sendPreparedRuntimeMessage-->>TodoWorkspace: runtime task creation result
  TodoWorkspace->>TodoDetailPanel: update state and events
Loading

Possibly related PRs

  • wecode-ai/Wegent#1799: Both modify useWorkbenchRuntimeMessaging.ts's runtime task request wiring, extending the request payload with additional fields.
  • wecode-ai/Wegent#1983: Both modify TODO workspace UI/runtime wiring in DesktopWorkbenchLayout.tsx and createProjectRuntimeTask handling.

Suggested reviewers: micro66

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding support for a todo workspace feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch todo_workspace

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
wework/src/features/todo/TodoWorkItems.tsx (1)

86-103: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle global creation independently of the visible board columns.

createRequest is ignored in list layout, and the board can omit inbox when filters or showEmptyGroups hide it. In either case, the header/sidebar Create action sets a request but no input opens. Forward the request to TodoList and ensure the requested board state is rendered.

Also applies to: 399-411

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkItems.tsx` around lines 86 - 103, The global
create request is only handled by the board when its target column is visible.
Forward createRequest to TodoList, and update TodoBoard’s
column-selection/rendering logic to ensure the requested state column is
included even when filters or showEmptyGroups would otherwise hide it, so
header/sidebar Create opens the appropriate input in both layouts.
🧹 Nitpick comments (1)
wework/src/features/todo/TodoWorkspace.tsx (1)

234-242: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Split this orchestration component before it grows further.

TodoWorkspace now runs from Line 234 through Line 1027 and owns persistence, workflow generation, execution, filtering, and the full view. Extract focused hooks/components for work-item storage, workflow actions, and header/content rendering.

As per coding guidelines, keep functions focused and split source files over 1000 lines.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkspace.tsx` around lines 234 - 242, The
TodoWorkspace component spans over 1000 lines and combines persistence logic,
workflow generation, execution, filtering, and view rendering. Extract custom
hooks to isolate work-item storage state and persistence, workflow action
handlers (generate, execute, filter), and separate the header and content
rendering into focused sub-components. Pass these extracted concerns to
TodoWorkspace as props or through composition to keep the main component under
the 1000-line guideline while maintaining focused responsibilities for each
extracted piece.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build_image.sh`:
- Line 18: Remove the unintended “aass” suffix from the echo command in
build_image.sh so the output header is exactly “Build docker images for Wegent
components”.

In `@wework/src-tauri/src/todo_store.rs`:
- Around line 35-48: Normalize the path in safe_relative_path before
protected-entry checks so equivalent forms such as context/., work/., and
./context resolve to their canonical relative paths. Update
is_protected_workspace_path and the rename/delete flows to use this normalized
value, preserving rejection of parent, root, and prefix components. Add
regression coverage for all three bypass forms.
- Around line 83-98: Serialize TODO persistence writes per scope: update
saveLocalWorkItems in wework/src/features/todo/todoModel.ts:221-228 to await or
queue save_todo_store calls so rapid edits commit in order and only the latest
snapshot reaches disk; update save_todo_store in
wework/src-tauri/src/todo_store.rs:83-98 to prevent overlapping saves for the
same scope from sharing and stomping the temporary file, using per-scope
serialization while preserving atomic rename behavior.

In `@wework/src/features/todo/TodoDetailPanel.tsx`:
- Around line 214-218: Update the workspace open/reveal handlers, including
revealWorkspace and the corresponding flow around lines 632-641, to catch
rejected openLocalFile and revealLocalFile promises and assign the caught error
to workspaceError. Preserve the existing path and workspace-item checks while
ensuring both failure paths provide user feedback through that error state.
- Around line 427-444: The TodoDetailPanel controls use undersized desktop
dimensions. Update the standard inputs around the blocker and next-action
fields, plus the referenced control ranges, from h-7 to h-8; update icon-only
controls from h-6 w-6 to h-8 w-8 while preserving their existing behavior and
styling.
- Around line 421-745: The TodoDetailPanel component has grown too large and
contains several unrelated UI sections. Extract the workflow section around
item.kind === 'draft' into a focused component, the shared workspace section
around workspaceEntries into another, and the Properties section into a third
component, passing only the required item data, callbacks, state, and
translation helpers; update TodoDetailPanel to render these components while
preserving current behavior.
- Around line 160-165: Update the workspace-loading useEffect around
listTodoWorkspace so previous requests cannot overwrite results for a newer item
or update. Clear workspace entries when item.workspaceItemId changes, and cancel
or sequence each request so only the latest request may call
setWorkspaceEntries; preserve the empty-list fallback for the active request’s
failure.
- Around line 427-442: Update the TodoDetailPanel inputs identified by
todo-detail-blocker-input and todo-detail-next-action-input so their DOM state
resets when item.id changes, either by keying the detail panel or these inputs
with item.id. Preserve the existing trimmed onBlur updates while ensuring
switching items cannot retain or submit the previous item’s values.

In `@wework/src/features/todo/todoModel.ts`:
- Around line 208-218: Update hydrateLocalWorkItems to fall back to
loadLocalWorkItems only when load_todo_store returns null; remove the
catch-based fallback so rejected reads and malformed JSON propagate. Also update
the listing logic at wework/src/features/todo/todoModel.ts lines 256-263 to
propagate listing errors rather than treating them as an empty workspace.
- Around line 73-129: Replace the hardcoded Chinese status and workflow template
names in DEFAULT_TODO_WORKFLOW and TODO_WORKFLOW_TEMPLATES with stable
translation keys, then resolve them through useTranslation wherever these
configs are displayed. Add matching entries for every status and template key
under the appropriate Wework namespaces in both en and zh-CN locale trees,
preserving the current Chinese text in zh-CN and providing English text in en.
- Around line 265-279: The workspace upload flow must prevent unbounded memory
use. In wework/src/features/todo/todoModel.ts lines 265-279, update
writeTodoWorkspaceFile to enforce the documented size limit before calling
file.arrayBuffer(), or pass a file path to a streaming backend command. In
wework/src-tauri/src/todo_store.rs lines 135-151, enforce the same limit
server-side and replace whole-file collection and synchronous writing with
buffered or streamed I/O for supported large files.

In `@wework/src/features/todo/TodoMyWork.tsx`:
- Around line 5-7: Update TodoMyWork and the TodoDetailItem model to carry
stable signed-in user, assignee, and confirmer IDs, then filter “My work” groups
by matching those identities instead of only assigneeType === 'human'. Ensure
review items are included only when the signed-in user matches the confirmer.

In `@wework/src/features/todo/TodoWorkflowDialog.tsx`:
- Around line 108-116: Apply the responsive control-size contract to the close
button in TodoWorkflowDialog.tsx (108-116) and the quick-create and related
column/list actions in TodoWorkItems.tsx (586-596): use at least 44px touch
targets on mobile while preserving h-8 w-8 for desktop icon-only controls.
- Around line 50-63: The TodoWorkflowDialog dependency graph must reject cycles
before accepting changes or saving. Update toggleDependency and the
save/runDraft validation path to detect whether the proposed workTypes
dependencies contain any cycle, prevent the toggle or save when cyclic, and
preserve acyclic configurations.
- Around line 263-273: Add descriptive data-testid attributes to the
clear-workflow and cancel Buttons in TodoWorkflowDialog, using stable,
action-specific selector values. Preserve their existing onClick handlers,
labels, and styling.

In `@wework/src/features/todo/TodoWorkspace.test.tsx`:
- Around line 559-564: Update the localStorage assertion in the TodoWorkspace
test to parse the stored JSON and locate the specific implementation child by
its ID or workTypeKey, rather than searching the entire serialized store. Assert
that this child has the expected title “等待 API 定稿”, state “backlog”, and type
“ai”, preserving the existing waitFor behavior.
- Around line 303-322: Extend coverage beyond the component tests in
TodoWorkspace.test.tsx by adding E2E tests for work-item creation, AI launch,
workflow application, completion, persistence, IPC, and recovery using the real
backend and Tauri runtime. Preserve the existing tests, and add isolated
ai:verify evidence that exercises these flows with real backend requests and
validates runtime behavior rather than injected callbacks or localStorage alone.

In `@wework/src/features/todo/TodoWorkspace.tsx`:
- Around line 452-453: Replace the hardcoded Chinese summaries in the persisted
event entries within TodoWorkspace with stable event types or translation keys,
ensuring stored history remains locale-independent. Add the corresponding keys
to both English and Chinese locale trees, and use the translation wrapper when
rendering these events so summaries are localized at display time.
- Around line 379-388: Update the projectItemCounts useMemo calculation to
exclude runtime workItems that are linked by local items, matching the displayed
list’s filtering behavior. Ensure each linked runtime task is omitted before
counting, while retaining all local root items and unlinked runtime tasks
without double-counting.
- Around line 415-421: Update the TodoWorkspace hydration and persistence
effects around hydrateLocalWorkItems and saveLocalWorkItems to track hydration
completion for the current user scope. Apply the hydrated result
unconditionally, including an empty array, and prevent saves until that scope’s
asynchronous hydration finishes; reset or invalidate the hydration state when
user?.id changes so prior-user items cannot be persisted under the new scope.

---

Outside diff comments:
In `@wework/src/features/todo/TodoWorkItems.tsx`:
- Around line 86-103: The global create request is only handled by the board
when its target column is visible. Forward createRequest to TodoList, and update
TodoBoard’s column-selection/rendering logic to ensure the requested state
column is included even when filters or showEmptyGroups would otherwise hide it,
so header/sidebar Create opens the appropriate input in both layouts.

---

Nitpick comments:
In `@wework/src/features/todo/TodoWorkspace.tsx`:
- Around line 234-242: The TodoWorkspace component spans over 1000 lines and
combines persistence logic, workflow generation, execution, filtering, and view
rendering. Extract custom hooks to isolate work-item storage state and
persistence, workflow action handlers (generate, execute, filter), and separate
the header and content rendering into focused sub-components. Pass these
extracted concerns to TodoWorkspace as props or through composition to keep the
main component under the 1000-line guideline while maintaining focused
responsibilities for each extracted piece.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6748923-0276-495e-992d-1042daace5e1

📥 Commits

Reviewing files that changed from the base of the PR and between f59afc3 and 18a6054.

📒 Files selected for processing (17)
  • build_image.sh
  • wework/src-tauri/src/lib.rs
  • wework/src-tauri/src/todo_store.rs
  • wework/src/components/layout/DesktopWorkbenchLayout.tsx
  • wework/src/features/todo/TodoDetailPanel.tsx
  • wework/src/features/todo/TodoMyWork.tsx
  • wework/src/features/todo/TodoNavigation.tsx
  • wework/src/features/todo/TodoWorkItems.tsx
  • wework/src/features/todo/TodoWorkflowDialog.tsx
  • wework/src/features/todo/TodoWorkspace.test.tsx
  • wework/src/features/todo/TodoWorkspace.tsx
  • wework/src/features/todo/todoModel.test.ts
  • wework/src/features/todo/todoModel.ts
  • wework/src/features/workbench/useWorkbenchRuntimeMessaging.ts
  • wework/src/features/workbench/workbenchContextTypes.ts
  • wework/src/i18n/locales/en/common.json
  • wework/src/i18n/locales/zh-CN/common.json

Comment thread build_image.sh
# Function to show help
show_help() {
echo "Build docker images for Wegent components"
echo "Build docker images for Wegent components"aass

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove the unintended aass suffix.

The command remains syntactically valid, but --help prints Build docker images for Wegent componentsaass instead of the expected header.

Proposed fix
-    echo "Build docker images for Wegent components"aass
+    echo "Build docker images for Wegent components"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
echo "Build docker images for Wegent components"aass
echo "Build docker images for Wegent components"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build_image.sh` at line 18, Remove the unintended “aass” suffix from the echo
command in build_image.sh so the output header is exactly “Build docker images
for Wegent components”.

Comment on lines +35 to +48
fn safe_relative_path(value: &str) -> Result<PathBuf, String> {
let path = Path::new(value);
if path.as_os_str().is_empty() || path.is_absolute() {
return Err("Workspace path must be relative".to_string());
}
if path.components().any(|component| {
matches!(
component,
Component::ParentDir | Component::RootDir | Component::Prefix(_)
)
}) {
return Err("Workspace path escapes the TODO directory".to_string());
}
Ok(path.to_path_buf())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Normalize paths before enforcing protected entries.

safe_relative_path accepts current-directory components, while protection uses the original string. Inputs such as context/., work/., or ./context therefore resolve to protected directories but bypass is_protected_workspace_path, allowing them to be renamed or recursively deleted.

Proposed fix
 fn safe_relative_path(value: &str) -> Result<PathBuf, String> {
     let path = Path::new(value);
     if path.as_os_str().is_empty() || path.is_absolute() {
         return Err("Workspace path must be relative".to_string());
     }
     if path.components().any(|component| {
         matches!(
             component,
-            Component::ParentDir | Component::RootDir | Component::Prefix(_)
+            Component::CurDir
+                | Component::ParentDir
+                | Component::RootDir
+                | Component::Prefix(_)
         )
     }) {
-        return Err("Workspace path escapes the TODO directory".to_string());
+        return Err("Workspace path must be normalized".to_string());
     }
     Ok(path.to_path_buf())
 }

Also add regression cases for context/., work/., and ./context.

Also applies to: 154-159, 190-201

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src-tauri/src/todo_store.rs` around lines 35 - 48, Normalize the path
in safe_relative_path before protected-entry checks so equivalent forms such as
context/., work/., and ./context resolve to their canonical relative paths.
Update is_protected_workspace_path and the rename/delete flows to use this
normalized value, preserving rejection of parent, root, and prefix components.
Add regression coverage for all three bypass forms.

Comment on lines +83 to +98
pub fn save_todo_store(
app: tauri::AppHandle,
scope: String,
contents: String,
) -> Result<(), String> {
serde_json::from_str::<serde_json::Value>(&contents)
.map_err(|error| format!("TODO store must contain valid JSON: {error}"))?;
let root = store_root(&app)?;
std::fs::create_dir_all(&root)
.map_err(|error| format!("Failed to create TODO store directory: {error}"))?;
let target = root.join(format!("{}.json", safe_key(&scope)?));
let temporary = target.with_extension("json.tmp");
std::fs::write(&temporary, contents)
.map_err(|error| format!("Failed to write TODO store: {error}"))?;
std::fs::rename(temporary, target)
.map_err(|error| format!("Failed to commit TODO store: {error}"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect target-platform configuration and all persistence call sites.
fd -i 'Cargo.toml|Cargo.lock|rust-toolchain.toml|*.yml|*.yaml' \
  --exec rg -n -C2 'windows|macos|linux|target_os|std::fs::rename' {}

rg -n -C3 --type=ts --type=tsx '\bsaveLocalWorkItems\s*\(' .
rg -n -C3 --type=rust '\bsave_todo_store\b|std::fs::rename\s*\(' .

Repository: wecode-ai/Wegent

Length of output: 2646


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- todo_store.rs (relevant section) ---'
sed -n '70,110p' wework/src-tauri/src/todo_store.rs

echo
echo '--- todoModel.ts (relevant section) ---'
sed -n '200,245p' wework/src/features/todo/todoModel.ts

echo
echo '--- search for save_todo_store call handling ---'
rg -n -C2 "invoke\\('save_todo_store'|save_todo_store\\(|saveLocalWorkItems\\(" wework/src

echo
echo '--- search for other rename uses in todo_store.rs ---'
rg -n -C2 'std::fs::rename|with_extension\("json.tmp"\)|create_dir_all' wework/src-tauri/src/todo_store.rs

Repository: wecode-ai/Wegent

Length of output: 6569


🌐 Web query:

Rust std::fs::rename Windows overwrite existing destination semantics MoveFileExW REPLACE_EXISTING

💡 Result:

On Windows, Rust's std::fs::rename will overwrite an existing destination file [1][2]. The implementation details of this operation have evolved to provide safer and more POSIX-like behavior [3][4]: 1. Core Semantics: By default, std::fs::rename replaces the destination file if it already exists [1][2]. If the destination is an existing directory, the operation will generally fail [1][2]. 2. Underlying APIs: Rust does not rely on a single Windows API call but instead attempts a series of strategies depending on OS and filesystem support [3][5]: - Modern POSIX Semantics: On newer Windows versions (Windows 10 1709+), Rust attempts to use SetFileInformationByHandle with the FILE_RENAME_FLAG_POSIX_SEMANTICS and FILE_RENAME_FLAG_REPLACE_IF_EXISTS flags [4][6]. This allows for atomic renames even if the destination file is currently open (provided it was opened with appropriate sharing permissions) [3][4]. - Legacy Fallback: If the advanced POSIX-style rename is not supported (e.g., due to filesystem limitations or older OS versions), Rust falls back to standard Windows APIs like MoveFileExW (using the MOVEFILE_REPLACE_EXISTING flag) [1][3][7][5]. 3. Evolution: Recent changes in the Rust standard library (post-2024) have improved reliability by attempting non-atomic renames first in certain environments (like some Windows Server configurations) and falling back to atomic ones only if necessary, ensuring better compatibility across diverse Windows storage environments [5]. In summary, you can rely on std::fs::rename to overwrite an existing destination file on Windows [1][2], with the library automatically handling the underlying complexity of choosing the best available Windows API to perform the rename safely [3][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- todoModel.ts saveLocalWorkItems and nearby callers ---'
sed -n '200,320p' wework/src/features/todo/todoModel.ts

echo
echo '--- direct references to saveLocalWorkItems within wework/src ---'
rg -n -C3 '\bsaveLocalWorkItems\s*\(' wework/src --glob '*.ts' --glob '*.tsx'

echo
echo '--- direct references to save_todo_store within repo ---'
rg -n -C3 '\bsave_todo_store\b' wework/src-tauri wework/src --glob '*.rs' --glob '*.ts' --glob '*.tsx'

Repository: wecode-ai/Wegent

Length of output: 6420


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- todoModel.ts around load/save flow ---'
sed -n '170,240p' wework/src/features/todo/todoModel.ts

echo
echo '--- todo_store.rs load path ---'
sed -n '1,70p' wework/src-tauri/src/todo_store.rs

Repository: wecode-ai/Wegent

Length of output: 4670


Serialize TODO persistence writes per scope.

  • wework/src/features/todo/todoModel.ts#L221-L228: saveLocalWorkItems() fires save_todo_store fire-and-forget, so rapid edits can persist out of order.
  • wework/src-tauri/src/todo_store.rs#L83-L98: every save for a scope uses the same temp file path, so overlapping writes can stomp each other’s in-progress contents.

Queue or await the save path so only the latest snapshot reaches disk.

📍 Affects 2 files
  • wework/src-tauri/src/todo_store.rs#L83-L98 (this comment)
  • wework/src/features/todo/todoModel.ts#L221-L228
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src-tauri/src/todo_store.rs` around lines 83 - 98, Serialize TODO
persistence writes per scope: update saveLocalWorkItems in
wework/src/features/todo/todoModel.ts:221-228 to await or queue save_todo_store
calls so rapid edits commit in order and only the latest snapshot reaches disk;
update save_todo_store in wework/src-tauri/src/todo_store.rs:83-98 to prevent
overlapping saves for the same scope from sharing and stomping the temporary
file, using per-scope serialization while preserving atomic rename behavior.

Comment on lines +160 to +165
useEffect(() => {
if (!item.workspaceItemId) return
void listTodoWorkspace(item.workspaceItemId)
.then(setWorkspaceEntries)
.catch(() => setWorkspaceEntries([]))
}, [item.workspaceItemId, item.updatedAt])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Prevent stale workspace requests from overwriting newer results.

A slower request for the previous item or update can replace the current workspace list, exposing incorrect file actions. Cancel or sequence requests and clear entries when the workspace ID changes.

Proposed fix
   useEffect(() => {
-    if (!item.workspaceItemId) return
-    void listTodoWorkspace(item.workspaceItemId)
-      .then(setWorkspaceEntries)
-      .catch(() => setWorkspaceEntries([]))
+    const itemId = item.workspaceItemId
+    if (!itemId) {
+      setWorkspaceEntries([])
+      return
+    }
+    let cancelled = false
+    void listTodoWorkspace(itemId).then(entries => {
+      if (!cancelled) setWorkspaceEntries(entries)
+    })
+    return () => {
+      cancelled = true
+    }
   }, [item.workspaceItemId, item.updatedAt])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoDetailPanel.tsx` around lines 160 - 165, Update
the workspace-loading useEffect around listTodoWorkspace so previous requests
cannot overwrite results for a newer item or update. Clear workspace entries
when item.workspaceItemId changes, and cancel or sequence each request so only
the latest request may call setWorkspaceEntries; preserve the empty-list
fallback for the active request’s failure.

Comment on lines +214 to +218
const revealWorkspace = async () => {
if (!item.workspaceItemId) return
const path = await getTodoWorkspacePath(item.workspaceItemId)
if (path) await revealLocalFile(path)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Surface workspace open and reveal failures.

openLocalFile and revealLocalFile may reject, but these paths leave an unhandled promise and no user feedback. Catch failures and assign them to workspaceError.

Also applies to: 632-641

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoDetailPanel.tsx` around lines 214 - 218, Update
the workspace open/reveal handlers, including revealWorkspace and the
corresponding flow around lines 632-641, to catch rejected openLocalFile and
revealLocalFile promises and assign the caught error to workspaceError. Preserve
the existing path and workspace-item checks while ensuring both failure paths
provide user feedback through that error state.

Comment on lines +303 to +322
await quickCreate('Investigate and fix the bug')
await userEvent.click(screen.getByText('Investigate and fix the bug'))
await userEvent.selectOptions(screen.getByTestId('todo-detail-assignee-select'), 'ai')
await userEvent.click(screen.getByTestId('todo-detail-run'))

await waitFor(() => expect(onRunTodo).toHaveBeenCalledTimes(1))
expect(onRunTodo).toHaveBeenCalledWith(
expect.objectContaining({
project: expect.objectContaining({ id: 7 }),
message: 'Investigate and fix the bug',
goal: 'Ship a verified fix',
goal: undefined,
attachments: [],
collaborationMode: 'plan',
})
)
await waitFor(() => expect(screen.queryByTestId('todo-create-dialog')).not.toBeInTheDocument())
await waitFor(() => {
const stored = window.localStorage.getItem('wework:todo:work-items:1')
expect(stored).toContain('created-task')
expect(stored).toContain('已关联 AI 执行会话')
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Add real-backend and real-Tauri coverage for these core flows.

These are component tests using localStorage and injected callbacks; they do not validate actual runtime creation, workspace persistence, IPC, or recovery. Keep them, but add E2E coverage and isolated ai:verify evidence for creation, launch, workflow application, and completion.

As per coding guidelines, core flows require automated E2E regression coverage, and Wework runtime/Tauri changes require isolated real-Tauri verification using real backend requests.

Also applies to: 455-592

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkspace.test.tsx` around lines 303 - 322,
Extend coverage beyond the component tests in TodoWorkspace.test.tsx by adding
E2E tests for work-item creation, AI launch, workflow application, completion,
persistence, IPC, and recovery using the real backend and Tauri runtime.
Preserve the existing tests, and add isolated ai:verify evidence that exercises
these flows with real backend requests and validates runtime behavior rather
than injected callbacks or localStorage alone.

Source: Coding guidelines

Comment on lines +559 to +564
await waitFor(() => {
const stored = window.localStorage.getItem('wework:todo:work-items:1')
expect(stored).toContain('等待 API 定稿')
expect(stored).toContain('"state":"backlog"')
expect(stored).toContain('"type":"ai"')
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the updated child instead of searching the whole store.

Other workflow items already contain "state":"backlog" and an AI assignee, so these substring checks can pass even if the selected implementation child was updated incorrectly. Parse the JSON, locate that child by ID or workTypeKey, and assert its exact fields.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkspace.test.tsx` around lines 559 - 564,
Update the localStorage assertion in the TodoWorkspace test to parse the stored
JSON and locate the specific implementation child by its ID or workTypeKey,
rather than searching the entire serialized store. Assert that this child has
the expected title “等待 API 定稿”, state “backlog”, and type “ai”, preserving the
existing waitFor behavior.

Comment on lines 379 to +388
const projectItemCounts = useMemo(
() =>
Object.fromEntries(
projectEntries.map(entry => [
entry.project.id,
entry.workspaces.reduce((total, workspace) => total + workspace.tasks.length, 0) +
drafts.filter(draft => draft.projectId === entry.project.id).length,
workItems.filter(item => item.projectId === entry.project.id && !item.parentId).length,
])
),
[drafts, projectEntries]
[workItems, projectEntries]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Exclude linked runtime tasks from sidebar counts.

The displayed list removes runtime tasks referenced by local items, but this count still adds every runtime task plus every local root. After running a TODO, the sidebar counts that work item twice.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkspace.tsx` around lines 379 - 388, Update
the projectItemCounts useMemo calculation to exclude runtime workItems that are
linked by local items, matching the displayed list’s filtering behavior. Ensure
each linked runtime task is omitted before counting, while retaining all local
root items and unlinked runtime tasks without double-counting.

Comment on lines 415 to +421
useEffect(() => {
try {
window.localStorage.setItem(
`${TODO_DRAFTS_STORAGE_KEY}:${user?.id ?? 'local'}`,
JSON.stringify(drafts)
)
} catch {
// Drafts remain available for the current session.
}
}, [drafts, user?.id])
void hydrateLocalWorkItems(user?.id).then(items => {
if (items.length > 0) setWorkItems(items)
})
}, [user?.id])

useEffect(() => saveLocalWorkItems(user?.id, workItems), [user?.id, workItems])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

Gate persistence until the current user scope is hydrated.

Line 416 starts an asynchronous load, while Line 421 immediately saves the initial local state. In Tauri this can overwrite the authoritative store before loading finishes; on user changes it can also save the previous user's items into the new scope. Always apply the hydrated result—including []—and suppress saves until hydration for the current scope completes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkspace.tsx` around lines 415 - 421, Update
the TodoWorkspace hydration and persistence effects around hydrateLocalWorkItems
and saveLocalWorkItems to track hydration completion for the current user scope.
Apply the hydrated result unconditionally, including an empty array, and prevent
saves until that scope’s asynchronous hydration finishes; reset or invalidate
the hydration state when user?.id changes so prior-user items cannot be
persisted under the new scope.

Comment on lines +452 to +453
events: [
{ id: createLocalWorkItemId(), type: 'created', summary: '事项已创建', createdAt: now },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize persisted event summaries.

These Chinese literals appear in persisted history for every locale and cannot be retranslated later. Store stable event types/translation keys and translate when rendering, with entries in both locale trees.

As per coding guidelines, new Wework copy must use the translation wrapper and be added to both English and Chinese locales.

Also applies to: 476-481, 569-574, 629-630, 969-975, 994-1000

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkspace.tsx` around lines 452 - 453, Replace
the hardcoded Chinese summaries in the persisted event entries within
TodoWorkspace with stable event types or translation keys, ensuring stored
history remains locale-independent. Add the corresponding keys to both English
and Chinese locale trees, and use the translation wrapper when rendering these
events so summaries are localized at display time.

Source: Coding guidelines

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
wework/src/features/todo/TodoWorkItems.tsx (2)

86-103: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Forward global create requests to list layout.

createRequest only reaches TodoBoard, so the global create action does nothing when layout === 'list'. Pass it to TodoList and open/reset the requested state when its token changes.

Also applies to: 622-637

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkItems.tsx` around lines 86 - 103, Update the
TodoList branch in TodoWorkItems to pass createRequest, then handle
request-token changes inside TodoList by opening the create UI and resetting the
requested state. Preserve the existing TodoBoard behavior and ensure repeated
requests are processed only when the token changes.

409-427: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Always render the requested quick-create state.

The requested state can be removed by stateFilter or showEmptyGroups === false, so no TodoColumn receives forceCreateToken. Include createRequest.state in states while the request is active.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkItems.tsx` around lines 409 - 427, Update
the state list used by the TodoColumn map in TodoWorkItems so an active
createRequest.state is included even when stateFilter removes it or
showEmptyGroups hides empty groups. Preserve the existing state filtering
behavior when no quick-create request is active, ensuring the matching
TodoColumn receives forceCreateToken.
🧹 Nitpick comments (2)
wework/src/features/todo/TodoWorkspace.tsx (1)

234-242: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Extract the Todo persistence and workflow orchestration.

TodoWorkspace now contains hundreds of lines of persistence, workflow, runtime, and UI logic. Move these concerns into focused hooks such as useTodoPersistence and useTodoWorkflowExecution.

As per coding guidelines, keep functions focused, preferably under 50 lines, and split source files over 1000 lines.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkspace.tsx` around lines 234 - 242, Refactor
TodoWorkspace into focused hooks, extracting persistence responsibilities into
useTodoPersistence and workflow/runtime orchestration into
useTodoWorkflowExecution. Keep TodoWorkspace focused on composing hooks and
rendering UI, preserve existing behavior and dependencies, and split the
implementation into appropriately sized files with functions preferably under 50
lines.

Source: Coding guidelines

wework/src/features/todo/TodoDetailPanel.tsx (1)

965-1032: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Split the detail panel into cohesive modules.

The file now exceeds 1000 lines. Extract the workflow, shared-workspace, properties, and activity sections into focused components.

As per coding guidelines, source files over 1000 lines must be split.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoDetailPanel.tsx` around lines 965 - 1032, Split
TodoDetailPanel into focused components/modules, extracting the workflow,
shared-workspace, properties (including EditableProperty, Property,
formatDetailDate, and formatBytes), and activity sections while preserving
existing behavior and interfaces. Keep TodoDetailPanel responsible only for
composition and coordination, and move each cohesive section into appropriately
scoped components.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build_image.sh`:
- Line 18: Remove the unintended “aass” suffix from the help banner echo command
so it prints only “Build docker images for Wegent components”.

In `@wework/src-tauri/src/todo_store.rs`:
- Around line 35-49: Update safe_relative_path to reject Component::CurDir and
validate protected paths using normalized components rather than the raw input
string, covering aliases such as ".", "./README.md", and "context/". Apply the
same validation to the affected deletion flows and add regression tests for
these path forms.

In `@wework/src/features/todo/TodoDetailPanel.tsx`:
- Around line 676-679: Update the Properties heading in TodoDetailPanel to use
the existing t(...) localization function, then add the corresponding key under
the appropriate Wework namespace in both English and Chinese locale files, with
the correct translated values.
- Around line 421-446: Update the editable blocker and next-action inputs in
TodoDetailPanel so their displayed values reset when item.id changes. Either
control both inputs with state synchronized to the selected item, or key each
input by item.id while preserving the existing trimmed onBlur updates.
- Around line 160-165: Update the workspace-loading useEffect around
listTodoWorkspace so responses are applied only if they belong to the latest
item request. Track cancellation or request identity within the effect, ignore
stale successes and failures after cleanup, and preserve clearing entries when
the active request fails.

In `@wework/src/features/todo/TodoMyWork.tsx`:
- Around line 15-40: Update TodoMyWork to receive a stable current principal ID
and restrict the “action” group to human-assigned items whose explicit assignee
ID matches that principal. Replace the assigneeType-only filter in the action
group while preserving the existing state exclusions and leave the AI/review
grouping unchanged.

In `@wework/src/features/todo/TodoWorkflowDialog.tsx`:
- Around line 263-273: Add descriptive, stable data-testid attributes to the
clear-workflow and cancel Buttons in TodoWorkflowDialog’s footer. Use distinct
selectors that identify each action, while preserving their existing onClick
behavior and labels.
- Around line 90-96: Update the dialog section’s width classes in the
TodoWorkflowDialog markup to use full available width while retaining a 560px
maximum, replacing the fixed w-[560px] sizing with w-full max-w-[560px].
Preserve the existing layout and styling classes.
- Around line 50-63: Prevent cyclic dependencies in the workflow state before
saving: update toggleDependency or the save handler to detect whether
dependencyKey can already reach workTypeKey through the existing dependsOn
links, and reject the change when it would create a cycle. Preserve valid
dependency updates and ensure the save path cannot persist cyclic graphs.
- Around line 108-116: Update the mobile TODO controls to provide at least 44px
touch targets: adjust the close and other h-7/h-8 controls in
wework/src/features/todo/TodoWorkflowDialog.tsx:108-116, enlarge the
quick-create, submit, and list-creation actions in
wework/src/features/todo/TodoWorkItems.tsx:513-521, and enlarge the workspace,
workflow, property, and confirmation actions in
wework/src/features/todo/TodoDetailPanel.tsx:588-605, using responsive sizing
while preserving desktop dimensions.

In `@wework/src/features/todo/TodoWorkspace.tsx`:
- Around line 592-597: Update deleteDraft to invoke and await a Tauri command
that removes the filesystem workspace for draftId before calling setWorkItems or
clearing the selection. Surface command failures and preserve the existing state
only after deletion succeeds; add the command implementation using the workspace
root path and ensure it removes the entire workspace directory.
- Around line 415-421: Update the hydration and persistence effects in
TodoWorkspace to track the current user’s hydration generation, always apply
hydrated results including empty arrays, and gate saveLocalWorkItems until
hydration for that same user scope completes. Ensure stale hydration completions
cannot update or enable persistence for a newer user, and add a regression
covering switching from user A with items to empty user B. Verify the Tauri
behavior change with isolated real-Tauri execution via scripts/ai-verify.mjs.
- Around line 552-558: Update the onRunTodo call in TodoWorkspace so its message
is always non-empty: use draft.description when it contains meaningful text,
otherwise fall back to draft.objective or the todo title. Preserve the existing
goal, attachments, and collaborationMode behavior.

---

Outside diff comments:
In `@wework/src/features/todo/TodoWorkItems.tsx`:
- Around line 86-103: Update the TodoList branch in TodoWorkItems to pass
createRequest, then handle request-token changes inside TodoList by opening the
create UI and resetting the requested state. Preserve the existing TodoBoard
behavior and ensure repeated requests are processed only when the token changes.
- Around line 409-427: Update the state list used by the TodoColumn map in
TodoWorkItems so an active createRequest.state is included even when stateFilter
removes it or showEmptyGroups hides empty groups. Preserve the existing state
filtering behavior when no quick-create request is active, ensuring the matching
TodoColumn receives forceCreateToken.

---

Nitpick comments:
In `@wework/src/features/todo/TodoDetailPanel.tsx`:
- Around line 965-1032: Split TodoDetailPanel into focused components/modules,
extracting the workflow, shared-workspace, properties (including
EditableProperty, Property, formatDetailDate, and formatBytes), and activity
sections while preserving existing behavior and interfaces. Keep TodoDetailPanel
responsible only for composition and coordination, and move each cohesive
section into appropriately scoped components.

In `@wework/src/features/todo/TodoWorkspace.tsx`:
- Around line 234-242: Refactor TodoWorkspace into focused hooks, extracting
persistence responsibilities into useTodoPersistence and workflow/runtime
orchestration into useTodoWorkflowExecution. Keep TodoWorkspace focused on
composing hooks and rendering UI, preserve existing behavior and dependencies,
and split the implementation into appropriately sized files with functions
preferably under 50 lines.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c6748923-0276-495e-992d-1042daace5e1

📥 Commits

Reviewing files that changed from the base of the PR and between f59afc3 and 18a6054.

📒 Files selected for processing (17)
  • build_image.sh
  • wework/src-tauri/src/lib.rs
  • wework/src-tauri/src/todo_store.rs
  • wework/src/components/layout/DesktopWorkbenchLayout.tsx
  • wework/src/features/todo/TodoDetailPanel.tsx
  • wework/src/features/todo/TodoMyWork.tsx
  • wework/src/features/todo/TodoNavigation.tsx
  • wework/src/features/todo/TodoWorkItems.tsx
  • wework/src/features/todo/TodoWorkflowDialog.tsx
  • wework/src/features/todo/TodoWorkspace.test.tsx
  • wework/src/features/todo/TodoWorkspace.tsx
  • wework/src/features/todo/todoModel.test.ts
  • wework/src/features/todo/todoModel.ts
  • wework/src/features/workbench/useWorkbenchRuntimeMessaging.ts
  • wework/src/features/workbench/workbenchContextTypes.ts
  • wework/src/i18n/locales/en/common.json
  • wework/src/i18n/locales/zh-CN/common.json

Comment thread build_image.sh
# Function to show help
show_help() {
echo "Build docker images for Wegent components"
echo "Build docker images for Wegent components"aass

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove the unintended aass suffix from the help banner.

The current command prints Build docker images for Wegent componentsaass, degrading the user-facing help output.

Proposed fix
-    echo "Build docker images for Wegent components"aass
+    echo "Build docker images for Wegent components"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
echo "Build docker images for Wegent components"aass
echo "Build docker images for Wegent components"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build_image.sh` at line 18, Remove the unintended “aass” suffix from the help
banner echo command so it prints only “Build docker images for Wegent
components”.

Comment on lines +35 to +49
fn safe_relative_path(value: &str) -> Result<PathBuf, String> {
let path = Path::new(value);
if path.as_os_str().is_empty() || path.is_absolute() {
return Err("Workspace path must be relative".to_string());
}
if path.components().any(|component| {
matches!(
component,
Component::ParentDir | Component::RootDir | Component::Prefix(_)
)
}) {
return Err("Workspace path escapes the TODO directory".to_string());
}
Ok(path.to_path_buf())
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject normalized aliases of protected paths.

relativePath='.' passes validation and makes deletion target the entire workspace. Aliases such as ./README.md or context/ can also bypass the raw-string protection check. Reject Component::CurDir and detect protected entries from normalized components; add regression tests for these forms.

Proposed fix
 if path.components().any(|component| {
     matches!(
         component,
-        Component::ParentDir | Component::RootDir | Component::Prefix(_)
+        Component::CurDir
+            | Component::ParentDir
+            | Component::RootDir
+            | Component::Prefix(_)
     )
 }) {
 fn is_protected_workspace_path(path: &Path) -> bool {
-    matches!(
-        path.to_string_lossy().as_ref(),
-        "README.md" | "context" | "work"
-    )
+    let mut components = path.components()
+    matches!(
+        (components.next(), components.next()),
+        (Some(Component::Normal(name)), None)
+            if matches!(name.to_str(), Some("README.md" | "context" | "work"))
+    )
 }

Also applies to: 154-209, 282-311

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src-tauri/src/todo_store.rs` around lines 35 - 49, Update
safe_relative_path to reject Component::CurDir and validate protected paths
using normalized components rather than the raw input string, covering aliases
such as ".", "./README.md", and "context/". Apply the same validation to the
affected deletion flows and add regression tests for these path forms.

Comment on lines +160 to +165
useEffect(() => {
if (!item.workspaceItemId) return
void listTodoWorkspace(item.workspaceItemId)
.then(setWorkspaceEntries)
.catch(() => setWorkspaceEntries([]))
}, [item.workspaceItemId, item.updatedAt])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Discard stale workspace-loading responses.

Switching items before an earlier request resolves can overwrite the new item’s entries with the old workspace. Subsequent rename/delete actions then use the current item ID with stale paths.

Proposed fix
 useEffect(() => {
-  if (!item.workspaceItemId) return
+  let cancelled = false
+  setWorkspaceEntries([])
+  if (!item.workspaceItemId) return
   void listTodoWorkspace(item.workspaceItemId)
-    .then(setWorkspaceEntries)
-    .catch(() => setWorkspaceEntries([]))
+    .then(entries => {
+      if (!cancelled) setWorkspaceEntries(entries)
+    })
+  return () => {
+    cancelled = true
+  }
 }, [item.workspaceItemId, item.updatedAt])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (!item.workspaceItemId) return
void listTodoWorkspace(item.workspaceItemId)
.then(setWorkspaceEntries)
.catch(() => setWorkspaceEntries([]))
}, [item.workspaceItemId, item.updatedAt])
useEffect(() => {
let cancelled = false
setWorkspaceEntries([])
if (!item.workspaceItemId) return
void listTodoWorkspace(item.workspaceItemId)
.then(entries => {
if (!cancelled) setWorkspaceEntries(entries)
})
return () => {
cancelled = true
}
}, [item.workspaceItemId, item.updatedAt])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoDetailPanel.tsx` around lines 160 - 165, Update
the workspace-loading useEffect around listTodoWorkspace so responses are
applied only if they belong to the latest item request. Track cancellation or
request identity within the effect, ignore stale successes and failures after
cleanup, and preserve clearing entries when the active request fails.

Comment on lines +421 to +446
{onUpdateItem && (
<section className="mt-[18px] grid gap-2 sm:grid-cols-2">
<label className="rounded-lg border border-border bg-muted/30 p-3">
<span className="text-[10px] font-semibold text-text-muted">
{t('todo.blocked', '阻塞')}
</span>
<input
data-testid="todo-detail-blocker-input"
defaultValue={item.blocker}
onBlur={event => onUpdateItem({ blocker: event.target.value.trim() })}
placeholder={t('todo.blocker_placeholder', '没有阻塞')}
className="mt-1 h-7 w-full bg-transparent text-[11px] text-text-primary outline-none placeholder:text-text-muted"
/>
</label>
<label className="rounded-lg border border-border bg-muted/30 p-3">
<span className="text-[10px] font-semibold text-text-muted">
{t('todo.next_action', '下一步')}
</span>
<input
data-testid="todo-detail-next-action-input"
defaultValue={item.nextAction}
onBlur={event => onUpdateItem({ nextAction: event.target.value.trim() })}
placeholder={t('todo.next_action_placeholder', '写下明确的下一步')}
className="mt-1 h-7 w-full bg-transparent text-[11px] text-text-primary outline-none placeholder:text-text-muted"
/>
</label>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reset editable fields when the selected item changes.

Because these inputs use defaultValue, selecting a child or another item leaves the previous item’s text in the DOM; blurring can save that stale value onto the new item. Use controlled state or key each input by item.id.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoDetailPanel.tsx` around lines 421 - 446, Update
the editable blocker and next-action inputs in TodoDetailPanel so their
displayed values reset when item.id changes. Either control both inputs with
state synchronized to the selected item, or key each input by item.id while
preserving the existing trimmed onBlur updates.

Comment on lines 676 to 679
<section className="mt-[18px]">
<h3 className="mb-2 text-[13px] font-semibold text-[#343A40] dark:text-text-primary">
Properties
</h3>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize the new Properties heading.

Properties remains English in the Chinese UI. Replace it with t(...) and add the key to both locale files.

As per coding guidelines, new copy must be added to the appropriate Wework namespace in both English and Chinese locales.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoDetailPanel.tsx` around lines 676 - 679, Update
the Properties heading in TodoDetailPanel to use the existing t(...)
localization function, then add the corresponding key under the appropriate
Wework namespace in both English and Chinese locale files, with the correct
translated values.

Source: Coding guidelines

Comment on lines +108 to +116
<button
type="button"
data-testid="todo-workflow-close"
onClick={onClose}
className="flex h-8 w-8 items-center justify-center rounded-md text-text-muted hover:bg-muted"
aria-label={t('workbench.close', '关闭')}
>
<X className="h-4 w-4" />
</button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Make all new mobile actions meet the 44px touch-target requirement.

The new workflow and TODO controls consistently retain desktop dimensions on mobile.

  • wework/src/features/todo/TodoWorkflowDialog.tsx#L108-L116: use responsive 44px sizing for the close action and audit the dialog’s h-7/h-8 controls.
  • wework/src/features/todo/TodoWorkItems.tsx#L513-L521: enlarge quick-create, submit, and list creation actions on mobile.
  • wework/src/features/todo/TodoDetailPanel.tsx#L588-L605: enlarge workspace, workflow, property, and confirmation actions on mobile.

As per coding guidelines, mobile controls must be at least 44px × 44px.

📍 Affects 3 files
  • wework/src/features/todo/TodoWorkflowDialog.tsx#L108-L116 (this comment)
  • wework/src/features/todo/TodoWorkItems.tsx#L513-L521
  • wework/src/features/todo/TodoDetailPanel.tsx#L588-L605
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkflowDialog.tsx` around lines 108 - 116,
Update the mobile TODO controls to provide at least 44px touch targets: adjust
the close and other h-7/h-8 controls in
wework/src/features/todo/TodoWorkflowDialog.tsx:108-116, enlarge the
quick-create, submit, and list-creation actions in
wework/src/features/todo/TodoWorkItems.tsx:513-521, and enlarge the workspace,
workflow, property, and confirmation actions in
wework/src/features/todo/TodoDetailPanel.tsx:588-605, using responsive sizing
while preserving desktop dimensions.

Source: Coding guidelines

Comment on lines +263 to +273
<footer className="flex h-14 items-center justify-end gap-2 border-t border-border px-5">
<Button
type="button"
variant="ghost"
onClick={() => setWorkTypes(DEFAULT_TODO_WORKFLOW.workTypes)}
>
{t('todo.clear_workflow', '清空流程')}
</Button>
<Button type="button" variant="secondary" onClick={onClose}>
{t('common.cancel', '取消')}
</Button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add stable selectors to the new footer actions.

Both clear and cancel are new interactive elements without descriptive data-testid values.

As per coding guidelines, all new interactive elements must have descriptive data-testid values.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkflowDialog.tsx` around lines 263 - 273, Add
descriptive, stable data-testid attributes to the clear-workflow and cancel
Buttons in TodoWorkflowDialog’s footer. Use distinct selectors that identify
each action, while preserving their existing onClick behavior and labels.

Source: Coding guidelines

Comment on lines 415 to +421
useEffect(() => {
try {
window.localStorage.setItem(
`${TODO_DRAFTS_STORAGE_KEY}:${user?.id ?? 'local'}`,
JSON.stringify(drafts)
)
} catch {
// Drafts remain available for the current session.
}
}, [drafts, user?.id])
void hydrateLocalWorkItems(user?.id).then(items => {
if (items.length > 0) setWorkItems(items)
})
}, [user?.id])

useEffect(() => saveLocalWorkItems(user?.id, workItems), [user?.id, workItems])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Gate persistence until the matching user scope is hydrated.

On a user change, Line 421 immediately saves the previous workItems under the new user before hydration resolves. Empty hydration results are then ignored by Line 417, leaking or restoring stale items across user scopes. Track the hydration generation, always apply empty results, and enable saves only after that same scope finishes hydrating.

Add a regression covering user A with items switching to an empty user B.

As per coding guidelines, Tauri behavior changes require isolated real-Tauri verification using scripts/ai-verify.mjs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkspace.tsx` around lines 415 - 421, Update
the hydration and persistence effects in TodoWorkspace to track the current
user’s hydration generation, always apply hydrated results including empty
arrays, and gate saveLocalWorkItems until hydration for that same user scope
completes. Ensure stale hydration completions cannot update or enable
persistence for a newer user, and add a regression covering switching from user
A with items to empty user B. Verify the Tauri behavior change with isolated
real-Tauri execution via scripts/ai-verify.mjs.

Source: Coding guidelines

Comment on lines 552 to 558
const address = await onRunTodo({
project,
message: draft.markdown,
goal: draft.goal || undefined,
message: draft.description,
goal: draft.objective || undefined,
attachments: draft.attachments,
collaborationMode: draft.runtimeRefs.length === 0 ? 'plan' : 'default',
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Provide non-empty input when launching workflow stages.

Auto-created and manually added children have description: '', but createProjectRuntimeTask rejects an empty trimmed message. Every such AI stage therefore fails to launch. Fall back to its objective or title.

Proposed fix
-      message: draft.description,
+      message: draft.description.trim() || draft.objective.trim() || draft.title,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const address = await onRunTodo({
project,
message: draft.markdown,
goal: draft.goal || undefined,
message: draft.description,
goal: draft.objective || undefined,
attachments: draft.attachments,
collaborationMode: draft.runtimeRefs.length === 0 ? 'plan' : 'default',
})
const address = await onRunTodo({
project,
message: draft.description.trim() || draft.objective.trim() || draft.title,
goal: draft.objective || undefined,
attachments: draft.attachments,
collaborationMode: draft.runtimeRefs.length === 0 ? 'plan' : 'default',
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkspace.tsx` around lines 552 - 558, Update
the onRunTodo call in TodoWorkspace so its message is always non-empty: use
draft.description when it contains meaningful text, otherwise fall back to
draft.objective or the todo title. Preserve the existing goal, attachments, and
collaborationMode behavior.

Comment on lines 592 to 597
const deleteDraft = (draftId: string) => {
setDrafts(current => current.filter(draft => draft.id !== draftId))
setWorkItems(current =>
current.filter(item => item.id !== draftId && item.parentId !== draftId)
)
setSelectedItemId(null)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Delete the filesystem workspace with the work item.

This removes the persisted model but leaves todo/workspaces/<id> and all user files behind indefinitely. Add a Tauri command that removes the root workspace, await it, surface failures, and only then remove the item from state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/todo/TodoWorkspace.tsx` around lines 592 - 597, Update
deleteDraft to invoke and await a Tauri command that removes the filesystem
workspace for draftId before calling setWorkItems or clearing the selection.
Surface command failures and preserve the existing state only after deletion
succeeds; add the command implementation using the workspace root path and
ensure it removes the entire workspace directory.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant