@@ -68,7 +68,8 @@ export function useDocument({ path, onDirtyChange }: Options) {
6868 } ) ;
6969 diskMtimeRef . current = mtime ;
7070 savedRef . current = content ;
71- setDirty ( false ) ;
71+ // Edits typed while the write was in flight must stay dirty.
72+ setDirty ( bufferRef . current !== content ) ;
7273 notifyDocumentSaved ( path ) ;
7374 } , [ path ] ) ;
7475
@@ -139,6 +140,8 @@ export function useDocument({ path, onDirtyChange }: Options) {
139140 // Load on path change.
140141 useEffect ( ( ) => {
141142 let cancelled = false ;
143+ // "Open anyway" is a per-file decision; a new path starts unforced.
144+ forceRef . current = false ;
142145 setDoc ( { status : "loading" } ) ;
143146 setDirty ( false ) ;
144147
@@ -163,11 +166,14 @@ export function useDocument({ path, onDirtyChange }: Options) {
163166 . catch ( ( e ) => setDoc ( { status : "error" , message : String ( e ) } ) ) ;
164167 } , [ readFromDisk , adoptRead ] ) ;
165168
166- // Skipped while dirty: never clobber unsaved edits.
169+ // Skipped while dirty: never clobber unsaved edits. Re-checked when the
170+ // read resolves, since typing can start while it is in flight.
167171 const reload = useCallback ( ( ) : boolean => {
168172 if ( dirtyRef . current ) return false ;
169173 void readFromDisk ( forceRef . current )
170- . then ( ( res ) => adoptRead ( res , true ) )
174+ . then ( ( res ) => {
175+ if ( ! dirtyRef . current ) adoptRead ( res , true ) ;
176+ } )
171177 // Transient failures (e.g. ENOENT mid atomic-rename) must not replace
172178 // a healthy buffer with an error screen.
173179 . catch ( ( e ) => console . warn ( "[editor] reload failed" , path , e ) ) ;
@@ -181,15 +187,21 @@ export function useDocument({ path, onDirtyChange }: Options) {
181187 } , [ clearAutoSaveTimer , saveNow ] ) ;
182188
183189 // Adopt externally formatted disk content as the saved baseline before the
184- // matching editor dispatch lands, so the buffer never flashes dirty.
190+ // matching editor dispatch lands, so the buffer never flashes dirty. The
191+ // formatter's own write must also become the known mtime, or the next save
192+ // would report it as an external conflict.
185193 // Returns the LF-normalized text the caller should dispatch.
186- const adoptDiskText = useCallback ( ( diskText : string ) : string => {
187- eolRef . current = detectEol ( diskText ) ;
188- const content = normalizeToLf ( diskText ) ;
189- savedRef . current = content ;
190- setDirty ( bufferRef . current !== content ) ;
191- return content ;
192- } , [ ] ) ;
194+ const adoptDiskText = useCallback (
195+ ( diskText : string , mtime : number ) : string => {
196+ eolRef . current = detectEol ( diskText ) ;
197+ diskMtimeRef . current = mtime ;
198+ const content = normalizeToLf ( diskText ) ;
199+ savedRef . current = content ;
200+ setDirty ( bufferRef . current !== content ) ;
201+ return content ;
202+ } ,
203+ [ ] ,
204+ ) ;
193205
194206 const onChange = useCallback (
195207 ( next : string ) => {
0 commit comments