Skip to content

feat(desktop): wire Link task command to v2 workspaces#4493

Merged
saddlepaddle merged 1 commit into
mainfrom
feat/link-task-command
May 13, 2026
Merged

feat(desktop): wire Link task command to v2 workspaces#4493
saddlepaddle merged 1 commit into
mainfrom
feat/link-task-command

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented May 13, 2026

Summary

Closes SUPER-685.

  • Link task command actually links. The command palette picker now writes taskId to the current v2 workspace via useOptimisticCollectionActions().v2Workspaces.updateWorkspace; the v2WorkspacePatch and the collection's onUpdate handler both carry taskId end-to-end. v2Workspace.update trpc accepts taskId (uuid, nullable, optional) and validates it belongs to the workspace's org (mirroring create). null is the unlink path.
  • Linked task surfaces in the v2 sidebar hover card. New LinkedTaskSection renders a "Task" row with StatusIcon, slug, truncated title, an in-app <Link to="/tasks/$taskId">, and an external-link icon when externalUrl is set. taskId is projected through all three sidebar workspace query/build sites in useDashboardSidebarData.
  • Picker UI matches IssueLinkCommand. StatusIcon + two-line title/slug+status layout. The Show closed checkbox was dropped in favor of sorting in Linear list-view order (started → unstarted → backlog → completed → canceled, then status.position, then priority) — closed tasks naturally fall to the bottom and remain searchable.
  • Search quality + responsiveness. Picker now uses the shared useHybridSearch hook (exact on slug/labels, fuzzy on title/description) instead of a single permissive Fuse. Both the picker and the main TasksView use useDeferredValue on the search query so heavy re-renders yield to keystrokes.

Test plan

  • Open command palette inside a v2 workspace → Link task → pick a task → workspace gets taskId, sidebar hover card shows it.
  • Hover the workspace → click the linked task slug → navigates to /tasks/$taskId; external-link icon opens the Linear URL when present.
  • Confirm closed/canceled tasks sort to the bottom in the picker; search "contact us" no longer matches "Context menus for panes".
  • Type fast in the picker and in the tasks list search input — input stays responsive, results catch up when paused.
  • bun run typecheck and bun run lint pass.

Summary by cubic

Links tasks to v2 workspaces from the command palette and shows the linked task in the sidebar hover card. Closes SUPER-685 and aligns the picker UI and search with the tasks list.

  • New Features

    • Linking writes taskId to the current v2 workspace via useOptimisticCollectionActions().v2Workspaces.updateWorkspace; passing null unlinks.
    • v2Workspace.update now accepts taskId (uuid, nullable, optional) and validates it belongs to the workspace’s org.
    • Sidebar hover card displays the linked task with StatusIcon, slug, truncated title, an in-app link to /tasks/$taskId, and an external link when externalUrl exists. Picker UI matches IssueLinkCommand.
  • Performance

    • Picker uses useHybridSearch and tasks list sort order (started → unstarted → backlog → completed → canceled, then status.position, then priority); closed tasks naturally sink but stay searchable.
    • Both the picker and TasksView use useDeferredValue for smoother typing under heavy re-renders.

Written for commit cf223da. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • New Features

    • Link tasks to workspaces and view linked task details in workspace hover cards
    • Display task status, slug, and external links within linked task sections
    • Improved hybrid search functionality for better task discovery and filtering
  • Improvements

    • Enhanced search performance with deferred query handling
    • Task lists now display with status icons and priority information

Review Change Stack

Closes SUPER-685. The command palette Link task picker now actually links the
chosen task to the current v2 workspace, optimistically through tanstack-db and
persisted via the v2Workspace.update trpc mutation (which now accepts taskId).
The hover card surfaces the linked task with a StatusIcon and links to its
detail page. Picker UI uses the hybrid search hook and useDeferredValue, matched
to the tasks list view; the same deferred pattern is applied to the tasks view
search so heavy re-renders yield to keystrokes.
@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 13, 2026

Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

📝 Walkthrough

Walkthrough

This PR implements workspace task linking by adding a taskId field to v2Workspaces, enabling users to associate tasks with workspaces. Backend validation ensures task existence and org membership, frontend types propagate taskId through the sidebar, and UI components display linked tasks in workspace hover cards. The task selection UI was refactored from Fuse.js to hybrid search with deferred values for better performance.

Changes

Workspace task linking

