Unverified 提交 bb2eadfe authored 作者: keppo-bot[bot]'s avatar keppo-bot[bot] 提交者: GitHub

Add setting to keep app previews running forever (#3273)

## Summary - Adds a `previewIdleTimeoutPolicy` enum setting (`"default"` | `"never"`) that controls the 10-minute idle GC for app previews. - When set to `"never"`, the idle GC is skipped so previews stay warm across app switches (more memory, faster preview loads). - Exposes a new toggle in Workflow Settings: **Keep app previews running forever** (disabled by default). ## Test plan - [ ] Leave the toggle off: open an app preview, switch to another app, wait 10+ minutes — original preview should be stopped as before. - [ ] Turn the toggle on: do the same — the original preview should keep running after the idle window. - [ ] Toggling off should restore GC on the next minute-interval. - [x] All existing unit tests pass (snapshots updated). - [x] Lint + typecheck pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/3273" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open in Devin Review"> </picture> </a> <!-- devin-review-badge-end --> --------- Co-authored-by: 's avatarWill Chen <7344640+wwwillchen@users.noreply.github.com> Co-authored-by: 's avatarClaude Opus 4.7 (1M context) <noreply@anthropic.com>
上级 0d33110f
...@@ -7,6 +7,7 @@ When adding a new toggle/setting to the Settings page: ...@@ -7,6 +7,7 @@ When adding a new toggle/setting to the Settings page:
3. Add a `SETTING_IDS` entry and search index entry in `src/lib/settingsSearchIndex.ts` 3. Add a `SETTING_IDS` entry and search index entry in `src/lib/settingsSearchIndex.ts`
4. Create a switch component (e.g., `src/components/MySwitch.tsx`) - follow `AutoApproveSwitch.tsx` as a template 4. Create a switch component (e.g., `src/components/MySwitch.tsx`) - follow `AutoApproveSwitch.tsx` as a template
5. Import and add the switch to the relevant section in `src/pages/settings.tsx` 5. Import and add the switch to the relevant section in `src/pages/settings.tsx`
6. Adding a field to `DEFAULT_SETTINGS` breaks the inline snapshots in `src/__tests__/readSettings.test.ts`. After confirming the diff is just your new field, regenerate them with `npm test -- -u`.
For settings whose default can be overridden remotely: For settings whose default can be overridden remotely:
......
...@@ -77,6 +77,7 @@ describe("readSettings", () => { ...@@ -77,6 +77,7 @@ describe("readSettings", () => {
"hasRunBefore": false, "hasRunBefore": false,
"isRunning": false, "isRunning": false,
"lastKnownPerformance": undefined, "lastKnownPerformance": undefined,
"previewIdleTimeoutPolicy": "default",
"providerSettings": {}, "providerSettings": {},
"releaseChannel": "stable", "releaseChannel": "stable",
"selectedChatMode": "build", "selectedChatMode": "build",
...@@ -469,6 +470,7 @@ describe("readSettings", () => { ...@@ -469,6 +470,7 @@ describe("readSettings", () => {
"hasRunBefore": false, "hasRunBefore": false,
"isRunning": false, "isRunning": false,
"lastKnownPerformance": undefined, "lastKnownPerformance": undefined,
"previewIdleTimeoutPolicy": "default",
"providerSettings": {}, "providerSettings": {},
"releaseChannel": "stable", "releaseChannel": "stable",
"selectedChatMode": "build", "selectedChatMode": "build",
......
import { useSettings } from "@/hooks/useSettings";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
export function KeepPreviewsRunningSwitch() {
const { settings, updateSettings } = useSettings();
const isEnabled = settings?.previewIdleTimeoutPolicy === "never";
return (
<div className="flex items-center space-x-2">
<Switch
id="keep-previews-running"
aria-label="Keep app previews running forever"
checked={isEnabled}
onCheckedChange={(checked) => {
updateSettings({
previewIdleTimeoutPolicy: checked ? "never" : "default",
});
}}
/>
<Label htmlFor="keep-previews-running">
Keep app previews running forever
</Label>
</div>
);
}
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
stopCloudSandboxFileSync, stopCloudSandboxFileSync,
unregisterRunningCloudSandbox, unregisterRunningCloudSandbox,
} from "./cloud_sandbox_provider"; } from "./cloud_sandbox_provider";
import { readSettings } from "../../main/settings";
const logger = log.scope("process_manager"); const logger = log.scope("process_manager");
...@@ -244,6 +245,10 @@ export function getCurrentlySelectedAppId(): number | null { ...@@ -244,6 +245,10 @@ export function getCurrentlySelectedAppId(): number | null {
* and are not the currently selected app. * and are not the currently selected app.
*/ */
export async function garbageCollectIdleApps(): Promise<void> { export async function garbageCollectIdleApps(): Promise<void> {
if (readSettings().previewIdleTimeoutPolicy === "never") {
return;
}
const now = Date.now(); const now = Date.now();
const appsToStop: number[] = []; const appsToStop: number[] = [];
......
...@@ -369,6 +369,7 @@ const BaseUserSettingsFields = { ...@@ -369,6 +369,7 @@ const BaseUserSettingsFields = {
enableContextCompaction: z.boolean().optional(), enableContextCompaction: z.boolean().optional(),
skipNotificationBanner: z.boolean().optional(), skipNotificationBanner: z.boolean().optional(),
enableSelectAppFromHomeChatInput: z.boolean().optional(), enableSelectAppFromHomeChatInput: z.boolean().optional(),
previewIdleTimeoutPolicy: z.enum(["default", "never"]).optional(),
}; };
/** /**
......
...@@ -23,6 +23,7 @@ export const SETTING_IDS = { ...@@ -23,6 +23,7 @@ export const SETTING_IDS = {
autoApprove: "setting-auto-approve", autoApprove: "setting-auto-approve",
autoFix: "setting-auto-fix", autoFix: "setting-auto-fix",
autoExpandPreview: "setting-auto-expand-preview", autoExpandPreview: "setting-auto-expand-preview",
keepPreviewsRunning: "setting-keep-previews-running",
chatEventNotification: "setting-chat-event-notification", chatEventNotification: "setting-chat-event-notification",
thinkingBudget: "setting-thinking-budget", thinkingBudget: "setting-thinking-budget",
maxChatTurns: "setting-max-chat-turns", maxChatTurns: "setting-max-chat-turns",
...@@ -145,6 +146,25 @@ export const SETTINGS_SEARCH_INDEX: SearchableSettingItem[] = [ ...@@ -145,6 +146,25 @@ export const SETTINGS_SEARCH_INDEX: SearchableSettingItem[] = [
sectionId: SECTION_IDS.workflow, sectionId: SECTION_IDS.workflow,
sectionLabel: "Workflow", sectionLabel: "Workflow",
}, },
{
id: SETTING_IDS.keepPreviewsRunning,
label: "Keep app previews running forever",
description:
"Prevent idle app previews from being stopped after 10 minutes; uses more memory but enables faster preview loads when switching apps",
keywords: [
"preview",
"idle",
"timeout",
"gc",
"garbage collect",
"memory",
"forever",
"keep",
"running",
],
sectionId: SECTION_IDS.workflow,
sectionLabel: "Workflow",
},
{ {
id: SETTING_IDS.chatEventNotification, id: SETTING_IDS.chatEventNotification,
label: "Notifications", label: "Notifications",
......
...@@ -48,6 +48,7 @@ const DEFAULT_SETTINGS: UserSettings = { ...@@ -48,6 +48,7 @@ const DEFAULT_SETTINGS: UserSettings = {
enableNativeGit: true, enableNativeGit: true,
autoExpandPreviewPanel: true, autoExpandPreviewPanel: true,
enableContextCompaction: true, enableContextCompaction: true,
previewIdleTimeoutPolicy: "default",
}; };
const SETTINGS_FILE = "user-settings.json"; const SETTINGS_FILE = "user-settings.json";
......
...@@ -22,6 +22,7 @@ import { Switch } from "@/components/ui/switch"; ...@@ -22,6 +22,7 @@ import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { AutoFixProblemsSwitch } from "@/components/AutoFixProblemsSwitch"; import { AutoFixProblemsSwitch } from "@/components/AutoFixProblemsSwitch";
import { AutoExpandPreviewSwitch } from "@/components/AutoExpandPreviewSwitch"; import { AutoExpandPreviewSwitch } from "@/components/AutoExpandPreviewSwitch";
import { KeepPreviewsRunningSwitch } from "@/components/KeepPreviewsRunningSwitch";
import { ChatEventNotificationSwitch } from "@/components/ChatEventNotificationSwitch"; import { ChatEventNotificationSwitch } from "@/components/ChatEventNotificationSwitch";
import { AutoUpdateSwitch } from "@/components/AutoUpdateSwitch"; import { AutoUpdateSwitch } from "@/components/AutoUpdateSwitch";
import { ReleaseChannelSelector } from "@/components/ReleaseChannelSelector"; import { ReleaseChannelSelector } from "@/components/ReleaseChannelSelector";
...@@ -425,6 +426,14 @@ export function WorkflowSettings() { ...@@ -425,6 +426,14 @@ export function WorkflowSettings() {
</div> </div>
</div> </div>
<div id={SETTING_IDS.keepPreviewsRunning} className="space-y-1 mt-4">
<KeepPreviewsRunningSwitch />
<div className="text-sm text-gray-500 dark:text-gray-400">
Note: this may take more memory but allows faster preview loads when
switching apps.
</div>
</div>
<div id={SETTING_IDS.chatEventNotification} className="space-y-1 mt-4"> <div id={SETTING_IDS.chatEventNotification} className="space-y-1 mt-4">
<ChatEventNotificationSwitch /> <ChatEventNotificationSwitch />
<div className="text-sm text-gray-500 dark:text-gray-400"> <div className="text-sm text-gray-500 dark:text-gray-400">
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论