Skip to content

Commit be54470

Browse files
committed
Block TemplateInline file generation until "delete existing files" alert resolves
TemplateInline components with generateFile={true} auto-execute on page mount, which was regenerating files (like .github/workflows/ci.yml) immediately after the user deleted them. The user would delete files, refresh, and see them reappear because: 1. Generated files check runs first → finds 0 files → no alert 2. TemplateInline auto-renders → recreates the files Fix: add a `renderingEnabled` flag to GeneratedFilesContext that starts false and is only set to true once the generated files check completes and either: - No existing files were found, or - The user resolved the alert (clicked "Keep Files" or "Delete Files") TemplateInline now checks this flag before auto-rendering when generateFile is true, preventing the race condition where files are regenerated before the user decides what to do with existing ones. https://claude.ai/code/session_01Js4Mce3EEoW3Z83kpNuSEC
1 parent 707053c commit be54470

File tree

4 files changed

+59
-9
lines changed

4 files changed

+59
-9
lines changed

web/src/App.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { GeneratedFilesAlert, shouldShowGeneratedFilesAlert } from './components
1212
import { getDirectoryPath, hasGeneratedFiles } from './lib/utils'
1313
import { useGetRunbook } from './hooks/useApiGetRunbook'
1414
import { useFileTree } from './hooks/useFileTree'
15+
import { useGeneratedFiles } from './hooks/useGeneratedFiles'
1516
import { useGitWorkTree } from './contexts/useGitWorkTree'
1617
import { useWatchMode } from './hooks/useWatchMode'
1718
import { useApiGeneratedFilesCheck } from './hooks/useApiGeneratedFilesCheck'
@@ -57,6 +58,7 @@ function App() {
5758

5859
// Get file tree state to detect when files are generated
5960
const { fileTree, setFileTree } = useFileTree()
61+
const { setRenderingEnabled } = useGeneratedFiles()
6062
const hasFiles = hasGeneratedFiles(fileTree)
6163

6264
// Get git worktree state to detect when a repo is cloned
@@ -124,6 +126,33 @@ function App() {
124126
generatedFilesCheck.data?.hasFiles,
125127
alertDismissedThisSession,
126128
]);
129+
130+
// Enable file-generating template rendering once the generated files check resolves
131+
// and either no files exist or the alert won't be shown (dismissed / "don't ask again").
132+
// This prevents TemplateInline components from regenerating files before the user
133+
// decides to keep or delete existing ones.
134+
useEffect(() => {
135+
if (generatedFilesCheck.isLoading) return;
136+
// No existing files → safe to render immediately
137+
if (!generatedFilesCheck.data?.hasFiles) {
138+
setRenderingEnabled(true);
139+
return;
140+
}
141+
// Files exist but user previously chose "don't ask again"
142+
if (!shouldShowGeneratedFilesAlert()) {
143+
setRenderingEnabled(true);
144+
return;
145+
}
146+
// Files exist and alert has been dismissed this session (keep or delete)
147+
if (alertDismissedThisSession) {
148+
setRenderingEnabled(true);
149+
}
150+
}, [
151+
generatedFilesCheck.isLoading,
152+
generatedFilesCheck.data?.hasFiles,
153+
alertDismissedThisSession,
154+
setRenderingEnabled,
155+
]);
127156

128157
// Extract commonly used values
129158
// Prefer remoteSource (original GitHub/GitLab URL) over local temp path for display

