Skip to content

Commit 462cd81

Browse files
committed
fix(files): derive markdown round-trip verdict from live content, not a locked stale snapshot
The gate locked isRoundTripSafe on the first post-stream snapshot, which is often the empty create_file buffer before the agent's server write lands — wrongly leaving an unsafe document editable. Derive the verdict from the current content (memoized on the bytes) so canEdit tracks the real payload.
1 parent 511fc6b commit 462cd81

1 file changed

Lines changed: 12 additions & 11 deletions

File tree

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/markdown-file-editor.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useRef } from 'react'
3+
import { useMemo } from 'react'
44
import type { WorkspaceFileRecord } from '@/lib/uploads/contexts/workspace'
55
import { useWorkspaceFileContent } from '@/hooks/queries/workspace-files'
66
import type { SaveStatus } from '@/hooks/use-autosave'
@@ -29,10 +29,6 @@ interface MarkdownFileEditorProps {
2929
* (raw HTML, footnotes, linked images, >128KB); editing those would corrupt them, so the gate marks
3030
* them read-only (autosave never fires) while still rendering them in the same rich editor. There is
3131
* no separate raw/Monaco editor.
32-
*
33-
* The round-trip decision is made once, on the first SETTLED content, and locked for the mount
34-
* (keyed by file id). It is deferred while streaming — partial content would misclassify, and the
35-
* editor renders the live stream read-only regardless of the eventual verdict.
3632
*/
3733
export function MarkdownFileEditor({
3834
file,
@@ -51,20 +47,25 @@ export function MarkdownFileEditor({
5147

5248
const isStreaming = streamingContent !== undefined
5349

54-
const decisionRef = useRef<boolean | null>(null)
55-
if (decisionRef.current === null && !isStreaming && data !== undefined) {
56-
decisionRef.current = isRoundTripSafe(data)
57-
}
50+
// Whether the file's content round-trips losslessly through the editor. Derived from the live
51+
// content — memoized on the bytes, so it only re-probes when they actually change — rather than
52+
// locked on the first snapshot: locking could capture a stale/empty buffer (e.g. a just-created
53+
// file before an agent stream's server write lands) and wrongly leave an unsafe document editable.
54+
// Deferred while streaming: the content is partial and the editor renders the stream read-only.
55+
const isContentRoundTripSafe = useMemo(
56+
() => (isStreaming || data === undefined ? null : isRoundTripSafe(data)),
57+
[isStreaming, data]
58+
)
5859

59-
if (decisionRef.current === null && isLoading && !isStreaming) {
60+
if (isContentRoundTripSafe === null && isLoading && !isStreaming) {
6061
return <PreviewLoadingFrame className='flex flex-1 flex-col' />
6162
}
6263

6364
return (
6465
<RichMarkdownEditor
6566
file={file}
6667
workspaceId={workspaceId}
67-
canEdit={canEdit && decisionRef.current !== false}
68+
canEdit={canEdit && isContentRoundTripSafe !== false}
6869
autoFocus={autoFocus}
6970
onDirtyChange={onDirtyChange}
7071
onSaveStatusChange={onSaveStatusChange}

0 commit comments

Comments
 (0)