refactor(client): 四处轮询站点迁移到 use-polling,语义逐一保持
- SubagentView useSubagentLive:self-scheduling(上一请求 settle 后才排 下一次,不重叠)+ 每代 AbortSignal + signal.aborted 拦迟到写入,行为 与原 disposed 标志等价(teardown 同步置 disposed 并 abort)。 - SideChatView:立即拉取 effect 与 2s 定时拆分——输入变化(含 running 翻转)仍先拉一次转录,运行中间隔 tick 拉转录+徽章;fetchThread 自带 的逐次 abort 与卸载 abort 原样保留。 - GitLens:可见时 2s 固定静默 tick,无初始突发(mount/scope 变更的完整 刷新在别处);refresh 身份变化照旧重启间隔相位。 - ChangesTab:输入变化即拉一次(含隐藏态翻转),可见时 2.5s 固定间隔; pull 内部 pollGen 代际守卫不动。 守护 spec 全绿:subagent-live-polling + subagent-jobs-view 13/13、 sidechat-view + sidechat-transcript 29/29、git-view-worktree + changes-tab + changes-ops 13/13;断言零改动。
This commit is contained in:
1 parent
6ef11ae010
commit
1ec1565b13
4 files changed
+44
-55
No files matched your search
+15
-36
@@ -53,6 +53,7 @@ import {
|
||||
type TreeJob,
|
||||
} from './subagent-jobs.ts'
|
||||
import { api, type JobOutputResult } from './api.ts'
|
||||
import { usePolling } from './use-polling.ts'
|
||||
import { IconStopOutline16 } from './icons.tsx'
|
||||
import { t } from './locales.ts'
|
||||
import css from './SubagentView.module.css'
|
||||
@@ -180,52 +181,30 @@ function SubagentLiveLines(props: { live: LastActivity | undefined }) {
|
||||
/**
|
||||
* One shared live-preview poller for the whole Subagent tree. Unlike the old
|
||||
* per-card `subagents.history` timers, this sends at most ONE `subagents.live`
|
||||
* request at a time: a recursive timeout starts only after the previous
|
||||
* request settles, so a slow host never sees abort/restart storms.
|
||||
* request at a time (the shared poller's self-scheduling mode arms the next
|
||||
* tick only after the previous request settles, so a slow host never sees
|
||||
* abort/restart storms); a response settling after the poller stopped (page
|
||||
* hidden, tree re-rooted) is dropped via the aborted signal.
|
||||
*/
|
||||
function useSubagentLive(
|
||||
rootId: string | undefined,
|
||||
active: boolean,
|
||||
): Readonly<Record<string, LastActivity>> {
|
||||
const [live, setLive] = useState<Record<string, LastActivity>>({})
|
||||
const controllerRef = useRef<AbortController | undefined>(undefined)
|
||||
|
||||
// A new tree must never inherit another root's live previews.
|
||||
useEffect(() => { setLive({}) }, [rootId])
|
||||
|
||||
useEffect(() => {
|
||||
if (rootId === undefined || !active) return
|
||||
const targetRootId = rootId
|
||||
let disposed = false
|
||||
let timer: number | undefined
|
||||
|
||||
const schedule = (): void => {
|
||||
if (disposed) return
|
||||
timer = window.setTimeout(() => { void load() }, POLL_MS)
|
||||
}
|
||||
async function load(): Promise<void> {
|
||||
if (disposed) return
|
||||
const controller = new AbortController()
|
||||
controllerRef.current = controller
|
||||
try {
|
||||
const result = await api.subagentsLive(targetRootId, controller.signal)
|
||||
if (!disposed) setLive(result.live)
|
||||
} catch {
|
||||
// Keep the last known live map; the next scheduled poll retries.
|
||||
} finally {
|
||||
if (controllerRef.current === controller) controllerRef.current = undefined
|
||||
if (!disposed) schedule()
|
||||
}
|
||||
}
|
||||
|
||||
void load()
|
||||
return () => {
|
||||
disposed = true
|
||||
if (timer !== undefined) window.clearTimeout(timer)
|
||||
controllerRef.current?.abort()
|
||||
controllerRef.current = undefined
|
||||
}
|
||||
}, [rootId, active])
|
||||
const poll = useCallback(async (signal: AbortSignal): Promise<void> => {
|
||||
if (rootId === undefined) return
|
||||
const result = await api.subagentsLive(rootId, signal)
|
||||
if (!signal.aborted) setLive(result.live)
|
||||
}, [rootId])
|
||||
usePolling(rootId !== undefined && active, poll, {
|
||||
intervalMs: POLL_MS,
|
||||
mode: 'self-scheduling',
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
return live
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user