From 20bad3ef845447e2b8e042ad75ccd72b69ea714b Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Tue, 2 Jun 2026 11:24:01 -0700 Subject: [PATCH] fix: address review findings (#4485) --- .../src/components/LinearItemDrawer.tsx | 19 +- src/renderer/src/components/TaskPage.tsx | 322 +++++++----------- .../src/components/linear-priority-icon.tsx | 92 +++++ src/renderer/src/store/slices/ui.ts | 4 +- 4 files changed, 234 insertions(+), 203 deletions(-) create mode 100644 src/renderer/src/components/linear-priority-icon.tsx diff --git a/src/renderer/src/components/LinearItemDrawer.tsx b/src/renderer/src/components/LinearItemDrawer.tsx index f33836551e4..8dd5cd9cd40 100644 --- a/src/renderer/src/components/LinearItemDrawer.tsx +++ b/src/renderer/src/components/LinearItemDrawer.tsx @@ -1,10 +1,8 @@ /* eslint-disable max-lines -- Why: the Linear drawer co-locates read-only preview, edit controls, and comment input so the full issue surface stays in one file. */ import React, { useCallback, useEffect, useRef, useState } from 'react' import { - AlertTriangle, ArrowRight, ChevronDown, - Circle, ExternalLink, Gauge, LoaderCircle, @@ -37,6 +35,7 @@ import { getLinearStateMarkerStyle, getLinearStatePillStyle } from '@/components/linear-state-pill-style' +import { LinearPriorityIcon } from '@/components/linear-priority-icon' import type { LinearIssue, LinearComment } from '../../../shared/types' import { linearAddIssueComment, @@ -440,11 +439,7 @@ export function LinearIssueEditSection({ className={propertyRowClass} aria-busy={priorityPending} > - {localPriority === 1 ? ( - - ) : ( - - )} + {PRIORITY_LABELS[localPriority] ?? `P${localPriority}`} @@ -458,10 +453,11 @@ export function LinearIssueEditSection({ type="button" onClick={() => handlePriorityChange(String(p))} className={cn( - LINEAR_EDIT_MENU_ITEM_CLASS, + LINEAR_EDIT_MENU_ITEM_WITH_ICON_CLASS, localPriority === p && 'bg-accent/50' )} > + {PRIORITY_LABELS[p]} ))} @@ -743,6 +739,7 @@ export function LinearIssueEditSection({ className={LINEAR_EDIT_CHIP_CLASS} aria-busy={priorityPending} > + {PRIORITY_LABELS[localPriority] ?? `P${localPriority}`} @@ -755,8 +752,12 @@ export function LinearIssueEditSection({ key={p} type="button" onClick={() => handlePriorityChange(String(p))} - className={cn(LINEAR_EDIT_MENU_ITEM_CLASS, localPriority === p && 'bg-accent/50')} + className={cn( + LINEAR_EDIT_MENU_ITEM_WITH_ICON_CLASS, + localPriority === p && 'bg-accent/50' + )} > + {PRIORITY_LABELS[p]} ))} diff --git a/src/renderer/src/components/TaskPage.tsx b/src/renderer/src/components/TaskPage.tsx index d08cf8a4659..877d049e98d 100644 --- a/src/renderer/src/components/TaskPage.tsx +++ b/src/renderer/src/components/TaskPage.tsx @@ -36,8 +36,7 @@ import { X, FolderKanban, Tag, - UserRound, - AlertTriangle + UserRound } from 'lucide-react' import { toast } from 'sonner' @@ -88,6 +87,7 @@ import { LinearScopeSelector } from '@/components/linear-scope-selector' import RepoBadgeLabel from '@/components/repo/RepoBadgeLabel' import IssueSourceIndicator, { sameGitHubOwnerRepo } from '@/components/github/IssueSourceIndicator' import IssueSourceSelector, { issueSourceChipClass } from '@/components/github/IssueSourceSelector' +import { LinearPriorityIcon } from '@/components/linear-priority-icon' import { reconcileLinearTeamSelection } from '@/components/task-page-linear-team-selection' import { useConfirmationDialog } from '@/components/confirmation-dialog' import { @@ -306,16 +306,6 @@ const SOURCE_OPTIONS: SourceOption[] = [ } ] -type LinearPresetId = 'assigned' | 'created' | 'all' | 'completed' -type LinearPreset = { id: LinearPresetId; label: string } - -const LINEAR_PRESETS: LinearPreset[] = [ - { id: 'all', label: 'All' }, - { id: 'assigned', label: 'My Issues' }, - { id: 'created', label: 'Created' }, - { id: 'completed', label: 'Completed' } -] - type JiraPresetId = 'assigned' | 'reported' | 'all' | 'done' type JiraPreset = { id: JiraPresetId; label: string } @@ -751,23 +741,23 @@ function groupLinearIssues( } function getLinearIssueGridTemplate(visibleProperties: ReadonlySet): string { - const columns = ['96px', 'minmax(180px,1.4fr)'] - if (visibleProperties.has('state')) { - columns.push('140px') - } - if (visibleProperties.has('priority')) { - columns.push('92px') - } - if (visibleProperties.has('assignee')) { - columns.push('150px') + const columns = ['96px', 'minmax(240px,1.55fr)'] + if (visibleProperties.has('labels')) { + columns.push('minmax(168px,0.9fr)') } if (visibleProperties.has('team')) { - columns.push('160px') + columns.push('minmax(172px,0.9fr)') + } + if (visibleProperties.has('state')) { + columns.push('138px') + } + if (visibleProperties.has('assignee')) { + columns.push('64px') } if (visibleProperties.has('updated')) { - columns.push('100px') + columns.push('104px') } - columns.push('72px') + columns.push('64px') return columns.join(' ') } @@ -3137,7 +3127,6 @@ export default function TaskPage(): React.JSX.Element { const [linearError, setLinearError] = useState(null) const [linearSearchInput, setLinearSearchInput] = useState('') const [appliedLinearSearch, setAppliedLinearSearch] = useState('') - const [activeLinearPreset, setActiveLinearPreset] = useState('all') const [linearViewMode, setLinearViewMode] = useState('list') const [linearGroupBy, setLinearGroupBy] = useState('none') const [linearOrderBy, setLinearOrderBy] = useState('priority') @@ -3315,10 +3304,8 @@ export default function TaskPage(): React.JSX.Element { setActiveTaskPreset(presetId) } - const linearPreset = taskResumeState?.linearPreset ?? 'all' const linearQuery = taskResumeState?.linearQuery ?? '' setLinearMode(taskResumeState?.linearMode ?? 'issues') - setActiveLinearPreset(linearPreset) setLinearSearchInput(linearQuery) setAppliedLinearSearch(linearQuery) @@ -5373,7 +5360,6 @@ export default function TaskPage(): React.JSX.Element { useEffect(() => { setLinearIssueLimit(LINEAR_ITEM_LIMIT) }, [ - activeLinearPreset, appliedLinearSearch, linearMode, selectedLinearCustomView?.id, @@ -5407,7 +5393,7 @@ export default function TaskPage(): React.JSX.Element { const readArgs = trimmed.length > 0 ? ({ kind: 'search', query: trimmed, limit: LINEAR_ITEM_LIMIT } as const) - : ({ kind: 'list', filter: activeLinearPreset, limit: effectiveLinearIssueLimit } as const) + : ({ kind: 'list', filter: 'all', limit: effectiveLinearIssueLimit } as const) const cachedResult = getCachedLinearIssues(readArgs) if (readArgs.kind === 'search') { setLinearIssuesHasMore(false) @@ -5425,7 +5411,7 @@ export default function TaskPage(): React.JSX.Element { const requestSignature = trimmed.length > 0 ? `${selectedLinearWorkspaceId ?? 'default'}::search::${trimmed}::${LINEAR_ITEM_LIMIT}` - : `${selectedLinearWorkspaceId ?? 'default'}::list::${activeLinearPreset}::${effectiveLinearIssueLimit}` + : `${selectedLinearWorkspaceId ?? 'default'}::list::all::${effectiveLinearIssueLimit}` const previousRequest = lastLinearRequestRef.current const forceRefresh = linearRefreshNonce > 0 && @@ -5512,7 +5498,6 @@ export default function TaskPage(): React.JSX.Element { linearStatus.connected, selectedLinearWorkspaceId, appliedLinearSearch, - activeLinearPreset, linearIssueLimit, linearRefreshNonce, taskResumeApplied, @@ -6586,85 +6571,53 @@ export default function TaskPage(): React.JSX.Element { {linearMode === 'issues' ? ( - <> -
- {LINEAR_PRESETS.map((preset) => { - const active = !linearSearchInput && activeLinearPreset === preset.id - return ( - - ) - })} -
-
-
- - setLinearSearchInput(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') { - if ( - shouldSuppressEnterSubmit( - { - isComposing: e.nativeEvent.isComposing, - shiftKey: e.shiftKey - }, - false - ) - ) { - return - } - e.preventDefault() - const trimmed = linearSearchInput.trim() - setLinearSearchInput(trimmed) - setAppliedLinearSearch(trimmed) - setTaskResumeState({ linearQuery: trimmed, linearMode: 'issues' }) - setLinearRefreshNonce((n) => n + 1) +
+
+ + setLinearSearchInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + if ( + shouldSuppressEnterSubmit( + { + isComposing: e.nativeEvent.isComposing, + shiftKey: e.shiftKey + }, + false + ) + ) { + return } + e.preventDefault() + const trimmed = linearSearchInput.trim() + setLinearSearchInput(trimmed) + setAppliedLinearSearch(trimmed) + setTaskResumeState({ linearQuery: trimmed, linearMode: 'issues' }) + setLinearRefreshNonce((n) => n + 1) + } + }} + placeholder="Search Linear issues..." + className="h-8 rounded-md border-border/50 bg-background pl-8 pr-8 text-xs" + /> + {linearSearchInput ? ( + - ) : null} -
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground transition hover:text-foreground" + > + + + ) : null}
- +
) : linearMode === 'projects' && !selectedLinearProject ? (
@@ -8286,10 +8239,12 @@ export default function TaskPage(): React.JSX.Element { > Key Issue - {effectiveLinearDisplayProperties.has('state') ? Status : null} - {effectiveLinearDisplayProperties.has('priority') ? Priority : null} - {effectiveLinearDisplayProperties.has('assignee') ? Assignee : null} + {effectiveLinearDisplayProperties.has('labels') ? Labels : null} {effectiveLinearDisplayProperties.has('team') ? Team : null} + {effectiveLinearDisplayProperties.has('state') ? Status : null} + {effectiveLinearDisplayProperties.has('assignee') ? ( + Assignee + ) : null} {effectiveLinearDisplayProperties.has('updated') ? Updated : null}
@@ -8429,8 +8384,14 @@ export default function TaskPage(): React.JSX.Element { >
-
- {issue.identifier} +
+ {effectiveLinearDisplayProperties.has('priority') ? ( + + ) : null} + {issue.identifier}

{issue.title} @@ -8466,9 +8427,6 @@ export default function TaskPage(): React.JSX.Element { {effectiveLinearDisplayProperties.has('state') ? ( ) : null} - {effectiveLinearDisplayProperties.has('priority') ? ( - {getLinearPriorityLabel(issue.priority)} - ) : null} {effectiveLinearDisplayProperties.has('assignee') ? ( {issue.assignee?.displayName ?? 'Unassigned'} ) : null} @@ -8556,12 +8514,17 @@ export default function TaskPage(): React.JSX.Element { )} style={linearIssueGridStyle} > - - {issue.identifier} - +
+ + {issue.identifier} + +
+ {effectiveLinearDisplayProperties.has('priority') ? ( + + ) : null} {issue.identifier} @@ -8573,11 +8536,6 @@ export default function TaskPage(): React.JSX.Element { {effectiveLinearDisplayProperties.has('state') ? ( ) : null} - {effectiveLinearDisplayProperties.has('priority') ? ( - - {getLinearPriorityLabel(issue.priority)} - - ) : null} {effectiveLinearDisplayProperties.has('assignee') ? ( {issue.assignee?.displayName ?? 'Unassigned'} @@ -8589,59 +8547,61 @@ export default function TaskPage(): React.JSX.Element { ) : null}
- {effectiveLinearDisplayProperties.has('labels') ? ( -
- {labels.map((label) => ( - - {label} - - ))} - {issue.labels.length > labels.length ? ( - - +{issue.labels.length - labels.length} - - ) : null} -
- ) : null}
+ {effectiveLinearDisplayProperties.has('labels') ? ( +
+ {labels.map((label) => ( + + {label} + + ))} + {issue.labels.length > labels.length ? ( + + +{issue.labels.length - labels.length} + + ) : null} +
+ ) : null} + + {effectiveLinearDisplayProperties.has('team') ? ( +
+
{teamLabel}
+
+ ) : null} + {effectiveLinearDisplayProperties.has('state') ? (
) : null} - {effectiveLinearDisplayProperties.has('priority') ? ( - - {getLinearPriorityLabel(issue.priority)} - - ) : null} - {effectiveLinearDisplayProperties.has('assignee') ? ( -
- {issue.assignee?.avatarUrl ? ( - {issue.assignee.displayName} - ) : ( - - {issue.assignee?.displayName?.slice(0, 1) ?? '-'} - - )} - - {issue.assignee?.displayName ?? 'Unassigned'} - -
- ) : null} - - {effectiveLinearDisplayProperties.has('team') ? ( -
-
{teamLabel}
+
+ + +
+ {issue.assignee?.avatarUrl ? ( + {issue.assignee.displayName} + ) : ( + (issue.assignee?.displayName?.slice(0, 1) ?? '-') + )} +
+
+ + {issue.assignee?.displayName ?? 'Unassigned'} + +
) : null} @@ -9191,19 +9151,7 @@ export default function TaskPage(): React.JSX.Element { disabled={newLinearIssueSubmitting} className="flex items-center gap-1.5 px-2 py-1 rounded-md text-xs border border-border/80 bg-muted/15 hover:bg-muted/50 active:bg-muted transition-colors text-foreground/80 cursor-pointer disabled:opacity-50" > - + {newLinearIssuePriority === 1 ? 'Urgent' @@ -9240,19 +9188,7 @@ export default function TaskPage(): React.JSX.Element { }`} >
- + {p.label}
{newLinearIssuePriority === p.val && ( diff --git a/src/renderer/src/components/linear-priority-icon.tsx b/src/renderer/src/components/linear-priority-icon.tsx new file mode 100644 index 00000000000..0741343cc15 --- /dev/null +++ b/src/renderer/src/components/linear-priority-icon.tsx @@ -0,0 +1,92 @@ +import React from 'react' + +import { cn } from '@/lib/utils' + +// Why: mirror Linear's priority glyph shape while keeping color on Orca tokens. +const LINEAR_PRIORITY_ICON_LABELS: Record = { + 0: 'No priority', + 1: 'Urgent', + 2: 'High', + 3: 'Medium', + 4: 'Low' +} + +function getLinearPriorityBarCount(priority: number): number { + if (priority === 2) { + return 3 + } + if (priority === 3) { + return 2 + } + if (priority === 4) { + return 1 + } + return 0 +} + +export function getLinearPriorityIconLabel(priority: number): string { + return LINEAR_PRIORITY_ICON_LABELS[priority] ?? `P${priority}` +} + +export function LinearPriorityIcon({ + priority, + className, + label = getLinearPriorityIconLabel(priority) +}: { + priority: number + className?: string + label?: string +}): React.JSX.Element { + if (priority === 1) { + return ( + + + Priority: {label} + + ) + } + + if (priority === 0) { + return ( + + + ) + } + + const activeBars = getLinearPriorityBarCount(priority) + return ( + + {[1, 2, 3].map((bar) => ( + + ) +} diff --git a/src/renderer/src/store/slices/ui.ts b/src/renderer/src/store/slices/ui.ts index b92914c1af5..0c8595f56c1 100644 --- a/src/renderer/src/store/slices/ui.ts +++ b/src/renderer/src/store/slices/ui.ts @@ -1076,9 +1076,11 @@ export const createUISlice: StateCreator = (set, get) if (query) { state.prefetchLinearIssues({ kind: 'search', query, limit: LINEAR_TASK_PREFETCH_LIMIT }) } else { + // Why: TaskPage no longer exposes Linear preset filters; keep warm + // prefetch aligned with the default unsearched issue list. state.prefetchLinearIssues({ kind: 'list', - filter: resume?.linearPreset ?? 'all', + filter: 'all', limit: LINEAR_TASK_PREFETCH_LIMIT }) }