✨ feat(vscode): 头脑风暴会话可清除,悬停提示改窄改紧凑
工单记着的头脑风暴会话 id 此前没有清除入口,续聊只能 resume 旧会话, 把几个版本前的提示词和它当时报的错一起带回来。会话 id 行新增清除按钮, 确认后把 state JSON 的 sessionId 置空,下次续聊新开会话;会话文件不动, 手上有 id 仍可 claude --resume 找回。 悬停气泡此前贴着视口右边测量尺寸:fixed 元素的可用宽度是「视口宽 - left」, 贴边测会被挤窄、行数变多,按那个高度定位就浮高一截。改成先在左上角量再落位, 字号行高收紧,提示文案压成一行。顺带修掉「重置为待办」的错误说明——它会停 会话、删 worktree 和未合并 PR,不是原样保留。 Claude-Session: https://claude.ai/code/session_01REdhpbaNyqWASF2HWsnTFv
This commit is contained in:
1 parent
9fadcac12b
commit
5bfd759822
11 files changed
+124
-23
No files matched your search
@@ -45,6 +45,7 @@ interface BottomTabsProps {
|
||||
onResetToTodo: (issueNumber: number) => void
|
||||
onCloseSessionTab: (issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test') => void
|
||||
onContinueBrainstorm: (issueNumber: number) => void
|
||||
onClearBrainstormSession: (issueNumber: number) => void
|
||||
onUpdateAutoReview: (issueNumber: number, value: boolean) => void
|
||||
onUpdateProfilePath: (issueNumber: number, profilePath: string) => void
|
||||
onUpdateBrainstormProfilePath: (issueNumber: number, brainstormProfilePath: string) => void
|
||||
@@ -142,6 +143,7 @@ export function BottomTabs(props: BottomTabsProps) {
|
||||
onResetToTodo={props.onResetToTodo}
|
||||
onCloseSessionTab={props.onCloseSessionTab}
|
||||
onContinueBrainstorm={props.onContinueBrainstorm}
|
||||
onClearBrainstormSession={props.onClearBrainstormSession}
|
||||
onUpdateAutoReview={props.onUpdateAutoReview}
|
||||
onUpdateProfilePath={props.onUpdateProfilePath}
|
||||
onUpdateBrainstormProfilePath={props.onUpdateBrainstormProfilePath}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ClaudeProfile } from '../hooks/useIssues'
|
||||
import type { Issue, IssueColumn } from '../types'
|
||||
import type { PropertyGroup, RowAction } from './property-grid'
|
||||
import { ArrowRightLeft, CircleSlash, Download, ExternalLink, GitMerge, Loader2, Play, RotateCcw, Terminal, Trash2, X } from 'lucide-react'
|
||||
import { ArrowRightLeft, CircleSlash, Download, Eraser, ExternalLink, GitMerge, Loader2, Play, RotateCcw, Terminal, Trash2, X } from 'lucide-react'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { isIssueLocked } from '../lib/dependencies'
|
||||
import { isIssueMine, issueOwnerLogin } from '../lib/issueOwnership'
|
||||
@@ -93,6 +93,7 @@ interface IssueDetailPanelProps {
|
||||
* 打进去,关了就 `claude --resume <id>` 带着提示词恢复,没会话就新开一个。
|
||||
*/
|
||||
onContinueBrainstorm: (issueNumber: number) => void
|
||||
onClearBrainstormSession: (issueNumber: number) => void
|
||||
/** Persist a per-issue `autoReview` override into the state JSON. */
|
||||
onUpdateAutoReview: (issueNumber: number, value: boolean) => void
|
||||
/** 已知 Claude profile 列表,用于「配置文件」下拉选项。 */
|
||||
@@ -144,6 +145,7 @@ export function IssueDetailPanel({
|
||||
onResetToTodo,
|
||||
onCloseSessionTab,
|
||||
onContinueBrainstorm,
|
||||
onClearBrainstormSession,
|
||||
onUpdateAutoReview,
|
||||
profiles,
|
||||
onUpdateProfilePath,
|
||||
@@ -236,6 +238,16 @@ export function IssueDetailPanel({
|
||||
},
|
||||
})
|
||||
}
|
||||
if (hasSession) {
|
||||
secondaryActions.push({
|
||||
icon: <Eraser className="size-3.5" />,
|
||||
title: '清除会话:下次续聊新开一个,不再带旧会话的历史',
|
||||
onClick: () => {
|
||||
if (issue)
|
||||
onClearBrainstormSession(issue.number)
|
||||
},
|
||||
})
|
||||
}
|
||||
return {
|
||||
key: 'sessionId',
|
||||
label: '头脑风暴会话id',
|
||||
@@ -523,7 +535,7 @@ export function IssueDetailPanel({
|
||||
],
|
||||
},
|
||||
],
|
||||
[onResumeSession, onResumeReviewSession, onResumeTestSession, onStartTestSession, onOpenFile, onImplement, onOpenPr, onGeneratePrDiffSummary, onOpenWorktree, onDeleteWorktree, onMergeBranch, onCloseSessionTab, onContinueBrainstorm, onOpenLogs, issue, locked, lockTitle, globalAutoReview, prDiffSummaryRunning, profiles],
|
||||
[onResumeSession, onResumeReviewSession, onResumeTestSession, onStartTestSession, onOpenFile, onImplement, onOpenPr, onGeneratePrDiffSummary, onOpenWorktree, onDeleteWorktree, onMergeBranch, onCloseSessionTab, onContinueBrainstorm, onClearBrainstormSession, onOpenLogs, issue, locked, lockTitle, globalAutoReview, prDiffSummaryRunning, profiles],
|
||||
)
|
||||
|
||||
// 同事的工单只读:属性表统一消毒,顶部只留查看类按钮。
|
||||
@@ -617,7 +629,7 @@ export function IssueDetailPanel({
|
||||
</span>
|
||||
)
|
||||
: null}
|
||||
<Tooltip label={issue.source === 'youtrack' ? '在浏览器里打开这张 YouTrack 卡' : '在浏览器里打开这个 Gitea 工单'}>
|
||||
<Tooltip label="在浏览器里打开">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onOpenInBrowser(issue.htmlUrl)}
|
||||
@@ -632,7 +644,7 @@ export function IssueDetailPanel({
|
||||
{issue.source !== 'youtrack' && !readOnly && (
|
||||
<>
|
||||
{issue.handoffAttachmentId && me !== undefined && (issue.assignees ?? []).includes(me) && (
|
||||
<Tooltip label={`接管 ${issue.handoffFrom ?? '?'} 移交过来的工单:拉回会话记录和 worktree`}>
|
||||
<Tooltip label={`接管 ${issue.handoffFrom ?? '?'} 的移交`}>
|
||||
<button
|
||||
type="button"
|
||||
disabled={isHandoffRunning(issue.number)}
|
||||
@@ -646,7 +658,7 @@ export function IssueDetailPanel({
|
||||
</Tooltip>
|
||||
)}
|
||||
{!issue.handoffAttachmentId && issue.column !== 'done' && ((issue.assignees ?? []).length === 0 || (me !== undefined && (issue.assignees ?? []).includes(me))) && (
|
||||
<Tooltip label="移交给同事:把会话记录打包传过去,对方接管后继续做">
|
||||
<Tooltip label="移交给同事">
|
||||
<button
|
||||
type="button"
|
||||
disabled={isHandoffRunning(issue.number)}
|
||||
@@ -659,14 +671,14 @@ export function IssueDetailPanel({
|
||||
</Tooltip>
|
||||
)}
|
||||
{issue.handoffAttachmentId && !(me !== undefined && (issue.assignees ?? []).includes(me)) && (
|
||||
<Tooltip label={`${issue.handoffFrom ?? '?'} 已移交,等待 ${(issue.assignees ?? []).join(', ') || '?'} 接管`}>
|
||||
<Tooltip label={`${issue.handoffFrom ?? '?'} 已移交,等 ${(issue.assignees ?? []).join(', ') || '?'} 接管`}>
|
||||
<span className="inline-flex shrink-0 items-center rounded border border-[var(--vscode-panel-border)] px-1.5 py-0.5 text-[10px] opacity-70">
|
||||
待接管
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
{(issue.column === 'in-progress' || issue.column === 'review') && (
|
||||
<Tooltip label="打回待办:卡片挪回待办列,会话和 worktree 不动">
|
||||
<Tooltip label="重置为待办(停会话、删 worktree 和未合并 PR)">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onResetToTodo(issue.number)}
|
||||
@@ -677,7 +689,7 @@ export function IssueDetailPanel({
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip label="关闭工单:Gitea 上置为 closed,卡片从看板消失,可在 Gitea 里重开">
|
||||
<Tooltip label="关闭工单(Gitea 里可重开)">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onCloseIssue(issue.number)}
|
||||
@@ -687,7 +699,7 @@ export function IssueDetailPanel({
|
||||
<CircleSlash className="size-3.5" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip label="删除工单:从 Gitea 彻底删掉,不可撤销">
|
||||
<Tooltip label="删除工单(不可撤销)">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => issue && onDeleteIssue?.(issue.number)}
|
||||
|
||||
@@ -25,14 +25,14 @@ interface TooltipProps {
|
||||
* 上方放不下就翻到下方,左右钳在视口内。
|
||||
*/
|
||||
export function Tooltip({ label, children, className }: TooltipProps): ReactElement {
|
||||
const [pos, setPos] = useState<{ top: number, left: number } | null>(null)
|
||||
const [visible, setVisible] = useState(false)
|
||||
const wrapRef = useRef<HTMLSpanElement | null>(null)
|
||||
const bubbleRef = useRef<HTMLDivElement | null>(null)
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
||||
|
||||
const hide = useCallback((): void => {
|
||||
clearTimeout(timerRef.current)
|
||||
setPos(null)
|
||||
setVisible(false)
|
||||
}, [])
|
||||
|
||||
const show = useCallback((): void => {
|
||||
@@ -40,18 +40,17 @@ export function Tooltip({ label, children, className }: TooltipProps): ReactElem
|
||||
return
|
||||
clearTimeout(timerRef.current)
|
||||
timerRef.current = setTimeout(() => {
|
||||
const rect = wrapRef.current?.getBoundingClientRect()
|
||||
if (rect)
|
||||
setPos({ top: rect.top, left: rect.left + rect.width / 2 })
|
||||
if (wrapRef.current)
|
||||
setVisible(true)
|
||||
}, SHOW_DELAY_MS)
|
||||
}, [label])
|
||||
|
||||
useEffect(() => () => clearTimeout(timerRef.current), [])
|
||||
|
||||
// 气泡渲染后才知道自身尺寸,这时才能定最终位置:默认在上方,
|
||||
// 上方空间不够就翻到触发元素下方;水平居中后钳进视口。
|
||||
// 气泡先在左上角量一次尺寸再落位。必须量在 left:0:fixed 元素的可用宽度是
|
||||
// 「视口宽 - left」,贴右边量会被挤窄、行数变多,按那个高度定位就会浮高一截。
|
||||
useEffect(() => {
|
||||
if (!pos)
|
||||
if (!visible)
|
||||
return
|
||||
const bubble = bubbleRef.current
|
||||
const rect = wrapRef.current?.getBoundingClientRect()
|
||||
@@ -65,7 +64,7 @@ export function Tooltip({ label, children, className }: TooltipProps): ReactElem
|
||||
bubble.style.top = `${top}px`
|
||||
bubble.style.left = `${left}px`
|
||||
bubble.style.visibility = 'visible'
|
||||
}, [pos])
|
||||
}, [visible])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -80,12 +79,12 @@ export function Tooltip({ label, children, className }: TooltipProps): ReactElem
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
{pos && label && createPortal(
|
||||
{visible && label && createPortal(
|
||||
<div
|
||||
ref={bubbleRef}
|
||||
role="tooltip"
|
||||
style={{ top: pos.top, left: pos.left, visibility: 'hidden' }}
|
||||
className="pointer-events-none fixed z-50 max-w-64 rounded border border-[var(--vscode-editorHoverWidget-border,var(--vscode-panel-border))] bg-[var(--vscode-editorHoverWidget-background,var(--vscode-editorWidget-background))] px-2 py-1 text-xs leading-snug text-[var(--vscode-editorHoverWidget-foreground,var(--vscode-foreground))] shadow-md"
|
||||
style={{ top: 0, left: 0, visibility: 'hidden' }}
|
||||
className="pointer-events-none fixed z-50 max-w-[22rem] rounded border border-[var(--vscode-editorHoverWidget-border,var(--vscode-panel-border))] bg-[var(--vscode-editorHoverWidget-background,var(--vscode-editorWidget-background))] px-2 py-1 text-[11px] leading-tight break-words text-[var(--vscode-editorHoverWidget-foreground,var(--vscode-foreground))] shadow-md"
|
||||
>
|
||||
{label}
|
||||
</div>,
|
||||
|
||||
Reference in new issue
Block a user