⚡ perf(vscode): 看板打开先出快照、评论只拉末页

This commit is contained in:
cruldra committed 2026-09-01 07:09:55 +08:00
1 parent 904ab7491d
commit a68caf5f6e
9 files changed
+447 -115

No files matched your search

+70 -26
View File
@@ -13,7 +13,6 @@ import { randomBytes } from 'node:crypto'
import * as fs from 'node:fs'
import * as path from 'node:path'
import { env, TabInputTerminal, ThemeColor, Uri, ViewColumn, window, workspace } from 'vscode'
import { loginForToken } from '../auth/identity'
import { deleteToken, getToken } from '../auth/secrets'
import { detectRepo } from '../git/remote'
import {
@@ -43,6 +42,7 @@ import * as worktree from './handlers/worktree'
import * as youtrackIssues from './handlers/youtrackIssues'
import { PALETTE, resolveIssueColor, themeColorIdToIconUri } from './issueColor'
import { KANBAN_SCOPE_KEY } from './kanbanScope'
import { KANBAN_SNAPSHOT_KEY, parseKanbanSnapshot, snapshotScriptTag } from './kanbanSnapshot'
/** Resolved at runtime via getProfilesDir() — do not hardcode user paths. */
export { getDefaultProfilePath } from '../cc/profiles'
@@ -62,6 +62,10 @@ export class KanbanWebviewPanel {
* alone is an unambiguous key.
*/
private issueRefs = new Map<number, { source: 'gitea' | 'youtrack', externalId?: string }>()
/** webview 已发过 issues/refresh,此后才 postMessage,避免启动时消息丢。 */
private webviewReady = false
private loadInFlight: Promise<void> | undefined
private lastIssuesUpdate: Extract<ExtensionToWebview, { type: 'issues/update' }> | undefined
/**
* sessionId → its dedicated terminal in the editor area. Populated when the
* user presses Enter on a card; entry is removed when the terminal is
@@ -232,6 +236,8 @@ export class KanbanWebviewPanel {
}
this.panel.webview.html = this.buildHtml()
// 和 webview JS 启动重叠:有快照则 HTML 已带上一屏,这里后台拉新。
void this.loadAndPush()
// Let the always-on webhook coordinator know who to refresh + toast for.
webhookCoordinator.setActivePanel(this)
@@ -342,6 +348,15 @@ export class KanbanWebviewPanel {
private handleMessage(msg: WebviewToExtension): void {
if (msg.type === 'issues/refresh') {
const first = !this.webviewReady
this.webviewReady = true
if (this.loadInFlight)
return
if (first && this.lastIssuesUpdate) {
this.postMessage(this.lastIssuesUpdate)
this.postMessage({ type: 'commit/has-changes', value: this.hasGitChanges })
return
}
void this.loadAndPush()
return
}
@@ -835,13 +850,38 @@ export class KanbanWebviewPanel {
return terminals.withLiveTerminalTabState(this, issues)
}
private async loadYouTrackSafe(): Promise<Issue[]> {
try {
return await loadYouTrackIssues(this.context)
}
catch (ytErr) {
const message = ytErr instanceof Error ? ytErr.message : String(ytErr)
logger.add({ level: 'error', source: 'youtrack', message: '加载 YouTrack 问题失败', details: message })
this.postMessage({
type: 'toast/show',
id: makeNonce(),
level: 'error',
message: `YouTrack 同步失败:${message}`,
dismissOnTimer: 6000,
})
return []
}
}
/**
* Reloads issues from gitea and pushes to the webview. Public so the
* always-on webhook coordinator can refresh us when a PR event arrives.
*/
async loadAndPush(): Promise<void> {
this.postMessage({ type: 'issues/loading' })
if (this.loadInFlight)
return this.loadInFlight
this.loadInFlight = this.loadAndPushInner().finally(() => {
this.loadInFlight = undefined
})
return this.loadInFlight
}
private async loadAndPushInner(): Promise<void> {
const workspaceRoot = workspace.workspaceFolders?.[0]?.uri.fsPath
// eslint-disable-next-line no-console
console.log('[superpowers/panel] loadAndPush workspaceRoot=', workspaceRoot)
@@ -900,24 +940,10 @@ export class KanbanWebviewPanel {
const scope = this.context.workspaceState.get<KanbanScope>(KANBAN_SCOPE_KEY) ?? 'mine'
try {
const giteaIssues = await loadIssues({ host, token, owner, repo, workspaceRoot, scope })
// YouTrack is a best-effort second source: a failure here must never
// break the gitea board, so swallow it into a toast + log.
let youtrackList: Issue[] = []
try {
youtrackList = await loadYouTrackIssues(this.context)
}
catch (ytErr) {
const message = ytErr instanceof Error ? ytErr.message : String(ytErr)
logger.add({ level: 'error', source: 'youtrack', message: '加载 YouTrack 问题失败', details: message })
this.postMessage({
type: 'toast/show',
id: makeNonce(),
level: 'error',
message: `YouTrack 同步失败:${message}`,
dismissOnTimer: 6000,
})
}
const [{ issues: giteaIssues, me }, youtrackList] = await Promise.all([
loadIssues({ host, token, owner, repo, workspaceRoot, scope }),
this.loadYouTrackSafe(),
])
// 本机记录(会话 id / worktree 等)叠加在共享状态之上,附加本机 codex
// 会话文件存在性,再补 live terminal 态。
const issues = this.withLiveTerminalTabState(
@@ -929,19 +955,29 @@ export class KanbanWebviewPanel {
issues.map(i => [i.number, { source: i.source ?? 'gitea', externalId: i.externalId }] as const),
)
const ytSettings = getSettings(this.context)
const me = await loginForToken(host, token)
this.postMessage({
const payload: Extract<ExtensionToWebview, { type: 'issues/update' }> = {
type: 'issues/update',
issues,
scope,
globalAutoReview: ytSettings.autoReview,
youtrackConfigured: ytSettings.youtrackBaseUrl.trim() !== '' && ytSettings.youtrackProjectShortName.trim() !== '',
me,
}
this.lastIssuesUpdate = payload
void this.context.workspaceState.update(KANBAN_SNAPSHOT_KEY, {
issues,
scope,
me,
globalAutoReview: payload.globalAutoReview,
youtrackConfigured: payload.youtrackConfigured,
})
// Re-publish the cached git state so a slow webview boot (or a
// refresh after git events already fired) still picks up the latest
// value. Cheap — the webview discards if equal.
this.postMessage({ type: 'commit/has-changes', value: this.hasGitChanges })
if (this.webviewReady) {
this.postMessage(payload)
// Re-publish the cached git state so a slow webview boot (or a
// refresh after git events already fired) still picks up the latest
// value. Cheap — the webview discards if equal.
this.postMessage({ type: 'commit/has-changes', value: this.hasGitChanges })
}
}
catch (err) {
if (err instanceof GiteaApiError && err.status === 401) {
@@ -1174,6 +1210,14 @@ export class KanbanWebviewPanel {
`<head>\n <meta http-equiv="Content-Security-Policy" content="${csp}">`,
)
const snapshot = parseKanbanSnapshot(this.context.workspaceState.get(KANBAN_SNAPSHOT_KEY))
if (snapshot) {
html = html.replace(
'</body>',
` ${snapshotScriptTag(nonce, snapshot)}\n </body>`,
)
}
return html
}
}