Skip to content

Commit f844a6a

Browse files
committed
fix(files): lock the markdown round-trip verdict on opened content, never strand dirty edits
The round-trip-safety verdict now gates editability only at open time — computed once, on the exact content the editor mounts with, and locked for its lifetime. A dirty document is round-trip-safe by construction (the editor only emits safe markdown), so the verdict must never flip off mid-edit: doing so disabled autosave, ⌘S, the toolbar Save and the unmount flush, stranding unsaved edits. Locking on the opened (reconciled) content also fixes the stale post-stream empty-buffer snapshot, and lets the redundant MarkdownFileEditor gate (plus its duplicate content fetch) be deleted.
1 parent c308124 commit f844a6a

3 files changed

Lines changed: 21 additions & 86 deletions

File tree

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ const PdfViewerCore = dynamic(() => import('./pdf-viewer').then((m) => m.PdfView
3232
ssr: false,
3333
})
3434

35-
const MarkdownFileEditor = dynamic(
36-
() => import('./rich-markdown-editor/markdown-file-editor').then((m) => m.MarkdownFileEditor),
35+
const RichMarkdownEditor = dynamic(
36+
() => import('./rich-markdown-editor/rich-markdown-editor').then((m) => m.RichMarkdownEditor),
3737
{ ssr: false, loading: () => <PreviewLoadingFrame className='flex flex-1 flex-col' /> }
3838
)
3939

@@ -130,7 +130,7 @@ export function FileViewer({
130130
// the bubble menu, and every other editing affordance.
131131
if (isMarkdownFile(file)) {
132132
return (
133-
<MarkdownFileEditor key={file.id} file={file} workspaceId={workspaceId} canEdit={false} />
133+
<RichMarkdownEditor key={file.id} file={file} workspaceId={workspaceId} canEdit={false} />
134134
)
135135
}
136136
return <ReadOnlyTextPreview file={file} workspaceId={workspaceId} />
@@ -143,7 +143,7 @@ export function FileViewer({
143143

144144
if (isMarkdownFile(file)) {
145145
return (
146-
<MarkdownFileEditor
146+
<RichMarkdownEditor
147147
key={file.id}
148148
file={file}
149149
workspaceId={workspaceId}

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

Lines changed: 0 additions & 79 deletions
This file was deleted.

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
splitFrontmatter,
2121
} from './markdown-fidelity'
2222
import { EditorBubbleMenu } from './menus/bubble-menu'
23+
import { isRoundTripSafe } from './round-trip-safety'
2324
import '@/components/emcn/components/code/code.css'
2425
import './rich-markdown-editor.css'
2526

@@ -116,7 +117,7 @@ export const RichMarkdownEditor = memo(function RichMarkdownEditor({
116117
file={file}
117118
workspaceId={workspaceId}
118119
initialContent={content}
119-
isEditable={canEdit}
120+
canEdit={canEdit}
120121
autoFocus={autoFocus}
121122
onChange={setDraftContent}
122123
onSaveShortcut={saveImmediately}
@@ -128,7 +129,7 @@ interface LoadedRichMarkdownEditorProps {
128129
file: WorkspaceFileRecord
129130
workspaceId: string
130131
initialContent: string
131-
isEditable: boolean
132+
canEdit: boolean
132133
autoFocus?: boolean
133134
onChange: (markdown: string) => void
134135
onSaveShortcut: () => Promise<void>
@@ -144,11 +145,24 @@ function LoadedRichMarkdownEditor({
144145
file,
145146
workspaceId,
146147
initialContent,
147-
isEditable,
148+
canEdit,
148149
autoFocus,
149150
onChange,
150151
onSaveShortcut,
151152
}: LoadedRichMarkdownEditorProps) {
153+
// Whether the opened content round-trips losslessly through the editor — computed once, on the
154+
// exact content the editor opens with (keyed by file id, so it remounts per file), and locked for
155+
// the editor's lifetime. A round-trip-unsafe document (raw HTML, footnotes, >128KB, …) opens
156+
// read-only so an edit can't corrupt it; a safe one stays editable. It is never re-derived: a
157+
// dirty document is round-trip-safe by construction (the editor only emits safe markdown), so
158+
// flipping editability off mid-edit would only strand unsaved edits (autosave, ⌘S, the toolbar
159+
// Save, and the unmount flush all gate on it).
160+
const roundTripSafeRef = useRef<boolean | null>(null)
161+
if (roundTripSafeRef.current === null) {
162+
roundTripSafeRef.current = isRoundTripSafe(initialContent)
163+
}
164+
const isEditable = canEdit && roundTripSafeRef.current
165+
152166
const { frontmatter, body } = useMemo(() => splitFrontmatter(initialContent), [initialContent])
153167
const frontmatterRef = useRef(frontmatter)
154168
frontmatterRef.current = frontmatter

0 commit comments

Comments
 (0)