perf: cut the boot and poll overhead (one settings fetch, one git process per tick, one fold per poll)

Boot: loadPrefs and loadExternalDisable each fetched the settings
document, serially, before the first paint — and the second had no
timeout, so a stalled wire kept the sidebar unmounted forever. Both
decisions now ride ONE fetch (loadBootDecision) under the existing
2s race.

GitLens: the 2s silent poll re-listed worktrees on every tick (a
second git process per tick for a list that almost never changes, and
a fresh worktrees array re-render each time). Silent ticks now fetch
only the selected checkout's status; every 15th tick (~30s) re-lists
and re-runs auto-selection, so an agent-created linked checkout is
still picked up mid-session.

ChangesTab: extractFileOps parses every accumulated tool/call (up to
4000 events); the poll ran it once for the badge and the render ran
it AGAIN per tick, and the fresh array defeated the downstream memo.
The fold now runs once per poll and its result rides a ref shared by
badge and render (reset on session switch).
This commit is contained in:
Menghuan1918
2026-09-01 15:58:44 +08:00
parent e2699a099e
commit 427552e4f9
5 changed files with 93 additions and 19 deletions
+18 -16
View File
@@ -21,7 +21,7 @@ import { registerOpenPathInterception, registerTurnTailInterception } from './in
import { registerLinkInterception } from './link-intercept.ts'
import { registerImeGuard } from './ime-guard.ts'
import { registerSettingsNavIcon } from './settings-nav-icon.ts'
import { loadExternalDisable, loadPrefs } from './prefs.ts'
import { loadBootDecision } from './prefs.ts'
import { SideCardSection } from './SideCardSection.tsx'
import { api } from './api.ts'
import { LOCALE_NS, attachLocale, attachBetterLocale, t, zh, en,
@@ -275,24 +275,26 @@ export function apply(ctx: Context): void {
}
const sync = async (): Promise<void> => {
if (disposed) return
// Resolve the user's side card prefs BEFORE the first session seeds,
// so a brand-new conversation opens (or stays closed) at the chosen
// width from first paint. A settings route failure falls back to the
// schema defaults; the sidebar still mounts (a stalled wire gives up
// after the timeout and mounts on the defaults).
const prefs = await Promise.race([
loadPrefs(api),
// Resolve the user's side card prefs and the external-disable flag
// from ONE settings fetch BEFORE the first session seeds, so a
// brand-new conversation opens (or stays closed) at the chosen width
// from first paint. A settings route failure falls back to the schema
// defaults; the sidebar still mounts (a stalled wire gives up after
// the timeout and mounts on the defaults — the external-disable check
// rides the same fetch, so one round trip covers both decisions).
const decision = await Promise.race([
loadBootDecision(api),
new Promise<null>(resolve => { const timer = window.setTimeout(() => resolve(null), 2000) }),
])
if (prefs !== null) sidebarStore.setPrefs(prefs)
if (disposed) return
// Mutual exclusion with the dsh-web-ui family right panel: while the
// aionui-panel provider is selected, the sidebar must not mount at
// all. Re-evaluated on every settings-document update (live switch).
const suspended = await loadExternalDisable(api)
if (disposed) return
sidebarStore.setSuspended(suspended)
if (suspended) unmount()
if (decision !== null) {
sidebarStore.setPrefs(decision.prefs)
// Mutual exclusion with the dsh-web-ui family right panel: while the
// aionui-panel provider is selected, the sidebar must not mount at
// all. Re-evaluated on every settings-document update (live switch).
sidebarStore.setSuspended(decision.suspended)
}
if (decision?.suspended) unmount()
else mount()
}
void sync()