From e2a938f2cfe90fc4f38585f6804b6042202c911b Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 5 Oct 2026 00:28:37 +0900 Subject: [PATCH] =?UTF-8?q?fix(M02):=20=EC=83=81=EC=84=B8=20=EB=A8=B8?= =?UTF-8?q?=EB=A6=AC=20[=EB=8F=8C=EC=95=84=EA=B0=80=EA=B8=B0]=20=3D=20?= =?UTF-8?q?=EC=9D=BC=EB=B0=98=20=EB=8B=A8=EC=B6=94=20=C2=B7=20=EB=A8=B8?= =?UTF-8?q?=EB=A6=AC=20=EB=8B=A8=EC=B6=94=20=EB=8C=80=EB=B9=84=20=EC=8B=9C?= =?UTF-8?q?=EB=82=98=EB=A6=AC=EC=98=A4(43-5=20=C2=B7=20E1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 도면 · 일위대가 · 면적산출 상세 [돌아가기] = 공용 기본(filled · 32px · 머리 단추와 같은 높이 · ghost sm 아님) - E1 대비 1.11 = ORCA 탭에서 단추 color 전환이 멈춘 채 읽힌 착오 · 전환 끄고 재면 어두움 8.01 · 밝음 16.65 → 색 고침 없음 - 시나리오 resources/tester/ui_checks/43-5_돌아가기_대비.js · 시험 한 줄 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ --- .../M02_MasterTemplete_UI_Main.ts | 4 +- resources/tester/test_m02_structure_panel.py | 7 ++ .../tester/ui_checks/43-5_돌아가기_대비.js | 96 +++++++++++++++++++ 3 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 resources/tester/ui_checks/43-5_돌아가기_대비.js diff --git a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts index 13630fac5..77d6b3146 100644 --- a/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts +++ b/M02_MasterTemplete/M02_MasterTemplete_UI_Main.ts @@ -130,8 +130,8 @@ export function createMain( const title = el("h2", { className: "m02-main__title", text: L("M02_PickTemplate") }); const reload = createButton({ label: L("M02_Reload"), variant: "ghost" }); const save = createButton({ label: L("M02_Save"), variant: "filled" }); - // 구조물 도면 · 면적산출 상세 화면 → 구조물집계표(그 열 고른 채) - const back = createButton({ label: L("M02_Back"), variant: "ghost", size: "sm" }); + // 구조물 도면 · 일위대가 · 면적산출 상세 화면 → 구조물집계표(그 열 고른 채) · 일반 단추(43-5 · 알약 · ghost 아님) + const back = createButton({ label: L("M02_Back") }); const slot = el("div", { className: "m02-main__slot" }); // 도면 양식의 작도 영역 칸이 들어옴 const notice = el("div", { className: "m02-main__notice", attrs: { hidden: "" } }); const host = el("div", { className: "m02-main__host" }); diff --git a/resources/tester/test_m02_structure_panel.py b/resources/tester/test_m02_structure_panel.py index be79312be..d73ce71e0 100644 --- a/resources/tester/test_m02_structure_panel.py +++ b/resources/tester/test_m02_structure_panel.py @@ -238,3 +238,10 @@ def test_상세_화면_돌아가기는_집계표로_그_열_고른_채(): body = main.split('back.addEventListener("click"')[1] assert "confirmLeave()" in body and "STRUCTURE_TABLE" in body assert "sheet?.selectColumn(column)" in body + + +def test_상세_머리_돌아가기는_일반_단추(): + """43-5 — 도면 · 일위대가 · 면적산출 상세 머리 [돌아가기] = 공용 기본 단추(filled · 같은 높이 · + 알약 · ghost · sm 아님) · 대비는 ui_checks/43-5 시나리오(어두움 8.01).""" + main = _text(MAIN) + assert 'const back = createButton({ label: L("M02_Back") });' in main diff --git a/resources/tester/ui_checks/43-5_돌아가기_대비.js b/resources/tester/ui_checks/43-5_돌아가기_대비.js new file mode 100644 index 000000000..7207f3c96 --- /dev/null +++ b/resources/tester/ui_checks/43-5_돌아가기_대비.js @@ -0,0 +1,96 @@ +/* 43-5 · E1 — 도면 · 일위대가 · 면적산출 상세 머리 [돌아가기] 꼴 · 머리 단추 글자 대비(밝음 · 어두움) + * 시작 화면: M02 마스터 템플릿 · 마스터 층(#/m02-master-template?open=구조물조합&root=master) · 시스템 관리자 + * 부르는 법: orca eval --expression "$(cat 이 파일)" (async 식 · JSON 글을 돌려줌) + * 기대 수치: + * - 세 상세 모두 back.cls = "ui-btn ui-btn--filled"(ghost · sm 없음) · back.h = 머리 다른 단추 높이(±1px) + * - 밝음 · 어두움(앱 ☾ 단추) 모두 머리 보이는 단추 대비 ≥ 4.5 + * - 끝나면 처음 테마로 되돌림(theme.end = theme.start) + * ⚠ 단추 color 전환(120ms)이 ORCA 탭에선 0ms 에 멈춰 옛 색이 읽힘(1.11 착오 · E1) — 재는 동안 전환을 끔 + */ +(async () => { + const w = (ms) => new Promise((r) => setTimeout(r, ms)); + const rgb = (s) => (s.match(/[\d.]+/g) || []).map(Number); + const lum = ([r, g, b]) => { + const f = (v) => ((v /= 255) <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4); + return 0.2126 * f(r) + 0.7152 * f(g) + 0.0722 * f(b); + }; + /** 실제로 보이는 바탕 — 투명이면 조상으로 */ + const bgOf = (el) => { + for (let e = el; e; e = e.parentElement) { + const c = rgb(getComputedStyle(e).backgroundColor); + if (c.length >= 3 && (c.length < 4 || c[3] > 0.5)) return c.slice(0, 3); + } + return [255, 255, 255]; + }; + const ratio = (el) => { + const a = lum(rgb(getComputedStyle(el).color).slice(0, 3)); + const b = lum(bgOf(el)); + return +((Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05)).toFixed(2); + }; + const theme = () => document.documentElement.getAttribute("data-theme") || "auto"; + const moon = () => + [...document.querySelectorAll("button")].find((b) => /^[☾☀]$/.test(b.textContent.trim())); + const headButtons = () => + [...document.querySelectorAll(".m02-main__head button")].filter( + (b) => !b.hidden && b.offsetParent, + ); + const openRow = async (sectionTitle, prefix) => { + const sec = [...document.querySelectorAll(".m02-side__summary > section")].find( + (s) => s.querySelector("h3").textContent.trim() === sectionTitle, + ); + sec.classList.remove("is-collapsed"); + [...sec.querySelectorAll("button")].find((b) => b.textContent.startsWith(prefix)).click(); + await w(5000); + }; + const measure = () => { + const back = [...document.querySelectorAll(".m02-main__head button")].find( + (b) => b.textContent.trim() === "돌아가기", + ); + const others = headButtons().filter((b) => b !== back); + return { + title: document.querySelector(".m02-main__title")?.textContent, + back: back && { + cls: back.className, + h: Math.round(back.getBoundingClientRect().height), + radius: getComputedStyle(back).borderRadius, + }, + otherH: others.map((b) => Math.round(b.getBoundingClientRect().height)), + contrast: Object.fromEntries( + headButtons().map((b) => [b.textContent.trim() || b.title, ratio(b)]), + ), + }; + }; + const still = document.createElement("style"); + still.textContent = "*,*::before,*::after{transition:none!important}"; + document.head.append(still); + const start = theme(); + const out = { theme: { start }, pages: {} }; + for (const [name, sec, prefix] of [ + ["도면", "구조물 도면", "SD-000001"], + ["일위대가", "일위대가", "SU-000001"], + ["면적산출", "면적산출", "SA-000001"], + ]) { + await openRow(sec, prefix); + const light = measure(); + moon()?.click(); + await w(400); + const dark = { theme: theme(), ...measure() }; + moon()?.click(); + await w(400); + out.pages[name] = { light, dark }; + } + if (theme() !== start) moon()?.click(); + out.theme.end = theme(); + still.remove(); + const all = Object.values(out.pages).flatMap((p) => [ + ...Object.values(p.light.contrast), + ...Object.values(p.dark.contrast), + ]); + out.minContrast = Math.min(...all); + out.backOk = Object.values(out.pages).every( + (p) => + p.light.back?.cls === "ui-btn ui-btn--filled" && + p.light.otherH.every((h) => Math.abs(h - p.light.back.h) <= 1), + ); + return JSON.stringify(out); +})();