style: 저장소 전체 포맷터 일괄 적용 (prettier·biome·ruff)

파일마다 포맷 폭이 달라(≈80 대 100) 한 줄만 고쳐도 포맷터가 무관한 줄을 대량
재포맷했음. 사용자 지시로 전체를 한 번에 맞춤. 코드 동작 변경 없음 — 포맷만.

- 프론트엔드 `.ts/.css/.html` → 저장소 prettier (`.prettierrc`, printWidth 100)
- `B07_DesignDetail/openwebcad/**` → 자체 biome (tab 들여쓰기·single quote·lineWidth 100).
  `biome format` 만 사용 — `biome lint --write` 는 포맷 아닌 코드 수정까지 하므로 제외
- 파이썬 → `ruff format` (엔진 코드는 이미 정합, resources·scratch 스크립트 24개만 변경)

두 포맷터가 서로 되돌리지 않도록 `.prettierignore` 신규 — openwebcad 와 빌드·산출물
폴더를 prettier 대상에서 뺌. `.prettierrc` 에 `endOfLine: "auto"` 추가 — 기본값 `lf` 가
`core.autocrlf=true` 로 받은 CRLF 파일을 매번 전부 다시 써서 `--list-different` 가
실제 포맷 차이를 가리고 있었음.

검증: `tsc --noEmit` 통과(루트·openwebcad 둘 다), pytest 349 passed / 17 skipped /
0 failed, CAD vitest 87건 중 81 passed / 6 failed(laptop-sub 기준선과 동일, 회귀 없음).
포맷터 재실행 시 prettier·biome 모두 변경 0건.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-02 07:08:24 +09:00
co-authored by Claude Opus 5
parent 43f23c38ef
commit 4cb9b15939
143 changed files with 4133 additions and 4103 deletions
+29 -92
View File
@@ -133,10 +133,7 @@ function buildDrawingSidePanel(
return panel;
}
const drawingButton = (
drawing: DesignDrawingItem,
label: string,
): HTMLButtonElement => {
const drawingButton = (drawing: DesignDrawingItem, label: string): HTMLButtonElement => {
const button = document.createElement("button");
button.type = "button";
button.className = "b07-drawing-button";
@@ -175,8 +172,7 @@ function buildDrawingSidePanel(
const button = drawingButton(drawing, group.label);
// 도각만 있는 도면은 그렇다고 알린다 — 빈 화면을 보고 오류로 오해하지 않게.
button.dataset.pending = String(drawing.kind === "blank");
if (drawing.kind === "blank")
button.title = "준비 중 — 도각만 표시합니다";
if (drawing.kind === "blank") button.title = "준비 중 — 도각만 표시합니다";
panel.append(button);
continue;
}
@@ -198,10 +194,7 @@ function buildDrawingSidePanel(
return panel;
}
const GROUND_TYPE_LABEL: Record<
CrossDesignInfo["ground_type"],
keyof typeof ui_locales
> = {
const GROUND_TYPE_LABEL: Record<CrossDesignInfo["ground_type"], keyof typeof ui_locales> = {
soil: "B06_Design_Ground_Soil",
ripping_rock: "B06_Design_Ground_Ripping",
blasting_rock: "B06_Design_Ground_Blasting",
@@ -218,8 +211,7 @@ function cutSideLabel(mode: CrossDesignInfo["section_mode"]): string {
/** 측구 규격 표시 문자열 (design 신구조: 형식별 ditch spec, F-2 호환). */
function ditchLabel(design: CrossDesignInfo): string {
const ditch = design.ditch;
if (!ditch || ditch.type === "none" || design.ditch_enabled === false)
return "없음";
if (!ditch || ditch.type === "none" || design.ditch_enabled === false) return "없음";
if (ditch.type === "l_type")
return `L형 ${ditch.width_m.toFixed(2)}×${ditch.depth_m.toFixed(2)}m`;
return `${ditch.top_width_m.toFixed(2)}×${ditch.depth_m.toFixed(2)}m`;
@@ -239,10 +231,7 @@ function infoRow(label: string, value: string): HTMLElement {
}
/** 선택 횡단도의 지반정보/계획정보를 2분할로 렌더한다 (잠정치, B07 확정 시 재계산). */
function buildDesignInfoPanel(
title: string,
design: CrossDesignInfo | null,
): HTMLElement {
function buildDesignInfoPanel(title: string, design: CrossDesignInfo | null): HTMLElement {
const panel = document.createElement("div");
panel.className = "b07-info";
const heading = document.createElement("div");
@@ -252,9 +241,7 @@ function buildDesignInfoPanel(
const confirmed = design?.status === "confirmed";
const badge = document.createElement("span");
badge.className = `b07-info__badge${confirmed ? " b07-info__badge--confirmed" : ""}`;
badge.textContent = confirmed
? L("B07_Info_Confirmed")
: L("B07_Info_Provisional");
badge.textContent = confirmed ? L("B07_Info_Confirmed") : L("B07_Info_Provisional");
heading.append(stationName, badge);
panel.append(heading);
@@ -276,9 +263,7 @@ function buildDesignInfoPanel(
infoRow(L("B07_Info_CutSide"), cutSideLabel(design.section_mode)),
infoRow(
L("B07_Info_DitchSide"),
design.ditch_side === "left"
? L("B06_Design_Ditch_Left")
: L("B06_Design_Ditch_Right"),
design.ditch_side === "left" ? L("B06_Design_Ditch_Left") : L("B06_Design_Ditch_Right"),
),
);
@@ -288,10 +273,7 @@ function buildDesignInfoPanel(
planTitle.textContent = L("B07_Info_Plan_Title");
plan.append(
planTitle,
infoRow(
L("B07_Info_DesignElevation"),
`${design.design_elevation_m.toFixed(2)}m`,
),
infoRow(L("B07_Info_DesignElevation"), `${design.design_elevation_m.toFixed(2)}m`),
infoRow(L("B07_Info_CutSlope"), `1:${design.cut_slope_ratio}`),
infoRow(L("B07_Info_FillSlope"), `1:${design.fill_slope_ratio}`),
infoRow(L("B07_Info_RoadWidth"), `${design.roadbed_width_m.toFixed(2)}m`),
@@ -317,10 +299,8 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
fetchWorkflowState(projectId),
fetchDesignDrawingList(projectId),
]);
if (workflowResult.status === "fulfilled")
workflowState = workflowResult.value;
if (drawingResult.status === "fulfilled")
drawings = drawingResult.value.drawings;
if (workflowResult.status === "fulfilled") workflowState = workflowResult.value;
if (drawingResult.status === "fulfilled") drawings = drawingResult.value.drawings;
else
drawingError =
drawingResult.reason instanceof Error
@@ -350,25 +330,19 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
// 단계 완료 기준은 횡단도만 본다 (종단도 확정 여부는 다음 단계 진행과 무관).
const isCross = (item: DesignDrawingItem): boolean => item.kind === "cross";
let allDrawingsConfirmed =
drawings.some(isCross) &&
drawings.filter(isCross).every((item) => item.confirmed);
drawings.some(isCross) && drawings.filter(isCross).every((item) => item.confirmed);
let resolveSave: ((payload: SaveResult) => void) | undefined;
let drawingListEl: HTMLElement | undefined;
const infoPanelHost = document.createElement("div");
infoPanelHost.className = "b07-info-host";
const updateInfoPanel = (
drawing: DesignDrawingItem,
response: DesignDrawingResponse,
): void => {
const updateInfoPanel = (drawing: DesignDrawingItem, response: DesignDrawingResponse): void => {
if (drawing.kind !== "cross") {
infoPanelHost.replaceChildren();
return;
}
const title = drawing.label;
infoPanelHost.replaceChildren(
buildDesignInfoPanel(title, response.design ?? null),
);
infoPanelHost.replaceChildren(buildDesignInfoPanel(title, response.design ?? null));
};
/**
@@ -406,11 +380,9 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
) ?? undefined;
const highlightActive = (drawingId: string) => {
drawingListEl
?.querySelectorAll<HTMLButtonElement>(".b07-drawing-button")
.forEach((item) => {
item.dataset.active = String(item.dataset.drawingId === drawingId);
});
drawingListEl?.querySelectorAll<HTMLButtonElement>(".b07-drawing-button").forEach((item) => {
item.dataset.active = String(item.dataset.drawingId === drawingId);
});
};
const buildMeta = (
@@ -449,24 +421,15 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
const drawingCache = new Map<string, Promise<DesignDrawingResponse>>();
/** 도면 하나를 받아 구조물까지 얹은 응답. 같은 id로 겹쳐 부르면 같은 Promise를 쓴다. */
const requestDrawing = (
drawing: DesignDrawingItem,
): Promise<DesignDrawingResponse> => {
const requestDrawing = (drawing: DesignDrawingItem): Promise<DesignDrawingResponse> => {
const cached = drawingCache.get(drawing.id);
if (cached) return cached;
const request = (async () => {
const response = await fetchDesignDrawing(
projectId as string,
drawing.id,
);
const response = await fetchDesignDrawing(projectId as string, drawing.id);
// 구조물(배수관·기슭막이·세월교·BOX·물넘이포장)은 B06 산식이 프론트에 있어
// 여기서 얹는다. 확정본은 이미 구조물이 담겨 저장돼 있으므로 건드리지 않는다.
if (drawing.kind === "cross" && !response.confirmed) {
await appendStructureEntities(
projectId as string,
response.route_id,
response.drawing,
);
await appendStructureEntities(projectId as string, response.route_id, response.drawing);
}
return response;
})().catch((error) => {
@@ -522,9 +485,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
} catch (error) {
cadHost.dataset.loading = "false";
cadHost.dataset.error =
error instanceof Error
? error.message
: "CAD 도면을 불러오지 못했습니다.";
error instanceof Error ? error.message : "CAD 도면을 불러오지 못했습니다.";
showToast(cadHost.dataset.error, "error");
if (currentDrawing) highlightActive(currentDrawing.id);
} finally {
@@ -549,10 +510,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
const requestCadDrawing = (): Promise<SaveResult> =>
new Promise((resolve, reject) => {
resolveSave = resolve;
frame.contentWindow?.postMessage(
{ type: CAD_SAVE_REQUEST_MESSAGE },
window.location.origin,
);
frame.contentWindow?.postMessage({ type: CAD_SAVE_REQUEST_MESSAGE }, window.location.origin);
window.setTimeout(() => {
if (!resolveSave) return;
resolveSave = undefined;
@@ -596,9 +554,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
}
} catch (error) {
showToast(
error instanceof Error
? error.message
: "현재 도면을 확정하지 못했습니다.",
error instanceof Error ? error.message : "현재 도면을 확정하지 못했습니다.",
"error",
);
} finally {
@@ -628,9 +584,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
showToast("확정을 풀었습니다. 고친 뒤 다시 확정하세요.", "info");
} catch (error) {
showToast(
error instanceof Error
? error.message
: "도면 확정 상태를 되돌리지 못했습니다.",
error instanceof Error ? error.message : "도면 확정 상태를 되돌리지 못했습니다.",
"error",
);
} finally {
@@ -650,11 +604,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
cadHost.prepend(frameEditor.banner);
window.addEventListener("message", (event: MessageEvent<unknown>) => {
if (
event.origin !== window.location.origin ||
event.source !== frame.contentWindow
)
return;
if (event.origin !== window.location.origin || event.source !== frame.contentWindow) return;
const message = event.data as {
type?: string;
detail?: string;
@@ -672,8 +622,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
(item) => item === message.kind,
);
// autoClose:false로 온 안내(백업 되살리기)는 오래 띄운다 — 누를 시간을 준다.
const duration =
message.durationMs === 0 ? 15000 : (message.durationMs ?? 3000);
const duration = message.durationMs === 0 ? 15000 : (message.durationMs ?? 3000);
const actionId = message.actionId;
showToast(
message.text ?? "",
@@ -693,8 +642,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
} else if (message.type === CAD_LOADED_MESSAGE) {
cadHost.dataset.loading = "false";
} else if (message.type === CAD_ERROR_MESSAGE) {
cadHost.dataset.error =
message.detail ?? "CAD 도면을 표시하지 못했습니다.";
cadHost.dataset.error = message.detail ?? "CAD 도면을 표시하지 못했습니다.";
cadHost.dataset.loading = "false";
showToast(cadHost.dataset.error, "error");
} else if (message.type === CAD_CHANGED_MESSAGE) {
@@ -704,11 +652,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
if (!frameEditor.isEditing()) cadDirty = message.dirty !== false;
} else if (message.type === CAD_NAVIGATE_MESSAGE && message.direction) {
navigateDrawing(message.direction);
} else if (
message.type === CAD_SAVE_RESPONSE_MESSAGE &&
message.drawing &&
resolveSave
) {
} else if (message.type === CAD_SAVE_RESPONSE_MESSAGE && message.drawing && resolveSave) {
const resolve = resolveSave;
resolveSave = undefined;
resolve({
@@ -718,11 +662,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
}
});
const drawingPanel = buildDrawingSidePanel(
drawings,
selectDrawing,
drawingError,
);
const drawingPanel = buildDrawingSidePanel(drawings, selectDrawing, drawingError);
drawingListEl = drawingPanel;
const confirmActions = document.createElement("div");
// 하단 고정은 공용 ui-sidebar-actions로 통일 — 사이드 본문이 [스크롤 영역][액션 줄]로
@@ -744,10 +684,7 @@ export async function renderB07DesignDetail(root: HTMLElement): Promise<void> {
onStepClick: (stepIndex) => {
if (!projectId) return;
if (stepIndex > 5 && !allDrawingsConfirmed) {
showToast(
"모든 설계 도면을 확정한 뒤 다음 단계로 이동할 수 있습니다.",
"warning",
);
showToast("모든 설계 도면을 확정한 뒤 다음 단계로 이동할 수 있습니다.", "warning");
return;
}
goToWorkflowStage(projectId, WORKFLOW_STEP_ROUTES[stepIndex]);