From be9341029975da6ea662961a940af5b96061f718 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Tue, 29 Sep 2026 19:45:22 +0900 Subject: [PATCH] =?UTF-8?q?feat(B05):=20=EB=85=B8=EC=84=A0=20=EC=B6=94?= =?UTF-8?q?=EC=B2=9C=20=E2=80=94=20=EB=90=98=EB=8F=8C=EB=A6=AC=EA=B8=B0=20?= =?UTF-8?q?=C2=B7=20=EB=8B=A4=EC=8B=9C=ED=95=98=EA=B8=B0=EA=B0=80=20?= =?UTF-8?q?=E3=80=8C=ED=98=84=EC=9E=AC=20=EC=B6=94=EC=B2=9C=E3=80=8D=20?= =?UTF-8?q?=EA=B8=80=EC=9D=84=20=ED=95=A8=EA=BB=98=20=EB=90=98=EC=82=B4?= =?UTF-8?q?=EB=A6=BC(=EA=B1=B8=EC=9D=8C=EB=A7=88=EB=8B=A4=20=EC=B6=94?= =?UTF-8?q?=EC=B2=9C=EC=9D=84=20=EC=8B=A4=EC=9D=8C)=20(PLAN=2023-2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_019BVZd1M6WUf1YEy9gbAE1G --- B05_Profile/B05_Profile_UI_RouteEdit.ts | 18 +++++++++++++++--- .../B05_Profile_UI_RouteEdit_History.ts | 3 +++ .../B05_Profile_UI_RouteEdit_Initial.ts | 10 ++++++++++ .../tester/helper_b05_routeedit_initial.cjs | 18 ++++++++++++++++++ resources/tester/test_b05_routeedit_initial.py | 10 ++++++++++ 5 files changed, 56 insertions(+), 3 deletions(-) diff --git a/B05_Profile/B05_Profile_UI_RouteEdit.ts b/B05_Profile/B05_Profile_UI_RouteEdit.ts index 90c2c7ceb..6e77cbd6d 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit.ts @@ -368,7 +368,16 @@ export async function openRouteEditModal( /** 지금 편집값을 사진 한 벌로 담는다 — 되돌리기가 쌓아 두는 것. */ function snapshotNow(): RouteEditSnapshot { - return { planned, curveOn, curveRadius, curveLock, curveArc, apexLock, picked }; + return { + planned, + curveOn, + curveRadius, + curveLock, + curveArc, + apexLock, + picked, + rec: initial.tag(), + }; } /** 사진 한 벌을 편집값으로 되살린다. 되돌리기는 새 걸음이 아니고, 초기값 싣기는 새 걸음이다. */ @@ -380,7 +389,10 @@ export async function openRouteEditModal( const historyControls = bindHistoryControls({ overlay, getHistory: () => history, - restore: (snapshot, message) => restoreSnapshot(snapshot, message, false), + restore: (snapshot, message) => { + initial.restoreTag(snapshot.rec); // 「현재 추천」도 그 걸음 것으로 — 노선과 짝. + restoreSnapshot(snapshot, message, false); + }, }); // ── 조작 — 노드 끌기 · 집기는 `_Pointer` 몫 / 두 번 클릭 삽입 / 오른쪽 클릭 삭제 ── @@ -494,9 +506,9 @@ export async function openRouteEditModal( ({ nodes: nodeInfo, curves: curveInfo } = flat); picked = -1; // 여기가 [연 상태로]가 돌아갈 자리다 — 창을 연 그대로. + initial.ready(snapshotNow(), plan.edited, plan.current_initial ?? null); history = createRouteEditHistory(snapshotNow()); historyControls.sync(); - initial.ready(snapshotNow(), plan.edited, plan.current_initial ?? null); syncCurveBar(); if (!planned.length) planned = plannedLine.map((vertex) => [vertex[0], vertex[1]]); meta = drainage.meta; diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_History.ts b/B05_Profile/B05_Profile_UI_RouteEdit_History.ts index 6dfcfeb7c..fda74f078 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_History.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_History.ts @@ -24,6 +24,8 @@ export interface RouteEditSnapshot { /** 교각점(꺾임점) 자리를 못 박았나 — 켜진 자리는 끌어도 안 움직인다(2026-09-12 지시 ①). */ apexLock: boolean[]; picked: number; + /** 이 걸음의 「현재 추천」 — `_Initial` 이 적고 되돌릴 때 되살린다(뜻은 그쪽만 안다). 노선과 짝. */ + rec?: unknown; } /** 쌓아 둘 사진 수 상한 — 넘으면 오래된 것부터 버린다. */ @@ -54,6 +56,7 @@ function clone(snapshot: RouteEditSnapshot): RouteEditSnapshot { curveArc: [...snapshot.curveArc], apexLock: [...snapshot.apexLock], picked: snapshot.picked, + rec: snapshot.rec, }; } diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Initial.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Initial.ts index 2545db0e7..eaa7f8e47 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_Initial.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_Initial.ts @@ -57,6 +57,9 @@ export interface InitialPanel { weights: RouteInitialWeights | null; criteria: RouteInitialCriteria | null; }; + /** 지금 추천 — 편집 걸음(사진)에 실어 둔다. 되돌리기 · 다시하기가 `restoreTag` 로 되살린다. */ + tag: () => InitialChoice | null; + restoreTag: (tag: unknown) => void; /** [취소] — 이 창에서 바꾼 추천을 창을 열 때 것으로 되돌린다. */ cancel: () => void; /** [확인] 뒤 결과 캐시를 비웠다 — 추천은 새 노선의 출발점이라 다시 적는다. */ @@ -199,6 +202,13 @@ export function createInitialPanel(params: InitialPanelParams): InitialPanel { const criteria = choice ? choice.criteriaOverrides : base.criteria; return { mode: now.logic, weights: now.weights, criteria }; }, + tag: () => choice, + restoreTag(tag) { + if (tag === undefined) return; // 추천을 안 실은 옛 걸음 — 그대로 둔다. + choice = tag as InitialChoice | null; + writeInitialChoice(projectId, choice); + render(); + }, cancel: () => writeInitialChoice(projectId, openingChoice), keep: () => writeInitialChoice(projectId, choice), }; diff --git a/resources/tester/helper_b05_routeedit_initial.cjs b/resources/tester/helper_b05_routeedit_initial.cjs index cb6ee6e38..f74dc95d9 100644 --- a/resources/tester/helper_b05_routeedit_initial.cjs +++ b/resources/tester/helper_b05_routeedit_initial.cjs @@ -238,6 +238,23 @@ const mapLines = { oneDot: model.mapPaths([{ kind: "preview", points: [[5, 5]] }], 460, 340), }; +// ⑦ 되돌리기 · 다시하기가 걸음마다 실은 「현재 추천」을 되살린다(노선과 짝). +const history = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_History.ts")); +const step = (x, rec) => ({ + planned: [[x, 0], [x + 1, 1]], + curveOn: [true, true], + curveRadius: [null, null], + curveLock: [null, null], + curveArc: [null, null], + apexLock: [false, false], + picked: -1, + rec, +}); +const trail = history.createRouteEditHistory(step(0, { logic: "follow" })); +trail.commit(step(1, { logic: "earthwork" })); +trail.commit(step(2, null)); +const historyRecs = [trail.undo(), trail.undo(), trail.redo(), trail.redo()].map((one) => one.rec); + // ④ 캐시 왕복 — 결과 통 프로젝트 칸. 꼴이 틀린 값은 안 읽는다. model.writeInitialChoice("p-1", plain); const readBack = model.readInitialChoice("p-1"); @@ -278,5 +295,6 @@ process.stdout.write( sameAsServer, initialRoute, mapLines, + historyRecs, }), ); diff --git a/resources/tester/test_b05_routeedit_initial.py b/resources/tester/test_b05_routeedit_initial.py index 5b79e2e4d..43a2adbdd 100644 --- a/resources/tester/test_b05_routeedit_initial.py +++ b/resources/tester/test_b05_routeedit_initial.py @@ -182,3 +182,13 @@ def test_도구_막대는_노선_추천_하나만(): ).read() for name in ("follow", "earthwork", "gentle", "weighted"): assert f'"{name}"' in recommend + + +def test_되돌리기_다시하기가_현재_추천을_함께_되살림(results): + """걸음마다 「현재 추천」이 노선과 짝 — 추천 없음(null)도 그대로 되돌아온다.""" + assert results["historyRecs"] == [ + {"logic": "earthwork"}, + {"logic": "follow"}, + {"logic": "earthwork"}, + None, + ]