From c8468e789decb50fc73fee53d4b108e781882b28 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 6 Sep 2026 23:13:05 +0900 Subject: [PATCH] =?UTF-8?q?fix(B05):=20=EC=9C=A0=ED=86=A0=20=EB=B0=B0?= =?UTF-8?q?=EB=B6=84=20=EC=84=A0=EB=B0=98=EC=9E=85=EC=9D=B4=20=EC=A0=91?= =?UTF-8?q?=ED=9E=8C=20=EC=83=81=ED=83=9C=EC=97=90=EC=84=9C=20=EC=95=88=20?= =?UTF-8?q?=EB=8F=8C=EB=8D=98=20=EA=B2=83=20=C2=B7=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=EC=9D=B4=20=EC=9A=94=EC=B2=AD=EC=9D=84=20=EB=8B=A4=EC=8B=9C=20?= =?UTF-8?q?=EB=B6=80=EB=A5=B4=EB=8D=98=20=EB=90=98=EB=A8=B9=EC=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - schedule 호출이 draw 의 접힘 가드 뒤에 있어 패널을 접어 두면 한 번도 안 불렸음. 그래서 「편집이 멈추면 조용히 받아 둔다」가 성립하지 않고 펼친 뒤에야 첫 요청이 나갔음 (보조 창 실측: 편집 6회에 요청 0건). 접힘 여부와 무관하게 지나는 emitSummary 로 옮김 — 거기서 이미 고른 결과를 쓰므로 계산 비용 0. - 응답이 오면 곡선을 다시 그리고, 그리기가 다시 schedule 을 불러 요청이 끝없이 되돌아왔음. 입력 표식(측점 수 + 마지막·중간 누계)을 두어 같은 값이면 안 보냄. 자체검증(공용 브라우저, 패널 접은 채 계획고 6회 편집) — 요청 1건, 그 뒤 4초 동안 추가 0건. 고치기 전에는 편집 중 0건 + 펼친 뒤 1건 + 되먹임 1건이었음. 시험 400 통과·17 건너뜀, typecheck 통과. Co-Authored-By: Claude Opus 5 (1M context) --- B05_Profile/B05_Profile_Api_HaulPlan.ts | 18 ++++++++++++++++++ B05_Profile/B05_Profile_UI_Profile_MassHaul.ts | 7 +++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/B05_Profile/B05_Profile_Api_HaulPlan.ts b/B05_Profile/B05_Profile_Api_HaulPlan.ts index b84433fb..79c4b3eb 100644 --- a/B05_Profile/B05_Profile_Api_HaulPlan.ts +++ b/B05_Profile/B05_Profile_Api_HaulPlan.ts @@ -44,6 +44,21 @@ export function createHaulPlanPrefetch( let sequence = 0; let plan: HaulPlanPayload = null; let pending: unknown = null; + /** 마지막으로 보낸 입력의 표식 — 같은 값이면 다시 묻지 않는다. */ + let sentKey: string | null = null; + + /** + * 입력이 달라졌는지 가리는 짧은 표식. 응답이 오면 곡선을 다시 그리고, 그리기가 다시 + * `schedule` 을 부르므로 **표식이 없으면 요청이 끝없이 되돌아온다**(2026-09-06 실측). + * 누가토량이 바뀌면 마지막 누계와 측점 수 중 하나는 반드시 달라진다. + */ + function keyOf(result: unknown): string { + const points = (result as { points?: Array<{ cumulative_volume_m3?: number }> })?.points; + if (!Array.isArray(points) || !points.length) return "empty"; + const last = points[points.length - 1]?.cumulative_volume_m3 ?? 0; + const mid = points[Math.floor(points.length / 2)]?.cumulative_volume_m3 ?? 0; + return `${points.length}:${last.toFixed(3)}:${mid.toFixed(3)}`; + } async function send(result: unknown, seq: number): Promise { const route = routeId(); @@ -76,6 +91,9 @@ export function createHaulPlanPrefetch( return { schedule(result) { + const key = keyOf(result); + if (key === sentKey) return; // 같은 값 — 응답이 부른 재그리기가 되돌아온 것이다. + sentKey = key; pending = result; sequence += 1; const seq = sequence; diff --git a/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts b/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts index 56f99e9e..93524115 100644 --- a/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts +++ b/B05_Profile/B05_Profile_UI_Profile_MassHaul.ts @@ -338,6 +338,10 @@ export function createRouteMassHaulPanel( // 총괄값 기준은 **횡단 고정**이다(2026-09-06 사용자: 종단 기준 곡선은 뒤에 없앨 예정). // 범례에서 무엇을 켜 두었든 배지·상단줄이 흔들리지 않는다. const picked = series.find((entry) => entry.basis === "cross") ?? series[0]; + // 배분 선반입도 **여기서** 건다 — 접힘 여부와 무관하게 지나는 자리다(2026-09-06). + // 아래 `draw` 안에 두었더니 접힘 가드 뒤라 한 번도 안 불렸고, 그래서 「편집이 멈추면 + // 조용히 받아 둔다」가 성립하지 않았다(펼친 뒤에야 첫 요청이 나갔다). + haulPrefetch?.schedule(picked.result); onSummary(badgeValuesFrom(picked.result)); } @@ -407,9 +411,8 @@ export function createRouteMassHaulPanel( // 토량 분배는 켜 둔 첫 곡선(정식 우선)에만 얹는다 — B06과 같은 규칙. const banded = series.find((entry) => visible.has(entry.key)); // 배분은 **서버가 만든다** — 그 산식을 번들에 남기지 않으려고 옮겼다(2026-09-06). - // 편집이 멈추면 선반입기가 뒤에서 받아 두므로 펼칠 때는 이미 도착해 있다. + // 요청은 `emitSummary` 가 걸어 두므로(접힘 여부와 무관) 펼칠 때는 이미 도착해 있다. // 아직 못 받았으면 곡선만 그린다(분배 도형은 값이 오면 다시 그려진다). - if (banded) haulPrefetch?.schedule(banded.result); const haulPlan: HaulPlan | null = banded && visible.has(MASS_HAUL_BALANCE_KEY) ? (haulPrefetch?.current() ?? null) : null; // 요약 막대는 2026-09-06 사용자 지시로 뺐다 — 총괄값은 종단 상단줄이 늘 보여 주고,