22import { useCallback , useMemo } from "react" ;
33import { STUDIO_MOTION_PATH } from "../components/editor/studioMotion" ;
44import { serializeStudioFileMutations } from "../utils/studioFileMutationCoordinator" ;
5+ import type { RestoreFiles } from "../utils/gsapUndoRestore" ;
56
67interface HistoryResult {
78 ok : boolean ;
89 reason ?: string ;
910 message ?: string ;
1011 label ?: string ;
1112 paths ?: string [ ] ;
13+ undoes ?: string ;
1214 /** Per-file restored/previous content, used to soft-apply the preview. */
13- files ?: Record < string , { previous : string ; restored : string } > ;
15+ files ?: RestoreFiles ;
1416}
15- type RestoreFiles = Record < string , { previous : string ; restored : string } > ;
1617interface HistoryFileCallbacks {
1718 readFile : ( path : string ) => Promise < string > ;
1819 serialize ?: < T > ( paths : readonly string [ ] , task : ( ) => Promise < T > ) => Promise < T > ;
1920}
2021export interface EditHistoryHandle {
2122 undo : ( cb : HistoryFileCallbacks ) => Promise < HistoryResult > ;
2223 redo : ( cb : HistoryFileCallbacks ) => Promise < HistoryResult > ;
23- predict ?: ( direction : "undo" | "redo" ) => RestoreFiles | null ;
24+ predict ?: ( direction : "undo" | "redo" ) => { id : string ; files : RestoreFiles } | null ;
2425 state : {
2526 undo : ReadonlyArray < { createdAt : number } > ;
2627 redo : ReadonlyArray < { createdAt : number } > ;
@@ -34,7 +35,7 @@ export interface UseEditHistoryActionsOptions {
3435 writeProjectFile : ( path : string , content : string ) => Promise < void > ;
3536 showToast : ( message : string , tone ?: "error" | "info" ) => void ;
3637 syncHistoryPreviewAfterApply : ( restore : Pick < HistoryResult , "paths" | "files" > ) => Promise < void > ;
37- showHistoryRestoreNow ?: ( files : RestoreFiles ) => boolean ;
38+ showHistoryRestoreNow ?: ( files : RestoreFiles ) => ( ( ) => void ) | null ;
3839 waitForPendingDomEditSaves : ( ) => Promise < void > ;
3940 onAfterUndoRedo ?: ( restore : Pick < HistoryResult , "paths" | "files" > ) => void ;
4041 /** Active composition path — decides whether undo/redo must resync the SDK session. */
@@ -73,21 +74,19 @@ export function useEditHistoryActions({
7374 const noun = direction === "undo" ? "Undo" : "Redo" ;
7475 // Paint the step in the key's own task when this tab knows it; the server's answer then confirms or corrects.
7576 const predicted = editHistory . predict ?.( direction ) ?? null ;
76- const shown = predicted && showHistoryRestoreNow ?.( predicted ) ? predicted : null ;
77+ const putBack = predicted ? ( showHistoryRestoreNow ?.( predicted . files ) ?? null ) : null ;
7778 let result : HistoryResult = { ok : false , reason : "failed" } ;
79+ let serverSteppedShown = false ;
7880 try {
7981 await waitForPendingDomEditSaves ( ) ;
8082 result = await editHistory [ direction ] ( {
8183 readFile : readHistoryFile ,
8284 serialize : serializeHistoryFiles ,
8385 } ) ;
86+ serverSteppedShown =
87+ Boolean ( putBack && result . ok && result . label ) && result . undoes === predicted ?. id ;
8488 } finally {
85- if ( shown && ! ( result . ok && result . label ) ) {
86- void syncHistoryPreviewAfterApply ( {
87- paths : Object . keys ( shown ) ,
88- files : swapRestore ( shown ) ,
89- } ) ;
90- }
89+ if ( putBack && ! serverSteppedShown ) putBack ( ) ;
9190 }
9291 if ( ! result . ok && result . reason === "content-mismatch" ) {
9392 showToast (
@@ -101,7 +100,8 @@ export function useEditHistoryActions({
101100 return ;
102101 }
103102 if ( result . ok && result . label ) {
104- const restore = { paths : result . paths , files : fromShown ( result . files , shown ) } ;
103+ const files = serverSteppedShown ? fromShown ( result . files , predicted ! . files ) : result . files ;
104+ const restore = { paths : result . paths , files } ;
105105 onAfterUndoRedo ?.( restore ) ;
106106 if ( activeCompPath && result . paths ?. includes ( activeCompPath ) ) {
107107 forceReloadSdkSession ?.( ) ;
@@ -129,17 +129,8 @@ export function useEditHistoryActions({
129129 return useMemo ( ( ) => ( { undo, redo } ) , [ undo , redo ] ) ;
130130}
131131
132- function swapRestore ( files : RestoreFiles ) : RestoreFiles {
133- return Object . fromEntries (
134- Object . entries ( files ) . map ( ( [ path , f ] ) => [
135- path ,
136- { previous : f . restored , restored : f . previous } ,
137- ] ) ,
138- ) ;
139- }
140-
141- function fromShown ( files : RestoreFiles | undefined , shown : RestoreFiles | null ) {
142- if ( ! files || ! shown ) return files ;
132+ function fromShown ( files : RestoreFiles | undefined , shown : RestoreFiles ) {
133+ if ( ! files ) return files ;
143134 return Object . fromEntries (
144135 Object . entries ( files ) . map ( ( [ path , f ] ) => [
145136 path ,
0 commit comments