Skip to content

feat(project-space): link local tasks to project spaces - #2409

Merged
qdaxb merged 12 commits into
mainfrom
fix/project-space-task-binding
Aug 4, 2026
Merged

feat(project-space): link local tasks to project spaces#2409
qdaxb merged 12 commits into
mainfrom
fix/project-space-task-binding

Conversation

@qdaxb

@qdaxb qdaxb commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What changed

  • link local projects to local or cloud project spaces and configure an automatic project-space destination per local project
  • show the pending board destination in the composer before sending, with manual selection through the + menu
  • create or reuse a project-space task when a conversation starts, using idempotent backend and local-runtime binding APIs
  • let the Environment panel list and operate on both local and cloud project spaces
  • keep project spaces visible when loading their task list fails, and route each action through the API that owns the selected space
  • gate the new project-level setting behind Experimental features
  • add backend, frontend, local-runtime, desktop E2E, i18n, and bilingual documentation coverage

Why

Local projects and project spaces represent different boundaries: the local project owns the code and execution workspace, while the project space owns task tracking and collaboration. Users need an explicit relationship between them so new conversations can be followed on a board without silently changing the execution environment.

Root cause

The task binding picker was hard-coded to the cloud delivery API, so local project spaces never appeared and selecting a space could not preserve its owning API. The existing flow also lacked a project-level default and idempotent conversation-to-board-task tracking, which could result in missing or duplicate board items.

User impact

  • a local project can define the project space automatically used by its new conversations
  • the composer clearly displays the board destination before the first message is sent
  • users can override or remove the destination for an individual conversation
  • both local and cloud spaces appear in the Environment binding picker
  • repeated synchronization of the same conversation does not create duplicate tasks

Validation

  • pnpm --filter wework test — 269 files, 2673 tests passed
  • pnpm --filter wework lint
  • pnpm --filter wework typecheck
  • Prettier check for all changed Wework and documentation files
  • cd backend && uv run pytest tests/api/test_cloud_projects_api.py — 22 passed
  • pre-push Wework ESLint, TypeScript, unit tests, Backend Black/isort/syntax, settings, and Alembic checks passed
  • isolated real Tauri verification:
    • enabled Experimental features
    • created a local project space
    • opened an existing task's Environment panel
    • confirmed the local space appeared together with cloud spaces in the project-space binding picker

Summary by CodeRabbit

  • New Features

    • Link local projects to project spaces and configure automatic joining.
    • Select a project space directly from the chat composer.
    • Manage local project bindings, including adding, updating defaults, and unlinking.
    • Track runtime tasks and reflect execution progress in project-space boards.
    • Choose tasks across local and cloud project spaces.
    • Select desktop controls by label or value in supported workflows.
  • Bug Fixes

    • Prevented duplicate task tracking and improved retry handling.
    • Preserved project selections when loading another project fails.
    • Improved default-binding management when changing project associations.
  • Documentation

    • Added English and Chinese guidance for project linking and task synchronization.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This change adds local project-space bindings, runtime task tracking, multi-API project selection, automatic project association, composer project selection, lifecycle status synchronization, documentation, E2E coverage, and desktop select-control automation.

Changes

Project-space bindings and runtime tracking