Layer / File(s) Summary
Backend task linking validation
packages/trpc/src/router/v2-workspace/v2-workspace.ts
TRPC v2WorkspaceRouter.update accepts optional taskId field and validates that tasks exist and belong to the workspace's organization.
Frontend workspace type with taskId
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.ts
DashboardSidebarWorkspace type adds taskId: string | null field.
Sidebar data pipeline taskId propagation
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
Live queries and computed workspace objects thread taskId through sidebar workspaces, main workspaces, cloud fallbacks, and pending entries.
Optimistic collection updates and wiring
apps/desktop/src/renderer/routes/_authenticated/hooks/useOptimisticCollectionActions/useOptimisticCollectionActions.ts, apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
V2WorkspacePatch type adds taskId support, optimistic updates apply it to draft workspaces, and collections forward taskId to the TRPC mutation.
Linked task display component
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/components/LinkedTaskSection/LinkedTaskSection.tsx, ...LinkedTaskSection/index.ts
New LinkedTaskSection component fetches and renders task details, status icon, task link, and optional external URL icon.
Workspace hover card linked task integration
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/DashboardSidebarWorkspaceHoverCardContent.tsx
Hover card extracts taskId from workspace and conditionally renders LinkedTaskSection when task is linked.
Task search and selection UI refactor
apps/desktop/src/renderer/commandPalette/ui/LinkTask/LinkTaskFrame.tsx
Replaces Fuse.js with hybrid search, uses useDeferredValue for responsive query handling, displays task status icons and metadata, and calls v2Workspaces.updateWorkspace to link selected tasks.
Search deferred value optimization
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx
Applies useDeferredValue pattern to search queries for responsive UI during rapid input across board, table, and PR/issue views.

Sequence Diagram

sequenceDiagram
  participant User
  participant LinkTaskFrame
  participant useDeferredValue
  participant useHybridSearch
  participant v2Workspaces
  participant TRPC
  participant UI as Hover Card

  User->>LinkTaskFrame: Type search query
  LinkTaskFrame->>useDeferredValue: Create deferredQuery
  useDeferredValue->>useHybridSearch: Pass deferred query
  useHybridSearch->>LinkTaskFrame: Return filtered results
  LinkTaskFrame->>User: Display status icons & task details
  
  User->>LinkTaskFrame: Select task
  LinkTaskFrame->>v2Workspaces: updateWorkspace(workspaceId, {taskId})
  v2Workspaces->>TRPC: Call router.update with taskId
  TRPC->>TRPC: Validate task exists & belongs to org
  TRPC->>v2Workspaces: Return updated workspace
  v2Workspaces->>UI: Workspace data includes taskId
  UI->>LinkedTaskSection: Render with taskId
  LinkedTaskSection->>User: Display linked task info
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #4303: Adds workspace task linking UI surface and v2Workspaces.taskId propagation through frontend workspace components, completing the linked task display feature (though Linear attachment flow is not included here).

Poem

🐰 A workspace now holds a task in its hand,
Through hybrid search we swiftly command,
Deferred values smooth the typing flow,
Status icons proudly on display show,
From hover card hints, linked tasks brightly glow!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(desktop): wire Link task command to v2 workspaces' clearly and concisely describes the main feature being implemented: wiring/connecting the Link task command functionality to v2 workspaces, which is the primary objective of this PR.
Description check ✅ Passed The pull request description is comprehensive and well-structured, providing a detailed summary of changes, linking to the related issue (SUPER-685), explaining the test plan with specific steps, and including both a manual and auto-generated summary. All key sections of the template are adequately addressed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/link-task-command

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

❤️ Share

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 13, 2026

Greptile Summary

