⚡ perf(vscode): 看板打开先出快照、评论只拉末页
This commit is contained in:
1 parent
904ab7491d
commit
a68caf5f6e
9 files changed
+447
-115
No files matched your search
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user