Layer / File(s) Summary
Backend binding and tracking contracts
backend/app/schemas/*, backend/app/api/endpoints/*, backend/app/services/*, backend/tests/api/test_cloud_projects_api.py
Adds local-binding update and delete APIs, runtime task tracking schemas and endpoints, active-binding lookup, ownership checks, default reassignment, and status updates.
Delivery APIs and local persistence
wework/src/api/deliveries.ts, wework/src/api/local/localDelivery.ts, wework/src/api/local/localDelivery.test.ts
Adds single-flight task tracking, runtime status updates, persisted local bindings, default exclusivity, deletion, retries, and concurrency coverage.
Binding discovery and management UI
wework/src/features/todo/projectSpaceLocalBindings.ts, wework/src/components/projects/*, wework/src/features/todo/CloudProjectManageView.tsx, wework/src/components/layout/DesktopWorkbenchLayout.tsx, wework/src/components/layout/DesktopSidebar.tsx
Adds binding discovery, automatic-join persistence, project editing, cloud-project linking, and experimental-feature wiring.
Workbench context and project selection
wework/src/components/layout/DesktopWorkbenchMain.tsx, wework/src/features/todo/TodoBindingPicker.tsx, wework/src/components/chat/*, wework/src/features/workbench/WorkbenchProvider.tsx
Routes project-specific APIs, injects project context into composers, supports multi-API task selection, and synchronizes runtime status changes.
Documentation, localization, and E2E validation
docs/*, wework/src/i18n/locales/*, wework/e2e/desktop/task-flow.e2e.mjs
Adds project-space instructions, translated labels, task-flow setup, and verification of completed tasks in project-space boards.

Desktop select control support

Layer / File(s) Summary
Select command execution
wework/src/extensions/desktop-control-contract.ts, wework/src/e2e/desktop-control-select.ts, wework/src/e2e/automation.ts, wework/src/e2e/desktop-control-select.test.ts
Adds label- or value-based HTML select handling, native event dispatch, element validation, and tests for successful and invalid selections.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Workbench
  participant BindingAPI
  participant DeliveryAPI
  participant ProjectSpace
  User->>Workbench: select project-space context
  Workbench->>BindingAPI: resolve bindings and project options
  BindingAPI-->>Workbench: return selected project context
  Workbench->>DeliveryAPI: track runtime task
  DeliveryAPI->>ProjectSpace: create or reuse task binding
  ProjectSpace-->>DeliveryAPI: return tracked item and binding
  DeliveryAPI-->>Workbench: return tracked task
  Workbench->>DeliveryAPI: update execution status
  DeliveryAPI->>ProjectSpace: update item workflow status
Loading

Possibly related PRs

  • wecode-ai/Wegent#2211: Both changes use WorkbenchProvider lifecycle state to synchronize runtime task status.
  • wecode-ai/Wegent#2359: Both changes integrate project-space behavior across workbench, composer, workspace, and task-flow components.
  • wecode-ai/Wegent#2415: Both changes extend DesktopWorkbenchMain.tsx with complementary project-space task navigation or tracking behavior.
🚥 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 summarizes the main change: linking local tasks to project spaces.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/project-space-task-binding

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.

@qdaxb
qdaxb marked this pull request as ready for review August 4, 2026 09:37
@qdaxb
qdaxb enabled auto-merge August 4, 2026 09:37
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
putComment timed out

@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: 16

Caution

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

⚠️ Outside diff range comments (1)
wework/src/components/chat/composer/AddContextMenu.tsx (1)

275-285: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reset view when the trigger closes the menu.

Clicking the trigger while view === 'project-spaces' calls setOpen(false) instead of closeMenu(). The next open renders the project-space submenu instead of the root menu.

Proposed fix
-        onClick={() => !disabled && setOpen(current => !current)}
+        onClick={() => {
+          if (disabled) return
+          if (open) {
+            closeMenu()
+            return
+          }
+          setOpen(true)
+        }}
🤖 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/components/chat/composer/AddContextMenu.tsx` around lines 275 -
285, Update the trigger button’s onClick handler in AddContextMenu so closing
the menu uses closeMenu() rather than directly calling setOpen(false), while
preserving the existing disabled guard and toggle behavior. This must reset view
to the root state when closing from the project-spaces submenu.
🧹 Nitpick comments (7)
backend/tests/api/test_cloud_projects_api.py (1)

836-871: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Split the binding scenarios and cover PATCH.

This function now contains link, default reassignment, and deletion flows. It exceeds 80 lines. It does not call the new PATCH endpoint.

Move reassignment and deletion into focused tests. Add a PATCH test that promotes an existing binding and verifies that its peer is no longer default.

As per coding guidelines, “functions should remain focused, preferably under 50 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 `@backend/tests/api/test_cloud_projects_api.py` around lines 836 - 871, Split
the oversized test around the existing cloud-project binding setup into focused
tests: keep link creation in its own test, move default reassignment and
deletion into separate tests, and add coverage for the PATCH endpoint that
promotes an existing binding while asserting its peer is no longer default. Use
the existing test client, authentication helper, and binding identifiers
consistently, and keep each test under 50 lines.

Source: Coding guidelines

wework/src/components/layout/DesktopWorkbenchMain.tsx (2)

710-716: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse the memoized todoBindingApis here.

submitPaneInput calls projectSpaceBindingApis(services) on every submission and keeps services in the dependency list. Line 606 already memoizes the same result as todoBindingApis. Use it and depend on todoBindingApis.length instead, so the callback identity no longer changes with the whole services object.

♻️ Proposed change
-          projectSpaceBindingApis(services).length > 0
+          todoBindingApis.length > 0
       sendPaneInput,
-      services,
+      todoBindingApis,
       setPendingCloudContext,
     ]

Also applies to: 746-760

🤖 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/components/layout/DesktopWorkbenchMain.tsx` around lines 710 -
716, Update the pending auto-join resolution logic in submitPaneInput to reuse
the memoized todoBindingApis value instead of calling
projectSpaceBindingApis(services). Change the callback dependency from the whole
services object to todoBindingApis.length, including the related logic at the
additional referenced range, while preserving the existing condition and
behavior.

606-640: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider extracting the project-space association logic from this component.

This change adds nine state values, three effects, and several callbacks for project-space association to DesktopWorkbenchPane, which is already far longer than 50 lines in a file well over 1000 lines. Move the pending-selection state, the local-binding refresh listener, and the project-loading effect into a dedicated hook, for example useProjectSpaceAssociation, next to projectSpaceLocalBindings.ts. The hook also gives the module-level pendingTodoBinding a clear owner.

As per coding guidelines: "functions should remain focused, preferably under 50 lines" and "split 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/components/layout/DesktopWorkbenchMain.tsx` around lines 606 -
640, Extract the project-space association state and behavior from
DesktopWorkbenchPane into a dedicated useProjectSpaceAssociation hook located
beside projectSpaceLocalBindings.ts. Move pending selection state,
pendingTodoBinding ownership, the local-binding refresh listener,
project-loading effect, and related association callbacks into the hook, then
have the component consume its returned state and actions while preserving
existing behavior.

Source: Coding guidelines

wework/src/features/todo/projectSpaceLocalBindings.ts (2)

67-72: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Guard the empty-API case in findProjectSpaceContextForTask.

Promise.any([]) rejects with AggregateError. The current callers guard the length, but the exported helper does not. Add an explicit early rejection or a documented precondition so a future caller does not receive an opaque AggregateError.

🤖 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/projectSpaceLocalBindings.ts` around lines 67 - 72,
Update findProjectSpaceContextForTask to explicitly handle an empty apis array
before calling Promise.any, using a clear intentional rejection or an
established documented precondition rather than allowing an opaque
AggregateError; preserve the existing Promise.any behavior for non-empty API
lists.

100-116: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider reducing the per-project binding requests.

loadProjectSpaceBindingOptions calls listLocalBindings once per project for every API. For M APIs and N projects, this issues M×N requests when the dialog opens. If the backend exposes a bindings listing scoped by local_project_id, use it instead, or add a bulk endpoint later.

🤖 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/projectSpaceLocalBindings.ts` around lines 100 -
116, Update loadProjectSpaceBindingOptions to avoid calling
api.listLocalBindings separately for every project in the nested projects.items
mapping. Prefer an available bindings-listing API scoped by local_project_id,
reusing the returned bindings to compute matchingBinding for each project; if no
such API exists, structure the change to support a future bulk endpoint while
preserving the existing project options and matching behavior.
wework/src/features/todo/TodoBindingPicker.tsx (1)

43-62: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Load loop items only for the selected project space.

The effect calls listLoopItems for every project on every API when the dialog opens. The UI shows items for one project at a time (selectedOption.items). With M APIs and N projects this issues M×N item requests, where one is needed. The previous single-API version already had this cost; the multi-API change multiplies it.

Load projects first, then fetch items for the selected option and cache them per key.

🤖 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/TodoBindingPicker.tsx` around lines 43 - 62, The
useEffect currently fetches loop items for every project returned by every API;
change it to fetch projects first, then call listLoopItems only for the selected
project space. Cache the fetched items by the selected option’s unique key and
have selectedOption.items read from that cache, preserving project loading while
reducing item requests to one per selected space.
wework/src/components/layout/DesktopWorkbenchLayout.tsx (1)

122-125: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Unstable project-space API array identity can drive a repeated fetch. The producer memoizes the API array on the whole services object, and the consumer effect depends on that array identity while setting state. Together these make the load loop depend on an object identity the consumer does not control.

  • wework/src/components/layout/DesktopWorkbenchLayout.tsx#L122-L125: depend on services?.projectSpaceApis?.local, services?.projectSpaceApis?.cloud, and services?.deliveryApi instead of the whole services object, so the memo returns a stable array.
  • wework/src/components/projects/LocalProjectEditDialog.tsx#L116-L139: stop depending on the projectSpaceApis array identity. Depend on a derived stable key or on projectSpaceApis.length, and read the list from a ref inside the effect.
🤖 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/components/layout/DesktopWorkbenchLayout.tsx` around lines 122 -
125, The project-space API array identity can trigger repeated loading. In
wework/src/components/layout/DesktopWorkbenchLayout.tsx lines 122-125, update
the useMemo dependencies in the availableProjectSpaceBindingApis setup to use
services?.projectSpaceApis?.local, services?.projectSpaceApis?.cloud, and
services?.deliveryApi instead of the whole services object. In
wework/src/components/projects/LocalProjectEditDialog.tsx lines 116-139, remove
the projectSpaceApis array identity from the effect dependencies, use a stable
derived key or projectSpaceApis.length, and read the current API list from a ref
inside the effect.
🤖 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 `@backend/app/api/endpoints/deliveries.py`:
- Around line 243-247: The runtime tracking flow currently hardcodes in_progress
and in_review, which may not exist in a custom native board. Update
track_cloud_project_task() and update_runtime_task_tracking_status() to resolve
and reuse the corresponding configured status IDs from board_config.statuses for
both LoopItemCreate and LoopItemUpdate transitions, preserving the existing
transition behavior while validating against the configured board.

In `@backend/app/services/loop_items/service.py`:
- Around line 795-803: The task-binding lookup must exclude bindings whose
associated loop item has been soft-deleted, rather than relying only on
unlinked_at. Update the query around LoopItemTaskBinding to atomically filter
out deleted items, or ensure LoopItemService.delete unlinks their bindings,
while preserving active unlinked bindings. Add a regression test covering
deletion of a tracked item followed by starting the same runtime task again.

In `@wework/e2e/desktop/task-flow.e2e.mjs`:
- Around line 3508-3510: Update the project-space-context-pill waitFor assertion
in the task flow to avoid requiring the Chinese localized text. Assert only the
stable data-testid, or configure the assertion to accept both the Chinese and
English labels while preserving the existing timeout.

In `@wework/src/api/local/localDelivery.ts`:
- Around line 576-584: Update the error handling around the
runtime_tasks.context request in the binding retrieval flow to return null only
when the runtime task context is confirmed missing. Distinguish that not-found
condition explicitly, and rethrow transport or executor failures so
WorkbenchProvider can preserve the synchronization signature and retry behavior.
- Around line 551-570: Replace the separate lookup, createLoopItem, and bindTask
calls inside trackProjectTaskOnce with one idempotent executor operation keyed
by the runtime task and target project, ensuring retries reuse the existing
binding or item instead of creating duplicates. Propagate operational lookup,
creation, and binding failures; only treat an explicitly absent context as
unlinked. Add a regression test covering bindTask failure after successful item
creation and verifying the failure is returned without creating a duplicate on
retry.

In `@wework/src/components/chat/composer/AddContextMenu.tsx`:
- Around line 140-151: Update the focus management in AddContextMenu so changing
view to the project-spaces submenu focuses an enabled submenu control, such as
the Back button or first available project option, instead of relying only on
open changes. Keep trigger restoration limited to menu closure, and use the
existing view/open state and submenu control references to preserve focus
behavior elsewhere.
- Around line 28-30: Support clearing the pending project-space destination from
the + menu by adding a clear-selection callback or nullable selection contract
to AddContextMenu, rendering a clear action when selected, and propagating it
through ComposerToolbar and ProjectChatComposer. Update AddContextMenu.test.tsx
with a regression test verifying the selection is cleared and the menu closes,
and add the new user-facing clear action copy to both locale namespaces.

In `@wework/src/components/layout/DesktopWorkbenchMain.tsx`:
- Around line 1059-1174: Remove pendingCloudProject from the dependency array of
the project-resolution useEffect. Preserve the existing guard by reading the
current pending project through a ref, or separate default selection from
fetching so setting pendingCloudProject does not rerun the API fan-out; keep the
default-selection behavior unchanged.
- Around line 1184-1187: Add the missing workbench.untitled_task translation key
to both English and Simplified Chinese common locale files, using the existing
fallback text and appropriate English and Chinese translations.

In `@wework/src/components/projects/LocalProjectEditDialog.tsx`:
- Around line 294-318: Add the missing workbench.loading key to the English
common locale, using the English loading text and matching the existing key
structure in the Chinese locale. Do not change LocalProjectEditDialog or other
translations.

In `@wework/src/features/todo/CloudProjectManageView.tsx`:
- Around line 133-138: Update the project-selection flow around the promise
callback that sets members, items, and localBindings so selectedLocalProjectId
is reconciled to the first unbound local project after bindings load, and
likewise after a binding is added. Ensure the selected project remains in the
available option set and prevent duplicate binding requests, then add a
regression test covering an initially selected project that is already bound.
- Around line 503-546: Increase the mobile touch targets to at least 44px by
44px: update the unlink button, selector, and Link button in
CloudProjectManageView.tsx (lines 503-546); update the Back button in
AddContextMenu.tsx (lines 142-151); and update all root-menu action targets in
AddContextMenu.tsx (lines 186-269). Preserve their existing behavior and styling
while applying the minimum dimensions at mobile widths.
- Around line 521-537: Add a translated accessible label to the select in the
local-project binding control, using the existing i18n/translation mechanism and
associating it with the element via a label or aria-label. Keep the current
selection and filtering behavior unchanged.

In `@wework/src/features/todo/projectSpaceLocalBindings.test.ts`:
- Around line 30-45: Add the required user_id fixture property to the object
returned by the binding() test helper, using a consistent representative value
while preserving all existing fields and behavior.

In `@wework/src/features/todo/TodoBindingPicker.tsx`:
- Line 157: Replace the hardcoded message in TodoBindingPicker’s currentOption
validation with a useTranslation('common') lookup, and add the corresponding key
and English/Chinese translations to both common.json locale files.

In `@wework/src/features/workbench/WorkbenchProvider.tsx`:
- Around line 264-278: The status synchronization block in WorkbenchProvider
must serialize updates by runtime task and individual API so transitions cannot
arrive out of order. Replace the shared signature/batch handling around
trackingStatusSignaturesRef with per-target ordered queues or equivalent state,
and retry rejected targets independently rather than requiring every API call to
reject; preserve successful targets and avoid treating fulfilled null responses
as failures. Add regression tests covering out-of-order transitions and partial
API failures.

---

Outside diff comments:
In `@wework/src/components/chat/composer/AddContextMenu.tsx`:
- Around line 275-285: Update the trigger button’s onClick handler in
AddContextMenu so closing the menu uses closeMenu() rather than directly calling
setOpen(false), while preserving the existing disabled guard and toggle
behavior. This must reset view to the root state when closing from the
project-spaces submenu.

---

Nitpick comments:
In `@backend/tests/api/test_cloud_projects_api.py`:
- Around line 836-871: Split the oversized test around the existing
cloud-project binding setup into focused tests: keep link creation in its own
test, move default reassignment and deletion into separate tests, and add
coverage for the PATCH endpoint that promotes an existing binding while
asserting its peer is no longer default. Use the existing test client,
authentication helper, and binding identifiers consistently, and keep each test
under 50 lines.

In `@wework/src/components/layout/DesktopWorkbenchLayout.tsx`:
- Around line 122-125: The project-space API array identity can trigger repeated
loading. In wework/src/components/layout/DesktopWorkbenchLayout.tsx lines
122-125, update the useMemo dependencies in the availableProjectSpaceBindingApis
setup to use services?.projectSpaceApis?.local,
services?.projectSpaceApis?.cloud, and services?.deliveryApi instead of the
whole services object. In
wework/src/components/projects/LocalProjectEditDialog.tsx lines 116-139, remove
the projectSpaceApis array identity from the effect dependencies, use a stable
derived key or projectSpaceApis.length, and read the current API list from a ref
inside the effect.

In `@wework/src/components/layout/DesktopWorkbenchMain.tsx`:
- Around line 710-716: Update the pending auto-join resolution logic in
submitPaneInput to reuse the memoized todoBindingApis value instead of calling
projectSpaceBindingApis(services). Change the callback dependency from the whole
services object to todoBindingApis.length, including the related logic at the
additional referenced range, while preserving the existing condition and
behavior.
- Around line 606-640: Extract the project-space association state and behavior
from DesktopWorkbenchPane into a dedicated useProjectSpaceAssociation hook
located beside projectSpaceLocalBindings.ts. Move pending selection state,
pendingTodoBinding ownership, the local-binding refresh listener,
project-loading effect, and related association callbacks into the hook, then
have the component consume its returned state and actions while preserving
existing behavior.

In `@wework/src/features/todo/projectSpaceLocalBindings.ts`:
- Around line 67-72: Update findProjectSpaceContextForTask to explicitly handle
an empty apis array before calling Promise.any, using a clear intentional
rejection or an established documented precondition rather than allowing an
opaque AggregateError; preserve the existing Promise.any behavior for non-empty
API lists.
- Around line 100-116: Update loadProjectSpaceBindingOptions to avoid calling
api.listLocalBindings separately for every project in the nested projects.items
mapping. Prefer an available bindings-listing API scoped by local_project_id,
reusing the returned bindings to compute matchingBinding for each project; if no
such API exists, structure the change to support a future bulk endpoint while
preserving the existing project options and matching behavior.

In `@wework/src/features/todo/TodoBindingPicker.tsx`:
- Around line 43-62: The useEffect currently fetches loop items for every
project returned by every API; change it to fetch projects first, then call
listLoopItems only for the selected project space. Cache the fetched items by
the selected option’s unique key and have selectedOption.items read from that
cache, preserving project loading while reducing item requests to one per
selected space.
🪄 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 Plus

Run ID: eca56d56-bcd9-4610-aa10-e731b6919cd7

📥 Commits

Reviewing files that changed from the base of the PR and between 76758c6 and b4e7cca.

📒 Files selected for processing (35)
  • backend/app/api/endpoints/cloud_projects.py
  • backend/app/api/endpoints/deliveries.py
  • backend/app/schemas/cloud_project.py
  • backend/app/schemas/delivery.py
  • backend/app/services/cloud_projects/service.py
  • backend/app/services/loop_items/service.py
  • backend/tests/api/test_cloud_projects_api.py
  • docs/en/wework/projects.md
  • docs/en/wework/tasks.md
  • docs/zh/wework/projects.md
  • docs/zh/wework/tasks.md
  • wework/e2e/desktop/task-flow.e2e.mjs
  • wework/src/api/deliveries.ts
  • wework/src/api/local/localDelivery.test.ts
  • wework/src/api/local/localDelivery.ts
  • wework/src/components/chat/ChatInput.tsx
  • wework/src/components/chat/composer/AddContextMenu.test.tsx
  • wework/src/components/chat/composer/AddContextMenu.tsx
  • wework/src/components/chat/composer/ComposerToolbar.tsx
  • wework/src/components/chat/composer/ProjectChatComposer.tsx
  • wework/src/components/layout/DesktopSidebar.test.tsx
  • wework/src/components/layout/DesktopSidebar.tsx
  • wework/src/components/layout/DesktopWorkbenchLayout.tsx
  • wework/src/components/layout/DesktopWorkbenchMain.tsx
  • wework/src/components/projects/LocalProjectEditDialog.test.tsx
  • wework/src/components/projects/LocalProjectEditDialog.tsx
  • wework/src/features/todo/CloudProjectManageView.tsx
  • wework/src/features/todo/CloudTodoWorkspace.tsx
  • wework/src/features/todo/TodoBindingPicker.test.tsx
  • wework/src/features/todo/TodoBindingPicker.tsx
  • wework/src/features/todo/projectSpaceLocalBindings.test.ts
  • wework/src/features/todo/projectSpaceLocalBindings.ts
  • wework/src/features/workbench/WorkbenchProvider.tsx
  • wework/src/i18n/locales/en/common.json
  • wework/src/i18n/locales/zh-CN/common.json

Comment on lines +243 to +247
LoopItemCreate(
title=values.task_title,
description=values.description,
status="in_progress",
),

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^backend/app/api/endpoints/deliveries\.py$|board|loop_item|project|delivery)' | head -200

echo "== deliveries outline =="
ast-grep outline backend/app/api/endpoints/deliveries.py --view condensed || true

echo "== relevant deliveries sections =="
sed -n '1,380p' backend/app/api/endpoints/deliveries.py | cat -n

echo "== search lifecycle/status identifiers =="
rg -n 'in_progress|in_review|execution_status|status_ids|status_id|project board|board|LoopItemCreate|LoopItemUpdate|loop_item_service|external_loop_item_provider|configured' backend/app -S

Repository: wecode-ai/Wegent

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== loop_item service outline =="
ast-grep outline backend/app/services/loop_items/service.py --view signatures || true

echo "== loop_item schema relevant lines =="
sed -n '1,80p' backend/app/schemas/delivery.py | cat -n

echo "== loop_item provider router relevant lines =="
sed -n '1,260p' backend/app/services/loop_items/provider_router.py | cat -n

echo "== local loop item service relevant methods =="
rg -n "def (create|update|validate.*status|normalize.*status|response_values)|status_ids|board_config|board_mapping|in_progress|in_review" backend/app/services/loop_items/service.py backend/app/schemas/delivery.py backend/app/schemas/project.py -S

echo "== project schema relevant lines =="
sed -n '90,155p' backend/app/schemas/cloud_project.py | cat -n

echo "== provider router status tests/usages =="
rg -n "board_mapping|board_config|status_ids|required_statuses|LoopItemCreate|LoopItemUpdate|resolve.*status|to_board|from_board|in_progress|in_review" backend/app -S --max-count 120

Repository: wecode-ai/Wegent

Length of output: 20222


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== loop_item service create/update implementations =="
sed -n '45,300p' backend/app/services/loop_items/service.py | cat -n
sed -n '520,570p' backend/app/services/loop_items/service.py | cat -n

echo "== project update implementation =="
sed -n '130,205p' backend/app/services/cloud_projects/service.py | cat -n

echo "== runtime task tracking status schema =="
sed -n '240,280p' backend/app/schemas/delivery.py | cat -n

echo "== project status metadata default =="
sed -n '65,90p' backend/app/services/cloud_projects/service.py | cat -n

echo "== status map tests relevant =="
rg -n "board_mapping|board_config|in_progress|in_review|RuntimeTaskTrack|tracking-status|task_provider" backend/tests -S

Repository: wecode-ai/Wegent

Length of output: 26392


Map runtime status transitions to the configured board.

Native projects validate LoopItemCreate and LoopItemUpdate statuses against board_config.statuses. A custom native board can remove or rename in_progress/in_review, so track_cloud_project_task() and update_runtime_task_tracking_status() will reject those transitions unless the runtime mapping uses the configured status IDs. Store or look up those IDs when tracking and apply the same mapping to both transitions.

📍 Affects 1 file
  • backend/app/api/endpoints/deliveries.py#L243-L247 (this comment)
  • backend/app/api/endpoints/deliveries.py#L275-L300
🤖 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 `@backend/app/api/endpoints/deliveries.py` around lines 243 - 247, The runtime
tracking flow currently hardcodes in_progress and in_review, which may not exist
in a custom native board. Update track_cloud_project_task() and
update_runtime_task_tracking_status() to resolve and reuse the corresponding
configured status IDs from board_config.statuses for both LoopItemCreate and
LoopItemUpdate transitions, preserving the existing transition behavior while
validating against the configured board.

Comment on lines +795 to +803
return (
db.query(LoopItemTaskBinding)
.filter(
LoopItemTaskBinding.task_user_id == user_id,
LoopItemTaskBinding.device_id == device_id,
LoopItemTaskBinding.task_id == task_id,
loop_datetime_is_unset(LoopItemTaskBinding.unlinked_at),
)
.first()

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 | 🏗️ Heavy lift

Do not treat a deleted TODO binding as active.

LoopItemService.delete soft-deletes the item but does not unlink its task binding. This query then returns that binding because it only checks unlinked_at. The tracking route loads the deleted item and returns 404, so the runtime task cannot be tracked again.

Unlink bindings for deleted items, or exclude them atomically from this lookup. Add a regression test that deletes a tracked item and starts the same runtime task again.

🤖 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 `@backend/app/services/loop_items/service.py` around lines 795 - 803, The
task-binding lookup must exclude bindings whose associated loop item has been
soft-deleted, rather than relying only on unlinked_at. Update the query around
LoopItemTaskBinding to atomically filter out deleted items, or ensure
LoopItemService.delete unlinks their bindings, while preserving active unlinked
bindings. Add a regression test covering deletion of a tracked item followed by
starting the same runtime task again.

Comment on lines +3508 to +3510
await control.command('waitFor', '[data-testid="project-space-context-pill"]', {
text: '加入看板 · Task Follow-up Board',
timeoutMs: DEFAULT_STEP_TIMEOUT_MS,

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file excerpt =="
sed -n '3480,3535p' wework/e2e/desktop/task-flow.e2e.mjs

echo
echo "== related i18n keys =="
rg -n '"project-space-context-pill"|"Add to board|加入看板|Task Follow-up Board|project.*space.*context|context-pill' wework/src wework/e2e/desktop/task-flow.e2e.mjs -S

echo
echo "== desktop runner / locale setup =="
rg -n 'DEFAULT_STEP_TIMEOUT_MS|WAIT|run-checkpoint|core-task-flow|task-flow|locale|e2e:desktop|VITE|i18n|CHROME|LANG|Project Follow-up|Task Follow-up Board' wework -S | head -n 200

Repository: wecode-ai/Wegent

Length of output: 25289


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== desktop runner top section =="
sed -n '1,140p' wework/e2e/desktop/run-checkpoints.mjs

echo
echo "== AI verify env top section =="
sed -n '1,80p' wework/scripts/ai-verify-environment.mjs

echo
echo "== task-flow startup / locale / i18n setup indicators =="
sed -n '1,120p' wework/e2e/desktop/task-flow.e2e.mjs
rg -n 'languagePreference|i18n\.use|changeLanguage|i18n\.init|navigator\.language|i18next-browser-languagedetector|VITE_WEWORK|WEWORK_E2E_DESKTOP|i18next' wework/src/i18n wework/e2e wework/scripts -S

Repository: wecode-ai/Wegent

Length of output: 17844


Make the context-pill assertion locale-independent.

This assertion requires the Chinese label, but English renders Add to board · Task Follow-up Board. If the runner uses English, this stable project-space-context-pill can be present without matching the asserted text. Assert only the data-testid, or accept both localized 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/e2e/desktop/task-flow.e2e.mjs` around lines 3508 - 3510, Update the
project-space-context-pill waitFor assertion in the task flow to avoid requiring
the Chinese localized text. Assert only the stable data-testid, or configure the
assertion to accept both the Chinese and English labels while preserving the
existing timeout.

Source: Coding guidelines

Comment on lines +551 to +570
return trackProjectTaskOnce(projectId, task, async () => {
try {
const existing = await request<LocalTaskBindingRecord>('runtime_tasks.context', {
device_id: task.deviceId,
task_id: task.taskId,
})
if (existing.loop_item_id) {
return { item: await api.getLoopItem(existing.loop_item_id) }
}
} catch {
// Missing context is the expected first-run path.
}
const item = await api.createLoopItem(projectId, {
title: taskTitle,
description,
status: 'in_progress',
})
await api.bindTask(item.id, task, taskTitle)
return { item }
})

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 | 🏗️ Heavy lift

Make local task tracking atomic.

The code performs context lookup, item creation, and task binding as separate executor calls. A transient lookup failure follows the unlinked path. A todos.bind failure after todos.create persists leaves an orphan item. A retry can create another item.

Move this workflow to one idempotent executor operation keyed by the runtime task and target project. Return operational failures instead of treating them as an unlinked task. Add a regression test for a bind failure after successful item creation.

🤖 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/api/local/localDelivery.ts` around lines 551 - 570, Replace the
separate lookup, createLoopItem, and bindTask calls inside trackProjectTaskOnce
with one idempotent executor operation keyed by the runtime task and target
project, ensuring retries reuse the existing binding or item instead of creating
duplicates. Propagate operational lookup, creation, and binding failures; only
treat an explicitly absent context as unlinked. Add a regression test covering
bindTask failure after successful item creation and verifying the failure is
returned without creating a duplicate on retry.

Comment on lines +576 to +584
let binding: LocalTaskBindingRecord
try {
binding = await request<LocalTaskBindingRecord>('runtime_tasks.context', {
device_id: task.deviceId,
task_id: task.taskId,
})
} catch {
return 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.

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

Return null only for a confirmed missing runtime task context.

This catch converts every runtime_tasks.context failure into a successful no-op. A disconnected executor then looks identical to an unlinked task. WorkbenchProvider can retain the synchronization signature and skip later retries.

Detect the not-found case explicitly. Rethrow transport and executor failures.

🤖 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/api/local/localDelivery.ts` around lines 576 - 584, Update the
error handling around the runtime_tasks.context request in the binding retrieval
flow to return null only when the runtime task context is confirmed missing.
Distinguish that not-found condition explicitly, and rethrow transport or
executor failures so WorkbenchProvider can preserve the synchronization
signature and retry behavior.

Comment on lines +503 to +546
<button
type="button"
data-testid={`cloud-project-local-binding-remove-${binding.local_project_id}`}
aria-label={t('todo.local_binding_unlink', '解除关联')}
disabled={bindingBusyId !== null}
onClick={() => void removeLocalProjectBinding(binding)}
className="grid size-8 place-items-center rounded-lg text-text-muted hover:bg-muted hover:text-danger disabled:opacity-50"
>
<Trash2 className="size-4" />
</button>
</div>
)
})}
{localProjects.some(
localProject =>
!localBindings.some(binding => binding.local_project_id === localProject.id)
) ? (
<div className="flex gap-2 rounded-xl bg-muted p-2">
<select
data-testid="cloud-project-local-binding-project"
value={selectedLocalProjectId ?? ''}
onChange={event => setSelectedLocalProjectId(Number(event.target.value) || null)}
className="h-9 min-w-0 flex-1 rounded-lg border border-border bg-background px-2 text-sm outline-none"
>
{localProjects
.filter(
localProject =>
!localBindings.some(binding => binding.local_project_id === localProject.id)
)
.map(localProject => (
<option key={localProject.id} value={localProject.id}>
{localProject.name}
</option>
))}
</select>
<button
type="button"
data-testid="cloud-project-local-binding-add"
disabled={!selectedLocalProjectId || bindingBusyId !== null}
onClick={() => void addLocalProjectBinding()}
className="h-9 rounded-lg px-3 text-xs text-text-secondary hover:bg-background disabled:opacity-50"
>
{t('todo.local_binding_link', '关联')}
</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 | 🟡 Minor | ⚡ Quick win

Use 44px mobile touch targets.

The new controls use 32px or 36px default dimensions. These dimensions also apply at mobile widths.

  • wework/src/features/todo/CloudProjectManageView.tsx#L503-L546: use 44px minimum dimensions for the unlink button, selector, and Link button on mobile.
  • wework/src/components/chat/composer/AddContextMenu.tsx#L142-L151: use a 44px minimum Back button target on mobile.
  • wework/src/components/chat/composer/AddContextMenu.tsx#L186-L269: use 44px minimum root-menu action targets on mobile.

As per coding guidelines, “Mobile controls must be at least 44px × 44px.”

📍 Affects 2 files
  • wework/src/features/todo/CloudProjectManageView.tsx#L503-L546 (this comment)
  • wework/src/components/chat/composer/AddContextMenu.tsx#L142-L151
  • wework/src/components/chat/composer/AddContextMenu.tsx#L186-L269
🤖 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/CloudProjectManageView.tsx` around lines 503 - 546,
Increase the mobile touch targets to at least 44px by 44px: update the unlink
button, selector, and Link button in CloudProjectManageView.tsx (lines 503-546);
update the Back button in AddContextMenu.tsx (lines 142-151); and update all
root-menu action targets in AddContextMenu.tsx (lines 186-269). Preserve their
existing behavior and styling while applying the minimum dimensions at mobile
widths.

Source: Coding guidelines

Comment on lines +521 to +537
<select
data-testid="cloud-project-local-binding-project"
value={selectedLocalProjectId ?? ''}
onChange={event => setSelectedLocalProjectId(Number(event.target.value) || null)}
className="h-9 min-w-0 flex-1 rounded-lg border border-border bg-background px-2 text-sm outline-none"
>
{localProjects
.filter(
localProject =>
!localBindings.some(binding => binding.local_project_id === localProject.id)
)
.map(localProject => (
<option key={localProject.id} value={localProject.id}>
{localProject.name}
</option>
))}
</select>

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

Give the local-project selector an accessible name.

The select has no associated label or aria-label. Screen readers cannot identify its purpose.

Add a translated accessible label.

🤖 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/CloudProjectManageView.tsx` around lines 521 - 537,
Add a translated accessible label to the select in the local-project binding
control, using the existing i18n/translation mechanism and associating it with
the element via a label or aria-label. Keep the current selection and filtering
behavior unchanged.

Comment on lines +30 to +45
function binding(
id: string,
projectId: string,
localProjectId: number,
isDefault: boolean
): CloudProjectLocalBinding {
return {
id,
cloud_project_id: projectId,
local_project_id: localProjectId,
device_id: 'device-1',
is_default: isDefault,
created_at: '2026-08-04T00:00:00Z',
updated_at: '2026-08-04T00:00:00Z',
}
}

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether user_id is a required field on CloudProjectLocalBinding.
fd -t f 'deliveries.ts' wework/src/api --exec ast-grep run --lang typescript --pattern 'export interface CloudProjectLocalBinding { $$$ }' {}

# Show the test helper for comparison.
rg -n -A 18 'function binding\(' wework/src/features/todo/projectSpaceLocalBindings.test.ts

Repository: wecode-ai/Wegent

Length of output: 1176


🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -t f 'projectSpaceLocalBindings.test.ts|deliveries.ts' wework/src | sed 's#^\./##'

printf '\n--- deliveries interface ---\n'
sed -n '170,192p' wework/src/api/deliveries.ts

printf '\n--- test helper and imports ---\n'
sed -n '1,60p' wework/src/features/todo/projectSpaceLocalBindings.test.ts

printf '\n--- typecheck script/package scripts ---\n'
if [ -f wework/package.json ]; then
  node -e 'const p=require("./wework/package.json"); console.log("scripts:", JSON.stringify(p.scripts||{}, null, 2))'
fi

printf '\n--- relevant CloudProjectLocalBinding usages with user_id ---\n'
rg -n 'CloudProjectLocalBinding|user_id' wework/src/features/todo/projectSpaceLocalBindings.test.ts wework/src/api -g '*.ts' | head -80

Repository: wecode-ai/Wegent

Length of output: 8916


Add user_id to binding().

CloudProjectLocalBinding still requires user_id, but this helper returns an object without it. Add a fixture user_id value so pnpm run typecheck succeeds.

🐛 Proposed fix
   return {
     id,
     cloud_project_id: projectId,
     local_project_id: localProjectId,
+    user_id: 1,
     device_id: 'device-1',
     is_default: isDefault,
📝 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
function binding(
id: string,
projectId: string,
localProjectId: number,
isDefault: boolean
): CloudProjectLocalBinding {
return {
id,
cloud_project_id: projectId,
local_project_id: localProjectId,
device_id: 'device-1',
is_default: isDefault,
created_at: '2026-08-04T00:00:00Z',
updated_at: '2026-08-04T00:00:00Z',
}
}
function binding(
id: string,
projectId: string,
localProjectId: number,
isDefault: boolean
): CloudProjectLocalBinding {
return {
id,
cloud_project_id: projectId,
local_project_id: localProjectId,
user_id: 1,
device_id: 'device-1',
is_default: isDefault,
created_at: '2026-08-04T00:00:00Z',
updated_at: '2026-08-04T00:00:00Z',
}
}
🤖 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/projectSpaceLocalBindings.test.ts` around lines 30 -
45, Add the required user_id fixture property to the object returned by the
binding() test helper, using a consistent representative value while preserving
all existing fields and behavior.

: null
const currentOption =
projectOptions.find(option => option.key === currentProjectKey) ?? selectedOption
if (!currentOption) throw new 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.

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

Translate the new error message.

Line 157 adds the hardcoded string '关联的项目空间不可用'. Route it through useTranslation('common') and add the key to both wework/src/i18n/locales/en/common.json and wework/src/i18n/locales/zh-CN/common.json. The surrounding strings in this file are also hardcoded; convert at least the newly added one.

As per coding guidelines: "Add new user-facing copy to the appropriate Wework namespace in both src/i18n/locales/en/ and src/i18n/locales/zh-CN/."

🤖 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/TodoBindingPicker.tsx` at line 157, Replace the
hardcoded message in TodoBindingPicker’s currentOption validation with a
useTranslation('common') lookup, and add the corresponding key and
English/Chinese translations to both common.json locale files.

Source: Coding guidelines

Comment on lines +264 to +278
const signature = `${executionStatus}:${taskStatus}`
if (trackingStatusSignaturesRef.current.get(key) === signature) continue
trackingStatusSignaturesRef.current.set(key, signature)
void Promise.allSettled(
trackingApis.map(api => api!.updateTaskTrackingStatus(lifecycle.address, executionStatus))
).then(results => {
if (results.every(result => result.status === 'rejected')) {
trackingStatusSignaturesRef.current.delete(key)
console.warn('[Wework] Failed to synchronize project board task status', {
address: lifecycle.address,
executionStatus,
errors: results.map(result => (result.status === 'rejected' ? result.reason : 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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Serialize and retry status updates per task and API.

Each new signature starts another batch without waiting for the previous batch. A delayed running update can arrive after succeeded and move a local item from in_review back to in_progress. A fulfilled null from a non-owning API also makes every(result => result.status === 'rejected') false, so a rejected owning API is not retried.

Keep ordered synchronization state per API and runtime task. Serialize transitions and retry each failed target. Add out-of-order and partial-failure regression tests.

🤖 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/workbench/WorkbenchProvider.tsx` around lines 264 - 278,
The status synchronization block in WorkbenchProvider must serialize updates by
runtime task and individual API so transitions cannot arrive out of order.
Replace the shared signature/batch handling around trackingStatusSignaturesRef
with per-target ordered queues or equivalent state, and retry rejected targets
independently rather than requiring every API call to reject; preserve
successful targets and avoid treating fulfilled null responses as failures. Add
regression tests covering out-of-order transitions and partial API failures.

@qdaxb
qdaxb added this pull request to the merge queue Aug 4, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 4, 2026
@qdaxb
qdaxb enabled auto-merge August 4, 2026 11:44
@qdaxb
qdaxb added this pull request to the merge queue Aug 4, 2026
@qdaxb
qdaxb removed this pull request from the merge queue due to a manual request Aug 4, 2026
@qdaxb
qdaxb enabled auto-merge August 4, 2026 14:02
@qdaxb
qdaxb added this pull request to the merge queue Aug 4, 2026
@qdaxb
qdaxb removed this pull request from the merge queue due to a manual request Aug 4, 2026
@qdaxb
qdaxb added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 6da048f Aug 4, 2026
48 checks passed
@qdaxb
qdaxb deleted the fix/project-space-task-binding branch August 4, 2026 16:27
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