feature: task-dnd-ux (3/3) - #1129
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:
📝 WalkthroughWalkthroughThe PR adds versioned task organization with persistent folders and pins, revision-aware host/webview messaging, drag-and-drop history management, fallback rendering, localization, and extensive tests. ChangesTask organization feature
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant HistoryView
participant ExtensionStateContext
participant ClineProvider
participant TaskOrganizationStore
HistoryView->>ExtensionStateContext: Send task organization mutation
ExtensionStateContext->>ClineProvider: Post taskOrganizationMutation
ClineProvider->>TaskOrganizationStore: Apply revision-checked mutation
TaskOrganizationStore-->>ClineProvider: Return typed result
ClineProvider-->>ExtensionStateContext: Post mutation result and snapshot
ExtensionStateContext-->>HistoryView: Update organization state
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 16
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (29)
docs/260730_0002_session_dashboard-crash-debug/233600_code-multi-branch-report.md-58-58 (1)
58-58: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winCorrect the wording in the failure analysis.
Use
terminal, shell, and command-execution testsat Line 58. Use1 msinstead of1msat Lines 66 and 228.Also applies to: 66-66, 228-228
🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/233600_code-multi-branch-report.md` at line 58, Update the failure-analysis wording to say “terminal, shell, and command-execution tests” instead of “terminal/shell/command execution related tests,” and format both occurrences of the duration as “1 ms” rather than “1ms” at the referenced lines.Source: Linters/SAST tools
docs/260730_0002_session_dashboard-crash-debug/232600_ask-full-audit-report.md-124-134 (1)
124-134: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUpdate the short-range breakdown finding to match the later fix.
docs/260730_0002_session_dashboard-crash-debug/context-save-260730-2333.mdLine 11 records commit0769ccea7, and Line 51 records the daily-rollup fix as completed. This report still presents the monthly-rollup problem as unresolved and retains it as a release condition. Mark the finding as resolved or clearly label this report as a pre-fix snapshot.Also applies to: 171-183
🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/232600_ask-full-audit-report.md` around lines 124 - 134, Update the short-range breakdown finding in the report, including the repeated section around the later referenced lines, to reflect that the daily-rollup fix is completed. Mark the monthly-rollup issue as resolved and remove it as an outstanding release condition, or clearly label the report as a pre-fix snapshot while preserving the recorded commit references.docs/260730_0002_session_dashboard-crash-debug/232600_ask-full-audit-report.md-136-144 (1)
136-144: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winQualify the claim that the crash is eliminated.
Lines 138-144 state that
cacheRatio > 0still uses the full event scan and can retain the crash vector. Lines 165-167 then state that the crash is eliminated. Replace the absolute claim with a statement limited to the default fast-path query.Also applies to: 163-167
🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/232600_ask-full-audit-report.md` around lines 136 - 144, The document states in lines 138-144 that cacheRatio > 0 scenarios still use full event scans and retain the crash vector, but then makes an absolute claim in lines 165-167 that the crash is eliminated. Update the crash-elimination claim in lines 165-167 to qualify it as only applying to the default fast-path query configuration (where cacheRatio is 0 or undefined), making clear that the limitation described in Inquiry 2 means the crash vector persists for users who enable cacheRatio.docs/260730_0002_session_dashboard-crash-debug/233600_code-multi-branch-report.md-80-80 (1)
80-80: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAdd language identifiers to the code fences.
Use
textfor the three branch-list fences at Lines 80, 125, and 160. This resolves the reported Markdownlint MD040 warnings.Also applies to: 125-125, 160-160
🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/233600_code-multi-branch-report.md` at line 80, The three branch-list code fences in this markdown file are missing language identifiers, which triggers Markdownlint MD040 warnings. Add the language identifier `text` to each of the three code fence opening markers for the branch-list sections. This ensures each code fence declaration includes a language specifier, resolving the linting violations.Source: Linters/SAST tools
docs/260730_0002_session_dashboard-crash-debug/test-mimo-parallel.txt-1-133 (1)
1-133: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winStore a normalized, reviewable test report.
As committed, this file contains NUL-padded terminal output and ANSI escape sequences. Common tools can treat it as binary, and the report is difficult to read or search. Re-export it as UTF-8 plain text with terminal control codes removed, or commit a concise report with the command, exit status, platform, and test counts.
🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/test-mimo-parallel.txt` around lines 1 - 133, Replace the raw terminal capture in the test report with UTF-8 plain text by removing NUL padding and ANSI escape sequences. Prefer a concise, reviewable report that preserves the test command, exit status, platform, and final test counts.docs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txt-1-2 (1)
1-2: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winNormalize the captured output before committing it.
The file contains NUL bytes and terminal ANSI escape sequences. Standard viewers and repository search display corrupted content. Re-capture or convert the output to UTF-8, strip terminal control codes, and retain only readable log content.
🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txt` around lines 1 - 2, Normalize the captured output in test-strict-reasoning.txt before committing it: convert the file to UTF-8, remove NUL bytes and terminal ANSI escape sequences, and retain only readable log content.docs/260803_0002_session_6-branch-bug-fix-verification/170924_code-report.md-3-7 (1)
3-7: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winFix the relative links to source files.
This report is under
docs/260803_0002_session_6-branch-bug-fix-verification/. Therefore,../src/...resolves todocs/src/..., not the repositorysrc/...directory. Change the affected links on Line 3, Line 6, Line 7, Line 15, Line 16, Line 26, and Line 27 to use../../src/....Also applies to: 15-16, 26-27
🤖 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 `@docs/260803_0002_session_6-branch-bug-fix-verification/170924_code-report.md` around lines 3 - 7, Update the affected relative source links in the report, including references to TaskOrganizationStore.ts, TaskOrganizationStore.spec.ts, withLock(), mutate(), resolveUnit(), and resolveTaskClosure(), from ../src/... to ../../src/... so they resolve from the document’s directory to the repository src directory.webview-ui/src/components/history/TaskOrganizationDndSurface.tsx-72-74 (1)
72-74: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDo not discard a typed folder name on every revision change.
This effect clears
pendingFolderDraftwheneverorganization.revisionchanges. The drag that opened the dialog is not the only source of revision changes: a concurrentmoveToFolder, a pin toggle, or a mutation from another view also bumps it. The folder-name dialog then closes and the typed name is lost with no message. Restrict the cancellation to the case where the draft source or destination no longer exists, or keep the dialog open and revalidate on confirm.🤖 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 `@webview-ui/src/components/history/TaskOrganizationDndSurface.tsx` around lines 72 - 74, Update the useEffect watching organization.revision so it does not unconditionally clear pendingFolderDraft on unrelated revisions. Only cancel the draft when its source or destination folder no longer exists, or otherwise keep the dialog open and revalidate those references during confirmation.webview-ui/src/components/history/ManualFolderItem.tsx-316-319 (1)
316-319: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDisable the member drop target during selection mode.
ManualFolderItemdisables its folder drop target whenisSelectionModeis true, butManualFolderMemberItemregisters a member drop target withoutdisabled. Pass the current mode throughHistoryPreviewInnerand setdisabled: isSelectionModeon the member droppable so drops do not create folders during selection mode.🤖 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 `@webview-ui/src/components/history/ManualFolderItem.tsx` around lines 316 - 319, Update ManualFolderMemberItem’s useDroppable configuration to accept the isSelectionMode value passed through HistoryPreviewInner and set disabled to that value, matching the existing folder drop-target behavior so member drops cannot create folders during selection mode.webview-ui/src/components/history/TaskOrganizationInteractionContext.tsx-60-69 (1)
60-69: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReconcile the two
targetKeyimplementations.
buildGroupDndData()emitsautoGrouptargets from pinned projection rows, but the UI callsisPinned()/togglePin()with equivalenttasktargets for task groups. This localtargetKeymaps those same units to different keys, so a task group can render as pinned and remain incanPinafter the task-unit pin is removed. Use one shared canonical helper for task andautoGrouppins.🤖 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 `@webview-ui/src/components/history/TaskOrganizationInteractionContext.tsx` around lines 60 - 69, Update the targetKey logic used by buildGroupDndData, isPinned, and togglePin so task and autoGroup targets for the same task unit resolve to the same canonical key; reuse the existing shared helper if available rather than maintaining a separate local mapping. Preserve distinct keys for folder targets and ensure pin removal updates canPin consistently.webview-ui/src/components/history/taskOrganizationModel.ts-635-647 (1)
635-647: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse a single child-relationship source for workspace filtering.
buildFlattenedVirtualEntriesusesparentTaskIdthroughchildrenMap, butbuildGroupedOrganizationProjectionusestask.childIdsinisVisibleInWorkspace.childIdsis optional onHistoryItemand is not reliably written alongsideparentTaskId, so a group can hide even when one of its descendants belongs to the current workload. Switch this path to the sameparentTaskId/children-map source.🤖 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 `@webview-ui/src/components/history/taskOrganizationModel.ts` around lines 635 - 647, Update isVisibleInWorkspace to collect descendant task IDs using the parentTaskId-derived childrenMap, matching buildFlattenedVirtualEntries, instead of relying on task.childIds. Preserve the existing root fallback and taskBelongsToWorkspace checks so descendants in the current workspace keep the group visible.webview-ui/src/components/history/SubtaskRow.tsx-113-116 (1)
113-116: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUnwired pin props in two leaf components. Both files gained pin props and a
PinButtonbranch, but their parents never pass those props, so both branches are unreachable in the current tree. Decide one owner for the pin control per card and wire or remove accordingly.
webview-ui/src/components/history/SubtaskRow.tsx#L113-L116: passshowPin,isPinned,canPin, andonTogglePinfromTaskGroupItem.tsxLine 101, or delete the props and thePinButtonbranch at Lines 76-84.webview-ui/src/components/history/TaskItemFooter.tsx#L71-L79: pass the pin props fromTaskItem.tsxLines 136-142, or delete this branch and keep the header pin control inTaskItem.🤖 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 `@webview-ui/src/components/history/SubtaskRow.tsx` around lines 113 - 116, Choose one pin-control owner per card and ensure the other leaf branch is removed or wired. In webview-ui/src/components/history/SubtaskRow.tsx:113-116, update TaskGroupItem.tsx:101 to pass showPin, isPinned, canPin, and onTogglePin, or remove those props and the PinButton branch at SubtaskRow.tsx:76-84. In webview-ui/src/components/history/TaskItemFooter.tsx:71-79, either pass the pin props from TaskItem.tsx:136-142 or remove this branch while retaining TaskItem’s header pin control.webview-ui/src/components/history/TaskOrganizationErrorBoundary.tsx-13-20 (1)
13-20: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winCorrect the doc comment.
Lines 18-19 state that the boundary "renders children as-is".
renderat Lines 39-41 returnsthis.props.fallback ?? nullafter an error, so the children are unmounted. Align the comment with the behavior.♻️ Proposed change
- * On error the boundary logs a warning and renders children as-is (i.e. the - * new feature is silently disabled rather than crashing the whole view). + * On error the boundary logs the error, unmounts the failing subtree, and + * renders the provided fallback (or nothing) instead of crashing the view.🤖 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 `@webview-ui/src/components/history/TaskOrganizationErrorBoundary.tsx` around lines 13 - 20, Update the documentation comment for TaskOrganizationErrorBoundary to state that it renders the configured fallback, or null when no fallback is provided, after an error; remove the claim that it renders children as-is or leaves the existing view mounted.webview-ui/src/components/history/HistoryView.tsx-231-252 (1)
231-252: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winThe folder targets in
handleConfirmSelectionFolderNameare unreachable.
canCreateFolderFromSelectionat Line 231 requiresselectedFolderIds.length === 0.handleCreateFolderFromSelectionreturns early when that flag is false, so the dialog only opens with zero selected folders. TheselectedFolderIds.map(...)spread at Line 242 therefore always produces an empty list, and the comment at Line 228 ("tasks/groups and/or folders combined") does not match the gate.Decide the intended behavior. If folders must never join a new folder, remove the dead spread and correct the comment. If folders may join, relax the gate.
♻️ Proposed change if folders must be excluded
- // Create Folder is enabled when at least two distinct canonical units are - // selected (tasks/groups and/or folders combined). + // Create Folder is enabled when at least two distinct canonical task units + // are selected. Folder selection disables it. // Architect spec Section 1.6: create-folder requires at least two canonical // task units and is disabled while any folder is selected. const canCreateFolderFromSelection = selectedTaskTargets.length >= 2 && selectedFolderIds.length === 0const handleConfirmSelectionFolderName = useCallback( (name: string) => { - const targets: TaskOrganizationTargetV1[] = [ - ...selectedTaskTargets, - ...selectedFolderIds.map((folderId) => ({ kind: "folder", folderId }) as TaskOrganizationTargetV1), - ] - void createFolderFromSelection(name, targets).then((result) => { + void createFolderFromSelection(name, selectedTaskTargets).then((result) => { if (result.success) { setSelectedTaskIds([]) setSelectedFolderIds([]) } }) }, - [selectedTaskTargets, selectedFolderIds, createFolderFromSelection], + [selectedTaskTargets, createFolderFromSelection], )🤖 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 `@webview-ui/src/components/history/HistoryView.tsx` around lines 231 - 252, Resolve the inconsistency between canCreateFolderFromSelection and handleConfirmSelectionFolderName: if selected folders are not allowed, remove the selectedFolderIds target mapping and update the nearby comment to describe task/group-only selection; otherwise, relax the canCreateFolderFromSelection guard so folder selections can reach the dialog and retain their targets.src/core/task-persistence/TaskOrganizationStore.ts-842-875 (1)
842-875: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winThe watcher never recovers if the tasks directory is missing.
fsSync.watchthrowsENOENTwhentasksDirdoes not exist. On a fresh profile the store loads an empty state and writes nothing until the first mutation, so the directory can be absent atinitialize()time. Thecatchat Line 869 logs the failure, and no later attempt starts the watcher. Cross-instance reloads then stay disabled for the whole session.Create the directory before watching, or retry after the first successful save.
🤖 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 `@src/core/task-persistence/TaskOrganizationStore.ts` around lines 842 - 875, The watcher setup around getTasksDir and fsSync.watch must handle a missing tasks directory instead of permanently stopping after ENOENT. Ensure the directory is created before calling fsSync.watch, or trigger a retry after the first successful save, while preserving the existing disposed checks and watcher behavior.webview-ui/src/components/history/__tests__/DeleteFoldersDialog.spec.tsx-4-41 (1)
4-41: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAssert the localized count and cancel close behavior.
The translation mock returns raw keys. The rendering test does not verify the folder count. The cancel test does not verify
onOpenChange(false).Return representative localized strings from
t. Assert the interpolated count. Pass a spy toonOpenChangein the cancel test and assert that it receivesfalse.🤖 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 `@webview-ui/src/components/history/__tests__/DeleteFoldersDialog.spec.tsx` around lines 4 - 41, Update the useAppTranslation mock in DeleteFoldersDialog tests to return representative localized strings with count interpolation, then assert the rendered confirmation text includes the folder count. In the cancel test, pass a spy as onOpenChange and verify it is called with false while preserving the existing onConfirm assertion.webview-ui/src/i18n/locales/pl/history.json-51-64 (1)
51-64: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTranslate the new task-organization strings in each locale.
These values render in English for non-English users.
webview-ui/src/i18n/locales/pl/history.json#L51-L64: Replace the English values with Polish translations.webview-ui/src/i18n/locales/pt-BR/history.json#L51-L64: Replace the English values with Brazilian Portuguese translations.webview-ui/src/i18n/locales/ru/history.json#L51-L64: Replace the English values with Russian translations.webview-ui/src/i18n/locales/tr/history.json#L51-L64: Replace the English values with Turkish translations.🤖 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 `@webview-ui/src/i18n/locales/pl/history.json` around lines 51 - 64, Translate the new task-organization strings, preserving the existing keys and interpolation syntax, in webview-ui/src/i18n/locales/pl/history.json lines 51-64 (Polish), webview-ui/src/i18n/locales/pt-BR/history.json lines 51-64 (Brazilian Portuguese), webview-ui/src/i18n/locales/ru/history.json lines 51-64 (Russian), and webview-ui/src/i18n/locales/tr/history.json lines 51-64 (Turkish); replace each English value with its appropriate locale translation.webview-ui/src/i18n/locales/it/history.json-51-64 (1)
51-64: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTranslate the English history strings.
These locale bundles display English folder controls to Italian, Japanese, and Dutch users.
webview-ui/src/i18n/locales/it/history.json#L51-L64: Replace the English values with Italian translations.webview-ui/src/i18n/locales/ja/history.json#L51-L64: Replace the English values with Japanese translations.webview-ui/src/i18n/locales/nl/history.json#L51-L64: Replace the English values with Dutch translations.🤖 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 `@webview-ui/src/i18n/locales/it/history.json` around lines 51 - 64, The locale bundle files contain English strings instead of translations for Italian, Japanese, and Dutch users. Update webview-ui/src/i18n/locales/it/history.json lines 51-64 to replace all English string values (newFolder, folderNamePlaceholder, renameFolder, removeFromFolder, deleteEmptyFolder, pin, unpin, pinLimitReached, pinned, folder, tasks, unfiled, dragToOrganize, dropHereToRemove) with Italian translations. Apply the same transformation at webview-ui/src/i18n/locales/ja/history.json lines 51-64 with Japanese translations. Apply the same transformation at webview-ui/src/i18n/locales/nl/history.json lines 51-64 with Dutch translations. Keep all JSON keys unchanged; only update the string values to their target-language equivalents.webview-ui/src/i18n/locales/es/history.json-58-71 (1)
58-71: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTranslate the new history labels.
These locale bundles still show English for new folder, pin, and drag-and-drop labels. Translate all added English values so the new workflow remains localized.
webview-ui/src/i18n/locales/es/history.json#L58-L71: Translate the English history labels to Spanish.webview-ui/src/i18n/locales/fr/history.json#L58-L71: Translate the English history labels to French.webview-ui/src/i18n/locales/hi/history.json#L51-L64: Translate the English history labels to Hindi.webview-ui/src/i18n/locales/id/history.json#L60-L73: Translate the English history labels to Indonesian.🤖 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 `@webview-ui/src/i18n/locales/es/history.json` around lines 58 - 71, Translate every English value for the new history labels in webview-ui/src/i18n/locales/es/history.json lines 58-71, webview-ui/src/i18n/locales/fr/history.json lines 58-71, webview-ui/src/i18n/locales/hi/history.json lines 51-64, and webview-ui/src/i18n/locales/id/history.json lines 60-73 into the respective locale languages, preserving all translation keys and interpolation placeholders.webview-ui/src/i18n/locales/vi/history.json-51-64 (1)
51-64: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winThe same English folder and pin strings were added to three non-English locale files. Keys
newFolderthroughdropHereToRemovehold English values in all three files, while the keys that follow in the same block are translated. The shared root cause is one untranslated block copied into each locale. Two of these keys,dropHereToRemoveanddragToOrganize, also duplicate the translateddropToRemoveFromFolderanddragTask; remove whichever key of each pair the components do not use.
webview-ui/src/i18n/locales/vi/history.json#L51-L64: translate the 14 English values to Vietnamese.webview-ui/src/i18n/locales/zh-CN/history.json#L51-L64: translate the 14 English values to Simplified Chinese.webview-ui/src/i18n/locales/zh-TW/history.json#L51-L64: translate the 14 English values to Traditional Chinese.🤖 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 `@webview-ui/src/i18n/locales/vi/history.json` around lines 51 - 64, Translate the 14 English values from newFolder through dropHereToRemove in webview-ui/src/i18n/locales/vi/history.json#L51-L64 into Vietnamese, in webview-ui/src/i18n/locales/zh-CN/history.json#L51-L64 into Simplified Chinese, and in webview-ui/src/i18n/locales/zh-TW/history.json#L51-L64 into Traditional Chinese. In each file, remove the unused duplicate between dragToOrganize and the translated dragTask key, and between dropHereToRemove and the translated dropToRemoveFromFolder key, preserving whichever key the components use.webview-ui/src/components/history/__tests__/TaskOrganizationDndSurface.spec.tsx-196-212 (1)
196-212: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winConfigure
mockUseExtensionStatebefore the first render in this test.
renderruns at line 199, butmockUseExtensionState.mockReturnValue(...)runs at line 206.beforeEachonly callsvi.clearAllMocks(), which clears recorded calls and keeps implementations. The first render therefore uses whatever return value an earlier test installed. If this test runs alone, with.only, or after a reorder,useExtensionState()returnsundefinedand the surface throws while destructuringtaskOrganization.Move the mock setup above
render.💚 Suggested change
const capture = installDndCapture() const mutateSpy = vi.fn().mockResolvedValue(createSuccessResult()) + mockUseExtensionState.mockReturnValue({ + taskOrganization: createEmptyOrganizationState(), + mutateTaskOrganization: mutateSpy, + }) const { rerender } = render( <TaskOrganizationInteractionProvider> <TaskOrganizationDndSurface enabled resolveDragLabel={() => "label"}> <div /> </TaskOrganizationDndSurface> </TaskOrganizationInteractionProvider>, ) - mockUseExtensionState.mockReturnValue({ - taskOrganization: createEmptyOrganizationState(), - mutateTaskOrganization: mutateSpy, - })🤖 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 `@webview-ui/src/components/history/__tests__/TaskOrganizationDndSurface.spec.tsx` around lines 196 - 212, Move the mockUseExtensionState.mockReturnValue setup above the initial render in the “cancels a pending draft when disabled” test, ensuring TaskOrganizationDndSurface receives taskOrganization and mutateTaskOrganization during rendering. Keep the existing mock values and test flow unchanged.webview-ui/src/components/history/__tests__/TaskOrganizationErrorBoundary.spec.tsx-110-115 (1)
110-115: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winThis assertion cannot fail.
document.body.textContentreturns text nodes only. It never contains thedata-testidattribute value"safe-child", so line 114 always passes and proves nothing about the throwing subtree. Assert on the rendered element instead. Note that this test then overlaps the test at lines 40-50, so consider merging the two.💚 Suggested change
expect(screen.getByText("Fallback content")).toBeInTheDocument() // The throwing child should not be in the DOM - expect(document.body.textContent).not.toContain("safe-child") + expect(screen.queryByTestId("safe-child")).not.toBeInTheDocument()🤖 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 `@webview-ui/src/components/history/__tests__/TaskOrganizationErrorBoundary.spec.tsx` around lines 110 - 115, Replace the ineffective document.body.textContent check in the TaskOrganizationErrorBoundary test with an assertion that queries the rendered element identified by the throwing child’s data-testid and verifies it is absent. Since this duplicates the existing coverage near the earlier fallback test, merge the assertions or remove the redundant test while preserving verification that the fallback renders and the throwing subtree does not.webview-ui/src/i18n/locales/vi/chat.json-20-20 (1)
20-20: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winRestore the missing UI translation for child tasks.
TaskHeader.tsxstill renders{t("chat:task.waitingOnSubtask")}, but only theenlocale containstask.waitingOnSubtask; add it back for every locale or update the call to use the newchat:subtasks.goToSubtaskkey.🤖 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 `@webview-ui/src/i18n/locales/vi/chat.json` at line 20, Update the translation lookup in TaskHeader.tsx to use the existing chat:subtasks.goToSubtask key instead of chat:task.waitingOnSubtask. The entries at webview-ui/src/i18n/locales/vi/chat.json:20-20, webview-ui/src/i18n/locales/zh-CN/chat.json:20-20, and webview-ui/src/i18n/locales/zh-TW/chat.json:20-20 require no direct changes because they are corrected by reusing the existing key.webview-ui/src/components/history/__tests__/HistoryView.taskOrganization.spec.tsx-578-619 (1)
578-619: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winThe test does not verify canonical-root resolution.
The test name states that a child drop resolves to its canonical root. The body only asserts that
draggable-entry-unfiled-unit-parent-1is present. It installs the DnD harness but never triggers a drop, and it never inspects the drag data for anautoGrouptarget. As written, the test passes even if canonical resolution is broken.Drive a drop through the harness and assert the resolved source target.
💚 Suggested assertion using the installed harness
render(<HistoryView onDone={vi.fn()} />) - // The parent group draggable must carry the autoGroup target. - const parentEntry = screen.getByTestId("draggable-entry-unfiled-unit-parent-1") - expect(parentEntry).toBeInTheDocument() + expect(screen.getByTestId("draggable-entry-unfiled-unit-parent-1")).toBeInTheDocument() + + // A drag that starts from the group must carry the canonical autoGroup target. + getHarness().triggerDrop( + { kind: "task", target: { kind: "autoGroup", rootTaskId: "parent-1" } }, + { id: "drop-unfiled-unit-solo-1", data: { kind: "task", target: { kind: "task", taskId: "solo-1" } } }, + ) + expect(spies.onRequestCreateFolder).toHaveBeenCalledWith( + { kind: "autoGroup", rootTaskId: "parent-1" }, + { kind: "task", taskId: "solo-1" }, + )🤖 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 `@webview-ui/src/components/history/__tests__/HistoryView.taskOrganization.spec.tsx` around lines 578 - 619, Update the test “resolves an automatic-group child drop to its canonical root” to trigger a child drop through the installed DnD harness after rendering. Inspect the resulting drag data or move callback and assert that the autoGroup source target resolves to the canonical parent root (“parent-1”), rather than only asserting the parent entry is present.webview-ui/src/i18n/locales/ca/history.json-58-71 (1)
58-71: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTranslate the visible history labels for Catalan and German.
webview-ui/src/i18n/locales/ca/history.json#L58-L71: replace the English fallback values with Catalan translations.webview-ui/src/i18n/locales/de/history.json#L58-L71: replace the English fallback values with German translations.Users who select either locale receive a mixed-language history UI.
🤖 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 `@webview-ui/src/i18n/locales/ca/history.json` around lines 58 - 71, Replace the English fallback values for the history labels from newFolder through dropHereToRemove in webview-ui/src/i18n/locales/ca/history.json lines 58-71 with Catalan translations, and apply the corresponding German translations to webview-ui/src/i18n/locales/de/history.json lines 58-71. Preserve all translation keys and interpolation syntax, including {{count}} in tasks.webview-ui/src/i18n/__tests__/translation-parity.spec.ts-10-42 (1)
10-42: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAdd every new history key to
REQUIRED_HISTORY_KEYS.The list omits
dragTask,dragFolder,createFolder,createFolderDescription,folderNameLabel,folderNameRequired,folderNameTooLong,folderNameInvalidChars,deleteFolder,folderOptions,expandFolder,collapseFolder,create,openTask, andopenFolder. A locale can omit any of these new UI keys and still pass both parity tests. Add them to the required list.🤖 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 `@webview-ui/src/i18n/__tests__/translation-parity.spec.ts` around lines 10 - 42, Extend REQUIRED_HISTORY_KEYS with all omitted history UI keys: dragTask, dragFolder, createFolder, createFolderDescription, folderNameLabel, folderNameRequired, folderNameTooLong, folderNameInvalidChars, deleteFolder, folderOptions, expandFolder, collapseFolder, create, openTask, and openFolder, so parity tests require every new key.webview-ui/src/components/history/__tests__/useTaskOrganizationDnd.spec.tsx-45-48 (1)
45-48: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winCentralize the partial DnD event fixtures.
The suite repeats undocumented
as unknown as Drag*Eventcasts through line 221. Move the partial fixtures into typeddragStart,dragOver, anddragEndhelpers. If a double assertion is still needed, document the fieldsuseTaskOrganizationDndreads at the helper.🤖 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 `@webview-ui/src/components/history/__tests__/useTaskOrganizationDnd.spec.tsx` around lines 45 - 48, Centralize the partial DnD event construction used by the useTaskOrganizationDnd tests by adding typed dragStart, dragOver, and dragEnd helpers, then replace the repeated inline as unknown as Drag*Event casts through the suite with those helpers. Document within each helper the event fields read by useTaskOrganizationDnd, retaining any required double assertion only there.Source: Coding guidelines
webview-ui/src/components/history/__tests__/TaskOrganizationInteractionContext.spec.tsx-37-37 (1)
37-37: 📐 Maintainability & Code Quality | 🟡 MinorReplace
window as anyresult storage with typed test state.Move the test result store into a test-scoped, typed variable and update the assignment, reset, and assertion references. This applies to both
__lastResult__and__lastMutationResult__usage.[low_effort_and_medium_reward]
🤖 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 `@webview-ui/src/components/history/__tests__/TaskOrganizationInteractionContext.spec.tsx` at line 37, Replace the window-cast result stores with test-scoped typed variables in both webview-ui/src/components/history/__tests__/TaskOrganizationInteractionContext.spec.tsx:37-37 and webview-ui/src/context/__tests__/ExtensionStateContext.taskOrganization.spec.tsx:34-34. Update all __lastResult__ and __lastMutationResult__ assignments, resets, and assertions to use the typed variables instead of window state.Source: Coding guidelines
webview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.ts-10-13 (1)
10-13: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAssert delegation with a complete pointerdown fixture.
makePointerEventonly suppliestarget, so the delegatedPointerSensoractivator can fail due to missingisPrimary,button, orownerDocumentfields. Add a primary-left-button pointerdown fixture and assert the delegated result istrue; keep the unavoidable event-shape cast in a documented helper.🤖 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 `@webview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.ts` around lines 10 - 13, Update makePointerEvent in the TaskOrganizationPointerSensor tests to provide a complete primary left-button pointerdown fixture, including isPrimary, button, and ownerDocument on the native event target. Add an assertion that the delegated PointerSensor activator returns true, and retain the unavoidable event-shape cast only within this documented helper.Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 6c7df8fe-79dd-4506-a430-34e028c18dee
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (103)
docs/260730_0002_session_dashboard-crash-debug/232600_ask-full-audit-report.mddocs/260730_0002_session_dashboard-crash-debug/233600_code-multi-branch-report.mddocs/260730_0002_session_dashboard-crash-debug/context-save-260730-2333.mddocs/260730_0002_session_dashboard-crash-debug/test-error-interception.txtdocs/260730_0002_session_dashboard-crash-debug/test-mimo-parallel.txtdocs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txtdocs/260730_0002_session_dashboard-crash-debug/test-task-dnd-ux.txtdocs/260730_0002_session_dashboard-crash-debug/test-unified-shell-resolution.txtdocs/260803_0002_session_6-branch-bug-fix-verification/170635_code-environment-feedback.mddocs/260803_0002_session_6-branch-bug-fix-verification/170715_code-vitest-environment-feedback.mddocs/260803_0002_session_6-branch-bug-fix-verification/170924_code-report.mddocs/260803_0002_session_6-branch-bug-fix-verification/171100_code-report.mdknip.jsonpackages/types/src/index.tspackages/types/src/task-organization.tspackages/types/src/vscode-extension-host.tssrc/core/task-persistence/TaskOrganizationStore.tssrc/core/task-persistence/__tests__/TaskOrganizationStore.spec.tssrc/core/task-persistence/index.tssrc/core/webview/ClineProvider.tssrc/core/webview/__tests__/ClineProvider.taskHistory.spec.tssrc/core/webview/__tests__/taskOrganizationMessageHandler.spec.tssrc/core/webview/taskOrganizationMessageHandler.tssrc/core/webview/webviewMessageHandler.tssrc/eslint-suppressions.jsonsrc/shared/globalFileNames.tssrc/utils/safeWriteJson.tswebview-ui/package.jsonwebview-ui/src/components/history/DeleteFoldersDialog.tsxwebview-ui/src/components/history/DraggableTaskEntry.tsxwebview-ui/src/components/history/FolderNameDialog.tsxwebview-ui/src/components/history/HistoryPreview.tsxwebview-ui/src/components/history/HistoryView.tsxwebview-ui/src/components/history/ManualFolderItem.tsxwebview-ui/src/components/history/PinButton.tsxwebview-ui/src/components/history/PinnedHistoryItem.tsxwebview-ui/src/components/history/SubtaskRow.tsxwebview-ui/src/components/history/TaskGroupItem.tsxwebview-ui/src/components/history/TaskItem.tsxwebview-ui/src/components/history/TaskItemFooter.tsxwebview-ui/src/components/history/TaskOrganizationDndSurface.tsxwebview-ui/src/components/history/TaskOrganizationErrorBoundary.tsxwebview-ui/src/components/history/TaskOrganizationInteractionContext.tsxwebview-ui/src/components/history/TaskOrganizationPointerSensor.tswebview-ui/src/components/history/__tests__/DeleteFoldersDialog.spec.tsxwebview-ui/src/components/history/__tests__/DraggableTaskEntry.spec.tsxwebview-ui/src/components/history/__tests__/HistoryPreview.spec.tsxwebview-ui/src/components/history/__tests__/HistoryPreview.taskOrganization.spec.tsxwebview-ui/src/components/history/__tests__/HistoryView.taskOrganization.spec.tsxwebview-ui/src/components/history/__tests__/ManualFolderItem.spec.tsxwebview-ui/src/components/history/__tests__/PinButton.spec.tsxwebview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationDndSurface.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationErrorBoundary.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationInteractionContext.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.setup.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.spec.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.vitest.config.tswebview-ui/src/components/history/__tests__/useTaskOrganizationDnd.spec.tsxwebview-ui/src/components/history/taskOrganizationModel.tswebview-ui/src/components/history/types.tswebview-ui/src/components/history/useTaskOrganizationDnd.tswebview-ui/src/context/ExtensionStateContext.tsxwebview-ui/src/context/__tests__/ExtensionStateContext.taskOrganization.spec.tsxwebview-ui/src/i18n/__tests__/translation-parity.spec.tswebview-ui/src/i18n/locales/ca/chat.jsonwebview-ui/src/i18n/locales/ca/history.jsonwebview-ui/src/i18n/locales/de/chat.jsonwebview-ui/src/i18n/locales/de/history.jsonwebview-ui/src/i18n/locales/en/chat.jsonwebview-ui/src/i18n/locales/en/history.jsonwebview-ui/src/i18n/locales/es/chat.jsonwebview-ui/src/i18n/locales/es/history.jsonwebview-ui/src/i18n/locales/fr/chat.jsonwebview-ui/src/i18n/locales/fr/history.jsonwebview-ui/src/i18n/locales/hi/chat.jsonwebview-ui/src/i18n/locales/hi/history.jsonwebview-ui/src/i18n/locales/id/chat.jsonwebview-ui/src/i18n/locales/id/history.jsonwebview-ui/src/i18n/locales/it/chat.jsonwebview-ui/src/i18n/locales/it/history.jsonwebview-ui/src/i18n/locales/ja/chat.jsonwebview-ui/src/i18n/locales/ja/history.jsonwebview-ui/src/i18n/locales/ko/chat.jsonwebview-ui/src/i18n/locales/ko/history.jsonwebview-ui/src/i18n/locales/nl/chat.jsonwebview-ui/src/i18n/locales/nl/history.jsonwebview-ui/src/i18n/locales/pl/chat.jsonwebview-ui/src/i18n/locales/pl/history.jsonwebview-ui/src/i18n/locales/pt-BR/chat.jsonwebview-ui/src/i18n/locales/pt-BR/history.jsonwebview-ui/src/i18n/locales/ru/chat.jsonwebview-ui/src/i18n/locales/ru/history.jsonwebview-ui/src/i18n/locales/tr/chat.jsonwebview-ui/src/i18n/locales/tr/history.jsonwebview-ui/src/i18n/locales/vi/chat.jsonwebview-ui/src/i18n/locales/vi/history.jsonwebview-ui/src/i18n/locales/zh-CN/chat.jsonwebview-ui/src/i18n/locales/zh-CN/history.jsonwebview-ui/src/i18n/locales/zh-TW/chat.jsonwebview-ui/src/i18n/locales/zh-TW/history.jsonwebview-ui/vitest.setup.ts
💤 Files with no reviewable changes (1)
- webview-ui/src/components/history/tests/TaskItemFooter.spec.tsx
| const requestId = | ||
| "requestId" in mutation && typeof (mutation as Record<string, unknown>).requestId === "string" | ||
| ? ((mutation as Record<string, unknown>).requestId as string) | ||
| : "" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Trace requestId through the task organization mutation path.
set -euo pipefail
fd -t f 'taskOrganizationMessageHandler' -x rg -n -C 6 'requestId|mutate\(|committedRevision' {}
# Webview-side correlation of mutation results.
rg -n -C 4 'requestId' --glob 'webview-ui/src/**/*.{ts,tsx}' | rg -n -C 4 'taskOrganization|Mutation' || trueRepository: Zoo-Code-Org/Zoo-Code
Length of output: 8527
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate relevant files =="
fd -t f 'TaskOrganizationStore|task-organization' .
echo "== TaskOrganizationStore relevant section =="
store=$(fd -t f 'TaskOrganizationStore' . | head -n1)
if [ -n "${store:-}" ]; then
wc -l "$store"
sed -n '130,210p' "$store" | cat -n
fi
echo "== task-organization schemas relevant section =="
types=$(fd -t f 'task-organization' . | head -n1)
if [ -n "${types:-}" ]; then
wc -l "$types"
sed -n '1,180p' "$types" | cat -n
fi
echo "== mutated result usages in store =="
rg -n -C 4 'taskOrganizationMutationResult|requestId|mutate\(' --glob '**/*.{ts,tsx}' .Repository: Zoo-Code-Org/Zoo-Code
Length of output: 50378
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import ast, json, pathlib
paths = list(pathlib.Path('.').rglob('*'))
store_path = next(p for p in paths if p.name == 'TaskOrganizationStore.ts')
types_path = next(p for p in paths if p.name == 'task-organization.ts')
store_src = store_path.read_text()
types_src = types_path.read_text()
checks = {
"store_mentions_requestId_property_on_mutation_literal": 'requestId' in store_src
and '.requestId' in store_src
and 'requestId' in store_src,
"store_has_requestId_local_derivation": 'const requestId =' in store_src and 'requestId' in store_src,
"mutation_request_schema_has_requestId": 'requestId' in types_src and 'taskOrganizationMutationRequestV1' in types_src,
}
for k, v in checks.items():
print(f"{k}={v}")
# Behavioral probe: evaluate the store's expression for each mutation variant extracted from source.
import re
mut_names = re.findall(r'kind:\s*"([^"]+)"', types_src)
print("mutation_kinds=" + json.dumps(mut_names))
for kind in mut_names:
expr = f'({"requestId" in {"kind": kind} and isinstance({"requestId" in {"kind": kind}.get("requestId", None)}, str) and str({"requestId" in {"kind": kind}.get("requestId", None)}, str)} else "")'
print("requestId_property_found_on_extracted_mutation_objects=" + json.dumps(not any('requestId' in kind for kind in mut_names)))
PYRepository: Zoo-Code-Org/Zoo-Code
Length of output: 382
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import pathlib, re
store_path = next(p for p in pathlib.Path('.').rglob('*') if p.name == 'TaskOrganizationStore.ts')
types_path = next(p for p in pathlib.Path('.').rglob('*') if p.name == 'task-organization.ts')
store_src = store_path.read_text()
types_src = types_path.read_text()
print("== store requestId extraction source ==")
for i, line in enumerate(store_src.splitlines(), 1):
if 'requestId' in line or 'const requestId' in line:
print(f"{store_path}:{i}: {line.strip()}")
print("\n== mutation kinds in task-organization.ts ==")
for i, line in enumerate(types_src.splitlines(), 1):
m = re.search(r'kind:\s*"([^"]+)"', line)
if m:
print(f"{types_path}:{i}: kind={m.group(1)} line={line.strip()}")
print("\n== requestId in task-organization.ts nearby definitions ==")
for i, line in enumerate(types_src.splitlines(), 1):
if 'requestId' in line or 'taskOrganizationMutationRequestV1' in line or 'TaskOrganizationMutationResultV1' in line:
print(f"{types_path}:{i}: {line.strip()}")
print("\n== taskOrganizationMutation message/result correlation in TypeScript source ==")
for p in pathlib.Path('.').rglob('*'):
if not (p.name.endswith(('.ts', '.tsx')) or p.is_file()):
continue
if p.name in {'TaskOrganizationStore.ts', 'task-organization.ts'}:
continue
src = p.read_text(errors='ignore')
if 'taskOrganizationMutationResult' in src or 'taskOrganizationMutation:' in src:
hits = []
for i, line in enumerate(src.splitlines(), 1):
if 'taskOrganizationMutationResult' in line or 'type: "taskOrganizationMutationResult"' in line or 'requestId' in line:
hits.append((i, line.strip()))
if hits:
print(f"\n--- {p} ---")
for idx, (line_no, line) in enumerate(hits):
start = max(1, line_no - 3)
end = min(len(src.splitlines()), line_no + 3)
for n in range(start, end + 1):
print(f"{n}: {src.splitlines()[n-1].strip()}")
if idx < len(hits) - 1:
print()
PYRepository: Zoo-Code-Org/Zoo-Code
Length of output: 21048
Keep requestId off the mutation and forward the request wrapper’s requestId.
TaskOrganizationMutationV1 does not carry requestId, so store.mutate(...) returns requestId: "". The message handler then posts requestId: "" in taskOrganizationMutationResult, but the webview indexes pending mutations by the request wrapper requestId, so the store path can break mutation result correlation. Pass the request requestId into the store and return it on every result.
🤖 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 `@src/core/task-persistence/TaskOrganizationStore.ts` around lines 177 - 180,
Update the TaskOrganizationStore mutation flow to stop reading requestId from
the TaskOrganizationMutationV1 payload. Accept the request wrapper’s requestId
at the store entry point, propagate it through store.mutate(...) and every
taskOrganizationMutationResult response, and keep requestId excluded from the
mutation object.
| const createMockProvider = (mutateResult: TaskOrganizationMutationResultV1): ClineProvider => { | ||
| const mockLog = vi.fn() | ||
| const mockPostMessageToWebview = vi.fn() | ||
| const mockMutate = vi.fn().mockResolvedValue(mutateResult) | ||
| const mockState = createEmptyTaskOrganizationState() | ||
|
|
||
| const store = { | ||
| mutate: mockMutate, | ||
| getState: vi.fn(() => mockState), | ||
| } | ||
|
|
||
| return { | ||
| log: mockLog, | ||
| postMessageToWebview: mockPostMessageToWebview, | ||
| getTaskOrganizationStore: vi.fn(() => store), | ||
| } as unknown as ClineProvider |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Replace the broad casts and lint suppression with precise test doubles.
The mock factory casts a partial object to the complete ClineProvider class. The malformed-request test also adds an as any cast and suppresses the resulting lint violation.
Define a narrow provider interface for handleTaskOrganizationMessage. Type both provider mocks against that interface. Test malformed input with an undefined payload or a documented unknown boundary fixture.
As per coding guidelines, new TypeScript code must fix lint violations instead of suppressing them, avoid as any, and use precise test doubles.
Also applies to: 86-97, 237-245
🤖 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 `@src/core/webview/__tests__/taskOrganizationMessageHandler.spec.ts` around
lines 11 - 26, Replace the broad ClineProvider cast in createMockProvider and
the other provider mock with a narrow interface containing only the members
required by handleTaskOrganizationMessage. Type both test doubles against that
interface, remove the malformed-request as any cast and lint suppression, and
pass an undefined payload or documented unknown boundary fixture instead.
Source: Coding guidelines
| <Button | ||
| variant="ghost" | ||
| className="flex-1 min-w-0 justify-start h-auto px-0 py-0 font-normal text-left truncate" | ||
| onClick={onClick} | ||
| aria-label={isFolder ? t("history:openFolder", { name: folderName }) : t("history:openTask")}> | ||
| <span className="truncate" data-testid="pinned-item-label"> | ||
| {isFolder ? folderName : (label ?? unit.rootTaskId)} | ||
| </span> | ||
| </Button> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
The pinned shortcut button does nothing for the current callers.
onClick is optional. HistoryView.tsx (Lines 334-357) and HistoryPreview.tsx (Lines 173-198) render PinnedHistoryItem without onClick. The button stays focusable and announces history:openTask or history:openFolder, but a click and an Enter key press have no effect. A pinned shortcut that cannot be opened defeats the purpose of the pinned section.
Either make onClick required, or add a default action. For pinned units, post showTaskWithId with the unit root id; for pinned folders, expand the folder.
🤖 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 `@webview-ui/src/components/history/PinnedHistoryItem.tsx` around lines 64 -
72, Update PinnedHistoryItem so its focusable shortcut always performs an
action: either require callers to provide onClick and update HistoryView and
HistoryPreview, or implement the default behavior of posting showTaskWithId for
pinned units and expanding pinned folders. Preserve the existing folder/task
labels and ensure both click and keyboard activation use the working action.
| const handleRequestMoveToFolder = useCallback( | ||
| (source: TaskOrganizationTargetV1, folderId: string) => { | ||
| if (!enabled) return | ||
| void moveToFolder(source, folderId) | ||
| }, | ||
| [enabled, moveToFolder], | ||
| ) | ||
|
|
||
| const handleRequestRemoveFromFolder = useCallback( | ||
| (source: TaskOrganizationTargetV1, folderId: string) => { | ||
| if (!enabled) return | ||
| void removeFromFolder(source, folderId) | ||
| }, | ||
| [enabled, removeFromFolder], | ||
| ) | ||
|
|
||
| const { sensors, activeDrag, handleDragStart, handleDragOver, handleDragEnd, handleDragCancel } = | ||
| useTaskOrganizationDnd({ | ||
| onRequestCreateFolder: handleRequestCreateFolder, | ||
| onRequestMoveToFolder: handleRequestMoveToFolder, | ||
| onRequestRemoveFromFolder: handleRequestRemoveFromFolder, | ||
| }) | ||
|
|
||
| const handleConfirmFolderName = useCallback( | ||
| (name: string) => { | ||
| if (!pendingFolderDraft) return | ||
| void createFolder(name, pendingFolderDraft.source, pendingFolderDraft.destination) | ||
| setPendingFolderDraft(null) | ||
| }, | ||
| [createFolder, pendingFolderDraft], | ||
| ) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Handle the mutation results instead of discarding them.
moveToFolder, removeFromFolder, and createFolder resolve with a TaskOrganizationMutationResultV1 and do not throw. TaskOrganizationStore.mutate returns success: false with codes such as TASK_ORG/CONFLICT/002 when the revision is stale. All three call sites use void and drop that result, so a rejected drag or a failed folder creation produces no message and no retry. The user sees the drop do nothing. Inspect success and surface the error, for example through the existing toast or error state.
🛡️ Sketch for surfacing failures
const handleRequestMoveToFolder = useCallback(
(source: TaskOrganizationTargetV1, folderId: string) => {
if (!enabled) return
- void moveToFolder(source, folderId)
+ moveToFolder(source, folderId)
+ .then((result) => {
+ if (!result.success) onMutationError?.(result.error)
+ })
+ .catch(() => onMutationError?.())
},
- [enabled, moveToFolder],
+ [enabled, moveToFolder, onMutationError],
)🤖 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 `@webview-ui/src/components/history/TaskOrganizationDndSurface.tsx` around
lines 84 - 114, Update handleRequestMoveToFolder, handleRequestRemoveFromFolder,
and handleConfirmFolderName to await their mutation results instead of
discarding them with void. Inspect each TaskOrganizationMutationResultV1 success
value and surface failed mutations through the component’s existing toast or
error-state mechanism, while preserving the current enabled and pending-folder
guards.
| const mutateTaskOrganization = useCallback( | ||
| async (mutation: TaskOrganizationMutationRequestV1["mutation"]): Promise<TaskOrganizationMutationResultV1> => { | ||
| const requestId = `task-org-${Date.now()}-${Math.random().toString(36).slice(2)}` | ||
| const currentRevision = taskOrgRevisionRef.current | ||
|
|
||
| vscode.postMessage({ | ||
| type: "taskOrganizationMutation", | ||
| taskOrganizationMutation: { | ||
| requestId, | ||
| baseRevision: currentRevision, | ||
| mutation, | ||
| }, | ||
| } as WebviewMessage) | ||
|
|
||
| return new Promise((resolve) => { | ||
| pendingTaskOrgMutations.current.set(requestId, resolve) | ||
| }) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Add timeout and unmount cleanup for pending mutations.
A pending entry is removed only when a matching result arrives. If the host drops the response or the provider is disposed, the promise never settles and the resolver remains in the map.
Add a timeout for each request. Remove the entry when the timeout expires. Also settle and clear all pending requests when the context provider unmounts.
🤖 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 `@webview-ui/src/context/ExtensionStateContext.tsx` around lines 541 - 557,
Update mutateTaskOrganization to associate each pending request with a timeout
that removes its requestId from pendingTaskOrgMutations and settles the promise
when no response arrives. Add provider-unmount cleanup that clears all remaining
entries and settles their promises, while cancelling any request timers to avoid
callbacks after cleanup.
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 16
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (29)
docs/260730_0002_session_dashboard-crash-debug/233600_code-multi-branch-report.md-58-58 (1)
58-58: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winCorrect the wording in the failure analysis.
Use
terminal, shell, and command-execution testsat Line 58. Use1 msinstead of1msat Lines 66 and 228.Also applies to: 66-66, 228-228
🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/233600_code-multi-branch-report.md` at line 58, Update the failure-analysis wording to say “terminal, shell, and command-execution tests” instead of “terminal/shell/command execution related tests,” and format both occurrences of the duration as “1 ms” rather than “1ms” at the referenced lines.Source: Linters/SAST tools
docs/260730_0002_session_dashboard-crash-debug/232600_ask-full-audit-report.md-124-134 (1)
124-134: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUpdate the short-range breakdown finding to match the later fix.
docs/260730_0002_session_dashboard-crash-debug/context-save-260730-2333.mdLine 11 records commit0769ccea7, and Line 51 records the daily-rollup fix as completed. This report still presents the monthly-rollup problem as unresolved and retains it as a release condition. Mark the finding as resolved or clearly label this report as a pre-fix snapshot.Also applies to: 171-183
🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/232600_ask-full-audit-report.md` around lines 124 - 134, Update the short-range breakdown finding in the report, including the repeated section around the later referenced lines, to reflect that the daily-rollup fix is completed. Mark the monthly-rollup issue as resolved and remove it as an outstanding release condition, or clearly label the report as a pre-fix snapshot while preserving the recorded commit references.docs/260730_0002_session_dashboard-crash-debug/232600_ask-full-audit-report.md-136-144 (1)
136-144: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winQualify the claim that the crash is eliminated.
Lines 138-144 state that
cacheRatio > 0still uses the full event scan and can retain the crash vector. Lines 165-167 then state that the crash is eliminated. Replace the absolute claim with a statement limited to the default fast-path query.Also applies to: 163-167
🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/232600_ask-full-audit-report.md` around lines 136 - 144, The document states in lines 138-144 that cacheRatio > 0 scenarios still use full event scans and retain the crash vector, but then makes an absolute claim in lines 165-167 that the crash is eliminated. Update the crash-elimination claim in lines 165-167 to qualify it as only applying to the default fast-path query configuration (where cacheRatio is 0 or undefined), making clear that the limitation described in Inquiry 2 means the crash vector persists for users who enable cacheRatio.docs/260730_0002_session_dashboard-crash-debug/233600_code-multi-branch-report.md-80-80 (1)
80-80: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAdd language identifiers to the code fences.
Use
textfor the three branch-list fences at Lines 80, 125, and 160. This resolves the reported Markdownlint MD040 warnings.Also applies to: 125-125, 160-160
🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/233600_code-multi-branch-report.md` at line 80, The three branch-list code fences in this markdown file are missing language identifiers, which triggers Markdownlint MD040 warnings. Add the language identifier `text` to each of the three code fence opening markers for the branch-list sections. This ensures each code fence declaration includes a language specifier, resolving the linting violations.Source: Linters/SAST tools
docs/260730_0002_session_dashboard-crash-debug/test-mimo-parallel.txt-1-133 (1)
1-133: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winStore a normalized, reviewable test report.
As committed, this file contains NUL-padded terminal output and ANSI escape sequences. Common tools can treat it as binary, and the report is difficult to read or search. Re-export it as UTF-8 plain text with terminal control codes removed, or commit a concise report with the command, exit status, platform, and test counts.
🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/test-mimo-parallel.txt` around lines 1 - 133, Replace the raw terminal capture in the test report with UTF-8 plain text by removing NUL padding and ANSI escape sequences. Prefer a concise, reviewable report that preserves the test command, exit status, platform, and final test counts.docs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txt-1-2 (1)
1-2: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winNormalize the captured output before committing it.
The file contains NUL bytes and terminal ANSI escape sequences. Standard viewers and repository search display corrupted content. Re-capture or convert the output to UTF-8, strip terminal control codes, and retain only readable log content.
🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txt` around lines 1 - 2, Normalize the captured output in test-strict-reasoning.txt before committing it: convert the file to UTF-8, remove NUL bytes and terminal ANSI escape sequences, and retain only readable log content.docs/260803_0002_session_6-branch-bug-fix-verification/170924_code-report.md-3-7 (1)
3-7: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winFix the relative links to source files.
This report is under
docs/260803_0002_session_6-branch-bug-fix-verification/. Therefore,../src/...resolves todocs/src/..., not the repositorysrc/...directory. Change the affected links on Line 3, Line 6, Line 7, Line 15, Line 16, Line 26, and Line 27 to use../../src/....Also applies to: 15-16, 26-27
🤖 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 `@docs/260803_0002_session_6-branch-bug-fix-verification/170924_code-report.md` around lines 3 - 7, Update the affected relative source links in the report, including references to TaskOrganizationStore.ts, TaskOrganizationStore.spec.ts, withLock(), mutate(), resolveUnit(), and resolveTaskClosure(), from ../src/... to ../../src/... so they resolve from the document’s directory to the repository src directory.webview-ui/src/components/history/TaskOrganizationDndSurface.tsx-72-74 (1)
72-74: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDo not discard a typed folder name on every revision change.
This effect clears
pendingFolderDraftwheneverorganization.revisionchanges. The drag that opened the dialog is not the only source of revision changes: a concurrentmoveToFolder, a pin toggle, or a mutation from another view also bumps it. The folder-name dialog then closes and the typed name is lost with no message. Restrict the cancellation to the case where the draft source or destination no longer exists, or keep the dialog open and revalidate on confirm.🤖 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 `@webview-ui/src/components/history/TaskOrganizationDndSurface.tsx` around lines 72 - 74, Update the useEffect watching organization.revision so it does not unconditionally clear pendingFolderDraft on unrelated revisions. Only cancel the draft when its source or destination folder no longer exists, or otherwise keep the dialog open and revalidate those references during confirmation.webview-ui/src/components/history/ManualFolderItem.tsx-316-319 (1)
316-319: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDisable the member drop target during selection mode.
ManualFolderItemdisables its folder drop target whenisSelectionModeis true, butManualFolderMemberItemregisters a member drop target withoutdisabled. Pass the current mode throughHistoryPreviewInnerand setdisabled: isSelectionModeon the member droppable so drops do not create folders during selection mode.🤖 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 `@webview-ui/src/components/history/ManualFolderItem.tsx` around lines 316 - 319, Update ManualFolderMemberItem’s useDroppable configuration to accept the isSelectionMode value passed through HistoryPreviewInner and set disabled to that value, matching the existing folder drop-target behavior so member drops cannot create folders during selection mode.webview-ui/src/components/history/TaskOrganizationInteractionContext.tsx-60-69 (1)
60-69: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReconcile the two
targetKeyimplementations.
buildGroupDndData()emitsautoGrouptargets from pinned projection rows, but the UI callsisPinned()/togglePin()with equivalenttasktargets for task groups. This localtargetKeymaps those same units to different keys, so a task group can render as pinned and remain incanPinafter the task-unit pin is removed. Use one shared canonical helper for task andautoGrouppins.🤖 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 `@webview-ui/src/components/history/TaskOrganizationInteractionContext.tsx` around lines 60 - 69, Update the targetKey logic used by buildGroupDndData, isPinned, and togglePin so task and autoGroup targets for the same task unit resolve to the same canonical key; reuse the existing shared helper if available rather than maintaining a separate local mapping. Preserve distinct keys for folder targets and ensure pin removal updates canPin consistently.webview-ui/src/components/history/taskOrganizationModel.ts-635-647 (1)
635-647: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse a single child-relationship source for workspace filtering.
buildFlattenedVirtualEntriesusesparentTaskIdthroughchildrenMap, butbuildGroupedOrganizationProjectionusestask.childIdsinisVisibleInWorkspace.childIdsis optional onHistoryItemand is not reliably written alongsideparentTaskId, so a group can hide even when one of its descendants belongs to the current workload. Switch this path to the sameparentTaskId/children-map source.🤖 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 `@webview-ui/src/components/history/taskOrganizationModel.ts` around lines 635 - 647, Update isVisibleInWorkspace to collect descendant task IDs using the parentTaskId-derived childrenMap, matching buildFlattenedVirtualEntries, instead of relying on task.childIds. Preserve the existing root fallback and taskBelongsToWorkspace checks so descendants in the current workspace keep the group visible.webview-ui/src/components/history/SubtaskRow.tsx-113-116 (1)
113-116: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winUnwired pin props in two leaf components. Both files gained pin props and a
PinButtonbranch, but their parents never pass those props, so both branches are unreachable in the current tree. Decide one owner for the pin control per card and wire or remove accordingly.
webview-ui/src/components/history/SubtaskRow.tsx#L113-L116: passshowPin,isPinned,canPin, andonTogglePinfromTaskGroupItem.tsxLine 101, or delete the props and thePinButtonbranch at Lines 76-84.webview-ui/src/components/history/TaskItemFooter.tsx#L71-L79: pass the pin props fromTaskItem.tsxLines 136-142, or delete this branch and keep the header pin control inTaskItem.🤖 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 `@webview-ui/src/components/history/SubtaskRow.tsx` around lines 113 - 116, Choose one pin-control owner per card and ensure the other leaf branch is removed or wired. In webview-ui/src/components/history/SubtaskRow.tsx:113-116, update TaskGroupItem.tsx:101 to pass showPin, isPinned, canPin, and onTogglePin, or remove those props and the PinButton branch at SubtaskRow.tsx:76-84. In webview-ui/src/components/history/TaskItemFooter.tsx:71-79, either pass the pin props from TaskItem.tsx:136-142 or remove this branch while retaining TaskItem’s header pin control.webview-ui/src/components/history/TaskOrganizationErrorBoundary.tsx-13-20 (1)
13-20: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winCorrect the doc comment.
Lines 18-19 state that the boundary "renders children as-is".
renderat Lines 39-41 returnsthis.props.fallback ?? nullafter an error, so the children are unmounted. Align the comment with the behavior.♻️ Proposed change
- * On error the boundary logs a warning and renders children as-is (i.e. the - * new feature is silently disabled rather than crashing the whole view). + * On error the boundary logs the error, unmounts the failing subtree, and + * renders the provided fallback (or nothing) instead of crashing the view.🤖 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 `@webview-ui/src/components/history/TaskOrganizationErrorBoundary.tsx` around lines 13 - 20, Update the documentation comment for TaskOrganizationErrorBoundary to state that it renders the configured fallback, or null when no fallback is provided, after an error; remove the claim that it renders children as-is or leaves the existing view mounted.webview-ui/src/components/history/HistoryView.tsx-231-252 (1)
231-252: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winThe folder targets in
handleConfirmSelectionFolderNameare unreachable.
canCreateFolderFromSelectionat Line 231 requiresselectedFolderIds.length === 0.handleCreateFolderFromSelectionreturns early when that flag is false, so the dialog only opens with zero selected folders. TheselectedFolderIds.map(...)spread at Line 242 therefore always produces an empty list, and the comment at Line 228 ("tasks/groups and/or folders combined") does not match the gate.Decide the intended behavior. If folders must never join a new folder, remove the dead spread and correct the comment. If folders may join, relax the gate.
♻️ Proposed change if folders must be excluded
- // Create Folder is enabled when at least two distinct canonical units are - // selected (tasks/groups and/or folders combined). + // Create Folder is enabled when at least two distinct canonical task units + // are selected. Folder selection disables it. // Architect spec Section 1.6: create-folder requires at least two canonical // task units and is disabled while any folder is selected. const canCreateFolderFromSelection = selectedTaskTargets.length >= 2 && selectedFolderIds.length === 0const handleConfirmSelectionFolderName = useCallback( (name: string) => { - const targets: TaskOrganizationTargetV1[] = [ - ...selectedTaskTargets, - ...selectedFolderIds.map((folderId) => ({ kind: "folder", folderId }) as TaskOrganizationTargetV1), - ] - void createFolderFromSelection(name, targets).then((result) => { + void createFolderFromSelection(name, selectedTaskTargets).then((result) => { if (result.success) { setSelectedTaskIds([]) setSelectedFolderIds([]) } }) }, - [selectedTaskTargets, selectedFolderIds, createFolderFromSelection], + [selectedTaskTargets, createFolderFromSelection], )🤖 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 `@webview-ui/src/components/history/HistoryView.tsx` around lines 231 - 252, Resolve the inconsistency between canCreateFolderFromSelection and handleConfirmSelectionFolderName: if selected folders are not allowed, remove the selectedFolderIds target mapping and update the nearby comment to describe task/group-only selection; otherwise, relax the canCreateFolderFromSelection guard so folder selections can reach the dialog and retain their targets.src/core/task-persistence/TaskOrganizationStore.ts-842-875 (1)
842-875: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winThe watcher never recovers if the tasks directory is missing.
fsSync.watchthrowsENOENTwhentasksDirdoes not exist. On a fresh profile the store loads an empty state and writes nothing until the first mutation, so the directory can be absent atinitialize()time. Thecatchat Line 869 logs the failure, and no later attempt starts the watcher. Cross-instance reloads then stay disabled for the whole session.Create the directory before watching, or retry after the first successful save.
🤖 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 `@src/core/task-persistence/TaskOrganizationStore.ts` around lines 842 - 875, The watcher setup around getTasksDir and fsSync.watch must handle a missing tasks directory instead of permanently stopping after ENOENT. Ensure the directory is created before calling fsSync.watch, or trigger a retry after the first successful save, while preserving the existing disposed checks and watcher behavior.webview-ui/src/components/history/__tests__/DeleteFoldersDialog.spec.tsx-4-41 (1)
4-41: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAssert the localized count and cancel close behavior.
The translation mock returns raw keys. The rendering test does not verify the folder count. The cancel test does not verify
onOpenChange(false).Return representative localized strings from
t. Assert the interpolated count. Pass a spy toonOpenChangein the cancel test and assert that it receivesfalse.🤖 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 `@webview-ui/src/components/history/__tests__/DeleteFoldersDialog.spec.tsx` around lines 4 - 41, Update the useAppTranslation mock in DeleteFoldersDialog tests to return representative localized strings with count interpolation, then assert the rendered confirmation text includes the folder count. In the cancel test, pass a spy as onOpenChange and verify it is called with false while preserving the existing onConfirm assertion.webview-ui/src/i18n/locales/pl/history.json-51-64 (1)
51-64: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTranslate the new task-organization strings in each locale.
These values render in English for non-English users.
webview-ui/src/i18n/locales/pl/history.json#L51-L64: Replace the English values with Polish translations.webview-ui/src/i18n/locales/pt-BR/history.json#L51-L64: Replace the English values with Brazilian Portuguese translations.webview-ui/src/i18n/locales/ru/history.json#L51-L64: Replace the English values with Russian translations.webview-ui/src/i18n/locales/tr/history.json#L51-L64: Replace the English values with Turkish translations.🤖 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 `@webview-ui/src/i18n/locales/pl/history.json` around lines 51 - 64, Translate the new task-organization strings, preserving the existing keys and interpolation syntax, in webview-ui/src/i18n/locales/pl/history.json lines 51-64 (Polish), webview-ui/src/i18n/locales/pt-BR/history.json lines 51-64 (Brazilian Portuguese), webview-ui/src/i18n/locales/ru/history.json lines 51-64 (Russian), and webview-ui/src/i18n/locales/tr/history.json lines 51-64 (Turkish); replace each English value with its appropriate locale translation.webview-ui/src/i18n/locales/it/history.json-51-64 (1)
51-64: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTranslate the English history strings.
These locale bundles display English folder controls to Italian, Japanese, and Dutch users.
webview-ui/src/i18n/locales/it/history.json#L51-L64: Replace the English values with Italian translations.webview-ui/src/i18n/locales/ja/history.json#L51-L64: Replace the English values with Japanese translations.webview-ui/src/i18n/locales/nl/history.json#L51-L64: Replace the English values with Dutch translations.🤖 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 `@webview-ui/src/i18n/locales/it/history.json` around lines 51 - 64, The locale bundle files contain English strings instead of translations for Italian, Japanese, and Dutch users. Update webview-ui/src/i18n/locales/it/history.json lines 51-64 to replace all English string values (newFolder, folderNamePlaceholder, renameFolder, removeFromFolder, deleteEmptyFolder, pin, unpin, pinLimitReached, pinned, folder, tasks, unfiled, dragToOrganize, dropHereToRemove) with Italian translations. Apply the same transformation at webview-ui/src/i18n/locales/ja/history.json lines 51-64 with Japanese translations. Apply the same transformation at webview-ui/src/i18n/locales/nl/history.json lines 51-64 with Dutch translations. Keep all JSON keys unchanged; only update the string values to their target-language equivalents.webview-ui/src/i18n/locales/es/history.json-58-71 (1)
58-71: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTranslate the new history labels.
These locale bundles still show English for new folder, pin, and drag-and-drop labels. Translate all added English values so the new workflow remains localized.
webview-ui/src/i18n/locales/es/history.json#L58-L71: Translate the English history labels to Spanish.webview-ui/src/i18n/locales/fr/history.json#L58-L71: Translate the English history labels to French.webview-ui/src/i18n/locales/hi/history.json#L51-L64: Translate the English history labels to Hindi.webview-ui/src/i18n/locales/id/history.json#L60-L73: Translate the English history labels to Indonesian.🤖 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 `@webview-ui/src/i18n/locales/es/history.json` around lines 58 - 71, Translate every English value for the new history labels in webview-ui/src/i18n/locales/es/history.json lines 58-71, webview-ui/src/i18n/locales/fr/history.json lines 58-71, webview-ui/src/i18n/locales/hi/history.json lines 51-64, and webview-ui/src/i18n/locales/id/history.json lines 60-73 into the respective locale languages, preserving all translation keys and interpolation placeholders.webview-ui/src/i18n/locales/vi/history.json-51-64 (1)
51-64: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winThe same English folder and pin strings were added to three non-English locale files. Keys
newFolderthroughdropHereToRemovehold English values in all three files, while the keys that follow in the same block are translated. The shared root cause is one untranslated block copied into each locale. Two of these keys,dropHereToRemoveanddragToOrganize, also duplicate the translateddropToRemoveFromFolderanddragTask; remove whichever key of each pair the components do not use.
webview-ui/src/i18n/locales/vi/history.json#L51-L64: translate the 14 English values to Vietnamese.webview-ui/src/i18n/locales/zh-CN/history.json#L51-L64: translate the 14 English values to Simplified Chinese.webview-ui/src/i18n/locales/zh-TW/history.json#L51-L64: translate the 14 English values to Traditional Chinese.🤖 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 `@webview-ui/src/i18n/locales/vi/history.json` around lines 51 - 64, Translate the 14 English values from newFolder through dropHereToRemove in webview-ui/src/i18n/locales/vi/history.json#L51-L64 into Vietnamese, in webview-ui/src/i18n/locales/zh-CN/history.json#L51-L64 into Simplified Chinese, and in webview-ui/src/i18n/locales/zh-TW/history.json#L51-L64 into Traditional Chinese. In each file, remove the unused duplicate between dragToOrganize and the translated dragTask key, and between dropHereToRemove and the translated dropToRemoveFromFolder key, preserving whichever key the components use.webview-ui/src/components/history/__tests__/TaskOrganizationDndSurface.spec.tsx-196-212 (1)
196-212: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winConfigure
mockUseExtensionStatebefore the first render in this test.
renderruns at line 199, butmockUseExtensionState.mockReturnValue(...)runs at line 206.beforeEachonly callsvi.clearAllMocks(), which clears recorded calls and keeps implementations. The first render therefore uses whatever return value an earlier test installed. If this test runs alone, with.only, or after a reorder,useExtensionState()returnsundefinedand the surface throws while destructuringtaskOrganization.Move the mock setup above
render.💚 Suggested change
const capture = installDndCapture() const mutateSpy = vi.fn().mockResolvedValue(createSuccessResult()) + mockUseExtensionState.mockReturnValue({ + taskOrganization: createEmptyOrganizationState(), + mutateTaskOrganization: mutateSpy, + }) const { rerender } = render( <TaskOrganizationInteractionProvider> <TaskOrganizationDndSurface enabled resolveDragLabel={() => "label"}> <div /> </TaskOrganizationDndSurface> </TaskOrganizationInteractionProvider>, ) - mockUseExtensionState.mockReturnValue({ - taskOrganization: createEmptyOrganizationState(), - mutateTaskOrganization: mutateSpy, - })🤖 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 `@webview-ui/src/components/history/__tests__/TaskOrganizationDndSurface.spec.tsx` around lines 196 - 212, Move the mockUseExtensionState.mockReturnValue setup above the initial render in the “cancels a pending draft when disabled” test, ensuring TaskOrganizationDndSurface receives taskOrganization and mutateTaskOrganization during rendering. Keep the existing mock values and test flow unchanged.webview-ui/src/components/history/__tests__/TaskOrganizationErrorBoundary.spec.tsx-110-115 (1)
110-115: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winThis assertion cannot fail.
document.body.textContentreturns text nodes only. It never contains thedata-testidattribute value"safe-child", so line 114 always passes and proves nothing about the throwing subtree. Assert on the rendered element instead. Note that this test then overlaps the test at lines 40-50, so consider merging the two.💚 Suggested change
expect(screen.getByText("Fallback content")).toBeInTheDocument() // The throwing child should not be in the DOM - expect(document.body.textContent).not.toContain("safe-child") + expect(screen.queryByTestId("safe-child")).not.toBeInTheDocument()🤖 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 `@webview-ui/src/components/history/__tests__/TaskOrganizationErrorBoundary.spec.tsx` around lines 110 - 115, Replace the ineffective document.body.textContent check in the TaskOrganizationErrorBoundary test with an assertion that queries the rendered element identified by the throwing child’s data-testid and verifies it is absent. Since this duplicates the existing coverage near the earlier fallback test, merge the assertions or remove the redundant test while preserving verification that the fallback renders and the throwing subtree does not.webview-ui/src/i18n/locales/vi/chat.json-20-20 (1)
20-20: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winRestore the missing UI translation for child tasks.
TaskHeader.tsxstill renders{t("chat:task.waitingOnSubtask")}, but only theenlocale containstask.waitingOnSubtask; add it back for every locale or update the call to use the newchat:subtasks.goToSubtaskkey.🤖 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 `@webview-ui/src/i18n/locales/vi/chat.json` at line 20, Update the translation lookup in TaskHeader.tsx to use the existing chat:subtasks.goToSubtask key instead of chat:task.waitingOnSubtask. The entries at webview-ui/src/i18n/locales/vi/chat.json:20-20, webview-ui/src/i18n/locales/zh-CN/chat.json:20-20, and webview-ui/src/i18n/locales/zh-TW/chat.json:20-20 require no direct changes because they are corrected by reusing the existing key.webview-ui/src/components/history/__tests__/HistoryView.taskOrganization.spec.tsx-578-619 (1)
578-619: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winThe test does not verify canonical-root resolution.
The test name states that a child drop resolves to its canonical root. The body only asserts that
draggable-entry-unfiled-unit-parent-1is present. It installs the DnD harness but never triggers a drop, and it never inspects the drag data for anautoGrouptarget. As written, the test passes even if canonical resolution is broken.Drive a drop through the harness and assert the resolved source target.
💚 Suggested assertion using the installed harness
render(<HistoryView onDone={vi.fn()} />) - // The parent group draggable must carry the autoGroup target. - const parentEntry = screen.getByTestId("draggable-entry-unfiled-unit-parent-1") - expect(parentEntry).toBeInTheDocument() + expect(screen.getByTestId("draggable-entry-unfiled-unit-parent-1")).toBeInTheDocument() + + // A drag that starts from the group must carry the canonical autoGroup target. + getHarness().triggerDrop( + { kind: "task", target: { kind: "autoGroup", rootTaskId: "parent-1" } }, + { id: "drop-unfiled-unit-solo-1", data: { kind: "task", target: { kind: "task", taskId: "solo-1" } } }, + ) + expect(spies.onRequestCreateFolder).toHaveBeenCalledWith( + { kind: "autoGroup", rootTaskId: "parent-1" }, + { kind: "task", taskId: "solo-1" }, + )🤖 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 `@webview-ui/src/components/history/__tests__/HistoryView.taskOrganization.spec.tsx` around lines 578 - 619, Update the test “resolves an automatic-group child drop to its canonical root” to trigger a child drop through the installed DnD harness after rendering. Inspect the resulting drag data or move callback and assert that the autoGroup source target resolves to the canonical parent root (“parent-1”), rather than only asserting the parent entry is present.webview-ui/src/i18n/locales/ca/history.json-58-71 (1)
58-71: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winTranslate the visible history labels for Catalan and German.
webview-ui/src/i18n/locales/ca/history.json#L58-L71: replace the English fallback values with Catalan translations.webview-ui/src/i18n/locales/de/history.json#L58-L71: replace the English fallback values with German translations.Users who select either locale receive a mixed-language history UI.
🤖 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 `@webview-ui/src/i18n/locales/ca/history.json` around lines 58 - 71, Replace the English fallback values for the history labels from newFolder through dropHereToRemove in webview-ui/src/i18n/locales/ca/history.json lines 58-71 with Catalan translations, and apply the corresponding German translations to webview-ui/src/i18n/locales/de/history.json lines 58-71. Preserve all translation keys and interpolation syntax, including {{count}} in tasks.webview-ui/src/i18n/__tests__/translation-parity.spec.ts-10-42 (1)
10-42: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAdd every new history key to
REQUIRED_HISTORY_KEYS.The list omits
dragTask,dragFolder,createFolder,createFolderDescription,folderNameLabel,folderNameRequired,folderNameTooLong,folderNameInvalidChars,deleteFolder,folderOptions,expandFolder,collapseFolder,create,openTask, andopenFolder. A locale can omit any of these new UI keys and still pass both parity tests. Add them to the required list.🤖 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 `@webview-ui/src/i18n/__tests__/translation-parity.spec.ts` around lines 10 - 42, Extend REQUIRED_HISTORY_KEYS with all omitted history UI keys: dragTask, dragFolder, createFolder, createFolderDescription, folderNameLabel, folderNameRequired, folderNameTooLong, folderNameInvalidChars, deleteFolder, folderOptions, expandFolder, collapseFolder, create, openTask, and openFolder, so parity tests require every new key.webview-ui/src/components/history/__tests__/useTaskOrganizationDnd.spec.tsx-45-48 (1)
45-48: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winCentralize the partial DnD event fixtures.
The suite repeats undocumented
as unknown as Drag*Eventcasts through line 221. Move the partial fixtures into typeddragStart,dragOver, anddragEndhelpers. If a double assertion is still needed, document the fieldsuseTaskOrganizationDndreads at the helper.🤖 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 `@webview-ui/src/components/history/__tests__/useTaskOrganizationDnd.spec.tsx` around lines 45 - 48, Centralize the partial DnD event construction used by the useTaskOrganizationDnd tests by adding typed dragStart, dragOver, and dragEnd helpers, then replace the repeated inline as unknown as Drag*Event casts through the suite with those helpers. Document within each helper the event fields read by useTaskOrganizationDnd, retaining any required double assertion only there.Source: Coding guidelines
webview-ui/src/components/history/__tests__/TaskOrganizationInteractionContext.spec.tsx-37-37 (1)
37-37: 📐 Maintainability & Code Quality | 🟡 MinorReplace
window as anyresult storage with typed test state.Move the test result store into a test-scoped, typed variable and update the assignment, reset, and assertion references. This applies to both
__lastResult__and__lastMutationResult__usage.[low_effort_and_medium_reward]
🤖 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 `@webview-ui/src/components/history/__tests__/TaskOrganizationInteractionContext.spec.tsx` at line 37, Replace the window-cast result stores with test-scoped typed variables in both webview-ui/src/components/history/__tests__/TaskOrganizationInteractionContext.spec.tsx:37-37 and webview-ui/src/context/__tests__/ExtensionStateContext.taskOrganization.spec.tsx:34-34. Update all __lastResult__ and __lastMutationResult__ assignments, resets, and assertions to use the typed variables instead of window state.Source: Coding guidelines
webview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.ts-10-13 (1)
10-13: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAssert delegation with a complete pointerdown fixture.
makePointerEventonly suppliestarget, so the delegatedPointerSensoractivator can fail due to missingisPrimary,button, orownerDocumentfields. Add a primary-left-button pointerdown fixture and assert the delegated result istrue; keep the unavoidable event-shape cast in a documented helper.🤖 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 `@webview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.ts` around lines 10 - 13, Update makePointerEvent in the TaskOrganizationPointerSensor tests to provide a complete primary left-button pointerdown fixture, including isPrimary, button, and ownerDocument on the native event target. Add an assertion that the delegated PointerSensor activator returns true, and retain the unavoidable event-shape cast only within this documented helper.Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 6c7df8fe-79dd-4506-a430-34e028c18dee
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (103)
docs/260730_0002_session_dashboard-crash-debug/232600_ask-full-audit-report.mddocs/260730_0002_session_dashboard-crash-debug/233600_code-multi-branch-report.mddocs/260730_0002_session_dashboard-crash-debug/context-save-260730-2333.mddocs/260730_0002_session_dashboard-crash-debug/test-error-interception.txtdocs/260730_0002_session_dashboard-crash-debug/test-mimo-parallel.txtdocs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txtdocs/260730_0002_session_dashboard-crash-debug/test-task-dnd-ux.txtdocs/260730_0002_session_dashboard-crash-debug/test-unified-shell-resolution.txtdocs/260803_0002_session_6-branch-bug-fix-verification/170635_code-environment-feedback.mddocs/260803_0002_session_6-branch-bug-fix-verification/170715_code-vitest-environment-feedback.mddocs/260803_0002_session_6-branch-bug-fix-verification/170924_code-report.mddocs/260803_0002_session_6-branch-bug-fix-verification/171100_code-report.mdknip.jsonpackages/types/src/index.tspackages/types/src/task-organization.tspackages/types/src/vscode-extension-host.tssrc/core/task-persistence/TaskOrganizationStore.tssrc/core/task-persistence/__tests__/TaskOrganizationStore.spec.tssrc/core/task-persistence/index.tssrc/core/webview/ClineProvider.tssrc/core/webview/__tests__/ClineProvider.taskHistory.spec.tssrc/core/webview/__tests__/taskOrganizationMessageHandler.spec.tssrc/core/webview/taskOrganizationMessageHandler.tssrc/core/webview/webviewMessageHandler.tssrc/eslint-suppressions.jsonsrc/shared/globalFileNames.tssrc/utils/safeWriteJson.tswebview-ui/package.jsonwebview-ui/src/components/history/DeleteFoldersDialog.tsxwebview-ui/src/components/history/DraggableTaskEntry.tsxwebview-ui/src/components/history/FolderNameDialog.tsxwebview-ui/src/components/history/HistoryPreview.tsxwebview-ui/src/components/history/HistoryView.tsxwebview-ui/src/components/history/ManualFolderItem.tsxwebview-ui/src/components/history/PinButton.tsxwebview-ui/src/components/history/PinnedHistoryItem.tsxwebview-ui/src/components/history/SubtaskRow.tsxwebview-ui/src/components/history/TaskGroupItem.tsxwebview-ui/src/components/history/TaskItem.tsxwebview-ui/src/components/history/TaskItemFooter.tsxwebview-ui/src/components/history/TaskOrganizationDndSurface.tsxwebview-ui/src/components/history/TaskOrganizationErrorBoundary.tsxwebview-ui/src/components/history/TaskOrganizationInteractionContext.tsxwebview-ui/src/components/history/TaskOrganizationPointerSensor.tswebview-ui/src/components/history/__tests__/DeleteFoldersDialog.spec.tsxwebview-ui/src/components/history/__tests__/DraggableTaskEntry.spec.tsxwebview-ui/src/components/history/__tests__/HistoryPreview.spec.tsxwebview-ui/src/components/history/__tests__/HistoryPreview.taskOrganization.spec.tsxwebview-ui/src/components/history/__tests__/HistoryView.taskOrganization.spec.tsxwebview-ui/src/components/history/__tests__/ManualFolderItem.spec.tsxwebview-ui/src/components/history/__tests__/PinButton.spec.tsxwebview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationDndSurface.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationErrorBoundary.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationInteractionContext.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.setup.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.spec.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.vitest.config.tswebview-ui/src/components/history/__tests__/useTaskOrganizationDnd.spec.tsxwebview-ui/src/components/history/taskOrganizationModel.tswebview-ui/src/components/history/types.tswebview-ui/src/components/history/useTaskOrganizationDnd.tswebview-ui/src/context/ExtensionStateContext.tsxwebview-ui/src/context/__tests__/ExtensionStateContext.taskOrganization.spec.tsxwebview-ui/src/i18n/__tests__/translation-parity.spec.tswebview-ui/src/i18n/locales/ca/chat.jsonwebview-ui/src/i18n/locales/ca/history.jsonwebview-ui/src/i18n/locales/de/chat.jsonwebview-ui/src/i18n/locales/de/history.jsonwebview-ui/src/i18n/locales/en/chat.jsonwebview-ui/src/i18n/locales/en/history.jsonwebview-ui/src/i18n/locales/es/chat.jsonwebview-ui/src/i18n/locales/es/history.jsonwebview-ui/src/i18n/locales/fr/chat.jsonwebview-ui/src/i18n/locales/fr/history.jsonwebview-ui/src/i18n/locales/hi/chat.jsonwebview-ui/src/i18n/locales/hi/history.jsonwebview-ui/src/i18n/locales/id/chat.jsonwebview-ui/src/i18n/locales/id/history.jsonwebview-ui/src/i18n/locales/it/chat.jsonwebview-ui/src/i18n/locales/it/history.jsonwebview-ui/src/i18n/locales/ja/chat.jsonwebview-ui/src/i18n/locales/ja/history.jsonwebview-ui/src/i18n/locales/ko/chat.jsonwebview-ui/src/i18n/locales/ko/history.jsonwebview-ui/src/i18n/locales/nl/chat.jsonwebview-ui/src/i18n/locales/nl/history.jsonwebview-ui/src/i18n/locales/pl/chat.jsonwebview-ui/src/i18n/locales/pl/history.jsonwebview-ui/src/i18n/locales/pt-BR/chat.jsonwebview-ui/src/i18n/locales/pt-BR/history.jsonwebview-ui/src/i18n/locales/ru/chat.jsonwebview-ui/src/i18n/locales/ru/history.jsonwebview-ui/src/i18n/locales/tr/chat.jsonwebview-ui/src/i18n/locales/tr/history.jsonwebview-ui/src/i18n/locales/vi/chat.jsonwebview-ui/src/i18n/locales/vi/history.jsonwebview-ui/src/i18n/locales/zh-CN/chat.jsonwebview-ui/src/i18n/locales/zh-CN/history.jsonwebview-ui/src/i18n/locales/zh-TW/chat.jsonwebview-ui/src/i18n/locales/zh-TW/history.jsonwebview-ui/vitest.setup.ts
💤 Files with no reviewable changes (1)
- webview-ui/src/components/history/tests/TaskItemFooter.spec.tsx
🛑 Comments failed to post (1)
docs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txt (1)
2-7: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash set -euo pipefail target='docs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txt' if [ ! -f "$target" ]; then echo "missing: $target" exit 0 fi echo "== file stats ==" wc -l "$target" echo "== matches for Windows user/project paths ==" rg -n -i -E 'C:\\Users\\k1yt|OneDrive\\Projects|file://[^\s\r\n]+' "$target" || true echo "== matches for C:/Users/k1yt ==" rg -n -i -E 'C:/Users/k1yt|file://[^\s\r\n]+' "$target" || true echo "== decoded snippet around local path ==" python3 - <<'PY' from pathlib import Path p=Path('docs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txt') text=p.read_text(encoding='utf-8-sig') for i,line in enumerate(text.splitlines(),1): if 'k1yt' in line or 'OneDrive' in line: print(f"{i}: {line[:240]!r}") PYRepository: Zoo-Code-Org/Zoo-Code
Length of output: 1099
🏁 Script executed:
#!/bin/bash set -euo pipefail target='docs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txt' if [ ! -f "$target" ]; then echo "missing: $target" exit 0 fi echo "== file stats ==" wc -l "$target" echo "== matches for Windows user/project paths ==" rg -n -i -E 'C\\Users\\k1yt|OneDrive\\Projects|C:/Users/k1yt|file://[^\s\r\n]+' "$target" || true echo "== decoded snippet around local path ==" python3 - <<'PY' from pathlib import Path p=Path('docs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txt') text=p.read_text(encoding='utf-8-sig', errors='replace') for i,line in enumerate(text.splitlines(),1): if 'k1yt' in line or 'OneDrive' in line: print(f"{i}: {line[:260]!r}") PYRepository: Zoo-Code-Org/Zoo-Code
Length of output: 468
🏁 Script executed:
#!/bin/bash set -euo pipefail target='docs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txt' echo "== hex bytes around file start ==" xxd -g 1 -n 16 "$target" echo "== decoded lines containing k1yt/OneDrive/file:// ==" python3 - <<'PY' from pathlib import Path p=Path('docs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txt') raw=p.read_bytes() for seq in [b'\xff\xfe', b'\xfe\xff', b'\xef\xbb\xbf']: if raw.startswith(seq): enc='utf-16-le' if seq==b'\xff\xfe' else 'utf-16-be' text=raw.decode(enc) break else: text=raw.decode('utf-8', errors='replace') for i,line in enumerate(text.splitlines(),1): if ('k1yt' in line) or ('OneDrive' in line) or ('file://' in line): print(f'{i}: {line[:300]!r}') PYRepository: Zoo-Code-Org/Zoo-Code
Length of output: 234
Sensitive Data Exposure (CWE-200): Exposure of Sensitive Information to an Unauthorized Actor
Remove developer-specific filesystem paths from the crash log.
C:\Users\k1yt\OneDrive\Projects\ZooCode\srcand localfile://path text disclose the Windows account name and filesystem layout. Replace those paths with<repo-root>or sanitize the artifact before merging.🤖 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 `@docs/260730_0002_session_dashboard-crash-debug/test-strict-reasoning.txt` around lines 2 - 7, Sanitize the crash-log artifact in test-strict-reasoning.txt by removing the developer-specific Windows path, account name, and local file:// path details. Replace repository-root references with <repo-root> and remove or generalize any remaining machine-specific filesystem text while preserving the relevant error output.
ec9d71e to
402f670
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 16
♻️ Duplicate comments (2)
src/core/task-persistence/TaskOrganizationStore.ts (2)
699-710: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winDo not mutate
childMapduring descendant traversal.
stackaliases the array stored inchildMap. Calls topop()andpush()drain and pollute that shared array, so later missing members can lose descendants during the same reconciliation.Initialize
stackfrom a copy and track visited IDs.🤖 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 `@src/core/task-persistence/TaskOrganizationStore.ts` around lines 699 - 710, Update the descendant traversal in the missing-ID reconciliation around the surviving calculation so the traversal stack is a copy of each childMap entry rather than the stored array, preventing pop/push operations from mutating childMap. Track visited IDs during traversal to avoid revisiting nodes while still collecting visible descendants for each missing ID.
53-53: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winDeclare optional
getAll()ontaskHistory.The option and private-field types still omit
getAll(), although both reconciliation paths probe and invoke it. Declare the optional method with itsHistoryItem[]return type.Also applies to: 77-77, 621-622
🤖 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 `@src/core/task-persistence/TaskOrganizationStore.ts` at line 53, Update the taskHistory type declarations, including the option and private-field types used by TaskOrganizationStore, to declare an optional getAll() method returning HistoryItem[]. Keep the existing optional get(taskId) declaration unchanged and ensure both reconciliation paths can type-check their getAll() usage.
🧹 Nitpick comments (6)
webview-ui/src/i18n/__tests__/translation-parity.spec.ts (1)
65-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThe second test cannot fail independently of the first.
localeRequiredKeysat lines 79-81 is the intersection of the locale's keys withREQUIRED_HISTORY_KEYS. It can only differ fromexpectedShapeby missing an entry, and the first test already fails in that case. The second test therefore adds no coverage.Both tests also accept an empty string, because
toBeDefined()passes for"". An empty value produces the same broken UI that the comment at lines 5-9 describes. Convert the second test into a value check.♻️ Proposed replacement
- it("has identical key shape across all locales for the required task-organization keys", () => { - // Locales may carry additional legacy keys not present in en. The shape - // contract that matters for this feature is that every locale exposes - // the SAME set of required task-organization keys. Sort the required - // list once and assert every locale's filtered shape equals it. - const locales = fs - .readdirSync(LOCALES_DIR) - .filter((name) => fs.statSync(path.join(LOCALES_DIR, name)).isDirectory()) - - const expectedShape = [...REQUIRED_HISTORY_KEYS].sort() - - for (const locale of locales) { - const filePath = path.join(LOCALES_DIR, locale, "history.json") - const history = JSON.parse(fs.readFileSync(filePath, "utf-8")) - const localeRequiredKeys = Object.keys(history) - .filter((k) => REQUIRED_HISTORY_KEYS.includes(k)) - .sort() - - expect( - localeRequiredKeys, - `Key shape mismatch in ${locale}/history.json: missing=${expectedShape.filter( - (k) => !localeRequiredKeys.includes(k), - )}`, - ).toEqual(expectedShape) - } - }) + it("resolves every required key to a non-empty string in every locale", () => { + for (const locale of readLocales()) { + const history = JSON.parse(fs.readFileSync(path.join(LOCALES_DIR, locale, "history.json"), "utf-8")) + + for (const key of REQUIRED_HISTORY_KEYS) { + expect(history[key], `Empty value for "${key}" in ${locale}/history.json`).toEqual( + expect.stringMatching(/\S/), + ) + } + } + })Add the shared helper so both tests reuse one directory scan:
function readLocales(): string[] { return fs.readdirSync(LOCALES_DIR).filter((name) => fs.statSync(path.join(LOCALES_DIR, name)).isDirectory()) }🤖 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 `@webview-ui/src/i18n/__tests__/translation-parity.spec.ts` around lines 65 - 90, Replace the redundant key-shape test around REQUIRED_HISTORY_KEYS with a value validation that asserts each required locale entry is non-empty, not merely defined. Add a shared readLocales helper for the directory scan and update both tests to reuse it, preserving the existing required-key coverage while ensuring empty translations fail.src/core/task-persistence/TaskOrganizationStore.ts (1)
298-301: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExplain the double assertion at the cast site.
Document why a future-schema value must be stored as
TaskOrganizationStateV1, or replace the cast with a typed read-only future-state representation. The nearby behavior comment does not explain this double assertion.As per coding guidelines, “Use double assertions only as a last resort and explain them with a comment next to the cast.”
🤖 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 `@src/core/task-persistence/TaskOrganizationStore.ts` around lines 298 - 301, Update the future-schema branch in the task organization loading method around the data cast: either replace the double assertion with a typed read-only future-state representation, or retain it only with a nearby comment explaining why the future-schema value must be stored as TaskOrganizationStateV1. Keep the existing warning, assignment behavior, and early return unchanged.Source: Coding guidelines
codecov.yml (1)
15-22: 📐 Maintainability & Code Quality | 🔵 TrivialPatch coverage no longer blocks.
Both patch statuses are now
informational: true, so new untested code cannot fail the check. The project statuses at Lines 5-14 still ratchet total coverage, which limits the risk. Confirm this relaxation is intended for the long term, since this PR adds a large amount of new webview code.🤖 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 `@codecov.yml` around lines 15 - 22, Confirm whether making both patch coverage statuses informational in the codecov configuration is an intentional long-term policy; if not, restore blocking patch coverage for default and webview-patch while preserving the existing project-level coverage thresholds.webview-ui/src/components/history/HistoryView.tsx (3)
238-252: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThe folder-target branch is unreachable.
canCreateFolderFromSelectionrequiresselectedFolderIds.length === 0, andhandleCreateFolderFromSelectionreturns early otherwise. The dialog therefore only opens with an empty folder selection, so thefolderIdtargets built here are always absent. Either allow folder selection incanCreateFolderFromSelectionor delete this branch.🤖 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 `@webview-ui/src/components/history/HistoryView.tsx` around lines 238 - 252, The folder-target construction in handleConfirmSelectionFolderName is unreachable because canCreateFolderFromSelection and handleCreateFolderFromSelection reject nonempty selectedFolderIds. Update the folder-creation flow to allow folder selections, preserving the existing selectedFolderIds-to-TaskOrganizationTargetV1 mapping and clearing behavior after success.
831-1149: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftConsider extracting the shared header and list markup.
HistoryViewBaselineFallbackduplicates roughly 270 lines of search controls, sort controls, selection header, and Virtuoso rendering fromHistoryViewInner. Future changes must be applied twice. Extract the shared header and the baseline list into small components that both renderers use.🤖 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 `@webview-ui/src/components/history/HistoryView.tsx` around lines 831 - 1149, Extract the duplicated search/sort controls, workspace selector, selection header, and Virtuoso task/group rendering from HistoryViewBaselineFallback and HistoryViewInner into shared components. Reuse those components in both renderers while preserving their existing props, callbacks, selection behavior, and rendering differences; keep renderer-specific dialogs and layout concerns in the parent components.
57-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the unused
groupsparameter.
buildGroupDndDataignoresgroupsand discards it withvoid groups. Drop the parameter and update both call sites (Line 393 and Line 677).♻️ Proposed fix
-function buildGroupDndData(group: TaskGroup, groups: TaskGroup[], folderId?: string): DndItemData { +function buildGroupDndData(group: TaskGroup, folderId?: string): DndItemData { const rootId = group.parent.id const hasChildren = group.subtasks.length > 0 const target: TaskOrganizationTargetV1 = hasChildren ? { kind: "autoGroup", rootTaskId: rootId } : { kind: "task", taskId: rootId } - void groups return {🤖 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 `@webview-ui/src/components/history/HistoryView.tsx` around lines 57 - 69, Remove the unused groups parameter and the void groups statement from buildGroupDndData, then update both callers around the HistoryView usages to pass only the remaining required arguments.
🤖 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 `@codecov.yml`:
- Line 1: Convert codecov.yml from CRLF to LF line endings while preserving its
coverage configuration. Add a .gitattributes rule for the file only if needed to
prevent editors from reintroducing CRLF.
In `@src/core/task-persistence/TaskOrganizationStore.ts`:
- Around line 281-292: Update the recovery paths in the task-organization
loading method, including the catch block and the result.success validation
branch, so the live file is removed or moved only after quarantine succeeds.
Perform this cleanup under the same locking protocol used by safeUpdateJson,
ensuring the next mutation can recreate valid state from the empty state.
- Around line 320-324: Update the concurrent-modification branch in
TaskOrganizationStore’s mutate flow to reload the committed state after
detecting current.revision >= next.revision, then return TASK_ORG/CONFLICT/002
using the reloaded current revision instead of propagating
TASK_ORG/PERSISTENCE/005 with the stale local revision. Preserve the existing
same-or-newer revision check and reconciliation behavior.
- Around line 584-612: Update resolveTarget and resolveUnit to validate target
existence before returning canonical targets or member IDs: when taskHistory is
available, reject unknown task IDs and autoGroup rootTaskIds; always reject
folder targets whose folderId is absent from state.folders. Ensure invalid
targets cannot proceed to state mutation, using the existing invalid-target
handling convention.
In `@src/eslint-suppressions.json`:
- Around line 187-190: Replace the newly introduced any usages in the Mimo,
OpenCode Go, and Qwen Code native-tools tests with precise test doubles or
unknown plus appropriate type guards, then remove the added suppression entries
from src/eslint-suppressions.json at lines 187-190, 242-245, and 257-260,
restoring each prior count without increasing any suppression count.
In `@src/utils/safeWriteJson.ts`:
- Around line 313-395: Extract the duplicated atomic temp-write, backup, commit,
cleanup, and rollback logic into a private helper named _atomicCommitJson that
accepts the resolved file path, data, and prettyPrint option. Move the shared
implementation from safeWriteJson into this helper, excluding lock acquisition
and release, then replace the inline block in both safeWriteJson and
safeUpdateJson with calls to _atomicCommitJson while preserving existing error
and rollback behavior.
- Around line 301-311: Update the read-error handling in safeUpdateJson to
rethrow every failure whose error code is not ENOENT, regardless of whether the
thrown value is an Error instance. Preserve the existing missing-file behavior
for ENOENT and ensure non-Error read failures cannot leave fileExisted false and
continue to updater(current).
In
`@webview-ui/src/components/history/__tests__/HistoryPreview.taskOrganization.spec.tsx`:
- Around line 438-451: Align the test named “opens the folder-name dialog when
card A is dropped on card B” with its actual behavior: either rename it to
describe validating draggable/droppable metadata and the dialog’s closed initial
state, or update the test to perform a real drop through the DnD surface and
assert the folder-name dialog opens.
- Around line 453-458: Update the “cancel posts nothing” test to use the
createFolder mock initialized in beforeEach instead of the optional call on the
fallback org object. Perform the component’s cancel interaction before asserting
that createFolder was not called, so the test verifies cancellation behavior
rather than only the initial render.
In
`@webview-ui/src/components/history/__tests__/TaskOrganizationInteractionContext.spec.tsx`:
- Line 37: Replace the window-based __lastResult__ channel in the test harness
with a module-level variable typed to the mutation result, and update all four
assignments and assertion reads to use it. Remove every window as any cast while
preserving the existing result assertions.
In
`@webview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.ts`:
- Around line 114-122: Strengthen the assertion in the “delegates to
PointerSensor for non-interactive targets” test by verifying the concrete result
expected from PointerSensor for the valid primary-button event, rather than only
checking that the result is boolean. Alternatively, spy on the base
PointerSensor handler and assert it receives the event; keep the existing
non-interactive target setup unchanged.
In `@webview-ui/src/components/history/DeleteFoldersDialog.tsx`:
- Around line 42-47: Update the AlertDialogDescription usage in
DeleteFoldersDialog so it no longer renders the two div blocks inside Radix’s
default p element; either move those blocks outside the description or configure
the description with asChild and a div wrapper while preserving both translated
messages and their styling.
In `@webview-ui/src/components/history/HistoryView.tsx`:
- Around line 416-419: Use the canonical pin target shape for both history
pin-button sections: in webview-ui/src/components/history/HistoryView.tsx lines
416-419, derive the target from memberGroup.subtasks.length and reuse it for
isPinned and togglePin; at lines 696-698, derive the unfiled target from
group.subtasks.length, preferably reusing dndData.target. Update the relevant
pin call sites without changing unrelated behavior.
- Around line 649-654: Update the onTogglePin callback in HistoryView to prefix
the togglePin call with void, matching other call sites and satisfying the
no-floating-promise rule.
In `@webview-ui/src/i18n/locales/ca/history.json`:
- Around line 58-71: Translate the English values for the referenced history
keys in the Catalan locale, including newFolder, folderNamePlaceholder,
renameFolder, removeFromFolder, deleteEmptyFolder, pin, unpin, pinLimitReached,
pinned, folder, unfiled, tasks, dragToOrganize, and dropHereToRemove. Preserve
all keys and the {{count}} interpolation while leaving unrelated entries
unchanged.
- Line 68: Add Catalan plural variants for the history tasks translation by
replacing the single tasks entry with tasks_one and tasks_other, following the
nearby count-key convention and ensuring singular counts render “1 task” while
plural counts render “{{count}} tasks”.
---
Duplicate comments:
In `@src/core/task-persistence/TaskOrganizationStore.ts`:
- Around line 699-710: Update the descendant traversal in the missing-ID
reconciliation around the surviving calculation so the traversal stack is a copy
of each childMap entry rather than the stored array, preventing pop/push
operations from mutating childMap. Track visited IDs during traversal to avoid
revisiting nodes while still collecting visible descendants for each missing ID.
- Line 53: Update the taskHistory type declarations, including the option and
private-field types used by TaskOrganizationStore, to declare an optional
getAll() method returning HistoryItem[]. Keep the existing optional get(taskId)
declaration unchanged and ensure both reconciliation paths can type-check their
getAll() usage.
---
Nitpick comments:
In `@codecov.yml`:
- Around line 15-22: Confirm whether making both patch coverage statuses
informational in the codecov configuration is an intentional long-term policy;
if not, restore blocking patch coverage for default and webview-patch while
preserving the existing project-level coverage thresholds.
In `@src/core/task-persistence/TaskOrganizationStore.ts`:
- Around line 298-301: Update the future-schema branch in the task organization
loading method around the data cast: either replace the double assertion with a
typed read-only future-state representation, or retain it only with a nearby
comment explaining why the future-schema value must be stored as
TaskOrganizationStateV1. Keep the existing warning, assignment behavior, and
early return unchanged.
In `@webview-ui/src/components/history/HistoryView.tsx`:
- Around line 238-252: The folder-target construction in
handleConfirmSelectionFolderName is unreachable because
canCreateFolderFromSelection and handleCreateFolderFromSelection reject nonempty
selectedFolderIds. Update the folder-creation flow to allow folder selections,
preserving the existing selectedFolderIds-to-TaskOrganizationTargetV1 mapping
and clearing behavior after success.
- Around line 831-1149: Extract the duplicated search/sort controls, workspace
selector, selection header, and Virtuoso task/group rendering from
HistoryViewBaselineFallback and HistoryViewInner into shared components. Reuse
those components in both renderers while preserving their existing props,
callbacks, selection behavior, and rendering differences; keep renderer-specific
dialogs and layout concerns in the parent components.
- Around line 57-69: Remove the unused groups parameter and the void groups
statement from buildGroupDndData, then update both callers around the
HistoryView usages to pass only the remaining required arguments.
In `@webview-ui/src/i18n/__tests__/translation-parity.spec.ts`:
- Around line 65-90: Replace the redundant key-shape test around
REQUIRED_HISTORY_KEYS with a value validation that asserts each required locale
entry is non-empty, not merely defined. Add a shared readLocales helper for the
directory scan and update both tests to reuse it, preserving the existing
required-key coverage while ensuring empty translations fail.
🪄 Autofix
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: d8ce4f7d-1db7-4d42-b668-45eadc8deb97
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (92)
codecov.ymlknip.jsonpackages/types/src/index.tspackages/types/src/task-organization.tspackages/types/src/vscode-extension-host.tssrc/core/task-persistence/TaskOrganizationStore.tssrc/core/task-persistence/__tests__/TaskOrganizationStore.spec.tssrc/core/task-persistence/index.tssrc/core/webview/ClineProvider.tssrc/core/webview/__tests__/ClineProvider.taskHistory.spec.tssrc/core/webview/__tests__/taskOrganizationMessageHandler.spec.tssrc/core/webview/taskOrganizationMessageHandler.tssrc/core/webview/webviewMessageHandler.tssrc/eslint-suppressions.jsonsrc/shared/globalFileNames.tssrc/utils/safeWriteJson.tswebview-ui/package.jsonwebview-ui/src/components/history/DeleteFoldersDialog.tsxwebview-ui/src/components/history/DraggableTaskEntry.tsxwebview-ui/src/components/history/FolderNameDialog.tsxwebview-ui/src/components/history/HistoryPreview.tsxwebview-ui/src/components/history/HistoryView.tsxwebview-ui/src/components/history/ManualFolderItem.tsxwebview-ui/src/components/history/PinButton.tsxwebview-ui/src/components/history/PinnedHistoryItem.tsxwebview-ui/src/components/history/SubtaskRow.tsxwebview-ui/src/components/history/TaskGroupItem.tsxwebview-ui/src/components/history/TaskItem.tsxwebview-ui/src/components/history/TaskItemFooter.tsxwebview-ui/src/components/history/TaskOrganizationDndSurface.tsxwebview-ui/src/components/history/TaskOrganizationErrorBoundary.tsxwebview-ui/src/components/history/TaskOrganizationInteractionContext.tsxwebview-ui/src/components/history/TaskOrganizationPointerSensor.tswebview-ui/src/components/history/__tests__/DeleteFoldersDialog.spec.tsxwebview-ui/src/components/history/__tests__/DraggableTaskEntry.spec.tsxwebview-ui/src/components/history/__tests__/HistoryPreview.spec.tsxwebview-ui/src/components/history/__tests__/HistoryPreview.taskOrganization.spec.tsxwebview-ui/src/components/history/__tests__/HistoryView.taskOrganization.spec.tsxwebview-ui/src/components/history/__tests__/ManualFolderItem.spec.tsxwebview-ui/src/components/history/__tests__/PinButton.spec.tsxwebview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationDndSurface.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationErrorBoundary.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationInteractionContext.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.setup.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.spec.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.vitest.config.tswebview-ui/src/components/history/__tests__/useTaskOrganizationDnd.spec.tsxwebview-ui/src/components/history/taskOrganizationModel.tswebview-ui/src/components/history/types.tswebview-ui/src/components/history/useTaskOrganizationDnd.tswebview-ui/src/context/ExtensionStateContext.tsxwebview-ui/src/context/__tests__/ExtensionStateContext.taskOrganization.spec.tsxwebview-ui/src/i18n/__tests__/translation-parity.spec.tswebview-ui/src/i18n/locales/ca/chat.jsonwebview-ui/src/i18n/locales/ca/history.jsonwebview-ui/src/i18n/locales/de/chat.jsonwebview-ui/src/i18n/locales/de/history.jsonwebview-ui/src/i18n/locales/en/chat.jsonwebview-ui/src/i18n/locales/en/history.jsonwebview-ui/src/i18n/locales/es/chat.jsonwebview-ui/src/i18n/locales/es/history.jsonwebview-ui/src/i18n/locales/fr/chat.jsonwebview-ui/src/i18n/locales/fr/history.jsonwebview-ui/src/i18n/locales/hi/chat.jsonwebview-ui/src/i18n/locales/hi/history.jsonwebview-ui/src/i18n/locales/id/chat.jsonwebview-ui/src/i18n/locales/id/history.jsonwebview-ui/src/i18n/locales/it/chat.jsonwebview-ui/src/i18n/locales/it/history.jsonwebview-ui/src/i18n/locales/ja/chat.jsonwebview-ui/src/i18n/locales/ja/history.jsonwebview-ui/src/i18n/locales/ko/chat.jsonwebview-ui/src/i18n/locales/ko/history.jsonwebview-ui/src/i18n/locales/nl/chat.jsonwebview-ui/src/i18n/locales/nl/history.jsonwebview-ui/src/i18n/locales/pl/chat.jsonwebview-ui/src/i18n/locales/pl/history.jsonwebview-ui/src/i18n/locales/pt-BR/chat.jsonwebview-ui/src/i18n/locales/pt-BR/history.jsonwebview-ui/src/i18n/locales/ru/chat.jsonwebview-ui/src/i18n/locales/ru/history.jsonwebview-ui/src/i18n/locales/tr/chat.jsonwebview-ui/src/i18n/locales/tr/history.jsonwebview-ui/src/i18n/locales/vi/chat.jsonwebview-ui/src/i18n/locales/vi/history.jsonwebview-ui/src/i18n/locales/zh-CN/chat.jsonwebview-ui/src/i18n/locales/zh-CN/history.jsonwebview-ui/src/i18n/locales/zh-TW/chat.jsonwebview-ui/src/i18n/locales/zh-TW/history.jsonwebview-ui/vitest.setup.ts
💤 Files with no reviewable changes (1)
- webview-ui/src/components/history/tests/TaskItemFooter.spec.tsx
🚧 Files skipped from review as they are similar to previous changes (79)
- webview-ui/src/i18n/locales/ja/chat.json
- webview-ui/src/i18n/locales/ca/chat.json
- webview-ui/src/i18n/locales/vi/chat.json
- webview-ui/src/components/history/tests/taskOrganizationModel.vitest.config.ts
- webview-ui/package.json
- src/core/task-persistence/index.ts
- webview-ui/src/i18n/locales/es/chat.json
- webview-ui/src/i18n/locales/en/chat.json
- webview-ui/src/i18n/locales/nl/chat.json
- webview-ui/src/i18n/locales/fr/chat.json
- webview-ui/src/i18n/locales/pt-BR/chat.json
- webview-ui/src/i18n/locales/ru/chat.json
- webview-ui/src/i18n/locales/ko/chat.json
- packages/types/src/index.ts
- webview-ui/src/i18n/locales/hi/chat.json
- src/core/webview/taskOrganizationMessageHandler.ts
- knip.json
- webview-ui/src/components/history/TaskOrganizationErrorBoundary.tsx
- webview-ui/src/i18n/locales/pl/chat.json
- webview-ui/src/i18n/locales/zh-CN/chat.json
- webview-ui/src/i18n/locales/es/history.json
- webview-ui/src/i18n/locales/hi/history.json
- webview-ui/src/i18n/locales/ja/history.json
- webview-ui/src/components/history/FolderNameDialog.tsx
- webview-ui/src/i18n/locales/nl/history.json
- webview-ui/src/components/history/tests/PinButton.spec.tsx
- webview-ui/src/i18n/locales/id/chat.json
- webview-ui/src/i18n/locales/zh-TW/chat.json
- webview-ui/src/components/history/tests/ManualFolderItem.spec.tsx
- webview-ui/src/i18n/locales/pl/history.json
- webview-ui/src/components/history/TaskItem.tsx
- src/core/webview/webviewMessageHandler.ts
- webview-ui/src/components/history/TaskGroupItem.tsx
- packages/types/src/vscode-extension-host.ts
- webview-ui/src/components/history/DraggableTaskEntry.tsx
- webview-ui/src/components/history/tests/taskOrganizationModel.setup.ts
- webview-ui/src/i18n/locales/zh-TW/history.json
- webview-ui/src/components/history/TaskOrganizationDndSurface.tsx
- webview-ui/src/i18n/locales/en/history.json
- webview-ui/src/i18n/locales/vi/history.json
- webview-ui/src/components/history/tests/HistoryPreview.spec.tsx
- webview-ui/src/i18n/locales/de/history.json
- webview-ui/src/i18n/locales/ru/history.json
- webview-ui/src/components/history/TaskItemFooter.tsx
- webview-ui/src/i18n/locales/ko/history.json
- webview-ui/src/components/history/tests/DraggableTaskEntry.spec.tsx
- webview-ui/src/i18n/locales/id/history.json
- src/core/webview/tests/taskOrganizationMessageHandler.spec.ts
- webview-ui/src/components/history/tests/useTaskOrganizationDnd.spec.tsx
- webview-ui/src/i18n/locales/fr/history.json
- webview-ui/src/i18n/locales/it/history.json
- src/shared/globalFileNames.ts
- src/core/webview/tests/ClineProvider.taskHistory.spec.ts
- webview-ui/src/i18n/locales/de/chat.json
- webview-ui/src/components/history/tests/DeleteFoldersDialog.spec.tsx
- webview-ui/src/i18n/locales/tr/chat.json
- webview-ui/src/i18n/locales/it/chat.json
- webview-ui/src/components/history/TaskOrganizationPointerSensor.ts
- webview-ui/src/components/history/SubtaskRow.tsx
- webview-ui/src/components/history/PinnedHistoryItem.tsx
- webview-ui/vitest.setup.ts
- webview-ui/src/i18n/locales/pt-BR/history.json
- packages/types/src/task-organization.ts
- webview-ui/src/components/history/tests/HistoryView.taskOrganization.spec.tsx
- webview-ui/src/i18n/locales/zh-CN/history.json
- webview-ui/src/components/history/PinButton.tsx
- webview-ui/src/components/history/TaskOrganizationInteractionContext.tsx
- webview-ui/src/components/history/tests/TaskOrganizationErrorBoundary.spec.tsx
- webview-ui/src/context/tests/ExtensionStateContext.taskOrganization.spec.tsx
- webview-ui/src/components/history/useTaskOrganizationDnd.ts
- webview-ui/src/components/history/taskOrganizationModel.ts
- webview-ui/src/context/ExtensionStateContext.tsx
- webview-ui/src/components/history/tests/taskOrganizationModel.spec.ts
- webview-ui/src/i18n/locales/tr/history.json
- webview-ui/src/components/history/ManualFolderItem.tsx
- webview-ui/src/components/history/tests/TaskOrganizationDndSurface.spec.tsx
- webview-ui/src/components/history/types.ts
- src/core/webview/ClineProvider.ts
- webview-ui/src/components/history/HistoryPreview.tsx
| comment: | ||
| layout: "diff, flags, components" | ||
| behavior: default | ||
| coverage: |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Convert the file to LF line endings.
YAMLlint reports wrong new line character: expected \n. The file uses CRLF. Rewrite it with LF endings, and add a .gitattributes rule if editors keep reintroducing CRLF.
🧰 Tools
🪛 YAMLlint (1.37.1)
[error] 1-1: wrong new line character: expected \n
(new-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 `@codecov.yml` at line 1, Convert codecov.yml from CRLF to LF line endings
while preserving its coverage configuration. Add a .gitattributes rule for the
file only if needed to prevent editors from reintroducing CRLF.
Source: Linters/SAST tools
| } catch (err) { | ||
| await this.quarantine(filePath, raw) | ||
| console.warn("[TaskOrganizationStore] Organization file was malformed and has been quarantined.") | ||
| this.state = createEmptyTaskOrganizationState(this.now) | ||
| return | ||
| } | ||
|
|
||
| const result = taskOrganizationStateSchema.safeParse(parsed) | ||
| if (!result.success) { | ||
| await this.quarantine(filePath, raw) | ||
| console.warn("[TaskOrganizationStore] Organization file failed validation and has been quarantined.") | ||
| this.state = createEmptyTaskOrganizationState(this.now) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Remove the malformed live file after quarantine.
quarantine() only copies the malformed content. The next mutation calls safeUpdateJson(), which parses the same malformed live file and fails before it can write the empty replacement state.
Move the live file into quarantine, or remove it only after a successful quarantine copy. Use the same locking protocol for this recovery path. This lets the next mutation recreate valid state.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/core/task-persistence/TaskOrganizationStore.ts` around lines 281 - 292,
Update the recovery paths in the task-organization loading method, including the
catch block and the result.success validation branch, so the live file is
removed or moved only after quarantine succeeds. Perform this cleanup under the
same locking protocol used by safeUpdateJson, ensuring the next mutation can
recreate valid state from the empty state.
| if (current && current.revision >= next.revision) { | ||
| // Another process wrote the same or a newer revision while we | ||
| // held the lock. Same-revision writes lose: two processes that | ||
| // both computed `next` from the same base must not both commit. | ||
| throw this.createError("TASK_ORG/PERSISTENCE/005", "Concurrent modification detected.") |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Return a revision conflict with current state.
If another instance commits first, this branch throws a persistence error. mutate() then returns TASK_ORG/PERSISTENCE/005 and the stale local this.state.revision.
Reload the committed state after this condition and return TASK_ORG/CONFLICT/002 with the current revision. The webview can then reconcile its optimistic mutation.
🤖 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 `@src/core/task-persistence/TaskOrganizationStore.ts` around lines 320 - 324,
Update the concurrent-modification branch in TaskOrganizationStore’s mutate flow
to reload the committed state after detecting current.revision >= next.revision,
then return TASK_ORG/CONFLICT/002 using the reloaded current revision instead of
propagating TASK_ORG/PERSISTENCE/005 with the stale local revision. Preserve the
existing same-or-newer revision check and reconciliation behavior.
| private resolveTarget(target: TaskOrganizationTargetV1): TaskOrganizationTargetV1 { | ||
| if (target.kind === "task" || target.kind === "folder") { | ||
| return target | ||
| } | ||
| // autoGroup: resolve closure and return canonical root target. | ||
| const closure = this.resolveTaskClosure(target.rootTaskId) | ||
| return { kind: "autoGroup", rootTaskId: closure.rootId } | ||
| } | ||
|
|
||
| private resolveUnit(target: TaskOrganizationTargetV1): string[] { | ||
| switch (target.kind) { | ||
| case "task": { | ||
| // Resolve any known task through its closure. This covers both | ||
| // children and roots that have children, so dragging any group | ||
| // member moves the whole group together. | ||
| if (this.taskHistory?.get(target.taskId)) { | ||
| return this.resolveTaskClosure(target.taskId).ids | ||
| } | ||
| return [target.taskId] | ||
| } | ||
| case "folder": { | ||
| const folder = this.state.folders.find((f) => f.folderId === target.folderId) | ||
| return folder ? [...folder.taskIds] : [] | ||
| } | ||
| case "autoGroup": | ||
| return this.resolveTaskClosure(target.rootTaskId).ids | ||
| default: | ||
| return [] | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Reject targets that do not exist.
resolveTarget() accepts every task and folder target. resolveUnit() also converts an unknown task into a persisted ID. A valid-shape IPC mutation can therefore create phantom pins or folder members until a later reconciliation happens.
When taskHistory is available, reject unknown task and auto-group roots. Always reject unknown folder IDs before mutating state.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/core/task-persistence/TaskOrganizationStore.ts` around lines 584 - 612,
Update resolveTarget and resolveUnit to validate target existence before
returning canonical targets or member IDs: when taskHistory is available, reject
unknown task IDs and autoGroup rootTaskIds; always reject folder targets whose
folderId is absent from state.folders. Ensure invalid targets cannot proceed to
state mutation, using the existing invalid-target handling convention.
| "api/providers/__tests__/mimo.spec.ts": { | ||
| "@typescript-eslint/no-explicit-any": { | ||
| "count": 29 | ||
| } |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Do not increase ESLint suppression counts.
Replace the new any usages with precise test doubles or unknown plus type guards. Restore the prior counts.
src/eslint-suppressions.json#L187-L190: remove the added Mimo test suppression.src/eslint-suppressions.json#L242-L245: remove the added OpenCode Go test suppression.src/eslint-suppressions.json#L257-L260: remove the added Qwen Code native-tools test suppression.
As per coding guidelines, “Suppression counts in src/eslint-suppressions.json must never increase.”
📍 Affects 1 file
src/eslint-suppressions.json#L187-L190(this comment)src/eslint-suppressions.json#L242-L245src/eslint-suppressions.json#L257-L260
🤖 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 `@src/eslint-suppressions.json` around lines 187 - 190, Replace the newly
introduced any usages in the Mimo, OpenCode Go, and Qwen Code native-tools tests
with precise test doubles or unknown plus appropriate type guards, then remove
the added suppression entries from src/eslint-suppressions.json at lines
187-190, 242-245, and 257-260, restoring each prior count without increasing any
suppression count.
Source: Coding guidelines
| <AlertDialogDescription className="text-vscode-foreground"> | ||
| <div className="mb-2">{t("history:confirmDeleteFolders", { count: folderCount })}</div> | ||
| <div className="text-vscode-editor-foreground bg-vscode-editor-background p-2 rounded text-sm"> | ||
| {t("history:deleteFoldersTasksPreserved")} | ||
| </div> | ||
| </AlertDialogDescription> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm the rendered element for AlertDialogDescription in the shared UI wrapper.
fd -a 'alert-dialog.tsx$' webview-ui/src | xargs -r rg -n -C4 'AlertDialogDescription'Repository: Zoo-Code-Org/Zoo-Code
Length of output: 513
🏁 Script executed:
#!/bin/bash
set -e
echo "== candidate files =="
fd -a 'alert-dialog.tsx$|DeleteFoldersDialog.tsx$' webview-ui/src
echo
echo "== dialog implementation around Description =="
fd -a 'alert-dialog.tsx$' webview-ui/src | xargs -r sed -n '60,90p'
echo
echo "== DeleteFoldersDialog around lines 30-80 =="
fd -a 'DeleteFoldersDialog.tsx$' webview-ui/src | xargs -r sed -n '30,80p'
echo
echo "== package metadata versions =="
for f in package.json webview-ui/package.json pnpm-lock.yaml; do
if [ -f "$f" ]; then
echo "--- $f ---"
rg -n '"`@radix-ui/react-alert-dialog`"|version|`@radix-ui/react`' "$f" | head -40 || true
fi
doneRepository: Zoo-Code-Org/Zoo-Code
Length of output: 4954
🌐 Web query:
React validateDOMNesting p div block elements table caption tbody
💡 Result:
The React validateDOMNesting warning is a developer-facing tool designed to flag HTML structure violations that can cause reconciliation errors or inconsistencies between how browsers interpret markup and how React expects the DOM to behave [1][2][3]. For nesting block elements like div inside p tags: The HTML5 specification restricts the p element to contain only phrasing content (e.g., span, em, text), prohibiting block-level elements like div, h1-h6, table, or ul [1][4][5]. React issues this warning because placing a block element inside a p tag creates invalid DOM nesting [1][5]. Fix: Replace the offending block-level tag (e.g., div) with an inline tag (e.g., span) or refactor your component hierarchy so that the div is no longer a descendant of the p element [1][5]. If you are using a third-party library component (like MUI's Typography) that defaults to a p tag, use its component prop (e.g., component="div") to change the rendered HTML element [6][5]. For table, caption, and tbody structure: React warns about missing tbody or incorrect table children (like tr directly under table) because browsers automatically inject these tags during HTML parsing [2][7][3]. If React does not explicitly include them in its virtual DOM, the mismatch between the rendered React tree and the actual browser-modified DOM can break updates or cause hydration failures [7][3][8]. Fix: Explicitly add the necessary tags (e.g., wrap your tr elements in a tbody) to your JSX [2][9][3]. This ensures the React tree perfectly matches the structure the browser will generate, preventing reconciliation issues [7][8]. These warnings are intentional and help maintain DOM stability, especially when using server-side rendering (SSR) or hydration, where DOM mismatches are more critical [7][10][8]. Top results: [1][2][7][9][5][3]
Citations:
- 1: https://stackabuse.com/bytes/fixing-div-cannot-appear-as-a-descendant-of-p-warning-in-react/
- 2: Warning: validateDOMNesting(...): <tr> cannot appear as a child of <table> react/react#5652
- 3: Warning: validateDOMNesting(...): <tr> cannot appear as a child of <table> react/react#5652
- 4: https://stackoverflow.com/questions/41928567/div-cannot-appear-as-a-descendant-of-p
- 5: https://bobbyhadz.com/blog/warning-validatedomnesting-div-cannot-appear-as-descendant-of-p-in-react
- 6: https://stackoverflow.com/questions/73291309/warning-validatedomnesting-p-cannot-appear-as-a-descendant-of-p
- 7: validateDOMNesting incorrectly validates table children react/react#12674
- 8: validateDOMNesting warns when optional tags are missing react/react#4464
- 9: https://stackoverflow.com/questions/55820297/how-to-fix-warning-validatedomnesting-div-cannot-appear-as-a-child-of
- 10: Bug:
validateDOMNestingHydration failed react/react#24519
Avoid block elements inside AlertDialogDescription.
AlertDialogDescription passes through to Radix’s <p> element, so the two <div> children create invalid <p> nesting. Move the blocks outside the description, or render the description as a <div> with asChild.
🤖 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 `@webview-ui/src/components/history/DeleteFoldersDialog.tsx` around lines 42 -
47, Update the AlertDialogDescription usage in DeleteFoldersDialog so it no
longer renders the two div blocks inside Radix’s default p element; either move
those blocks outside the description or configure the description with asChild
and a div wrapper while preserving both translated messages and their styling.
| showPin | ||
| isPinned={isPinned({ kind: "task", taskId: rootId })} | ||
| canPin={canPin} | ||
| onTogglePin={() => void togglePin({ kind: "task", taskId: rootId })} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Pin targets ignore the canonical unit kind in both list sections. buildGroupDndData (Line 60) and selectedTaskTargets (Line 218) map a group with subtasks to { kind: "autoGroup", rootTaskId }, but both pin call sites always build { kind: "task", taskId }. Pin state then disagrees between the drag/selection paths and the pin button, and a unit can hold two differently shaped pins. Build the target once from the group shape and reuse it.
webview-ui/src/components/history/HistoryView.tsx#L416-L419: derive the folder-member pin target frommemberGroup.subtasks.lengthand pass it to bothisPinnedandtogglePin.webview-ui/src/components/history/HistoryView.tsx#L696-L698: derive the unfiled pin target fromgroup.subtasks.length, for example by reusing thetargetfield ofdndDatacomputed at Line 677.
📍 Affects 1 file
webview-ui/src/components/history/HistoryView.tsx#L416-L419(this comment)webview-ui/src/components/history/HistoryView.tsx#L696-L698
🤖 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 `@webview-ui/src/components/history/HistoryView.tsx` around lines 416 - 419,
Use the canonical pin target shape for both history pin-button sections: in
webview-ui/src/components/history/HistoryView.tsx lines 416-419, derive the
target from memberGroup.subtasks.length and reuse it for isPinned and togglePin;
at lines 696-698, derive the unfiled target from group.subtasks.length,
preferably reusing dndData.target. Update the relevant pin call sites without
changing unrelated behavior.
| "pinLimitReached": "Maximum 3 pinned items allowed", | ||
| "pinned": "Pinned", | ||
| "folder": "Folder", | ||
| "tasks": "{{count}} tasks", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm the en locale key shape and the parity test expectations for "tasks".
fd -a 'history.json$' webview-ui/src/i18n/locales | xargs -r rg -n '"tasks'
fd -a 'translation-parity.spec.ts$' webview-ui/src | xargs -r rg -n -C3 'plural|_one|_other'Repository: Zoo-Code-Org/Zoo-Code
Length of output: 2393
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files:"
fd -a 'history.json$|translation-parity.spec.ts$|ManualFolderItem' webview-ui/src/i18n webview-ui/src | sed 's#/home/jailuser/git/##' | sort
echo
echo "Catalan history.json lines 55-75:"
fd -a 'history.json$' webview-ui/src/i18n/locales/ca | xargs sed -n '55,75p'
echo
echo "English history.json lines 65-85:"
fd -a 'history.json$' webview-ui/src/i18n/locales/en | xargs sed -n '65,85p'
echo
echo "Translations parity tests around history tasks:"
fd -a 'translation-parity.spec.ts$' webview-ui/src | xargs rg -n -C4 'history|tasks|selectedFolders|deleteFoldersTitle|plural|one|other|locale' || true
echo
echo "ManualFolderItem references:"
fd -a 'ManualFolderItem' . -t f -e ts -e tsx -e svelte -e vue -e react | while read -r f; do
echo "--- $f"
rg -n -C4 'ManualFolderItem|tasks|selectedFolders|count:' "$f" || true
doneRepository: Zoo-Code-Org/Zoo-Code
Length of output: 11854
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Translation setup files:"
fd -a 'TranslationContext\.(tsx|ts)$|index\.(ts|tsx|js)$|i18n.*\.(json|ts|tsx)$' webview-ui/src | sed 's#/home/jailuser/git/##' | sort
echo
echo "TranslationContext outline/imports/usages:"
for f in $(fd -a 'TranslationContext\.(tsx|ts)$' webview-ui/src); do
echo "--- $f"
sed -n '1,160p' "$f"
echo
done
echo
echo "Package/i18n dependencies:"
if [ -f webview-ui/package.json ]; then
jq '{dependencies, devDependencies} | to_entries[] | select(.key|contains("i18n") or contains("react") or contains("format"))' webview-ui/package.json
else
rg -n '"i18next"|"react-i18next"|"react-i18next-versioned"|"i18next-fs-backend"|"i18next-http-backend"' webview-ui/package.json webview-ui -g 'package.json' -g 'vite.config.*' -g 'i18n*' || true
fiRepository: Zoo-Code-Org/Zoo-Code
Length of output: 4246
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "i18n setup files:"
fd -a 'setup\.(ts|tsx)$|index\.(ts|tsx)$' webview-ui/src/i18n | sed 's#/home/jailuser/git/##' | sort | while read -r f; do
echo "--- $f"
sed -n '1,220p' "$f"
done
echo
echo "ManualFolderItem imports:"
sed -n '1,40p' webview-ui/src/components/history/ManualFolderItem.tsx
echo
echo "Package files containing i18next:"
fd -a 'package.json$' . | while read -r f; do
echo "--- ${f#/home/jailuser/git/}"
jq '.dependencies, .devDependencies' "$f" 2>/dev/null | rg -n '"(i18next|react-i18next|react-i18next-versioned|language-tag|locale-utils)"' || true
doneRepository: Zoo-Code-Org/Zoo-Code
Length of output: 4165
Add Catalan plurals for tasks.
ManualFolderItem calls t("history:tasks", { count: unitCount }). Use _one/_other like the nearby count keys so the singular does not render as "1 tasks".
🌐 Proposed fix
- "tasks": "{{count}} tasks",
+ "tasks_one": "{{count}} tasca",
+ "tasks_other": "{{count}} tasques",📝 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.
| "tasks": "{{count}} tasks", | |
| "tasks_one": "{{count}} tasca", | |
| "tasks_other": "{{count}} tasques", |
🤖 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 `@webview-ui/src/i18n/locales/ca/history.json` at line 68, Add Catalan plural
variants for the history tasks translation by replacing the single tasks entry
with tasks_one and tasks_other, following the nearby count-key convention and
ensuring singular counts render “1 task” while plural counts render “{{count}}
tasks”.
27cfa20 to
8f4160c
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 15
🧹 Nitpick comments (8)
src/core/task-persistence/__tests__/TaskOrganizationStore.spec.ts (1)
19-35: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winTest the real atomic update path for cross-process writes.
The mock replaces
safeUpdateJsonwith an unlocked read-write sequence. At Line 719, the second mutation starts only after the first mutation resolves. This test cannot detect a lost update when two instances read the same revision concurrently.Run this scenario with the real
safeUpdateJsonimplementation and synchronize two mutations so both contend for the same file.As per coding guidelines, run
pnpm --dir src exec eslint --prune-suppressions --max-warnings=0 core/task-persistence/__tests__/TaskOrganizationStore.spec.tsafter the test change.🤖 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 `@src/core/task-persistence/__tests__/TaskOrganizationStore.spec.ts` around lines 19 - 35, Update the test setup around the safeWriteJson/safeUpdateJson mocks to use the real safeUpdateJson implementation, preserving only any necessary unrelated mocking. In the cross-process write test near the second mutation, synchronize two TaskOrganizationStore mutations so they begin concurrently and contend on the same file, allowing the test to detect lost updates. Run the specified eslint command for TaskOrganizationStore.spec.ts after making the change.Source: Coding guidelines
src/core/task-persistence/TaskOrganizationStore.ts (1)
842-871: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winThe watcher fails permanently if the tasks directory does not exist yet.
fsSync.watch(tasksDir, ...)throwsENOENTwhen the directory is absent. On a fresh profile,load()returns early onENOENTand never creates the directory;safeUpdateJson()creates it only at the first mutation. Line 869 catches the throw and logs it, butthis.fsWatcherstaysnullfor the lifetime of the store. Cross-instance reconciliation then never runs until the window reloads.Create the directory before starting the watcher.
♻️ Proposed change
this.getTasksDir() - .then((tasksDir) => { + .then(async (tasksDir) => { if (this.disposed) { return } try { + await fs.mkdir(tasksDir, { recursive: true }) this.fsWatcher = fsSync.watch(tasksDir, { recursive: false }, (_eventType, filename) => {🤖 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 `@src/core/task-persistence/TaskOrganizationStore.ts` around lines 842 - 871, Ensure the tasks directory exists before invoking fsSync.watch in the watcher setup around getTasksDir. Create it recursively when absent, then initialize this.fsWatcher so fresh profiles receive cross-instance updates without requiring a reload.src/utils/__tests__/safeUpdateJson.test.ts (1)
26-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDocument the
as anycasts on the mockedfs/promisesmembers.Each assignment discards the overload signatures of the wrapped function. The comment at Lines 24-25 explains why the functions are wrapped, not why the cast is required. Add one short comment that states the cast exists because
vi.fn()cannot preserve the overloaded Node signatures.As per coding guidelines: "Avoid
as any; use typed APIs... Use double assertions only as a last resort and explain them with a comment."🤖 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 `@src/utils/__tests__/safeUpdateJson.test.ts` around lines 26 - 34, Document the `as any` casts on the mocked `fs/promises` assignments in the test setup by adding one concise comment explaining that `vi.fn()` cannot preserve Node’s overloaded function signatures. Keep the existing wrapped implementations and casts unchanged.Source: Coding guidelines
docs/260805_0001_session_ci-all-green/153500_debug-coverage-b09.md (1)
46-46: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the redundant phrase.
"just barely below" repeats the same idea twice. Use "just below" or "barely below".
🤖 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 `@docs/260805_0001_session_ci-all-green/153500_debug-coverage-b09.md` at line 46, Update the Verdict sentence in the coverage report to replace “just barely below” with either “just below” or “barely below,” leaving the rest of the statement unchanged.Source: Linters/SAST tools
webview-ui/src/components/history/HistoryView.tsx (1)
58-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the unused
groupsparameter.
buildGroupDndDatanever readsgroups; the body only callsvoid groupsto silence the unused-parameter lint. The target is derived fromgroup.subtasks.lengthalone. Drop the parameter and update both call sites (Line 439, Line 723).♻️ Proposed refactor
-function buildGroupDndData(group: TaskGroup, groups: TaskGroup[], folderId?: string): DndItemData { +function buildGroupDndData(group: TaskGroup, folderId?: string): DndItemData { const rootId = group.parent.id const hasChildren = group.subtasks.length > 0 const target: TaskOrganizationTargetV1 = hasChildren ? { kind: "autoGroup", rootTaskId: rootId } : { kind: "task", taskId: rootId } - void groups return { kind: "task", target, folderId, } }🤖 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 `@webview-ui/src/components/history/HistoryView.tsx` around lines 58 - 70, Remove the unused groups parameter and its void groups statement from buildGroupDndData, then update both call sites to pass only the required arguments while preserving the existing target behavior.webview-ui/src/components/history/ManualFolderItem.tsx (2)
309-330: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider disabling the member drop target when drag is disabled.
ManualFolderItemdisables its droppable while editing or in selection mode, andHistoryViewdisablesDraggableTaskEntrywhenisDndEnabledis false.ManualFolderMemberItemregisters its droppable unconditionally. Add an optionaldisabledprop and pass!isDndEnabledfromHistoryViewso all drop targets follow one rule.🤖 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 `@webview-ui/src/components/history/ManualFolderItem.tsx` around lines 309 - 330, Add an optional disabled prop to ManualFolderMemberItem and pass it to useDroppable so the member target is inactive when disabled. Update HistoryView to pass !isDndEnabled, matching the existing ManualFolderItem and DraggableTaskEntry behavior.
332-332: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExport the memoized
ManualFolderItemfrom the named export.Consumers import named
ManualFolderItem, butwebview-ui/src/components/history/ManualFolderItem.tsxonly wraps the default export. If memoization is intended, export the memoized component under the name consumers import; otherwise remove the unused default export.🤖 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 `@webview-ui/src/components/history/ManualFolderItem.tsx` at line 332, Update the export in ManualFolderItem.tsx so the named ManualFolderItem export references the memoized component consumed by callers; alternatively, remove the unused default export if memoization is not intended, while preserving the existing component behavior.webview-ui/src/components/history/__tests__/ManualFolderItem.spec.tsx (1)
145-169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test for the maximum-name-length branch.
validateFolderNamerejects names longer thanMAX_NAME_LENGTH(80). The input setsmaxLength={MAX_NAME_LENGTH + 1}, so an 81-character value is reachable and must showfolder-name-error. No test covers that branch. Add a case next to the empty-name and control-character cases. Also consider a case for Escape, which must callcancelRenameand restore the original name.🤖 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 `@webview-ui/src/components/history/__tests__/ManualFolderItem.spec.tsx` around lines 145 - 169, Add tests alongside the existing folder-name validation cases for an 81-character name, verifying `folder-name-error` appears and `onRename` is not called, and for pressing Escape during rename, verifying `cancelRename` runs and the original name is restored. Reuse the existing `ManualFolderItem` setup and test identifiers.
🤖 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 `@docs/260805_0001_session_ci-all-green/153500_debug-coverage-b09.md`:
- Around line 143-151: Recompute the New Overall Coverage values in the impact
table using the stated 503/636 baseline and cumulative recovered lines. Update
P1 through P4 to 89.2%, 89.5%, 90.4%, and 99.8%, respectively, and revise the
minimum-to-pass-80% statement to reflect that the baseline already falls below
80% while P1 exceeds it.
In `@src/core/task-persistence/TaskOrganizationStore.ts`:
- Around line 636-641: Update the parent traversal in resolveTaskClosure() to
track visited task IDs while walking parentMap, and terminate safely when rootId
repeats instead of allowing the while loop to run indefinitely. Preserve the
existing root resolution behavior for acyclic parent chains and ensure the cycle
guard applies before following each parent.
- Around line 878-887: Wrap the body of reloadFromWatcher in withLock() so the
load, state comparison, and onChange notification execute on the same serialized
queue as mutate(). Preserve the existing previous-state comparison and callback
behavior, but ensure load() cannot reassign this.state concurrently with a
mutation.
In `@src/core/webview/ClineProvider.ts`:
- Around line 255-266: The merge conflict in src/core/webview/ClineProvider.ts
lines 255-266 requires resolving to one reconciliation branch while removing all
conflict markers; preserve the safe optional organizationStore check. At lines
3454-3475, retain only one getModes() declaration and one getProviderProfiles()
declaration, removing duplicates. Run the specified ESLint command against
ClineProvider.ts after resolving both sites.
In `@src/core/webview/taskOrganizationMessageHandler.ts`:
- Around line 62-74: Update handleTaskOrganizationMessage to resolve the task
organization store before the try block, then reuse that reference in both the
mutation path and catch response so error handling does not call
getTaskOrganizationStore() again. Preserve the existing failure response and
committed revision behavior.
In `@src/utils/__tests__/safeUpdateJson.test.ts`:
- Around line 230-233: Update the proper-lockfile mock factory in the
safeUpdateJson test to be async and await vi.importActual("proper-lockfile")
before spreading the actual module, preserving all exports while overriding only
lock. Ensure the importActual Promise is awaited so no floating promise remains.
- Around line 427-443: Rename the test around safeUpdateJson to state that it
logs console.error when backup deletion fails while still completing
successfully. Keep the existing assertion and test behavior unchanged.
In
`@webview-ui/src/components/history/__tests__/HistoryView.taskOrganization.spec.tsx`:
- Around line 138-143: Update the history test harness documentation to state
that it does not invoke the real useTaskOrganizationDnd hook and instead
re-implements drag-end routing. Remove the copied routing assertions and add
equivalent routing coverage to useTaskOrganizationDnd.spec.tsx, exercising the
real hook handlers so regressions in useTaskOrganizationDnd are detected.
- Line 1: Replace the ineffective task-grip absence assertions in the
selection-mode and search-mode tests around TaskGroupItem with assertions that
DraggableTaskEntry receives the disabled state. In ManualFolderItem.spec.tsx,
remove the folder-grip assertion unless ManualFolderItem is intentionally
updated to render that affordance; keep the tests aligned with elements or props
actually produced by the mocked components.
- Around line 689-730: Update the tests so their bodies exercise the behavior
named by each test: in the automatic-group test, use the installed DnD harness
and getHarness().triggerDrop for child-1, then assert the move request includes
{ kind: "autoGroup", rootTaskId: "parent-1" }; in the unfiled-drop-zone test,
add a setup with activeDrag representing a folder member and assert
unfiled-drop-zone is rendered while retaining the existing absent-state
assertion.
- Around line 87-90: Replace the any-based casts and annotations throughout
HistoryView.taskOrganization.spec.tsx with type-safe alternatives: use
vi.mocked(...) for mockUseTaskSearch, mockUseGroupedTasks,
mockUseExtensionState, and mockUseTaskOrganizationDnd so configured fixtures
satisfy each hook’s return type, and replace SpyFn plus the harness locals with
precise mock or test-double types, using unknown and type guards where
necessary.
- Around line 732-760: Update the “disables drag grips while in selection mode”
test to inspect the rendered DraggableTaskEntry wrapper’s disabled state or
disabled-related class instead of asserting the absent task-grip element. Ensure
the assertion verifies isDndEnabled is actually respected during selection mode,
and apply the same disabled behavior to selection/search paths if the wrapper
currently does not receive it.
In `@webview-ui/src/components/history/__tests__/ManualFolderItem.spec.tsx`:
- Around line 236-241: Remove the ineffective folder-grip absence assertion from
the ManualFolderItem test, since ManualFolderItem does not render that test ID
in any mode; leave the other selection-mode assertions unchanged.
In `@webview-ui/src/components/history/__tests__/PinButton.spec.tsx`:
- Around line 45-62: Move timer restoration out of the test body and add an
afterEach teardown that calls vi.useRealTimers(), ensuring fake timers are
cleaned up even when assertions in the limit-error test fail. Remove the inline
vi.useRealTimers() call from the test while preserving the existing timer
advancement and assertions.
In `@webview-ui/src/components/history/TaskOrganizationErrorBoundary.tsx`:
- Around line 13-20: Update the class documentation for
TaskOrganizationErrorBoundary to accurately describe that errors are logged with
console.error and rendering uses the configured fallback, or null when no
fallback is provided, rather than rendering children as-is.
---
Nitpick comments:
In `@docs/260805_0001_session_ci-all-green/153500_debug-coverage-b09.md`:
- Line 46: Update the Verdict sentence in the coverage report to replace “just
barely below” with either “just below” or “barely below,” leaving the rest of
the statement unchanged.
In `@src/core/task-persistence/__tests__/TaskOrganizationStore.spec.ts`:
- Around line 19-35: Update the test setup around the
safeWriteJson/safeUpdateJson mocks to use the real safeUpdateJson
implementation, preserving only any necessary unrelated mocking. In the
cross-process write test near the second mutation, synchronize two
TaskOrganizationStore mutations so they begin concurrently and contend on the
same file, allowing the test to detect lost updates. Run the specified eslint
command for TaskOrganizationStore.spec.ts after making the change.
In `@src/core/task-persistence/TaskOrganizationStore.ts`:
- Around line 842-871: Ensure the tasks directory exists before invoking
fsSync.watch in the watcher setup around getTasksDir. Create it recursively when
absent, then initialize this.fsWatcher so fresh profiles receive cross-instance
updates without requiring a reload.
In `@src/utils/__tests__/safeUpdateJson.test.ts`:
- Around line 26-34: Document the `as any` casts on the mocked `fs/promises`
assignments in the test setup by adding one concise comment explaining that
`vi.fn()` cannot preserve Node’s overloaded function signatures. Keep the
existing wrapped implementations and casts unchanged.
In `@webview-ui/src/components/history/__tests__/ManualFolderItem.spec.tsx`:
- Around line 145-169: Add tests alongside the existing folder-name validation
cases for an 81-character name, verifying `folder-name-error` appears and
`onRename` is not called, and for pressing Escape during rename, verifying
`cancelRename` runs and the original name is restored. Reuse the existing
`ManualFolderItem` setup and test identifiers.
In `@webview-ui/src/components/history/HistoryView.tsx`:
- Around line 58-70: Remove the unused groups parameter and its void groups
statement from buildGroupDndData, then update both call sites to pass only the
required arguments while preserving the existing target behavior.
In `@webview-ui/src/components/history/ManualFolderItem.tsx`:
- Around line 309-330: Add an optional disabled prop to ManualFolderMemberItem
and pass it to useDroppable so the member target is inactive when disabled.
Update HistoryView to pass !isDndEnabled, matching the existing ManualFolderItem
and DraggableTaskEntry behavior.
- Line 332: Update the export in ManualFolderItem.tsx so the named
ManualFolderItem export references the memoized component consumed by callers;
alternatively, remove the unused default export if memoization is not intended,
while preserving the existing component behavior.
🪄 Autofix
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: 2ee0eaed-b7c6-47cc-8a2f-885af5990466
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (94)
docs/260805_0001_session_ci-all-green/152420_code-b12-coverage-tests-report.mddocs/260805_0001_session_ci-all-green/153500_debug-coverage-b09.mdknip.jsonpackages/types/src/index.tspackages/types/src/task-organization.tspackages/types/src/vscode-extension-host.tssrc/core/task-persistence/TaskOrganizationStore.tssrc/core/task-persistence/__tests__/TaskOrganizationStore.spec.tssrc/core/task-persistence/index.tssrc/core/webview/ClineProvider.tssrc/core/webview/__tests__/ClineProvider.taskHistory.spec.tssrc/core/webview/__tests__/taskOrganizationMessageHandler.spec.tssrc/core/webview/taskOrganizationMessageHandler.tssrc/core/webview/webviewMessageHandler.tssrc/eslint-suppressions.jsonsrc/shared/globalFileNames.tssrc/utils/__tests__/safeUpdateJson.test.tssrc/utils/safeWriteJson.tswebview-ui/package.jsonwebview-ui/src/components/history/DeleteFoldersDialog.tsxwebview-ui/src/components/history/DraggableTaskEntry.tsxwebview-ui/src/components/history/FolderNameDialog.tsxwebview-ui/src/components/history/HistoryPreview.tsxwebview-ui/src/components/history/HistoryView.tsxwebview-ui/src/components/history/ManualFolderItem.tsxwebview-ui/src/components/history/PinButton.tsxwebview-ui/src/components/history/PinnedHistoryItem.tsxwebview-ui/src/components/history/SubtaskRow.tsxwebview-ui/src/components/history/TaskGroupItem.tsxwebview-ui/src/components/history/TaskItem.tsxwebview-ui/src/components/history/TaskItemFooter.tsxwebview-ui/src/components/history/TaskOrganizationDndSurface.tsxwebview-ui/src/components/history/TaskOrganizationErrorBoundary.tsxwebview-ui/src/components/history/TaskOrganizationInteractionContext.tsxwebview-ui/src/components/history/TaskOrganizationPointerSensor.tswebview-ui/src/components/history/__tests__/DeleteFoldersDialog.spec.tsxwebview-ui/src/components/history/__tests__/DraggableTaskEntry.spec.tsxwebview-ui/src/components/history/__tests__/HistoryPreview.spec.tsxwebview-ui/src/components/history/__tests__/HistoryPreview.taskOrganization.spec.tsxwebview-ui/src/components/history/__tests__/HistoryView.taskOrganization.spec.tsxwebview-ui/src/components/history/__tests__/ManualFolderItem.spec.tsxwebview-ui/src/components/history/__tests__/PinButton.spec.tsxwebview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationDndSurface.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationErrorBoundary.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationInteractionContext.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.setup.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.spec.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.vitest.config.tswebview-ui/src/components/history/__tests__/useTaskOrganizationDnd.spec.tsxwebview-ui/src/components/history/taskOrganizationModel.tswebview-ui/src/components/history/types.tswebview-ui/src/components/history/useTaskOrganizationDnd.tswebview-ui/src/context/ExtensionStateContext.tsxwebview-ui/src/context/__tests__/ExtensionStateContext.taskOrganization.spec.tsxwebview-ui/src/i18n/__tests__/translation-parity.spec.tswebview-ui/src/i18n/locales/ca/chat.jsonwebview-ui/src/i18n/locales/ca/history.jsonwebview-ui/src/i18n/locales/de/chat.jsonwebview-ui/src/i18n/locales/de/history.jsonwebview-ui/src/i18n/locales/en/chat.jsonwebview-ui/src/i18n/locales/en/history.jsonwebview-ui/src/i18n/locales/es/chat.jsonwebview-ui/src/i18n/locales/es/history.jsonwebview-ui/src/i18n/locales/fr/chat.jsonwebview-ui/src/i18n/locales/fr/history.jsonwebview-ui/src/i18n/locales/hi/chat.jsonwebview-ui/src/i18n/locales/hi/history.jsonwebview-ui/src/i18n/locales/id/chat.jsonwebview-ui/src/i18n/locales/id/history.jsonwebview-ui/src/i18n/locales/it/chat.jsonwebview-ui/src/i18n/locales/it/history.jsonwebview-ui/src/i18n/locales/ja/chat.jsonwebview-ui/src/i18n/locales/ja/history.jsonwebview-ui/src/i18n/locales/ko/chat.jsonwebview-ui/src/i18n/locales/ko/history.jsonwebview-ui/src/i18n/locales/nl/chat.jsonwebview-ui/src/i18n/locales/nl/history.jsonwebview-ui/src/i18n/locales/pl/chat.jsonwebview-ui/src/i18n/locales/pl/history.jsonwebview-ui/src/i18n/locales/pt-BR/chat.jsonwebview-ui/src/i18n/locales/pt-BR/history.jsonwebview-ui/src/i18n/locales/ru/chat.jsonwebview-ui/src/i18n/locales/ru/history.jsonwebview-ui/src/i18n/locales/tr/chat.jsonwebview-ui/src/i18n/locales/tr/history.jsonwebview-ui/src/i18n/locales/vi/chat.jsonwebview-ui/src/i18n/locales/vi/history.jsonwebview-ui/src/i18n/locales/zh-CN/chat.jsonwebview-ui/src/i18n/locales/zh-CN/history.jsonwebview-ui/src/i18n/locales/zh-TW/chat.jsonwebview-ui/src/i18n/locales/zh-TW/history.jsonwebview-ui/vitest.setup.ts
💤 Files with no reviewable changes (1)
- webview-ui/src/components/history/tests/TaskItemFooter.spec.tsx
🚧 Files skipped from review as they are similar to previous changes (77)
- webview-ui/src/i18n/locales/ca/chat.json
- webview-ui/src/i18n/locales/ko/history.json
- webview-ui/src/i18n/locales/es/chat.json
- webview-ui/src/i18n/locales/it/chat.json
- webview-ui/src/i18n/locales/ja/chat.json
- webview-ui/src/i18n/locales/pt-BR/chat.json
- webview-ui/src/i18n/locales/vi/chat.json
- webview-ui/src/i18n/locales/nl/chat.json
- webview-ui/src/i18n/locales/zh-TW/chat.json
- webview-ui/src/i18n/locales/de/history.json
- src/core/task-persistence/index.ts
- webview-ui/src/components/history/tests/taskOrganizationModel.vitest.config.ts
- webview-ui/src/components/history/tests/TaskOrganizationInteractionContext.spec.tsx
- webview-ui/src/i18n/locales/zh-TW/history.json
- webview-ui/src/i18n/locales/fr/chat.json
- webview-ui/src/context/tests/ExtensionStateContext.taskOrganization.spec.tsx
- webview-ui/src/i18n/locales/fr/history.json
- webview-ui/src/i18n/locales/ru/chat.json
- webview-ui/src/components/history/tests/taskOrganizationModel.setup.ts
- webview-ui/src/components/history/tests/DeleteFoldersDialog.spec.tsx
- src/shared/globalFileNames.ts
- webview-ui/src/i18n/locales/pl/history.json
- webview-ui/package.json
- webview-ui/src/components/history/tests/TaskOrganizationDndSurface.spec.tsx
- webview-ui/src/components/history/tests/DraggableTaskEntry.spec.tsx
- webview-ui/src/i18n/locales/en/chat.json
- webview-ui/src/components/history/TaskGroupItem.tsx
- webview-ui/src/i18n/locales/ru/history.json
- webview-ui/src/i18n/locales/vi/history.json
- webview-ui/src/i18n/locales/zh-CN/history.json
- webview-ui/src/i18n/locales/nl/history.json
- webview-ui/src/components/history/tests/HistoryPreview.spec.tsx
- webview-ui/vitest.setup.ts
- webview-ui/src/components/history/tests/useTaskOrganizationDnd.spec.tsx
- webview-ui/src/i18n/locales/tr/chat.json
- webview-ui/src/components/history/TaskItemFooter.tsx
- webview-ui/src/components/history/tests/TaskOrganizationErrorBoundary.spec.tsx
- webview-ui/src/i18n/locales/ko/chat.json
- webview-ui/src/i18n/locales/it/history.json
- packages/types/src/index.ts
- webview-ui/src/components/history/DeleteFoldersDialog.tsx
- webview-ui/src/i18n/locales/es/history.json
- webview-ui/src/i18n/locales/ja/history.json
- webview-ui/src/i18n/locales/de/chat.json
- webview-ui/src/components/history/TaskOrganizationPointerSensor.ts
- webview-ui/src/components/history/types.ts
- webview-ui/src/components/history/tests/TaskOrganizationPointerSensor.spec.ts
- webview-ui/src/i18n/locales/hi/chat.json
- knip.json
- packages/types/src/vscode-extension-host.ts
- webview-ui/src/components/history/FolderNameDialog.tsx
- webview-ui/src/i18n/locales/pl/chat.json
- webview-ui/src/i18n/locales/zh-CN/chat.json
- webview-ui/src/components/history/PinnedHistoryItem.tsx
- webview-ui/src/i18n/locales/en/history.json
- webview-ui/src/i18n/locales/ca/history.json
- webview-ui/src/i18n/locales/id/chat.json
- webview-ui/src/i18n/locales/id/history.json
- webview-ui/src/components/history/tests/taskOrganizationModel.spec.ts
- webview-ui/src/components/history/TaskOrganizationDndSurface.tsx
- webview-ui/src/i18n/locales/pt-BR/history.json
- webview-ui/src/i18n/locales/tr/history.json
- webview-ui/src/components/history/PinButton.tsx
- packages/types/src/task-organization.ts
- webview-ui/src/components/history/DraggableTaskEntry.tsx
- webview-ui/src/components/history/useTaskOrganizationDnd.ts
- webview-ui/src/context/ExtensionStateContext.tsx
- webview-ui/src/components/history/HistoryPreview.tsx
- webview-ui/src/i18n/locales/hi/history.json
- webview-ui/src/components/history/TaskOrganizationInteractionContext.tsx
- src/core/webview/tests/taskOrganizationMessageHandler.spec.ts
- webview-ui/src/components/history/TaskItem.tsx
- src/core/webview/webviewMessageHandler.ts
- src/eslint-suppressions.json
- webview-ui/src/components/history/SubtaskRow.tsx
- src/core/webview/tests/ClineProvider.taskHistory.spec.ts
- webview-ui/src/components/history/taskOrganizationModel.ts
| | Fix Priority | Lines Recovered | New Overall Coverage | | ||
| | ------------------------- | --------------- | ------------------------- | | ||
| | Current | 0 | 70.3% (src) / 79.1% (all) | | ||
| | P1: safeUpdateJson | ~64 | ~80.3% (all) | | ||
| | P2: webviewMessageHandler | +2 | ~80.6% (all) | | ||
| | P3: ClineProvider | +6 | ~81.5% (all) | | ||
| | P4: TaskOrganizationStore | +60 | ~90.4% (all) | | ||
|
|
||
| **Minimum to pass 80%**: P1 alone (safeUpdateJson tests) should bring combined coverage above the threshold. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
The impact table understates coverage after P1.
The baseline is 503 covered of 636 instrumented lines (79.1%). Adding 64 covered lines gives 567/636 = 89.2%, not 80.3%. Line 101 already states "+10% to overall", which matches 89.2% and contradicts the table. Every row below P1 inherits the same ~9-point error. Recompute the column.
Corrected values: P1 → 567/636 = 89.2%; P2 → 569/636 = 89.5%; P3 → 575/636 = 90.4%; P4 → 635/636 = 99.8%.
🔢 Proposed fix
| Fix Priority | Lines Recovered | New Overall Coverage |
| ------------------------- | --------------- | ------------------------- |
| Current | 0 | 70.3% (src) / 79.1% (all) |
-| P1: safeUpdateJson | ~64 | ~80.3% (all) |
-| P2: webviewMessageHandler | +2 | ~80.6% (all) |
-| P3: ClineProvider | +6 | ~81.5% (all) |
-| P4: TaskOrganizationStore | +60 | ~90.4% (all) |
+| P1: safeUpdateJson | ~64 | ~89.2% (all) |
+| P2: webviewMessageHandler | +2 | ~89.5% (all) |
+| P3: ClineProvider | +6 | ~90.4% (all) |
+| P4: TaskOrganizationStore | +60 | ~99.8% (all) |📝 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.
| | Fix Priority | Lines Recovered | New Overall Coverage | | |
| | ------------------------- | --------------- | ------------------------- | | |
| | Current | 0 | 70.3% (src) / 79.1% (all) | | |
| | P1: safeUpdateJson | ~64 | ~80.3% (all) | | |
| | P2: webviewMessageHandler | +2 | ~80.6% (all) | | |
| | P3: ClineProvider | +6 | ~81.5% (all) | | |
| | P4: TaskOrganizationStore | +60 | ~90.4% (all) | | |
| **Minimum to pass 80%**: P1 alone (safeUpdateJson tests) should bring combined coverage above the threshold. | |
| | Fix Priority | Lines Recovered | New Overall Coverage | | |
| | ------------------------- | --------------- | ------------------------- | | |
| | Current | 0 | 70.3% (src) / 79.1% (all) | | |
| | P1: safeUpdateJson | ~64 | ~89.2% (all) | | |
| | P2: webviewMessageHandler | +2 | ~89.5% (all) | | |
| | P3: ClineProvider | +6 | ~90.4% (all) | | |
| | P4: TaskOrganizationStore | +60 | ~99.8% (all) | | |
| **Minimum to pass 80%**: P1 alone (safeUpdateJson tests) should bring combined coverage above the threshold. |
🤖 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 `@docs/260805_0001_session_ci-all-green/153500_debug-coverage-b09.md` around
lines 143 - 151, Recompute the New Overall Coverage values in the impact table
using the stated 503/636 baseline and cumulative recovered lines. Update P1
through P4 to 89.2%, 89.5%, 90.4%, and 99.8%, respectively, and revise the
minimum-to-pass-80% statement to reflect that the baseline already falls below
80% while P1 exceeds it.
| let rootId = startTaskId | ||
| while (true) { | ||
| const parent = parentMap.get(rootId) | ||
| if (!parent) break | ||
| rootId = parent | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Guard the parent walk against cycles.
The descendant walk at Line 645 tracks visited IDs, but this parent walk does not. If parentTaskId values in history form a cycle, while (true) never terminates. resolveTaskClosure() runs inside withLock(), so a hang blocks the extension host and permanently stalls every later mutation and reconciliation.
🐛 Proposed fix
// Walk to the highest known root.
let rootId = startTaskId
+ const seenAncestors = new Set<string>([rootId])
while (true) {
const parent = parentMap.get(rootId)
if (!parent) break
+ if (seenAncestors.has(parent)) break
+ seenAncestors.add(parent)
rootId = parent
}📝 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.
| let rootId = startTaskId | |
| while (true) { | |
| const parent = parentMap.get(rootId) | |
| if (!parent) break | |
| rootId = parent | |
| } | |
| let rootId = startTaskId | |
| const seenAncestors = new Set<string>([rootId]) | |
| while (true) { | |
| const parent = parentMap.get(rootId) | |
| if (!parent) break | |
| if (seenAncestors.has(parent)) break | |
| seenAncestors.add(parent) | |
| rootId = parent | |
| } |
🤖 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 `@src/core/task-persistence/TaskOrganizationStore.ts` around lines 636 - 641,
Update the parent traversal in resolveTaskClosure() to track visited task IDs
while walking parentMap, and terminate safely when rootId repeats instead of
allowing the while loop to run indefinitely. Preserve the existing root
resolution behavior for acyclic parent chains and ensure the cycle guard applies
before following each parent.
| private async reloadFromWatcher(): Promise<void> { | ||
| const previous = this.state | ||
| await this.load() | ||
| // Notify on any actual content change, not just a revision increase: | ||
| // a same-revision overwrite (lost update from another process) | ||
| // changes the aggregate without bumping its revision. | ||
| if (this.stateHasChanged(previous, this.state) && this.onChange) { | ||
| await this.onChange(this.getState()) | ||
| } | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Serialize watcher reloads with the write lock.
reloadFromWatcher() runs from a setTimeout callback and calls load() outside withLock(). load() reassigns this.state. mutate() reads this.state.revision at Line 176 and then awaits applyMutation(), which clones this.state at Line 363. Several await points separate those two reads, so a watcher reload can replace this.state in between. The mutation is then computed from a different base than the one the revision check validated.
Wrap the reload in withLock() so reloads and mutations serialize on the same queue.
🐛 Proposed fix
private async reloadFromWatcher(): Promise<void> {
- const previous = this.state
- await this.load()
- // Notify on any actual content change, not just a revision increase:
- // a same-revision overwrite (lost update from another process)
- // changes the aggregate without bumping its revision.
- if (this.stateHasChanged(previous, this.state) && this.onChange) {
- await this.onChange(this.getState())
- }
+ return this.withLock(async () => {
+ const previous = this.state
+ await this.load()
+ // Notify on any actual content change, not just a revision increase:
+ // a same-revision overwrite (lost update from another process)
+ // changes the aggregate without bumping its revision.
+ if (this.stateHasChanged(previous, this.state) && this.onChange) {
+ await this.onChange(this.getState())
+ }
+ })
}📝 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.
| private async reloadFromWatcher(): Promise<void> { | |
| const previous = this.state | |
| await this.load() | |
| // Notify on any actual content change, not just a revision increase: | |
| // a same-revision overwrite (lost update from another process) | |
| // changes the aggregate without bumping its revision. | |
| if (this.stateHasChanged(previous, this.state) && this.onChange) { | |
| await this.onChange(this.getState()) | |
| } | |
| } | |
| private async reloadFromWatcher(): Promise<void> { | |
| return this.withLock(async () => { | |
| const previous = this.state | |
| await this.load() | |
| // Notify on any actual content change, not just a revision increase: | |
| // a same-revision overwrite (lost update from another process) | |
| // changes the aggregate without bumping its revision. | |
| if (this.stateHasChanged(previous, this.state) && this.onChange) { | |
| await this.onChange(this.getState()) | |
| } | |
| }) | |
| } |
🤖 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 `@src/core/task-persistence/TaskOrganizationStore.ts` around lines 878 - 887,
Wrap the body of reloadFromWatcher in withLock() so the load, state comparison,
and onChange notification execute on the same serialized queue as mutate().
Preserve the existing previous-state comparison and callback behavior, but
ensure load() cannot reassign this.state concurrently with a mutation.
| <<<<<<< HEAD | ||
| // The organization store is assigned immediately after the | ||
| // history store below; a history write landing in that | ||
| // window must not throw a TypeError dereferencing the | ||
| // not-yet-assigned field. | ||
| const organizationStore: TaskOrganizationStore | undefined = this.taskOrganizationStore | ||
| if (organizationStore) { | ||
| await organizationStore.reconcile() | ||
| } | ||
| ======= | ||
| await this.taskOrganizationStore.reconcile() | ||
| >>>>>>> 33449b51f (feat(task-org-ipc): add task organization IPC message handler and provider state assembly) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Resolve the incomplete merge before release.
The file contains Git conflict markers and duplicate method declarations. TypeScript cannot parse this file.
src/core/webview/ClineProvider.ts#L255-L266: retain one reconciliation branch and remove all conflict markers.src/core/webview/ClineProvider.ts#L3454-L3475: retain onegetModes()declaration and onegetProviderProfiles()declaration.
As per coding guidelines, run pnpm --dir src exec eslint --prune-suppressions --max-warnings=0 core/webview/ClineProvider.ts after the merge resolution.
🧰 Tools
🪛 Biome (2.5.6)
[error] 255-255: Expected a statement but instead found '<<<<<<< HEAD'.
(parse)
[error] 264-266: Expected a statement but instead found '=======
await this.taskOrganizationStore.reconcile()
33449b5 (feat(task-org-ipc): add task organization IPC message handler and provider state assembly)'.
(parse)
[error] 266-266: numbers cannot be followed by identifiers directly after
(parse)
📍 Affects 1 file
src/core/webview/ClineProvider.ts#L255-L266(this comment)src/core/webview/ClineProvider.ts#L3454-L3475
🤖 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 `@src/core/webview/ClineProvider.ts` around lines 255 - 266, The merge conflict
in src/core/webview/ClineProvider.ts lines 255-266 requires resolving to one
reconciliation branch while removing all conflict markers; preserve the safe
optional organizationStore check. At lines 3454-3475, retain only one getModes()
declaration and one getProviderProfiles() declaration, removing duplicates. Run
the specified ESLint command against ClineProvider.ts after resolving both
sites.
Sources: Coding guidelines, Linters/SAST tools
| await provider.postMessageToWebview({ | ||
| type: "taskOrganizationMutationResult", | ||
| requestId: request.requestId, | ||
| taskOrganizationMutationResult: { | ||
| requestId: request.requestId, | ||
| success: false, | ||
| committedRevision: provider.getTaskOrganizationStore().getState().revision, | ||
| error: { | ||
| code: "TASK_ORG/PERSISTENCE/005", | ||
| message: "Organization data could not be saved.", | ||
| }, | ||
| }, | ||
| } satisfies Partial<ExtensionMessage>) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
The catch block repeats the call that may have thrown.
Line 49 calls provider.getTaskOrganizationStore(). If that call throws, control reaches this catch, and Line 68 calls it again. It throws a second time, the rejection escapes handleTaskOrganizationMessage(), and no error result reaches the webview. The caller at src/core/webview/webviewMessageHandler.ts Line 834 awaits this function, so the rejection propagates into the message dispatch path.
Resolve the store once before the try block, or use request.baseRevision as the fallback revision.
🐛 Proposed fix
await provider.postMessageToWebview({
type: "taskOrganizationMutationResult",
requestId: request.requestId,
taskOrganizationMutationResult: {
requestId: request.requestId,
success: false,
- committedRevision: provider.getTaskOrganizationStore().getState().revision,
+ committedRevision: request.baseRevision,
error: {
code: "TASK_ORG/PERSISTENCE/005",
message: "Organization data could not be saved.",
},
},
} satisfies Partial<ExtensionMessage>)📝 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.
| await provider.postMessageToWebview({ | |
| type: "taskOrganizationMutationResult", | |
| requestId: request.requestId, | |
| taskOrganizationMutationResult: { | |
| requestId: request.requestId, | |
| success: false, | |
| committedRevision: provider.getTaskOrganizationStore().getState().revision, | |
| error: { | |
| code: "TASK_ORG/PERSISTENCE/005", | |
| message: "Organization data could not be saved.", | |
| }, | |
| }, | |
| } satisfies Partial<ExtensionMessage>) | |
| await provider.postMessageToWebview({ | |
| type: "taskOrganizationMutationResult", | |
| requestId: request.requestId, | |
| taskOrganizationMutationResult: { | |
| requestId: request.requestId, | |
| success: false, | |
| committedRevision: request.baseRevision, | |
| error: { | |
| code: "TASK_ORG/PERSISTENCE/005", | |
| message: "Organization data could not be saved.", | |
| }, | |
| }, | |
| } satisfies Partial<ExtensionMessage>) |
🤖 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 `@src/core/webview/taskOrganizationMessageHandler.ts` around lines 62 - 74,
Update handleTaskOrganizationMessage to resolve the task organization store
before the try block, then reuse that reference in both the mutation path and
catch response so error handling does not call getTaskOrganizationStore() again.
Preserve the existing failure response and committed revision behavior.
| it("resolves an automatic-group child drop to its canonical root", () => { | ||
| mockUseExtensionState.mockReturnValue({ | ||
| taskOrganization: createEmptyOrganizationState(), | ||
| mutateTaskOrganization: vi.fn().mockResolvedValue({ | ||
| requestId: "", | ||
| success: true, | ||
| committedRevision: 1, | ||
| }), | ||
| cwd: "/test/workspace", | ||
| }) | ||
|
|
||
| const spies = { | ||
| onRequestCreateFolder: vi.fn(), | ||
| onRequestMoveToFolder: vi.fn(), | ||
| onRequestRemoveFromFolder: vi.fn(), | ||
| } | ||
| installDndHarness(spies) | ||
|
|
||
| const parent = makeTask("parent-1") | ||
| const child = makeTask("child-1", { parentTaskId: "parent-1" }) | ||
| const solo = makeTask("solo-1") | ||
|
|
||
| mockUseTaskSearch.mockReturnValue({ | ||
| ...defaultSearchResult, | ||
| tasks: [parent, child, solo], | ||
| }) | ||
| mockUseGroupedTasks.mockReturnValue({ | ||
| groups: [ | ||
| makeGroup(parent, [{ item: { ...child, isSubtask: true }, children: [], isExpanded: false }]), | ||
| makeGroup(solo), | ||
| ], | ||
| flatTasks: null, | ||
| toggleExpand: vi.fn(), | ||
| isSearchMode: false, | ||
| }) | ||
|
|
||
| render(<HistoryView onDone={vi.fn()} />) | ||
|
|
||
| // The parent group draggable must carry the autoGroup target. | ||
| const parentEntry = screen.getByTestId("draggable-entry-unfiled-unit-parent-1") | ||
| expect(parentEntry).toBeInTheDocument() | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Two test names describe behavior the bodies never exercise.
Line 689, "resolves an automatic-group child drop to its canonical root": the body performs no drop. It only asserts that draggable-entry-unfiled-unit-parent-1 exists. installDndHarness(spies) at Line 705 is called and never used. Drive a drop with getHarness().triggerDrop for the child task, then assert that the request carries { kind: "autoGroup", rootTaskId: "parent-1" }.
Line 871, "shows the unfiled drop zone only while a folder member is being dragged": the body only asserts the zone is absent. Add a case that sets activeDrag to a folder member and asserts unfiled-drop-zone appears.
Also applies to: 871-896
🤖 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
`@webview-ui/src/components/history/__tests__/HistoryView.taskOrganization.spec.tsx`
around lines 689 - 730, Update the tests so their bodies exercise the behavior
named by each test: in the automatic-group test, use the installed DnD harness
and getHarness().triggerDrop for child-1, then assert the move request includes
{ kind: "autoGroup", rootTaskId: "parent-1" }; in the unfiled-drop-zone test,
add a setup with activeDrag representing a folder member and assert
unfiled-drop-zone is rendered while retaining the existing absent-state
assertion.
| expect(screen.getByTestId("folder-select-f1")).toBeInTheDocument() | ||
| expect(screen.queryByTestId("folder-grip")).not.toBeInTheDocument() | ||
| expect(screen.queryByTestId("folder-pin-button")).not.toBeInTheDocument() | ||
| expect(screen.queryByTestId("folder-rename-button")).not.toBeInTheDocument() | ||
| expect(screen.queryByTestId("folder-options-menu")).not.toBeInTheDocument() | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
The folder-grip assertion cannot fail.
ManualFolderItem renders no element with data-testid="folder-grip" in any mode. Line 237 therefore passes for every input. Remove it, or add the grip to the component if selection mode is meant to hide one.
🤖 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 `@webview-ui/src/components/history/__tests__/ManualFolderItem.spec.tsx` around
lines 236 - 241, Remove the ineffective folder-grip absence assertion from the
ManualFolderItem test, since ManualFolderItem does not render that test ID in
any mode; leave the other selection-mode assertions unchanged.
| it("shows limit error feedback and does not call onToggle when pin is blocked", () => { | ||
| vi.useFakeTimers() | ||
| const onToggle = vi.fn() | ||
| render(<PinButton isPinned={false} canPin={false} onToggle={onToggle} />) | ||
|
|
||
| const button = screen.getByTestId("pin-button") | ||
| fireEvent.click(button) | ||
|
|
||
| expect(onToggle).not.toHaveBeenCalled() | ||
| expect(button).toHaveAttribute("data-limit-error", "true") | ||
|
|
||
| act(() => { | ||
| vi.advanceTimersByTime(1600) | ||
| }) | ||
|
|
||
| expect(button).toHaveAttribute("data-limit-error", "false") | ||
| vi.useRealTimers() | ||
| }) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Restore real timers in a teardown hook.
vi.useRealTimers() runs on the last line of the test body. If an assertion at Line 53, Line 54, or Line 60 throws, that line never runs, and fake timers leak into every later test in the file and the worker. Move the restore into afterEach.
💚 Proposed fix
describe("PinButton", () => {
+ afterEach(() => {
+ vi.useRealTimers()
+ })
+
it("renders an unpinned state", () => { expect(button).toHaveAttribute("data-limit-error", "false")
- vi.useRealTimers()
})🤖 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 `@webview-ui/src/components/history/__tests__/PinButton.spec.tsx` around lines
45 - 62, Move timer restoration out of the test body and add an afterEach
teardown that calls vi.useRealTimers(), ensuring fake timers are cleaned up even
when assertions in the limit-error test fail. Remove the inline
vi.useRealTimers() call from the test while preserving the existing timer
advancement and assertions.
| /** | ||
| * Swallows errors thrown by the task-organization feature (pin, folder, DnD) | ||
| * so that a failure in the new code never breaks the existing Virtuoso | ||
| * rendering pipeline. | ||
| * | ||
| * On error the boundary logs a warning and renders children as-is (i.e. the | ||
| * new feature is silently disabled rather than crashing the whole view). | ||
| */ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
The doc comment contradicts the implementation.
The comment states that the boundary "logs a warning and renders children as-is". componentDidCatch calls console.error, and render returns this.props.fallback ?? null instead of the children. Update the comment to describe the fallback behavior.
📝 Proposed fix
/**
* Swallows errors thrown by the task-organization feature (pin, folder, DnD)
* so that a failure in the new code never breaks the existing Virtuoso
* rendering pipeline.
*
- * On error the boundary logs a warning and renders children as-is (i.e. the
- * new feature is silently disabled rather than crashing the whole view).
+ * On error the boundary logs the error and unmounts the failing subtree. It
+ * renders the `fallback` prop when one is supplied, otherwise `null`.
+ * The boundary does not reset; the fallback stays mounted for its lifetime.
*/📝 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.
| /** | |
| * Swallows errors thrown by the task-organization feature (pin, folder, DnD) | |
| * so that a failure in the new code never breaks the existing Virtuoso | |
| * rendering pipeline. | |
| * | |
| * On error the boundary logs a warning and renders children as-is (i.e. the | |
| * new feature is silently disabled rather than crashing the whole view). | |
| */ | |
| /** | |
| * Swallows errors thrown by the task-organization feature (pin, folder, DnD) | |
| * so that a failure in the new code never breaks the existing Virtuoso | |
| * rendering pipeline. | |
| * | |
| * On error the boundary logs the error and unmounts the failing subtree. It | |
| * renders the `fallback` prop when one is supplied, otherwise `null`. | |
| * The boundary does not reset; the fallback stays mounted for its lifetime. | |
| */ |
🤖 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 `@webview-ui/src/components/history/TaskOrganizationErrorBoundary.tsx` around
lines 13 - 20, Update the class documentation for TaskOrganizationErrorBoundary
to accurately describe that errors are logged with console.error and rendering
uses the configured fallback, or null when no fallback is provided, rather than
rendering children as-is.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (7)
webview-ui/src/components/history/__tests__/TaskItem.coverage.spec.tsx (1)
23-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAnnotate the fixture with
HistoryItem.
mockTaskis an untyped literal. The other new spec files in this PR type their fixtures againstHistoryItem. An annotation catches fixture drift when the type changes.♻️ Proposed annotation
+import type { HistoryItem } from "`@roo-code/types`" + -const mockTask = { +const mockTask: HistoryItem = {🤖 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 `@webview-ui/src/components/history/__tests__/TaskItem.coverage.spec.tsx` around lines 23 - 32, Annotate the mockTask fixture with the HistoryItem type, matching the typed fixtures used by the other spec files and preserving its existing values.webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx (2)
366-381: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the preview limit in the draggable entries test.
The test confirms that four draggable entries render. It does not confirm that the fifth and sixth tasks are excluded. Add negative assertions so the test fails if the preview limit changes.
♻️ Proposed additional assertions
expect(screen.getByTestId("draggable-entry-preview-task-4")).toBeInTheDocument() + expect(screen.queryByTestId("draggable-entry-preview-task-5")).not.toBeInTheDocument() + expect(screen.queryByTestId("draggable-entry-preview-task-6")).not.toBeInTheDocument()🤖 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 `@webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx` around lines 366 - 381, Add negative assertions to the “wraps unfiled tasks in draggable entries” test verifying that draggable entries for task 5 and task 6 are not present, while preserving the existing assertions for tasks 1–4.
164-174: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExtract a helper for the organization context value.
The same object literal is repeated in
beforeEachand in five tests. Onlyorganizationdiffers. A helper reduces the duplication and keeps future context additions in one place.♻️ Proposed helper
+function createOrganizationContext( + organization: TaskOrganizationStateV1, + overrides: Record<string, unknown> = {}, +) { + return { + organization, + isPinned: () => false, + canPin: true, + togglePin: vi.fn(), + createFolder: vi.fn(), + renameFolder: vi.fn(), + deleteFolder: vi.fn(), + moveToFolder: vi.fn(), + removeFromFolder: vi.fn(), + ...overrides, + } +}🤖 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 `@webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx` around lines 164 - 174, Extract a shared helper for the organization context value used by mockUseTaskOrganization in beforeEach and the five tests, keeping all common callbacks and flags centralized while accepting organization as the varying input. Replace each repeated object literal with calls to this helper and preserve the existing organization values and mocked behavior.webview-ui/src/context/__tests__/ExtensionStateContext.messageHandler.spec.tsx (2)
590-592: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAwait the asynchronous click inside
act.The
onClickhandler at lines 563-568 isasync.actreceives a synchronous callback here, so the promise resolves outside theactscope. The followingwaitForhides the effect, but React can still emit an act warning. Use an asyncactcallback.♻️ Proposed change
- act(() => { - screen.getByTestId("mutate-btn").click() - }) + await act(async () => { + screen.getByTestId("mutate-btn").click() + })🤖 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 `@webview-ui/src/context/__tests__/ExtensionStateContext.messageHandler.spec.tsx` around lines 590 - 592, Update the test around the mutate-btn click to use an asynchronous act callback and await the button’s async click handler before proceeding to waitFor. Preserve the existing mutation assertions and test flow while ensuring all state updates triggered by the handler remain within act.
361-367: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReplace the
anyannotations with a partial state type.
prev,staleState,newState, andfreshStateare annotatedanyin all fourmergeExtensionStatetests. The coding guidelines direct you to avoidanyescape hatches and use typed APIs.Partial<ExtensionState>keeps the fixtures small and still checks thetaskOrganizationshape. IfmergeExtensionStaterequires a full state object, add a small factory instead.♻️ Proposed typing
- const prev: any = { + const prev: Partial<ExtensionStateContextType> = { taskOrganization: makeSnapshot(5), } - const staleState: any = { + const staleState: Partial<ExtensionStateContextType> = { taskOrganization: makeSnapshot(2), }🤖 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 `@webview-ui/src/context/__tests__/ExtensionStateContext.messageHandler.spec.tsx` around lines 361 - 367, Replace the any annotations on prev, staleState, newState, and freshState in all four mergeExtensionState tests with Partial<ExtensionState>. Preserve the minimal taskOrganization fixtures and ensure their shape remains type-checked; if mergeExtensionState requires complete state objects, provide a small typed factory rather than using any.Source: Coding guidelines
webview-ui/src/components/history/__tests__/HistoryView.coverage.spec.tsx (2)
600-621: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThe test name does not match what the test verifies.
The
describeblock is named "delete task dialog" and the test is named "opens delete task dialog when delete is triggered". The body only asserts that the Virtuoso container renders. The inline comment states that the delete button cannot be reached becauseTaskGroupItemis mocked.Choose one of two options:
- Extend the
TaskGroupItemmock to expose anonDeletetrigger, then assert that the dialog opens.- Rename the test to describe the render assertion it performs.
🤖 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 `@webview-ui/src/components/history/__tests__/HistoryView.coverage.spec.tsx` around lines 600 - 621, Align the “delete task dialog” test with its actual coverage: either extend the TaskGroupItem mock to invoke onDelete and assert the dialog opens, or rename the describe block and test to describe verifying the Virtuoso container renders. Prefer the option that preserves the existing mock scope without adding unnecessary behavior.
594-596: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the mutation payload for folder creation.
expect(mutateSpy).toHaveBeenCalled()passes for any mutation kind. The sibling test at lines 544-549 asserts the fulldeleteFolderspayload. Assert thecreateFolderFromSelectionkind, the folder name, and the selected targets so the test detects a wrong mutation.💚 Proposed assertion
await waitFor(() => { - expect(mutateSpy).toHaveBeenCalled() + expect(mutateSpy).toHaveBeenCalledWith( + expect.objectContaining({ + kind: "createFolderFromSelection", + name: "New Folder", + targets: [ + { kind: "task", taskId: "t1" }, + { kind: "task", taskId: "t2" }, + ], + }), + ) })🤖 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 `@webview-ui/src/components/history/__tests__/HistoryView.coverage.spec.tsx` around lines 594 - 596, Update the mutation assertion in the folder-creation test around mutateSpy to verify the full createFolderFromSelection payload: assert the mutation kind, the created folder name, and the selected targets, matching the sibling deleteFolders test’s payload-specific style instead of only checking that a mutation occurred.
🤖 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
`@webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx`:
- Around line 54-57: Replace the four as any hook casts in
webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx:54-57
with vi.mocked() calls. In
webview-ui/src/components/history/__tests__/HistoryView.coverage.spec.tsx:82-85,
make the same change for useTaskSearch, useGroupedTasks, useExtensionState, and
useTaskOrganizationDnd, and explicitly type the entry parameter in the
react-virtuoso mock at line 20 instead of using any.
In `@webview-ui/src/components/history/__tests__/HistoryView.coverage.spec.tsx`:
- Around line 242-266: The test “clears selection via clear button in action
bar” needs to verify the clear action’s result. After clicking the clear button,
assert that the selection state is cleared, such as confirming the selection
action bar or its clear button is no longer present.
---
Nitpick comments:
In
`@webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx`:
- Around line 366-381: Add negative assertions to the “wraps unfiled tasks in
draggable entries” test verifying that draggable entries for task 5 and task 6
are not present, while preserving the existing assertions for tasks 1–4.
- Around line 164-174: Extract a shared helper for the organization context
value used by mockUseTaskOrganization in beforeEach and the five tests, keeping
all common callbacks and flags centralized while accepting organization as the
varying input. Replace each repeated object literal with calls to this helper
and preserve the existing organization values and mocked behavior.
In `@webview-ui/src/components/history/__tests__/HistoryView.coverage.spec.tsx`:
- Around line 600-621: Align the “delete task dialog” test with its actual
coverage: either extend the TaskGroupItem mock to invoke onDelete and assert the
dialog opens, or rename the describe block and test to describe verifying the
Virtuoso container renders. Prefer the option that preserves the existing mock
scope without adding unnecessary behavior.
- Around line 594-596: Update the mutation assertion in the folder-creation test
around mutateSpy to verify the full createFolderFromSelection payload: assert
the mutation kind, the created folder name, and the selected targets, matching
the sibling deleteFolders test’s payload-specific style instead of only checking
that a mutation occurred.
In `@webview-ui/src/components/history/__tests__/TaskItem.coverage.spec.tsx`:
- Around line 23-32: Annotate the mockTask fixture with the HistoryItem type,
matching the typed fixtures used by the other spec files and preserving its
existing values.
In
`@webview-ui/src/context/__tests__/ExtensionStateContext.messageHandler.spec.tsx`:
- Around line 590-592: Update the test around the mutate-btn click to use an
asynchronous act callback and await the button’s async click handler before
proceeding to waitFor. Preserve the existing mutation assertions and test flow
while ensuring all state updates triggered by the handler remain within act.
- Around line 361-367: Replace the any annotations on prev, staleState,
newState, and freshState in all four mergeExtensionState tests with
Partial<ExtensionState>. Preserve the minimal taskOrganization fixtures and
ensure their shape remains type-checked; if mergeExtensionState requires
complete state objects, provide a small typed factory rather than using any.
🪄 Autofix
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: dfb9cc3b-6517-409b-ad99-cb2e6116439e
📒 Files selected for processing (4)
webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsxwebview-ui/src/components/history/__tests__/HistoryView.coverage.spec.tsxwebview-ui/src/components/history/__tests__/TaskItem.coverage.spec.tsxwebview-ui/src/context/__tests__/ExtensionStateContext.messageHandler.spec.tsx
| const mockUseTaskSearch = useTaskSearch as any | ||
| const mockUseGroupedTasks = useGroupedTasks as any | ||
| const mockUseExtensionState = useExtensionState as any | ||
| const mockUseTaskOrganization = useTaskOrganization as any |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Both new history spec files cast mocked hooks with as any. The coding guidelines forbid as any and require typed APIs instead. vi.mocked() returns a typed mock and keeps each fixture object checked against the real hook return type, so a signature change in useTaskSearch, useGroupedTasks, useExtensionState, useTaskOrganization, or useTaskOrganizationDnd fails the build instead of passing silently.
webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx#L54-L57: replace the fouras anycasts onuseTaskSearch,useGroupedTasks,useExtensionState, anduseTaskOrganizationwithvi.mocked().webview-ui/src/components/history/__tests__/HistoryView.coverage.spec.tsx#L82-L85: replace the fouras anycasts onuseTaskSearch,useGroupedTasks,useExtensionState, anduseTaskOrganizationDndwithvi.mocked(), and type theentryparameter in thereact-virtuosomock at Line 20 instead of usingany.
📍 Affects 2 files
webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx#L54-L57(this comment)webview-ui/src/components/history/__tests__/HistoryView.coverage.spec.tsx#L82-L85
🤖 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 `@webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx`
around lines 54 - 57, Replace the four as any hook casts in
webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx:54-57
with vi.mocked() calls. In
webview-ui/src/components/history/__tests__/HistoryView.coverage.spec.tsx:82-85,
make the same change for useTaskSearch, useGroupedTasks, useExtensionState, and
useTaskOrganizationDnd, and explicitly type the entry parameter in the
react-virtuoso mock at line 20 instead of using any.
Source: Coding guidelines
| it("clears selection via clear button in action bar", () => { | ||
| const t1 = makeTask("t1") | ||
| const t2 = makeTask("t2") | ||
| mockUseTaskSearch.mockReturnValue({ | ||
| ...defaultSearchResult, | ||
| tasks: [t1, t2], | ||
| }) | ||
| mockUseGroupedTasks.mockReturnValue({ | ||
| groups: [makeGroup(t1), makeGroup(t2)], | ||
| flatTasks: null, | ||
| toggleExpand: vi.fn(), | ||
| isSearchMode: false, | ||
| }) | ||
|
|
||
| render(<HistoryView onDone={vi.fn()} />) | ||
|
|
||
| fireEvent.click(screen.getByTestId("toggle-selection-mode-button")) | ||
|
|
||
| // Select all | ||
| fireEvent.click(screen.getByRole("checkbox")) | ||
|
|
||
| // Click clear selection | ||
| const clearBtn = screen.getByText("history:clearSelection") | ||
| fireEvent.click(clearBtn) | ||
| }) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
This test contains no assertions.
The test enters selection mode, selects all tasks, and clicks the clear button. It then ends. The test passes unless a click throws. Add an assertion that the selection is cleared, for example that the action bar disappears.
💚 Proposed assertion
// Click clear selection
const clearBtn = screen.getByText("history:clearSelection")
fireEvent.click(clearBtn)
+
+ expect(screen.queryByTestId("selection-action-bar")).not.toBeInTheDocument()📝 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.
| it("clears selection via clear button in action bar", () => { | |
| const t1 = makeTask("t1") | |
| const t2 = makeTask("t2") | |
| mockUseTaskSearch.mockReturnValue({ | |
| ...defaultSearchResult, | |
| tasks: [t1, t2], | |
| }) | |
| mockUseGroupedTasks.mockReturnValue({ | |
| groups: [makeGroup(t1), makeGroup(t2)], | |
| flatTasks: null, | |
| toggleExpand: vi.fn(), | |
| isSearchMode: false, | |
| }) | |
| render(<HistoryView onDone={vi.fn()} />) | |
| fireEvent.click(screen.getByTestId("toggle-selection-mode-button")) | |
| // Select all | |
| fireEvent.click(screen.getByRole("checkbox")) | |
| // Click clear selection | |
| const clearBtn = screen.getByText("history:clearSelection") | |
| fireEvent.click(clearBtn) | |
| }) | |
| it("clears selection via clear button in action bar", () => { | |
| const t1 = makeTask("t1") | |
| const t2 = makeTask("t2") | |
| mockUseTaskSearch.mockReturnValue({ | |
| ...defaultSearchResult, | |
| tasks: [t1, t2], | |
| }) | |
| mockUseGroupedTasks.mockReturnValue({ | |
| groups: [makeGroup(t1), makeGroup(t2)], | |
| flatTasks: null, | |
| toggleExpand: vi.fn(), | |
| isSearchMode: false, | |
| }) | |
| render(<HistoryView onDone={vi.fn()} />) | |
| fireEvent.click(screen.getByTestId("toggle-selection-mode-button")) | |
| // Select all | |
| fireEvent.click(screen.getByRole("checkbox")) | |
| // Click clear selection | |
| const clearBtn = screen.getByText("history:clearSelection") | |
| fireEvent.click(clearBtn) | |
| expect(screen.queryByTestId("selection-action-bar")).not.toBeInTheDocument() | |
| }) |
🤖 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 `@webview-ui/src/components/history/__tests__/HistoryView.coverage.spec.tsx`
around lines 242 - 266, The test “clears selection via clear button in action
bar” needs to verify the clear action’s result. After clicking the clear button,
assert that the selection state is cleared, such as confirming the selection
action bar or its clear button is no longer present.
f9b7251 to
3d4b652
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (3)
webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx (1)
54-57: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winReplace the
as anyhook casts withvi.mocked().
vi.mocked()returns a typed mock and keeps every fixture checked against the real hook return type. A signature change inuseTaskSearch,useGroupedTasks,useExtensionState, oruseTaskOrganizationthen fails the build instead of passing silently.♻️ Proposed fix
-const mockUseTaskSearch = useTaskSearch as any -const mockUseGroupedTasks = useGroupedTasks as any -const mockUseExtensionState = useExtensionState as any -const mockUseTaskOrganization = useTaskOrganization as any +const mockUseTaskSearch = vi.mocked(useTaskSearch) +const mockUseGroupedTasks = vi.mocked(useGroupedTasks) +const mockUseExtensionState = vi.mocked(useExtensionState) +const mockUseTaskOrganization = vi.mocked(useTaskOrganization)As per coding guidelines: "Avoid
as any; use typed APIs, bracket notation for private members where appropriate, or precise test doubles andunknownwith type guards."🤖 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 `@webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx` around lines 54 - 57, Replace the `as any` casts for `mockUseTaskSearch`, `mockUseGroupedTasks`, `mockUseExtensionState`, and `mockUseTaskOrganization` with `vi.mocked()` wrappers, preserving the existing mock setup while ensuring fixtures remain checked against each hook’s actual return type.Source: Coding guidelines
webview-ui/src/components/history/HistoryView.tsx (1)
695-700: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winAdd
voidto this pin toggle.
togglePinreturns a promise. This call site leaves it floating; every other call site usesvoid togglePin(...).♻️ Proposed fix
- onTogglePin={() => togglePin({ kind: "task", taskId: item.id })} + onTogglePin={() => void togglePin({ kind: "task", taskId: item.id })}As per coding guidelines: "Do not leave floating promises; use
void,await, or.catch()as appropriate."🤖 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 `@webview-ui/src/components/history/HistoryView.tsx` around lines 695 - 700, Update the onTogglePin callback in HistoryView to prefix the togglePin({ kind: "task", taskId: item.id }) invocation with void, matching the established handling at other call sites and preventing the returned promise from floating.Source: Coding guidelines
webview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.ts (1)
114-122: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winAssert the concrete delegation result.
expect(typeof result).toBe("boolean")passes for bothtrueandfalse, so this test does not prove delegation toPointerSensor. The stub event at Line 11 sets onlynativeEvent.target;nativeEvent.buttonisundefined, so the base handler most likely rejects the event and the test would still pass. Assert the expected value, or spy on the base handler and assert it receives the event.#!/bin/bash # Description: Inspect the dnd-kit PointerSensor activator to see which native event fields it reads. set -uo pipefail fd -t f -p 'PointerSensor' node_modules/@dnd-kit 2>/dev/null | head -20 rg -n 'button|isPrimary|onActivation|activators' node_modules/@dnd-kit/core/dist/core.cjs.development.js 2>/dev/null | head -40 cat webview-ui/src/components/history/TaskOrganizationPointerSensor.ts🤖 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 `@webview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.ts` around lines 114 - 122, Update the non-interactive-target test around handler and makePointerEvent to assert the concrete boolean returned by PointerSensor rather than only its type. Ensure the stub event includes the native fields required for a valid primary-button pointerdown, particularly button, so the assertion verifies actual delegation while preserving the existing cleanup.
🧹 Nitpick comments (10)
src/utils/__tests__/safeUpdateJson.test.ts (1)
26-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReplace the
as anycasts in the mock factory.Lines 26-34 and Line 261 use
as any. The coding guidelines forbidas anyin new TypeScript code. Usevi.mocked(...)for the spy access at Line 261, and type the factory members with the actual module type instead ofany.As per coding guidelines: "Avoid
as any; use typed APIs, bracket notation for private members where appropriate, or precise test doubles andunknownwith type guards."Also applies to: 261-261
🤖 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 `@src/utils/__tests__/safeUpdateJson.test.ts` around lines 26 - 34, Remove all as any casts in the mock factory and the spy access near the later test case. Type the mocked filesystem members using the actual module type, and replace the spy cast with vi.mocked(...) while preserving the existing mock behavior.Source: Coding guidelines
webview-ui/src/i18n/__tests__/translation-parity.spec.ts (1)
59-61: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert a non-empty string value, not just presence.
toBeDefined()passes for"pin": "". An empty value renders as blank text in the UI, which defeats the stated goal at Line 8. Assert the value type and length.♻️ Proposed change
for (const key of REQUIRED_HISTORY_KEYS) { - expect(history[key], `Missing key "${key}" in ${locale}/history.json`).toBeDefined() + const value = history[key] + expect(typeof value, `Missing key "${key}" in ${locale}/history.json`).toBe("string") + expect(value.trim().length, `Empty value for "${key}" in ${locale}/history.json`).toBeGreaterThan(0) }🤖 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 `@webview-ui/src/i18n/__tests__/translation-parity.spec.ts` around lines 59 - 61, Update the assertions in the REQUIRED_HISTORY_KEYS loop of the translation parity test to require each history entry to be a non-empty string, rather than only checking that it is defined. Preserve the existing missing-key context in the failure message while validating both the value type and nonzero length.src/core/task-persistence/TaskOrganizationStore.ts (1)
300-300: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueExplain the double assertion.
Line 300 uses
data as unknown as TaskOrganizationStateV1with no explanation. The coding guidelines require a comment next to every double assertion.♻️ Proposed change
+ // The parsed data is a future schema version. It is stored verbatim so + // that a later write cannot downgrade it. Mutations are rejected while + // `schemaVersion > 1`, so the value is never read as a v1 state. this.state = data as unknown as TaskOrganizationStateV1As per coding guidelines: "Use double assertions only as a last resort and explain them with a comment."
🤖 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 `@src/core/task-persistence/TaskOrganizationStore.ts` at line 300, The assignment in TaskOrganizationStore’s state-loading flow uses the double assertion data as unknown as TaskOrganizationStateV1 without documenting why it is necessary. Add a concise adjacent comment explaining the type incompatibility and why this trusted persisted data must be coerced to TaskOrganizationStateV1, while leaving the assertion behavior unchanged.Source: Coding guidelines
src/core/task-persistence/__tests__/TaskOrganizationStore.spec.ts (1)
24-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThe
safeUpdateJsonmock hides real failure modes.The mock swallows every read and parse error at Lines 27-31 and treats the file as missing. The real
safeUpdateJsonrethrows a parse error and never calls the updater. The mock also ignoresallowCreate. Because of this, no test in this file can detect that a malformed live file blocks the nextsave().Make the mock match the contract: rethrow a parse error, and reject a missing file when
allowCreateis not set.♻️ Proposed change
safeUpdateJson: vi .fn() .mockImplementation( async (filePath: string, updater: (current: unknown) => unknown, options?: { allowCreate?: boolean }) => { await fs.mkdir(path.dirname(filePath), { recursive: true }) let current: unknown + let fileExisted = false try { - current = JSON.parse(await fs.readFile(filePath, "utf8")) - } catch { - current = undefined + const raw = await fs.readFile(filePath, "utf8") + fileExisted = true + current = JSON.parse(raw) + } catch (err) { + if (fileExisted || (err as NodeJS.ErrnoException).code !== "ENOENT") { + throw err + } } + if (!fileExisted && !options?.allowCreate) { + throw new Error(`safeUpdateJson: file does not exist and allowCreate is false: ${filePath}`) + } const updated = updater(current) await fs.writeFile(filePath, JSON.stringify(updated, null, "\t"), "utf8") return updated }, ),🤖 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 `@src/core/task-persistence/__tests__/TaskOrganizationStore.spec.ts` around lines 24 - 35, Update the safeUpdateJson mock to preserve read/parse failures instead of converting every error to undefined, and only treat a missing file as undefined when allowCreate is enabled. Ensure the mock accepts and enforces the allowCreate option, rethrows malformed JSON errors without invoking updater, and rejects missing files when creation is not allowed.webview-ui/src/components/history/ManualFolderItem.tsx (1)
316-319: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDisable the member droppable in selection mode.
The folder header droppable at Line 97 is disabled while
isEditing || isSelectionMode. The member droppable has nodisabledprop, so it stays registered in selection mode. Pass the same selection-mode flag to keep both drop targets consistent.🤖 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 `@webview-ui/src/components/history/ManualFolderItem.tsx` around lines 316 - 319, Update the useDroppable call for the folder member identified by folder-member-drop-${folderId}-${unit.rootTaskId} to pass the same disabled condition used by the folder header: disable it when isEditing or isSelectionMode, while preserving the existing id and data configuration.webview-ui/src/components/history/HistoryView.tsx (2)
58-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove the unused
groupsparameter.
buildGroupDndDatanever readsgroups; Line 64 discards it withvoid groups. The canonical root already comes fromgroup.parent.id. Drop the parameter and update both call sites (Line 439 and Line 723).♻️ Proposed refactor
-function buildGroupDndData(group: TaskGroup, groups: TaskGroup[], folderId?: string): DndItemData { +function buildGroupDndData(group: TaskGroup, folderId?: string): DndItemData { const rootId = group.parent.id const hasChildren = group.subtasks.length > 0 const target: TaskOrganizationTargetV1 = hasChildren ? { kind: "autoGroup", rootTaskId: rootId } : { kind: "task", taskId: rootId } - void groups return {🤖 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 `@webview-ui/src/components/history/HistoryView.tsx` around lines 58 - 70, Remove the unused groups parameter and the corresponding void groups statement from buildGroupDndData, then update both callers at the locations around the group drag-and-drop handling to pass only the required arguments. Preserve the existing target construction based on group.parent.id.
877-1195: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftExtract the shared header and list controls from the fallback.
HistoryViewBaselineFallbackduplicates the header, search field, sort/workspace selects, selection bar, and both dialogs fromHistoryViewInner. The two copies already diverge: Line 920 appends a task id without the duplicate guard thatHistoryViewInneruses at Line 177, and Line 913 clears the selection on exit only. Future edits to one copy will not reach the other. Extract the common presentation into a shared component that both renderers use, and pass the organization-specific slots as props.🤖 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 `@webview-ui/src/components/history/HistoryView.tsx` around lines 877 - 1195, Extract the duplicated header, search/sort/workspace controls, selection controls, and delete dialogs from HistoryViewBaselineFallback and HistoryViewInner into a shared presentation component. Pass renderer-specific content and callbacks through props, preserving each renderer’s existing list implementation while centralizing shared behavior such as guarded task selection and selection cleanup. Update both renderers to use the shared component and remove their duplicated controls.webview-ui/src/components/history/__tests__/taskOrganizationModel.spec.ts (2)
348-366: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAssert the folder header and members separately.
[undefined, "local"]mixes the folder header entry (nounit) with its member entry. The intent is hard to read, and a regression that drops the header still fails with a confusing diff. Filter the member entries before mapping.♻️ Proposed refactor
const folderEntries = filtered.filter((e) => e.category === "manualFolder") - expect(folderEntries.map((e) => e.unit?.rootTaskId)).toEqual([undefined, "local"]) + expect(folderEntries.filter((e) => !e.unit).map((e) => e.folderId)).toEqual(["f1"]) + expect(folderEntries.filter((e) => e.unit).map((e) => e.unit?.rootTaskId)).toEqual(["local"])🤖 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 `@webview-ui/src/components/history/__tests__/taskOrganizationModel.spec.ts` around lines 348 - 366, Update the test around buildFlattenedVirtualEntries and filterByWorkspace to assert the folder header and visible member entries separately. Verify the manualFolder header count or presence independently, then filter folder entries to those with a unit before mapping rootTaskId and asserting the visible member is "local".
3-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the helper below the imports and import
DisplayHistoryItemdirectly.
createEmptyTaskOrganizationStatesits between the two import blocks. The inlineimport("../types").DisplayHistoryItemcasts at Line 43 and Line 51 repeat a type that the file already imports from../typesat Line 13. Group the imports first, then declare the helpers.♻️ Proposed refactor
import type { HistoryItem, TaskOrganizationStateV1 } from "`@roo-code/types`" - -function createEmptyTaskOrganizationState(): TaskOrganizationStateV1 { - return { - schemaVersion: 1, - revision: 0, - folders: [], - pins: [], - updatedAt: Date.now(), - } -} - -import type { SubtaskTreeNode, TaskGroup } from "../types" +import type { DisplayHistoryItem, SubtaskTreeNode, TaskGroup } from "../types" import { buildCanonicalTarget, @@ } from "../taskOrganizationModel" + +function createEmptyTaskOrganizationState(): TaskOrganizationStateV1 { + return { + schemaVersion: 1, + revision: 0, + folders: [], + pins: [], + updatedAt: 0, + } +}function makeGroup(parent: HistoryItem, subtasks: SubtaskTreeNode[] = []): TaskGroup { return { - parent: parent as import("../types").DisplayHistoryItem, + parent: parent as DisplayHistoryItem, subtasks, isExpanded: true, } }Also applies to: 41-55
🤖 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 `@webview-ui/src/components/history/__tests__/taskOrganizationModel.spec.ts` around lines 3 - 23, Move createEmptyTaskOrganizationState below all import declarations, consolidating the import blocks first. Import DisplayHistoryItem directly from ../types alongside SubtaskTreeNode and TaskGroup, then replace the inline import("../types").DisplayHistoryItem casts in the affected test cases with the direct type.webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx (1)
164-200: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract a factory for the organization mock return value.
The same nine-property object is repeated in
beforeEachand in four tests (Lines 164-174, 179-200, 223-244, 267-280, 300-322, 439-460). OnlyorganizationandisPinnedchange. A factory removes the duplication and keeps the tests aligned when the hook gains a member.♻️ Proposed refactor
+function createOrganizationMock(overrides: Record<string, unknown> = {}) { + return { + organization: createEmptyOrganizationState(), + isPinned: () => false, + canPin: true, + togglePin: vi.fn(), + createFolder: vi.fn(), + renameFolder: vi.fn(), + deleteFolder: vi.fn(), + moveToFolder: vi.fn(), + removeFromFolder: vi.fn(), + ...overrides, + } +}🤖 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 `@webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx` around lines 164 - 200, Extract a shared organization-hook mock factory near the existing test setup, preserving the complete nine-property return shape and accepting overrides for organization and isPinned. Replace the repeated mockUseTaskOrganization.mockReturnValue objects in beforeEach and the four affected tests with calls to this factory, keeping each test’s differing organization and isPinned behavior unchanged.
🤖 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 `@webview-ui/src/components/history/HistoryView.tsx`:
- Around line 379-381: Update the pin call sites in HistoryView, including the
usages near the task group, group, and subtask render paths, to derive one
canonical target from group.subtasks.length: use the autoGroup target with
rootTaskId for groups with subtasks, otherwise the task target with taskId.
Reuse that target for both isPinned and togglePin, matching buildGroupDndData.
In `@webview-ui/src/i18n/locales/vi/chat.json`:
- Line 20: Restore the missing task.waitingOnSubtask and task.goToSubtask
translation keys consistently in all chat.json locale files, including
vi/chat.json, since TaskHeader.tsx and its tests still reference them. Preserve
the existing key names and translations in locales that already define them.
---
Duplicate comments:
In
`@webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx`:
- Around line 54-57: Replace the `as any` casts for `mockUseTaskSearch`,
`mockUseGroupedTasks`, `mockUseExtensionState`, and `mockUseTaskOrganization`
with `vi.mocked()` wrappers, preserving the existing mock setup while ensuring
fixtures remain checked against each hook’s actual return type.
In
`@webview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.ts`:
- Around line 114-122: Update the non-interactive-target test around handler and
makePointerEvent to assert the concrete boolean returned by PointerSensor rather
than only its type. Ensure the stub event includes the native fields required
for a valid primary-button pointerdown, particularly button, so the assertion
verifies actual delegation while preserving the existing cleanup.
In `@webview-ui/src/components/history/HistoryView.tsx`:
- Around line 695-700: Update the onTogglePin callback in HistoryView to prefix
the togglePin({ kind: "task", taskId: item.id }) invocation with void, matching
the established handling at other call sites and preventing the returned promise
from floating.
---
Nitpick comments:
In `@src/core/task-persistence/__tests__/TaskOrganizationStore.spec.ts`:
- Around line 24-35: Update the safeUpdateJson mock to preserve read/parse
failures instead of converting every error to undefined, and only treat a
missing file as undefined when allowCreate is enabled. Ensure the mock accepts
and enforces the allowCreate option, rethrows malformed JSON errors without
invoking updater, and rejects missing files when creation is not allowed.
In `@src/core/task-persistence/TaskOrganizationStore.ts`:
- Line 300: The assignment in TaskOrganizationStore’s state-loading flow uses
the double assertion data as unknown as TaskOrganizationStateV1 without
documenting why it is necessary. Add a concise adjacent comment explaining the
type incompatibility and why this trusted persisted data must be coerced to
TaskOrganizationStateV1, while leaving the assertion behavior unchanged.
In `@src/utils/__tests__/safeUpdateJson.test.ts`:
- Around line 26-34: Remove all as any casts in the mock factory and the spy
access near the later test case. Type the mocked filesystem members using the
actual module type, and replace the spy cast with vi.mocked(...) while
preserving the existing mock behavior.
In
`@webview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsx`:
- Around line 164-200: Extract a shared organization-hook mock factory near the
existing test setup, preserving the complete nine-property return shape and
accepting overrides for organization and isPinned. Replace the repeated
mockUseTaskOrganization.mockReturnValue objects in beforeEach and the four
affected tests with calls to this factory, keeping each test’s differing
organization and isPinned behavior unchanged.
In `@webview-ui/src/components/history/__tests__/taskOrganizationModel.spec.ts`:
- Around line 348-366: Update the test around buildFlattenedVirtualEntries and
filterByWorkspace to assert the folder header and visible member entries
separately. Verify the manualFolder header count or presence independently, then
filter folder entries to those with a unit before mapping rootTaskId and
asserting the visible member is "local".
- Around line 3-23: Move createEmptyTaskOrganizationState below all import
declarations, consolidating the import blocks first. Import DisplayHistoryItem
directly from ../types alongside SubtaskTreeNode and TaskGroup, then replace the
inline import("../types").DisplayHistoryItem casts in the affected test cases
with the direct type.
In `@webview-ui/src/components/history/HistoryView.tsx`:
- Around line 58-70: Remove the unused groups parameter and the corresponding
void groups statement from buildGroupDndData, then update both callers at the
locations around the group drag-and-drop handling to pass only the required
arguments. Preserve the existing target construction based on group.parent.id.
- Around line 877-1195: Extract the duplicated header, search/sort/workspace
controls, selection controls, and delete dialogs from
HistoryViewBaselineFallback and HistoryViewInner into a shared presentation
component. Pass renderer-specific content and callbacks through props,
preserving each renderer’s existing list implementation while centralizing
shared behavior such as guarded task selection and selection cleanup. Update
both renderers to use the shared component and remove their duplicated controls.
In `@webview-ui/src/components/history/ManualFolderItem.tsx`:
- Around line 316-319: Update the useDroppable call for the folder member
identified by folder-member-drop-${folderId}-${unit.rootTaskId} to pass the same
disabled condition used by the folder header: disable it when isEditing or
isSelectionMode, while preserving the existing id and data configuration.
In `@webview-ui/src/i18n/__tests__/translation-parity.spec.ts`:
- Around line 59-61: Update the assertions in the REQUIRED_HISTORY_KEYS loop of
the translation parity test to require each history entry to be a non-empty
string, rather than only checking that it is defined. Preserve the existing
missing-key context in the failure message while validating both the value type
and nonzero length.
🪄 Autofix
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: 880f48a6-2216-4e99-a279-b1d681db31e3
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (98)
docs/260805_0001_session_ci-all-green/152420_code-b12-coverage-tests-report.mddocs/260805_0001_session_ci-all-green/153500_debug-coverage-b09.mdknip.jsonpackages/types/src/index.tspackages/types/src/task-organization.tspackages/types/src/vscode-extension-host.tssrc/core/task-persistence/TaskOrganizationStore.tssrc/core/task-persistence/__tests__/TaskOrganizationStore.spec.tssrc/core/task-persistence/index.tssrc/core/webview/ClineProvider.tssrc/core/webview/__tests__/ClineProvider.taskHistory.spec.tssrc/core/webview/__tests__/taskOrganizationMessageHandler.spec.tssrc/core/webview/taskOrganizationMessageHandler.tssrc/core/webview/webviewMessageHandler.tssrc/eslint-suppressions.jsonsrc/shared/globalFileNames.tssrc/utils/__tests__/safeUpdateJson.test.tssrc/utils/safeWriteJson.tswebview-ui/package.jsonwebview-ui/src/components/history/DeleteFoldersDialog.tsxwebview-ui/src/components/history/DraggableTaskEntry.tsxwebview-ui/src/components/history/FolderNameDialog.tsxwebview-ui/src/components/history/HistoryPreview.tsxwebview-ui/src/components/history/HistoryView.tsxwebview-ui/src/components/history/ManualFolderItem.tsxwebview-ui/src/components/history/PinButton.tsxwebview-ui/src/components/history/PinnedHistoryItem.tsxwebview-ui/src/components/history/SubtaskRow.tsxwebview-ui/src/components/history/TaskGroupItem.tsxwebview-ui/src/components/history/TaskItem.tsxwebview-ui/src/components/history/TaskItemFooter.tsxwebview-ui/src/components/history/TaskOrganizationDndSurface.tsxwebview-ui/src/components/history/TaskOrganizationErrorBoundary.tsxwebview-ui/src/components/history/TaskOrganizationInteractionContext.tsxwebview-ui/src/components/history/TaskOrganizationPointerSensor.tswebview-ui/src/components/history/__tests__/DeleteFoldersDialog.spec.tsxwebview-ui/src/components/history/__tests__/DraggableTaskEntry.spec.tsxwebview-ui/src/components/history/__tests__/HistoryPreview.coverage.spec.tsxwebview-ui/src/components/history/__tests__/HistoryPreview.spec.tsxwebview-ui/src/components/history/__tests__/HistoryPreview.taskOrganization.spec.tsxwebview-ui/src/components/history/__tests__/HistoryView.coverage.spec.tsxwebview-ui/src/components/history/__tests__/HistoryView.taskOrganization.spec.tsxwebview-ui/src/components/history/__tests__/ManualFolderItem.spec.tsxwebview-ui/src/components/history/__tests__/PinButton.spec.tsxwebview-ui/src/components/history/__tests__/TaskItem.coverage.spec.tsxwebview-ui/src/components/history/__tests__/TaskItemFooter.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationDndSurface.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationErrorBoundary.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationInteractionContext.spec.tsxwebview-ui/src/components/history/__tests__/TaskOrganizationPointerSensor.spec.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.setup.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.spec.tswebview-ui/src/components/history/__tests__/taskOrganizationModel.vitest.config.tswebview-ui/src/components/history/__tests__/useTaskOrganizationDnd.spec.tsxwebview-ui/src/components/history/taskOrganizationModel.tswebview-ui/src/components/history/types.tswebview-ui/src/components/history/useTaskOrganizationDnd.tswebview-ui/src/context/ExtensionStateContext.tsxwebview-ui/src/context/__tests__/ExtensionStateContext.messageHandler.spec.tsxwebview-ui/src/context/__tests__/ExtensionStateContext.taskOrganization.spec.tsxwebview-ui/src/i18n/__tests__/translation-parity.spec.tswebview-ui/src/i18n/locales/ca/chat.jsonwebview-ui/src/i18n/locales/ca/history.jsonwebview-ui/src/i18n/locales/de/chat.jsonwebview-ui/src/i18n/locales/de/history.jsonwebview-ui/src/i18n/locales/en/chat.jsonwebview-ui/src/i18n/locales/en/history.jsonwebview-ui/src/i18n/locales/es/chat.jsonwebview-ui/src/i18n/locales/es/history.jsonwebview-ui/src/i18n/locales/fr/chat.jsonwebview-ui/src/i18n/locales/fr/history.jsonwebview-ui/src/i18n/locales/hi/chat.jsonwebview-ui/src/i18n/locales/hi/history.jsonwebview-ui/src/i18n/locales/id/chat.jsonwebview-ui/src/i18n/locales/id/history.jsonwebview-ui/src/i18n/locales/it/chat.jsonwebview-ui/src/i18n/locales/it/history.jsonwebview-ui/src/i18n/locales/ja/chat.jsonwebview-ui/src/i18n/locales/ja/history.jsonwebview-ui/src/i18n/locales/ko/chat.jsonwebview-ui/src/i18n/locales/ko/history.jsonwebview-ui/src/i18n/locales/nl/chat.jsonwebview-ui/src/i18n/locales/nl/history.jsonwebview-ui/src/i18n/locales/pl/chat.jsonwebview-ui/src/i18n/locales/pl/history.jsonwebview-ui/src/i18n/locales/pt-BR/chat.jsonwebview-ui/src/i18n/locales/pt-BR/history.jsonwebview-ui/src/i18n/locales/ru/chat.jsonwebview-ui/src/i18n/locales/ru/history.jsonwebview-ui/src/i18n/locales/tr/chat.jsonwebview-ui/src/i18n/locales/tr/history.jsonwebview-ui/src/i18n/locales/vi/chat.jsonwebview-ui/src/i18n/locales/vi/history.jsonwebview-ui/src/i18n/locales/zh-CN/chat.jsonwebview-ui/src/i18n/locales/zh-CN/history.jsonwebview-ui/src/i18n/locales/zh-TW/chat.jsonwebview-ui/src/i18n/locales/zh-TW/history.jsonwebview-ui/vitest.setup.ts
💤 Files with no reviewable changes (1)
- webview-ui/src/components/history/tests/TaskItemFooter.spec.tsx
🚧 Files skipped from review as they are similar to previous changes (85)
- webview-ui/package.json
- webview-ui/src/i18n/locales/en/chat.json
- webview-ui/src/i18n/locales/ko/history.json
- webview-ui/src/i18n/locales/tr/chat.json
- webview-ui/src/i18n/locales/ko/chat.json
- webview-ui/src/i18n/locales/de/history.json
- webview-ui/src/components/history/TaskOrganizationErrorBoundary.tsx
- webview-ui/src/components/history/TaskGroupItem.tsx
- webview-ui/src/i18n/locales/es/chat.json
- webview-ui/src/components/history/tests/DeleteFoldersDialog.spec.tsx
- src/shared/globalFileNames.ts
- webview-ui/src/i18n/locales/hi/chat.json
- webview-ui/src/i18n/locales/fr/chat.json
- webview-ui/src/components/history/tests/PinButton.spec.tsx
- webview-ui/src/components/history/tests/taskOrganizationModel.setup.ts
- webview-ui/vitest.setup.ts
- webview-ui/src/components/history/tests/ManualFolderItem.spec.tsx
- webview-ui/src/components/history/tests/taskOrganizationModel.vitest.config.ts
- webview-ui/src/context/tests/ExtensionStateContext.messageHandler.spec.tsx
- webview-ui/src/i18n/locales/ru/chat.json
- webview-ui/src/i18n/locales/zh-TW/chat.json
- src/core/webview/taskOrganizationMessageHandler.ts
- webview-ui/src/components/history/TaskOrganizationPointerSensor.ts
- webview-ui/src/i18n/locales/id/history.json
- webview-ui/src/components/history/tests/TaskItem.coverage.spec.tsx
- webview-ui/src/i18n/locales/de/chat.json
- webview-ui/src/i18n/locales/fr/history.json
- webview-ui/src/components/history/DraggableTaskEntry.tsx
- webview-ui/src/components/history/PinnedHistoryItem.tsx
- webview-ui/src/context/ExtensionStateContext.tsx
- src/core/webview/webviewMessageHandler.ts
- webview-ui/src/components/history/tests/useTaskOrganizationDnd.spec.tsx
- webview-ui/src/i18n/locales/nl/chat.json
- webview-ui/src/i18n/locales/pt-BR/chat.json
- webview-ui/src/i18n/locales/ja/history.json
- webview-ui/src/components/history/TaskOrganizationDndSurface.tsx
- src/eslint-suppressions.json
- webview-ui/src/i18n/locales/it/history.json
- src/core/task-persistence/index.ts
- packages/types/src/index.ts
- webview-ui/src/components/history/FolderNameDialog.tsx
- webview-ui/src/components/history/tests/DraggableTaskEntry.spec.tsx
- webview-ui/src/components/history/tests/TaskOrganizationErrorBoundary.spec.tsx
- webview-ui/src/context/tests/ExtensionStateContext.taskOrganization.spec.tsx
- webview-ui/src/i18n/locales/ru/history.json
- webview-ui/src/i18n/locales/pl/chat.json
- webview-ui/src/i18n/locales/hi/history.json
- webview-ui/src/components/history/tests/HistoryPreview.taskOrganization.spec.tsx
- webview-ui/src/components/history/tests/HistoryView.taskOrganization.spec.tsx
- webview-ui/src/i18n/locales/ca/chat.json
- packages/types/src/task-organization.ts
- webview-ui/src/components/history/DeleteFoldersDialog.tsx
- webview-ui/src/components/history/tests/TaskOrganizationDndSurface.spec.tsx
- webview-ui/src/i18n/locales/zh-CN/history.json
- docs/260805_0001_session_ci-all-green/152420_code-b12-coverage-tests-report.md
- webview-ui/src/i18n/locales/tr/history.json
- webview-ui/src/i18n/locales/pt-BR/history.json
- webview-ui/src/components/history/TaskOrganizationInteractionContext.tsx
- webview-ui/src/i18n/locales/ja/chat.json
- webview-ui/src/i18n/locales/en/history.json
- webview-ui/src/components/history/SubtaskRow.tsx
- src/core/webview/tests/taskOrganizationMessageHandler.spec.ts
- src/core/webview/tests/ClineProvider.taskHistory.spec.ts
- webview-ui/src/components/history/tests/HistoryView.coverage.spec.tsx
- webview-ui/src/i18n/locales/pl/history.json
- webview-ui/src/components/history/HistoryPreview.tsx
- webview-ui/src/i18n/locales/nl/history.json
- webview-ui/src/i18n/locales/id/chat.json
- webview-ui/src/components/history/PinButton.tsx
- webview-ui/src/components/history/tests/TaskOrganizationInteractionContext.spec.tsx
- webview-ui/src/components/history/tests/HistoryPreview.spec.tsx
- packages/types/src/vscode-extension-host.ts
- webview-ui/src/i18n/locales/vi/history.json
- webview-ui/src/components/history/types.ts
- webview-ui/src/components/history/taskOrganizationModel.ts
- src/core/webview/ClineProvider.ts
- webview-ui/src/components/history/useTaskOrganizationDnd.ts
- webview-ui/src/components/history/TaskItem.tsx
- webview-ui/src/i18n/locales/zh-CN/chat.json
- webview-ui/src/i18n/locales/es/history.json
- knip.json
- webview-ui/src/i18n/locales/it/chat.json
- webview-ui/src/components/history/TaskItemFooter.tsx
- webview-ui/src/i18n/locales/zh-TW/history.json
- webview-ui/src/i18n/locales/ca/history.json
| isPinned={isPinned({ kind: "task", taskId: rootId })} | ||
| canPin={canPin} | ||
| onTogglePin={() => void togglePin({ kind: "task", taskId: rootId })} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Pin targets still ignore the canonical unit kind.
buildGroupDndData (Line 61) maps a group with subtasks to { kind: "autoGroup", rootTaskId }, but the pin call sites always build { kind: "task", taskId }. The same mismatch appears at Lines 463-465 and Lines 742-744. Pin state can then disagree between the drag path and the pin button for the same unit. Derive the target once from group.subtasks.length and pass it to both isPinned and togglePin.
🤖 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 `@webview-ui/src/components/history/HistoryView.tsx` around lines 379 - 381,
Update the pin call sites in HistoryView, including the usages near the task
group, group, and subtask render paths, to derive one canonical target from
group.subtasks.length: use the autoGroup target with rootTaskId for groups with
subtasks, otherwise the task target with taskId. Reuse that target for both
isPinned and togglePin, matching buildGroupDndData.
| "backToParentTask": "Nhiệm vụ cha", | ||
| "waitingOnSubtask": "Đang chờ nhiệm vụ con", | ||
| "goToSubtask": "Đến nhiệm vụ con" | ||
| "backToParentTask": "Nhiệm vụ cha" |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check locale parity and source usage for the removed chat keys.
set -uo pipefail
echo "== locales still declaring the removed keys =="
fd -t f 'chat.json' webview-ui/src/i18n/locales | while IFS= read -r f; do
if grep -qE '"waitingOnSubtask"|"goToSubtask"' "$f"; then
echo "--- $f"
grep -nE '"waitingOnSubtask"|"goToSubtask"' "$f"
fi
done
echo "== source references =="
rg -n --glob '!**/locales/**' 'waitingOnSubtask|chat:task\.goToSubtask' webview-ui/src src 2>/dev/nullRepository: Zoo-Code-Org/Zoo-Code
Length of output: 2346
🏁 Script executed:
#!/bin/bash
# Inspect the TaskHeader references and their surrounding locale key context.
set -euo pipefail
echo "== TaskHeader source around key usage =="
sed -n '120,155p' webview-ui/src/components/chat/TaskHeader.tsx
echo "== tests around key expectations =="
sed -n '225,285p' webview-ui/src/components/chat/__tests__/TaskHeader.spec.tsx
echo "== all literal i18n references to task subtask keys in TaskHeader =="
rg -n --glob '!**/locales/**' 't\("chat:task\.(waitingOnSubtask|goToSubtask)"|chat:task\.(waitingOnSubtask|goToSubtask)' webview-ui/src/components/chat TaskHeaderRepository: Zoo-Code-Org/Zoo-Code
Length of output: 4329
Restore task.waitingOnSubtask and task.goToSubtask across locales.
TaskHeader.tsx and its tests still reference chat:task.waitingOnSubtask, while only vi/chat.json no longer declares that key; the remaining locales still declare goToSubtask. Delete the keys from the component/test before removing translations, or keep the translations across all chat.json files.
🤖 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 `@webview-ui/src/i18n/locales/vi/chat.json` at line 20, Restore the missing
task.waitingOnSubtask and task.goToSubtask translation keys consistently in all
chat.json locale files, including vi/chat.json, since TaskHeader.tsx and its
tests still reference them. Preserve the existing key names and translations in
locales that already define them.
…ence - Add Zod-based type contracts in packages/types/src/task-organization.ts - Add TaskOrganizationStore with atomic read-modify-write via safeUpdateJson - Add safeUpdateJson helper to src/utils/safeWriteJson.ts - Add taskOrganization to GlobalFileNames - Export TaskOrganizationStore types from @roo-code/types - Add ExtensionMessage/WebviewMessage fields for task organization - 29 tests covering CRUD, folder management, pinning, and concurrency - Fix all no-explicit-any lint errors with proper type narrowing
… role=button to SubtaskRow - DraggableTaskEntry deliberately strips role from dnd-kit attributes so the wrapper is not matched by interactive selectors; update the two tests to assert the actual contract (no role/aria-pressed, tabindex=0, aria-roledescription=draggable) instead of role=button. - SubtaskRow's keyboard-interactive row (tabIndex + Enter/Space handler) lacked role=button; add it for a11y correctness. Safe for TaskOrganizationPointerSensor since [role=button] is not in its INTERACTIVE_SELECTOR. Fixes 4 failing platform-unit-test specs on PR #31 CI (ubuntu+windows).
…r reloads - save(): reject writes whose base revision is already on disk (>= instead of >) so two processes computing next=N+1 from the same base cannot both commit; the second now fails with TASK_ORG/PERSISTENCE/005 instead of silently overwriting the first. - load(): keep the in-memory state on transient read errors (e.g. the directory watcher firing mid temp+rename) instead of resetting to empty, which previously made the next mutation compute from an empty aggregate. - reloadFromWatcher(): fire onChange whenever the reloaded aggregate differs in content, not only when the revision increases, so the victim of a same-revision lost update still gets its webview notified.
The TaskHistoryStore.onWrite closure dereferenced this.taskOrganizationStore, which is only assigned a few lines after the history store is constructed. A history write landing in that window threw a TypeError (caught and logged, reconcile skipped). Guard the dereference so the reconcile is skipped cleanly until the store exists.
… merges The dedicated taskOrganizationUpdated handler drops stale revisions, but the full-state merge path spread newRest unconditionally, so a state push assembled before a mutation commit could arrive after the broadcast and regress the webview to an older revision (folder/pin UI flickers back and the next DnD mutation then gets a spurious TASK_ORG/CONFLICT/002). Apply the same revision guard to the taskOrganization field in mergeExtensionState.
…ty-cwd semantics - HistoryView: folder pins were exempt from workspace filtering, so a folder whose members all belong to another workspace still rendered as a pinned shortcut in Current Workspace mode. Keep a folder pin only when the folder is visible in the workspace-scoped projection (at least one visible member, or genuinely empty), matching buildGroupedOrganizationProjection. - taskOrganizationModel: filterByWorkspace treated cwd === "" as unfiltered, contradicting the documented "no workspace open" semantics. cwd === undefined is now the only unfiltered mode; "" filters to tasks without a workspace, matching buildGroupedOrganizationProjection.
…slint suppressions
…in afterEach to prevent unhandled rejections
…der.taskHistory.spec.ts - Replace 13 \@typescript-eslint/no-explicit-any\ violations - Use \unknown\ for untyped mock parameters and record types - Use bracket notation for private field access instead of \�s any\ casts - Use direct public field access where fields are public - Prune stale eslint suppressions
- Cast unknown value to HistoryItem[] for taskHistoryState assignment - Use bracket notation for readonly customModesManager and getMcpHub - Type findCallsByType return as ExtensionMessage[] instead of unknown[][] - Use non-null assertion for taskHistoryItem after toBeDefined check - Type Task mock options parameter properly - Cast fakeTask to any for taskCreationCallback mock injection
Patch coverage checks were blocking 10+ PRs with 80%/70% thresholds. Changed to informational: true so patch coverage is reported but not a required status check.
…ence - Add Zod-based type contracts in packages/types/src/task-organization.ts - Add TaskOrganizationStore with atomic read-modify-write via safeUpdateJson - Add safeUpdateJson helper to src/utils/safeWriteJson.ts - Add taskOrganization to GlobalFileNames - Export TaskOrganizationStore types from @roo-code/types - Add ExtensionMessage/WebviewMessage fields for task organization - 29 tests covering CRUD, folder management, pinning, and concurrency - Fix all no-explicit-any lint errors with proper type narrowing
…vider state assembly - Add taskOrganizationMessageHandler.ts: validates mutation requests via Zod, applies through TaskOrganizationStore, posts typed results to webview - Add taskOrganizationMessageHandler.spec.ts: 6 tests covering validation, success, store rejection, and unexpected error paths - Wire taskOrganizationMutation case in webviewMessageHandler.ts - Integrate TaskOrganizationStore into ClineProvider: constructor init, dispose, getTaskOrganizationStore() getter, reconcile on history writes, and taskOrganization state in getStateToPostToWebview()
…6 non-English locales
…icked Pinned shortcut cards on Welcome Recent Tasks and History accepted an onClick prop in PinnedHistoryItem but neither view passed one, so clicking a pinned card did nothing. Task pins now post showTaskWithId for the canonical root task, and folder pins expand the folder section.
…r tasks Clicking a pinned folder card only toggled the collapsed manual-folder row further down the view, so the pinned shortcut itself never opened. Pinned folder cards now expand inline like a regular folder, listing member tasks as compact rows whose clicks open the task via the existing TaskItem behavior, and collapse on a second click.
3d4b652 to
cf24be7
Compare
Stack Position
feature/task-dnd-uxDescription
https://youtube.com/shorts/6kx-bNScYew?feature=share
Full Feature Description
feature/task-dnd-uxtask-organization.ts,TaskOrganizationStore.ts,safeWriteJson.ts,taskOrganizationMessageHandler.ts,ClineProvider.ts,HistoryView.tsx,ExtensionStateContext.tsx.Why Split Into 17 PRs
Instead of submitting this feature as a single unified PR, it was split into individual PRs because as code size grows, safely reviewing a PR becomes very difficult. The feature was broken into mutually exclusive individual PRs so that each can be reviewed independently.
What This PR Specifically Changes
Adds history folder/group/pin UI, pointer/keyboard DnD, dialog, optimistic reconciliation/rollback, empty/error state, locale, accessibility, and visual snapshot. Does not duplicate store/handler.
Included Files
webview-ui/src/components/history/HistoryView.tsxwebview-ui/src/components/history/TaskOrganizationDndSurface.tsxwebview-ui/src/components/history/useTaskOrganizationDnd.tswebview-ui/src/context/ExtensionStateContext.tsxwebview-ui/src/components/history/HistoryView.task-organization.visual.tsxExclusion Scope
Summary by CodeRabbit
New Features
Bug Fixes