This PR wires the Link Task command palette picker to v2 workspaces end-to-end: selecting a task optimistically sets taskId on the local collection, which the onUpdate handler persists via the tRPC v2Workspace.update mutation (validated to belong to the workspace's org). The sidebar hover card gains a new LinkedTaskSection component that queries the task by ID and renders a status icon, slug, title, in-app navigation link, and optional Linear external-link icon.

  • Picker UX overhaul: Fuse.js is replaced with useHybridSearch, tasks are sorted by status type → position → priority (closed tasks fall naturally to the bottom), and useDeferredValue is applied to the search query in both the picker and TasksView for keystroke responsiveness.
  • Full data flow: taskId is projected through all four workspace-building sites in useDashboardSidebarData and correctly defaults to null for pending workspaces.
  • Server validation: The tRPC mutation accepts taskId as uuid | null | undefined; truthy values are cross-checked against the workspace's org, and null (unlink) correctly bypasses validation.

Confidence Score: 4/5

Safe to merge — the link/unlink data flow is correct end-to-end and the server-side org validation mirrors the create path.

The implementation is solid: null correctly bypasses org-validation on the server, Drizzle treats undefined fields as no-ops, and taskId is projected through all four workspace-building paths. The only items worth a follow-up are a slightly noisy useMemo dependency (statusMap included in both search and sort branches when only needed for sort) and the LinkedTaskSection query depending on the full collections object rather than the two specific sub-collections it actually reads.

LinkedTaskSection.tsx (dependency array) and LinkTaskFrame.tsx (filtered memo deps) are the two spots worth a quick look; all other files are straightforward.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/commandPalette/ui/LinkTask/LinkTaskFrame.tsx Rewrites the picker UI: replaces Fuse with useHybridSearch, adds status-aware sorting, wires updateWorkspace for real linking, and defers the search query for responsiveness
packages/trpc/src/router/v2-workspace/v2-workspace.ts Adds taskId (uuid, nullable, optional) to the update mutation with correct org-scoped validation; null correctly bypasses the validation check for the unlink path
apps/desktop/src/renderer/routes/_authenticated/hooks/useOptimisticCollectionActions/useOptimisticCollectionActions.ts Extends V2WorkspacePatch with taskId and patches the optimistic draft correctly inside updateWorkspace
apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts Extends the onUpdate handler to destructure and forward taskId to the TRPC mutation; correctly handles null (unlink) and undefined (not changed)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/components/LinkedTaskSection/LinkedTaskSection.tsx New component rendering a linked task row with status icon, slug, truncated title, in-app link, and optional external-link icon; uses [collections, taskId] as query dependency instead of specific sub-collections
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts Projects taskId through all four workspace-building code paths; pending workspaces correctly default to taskId: null
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.ts Adds taskId: string
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx Adds useDeferredValue to the search query and threads deferredSearchQuery to all four content components for better input responsiveness
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/DashboardSidebarWorkspaceHoverCardContent.tsx Destructures taskId from workspace and conditionally renders LinkedTaskSection; straightforward integration
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/components/LinkedTaskSection/index.ts Barrel export for LinkedTaskSection

Sequence Diagram

sequenceDiagram
    participant User
    participant LinkTaskFrame
    participant OptimisticActions
    participant LocalCollection as Local Collection
    participant onUpdate as collections.ts onUpdate
    participant TRPC as TRPC v2Workspace.update

    User->>LinkTaskFrame: selects a task
    LinkTaskFrame->>OptimisticActions: "v2Workspaces.updateWorkspace(workspaceId, { taskId })"
    OptimisticActions->>LocalCollection: "immer draft → draft.taskId = taskId"
    LocalCollection-->>LinkTaskFrame: optimistic update applied
    LinkTaskFrame->>User: toast.success("Linked … to workspace") + close palette
    LocalCollection->>onUpdate: "fires async with { original, changes: { taskId } }"
    onUpdate->>TRPC: "v2Workspace.update({ id, taskId })"
    TRPC->>TRPC: validate taskId belongs to org
    alt success
        TRPC-->>onUpdate: "{ txid }"
        onUpdate-->>LocalCollection: confirmed
    else failure
        TRPC-->>onUpdate: TRPCError
        onUpdate-->>LocalCollection: rollback optimistic change
        LocalCollection-->>User: toast.error("Failed to update workspace")
    end

    Note over User,LocalCollection: Sidebar hover card reads taskId from DashboardSidebarWorkspace and renders LinkedTaskSection while taskId is set
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/desktop/src/renderer/commandPalette/ui/LinkTask/LinkTaskFrame.tsx:126
`statusMap` is included in the dependency array but is only consulted in the no-query branch of the sort. When `deferredQuery` is truthy the memo takes the `search(deferredQuery)` branch, which doesn't touch `statusMap`, so any status data update will needlessly re-derive `filtered` and re-render the list mid-keystroke — undercutting the `useDeferredValue` intent.

```suggestion
	}, [deferredQuery, search, tasks, statusMap]); // statusMap is only used in the sort path (no query); consider splitting into two memos if status churn causes visible re-renders
```

### Issue 2 of 2
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/components/LinkedTaskSection/LinkedTaskSection.tsx:35
**Overly broad query dependency**

The live query only reads `collections.tasks` and `collections.taskStatuses`, but `collections` (the whole object) is listed as the dependency. `LinkTaskFrame` uses the more specific pattern — `[collections.tasks]` and `[collections.taskStatuses]` — for exactly this reason. If the `collections` reference is not perfectly stable, the join will re-run whenever any collection changes (PRs, hosts, members, …), not just on task or status changes. Consider `[collections.tasks, collections.taskStatuses, taskId]` to stay consistent with the rest of the file.

Reviews (1): Last reviewed commit: "feat(desktop): wire Link task command to..." | Re-trigger Greptile

return search(deferredQuery)
.slice(0, MAX_RESULTS)
.map((r) => r.item);
}, [deferredQuery, search, tasks, statusMap]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 statusMap is included in the dependency array but is only consulted in the no-query branch of the sort. When deferredQuery is truthy the memo takes the search(deferredQuery) branch, which doesn't touch statusMap, so any status data update will needlessly re-derive filtered and re-render the list mid-keystroke — undercutting the useDeferredValue intent.

