✨ feat(vscode): 审查列可以手动标记为通过
This commit is contained in:
1 parent
20249d6a08
commit
4094ffa49e
12 files changed
+157
-6
No files matched your search
@@ -69,6 +69,7 @@ export function App() {
|
||||
setDependency,
|
||||
clearDependency,
|
||||
updateIssueAutoReview,
|
||||
updateIssueReviewPassed,
|
||||
updateIssueProfilePath,
|
||||
updateIssueBrainstormProfilePath,
|
||||
updateIssueTestProfilePath,
|
||||
@@ -399,6 +400,7 @@ export function App() {
|
||||
onColumnChange={changeColumn}
|
||||
onDependencySet={setDependency}
|
||||
onDependencyClear={clearDependency}
|
||||
onSetReviewPassed={updateIssueReviewPassed}
|
||||
me={me}
|
||||
avatars={avatars}
|
||||
/>
|
||||
@@ -435,6 +437,7 @@ export function App() {
|
||||
onContinueBrainstorm={continueBrainstorm}
|
||||
onClearSession={clearSession}
|
||||
onUpdateAutoReview={updateIssueAutoReview}
|
||||
onUpdateReviewPassed={updateIssueReviewPassed}
|
||||
onUpdateProfilePath={updateIssueProfilePath}
|
||||
onUpdateBrainstormProfilePath={updateIssueBrainstormProfilePath}
|
||||
onUpdateTestProfilePath={updateIssueTestProfilePath}
|
||||
|
||||
@@ -55,6 +55,7 @@ export interface BottomTabsProps {
|
||||
onContinueBrainstorm: (issueNumber: number) => void
|
||||
onClearSession: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype') => void
|
||||
onUpdateAutoReview: (issueNumber: number, value: boolean) => void
|
||||
onUpdateReviewPassed: (issueNumber: number, value: boolean) => void
|
||||
onUpdateProfilePath: (issueNumber: number, profilePath: string) => void
|
||||
onUpdateBrainstormProfilePath: (issueNumber: number, brainstormProfilePath: string) => void
|
||||
onUpdateTestProfilePath: (issueNumber: number, testProfilePath: string) => void
|
||||
@@ -171,6 +172,7 @@ export function BottomTabs(props: BottomTabsProps) {
|
||||
onContinueBrainstorm={props.onContinueBrainstorm}
|
||||
onClearSession={props.onClearSession}
|
||||
onUpdateAutoReview={props.onUpdateAutoReview}
|
||||
onUpdateReviewPassed={props.onUpdateReviewPassed}
|
||||
onUpdateProfilePath={props.onUpdateProfilePath}
|
||||
onUpdateBrainstormProfilePath={props.onUpdateBrainstormProfilePath}
|
||||
onUpdateTestProfilePath={props.onUpdateTestProfilePath}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Ref } from 'react'
|
||||
import type { Issue } from '../types'
|
||||
import { useSortable } from '@dnd-kit/sortable'
|
||||
import { CSS } from '@dnd-kit/utilities'
|
||||
import { Lock } from 'lucide-react'
|
||||
import { Check, Lock } from 'lucide-react'
|
||||
import { forwardRef } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Card } from './ui/card'
|
||||
@@ -26,6 +26,8 @@ interface IssueCardProps {
|
||||
ownerLogin?: string
|
||||
/** 归属人头像 data URI;缺失时回退首字母圆点。 */
|
||||
ownerAvatar?: string
|
||||
/** 审查列:手动标通过或改回审查中。 */
|
||||
onSetReviewPassed?: (issueNumber: number, value: boolean) => void
|
||||
}
|
||||
|
||||
/** 同事工单的归属人标记:有头像用头像,没有就用 login 首字母。 */
|
||||
@@ -52,7 +54,7 @@ function OwnerBadge({ login, avatar }: { login: string, avatar?: string }) {
|
||||
}
|
||||
|
||||
export const IssueCard = forwardRef<HTMLDivElement, IssueCardProps>(
|
||||
({ issue, selected, onSelect, depth = 0, locked, lockedPrerequisite, readOnly, ownerLogin, ownerAvatar }, ref) => {
|
||||
({ issue, selected, onSelect, depth = 0, locked, lockedPrerequisite, readOnly, ownerLogin, ownerAvatar, onSetReviewPassed }, ref) => {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging }
|
||||
= useSortable({ id: issue.id, data: { column: issue.column }, disabled: readOnly })
|
||||
|
||||
@@ -72,6 +74,9 @@ export const IssueCard = forwardRef<HTMLDivElement, IssueCardProps>(
|
||||
(ref as React.MutableRefObject<HTMLDivElement | null>).current = node
|
||||
}
|
||||
|
||||
const showMark = issue.column === 'review' && !readOnly && issue.source !== 'youtrack' && onSetReviewPassed != null
|
||||
const cornerCount = (showMark ? 1 : 0) + (locked ? 1 : 0) + (readOnly && ownerLogin ? 1 : 0)
|
||||
|
||||
return (
|
||||
<Card
|
||||
ref={setRefs as unknown as Ref<HTMLDivElement>}
|
||||
@@ -91,6 +96,26 @@ export const IssueCard = forwardRef<HTMLDivElement, IssueCardProps>(
|
||||
: undefined}
|
||||
>
|
||||
<div className="absolute right-1.5 top-1.5 flex items-center gap-1">
|
||||
{showMark
|
||||
? (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'grid size-3.5 place-items-center rounded-sm hover:bg-black/10 dark:hover:bg-white/10',
|
||||
issue.reviewPassed === true ? 'text-green-400' : 'text-amber-400',
|
||||
)}
|
||||
data-tooltip={issue.reviewPassed === true ? '已通过,点击改回审查中' : '标记为已通过'}
|
||||
aria-label={issue.reviewPassed === true ? '改回审查中' : '标记为已通过'}
|
||||
onPointerDown={e => e.stopPropagation()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onSetReviewPassed(issue.number, issue.reviewPassed !== true)
|
||||
}}
|
||||
>
|
||||
<Check className="size-3" />
|
||||
</button>
|
||||
)
|
||||
: null}
|
||||
{locked
|
||||
? (
|
||||
<span
|
||||
@@ -116,10 +141,12 @@ export const IssueCard = forwardRef<HTMLDivElement, IssueCardProps>(
|
||||
: null}
|
||||
<div className={cn(
|
||||
'truncate',
|
||||
(locked || (readOnly && ownerLogin)) && 'pr-5',
|
||||
locked && readOnly && ownerLogin && 'pr-10',
|
||||
cornerCount === 1 && 'pr-5',
|
||||
cornerCount === 2 && 'pr-10',
|
||||
cornerCount >= 3 && 'pr-14',
|
||||
issue.column === 'review' && (issue.reviewPassed === true ? 'text-green-400' : 'text-amber-400'),
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
<span className="font-mono opacity-60">
|
||||
{issue.source === 'youtrack' ? issue.externalId : `#${issue.number}`}
|
||||
</span>
|
||||
|
||||
@@ -119,6 +119,8 @@ interface IssueDetailPanelProps {
|
||||
onClearSession: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype') => void
|
||||
/** Persist a per-issue `autoReview` override into the state JSON. */
|
||||
onUpdateAutoReview: (issueNumber: number, value: boolean) => void
|
||||
/** 审查列手动标记通过。 */
|
||||
onUpdateReviewPassed: (issueNumber: number, value: boolean) => void
|
||||
/** 已知 Claude profile 列表,用于「配置文件」下拉选项。 */
|
||||
profiles: ClaudeProfile[]
|
||||
/** Persist a per-issue `profilePath` override into the state JSON. */
|
||||
@@ -180,6 +182,7 @@ export function IssueDetailPanel({
|
||||
onContinueBrainstorm,
|
||||
onClearSession,
|
||||
onUpdateAutoReview,
|
||||
onUpdateReviewPassed,
|
||||
profiles,
|
||||
onUpdateProfilePath,
|
||||
onUpdateBrainstormProfilePath,
|
||||
@@ -559,6 +562,14 @@ export function IssueDetailPanel({
|
||||
type: 'boolean',
|
||||
description: `打开新 PR 时是否自动启动 codex 审查;未设置时跟随全局(当前全局:${globalAutoReview ? '开' : '关'})`,
|
||||
},
|
||||
...(issue?.column === 'review' && issue.source !== 'youtrack'
|
||||
? [{
|
||||
key: 'reviewPassed',
|
||||
label: '审查通过',
|
||||
type: 'boolean' as const,
|
||||
description: '手动标成已通过,标题变绿。新的提交会清掉这个标记。',
|
||||
}]
|
||||
: []),
|
||||
{
|
||||
key: 'color',
|
||||
label: '颜色',
|
||||
@@ -772,6 +783,7 @@ export function IssueDetailPanel({
|
||||
testSessionId: issue.testSessionId ?? null,
|
||||
prototypeSessionId: issue.prototypeSessionId ?? null,
|
||||
autoReview: issue.autoReview ?? globalAutoReview,
|
||||
reviewPassed: issue.reviewPassed === true,
|
||||
color: issue.color ?? null,
|
||||
// 展示/选中值按 basename 映射到本机 profiles,避免跨机绝对路径变成「自定义」项。
|
||||
brainstormProfilePath: matchProfileOption(issue.brainstormProfilePath, profiles) || null,
|
||||
@@ -1008,6 +1020,8 @@ export function IssueDetailPanel({
|
||||
return
|
||||
if (key === 'autoReview' && issue && typeof value === 'boolean')
|
||||
onUpdateAutoReview(issue.number, value)
|
||||
if (key === 'reviewPassed' && issue && typeof value === 'boolean')
|
||||
onUpdateReviewPassed(issue.number, value)
|
||||
if (key === 'brainstormProfilePath' && issue && typeof value === 'string')
|
||||
onUpdateBrainstormProfilePath(issue.number, value)
|
||||
if (key === 'profilePath' && issue && typeof value === 'string')
|
||||
|
||||
@@ -41,6 +41,7 @@ interface KanbanBoardProps {
|
||||
onColumnChange?: (issueNumber: number, toColumn: IssueColumn, source?: 'gitea' | 'youtrack', externalId?: string) => void
|
||||
onDependencySet?: (issueNumber: number, prerequisiteNumber: number) => void
|
||||
onDependencyClear?: (issueNumber: number, prerequisiteNumber: number) => void
|
||||
onSetReviewPassed?: (issueNumber: number, value: boolean) => void
|
||||
/** 当前 Gitea login;用于判定哪些卡是同事的(只读)。 */
|
||||
me?: string
|
||||
/** Gitea login → 头像 data URI。 */
|
||||
@@ -117,6 +118,7 @@ export function KanbanBoard({
|
||||
onColumnChange,
|
||||
onDependencySet,
|
||||
onDependencyClear,
|
||||
onSetReviewPassed,
|
||||
me,
|
||||
avatars,
|
||||
}: KanbanBoardProps) {
|
||||
@@ -281,6 +283,7 @@ export function KanbanBoard({
|
||||
readOnly={readOnly}
|
||||
ownerLogin={ownerLogin}
|
||||
ownerAvatar={ownerLogin ? avatars?.[ownerLogin] : undefined}
|
||||
onSetReviewPassed={onSetReviewPassed}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -232,6 +232,7 @@ export interface UseIssuesResult {
|
||||
setDependency: (issueNumber: number, prerequisiteNumber: number) => void
|
||||
clearDependency: (issueNumber: number, prerequisiteNumber: number) => void
|
||||
updateIssueAutoReview: (issueNumber: number, value: boolean) => void
|
||||
updateIssueReviewPassed: (issueNumber: number, value: boolean) => void
|
||||
/**
|
||||
* 覆盖该工单实施会话使用的 Claude 配置文件,乐观更新本地 state 后
|
||||
* postMessage 持久化到 state JSON。失败时扩展端 push `issue/patch` 回滚。
|
||||
@@ -730,6 +731,20 @@ export function useIssues(): UseIssuesResult {
|
||||
postMessage({ type: 'issue/update-auto-review', issueNumber, value })
|
||||
}, [])
|
||||
|
||||
const updateIssueReviewPassed = useCallback((issueNumber: number, value: boolean): void => {
|
||||
setState((prev) => {
|
||||
if (prev.status !== 'ready')
|
||||
return prev
|
||||
return {
|
||||
status: 'ready',
|
||||
issues: prev.issues.map(i =>
|
||||
i.number === issueNumber ? { ...i, reviewPassed: value } : i,
|
||||
),
|
||||
}
|
||||
})
|
||||
postMessage({ type: 'issue/update-review-passed', issueNumber, value })
|
||||
}, [])
|
||||
|
||||
const updateIssueProfilePath = useCallback((issueNumber: number, profilePath: string): void => {
|
||||
// Optimistically update local state so the dropdown reflects immediately
|
||||
// without remounting the issues list / detail panel via a full reload.
|
||||
@@ -1097,5 +1112,5 @@ export function useIssues(): UseIssuesResult {
|
||||
])
|
||||
}, [state, me])
|
||||
|
||||
return { state, settings, avatars, isSessionTabPending, globalAutoReview, youtrackConfigured, importYouTrack, toasts, profiles, setIssues, refresh, refreshing, scope, toggleScope, saveSettings, listYouTrackProjects, youtrackProjects, dismissSettings, requestEditAuth, createIssue, dismissToast, openUrl, resumeSession, resumeReviewSession, startReviewSession, startTestSession, resumeTestSession, startPrototypeSession, resumePrototypeSession, focusSession, openFile, openPrototypeInFigma, implement, generatePrDiffSummary, isPrDiffSummaryRunning, openPr, openWorktree, deleteWorktree, mergeBranch, deleteIssue, closeIssue, closeSessionTab, continueBrainstorm, changeColumn, setDependency, clearDependency, updateIssueAutoReview, updateIssueProfilePath, updateIssueBrainstormProfilePath, clearSession, updateIssueTestProfilePath, updateIssuePrototypeProfilePath, logs, fetchLogs, clearLogs, pendingSelectId, clearPendingSelect, pendingSelectTab, clearPendingSelectTab, commitRunning, runCommit, hasChanges, release, runRelease, cherryPick, dismissCherryPick, confirmCherryPick, envLocked, envFileCount, envLockRunning, envLockTitle, toggleEnvLock, me, handoffUsers, requestHandoffUsers, startHandoff, acceptHandoff, isHandoffRunning, askHandoff, cancelHandoffRequest, acceptHandoffRequest, rejectHandoffRequest }
|
||||
return { state, settings, avatars, isSessionTabPending, globalAutoReview, youtrackConfigured, importYouTrack, toasts, profiles, setIssues, refresh, refreshing, scope, toggleScope, saveSettings, listYouTrackProjects, youtrackProjects, dismissSettings, requestEditAuth, createIssue, dismissToast, openUrl, resumeSession, resumeReviewSession, startReviewSession, startTestSession, resumeTestSession, startPrototypeSession, resumePrototypeSession, focusSession, openFile, openPrototypeInFigma, implement, generatePrDiffSummary, isPrDiffSummaryRunning, openPr, openWorktree, deleteWorktree, mergeBranch, deleteIssue, closeIssue, closeSessionTab, continueBrainstorm, changeColumn, setDependency, clearDependency, updateIssueAutoReview, updateIssueReviewPassed, updateIssueProfilePath, updateIssueBrainstormProfilePath, clearSession, updateIssueTestProfilePath, updateIssuePrototypeProfilePath, logs, fetchLogs, clearLogs, pendingSelectId, clearPendingSelect, pendingSelectTab, clearPendingSelectTab, commitRunning, runCommit, hasChanges, release, runRelease, cherryPick, dismissCherryPick, confirmCherryPick, envLocked, envFileCount, envLockRunning, envLockTitle, toggleEnvLock, me, handoffUsers, requestHandoffUsers, startHandoff, acceptHandoff, isHandoffRunning, askHandoff, cancelHandoffRequest, acceptHandoffRequest, rejectHandoffRequest }
|
||||
}
|
||||
@@ -267,6 +267,7 @@ export type WebviewToExtension
|
||||
| { type: 'dependency/set', issueNumber: number, prerequisiteNumber: number }
|
||||
| { type: 'dependency/clear', issueNumber: number, prerequisiteNumber: number }
|
||||
| { type: 'issue/update-auto-review', issueNumber: number, value: boolean }
|
||||
| { type: 'issue/update-review-passed', issueNumber: number, value: boolean }
|
||||
| { type: 'issue/update-profile-path', issueNumber: number, profilePath: string }
|
||||
| { type: 'issue/update-brainstorm-profile-path', issueNumber: number, brainstormProfilePath: string }
|
||||
| { type: 'issue/clear-session', issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype' }
|
||||
|
||||
Reference in new issue
Block a user