From 4d66abb7fa3a3fa7964f88913d4402a846321fc2 Mon Sep 17 00:00:00 2001 From: ksj09406 Date: Thu, 1 Oct 2026 13:19:05 +0900 Subject: [PATCH] =?UTF-8?q?=EB=81=8C=20=EB=95=8C=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EC=9D=80=20=ED=8E=B8=EC=A7=91?= =?UTF-8?q?=EC=9D=84=20=EB=AC=BB=EB=8A=94=EB=8B=A4=20(=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=9A=94=EC=B2=AD=20=C2=B7=20=EA=B2=B0=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 편집이 있어도 하단 [종료] 를 누르면 묻지 않고 꺼졌다(편집은 보관본이 다음에 되살렸다). 사용자 판단 "잘못 고치고 있었으면 버릴 수 있어야 한다" 에 따라 끌 때 [저장하고 종료] · [저장하지 않고 종료] · [취소] 를 묻는다. 범위(사용자 결정): 파일 비교의 모든 탭 - 지금 화면(두 파일 · Git 모드 · 네 칸), 뒤에 맡겨 둔 탭, 닫았지만 편집이 남은 짝. 메모는 제외. 창에 파일 이름을 댄다. 저장은 기존 길(SaveWorkingCopy · WritePlainFile)로, 하나라도 못 쓰면 끄지 않는다. 버리면 걸려 있던 보관 쓰기를 기다린 뒤 보관본을 비워 다음에 되살아 나지 않게 한다. 플랫폼의 ICloseGuard 를 HomeViewModel 이 구현(플랫폼 수정 없음). 처음엔 곧바로 답해 셸의 Close() 가 Closing 안에서 불려 창이 안 꺼졌다(로그: 창을 닫는 중에는 Close 를 호출할 수 없습니다) - await Task.Yield() 로 비켜선 뒤 묻는다. 시험: 빌드 경고 0 · 오류 0. 화면 g19-exit-confirm 다섯 경우 실패 0 - 고친 것 없으면 묻지 않음, [취소] 면 그대로, [저장하지 않고 종료] 면 파일 그대로 · 보관본 빔 · 다시 켜도 안 살아남, 고친 직후 [저장하고 종료] 면 파일에 씀, 뒤 탭에만 편집이 있어도 묻고 그 파일만 씀. Git 모드는 화면 시험 안 함. 기존 회귀는 사용자 지시로 돌리지 않았다. Agent: main (claude-opus-5-5) Co-Authored-By: Claude Opus 5.5 --- doc/history/2026-09-23-12차.md | 17 ++ doc/history/changelog.md | 1 + doc/진행상황.md | 1 + .../ViewModels/HomeViewModel.Close.cs | 179 ++++++++++++++++++ src/MergeHub/ViewModels/HomeViewModel.Tabs.cs | 5 +- 5 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 src/MergeHub/ViewModels/HomeViewModel.Close.cs diff --git a/doc/history/2026-09-23-12차.md b/doc/history/2026-09-23-12차.md index 3e091eb..b126531 100644 --- a/doc/history/2026-09-23-12차.md +++ b/doc/history/2026-09-23-12차.md @@ -2,6 +2,23 @@ 지시서: [../작업지시/12차.md](../작업지시/12차.md) +## 끌 때 저장하지 않은 편집을 묻는다 (2026-09-30 밤, 사용자 요청 · 결정 · 완료) + +사용자 보고: 편집이 있는데 하단 [종료] 를 누르면 그냥 꺼진다. 편집은 보관본(E-1)이 다음에 되살렸지만(g16 로 확인), 사용자 판단은 +"잘못 고치고 있었으면 어떻게 하나 - 버릴 수 있어야 한다" 였다. 결정(사용자): **파일 비교의 모든 탭**(Git 모드 포함), 메모는 제외, +[저장하고 종료] · [저장하지 않고 종료] · [취소], 버리면 **보관본도 비워** 다음에 되살아나지 않게. + +- 플랫폼에 이미 있는 `ICloseGuard` 를 `HomeViewModel` 이 구현(`HomeViewModel.Close.cs`) — 셸이 열린 탭 화면의 DataContext 에 묻는다. 플랫폼은 고치지 않음. +- 묻는 범위: 지금 화면(두 파일 · Git 모드 · 네 칸 — 스타일 맞춤만 바뀐 글은 빼는 `WillAskBeforeDiscard` 와 같은 기준) · 뒤에 맡겨 둔 탭 · 닫았지만 편집이 남은 짝(`(닫은 탭)` 이라고 적는다). 창에 파일 이름을 10개까지. +- 저장: 지금 화면은 기존 `SaveWorkingCopy`(Git 충돌 파일 · 왼쪽 칸까지), 맡겨 둔 글은 그 짝의 저장 형식으로 `WritePlainFile`(백업 포함). **하나라도 못 쓰면 끄지 않는다.** +- 보관본: 평소 쓰기는 다른 실(`Task.Run`)이라, 끌 때는 걸려 있던 쓰기(`_sessionWrite`)를 기다린 뒤 **그 자리에서** 비운다 — 늦게 도착한 옛 쓰기가 방금 비운 것을 다시 채우지 않게. 묻지 않고 끄는 경우(고친 것 없음)도 비워 맞춘다. +- **처음엔 창이 꺼지지 않았다** — 기록: `Shell: 닫기 확인 중 오류 … 창을 닫는 중에는 … Close 를 호출할 수 없습니다`. 셸은 답을 받은 뒤 `Close()` 를 다시 부르는데, + 곧바로 답하면 그게 아직 Closing 안이다. `await Task.Yield()` 로 한 차례 비켜선 뒤 묻게 해 풀었다(플랫폼은 그대로 — 동기로 답하는 가드가 있으면 같은 일이 난다는 점만 적어 둔다). + +시험 `g19-exit-confirm`(새, 사용자 지시로 이것만): ① 고친 것 없음 → 묻지 않고 꺼짐 ② 고친 뒤 종료 → 창(파일 이름 · 단추 셋) → [취소] 면 앱 · 편집 그대로 +③ [저장하지 않고 종료] → 꺼짐 · 파일 그대로 · 보관본 빔 · 다시 켜도 안 살아남 ④ 고친 직후(0.3초) [저장하고 종료] → 파일에 씀 · 보관본 빔 +⑤ 뒤 탭에만 편집(alpha, 화면은 bravo) → 창에 alpha.cs · [저장하고 종료] 로 alpha 만 씀. **실패 0.** 대본의 [종료] 누름은 다른 실에서 한다(창이 뜨면 그 누름이 돌아오지 않는다). +**Git 모드는 화면으로 시험하지 않았다** — 지금 화면의 저장은 기존 `SaveWorkingCopy` 를 그대로 쓴다. **기존 회귀는 사용자 지시로 돌리지 않았다.** ## 엔터를 꾹 누르면 줄이 어긋난다 · Ctrl+휠로 키우면 화면이 잘린다 · 내리면 BASE 와 LOCAL 이 따로 논다 (2026-09-30 밤, 사용자 보고 · 완료) ### 엔터 꾹 — 캐럿과 LOCAL 칸의 줄이 다르다 (사진) diff --git a/doc/history/changelog.md b/doc/history/changelog.md index c9ee4b9..d21680d 100644 --- a/doc/history/changelog.md +++ b/doc/history/changelog.md @@ -8,6 +8,7 @@ | 날짜 | 항목 | 커밋 | |---|---|---| +| 2026-09-30 | **끌 때 저장하지 않은 편집을 묻는다(사용자 요청 · 결정)** — 파일 비교의 모든 탭(지금 화면 · Git 모드 · 뒤 탭 · 닫은 탭, 메모 제외)에 편집이 있으면 [저장하고 종료] · [저장하지 않고 종료] · [취소]. 저장은 기존 길(`SaveWorkingCopy` · `WritePlainFile`), 하나라도 실패하면 끄지 않음. 버리면 걸려 있던 보관 쓰기를 기다린 뒤 보관본을 비워 다음에 안 살아남. 플랫폼 `ICloseGuard` 사용(수정 없음) — 곧바로 답하면 셸의 `Close()` 가 Closing 안에서 불려 창이 안 꺼져 `await Task.Yield()` 로 비켜섬. 화면 `g19-exit-confirm` 다섯 경우 실패 0(Git 모드는 화면 시험 안 함). **기존 회귀는 사용자 지시로 돌리지 않음.** 빌드 경고 0 · 오류 0 | 아래 | | 2026-09-30 | **엔터 꾹 · 확대 · 쭉 내리기에서 칸 어긋남(사용자 보고)** — ① 엔터를 꾹 누르면 캐럿 줄과 LOCAL 칸 줄이 벌어졌다 → 캐럿은 글에서 센 줄 자리로, 메아리 막기 중에도 LOCAL 칸은 편집기를 따라감. ② Ctrl+휠 확대 뒤 칸 위가 비고 내리면 BASE 가 다른 줄: 가상화 목록 높이 어림 차이(편집기 28,896 · 목록 27,940)를 네 칸 공통 이동값으로 메우다 467px 까지 커졌다 → 두 줄 넘으면 칸을 실제로 굴리고 칸별 보정으로 기억, BASE · REMOTE 는 같은 줄끼리 맞춤(한쪽에만 있는 묶음 안은 제외 — 짝을 억지로 불러 초당 600번 되풀이하던 것을 기록으로 잡음). ③ 편집기 `GetFirstVisibleLineIndex`(886 → 1033) · 확대 직후 `GetRectFromCharacterIndex`(1,892px 틀림)가 틀려 → 줄 높이 고정이면 계산. 화면 `g17-hold-enter` 통과(남은 ✗ 는 대본 한계 · 꾹 누름 흉내에서 Windows 가 반복 키를 합치는 것 — 고치기 전과 같음). **기존 회귀는 사용자 지시로 돌리지 않음.** 빌드 경고 0 · 오류 0 | 아래 | | 2026-09-30 | **머지 주석 · 타이핑 속도 · 탭 알림(사용자 요청 · 보고)** — ① Accept 뒤 캐럿이 새 주석 줄에 서서 Alt+↓ 를 두 번 눌러야 했다 → 주석을 끼우기 직전 캐럿 행을 기억해 글을 갈아 끼운 뒤 그 행으로(글을 막 갈아 끼운 TextBox 의 줄 정보가 옛 글 기준이라는 것을 기록으로 확인 → 줄 자리는 글에서 직접 셈). ② 줄을 지우는 반영에도 주석 — 한 줄 `삭제`, 여러 줄 `삭제 N줄`(사용자 선택, 왼쪽 · 3-Way · 구간 · 메서드 모두). ③ 색 구분 "줄 안에서 바뀐 부분" 에 줄 수. ④ 타이핑: 1,806줄에서 재 보니 엔터마다 재정렬 0.4초의 대부분이 목록에 1,800줄을 한 줄씩 넣는 알림(파일 비교 자체는 0.012초) → `MergeLineCollection.ReplaceAll`(알림 한 번) + `Patch`(번호 말고 같은 줄은 기존 객체를 두고 번호만, 가운데만 빼고 넣기) → 엔터 한 번 약 0.55초 → 약 0.05초(뒤 다시 칠한 줄 290 → 0~8). ⑤ 탭이 20개를 넘어 조용히 닫히면 알림. 하네스 `NoteTest` 106 · `PatchTest`(새) 16, 화면 `g12-note` · `g14-inline` · `g15-typing-tabs` 통과. **기존 회귀는 사용자 지시로 돌리지 않음.** 빌드 경고 0 · 오류 0 | 아래 | | 2026-09-30 | **커밋 뒤 사용자 요청 셋** — ① 폴더 비교에서 두 번 눌러 파일 비교로 가도 하단 탭이 옛 탭에 남던 것: 사용자가 쓰던 앱을 읽기만 해서 재현(화면 파일 비교 · 선택 0번), 원인은 공용 플랫폼 셸의 `NavList` 가 `SelectedMainTab` 을 따라가지 않는 것 — 허락받아 `UbisamCommonPlatform` 에서 고침(MergeHub 코드 변경 없음), `g10-drop` 선택 [0] → [1]. ② Git 탭에 폴더 · 파일을 끌어다 놓으면 그 저장소 최상위를 연다(`PathUtil.RepositoryFolderFor`, `[폴더...]` 와 같은 규칙, 탭 전체 Preview 로 받아 TextBox 가 먹는 문제 회피). `PathTest` 62 → 68건(먼저 실패 확인). 흉내 낸 OLE 끌기는 이미 되는 폴더 비교 칸에도 안 들어가 도구 한계로 보고 사용자가 직접 확인(로그 저장소 열기 두 번). ③ 파일 비교 편집 칸 바탕을 회색(페이지 바탕이 비침) → 밝음 #FAFAFA · 어두움 #252830(순검정 · #1E1E1E 를 거쳐 후보를 보고 사용자가 고름, `PaneBackgroundConverter`). `g11-pane` 두 테마 화소로 확인. 빌드 경고 0 · 오류 0 | 아래 | diff --git a/doc/진행상황.md b/doc/진행상황.md index dce5ce0..2485786 100644 --- a/doc/진행상황.md +++ b/doc/진행상황.md @@ -446,6 +446,7 @@ | 상태 | 항목 | |---|---| +| **완료** | **끌 때 저장하지 않은 편집을 묻는다**(사용자 요청 · 결정) — 파일 비교의 모든 탭(Git 모드 · 뒤 탭 · 닫은 탭 포함, 메모 제외)에 편집이 있으면 [저장하고 종료] · [저장하지 않고 종료] · [취소]. 버리면 보관본도 비워 다음에 안 살아남. 플랫폼 `ICloseGuard` 를 그대로 씀(플랫폼 수정 없음). 화면 `g19-exit-confirm` 실패 0. Git 모드는 화면 시험 안 함 · 기존 회귀는 사용자 지시로 안 돌림 | | **완료** | **엔터 꾹 · Ctrl+휠 확대 · 쭉 내리기에서 칸이 어긋난다**(사용자 보고) — 캐럿 줄 ≠ LOCAL 칸 줄, 확대하면 칸 위가 비고, 내리면 BASE 가 다른 줄을 봤다. 가상화 목록의 높이 어림 차이를 이동값으로 메우지 않고 칸을 실제로 굴려 칸별로 기억, BASE 는 같은 줄끼리 맞춤, 편집기 보이는 줄은 줄 높이로 계산(편집기의 `GetFirstVisibleLineIndex` · 확대 직후 `GetRectFromCharacterIndex` 가 틀렸다). 화면 `g17-hold-enter` — 엔터 뒤 936=936, 확대 · 30칸 내린 뒤 LOCAL = BASE. 기존 회귀는 사용자 지시로 안 돌림 | | **완료** | **파일 비교 타이핑 · 엔터 속도**(사용자 보고) — 재서 보니 엔터마다 도는 재정렬 0.4초의 대부분이 1,800줄을 목록에 한 줄씩 넣는 알림이었다. 한 번에 넣기(`ReplaceAll`) + 번호 말고 같은 줄은 기존 객체를 두고 가운데만 바꾸기(`Patch`)로 엔터 한 번 약 0.55초 → 약 0.05초. 하네스 `PatchTest` 16건, 화면 `g15` ① 새 줄 · 번호 제자리. 기존 회귀는 사용자 지시로 안 돌림 | | **완료** | **머지 주석 두 가지**(사용자 보고) — Accept 뒤 캐럿이 새 주석 줄에 서서 Alt+↓ 를 두 번 눌러야 했던 것(캐럿을 누르기 전 행에 둔다), 줄을 지우는 반영에도 주석 + `삭제` · 여러 줄이면 `삭제 N줄`(사용자 선택). `NoteTest` 106건, 화면 `g12-note` 실패 0 | diff --git a/src/MergeHub/ViewModels/HomeViewModel.Close.cs b/src/MergeHub/ViewModels/HomeViewModel.Close.cs new file mode 100644 index 0000000..2e55a60 --- /dev/null +++ b/src/MergeHub/ViewModels/HomeViewModel.Close.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MergeHub.Services; +using UbisamBase.Core.Messaging; +using UbisamBase.Core.Modules; + +namespace MergeHub.ViewModels +{ + /// + /// 프로그램을 끌 때 저장하지 않은 편집을 묻는다(2026-09-30 사용자 요청). + /// + /// 예전에는 묻지 않고 꺼졌다 - 편집은 보관본(12차 E-1)이 다음에 켤 때 되살렸다. 그런데 사용자 판단은 + /// "잘못 고치고 있었으면 어떻게 하나 - 버릴 수 있어야 한다" 였다. 그래서 끌 때 한 번 묻는다: + /// [저장하고 종료] · [저장하지 않고 종료] · [취소]. 버리면 보관본도 비워 다음에 되살아나지 않게 한다. + /// + /// + /// 셸 창의 Closing 에 직접 붙지 않고 플랫폼의 를 쓴다 - 셸이 열린 탭 화면의 + /// DataContext 에 물어 준다. 모듈이 저마다 Closing 에 붙으면 서로 Cancel/Close 를 걸어 부딪힌다. + /// + /// + /// + /// 묻는 범위는 파일 비교의 모든 탭(사용자 결정): 지금 화면(두 파일 · Git 모드 · 네 칸), + /// 뒤에 맡겨 둔 탭, 그리고 닫았지만 편집이 남은 짝(보관함). 메모는 넣지 않는다(사용자 결정 - 메모는 늘 스스로 저장한다). + /// + /// + public sealed partial class HomeViewModel : ICloseGuard + { + /// 저장하지 않은 편집 하나. 창에 이름을 대고, 저장할 때 그대로 쓴다. + private sealed class PendingEdit + { + public string Name = string.Empty; + + /// 지금 화면이면 null - 화면의 저장 길()로 쓴다. + public string? Left, Right; + public DocTabState? State; + } + + /// 창에 이름을 이만큼만 늘어놓는다. 넘치면 "외 N개". + private const int CloseListMax = 10; + + public async Task CanCloseAsync() + { + // 먼저 한 번 비켜선다. 셸은 창의 Closing 안에서 이것을 부르고, 답이 오면 Close() 를 다시 부른다. + // 여기서 곧바로 답하면 그 Close() 가 아직 Closing 안에서 불려 WPF 가 거절했다 + // (기록: "창을 닫는 중에는 … Close 를 호출할 수 없습니다" - 창이 꺼지지 않고 남았다). + // 한 차례 넘기면 Closing 이 끝난 뒤에 묻고 답한다. + await Task.Yield(); + + var pending = CollectPendingEdits(); + + if (pending.Count == 0) + { + // 물을 것이 없다. 보관본에 옛 편집이 남아 있으면 안 되므로 지금 상태로 한 번 맞춰 둔다 + // (보관은 2초 뒤에 쓰므로, 저장한 직후 바로 끄면 옛 글이 남아 있을 수 있다). + FlushSessionNow(clear: true); + return true; + } + + var names = pending.Take(CloseListMax).Select(p => " · " + p.Name).ToList(); + if (pending.Count > CloseListMax) names.Add($" · 외 {pending.Count - CloseListMax}개"); + + var choice = MessageUtil.ShowChoice( + "저장하지 않은 편집", + $"저장하지 않은 편집이 있는 파일이 {pending.Count}개 있습니다.\n\n{string.Join("\n", names)}\n\n" + + "저장하지 않고 종료하면 고친 내용이 사라지고, 다음에 켤 때도 되살아나지 않습니다.", + "저장하고 종료", "저장하지 않고 종료"); + + switch (choice) + { + case DialogChoice.Primary: + // 하나라도 못 쓰면 끄지 않는다 - 여기서 꺼지면 저장된 줄 알고 잃는다(ConfirmDiscard 와 같은 원칙). + if (!SavePendingEdits(pending)) return false; + FlushSessionNow(clear: true); + return true; + + case DialogChoice.Secondary: + FlushSessionNow(clear: true); + _workspace.AppendLog($"종료: 저장하지 않은 편집 {pending.Count}개를 버림"); + return true; + + default: + return false; + } + } + + /// + /// 저장하지 않은 편집을 모두 모은다. 지금 화면을 맨 앞에 둔다 - 사용자가 보고 있는 것이다. + /// + private List CollectPendingEdits() + { + var list = new List(); + + // 지금 화면. 스타일 맞춤으로만 바뀐 글은 세지 않는다(다시 열면 똑같이 만들어진다 - WillAskBeforeDiscard 와 같은 기준). + if ((HasChanges && !OnlyStyleChanged) || HasLeftChanges) + { + var editing = IsPlainMode ? RightFile : (_lastLoadedFile ?? SelectedFile); + list.Add(new PendingEdit + { + Name = string.IsNullOrWhiteSpace(editing) ? "지금 파일" + : IsPlainMode ? Path.GetFileName(editing) : editing! + }); + } + + // 뒤에 맡겨 둔 탭. 두 파일 비교 화면이면 떠 있는 탭은 화면이 주인이라 위에서 이미 셌다. + foreach (var tab in Tabs.Tabs) + { + if (TabsCanHoldEdits && ReferenceEquals(tab, Tabs.Current)) continue; + if (tab.Parked is not { HasEdits: true } state) continue; + // 탭 제목은 같은 이름 파일을 가르려고 폴더를 덧붙여 길어진다 - 창에는 지금 화면 · 닫은 탭과 같이 파일 이름만 댄다. + list.Add(new PendingEdit { Name = Path.GetFileName(tab.Right), Left = tab.Left, Right = tab.Right, State = state }); + } + + // 닫았지만 편집이 남은 짝. 탭 줄에 없어 사용자가 잊었을 수 있으니 그렇다고 적는다. + foreach (var kept in Tabs.AllStashed()) + { + if (!kept.State.HasEdits) continue; + list.Add(new PendingEdit + { + Name = Path.GetFileName(kept.Right) + " (닫은 탭)", + Left = kept.Left, + Right = kept.Right, + State = kept.State + }); + } + + return list; + } + + /// 모은 편집을 파일에 쓴다. 하나라도 못 쓰면 거기서 멈추고 false. + private bool SavePendingEdits(IEnumerable pending) + { + foreach (var edit in pending) + { + if (edit.State is null) + { + // 지금 화면 - Git 모드의 충돌 파일 · 왼쪽 칸까지 챙기는 기존 길을 그대로 쓴다. + if (!SaveWorkingCopy()) return false; + continue; + } + + try + { + // 맡겨 둔 글은 그 짝에 걸어 둔 저장 형식으로. 없으면(null) 원본 형식 그대로다. + if (edit.State.Text is not null && !string.IsNullOrWhiteSpace(edit.Right)) + WritePlainFile(edit.Right!, edit.State.Text, edit.State.SaveEncoding, edit.State.SaveNewLine); + if (edit.State.LeftText is not null && !string.IsNullOrWhiteSpace(edit.Left)) + WritePlainFile(edit.Left!, edit.State.LeftText, edit.State.SaveEncoding, edit.State.SaveNewLine); + + _workspace.AppendLog($"종료하며 저장: {edit.Name}"); + } + catch (Exception ex) + { + MessageUtil.ShowErrorToast($"{edit.Name} 을 저장하지 못했습니다: {ex.Message}"); + return false; + } + } + + return true; + } + + /// + /// 보관본을 지금 이 자리에서 맞춘다. 평소의 보관()은 다른 실로 넘기는데, + /// 끄는 순간에는 그 실이 끝나기 전에 프로세스가 내려갈 수 있고, 거꾸로 늦게 도착한 옛 보관이 + /// 방금 비운 보관본을 다시 채울 수도 있다 - 그래서 걸려 있던 쓰기를 먼저 기다린 뒤 비운다. + /// + private void FlushSessionNow(bool clear) + { + _sessionTimer.Stop(); + try { _sessionWrite?.Wait(TimeSpan.FromSeconds(3)); } + catch (AggregateException) { } // 보관 실패는 끄는 것을 막지 않는다 + + if (clear) Session.Clear(); + } + } +} diff --git a/src/MergeHub/ViewModels/HomeViewModel.Tabs.cs b/src/MergeHub/ViewModels/HomeViewModel.Tabs.cs index 4c385b2..9551757 100644 --- a/src/MergeHub/ViewModels/HomeViewModel.Tabs.cs +++ b/src/MergeHub/ViewModels/HomeViewModel.Tabs.cs @@ -369,6 +369,9 @@ namespace MergeHub.ViewModels private bool _sessionReady; + /// 마지막으로 넘긴 보관 쓰기. 끌 때 이것이 끝나기를 기다린 뒤 보관본을 비운다(HomeViewModel.Close). + private Task? _sessionWrite; + /// 되살리는 중. 그때 일어나는 변화로 다시 보관하지 않는다. private bool _restoringSession; @@ -465,7 +468,7 @@ namespace MergeHub.ViewModels } var store = Session; - Task.Run(() => store.Save(snapshot)); + _sessionWrite = Task.Run(() => store.Save(snapshot)); } ///