Skip to content

Commit 9bb87c8

Browse files
committed
opt fixes
1 parent bcafc3d commit 9bb87c8

8 files changed

Lines changed: 158 additions & 34 deletions

File tree

apps/desktop/src/main/services/files/fileSearchIndexService.ts

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ const VOLATILE_ADE_FILES = new Set([
2121
".ade/ade.sock",
2222
".ade/local.secret.yaml",
2323
]);
24+
const ALWAYS_SKIPPED_DIRECTORY_NAMES = new Set([
25+
".git",
26+
".next",
27+
".nuxt",
28+
".svelte-kit",
29+
".turbo",
30+
".vite",
31+
"coverage",
32+
"dist",
33+
"node_modules",
34+
]);
2435

2536
type IndexedFile = {
2637
path: string;
@@ -41,6 +52,11 @@ type WorkspaceIndex = {
4152
builtAt: string | null;
4253
};
4354

55+
type IgnoreOptions = {
56+
shouldIgnore: (relPath: string, includeIgnored: boolean) => Promise<boolean>;
57+
primeIgnoreCache?: (relPaths: string[], includeIgnored: boolean) => Promise<void>;
58+
};
59+
4460
function isVolatileAdeRuntimePath(relPath: string): boolean {
4561
return VOLATILE_ADE_FILES.has(relPath)
4662
|| VOLATILE_ADE_PREFIXES.some((prefix) => relPath === prefix.slice(0, -1) || relPath.startsWith(prefix));
@@ -161,15 +177,9 @@ export function createFileSearchIndexService() {
161177
});
162178
};
163179

164-
const shouldSkipDirectoryName = (name: string): boolean => {
165-
if (name === ".git") return true;
166-
if (name === "node_modules") return true;
167-
return false;
168-
};
180+
const shouldSkipDirectoryName = (name: string): boolean => ALWAYS_SKIPPED_DIRECTORY_NAMES.has(name);
169181

