🐛 fix(vscode): 刷新按钮加载态可见,图标随重拉转圈
刷新按钮此前只发消息重拉数据,看板已有列表时界面毫无变化,用户 无法判断点击是否生效。消息协议里的 issues/loading 从未被发送。 loadAndPush 现在在开跑前推 issues/loading,并在 finally 里推新增的 issues/loaded,保证成功、报错、缺 token 提前返回都能收尾;webhook 触发的自动重拉同样走这条链路。webview 据此维护 refreshing 状态, 刷新图标转圈并在期间禁用按钮。 Claude-Session: https://claude.ai/code/session_01REdhpbaNyqWASF2HWsnTFv
This commit is contained in:
1 parent
9167b86708
commit
ef0750b4ef
7 files changed
+30
-5
No files matched your search
+1
-1
@@ -2,7 +2,7 @@
|
||||
"publisher": "clurdra",
|
||||
"name": "superpowers-vscode-clurdra",
|
||||
"displayName": "Superpowers-clurdra",
|
||||
"version": "0.2.125",
|
||||
"version": "0.2.126",
|
||||
"packageManager": "pnpm@10.27.0",
|
||||
"description": "Superpowers specs and plans Kanban explorer",
|
||||
"author": "clurdra",
|
||||
|
||||
@@ -912,8 +912,12 @@ export class KanbanWebviewPanel {
|
||||
async loadAndPush(): Promise<void> {
|
||||
if (this.loadInFlight)
|
||||
return this.loadInFlight
|
||||
// 每次真正开跑都成对推 loading/loaded,webview 据此转刷新图标;
|
||||
// 无论成功、报错还是缺 token 中途返回,finally 都会收尾。
|
||||
this.postMessage({ type: 'issues/loading' })
|
||||
this.loadInFlight = this.loadAndPushInner().finally(() => {
|
||||
this.loadInFlight = undefined
|
||||
this.postMessage({ type: 'issues/loaded' })
|
||||
})
|
||||
return this.loadInFlight
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ export type ToastLevel = 'info' | 'success' | 'error'
|
||||
|
||||
export type ExtensionToWebview
|
||||
= | { type: 'issues/loading' }
|
||||
| { type: 'issues/loaded' }
|
||||
| { type: 'issues/update', issues: Issue[], scope: 'mine' | 'all', globalAutoReview: boolean, youtrackConfigured: boolean, me?: string, avatars?: Record<string, string> }
|
||||
| { type: 'issues/error', message: string }
|
||||
| { type: 'issue/patch', issueNumber: number, patch: { autoReview?: boolean, specFile?: string, planFile?: string, prDiffFile?: string | null, sessionId?: string | null, implementSessionId?: string | null, reviewSessionId?: string | null, reviewSessionFileExists?: boolean, testSessionId?: string | null, pr?: string | null, implementStatus?: 'running' | 'done' | 'failed' | null, column?: IssueColumn, worktreePath?: string | null, prMerged?: boolean, prMergedAt?: string | null, branch?: string | null, color?: string, worktreeExists?: boolean, brainstormTabOpen?: boolean, implementTabOpen?: boolean, reviewTabOpen?: boolean, testTabOpen?: boolean, profilePath?: string | null, brainstormProfilePath?: string | null, testProfilePath?: string | null, assignees?: string[], handoffAttachmentId?: string | null, handoffFrom?: string | null } }
|
||||
|
||||
@@ -30,6 +30,7 @@ export function App() {
|
||||
avatars,
|
||||
setIssues,
|
||||
refresh,
|
||||
refreshing,
|
||||
saveSettings,
|
||||
listYouTrackProjects,
|
||||
youtrackProjects,
|
||||
@@ -305,6 +306,7 @@ export function App() {
|
||||
<>
|
||||
<PanelHeader
|
||||
onRefresh={refresh}
|
||||
refreshing={refreshing}
|
||||
scope={scope}
|
||||
onToggleScope={toggleScope}
|
||||
onEditAuth={requestEditAuth}
|
||||
@@ -331,6 +333,7 @@ export function App() {
|
||||
<>
|
||||
<PanelHeader
|
||||
onRefresh={refresh}
|
||||
refreshing={refreshing}
|
||||
scope={scope}
|
||||
onToggleScope={toggleScope}
|
||||
onEditAuth={requestEditAuth}
|
||||
@@ -429,6 +432,7 @@ export function App() {
|
||||
<>
|
||||
<PanelHeader
|
||||
onRefresh={refresh}
|
||||
refreshing={refreshing}
|
||||
scope={scope}
|
||||
onToggleScope={toggleScope}
|
||||
onEditAuth={requestEditAuth}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
interface Props {
|
||||
onRefresh: () => void
|
||||
/** host 正在重拉看板;为 true 时刷新图标转起来。 */
|
||||
refreshing: boolean
|
||||
/** 看板范围:'mine' 只看我的(assigned/created),'all' 团队全部工单。 */
|
||||
scope: 'mine' | 'all'
|
||||
onToggleScope: () => void
|
||||
@@ -65,6 +67,7 @@ function releaseTitle(release: ReleaseStatus | undefined): string {
|
||||
|
||||
export function PanelHeader({
|
||||
onRefresh,
|
||||
refreshing,
|
||||
scope,
|
||||
onToggleScope,
|
||||
onEditAuth,
|
||||
@@ -207,11 +210,13 @@ export function PanelHeader({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRefresh}
|
||||
title="刷新"
|
||||
disabled={refreshing}
|
||||
title={refreshing ? '正在刷新…' : '刷新'}
|
||||
aria-label="刷新"
|
||||
className="flex h-6 w-6 items-center justify-center rounded hover:bg-[var(--vscode-toolbar-hoverBackground)]"
|
||||
aria-busy={refreshing}
|
||||
className="flex h-6 w-6 items-center justify-center rounded hover:bg-[var(--vscode-toolbar-hoverBackground)] disabled:cursor-not-allowed"
|
||||
>
|
||||
<RefreshCw size={14} />
|
||||
<RefreshCw size={14} className={refreshing ? 'animate-spin' : undefined} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -159,6 +159,11 @@ export interface UseIssuesResult {
|
||||
profiles: ClaudeProfile[]
|
||||
setIssues: (issues: Issue[]) => void
|
||||
refresh: () => void
|
||||
/**
|
||||
* True while the host is reloading the board (手动刷新、切换范围、webhook
|
||||
* 触发都算)。看板已有列表时不会退回 loading 空屏,靠它转刷新图标给反馈。
|
||||
*/
|
||||
refreshing: boolean
|
||||
saveSettings: (values: SettingsValues) => void
|
||||
/**
|
||||
* Fetch the YouTrack project list for the settings dropdown using the
|
||||
@@ -344,6 +349,7 @@ export function useIssues(): UseIssuesResult {
|
||||
// 默认团队全部,与扩展侧 DEFAULT_KANBAN_SCOPE 一致(两个包不共享源码)。
|
||||
const [scope, setScope] = useState<'mine' | 'all'>(injected?.scope ?? 'all')
|
||||
const [youtrackConfigured, setYoutrackConfigured] = useState<boolean>(injected?.youtrackConfigured ?? false)
|
||||
const [refreshing, setRefreshing] = useState<boolean>(false)
|
||||
const [toasts, setToasts] = useState<ToastItem[]>([])
|
||||
const [profiles, setProfiles] = useState<ClaudeProfile[]>([])
|
||||
const [logs, setLogs] = useState<LogEntry[]>([])
|
||||
@@ -700,8 +706,12 @@ export function useIssues(): UseIssuesResult {
|
||||
const cleanup = onMessage((msg) => {
|
||||
switch (msg.type) {
|
||||
case 'issues/loading':
|
||||
setRefreshing(true)
|
||||
setState(prev => prev.status === 'ready' ? prev : { status: 'loading' })
|
||||
break
|
||||
case 'issues/loaded':
|
||||
setRefreshing(false)
|
||||
break
|
||||
case 'issues/update':
|
||||
setState({ status: 'ready', issues: msg.issues })
|
||||
setScope(msg.scope)
|
||||
@@ -908,5 +918,5 @@ export function useIssues(): UseIssuesResult {
|
||||
return cleanup
|
||||
}, [clearPrDiffSummaryRunning, markHandoffRunning])
|
||||
|
||||
return { state, settings, avatars, globalAutoReview, youtrackConfigured, importYouTrack, toasts, profiles, setIssues, refresh, 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, 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, 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, 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 }
|
||||
}
|
||||
@@ -80,6 +80,7 @@ export interface ProfilesData {
|
||||
|
||||
export type ExtensionToWebview
|
||||
= | { type: 'issues/loading' }
|
||||
| { type: 'issues/loaded' }
|
||||
| { type: 'issues/update', issues: Issue[], scope: 'mine' | 'all', globalAutoReview: boolean, youtrackConfigured: boolean, me?: string, avatars?: Record<string, string> }
|
||||
| { type: 'issues/error', message: string }
|
||||
| { type: 'issue/patch', issueNumber: number, patch: { autoReview?: boolean, specFile?: string, planFile?: string, prDiffFile?: string | null, sessionId?: string | null, implementSessionId?: string | null, reviewSessionId?: string | null, reviewSessionFileExists?: boolean, testSessionId?: string | null, pr?: string | null, implementStatus?: 'running' | 'done' | 'failed' | null, column?: IssueColumn, worktreePath?: string | null, prMerged?: boolean, prMergedAt?: string | null, branch?: string | null, color?: string, worktreeExists?: boolean, brainstormTabOpen?: boolean, implementTabOpen?: boolean, reviewTabOpen?: boolean, testTabOpen?: boolean, profilePath?: string | null, brainstormProfilePath?: string | null, testProfilePath?: string | null, assignees?: string[], handoffAttachmentId?: string | null, handoffFrom?: string | null } }
|
||||
|
||||
Reference in new issue
Block a user