diff --git a/B05_Profile/B05_Profile_UI_Page_Structures.ts b/B05_Profile/B05_Profile_UI_Page_Structures.ts index 61cde2be..efb7662f 100644 --- a/B05_Profile/B05_Profile_UI_Page_Structures.ts +++ b/B05_Profile/B05_Profile_UI_Page_Structures.ts @@ -248,7 +248,16 @@ export function createStructuresBridge(deps: StructuresBridgeDeps) { async function persistStructures(next: StructureInstance[]): Promise { if (deps.isRestoring()) return; try { - const saved = await saveStructures(deps.projectId, structureRevision, next); + // 판번호는 **쓰기 직전에** 서버에서 다시 받는다. 진입 때 받은 값으로 보내면, + // 화면이 떠 있는 동안 정본이 한 번이라도 바뀌었을 때(다른 창 저장·노선 재계산) + // 409 로 거절되고 아래 실패 처리가 초안을 지워 **사용자가 넣은 구조물이 통째로 + // 사라졌다**(2026-09-06 실측: B06에서 구조물을 넣고 [저장]해도 정본에 안 남음). + const current = await fetchStructures(deps.projectId).catch(() => null); + const saved = await saveStructures( + deps.projectId, + current ? current.revision : structureRevision, + next, + ); structureRevision = saved.revision; writePending(null); // 서버가 새 항목에 식별자를 붙이므로 그 결과로 화면 목록을 맞춘다. @@ -263,14 +272,15 @@ export function createStructuresBridge(deps: StructuresBridgeDeps) { ); } } catch (error) { + // 실패해도 **초안은 지우지 않는다** — 지우면 사용자가 넣은 구조물이 사라진다 + // (2026-09-06 정정). 화면 목록도 그대로 두고 다시 [저장]하면 된다. if (error instanceof StructureConflictError) { - await refreshStructuresFromServer(); - showToast("다른 창에서 구조물이 먼저 저장되어 최신 내용으로 되돌렸습니다.", "error"); + showToast( + "다른 창에서 구조물이 먼저 저장돼 이번 저장을 건너뛰었습니다. 다시 [저장]해 주세요.", + "error", + ); return; } - // 저장이 거절되면 화면에만 남은 항목은 식별자가 없어 고치지도 지우지도 못한다. - // 서버 정본으로 되돌려 화면과 정본을 다시 일치시킨다(2026-08-16 크로스체크 지적 1). - await refreshStructuresFromServer(); showToast(error instanceof Error ? error.message : "구조물 저장에 실패했습니다.", "error"); } }