From 9980e8fd4c3dbff61d58177cc393903da506bcc6 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Fri, 16 Oct 2026 14:51:28 +0900 Subject: [PATCH] =?UTF-8?q?fix(B05=20=EB=B0=B0=EC=88=98=EC=9C=A0=EC=97=AD)?= =?UTF-8?q?:=20349-0e=20=EC=9E=90=EB=8F=99=EC=9D=80=20=ED=9D=90=EB=A6=BC?= =?UTF-8?q?=20=EC=97=86=EC=9D=B4=20=C2=B7=20=EB=82=A1=EC=9D=80=20=EB=8F=99?= =?UTF-8?q?=EC=95=88=20=EC=A0=9C=EC=95=88=20[=EC=A0=81=EC=9A=A9]=20?= =?UTF-8?q?=EB=81=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자동 모드 = 칸 흐림 · [업데이트] 없음 · 계산 중엔 구석 「계산 중」만 · 답 오면 조용히 바꿈(수동 그대로) - 낡은 동안 제안 [적용] 끔(두 모드) · title 「노선이 바뀌어 제안이 낡음 — 새 계산 뒤 적용」 · 새 답이 오면 다시 켬 - test_349_drainage_live_flow 고쳐 씀(자동 흐림 없음 · 낡은 [적용] 막음) - PLAN.md 349-0e Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte --- B05_Profile/B05_Profile_UI_Drainage_Live.ts | 6 +- .../B05_Profile_UI_Drainage_Suggest.ts | 12 +++- .../tester/helper_349_drainage_live_flow.cjs | 6 ++ .../tester/test_349_drainage_live_flow.py | 57 ++++++++++++++----- 4 files changed, 62 insertions(+), 19 deletions(-) diff --git a/B05_Profile/B05_Profile_UI_Drainage_Live.ts b/B05_Profile/B05_Profile_UI_Drainage_Live.ts index 503b39851..1b581f947 100644 --- a/B05_Profile/B05_Profile_UI_Drainage_Live.ts +++ b/B05_Profile/B05_Profile_UI_Drainage_Live.ts @@ -5,7 +5,8 @@ * * - 단계(`stage`)는 계산 묶음(`design_pipeline` · 붙이기 = sub7)이 노선 초안 · 관이 바뀔 때 부름 — * 기다리지 않고 null 을 돌려줌(배수유역은 뒤 단계 · 출력 없음). - * - 노선 초안이 있으면 「낡음」 — 자동 = 3 s 조용한 뒤 셈 · 수동 = 칸만 흐림 + 가운데 [업데이트]. + * - 노선 초안이 있으면 「낡음」 — 자동 = 흐림 없이 3 s 조용한 뒤 셈(구석 「계산 중」만) · 수동 = 칸만 + * 흐림 + 가운데 [업데이트]. 낡은 동안 제안 [적용]은 끔(두 모드 · 새 답이 오면 다시 켬). * - 다시 조작하면 처음으로 — 떠 있는 요청을 끊고(AbortController) 다시 흐림 또는 다시 3 s. * 묶음이 같은 노선 · 관으로 다시 돌면(입력 지문 같음) 그대로 둠 — 떠 있는 답을 받음. * - 서버가 「낡음」으로 답하면(노선 [저장] 뒤 등) 같은 흐림 + [업데이트] · 자동이면 3 s 뒤. @@ -125,7 +126,8 @@ export function createDrainageLive(deps: DrainageLiveDeps): DrainageLive { header.append(gear); const paint = (): void => { - deps.root.classList.toggle("is-stale", stale); + deps.root.classList.toggle("is-stale", stale && mode === "manual"); + suggest.setStale(stale); veil.hidden = !stale || mode === "auto" || inflight !== null; busy.root.hidden = inflight === null; }; diff --git a/B05_Profile/B05_Profile_UI_Drainage_Suggest.ts b/B05_Profile/B05_Profile_UI_Drainage_Suggest.ts index c100f29d3..71bd39b57 100644 --- a/B05_Profile/B05_Profile_UI_Drainage_Suggest.ts +++ b/B05_Profile/B05_Profile_UI_Drainage_Suggest.ts @@ -35,6 +35,8 @@ export function suggestionText(item: Suggestion): string { export interface DrainageSuggest { root: HTMLElement; update(response: DetailBasinResponse): void; + /** 노선 · 관이 바뀌어 낡음 — [적용] 끔(옛 노선 기준 제안이 새 노선에 안 들어가게). */ + setStale(stale: boolean): void; /** 관 자리 후보 누가거리 — 배수유역도 점선 원. */ candidates(): number[]; } @@ -53,8 +55,9 @@ export function createDrainageSuggest( apply.type = "button"; apply.className = "b05-drainage__analyze"; apply.textContent = "적용"; - apply.title = + const applyTitle = "제안을 관 초안에 한 번에 얹습니다(관 더함 · 관경 · 시설 바꿈) — [저장] 때 남습니다."; + apply.title = applyTitle; head.append(title, apply); const list = document.createElement("ul"); list.className = "b05-drainage__suggest-list"; @@ -62,7 +65,8 @@ export function createDrainageSuggest( let suggestions: Suggestion[] = []; apply.addEventListener("click", () => { - if (suggestions.length) applyPoints(applySuggestions(pipeRequest(), suggestions)); + if (suggestions.length && !apply.disabled) + applyPoints(applySuggestions(pipeRequest(), suggestions)); }); return { @@ -81,6 +85,10 @@ export function createDrainageSuggest( }), ); }, + setStale(stale) { + apply.disabled = stale; + apply.title = stale ? "노선이 바뀌어 제안이 낡음 — 새 계산 뒤 적용" : applyTitle; + }, candidates: () => suggestions.filter((item) => item.kind === "position").map((item) => item.chainageM), }; diff --git a/resources/tester/helper_349_drainage_live_flow.cjs b/resources/tester/helper_349_drainage_live_flow.cjs index b8ece017b..afbc2db29 100644 --- a/resources/tester/helper_349_drainage_live_flow.cjs +++ b/resources/tester/helper_349_drainage_live_flow.cjs @@ -177,6 +177,8 @@ async function main() { veil: !veil.hidden, busy: !busy.hidden, fetches: calls.length, + applyOff: live.suggest.root.children[0].children[1].disabled, + applyTitle: live.suggest.root.children[0].children[1].title, }); const draft = (rev, x) => ({ rev, @@ -227,6 +229,10 @@ async function main() { update.fire("click"); stage(draft(5, 90)); out.movedWhileBusy = { ...look(), aborted: calls[1].aborted }; + // 낡은 동안 제안 [적용] — 끔 · 눌러도 옛 제안이 안 들어감 + live.suggest.root.children[0].children[1].fire("click"); + await flush(); + out.staleApply = detailCalls.length; update.fire("click"); answer(calls[2]); await flush(); diff --git a/resources/tester/test_349_drainage_live_flow.py b/resources/tester/test_349_drainage_live_flow.py index f247bd8ba..06fa912b3 100644 --- a/resources/tester/test_349_drainage_live_flow.py +++ b/resources/tester/test_349_drainage_live_flow.py @@ -5,7 +5,8 @@ (입력 지문) · 답이 오면 흐림 · 「계산 중」 사라짐 · 제안 「제안 N건」 · [적용]. - 요청 중 또 옮기면 앞 요청 끊고 다시 흐림. - 노선 [저장] 뒤 서버 답이 「낡음」 = 수동은 흐림 + [업데이트] · 자동은 3 s 뒤 조용히. -- ⚙ 자동: 3 s 조용한 뒤 셈 · 그 사이 또 옮기면 처음부터 · [업데이트] 막 없음. +- ⚙ 자동: 흐림 없음 · 3 s 조용한 뒤 셈 · 그 사이 또 옮기면 처음부터 · [업데이트] 막 없음(349-0e). +- 낡은 동안 제안 [적용] 끔(두 모드) · 새 답이 오면 다시 켬(349-0e). """ import json @@ -30,17 +31,31 @@ def r(): return json.loads(proc.stdout) -def state(stale, veil, busy, fetches): - return {"stale": stale, "blur": stale, "veil": veil, "busy": busy, "fetches": fetches} +STALE_TITLE = "노선이 바뀌어 제안이 낡음 — 새 계산 뒤 적용" + + +def state(stale, veil, busy, fetches, auto=False): + return { + "stale": stale, + "blur": stale and not auto, # 자동 = 흐림 없음 + "veil": veil, + "busy": busy, + "fetches": fetches, + "applyOff": stale, + } + + +def look(row): + return {k: v for k, v in row.items() if k != "applyTitle"} def test_manual_update_answer_lands_after_same_rerun(r): - assert r["moved"] == state(True, True, False, 0) - assert r["sameAgain"] == state(True, True, False, 0) # 판 번호만 바뀜 — 다시 안 흐림 + assert look(r["moved"]) == state(True, True, False, 0) + assert look(r["sameAgain"]) == state(True, True, False, 0) # 판 번호만 바뀜 — 다시 안 흐림 assert r["sent"]["fetches"] == 1 and r["sent"]["busy"] and not r["sent"]["veil"] rerun = r["rerunWhileBusy"] assert not rerun["aborted"] and rerun["busy"] # 같은 입력 다시 돎 — 요청 그대로 - assert r["answered"] == {**state(False, False, False, 1), "applied": 1} + assert look(r["answered"]) == {**state(False, False, False, 1), "applied": 1} def test_suggestions_and_apply(r): @@ -55,29 +70,41 @@ def test_suggestions_and_apply(r): def test_move_while_busy_cancels(r): moved = r["movedWhileBusy"] assert moved["aborted"] and moved["veil"] and not moved["busy"] - assert r["fresh"] == state(False, False, False, 3) + assert look(r["fresh"]) == state(False, False, False, 3) + + +def test_stale_blocks_apply(r): + assert r["movedWhileBusy"]["applyOff"] + assert r["movedWhileBusy"]["applyTitle"] == STALE_TITLE + assert r["staleApply"] == 1 # 눌러도 옛 제안이 안 들어감(앞 [적용] 한 번뿐) + assert not r["fresh"]["applyOff"] and r["fresh"]["applyTitle"] != STALE_TITLE def test_saved_stale_shows_update(r): - assert {k: v for k, v in r["savedStale"].items() if k != "status"} == state( + assert {k: v for k, v in look(r["savedStale"]).items() if k != "status"} == state( True, True, False, 3 ) assert r["savedStale"]["status"] == "배수유역 낡음" def test_auto_mode(r): - assert r["autoSwitch"] == state(True, False, False, 3) # 자동 — 흐림만 · [업데이트] 없음 + assert look(r["autoSwitch"]) == state( + True, False, False, 3, True + ) # 자동 — 흐림 없음 · [업데이트] 없음 assert r["auto2999"]["fetches"] == 3 assert r["auto3000"]["fetches"] == 4 and r["auto3000"]["route"] is None - assert r["autoDone"] == state(False, False, False, 4) - assert r["autoRestart"] == state(True, False, False, 4) # 2 s + 2 s — 처음부터 다시 3 s - assert r["autoMoved"] == {**state(False, False, False, 5), "mode": "auto"} - assert r["autoSavedStale"] == state(True, False, False, 5) - assert r["autoSavedDone"] == state(False, False, False, 6) + assert r["auto3000"]["busy"] and not r["auto3000"]["blur"] # 계산 중 = 구석 표시만 + assert look(r["autoDone"]) == state(False, False, False, 4) + assert look(r["autoRestart"]) == state( + True, False, False, 4, True + ) # 2 s + 2 s — 처음부터 다시 3 s + assert look(r["autoMoved"]) == {**state(False, False, False, 5), "mode": "auto"} + assert look(r["autoSavedStale"]) == state(True, False, False, 5, True) + assert look(r["autoSavedDone"]) == state(False, False, False, 6) def test_input_key(r): - assert r["keys"] == { + assert look(r["keys"]) == { "rev": True, "order": True, "node": False,