fix(B07): 무한 로딩 두 자리 — 미리받기가 클릭을 막던 것과 응답 없는 CAD
무한 로딩으로 보이던 자리가 둘이었음. 1) 미리받기가 클릭 앞을 막음 (진짜 원인) 진입 직후 도면 31 장을 순서대로 미리 받는데, 한 장이 0.3~1.0초라 그 사이 클릭한 도면이 앞의 여러 장 뒤에 줄을 서서 30초 넘게 안 열렸음. 화면은 「불러오는 중」 그대로였음. - 클릭이 도는 동안 미리받기가 쉬게 함 - 클릭이 이미 받아 둔 장은 건너뜀 2) CAD 가 아무 말도 안 하면 끝이 없었음 끝을 알리는 것이 drawing-loaded · drawing-error 두 통지뿐이라, iframe 이 안 뜨거나(ready 없음) 도면을 여는 중에 멈추면 영원히 돎. - 지켜보는 시계 추가 (ready 20초 · 도면 15초) - iframe 자체가 못 뜨면 기다리지 않고 바로 끊음 - 화면 표시만 끊고 요청은 살려 둠 — 늦게 오면 그대로 받음 곁들여 B07 의 「모든 도면 확정 뒤에 이동」 가드를 개발환경에서만 품 (B09 로 가는 길. 운영에서는 그대로 막음). 확인: 단계 표시줄 B08·B09 가 눌리고 B07 → B08 이동됨. tsc 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -134,6 +134,50 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
|||||||
frameFields: Record<string, string>;
|
frameFields: Record<string, string>;
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
|
// ⚠ CAD 는 iframe 이라 **저쪽이 아무 말도 안 하면 화면이 영원히 「불러오는 중」에 머문다**
|
||||||
|
// (2026-09-09 사용자 보고 — 무한 로딩). 끝을 알리는 것은 `drawing-loaded` ·
|
||||||
|
// `drawing-error` 두 통지뿐이고, 그것이 안 오는 길이 둘 있다.
|
||||||
|
// ① iframe 이 아예 안 뜸 — `dist/` 가 없거나 스크립트가 죽음 ⇒ `ready` 가 안 옴
|
||||||
|
// ② 떴는데 도면을 여는 중에 멈춤 ⇒ `loaded` 도 `error` 도 안 옴
|
||||||
|
// 아래 시계가 그 자리를 끊는다. ⚠ **화면 표시만 끊는다** — 뒤늦게 응답이 오면
|
||||||
|
// 그대로 받아 정상으로 되돌아간다(요청을 취소하지 않는다).
|
||||||
|
const CAD_READY_TIMEOUT_MS = 20000;
|
||||||
|
const CAD_LOAD_TIMEOUT_MS = 15000;
|
||||||
|
let loadWatchdog: number | undefined;
|
||||||
|
|
||||||
|
const failCad = (detail: string): void => {
|
||||||
|
cadHost.dataset.loading = "false";
|
||||||
|
cadHost.dataset.error = detail;
|
||||||
|
showToast(detail, "error");
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopLoadWatchdog = (): void => {
|
||||||
|
if (loadWatchdog === undefined) return;
|
||||||
|
window.clearTimeout(loadWatchdog);
|
||||||
|
loadWatchdog = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const startLoadWatchdog = (): void => {
|
||||||
|
stopLoadWatchdog();
|
||||||
|
// 아직 `ready` 를 못 받았으면 iframe 이 뜨기를 기다리는 중이라 더 길게 준다.
|
||||||
|
const wait = cadReady ? CAD_LOAD_TIMEOUT_MS : CAD_READY_TIMEOUT_MS;
|
||||||
|
loadWatchdog = window.setTimeout(() => {
|
||||||
|
loadWatchdog = undefined;
|
||||||
|
if (cadHost.dataset.loading !== "true") return;
|
||||||
|
failCad(
|
||||||
|
cadReady
|
||||||
|
? "CAD 가 도면을 여는 데 너무 오래 걸립니다. 다시 눌러 보세요."
|
||||||
|
: "CAD 화면이 응답하지 않습니다. 새로고침해도 같으면 CAD 빌드(dist)를 확인하세요.",
|
||||||
|
);
|
||||||
|
}, wait);
|
||||||
|
};
|
||||||
|
|
||||||
|
// iframe 자체가 못 뜨는 경우 — 이때는 `ready` 가 영영 안 오므로 기다릴 것 없이 끊는다.
|
||||||
|
frame.addEventListener("error", () => {
|
||||||
|
stopLoadWatchdog();
|
||||||
|
failCad("CAD 화면을 불러오지 못했습니다. CAD 빌드(dist)를 확인하세요.");
|
||||||
|
});
|
||||||
|
|
||||||
let currentDrawing: DesignDrawingItem | undefined;
|
let currentDrawing: DesignDrawingItem | undefined;
|
||||||
let currentIndex = -1;
|
let currentIndex = -1;
|
||||||
let currentConfirmed = false;
|
let currentConfirmed = false;
|
||||||
@@ -261,10 +305,19 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
|||||||
return request;
|
return request;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 목록 전체를 순서대로 미리 받는다. 화면을 막지 않도록 한 번에 하나씩만 간다. */
|
/** 목록 전체를 순서대로 미리 받는다. 화면을 막지 않도록 한 번에 하나씩만 간다.
|
||||||
|
*
|
||||||
|
* ⚠ **사용자가 누른 도면에 자리를 내준다** (2026-09-09 — 무한 로딩으로 보이던 자리).
|
||||||
|
* 도면이 31 장이고 한 장에 0.3~1.0초라, 미리 받기가 도는 동안 클릭하면 그 요청이
|
||||||
|
* **앞의 여러 장 뒤에 줄을 서서** 30초 넘게 안 열렸다. 화면은 「불러오는 중」 그대로였다.
|
||||||
|
* 한 장 받을 때마다 **클릭이 끼어들 틈**을 주고, 클릭이 도는 동안은 쉬어 준다.
|
||||||
|
*/
|
||||||
const prefetchAllDrawings = async (): Promise<void> => {
|
const prefetchAllDrawings = async (): Promise<void> => {
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
for (const item of drawings) {
|
for (const item of drawings) {
|
||||||
|
// 클릭이 도는 중이면 끝날 때까지 기다린다 — 미리 받기는 급하지 않다.
|
||||||
|
while (loadInFlight) await new Promise((done) => window.setTimeout(done, 120));
|
||||||
|
if (drawingCache.has(item.id)) continue; // 클릭이 이미 받아 둔 장은 건너뛴다
|
||||||
try {
|
try {
|
||||||
await requestDrawing(item);
|
await requestDrawing(item);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -294,6 +347,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
|||||||
if (button) button.dataset.loading = "true";
|
if (button) button.dataset.loading = "true";
|
||||||
cadHost.dataset.loading = "true";
|
cadHost.dataset.loading = "true";
|
||||||
cadHost.dataset.error = ""; // 앞선 실패 표시를 지운다
|
cadHost.dataset.error = ""; // 앞선 실패 표시를 지운다
|
||||||
|
startLoadWatchdog(); // 저쪽이 말이 없으면 여기서 끊는다
|
||||||
try {
|
try {
|
||||||
const response = await requestDrawing(drawing);
|
const response = await requestDrawing(drawing);
|
||||||
currentDrawing = drawing;
|
currentDrawing = drawing;
|
||||||
@@ -304,10 +358,8 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
|||||||
updateInfoPanel(drawing, response);
|
updateInfoPanel(drawing, response);
|
||||||
sendLoad(response.drawing, buildMeta(drawing, response, index));
|
sendLoad(response.drawing, buildMeta(drawing, response, index));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
cadHost.dataset.loading = "false";
|
stopLoadWatchdog(); // 여기서 이미 끝났다 — 시계를 두면 늦게 또 오류를 띄운다
|
||||||
cadHost.dataset.error =
|
failCad(error instanceof Error ? error.message : "CAD 도면을 불러오지 못했습니다.");
|
||||||
error instanceof Error ? error.message : "CAD 도면을 불러오지 못했습니다.";
|
|
||||||
showToast(cadHost.dataset.error, "error");
|
|
||||||
if (currentDrawing) highlightActive(currentDrawing.id);
|
if (currentDrawing) highlightActive(currentDrawing.id);
|
||||||
} finally {
|
} finally {
|
||||||
loadInFlight = false;
|
loadInFlight = false;
|
||||||
@@ -493,19 +545,22 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
|||||||
);
|
);
|
||||||
} else if (message.type === CAD_READY_MESSAGE) {
|
} else if (message.type === CAD_READY_MESSAGE) {
|
||||||
cadReady = true;
|
cadReady = true;
|
||||||
if (pendingLoad)
|
if (pendingLoad) {
|
||||||
sendLoad(
|
sendLoad(
|
||||||
pendingLoad.drawing,
|
pendingLoad.drawing,
|
||||||
pendingLoad.meta,
|
pendingLoad.meta,
|
||||||
pendingLoad.frameEdit,
|
pendingLoad.frameEdit,
|
||||||
pendingLoad.frameFields,
|
pendingLoad.frameFields,
|
||||||
);
|
);
|
||||||
|
// 기다리던 것이 「iframe 이 뜨기」에서 「도면이 열리기」로 바뀌었다 — 시계를 다시 건다.
|
||||||
|
if (cadHost.dataset.loading === "true") startLoadWatchdog();
|
||||||
|
}
|
||||||
} else if (message.type === CAD_LOADED_MESSAGE) {
|
} else if (message.type === CAD_LOADED_MESSAGE) {
|
||||||
|
stopLoadWatchdog();
|
||||||
cadHost.dataset.loading = "false";
|
cadHost.dataset.loading = "false";
|
||||||
} else if (message.type === CAD_ERROR_MESSAGE) {
|
} else if (message.type === CAD_ERROR_MESSAGE) {
|
||||||
cadHost.dataset.error = message.detail ?? "CAD 도면을 표시하지 못했습니다.";
|
stopLoadWatchdog();
|
||||||
cadHost.dataset.loading = "false";
|
failCad(message.detail ?? "CAD 도면을 표시하지 못했습니다.");
|
||||||
showToast(cadHost.dataset.error, "error");
|
|
||||||
} else if (message.type === CAD_CHANGED_MESSAGE) {
|
} else if (message.type === CAD_CHANGED_MESSAGE) {
|
||||||
// 편집 통지는 **미저장 표시**만 세운다. 확정을 푸는 것은 [수정] 하나뿐이다
|
// 편집 통지는 **미저장 표시**만 세운다. 확정을 푸는 것은 [수정] 하나뿐이다
|
||||||
// (2026-09-01 사용자 확정) — 예전에는 이 통지가 확정을 풀어, 되돌리기나 색
|
// (2026-09-01 사용자 확정) — 예전에는 이 통지가 확정을 풀어, 되돌리기나 색
|
||||||
@@ -551,7 +606,10 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
|
|||||||
routes: WORKFLOW_STEP_ROUTES,
|
routes: WORKFLOW_STEP_ROUTES,
|
||||||
onStepClick: (stepIndex) => {
|
onStepClick: (stepIndex) => {
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
if (stepIndex > 5 && !allDrawingsConfirmed) {
|
// ⚠ 개발에서는 막지 않는다 (2026-09-09 사용자 지시) — B08·B09 를 확정 전에 봐야
|
||||||
|
// 화면 검증이 되고, 그 우회 단추가 **B08 안에** 있어 여기서 막히면 닿을 길이 없다.
|
||||||
|
// 운영에서는 그대로 막는다.
|
||||||
|
if (stepIndex > 5 && !allDrawingsConfirmed && !import.meta.env.DEV) {
|
||||||
showToast("모든 설계 도면을 확정한 뒤 다음 단계로 이동할 수 있습니다.", "warning");
|
showToast("모든 설계 도면을 확정한 뒤 다음 단계로 이동할 수 있습니다.", "warning");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user