feat(project-space): link local tasks to project spaces - #2409
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis 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. ChangesProject-space bindings and runtime tracking
Desktop select control support
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
There was a problem hiding this comment.
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 winReset
viewwhen the trigger closes the menu.Clicking the trigger while
view === 'project-spaces'callssetOpen(false)instead ofcloseMenu(). 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 winSplit 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 winReuse the memoized
todoBindingApishere.
submitPaneInputcallsprojectSpaceBindingApis(services)on every submission and keepsservicesin the dependency list. Line 606 already memoizes the same result astodoBindingApis. Use it and depend ontodoBindingApis.lengthinstead, so the callback identity no longer changes with the wholeservicesobject.♻️ Proposed change
- projectSpaceBindingApis(services).length > 0 + todoBindingApis.length > 0sendPaneInput, - 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 liftConsider 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 exampleuseProjectSpaceAssociation, next toprojectSpaceLocalBindings.ts. The hook also gives the module-levelpendingTodoBindinga 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 valueGuard the empty-API case in
findProjectSpaceContextForTask.
Promise.any([])rejects withAggregateError. 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 opaqueAggregateError.🤖 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 valueConsider reducing the per-project binding requests.
loadProjectSpaceBindingOptionscallslistLocalBindingsonce 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 bylocal_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 winLoad loop items only for the selected project space.
The effect calls
listLoopItemsfor 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 winUnstable project-space API array identity can drive a repeated fetch. The producer memoizes the API array on the whole
servicesobject, 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 onservices?.projectSpaceApis?.local,services?.projectSpaceApis?.cloud, andservices?.deliveryApiinstead of the wholeservicesobject, so the memo returns a stable array.wework/src/components/projects/LocalProjectEditDialog.tsx#L116-L139: stop depending on theprojectSpaceApisarray identity. Depend on a derived stable key or onprojectSpaceApis.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
📒 Files selected for processing (35)
backend/app/api/endpoints/cloud_projects.pybackend/app/api/endpoints/deliveries.pybackend/app/schemas/cloud_project.pybackend/app/schemas/delivery.pybackend/app/services/cloud_projects/service.pybackend/app/services/loop_items/service.pybackend/tests/api/test_cloud_projects_api.pydocs/en/wework/projects.mddocs/en/wework/tasks.mddocs/zh/wework/projects.mddocs/zh/wework/tasks.mdwework/e2e/desktop/task-flow.e2e.mjswework/src/api/deliveries.tswework/src/api/local/localDelivery.test.tswework/src/api/local/localDelivery.tswework/src/components/chat/ChatInput.tsxwework/src/components/chat/composer/AddContextMenu.test.tsxwework/src/components/chat/composer/AddContextMenu.tsxwework/src/components/chat/composer/ComposerToolbar.tsxwework/src/components/chat/composer/ProjectChatComposer.tsxwework/src/components/layout/DesktopSidebar.test.tsxwework/src/components/layout/DesktopSidebar.tsxwework/src/components/layout/DesktopWorkbenchLayout.tsxwework/src/components/layout/DesktopWorkbenchMain.tsxwework/src/components/projects/LocalProjectEditDialog.test.tsxwework/src/components/projects/LocalProjectEditDialog.tsxwework/src/features/todo/CloudProjectManageView.tsxwework/src/features/todo/CloudTodoWorkspace.tsxwework/src/features/todo/TodoBindingPicker.test.tsxwework/src/features/todo/TodoBindingPicker.tsxwework/src/features/todo/projectSpaceLocalBindings.test.tswework/src/features/todo/projectSpaceLocalBindings.tswework/src/features/workbench/WorkbenchProvider.tsxwework/src/i18n/locales/en/common.jsonwework/src/i18n/locales/zh-CN/common.json
| LoopItemCreate( | ||
| title=values.task_title, | ||
| description=values.description, | ||
| status="in_progress", | ||
| ), |
There was a problem hiding this comment.
🎯 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 -SRepository: 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 120Repository: 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 -SRepository: 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.
| 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() |
There was a problem hiding this comment.
🗄️ 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.
| await control.command('waitFor', '[data-testid="project-space-context-pill"]', { | ||
| text: '加入看板 · Task Follow-up Board', | ||
| timeoutMs: DEFAULT_STEP_TIMEOUT_MS, |
There was a problem hiding this comment.
🎯 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 200Repository: 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 -SRepository: 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
| 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 } | ||
| }) |
There was a problem hiding this comment.
🗄️ 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.
| let binding: LocalTaskBindingRecord | ||
| try { | ||
| binding = await request<LocalTaskBindingRecord>('runtime_tasks.context', { | ||
| device_id: task.deviceId, | ||
| task_id: task.taskId, | ||
| }) | ||
| } catch { | ||
| return null | ||
| } |
There was a problem hiding this comment.
🗄️ 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.
| <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> |
There was a problem hiding this comment.
🎯 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-L151wework/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
| <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> |
There was a problem hiding this comment.
🎯 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.
| 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', | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 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.tsRepository: 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 -80Repository: 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.
| 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('关联的项目空间不可用') |
There was a problem hiding this comment.
📐 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
| 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)), | ||
| }) | ||
| } | ||
| }) |
There was a problem hiding this comment.
🗄️ 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.
What changed
+menuWhy
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
Validation
pnpm --filter wework test— 269 files, 2673 tests passedpnpm --filter wework lintpnpm --filter wework typecheckcd backend && uv run pytest tests/api/test_cloud_projects_api.py— 22 passedSummary by CodeRabbit
New Features
Bug Fixes
Documentation