⚡ perf(vscode): 开会话去掉多余远程往返,spinner 跟随真实进度

开会话原先要先全量拉工单(含每张工单的评论、依赖、PR)做前置锁检查,
再各读一次 state JSON 给 pre-create 钩子和终端图标,几秒后才 createTerminal。

- 锁检查改用主进程已算好的工单快照,快照里没有该工单才回落全量拉取
- state JSON 一次读两处用,且提前发起与本地准备重叠
- 会话行 spinner 由扩展的 session/tab-settled 消息驱动,不再是固定 1.5s 假动画,
  消息丢失时 60s 超时兜底

Claude-Session: https://claude.ai/code/session_01Kgmb1LvrPmFX3NoUDB4zZC
This commit is contained in:
cruldra committed 2026-09-13 10:14:48 +08:00
1 parent b182e0ac5b
commit 70b826123c
12 files changed
+218 -71

No files matched your search

+56 -4
View File
@@ -184,6 +184,8 @@ export interface UseIssuesResult {
dismissToast: (id: string) => void
openUrl: (url: string) => void
resumeSession: (sessionId: string, kind: 'implement' | 'brainstorm', profilePath?: string, cwd?: string, issueNumber?: number) => void
/** 该会话 id 的开 tab 请求还没被扩展处理完 → 行内转 spinner。 */
isSessionTabPending: (sessionId: string) => boolean
resumeReviewSession: (sessionId: string, issueNumber: number, cwd?: string) => void
startTestSession: (issueNumber: number) => void
resumeTestSession: (sessionId: string, issueNumber: number, cwd?: string) => void
@@ -479,6 +481,50 @@ export function useIssues(): UseIssuesResult {
postMessage({ type: 'toast/open-url', url })
}, [])
// 开 tab 是秒级操作(要读 state JSON、跑 pre-create 钩子),行内 spinner 要
// 真实反映进度:发请求时标 pending,扩展回 session/tab-settled 时清。消息万一
// 丢了就靠 PENDING_TIMEOUT_MS 兜底,spinner 不会永远转下去。
const [pendingSessionTabs, setPendingSessionTabs] = useState<Set<string>>(() => new Set())
const pendingTimersRef = useRef<Map<string, number>>(new Map())
const PENDING_TIMEOUT_MS = 60_000
const clearSessionTabPending = useCallback((sessionId: string): void => {
const timer = pendingTimersRef.current.get(sessionId)
if (timer !== undefined) {
window.clearTimeout(timer)
pendingTimersRef.current.delete(sessionId)
}
setPendingSessionTabs((prev) => {
if (!prev.has(sessionId))
return prev
const next = new Set(prev)
next.delete(sessionId)
return next
})
}, [])
const markSessionTabPending = useCallback((sessionId: string): void => {
const existing = pendingTimersRef.current.get(sessionId)
if (existing !== undefined)
window.clearTimeout(existing)
pendingTimersRef.current.set(
sessionId,
window.setTimeout(() => clearSessionTabPending(sessionId), PENDING_TIMEOUT_MS),
)
setPendingSessionTabs(prev => new Set(prev).add(sessionId))
}, [clearSessionTabPending])
const isSessionTabPending = useCallback(
(sessionId: string): boolean => pendingSessionTabs.has(sessionId),
[pendingSessionTabs],
)
useEffect(() => () => {
for (const timer of pendingTimersRef.current.values())
window.clearTimeout(timer)
pendingTimersRef.current.clear()
}, [])
const resumeSession = useCallback((
sessionId: string,
kind: 'implement' | 'brainstorm',
@@ -486,24 +532,27 @@ export function useIssues(): UseIssuesResult {
cwd?: string,
issueNumber?: number,
): void => {
markSessionTabPending(sessionId)
postMessage({ type: 'session/resume', sessionId, kind, profilePath, cwd, issueNumber })
}, [])
}, [markSessionTabPending])
const focusSession = useCallback((issueNumber: number): void => {
postMessage({ type: 'session/focus', issueNumber })
}, [])
const resumeReviewSession = useCallback((sessionId: string, issueNumber: number, cwd?: string): void => {
markSessionTabPending(sessionId)
postMessage({ type: 'session/resume-review', sessionId, issueNumber, cwd })
}, [])
}, [markSessionTabPending])
const startTestSession = useCallback((issueNumber: number): void => {
postMessage({ type: 'session/start-test', issueNumber })
}, [])
const resumeTestSession = useCallback((sessionId: string, issueNumber: number, cwd?: string): void => {
markSessionTabPending(sessionId)
postMessage({ type: 'session/resume-test', sessionId, issueNumber, cwd })
}, [])
}, [markSessionTabPending])
const openFile = useCallback((path: string): void => {
postMessage({ type: 'editor/open-file', path })
@@ -722,6 +771,9 @@ export function useIssues(): UseIssuesResult {
useEffect(() => {
const cleanup = onMessage((msg) => {
switch (msg.type) {
case 'session/tab-settled':
clearSessionTabPending(msg.sessionId)
break
case 'issues/loading':
setRefreshing(true)
setState(prev => prev.status === 'ready' ? prev : { status: 'loading' })
@@ -938,5 +990,5 @@ export function useIssues(): UseIssuesResult {
return cleanup
}, [clearPrDiffSummaryRunning, markHandoffRunning])
return { state, settings, avatars, globalAutoReview, youtrackConfigured, importYouTrack, toasts, profiles, setIssues, refresh, refreshing, scope, toggleScope, saveSettings, listYouTrackProjects, youtrackProjects, dismissSettings, requestEditAuth, createIssue, dismissToast, openUrl, resumeSession, resumeReviewSession, startTestSession, resumeTestSession, focusSession, openFile, implement, generatePrDiffSummary, isPrDiffSummaryRunning, openPr, openWorktree, deleteWorktree, mergeBranch, deleteIssue, closeIssue, closeSessionTab, continueBrainstorm, changeColumn, setDependency, clearDependency, updateIssueAutoReview, updateIssueProfilePath, updateIssueBrainstormProfilePath, clearBrainstormSession, updateIssueTestProfilePath, logs, fetchLogs, clearLogs, pendingSelectId, clearPendingSelect, commitRunning, runCommit, hasChanges, release, runRelease, cherryPick, dismissCherryPick, confirmCherryPick, envLocked, envFileCount, envLockRunning, envLockTitle, toggleEnvLock, me, handoffUsers, requestHandoffUsers, startHandoff, acceptHandoff, isHandoffRunning }
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, startTestSession, resumeTestSession, focusSession, openFile, implement, generatePrDiffSummary, isPrDiffSummaryRunning, openPr, openWorktree, deleteWorktree, mergeBranch, deleteIssue, closeIssue, closeSessionTab, continueBrainstorm, changeColumn, setDependency, clearDependency, updateIssueAutoReview, updateIssueProfilePath, updateIssueBrainstormProfilePath, clearBrainstormSession, updateIssueTestProfilePath, logs, fetchLogs, clearLogs, pendingSelectId, clearPendingSelect, commitRunning, runCommit, hasChanges, release, runRelease, cherryPick, dismissCherryPick, confirmCherryPick, envLocked, envFileCount, envLockRunning, envLockTitle, toggleEnvLock, me, handoffUsers, requestHandoffUsers, startHandoff, acceptHandoff, isHandoffRunning }
}