fix(계산묶음): 349-0m 화면 되돌리기에서도 되돌림 길 — 초안 여부 = 묶음 입력 · 되돌림 = 차례 끝
- 까닭: 화면 Ctrl+Z 는 페이지 사진첩이 세션 노선 초안 칸을 먼저 비우고 다리에 알림 → 묶음이 가게 칸만 보고 「초안 아니었음」으로 판정해 되돌림을 건너뜀 - 초안이었나 = 묶음이 쥔 노선 입력 · 같은 알림이 두 번 와도 한 차례(값이 같으면 차례 없음) - 되돌림 판정 = 그 차례가 끝난 뒤(ground 알림에 안 기댐) · 밀린 차례면 초안 전 상세를 그대로 둠 - 시험 test_349_0l 되돌리기를 화면 꼴로(칸 먼저 비움 · 알림 두 번) — 옛 코드는 restored 없음 · 다시 셈 1 로 재현 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
This commit is contained in:
@@ -26,7 +26,6 @@ import { createStationsStage } from "./logic/design_pipeline_stations";
|
||||
import type {
|
||||
FollowPatch,
|
||||
PipelineInputs,
|
||||
PipelineOutputs,
|
||||
PipelineRunRecord,
|
||||
PipelineStation,
|
||||
RouteDraft,
|
||||
@@ -229,9 +228,7 @@ function createPagePipeline(
|
||||
let quietGeneration = -1;
|
||||
/** 노선 초안 전 상세(종단도가 그리던 것) — 초안을 버리면 이것으로(다시 셈 없음 · 349-0k). */
|
||||
let restoreTo: SectionDetailResponse | null = null;
|
||||
let restoring = false;
|
||||
/** 초안을 버린 차례 기록 · 되돌림이 끝나는 약속(종단도는 그 뒤 저장 기준으로 — 349-0l). */
|
||||
let dropRun: Promise<PipelineRunRecord> | null = null;
|
||||
/** 초안을 버린 차례가 끝나 되돌림까지 마치는 약속(종단도는 그 뒤 저장 기준으로 — 349-0l). */
|
||||
let dropping: Promise<string> | null = null;
|
||||
/** 노선 초안 전 가게 관 · 구조물 — 초안 동안 follow 가 새 측점을 가게에 씀 · 초안을 버리면 이것으로. */
|
||||
let beforeDraft: Facilities | null = null;
|
||||
@@ -319,29 +316,23 @@ function createPagePipeline(
|
||||
const saved = event.kind === "stage" && (event.stage === "ground" || event.stage === "profile");
|
||||
if (!(saved && event.generation === quietGeneration)) publish(event);
|
||||
});
|
||||
// 노선 초안을 버린 차례 — 저장 노선 측점이 나오면 초안 전 상세로 되돌림(열 때와 같은 길)
|
||||
pipe.subscribe((event) => {
|
||||
if (event.kind !== "stage" || event.stage !== "ground") return;
|
||||
if (!restoring || event.generation !== quietGeneration) return;
|
||||
restoring = false;
|
||||
/** 노선 초안을 버린 차례 끝 — 저장 노선 측점으로 초안 전 상세에 되돌림(열 때와 같은 길) · 기록에 길 남김.
|
||||
* 차례 끝에서 판정 — ground 를 건너뛰어도 · 사진첩 되돌리기가 가게 초안을 먼저 비워도(349-0m). */
|
||||
const finishDrop = async (record: PipelineRunRecord): Promise<string> => {
|
||||
const routeId = canon?.routeId;
|
||||
const stations = (event.value as PipelineOutputs["ground"] | null)?.stations ?? [];
|
||||
const saved = restoreTo;
|
||||
restoreTo = null;
|
||||
if (typeof routeId !== "number" || !stations.length || !options.onDraftDropped) return;
|
||||
const drop = options.onDraftDropped;
|
||||
const record = dropRun;
|
||||
dropping = new Promise<string>((resolve) =>
|
||||
queueMicrotask(
|
||||
() => void drop(routeId, saved, stations).then(resolve, () => resolve("error")),
|
||||
),
|
||||
);
|
||||
void dropping.then((why) =>
|
||||
record?.then((run) =>
|
||||
Object.assign(run, { restored: why === "saved" || why === "unchanged", restoreWhy: why }),
|
||||
),
|
||||
);
|
||||
});
|
||||
const stations = pipe.output("ground")?.stations ?? [];
|
||||
let why: string = record.outcome; // 새 조작에 밀림 · 단계 실패 — 초안 전 상세는 그대로 둠
|
||||
if (record.outcome === "done") {
|
||||
why = "none";
|
||||
if (typeof routeId === "number" && stations.length && options.onDraftDropped) {
|
||||
const saved = restoreTo;
|
||||
restoreTo = null;
|
||||
why = await options.onDraftDropped(routeId, saved, stations).catch(() => "error");
|
||||
}
|
||||
}
|
||||
Object.assign(record, { restored: why === "saved" || why === "unchanged", restoreWhy: why });
|
||||
return why;
|
||||
};
|
||||
// 사용자 노선 조작 뒤 follow 가 다시 매긴 관 · 구조물 = 가게(목록 · 알약 · 횡단 보기가 바로 새 측점) ·
|
||||
// 초안을 버리면 초안 전 값 · 열 때(start 차례)는 안 씀 — 알림 밖(틱 뒤)에서 씀(쓰기 고리 막음과 안 부딪힘)
|
||||
pipe.subscribe((event) => {
|
||||
@@ -439,7 +430,7 @@ function createPagePipeline(
|
||||
canonChanged();
|
||||
started = true;
|
||||
restoreTo = null;
|
||||
restoring = false;
|
||||
dropping = null;
|
||||
void pipe.start(inputsNow());
|
||||
openGeneration = quietGeneration = pipe.generation;
|
||||
};
|
||||
@@ -491,20 +482,23 @@ function createPagePipeline(
|
||||
saved: () => store.get("routeDraft"),
|
||||
edited(edit: RouteDraftEdit | null) {
|
||||
const stored = store.get("routeDraft");
|
||||
// 묶음이 쥔 노선 초안 — 가게 칸은 사진첩 되돌리기가 먼저 비울 수 있음(349-0m)
|
||||
const wasDraft = pipe.input("route") !== null;
|
||||
let draft: RouteDraft | null = null;
|
||||
if (edit) {
|
||||
draft = { ...edit, rev: (stored?.rev ?? 0) + 1, terrain: analysisTerrain(projectId) };
|
||||
if (stored && same({ ...stored, rev: 0 }, { ...draft, rev: 0 })) draft = stored;
|
||||
}
|
||||
if (draft && !stored && !restoring) restoreTo = savedSource().detail ?? null;
|
||||
if (draft && !wasDraft) restoreTo ??= savedSource().detail ?? null;
|
||||
store.set("routeDraft", draft);
|
||||
if (!started) return start();
|
||||
const before = pipe.output("route")?.line ?? null;
|
||||
const after = draft ? draftLine(draft, plan?.min_radius_m ?? 0).line : savedLine();
|
||||
const generation = pipe.generation;
|
||||
const run = pipe.update("route", draft, routeRange(before, after));
|
||||
restoring = !draft && !!stored && pipe.generation !== generation;
|
||||
if (restoring) [quietGeneration, dropRun, dropping] = [pipe.generation, run, null];
|
||||
if (run && !draft && wasDraft) {
|
||||
quietGeneration = pipe.generation;
|
||||
dropping = run.then(finishDrop);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* 상세 가게(`B06_Section_Section_Store`). 화면 부품(DOM · SVG · 오버레이 · 도구줄)과 서버 창구만 가짜 ·
|
||||
* 그리기는 진짜 `renderProfile` 의 낡음 판정 줄만 · 다시 셈 창구(`refreshCrossDesigns`)는 부른 수를 세고 면적을
|
||||
* 바꿈(서버와 다른 값 흉내). 검증_sub7 노선(관 11 · 구조물 1 은 시험이 관 자리에 놓음).
|
||||
* 되돌리기 = 화면 Ctrl+Z 꼴 — 사진첩이 세션 노선 초안 칸을 먼저 비운 뒤 다리에 알림(349-0m).
|
||||
* 입력 argv[2] JSON · argv[3] 격자 이진. 결과 = 표준출력 JSON. 짝 = `test_349_0l_undo_draw.py`. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
@@ -208,6 +209,7 @@ const layouts = load("B06_Section", "B06_Section_Structure_Layouts.ts");
|
||||
const profileData = load("B05_Profile", "logic", "B05_Profile_UI_Profile_Data.ts");
|
||||
const alignmentMod = load("B05_Profile", "logic", "B05_Profile_UI_Profile_Alignment.ts");
|
||||
const baseMod = load("A00_Common", "design", "design_profile_base.ts");
|
||||
const pageState = load("A00_Common", "b_page_state.ts");
|
||||
const massHaul = load("common_util", "common_util_mass_haul.ts");
|
||||
const profileMod = load("A00_Common", "design", "design_profile.ts");
|
||||
|
||||
@@ -340,9 +342,12 @@ async function run() {
|
||||
doubled(await live());
|
||||
const drag = await measure(mark, refreshMark);
|
||||
|
||||
// 되돌리기 한 번(초안 버림)
|
||||
// 되돌리기 한 번(초안 버림) — 화면 Ctrl+Z 꼴(349-0m): 페이지 사진첩이 세션 route-draft 칸을 먼저 되쓰고
|
||||
// 편집기 되살림(fromPage → tell)이 다리에 null 을 알림 · 같은 알림이 한 번 더 와도 한 차례
|
||||
mark = runs().length;
|
||||
refreshMark = refreshCalls;
|
||||
pageState.writeState("route-draft", null, P);
|
||||
bridge.edited(null);
|
||||
bridge.edited(null);
|
||||
await quiet();
|
||||
const undo = await measure(mark, refreshMark);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
· 열기 → 노선 꼭짓점 끌기 → 되돌리기 → 횡단 보기 → 종단 보기 — 누가토량 · 측점 · 횡단 = 열기 값
|
||||
· 진짜 종단도 낡음 판정 · crossPreview 예약을 거침 — 되돌린 뒤 다시 셈 0 · 낡음 0
|
||||
· 묶음 기록에 되돌림 길(restored · 까닭) · 되돌리기 = 묶음 한 번
|
||||
· 되돌리기 = 화면 Ctrl+Z 꼴(사진첩이 세션 노선 초안 칸을 먼저 비움 · 알림 두 번 · 349-0m)
|
||||
짝: `helper_349_0l_undo_draw.cjs`(실제 TS 를 Node 로 · 화면 부품 · 서버 창구만 가짜).
|
||||
"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user