web/src/components/mdx/TemplateInline/TemplateInline.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { extractTemplateFiles } from './lib/extractTemplateFiles'
1111
import { extractOutputDependencies, extractOutputDependenciesFromString } from './lib/extractOutputDependencies'
1212
import type { FileTreeNode, File } from '@/components/artifacts/code/FileTree'
1313
import { useFileTree } from '@/hooks/useFileTree'
14+
import { useGeneratedFiles } from '@/hooks/useGeneratedFiles'
1415
import { useGitWorkTree } from '@/contexts/useGitWorkTree'
1516
import { CodeFile } from '@/components/artifacts/code/CodeFile'
1617
import { AlertTriangle } from 'lucide-react'
@@ -62,6 +63,8 @@ function TemplateInline({
6263
// Get file tree for merging (Generated tab) and worktree context for invalidation (All files tab)
6364
const { setFileTree } = useFileTree();
6465
const { invalidateTree } = useGitWorkTree();
66+
// Check if file-generating rendering is allowed (blocked while "delete existing files" alert is pending)
67+
const { renderingEnabled } = useGeneratedFiles();
6568

6669
// Get inputs for API requests and derive values map for lookups
6770
const inputs = useInputs(inputsId);
@@ -203,11 +206,17 @@ function TemplateInline({
203206
const hasTriggeredInitialRender = useRef(false);
204207

205208
useEffect(() => {
209+
// When generateFile is true, wait until the "Existing Generated Files" alert
210+
// is resolved so we don't regenerate files the user just deleted.
211+
if (generateFile && !renderingEnabled) {
212+
return;
213+
}
214+
206215
// Check if we have all input dependencies
207216
if (!hasAllInputDependencies(inputValues)) {
208217
return;
209218
}
210-
219+
211220
// Check if we have all output dependencies
212221
if (!hasAllOutputDependencies) {
213222
return;
@@ -224,22 +233,22 @@ function TemplateInline({
224233
const valuesKey = JSON.stringify(inputs);
225234
const outputsKey = JSON.stringify(allOutputs);
226235
const combinedKey = `${valuesKey}|${outputsKey}`;
227-
236+
228237
if (combinedKey === lastRenderedVariablesRef.current) {
229238
return;
230239
}
231-
240+
232241
// Clear existing timer
233242
if (autoUpdateTimerRef.current) {
234243
clearTimeout(autoUpdateTimerRef.current);
235244
}
236-
245+
237246
// Determine if this is initial render or auto-update
238247
const isInitialRender = !hasTriggeredInitialRender.current;
239-
248+
240249
// Debounce for auto-updates, immediate for initial render
241250
const delay = isInitialRender ? 0 : 300;
242-
251+
243252
autoUpdateTimerRef.current = setTimeout(() => {
244253
hasTriggeredInitialRender.current = true;
245254

@@ -265,7 +274,7 @@ function TemplateInline({
265274
console.error(`[TemplateInline][${outputPath}] Render failed:`, err);
266275
});
267276
}, delay);
268-
}, [inputValues, inputs, allOutputs, hasAllInputDependencies, hasAllOutputDependencies, outputPath, renderTemplate, setFileTree, generateFile, target, invalidateTree]);
277+
}, [inputValues, inputs, allOutputs, hasAllInputDependencies, hasAllOutputDependencies, outputPath, renderTemplate, setFileTree, generateFile, target, invalidateTree, renderingEnabled]);
269278

270279
// Cleanup timer on unmount
271280
useEffect(() => {

web/src/contexts/GeneratedFilesContext.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ interface GeneratedFilesProviderProps {
1010
export const GeneratedFilesProvider: React.FC<GeneratedFilesProviderProps> = ({ children }) => {
1111
const [fileTree, setFileTree] = useState<FileTreeNode[] | null>(null)
1212
const [localPath, setLocalPath] = useState<string | null>(null)
13-
13+
// Start disabled until the generated files check resolves (prevents TemplateInline
14+
// from regenerating files before the user decides to keep/delete existing ones)
15+
const [renderingEnabled, setRenderingEnabled] = useState(false)
16+
1417
// Stable reference to prevent unnecessary re-renders in consuming components
1518
// Support both direct values and functional updates
1619
const stableSetFileTree = useCallback((newFileTree: FileTreeNode[] | null | ((prevFileTree: FileTreeNode[] | null) => FileTreeNode[] | null)) => {
@@ -21,8 +24,12 @@ export const GeneratedFilesProvider: React.FC<GeneratedFilesProviderProps> = ({
2124
setLocalPath(path)
2225
}, [])
2326

27+
const stableSetRenderingEnabled = useCallback((enabled: boolean) => {
28+
setRenderingEnabled(enabled)
29+
}, [])
30+
2431
return (
25-
<GeneratedFilesContext.Provider value={{ fileTree, setFileTree: stableSetFileTree, localPath, setLocalPath: stableSetLocalPath }}>
32+
<GeneratedFilesContext.Provider value={{ fileTree, setFileTree: stableSetFileTree, localPath, setLocalPath: stableSetLocalPath, renderingEnabled, setRenderingEnabled: stableSetRenderingEnabled }}>
2633
{children}
2734
</GeneratedFilesContext.Provider>
2835
)

web/src/contexts/GeneratedFilesContext.types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ export interface GeneratedFilesContextType {
66
setFileTree: (fileTree: FileTreeNode[] | null | ((prevFileTree: FileTreeNode[] | null) => FileTreeNode[] | null)) => void
77
localPath: string | null
88
setLocalPath: (path: string | null) => void
9+
/** Whether file-generating templates are allowed to auto-render. False while the
10+
* "Existing Generated Files Detected" alert is pending so that TemplateInline
11+
* components don't regenerate files before the user decides to keep or delete. */
12+
renderingEnabled: boolean
13+
setRenderingEnabled: (enabled: boolean) => void
914
}
1015

1116
export const GeneratedFilesContext = createContext<GeneratedFilesContextType | undefined>(undefined)

0 commit comments

Comments
 (0)