feat(M02): 구조물 옵션 정의 = 명세 이름 고치기 칸(명세 있는 종류 · 52-11)

- 명세가 있는 종류(STRUCTURE_SPECS)는 컨테이너 제목 › 항목 이름 › 값 이름 나무 · 이름만 고침 · 키 · 필수/선택 · 꼴은 읽기만
- 필수 컨테이너 제목 = 정의 그룹 이름 자리 · 나머지 = 종류.이름 표(B05 · B06 그리기와 같은 차례)
- 미리보기 = B05 · B06 명세 폼에 고친 이름 · 빈 이름은 저장 막음 · [저장] = 새 판 + 이력(48장 길 그대로)
- 옛 편집기(옵션 · 배치 · 그룹 · 규칙 · 기준 측점)는 명세 종류에서 숨김 · 명세 없는 종류는 그대로
- 시험: test_m02_so_def_spec(정의 사본) · ui_checks/52-11_정의_이름고치기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
This commit is contained in:
2026-10-05 23:42:11 +09:00
co-authored by Claude Opus 5.5
parent 9f3a00041d
commit 103a207cee
9 changed files with 501 additions and 15 deletions
@@ -0,0 +1,67 @@
/* =============================================================================
* M02_SO_Def_Spec.ts
* 구조물 명세가 있는 종류(PLAN 52 · `STRUCTURE_SPECS`)의 이름 고치기 칸(52-11) — 나무 꼴
* 컨테이너 제목 › 항목 이름 › 값 이름 · 이름만 고침 · 키 · 필수/선택 · 꼴은 읽기만 ·
* 아래 미리보기 = B05 · B06 이 그리는 같은 명세 폼(`createSpecForm`)에 고친 이름을 얹음.
* 구조물군 · 종류 이름은 왼쪽 목록 · 머리 칸이 그대로 고침.
* ========================================================================== */
import { createTag, el } from "@ui/ui_template_elements";
import { createSpecForm } from "../../A00_Common/design/design_structure_form";
import type { StructureSpec } from "../../A00_Common/design/design_structure_spec";
import type { StructDef, TypeDef } from "./M02_SO_Def_Model";
import { setSpecName, specNameRows, type SpecNameRow } from "./M02_SO_Def_Spec_Model";
import { head, textBox } from "./M02_SO_Def_Ui";
import { to, type OptionsTextKey } from "./M02_SO_Text";
const TAG: Record<NonNullable<SpecNameRow["tag"]>, OptionsTextKey> = {
required: "SpecRequired",
optional: "SpecOptional",
select: "FormSelect",
number: "FormNumber",
auto: "FormAuto",
};
export function buildSpecBlock(opt: {
def: StructDef;
type: TypeDef;
spec: StructureSpec;
edit: boolean;
changed: () => void;
}): HTMLElement {
const { def, type, spec, edit, changed } = opt;
const rows = specNameRows(def, spec).map((row) =>
el("div", {
className: `m02-sod__specrow is-level-${row.level}`,
attrs: { "data-sod-spec": row.nameKey },
children: [
textBox(row.label, (v) => setSpecName(def, type.type_id, row, v), changed, {
edit,
attr: "data-sod-spec-name",
cls: "m02-sod__name",
}),
el("small", { className: "m02-opts__key", text: row.key }),
...(row.tag ? [createTag(to(TAG[row.tag]), "neutral")] : []),
],
}),
);
// 미리보기 — 값 없는 새 구조물 폼 · 잇기 줄 없음(조합은 프로젝트 몫)
const preview = createSpecForm(null);
const groups = Object.fromEntries((def.그룹[type.type_id] ?? []).map((g) => [g.id, g.이름]));
preview.write(spec, {}, def.종류[type.type_id]?.이름 ?? {}, groups);
preview.root.inert = true;
return el("div", {
className: "m02-sod__block",
attrs: { "data-sod-spec-block": "" },
children: [
...head(to("SpecHead"), to("SpecNote")),
...rows,
...head(to("SpecPreview")),
el("div", {
className: "m02-sod__specpreview",
attrs: { "data-sod-spec-preview": "" },
children: [preview.root],
}),
],
});
}