1+ import { NestedButton } from "@components/ui/NestedButton" ;
12import { Tooltip } from "@components/ui/Tooltip" ;
23import type { SidebarPrState } from "@features/sidebar/hooks/useTaskPrStatus" ;
34import type { WorkspaceMode } from "@main/services/workspace/schemas" ;
4- import { Archive , PushPin } from "@phosphor-icons/react" ;
5+ import { Archive , GitPullRequest , PushPin } from "@phosphor-icons/react" ;
6+ import { parseGithubUrl } from "@posthog/git/utils" ;
57import type { TaskRunStatus } from "@shared/types" ;
8+ import { openUrlInBrowser } from "@utils/browser" ;
69import { formatRelativeTimeShort } from "@utils/time" ;
7- import { useCallback , useEffect , useRef , useState } from "react" ;
10+ import { useCallback , useEffect , useMemo , useRef , useState } from "react" ;
811import { SidebarItem } from "../SidebarItem" ;
912import { TaskIcon } from "./TaskIcon" ;
1013
14+ function PrBadge ( { url, number } : { url : string ; number : number } ) {
15+ return (
16+ < Tooltip content = "Open pull request" side = "top" >
17+ < NestedButton
18+ aria-label = { `Open pull request #${ number } ` }
19+ className = "flex h-4 shrink-0 cursor-pointer items-center gap-0.5 rounded bg-gray-3 px-1 text-[11px] text-gray-11 transition-colors hover:bg-gray-4 hover:text-gray-12"
20+ onActivate = { ( ) => {
21+ void openUrlInBrowser ( url ) ;
22+ } }
23+ >
24+ < GitPullRequest size = { 10 } weight = "bold" />
25+ { `#${ number } ` }
26+ </ NestedButton >
27+ </ Tooltip >
28+ ) ;
29+ }
30+
1131interface TaskItemProps {
1232 depth ?: number ;
1333 taskId : string ;
@@ -27,6 +47,7 @@ interface TaskItemProps {
2747 slackThreadUrl ?: string ;
2848 prState ?: SidebarPrState ;
2949 hasDiff ?: boolean ;
50+ prUrl ?: string | null ;
3051 timestamp ?: number ;
3152 isEditing ?: boolean ;
3253 onClick : ( e : React . MouseEvent ) => void ;
@@ -53,50 +74,24 @@ function TaskHoverToolbar({
5374 < span className = "hidden shrink-0 items-center gap-0.5 group-hover:flex" >
5475 { onTogglePin && (
5576 < Tooltip content = { isPinned ? "Unpin task" : "Pin task" } side = "top" >
56- { /* biome-ignore lint/a11y/useSemanticElements: Cannot use button inside parent button (SidebarItem) */ }
57- < span
58- role = "button"
59- tabIndex = { 0 }
77+ < NestedButton
78+ aria-label = { isPinned ? "Unpin task" : "Pin task" }
6079 className = "flex h-5 w-5 cursor-pointer items-center justify-center rounded text-gray-10 transition-colors hover:bg-gray-4 hover:text-gray-12"
61- onClick = { ( e ) => {
62- e . stopPropagation ( ) ;
63- onTogglePin ( ) ;
64- } }
65- onDoubleClick = { ( e ) => e . stopPropagation ( ) }
66- onKeyDown = { ( e ) => {
67- if ( e . key === "Enter" || e . key === " " ) {
68- e . preventDefault ( ) ;
69- e . stopPropagation ( ) ;
70- onTogglePin ( ) ;
71- }
72- } }
80+ onActivate = { onTogglePin }
7381 >
7482 < PushPin size = { 12 } weight = { isPinned ? "fill" : "regular" } />
75- </ span >
83+ </ NestedButton >
7684 </ Tooltip >
7785 ) }
7886 { onArchive && (
7987 < Tooltip content = "Archive task" side = "top" >
80- { /* biome-ignore lint/a11y/useSemanticElements: Cannot use button inside parent button (SidebarItem) */ }
81- < span
82- role = "button"
83- tabIndex = { 0 }
88+ < NestedButton
89+ aria-label = "Archive task"
8490 className = "flex h-5 w-5 cursor-pointer items-center justify-center rounded text-gray-10 transition-colors hover:bg-gray-4 hover:text-gray-12"
85- onClick = { ( e ) => {
86- e . stopPropagation ( ) ;
87- onArchive ( ) ;
88- } }
89- onDoubleClick = { ( e ) => e . stopPropagation ( ) }
90- onKeyDown = { ( e ) => {
91- if ( e . key === "Enter" || e . key === " " ) {
92- e . preventDefault ( ) ;
93- e . stopPropagation ( ) ;
94- onArchive ( ) ;
95- }
96- } }
91+ onActivate = { onArchive }
9792 >
9893 < Archive size = { 12 } />
99- </ span >
94+ </ NestedButton >
10095 </ Tooltip >
10196 ) }
10297 </ span >
@@ -123,6 +118,7 @@ export function TaskItem({
123118 slackThreadUrl,
124119 prState,
125120 hasDiff,
121+ prUrl,
126122 timestamp,
127123 isEditing = false ,
128124 onClick,
@@ -149,11 +145,19 @@ export function TaskItem({
149145 />
150146 ) ;
151147
152- const timestampNode = timestamp ? (
153- < span className = "shrink-0 text-[11px] text-gray-11 group-hover:hidden" >
154- { formatRelativeTimeShort ( timestamp ) }
155- </ span >
156- ) : null ;
148+ const prRef = useMemo ( ( ) => ( prUrl ? parseGithubUrl ( prUrl ) : null ) , [ prUrl ] ) ;
149+ const prBadge =
150+ prUrl && prRef ?. kind === "pr" ? (
151+ < PrBadge url = { prUrl } number = { prRef . number } />
152+ ) : null ;
153+
154+ // The PR badge takes the timestamp's slot, so hide the timestamp when shown.
155+ const timestampNode =
156+ timestamp && ! prBadge ? (
157+ < span className = "shrink-0 text-[11px] text-gray-11 group-hover:hidden" >
158+ { formatRelativeTimeShort ( timestamp ) }
159+ </ span >
160+ ) : null ;
157161
158162 const toolbar =
159163 ! hideHoverActions && ( onArchive || onTogglePin ) ? (
@@ -165,8 +169,9 @@ export function TaskItem({
165169 ) : null ;
166170
167171 const endContent =
168- timestampNode || toolbar ? (
172+ prBadge || timestampNode || toolbar ? (
169173 < >
174+ { prBadge }
170175 { timestampNode }
171176 { toolbar }
172177 </ >
0 commit comments