fix(B05): 초기화 후 계획선이 원지반과 어긋나던 문제

브라우저 세션에 남은 계획선 편집 초안이 초기값 위에 다시 얹혀,
초기화 직후에는 멀쩡하다가 새로고침 한 번에 계획선이 측점에서 떨어짐
(실측: 측점 66개 중 25개가 10cm 초과, 최대 6.0m).

- [초기화]가 `b05-profile-alignment-draft:*` 세션 초안을 전부 제거
- 노선번호 없는 초안 키(`:none`) 폐지 — 어느 노선에나 되붙어 초기화로도
  떨어지지 않던 구멍

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 17:02:07 +09:00
co-authored by Claude Opus 5
parent 738e09e7b1
commit 92441eb8c9
2 changed files with 32 additions and 4 deletions
@@ -32,6 +32,7 @@ import {
import { clearStandardCrossSession } from "../B06_Section/B06_Section_UI_Standard_Panel";
import { saveCorridorIfDirty } from "./B05_Profile_UI_Corridor";
import { circlePoint, routePoint } from "./B05_Profile_UI_Page_Helpers";
import { clearAlignmentDrafts } from "./B05_Profile_UI_Profile_Edit";
import type { createRoutePanel } from "./B05_Profile_UI_Panel";
import type { createRouteProfilePanel } from "./B05_Profile_UI_Profile_Panel";
import type { createStructuresBridge } from "./B05_Profile_UI_Page_Structures";
@@ -206,6 +207,9 @@ export async function resetDesignAction(ctx: PageActionContext): Promise<void> {
ctx.uphillOverrides.clear();
ctx.persistUphillOverrides();
clearStandardCrossSession(ctx.projectId);
// 계획선 편집 초안도 함께 버린다 — 남기면 초기값 위에 옛 편집이 다시 얹혀 계획선이
// 측점에서 원지반선과 만나지 않는다(2026-09-04 실측: 새로고침 후 최대 6.0m 어긋남).
clearAlignmentDrafts();
showToast(L("B05_Route_Reset_Success"), "success");
navigateTo(ROUTES.B05_PROFILE);
} catch (error) {
+28 -4
View File
@@ -86,7 +86,27 @@ export interface ProfileEditStore {
* 쓰면 초안 편집분이 B06에서 되돌아간 것처럼 보인다.
*/
export function readAlignmentDraft(routeId: number | null): AlignmentEdits | null {
return readDraft(`${DRAFT_KEY_PREFIX}:${routeId ?? "none"}`);
return routeId === null ? null : readDraft(`${DRAFT_KEY_PREFIX}:${routeId}`);
}
/**
* 계획선 편집 초안을 모두 버린다 — [초기화]가 부른다.
*
* 초기화는 서버 값을 초기 스냅샷으로 되돌리는데, 이 초안이 남아 있으면 화면이 그 위에
* 옛 편집 델타를 다시 얹어 계획선이 측점에서 원지반선과 만나지 않는다(2026-09-04 실측:
* 초기화 직후에는 멀쩡하다가 새로고침 한 번에 최대 6.0m 어긋남).
*/
export function clearAlignmentDrafts(): void {
try {
const keys: string[] = [];
for (let index = 0; index < sessionStorage.length; index += 1) {
const key = sessionStorage.key(index);
if (key?.startsWith(`${DRAFT_KEY_PREFIX}:`)) keys.push(key);
}
keys.forEach((key) => sessionStorage.removeItem(key));
} catch {
// 세션 저장소를 못 쓰는 환경이면 남길 초안도 없다.
}
}
function readDraft(storageKey: string): AlignmentEdits | null {
@@ -109,14 +129,17 @@ function readDraft(storageKey: string): AlignmentEdits | null {
* 초기값은 **서버에 저장된 편집분**이고, 세션 초안이 남아 있으면(= 확정 없이
* 새로고침한 경우) 초안을 우선 채택하고 미저장 상태로 표시한다. routeId가 바뀌면
* 초안 키도 바뀌어 이전 노선의 편집이 새 노선에 잘못 얹히지 않는다.
*
* routeId가 아직 없으면 **초안을 두지 않는다**(2026-09-04). 노선 없는 키(`:none`)에
* 쌓인 초안은 어느 노선에나 되붙어 초기화로도 떨어지지 않았다.
*/
export function createProfileEditStore(
routeId: number | null,
saved: AlignmentEdits,
onChange: () => void,
): ProfileEditStore {
const storageKey = `${DRAFT_KEY_PREFIX}:${routeId ?? "none"}`;
const draft = readDraft(storageKey);
const storageKey = routeId === null ? null : `${DRAFT_KEY_PREFIX}:${routeId}`;
const draft = storageKey ? readDraft(storageKey) : null;
let current = draft ?? saved;
let unsaved = draft !== null;
/**
@@ -127,6 +150,7 @@ export function createProfileEditStore(
let savedBaseline = saved;
function dropDraft(): void {
if (!storageKey) return;
try {
sessionStorage.removeItem(storageKey);
} catch {
@@ -138,7 +162,7 @@ export function createProfileEditStore(
current = next;
unsaved = true;
try {
sessionStorage.setItem(storageKey, JSON.stringify(current));
if (storageKey) sessionStorage.setItem(storageKey, JSON.stringify(current));
} catch {
// 초안 보관 실패는 편집을 막지 않는다.
}