@@ -17,6 +17,7 @@ import { isAudioFile } from "../helpers/mime.js";
1717import { generateWaveformCache } from "../helpers/waveform.js" ;
1818import { validateUploadedMediaBuffer } from "../helpers/mediaValidation.js" ;
1919import { isSafePath } from "../helpers/safePath.js" ;
20+ import { backupPathForResponse , snapshotBeforeWrite } from "../helpers/backupJournal.js" ;
2021import type { GsapAnimation } from "../../parsers/gsapSerialize.js" ;
2122import {
2223 removeElementFromHtml ,
@@ -94,15 +95,25 @@ type MutationTarget = {
9495/** Write `next` to `absPath` only if it differs from `original`, returning a standardized change response. */
9596function writeIfChanged (
9697 c : RouteContext ,
98+ projectDir : string ,
99+ filePath : string ,
97100 absPath : string ,
98101 original : string ,
99102 next : string ,
100103) : Response {
101104 if ( next === original ) {
102- return c . json ( { ok : true , changed : false , content : original } ) ;
105+ return c . json ( { ok : true , changed : false , content : original , path : filePath } ) ;
103106 }
107+ const backup = snapshotBeforeWrite ( projectDir , absPath ) ;
108+ if ( backup . error ) console . warn ( `Failed to create backup for ${ filePath } : ${ backup . error } ` ) ;
104109 writeFileSync ( absPath , next , "utf-8" ) ;
105- return c . json ( { ok : true , changed : true , content : next } ) ;
110+ return c . json ( {
111+ ok : true ,
112+ changed : true ,
113+ content : next ,
114+ path : filePath ,
115+ backupPath : backupPathForResponse ( projectDir , backup . backupPath ) ,
116+ } ) ;
106117}
107118
108119/**
@@ -815,9 +826,15 @@ export function registerFileRoutes(api: Hono, adapter: StudioApiAdapter): void {
815826
816827 ensureDir ( res . absPath ) ;
817828 const body = await c . req . text ( ) ;
829+ const backup = snapshotBeforeWrite ( res . project . dir , res . absPath ) ;
830+ if ( backup . error ) console . warn ( `Failed to create backup for ${ res . filePath } : ${ backup . error } ` ) ;
818831 writeFileSync ( res . absPath , body , "utf-8" ) ;
819832
820- return c . json ( { ok : true } ) ;
833+ return c . json ( {
834+ ok : true ,
835+ path : res . filePath ,
836+ backupPath : backupPathForResponse ( res . project . dir , backup . backupPath ) ,
837+ } ) ;
821838 } ) ;
822839
823840 // ── Create (fail if exists) ──
@@ -867,6 +884,8 @@ export function registerFileRoutes(api: Hono, adapter: StudioApiAdapter): void {
867884 const originalContent = readFileSync ( ctx . absPath , "utf-8" ) ;
868885 return writeIfChanged (
869886 c ,
887+ ctx . project . dir ,
888+ ctx . filePath ,
870889 ctx . absPath ,
871890 originalContent ,
872891 removeElementFromHtml ( originalContent , parsed . target ) ,
@@ -900,10 +919,19 @@ export function registerFileRoutes(api: Hono, adapter: StudioApiAdapter): void {
900919 parsed . body . newId ,
901920 ) ;
902921 if ( ! result . matched ) {
903- return c . json ( { ok : false , changed : false , content : originalContent } ) ;
922+ return c . json ( { ok : false , changed : false , content : originalContent , path : ctx . filePath } ) ;
904923 }
924+ const backup = snapshotBeforeWrite ( ctx . project . dir , ctx . absPath ) ;
925+ if ( backup . error ) console . warn ( `Failed to create backup for ${ ctx . filePath } : ${ backup . error } ` ) ;
905926 writeFileSync ( ctx . absPath , result . html , "utf-8" ) ;
906- return c . json ( { ok : true , changed : true , content : result . html , newId : result . newId } ) ;
927+ return c . json ( {
928+ ok : true ,
929+ changed : true ,
930+ content : result . html ,
931+ newId : result . newId ,
932+ path : ctx . filePath ,
933+ backupPath : backupPathForResponse ( ctx . project . dir , backup . backupPath ) ,
934+ } ) ;
907935 } ) ;
908936
909937 api . post ( "/projects/:id/file-mutations/patch-element/*" , async ( c ) => {
@@ -931,10 +959,25 @@ export function registerFileRoutes(api: Hono, adapter: StudioApiAdapter): void {
931959 parsed . body . operations ,
932960 ) ;
933961 if ( patched === originalContent ) {
934- return c . json ( { ok : true , changed : false , matched, content : originalContent } ) ;
962+ return c . json ( {
963+ ok : true ,
964+ changed : false ,
965+ matched,
966+ content : originalContent ,
967+ path : ctx . filePath ,
968+ } ) ;
935969 }
970+ const backup = snapshotBeforeWrite ( ctx . project . dir , ctx . absPath ) ;
971+ if ( backup . error ) console . warn ( `Failed to create backup for ${ ctx . filePath } : ${ backup . error } ` ) ;
936972 writeFileSync ( ctx . absPath , patched , "utf-8" ) ;
937- return c . json ( { ok : true , changed : true , matched, content : patched } ) ;
973+ return c . json ( {
974+ ok : true ,
975+ changed : true ,
976+ matched,
977+ content : patched ,
978+ path : ctx . filePath ,
979+ backupPath : backupPathForResponse ( ctx . project . dir , backup . backupPath ) ,
980+ } ) ;
938981 } ) ;
939982
940983 api . post ( "/projects/:id/file-mutations/probe-element/*" , async ( c ) => {
@@ -1113,7 +1156,12 @@ export function registerFileRoutes(api: Hono, adapter: StudioApiAdapter): void {
11131156 const newScript = typeof result === "string" ? result : result . script ;
11141157 const changed = newScript !== block . scriptText ;
11151158 const newHtml = changed ? block . replaceScript ( newScript ) : html ;
1159+ let backupPath : string | null = null ;
11161160 if ( changed ) {
1161+ const backup = snapshotBeforeWrite ( res . project . dir , res . absPath ) ;
1162+ if ( backup . error )
1163+ console . warn ( `Failed to create backup for ${ res . filePath } : ${ backup . error } ` ) ;
1164+ backupPath = backupPathForResponse ( res . project . dir , backup . backupPath ) ;
11171165 writeFileSync ( res . absPath , newHtml , "utf-8" ) ;
11181166 }
11191167
@@ -1126,6 +1174,8 @@ export function registerFileRoutes(api: Hono, adapter: StudioApiAdapter): void {
11261174 before : html ,
11271175 after : newHtml ,
11281176 scriptText : newScript ,
1177+ path : res . filePath ,
1178+ backupPath,
11291179 } ;
11301180 if ( typeof result !== "string" && result . skippedSelectors . length > 0 ) {
11311181 responsePayload . skippedSelectors = result . skippedSelectors ;
0 commit comments