170-
const buildWorkspace = async (index: WorkspaceIndex, opts: {
171-
shouldIgnore: (relPath: string, includeIgnored: boolean) => Promise<boolean>;
172-
}): Promise<void> => {
182+
const buildWorkspace = async (index: WorkspaceIndex, opts: IgnoreOptions): Promise<void> => {
173183
index.files.clear();
174184
index.totalContentBytes = 0;
175185

@@ -187,8 +197,13 @@ export function createFileSearchIndexService() {
187197
continue;
188198
}
189199

190-
for (const entry of entries) {
191-
const relPath = normalizeRelative(path.join(relDir, entry.name));
200+
const candidates = entries
201+
.map((entry) => ({ entry, relPath: normalizeRelative(path.join(relDir, entry.name)) }))
202+
.filter((candidate) => Boolean(candidate.relPath));
203+
204+
await opts.primeIgnoreCache?.(candidates.map((candidate) => candidate.relPath), index.includeIgnored);
205+
206+
for (const { entry, relPath } of candidates) {
192207
if (!relPath) continue;
193208
if (shouldSkipPathPrefix(relPath, index.includeIgnored)) continue;
194209
if (entry.isDirectory() && shouldSkipDirectoryName(entry.name)) continue;
@@ -215,9 +230,8 @@ export function createFileSearchIndexService() {
215230
index.builtAt = new Date().toISOString();
216231
};
217232

218-
const ensureBuilt = async (workspaceId: string, rootPath: string, opts: {
233+
const ensureBuilt = async (workspaceId: string, rootPath: string, opts: IgnoreOptions & {
219234
includeIgnored: boolean;
220-
shouldIgnore: (relPath: string, includeIgnored: boolean) => Promise<boolean>;
221235
}): Promise<WorkspaceIndex> => {
222236
const index = getOrCreateWorkspaceIndex(workspaceId, rootPath, opts.includeIgnored);
223237
if (index.files.size > 0 || index.builtAt) return index;
@@ -239,10 +253,12 @@ export function createFileSearchIndexService() {
239253
rootPath: string;
240254
includeIgnored: boolean;
241255
shouldIgnore: (relPath: string, includeIgnored: boolean) => Promise<boolean>;
256+
primeIgnoreCache?: (relPaths: string[], includeIgnored: boolean) => Promise<void>;
242257
}): Promise<void> {
243258
await ensureBuilt(args.workspaceId, args.rootPath, {
244259
includeIgnored: args.includeIgnored,
245-
shouldIgnore: args.shouldIgnore
260+
shouldIgnore: args.shouldIgnore,
261+
primeIgnoreCache: args.primeIgnoreCache
246262
});
247263
},
248264

@@ -253,10 +269,12 @@ export function createFileSearchIndexService() {
253269
limit: number;
254270
includeIgnored: boolean;
255271
shouldIgnore: (relPath: string, includeIgnored: boolean) => Promise<boolean>;
272+
primeIgnoreCache?: (relPaths: string[], includeIgnored: boolean) => Promise<void>;
256273
}): Promise<FilesQuickOpenItem[]> {
257274
const index = await ensureBuilt(args.workspaceId, args.rootPath, {
258275
includeIgnored: args.includeIgnored,
259-
shouldIgnore: args.shouldIgnore
276+
shouldIgnore: args.shouldIgnore,
277+
primeIgnoreCache: args.primeIgnoreCache
260278
});
261279

262280
const scored: FilesQuickOpenItem[] = [];
@@ -276,10 +294,12 @@ export function createFileSearchIndexService() {
276294
limit: number;
277295
includeIgnored: boolean;
278296
shouldIgnore: (relPath: string, includeIgnored: boolean) => Promise<boolean>;
297+
primeIgnoreCache?: (relPaths: string[], includeIgnored: boolean) => Promise<void>;
279298
}): Promise<FilesSearchTextMatch[]> {
280299
const index = await ensureBuilt(args.workspaceId, args.rootPath, {
281300
includeIgnored: args.includeIgnored,
282-
shouldIgnore: args.shouldIgnore
301+
shouldIgnore: args.shouldIgnore,
302+
primeIgnoreCache: args.primeIgnoreCache
283303
});
284304

285305
const out: FilesSearchTextMatch[] = [];

apps/desktop/src/main/services/files/fileService.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,37 @@ describe("fileService", () => {
235235
}
236236
});
237237

238+
it("keeps generated output directories out of the file search index", async () => {
239+
const rootPath = fs.mkdtempSync(path.join(os.tmpdir(), "ade-file-service-generated-search-"));
240+
const { execSync } = await import("node:child_process");
241+
execSync("git init", { cwd: rootPath, stdio: "ignore" });
242+
const laneService = createLaneServiceStub(rootPath);
243+
const service = createFileService({ laneService });
244+
245+
try {
246+
fs.mkdirSync(path.join(rootPath, "src"), { recursive: true });
247+
fs.mkdirSync(path.join(rootPath, "dist"), { recursive: true });
248+
fs.writeFileSync(path.join(rootPath, "src", "index.ts"), "needle source\n", "utf8");
249+
fs.writeFileSync(path.join(rootPath, "dist", "bundle.js"), "needle bundled output\n", "utf8");
250+
251+
const quickOpen = await service.quickOpen({
252+
workspaceId: "workspace-1",
253+
query: "bundle",
254+
includeIgnored: true,
255+
});
256+
const search = await service.searchText({
257+
workspaceId: "workspace-1",
258+
query: "needle",
259+
includeIgnored: true,
260+
});
261+
262+
expect(quickOpen).toEqual([]);
263+
expect(search.map((item) => item.path)).toEqual(["src/index.ts"]);
264+
} finally {
265+
fs.rmSync(rootPath, { recursive: true, force: true });
266+
}
267+
});
268+
238269
it("lists only the requested tree depth without extra file metadata", async () => {
239270
const rootPath = fs.mkdtempSync(path.join(os.tmpdir(), "ade-file-service-tree-"));
240271
const { execSync } = await import("node:child_process");

apps/desktop/src/main/services/files/fileService.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,8 @@ export function createFileService({
426426

427427
const shouldIgnoreForRoot = (rootPath: string) =>
428428
(relPath: string, includeIgnored: boolean) => isIgnoredPath(rootPath, relPath, includeIgnored);
429+
const primeIgnoreCacheForRoot = (rootPath: string) =>
430+
(relPaths: string[], includeIgnored: boolean) => primeIgnoreCache(rootPath, relPaths, includeIgnored);
429431

430432
const emitLaneMutation = (workspaceId: string, reason: string) => {
431433
if (!onLaneWorktreeMutation) return;
@@ -838,7 +840,8 @@ export function createFileService({
838840
query,
839841
limit,
840842
includeIgnored: Boolean(args.includeIgnored),
841-
shouldIgnore: shouldIgnoreForRoot(workspace.rootPath)
843+
shouldIgnore: shouldIgnoreForRoot(workspace.rootPath),
844+
primeIgnoreCache: primeIgnoreCacheForRoot(workspace.rootPath)
842845
});
843846
},
844847

@@ -853,7 +856,8 @@ export function createFileService({
853856
query,
854857
limit,
855858
includeIgnored: Boolean(args.includeIgnored),
856-
shouldIgnore: shouldIgnoreForRoot(workspace.rootPath)
859+
shouldIgnore: shouldIgnoreForRoot(workspace.rootPath),
860+
primeIgnoreCache: primeIgnoreCacheForRoot(workspace.rootPath)
857861
});
858862
},
859863

apps/desktop/src/renderer/components/chat/AgentChatComposer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2136,7 +2136,8 @@ export function AgentChatComposer({
21362136
setSelectedBuiltInBrowserContextId(null);
21372137
}, [builtInBrowserContextItems, selectedBuiltInBrowserContextId]);
21382138

2139-
const composerBeamActive = isActive && layoutVariant !== "grid-tile" && !iosSimulatorOpen && (turnActive || !chatHasMessages);
2139+
// Idle composer motion keeps the GPU busy; reserve the beam for active turns.
2140+
const composerBeamActive = isActive && layoutVariant !== "grid-tile" && !iosSimulatorOpen && turnActive;
21402141
const composerBeamVariant = turnActive ? "ocean" : "colorful";
21412142
const composerBeamDuration = turnActive ? 20 : 5;
21422143
const composerBeamStrength = turnActive ? 0.26 : 0.44;

apps/desktop/src/renderer/components/files/FilesPage.test.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,16 @@ describe("FilesPage", () => {
280280
stageFile: vi.fn(async () => undefined),
281281
unstageFile: vi.fn(async () => undefined),
282282
discardFile: vi.fn(async () => undefined),
283+
listRecentCommits: vi.fn(async () => []),
284+
},
285+
diff: {
286+
getFile: vi.fn(async ({ path, mode }: { path: string; mode: string }) => ({
287+
path,
288+
mode,
289+
original: { exists: true, text: fileContents[path] ?? "" },
290+
modified: { exists: true, text: fileContents[path] ?? "" },
291+
language: path.endsWith(".ts") ? "typescript" : "markdown",
292+
})),
283293
},
284294
app: {
285295
openPathInEditor: vi.fn(async () => undefined),

apps/desktop/src/renderer/components/files/FilesPage.tsx

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import type {
3333
FilesQuickOpenItem,
3434
FilesSearchTextMatch,
3535
FilesWorkspace,
36+
FileDiff,
3637
GitCommitSummary,
3738
LaneSummary,
3839
} from "../../../shared/types";
@@ -456,6 +457,12 @@ function getFileIcon(fileName: string): { icon: React.ComponentType<any>; color:
456457
return { icon: FileText, color: FILE_ICON_COLORS.default };
457458
}
458459

460+
function fileDiffHasRenderableChanges(diff: FileDiff): boolean {
461+
if (diff.original.exists !== diff.modified.exists) return true;
462+
if (diff.isBinary) return true;
463+
return diff.original.text !== diff.modified.text;
464+
}
465+
459466
function changeStatusColor(changeStatus: FileTreeNode["changeStatus"]): string {
460467
if (changeStatus === "A") return COLORS.success;
461468
if (changeStatus === "D") return COLORS.danger;
@@ -2873,7 +2880,15 @@ function FilesDiffPanel({
28732880
</div>
28742881

28752882
{error ? <div style={{ padding: 12, fontFamily: MONO_FONT, fontSize: 11, color: COLORS.danger }}>{error}</div> : null}
2876-
<div className="min-h-0 flex-1">{diff ? <MonacoDiffView ref={diffViewRef} diff={diff} className="h-full" theme={theme} /> : null}</div>
2883+
<div className="min-h-0 flex-1">
2884+
{diff && fileDiffHasRenderableChanges(diff) ? (
2885+
<MonacoDiffView ref={diffViewRef} diff={diff} className="h-full" theme={theme} />
2886+
) : diff ? (
2887+
<div className="flex h-full items-center justify-center px-4 text-xs" style={{ color: COLORS.textMuted }}>
2888+
No changes for this file.
2889+
</div>
2890+
) : null}
2891+
</div>
28772892
</div>
28782893
);
28792894
}

apps/desktop/src/renderer/components/run/LaneRuntimeBar.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ const dividerStyle = {
2222
paddingRight: 16,
2323
};
2424

25+
const RUNTIME_BAR_PASSIVE_REFRESH_MS = 10_000;
26+
const RUNTIME_BAR_HEALTH_REFRESH_MS = 30_000;
27+
2528
export function LaneRuntimeBar({ laneId, onOpenPreviewRouting }: LaneRuntimeBarProps) {
2629
const [health, setHealth] = useState<LaneHealthCheck | null>(null);
2730
const [preview, setPreview] = useState<LanePreviewInfo | null>(null);
@@ -84,13 +87,19 @@ export function LaneRuntimeBar({ laneId, onOpenPreviewRouting }: LaneRuntimeBarP
8487
return;
8588
}
8689
let cancelled = false;
90+
let lastHealthRefreshAt = 0;
8791
const runRefresh = (runHealthCheck: boolean) => {
8892
if (cancelled) return;
93+
if (document.visibilityState !== "visible") return;
94+
if (runHealthCheck) lastHealthRefreshAt = Date.now();
8995
refreshRuntimeState(laneId, { runHealthCheck });
9096
};
9197
runRefresh(false);
9298
const deferredTimer = window.setTimeout(() => runRefresh(true), 160);
93-
const refreshInterval = window.setInterval(() => runRefresh(true), 2500);
99+
const refreshInterval = window.setInterval(() => {
100+
const shouldRunHealthCheck = Date.now() - lastHealthRefreshAt >= RUNTIME_BAR_HEALTH_REFRESH_MS;
101+
runRefresh(shouldRunHealthCheck);
102+
}, RUNTIME_BAR_PASSIVE_REFRESH_MS);
94103

95104
return () => {
96105
cancelled = true;

apps/desktop/src/renderer/components/terminals/useSessionDelta.ts

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import { useEffect, useState } from "react";
22
import type { SessionDeltaSummary } from "../../../shared/types";
33

44
const deltaCache = new Map<string, SessionDeltaSummary | null>();
5+
const deltaInFlight = new Map<string, Promise<SessionDeltaSummary | null>>();
6+
const deltaQueue: Array<{
7+
sessionId: string;
8+
resolve: (value: SessionDeltaSummary | null) => void;
9+
}> = [];
510
const MAX_CACHED_SESSION_DELTAS = 128;
11+
const MAX_CONCURRENT_SESSION_DELTA_REQUESTS = 4;
12+
let activeDeltaRequests = 0;
613

714
function touchDeltaCacheEntry(sessionId: string, value: SessionDeltaSummary | null): void {
815
if (deltaCache.has(sessionId)) {
@@ -16,6 +23,46 @@ function touchDeltaCacheEntry(sessionId: string, value: SessionDeltaSummary | nu
1623
}
1724
}
1825

26+
function pumpDeltaQueue(): void {
27+
while (activeDeltaRequests < MAX_CONCURRENT_SESSION_DELTA_REQUESTS && deltaQueue.length > 0) {
28+
const next = deltaQueue.shift();
29+
if (!next) return;
30+
activeDeltaRequests += 1;
31+
window.ade.sessions
32+
.getDelta(next.sessionId)
33+
.then((result) => {
34+
touchDeltaCacheEntry(next.sessionId, result);
35+
next.resolve(result);
36+
})
37+
.catch(() => {
38+
touchDeltaCacheEntry(next.sessionId, null);
39+
next.resolve(null);
40+
})
41+
.finally(() => {
42+
deltaInFlight.delete(next.sessionId);
43+
activeDeltaRequests = Math.max(0, activeDeltaRequests - 1);
44+
pumpDeltaQueue();
45+
});
46+
}
47+
}
48+
49+
function fetchSessionDelta(sessionId: string): Promise<SessionDeltaSummary | null> {
50+
if (deltaCache.has(sessionId)) {
51+
const cached = deltaCache.get(sessionId) ?? null;
52+
touchDeltaCacheEntry(sessionId, cached);
53+
return Promise.resolve(cached);
54+
}
55+
const existing = deltaInFlight.get(sessionId);
56+
if (existing) return existing;
57+
58+
const request = new Promise<SessionDeltaSummary | null>((resolve) => {
59+
deltaQueue.push({ sessionId, resolve });
60+
pumpDeltaQueue();
61+
});
62+
deltaInFlight.set(sessionId, request);
63+
return request;
64+
}
65+
1966
export function useSessionDelta(sessionId: string | null, enabled: boolean) {
2067
const [delta, setDelta] = useState<SessionDeltaSummary | null>(
2168
sessionId ? deltaCache.get(sessionId) ?? null : null,
@@ -27,24 +74,11 @@ export function useSessionDelta(sessionId: string | null, enabled: boolean) {
2774
return;
2875
}
2976

30-
if (deltaCache.has(sessionId)) {
31-
const cached = deltaCache.get(sessionId) ?? null;
32-
touchDeltaCacheEntry(sessionId, cached);
33-
setDelta(cached);
34-
return;
35-
}
36-
3777
let cancelled = false;
38-
window.ade.sessions
39-
.getDelta(sessionId)
78+
fetchSessionDelta(sessionId)
4079
.then((result) => {
4180
if (cancelled) return;
42-
touchDeltaCacheEntry(sessionId, result);
4381
setDelta(result);
44-
})
45-
.catch(() => {
46-
// Cache the miss so we don't re-fetch on every render
47-
if (!cancelled) touchDeltaCacheEntry(sessionId, null);
4882
});
4983

5084
return () => {

0 commit comments

Comments
 (0)