From cb8cf2c9d2bd487a4c799ae0a212dbaeae9f380a Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 29 Aug 2026 11:39:03 +0900 Subject: [PATCH] =?UTF-8?q?fix(B05,B06):=20=EA=B5=AC=EC=A1=B0=EB=AC=BC=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EC=8B=A4=ED=8C=A8=EA=B0=80=20=ED=9A=A1?= =?UTF-8?q?=EB=8B=A8=20=EC=A0=80=EC=9E=A5=EA=B9=8C=EC=A7=80=20=EB=A7=89?= =?UTF-8?q?=EB=8D=98=20=EA=B2=83=EC=9D=84=20=EA=B3=A0=EC=B9=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 화면 실측에서 나왔다. 세션에 남은 미저장 구조물이 서버에 거절되자(등록되지 않은 타입 400) 예외가 B06 [저장] 전체를 중단시켜 sections/save가 아예 나가지 않았다. flushPendingStructures를 .catch()로 감싸 토스트만 띄우고 횡단 저장은 계속한다. 미저장분은 세션에 그대로 남아 다음 [저장]에서 다시 시도된다. --- B05_Profile/B05_Profile_UI_Page.ts | 3 ++- B06_Section/B06_Section_UI_Page.ts | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/B05_Profile/B05_Profile_UI_Page.ts b/B05_Profile/B05_Profile_UI_Page.ts index 42ab91ad..72da2379 100644 --- a/B05_Profile/B05_Profile_UI_Page.ts +++ b/B05_Profile/B05_Profile_UI_Page.ts @@ -629,7 +629,8 @@ export async function renderB05Route(root: HTMLElement): Promise { try { // 종단 계획선 편집은 화면에서만 계산해 두었으므로 저장 시점에 영속화한다. await profilePanel.save(); - // 구조물도 조작분이 세션에만 있다(2026-08-29 — CLAUDE.md 5장). + // 구조물도 조작분이 세션에만 있다(2026-08-29 — CLAUDE.md 5장). 실패는 자체 토스트로 + // 알리고 세션에 남겨 두므로, 여기서 저장 전체를 멈추지 않는다. await bridge.saveStructuresIfDirty(); // 비정규 측점·상단측 변경분까지 데이터로는 확정 저장하되, 단계 완료 전이는 하지 않는다. await confirmRoute( diff --git a/B06_Section/B06_Section_UI_Page.ts b/B06_Section/B06_Section_UI_Page.ts index a1276877..526fb411 100644 --- a/B06_Section/B06_Section_UI_Page.ts +++ b/B06_Section/B06_Section_UI_Page.ts @@ -555,8 +555,13 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise { // 조정창 구간값은 세션에만 있다 — 정본 payload를 모으기 전에 내보낸다 // (CLAUDE.md 5장: 영구저장은 [저장]·[확정]에서만). await stationControls.flushCulvertOptions(); - // B05에서 만지고 넘어온 구조물 조작분도 여기서 정본에 남긴다. - await flushPendingStructures(projectId); + // B05에서 만지고 넘어온 구조물 조작분도 여기서 정본에 남긴다. 실패해도 횡단 + // 저장까지 막지는 않는다 — 미저장분은 세션에 남으므로 다시 시도할 수 있다 + // (2026-08-29 실측: 타입이 거절되자 sections/save가 아예 나가지 않았다). + await flushPendingStructures(projectId).catch((error) => { + const detail = error instanceof Error ? ` ${error.message}` : ""; + showToast(`구조물 저장에 실패했습니다.${detail}`, "error"); + }); const edits = collectSectionEdits(); await saveSections( projectId, @@ -581,8 +586,13 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise { // 조정창 구간값은 세션에만 있다 — 정본 payload를 모으기 전에 내보낸다 // (CLAUDE.md 5장: 영구저장은 [저장]·[확정]에서만). await stationControls.flushCulvertOptions(); - // B05에서 만지고 넘어온 구조물 조작분도 여기서 정본에 남긴다. - await flushPendingStructures(projectId); + // B05에서 만지고 넘어온 구조물 조작분도 여기서 정본에 남긴다. 실패해도 횡단 + // 저장까지 막지는 않는다 — 미저장분은 세션에 남으므로 다시 시도할 수 있다 + // (2026-08-29 실측: 타입이 거절되자 sections/save가 아예 나가지 않았다). + await flushPendingStructures(projectId).catch((error) => { + const detail = error instanceof Error ? ` ${error.message}` : ""; + showToast(`구조물 저장에 실패했습니다.${detail}`, "error"); + }); const edits = collectSectionEdits(); await confirmSections( projectId,