Suggested change
}, [deferredQuery, search, tasks, statusMap]);
}, [deferredQuery, search, tasks, statusMap]); // statusMap is only used in the sort path (no query); consider splitting into two memos if status churn causes visible re-renders
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/commandPalette/ui/LinkTask/LinkTaskFrame.tsx
Line: 126

Comment:
`statusMap` is included in the dependency array but is only consulted in the no-query branch of the sort. When `deferredQuery` is truthy the memo takes the `search(deferredQuery)` branch, which doesn't touch `statusMap`, so any status data update will needlessly re-derive `filtered` and re-render the list mid-keystroke — undercutting the `useDeferredValue` intent.

```suggestion
	}, [deferredQuery, search, tasks, statusMap]); // statusMap is only used in the sort path (no query); consider splitting into two memos if status churn causes visible re-renders
```

How can I resolve this? If you propose a fix, please make it concise.

statusColor: s?.color ?? null,
statusProgress: s?.progressPercent ?? null,
})),
[collections, taskId],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Overly broad query dependency

The live query only reads collections.tasks and collections.taskStatuses, but collections (the whole object) is listed as the dependency. LinkTaskFrame uses the more specific pattern — [collections.tasks] and [collections.taskStatuses] — for exactly this reason. If the collections reference is not perfectly stable, the join will re-run whenever any collection changes (PRs, hosts, members, …), not just on task or status changes. Consider [collections.tasks, collections.taskStatuses, taskId] to stay consistent with the rest of the file.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/components/LinkedTaskSection/LinkedTaskSection.tsx
Line: 35

Comment:
**Overly broad query dependency**

The live query only reads `collections.tasks` and `collections.taskStatuses`, but `collections` (the whole object) is listed as the dependency. `LinkTaskFrame` uses the more specific pattern — `[collections.tasks]` and `[collections.taskStatuses]` — for exactly this reason. If the `collections` reference is not perfectly stable, the join will re-run whenever any collection changes (PRs, hosts, members, …), not just on task or status changes. Consider `[collections.tasks, collections.taskStatuses, taskId]` to stay consistent with the rest of the file.

How can I resolve this? If you propose a fix, please make it concise.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 10 files

@saddlepaddle saddlepaddle merged commit 37ce68e into main May 13, 2026
16 of 17 checks passed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@apps/desktop/src/renderer/commandPalette/ui/LinkTask/LinkTaskFrame.tsx`:
- Around line 128-131: The handler handleSelect currently calls
v2Workspaces.updateWorkspace and immediately shows toast.success and calls
setOpen(false) without awaiting or handling errors; change it to await the
updateWorkspace call (or handle its returned promise), wrap it in try/catch,
only call toast.success and setOpen(false) on success, and on failure call
toast.error with the error message (or a friendly message) and do not close the
frame; apply the same pattern to the other similar handler referenced (the one
at the other occurrence around line 147) so both updateWorkspace usages handle
async failures properly.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/components/LinkedTaskSection/LinkedTaskSection.tsx`:
- Around line 70-79: The external icon-only link in the LinkedTaskSection
component lacks an accessible name; update the anchor element (the <a> wrapping
the LuExternalLink icon in LinkedTaskSection) to include an explicit aria-label
(for example "Open task externally" or include the task title like `Open
{task.title} externally`) so screen readers can announce the purpose; keep the
existing title/onClick/rel attributes and ensure the aria-label value is
meaningful and localized if needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b27d7e0f-006a-46fb-8fbc-4530c89e49ab

📥 Commits

