fix(M01): 자리 표시 든 요소 줄 모달 — 요소조각의 푼 키로 단가 · 날글 안 찍음 (PLAN 5장 다시 돌림 3)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WRFJmmhPi4exAzHgPZHMT
This commit is contained in:
2026-09-24 13:30:16 +09:00
co-authored by Claude Sonnet 5
parent c1181bc9b5
commit 5e9df81cbd
3 changed files with 39 additions and 4 deletions
@@ -64,6 +64,9 @@ export interface ModalTarget {
values: Record<string, string>;
/** 서버 `요소조각` — 단가 자리(요소 · 로직 부르기)를 조각으로 · 없으면 로직 키만 알약으로 */
unitFrag?: Fragment[];
/** 서버가 입력으로 푼 단가 요소 키 · 글 — 자리 표시(`{…}`) 든 줄이 씀 */
unitRef?: string;
unitText?: string;
/** 서버 수량 조각 — 한 줄 풀이를 조각으로(알약 포함) · 없으면 식 풀이 글 */
qtyFrag?: Fragment[];
}
@@ -333,10 +336,31 @@ export function openMaterialModal(target: ModalTarget): void {
rate.append(muted("…")); // 후보 목록이 오면 그 줄로 채움 — 100건 밖이면 키로 따로 찾아 옴
} else if (cond) {
rate.append(muted(tl("Pill_NoPick")));
} else if (element.includes("{")) {
// 자리 표시 든 요소 — 날글 대신 서버가 푼 키로 단가 · 못 풀면 입력 이름만
const ref = target.unitRef;
if (!ref) {
const names = [...element.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]).join(" · ");
rate.append(muted(`${target.unitText || names} — ${tl("Pill_Unresolved")}`));
} else {
const show = (brief: ElementBrief | null): void => {
rate.replaceChildren(
brief
? priceView(ref, brief)
: muted(`${target.unitText ?? ref} — ${tl("Pill_NoValue")}`),
);
setSum(brief);
};
rate.append(muted("…"));
const cached = target.prices[ref];
if (cached) show(cached);
else
void searchElements(groupOf(ref), ref)
.then((got) => show(got.items.find((it) => it.ref === ref) ?? null))
.catch(() => show(null));
}
} else if (element) {
rate.append(
muted(`${element} — ${tl(element.includes("{") ? "Pill_NoPick" : "Pill_NoValue")}`),
);
rate.append(muted(`${element} — ${tl("Pill_NoValue")}`));
}
body.replaceChildren(
rate,