chore(조사): 요구 280 분석 여섯 — B06 정보 칸 · 드롭다운 · 페이지 이동 · 노선 바뀜 구조물 측점 · 스냅 특별측점 · B07 설정 · CAD 동작 · DWG

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-19 21:32:43 +09:00
co-authored by Claude Opus 5.5
parent b132c893bd
commit 47b0642f5b
6 changed files with 1111 additions and 0 deletions
@@ -0,0 +1,184 @@
# 요구 280 A — B06 횡단 카드 왼쪽 아래 정보 칸(280-1) · 전역 드롭다운 초점(280-6) — 분석
분석 = 클로드 에이전트(Opus 5.5 · 읽기만) · 기준 = `main_laptop_1` `b132c893b` · 추정은 (추정).
## 0. 결론
- 280-1 — 왼쪽 아래 칸 = 선 공용 칸(370-3). 처음부터 암이 뜨는 까닭 = 「안 고름 + 암 지반 → 암 칸」 갈래 하나(`B06_Section_UI_Cross_Design_Pick.ts:632`). 고름 저장 · 세션 복원 탓 아님(고름 = 모듈 변수 · 새로고침이면 사라짐).
- 280-1 고침 = 그 갈래 걷음(한 줄) → 안 고르면 빈 칸 · 암 정보는 점선을 눌러 고를 때만.
- B05 우측 횡단 판(393) = 같은 카드 부품이나 발 줄을 통째 걷어 둠 → 화면 변화 없음.
- 280-6 — 드롭다운은 전부 브라우저 기본 `<select>`(직접 만든 값 드롭다운 없음 · 휠로 값 바꾸는 우리 코드 없음). 원인 = 고른 뒤 초점이 select 에 남음 → ↑↓ · 휠이 값을 바꾸고 페이지 · CAD 단축키는 「초점이 select 면 비킴」 막이로 끊김.
- 280-6 고침 = 함수 하나(창 단위 듣기 · 마우스로 고른 change 뒤 blur) + 부르는 자리 셋(SPA `main.ts` · CAD iframe `cad_host.ts` load · 종단 새 창 `design_profile_popout.ts`). 기존 `blockNativeMenu(win, …)` 꼴 그대로 · CAD 앱 소스 · 빌드 손 안 댐.
## 1. 280-1 지금 동작
### 1.1 그리는 자리
| 걸음 | 자리 |
|---|---|
| 카드 발 줄 = 왼쪽 선 공용 칸 + 오른쪽 암 절토 경사 | `B06_Section_UI_Cross_Card_Chrome.ts:163-191` |
| 칸 부품(제목 · ◀▲↺▼▶ · 값 한 줄 · null = `is-empty` 숨김 · 띠 높이 유지) | `B06_Section_UI_Cross_Line_Cell.ts:74-139` · CSS `B06_Section_UI_Style_Cross.css:655-662` |
| 카드가 고름 배선 · 칸을 발 줄로 넘김 | `B06_Section_UI_Cross_View.ts:597-611 · 683` |
| 칸 내용 정함(`setActive`) | `B06_Section_UI_Cross_Design_Pick.ts:614-669` |
| 암 칸 제원(제목 「암 경계」 · 오프셋 · ▲▼ 0.1 m · ↺) | `B06_Section_UI_Cross_Design.ts:222-233` |
| 설계선 고름이 없는 카드(그림 없음 · `metrics` null)의 암 칸 | `Card_Chrome.ts:177-182` → `Cross_Design.ts:210-219` |
### 1.2 처음부터 암이 뜨는 까닭
- `Design_Pick.ts:632` `onRock || (!chosen && rock) ? rockCellState(rock)` — 안 고름 + 암 지반(`geometry_preset === "rock"` · `designLineMoves` `:389`)이면 암 칸.
- 설계 없는 측점 기본 지반 = 암(`Cross_Design.ts:83 · 255` `GROUND_ROCK_DEFAULT`) → 거의 모든 카드에 암 칸.
- 토사 = 이미 빈 칸.
### 1.3 무엇을 고르면 무엇이 뜨나(`setActive` `:614-669`)
| 고름 | 제목 | 값 | 단추 | 방향키 |
|---|---|---|---|---|
| 없음 · 암 지반 | 암 경계 | ±0.0m | ▲↺▼ | 안 붙음(▲▼ = 종단 계획고) |
| 없음 · 토사 | 빈 칸 | | | |
| 암 경계선 점선(키 `rock`) | 암 경계 | ±0.0m · 끄는 중 「… — 놓으면 반영」 | ▲↺▼ · 세로 끌기 | ▲▼ = 암 · ◀▶ 막음 |
| 중심점 | 중심점 | 「▲▼ = 종단 계획고 · 좌우 이동은 평면에서」 | 없음 | ▲▼ 종단으로 |
| 교차점 손잡이 | 사면 이동 (좌/우) | (절·성토 없음 ·) 노선에서 n.nnm · 이동 | ◀↺▶ | ◀▶ |
| 노면 · 노견 | 노면 높이 | 치수 · ▲▼ = 종단 계획고 | 없음 | ▲▼ 종단으로 |
| 소단 수평 단 | 소단 높이 (좌/우) | 도로 아래 n.nm · 치수 | ▲↺▼ | ▲▼ |
| 사면 · 구조물 트림 선 | 사면 이동 (좌/우) | 치수 · 이동 | ◀↺▶ | ◀▶ |
| 벽 · 본체 · 면적 | 선 고름 풀림(`dropLine`) → 「없음」 줄 | | | |
- 풂 = 같은 것 다시 누름(`Cross_View.ts:309-327`) · 다른 고름(`:274-275 · 303 · 314`) · 카드 고름 빠짐(`applySelection` `:258-280`) → `setActive(null)` → 「없음」 줄.
### 1.4 세션 · 새로고침
- 고름 = 모듈 변수 `picked`(측점 + 조각 키 · `Design_Pick.ts:295-315`) — sessionStorage 안 씀 · 카드 다시 그림에만 되살림 · 고른 카드 아니면 풂(`Cross_View.ts:329-330`).
- 새로고침 뒤 암 칸 = 고름 복원 아님 · 1.2 기본 갈래.
- 암 오프셋 값 = 세션 `aislo:draft:rockb:{p}:{r}`(`B06_Section_UI_Page_Persist.ts:179-213`) — 값 층 · 이번 고침과 무관.
### 1.5 B05 우측 횡단 판(393)
- 같은 부품(`createCrossSectionCard`) · 암 제어기 = 읽기 전용(`B05_Profile_UI_RouteEdit_Cross_Card.ts:51-57`).
- 머리 · 발 줄 · 줌 단추 통째 걷음(`:150`) · 누름 막음(`:152`) → 고침 뒤에도 화면 그대로 · 시험만 확인.
## 2. 280-1 고칠 자리 · 방법
- 필수(한 줄) — `Design_Pick.ts:632` `onRock || (!chosen && rock)` → `onRock`. 안 고름 = 끝 갈래 `!piece ? null` 로 빈 칸.
- 방향키 · 끌기 그대로 — 안 고름 때 이미 안 붙음(`open` `:658-662` · 끌기 `active: axis === "rock"` `:547`).
- 같이(일관 · 작음) — `Card_Chrome.ts:177-182` else 갈래 걷음(그림 없는 암 카드의 암 칸) → `Cross_Design.ts:210-219` `buildRockBoundaryControl` 안 쓰임 → 걷음 · import(`Card_Chrome.ts:14`) 정리.
- 주석 정리 — `Line_Cell.ts:5` · `Design_Pick.ts:8 · 447 · 631` · `Card_Chrome.ts:162 · 168` · `Cross_Design.ts:210 · 221`.
- 결과 — 암 조정 = 점선 누름 → ▲▼↺ · 끌기(누름 한 번 늚). 오른쪽 「암 절토 1:0.40 ↺」 칸 그대로(6장 4).
## 3. 280-6 지금 동작
### 3.1 드롭다운 종류 · 개수(만드는 자리 수 · 고리 안 생성은 하나로 셈)
| 자리 | 수 | 꼴 |
|---|---|---|
| A00_Common | 18 | spreadsheet 대화상자 · 도구줄 · 구조물 폼 `design/design_structure_form.ts:134`(B05 · B06 공용) |
| ui_template | 1 | `createSelectField`(`ui_template_elements.ts:155-184`) — 34 파일이 씀 |
| B01 4 · B02 2 · B04 3 · B05 12 · B06 2 · B07 3 · B08 7 · B09 1 | 34 | createElement · createSelectField · innerHTML `<select>` |
| CAD(openwebcad · iframe · React) | 7 | `RibbonWidgets.tsx:87 · 106 · 135 · 180` · `PropertiesEditor.tsx:53 · 71 · 90` |
| M01 12 · M02 40 | 52 | createSelectField · `el("select")` |
- 합 ≈ 110 자리 · 모두 기본 `<select>`.
- 직접 만든 값 드롭다운 없음. 비슷한 것 = 펼침 단추 · 메뉴(CAD `DropdownButton.tsx` · `inputController/snap-menu.ts` · `ui_template_context_menu.ts` · spreadsheet 메뉴) · 찾기 목록(`ui_template_find_list.ts` · `M02_DF_UnitPick.ts`) — 단추 · 글 칸이라 휠 · ↑↓ 로 값 안 바뀜 · 범위 밖.
- 휠로 select 값 바꾸는 우리 코드 없음(`wheel` 듣기 17 곳 = 확대 · 팬 · 모달 막음).
### 3.2 원인
- 고른 뒤 초점이 select 에 남음(브라우저 기본).
- 닫힌 select 에 초점 = ↑↓ · 글자 키가 값 바꿈(+ change) · Windows Chromium 은 휠도 값 바꿈(추정 · 화면 재현으로 확인).
- 페이지 단축키는 초점이 select 면 비킴 — `B05_Profile_UI_RouteEdit_Keys.ts:122` · `B05_Profile_UI_Profile_Panel_Tools.ts:318 · 369` · `B05_Profile_UI_RouteEdit_StructureMarks.ts:378` · `B06_Section_UI_Cross_Panel_Base.ts:282` · `B06_Section_UI_Cross_Design_Pick.ts:605` · `B06_Section_UI_Cross_Structure_Panel.ts:454` · `A00_Common/design/design_pick_escape.ts:20` · `ui_template_loupe.ts:82` · `B09_Estimation_UI_Tabs_Groups.ts:249` · `M02_DF_Diagram.ts:63` · CAD `input-controller.ts:376`(명령 글 · Esc · Delete 안 먹음) · `dxf-clip-paste.ts:78` · `ime-sink.ts:73`.
- CAD 덧 — 부모 `cad_host.ts:154-158` 마우스가 CAD 위로 와도 부모 select 에 초점이면 초점 안 넘김.
### 3.3 문서(창)는 셋
| 창 | 진입 | 기존 같은 꼴 |
|---|---|---|
| SPA — 모든 A · B · M 페이지(`A00_Common/index.html` 하나) | `A00_Common/main.ts:58-62` | `blockNativeMenu(window, …)` · `initLoupe(window, …)` |
| CAD iframe — B07 상세 · M02 도면 양식(같은 출처 `/b07-cad/`) | `ui_template/cad_host/cad_host.ts:150-152` load | `blockNativeMenu(frame.contentWindow, …)` |
| 종단 하단 새 창 — 종단도 · 표 · 유토 · 배수유역(select 있음) | `A00_Common/design/design_profile_popout.ts:63-113` | 키 · 포인터만 메인으로 넘김(`FORWARD_TYPES` · change 안 넘김) |
## 4. 280-6 고치는 방법(가장 작은 고침)
- `ui_template/ui_template_native_menu.ts`(21 줄 · 「브라우저 기본 동작 손봄」)에 함수 하나 더 · 머리 주석 갱신. 새 파일 안 만듦(vite 새 파일 차례 걱정 없음 · 두 진입이 이미 이 파일을 부름).
- 창 캡처 `pointerdown` → 마지막 입력 = 마우스 · `keydown` → 키보드.
- 창 캡처 `change` · 대상 `tagName === "SELECT"`(iframe 은 다른 realm — `instanceof HTMLSelectElement` 금지) · 마지막 입력 ≠ 키보드 → `setTimeout(0)` 뒤 그 select 가 아직 `ownerDocument.activeElement` 면 `blur()`.
- 한 틱 뒤 + activeElement 확인 = 페이지 change 처리(다시 그림 · 다른 칸 초점 · 모달 엶)가 먼저 끝남 · 이미 옮긴 초점 안 건드림 · 캡처라 stopPropagation 에 안 막힘.
- 처음 값 = 마우스(손가락 · `orca select` · 스크립트 change 도 풂).
- 부르는 자리 셋(한 줄씩) — `main.ts`(window) · `cad_host.ts` load(`frame.contentWindow`) · `design_profile_popout.ts` `open()`(`next`).
- CAD 앱 소스 · 빌드 안 건드림 — `RibbonWidgets.tsx` · `PropertiesEditor.tsx`(280-6 다른 항목 · 「선 두께」 · 「레이어」 이름 · 섞임 빈 칸)와 안 겹침.
- 풀린 뒤 — 초점 = body · CAD 는 글 물음 때 `ImeSink.sync` 가 받개로 초점 · 페이지 단축키 살아남(B06 ▲▼ = 고른 측점 계획고 등 · 의도).
## 5. 초점에 기대는 코드 · 깨질 위험
| 자리 | 고침 뒤 | 판정 |
|---|---|---|
| B08 이정 다리 편집 상자(`B08_Quantity_UI_Milestone_Diagram.ts:80-123` · 도로 구분 select + km 칸 · 상자 밖 focusout = 저장 · 닫힘) | 도로 고르면 곧 저장 · 상자 닫힘(km 고치려면 다시 엶) | 6장 3 |
| 모달 닫힘 초점 되돌림(`ui_template_elements.ts:330` · `ui_template_leave_dialog.ts:22` · `spreadsheet_link_ask.ts:26`) | select change 가 모달을 열면 닫힐 때 select 로 초점 돌아옴 → 그 경우만 남음 | 낮음 · 그대로 |
| B06 구조물 폼 초점 되살림(`B06_Section_UI_Page_Structures_Selection.ts:146-166`) | blur 틱이 먼저 · 되살림은 초점 칸 없으면 안 함 | 없음 · 화면 확인 |
| B05 구조물 위치 입력군 focusout 확정(`B05_Profile_UI_Structures_Panel_Events.ts:231-238`) | 입력군 안 select 없음(`B05_Profile_UI_Structures_Form.ts:106-108`) | 없음 |
| 표 편집 focusout(`ui_template_data_table.ts:536`) · M01 · M02 칸 blur 확정 | input 만 | 없음 |
| B05 되돌림 기록 창 change · keydown 듣기(`B05_Profile_UI_Profile_History.ts:131-148`) | blur 는 change 안 냄 | 없음 |
| 키보드 사용자(Tab 으로 와서 ↑↓) | 그대로 머묾(키보드 갈래 안 풂) | 없음 |
| 마우스로 펼친 뒤 ↑↓ + Enter | 마지막 입력 = 키보드 → 안 풂 | 의도 |
| Tab 다음 칸 | blur 뒤 Tab = 그 select 다음 칸(Chromium 순차 탐색 시작점 · 추정) | 화면 확인 |
## 6. 사용자 결정 갈래
| # | 물음 | 선택지 | 추천 |
|---|---|---|---|
| 1 | 키보드로 고른 때도 초점 뺌? | A 마우스로 고른 때만 · B 모든 고름(↑ 한 번에 초점 빠짐 · 키보드 훑기 깨짐) · C 마우스 + Enter | A |
| 2 | 숫자 칸(`type=number`)도 같이? | 휠 · ↑↓ 로 값 바뀌는 같은 꼴 · 이번엔 드롭다운만 / 같이 | 드롭다운만(요구대로) · 원하면 같은 함수 한 줄 |
| 3 | B08 이정 상자 | a 도로 고르면 곧 저장 · 닫힘(「입력 후 빠져나옴」 · 코드 0) · b 도로 고른 뒤 km 칸으로 초점(B08 한 줄) | a |
| 4 | 280-1 오른쪽 「암 절토 1:0.40 ↺」 칸 | 그대로(요구 = 왼쪽 칸) · 암을 고를 때만 | 그대로 |
| 5 | 280-1 벽 · 본체 · 면적 고름 때 왼쪽 칸 | 빈 칸(제 조정창 · 면적 표가 따로) · 그 정보도 칸에 | 빈 칸 |
## 7. 코더 나눔
| 일감 | 파일 | 겹침 | 모델 |
|---|---|---|---|
| 280-1 | `B06_Section_UI_Cross_Design_Pick.ts` · `_Cross_Card_Chrome.ts` · `_Cross_Design.ts`(죽은 함수) · `_Cross_Line_Cell.ts`(주석) · 시험 370-3 · 318 | 280-2(B06 좌측 패널 ? < > · 노선보기 · 횡단보기 걷음)와 파일 안 겹침 | Sonnet |
| 280-6 드롭다운 | `ui_template_native_menu.ts` · `A00_Common/main.ts` · `ui_template/cad_host/cad_host.ts` · `A00_Common/design/design_profile_popout.ts` · 새 시험 | `cad_host.ts` = 280-6 CAD 화면 정리(머리 줄 · 탭 줄 · 버튼 걷음)가 만질 수 있음 → load 한 줄이라 작음 · 같은 창에 묶거나 먼저 끝냄 · `main.ts` = 280-2 페이지 이동이 진입을 만지면 조심 | Opus(전역 · 여러 페이지 화면 확인) |
- 창 둘 병행 가능(서로 파일 안 겹침) · 둘 다 작아 한 창에 차례로도 됨.
## 8. 시험 · 화면 확인
### 8.1 280-1
- 고쳐 씀 — `test_370_3_rock_cell.py` + `helper_370_3_rock_cell.cjs`: `test_안_고르면_공용_칸은_암_버튼과_값` → 안 고름 = `empty True` · 슬롯 [] · 숨은 단추 눌러도 오프셋 [] · ▲ 종단으로 그대로 · `test_풀면_암_칸_사면_고르면_그_선_칸` → 풀면 빈 칸. 판정 선 · 암 고름 · 끌기 · 토사 그대로.
- else 걷으면 `test_318_cross_card.py::test_footer_without_center_text` 고쳐 씀(선 칸 없는 암 카드 = 암 절토 칸만) · helper `buildRockBoundaryControl` 흉내 걷음.
- 이웃 — `test_137_design_pick.py` · `test_139_6_line_shift.py` · `test_194_1_berm_depth.py` · `test_356_b06_card_center.py` · `test_370_2_toe_stop.py` · `test_179_1_line_dims_left.py` · `test_440_cross_draft_pick.py` · B05 판 `test_b05_routeedit_chrome_modal.py`.
- `npm run typecheck`.
- 화면(B06 · 자기 포트 · 검증 프로젝트 · 수치):
- 열자마자 `.b06-cross-card__linecell:not(.is-empty)` = 0.
- 암 점선(`.b06-chart__rock-hit`) 누름 → 그 칸 `is-empty` 없음 · 제목 「암 경계」 · ▲ 한 번 = 세션 `aislo:draft:rockb:{p}:{r}` 그 측점 +0.1 → ↺ 로 되돌림.
- 사면 누름 → 「사면 이동 (좌|우)」 · 다시 누름 → `is-empty`.
- 새로고침 → `:not(.is-empty)` = 0.
- B05 우측 판 — 카드 안 `footer` = 0(그대로).
### 8.2 280-6
- 새 — `resources/tester/test_280_6_select_release.py` + `helper_280_6_select_release.cjs`(`ui_template_native_menu.ts` 트랜스파일 · 가짜 창 · 가짜 select · 가짜 타이머):
1. pointerdown → change → 한 틱 뒤 blur 1
2. keydown → change → blur 0
3. 입력 없이 change → blur 1
4. INPUT change → blur 0
5. 처리가 초점을 옮김(activeElement ≠ 그 select) → blur 0
6. pointerdown → keydown → change → blur 0
7. 원본 — `main.ts` window · `cad_host.ts` load `frame.contentWindow` · `design_profile_popout.ts` `next` 부름
- 기존 — `test_common_native_menu.py`(같은 파일) · `test_180_3_b07_guide_toggle.py` · `test_m02_structure_panel.py`(cad_host 원본) · `test_282_1_profile_toolbar.py` · `test_354_1_masshaul_own_panel.py`(popout 원본).
- 화면(ORCA · 고치기 전 재현 먼저 · 수치):
- B05 좌측 설정 select 하나 `orca select` → `eval document.activeElement.tagName` = 고치기 전 SELECT · 뒤 BODY.
- 같은 select 위 `orca scroll` → 값 그대로(고치기 전 바뀌면 Windows 휠 재현 기록).
- 키보드 — eval focus → `orca keypress ArrowDown` → 값 바뀜 · activeElement 그대로 SELECT.
- B06 — select 고른 뒤 `ArrowUp` → 고른 측점 계획고 초안(세션) 생김 → 되돌림.
- B07 CAD — 리본 「선종류」 고른 뒤 iframe `contentDocument.activeElement.tagName` ≠ SELECT · 글쇠 `L` → 명령줄에 L.
- M01 · M02 · B08 select 하나씩 activeElement.
- 바꾼 값 모두 되돌림.
## 9. 위험
- Windows 휠 동작 = 브라우저 기본(추정) — 시험으로 못 잼 · 화면 재현으로만.
- `orca select` 가 키 이벤트로 고르는 꼴이면 마우스 갈래 판정이 안 섬 → eval 로 pointerdown + change 를 쏴 대신 잼(추정).
- popout 창 change 는 메인으로 안 넘어옴 → 새 창에 따로 붙여야 함(빠뜨리면 배수유역 창 select 만 남음).
- `cad_host.ts` load 는 iframe 문서가 새로 뜰 때마다 붙음 → 새 문서라 중복 없음.
- 280-1 — 암 조정 누름 한 번 늚 · 점선 판정이 설계선 판정 아래라 사면 발치 겹친 자리는 설계선이 먼저 골라짐(`Design_Pick.ts:534-535`).
- `Design_Pick.ts` 672 줄 · `Cross_View.ts` 685 줄 — 280-1 고침은 줄어드는 쪽 · 덧붙이면 700 줄 넘김 조심.
@@ -0,0 +1,160 @@
# 요구 280-2 — B05~B09 좌측 아래 [?] [<] [>] 페이지 이동 (분석 · 코드 X)
## 1. 지금 동작
### 1-1. 좌측 패널 맨 아래 단추 줄
| 페이지 | 지금 줄(왼 → 오) | 만드는 곳 |
| --- | --- | --- |
| B05 · B06 | [?] [초기화] [저장] [확정] [횡단 보기 / 노선 보기] | 한 벌 `A00_Common/design/design_dock.ts:58-142`(두 보기 같은 노드) · 붙임 `design_panel.ts:114` · 동작 `design_shared.ts:338-352` |
| B07 | [?] [도각 편집] [현재 도면 확정 / 수정] | 페이지 틀 `B07_DesignDetail_UI_Page.ts:574-586` — CAD 앱(openwebcad) 밖 · iframe 옆 좌측 패널 |
| B08 | [?] [저장] | `B08_Quantity_UI_Options.ts:226-234` |
| B09 | (스위치 두 줄) + [?] [저장] [내역 초기화] | `B09_Estimation_UI_Dock.ts:88-105` · 단추 `B09_Estimation_UI_Shell.ts:303-342` |
| (B04 · 범위 밖) | [?] [확정] [초기화] | `B04_PreProcess_UI_Page.ts:303-308` |
- 줄 껍데기 = 공용 `.ui-sidebar-actions`(`ui_template_overlay.css:317` · 단추 고르기 `:374-385`) · 단추는 페이지마다 따로 만듦.
- 공용이 붙이는 것은 [?] 하나 — `splitSidebarActions`(`ui_template_overlay.ts:119-140`) → `placeHelpButton`(`ui_template_help.ts:165-168`)이 `[data-help-row]`(없으면 줄 자신) 맨 앞에.
### 1-2. [?] 설명서
- **B02~B09 모두 이미 있음** — 「? 없는 페이지 새로 만듦」은 할 것 없음 · 글만 넣으면 됨.
- 글 자리 = `resources/help/B0N/*.md` · 파일 이름 차례 = 모달 왼쪽 차례 · 첫 `# ` 줄 = 제목 · 서버 `common_util/common_util_help_router.py`(`GET /api/help/B0N` · 부를 때마다 읽음 → 메모장 고침이 바로 보임).
- B05 · B06 = 짝 폴더 합쳐 보임(`ui_template_help.ts:113-118`).
- 지금 글 = 모든 페이지 `00_설명서.md` 113 B 빈 틀(제목 「이 화면에서 하는 일 · 작업 순서 · 버튼 · 주의할 점」만) · B05 구조물 12 · B06 표준 횡단면 1.
- 새 항목 = 그 폴더에 `NN_이름.md` 하나 더함.
### 1-3. 페이지 이동
- ☰ 서랍 · 진행 단계 판 = **같은 단계 단추 한 벌**(`createStepBar` · `ui_template_workflow_layout.ts:57-163` · 서랍은 `setDrawerSteps` 로 빌려 감 `:203`).
- 누름 → 페이지마다 `onStepClick` → `goToWorkflowStage`(`b_workflow_nav.ts:110-125`).
- 프로젝트 넘김 = `localStorage` `CURRENT_PROJECT_ID_KEY` · 주소 인자 없음 · B04 · B05 행은 준비 화면 확인을 거침.
- 단추 켬 · 끔: 개발(DEV) = 모두 켬 · 운영 = NOT_STARTED · STALE 끔(`:107-118`) · B04 = 시스템 관리자만(`:119-131` · 끈 단추 title 「시스템 관리자 전용 단계입니다.」).
- B05 · B06 = 한 페이지(`router.ts:58-60` · `design_page.ts`) — 주소만 바뀌면 `showWithinPage`(`main.ts:71`) → 보기 숨김 · 보임만 · 다시 안 실음 · 캐시 공유.
- [보기 전환] = `navigateTo` (`design_shared.ts:349-351`).
- 진행 단계: B05 ↔ B06 = `navigateTo` · 그 밖 = `goToWorkflowStage` (`design_page.ts:182-186`).
- B07 `onStepClick`(`:600-610`) = 운영에서 B09 는 횡단도 모두 확정 뒤.
- B08 `onStepClick`(`B08_Quantity_UI_Page.ts:152`) · B09(`B09_Estimation_UI_Shell.ts:355`) = `goToWorkflowStage` 만 · 저장 없음 · B08 은 `stages` 를 안 넘김(단계 모두 켬).
### 1-4. 떠날 때
- B05 · B06: `guardDesignLeave`(`design_leave.ts:96-124`) — `beforeunload` + 라우터 떠남 물음(`main.ts:83-95`) · 고침 있으면 [취소] [폐기] [저장](`ui_template_leave_dialog.ts`) · B05 ↔ B06 사이는 안 물음.
- B07: 페이지 떠남 물음 없음 · 도면 바꿀 때만 `window.confirm`(`mayDiscardEdits` `:326`) · CAD 앱 자동백업(도면별 칸)은 있음.
- B08 · B09: 없음 — 고친 값은 세션 초안(`b08-options` · `b08-materials` · `b08-hydro` · `b08-milestone` · `b09-estimation` · `b_page_state.ts:198-206`)에 남음.
## 2. 저장 길 (「저장 후 이동」에 쓸 것)
| 페이지 | 함수 · 줄 | 주소 | 실패 때 | 고칠 점 |
| --- | --- | --- | --- | --- |
| B05 · B06 | `saveSharedDesign(shared, false)` `design_shared.ts:396` | 횡단 저장 한 줄(`saveDesign`) | 알림 · 안 던짐 | 그대로(떠남 물음이 씀) |
| B07 | `confirmCurrentDrawing` `B07_DesignDetail_UI_Page.ts:421-470` | `PUT /projects/{id}/design-drawings/{장}/confirm` | 알림만 | 저장 = **확정 뿐**(미확정 저장 길 없음) · `all_confirmed` 면 스스로 B08 로 감(`:457-460`) → 이동 길에서는 끔 · 성공 여부 돌려줌 |
| B08 좌측 | `saveAll` `B08_Quantity_UI_Options.ts:195-226` | `PUT …/quantity/options` + `saveMilestone` `PUT …/quantity/milestone` | 알림만 | 성공 여부 돌려줌 · 1본 빈칸이면 멈춤 |
| B08 자재 탭 | `saveAll` `B08_Quantity_UI_Material.ts:202-216` | `PUT …/quantity/materials` | 알림만 | 같음 · 탭 안 닫힌 값 |
| B08 수리 탭 | `saveAll` `B08_Quantity_UI_Hydro.ts:136-155` | `PUT …/quantity/hydro` | 알림만 | 같음 · 탭 안 닫힌 값 |
| B09 | `run` `B09_Estimation_UI_Shell.ts:279-301`(단추 `:303`) | `PUT /projects/{id}/estimation` `{user}` | 알림만 | 성공 여부 돌려줌 |
## 3. 고치는 방법 (가장 작은 길)
핵심 = **[<] [>] = 이웃 진행 단계 단추 누름**.
- 단추는 [?] 를 붙이는 한 곳이 붙임 · 누름은 단계 단추를 쥔 틀(`createWorkflowLayout`)이 받음.
- 켬 · 끔(운영 막힘 · B04 관리자 · B09 끝) · B05 ↔ B06 같은 페이지 · B07 의 B09 문 · 저장은 지금 각 페이지 `onStepClick` 이 이미 쥔 자리 → 새 판단 코드 없음 · 다섯 페이지 한 벌.
### ① 공용 (ui_template)
- `ui_template_help.ts` `placeHelpButton` — [?] 바로 뒤에 [<] [>].
- class `ui-step-nav-btn` · `data-step-nav="-1" | "1"` · 누르면 bubbles `CustomEvent("ui-step-nav")`.
- 주소가 다섯(`b05-profile` · `b06-section` · `b07-design-detail` · `b08-quantity` · `b09-estimation`)일 때만 — `b09-print` · B03 · B04 · M01 · M02 에는 안 붙음 · B04 를 넣고 싶으면 목록에 한 낱말.
- B09 [>] = 꺼짐.
- `ui_template_workflow_layout.ts` `createWorkflowLayout` — `root` 에 `ui-step-nav` 듣개 한 개(약 15 줄).
- 켜진 칸(`is-active` · B05 · B06 은 `setActiveStep` 이 맞춰 둠) ±1 단계 단추를 찾음.
- 꺼졌으면 `showToast(그 단추 title)` · 켜졌으면 `.click()` = 같은 `onStepClick`.
- CSS — `ui_template_help.css:2` 정사각 규칙에 `.ui-step-nav-btn` 더함 · `ui_template_overlay.css:379-380` `:not(.ui-help-btn)` 에 `.ui-step-nav-btn` 더함.
- 결과 줄:
- B05 · B06 [?] [<] [>] [초기화] [저장] [확정]
- B07 [?] [<] [>] [도각 편집] [현재 도면 확정](280-6 뒤 확정 빠짐)
- B08 [?] [<] [>] [저장]
- B09 [?] [<] [>] [저장] [내역 초기화]
### ② B05 · B06 — [노선 보기] · [횡단 보기] 지움
- `design_dock.ts` — `switchView` 칸(`:30`) · `switchViewLabel`(`:48-50`) · `switchButton`(`:96-109`) · 글자 맞춤(`:119-120`) 지움 · `setView` 는 줄 class 바꿈만 남김.
- `design_shared.ts:348-351` `switchView` 지움.
- `ui_template_locale_b2.ts:148-149` `Design_Btn_ToCross` · `Design_Btn_ToProfile` 지움(`helper_design_view_parity.cjs:127-128` 같이).
- 이동: B05 [>] · B06 [<] = 지금 `onStepClick` 그대로 `navigateTo`(보기만 · 저장 · 물음 없음).
- B05 [<](B04) · B06 [>](B07) = `goToWorkflowStage` → 떠남 물음(고침 있을 때) — 결정 D1.
### ③ B07 · B08 · B09 — 저장 후 이동
- 공통 꼴 — `onStepClick` 을 「저장(성공 여부) → 성공이면 `goToWorkflowStage`」로.
- 실패 = 머묾(알림은 저장 길 그대로) · 고친 값 없으면 요청 없이 바로.
- B07 — 도각 편집 중이면 알림 · 머묾.
- 지금 장이 빈 장 · 이미 확정이면 바로 이동.
- 아니면 `confirmCurrentDrawing` 에 「자동 이동 끔」 인자 · 성공 여부 돌려줌 — 결정 D3.
- B08 — `createOptionsPanel` 이 저장 함수를 내놓음(돌림 꼴 `{ root, save }` 또는 콜백).
- 자재 · 수리 탭은 서면 페이지 한 칸에 저장 함수 등록 — `host.isConnected` 일 때만 · 탭 바꾸면 비움.
- 저장 차례 = 좌측(`b08-options` · `b08-milestone` 초안 있을 때) → 지금 탭(그 탭 초안 있을 때).
- B09 — `run` 이 성공 여부 돌려줌 · `b09-estimation` 초안 있으면 `PUT` · [>] 없음.
## 4. 코더 나눔
| 창 | 맡을 것 | 파일 | 모델 |
| --- | --- | --- | --- |
| A | ① 공용 + ② B05 · B06 + 새 시험 | `ui_template_help.ts` · `ui_template_help.css` · `ui_template_workflow_layout.ts` · `ui_template_overlay.css` · `design_dock.ts` · `design_shared.ts` · `ui_template_locale_b2.ts` · `test_design_view_parity.py` · `helper_design_view_parity.cjs` | Opus high |
| B | ③ B08 · B09 | `B08_Quantity_UI_Page.ts` · `B08_Quantity_UI_Options.ts` · `B08_Quantity_UI_Material.ts` · `B08_Quantity_UI_Hydro.ts` · `B09_Estimation_UI_Shell.ts` | Sonnet high |
| (280-6 창) | ③ B07 | `B07_DesignDetail_UI_Page.ts`(633 줄) — 280-6 「현재 도면 확정」 지움 · 280-5 설정 아이콘과 같은 파일 | Opus high |
- A ↔ B 파일 겹침 없음 · 약속 하나(「[<] [>] = 단계 단추 누름」 → 페이지는 `onStepClick` 만 고침) → 동시 착수 가능 · B 는 A 없이 ☰ 로 시험.
- B07 몫은 280-6 창에 붙임 — 같은 파일 · 700 줄 한도 가까움 · 확정 단추 지움과 한 차례.
- 창 = 둘(A · B) + B07 은 280-6 창.
## 5. 시험 · 화면 확인
### 기존 시험 (고친 대상만)
- A: `test_design_view_parity.py`(기대 `dockActions` → `["reset", "save", "confirm"]`) · `test_design_page_single.py` · `test_design_panel_dock_fixed.py` · `test_346_4_design_leave.py` · `test_311_panel_folded.py` · `test_418_reset_cleanup.py` · `test_225_drawer_shortcuts.py` · `test_device_drawer.py` · `test_54_2_help_files.py` · `test_269_panel_form.py`.
- B: `test_122_b08_options.py` · `test_303_b09_row_popup.py`.
- 둘 다 `npm run typecheck`.
### 새 시험
- `test_280_2_step_nav.py`(A · node 트랜스파일 · 가짜 DOM · `test_225` 꼴):
- 다섯 주소 = 줄 앞 [?] [<] [>] 차례 · `b03` · `b04` · `b09-print` · `m02` = 없음.
- ±1 = 이웃 단계 `onStepClick` 한 번 · 꺼진 단계 = 0 번 + 알림 글 = 그 단추 title.
- B09 [>] 꺼짐 · design dock 단추 = 초기화 · 저장 · 확정 셋.
- `test_280_2_save_move.py`(B · 저장 흉내):
- 저장 실패 → `goToWorkflowStage` 0 번 · 초안 없음 → 요청 0 · 성공 → 1 번(B08 · B09 각각).
- B07 몫은 280-6 창이 같은 꼴로 보탬(`/confirm` 1 · `<` 때 B08 로 안 튐).
### 화면 (ORCA · 자기 포트 · 배정 storage 프로젝트)
- 줄 차례 — 다섯 주소에서 `[data-help-row]`(없으면 `.ui-sidebar-actions`) 단추 글 목록 = 기대값.
- `ui_checks/54-20_좌측단추_치수.js`(wrap · over 0 · [?] [<] [>] w = h · 좁은 패널) · `54-2_도움말.js` · `44-3_좌측패널_하단고정.js`.
- B05 [>] → `location.hash` = `#/b06-section` · `__designPage.switchMs` 길이 +1 · save · confirm 자원 요청 0(`performance.getEntriesByType("resource")`) · B06 [<] 반대.
- B06 [>](고침 있음) → `.ui-confirm` 뜸 → [취소] = 주소 그대로(D1 가).
- B08 — 연암 % 바꿈 → [>] → `PUT quantity/options` 1 · hash `b09-estimation` · 세션 `b08-options` 없음.
- 1본 빈칸 → [>] → 주소 그대로 · 알림 → 값 되돌림.
- B09 — 칸 하나 고침 → [<] → `PUT estimation` 1 · hash `b08-quantity` → 값 되돌림 · 저장.
- B07(280-6 창) — 미확정 장 → [>] → `PUT …/confirm` 1 · hash `b08` / [<] → hash `b06`(B08 로 안 튐) → 확정 풀기(`invalidate`)로 되돌림.
- 일반 사용자 B05 [<] → 주소 그대로 · 알림 「시스템 관리자 전용 단계입니다.」 · B09 [>] `disabled = true`.
## 6. 사용자 결정
| 번호 | 물음 | 선택지 | 추천 |
| --- | --- | --- | --- |
| D1 | B05 [<](B04) · B06 [>](B07) — B05 · B06 무리를 벗어날 때 | (가) 지금처럼 고침 있으면 [취소] [폐기] [저장] 물음 (나) 묻지 않고 [저장] 뒤 이동 (다) 그냥 이동(고침은 세션에 남음 · 떠남 물음 걷음) | (가) — 이미 있음 · 코드 0 |
| D2 | 「저장 후 이동」이 먹는 길 | (가) [<] [>] · ☰ · 진행 단계 판 모두 (나) [<] [>] 만 | (가) — 한 길(`onStepClick` 하나) · (나)는 단추 길 따로 |
| D3 | B07 「저장」 = 확정 뿐 | (가) 보던 장이 확정 전이면 확정(편집 무관 · 빈 장 · 도각 편집 중 뺌) (나) 고친 때만 확정 (다) 확정 전 장 모두 일괄 확정 | (가) — 지우는 「현재 도면 확정」 몫 그대로 · (다)는 서버 길 · 시간 큼 |
| D4 | B08 「보이는 화면」 범위 | (가) 좌측 옵션 + 지금 탭 (나) 초안 있는 탭 모두 | (가) — 원문 그대로 · 다른 탭 초안은 세션에 남음 |
## 7. 위험
- 280-6 에서 B07 확정 단추를 지우면 같은 단추의 [수정](확정 풀기 · 유일한 길 `:476-499`)도 사라짐 → 자리 정함 필요(280-6 창 몫).
- B07 D3 (가)·(나) 모두 한 번에 한 장만 확정 → 운영 B09 문(횡단도 모두 확정)은 [<] [>] 만으로 못 엶 · 지금 임시 배포(5173 · DEV)는 문 우회라 당장 막힘 없음.
- B07 `confirmCurrentDrawing` 의 `all_confirmed` 자동 B08 이동(`:457-460`)을 안 끄면 [<] 가 B08 로 튐.
- 저장 함수 다섯이 실패를 삼킴(알림만) → 성공 여부를 안 돌리면 실패 뒤에도 이동 = 값 잃은 것처럼 보임.
- B08 자재 · 수리 탭은 늦게 섬 → 떠난 탭이 저장 함수를 등록하지 않게 `host.isConnected` 확인.
- D2 (가) 라도 🏠 대시보드 · 머리 메뉴 · 뒤로 가기는 `onStepClick` 밖 → 저장 안 함(B08 · B09 초안은 세션에 남음 · B07 은 CAD 자동백업만).
- B07 줄 [?] [<] [>] [도각 편집] [현재 도면 확정] — 280 px 패널에서 넘칠 수 있음 → 54-20 으로 잼 · 280-6 뒤 풀림.
- `b09-print` 주소도 `B09` 로 읽힘 → 주소 목록으로 거름(정규식 `^b0[5-9]` 금지).
- M01 · M02 · B03 는 `createWorkflowOverlays` 를 바로 씀(틀 없음) → 단추를 붙이면 누름을 받을 곳 없음 → 다섯 주소만.
- `B07_DesignDetail_UI_Page.ts` 633 줄 — 280-2 · 280-5 · 280-6 이 같이 고치면 700 줄 넘김 → 떼어 낼 자리(저장 · 확정 묶음) 미리 봄.
@@ -0,0 +1,168 @@
# 요구 280-3 — 평면 노선 바뀜 뒤 구조물 측점이 옛 자리에 남음
분석 = 클로드 에이전트(Opus 5.5 · 읽기만 · 진짜 TS node 재현 · 서버 임시 사본 재현) · 바탕 `b132c893b`(main_laptop_1 · 배포 main_desktop_1 `2f3b627dd` 와 코드 같음 · 뒤 커밋은 문서뿐).
재현 자료 = `C:\Users\umsan\AppData\Local\Temp\req280c_browser\`(repro.cjs · run.txt) · `req280c_server\`(scripts/probe.py · 검증_sub3 사본) · `req280c_me\drafts_frame.cjs`.
## 0. 결론
- **주원인(재현)** = 노선 초안 중 ↶ · ↷ — 계산 묶음 follow 단계가 계획고 사진만 내고 관 · 구조물 · 추가 측점은 안 냄(`A00_Common/design/design_pipeline_page.ts:288-290`). 묶음 입력이 마지막 끌기의 누가거리로 남음 → 되돌린 노선 위 옛 누가거리 자리에 구조물 측점(유령) · 진짜 자리엔 측점 없음.
- 가게 · 시설 줄 · 알약 · 평면 마커 = 사진첩이 되쓴 값(맞음) · 묶음 측점 · B06 카드(초안 상세) · 오른쪽 횡단 판 · 측점 열쇠 = 틀림 → 「구조물 없는 구조물 측점」 + 「측점 없는 구조물」이 한꺼번에.
- 오래 남는 조건 = 두 끌기 사이에 시설을 손으로 고침(그 값이 묶음 `given` 에 남아 맞춤이 건너뜀 `:560`). 손 안 댔으면 다음 맞춤 한 바퀴에 나음(잠깐 보임).
- 「같은 자리에 올리면 사라짐」 = 시설을 놓아 가게가 바뀌면 묶음에 다시 들어감(`design_pipeline_facilities.ts:63`) — 같은 자리 · 횡단 있는 구조물이 아니어도 시설 고침 하나면 사라짐(재현 166 맹암거 → 유령 0).
- 관 · 추가 측점 = 같은 줄 같은 흠.
- **둘째 원인(서버 · 실증)** = 옛 비정규 측점 이관이 낡은 측점을 「기타 구조물」(측점 안 심는 종류 = 측점 없는 구조물)로 만듦 · 초기값 사본에 박혀 [초기화] 때 되살아남.
- **셋째(잠깐 · 낮음)** = 초안 측점 맞춤 `withDraftStations` 가 옛 노선 틀 저장분 · 새 노선 틀 초안을 같은 누가거리로 견줌 — 묶음 갈아끼움 전 · 초안 둔 채 새로고침 첫 화면.
- 서버 [저장] 측점 맞춤 = 정상(노선 바뀜 · 안 바뀜 둘 다 끝에서 다시 맞춤).
- 평면 노선 끌기 · 방향키만으로는 재현 0(아래 2장) — 사용자 경로에 ↶ 가 있었는지 화면 재현으로 먼저 확인 필요.
## 1. 지금 동작
### 1-1 구조물 측점을 이루는 자료
| 자료 | 자리 | 열쇠 · 꼴 | 노선 바뀔 때 |
|---|---|---|---|
| 구조물 자리 | `B05_Profile/route/structures.json`(누가거리 · 구간 끝 · x · y) · 초안 = 가게 `structures`(세션) | `structure_id` | 브라우저 follow(`common_util_route_follow.ts` followStructures) → `writeFacilities`(`design_pipeline_page.ts:455-463`) · 서버 `follow_structures_to_route` → `rebase_drafts`(Node `facility.rebase`) |
| 관 자리 | `B04_PreProcess/drainage/edits/pipe_points.json` · 가게 `pipes` · 세션 id 장부 `pipe-ids` | 관 고정 id · 세션 id `pipe-sN` | `projectPipePoints` · 서버 `_place_pipes` · 장부 `placePipeIds`(시설 줄) |
| 추가 측점 | `plus_stations.json` · 가게 `plusStations` | 정수 m · 주인 `plus-{m}` | `followPlusStations` → `followPlusDraft` · 서버 `follow_plus_stations` |
| 측점 계획 | 파생 — `planStations`(`common_util_structure_station.ts`) ↔ `plan_stations` | 측점 · 이름 · 주인 | 위 셋에서 다시 셈 |
| 묶음 측점 | 계산 묶음 입력(`pipes` · `structures` · `plusStations`) → `buildStations` → 가게 `stations`(세션 `design-stations`) | `structure:<id>[:start\|:end]` · `pipe:<id>` · `structure:plus-<m>` | follow 가 입력을 고침(`design_pipeline.ts:212-221`) — **이번 흠 자리** |
| 종단 정본 측점 | `B06_Section/longitudinal/longitudinal.json` stations(`irregular` · `structure` 이름 · `grid`) | 누가거리 | 서버만 — `_merge_irregular_into_longitudinal`(`B05_Profile_Router_Confirm.py:413-470`) |
| 횡단 파일 · DB 행 | `cross_sections/cross_XXXm.json` · DB `cross_sections` | 누가거리 | 측점 맞춤 뒤 옛 파일 지움 · 행 0.5 m 안 옮김 / 지움(`B05_Profile_Repository.py:244-280`) |
| 상세 가게(메모리) | `B06_Section_Section_Store.ts` cache · 숨긴 측점(`parked`) · 구조물 없는 사본(`preview`) | 측점 열쇠로 앞 횡단 이어 받음 | `swapSectionStations`(`:247-290`) · `withDraftStations`(`B06_Section_Section_Drafts.ts:160-255`) · `withDraftShapes` |
| 초안 상세 | `pipelineDetail` = `draftSectionDetail`(`B05_Profile/logic/B05_Profile_Pipeline_Detail.ts`) | 묶음 측점 그대로 | 노선 초안 동안 B06 카드 · 종단도가 그림 |
| 측점 열쇠 목록 · 주인 | `B06_Section_Station_Keys.ts` lists · owners | 위 열쇠 | 갈아끼움마다 새 목록 · `carryStationKeys` |
| 세션 횡단 고침 | 측점 열쇠 칸 다섯(crossdesign 등) · 누가거리 칸(revetx · extrawall · basinadjust …) | 열쇠 · 0.01 m 글 | 갈아끼움 `moves` 로 옮김(`moveIdleStates`) · [저장] = Node `stations.carry` |
| 시설 줄 | `design_facility_lane.ts` — 알약 · B05 비정규 측점 · 평면 마커 원천 | 가게 값 | `lane.follow()` = 가게 초안 ?? 저장분 |
| 계획선 꼭짓점 | `profile_alignment` base_pvi · station_offsets | 누가거리 | `followMovedNodes` · 서버 `seat_route_moved` |
| 이관 기록 | `structures.json` `migrated_legacy` · `initial_snapshot/B05_Profile__route/structures.json` | `종류@누가거리` | 이관이 씀 · 추천 [적용] 때 초기값에 박힘 |
### 1-2 때마다 옮겨지는 것 · 남는 것
| 때 | 옮겨짐 | 남음 · 흠 |
|---|---|---|
| 노선 끌기 · 방향키(초안) | follow(좌표 → 정수 m) → 묶음 입력 · 가게 · 추가 측점 초안 · 묶음 측점 → 갈아끼움(열쇠 짝 · 이름 다시 얹음) · 초안 카드 · 누가거리 칸 | 유령 0(재현 A · A' · B · C · D · E) · 갈아끼움 전 초안 맞춤이 먼저 돌면 잠깐 빈 격자 사본(원인 3) |
| 마커 · 폼 옮김(초안 중) | 가게 → 묶음 feed(`given` = 그 값) → 측점 · 갈아끼움 | 정상(411 · 410) |
| ↶ · ↷(초안 중) | 사진첩 = 세션 칸(노선 · 계획고 · 관 · 구조물 · 추가 측점)을 먼저 되씀(`B05_Profile_UI_Profile_History.ts:212`) → 가게 · 시설 줄 · 알약 · 평면 마커 | **묶음 입력 관 · 구조물 · 추가 측점 = 앞 끌기 값 그대로**(원인 1) · feed 는 되살림 · 노선 초안 중이라 건너뜀(`design_pipeline_facilities.ts:63` · `:70`) |
| [저장] | 서버 — 구조물 · 추가 측점 · 관 좌표 따라감 → 종횡단 처음부터 → 초안 다시 얹기 → 측점 맞춤(`confirm_sections` · 끝 `save_sections` 의 `_stand_patch_stations`) → 횡단 넘김(Node) · 브라우저 = 가게 초안을 보냄(묶음 입력 아님) | 서버 정본 정상 · ↶ 유령도 [저장] 뒤 사라짐 · 이관(원인 2)은 저장 잠금 밖 |
| 새로고침(초안 둔 채) | 세션 초안 + `route-draft-hold` → 묶음 처음부터 = 가게 값 → 원인 1 나음 | 첫 차례 전 옛 틀 상세에 새 틀 초안을 맞춤(원인 3 · 잠깐) |
| [초기화] | 초기값 덮어씀 · 세션 초안 지움 | 초기값에 박힌 이관 유령 되살아남(원인 2 · 검증_sub3 실증) |
### 1-3 지난 길과 견줌
| 길 | 규칙 | 이번 |
|---|---|---|
| 410 follow xy | 좌표 = 정본 · `placeOnLine` 좌표 낡음 판정 | 정상 |
| 411 마커 = 새 측점 | 관 · 구조물 짝 = id | 정상 |
| 457 초안 카드 | 그림 · 이름 = 지금 현황(가게)을 새 측점에 다시 얹음 | 현황은 맞음 · 측점(묶음 입력)이 틀려 **이름 단 유령**이 옛 자리에 섬 |
| 459 `rebaseStructureDraft` | [저장] 때 따라가기만 한 것 = 서버 자리 · 옮긴 것 = 초안 | 정상(서버 받은 대로) |
| 356-4 되돌리기 = 그 걸음 사진 | 계획고 옮김 안 탐 · 주석 「관 · 구조물도 세션 그대로」 | **계획고만 묶음에 넣고 관 · 구조물 · 추가 측점은 안 넣음 ← 뿌리** |
## 2. 원인
### 원인 1 — 노선 초안 중 ↶ · ↷ 가 시설을 묶음에 안 넣음(높음 · 재현)
- `design_pipeline_page.ts:288-290` — `kept = replay && 노선 초안 ? 계획고 사진 : null` → `if (kept) return { alignment: kept }`. follow 패치에 `pipes` · `structures` · `plusStations` 없음 → 입력 칸이 앞 차례 follow 값(마지막 끌기 노선의 누가거리) 그대로.
- 되살림 동안 가게 → 묶음 먹이기 막힘: `design_pipeline_facilities.ts:63`(되살리는 중) · `:70`(노선 초안 중 = 「노선 길이 맡음」).
- 뒤 맞춤(`design_pipeline_page.ts:560`)은 가게 값이 묶음 입력 · 마지막 feed 값(`given`) 둘 다와 다를 때만 넣음 → 끌기 사이 손 고침이 있으면 `given` = 사진 값 → 건너뜀 → 유령 남음.
- 재현(진짜 TS · 노선 (0,0)→IP(u,40)→(260,40) 300 m · 격자 20 m · 옹벽 60/70/80 · 맹암거 150 · 흙막이 200/210/220 · 기타 240 · 관 110 · BOX 260):
| 순서 | 묶음 측점(시설) | 가게 · 시설 줄 | 판정 |
|---|---|---|---|
| +7.3 끌기 | 67/77/87 · 117 · 157 · 207/217/227 · 267 | 같음 | 정상 |
| 맹암거 손 157 → 163 · +10 끌기 | 70/80/90 · 120 · 166 · 210/220/230 · 270 | 같음 | 정상 |
| ↶ | 70/80/90 · 166 · 210/220/230(이름 단 채) | 67/77/87 · 163 · 207/217/227 | **유령 7 · 빠짐 7** · 관은 맞춤이 다시 넣어 나음 |
| 166 에 맹암거 놓음 | 가게 = 묶음 | — | 유령 0 |
| 손 고침 없는 같은 진행 ↶ | 유령 9 · 빠짐 9 | — | 맞춤 한 바퀴 뒤 0(잠깐) |
- 보이는 곳 — B06 갈아끼운 상세 = 유령 9(이름 없음) · 초안 카드 = 유령 9(「옹벽」 · 「맹암거」 · 「흙막이」 이름) · B05 종단 그래프 = 정상(시설 줄 = 가게).
- 관 = 같은 줄(관 손 고침 뒤 ↶ 면 남음) · 추가 측점 = 같은 줄.
### 원인 2 — 옛 비정규 측점 이관이 「기타 구조물」을 만듦(중간 · 실증)
- 부름 = B05 가 비정규 측점 있는 상세를 그릴 때마다(탭 · 노선마다 한 번 · `design_view_profile_sections.ts:126-150`) → `B05_Profile_Structures_Router.py:300-385` → `B05_Profile_Structures_Migration.py:58-74`.
- 조건 = 종단 정본 비정규 측점이 지금 심는 자리에 없음 + 이름이 지금 종류 이름 · 관 이름 꼴(「…관 D…」) 아님 → `etc` 구조물(이름 = 측점 이름 · 비면 「기타 구조물」). `etc` = 표 횡단도 `none` → **측점 없는 구조물 알약 · 마커** · 뒤 노선 바뀜에도 좌표로 따라감.
- 낡은 종단이 생기는 때 — 저장 도중 끊김(서버 다시 켬) · 다른 탭이 노선 [저장] 도중(이관이 저장 잠금 밖) · 추가 측점(이름 빈 글 = 이름 검사 늘 못 넘음) · M02 에서 이름 바꾼 종류.
- 증거 — `tmp/server.log:822048`(검증_sub3 구조물 1건 이관) · `storage/1/3/12953f5a-…/B05_Profile/route/structures.json` `migrated_legacy: ["etc@376.0"]` · `initial_snapshot/B05_Profile__route/structures.json` 에 「기타 구조물」 @376 남음.
- 임시 사본 재현 — 이름 없는 376 → 「기타 구조물」 · 옛 이름 600 → 「맹암거(구판)」 · 지금 종류 이름 700 → 건너뜀 · 지금 표 카드 이름 42종 = 모두 건너뜀.
### 원인 3 — 초안 측점 맞춤이 옛 틀 · 새 틀을 같은 누가거리로 견줌(낮음 · 잠깐)
- `B06_Section_Section_Drafts.ts:213-232` — `replaced()` = 저장분(옛 노선 누가거리) 종류 대 초안(새 노선 누가거리) 종류를 같은 m 에서 견줌 · 부름 = 캐시 상세를 읽을 때마다(`B06_Section_Section_Store.ts:138` · `:147` · `:198`).
- 재현(진짜 TS) — 옛 틀 상세 + 새 틀 초안(초안 둔 채 새로고침 · B06 바로 열기 첫 화면): 100 · 160 = 「구조물 없는 측점」(비정규 · 이름 없음) · 153 · 167 숨김 · 격자 120 · 180 에 「맹암거」 · 「옹벽」 이름 + 그림(그 땅 자리 아님) · 갈아끼움 전 차례(재현 W) 60/80/200/220/260 빈 격자 사본.
- 묶음 첫 차례 · 갈아끼움 뒤 나음 — 묶음이 못 서면(저장 종단 기준 · 지형 격자 없음) 남음.
### 낮음 · 덧
- 노선 안 바뀐 [저장]에서 구조물이 격자 측점을 떠나면 그 행의 구조물 조정 칸(`STRUCTURE_ADJUST_KEYS`)이 DB 에 남음 → 같은 자리에 다시 놓으면 되살아남(코드 확인 · 화면 확인 안 함) · 노선 [저장]은 `replace=True` 로 갈음.
- 측점 맞춤이 샘플링 조건 없으면 경고만 · 응답 「맞춤 성공」(`B06_Section_Router_Stations.py:327-332` · `B06_Section_Router_Confirm.py:104-107`).
## 3. 고치는 방법
### 뿌리 한 곳 — 브라우저(원인 1)
- `design_pipeline_page.ts:290` — ↶ · ↷ 차례 follow 패치 = 계획고 사진 + 사진첩이 되쓴 가게 값:
`if (kept) return { alignment: kept, pipes: store.get("pipes") ?? savedPipes, structures: store.get("structures") ?? savedStructures, plusStations: plusStationsOf(projectId) };`
- 가게 쓰기 = 같은 값이라 그냥 지남(`writeFacilities` 견줌) · 추가 측점 `followPlusDraft` 같은 값 · 계획선 427 세움(`followedAt`)은 지금처럼 안 탐.
- 짝 없음 — 브라우저 묶음 입력만 고침 · 서버 [저장]은 이미 가게 초안을 받음(같은 값) · 파이썬 · Node 묶음 영향 없음(`design_pipeline_page` 는 서버 묶음 밖).
### 서버(원인 2)
- `_is_managed_elsewhere`(`B05_Profile_Structures_Migration.py:58-74`) — 이름 빈 측점 = 추가 측점 정본(`plus_stations.json`) 소관 → 건너뜀.
- `migrate_structures`(`B05_Profile_Structures_Router.py:300-324`) — 저장 잠금(`common_util_save_guard.project_lock`) 안에서 · 종단 정본이 낡았으면(`_stale_marks` ≠ 0) 이관 건너뜀.
- 측점 맞춤 — 맞출 것이 있는데 샘플링 조건 없음 = `stations_synced: false`(알림).
### 원인 3(갈래 · 작음)
- 노선 초안 동안 ⓐ 상세가 저장 정본 객체(옛 틀)면 초안 대신 저장분으로 맞춤 · 얹음 ⓑ 갈아끼운 상세(새 틀)면 종류 대조(`replaced`) 건너뜀 — 측점 목록 = 묶음 갈아끼움 몫.
## 4. 코더 나눔
| 창 | 몫 | 파일 | 모델 |
|---|---|---|---|
| A 브라우저 | 원인 1 (+ 갈래면 원인 3) | `A00_Common/design/design_pipeline_page.ts`(691줄 · 700 안 넘게) · (원인 3) `B06_Section/B06_Section_Section_Drafts.ts` · 새 시험 · 도우미 | Opus high |
| B 서버 | 원인 2 · 맞춤 실패 알림 | `B05_Profile/B05_Profile_Structures_Migration.py` · `B05_Profile/B05_Profile_Structures_Router.py` · `B06_Section/B06_Section_Router_Stations.py` · 시험 | Opus high |
- 파일 겹침 없음 → 두 창 동시 · B 는 6장 결정 1 · 2 뒤.
- 덧(DB 조정 칸 남음) = 화면 확인 먼저 · 뒤 장.
## 5. 시험 · 화면 확인
### 기존 시험(고친 대상)
- A — `test_356_4_undo_grade` · `test_349_0o_redo_same` · `test_349_0k_undo_saved` · `test_349_0q_undo_echo` · `test_349_0d_follow_store` · `test_472_5c_drop_once` · `test_457_draft_card` · `test_458_swap_attach` · `test_459_route_seat_mirror` · `test_410_follow_xy` · `test_368_2_plus_stations` · `test_272_drop_store_stations` · (원인 3) `test_273_2_preview_stations` · `test_451_refresh_hold` · `test_440_cross_draft_pick`.
- B — `test_452_server_keys_prune_migrate` · `test_272_2_station_sync` · `test_472_6_design_save` · `test_472_6_route_save`.
- 거울 — 짝 계산 안 바뀜 · `test_322_3_plan_mirror` · `test_272_1_station_list` · `test_344_2_route_pairs` 그대로 통과.
### 새 시험
- A `test_280_3_route_undo_facilities.py`(도우미 = `helper_356_4_undo_grade.cjs` 판 · 진짜 묶음 · 사진첩):
① 끌기 1 → 구조물 손 옮김 → 끌기 2 → ↶ = 묶음 측점 시설 누가거리 = `planStations`(가게 관 · 구조물 · 추가 측점) · 초안 상세 유령 0 · 빠짐 0 ② ↷ 같음 ③ 관 손 옮김 판 ④ 추가 측점 판 ⑤ 손 안 댄 판 = 맞춤 전에도 0 ⑥ 계획고 사진 그대로(356-4 ▲ 남음) ⑦ 옛 코드 = 실패(유령 7 · 빠짐 7).
- B `test_280_3_migrate_guard.py`(임시 storage): 이름 빈 낡은 측점 · 옛 이름 낡은 측점 · 종단 낡음 = 이관 0 · 정본 맞는 옛 자유 글(「대피로 2.0m」)은 결정대로 · 조건 없음 + 맞출 것 있음 = `stations_synced` 거짓.
- (원인 3) 옛 틀 상세 + 새 틀 초안 → 숨김 · 사본 · 남의 자리 이름 0.
### 화면 확인(ORCA · 자기 검증 프로젝트 · 저장 안 함 · 끝에 세션 되돌림)
- 고치기 전 판에서 먼저 재현(판정 기준이 변경을 재는지) — 맹암거 · 옹벽 초안으로 놓음 → 교점 끌기 → 맹암거 마커 방향키 → 교점 끌기 → ↶.
- 잴 것(`orca eval`) — 묶음 `ground.stations` 시설 열쇠 · 누가거리 · `pipelineDetail` 비정규 누가거리 · 카드 이름 · 가게 `structures` · `pipes` · `plusStations` 누가거리 · B06 카드 제목 목록 → 네 벌 같음 · 유령 0 · 빠짐 0.
- 판 — ↷ · 관 마커 옮김 · 추가 측점 · B06 평면 패널 끌기 · ↶ 뒤 B05 ↔ B06 전환 · 새로고침 · ↶ 뒤 [저장] → 상세 GET 비정규 측점 = 계획.
- 끌기만(↶ 없음) 판도 유령 0 확인 — 0 이면 사용자 경로 = ↶ 로 봄 · 아니면 그 순서 기록.
- 서버 이관 = 임시 storage 시험만(공유 storage 쓰기 금지).
## 6. 사용자 결정 갈래
1. 옛 비정규 측점 이관 — (가) 막이 둠(이름 빈 측점 · 낡은 종단 건너뜀 · 저장 잠금) **추천** — 2026-08 이전 자유 글 측점 프로젝트 보존 (나) 이관 걷음 — 지금 판 측점은 모두 정본이 심음 · 옛 프로젝트 구조물 잃음.
2. 이미 생긴 「기타 구조물」(이관 기록 · 이름 「기타 구조물」 · 메모 없음) — (가) 그대로 · 사용자가 지움 (나) 목록 알림만 **추천** (다) 자동 지움 — 초기값 사본 포함 · storage 동기화로 데스크탑에 번짐.
3. ↶ · ↷ 때 시설 값 — (가) 그 걸음 사진(세션 값) **추천** · 356-4 원칙 (나) 좌표로 다시 따라감(손 옮김을 다시 판정).
4. 원인 3 — (가) 같이 고침 **추천** · 작음 (나) 뒤 장.
5. 사용자 재현 순서 확인 — 노선을 바꾸며 ↶ 를 썼는지 · 구조물을 손으로 옮긴 뒤인지(화면 재현이 끌기만으로 0 이면 물음).
## 7. 위험
- `design_pipeline_page.ts` 691줄 — 고침 뒤 700 넘으면 되살림 몫을 `B05_Profile/logic/B05_Profile_Pipeline_Grade.ts` `createFollowStage` 선택 칸으로.
- 사진첩 밖 칸 — 관 세션 id 장부 `pipe-ids` · 묶음 메모리 `beforeDraft` · `route-draft-hold` → ↶ 뒤 관 알약 id 짝(411-B) 확인.
- 되돌리기 시험(356-4 · 349-0k ~ 0q) 기대값 흔들림 — 계획고 · 측점 목록은 그대로여야 함.
- 원인 1 이 사용자 경로가 아닐 수 있음 — 끌기 · 방향키만 재현 0 · 화면 재현 먼저.
- 이관 걷음 = 옛 자유 글 측점 프로젝트 구조물 잃음 · 지움 = 사용자 자료(Synology 동기화).
- 원인 3 고침 = 초안 둔 채 새로고침 · hold 길(440-4 · 451 · 467 B1) 건드림.
- 서버 파이썬 고침 = 백엔드 다시 켬 · 배포(데스크탑 메인) = 전역 깃 싱크 때만.
- 곁 일감 겹침 — 요구 280-4(10 m 스냅 특별측점)가 격자 측점 · 묶음 stations 를 고침 · 460-3 ①(종단 판 접은 채 알약 옛 측점) 따로 남음.
@@ -0,0 +1,131 @@
# 요구 280-4 분석 — 10 m 스냅 특별측점 · 스냅 항목 1 m · 0.1 m 지움
## ① 지금 동작
| 자리 | 파일:줄 | 하는 일 |
|---|---|---|
| 맞춤(브라우저만 · 파이썬 짝 없음) | `logic/B05_Profile_RouteEdit_StationFit.ts:145-216` `fitCurveStations` | IP 를 밀어 MC 를 고른 간격 배수에(10 m = 10 m 배수 · 측점 No. = 20 m) · 후보 = 내림 · 올림 둘(`near` 158-163) · 곡선마다 기억 없음 · 측점 목록은 안 만듦 |
| 측점 밖 판정 | 같은 파일 64-79 `offStationMcs` | 고른 간격 배수만 「위」 — 상태줄 · `fitNote`(CurveBar.ts:168) · 개수(UI_RouteEdit.ts:397 · 550) 모두 여기 |
| 측점 목록 — 서버 | `B05_Profile_Engine_Sections_Core.py:394-428` `_station_layout` | 397-398 스냅 켬 = `snap_curve_stations`(298-324) 「호 안 일반측점 중 MC 에 가장 가까운 하나를 MC 로 **옮김**」(224-2) → 423-427 `curve_stations`(327-345) 「MC 에 측점이 없을 때만 더함」(407) |
| 측점 목록 — 브라우저 묶음 | `logic/B05_Profile_Sections_Stations.ts:292-299` · 243-265 `gridIndices` · 323-330 | 같은 옮김 · 옮긴 측점에 사라진 격자 번호(`grid:k`) · 곡선 측점 |
| 평면 눈금 | `B05_Profile_UI_RouteEdit_SnapStep.ts:83-90` · 109-117 | 같은 옮김 + 곡선 측점 |
| 저장 열쇠 | `B05_Profile_Route_Save_Parts.py:151-154` | 간격 배수 아닌 regular = 열쇠 없음(건너뜀) |
| 간격 저장 | 씀 `B05_Profile_Router_Replan.py:258-259` · 읽음 `B05_Profile_Engine_Sections.py:107-113` · 화면 기억 SnapStep.ts:72-77 | 곡선표 `station_snap_step` · pref `routeedit-snap-step` |
| 고르기 목록 | `B05_Profile_UI_RouteEdit_Chrome.ts:116-121` · SnapStep.ts:25 | 0.1 · 1 · 10 · 측점 No. |
재현 1 — 측점 목록(읽기 전용 스크립트 · 서버 `_station_layout` · BP–IP–EP 곡선 하나 · 측점 간격 20 m):
| 경우 | BC~EC | 측점(≤ 120 m) | 곡선 측점 |
|---|---|---|---|
| A 10 m · 긴 호 · MC 50(No.2+10) | 34.3~65.7 | 0 20 **50** 60 80 100 120 — **40 사라짐** | 없음 |
| B 10 m · 짧은 호(R 8.5) · MC 50 | 45.6~54.4 | 0 20 40 50 60 … | 50 |
| C 10 m · MC 60(No.3+0) | 44.3~75.7 | 0 20 40 60 … | 없음 |
| D 측점 No. · 못 맞춘 MC 47 | 31.3~62.7 | 0 20 **47** 60 … — **40 사라짐** | 없음 |
| E 스냅 끔 · MC 50 | 34.3~65.7 | 0 20 40 50 60 … | 50 |
- TS `buildStations` 같은 값 — A = `50 grid:2`(40 없음) · E = `40 grid:2` + `50 curve:50.00`.
재현 2 — 맞춤(StationFit 노드 스크립트 · helper_366_3 노선 · MC 목록):
| 걸음 | MC |
|---|---|
| ① 측점 No. 맞춤 | 100 180 280 380 460 |
| ② 10 m · IP2 +6 m | 100 **190(N+10)** 280 380 460 |
| ③ 측점 No. 로 돌린 뒤 앞 IP1 +3 m | 100 **200** … — 특별측점 사라짐 |
| ④ 10 m 그대로 · 앞 IP1 +3 m | 100 190 … — 남음(가까운 10 m 배수 · 5 m 넘게 밀리면 N+0 으로 감) |
| ⑤ 간격만 측점 No. 로 | 100 **180** … — 사라짐 |
| ② 노선 `offStationCount(20)` | 1 — N+10 을 「측점 밖」으로 셈 |
실데이터(storage 읽기):
- 저장 종단에 옮긴 일반측점 있는 곳 = 검증_sub8(`9872e9a7`) 하나 · 11 곳(541 · 601 · 641 …) · 다른 15 프로젝트(엄상준_검증 포함) 0.
- 곡선표 39 개 중 `station_snap_step` 칸 = 0(옛 0.1 · 1 m 저장 없음).
## ② 원인
1. 사라짐 = 224-2 「옮김」 — MC 가 N+10 이면 앞뒤 일반측점 거리가 같아 앞쪽 No.N 이 MC 로 옮겨짐(파이썬 `min` · TS `<` 둘 다 앞 먼저) · 옮긴 측점이 MC 를 차지해 407 곡선 측점은 안 더함 · 호가 짧을 때만 더함 → 같은 10 m 스냅이 호 길이 따라 갈림(A · B).
2. 같은 옮김이 측점 No. 못 맞춘 곡선(D)에도 — 20 m 격자 깨짐.
3. 「유지」 깨짐 = 맞춤이 곡선마다 기억 없이 고른 간격 하나로 다시 밂(재현 2 ③ · ⑤) · `offStationMcs` 가 고른 간격으로만 판정.
4. 덧 흠 — 옮긴 측점 열쇠 브라우저 `grid:k` ↔ 서버 열쇠 없음.
근거(지식DB):
- 별표2 실시설계 (1)(가) — 「측점 간격은 20미터 … 필요한 각 점에는 보조말뚝」(`original/법률/산림자원의 조성 및 관리에 관한 법률 시행규칙/별표/별표2_…md:24`).
- `original/산림과임업기술(임도)/2. 임도/4. 노선측량.md:143` — 「20m마다 측점말뚝 … 곡선부의 주요점 등에는 보조말뚝 … 측점번호를 부여」 · `:144` — 「곡선부에서도 연속적으로 … 20m 간격으로 측점말뚝 … BC · MC · EC 에도 말뚝을 박아 보조측점」.
- `…/5. 설계.md:157` — 측점 = 일련번호(No.11) · 보조측점 = 떨어진 거리(No.11+13).
- → 일반측점 20 m 는 곡선 안에서도 그대로 · MC 는 보조측점으로 더함 = 사용자 말과 같음(MC 하나만 = 407 사용자 규칙 그대로).
## ③ 고치는 방법
A. 측점 목록 — 「옮김」 지움(짝 셋 같이)
- Core.py:397-398 지움 · Sections_Stations.ts:292-299 `grid = original` · `gridIndices`(243-265) 지움(격자 번호 = 차례) · SnapStep.ts `stations` = 켬이면 `stationsAlong` 그대로 · `snap_curve_stations` ↔ `snapCurveStations` 함수 지움(쓰는 곳 없어짐) · 두 파일 머리 짝 글 · `design_calc_registry.ts:118` 글.
- 결과 = 스냅 끔과 같은 목록(재현 E) — 일반측점 20 m 그대로 + MC 가 일반측점 위가 아니면 곡선 측점 하나(`curve` · `curve:<누가거리>` · 「No.2+10 (MC)」) → 평면 눈금 · 종단 · B06 카드 · 토량 · B07 표가 이미 `curve` 를 받음(407 · 새 종류 없음).
- N+0 으로 옮기면 `curve_stations` 가 0.005 m 안 일반측점을 보고 안 더함 → 일반측점만(사용자 규칙 3 자동).
- 열쇠 덧 흠 자연히 풀림.
B. 「유지」 — 맞춤이 손대지 않은 곡선의 N+0 · N+10 을 지킴(`StationFit.ts` 만 · 브라우저 · 파이썬 짝 없음)
- 고치기 전 노선(`applyEdit` 의 `before` · `fitDrag` 의 `base`)에서 곡선마다 MC 가 N+10 인지 읽어 그 IP 좌표로 기억(노드 삽입 · 삭제에도 맞음).
- `fitCurveStations` — IP 좌표가 바뀐 곡선(손으로 옮김)만 고른 간격 배수 · 나머지는 옛 꼴(N+0 = 20 m 배수 · N+10 = 20 m 배수 + 10) 후보 둘 · 밀 거리 = 측점 간격(20).
- `offStationMcs` — 10 m 배수(N+10)도 「위」.
- 383 「서로 다른 측점」 비교 = 차례 번호 대신 누가거리.
- 저장 칸 없음 — 특별측점 = 「그 곡선 MC 가 N+10」이라는 노선 모양 · 다시 열어도 같은 판정.
- `UI_RouteEdit.ts`(695 줄) — 440 줄에 `before` 하나 · `fitLimits`(459-461)에 `stationM` · 줄 수 안 늘게.
C. 스냅 항목 1 m · 0.1 m 지움
| 자리 | 고침 |
|---|---|
| Chrome.ts:117-118 | option 둘 지움(10 m · 측점 No. 남음) · 113 title 낡은 글(「간격 절반」) 고침 |
| SnapStep.ts:25 | `STEPS = ["10", "station"]` → 옛 pref "0.1" · "1" · 옛 저장 값은 이미 있는 거름(73 · 98)이 측점 No. 로 |
| SnapStep.ts:5 · 10 머리글 · KeyNudge.ts:6 머리글 · Schema.py:436 · Route_Save.py:72 글 | 「10 m · 측점 No.」 둘만 |
| Engine_Sections.py:107-113 `load_station_snap_step` | 10 이 아니면 None — GET(Router_Replan.py:515) · B06 `_curve_marks` · 저장 측점 셈이 모두 이 한 곳으로 읽음 |
| 방향키 | 고칠 것 없음 — 켬 = 고른 간격 걸음 · 끔 0.1 m · Shift 1 m 그대로(스냅 항목과 별개) |
| `mcScale` ↔ `_mc_scale`(Frame.ts:159-161 · Core.py:292-295) · `fitNote` 자릿수(KeyNudge.ts:115) | 늘 정수가 됨 — 이번엔 둠(짝 · 시험 다섯 곳 흔듦) · 정리는 따로 |
## ④ 코더 나눔
- 추천 = 창 하나(Opus high) · 차례 A → C → B — A · C 는 같은 짝 · 같은 시험(224_2 · 390_5) · B 는 StationFit 하나 + RouteEdit 두 줄 · 합쳐 파일 열 개 안쪽.
- 둘로 나눌 때:
| 창 | 일 | 파일 |
|---|---|---|
| 1 | A + C | Core.py · Frame.ts · Sections_Stations.ts · SnapStep.ts · Chrome.ts · Engine_Sections.py(`load_station_snap_step` 만) · Schema.py · registry 글 · 시험 224_2 · 390_4 · 390_5 · 새 `test_280_4_stations` |
| 2 | B | StationFit.ts · UI_RouteEdit.ts(440 · 459-461 · 544-549) · 시험 366_3 · 383_2 · 408 · 새 `test_280_4_fit` |
- 280-3(노선 바뀜 구조물 측점)과 겹칠 수 있는 곳 = Engine_Sections.py · Route_Save_Parts.py — 이 일은 Engine_Sections.py 한 함수만.
- 서버 Node 묶음 다시 지음 없음 — 묶음 입구(corridor · server-calc · design-calc)가 Frame · Sections_Stations · StationFit 를 안 끎(코더가 build 로 재확인).
## ⑤ 시험 · 화면 확인
기존(지금 통과 114 — 224_2 · 390_5 · 407 · 390_4 = 73 · 366_3 · 344_3 둘 · 408 · frame_mirror · 407_3 = 41):
- 고쳐 씀: `test_224_2` `test_켬은_640_없고_631_있음` → 「켬 = 640 남고 631 곡선 측점(끔과 같음)」 · `_snap` 쓰는 단위 시험(40-70) · `test_스냅_셈이_두_쪽에서_같다`(옮김 함수 지우면 걷음) · `test_390_5` `STEPS` · `test_파이썬_짝_거울` · `helper_390_5` STEPS · `test_390_4` 0.1 · 1 글.
- 그대로 돌림: 407 · 407_3 · 344_3 둘 · frame_mirror · 366_3 · 383_2 · 408 · 472_3 registry.
새 시험:
1. 측점 목록 거울 — 재현 1 A~E(서버 `_station_layout` = TS `buildStations` 누가거리 · kind · 열쇠 · A = `40 grid:2` + `50 curve:50.00`).
2. N+0 으로 옮기면 곡선 측점 0 · 일반측점 20 m 그대로.
3. 맞춤 — 재현 2 노선: 측점 No. 로 돌림(⑤) · 앞 IP 옮김(③) 뒤에도 190 · 그 곡선 IP 를 끌면 고른 간격 따름 · `offStationCount(20)` = 0 · 손대지 않은 N+0 곡선이 N+10 으로 안 감.
4. 옛 간격 — `load_station_snap_step` 0.1 · 1 → None · 10 → 10 · STEPS 둘 · option 둘.
화면(ORCA · 배정 프로젝트 · 수치):
- B05 노선 편집 → [측점 스냅] 켬 · 간격 10 m → 곡선 IP 끌어 MC = No.k+10 → 간격 고르기 option 2 개(eval) · 종단 아래 표 측점 행에 k · k+10 · k+1 셋 · B06 카드 「k+10 (MC)」 계획고 0 아님 · 상태줄 「측점 밖 곡선」 없음.
- 간격 측점 No. 로 → MC 그대로 k×20+10 · 앞 IP 방향키 한 번 → MC 다시 k×20+10.
- 그 곡선 IP 를 N+0 으로 → (MC) 측점 없어짐 · 일반측점 20 m 그대로.
- [저장] 뒤 서버 종단 stations — `curve` k×20+10 · `regular` k×20 · (k+1)×20 · 끝나면 노선 되돌림(조작 전 DB 행 백업).
## ⑥ 사용자 결정
| # | 물음 | 갈래 | 추천 |
|---|---|---|---|
| 1 | 측점 No. 간격에서 못 맞춘 곡선(MC 47 등)도 같은 규칙? | 가 같음(일반측점 그대로 + 「No.2+7 (MC)」) · 나 10 m 일 때만(측점 No. 은 지금처럼 일반측점을 MC 로 옮김) | 가 — 20 m 규칙(지식DB)과 맞고 규칙 한 벌 · 나는 간격 따라 규칙 둘 · 서버도 간격으로 갈라야 함 |
| 2 | 특별측점 이름 | 가 「No.2+10 (MC)」(지금 곡선 측점 꼴) · 나 「No.2+10」((MC) 없음) | 가 — 왜 생긴 측점인지 보임 · 고칠 곳 없음 |
| 3 | 「유지」를 무엇에 묶나 | 가 그 곡선 MC(노선 모양으로 기억 · 저장 칸 없음) · 나 곡선마다 저장 칸 · 다 좌표(x · y) 고정(추가 측점 368 꼴) · 라 누가거리 고정 | 가 — 측점의 뜻이 「곡선 가운데」 · 다 · 라는 곡선이 움직이면 MC 에서 떨어짐 · 나는 편집 열 · 되돌리기 · 저장 등 열 곳 넘게 |
| 4 | 앞쪽 고침 · 간격 바꿈 뒤 다시 맞출 때 | 가 손대지 않은 곡선은 N+0 · N+10 그대로 · 나 고른 간격 따라 다시(지금) | 가 |
| 5 | 옛 저장 간격 0.1 · 1 m | 가 측점 No. 로 · 나 10 m 로 | 가(지금 storage 0 건) |
## ⑦ 위험
- 검증_sub8 = 옮긴 일반측점 11 곳 → 다음 [저장] 때 일반측점 제자리 + 곡선 측점 12 곳(그 11 곳 횡단 초안은 `grid:k` 로 제자리 측점에 감 · 자리 칸은 `sameSeat` 로 빠짐) · 다시 저장 전까지 B06 낡음 표시(`B06_Section_Router_Stations.py:161-200` `_curve_marks`) · 다른 프로젝트 0.
- 결정 1 「가」 = 못 맞춘 곡선마다 측점 하나 늚 → 횡단 셈 · 카드 늚.
- 손대지 않은 곡선 후보가 20 m 간격 → 10 m 때보다 못 맞춤이 늘 수 있음 → `test_390_5` 끌기 훑기 「측점 밖 0」 다시 잼.
- `fitDrag` 미리보기가 끌 때마다 옛 노선 MC 를 셈 → 누르는 동안 한 번만(기억).
- `UI_RouteEdit.ts` 695 줄 — 700 안 넘게(셈은 StationFit 에).
- 스냅 끔 때 손으로 MC 를 우연히 N+10(±0.05 m)에 둔 곡선도 특별측점으로 봄 — 드묾 · 사용자 규칙과 안 어긋남.
@@ -0,0 +1,215 @@
# 요구 280-5 · 280-6(화면 배치) — B07 설정 아이콘 · CAD 화면 정리 — 분석
분석 = 클로드 에이전트(Opus 5.5 · 읽기만) · 기준 = `main_laptop_1` `b132c893b` · 추정은 (추정) · 화면 실측 없음(코드 · CSS 셈).
겹침 표시 = 【CAD 동작】(다른 분석: 글자 근접 영역 · 아이콘 둘 강조 · 선 속성 · 이름 바꿈 · DWG 불러오기) · 【페이지 이동】(다른 분석: 좌측 하단 ? < >).
## 0. 결론
- 설정 아이콘 = 공용 카드 함수 `createPanelCard` 에 곁값 하나(⚙ 단추 · 캐럿 왼쪽) · 단일 단추(종단도 1장 · 토적도)는 단추 안 오른쪽 · 모달 = 공용 `openModal` 그대로(B07 평면도 관리 · B05 배수유역 ⚙ 가 이미 씀).
- 평면도 = [평면도 관리] 단추 지우고 ⚙ → 같은 `openPlanManager` · 횡단도 = 배치 칸 · 표 배율 · [안내선 숨기기] 를 모달로 옮김(부품 `buildCrossLayoutControls` · `buildGuideToggle` 그대로).
- X·Y 축척 = **지금 프로젝트마다 값 없음** — 모두 `config_system` 상수(평면도만 종류마다 축척 · 횡단 표 배율만 프로젝트 파일) · 새로 하려면 서버 일(설정 파일 · 엔진 문맥 변수 · 장 나눔 · 덧그림 TS · Node 번들 둘) — 화면 일과 나눔.
- CAD 화면 높이 = 제목줄 42 px + 탭 줄 약 32 px(셈 · 실측 필요) ≈ **74 px 더 확보** · 하단 도면 칸 · 우측 손가락 단추는 떠 있는 칸이라 높이 이득 없음(가림만 걷힘).
- ↶ ↷ = 리본 탭 줄(26 px 고정) 오른쪽 · 단추 20 px(접기 단추와 같은 꼴) → 줄 높이 그대로 · 저장 · JSON 내보내기 = DOM 두고 `hidden`(출력 탭에 같은 명령 남음).
- 탭 줄 = 부모 페이지 DOM · 리본 = iframe — 같은 줄에 두려면 **부모 탭 줄을 iframe 위에 겹쳐 띄움**(CAD 가 빈 자리 좌표를 `.cad-host` 에 CSS 변수로 적음 · ☰ 비키기 `host-burger.ts` 와 같은 꼴).
- ⚠ [현재 도면 확정] = **[수정](확정 풀기)과 한 단추** · 지우면 ① 확정본 읽기 전용을 풀 길 없음 ② 저장(확정) 길 없음 ③ 운영에서 B08 단계 막힘(개발은 우회라 안 보임) — 【페이지 이동】[>] 가 저장을 맡는 설계와 묶어 사용자 결정(7장 D1).
- B08 수량 · B09 은 B07 확정 상태에 기대지 않음(B08 = B05/B06 노선 확정 · B09 인쇄 = 확정본 있으면 저장본 · 없으면 서버 조립) — 기대는 것은 단계 4 완료(운영 단계 잠금)뿐.
## 1. 좌측 패널 항목 — 지금
| 항목 | 컨테이너 | 지금 설정 단추 | ⚙ 자리 | 모달 내용 |
|---|---|---|---|---|
| 표지 | 없음(단추) | 없음 | **없음**(사용자 제외) | — |
| 평면도 | 늘 카드(`buildPlanSection`) | [평면도 관리](카드 맨 아래) | 카드 제목 | 평면도 관리 모달 그대로 |
| 종단도 | 1장 = 단추 · 여러 장(30 측점 · A1 넘침) = 카드 | 없음 | 단추 안 오른쪽 / 카드 제목 | X(가로) · Y(세로) 축척 |
| 횡단도 | 늘 카드(표준 횡단면도 줄 + 장) | 카드 맨 아래 배치 칸(열 · 행 · [이 장] · [모든 장] · 표 배율 · [배율 적용] · [안내선 숨기기]) | 카드 제목 | 배치 칸 전부 + X · Y 축척 |
| 토적도(유토곡선) | 없음(1장 단추) | 없음 | 단추 안 오른쪽 | X · Y 축척 |
| 구조물 도면 | 늘 카드 | 없음 | 카드 제목 | X · Y — M02 그림이라 축척 없음(D3) |
| 전개도 | 늘 카드(종류 하위 묶음) | 없음 | 카드 제목 | X · Y(지금 자동) |
- 목록 정의 `B07_DesignDetail/B07_DesignDetail_UI_Panels.ts:27-45` · 그리기 `:111-187` · 단추/카드 갈림 `:140`(1장 이하 · 횡단 · 구조물 아님 = 단추) · 카드 `:165` · 횡단 칸 자리 `:183`.
- 평면도 `B07_DesignDetail_UI_PlanKinds.ts:256-285`(관리 단추 `:277-283`) · 전개도 `B07_DesignDetail_UI_Development.ts:19-50`.
- 서버 목록 `B07_DesignDetail_Router_Support.py:194-266`(종단 장 · 횡단 장 · 평면 종류 · 표지 · 표준 · 토적 · 구조물 · 전개도).
- 「컨테이너 없음」 = 표지 · 토적도 늘 · 종단도는 장 수에 따라 바뀜 → ⚙ 는 종류(kind) 기준 한 길로(단추든 카드든 같은 모달).
## 2. 공용 부품
| 부품 | 자리 | B07 에서 |
|---|---|---|
| 공용 모달 `openModal({title, closeLabel, mount, dialogClass, actions, wide, beforeClose})` | `ui_template/ui_template_modal.ts:26-62` | 이미 씀(평면도 관리 `B07_DesignDetail_UI_PlanManage.ts:239-254`) · Esc · 바깥 누름 · 첫 입력칸 초점 |
| 공용 카드 `createPanelCard(title, collapsed)` · 접기 위임 `attachCollapsible` | `ui_template/ui_template_collapsible.ts:14-31` | B07 카드 전부 · B08 · B09 도 씀 |
| 캐럿 ▾/▸ = 제목 `::after` · 제목 flex `space-between` | `ui_template/ui_template_theme.css:450-475` | ⚙ 를 제목 안 마지막 자식으로 + `margin-left:auto` → 캐럿 바로 왼쪽 |
| 선례 ⚙ = 글리프 단추 + `openModal` | `B05_Profile/B05_Profile_UI_Drainage_Live.ts:154-159 · 315` | 같은 꼴 |
| 입력 · 단추 `createInputField` · `createButton` | `ui_template/ui_template_elements.ts` | 배치 칸이 이미 씀 |
## 3. 280-5 항목별
### 3.1 설정 아이콘
1. 자리 — 페이지 틀(B07 · CAD 앱 아님). 카드 제목 `h3.ui-collapsible__title` = 글 + 캐럿(`::after`) · 단일 단추 `.b07-drawing-button`(flex · 가운데 · `B07_DesignDetail_UI_Style.css:73-84`).
2. 고침(가장 작게)
- `createPanelCard(title, collapsed, onSettings?)` — 있으면 제목에 `<button class="ui-collapsible__settings" aria-label="설정">⚙</button>` · 누름 = `stopPropagation()` 뒤 `onSettings()`(안 막으면 `attachCollapsible` 이 카드를 접음) · CSS `margin-left:auto` · 글 크기 = 제목 글(제목 높이 그대로).
- 단일 단추 = 단추 안 `<span role="button" tabindex="0">⚙</span>`(단추 안 단추 금지) · `position:absolute; right` + 단추 `position:relative`(이름 가운데 그대로) · 누름 · Enter = `stopPropagation` 뒤 설정(도면 열기 안 감).
- `buildDrawingSidePanel(…, onSettings?: (kind) => void)` 곁값 하나 · 표지 · `blank` 는 안 붙임.
- 모달 몸 = 새 파일 `B07_DesignDetail_UI_Settings.ts`(`openDrawingSettings(kind, …)` · `UI_Page.ts` 633 줄이라 넣으면 700 넘음).
3. 영향 — B08 · B09 카드는 곁값 없으면 그대로 · 제목 `textContent` 에 ⚙ 글자 섞임(B07 시험은 `dataset.kind` 로 봄 · 영향 없음) · 단축키 없음.
### 3.2 평면도 = [평면도 관리] → ⚙
1. 자리 — `B07_DesignDetail_UI_PlanKinds.ts:277-283`(단추) · 연결 `B07_DesignDetail_UI_Panels.ts:116-121` → `openPlanManager` · 닫힘 처리 `B07_DesignDetail_UI_Page.ts:553-569`.
2. 고침 — 단추 몇 줄 지우고 `createPanelCard(\`평면도 ${items.length}\`, collapsed, onManage)` · 모달은 그대로(종류 · 축척 · 장 순서 · 대피소 · 사토장 · 미리보기).
3. 영향 — 기능 잃음 없음 · 시험 `test_130_b07_plan_manage`(단추 글 「평면도 관리」 셈 · 누름) · `test_269_panel_form`(`"ui-panel-btn b07-plan-manage-open"` · `createPanelCard(\`평면도 ${items.length}\`, collapsed)` 글) 고침.
### 3.3 횡단도 배치 칸 → 모달
1. 자리 — 칸 만들기 `B07_DesignDetail_UI_Panels.ts:223-300` · 자리 · 채움 · 저장 `B07_DesignDetail_UI_Page.ts:162-217`(도면 열 때마다 `fillCrossLayout` · 받기 한 번 `crossLayoutRequest`) · 안내선 `B07_DesignDetail_UI_GuideToggle.ts`(모듈 값 · 저장 안 함) · `:132-134 · 215`.
2. 칸 · 저장 자리
| 칸 | 값 | 저장 |
|---|---|---|
| 배치 열 · 행(1~10) | `[열, 행]` | `PUT /api/b07/{id}/cross-layout` → `storage/…/B07_DesignDetail/…/cross_layout.json` `default` |
| [이 장] | 지금 연 장만 | 같은 파일 `sheets["장 번호"]` · 장 아니면 꺼짐 |
| [모든 장] | 기본값 · 장마다 값 지움 | `default` · `sheets: {}` |
| 표 배율(0.5~2) · [배율 적용] | 표 · 표 위 글 크기 | `table_scale` |
| [안내선 숨기기] | 점선 작도 영역 · 붉은 넘침 틀(화면만) | 저장 안 함(모듈 값) · CAD 에 `aislo:b08:guide-visible` |
- 서버 `B07_DesignDetail_Router.py:568-600` · `B07_DesignDetail_Router_Support.py:126 · 152-190`(배치 바꾸면 측점이 바뀐 확정 장은 확정 풀림).
3. 고침 — 모달 열 때 `fetchCrossLayout` · 지금 연 도면이 장이면 그 번호(`isCrossSheet`) · 저장 = 모달 닫고 지금 `save` 흐름 그대로(오버레이 · 쓰기 · 화면 다시 세움 · 같은 장 다시 엶) · [안내선] 단추 요소 하나를 모달 몸으로 옮겨 붙임(누르면 곧바로 · 모달 그대로) · 카드 밑 `crossLayoutSlot` 지움 · `fillCrossLayout` 의 `guideToggle.disabled` 줄은 도면 열 때 그대로.
4. 영향 — 기능 같음 · 좌측이 짧아짐 · 시험 `test_153_1_b07_cross_layout`(자리 = 카드 맨 아래 못박음) · `test_180_3_b07_guide_toggle` · `test_212_3_b07_cross_layout_buttons`(`controls.append(guideToggle);` 가 `UI_Page.ts` 에 있어야 함) 고침.
### 3.4 X · Y 축척
1. 지금 값 · 자리
| 도면 | X(가로) | Y(세로) | 정의 | 프로젝트마다? |
|---|---|---|---|---|
| 종단도 | 1/1,000 | 1/200 | `config/config_system.py:155-156` → `Engine_Cad_Long.py:51-55` 모듈 상수 `MM_H` · `MM_V`(18 곳) · 장 나눔 `:87-125`(A1 에 들게 측점 줄임 = X 에 기댐) · 도각 칸 `:662` | 없음 |
| 횡단도(장) | 1/100 | 1/100 | `config_system.py:157` → `Engine_Cad.py:424` `CROSS_MM` · `Engine_Cad_Sheet.py:98 · 263` · 덧그림 기준 `Engine_Cad.py:625` `mm_per_m`(값 하나) → TS `B07_DesignDetail_Cross_Overlay.ts:562 · 603-605` · `UI_Cad_Structures.ts` · Node `Cross_Extent_Node.ts:21`(`MM_PER_M = 10` 박음) | 없음(표 배율만 있음) |
| 표준 횡단면도 | 1/50 · 측구 상세 1/10 | 같음 | `config_system.py:194-195` → `Engine_Cad_Standard.py:35-45` | 없음 |
| 토적도 | 자동(후보 500~6,000 중 한 장에 드는 것) · 폴백 1/2,000 | 1 mm = 50 ㎥(표기 1:50,000) | `config_system.py:165-183` → `Engine_Cad_MassHaul.py:45-66 · 568` | 없음 |
| 전개도 | 자동(50~5,000 중 처음 드는 것) | 자동(가장 높은 박스 12~40 mm) | `Engine_Cad_Development_Draw.py:45-46 · 242-247 · 428` | 없음 |
| 구조물 도면 | 없음(M02 에서 종이 mm 로 그린 그림 + 도각) | — | `Router_Support_Structure.py` | — |
| 평면도(참고) | 종류마다 고름(500~6,000) | X = Y | `config_system.py:187-191` · 종류 파일 · `Router_PlanKinds.py:67-69` | **있음** |
- 도각 축척 글 = `scale_fields`(`Engine_Template.py:492-506`) — 엔진이 넘긴 분모를 적음 → 값만 바뀌면 도각 글도 따라감 · 도각 양식 `resources/master_template/drawing/*.json`(A1 · 표지 · 방위) **고칠 것 없음**.
- 근거 — 지식DB `technical_info/01_임도/02_상세설계/설계제원_총괄.md` 8장(종단 횡 1/1,000 · 종 1/200 · 횡단 1/100) · `03_미결_및_확인사항.md` #9(사방 종단 1/500 · 1/100 — 다름).
2. 새로 만들 때 고칠 것(서버 일)
- 프로젝트 파일 하나 `drawing_scales.json`(B07 설계 폴더 · `cross_layout.json` 옆) `{kind: {x, y}}` · 없으면 `config_system` 기본 · `GET/PUT /api/b07/{id}/drawing-scales`(검사 = 정수 분모 범위) — 새 파일 `B07_DesignDetail_Router_Support_Scale.py`(`Router_Support.py` 690 줄).
- 엔진 = 모듈 상수 → 지금 요청 값 — 길은 이미 있는 문맥 변수(`Engine_Template.py:61-76` `use_project_templates` 가 세운 프로젝트 폴더) 를 읽는 `drawing_scale(kind)` 한 함수 · 엔진 서명 안 고침(그 파일 머리 「엔진 6개의 서명을 줄줄이 고치지 않는다」와 같은 뜻).
- B09 인쇄도 같은 문맥 변수를 세움(`B09_Draft_Print_Drawings_Source.py:179 · 229`) → 따라감.
- 종단 X 바뀜 = 장 나눔 바뀜 → 장 id(`longitudinal_N`) 밀림 · 도면번호 · B09 인쇄 차례 바뀜 → 그 종류 확정 풀기(배치 바꿈 `write_cross_layout` 과 같은 꼴) 또는 묻기.
- 횡단 X ≠ Y = 덧그림 기준 `mm_per_m` 둘로(서버 · `Cross_Overlay.ts` · `UI_Cad_Structures.ts`) + Node 번들 둘 다시 지음(`config/b07_extent_node` · `tmp/print_drawings_node` — git 밖).
- 확정본 표시 = 표 배율 선례(`stale_scale` · 확정 칸에 값 도장)와 같은 꼴.
3. 영향 — 시험 `test_b07_long_sheet`(장 나눔) · `test_180_2_b07_cross_scale` · `test_b07_cross_overlay_extent` · `test_b07_masshaul_basin` · `test_119_b07_plan_scale` · `test_376_b07_structure_drawings` 돌림 · 백엔드 재시작.
## 4. 280-6 항목별(화면 배치)
높이 지금(셈) — CAD 제목줄 `--cad-title-height` 42 px(`openwebcad/src/App.css:6`) · 리본 122 px(`:9` · 탭 줄 26 px `:164-170` + 패널 줄) · 부모 탭 줄 약 32 px(`ui_template/cad_host/cad_host_tabs.css:3-14` · 위 4 + 탭 27(글 12 px × 1.5 + 4 + 4 + 선 1) + 밑선 1) · 캔버스 위 = 42 + 122 = 164 px.
### 4.1 탭 줄 → 출력 탭과 같은 줄
1. 자리 — 부모 페이지 `B07_DesignDetail_UI_Page.ts:588-590`(`cadStack` = 탭 줄 위 · CAD 아래 · `createCadTabs` `:390-403`) · 리본 탭 줄 = CAD 앱 `openwebcad/src/ribbon/Ribbon.tsx:182-206`(홈 · 삽입 · 주석 · 뷰 · 출력 + 접기 ⌃).
2. 고침
- CAD — 리본 탭 줄에 빈 자리 `<span class="cad-ribbon-tabs__slot">`(flex 1 · 출력 뒤 ↶ ↷ 앞) · 그 좌우 끝을 `window.frameElement.parentElement`(= `.cad-host`)에 `--cad-tab-slot-left/right` 로 적음(크기 바뀔 때 다시) — 짝 선례 `integration/host-burger.ts`(부모 DOM 을 읽음 · 같은 출처).
- 페이지 — 탭 줄을 `.cad-host` 안으로(`cad.element.append(tabs.element)`) · B07 CSS 로 `position:absolute; top:0; left/right = 위 변수; height:26px` · 탭 안 여백만 줄임(`cad_host_tabs.css` 공용은 그대로 — M02 도 씀).
- ☰ 비키기 — `host-burger.css` 의 `.cad-titlebar` 패딩을 `.cad-ribbon-tabs` 로 · `B07_DesignDetail_UI_Style.css:454` `is-bm-route .cad-tabs` 줄은 쓸모 없어짐.
3. 영향 — 기능 같음(닫기 × · 미저장 ●) · 많이 열면 그 칸 안 가로 스크롤 · 접힌 리본 · 잠깐 펼침도 탭 줄은 26 px 그대로라 안 겹침 · 【CAD 동작】`Ribbon.tsx` 겹침.
### 4.2 제목줄 지움 · 저장 · JSON 숨김 · ↶ ↷ 홈 탭 줄 오른쪽 · 램프 지움
1. 자리 — CAD 앱 `components/QuickAccessBar.tsx:17-44`(「Aislo CAD」+ 부제 `:20-23` · 빠른 단추 `QSAVE · EXPORT · UNDO · REDO` `:24-40` · 램프 + 「현재 도면」 `:41-44` · M02 = `hostSave` 면 안 그림 `:17`) · 놓는 곳 `components/Toolbar.tsx:52` · CSS `App.css:88-143 · 275-292` · 목록 `ribbon/ribbon.config.ts:208`.
2. 고침
- `:root --cad-title-height: 0px`(모든 칸이 이 변수로 자리 잡음 · TS 에 박힌 42 없음) · 제목줄 · 상표 · 램프 CSS 지움.
- 빠른 단추 = 리본 탭 줄 빈 자리 뒤 · 접기 ⌃ 앞에 그림(`hostSave` 면 안 그림 그대로 — M02 는 우하단 `ViewControls` 에 ↶ ↷ 있음) · `QSAVE` · `EXPORT` = 그려 두고 `hidden`(되살림 = 숨김 목록에서 빼기).
- 높이 그대로 — 탭 줄 `height: 26px` 고정 · 단추 20 × 24 px · `align-self:center` · `line-height:1`(접기 단추 `:171-185` 꼴) · `margin-left:auto` 는 빠른 묶음으로 옮김(`:172`).
- `hostTitle`(`state.ts` · 다리 `setHostTitle`) = 보일 곳 없어짐 — 남겨도 됨.
3. 영향 — 저장(브라우저 localStorage · 서버 저장 아님) · JSON 내보내기 = 출력 탭에 그대로(`ribbon.config.ts:194-205`) · Ctrl+S = B07 는 원래 아무 일 없음(`input-controller.ts:453-455`) · Ctrl+Z/Y 그대로 · 단독 `/b07-cad/` 도 제목줄 없어짐 · M02 카드가 숨기는 `.cad-titlebar`(`M02_MasterTemplete_DrawingCard.ts:42-44`) = 없는 칸 숨김이라 해 없음 · 【CAD 동작】`App.css` 겹침.
### 4.3 우측 손가락 단추(Esc · ↵ · 🗑 · ↶ · ↷ · ⌖)
1. 자리 — CAD 앱 `inputController/input-touch.ts:122-144`(`mountTouchBar` · 부름 `:119`) · `input-touch.css:1-30` — **`any-pointer: coarse`(손가락 화면이 하나라도 있는 기기)에서만 보임** → 사용자 기기 = 터치 노트북(추정).
2. 고침 — 「버튼만」 = `mountTouchBar` · 그 CSS 지움 · 손가락 동작(끌기 · 벌림 · 길게 누름)은 그대로.
3. 영향 — 자판 있는 기기는 잃는 것 없음(키 · 상태막대 「객체 스냅」 · 새 ↶ ↷) · **자판 없는 태블릿은 Esc · Enter · Delete 길 없어짐**(지움은 리본 수정 ▾ 지우기로 남음) → D2 · 시험 `test_cad_touch.py`(6개 · 44 px 못박음) 고침.
### 4.4 하단 도면 이름 · ‹ › · 미확정/확정 칸
1. 자리 — CAD 앱 `components/QuantityPanel.tsx:1-57`(떠 있는 칸 · 캔버스 아래 가운데) · 놓는 곳 `App.tsx:4 · 11` · CSS `App.css:724-855` · ‹ › = `aislo:b08:navigate` → `cad_host.ts:344-345` → `B07_DesignDetail_UI_Page.ts:127 · 412-417`.
2. 고침 — `App.tsx` 두 줄 · 파일 · CSS 지움 · 따라 죽는 것(`requestDrawingNavigation` · NAVIGATE 상수 · 페이지 `navigateDrawing`) 지움 · `cad_host.ts` `onNavigate` 곁값은 공용이라 남겨도 됨.
3. 영향 — 도면 이름 = 탭 · 좌측 목록 강조 · 확정 표시 = 좌측 단추 초록 띠(`B07_DesignDetail_UI_Style.css:103-110`) · 이전 · 다음 = 목록 · 탭(단축키 원래 없음) · `designMeta` 는 남김(읽기 전용 판정 `state.ts:279` · 수량표 저장 응답 `aislo-drawing-bridge.ts:163`) · 시험 `test_cad_host_tabs.py:221`(`void openTab(drawings[target]);` 글) 고침.
### 4.5 좌측 하단 [현재 도면 확정]
1. 자리 — 페이지 틀 `B07_DesignDetail_UI_Page.ts:226-246`(한 단추 = 미확정 [현재 도면 확정] · 확정 [수정]) · `:421-469` 확정 · `:476-499` 풀기 · `:574-586` 하단 줄([도각 편집] 과 한 줄) · CSS `:116-134`.
2. 확정이 지금 하는 일
| 일 | 자리 |
|---|---|
| CAD 편집본 저장(유일한 서버 저장 길) | `PUT …/design-drawings/{id}/confirm` `Router.py:395-497` → `drawings/{id}.json` + manifest `confirmed` `Router_Support.py:643-683` |
| 확정본 = CAD 읽기 전용 · 푸는 길 = [수정] 하나 | `state.ts:279` · `Page.ts:470-499` · 풀기 = `POST …/invalidate`(원본에서 다시 그림 · B06 측점 설계 잠정 · 단계 4 진행 중) `Router.py:500-545` |
| 횡단 측점 설계 상태 확정(값은 B06 그대로) | `Router.py:454-470` — 읽는 곳 = B07 정보 칸 배지뿐(`Panels.ts:343`) |
| 모든 횡단 장 확정 → 단계 4 완료 · B08 로 자동 이동 | `Router.py:471-474` · `Page.ts:457-460` |
| 운영 단계 잠금(개발 우회) | `Page.ts:600-610` · `ui_template_workflow_layout.ts:104-117` |
| B09 인쇄 = 확정본이면 저장본 · 아니면 서버 조립 | `B09_Draft_Print_Drawings_Source.py:1-8 · 220-236` |
| 「옛 배치 · 옛 표 배율 · 옛 도면층으로 확정된 장 — [수정]을 누르면…」 알림 | `Page.ts:361-376` |
| 저장 안 한 편집 경고 글이 [현재 도면 확정] 을 가리킴 | `Page.ts:326-333` |
3. 저장 후 이동이 확정을 대신하나 — **지금은 아님**. 【페이지 이동】[>] 가 「지금 탭 + 고친 탭(●)」을 confirm 길로 보내면 저장은 대신함 · 읽기 전용 풀기 · 단계 4 완료는 따로 정해야 함(D1).
4. 기대는 다른 페이지 — B08 = 아니오(`get_confirmed_route_context` = B05/B06 노선 확정 · B07 엔진 함수 `derived_cells` 만 씀) · B09 인쇄 = 저장본 고름에만 · 단계 잠금(운영) = 예.
5. 고침(갈래 D1-가 기준 · 가장 작게) — 단추 · `confirmCurrentDrawing` · `reopenCurrentDrawing` 의 단추 연결 지움(함수 몸은 [>] 저장 · 설정 모달 [원본에서 다시 그리기]가 다시 씀) · 읽기 전용 = `meta.confirmed` 로 안 묶음 · 알림 · 경고 글 고침 · [도각 편집] 줄은 그대로 · 【페이지 이동】과 같은 파일(`UI_Page.ts` · 하단 `ui-sidebar-actions`).
- 단계 4 완료 = 서버 한 길 더(「모든 횡단 확정」 규칙 `Router.py:471-474` 대신 [>] 저장 뒤 완료 · 새 PUT 또는 confirm 곁값) · 운영 잠금 `Page.ts:141-142 · 605` 의 `allDrawingsConfirmed` 도 같은 기준으로.
## 5. 코더 나눔
| 창 | 일 | 파일 | 겹침 | 모델 |
|---|---|---|---|---|
| 창 1 CAD 앱 | 4.1 CAD 쪽(빈 자리 · 변수) · 4.2 · 4.3 · 4.4 | `openwebcad/src/` `components/QuickAccessBar.tsx` · `Toolbar.tsx` · `ribbon/Ribbon.tsx` · `ribbon/ribbon.config.ts`(글) · `App.css` · `App.tsx` · `components/QuantityPanel.tsx`(지움) · `inputController/input-touch.ts · .css` · `integration/host-burger.ts · .css` · `integration/aislo-drawing-bridge.ts` | 【CAD 동작】`Ribbon.tsx` · `ribbon.config.ts` · `QuickAccessBar.tsx`(누른 뒤 blur) · `App.css` → 같은 창이 이어서 하거나 차례로 | Opus high |
| 창 2 B07 페이지 | 3.1 ~ 3.3 · 4.1 페이지 쪽(탭 줄 겹판) · 4.5 · 축척 칸 자리(빈 칸 없이 — 3 창 뒤 채움) | `ui_template/ui_template_collapsible.ts` · `ui_template_theme.css` · `B07_DesignDetail_UI_Panels.ts` · `_UI_PlanKinds.ts` · `_UI_Development.ts` · `_UI_Page.ts` · `_UI_Style.css` · 새 `_UI_Settings.ts` | 【페이지 이동】`_UI_Page.ts` · `_UI_Style.css` · 하단 줄 → B07 몫 페이지 이동을 이 창이 같이 또는 그 뒤 | Opus high |
| 창 3 축척 서버 | 3.4 | 새 `B07_DesignDetail_Router_Support_Scale.py` · 라우터(새 파일 또는 `Router_Frame.py`) · `_Engine_Template.py`(읽는 함수) · `_Engine_Cad_Long.py` · `_Engine_Cad.py` · `_Engine_Cad_Sheet.py` · `_Engine_Cad_MassHaul.py` · `_Engine_Cad_Development_Draw.py` · `_Api_Fetch.ts` · `_UI_Settings.ts`(축척 칸) · (X≠Y면) `_Cross_Overlay.ts` · `_UI_Cad_Structures.ts` · `_Cross_Extent_Node.ts` | 창 2 의 `_UI_Settings.ts` → **창 2 끝난 뒤** | Opus high |
- 창 1 · 창 2 병행 · 계약 = `.cad-host` 의 `--cad-tab-slot-left/right`(px) 두 값 · 창 3 은 창 2 뒤(빈 모달을 사용자에게 안 보이게 2 · 3 합친 뒤 화면 검증).
- 반영 — 창 1 = `npm run build`(openwebcad) → reload(옛 화면이면 새 탭) · 창 2 = reload · 창 3 = 백엔드 재시작 + Node 번들 다시 지음(X≠Y 일 때).
- 700 줄 — `UI_Page.ts` 633 · `Router_Support.py` 690 · `Engine_Cad_Long.py` 677 · `Engine_Cad.py` 647 → 새 일은 새 파일.
## 6. 시험 · 화면 확인
시험(고친 대상만 · `./venv/Scripts/python.exe -m pytest resources/tester/<파일> -q`)
| 창 | 지금 시험(돌림 · 고칠 것 ✎) | 새 시험 |
|---|---|---|
| 창 1 | `test_cad_touch.py` ✎ · `test_m02_drawing_card.py` · `test_cad_host_tabs.py` · CAD vitest(`resources/tester/cad` · `openwebcad/src/state.test.ts` ✎ 읽기 전용 바꾸면) · `npm run typecheck` | `test_280_6_cad_chrome.py` — 글 판정: 제목줄 없음 · 탭 줄 안 빠른 단추 · QSAVE · EXPORT `hidden` · `QuantityPanel` 없음 · 손가락 단추 없음 · `--cad-title-height: 0px` |
| 창 2 | `test_130_b07_plan_manage.py` ✎ · `test_269_panel_form.py` ✎ · `test_153_1_b07_cross_layout.py` ✎ · `test_180_3_b07_guide_toggle.py` ✎ · `test_212_3_b07_cross_layout_buttons.py` ✎ · `test_311_panel_folded.py` · `test_cad_host_tabs.py` ✎ · `test_212_b07_cross_station_layers.py` · `test_b07_confirm_keeps_design.py` | `test_280_5_b07_settings.py` + Node 헬퍼 — ⚙ 가 표지 빼고 붙음 · 누름 = 안 접힘 · 도면 안 엶 · 모달 제목 · 평면도 ⚙ = `openPlanManager` · 횡단 모달 = 배치 칸 + 안내선 · 카드에 배치 칸 없음 |
| 창 3 | `test_b07_long_sheet.py` · `test_180_2_b07_cross_scale.py` · `test_b07_cross_overlay_extent.py` · `test_b07_masshaul_basin.py` · `test_119_b07_plan_scale.py` · `test_376_b07_structure_drawings.py` | `test_280_5_b07_drawing_scales.py` — 파일 없음 = 기본 · 쓰기 · 읽기 · 범위 밖 400 · 종단 X 1/500 = 그림 폭 2 배 · 도각 축척 글 「500(H) · 200(V)」 · 바꾸면 그 종류 확정 풀림 |
화면 확인(ORCA · `eval` 수치 · 고치기 **전에** 같은 창 크기로 기준값 떠 둠)
- `f = document.querySelector('.cad-host__frame')` · `d = f.contentDocument`
- 높이 — `d.querySelector('.cad-ribbon-tabs').offsetHeight` = 26(전후 같음) · `d.querySelector('.cad-ribbon').offsetHeight` = 122 · `.cad-ribbon` 위 = 0 · 캔버스 `getBoundingClientRect().top` = 122(전 164) · 캔버스 높이 = 전 + 42 + 전 탭 줄 높이(≈ 74).
- 탭 줄 — 부모 `.cad-tabs` 위 − iframe 위 = 0 · 높이 ≤ 26 · 왼쪽 ≥ iframe 왼쪽 + [출력] 탭 오른쪽 · 오른쪽 ≤ ↶ 왼쪽 · 탭 눌러 장 바뀜 · ☰ 좁은 화면에서 [홈] 안 가림.
- 빠른 단추 — `.cad-ribbon-tabs` 안 ↶ ↷ 보임 · 💾 ⭳ `offsetParent === null` · `d.querySelector('.cad-titlebar')` = null · `.cad-file-state` = null.
- 손가락 단추 · 하단 칸 — `d.querySelector('.cad-touch-bar')` = null(D2-가) · `d.querySelector('.cad-qtable')` = null.
- 좌측 — 하단 줄에 「현재 도면 확정」 · 「수정」 글 없음 · 카드 제목 ⚙ 개수 = 평면도 · 횡단도 · 구조물 · 전개도 (+ 종단 카드면) · 단일 단추 ⚙ = 종단(1장) · 토적 · 표지 0 · ⚙ 전후 제목 `offsetHeight` 같음 · ⚙ 누름 → `.ui-modal` 1 · 카드 `is-collapsed` 그대로 · 탭 수 그대로.
- 횡단 모달 — 「배치 열」 · 「행」 · [이 장] · [모든 장] · 「표 배율」 · [안내선 숨기기] · [안내선] 누름 → CAD 점선 꺼짐(`aria-pressed`) · 배치 바꿔 적용 → 장 수 바뀜 → 원래 값으로 되돌림.
- 축척(창 3) — 종단 X 1/500 적용 → 종단 장 수 · 도각 축척 글 · 그림 폭 → 1/1,000 · 1/200 으로 되돌림.
## 7. 사용자 결정 갈래
| # | 물음 | 갈래 | 추천 |
|---|---|---|---|
| D1 | [현재 도면 확정] 지운 뒤 확정 | 가. 확정 = 「고친 도면 저장」 · 잠금 없음 — [>] 가 고친 탭(●) + 지금 탭 저장 → 단계 4 완료 → 이동 · 안 고친 도면은 늘 원본에서 새로 그림(B06 바뀜 따라감) · 저장본을 원본으로 = 설정 모달 [원본에서 다시 그리기] / 나. 잠금 그대로 — [>] 가 지금 · 고친 탭 확정 · [수정](풀기)은 설정 모달로 · 단계 완료는 지금처럼 「모든 횡단 장 확정」 / 다. [>] 가 모든 도면 확정(안 고친 장도 저장본으로 굳음 · B06 바뀜 안 따라감) | **가** — 사용자 말(「저장 후 이동이라 불필요」)과 맞음 · 나는 저장할 때마다 잠겨 다시 고치려면 [수정] 을 찾아야 함 · 09-01 「확정 풀기는 [수정] 하나」 결정을 바꾸는 일이라 물음 |
| D2 | 우측 손가락 단추 | 가. 다 지움(말 그대로 · 태블릿은 Esc · Enter 없어짐) / 나. 자판 없는 기기만 남김(`(hover:none) and (pointer:coarse)` · 터치 노트북엔 안 보임) | **나** — 겹침이 까닭이라 겹치는 기기에서만 걷힘 · 그래도 보이면 가 |
| D3 | 축척 칸 범위 | 종단 · 횡단 · 토적 · 전개도 X · Y 두 칸 / 구조물 도면 = 칸 없음(M02 그림) 또는 ⚙ 자체 없음 / 횡단 X ≠ Y 허용(덧그림 · Node 번들 고침) 또는 한 값 / 표준 횡단면도(1/50 · 1/10) 넣나 | X · Y 두 칸(도구 원칙) · 구조물 = ⚙ 있되 「M02 그림 — 축척 없음」 한 줄 · 횡단 X ≠ Y 허용(기본 1/100 · 1/100) · 표준 횡단 = 뺌 |
| D4 | 축척 값 받는 꼴 | 가. 분모 숫자 칸(정수 · 범위만 검사) / 나. 고르기 목록(지식DB 근거 값만) | **가** — 기본 = 지금 상수(임도 법정 · 08-29 확정) · 토적 · 전개도 = 「자동」 그대로 고를 수 있게 |
| D5 | 축척 바꾸면 그 종류 저장본(확정본) | 가. 물은 뒤 풀고 새로 그림(편집 사라짐) / 나. 그대로 두고 「옛 축척」 알림(표 배율 선례) | **가** — 축척 다른 저장본은 쓸모없음 · 묻는 창에 n 장 표시 |
| D6 | 축척 저장 층 | 프로젝트마다 / M02 마스터 기본 | 프로젝트마다(기본은 `config_system`) |
## 8. 위험
- 탭 줄 겹판 = iframe 이 잰 값에 기댐 — 리본 탭 글 · 글꼴이 바뀌면 다시 재야 함(크기 바뀜 관찰) · 잰 값 없을 때 기본값으로 [출력] 을 덮으면 그 탭이 안 눌림.
- ☰(좁은 화면) — 비키기 패딩을 리본 탭 줄로 안 옮기면 ☰ 가 [홈] 을 가림.
- CAD 앱은 B07 · M02 · B06 표 위 CAD · 평면도 미리보기가 함께 씀 — 제목줄 · 손가락 단추 · 하단 칸 지움은 모두에 번짐(M02 = 원래 제목줄 없음 · 하단 칸은 meta 없어 안 그림 · 미리보기 = `bare` · 영향 작음) · ↶ ↷ 는 `hostSave` 면 안 그려 M02 와 안 겹침.
- 확정 단추 지움 — D1 정하기 전에 지우면 저장 길 · 읽기 전용 풀기 · 운영 B08 길이 함께 끊김(개발은 단계 우회라 시험 · 화면에서 안 보임) → 【페이지 이동】[>] 저장 연동과 **같은 커밋**.
- 저장 안 한 탭 편집 — 지금은 탭에만 받아 둠 · [>] 가 그 탭들까지 저장 안 하면 페이지 이동 때 사라짐.
- 종단 X 축척 = 장 나눔 · 장 id · 도면번호 · B09 인쇄 차례가 함께 움직임.
- 횡단 축척 = 칸 넘침(붉은 틀) 늘어남 · 글 크기는 종이 mm 라 그대로 · Node 번들 두 벌은 git 밖이라 싱크 뒤 그 폴더에서 다시 지어야 반영.
- 공용 `createPanelCard` 곁값 — 안 넘긴 B08 · B09 는 그대로이나 같은 파일이라 B08 · B09 작업 창과 겹칠 수 있음.
- 겹치는 파일 — 【CAD 동작】 `Ribbon.tsx` · `ribbon.config.ts` · `QuickAccessBar.tsx` · `App.css` · 【페이지 이동】 `B07_DesignDetail_UI_Page.ts` · `B07_DesignDetail_UI_Style.css`.
- 【페이지 이동】분석(`2026-10-19_요구280_B_페이지이동.md`)은 B07 [>] = 지금 장 `/confirm` 1 번(D1-나 꼴)으로 둠 · B07 몫을 280-6 창에 붙임 → D1 답이 두 분석을 함께 정함 · 가면 [>] 가 고친 탭 전부 + 단계 완료까지.
@@ -0,0 +1,253 @@
# 요구 280-6 — CAD 동작 여섯 가지 · DWG 불러오기 (분석 · 코드 X)
경로 앞머리 `src/` = `B07_DesignDetail/openwebcad/src/`. CAD 앱 고침은 `npm run build` 뒤 반영.
## 1. 글자 스침 테두리 ≠ 더블클릭 입력 칸
### ① 지금 동작
| 항목 | 스침 테두리 `src/entities/TextEntity.ts:75-87` → `getBoundingBox` `:144-150` | 더블클릭 칸 `src/integration/text-inplace-edit.ts:148-167` | 실제 글 `src/drawControllers/screenCanvas.drawController.ts:544-580` |
| --- | --- | --- | --- |
| 기준점 | 상자 왼아래 = 기준점 | 정렬대로(왼 0 · 가운데 −50% · 오른 −100%) | 정렬대로(기본 가운데 · `DrawController.ts:59`) |
| 가로 | x ~ x + 글크기 × 글자 수 | 잰 폭 + 1.5 × 글크기 | 잰 폭 |
| 세로 | y ~ y + 글크기(위로만) | y ± 0.6 × 글크기(가운데) | 가운데 맞춤(`textBaseline = 'middle'` `:577`) |
| 회전 | 없음(축 나란한 상자) | 글 방향 따라 돎 | 돎 |
| 폭 잴 글꼴 | — | 기본 글꼴 · 굵게 · 기울임 빠짐(`measureTextScreen` `:529-535`) | 실제 글꼴 |
| 여러 줄 | 그 줄 하나 | 묶음 전체(`textLinesOf` `:40-51`) | — |
| 잡는 범위 | 기준점 반경 10 px(`input-controller.ts:279-291` · `pick-radius.ts:17` · `App.consts.ts:63`) | 기준점 거리 ≤ max((글자 수+2)×0.5×글크기, 2×글크기)(`:239-241`) | — |
| 최소 크기 | 없음 | 글 10 px 아래로 안 줄어듦(`:33` · `:151`) | — |
수치 보기(글크기 3 · 가운데 정렬 · 잰 폭 근사):
| 글 | 스침 테두리(월드) | 입력 칸(월드) | 어긋남 |
| --- | --- | --- | --- |
| 「배수관」 3자 · 폭 ≈ 9 | x 0 ~ +9 · y 0 ~ +3 | x −6.75 ~ +6.75 · y −1.8 ~ +1.8 | 왼끝 6.75 · 아래끝 1.8 · 위끝 1.2 |
| 「STA. 0+000」 10자 · 폭 ≈ 15.9 | x 0 ~ +30 · y 0 ~ +3 | x −10.2 ~ +10.2 · y −1.8 ~ +1.8 | 오른끝 19.8 더 넓음 |
| 90° 돈 글 | 가로로 누운 상자 | 세로로 선 칸 | 전부 |
### ② 원인
- 스침 테두리가 옛 원본의 임시 상자(`// TODO find better way` `:144`) — 정렬 · 가운데 맞춤 · 회전 · 실제 폭 모두 안 봄.
- 입력 칸은 293-1 때 따로 새로 계산 — 두 계산이 한 부품이 아님.
### ③ 고치는 방법
- 새 순수 함수 한 벌(예 `src/helpers/text-frame.ts`) — 글 테두리 = 정렬 · 가운데 맞춤 · 실제 폭 · 높이 1.2 × 글크기 · 회전 · 여유 한 값.
- `TextEntity.draw` 테두리(`:75-87`) = 그 네 귀(돈 사각형). `getBoundingBox` 는 그대로(줄맞춤 · 그립 · 공간 색인 · 창 고르기가 씀).
- `openTextEditor` `place`(`:148-167`) = 같은 함수 값으로 칸 자리 · 폭 · 높이. 여유 `fontPx * 1.5` 를 같은 여유 값으로.
- 폭 재기 = 기존 `measureTextScreen` 에 글꼴 · 굵게 · 기울임 받기(`drawTextScreen` 과 같은 글꼴 줄) · 파일 691줄 → 700 안쪽 유지.
- 자리표 칸 글(`hasBox` · 도각) = 테두리가 칸 · 입력 칸도 칸 크기로(결정 1-가).
- 고르기(선택) : 잡는 범위도 테두리로 — `TextEntity.distanceTo`(`:204-206`) = 테두리 안 0 · 밖은 테두리까지 거리 · 더블클릭은 `pickRadius()` 로(결정 1-나).
- 여러 줄 : 스침 때 묶음 전체 강조 — 기존 `expandSelectionWithGroups`(`helpers/entity-groups.ts:7`)를 스침 자리에 한 번(결정 1-다).
## 2. 상단 리본 아이콘 둘이 함께 켜짐
### ① 지금 동작
- 켜짐 규칙 = `command.tool === activeTool`(`src/ribbon/Ribbon.tsx:33`) · 도구 겹침 없음(명령 120개 도구 · 머신 이름 · 머신 `type` 모두 하나씩 — 전수 확인).
- 쉬는 상태 = 늘 「선택」 도구(`main.tsx:178` · 명령 끝 `tools/factories/end-command.ts:22`) → 홈 탭 「유틸리티」 큰 단추 「선택」(`ribbon.config.ts:119` · `commands.view.ts:33-40`)이 늘 켜짐.
- 스침 모양 = 켜짐 모양과 같음 — 둘 다 강조색 테두리 + 바탕(`App.css:378-382`) · 켜짐만 밑줄 2 px 더함(`:383-385`).
- 단추를 누르면 초점이 단추에 남음(초점 옮김 없음 · `ime-sink.ts:68-77` 은 글 받을 때만) · `.cad-tool` 초점 모양 없음 → 브라우저 기본 초점 테두리.
### ② 원인(세 갈래 · 앞 둘이 주원인)
| 갈래 | 보이는 때 | 판정 |
| --- | --- | --- |
| 가. 스침 = 켜짐 모양 | 쉬는 중 다른 단추에 마우스 → 「선택」 + 스친 단추 | 코드로 확정 |
| 나. 남은 초점 테두리 | 「선」 누름 → Esc · 단축키 → 「선택」 켜짐 + 「선」 초점 테두리 | 추정 · 화면 확인 필요(크롬은 키 누름 뒤 초점 테두리를 그림) |
| 다. 터치 · 펜 스침 남음 | 손가락으로 누른 단추가 스침 모양으로 남음 | 추정 |
### ③ 고치는 방법
- `App.css:378-385` — 스침 = 바탕만 · 켜짐 = 테두리 + 바탕 + 밑줄 · 스침 줄을 `@media (hover: hover)` 안으로(다 해결).
- `Ribbon.tsx` `CommandButton` · 넘침 단추(`:133-144`) · `QuickAccessBar.tsx:29-36` — 누른 뒤 `currentTarget.blur()` 한 줄(나 해결).
## 3. 이름 바꿈 — 선가중치 → 선 두께 · 도면층 → 레이어
### ① 지금 동작(보이는 글만 · 주석 뺌)
| 파일 | 선가중치 | 도면층 |
| --- | --- | --- |
| `src/commands/commands.layer.ts` | — | 28줄(이름 · 설명 · 28-163) |
| `src/tools/utility/layer-tools.ts` | — | 40줄(안내 · 알림 · 38-264) |
| `src/components/RibbonWidgets.tsx` | 85 · 86 | 133 · 134 · 151 · 155 |
| `src/components/PropertiesEditor.tsx` | 52 | 89 |
| `src/commands/commands.utility.ts` | 61(LWEIGHT 이름) | 195 |
| `src/tools/utility/property-tools.ts` | 103 · 113 | — |
| `src/components/LayerManager.tsx` | — | 164 · 166 |
| `src/commands/commands.modify.ts` | — | 290 |
| `src/components/InspectorPanel.tsx` | — | 43(왼쪽 탭) |
| `src/components/DropHint.tsx` | — | 20 |
| `src/helpers/get-new-layer.ts` | — | 9(새 층 이름 「새 도면층 N」) |
| `src/ribbon/ribbon.config.ts` | — | 89(패널 이름) |
| `src/tools/modify/offset-tool.ts` | — | 65(OFFSET 덧말) |
| `src/tools/modify/property-tools.ts` | — | 81 |
| `src/commands/command-catalog.json`(거울) | 1(111) | 10 |
| 합 | 6곳 | 보이는 83줄 + 거울 10 |
- 거울 JSON = ☰ [설정] CAD 단축키 탭이 읽음(`A00_Common/app_settings_cad.ts`) · 시험 `command-catalog.test.ts` 가 registry 와 같은지 봄.
- CAD 밖 한 곳 — `B07_DesignDetail/B07_DesignDetail_UI_Page.ts:373`(「측점 도면층 앞에 확정된 장」).
- 서버 글 · M02 화면 글에는 없음.
### ② 원인
- 원본(AutoCAD 한글판 낱말)을 그대로 씀.
### ③ 고치는 방법
- 글만 바꿈 · 명령 이름(LAYER · LWEIGHT) · 별칭 · 저장 열쇠(`layerId` · `lineWidth` · `layers`) 안 바꿈.
- 토씨 손봄 22곳 — 「도면층을 → 레이어를」 10 · 「도면층으로 → 레이어로」 8 · 「도면층은 → 레이어는」 2 · 「도면층과 → 레이어와」 2. 일괄 치환 금지.
- `command-catalog.json` 같은 커밋에.
- 「ByLayer로」(SETBYLAYER 이름 · JSON 50) — 그대로 둠 추천.
## 4. 색상 · 선가중치 · 선종류 메뉴 = 고른 것의 값
### ① 지금 동작
- 리본 「특성」(`src/components/RibbonWidgets.tsx:64-125`) = 늘 **그리기 기본값**(`getActiveLineColor/Width/Dash`) — 고른 것과 무관.
- 바꾸면 고른 것 + 그리기 기본값 둘 다 바뀜(`:78` · `:91` · `:110`).
- 왼쪽 특성 · 빠른 특성(`PropertiesEditor.tsx:19-104`) = 첫 객체 값만.
- 다시 그림 = 고름 바뀜이 `UPDATE_STATE` 를 쏨(`state.ts:376-387`) → 리본이 다시 읽음(`use-cad-refresh.ts`) — 이벤트 길은 이미 있음.
- ByLayer = 없음 — 객체가 색 · 두께 · 점선을 제 값으로 가짐 · 층 값은 `SETBYLAYER` 가 한 번 베껴 줄 뿐(`tools/modify/property-tools.ts:66-80`).
### ② 원인 · 같이 나온 흠
| 흠 | 자리 | 영향 |
| --- | --- | --- |
| 고른 것 값을 안 읽음 | `RibbonWidgets.tsx:65-67` | 요구 그대로 |
| **되돌리기 깨짐** — 객체를 제자리에서 고침 → 되돌리기 칸이 같은 객체를 쥐어 옛 값이 없음 · 같은 값 판정(`state.ts:600`)으로 칸도 안 쌓임 → Ctrl+Z 가 바로 앞 다른 작업(예 방금 그린 선)을 지움 | `RibbonWidgets.tsx:39-47` · `PropertiesEditor.tsx:12-17` | 코드 읽기로 확정 · 시험으로 먼저 재현 |
| **폴리선 색 · 두께가 안 바뀜** — 폴리선은 자식 선이 제 색으로 그림(`PolyLineEntity.ts:42-54`) · 메뉴는 부모 값만 고침 | 같은 곳 | B07 엔진 선은 거의 폴리선 → 바꿔도 화면 그대로 |
| 엔진 점선이 「실선」으로 보임 — 목록 밖 점선([6,4] · [4,2] · [3,3] · [8,3,2,3] · [10,3,2,3] · `Engine_Cad.py` · `_PlanKinds.py` · `_Standard.py`)을 실선으로 떨굼 | `dashToLineType` `:35-36` | 값 틀리게 보임 |
| 두께 목록 밖 값(1~5 밖) = 첫 항목처럼 보임 | `:97-101` | 값 틀리게 보임 |
| 같은 되돌리기 흠 — COLOR · LINETYPE · LWEIGHT · TRANSPARENCY(`tools/utility/property-tools.ts`) · MATCHPROP · SETBYLAYER(`tools/modify/property-tools.ts:28-34` · `:70-79`) · 레이어 옮기기 명령(`layer-tools.ts`) | — | 결정 4-라 |
### ③ 고치는 방법
- 새 작은 부품 한 벌(예 `src/helpers/selection-style.ts`) 둘:
- 읽기 = 고른 것의 「선 잎」(폴리선 → 자식 · 나머지 → 자신 · 글 빼기) 값이 모두 같으면 그 값 · 다르거나 목록 밖이면 빈 값 · 고른 것 없으면 그리기 기본값.
- 쓰기 = 기존 `changedCopies` + `replaceInPlace`(`src/tools/transform.helpers.ts:94-109` · id 그대로 사본으로 갈아 끼움 · 되돌리기 한 칸) · 사본의 폴리선 자식까지 같은 값.
- 리본 · 왼쪽 특성이 같은 부품을 씀(결정 4-다).
- 고르기 상자 = 맨 앞에 숨은 빈 항목(`<option value="" hidden>`) · 빈 값일 때 빈 칸으로 보임 · 고르면 고른 것 모두에 적용.
- 색 = `<input type=color>` 는 빈 값 불가 → `data-mixed` 붙여 CSS 로 빈 견본(투명 · 점선 테두리) · 고르면 적용(결정 4-나).
- 색 끌기 중 연발(`paintNowCommitLater` `:54-62`) = 첫 연발에 사본 만들고 이후 그 사본만 칠함 · 멎으면 한 칸 실음.
- 고른 것 있을 때 = 고른 것만 바뀜 · 그리기 기본값 그대로(결정 4-가).
- 점선 판정 = `[]` · `undefined` 모두 실선.
## 5. 우측 위 오토캐드 안내 글 줄바꿈
### ① 지금 동작
- 글 = 「오토캐드에서 WBLOCK(또는 DXFOUT)으로 저장 → 파일을 여기로 끌어다 놓기」 · 누르면 ①②③ 세 줄(`src/components/DropHint.tsx:15-21`).
- 자리 = 리본 바로 아래 오른쪽(`App.css:534-552` · `top: title + ribbon + 8px`) · B07 · M02 양식 둘 다.
### ② 원인
- `max-width: 360px`(`App.css:543`) + 줄바꿈 막기 없음 · 글 폭 ≈ 390 px(11 px) → 두 줄 · ①줄도 10 px 로 ≈ 400 px → 두 줄.
### ③ 고치는 방법
- `.cad-drop-hint` = `white-space: nowrap` · `max-width` 걷음(①②③ 줄도 따라감) — 두 줄.
- 안내가 말하는 길 = 끌어다 놓기 → `/api/projects/dxf-clip/file`(`src/integration/dxf-clip-paste.ts:37-43` · `:99-105`) → **M02 [파일 업로드]와 같은 서버 엔진**(아래 6장 표) · WBLOCK 은 도면 일부만 보낼 때만 필요 · 파일 통째도 됨.
- 6장 단추가 생기면 안내는 한 줄로 줄임 추천(결정 5).
## 6. 삽입 탭 「DWG 불러오기」
### ① 지금 동작 — 같은 엔진 두 길
| | M02 구조물 조합 · 양식 [파일 업로드] | B07 CAD 끌어다 놓기(이미 있음) |
| --- | --- | --- |
| 화면 | `M02_MasterTemplete/M02_MasterTemplete_Drawing.ts:296-331`(숨은 file 입력 `.dxf,.dwg`) · 보냄 `:145-161` | `src/integration/dxf-clip-paste.ts:37-60` · 끌기 `:88-105` · 붙이기 `prepareClip`(`dxf-clip.ts:24-49`) |
| 서버 | `POST /api/m02/drawing-import`(`M02_MasterTemplete_Router_Drawing.py:57-91`) → `import_frame_file` | `POST /api/projects/dxf-clip/file`(`B07_DesignDetail_Router_Frame.py:193-219`) → `drawing_file_to_entities` |
| 변환 | 둘 다 `_read_drawing_file`(`B07_DesignDetail_Engine_Frame_Import.py:375-385`) — DWG = LibreDWG `dwg2dxf` 별도 실행(동봉 `openwebcad/tools/libredwg/dwg2dxf.exe` · `:334-364` · 180 s) → ezdxf | 같음 |
| 레이어 | 한 층 「도각」으로 몰음(`frame_document` `Engine_Template.py:179-185`) | DXF 층 이름 그대로 · 같은 이름 있으면 그 층 · 없으면 새 층(층 색) |
| 넣는 방식 | 도면 **통째 바꿈** · 저장 전 | 지금 도면에 **더함** · 원좌표 1:1 · 골라진 채 · 되돌리기 한 칸(`addEntities(…, true)`) |
| 권한 | 로그인(`main.py:681` `protected`) | 로그인 + 회사(`main.py:655` `protected_with_company`) · 프로젝트 무관 · 저장 안 함 |
| 한도 | 20 MB · 도형 20,000 · DWG R14 ~ 2018(AC1032) | 같음(`Router_Frame.py:85`) |
- 읽는 도형 = 선 · 폴리선 · 원 · 호 · 타원 · 스플라인(선분으로) · 점 · TEXT · MTEXT · 블록 · 치수 · 지시선은 풀어서(`:208-261`).
- 버림 = 해치 · 솔리드 · 그림 · 선종류(점선) · 선 두께(모두 1).
- ByLayer 색 도형 = 흰색(`_color` `:64-73`) — 층 색은 새 층에만 실림 → B07 쪽은 오토캐드 도면이 거의 흰색으로 들어옴.
- 시험 `resources/tester/test_b07_dxf_clip.py` 에 DWG 파일 읽기(`REpyo.dwg`)까지 있음.
### ② 원인
- 단추만 없음 — 끌어다 놓기를 모르면 못 씀.
### ③ 고치는 방법
- `src/commands/commands.insert.ts` 에 명령 하나 — 이름 `IMPORT`(오토캐드 같은 이름) · 「DWG 불러오기」 · `run` = 기존 `pickFile('.dwg,.dxf', …)`(`helpers/pick-file.ts`) → 기존 `pasteDrawingFile` + 알림(`dxf-clip-paste.ts:62-68` `reportPaste` 를 내보냄).
- `src/ribbon/ribbon.config.ts:147-148` 삽입 탭 — 새 패널 「가져오기」 큰 단추 하나.
- `command-catalog.json` 한 줄.
- 확정 도면 막기 = `runCommand` 가 이미 막음(`run-command.ts:32`) · 서버 · 변환 · 레이어 · 되돌리기 = 그대로 재사용.
- `pickFile` 입력을 문서에 붙였다 뗌(`hidden`) — 지금은 문서 밖 입력이라 ORCA `upload` 가 못 닿음.
- (결정 6-다) ByLayer 도형 색 = 층 색 — `_entities_with_layers`(`:290-294`) 한 곳 · B07 붙이기 길만 바뀜(M02 양식 그대로).
## ④ 코더 나눔
| 창 | 항목 | 파일 | 모델 |
| --- | --- | --- | --- |
| 가 | 1 · 2 | `entities/TextEntity.ts` · `integration/text-inplace-edit.ts` · 새 `helpers/text-frame.ts` · `drawControllers/screenCanvas.drawController.ts`(691줄 · 늘림 최소) · `inputController/input-controller.ts`(1-다) · `ribbon/Ribbon.tsx` · `components/QuickAccessBar.tsx` · `App.css` `.cad-tool` 줄 | opus high(회전 · 정렬 기하) |
| 나 | 3 · 4 · 5 · 6 | 3장 표 파일 전부 · `components/RibbonWidgets.tsx` · `components/PropertiesEditor.tsx` · 새 `helpers/selection-style.ts` · `components/DropHint.tsx` · `commands/commands.insert.ts` · `ribbon/ribbon.config.ts` · `integration/dxf-clip-paste.ts` · `helpers/pick-file.ts` · `commands/command-catalog.json` · `App.css` `.cad-drop-hint` · 색 견본 줄 · (6-다) `B07_DesignDetail_Engine_Frame_Import.py` | opus medium(되돌리기) |
- 가 · 나 겹침 = `App.css` 하나(다른 규칙 덩이) — 합칠 때 충돌 적음.
- 3 · 4 를 한 창에 묶은 까닭 = `RibbonWidgets.tsx` · `PropertiesEditor.tsx` · `command-catalog.json` 같은 줄.
- **화면 배치 분석(다른 에이전트)과 겹칠 파일** — `App.css`(1102줄 · 이미 700 넘음) · `ribbon/Ribbon.tsx`(되돌리기 · 다시하기 홈 탭 오른쪽) · `ribbon/ribbon.config.ts`(`QUICK_ACCESS_COMMANDS`) · `components/QuickAccessBar.tsx`(제목 줄 지움) · `components/Toolbar.tsx` · `components/DropHint.tsx`(제목 줄 지우면 `--cad-title-height` 따라 위로 감).
- 같은 요구의 「드롭다운 고른 뒤 초점 남음」 항목 = `RibbonWidgets.tsx` 고르기 상자 · `Ribbon.tsx` 단추와 같은 뿌리(초점 남음) — 그 항목 코더와 같은 창이 좋음.
- 창 수 = 둘(가 · 나) · 배치 코더와 같은 날이면 배치 먼저 합치고 가 · 나가 받은 뒤 시작.
## ⑤ 시험 · 화면 확인
기존 시험(실행 `cd B07_DesignDetail/openwebcad && npx vitest run --dir ../../resources/tester/cad <이름>`):
| 항목 | 기존 | 새로(같은 폴더) |
| --- | --- | --- |
| 1 | `text-inplace-edit.test.ts` · `text-acad.test.ts` | `text-frame.test.ts` — 왼 · 가운데 · 오른 · 90° · 굵게: 테두리 네 귀 = 입력 칸 left · top · width · height · 회전(배율 2 · 잰 폭 40 모의) |
| 2 | — | (화면으로) |
| 3 | `command-catalog.test.ts` | 같은 파일에 「이름 · 설명에 도면층 · 선가중치 없음」 한 줄 |
| 4 | `src/state.test.ts` | `selection-style.test.ts` — 같은 값 → 그 값 · 다름 · 목록 밖 → 빈 값 · 적용 → 폴리선 자식까지 · Ctrl+Z 한 번 → 옛 값 · 고친 것 없을 때 기본값 |
| 5 | — | (화면으로) |
| 6 | `dxf-clip.test.ts` · `test_b07_dxf_clip.py`(pytest) · `command-catalog.test.ts` | `command-catalog.test.ts` 에 「삽입 탭에 IMPORT」 한 줄 · (6-다) `test_b07_dxf_clip.py` 에 ByLayer 색 한 건 |
- 타입 = `npm run check-types` · 빌드 = `npm run build`.
화면(ORCA · CAD 는 iframe — `document.querySelector('iframe').contentWindow` 로 eval):
| 항목 | 판정(수치) |
| --- | --- |
| 1 | 왼 · 가운데 · 오른 · 돈 글 각각 — 스침 뒤 `__aisloCad.highlighted()` 에 그 글 · 테두리 네 귀(디버그 창구 `debug-hook.ts` 에 글 테두리 한 칸 더함 → `screen()` 로 화면값) 와 더블클릭 뒤 `.cad-text-inplace` `getBoundingClientRect()` 차 ≤ 1 px · 여러 줄 글 = 묶음 줄 모두 강조 |
| 2 | 쉬는 중 「선」 위 마우스 — `[data-active=true]` 1개(선택) · 「선」 테두리색 ≠ 강조색 / 「선」 누름 → Esc → `document.activeElement` 가 `.cad-tool` 아님 · `:focus-visible` 0개 |
| 3 | `document.body.innerText` 에 「도면층」 · 「선가중치」 0 — 홈 탭 · 레이어 관리자 · 왼쪽 탭 · 넘침 메뉴(▾) · LAYER 명령 안내 줄 |
| 4 | 색 다른 선 둘 고름 → 두께 · 선종류 `select.value === ''` · 색 `data-mixed=true` → 두께 3 고름 → 두 선 자식까지 3 → Ctrl+Z 한 번 → 옛 값 · 방금 그린 선 남음 → 고름 풂 → 기본값 그대로 · 바꾼 값 되돌림 |
| 5 | `.cad-drop-hint` 높이 ≤ 한 줄(≈ 16 px) · `scrollWidth ≤ clientWidth` · 펼친 ①②③ 각 한 줄 |
| 6 | 삽입 탭 「DWG 불러오기」 → `orca upload`(붙은 입력) `REpyo.dwg` → 알림 「도형 N개」 · `__aisloCad.selection().length === N` · 새 레이어 · Ctrl+Z 한 번에 빠짐 · 확정 도면에선 막힘 알림 · 끝나면 Ctrl+Z 로 되돌림 |
## ⑥ 사용자 결정
| 번호 | 갈래 | 선택지 | 추천 |
| --- | --- | --- | --- |
| 1-가 | 도각 자리표 칸 글 | 입력 칸도 칸 크기 / 지금처럼 글 폭 | 칸 크기 |
| 1-나 | 잡는 범위 | 글 테두리 안 어디든 스침 · 고름 · 더블클릭 / 지금처럼 기준점 10 px | 테두리 안(끝 글자에서도 강조) |
| 1-다 | 여러 줄 글 스침 | 묶음 전체 강조 / 그 줄만 | 묶음 전체 |
| 2 | 스침 모양 | 바탕색만 / 지금 그대로 | 바탕색만 |
| 4-가 | 고른 것 있을 때 바꾸면 | 고른 것만(오토캐드와 같음) / 기본값도 같이(지금) | 고른 것만 |
| 4-나 | 색 「빈 항목」 | 빈 견본(점선 테두리) / 색 목록 드롭다운으로 바꿈 | 빈 견본 |
| 4-다 | 왼쪽 특성 · 빠른 특성 | 같은 규칙 / 리본만 | 같은 규칙 |
| 4-라 | 같은 되돌리기 흠 명령(COLOR · LWEIGHT · MATCHPROP 등 8개) | 이번에 같이 / 다음 차례 | 이번에 같이(같은 부품 한 줄씩) |
| 4-마 | 글자도 고른 때 | 선 메뉴는 선만 보고 글은 문자 스타일 칸 / 글 색도 선 색으로 | 선만 |
| 5 | 안내 글 | 한 줄로 그대로 / 「DWG · DXF 파일을 끌어다 놓거나 [삽입 → DWG 불러오기]」로 줄임 / 지움 | 줄임 |
| 6-가 | 넣는 자리 | 원좌표 그대로 · 골라진 채(끌어다 놓기와 같음) / 누른 점에 놓기 | 원좌표 그대로 |
| 6-나 | 붙인 도형이 화면 밖 | 그 범위로 화면 맞춤 / 그대로 | 화면 맞춤(기존 `fitView` 재사용) |
| 6-다 | ByLayer 도형 색 | 레이어 색 / 지금처럼 흰색 | 레이어 색 |
| 3 | CAD 밖 B07 안내 글 1곳 | 같이 「레이어」 / 그대로 | 그대로(요구가 「캐드에서」) |
## ⑦ 위험
- 1 — 테두리를 잡는 범위로 쓰면(1-나) 글 밑 선보다 글이 먼저 잡힘 · 줌아웃으로 글이 10 px 아래면 입력 칸만 커짐(최소 글 크기 남김).
- 1 — `getBoundingBox` 는 안 바꿈(줄맞춤 · 그립 · 창 고르기 · 공간 색인) — 바꾸면 영향 넓음.
- 2 — 「나」(초점 테두리)는 추정 — 화면에서 먼저 재현 뒤 판정.
- 3 — 토씨 22곳 · 거울 JSON 빠지면 `command-catalog.test.ts` 빨강.
- 4 — 사본 갈아 끼움 = id 그대로라 고름 유지 · 옮기기 도구와 같은 길 · 새 레이어(DWG)처럼 층 목록은 되돌리기 밖(`state.ts:591`).
- 4 — 되돌리기 흠은 이번 요구 전부터 있던 것 — 고치면 사용자가 겪던 「Ctrl+Z 가 다른 걸 지움」도 사라짐.
- 6 — DWG 단위(mm · m)와 B07 도면 단위가 다르면 크기 어긋남 — 골라진 채라 바로 SCALE · 단위 묻기는 나중.
- 6 — 서버가 파일을 다 읽은 뒤 20 MB 를 잼(M02 와 같음) · 리눅스 서버는 `dwg2dxf` 를 PATH 나 `.env` `LIBREDWG_DWG2DXF_PATH` 로.
- 공통 — `App.css` 이미 1102줄(700 넘음) · 이번 고침은 제자리 바꿈 위주로 늘림 최소 · 배치 코더와 같은 파일.