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 사용자 지시로 뺐다 — 총괄값은 종단 상단줄이 늘 보여 주고,