[fix/MAT-524] autosave 데이터 유실 방지#308
[fix/MAT-524] autosave 데이터 유실 방지#308b0nsu wants to merge 0 commit intofix/mat-349-handwriting-api-cache-strategyfrom
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
2ddfc82 to
9affde5
Compare
| onSuccess: () => { | ||
| console.log('[HW] saved scrapId:', saveScrapId); | ||
| lastSavedDataRef.current = base64Data; | ||
| onSaveSuccess?.(); |
There was a problem hiding this comment.
save 요청 보내고 mutation이 pending일 때 필기가 더 수정되면,
여기 콜백은 요청 보낼 때 시점의 base64Data만 lastSavedDataRef에 넣어서,
canvas 위의 수정된 stroke/text가 남아있어도 onSaveSuccess에서 hasUnsavedChanges를 false로 만드는데,
이러면 auto save 조건이 꺼지니까 다음 수정 전까지 저장이 안 될 수도 있음.
성공 시점에서 현재 캔버스를 다시 인코딩해서 요청 시점의 base64Data랑 같을 때만 saved하고 다르면 unsaved 유지나 예약이나 뭐 그런 로직을 추가로 넣어야할듯
| }, 50); // 50ms 지연으로 clear() 완료 보장 | ||
|
|
||
| return () => clearTimeout(loadTimer); | ||
| if (isLoading || initialLoadDoneRef.current) return; |
There was a problem hiding this comment.
initialLoadDoneRef가 true가 되면 handwritigngData가 바뀌더라도 바로 return 됨.
staleTime을 Intinity로 뒀으면 mutation/cache 갱신을 해줘야 화면이 동기화 되는데,
이렇게 하면 같은 scrapId에 대해서 invalidate, refetch 같은거 걸어서 GET 캐시 갱신해줘도 canvas 갱신이 안될수도 있음.
로드 중 autosave 차단이랑 서버 데이터 적용 여부를 분리하든가, updatedAt, data 변경시에 unsaved 상태 고려해서 재적용이나 무시하든가 해야할듯.
| initialLoadDoneRef.current = true; | ||
| onDataLoadedRef.current?.(); |
There was a problem hiding this comment.
decodeHandwritingData가 실패해도 여기서 initialLoadDoneRef를 true로 만들고, onDataLoaded가 호출됨.
이러면 빈 화면이 덮어써질수도 있음.
decode 실패해도 로드완료랑 저장 가능 상태로 두지말고 에러 상태로 넘기거나 아니면 상태를 안전하게 유지하는 로직이 필요해보임
| if (handwriting.isSaving) { | ||
| // 저장이 완료될 때까지 대기 | ||
| const checkSaveComplete = setInterval(() => { | ||
| if (!handwriting.isSaving) { |
There was a problem hiding this comment.
이거 stale closure일수도? 별도 effect로 분리하든 issaving을 ref로 동기화하든
| if (apply()) return; | ||
|
|
||
| const timer = setInterval(() => { | ||
| if (apply()) clearInterval(timer); | ||
| }, 50); | ||
|
|
||
| return () => clearInterval(timer); |
There was a problem hiding this comment.
이거 canvasref 안 잡히면 계속 돌텐데 max 횟수를 정하든 raf같은걸로 돌리든... 50ms면 초당 20번인데 애매해보임.
| const route = useRoute<ScrapDetailRouteProp>(); | ||
| const navigation = useNavigation<NativeStackNavigationProp<StudentRootStackParamList>>(); | ||
| const { id } = route.params; | ||
| const scrapId = Number(id); |
| if (base64Data === lastSavedDataRef.current) { | ||
| if (!isAutoSave) { | ||
| Alert.alert('알림', '변경사항이 없습니다.'); | ||
| } | ||
| return Promise.resolve(true); | ||
| } |
There was a problem hiding this comment.
하나 쓰고 undo하면 encoding한게 lastSavedDataRef랑 같을텐데
reducer에서 undo할 때 false로 안바꿔주니까 hasunsavedchanges가 true겠지
그리고 5초 autosave 돌렸으니까 가만히 냅두면 5초마다 계속 여기 돌텐데
onsavesuccess를 호출해주든 drawingstate에 콜백을 하나 더 추가를 하든.. 해봐
| const texts = canvasRef.current.getTexts(); | ||
|
|
||
| try { | ||
| const base64Data = encodeHandwritingData(strokes || [], texts || []); |
There was a problem hiding this comment.
5초마다 매번 메인 스레드에서 serializing을 돌린다? 부하가 너무 심하지 않을까?
근데 근본적으로 base64로 데이터를 저장하는 이유가 뭐야?
그냥 json object인데 그걸 왜 base64로 바꿔?
불필요한 wrapping 아닌가?
| [scrapId, canvasRef, updateHandwriting, onSaveSuccess, onSaveError, isSaving] | ||
| ); | ||
|
|
||
| // 5초마다 자동 저장 |
There was a problem hiding this comment.
근데 왜 debounce 안 걸고 쌩 5초마다 자동저장 하는거임? 웬만하면 debounce를 거는게 나을거같은데 더 필요하면 stroke 개수나 시간 limit도 추가로 걸고
There was a problem hiding this comment.
scrapId에 묶여있는 코드가 너무 많음..
- (unmount-mount)
- useHandwritingManager의 useEffect([scrapId]) (ref 리셋)
- ScrapDetailScreen의 useEffect([scrapId]) + setInterval(100) polling으로 isSaving 대기 후 reset
- 데이터 로드 effect의 setInterval(50) polling으로 canvasRef 대기
대충 찾아봐도 이정돈데 이러니까 timing이 자꾸 꼬여서 니가 apply에 polling 집어넣은거처럼 자꾸 덕지덕지 붙잖아...
scrapId 전환을 useHandwritingManager 내부 single responsibility로 묶고, screen에서는 뭐 scrapid의 hook instance만 쓰도록 하든가.. issaving 중에 전환은 큐에 넣고 ui 전환하도록 하든가.. 해봐..
9affde5 to
82a9d5c
Compare
Summary
staleTime: Infinity(MAT-349 / PR #288) 도입으로 인한 필기 데이터 유실 regression 수정 — sterdsterd 리뷰 후 전면 재설계 v3까지 진행. 13개 라인 코멘트 중 11개 해결, 2개는 후속 이슈로 분리.Linear
Changes
신규
features/student/scrap/hooks/saveCoordinator.ts— module-level pendingSaves Map + refireInFlight Set +mutationCache.subscribe기반 cleanup. TanStack Query v5 per-call callback unmount drop bug 우회.features/student/scrap/hooks/__tests__/saveCoordinator.test.ts— 단위 테스트 11개 (enqueue / coalesce / refire 성공/실패 / scrapId 독립성).수정
useHandwritingManager.ts— 전면 재작성:loadStatus(nouseReducer),decodeError/canvasTimeoutError/appliedTickstateflushIfDirty(scrapId)(sync, no await)mutationCache.subscribe로 mutation success/error 감지 →currentEncode === response.data비교 후 markAsSaved (sterdsterd C1)hasUnsavedRefguard로 pending 중 사용자 stroke 보존useFocusEffectinvalidateScrapDetailScreen.tsx— 호출부 정리:await handleSave패턴 모두 제거 →flushIfDirty + navigationonSaveSuccess/onDataLoaded→onSavedMatch/onSaveErrorputUpdateHandwriting.ts—handwritingSaveMutationFnexport +mutationKey: ['handwriting-save']useGetHandwriting.ts—gcTime: 1h추가 (탭 전환 후 30분 내 재진입 시 cache hit 보장)sterdsterd 코멘트 매핑
[]Testing
pnpm typecheck통과pnpm lint변경 파일 0 errorspnpm test --testPathPatterns=saveCoordinator11/11 통과Risk / Impact