@@ -339,6 +339,8 @@ type CloseWorkingTabConfirmState = {
339339 tabName: string;
340340};
341341
342+ const STARTUP_GIT_STATUS_BATCH_SIZE = 8;
343+
342344/**
343345 * 中文说明:将耗时(毫秒)格式化为带单位文本(如 `2s`、`1m 05s`、`1h 02m 05s`)。
344346 */
@@ -435,6 +437,43 @@ function resolveNextWorktreeRemarkIndex(args: {
435437 return maxIndex + 1;
436438}
437439
440+ /**
441+ * 中文说明:提取项目的“最近活跃时间戳”,用于启动阶段优先刷新更可能被立刻操作的项目。
442+ */
443+ function resolveProjectGitStatusPriorityTimestamp(project: Project | null | undefined): number {
444+ const lastOpenedAt = Number(project?.lastOpenedAt);
445+ if (Number.isFinite(lastOpenedAt) && lastOpenedAt > 0) return lastOpenedAt;
446+ const createdAt = Number(project?.createdAt);
447+ if (Number.isFinite(createdAt) && createdAt > 0) return createdAt;
448+ return 0;
449+ }
450+
451+ /**
452+ * 中文说明:构建启动阶段的 Git 状态探测队列。
453+ * - 仅保留有效项目 id 与目录路径;
454+ * - 按最近活跃时间优先,尽快回填用户更可能先点开的项目;
455+ * - 同优先级下按路径稳定排序,避免刷新顺序抖动。
456+ */
457+ function buildStartupGitStatusQueue(projects: Project[]): Array<{ id: string; dir: string }> {
458+ const seen = new Set<string>();
459+ return [...projects]
460+ .sort((left, right) => {
461+ const diff = resolveProjectGitStatusPriorityTimestamp(right) - resolveProjectGitStatusPriorityTimestamp(left);
462+ if (diff !== 0) return diff;
463+ return String(left?.winPath || "").localeCompare(String(right?.winPath || ""), undefined, {
464+ sensitivity: "base",
465+ numeric: true,
466+ });
467+ })
468+ .flatMap((project) => {
469+ const id = String(project?.id || "").trim();
470+ const dir = String(project?.winPath || "").trim();
471+ if (!id || !dir || seen.has(id)) return [];
472+ seen.add(id);
473+ return [{ id, dir }];
474+ });
475+ }
476+
438477type IdeOpenPrefs = {
439478 mode: "auto" | "builtin" | "custom";
440479 builtinId: BuiltinIdeId;
@@ -1101,28 +1140,62 @@ export default function CodexFlowManagerUI() {
11011140 return () => { try { window.clearTimeout(timer); } catch {} };
11021141 }, [dirTreeStore, projectsHydrated]);
11031142
1104- // Git 状态:批量刷新(用于分支/工作树识别与“目录缺失”判定)
1143+ // Git 状态:项目列表一到位就开始分批刷新,优先回填最近项目,避免启动时长时间看不到 worktree 入口
11051144 useEffect(() => {
1106- if (!projectsHydrated) return;
11071145 let cancelled = false;
1146+ const orderedPairs = buildStartupGitStatusQueue(projects);
1147+ const activeIds = new Set(orderedPairs.map((item) => item.id));
1148+
1149+ setGitInfoByProjectId((prev) => {
1150+ const next: Record<string, GitDirInfo> = {};
1151+ let changed = false;
1152+ for (const [projectId, info] of Object.entries(prev || {})) {
1153+ if (!activeIds.has(projectId)) {
1154+ changed = true;
1155+ continue;
1156+ }
1157+ next[projectId] = info;
1158+ }
1159+ return changed ? next : prev;
1160+ });
1161+
1162+ if (orderedPairs.length === 0) {
1163+ return () => { cancelled = true; };
1164+ }
1165+
1166+ /**
1167+ * 中文说明:按批次读取 Git 状态,并将结果增量合并到当前缓存。
1168+ */
1169+ const loadGitInfoBatch = async (batch: Array<{ id: string; dir: string }>): Promise<void> => {
1170+ if (batch.length === 0) return;
1171+ const res: any = await (window as any).host?.gitWorktree?.statusBatch?.(batch.map((item) => item.dir));
1172+ if (cancelled) return;
1173+ if (!(res && res.ok && Array.isArray(res.items))) return;
1174+ const items = res.items as GitDirInfo[];
1175+ setGitInfoByProjectId((prev) => {
1176+ const next = { ...prev };
1177+ let changed = false;
1178+ for (let i = 0; i < batch.length; i++) {
1179+ const projectId = batch[i]?.id;
1180+ const info = items[i];
1181+ if (!projectId || !info) continue;
1182+ next[projectId] = info;
1183+ changed = true;
1184+ }
1185+ return changed ? next : prev;
1186+ });
1187+ };
1188+
11081189 (async () => {
11091190 try {
1110- const dirs = projects.map((p) => p.winPath).filter(Boolean);
1111- const res: any = await (window as any).host?.gitWorktree?.statusBatch?.(dirs);
1112- if (cancelled) return;
1113- if (res && res.ok && Array.isArray(res.items)) {
1114- const next: Record<string, GitDirInfo> = {};
1115- for (let i = 0; i < projects.length; i++) {
1116- const p = projects[i];
1117- const info = (res.items[i] || null) as GitDirInfo | null;
1118- if (p?.id && info) next[p.id] = info;
1119- }
1120- setGitInfoByProjectId(next);
1121- }
1122- } catch {}
1191+ for (let start = 0; start < orderedPairs.length; start += STARTUP_GIT_STATUS_BATCH_SIZE) {
1192+ await loadGitInfoBatch(orderedPairs.slice(start, start + STARTUP_GIT_STATUS_BATCH_SIZE));
1193+ if (cancelled) return;
1194+ }
1195+ } catch {}
11231196 })();
11241197 return () => { cancelled = true; };
1125- }, [projectsHydrated, projects]);
1198+ }, [projects]);
11261199
11271200 /**
11281201 * 中文说明:解析当前节点所属 worktree 组的“管理父节点”。
0 commit comments