refactor(B07): 횡단 덧그림 서버 묶음이 B06 화면 허브를 끄지 않게 함 (PLAN 574-1 · 요구 418)

- Overlay 가 설계선 · 포장 그리기를 허브(_UI_Cross_Design) 대신 _Design_Line · _Design_Surface 에서 직접 가져옴
- 허브가 상세 창 · CAD · API · 부품 상자를 끌던 사슬 걷힘 — 묶음 482 → 240 KB
- 구조물 그림 여섯은 이미 logic 기하만 씀 · SVG 그리기는 B07 이 SVG → CAD 로 옮기므로 그대로 둠 · 가짜 DOM 계속 필요
- 지킴 시험 test_574 — 묶음 입구에서 닿는 TS 에 화면 코드 없음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 21:36:42 +09:00
co-authored by Claude Opus 5.5
parent 76bb0c3fd3
commit 4910fbaccc
2 changed files with 71 additions and 4 deletions
@@ -19,10 +19,9 @@ import { appendFordOverlay } from "../B06_Section/B06_Section_UI_Cross_Ford";
import { appendFordPavementOverlay } from "../B06_Section/B06_Section_UI_Cross_Ford_Pavement";
import { appendUnderdrainOverlay } from "../B06_Section/B06_Section_UI_Cross_Underdrain";
import { groundLine } from "../B06_Section/logic/B06_Section_Cross_Wall_Hatch_Geom";
import {
appendCrossDesignOverlay,
appendPavementOverlay,
} from "../B06_Section/B06_Section_UI_Cross_Design";
// 허브(`_UI_Cross_Design`)가 아닌 그리기 파일에서 직접 — 허브는 상세 창 · CAD · API 를 끈다(574-1)
import { appendCrossDesignOverlay } from "../B06_Section/B06_Section_UI_Cross_Design_Line";
import { appendPavementOverlay } from "../B06_Section/B06_Section_UI_Cross_Design_Surface";
import { appendRevetmentOverlay } from "../B06_Section/B06_Section_UI_Cross_Revetment";
import { clipPointsToPolygon, clipSegment, pointInPolygon } from "../A00_Common/b_polygon_clip";
@@ -0,0 +1,68 @@
"""B07 횡단 덧그림 서버 묶음(`b07-extent`)이 화면 코드를 끌지 않음 (574-1).
묶음 입구에서 값 import 를 따라가 닿는 TS 파일에 확인 창 · CAD · 상세 창 · API ·
부품 상자 · B06 허브(`_UI_Cross_Design`)가 없어야 함. `import type` 은 지워지므로 뺌.
"""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
ENTRY = ROOT / "B07_DesignDetail" / "B07_DesignDetail_Cross_Extent_Node.ts"
ALIAS = {"@util/": ROOT / "common_util", "@ui/": ROOT / "ui_template"}
_IMPORT = re.compile(
r"^\s*(?:import|export)\s+(type\s+)?(?:([^;]*?)\s+from\s+)?[\"']([^\"']+)[\"']",
re.M | re.S,
)
FORBIDDEN = re.compile(
r"ui_template|_Cad|Cross_Detail|_Api_|B06_Section_UI_Cross_Design\.ts$|three"
)
def _type_only(names: str | None) -> bool:
inner = (names or "").strip()
if not inner.startswith("{"):
return False
parts = [p.strip() for p in inner.strip("{} \n").split(",") if p.strip()]
return bool(parts) and all(p.startswith("type ") for p in parts)
def _resolve(origin: Path, spec: str) -> Path | None:
for prefix, base in ALIAS.items():
if spec.startswith(prefix):
target = base / spec[len(prefix) :]
break
else:
if not spec.startswith("."):
return None # node:fs · 라이브러리
target = origin.parent / spec
for suffix in (".ts", ""):
path = Path(str(target) + suffix).resolve()
if path.is_file():
return path
return None
def _reachable() -> set[Path]:
seen, queue = {ENTRY.resolve()}, [ENTRY.resolve()]
while queue:
current = queue.pop()
if current.suffix != ".ts":
continue
for match in _IMPORT.finditer(current.read_text(encoding="utf-8")):
type_kw, names, spec = match.groups()
if type_kw or _type_only(names):
continue
found = _resolve(current, spec)
if found and found not in seen:
seen.add(found)
queue.append(found)
return seen
def test_extent_bundle_pulls_no_screen_code():
reached = _reachable()
names = {p.relative_to(ROOT).as_posix() for p in reached}
assert "B07_DesignDetail/B07_DesignDetail_Cross_Overlay.ts" in names # 걷기가 실제로 돎
bad = sorted(n for n in names if FORBIDDEN.search(n))
assert not bad, bad