Reviewing files that changed from the base of the PR and between d1aee09 and cf223da.

📒 Files selected for processing (10)
  • apps/desktop/src/renderer/commandPalette/ui/LinkTask/LinkTaskFrame.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/DashboardSidebarWorkspaceHoverCardContent.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/components/LinkedTaskSection/LinkedTaskSection.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/components/LinkedTaskSection/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/useDashboardSidebarData/useDashboardSidebarData.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/types.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx
  • apps/desktop/src/renderer/routes/_authenticated/hooks/useOptimisticCollectionActions/useOptimisticCollectionActions.ts
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/collections.ts
  • packages/trpc/src/router/v2-workspace/v2-workspace.ts

Comment on lines 128 to 131
const handleSelect = (taskId: string, slug: string) => {
v2Workspaces.updateWorkspace(workspaceId, { taskId });
toast.success(`Linked ${slug} to workspace`);
void linkTaskToWorkspace(taskId, workspaceId);
setOpen(false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle mutation failure before showing success and closing the frame.

updateWorkspace is a remote write path, but the UI always shows success and closes immediately. If the mutation fails (e.g., org/task validation), users get a false success state.

Proposed fix
- const handleSelect = (taskId: string, slug: string) => {
- 	v2Workspaces.updateWorkspace(workspaceId, { taskId });
- 	toast.success(`Linked ${slug} to workspace`);
- 	setOpen(false);
- };
+ const handleSelect = async (taskId: string, slug: string) => {
+ 	try {
+ 		await v2Workspaces.updateWorkspace(workspaceId, { taskId });
+ 		toast.success(`Linked ${slug} to workspace`);
+ 		setOpen(false);
+ 	} catch {
+ 		toast.error(`Failed to link ${slug} to workspace`);
+ 	}
+ };
...
- onSelect={() => handleSelect(task.id, task.slug)}
+ onSelect={() => void handleSelect(task.id, task.slug)}

Also applies to: 147-147

🤖 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 `@apps/desktop/src/renderer/commandPalette/ui/LinkTask/LinkTaskFrame.tsx`
around lines 128 - 131, The handler handleSelect currently calls
v2Workspaces.updateWorkspace and immediately shows toast.success and calls
setOpen(false) without awaiting or handling errors; change it to await the
updateWorkspace call (or handle its returned promise), wrap it in try/catch,
only call toast.success and setOpen(false) on success, and on failure call
toast.error with the error message (or a friendly message) and do not close the
frame; apply the same pattern to the other similar handler referenced (the one
at the other occurrence around line 147) so both updateWorkspace usages handle
async failures properly.

Comment on lines +70 to +79
<a
href={task.externalUrl}
target="_blank"
rel="noopener noreferrer"
className="shrink-0 text-muted-foreground hover:text-foreground"
title="Open task externally"
onClick={(e) => e.stopPropagation()}
>
<LuExternalLink className="size-3" />
</a>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an explicit accessible name to the external icon link.

Use aria-label on this icon-only <a> so screen readers have a reliable name.

Suggested patch
 				{task.externalUrl && (
 					<a
 						href={task.externalUrl}
 						target="_blank"
 						rel="noopener noreferrer"
+						aria-label="Open task externally"
 						className="shrink-0 text-muted-foreground hover:text-foreground"
 						title="Open task externally"
 						onClick={(e) => e.stopPropagation()}
 					>
📝 Committable suggestion

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

Suggested change
<a
href={task.externalUrl}
target="_blank"
rel="noopener noreferrer"
className="shrink-0 text-muted-foreground hover:text-foreground"
title="Open task externally"
onClick={(e) => e.stopPropagation()}
>
<LuExternalLink className="size-3" />
</a>
<a
href={task.externalUrl}
target="_blank"
rel="noopener noreferrer"
aria-label="Open task externally"
className="shrink-0 text-muted-foreground hover:text-foreground"
title="Open task externally"
onClick={(e) => e.stopPropagation()}
>
<LuExternalLink className="size-3" />
</a>
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/components/LinkedTaskSection/LinkedTaskSection.tsx`
around lines 70 - 79, The external icon-only link in the LinkedTaskSection
component lacks an accessible name; update the anchor element (the <a> wrapping
the LuExternalLink icon in LinkedTaskSection) to include an explicit aria-label
(for example "Open task externally" or include the task title like `Open
{task.title} externally`) so screen readers can announce the purpose; keep the
existing title/onClick/rel attributes and ensure the aria-label value is
meaningful and localized if needed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant