feat(structure): 54-43 빈 칸 = 그냥 빈 칸 · 필수 칸 빈 보기 없음 · 빈 옛 값 = 기본값

- 공용 명세 폼: 필수 상자 고르기 칸 빈 보기 없음 · 빈 옛 값 = 정의 기본값(없으면 첫 보기)을 실제 값으로(저장도 그 값) · 선택 상자 빈 보기 = 빈 글 · 숫자 칸 자리표시 글 걷음
- 시설 폼: 배관 · BOX · 세월교 · 물넘이 포장에 정의 기본값 전달 · 독립 기슭막이 설치 측 · 형태 빈 보기 없음 · 높이 · 추가 흙막이(단) · 월류 높이 자리표시 글 걷음
- 일반 구조물 칸: 필수 · 기본값 있는 고르기 칸 빈 보기 없음 · 그 밖 빈 보기 = 빈 글 · 「필수 입력」 자리표시 걷음 · 규칙 선택지 좁힘이 빈 보기 없는 칸도 다룸
- 기준 측점 이름표 「(비우면 시작)」 걷음
- 시험: 기슭막이 높이 칸 = 자리표시 없음 · 화면 점검 ui_checks/54-43

화면 확인(sub5_검증 · B05 구조물 20개): 「선택 안 함」 · 「안 정함」 · 「비우면」 · 「기본」 0 · 필수 칸 빈 보기 0 · 필수 칸 빈 값 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
This commit is contained in:
2026-10-06 14:51:42 +09:00
co-authored by Claude Opus 5.5
parent 5f809b6761
commit d02f7033e1
9 changed files with 128 additions and 70 deletions
@@ -42,6 +42,12 @@ def test_TS_가_쓰는_칸이_파일에_있다() -> None:
def test_B05_높이_칸은_제안_없이_설계자_입력() -> None:
# 독립 기슭막이 높이 = 기본값 없음 · 「설계자 입력」 안내(제안값 단추 걷음 · PLAN 46-7)
assert 'createRevetmentFields(keys, { height: "" })' in FIELDS and '"설계자 입력"' in FIELDS
# 독립 기슭막이 높이 = 기본값 없음 · 빈 칸 그대로(54-43 · 자리표시 글 없음)
assert (
"createRevetmentFields(keys)" in FIELDS
and "placeholder"
not in FIELDS.split("createRevetmentFields(keys: RevetmentKeys)")[1].split(
"createBasinSplitFields"
)[0]
)
assert "기슭막이 높이 없음 — 근입만 그림" in CHROME
@@ -0,0 +1,48 @@
/* 54-43 — 구조물 배치 상자: 빈 칸 = 그냥 빈 칸 · 필수 칸 빈 보기 없음 · 빈 옛 값 = 기본값
* 시작 화면: B05 종단설계(#/b05-profile) 또는 B06 횡단설계(#/b06-section) · 검증 프로젝트(sub5_검증) · 로그인
* 부르는 법: 한 번 — 결과 `sessionStorage.getItem("__5443_out")`
* 목록의 구조물 전부를 차례로 열어 「구조물 배치」 상자 글 · 보기 글 · 자리표시 글을 셈
* 쓰는 것: 없음(열기만 · 저장 없음)
* 기대 수치: words = 0 · requiredBlank = 0 · blankText = 0 · requiredEmpty = 0 · optionalBlank > 0(선택 칸 빈 보기 그대로)
*/
(async () => {
const w = (ms) => new Promise((r) => setTimeout(r, ms));
const txt = (el) => el.innerText.replace(/\s+/g, " ").trim();
const WORDS = /선택 안 함|안 정함|비우면|기본/;
const OPTIONAL = new Set(["soil_add", "chute_add", "chute"]);
const out = { rows: 0, words: [], requiredBlank: [], blankText: [], requiredEmpty: [], optionalBlank: 0, seen: {} };
for (let i = 0; i < 50 && !document.querySelector(".b05-route__irregular-list li"); i++) await w(200);
const rows = [...document.querySelectorAll(".b05-route__irregular-list li")];
for (const row of rows) {
const head = txt(row).slice(0, 24);
row.click();
await w(1200);
const panel = document.querySelector(".b05-structure-section");
if (!panel) continue;
out.rows += 1;
const shown = (el) => !!el.offsetParent;
// 보이는 글 · 고르기 보기 글 · 자리표시 글
const texts = [
txt(panel),
...[...panel.querySelectorAll("select")].filter(shown).flatMap((s) => [...s.options].map((o) => o.text)),
...[...panel.querySelectorAll("input")].filter(shown).map((i) => i.placeholder),
];
for (const t of texts) {
const hit = t.match(WORDS);
if (hit) out.words.push(`${head}: …${t.slice(Math.max(0, hit.index - 12), hit.index + 14)}…`);
}
for (const sel of [...panel.querySelectorAll("select")].filter(shown)) {
const wrap = sel.closest("[data-spec-item]");
const box = sel.closest("[data-spec-container]");
const blank = [...sel.options].find((o) => o.value === "");
const name = wrap?.dataset.specItem ?? sel.closest("[data-option-keys]")?.dataset.optionKeys ?? "?";
out.seen[name] = (out.seen[name] ?? 0) + 1;
if (blank && blank.text !== "" && !wrap?.dataset.specShown) out.blankText.push(`${head}: ${name} 「${blank.text}」`);
if (box && !OPTIONAL.has(box.dataset.specContainer)) {
if (blank && !wrap?.dataset.specShown) out.requiredBlank.push(`${head}: ${name}`);
if (!sel.value && !wrap?.dataset.specShown) out.requiredEmpty.push(`${head}: ${name}`);
} else if (blank) out.optionalBlank += 1;
}
}
sessionStorage.setItem("__5443_out", JSON.stringify(out));
})();