Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1

This commit is contained in:
2026-10-19 10:16:39 +09:00
7 changed files with 79 additions and 11 deletions
@@ -3,12 +3,12 @@
* 계산 묶음 관 · 구조물 · 추가 측점 먹이기(PLAN 415 · 429 ⑨) — 가게 초안이 바뀌면 묶음에 넣음(비면 저장분).
* · [저장] 도중 = 안 넣음(H2) — 초안이 비는 사이 열 때 읽은 옛 저장분으로 돌지 않게 · 끝나면 `resync`
* (저장 목록 다시 읽기 · 입력 맞춤) 한 번.
* · 되돌리기 · 다시하기 = 세션만 되쓰고 가게 알림이 없음 → 묶음 입력과 견줘 다르면 넣음(415-2 · 노선 초안은
* 노선 길이 맡음).
* · 되돌리기 · 다시하기 = 아래 되살림 훅 한 길 — 묶음 입력과 견줘 다르면 넣음(415-2 · 노선 초안은 노선 길이
* 맡음) · 그 사이 가게 알림(`announceDesignStore` · 472-5)은 구독이 받지 않음(두 번 안 넣음).
* 저장 창구는 늦게 불러옴(묶음 시험이 화면 모듈을 안 끌게).
* ========================================================================== */
import { onPageHistory } from "../../B05_Profile/B05_Profile_UI_Profile_History";
import { onPageHistory, pageReplaying } from "../../B05_Profile/B05_Profile_UI_Profile_History";
import type { useDesignStore } from "./design_store";
import type { ChangeSource } from "./logic/design_pipeline_types";
@@ -57,7 +57,13 @@ export function feedFacilities(o: {
saving() ? afterSave() : o.feed(source, value);
const stops = [
...FACILITIES.map(({ source, key }) =>
o.store.subscribe(key, (value) => o.ready() && put(source, value ?? o.saved(key))),
o.store.subscribe(key, (value) => {
const now = value ?? o.saved(key);
// 묶음 입력과 같은 값 = 이미 받음(되살림 뒤 추가 측점 다시 알림 등) — 한 번만 넣음(472-5)
if (!o.ready() || pageReplaying() || JSON.stringify(now) === JSON.stringify(o.input(key)))
return;
put(source, now);
}),
),
onPageHistory({
restore: () => {
+4 -3
View File
@@ -241,11 +241,12 @@ const listeners = new Map<string, Set<(value: unknown) => void>>();
* 노선을 모르면 `routeId = null` — 노선 칸(`alignmentDraft` · `sectionDetail`)은 비어 보이고
* 쓰기는 버려진다. 프로젝트를 모르면 프로젝트 칸이 그렇다.
*/
/** 가게를 안 거친 세션 되씀(되돌리기 · 다시하기) 뒤 — 프로젝트 목록 칸을 듣는 쪽을 지금 값으로 부름
* (472-5 · 관 · 구조물 사본이 옛 값으로 남아 다음 조작이 되돌림을 덮던 흠). */
/** 가게를 안 거친 세션 되씀(되돌리기 · 다시하기) 뒤 — 관 · 구조물 칸 듣는 쪽을 지금 값으로 한 번 부름
* (472-5 · 사본이 옛 값으로 남아 다음 조작이 되돌림을 덮던 흠). 추가 측점 · 페이지 설정은 자기 되살림 훅이
* 알림(`_Api_PlusStations` · `_Api_PageSettings`) · 계산 묶음은 되살림 훅 한 길(구독은 되돌리는 동안 안 받음). */
export function announceDesignStore(projectId: string): void {
const store = useDesignStore(projectId, null);
(["structures", "pipes", "plusStations"] as const).forEach((key) => {
(["structures", "pipes"] as const).forEach((key) => {
const id = slotOf(key, projectId, null).id;
const value = store.get(key);
if (id) listeners.get(`${key}|${id}`)?.forEach((fn) => fn(value));
@@ -573,8 +573,16 @@ export function createStructuresSection(
},
getStructures: () => [...structures],
setPipeFacilities(pipes) {
const before = pipeFacilities;
pipeFacilities = pipes.map((pipe) => ({ ...pipe }));
followPipeList(panelContext, pipeFacilities);
// 열린 관 폼 — 값이 밖에서 바뀌면(되돌리기 · 다른 보기) 새 값으로 다시 엶 · 고치던 실시간 반영 · 임시 배치는
// 둠(472-5 · 옛 칸 값이 다음 조작에 되쓰이던 흠)
const at = selectedPipeChainage;
if (at === null || applyingLive || tempPipeChainage !== null) return;
const near = (pipe: PipeFacilityItem) => Math.abs(pipe.chainage_m - at) < PIPE_MATCH_M;
const now = pipeFacilities.find(near);
if (now && JSON.stringify(now) !== JSON.stringify(before.find(near))) loadPipeForm(now);
},
selectPipeByChainage(chainageM) {
if (chainageM !== null) callbacks.onReveal?.();
+6 -1
View File
@@ -105,6 +105,7 @@ async function run() {
const feeder = loader({
"../../B05_Profile/B05_Profile_UI_Profile_History": {
onPageHistory: (hooks) => ((restore = hooks.restore), () => undefined),
pageReplaying: () => false, // 묶음 먹이기가 되돌리는 동안 구독을 거름(472-5)
},
"./design_save": { savingDesign: () => false },
})(path.join(DESIGN, "design_pipeline_facilities.ts"));
@@ -116,7 +117,11 @@ async function run() {
saved: (key) => (key === "plusStations" ? [12] : []),
input: (key) => inputNow[key] ?? null,
routed: () => false,
feed: (source, value) => fed.push([source, value]),
// 묶음처럼 받은 값이 입력이 됨 — 같은 값 다시 알림은 안 넣음(472-5)
feed: (source, value) => {
fed.push([source, value]);
if (source === "plus") inputNow.plusStations = value;
},
resync: async () => undefined,
});
fakeStore.set("plusStations", [12, 30]);
+32 -1
View File
@@ -43,19 +43,49 @@ const pipes2 = [
];
store.set("structures", A);
store.set("pipes", pipes1);
m.writePlusDraft(P, [60]);
const history = m.createProfileHistory(() => undefined, 100);
store.set("structures", AB);
store.set("pipes", pipes2);
m.writePlusDraft(P, [60, 80]);
history.record();
const heard = { structures: [], pipes: [] };
const heard = { structures: [], pipes: [], plusStations: [] };
store.subscribe("structures", (v) => heard.structures.push(v));
store.subscribe("pipes", (v) => heard.pipes.push(v));
store.subscribe("plusStations", (v) => heard.plusStations.push(v));
// 계산 묶음 먹이기 — 되돌리기 한 번에 출처마다 한 번만(되살림 훅 한 길 · 472-5 겹침 정리)
const fed = [];
const input = { pipes: pipes2, structures: AB, plusStations: [60, 80] };
const keyOf = { pipe: "pipes", structure: "structures", plus: "plusStations" };
m.feedFacilities({
projectId: P,
store,
ready: () => true,
gone: () => false,
saved: () => null,
input: (key) => input[key],
routed: () => false,
feed: (source, value) => {
fed.push(source);
input[keyOf[source]] = value;
},
resync: async () => undefined,
});
const tick = () => new Promise((done) => setTimeout(done, 5));
history.undo();
await tick();
const afterUndo = {
structures: heard.structures.at(-1) ?? null,
pipes: heard.pipes.at(-1) ?? null,
};
const counts = {
structures: heard.structures.length,
pipes: heard.pipes.length,
plusStations: heard.plusStations.length,
fed: [...fed].sort(),
};
history.redo();
await tick();
const afterRedo = {
structures: heard.structures.at(-1) ?? null,
pipes: heard.pipes.at(-1) ?? null,
@@ -85,6 +115,7 @@ console.log(
JSON.stringify({
afterUndo,
afterRedo,
counts,
echo,
adopted,
picked,
@@ -4,3 +4,5 @@ export { pinDraftProject, useDesignStore } from "../../A00_Common/design/design_
export { adoptEditorPipes } from "../../B05_Profile/B05_Profile_UI_Drainage_Parts";
export { createPipeEditor } from "../../B05_Profile/B05_Profile_UI_Drainage_Pipes";
export { createFacilityStore } from "../../B05_Profile/B05_Profile_UI_Drainage_Facility";
export { feedFacilities } from "../../A00_Common/design/design_pipeline_facilities";
export { writePlusDraft } from "../../B05_Profile/B05_Profile_Api_PlusStations";
+17 -2
View File
@@ -1,8 +1,9 @@
"""PLAN 472-5 5-A (요구 273) — 관 · 구조물 값 주인 하나(설계 가게) · 사본 갈림 막음.
못박는 것
- 되돌리기 · 다시하기(세션 직접 되씀)가 가게 듣는 쪽을 되돌린 값으로 부름 — 옛 코드는 안 불러
종단 목록 · 횡단 판 · 배수 편집기가 옛 값을 쥐고 다음 조작이 되돌림을 덮었음(옛 코드 실패).
- 되돌리기 · 다시하기(세션 직접 되씀)가 가게 듣는 쪽을 되돌린 값으로 한 번씩 부름(5-C 훅 · 묶음과
안 겹침) — 옛 코드는 안 불러 종단 목록 · 횡단 판 · 배수 편집기가 옛 값을 쥐고 다음 조작이
되돌림을 덮었음(옛 코드 실패).
- 배수 편집기 받기 — 제 쓰기 메아리는 안 받음 · 남이 고친 옵션 · 차례도 받음 · 고른 관(id) 유지.
- 배선 — 종단 보기 = 관(숨어도) · 구조물(보일 때) 구독 · 횡단 보기 = 남 고침(옵션만도) 다시 실음 ·
횡단 판은 쓰기 전에 제 값을 적음(메아리 가름).
@@ -75,6 +76,16 @@ def test_되돌리기가_가게_듣는_쪽을_부름(out) -> None:
assert out["afterRedo"]["pipes"][0]["options"] == {"diameter_mm": 1000}
def test_되돌리기_알림은_한_번씩(out) -> None:
"""겹침 정리 — 관 · 구조물 = 가게 알림 한 번 · 추가 측점 = 자기 훅 · 묶음 = 출처마다 한 번."""
assert out["counts"] == {
"structures": 1,
"pipes": 1,
"plusStations": 1,
"fed": ["pipe", "plus", "structure"],
}
def test_배수_편집기_남_고침만_받음(out) -> None:
assert out["echo"] is False
assert out["adopted"] is True
@@ -105,5 +116,9 @@ def test_배선() -> None:
assert panel.index("seen.structures = JSON.stringify(structures);") < panel.index(
"writePendingStructures(deps.projectId, structures);"
)
# 열린 관 폼 — 밖에서 바뀐 값(되돌리기 · 다른 보기)으로 다시 엶(옛 칸 값 되쓰던 흠)
form = _read("B05_Profile", "B05_Profile_UI_Structures_Panel.ts")
assert "if (at === null || applyingLive || tempPipeChainage !== null) return;" in form
assert "JSON.stringify(now) !== JSON.stringify(before.find(near))) loadPipeForm(now);" in form
history = _read("B05_Profile", "B05_Profile_UI_Profile_History.ts")
assert "announceDesignStore(projectId);" in history