chore(조사): 요구 298 · 299 분석 일곱 — CAD · B05 B06 버튼 줄 · B07~B09 초기화 · 노선 저장 토스트 메모리 · 패널 하이라이트 B06 평면 창 · 보안 대 속도 · 고름 유지 임시 횡단

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AbccG79TsSMUjnGqBq1MUE
This commit is contained in:
2026-10-20 18:45:40 +09:00
co-authored by Claude Opus 5.5
parent fa0f584978
commit dca542805f
7 changed files with 1102 additions and 0 deletions
@@ -0,0 +1,147 @@
# 요구 298-1 — 내장 CAD 고칠 것 여덟 가지 (조사 · 코드 X)
조사 = 클로드 에이전트(Opus 5.5 · 읽기만) · 기준 = `main_laptop_1` `83ef315fa` · 추정은 (추정) · 화면 실측 없음.
경로 앞머리 `cad/` = `B07_DesignDetail/openwebcad/src/` · CAD 앱 고침은 `npm run build` 뒤 반영.
## 0. 결론
- 1 손가락 단추 = 파일 둘(`input-touch.ts` 한 덩이 · `input-touch.css` 통째) + 시험 하나 고침 · 손가락 동작(끌기 · 벌림 · 길게 누름)은 그대로.
- 2 남은 초점 테두리 = 리본 탭 단추(홈 · 삽입 …) · 묶음 이름 단추 · 접기 단추에 blur 없음 · 리본 `<nav>` 에 누름 한 곳 처리로 전부 막음.
- 3 B05 평면 상단 = 고르기 둘(간격 · 분석 지형) 모두 네이티브 select · 480 훅(창 전체 `change`)이 이미 닿음 · 고칠 것 없음(같은 값 다시 고름만 틈 · 추정).
- 4 M02 [수정] 상세 = 같은 openwebcad(`/b07-cad/index.html`) · 리본 접힘이라 [삽입] 누르면 [DWG 불러오기] 있음 · 491-2 「안 닿음」은 카드 미리보기(리본 숨김) 이야기 · 데스크탑 메인은 CAD 빌드가 안 돼 명령 자체가 없음(8 번) (추정) · M02 [파일 업로드]는 다른 길(통째 바꿈 · 한 층) — 지우고 CAD 한 길로 합칠 수 있음.
- 5 표지 = 할 일 없음 · ⚙ 일부러 뺌 · 491-2 그 조각만 지움.
- 6 축척 칸 = 칸마다 붉은 글이 그 칸 밑에 붙어 그 칸만 길어짐 · 칸 밑 글 숨기고 줄 아래 한 줄 알림.
- 7 구분선 = 이미 1 px 있음(`--color-border` = 밝음 #e6e2e3 · 어두움 #322a45 — 바탕과 거의 같아 안 보임) + 부모 도면 탭이 그 선을 덮음 · 색 한 단계 진하게 + 겹판 높이 25 px.
- 8 싱크 스크립트 = CAD 빌드 판단이 모으는 자리(sub8) 앞뒤 diff 하나 · 폴더마다 「지은 판 표지」를 견주게 바꿈 · Node 서버 묶음은 서버가 낡음을 스스로 다시 지음 → 안 넣음 추천.
## 1. 오른쪽 손가락 단추 지움
- 지금 — `cad/inputController/input-touch.ts:119` `mountTouchBar(ctl)` 부름 · `:122-144` 단추 여섯(Esc · ↵ · 🗑 · ↶ · ↷ · ⌖ → `handleKeyStroke`) · `input-touch.css:1-29`(이 단추만의 CSS · `(hover:none) and (pointer:coarse)` 때만 보임 · 486).
- 원인 — 486 에서 자판 없는 기기만 남김 · 사용자 = 모바일 · 탭에서도 불편 → 완전히 지움.
- 고칠 자리
- `input-touch.ts:5`(CSS import) · `:14`(머리 글 「자판 없이 쓰는 명령 단추 …」 줄) · `:119` · `:122-144` 지움.
- `input-touch.css` 파일 지움(다른 규칙 없음).
- `cad/inputController/prompt-input.ts:2` 주석 「손가락 단추」 낱말만 뺌(선택).
- 남김 — `attachTouch`(손가락 동작) · `input-controller.ts:374` `handleKeyStroke`(키보드 · vitest 다섯 곳이 씀).
- 다른 데서 쓰는 곳 — 없음(M02 · B06 · 부모 CSS · 설명서 `resources/help` 모두 0).
- 시험 — `resources/tester/test_cad_touch.py` 고쳐 씀: `:3` 설명 · `:42-52`(단추 attached · display · media) → 「`.cad-touch-bar` = null」 · `:60-64`(단추 6 · 44 px) → 같은 null · `:89`(Esc 단추 톡) 지움 · 손가락 끌기 · 벌림 · 확대 막기 없음 · 페이지 오류 0 은 그대로. 5174(sub 화면 서버) 꺼지면 건너뜀.
- 위험 — 자판 없는 태블릿은 Esc · Enter 길 없음(폴리선 끝내기 등 · 지우기는 리본 수정 ▾ 에 남음) · 사용자 뜻대로.
## 2. 리본 단추 누름 + Esc 뒤 초점 테두리
- 지금 — 487 이 명령 단추 · 넘침 메뉴 · ↶ ↷ 에 `event.currentTarget.blur()`(`cad/ribbon/Ribbon.tsx:38-41` · `:142-143` · `cad/components/QuickAccessBar.tsx:22-23`) · **탭 단추(홈 · 삽입 · 주석 · 뷰 · 출력 `Ribbon.tsx:211-221`) · 묶음 이름 단추(▾ 넘침 여는 것 `:125-134`) · 접기 ⌃(`:225-235`)엔 없음**.
- 원인 — 마우스로 누른 단추에 초점이 남음 → 그 뒤 키(Esc)를 누르면 크롬이 그 단추를 `:focus-visible` 로 바꿔 기본 초점 테두리를 그림 · `.cad-ribbon-tabs button` 초점 모양 없음(`cad/App.css:128-144`).
- 고칠 자리 — `Ribbon.tsx:208` `<nav>` 에 누름 처리 한 곳: 마우스 누름(`event.detail > 0`)이면 누른 자리의 가장 가까운 `button` blur · 키보드 Enter · Space(`detail === 0`)는 초점 그대로(접근성). 단추 셋의 blur 줄(위 셋)은 이 한 곳이 대신함 → 지워도 됨(줄 줄어듦). 고르기 · 색 칸은 `button` 아님이라 안 건드림(480 훅이 맡음).
- 시험 — 글 판정 한 줄(새 `test_298_cad.py` 또는 `test_cad_host_tabs.py` 에 「nav 에 blur 처리」) · 화면(ORCA · iframe `contentDocument`): [삽입] 탭 누름 → Esc → `activeElement` 가 리본 `button` 아님 · `:focus-visible` 0 / 묶음 이름 ▾ · 접기 ⌃ 같은 판정 / Tab 으로 탭 단추 옮겨 Enter → 초점 남음 · 밝음 · 어두움.
- 위험 — 거의 없음 · 누름 처리 안에서 다른 칸으로 초점을 옮기는 단추는 그 단추가 이미 초점 아님이라 blur 가 아무 일 안 함.
## 3. 드롭다운 「마우스로 고르면 초점 빠짐」 — B05 평면 상단
- 지금 — 평면 위 도구줄(`B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts:84-129`) 단추 옆 항목:
| 항목 | 꼴 | 자리 |
| --- | --- | --- |
| 간격(10 m · 측점 No.) — [측점 스냅] 옆 | 네이티브 `<select class="ui-select ui-select--sm">` | `:114-120` · 듣기 `B05_Profile_UI_RouteEdit_SnapStep.ts:67-91` |
| 분석 지형(라이다 · 수치지형도) — 라이다 있는 프로젝트만 | 네이티브 select | `:122-128` |
| 나머지(? · ↶ ↷ · 돌리기 · 1m 등고선 · 측점 · 기울기 · 절/성토선 · 거리 재기 · [3D]) | 단추(드롭다운 아님) · [3D] = `A00_Common/design/design_view_profile.ts:546` | — |
- 480 훅이 닿나 — 닿음. `ui_template/ui_template_native_menu.ts:27-42` `releaseSelectAfterPick(window)` = 창 캡처 단계 `change` · 부름 `A00_Common/main.ts:66`(B05 · B06 페이지 전부) · 종단 새 창 · CAD iframe 도 부름. 480 검증이 바로 이 「B05 간격」을 고치기 전 재현 · 뒤 초점 BODY 로 잼(PLAN 1112) · 데스크탑 메인 가지에도 들어 있음(`9a8ff3ee8` ⊂ `origin/main_desktop_1`).
- 남은 틈(추정) — 펼친 목록에서 **지금과 같은 값**을 마우스로 고르면 `change` 가 안 생겨 초점이 남음 → 그 뒤 ↑↓ 가 값을 바꿈. 화면 재현 먼저 · 고치면 같은 파일 한 곳.
- 고칠 자리 — 없음(틈은 재현 뒤 `ui_template_native_menu.ts` 한 곳).
- 시험 — 기존 `test_280_6_select_release.py` · `test_common_native_menu.py` 그대로 · 사용자 확인 몫.
- 위험 — 없음.
## 4. [삽입 → DWG 불러오기] — M02 구조물 카드 [수정] 상세 페이지
- 지금 — CAD 는 한 벌
| 자리 | 띄우는 길 | 리본 | DWG |
| --- | --- | --- | --- |
| B07 | `ui_template/cad_host/cad_host.ts:15 · 147` `/b07-cad/index.html` | 펼침 | [삽입 → DWG 불러오기] · 끌어다 놓기 |
| M02 [수정] 상세(구조물 · 도면 양식) | `M02_MasterTemplete_UI_Main.ts:445-462` → `M02_MasterTemplete_Drawing.ts:424` `mountStructureSheets` → `:164` `mountDrawingTemplate` → 같은 `createCadHost`(`:243`) | 접힘(`ribbonCollapsed: true` `:280`) — 탭 줄은 보임 · [삽입] 누르면 잠깐 펼침 | CAD 명령 그대로 있음(거르는 곳 없음 `cad/ribbon/ribbon.config.ts:144-150`) + 머리 [파일 업로드](`:295-332`) |
| M02 카드 미리보기 | `M02_MasterTemplete_DrawingCard.ts:68-95` 같은 함수 + `noImport: true`(`:88`) | 숨김 — iframe 에 CSS 얹음 `.cad-ribbon { display:none }`(`:42-45 · 93`) | 끌어다 놓기만(고치기 모드) |
- 원인
- 491-2 「M02 카드 미리보기엔 안 닿음」 = 카드는 리본을 일부러 숨김(47-2 「불러오기는 도면 상세에서만」) — 상세 페이지 이야기가 아님.
- 상세 페이지에서 사용자가 못 봤다면 — 데스크탑 메인(임시 배포) CAD 가 488-3 뒤 다시 안 지어짐(8 번) → 명령이 dist 에 없음 (추정 · 랩탑 화면 실측 필요) · 리본이 접혀 [삽입]을 눌러야 보이는 것도 한몫(추정).
- 「이원화」 = CAD 는 하나 · 갈린 것은 **불러오기 길 둘(셋)**:
| 길 | 서버 | 넣는 방식 | 층 |
| --- | --- | --- | --- |
| CAD [삽입 → DWG 불러오기] · 끌어다 놓기 | `POST /api/projects/dxf-clip/file`(`B07_DesignDetail_Router_Frame.py:193-219` · `main.py:656` 로그인 + 회사) | 지금 도면에 **더함** · 원좌표 · 골라진 채 · 되돌리기 한 번 · 화면 맞춤 | DXF 층 이름 그대로 · ByLayer = 층 색 |
| M02 머리 [파일 업로드] | `POST /api/m02/drawing-import`(`M02_MasterTemplete_Router_Drawing.py:57-91` · `main.py:683` 로그인만) | 도면 **통째 바꿈**(`M02_MasterTemplete_Drawing.ts:145-162 · 303-321`) | 한 층 「도각」(`Engine_Template.py:231-237` `frame_document`) |
| (참고) B07 [도각 편집] 파일 | `/{project_id}/frame-template/import`(`B07_DesignDetail_UI_Page.ts:398` · `cad_host_frame_edit.ts:140-146`) | 도각 양식 바꿈 | — |
- 변환 엔진은 이미 한 벌(`_read_drawing_file` · LibreDWG → ezdxf · `B07_DesignDetail_Engine_Frame_Import.py:374-385`).
- 고칠 자리(합치기 · 결정 4 가 기준)
- `M02_MasterTemplete_Drawing.ts:295-332` [파일 업로드] 덩이 · `:145-162` `importDrawingFile` · `:54-55` `noImport` 곁값 지움 → 도면 양식 · 구조물 상세 둘 다 CAD [삽입 → DWG 불러오기] · 끌어다 놓기 한 길.
- `M02_MasterTemplete_DrawingCard.ts:88` `noImport: true` 줄 지움(곁값 없어짐).
- `M02_MasterTemplete_CardHelp.ts:43` 도움말 「파일 업로드(DXF · DWG)는 여기」 → 「DWG 불러오기 = CAD [삽입]」.
- `M02_MasterTemplete_Router_Drawing.py:57-91` `/drawing-import` 길만 지움 · `import_frame_file` · `frame_document` 는 남김(B07 [도각 편집] `B07_DesignDetail_Router_Frame.py:137-141` 이 씀).
- (선택) 리본이 접혀 안 보이는 것 — M02 상세만 `ribbonCollapsed` 끔(`M02_MasterTemplete_Drawing.ts:280`) 또는 그대로(탭 누르면 보임).
- 서버 권한 — CAD 길은 회사 없는 사용자(시스템 관리자 빼고)에게 403(`common_util_auth.py:179-184`) · M02 층(개인)에 회사 없는 사용자가 있으면 `dxf-clip` 둘을 `protected` 로 옮김(라우터 갈라 `main.py` 한 줄) — 그런 사용자가 없으면 그대로.
- 시험 — `test_m02_card_shell.py:120-123 · 184-188`([파일 업로드] 글 못박음) 고쳐 씀 · `test_m02_router_drawing.py:38-64`(길 지우면 그 시험 지움 — 지우기는 사용자 몫이라 건너뜀 표시) · `resources/tester/ui_checks/47-1_카드_도움말.js` · `test_b07_dxf_clip.py` 그대로 · 화면: M02 [수정] 상세 → [삽입] → [DWG 불러오기] → `orca upload`(`.cad-pick-file`) `REpyo.dwg` → 도형 수 알림 · 골라진 수 같음 · 새 층 · Ctrl+Z 한 번 · [저장] 안 함 · 도면 양식 하나 같은 판정.
- 위험 — 통째 바꿈이 없어짐(지우고 불러오기 = Ctrl+A · Delete 뒤) · 도면 양식은 층이 「도각」 하나가 아니게 됨 — B07 엔진이 도각을 놓을 때 층을 도각 층으로 바꿔 씀(`Engine_Template.py:365`) 이라 영향 작음(추정) · 구조물 도면 층 이름이 DXF 이름 그대로 남음.
## 5. 표지 — 원본 개념 없음
- 지금 — ⚙ 는 표지 · 빈 도각 뺌(`B07_DesignDetail_UI_Panels.ts:109`) · 그래서 [원본에서 다시 그리기] 없음.
- 판정 — 사용자 뜻(원본 없음 · 고치려면 사용자가 직접 불러옴)과 같음 → **할 일 없음**. 직접 불러오기 = 4 번 [DWG 불러오기].
- 할 것 — PLAN 491-2 의 「표지 ⚙ 없어 저장본 되돌림 길 없음」 조각만 지움(브레인).
## 6. B07 ⚙ 축척 칸 — 붉은 글이 X 칸만 밂
- 지금 — 칸 하나 = 공용 `createInputField`(`ui_template/ui_template_elements.ts:87-127`) = 세로 줄(라벨 · 입력 · 오류 글 `:110-112`) · 오류 글은 비면 높이 0 · 차면 한두 줄(`ui_template_elements_styles.ts:113-119`) · 판정 `B07_DesignDetail_UI_Settings_Scale.ts:119-127` 이 칸마다 `setError` · 줄 = `.b07-scale__row` flex(`B07_DesignDetail_UI_Style.css:516-523`).
- 원인 — 틀린 칸만 글(「1/50~1/5,000 사이 정수」)이 붙어 그 칸만 길어짐 · 칸 폭 ≈ 136 px 라 글이 두 줄로 꺾임 → X · Y 높이 다름 · 아래 [자동] · 단추 줄이 밀림.
- 고칠 자리(추천) — 칸 밑 글 숨기고 줄 아래 알림 한 줄
- CSS `B07_DesignDetail_UI_Style.css:508` 근처 — `.b07-scale .ui-field__error { display: none }` + `.b07-scale__error`(붉은 글 · `--color-danger` · 12 px).
- TS `_UI_Settings_Scale.ts:119-127` `check` — 칸 `setError`(붉은 테두리)는 그대로 · 틀린 축 글을 모아 줄 아래 `<p class="b07-scale__error">` 에 한 줄(예 「X: 1/50~1/5,000 사이 정수」 · 둘 다면 「X · Y: …」) · 없으면 숨김 · `:150-156` 에 그 줄을 `row` 와 `buttons` 사이에.
- 다른 갈래 — 두 칸 오류 자리를 늘 한 줄씩 잡음(`min-height` + 한 줄) — 범위 글이 길면 넘침이라 덜 추천.
- 브레인이 요구 297 답에 낸 「제안」 글은 문서에 없음 — 다르면 그 판으로.
- 시험 — `test_280_5_b07_settings.py`(헬퍼 `helper_280_5_b07_settings.cjs:142 · 383` 가 칸 `setError` 값을 봄 → 그대로 통과) + 한 줄 더(줄 아래 알림 글 = 틀린 축) · 화면: 종단 X 50 → X · Y 입력 칸 `getBoundingClientRect().top` · `height` 같음 · 알림 한 줄 · 저장 꺼짐 · 되돌림 · 토적(자동 칸) · 밝음 · 어두움.
- 위험 — 없음(B07 만 · 공용 칸 안 고침).
## 7. 메뉴 탭 줄 · 내용(리본) 사이 구분선
- 지금
- 메뉴 탭 줄 = CAD `.cad-ribbon-tabs`(`cad/ribbon/Ribbon.tsx:209` · `cad/App.css:103-109` · `height: 26px` · border-box · `border-bottom: 1px solid var(--cad-line)`) + 그 위 부모 도면 탭 겹판(`B07_DesignDetail/B07_DesignDetail_UI_Style.css:477-491` · `height: 26px` · 바탕 투명).
- 내용 = `.cad-ribbon-panels`(`App.css:145-152`) · 바탕 = 탭 줄과 같은 `--cad-chrome-raised`.
- 선은 이미 있음 — `--cad-line` = `--color-border` = `--color-mist`(밝음 #e6e2e3 위 #ffffff · 어두움 #322a45 위 #251f38) → 거의 안 보임.
- 부모 도면 탭(`ui_template/cad_host/cad_host_tabs.css:16-31` · 불투명 `--color-canvas` · 밑 테두리 없음)이 줄 끝 26 px 까지 내려와 그 폭만큼 선을 덮음.
- 고칠 자리
- `cad/App.css:108` 선 색만 진하게 — 추천 `var(--color-smoke)`(밝음 #c1c7cf · 어두움 #4a4160 · `ui_template_theme.css:30 · 307` · 자동 어두움 블록에도 있음) · 두께 1 px 그대로 → **탭 줄 높이 26 px 그대로**(border-box 안).
- `B07_DesignDetail_UI_Style.css:483` 겹판 `height: 26px` → `25px` — 도면 탭이 선 위에서 멈춤(탭 높이 23 = 25 − 위 여백 2 · 딱 맞음).
- 시험 — `test_280_5_b07_settings.py:166`(`height:\s*26px` 못박음) → 25 · 화면: `.cad-ribbon-tabs` `offsetHeight` = 26 전후 같음 · 캔버스 top 전후 같음 · 겹판 `scrollHeight ≤ clientHeight`(세로 스크롤 없음) · 탭 눌러 장 바뀜 · 선이 도면 탭 밑까지 이어짐(스크린샷) · 밝음 · 어두움 · M02 상세(겹판 없음) 그대로.
- 위험 — 겹판 25 px 가 꽉 차 글꼴 · 확대에서 세로 스크롤이 생길 수 있음 → 생기면 겹판 26 그대로 두고 CAD 선만 · `--color-smoke` 는 테두리 선례 없는 토큰(뜻 = 비활성) — 화면 보고 `--cad-text-muted` 와 견줌.
## 8. 전역 깃 싱크 — CAD 빌드 빠짐
- 지금 — `.claude/git-sync-all.ps1`
- `:120-121` 모으는 자리 = 마지막 서브(sub8) · 그 HEAD 를 `$before` 로.
- `:129-132` `$cad` · `$cfg` = sub8 의 `$before → $after` diff 에 `openwebcad/` · `config/package*.json` 이 있나 **하나로만**.
- `:155-159` 랩탑 빌드(sub8 · 랩탑 메인) · `:168-171` 데스크탑 메인 빌드 = 모두 그 `$cad` · `$cfg` 를 씀.
- 원인 — sub8 은 앞 (전역 아닌) 싱크들에서 이미 CAD 고침을 받아 둠 → 전역 싱크 때 sub8 앞뒤 diff 에 CAD 가 없음 → `$cad = false` → 그동안 못 받던 임시 배포(데스크탑 메인)가 62 커밋(CAD 35 파일)을 받아도 빌드 안 함. 랩탑 메인(임시 배포였을 때)도 같은 꼴 · `$cfg`(config 설치)도 같은 흠.
- 고칠 자리(추천 = 폴더마다 「지은 판 표지」)
- 판정 = 그 폴더 `git rev-parse HEAD:B07_DesignDetail/openwebcad`(CAD 폴더 트리 해시) ≠ 지은 때 적어 둔 표지(`B07_DesignDetail/openwebcad/node_modules/.aislo-cad-tree` — git 밖 · `dist` 는 빌드가 비워서 안 씀) → 빌드 · 성공(0)이면 표지에 그 해시.
- config = 같은 꼴 `HEAD:config/package-lock.json` 해시 ↔ `config/node_modules/.aislo-lock` → `npm install`.
- `:131-132` 지움 · `:155-159` 의 `if ($cad)` · `if ($cfg)` → 폴더마다 판정 함수 · `:169-170` 데스크탑 몸 글에 같은 판정(받기 뒤 그 폴더에서) · `-Check` 면 짓지 않고 「CAD 낡음: 폴더」 만 찍음.
- 이 꼴은 앞 diff 와 무관 — 오늘처럼 이미 놓친 빌드도 다음 싱크가 잡음.
- 다른 갈래(덜 추천) — 폴더마다 받기 전 HEAD 를 적고 받은 뒤 diff — 이미 놓친 빌드는 못 잡음.
- `config/*_node` 서버 묶음(`package.json:9-15` build:corridor · server-calc · formula · spreadsheet · b07-extent · b07-note · design-calc)
- 지금 — 서버가 부를 때마다 낡음을 잼(`common_util/common_util_node_bundle.py:58-92` `bundle_stale` = 묶음보다 새 TS · 자료 있으면 참 → `build_bundle` `:95-110`) · git 받기가 파일 시각을 새로 찍어 첫 요청 때 스스로 다시 지음 · B09 인쇄 묶음(`tmp/print_drawings_node`)도 제 판정(`B09_Draft_Print_Drawings_Source.py:93`).
- 제안 — **스크립트에 안 넣음**(값은 이미 맞음 · 중복). 넣는다면 임시 배포 받기 뒤 미리 짓기 한 줄(첫 사용자 요청 지연 ≈ 3 s × 묶음 · 동시 요청 때 같은 묶음 겹쳐 짓기 막음 — `build_bundle` 잠금 없음)만.
- 시험 — 스크립트 시험 없음 → `-Check` 로 낡음 목록(첫 회 = 표지 없어 모든 대상 낡음) · 실제 한 번 = 폴더마다 「CAD 빌드 … = 0」 · 바로 다시 = 빌드 0 회.
- 위험 — 첫 실행은 대상 폴더(sub8 · 랩탑 메인 · 데스크탑 메인)마다 한 번씩 빌드(시간) · `openwebcad/package*.json` 이 바뀐 때는 `run build` 만으로 모자람 → 그때만 알림(자동 `npm install` 은 lock 을 고쳐 미커밋을 낼 수 있어 안 함) · 데스크탑 메인 빌드 중 몇 초 `dist` 빔(전역 싱크는 검증 사용자 쉴 때라 괜찮음) · `.claude/` 는 git 밖 한 벌 — 고치기 전 사용자에게 알림(OWNERS 규칙 6).
## 사용자 결정 필요
| # | 물음 | 갈래 | 추천 |
| --- | --- | --- | --- |
| 4 | M02 [파일 업로드](통째 바꿈 · 한 층) | 가. 지움 — DWG = CAD [삽입 → DWG 불러오기] · 끌어다 놓기 한 길(더함 · 원좌표 · 층 이름 그대로) / 나. 단추 남기되 CAD 명령을 부름(다리 글 한 종류 더함 · 동작은 가와 같음) / 다. 지금 그대로 둘 | **가** — 이원화 없음 · 바꾸려면 Ctrl+A · Delete 뒤 불러옴 |
| 4-범위 | 같이 합칠 곳 | 구조물 상세 + 도면 양식(같은 함수) / + B07 [도각 편집] 파일 불러오기까지 | 구조물 + 도면 양식 · B07 [도각 편집]은 다음 차례 |
## 작업 나누기 제안
| 창 | 항목 | 파일 | 반영 |
| --- | --- | --- | --- |
| 창 1 CAD · B07 | 1 · 2 · 6 · 7 | `cad/inputController/input-touch.ts` · `input-touch.css`(지움) · `cad/ribbon/Ribbon.tsx` · `cad/components/QuickAccessBar.tsx` · `cad/App.css:108` · `B07_DesignDetail_UI_Settings_Scale.ts` · `B07_DesignDetail_UI_Style.css`(축척 덩이 · 겹판 줄) · 시험 `test_cad_touch.py` · `test_280_5_b07_settings.py` · (새) `test_298_cad.py` | `npm run build` → reload · B07 페이지 reload |
| 창 2 M02 · DWG | 4(결정 뒤) | `M02_MasterTemplete_Drawing.ts` · `_DrawingCard.ts` · `_CardHelp.ts` · (가면) `_Router_Drawing.py` · 시험 `test_m02_card_shell.py` · `test_m02_router_drawing.py` · `ui_checks/47-1` | reload · (라우터 지우면) 백엔드 재시작 |
| 브레인 | 5 · 8 | PLAN 491-2 줄 · `.claude/git-sync-all.ps1`(사용자 알림 뒤) | 고친 스크립트로 전역 깃 싱크 = 데스크탑 메인 CAD 빌드 |
- 겹침 — 창 1 · 창 2 파일 0 · 둘 다 CAD 빌드는 창 1 만(창 2 는 CAD 소스 안 고침) → 병행.
- 3 = 고칠 것 없음(같은 값 틈은 사용자 확인 뒤 창 1 에 `ui_template_native_menu.ts` 한 곳).
- 둘 다 합친 뒤 전역 깃 싱크(8 고친 판) 한 번 = 사용자 요구 「데스크탑 CAD 빌드」.
@@ -0,0 +1,208 @@
# 요구 298-2 — B05 · B06 좌측 아래 단추 줄 · 진행 단계 상태 (조사 · 코드 X)
근거: `docs/raw/요구_1020/사용자요구_1020.md` 요구 298 2번(공통 · B05 · B06 줄) · `docs/raw/조사/2026-10-19_요구280_B_페이지이동.md` · PLAN 481 · 491-1.
## 0. 한눈에
| 요구 | 지금 | 할 일 |
| --- | --- | --- |
| 줄 = [?] [<] [>] [저장] [초기화] | [?] [<] [>] [초기화] [저장] [확정] | [확정] 지움 · 차례 바꿈 |
| [초기화] 테두리 · 글만 · 올리면 채움 | 빨강 채움(`danger`) · 올림 규칙 없음 | 새 모양 하나 |
| B05 [<](B04) 흐림 | 늘 켬 | 같은 페이지라 주소 따라 다시 맞춤 |
| B05 [>](B06) = 이동만 | 이미 됨(481-1) | 없음 |
| B06 [>](B07) = 저장 후 이동 = 확정 | 떠남 물음 뒤 이동 · 운영은 B07 단추 꺼져 알림만 | 확정 길을 [>] 로 |
| [저장] = 보는 화면 기준 | 이미 됨(한 본문 · 493) | 없음 |
| [<] [>] 로 단계 상태 | [확정]만 2 · 3 닫음 · [저장]은 구조물 · 노선만 뒤 STALE | 결정 D1 |
## 1. 단추 줄 · [초기화] 모양
지금:
- 줄 = `design_dock.ts:107` [초기화] [저장] [확정] · 앞의 [?] [<] [>] 는 `ui_template_help.ts:191-200` 이 붙임.
- 모양 — [초기화] `danger`(빨강 채움 · `ui_template_elements_styles.ts:65-68` · 올림 규칙 없음) · [저장] `ghost` · [확정] `filled`.
- 「테두리만 · 올리면 채움」 꼴 = `ghost`(`:50-55`)뿐 · 주색(보라) 고정 · 빨강 테두리 모양 없음.
- `danger` 는 삭제 · 거절 단추 20 곳 남짓(B01 · M02 · 확인 창 `ui_template_elements.ts:340` · 떠남 창 등)이 씀 → 고치면 모두 바뀜.
바꿀 것:
- 새 모양 `danger-ghost` — 바탕 없음 · 테두리 · 글 = `--color-danger` · 올림(`:hover:not(:disabled)`) = 바탕 `--color-danger` · 글 `--color-canvas`. 두 테마 토큰 있음(`ui_template_theme.css:40 · 335 · 377`).
- [확정] 단추 · `confirm` 칸 지움 · 차례 [저장] [초기화].
고칠 자리:
- `ui_template/ui_template_elements.ts:26` `ButtonVariant` 한 낱말.
- `ui_template/ui_template_elements_styles.ts:68` 뒤 규칙 둘.
- `A00_Common/design/design_dock.ts:5-7` 머리 글 · `:24-28` `confirm` 칸 · `:75` variant · `:83-95` 확정 단추 · 맵 · `:107` 차례.
- `A00_Common/design/design_shared.ts:345` · `:366` dock `confirm` 줄.
- `ui_template/ui_template_locale_b2.ts:194-195` `B06_Profile_Btn_Confirm` 지움(다른 쓰는 곳 없음).
시험:
- `test_design_view_parity.py:90-106` — `dockActions` `["save", "reset"]` · 글 `["저장", "초기화"]` · 부름 차례 고침.
- `helper_design_view_parity.cjs:126` 가짜 글 · `:206` confirm 동작 지움 · reset class `ui-btn--danger-ghost` 확인 보탬.
- `test_280_2_step_nav.py:71` `row.append(resetButton, saveButton, confirmButton);` 글 → 새 차례.
- 화면 — `ui_checks/54-20_좌측단추_치수.js`(넘침 0) · 밝음 · 어두움 [초기화] 바탕 투명 · 올림 = 빨강 채움.
위험:
- 요구 298 B07 · B08 · B09 새 [초기화]도 같은 모양 → 모양이 먼저 들어가야 함.
- [확정](filled) 빠지면 줄에 채운 단추 없음 — [저장] `ghost` 그대로(요구 밖).
## 2. B05 [<] 흐림 · B05 [>] · B06 [<]
지금:
- [<] [>] = `ui-step-nav` 올림 → `createWorkflowLayout` 이 켜진 칸 ±1 단계 단추 누름 · 꺼진 단추면 그 title 알림(`ui_template_workflow_layout.ts:210-217`).
- B05 [<] = 늘 켬(`ui_template_help.ts:197-199` · 흐림은 B09 [>] 뿐) · 누르면 일반 사용자 = 「시스템 관리자 전용 단계입니다.」 · 관리자 · 개발 = `goToWorkflowStage(B04)` → 떠남 물음.
- B05 [>] · B06 [<] = `design_page.ts:183-187` `navigateTo` — 같은 페이지 · 저장 · 물음 없음(481-1 화면 확인) → 할 일 없음.
- 켬 · 끔은 붙일 때 한 번(그때 `location.hash`) · B05 ↔ B06 는 페이지를 다시 안 세움 → 단추도 그대로.
바꿀 것:
- B05 주소 = [<] 흐림 · B06 = 켬. 붙일 때만 정하면 B05 로 열고 B06 로 가도 흐림이 남음(반대도).
- `placeHelpButton` 안 — 지금 주소로 켬 · 끔 맞추는 함수 + `hashchange` 듣개(단추가 화면에서 빠지면 풂) · 규칙 = 첫 주소(B05) [<] 흐림 · 끝 주소(B09) [>] 흐림.
고칠 자리: `ui_template/ui_template_help.ts:176-200`.
시험:
- `test_280_2_step_nav.py:43-47` B05 기대 `[False, True, False, False]` · `:53` `b05Prev` 누름 0 · `:62` `userPrev` → 흐림이라 누름 · 알림 0.
- 새 경우 — b05 → hash b06 → [<] 켬 → 다시 b05 → 흐림.
- `helper_280_2_step_nav.cjs` 가짜 window 에 `hashchange` 자리.
위험:
- 관리자도 [<] 로 B04 못 감 — ☰ · 진행 단계 판은 그대로.
- B09 [>] = 요구 298 B09 줄(저장 후 인쇄 미리보기)로 켜질 예정 → 같은 함수 · B09 창과 차례 맞춤.
## 3. B06 [>] = 확정 후 B07
지금 [확정]이 하는 일 — 브라우저(`design_shared.ts:390-453` → `design_save.ts:183-240`):
1. 다른 탭이 노선을 갈았나 → 갈렸으면 다시 세움 · 안 보냄(`design_shared.ts:375-384`).
2. 횡단 보기를 숨긴 채 세움(`ensureView("cross")`).
3. 한 본문 `confirm: true` · 관 조각 = 초안 없어도 빈 본문(`B05_Profile_Api_Pipes_Draft.ts:32-38`).
4. 성공 = 깨끗 표시(`markDesignClean`) · 횡단 캐시 비움 · 「종·횡단을 확정했습니다」 · `goToWorkflowStage(B07)`(`design_shared.ts:429-437`).
5. 노선 모양 바뀐 상태 = 저장만 · 「새 노선을 확인한 뒤 다시 [확정]하세요」 · 페이지 다시 세움 · 이동 없음 · `done` 안 부름(`design_save.ts:223-228`) · 서버도 늘 `save_sections`(`B06_Section_Router_DesignSave.py:183-184`).
서버(`B06_Section_Router_DesignSave.py:146-195`):
6. 관 차례 늘 · 다시 셈 켬 — 배수 분석 낡음이면 지금 노선으로 배수유역부터(`B04_PreProcess_Router_Basins.py:593-606` · [저장]은 낡음 표시만).
7. `confirm_sections`(`B06_Section_Router_Confirm.py:368-502`) — 측점 맞춤 · 옛 횡단 파일 정리 · 미지정 측점 기본값 · 고침 얹음 · 종횡단 · 노선 CONFIRMED · 단계 2 · 3 COMPLETE(`:439-448`) · 서버 재계산 · 측구 방향 B05 종단 역반영(`:460-484` · 확정만) · 3D 길 모형.
- [저장](`save_sections` `:275-365`)과 다른 점 = 6 · CONFIRMED · 단계 · 측구 역반영 · 이동. 미지정 측점 기본값은 둘 다 채움.
B05 [확정] 사라지면:
- B05 보기에서 단계 2 · 3 닫는 길 = B06 [>] · 단계 판 B07 누름(아래 같은 길)뿐.
- 배수 다시 셈 · 측구 역반영도 [>] 때만 — 지금 [확정]과 같음 · 새 빈틈 아님.
- 한 dock 한 본문 → 두 보기에서 함께 빠짐 · 그 밖 빈틈 없음.
B07 잠금:
- 화면 — 단계 단추 켬 = 개발(`import.meta.env.DEV`) 또는 NOT_STARTED · STALE 아님 또는 `stage_no === currentStage`(`ui_template_workflow_layout.ts:104-118`).
- `current_stage` = 앞이 모두 COMPLETE 인 다음 칸(`common_util_workflow_state.py:257-264`) → 체인 직후(2 · 3 IN_PROGRESS · 4 NOT_STARTED) = 2 → **운영에서 B07 단추 꺼짐 → B06 [>] = 「미시작」 알림만 · 확정 못 함**. 임시 배포 5173 = vite 개발이라 안 보임.
- 서버 — `B07_DesignDetail_Router.py:81-116` = 최신 CONFIRMED 노선 + 종단 CONFIRMED. 체인 확정(`mark_stage_complete=False`)도 데이터는 CONFIRMED(`Router_Confirm.py:439` 가 if 밖) → 사용자 [확정]과 무관하게 열림.
바꿀 것:
- `design_page.ts:183-187` `onStepClick` — 4 이상(B07 · B08 · B09) = `saveSharedDesign(shared, true, 가려는 주소)` · 성공 뒤 그 주소 · 실패 = 머묾 · 2 · 3 = `navigateTo` · 0 · 1 = 지금 그대로. ☰ · 단계 판 · [>] 한 길(481 정함).
- `design_shared.ts:390 · 435` — 가려는 주소 인자 · `WORKFLOW_STEP_ROUTES[4]` 고정 걷음.
- 운영 잠금 — `currentStage` 를 수 · 목록 둘 다 받게(`ui_template_workflow_layout.ts:30 · 46 · 116` `includes` 한 줄) · `design_page.ts:180` 이 2 · 3 · 4 를 늘 켬. B07 페이지 `currentStage: 5`(`B07_DesignDetail_UI_Page.ts:512-513`)와 같은 뜻 = 「옮길 때 닫으므로 켬」. 수 하나(4)만 주면 구조물 저장으로 3 이 STALE 일 때 B05 [>] 가 막힘.
- 자료 서기 전 [>] = 옛 [확정](`enabled: false` → `designReady`)처럼 막음 — 표식 없으면 알림 · 머묾.
- 노선 모양 바뀐 상태 [>] = 결정 D2 · 최소 = `design_save.ts:226` 글만 「… 다시 [>]」.
- 글 — `ui_template_locale_b2.ts:199-202` [저장] 풍선(「확정하지 않고」) · `:407-409` 「횡단을 확정하면」 · `B06_Section_UI_Standard_Panel.ts:334` 「[저장]·[확정]」. `B08_Quantity_Haul_Missing`(`:786-789`)은 B08 몫.
시험:
- `test_b06_save_channels.py:96` 글 `saveSharedDesign(shared, true)` → 새 부름 꼴.
- `test_272_detail_memory.py:119-120` · `test_416_page_cache.py:92` = `done: async (isConfirm)` · `if (isConfirm) {` … `goToWorkflowStage(` 글 조각 — 지키거나 같이 고침.
- 새 시험 — onStepClick 4 · 5 · 6 = 확정 1 + 그 주소 · 2 · 3 = navigateTo · 0 · 1 = goToWorkflowStage · 실패 = 이동 0 · 개발 아님 + 체인 직후 stages = B07 단추 켬.
- 화면(ORCA · 배정 프로젝트 · DB 행 떠 둠) — B06 [>] → `design/save` `confirm:true` 1 · hash `b07-design-detail` · 물음 0 · 단계 2 · 3 COMPLETE / `AISLO_DESIGN_SAVE_FAIL_AFTER=cross` → 머묾 · 고침 남음 / B05 보기 단계 판 B07 = 같음 · 「확정」 단추 0.
위험:
- [>] 이 무거움(배수 낡음 다시 셈 · 기본값 · 재계산 · 3D) · 고침 없어도 늘 한 번 → 「기능 먼저」로 둠 · 나중 = 고침 없고 3 COMPLETE 면 요청 없이 이동.
- 깨끗 표시가 이동보다 먼저(`design_shared.ts:430`) → 떠남 물음 안 뜸.
- 2 · 3 · 4 늘 켬 = 운영에서 B07 로 가는 길이 곧 확정 → 잠금 뜻 그대로.
## 4. 떠남 물음 · [저장] 기준
지금:
- B05 [<](B04) · B06 [>](B07) = `goToWorkflowStage` → 떠남 물음(`design_leave.ts:96-124`) · 고침 있으면 [취소] [폐기] [저장] · B05 ↔ B06 는 안 물음(`:90 · :110-114`). 491-1 「B06 [>] 물음 화면 미확인」.
- [저장] = 두 보기 몫 한 본문(`design_shared.ts:386-453` → `POST /design/save`) · 횡단 보기 숨긴 채 세움 · 들어온 보기 무관 = 세션 초안(화면 그대로) · 493 = 노선 손 고침도 화면 그대로 → **「보는 화면 기준」 이미 됨**.
바꿀 것:
- B05 [<] 흐림 · B06 [>] = 확정 → 두 길 모두 물음 안 탐 · 실패 = 머묾.
- 물음 남는 길 — ☰ · 단계 판 B03 · B04 · 🏠 · 머리 메뉴 · 뒤로 가기 · 탭 닫기.
- [저장] 코드 없음.
시험: `test_346_4_design_leave.py` 그대로 통과 · 화면 B06 [>](고침 있음) = 물음 0.
위험: 없음 — 491-1 그 줄은 길이 사라져 닫힘.
## 5. 진행 단계 상태 기계
표(`project_workflow_stages` · 칸 0 B03 ~ 6 B09):
| 사건 | 바뀜 | 자리 |
| --- | --- | --- |
| 파일 입력 끝 | 1+ NOT_STARTED · 0 COMPLETE | `B03_FileInput_Router_Temp.py:468-469` · `_Router_Helpers.py:273-274` |
| 전처리 시작 · 끝 | 1 IN_PROGRESS(뒤 COMPLETE → STALE) · 1 COMPLETE | `B03_FileInput_Service_WF1.py:82` · `B04_PreProcess_Router.py:109` · `B04_PreProcess_Service.py:59` |
| 노선 계산(체인 · 추천 [적용]) | 2 IN_PROGRESS · 3+ STALE | `B05_Profile_Router.py:170` |
| 체인 확정(`mark_stage_complete=False`) | 데이터 CONFIRMED · 3 IN_PROGRESS | `B06_Section_Router_Confirm.py:447-448` · `B03_FileInput_Service_Chain.py:290 · 309 · 568 · 652` · `B05_Profile_Route_Save.py:505 · 559` |
| 노선 손 고침 [저장] | 2 IN_PROGRESS · 3+ STALE → 3 IN_PROGRESS | `B05_Profile_Router_Replan.py:396` + 위 체인 확정 |
| [저장] 구조물 바뀜 | 3+ COMPLETE → STALE(2 그대로) | `B05_Profile_Structures_Router.py:203-225 · 473-474` |
| [저장] 관 · 계획고 · 추가 측점 · 관 옵션 · 상단측 · 횡단 | 없음 | `B06_Section_Router_DesignSave.py:146-195` |
| [확정] | 노선 CONFIRMED · 2 · 3 COMPLETE | `B06_Section_Router_Confirm.py:441-446` |
| [초기화] | 2 · 3 IN_PROGRESS · 4+ STALE | `B05_Profile_Router_Lifecycle.py:322-325` |
| B07 장 저장(confirm) | 다 확정 = 4 COMPLETE · 아니면 4 IN_PROGRESS | `B07_DesignDetail_Router.py:473-476` |
| B07 확정 풀기 · 배치 · 축척 | 4 IN_PROGRESS · 5+ STALE | `B07_DesignDetail_Router.py:539 · 629` · `_Router_Support_Scale.py:179` |
| B07 → B08 · B09 이동 | 4 COMPLETE | `B07_DesignDetail_Router.py:563-585` · `B07_DesignDetail_UI_Page.ts:481-503` |
| B08 · B09 | 없음 — 5 · 6 늘 NOT_STARTED | — |
| 개발 잠금 풀기 | ≤N COMPLETE(되돌림 표 남김) | `common_util_dev_unlock.py:60-100` |
- 공용 — `start_stage` = 그 칸 IN_PROGRESS + 뒤 COMPLETE → STALE(`common_util_workflow_state.py:44-90`) · `complete_stage` = COMPLETE · 6 이면 프로젝트 DONE(`:122-150`).
- 한 본문 실패면 단계 행도 되돌림(`common_util_save_guard.py:146-148 · 189-205`).
- 화면 — B05 · B06 단계 판은 열 때 한 번 그림 · 같은 페이지 [저장] 뒤 색은 다음 진입까지 옛 색 · B08 · B09 는 `stages` 를 안 넘겨 색 없음(`B08_Quantity_UI_Page.ts:159-172` · `B09_Estimation_UI_Shell.ts:348-362`).
[<] [>] 매핑 제안:
| 누름 | 지금 | 제안 |
| --- | --- | --- |
| B05 [>] · B06 [<] · 모든 [<] | 없음 | 없음(이동만) |
| B06 [>] | (옛 [확정]) 2 · 3 COMPLETE | 그대로 — 있음 |
| B07 [>] | 4 COMPLETE | 그대로 — 있음(481-3) |
| B08 [>] | 없음 | 5 COMPLETE — 새 · B08 창 |
| B09 [>](인쇄 미리보기) | 없음 · [>] 흐림 | 6 COMPLETE · DONE — 새 · B09 창 |
| B05 · B06 [저장] | 구조물 · 노선 바뀜만 뒤 STALE | D1 |
| B07 · B08 · B09 [저장] | B07 일부만 · B08 · B09 없음 | D1 같은 규칙을 각 창 |
D1 (나) 고칠 자리(서버 한 곳): `B06_Section_Router_DesignSave.py:190-195` 횡단 차례 성공 뒤 · `confirm` 거짓 · 노선 안 바뀜 → `start_stage(2)` · `start_stage(3)`([초기화] 꼴) · 노선 바뀐 [저장]은 이미 이 꼴.
시험(D1 나): 가짜 커서(`test_431_reset_gaps.py:130-140` 꼴) — [저장] = 2 · 3 IN_PROGRESS + 4+ STALE · [확정] = 2 · 3 COMPLETE · 실패 = 되돌림 + 실DB `test_472_6_*` 한 번 · 서버 다시 켬.
위험(D1 나): B06 [저장]만 눌러도 B07 STALE → 운영 B07 단추 막힘(3장 `currentStage` 목록이 B05 · B06 화면에선 풂 · B08 · B09 화면은 stages 없음이라 무관) · B07 저장본(고친 도면)은 STALE 와 무관하게 남음.
## 6. 지울 · 고칠 시험 · 글 모음
- 고침: `test_280_2_step_nav.py`(+ `helper_280_2_step_nav.cjs`) · `test_design_view_parity.py`(+ `helper_design_view_parity.cjs`) · `test_b06_save_channels.py:96` · `test_272_detail_memory.py:119-120` · `test_416_page_cache.py:92`(글 바뀌면).
- 그대로 통과 확인: `test_design_page_single.py` · `test_346_4_design_leave.py` · `test_design_panel_dock_fixed.py` · `test_311_panel_folded.py` · `test_418_reset_cleanup.py` · `test_54_2_help_files.py` · `npm run typecheck`.
- 지울 시험 없음. `ui_checks/*` 는 `[data-dock-action="save"]` 만 씀 → 그대로.
- 로케일: `B06_Profile_Btn_Confirm` 지움 · `B06_Profile_Confirm_Success` · `_Failed` 는 [>] 길이 계속 씀 → 남김 · 풍선 · 안내 글 셋(3장).
## 사용자 결정 필요
| 번호 | 물음 | 갈래 | 추천 |
| --- | --- | --- | --- |
| D1 | [<] [>] 로 단계 상태를 어디까지 | (가) [>] 만 그 단계를 닫음 · [저장]은 지금 규칙(구조물 · 노선만 뒤 STALE) (나) (가) + 앞 단계 [저장] 성공 = 그 단계 IN_PROGRESS · 뒤 COMPLETE → STALE (다) (나) + 처음 들어온 단계 NOT_STARTED → IN_PROGRESS | (나) — 계획고 · 관 · 횡단만 고쳐도 B07 이 「끝남」으로 남는 빈틈을 막음 · B07 · B08 · B09 「앞 단계 바뀌면 초기화 기준 다시」의 신호로 그대로 씀 · 서버 한 곳 |
| D2 | 노선 모양 바뀐 채 B06 [>] | (가) 지금처럼 저장만 · 머묾 · 「새 노선 확인 뒤 다시 [>]」 (나) 서버 한 본문이 노선 저장 뒤 확정까지 → 바로 B07(`DesignSave.py:183-184` + `design_save.ts:225-227`) | (가) — 새 노선의 배수 다시 셈 · 기본값 결과를 보고 닫음 · 글 한 줄 |
## 작업 나누기 제안
| 창 | 맡을 것 | 파일 | 겹침 |
| --- | --- | --- | --- |
| A 브라우저 | 1 · 2 · 3 · 4 · 6 | `ui_template_elements.ts` · `ui_template_elements_styles.ts` · `ui_template_help.ts` · `ui_template_workflow_layout.ts` · `design_dock.ts` · `design_shared.ts` · `design_page.ts` · `design_save.ts` · `ui_template_locale_b2.ts` · `B06_Section_UI_Standard_Panel.ts` · 시험 6장 | 모양 · help · layout 은 B07 · B08 · B09 창(요구 298 다른 줄)이 뒤에 씀 → A 먼저 · `orca reload` 만 |
| B 서버(D1 나 일 때) | 5 | `B06_Section_Router_DesignSave.py` · 새 가짜 커서 시험 | A 와 파일 겹침 없음 · 동시 착수 · 서버 다시 켬 |
| B07 · B08 · B09 창(별 줄) | 새 [초기화] = `danger-ghost` · B08 · B09 [>] 단계 닫음 · `stages` 넘김 · B09 [>] 켬 · B07 초기화 기준 = B06 [>] 뒤 첫 계산(단계 3 COMPLETE · D1 나면 4 STALE 걷힘) | 각 페이지 · `ui_template_help.ts`(B09 [>]) | A 합친 뒤 |
@@ -0,0 +1,205 @@
# 요구 298-2 C — B07 · B08 · B09 [초기화] · [저장] · [>] (분석 · 코드 X)
분석 = 클로드 에이전트(Opus · 읽기만) · 기준 `main_laptop_1` `83ef315fa` · 화면 실측 없음.
## 0. 결론
- 세 페이지 모두 「초기값 층」 없음 — 화면 값 = GET 마다 앞 단계 저장본에서 다시 셈 + 그 페이지 사용자 값(따로 사는 파일)을 덮음.
- 그래서 기준 = 「그 페이지 사용자 값 없이 지금 앞 단계 저장본으로 셈한 값」 → 떠 둘 것 · 지문 · 앞 단계 바뀜 감지 모두 불필요 · 앞 단계가 저장되면 기준이 저절로 새로 바뀜(요구 그대로).
- [초기화] = 그 페이지 사용자 값만 지움 → 다음 GET 이 기준을 그림 · B05 · B06 은 안 건드림.
- 지침 5장 「덮어씀(재계산 아님)」과 글자로 갈림 → 지침 한 줄(D2).
- 공용 `design_reset.ts` = B05 · B06 전용 길 → 안 씀 · 확인 창 · 오버레이 함수만 같이 씀.
- B09 [>] = 저장 → `#/b09-print` · 「b09-print」 글자는 셸에 못 둠(`test_302_1`) → 초안 등록부 `DRAFT` 에 다음 길 한 칸.
- 발견 — B08 은 B07 도면 · 수량표를 안 읽음 → B07 고침은 B08 기준에 안 들어감(B08 의 앞 단계 = B05 · B06 저장본만).
- B07 은 [저장] 단추 없음(옮길 때만 저장) → 요구 「저장 = 보는 화면」대로 새로 둠 제안(D6).
## 1. 공통
### 1-1. 앞 단계 바뀜 — 지금 아는 길
| 길 | 지금 | 쓸 수 있나 |
| --- | --- | --- |
| 단계 STALE | `start_stage` 가 뒤 단계 중 COMPLETE 만 STALE(`common_util_workflow_state.py:69-77`) · B05 노선 저장 = `start_stage(2)`(`B05_Profile_Route_Save.py:374`) · B06 확정 = `complete_stage(2·3)` — 뒤 안 건드림(`B06_Section_Router_Confirm.py:441-448`) · B06 임시 [저장] = 단계 안 건드림(`:275-290`) · stage 5 · 6 = 아무도 안 씀 · B08 · B09 화면은 `stages` 안 넘김(`B08_Quantity_UI_Page.ts:159-172` · `B09_Estimation_UI_Shell.ts:348-363`) | 못 씀 |
| 입력 지문 `_derived/` | `common_util_derived_store.py` = B04 · B06 만 · B07~B09 없음 | 없음 |
| B07 저장본 낡음 | `stale_layout` · `stale_scale` · `stale_layers` = B07 안 기준만(`B07_DesignDetail_Router_Support.py:509-518`) · 앞 단계 지문 없음 | 못 씀 |
| 늘 다시 셈 | B07 원본 = GET 마다 · B08 = GET 마다 B06 DB · B09 = GET 마다 B08 저장본 | 기준을 셈으로 두면 감지 필요 없음 |
### 1-2. CLAUDE.md 5장과 맞춤
- 초기값 층 「`initial_snapshot/` · 체인 직후 · 추천 [적용] 직후만」 — 가 갈래(D1) = 안 씀 → 맞음 · 나 갈래 = 새로 떠 두는 층 → 어긋남.
- 「[초기화] = 초기값을 작업본에 덮어씀(재계산 아님)」 — 가 = 사용자 값 지움 → 앞 단계 작업본에서 셈 → 글자로 재계산.
- 그 줄의 까닭(B05 는 사용자 고침이 여러 정본에 흩어져 재계산으로 초기값이 안 나옴 · `common_util_initial_snapshot.py:6-9`)이 B07~B09 엔 없음 — 사용자 값 = 따로 사는 파일 · 셈은 결정적.
- → 지침 한 줄 갈래(D2).
- 화면 잠금 오버레이 = [초기화] 허용 · 확인 창 = `showConfirmDialog`(화면 안 모달 · 네이티브 confirm 금지 이유 `design_reset.ts:5-7`).
- B05 · B06 [초기화]는 `b08-` · `b09-` 초안을 남김(`design_reset.ts:46-49`) → 반대로 B07~B09 [초기화]는 B05 · B06 값 안 건드림.
### 1-3. 공용 [초기화] 한 벌
- `design_reset.ts:30-63` = `resetRouteDesign` · `clearRouteLatestCache` · `invalidateSectionDetail` · `clearAlignmentDrafts` — B05 · B06 것 → 못 씀.
- 새 공용 함수 안 만듦 — 페이지마다 「확인 창 → 오버레이 → 요청 → 초안 비움 → 알림 → `location.reload()`」 10줄 안팎 · B09 는 이미 있는 `run` 이 오버레이 · 알림 맡음.
- 단추 = 글 `Common_Btn_Reset` · `variant: "danger"`(B05 · B06 과 같음 `design_dock.ts:73-77`) · 줄 맨 오른쪽 · 「채움 없음 · 올리면 채움」 꼴 = 298-2 공통 항목이 그 class 에 한 번.
## 2. B07
### 지금
- 원본 = GET 마다 엔진이 새로 그림 · 서버 캐시 없음(`B07_DesignDetail_Router.py:302-382` · `_Router_Support.py:469-640`) · 화면 모듈 캐시 `drawingCache`(`_UI_Page.ts:215`).
- 저장본 = `B07_DesignDetail/drawings/{id}.json` + `manifest.json` `confirmed`(`_Router_Support.py:643-682`) · 도면번호도 manifest(`:263-279`) · 앞 단계 지문 없음 → B06 이 바뀌어도 저장본 그대로 실림(`:487-521`).
- 저장 = 페이지 옮길 때만 — `saveEditedDrawings`(`_UI_Save.ts:55-109`) = 지금 탭 + ● 탭 `PUT …/confirm` · B08 · B09 로 갈 때 `completeDesignStage`(`_UI_Page.ts:483-503` · `_Router.py:562-590`).
- 원본 되돌림 = ⚙ [원본에서 다시 그리기] 지금 장 하나(`_UI_Save.ts:119-147`) → `POST …/invalidate` = manifest `confirmed=False` 만 · json 남음 · 그 장 측점 설계 `provisional` · `start_stage(4)`(`_Router.py:500-559` · `_Router_Support.py:685-690`).
- 설정(도면 그리는 조건) — `drawing_scales.json`(`_Engine_Template.py:95-137` · 바꾸면 그 종류 저장본 풂) · `cross_layout.json`(배치 · 표 배율 `_Router_Support.py:126-192`) · `plan_kinds/` · `plan_marks.json`(`_Router_Support_Plan.py`) · 표지 = 프로젝트 템플릿(M02 층 · B07 올리기 길 없음).
- CAD 자동백업 = localStorage `OPEN_WEB_CAD__RECOVERY__{id}`(`openwebcad/src/helpers/autosave.ts:20`) · 저장 · 다시 그리기가 안 지움 · RECOVER 명령으로만 되살림(리본에 없음).
- 좌측 줄 = [?] [<] [>] [도각 편집](`_UI_Page.ts:450-462`) · [저장] 없음.
### 초기화 기준 자리 제안
- 기준 = 원본(저장본 없을 때 GET 이 그리는 것) — 떠 두지 않음.
- [초기화] = 모든 저장본 풂(`confirmed=False` · 도면번호 남김) + 고친 탭 · CAD 고침 버림 + 새로 읽기 = [원본에서 다시 그리기]를 모든 장에.
- 설정(축척 · 배치 · 표 배율 · 평면도 종류 · 표식) = 남김 추천(D3).
### 갱신 시점
- B05 노선 저장 · B06 [저장] · [>] 로 B06 정본이 바뀌는 순간 원본이 바뀜 → 기준 저절로 새로.
- M02 도각 · 구조물 그림 · B07 설정 바뀜도 원본에 들어감.
- 저장본은 앞 단계가 바뀌어도 남음(지금과 같음) → [초기화]가 새 기준으로 데려감.
### 고칠 자리
- 서버 `POST /projects/{id}/design-drawings/reset` — 확정 장 모두 `confirmed=False`(`_invalidate_drawing` 꼴 · 번호 남김) · 그 안 횡단 측점 설계 `provisional`(`_Router.py:516-537` 꼴) · `start_stage(4)` · 응답 = 풀린 장 수.
- 자리 = `_Router.py`(640줄 → 약 680) · 넘치면 `_Router_Support_Scale.py` 라우터(이미 물림 · `main.py` 안 고침).
- `_Api_Fetch.ts`(399줄) — `resetDesignDrawings` 한 함수(`:185-194` 옆).
- `_UI_Save.ts`(147줄) — `resetDrawings(store)`: 물음(저장본 n 장 · 고친 탭 m) → 오버레이 → POST → (CAD 자동백업 열쇠 지움 · 선택) → 알림 → `location.reload()`.
- `_UI_Page.ts:459` — `actionRow.append(frameEditor.button);` 줄 그대로 · 다음 줄에 [저장] · [초기화](`test_280_5` 가 그 글을 못박음 · Page 에 confirm · invalidate 이름 금지 → 호출은 `_UI_Save.ts` 에).
- [저장](D6) = `saveEditedDrawings(store)` 만 · 이동 · 단계 완료 없음.
- [>] = 지금 그대로(`_UI_Page.ts:483-503`).
### 시험
- 새 `test_298_2_b07_reset.py` — 임시 storage manifest(확정 3 · 번호) → reset → 모두 미확정 · 번호 그대로 · 횡단 측점 `provisional` · `start_stage(4)` 1 번 · 줄 차례 [도각 편집] [저장] [초기화] · `resetDrawings` 차례 = 물음 → POST → reload · [취소] = 요청 0.
- 지금 시험 — `test_280_5_b07_settings` · `test_b07_confirm_keeps_design` · `test_280_5_b07_drawing_scales` · `test_280_2_step_nav` · `test_311_panel_folded` · `test_cad_host_tabs` · `npm run typecheck`.
- 화면(검증 storage) — 횡단 장 하나 고쳐 [저장] → 초록 띠 → [초기화] [취소] = 그대로 · [확인] = 띠 0 · 그 장 개체 수 = 원본 · 축척 그대로 · 줄 넘침 0(`ui_checks/54-20_좌측단추_치수.js`).
### 위험
- 줄 [?] [<] [>] [도각 편집] [저장] [초기화] — 280 px 에서 넘칠 수 있음 → 54-20 으로 잼 · 넘치면 [도각 편집]을 윗줄로.
- 풂은 json 파일 남김(지금 invalidate 와 같음) — 찌꺼기 · 다음 저장이 덮음.
- 횡단 측점 설계 status 를 B07 이 B06 행에 씀(지금 confirm · invalidate 도) — 읽는 곳 = B07 정보 칸 배지뿐.
- CAD 자동백업이 남으면 RECOVER 로 옛 고침 되살아날 수 있음(낮음).
- 여러 장 [저장] 중간 실패 = 앞 장은 저장된 채(`_UI_Save.ts:94-105` · 지금도).
- 앞 단계 바뀐 뒤 옛 저장본이 알림 없이 보임 — 지문 알림은 다음 장 후보.
## 3. B08
### 지금
| 값 | 자리 | 기본 |
| --- | --- | --- |
| 연암 % · 할증 % | `project_settings.json` › `quantity.rock_ratios_pct` · `material_surcharge_pct`(`B08_Quantity_Router_Options.py:72-85`) | 90 · 12/5/3 — 읽을 때 채움 |
| 재료 표 고친 칸 | `quantity.material_sheets`(`_Router_Material.py:247-254`) | 상수 + 연결 사본 셈 |
| 배관 1본 길이 | M02 프로젝트 층 템플릿 변수 `파형강관_1본_m`(`_Router_Options.py:54-66`) | 없으면 빈칸 · 저장 거부(`_UI_Options.ts:202`) · M02 [초기화]가 `templates/_initial` 로 |
| 수리 | `B08_Quantity/quantities/hydro_inputs.json`(`_Hydro_Site.py:27 · 130`) · 측점 키 | config + 연결(B05 세부유역 · B06 경사) |
| 이정 | `B08_Quantity/milestone/milestone.json` + 사용자 그림 · `auto/` · `place/`(`_Router_Milestone.py:28-35 · 93`) | 「초기값 층 없음」 · 그림은 올리는 즉시 저장 |
| 조합 연결(집계표 모달) | M02 프로젝트 항목 PUT 즉시(`M02_Project_Items_Router.py:154-175`) | — |
- 토공 · 집계표 = 읽기만 · GET 마다 B06 DB 다시 셈 · `_derived/` 없음.
- 세션 초안 `b08-options` · `b08-materials` · `b08-hydro` · `b08-milestone`(`b_page_state.ts:198-204`).
- 페이지 [초기화] 없음 · 이정 경로 탭 [초기화] = 사용자 그림만(`_UI_Milestone_Route.ts:117-127`).
- [저장](좌측) = 옵션 + 이정만(`_UI_Options.ts:199-229`) · 재료 · 수리 = 탭 안 단추 · [>] = 좌측 + 지금 탭(`_UI_Page.ts:154-171`).
- B07 도면 · 수량표 안 읽음 — B07 순수 함수 `derived_cells` 만 · 입력 = B06 `cross_sections`.
### 초기화 기준 자리 제안
- 기준 = 사용자 값 없을 때 GET 이 내는 값(기본 + 지금 B05 · B06 저장본) — 떠 두지 않음.
- 지움 — `quantity.rock_ratios_pct` · `material_surcharge_pct` · `material_sheets`(기본 꼴로 갈음 · 다른 칸 · `estimation` 구획 남김 = `save_section` `replace_keys`) · `hydro_inputs.json` · `milestone.json` · 초안 넷.
- 남김(추천 D3 · D4) — 1본 길이(M02 층) · 조합 연결(M02 항목) · 사용자 이정 그림.
### 갱신 시점
- B05 노선 저장 · B06 [저장] · [>] 뒤 다음 GET 부터 새 기준 · B07 은 기준에 안 들어감.
### 고칠 자리
- 서버 `_Router_Options.py`(103줄) — `POST /projects/{id}/quantity/reset` 한 길 · 위 지움 · 응답 = GET options 꼴.
- `_UI_Options.ts:231-239` — [저장] 옆 [초기화] · 물음 → 오버레이 → POST → `clearState` 넷 → 알림 → reload · 돌림 꼴 `{ panel, save }`(`:268`) 그대로.
- [저장] = 보는 화면 — 좌측 [저장]도 `saveShown`(`_UI_Page.ts:154`)으로 · `createOptionsPanel` 에 저장 길 곁값 하나(제안 · 지금은 탭 값 안 감).
### 시험
- 새 `test_298_2_b08_reset.py` — 임시 root(quantity 세 칸 + 남의 칸 + `estimation` 구획 · hydro · milestone · M02 1본) → reset → 세 칸 기본 · 남의 칸 · `estimation` · 1본 그대로 · 두 파일 없음 · GET options = 90 · 12/5/3 · [취소] = 요청 0 · 초안 넷 비움.
- 지금 — `test_122_b08_options` · `test_124_b08_surcharge_panel` · `test_121_b08_material_sheets` · `test_69_b08_hydro_site` · `test_244_1_b08_milestone` · `test_481_2_save_move`.
- 화면 — 연암 85 · 수리 재현기간 50 저장 → [초기화] → 90 · 기본 · 이정 기본 → 원래 값 되돌림.
### 위험
- 연암 % 지움 → B06 운반 배분(저장된 `haul_plan` · `B06_Section_Server_Calc_Prebuild.py:135-146`)은 B06 다시 저장 전까지 옛 값(지금도 연암 바꿀 때 같음).
- 1본 길이까지 지우면 빈칸 → [>] 막힘.
- `milestone.json` 지움 → 다음 [저장]이 없는 경로 그림을 지움(`_Router_Milestone.py:31`) → D4 「남김」이 실제로는 지움이 될 수 있음.
- 이정 그림 다시 캡처는 뒤에서 — reload 직후 옛 그림 잠깐.
## 4. B09
### 지금
- 사용자 값 = `B09_Estimation/v1/user_values.json`(`B09_Estimation_Store.py:40 · 226-276`) — `materials`(적용 · 공급 · 견적) · `estimate`(추정가격 · 공기 · 요율 등) · `bill`(고친 칸 · 더한 줄 · 지운 줄) · `schedule`(쉬는 날 · 공휴일 · 착공 · 줄).
- 따로 — 단가 책 `price_book.json`(첫 PUT 이 만듦 · [업데이트]가 새로 굳힘) · 견적서 `quotes/` · 서고 사본 `library/`.
- GET 마다 B08 저장본 + 단가로 다시 셈 · 저장 안 함(`_Router.py:162-196`) · 지문 없음 · 고친 칸 = 사용자 값 · 안 고친 칸 = B08 따라감 · B08 새 줄 = 「새 자동 줄」.
- [내역 초기화] = `user.bill` 만 빼고 PUT(`_UI_Shell.ts:323-337`) — 지금 화면 캐시째 저장.
- [저장] = PUT user(`:305-308`) · [<] = 고침 있으면 저장 뒤 이동(`:356-362`) · [>] 흐림(`ui_template_help.ts:197-199`) · 다음 단계 없음(`ui_template_workflow_layout.ts:211-217`).
- 인쇄 미리보기 `#/b09-print` = 초안 폴더 길(`draft/B09_Draft_Print_Route.ts:9-15` · `router.ts` glob) · 들어가는 길 = 초안 줄 [인쇄 미리보기](`draft/B09_Draft_Print_UI.ts:14-22` · 저장 없이) · 진행 단계 = B09 켜짐(`draft/B09_Draft_Print_Page.ts:287-297`) · 세션 초안을 덮어 보임(`draft/B09_Draft_Print_Cost.ts:94`).
- `test_302_1_print_frame.py:103-109` — `_UI_Shell.ts` · `_UI_Tabs.ts` · `router.ts` · `main.ts` · `config_frontend.ts` 에 「b09-print」 · 「Draft_Print」 글자 금지.
### 초기화 기준 자리 제안
- 기준 = `user` 비었을 때 GET(B08 저장본 + 굳힌 단가).
- [초기화] = `PUT {user: {}}` — 지금 길 그대로 · 서버 고침 없음 · `library.prune` 이 안 쓰는 서고 사본 지움(`_Router.py:214`).
- 남김(추천 D3 · D4) — 단가 책([업데이트] 몫) · 견적서 파일(적용 표시는 풀림 → 다시 고름).
### 갱신 시점
- B08 [저장] · [>] 뒤(B09 는 B08 저장본만 읽음 · 초안 안 읽음) · B05 · B06 저장 뒤 · 단가는 [업데이트] 때.
### 고칠 자리
- `_UI_Shell.ts:323-342` — [초기화] 단추 · 물음 → `run("/estimation", "PUT", …, { user: {} })` · `createDock([save, reset, 새 단추])` 맨 오른쪽.
- [>] — `_UI_Shell.ts` 에서 `left` 에 `ui-step-nav` 듣개(detail 1 → `stopPropagation` · 고침 있으면 `saveUser` 성공 뒤 `DRAFT.next`) · 「고침 있으면 저장」은 `onStepClick`(`:356-362`)과 한 함수.
- `_UI_Tabs.ts:128-132` `DRAFT` 에 `next?: () => void` 한 칸 · `draft/B09_Draft_Print_UI.ts` 에 `DRAFT.next = () => navigateTo(ROUTE.path)` 한 줄 · 초안 폴더 지우면 `next` 없음 → [>] 흐림 그대로.
- [>] 켬 — 공용 `ui_template_help.ts` 그대로(B09 흐림) · 셸이 `DRAFT.next` 있으면 `[data-step-nav="1"]` 흐림 풂.
- 초안 줄 [인쇄 미리보기] — 같은 「저장 뒤 이동」으로(지금은 저장 없이) · 작은 것.
- 진행 단계 — 인쇄 페이지 = B09 켜짐 그대로 · B09 완료(stage 6) 표시는 298-2 공통 「< > 진행 상태」 몫.
### 시험
- 고쳐 씀 — `test_184_3_b09_bill_menu.py:281-289`(`createDock([save, reset],` 글 못박음) · `test_303_b09_row_popup`(줄 차례) · `test_280_2_step_nav`(B09 [>] 흐림 = 공용만 봄 → 그대로 통과 예상) · `test_302_1_print_frame`(글자 금지 지킴).
- 새 `test_298_2_b09_reset_print.py` — [초기화] [취소] = 요청 0 · [확인] = PUT body `{user:{}}` 1 · [>] 고침 있음 → PUT 1 → `next` 1 · 실패 → `next` 0 · 고침 없음 → PUT 0 → `next` 1 · `next` 없음 → [>] 흐림.
- 지금 — `test_481_2_save_move` · `test_302_4_print_cost`.
- 화면 — 쉬는 날 · 내역 칸 하나 고침 → [>] → PUT 1 · hash `b09-print` · 인쇄 원가 = 고친 값 → [B09 복귀] → [초기화] → 기본 · 되돌림.
### 위험
- 견적서 적용 표시(`materials`) 풀림 — 파일은 남는데 적용 안 됨.
- 줄 [?] [<] [>] [저장] [내역 초기화] [초기화] 넘침 → 54-20 으로 잼.
- 단가 책 남김 = 「넘어온 직후」 단가와 다를 수 있음(사이에 [업데이트] 했으면) — 단가는 초기화 범위 밖으로 봄.
- 셸 듣개가 `left` 에서 안 막으면 공용 틀이 그냥 무시(다음 단계 없음) — 새 시험으로 막음.
## 5. 사용자 결정 필요
| # | 물음 | 갈래 | 추천 |
| --- | --- | --- | --- |
| D1 | 「넘어온 직후 계산 시점」의 뜻 | 가. 그 페이지 사용자 값 없이 지금 앞 단계 저장본으로 셈(떠 두지 않음 · 앞 단계 저장되면 저절로 새 기준) / 나. 앞 단계가 바뀐 뒤 처음 들어온 순간 화면을 떠 둠(전에 저장한 그 페이지 값도 담김 · 앞 단계 지문 · 새 저장 층) | 가 — 요구 「앞 단계 바꾸면 기준 새로」와 같음 · 코드 작음 · 나는 지침 5장 초기값 층과 어긋남 |
| D2 | 지침 5장 한 줄(가 일 때) | 「B07~B09 [초기화] = 그 페이지 사용자 값 지움 · 기준 = 앞 단계 작업본에서 셈(떠 두지 않음)」 더함 / 안 더함 | 더함 — 지금 줄 「덮어씀(재계산 아님)」과 글자로 갈림 |
| D3 | 지우지 않고 남길 설정 | B07 축척 · 횡단 배치 · 표 배율 · 평면도 종류 · 표식 / B08 1본 길이 · 조합 연결(M02 층) / B09 단가 책 · 공기 조건(쉬는 날 · 공휴일 · 착공) | 남김 = B07 설정 · B08 1본 · 조합 연결 · B09 단가 책 · 지움 = B09 공기 조건(그 페이지 사용자 값) |
| D4 | 사용자가 올린 파일 — B08 이정 그림 · B09 견적서 | 가. 남김(표지처럼 원본 개념 없음) / 나. 지움 | 가 — 단 B08 그림은 다음 [저장]이 없는 경로 그림을 지움(3장 위험) |
| D5 | B09 [내역 초기화] | 남김(내역만) / 지움([초기화] 하나) | 남김 — 요구 밖 지움 안 함 · 줄 넘치면 다시 |
| D6 | B07 [저장] 단추 | 새로 둠(이동 없이 지금 · ● 탭 저장) / 안 둠(옮길 때만) | 둠 — 요구 「저장 = B07 보는 화면」 |
## 6. 작업 나누기 제안
| 창 | 맡을 것 | 파일 | 앞뒤 |
| --- | --- | --- | --- |
| 1 B07 | 서버 reset · [저장] · [초기화] · 새 시험 | `B07_DesignDetail_Router.py` · `_Api_Fetch.ts` · `_UI_Save.ts` · `_UI_Page.ts` · `test_298_2_b07_reset.py` | D1 · D3 · D6 뒤 · 298-1 CAD 창과 `_UI_Page.ts` · `_UI_Style.css` 겹침 확인 |
| 2 B08 · B09 | B08 reset · B09 [초기화] · B09 [>] 인쇄 · 시험 셋 고쳐 씀 + 새 둘 | `B08_Quantity_Router_Options.py` · `_UI_Options.ts` · `B08_Quantity_UI_Page.ts` · `B09_Estimation_UI_Shell.ts` · `_UI_Tabs.ts` · `draft/B09_Draft_Print_UI.ts` | D1 · D3 · D4 · D5 뒤 |
- 창 1 · 2 파일 겹침 없음 → 병행.
- 단추 꼴(채움 없음 · 올리면 채움) · [<] [>] 진행 상태(stage 4 · 5 · 6 완료 · B08 · B09 `stages` 넘김) = 298-2 공통 창 몫 — 약속 = `variant: "danger"` · 글 `Common_Btn_Reset` · 줄 맨 오른쪽.
- 지침 5장 한 줄(D2) = 사용자 승인 뒤.
- 반영 — B07 · B08 서버 = 백엔드 재시작 · 화면 = reload · CAD 빌드 없음.
@@ -0,0 +1,118 @@
# 요구 298-3 — 노선 손 고침 [저장] 뒤 토스트 · out of memory 조사
범위: B05 평면 손 고침(추천 [적용] 아님) → [저장] · 읽기 전용 조사(코드 · git · 시험 파일) · 화면 조작 없음.
요약:
- 두 토스트 = 저장 한 줄기 끝 알림 둘(`design_save.ts`). 요약 한 줄은 거의 늘 같은 수 · 늘 참인 칸이라 소음임. 계획고 지움 알림은 소음이 아니라 「브라우저가 옮긴 계획고 편집이 서버 측점과 어긋나 버려짐」의 증상임.
- 「통째 새로 고침」 = `location.reload` 아님 · 페이지 안 다시 세움(`remountRoute`). 다시 세울 때 옛 페이지가 전역 듣개 네 곳에 붙들려 통째로 안 풀림 → 노선 [저장]마다 B05 페이지 한 벌(도엽 · LAS 등고선 · 코리도 · 상세 · 캔버스)이 쌓임 + 코리도 모듈 캐시가 노선 번호마다 쌓임 + 저장 직후 옛 페이지가 한 번 더 다시 읽은 뒤 버려짐. 새로고침 = 힙 비움이라 「리프레시하면 제대로 보임」과 맞음.
## 토스트 출처 · 지울 것 · 남길 것
노선 바뀐 [저장] 차례: 서버 한 본문 → 조각 뒤처리(`after`) → `ctx.done` → `noticeRouteSaved` → 페이지 다시 세움(`design_save.ts:207-231`).
| 토스트(종류) | 출처 | 알리는 사실 | 지우면 잃는 것 | 제안 |
| --- | --- | --- | --- | --- |
| 「계획고 편집 N곳을 지웠습니다 — 측점 · 관 · 구조물이 없는 자리: …」(info) | `A00_Common/design/design_save.ts:96-108` · 노선 저장 길 `:115-126`(부름 `:121`) · 노선 안 바뀐 저장 길 `design_profile_save.ts:37` | 서버 계획고 차례가 측점 · 관 · 구조물 0.006 m 안에 없는 편집 키를 버림(`B05_Profile_Router.py:473-483` · `Engine_Grade_Alignment.py:251-272`) | 노선 저장에선 「화면에 보인 계획고 편집이 저장 뒤 사라짐」의 유일한 신호 | 노선 저장 길에선 지움 · 근본은 아래 「고칠 자리 ④」 |
| 「노선 저장 — 관 지킴 N · 새 교차 관 N · 구조물 옮김 N · 멀어짐 N · 노선 밖 고침 N 건 · 배수유역 낡음」(info) | 글 `B05_Profile/B05_Profile_UI_RouteEdit_Apply.ts:145-157` · 띄움 `design_save.ts:176` | 서버 노선 저장 결과 수(`B05_Profile_Route_Save.py:578-586`) | 거의 없음 — 아래 칸별 | 통째 지움 |
| 「노선 밖 N 곳 — 측점 · 알약에서 뺌…」(warning) | 글 `B05_Profile_Api_PlusStations.ts:91-92` · 저장 뒤 `design_save.ts:170` · 편집 중 `B05_Profile_Api_PlusStations.ts:119-129` | 노선 끝 밖으로 나간 관 · 구조물 수 | 없음 — 편집 중 수가 바뀔 때 이미 같은 글이 뜸 | 저장 뒤 되풀이만 지움(편집 중 경고는 둠) |
| 「구조물 · 추가 측점 따라감 실패 — 옛 누가거리 그대로」(error) | `design_save.ts:171-175` | 서버 따라감 실패(`Route_Save.py:399-407`) | 진짜 실패 | 남김 |
| 「상단측 손 고침 N곳 — 노선이 바뀌어 새 지형으로 정했습니다」(info) | `B05_Profile_UI_RouteEdit_Apply.ts:182-183` | 요구 274 규칙 안내(손 상단측 버림) | 규칙 안내뿐 | 같은 결 — 지움 제안 |
| 「편집 내용을 저장했습니다」(success) · 저장 실패(error) · 「앞 저장이 끝나기를 기다린 뒤…」 · [확정] 길 「새 노선을 확인한 뒤 다시 [확정]」 | `design_shared.ts:443` · `design_save.ts:159-165 · 214 · 226` | 성공 · 실패 · 할 일 | 저장 됨 · 실패 신호 | 남김 |
요약 한 줄 칸별 근거:
- 관 지킴 = 옛 관 전부 수(`Route_Save.py:231` `len(old_pipes)`) — 늘 「다 지킴」 · 정보 없음.
- 새 교차 관 = 서버가 새 세류선 교차점에 자동으로 더한 관(494-2 부터 B04 기본값) — 편집 중 화면엔 없던 관이라 유일한 신호(사용자 결정 3).
- 구조물 옮김 · 멀어짐 = 493 부터 편집 중 화면이 이미 따라간 자리 · 멀어짐은 편집 중 경고(`design_view_profile.ts:127-132`)로 이미 봄.
- 노선 밖 고침 = 서버 `cross_dropped` + 넘김 `carry.outside`(`design_save.ts:224`). `cross_dropped` 는 「설계가 있던 옛 측점 중 새 노선에 같은 열쇠가 없는 것」(`Route_Save_Parts.py:184-202`) — 기본 설계까지 셈 · 사용자 고침 수 아님.
- 배수유역 낡음 = 서버가 늘 `True`(`Route_Save.py:585`) · 배수 칸이 스스로 흐림 · [업데이트] · 자동 3 s 셈(`B05_Profile_UI_Drainage_Panel.ts:310`).
계획고 지움이 노선 저장에서 생기는 까닭(확인 · 원인 갈래는 추정):
- 서버 노선 저장이 계획고를 이미 옮김(`Route_Save.py:532-539` → `:274-276` · 버림 없음).
- 이어 한 본문 계획고 차례가 브라우저 초안(노선 0 칸 = 브라우저 follow 가 옮긴 편집 · `design_pipeline_page.ts:484` · `Pipeline_Grade.ts:157-169`)으로 덮어씀(`B06_Section_Router_DesignSave.py:143-145 · 158-159`) → 브라우저 측점 ≠ 서버 측점인 키를 버림.
- 어긋남 후보 — 서버만 아는 관 자리(새 교차 관 · `_place_pipes`) · 노선 끝 · MC 곡선 측점 · 노선 0 칸이 비어 옛 노선 초안(`pendingAlignment(routeId)`)으로 대체. 토스트 글의 「자리: X m」 을 새 측점 · 관 목록과 맞대면 갈래가 갈림.
## 메모리 원인 후보(확신 순)
「통째 새로 고침」 길: `rebuildAfterRouteSave`(`B05_Profile_UI_RouteEdit_Apply.ts:190-196`) → 편집기 `onApplied` = `rebuildAfterReplan`(`design_view_profile.ts:474-478`) → `remountRoute`(`A00_Common/router.ts:131-135`) → 옛 `MountedPage.dispose`(`design_page.ts:281-292`) → 같은 해시 hashchange → 셸 · 페이지 새로 그림(`main.ts:73-86` · `router.ts:178-254`). 다시 세움은 저장마다 한 번(되부름 고리 없음 · 옛 캐시를 먼저 비워 `latestSource.verify` 갈래 안 탐). `location.reload` 는 [초기화]만(`design_reset.ts:57` · 주석 「제자리 다시 세우기는 옛 메모리 · 진행 중 요청이 남음」).
| # | 자리 | 근거 | 확신 |
| --- | --- | --- | --- |
| 1 | 옛 페이지 통째 안 풀림 — 전역 듣개 네 곳 | ① `design_leave.ts:101-107` `beforeunload` 듣개가 `root`(옛 페이지 노드) · `save`(→ `shared` 전부)를 쥠 · 떼는 때 = beforeunload 가 실제로 날 때뿐 · `design_page.ts:156` 이 페이지마다 하나 더 검. ② `ui_template/ui_template_overlay_drag.ts:168 · 200-201 · 208` 진행단계 패널 window `pointermove` · `pointerup` · `pointercancel` · `resize` 안 뗌(`ui_template_overlay.ts:243` · 페이지마다). ③ `B05_Profile_UI_Panel_Settings.ts:140-146` document `pointerup` · `keyup` 안 뗌(ponytail 주석 「새는 게 보이면 페이지 수명에 묶을 것」). ④ `ui_template_side_panels.ts:194-228` WeakRef 를 쓰나 `watched`(부모 노드)를 강하게 쥐어 절대 안 풀림. 떨어진 DOM 노드 하나만 쥐어도 그 나무 전체 · 거기 붙은 듣개 클로저(편집기 등고선 · 배수 레이어 · 상세 · 묶음 출력 · 3D 장면 CPU 기하)가 다 남음 | 높음(코드 확인 · 크기는 재야 함) |
| 2 | 코리도 모듈 캐시가 노선 번호마다 쌓임 | `B05_Profile_UI_Corridor.ts:45 · 152-186` 열쇠 = 프로젝트:노선 · 지우는 때 = 짓기 실패뿐 · 노선 [저장]마다 새 노선 번호 → 새 칸 · `withSections`(`:190-195`)가 그 칸에 상세 `cross_sections` 까지 붙임 · 페이지 열 때마다 3D 를 안 열어도 받음(`design_view_profile_sections.ts:96-106`) · 저장본 크기 예 14~16 MB JSON(storage `B05_Profile/corridor/corridor_*.json`) | 높음 |
| 3 | 저장 직후 옛 페이지가 한 번 더 다시 읽은 뒤 버려짐(두 번 일) | `design_save.ts:227` 이 `ctx.done(false)` 를 먼저 → `design_shared.ts:429-444` 두 보기 다시 읽기(`B05_Profile_UI_Page_Actions.ts:49-53` 최신 노선 + 상세 · `B06_Section_UI_Page_Persist.ts:412-419` 카드 전부) → 곧바로 `:228` 다시 세움. 그 다시 읽기는 페이지 노선 id 칸(`design_page_route.ts:3-4` 「페이지 동안 고정」 = 옛 노선)으로 상세 · 코리도를 받음 = 헛일 | 높음(코드 확인) |
| 4 | 큰 자료를 마운트마다 새로 풂(곱셈 인자) | `b_asset_cache.ts:303-310` 보관함 바이트를 부를 때마다 decode + `JSON.parse`(메모리 기억 없음) · 한 페이지에 도엽 레이어 두 번(편집기 `B05_Profile_UI_RouteEdit.ts:605-609` · 배수 칸 `B05_Profile_UI_Drainage_Panel.ts:448`) + LAS 등고선(`RouteEdit.ts:644-652`) + 코리도 · 크기 예 `도엽_등고선.display.geojson` 36.8 MB · `contour_csf_dtm_1.0m.json` 15.6 MB → 1 과 겹치면 저장마다 페이지 한 벌 무게가 그대로 쌓임 | 높음(일어남) · 크기는 재야 함 |
| 5 | 옛 페이지 뒤처리가 떠난 뒤까지 돎 | 시설 줄 `afterSave`(`design_pipeline_facilities.ts:48-55`)가 저장 끝을 100 ms 마다 기다렸다 resync(`design_pipeline_page.ts:629`) → reconcile 이 가게 노선 초안 null 대 묶음 초안을 보고 `bridge.edited(null)`(`:573-574`) → 초안 버림 차례 · 옛 상세 되돌림(`:650-655 · 375-409`) — `ctx.done` 과 같이 돎 · 배수 자동 셈 3 s 타이머(`B05_Profile_UI_Drainage_Live.ts:228 · 241`)를 배수 칸 `dispose`(`Drainage_Panel.ts:662-670`)가 안 끊음 · B06 쉬는 틈 줄(`B06_Section_UI_Section_Common.ts:139-158`)에 멈춤 없음 | 중간(차례 추정) |
| 6 | 494-1 쉬는 틈 200 ms 한도 | [저장]이 숨긴 채 세운 B06 보기(`design_shared.ts:394`)의 화면 밖 카드를 200 ms 안에 다 그림 → 버려질 옛 페이지 무게 늘림 · 줄이 `document.getElementById`(`B06_Section_UI_Section_View.ts:502-519`)라 B06 에서 저장했으면 새 페이지 같은 id 카드를 옛 자료 카드로 바꿔 낄 수 있음. 단 저장 뒤 탭 멈춤은 494-1 전 493-4 검증(「ORCA 는 [저장] 끝난 탭과 응답 끊김 · 새 탭은 정상」)에 이미 있음 → 주원인 아님 | 낮음 |
| 7 | 잠시 붙듦 | `B06_Section_UI_Cross_Panel_Base.ts:78-88` `lastPointer` = 마지막 누른 노드([저장] 단추) → 다음 누름까지 옛 나무 전체 · 편집기 키 듣개(`RouteEdit_Keys.ts:157-159` · `RouteEdit_StructureMarks.ts:398-400`)는 다음 키에서 스스로 뗌 | 낮음 |
| 8 | 큰 sessionStorage 사본 | 상세 세션 사본 · 가게 메모리 칸 1 s 뒤 씀(`design_store.ts:204-233`) — 한도 넘으면 쓰기 실패일 뿐 힙 폭증 아님 | 낮음 |
| — | WebGL 문맥 · rAF · setInterval · ResizeObserver | 3D `dispose` 가 `renderer.dispose` + `forceContextLoss`(`B05_Profile_UI_Viewer.ts:524-545`) · 그리기 고리 멈춤 · 편집기 `sizeWatch.disconnect`(`RouteEdit.ts:252-261`) · 묶음 `dispose`(`design_pipeline.ts:375-379`) — 새는 곳 못 찾음 | 문제 아님 |
판단: 1 + 4 가 저장마다 「페이지 한 벌」을 쌓고 2 가 노선마다 코리도를 쌓고 3 · 5 가 저장 직후 순간 최대치를 키움 → 무거운 프로젝트는 첫 [저장]에서도 렌더러 힙 한도에 닿을 수 있음. 1 의 뿌리(①②③)는 09-04 · 10-16 · 10-18 부터라 「전부터」와 맞음.
## 재현 · 재는 법(코더 · ORCA)
준비:
- 자기 포트 서버 · 자기 검증 프로젝트(`OWNERS.md` 자기 줄) · 탭은 `orca tab list --json` 의 자기 포트 탭 재사용.
- 저장 전 백업 — DB `routes` · 노선 자식 4 표 · `stages` + storage(노선 파일 · `pipe_points.json` · `structures.json` · `plus_stations.json` · B06 `longitudinal` · `cross_sections` · B05 `corridor`). 끝나면 되돌림 · 탭 새로고침 · 닫음.
계측(B05 연 뒤 · 「계산 중」 점 꺼진 뒤 · `orca eval` 한 번):
- 듣개 셈 — `EventTarget.prototype.addEventListener` · `removeEventListener` 를 감싸 대상이 window · document 인 것만 종류별 더함 · 뺌.
- WebGL 셈 — `HTMLCanvasElement.prototype.getContext` 를 감싸 `webgl` · `webgl2` 만든 수.
- 토스트 기록 — 토스트 그릇의 `.ui-toast` 생김을 MutationObserver 로 글 · 종류째 모음(3 s 뒤 사라짐).
- 옛 페이지 표 — `new WeakRef(document.querySelector('.design-page'))` · 편집기 캔버스도 하나.
- 기준값 — `performance.memory`(used · total · limit) · `.design-page` 수.
조작 · 잼:
1. 평면 교점 하나를 몇 m 끌기 → [저장] → 잠금 막 걷히고 「계산 중」 꺼진 뒤 10 s.
2. 토스트 기록 = 무엇이 떴나 · 계획고 지움의 「자리: X m」 를 새 상세 측점 · 관 목록과 맞댐(원인 갈래).
3. 듣개 차 — 새면 저장 한 번에 window `beforeunload` +1 · `pointermove` · `pointerup` · `pointercancel` · `resize` +1 · `scroll` +1 · document `pointerup` · `keyup` +1. 고친 뒤 기대 = 0.
4. 옛 페이지 — 큰 배열을 몇 번 만들고 버려 압박 · 30 s 뒤에도 WeakRef 가 살아 있으면 샘(고친 뒤 기대 = 풀림).
5. heap 차 — 같은 조작을 세 번 되풀이 · 저장마다 늘어난 양(기대: 옛 페이지 한 벌 + 코리도 한 벌) · `jsHeapSizeLimit` 에 닿는 차례 기록.
6. WebGL — 저장마다 새 문맥 1 · `window.__routeViewer` 하나만 · 3D 닫힘이면 `running()` 거짓.
7. 대조 — F5 뒤 heap = 깨끗한 기준.
주의: `performance.memory` 는 Chromium 만 — 없으면 작업 관리자 · `Get-Process` 로 렌더러 메모리. 저장 뒤 탭이 멈추면(493-4 기록) eval 이 안 돌아옴 → 저장 전 · 직후 짧게 나눠 잼. eval 끊김 = 이미 돎(저장 단계 재시도 금지).
## 고칠 자리 · 시험 · 위험
고칠 자리(작은 것부터):
1. 토스트 — `design_save.ts:168-177` 은 따라감 실패(오류)만 남김 · `routeSaveSummary`(`RouteEdit_Apply.ts:145-157`) 걷음 · 노선 저장 길 계획고 지움 알림(`design_save.ts:121`) 안 띄움 · 상단측 안내(`RouteEdit_Apply.ts:182-183`) 걷음(결정 따라).
2. 전역 듣개를 페이지 수명에 묶음 — `guardDesignLeave` 가 떼는 함수를 돌려주고 `design_page.ts` dispose 에서 부름 · `overlay_drag` window 듣개 넷을 AbortController 로(`root.isConnected` 거짓이면 끊음 · 모든 B 페이지 혜택) · `Panel_Settings` 는 `gradeAdvanced` 가 떨어지면 스스로 뗌 · `side_panels` 는 `watched` 도 WeakRef.
3. 코리도 캐시 = 프로젝트마다 지금 노선 한 벌(`Corridor.ts:161 · 185` 앞에서 같은 프로젝트 다른 노선 칸 지움).
4. 노선 바뀐 [저장]은 `ctx.done` 다시 읽기 대신 떠남 표(`markDesignClean`) + 「저장했습니다」만 → 곧바로 다시 세움.
5. 옛 페이지 뒤처리 끊기 — 배수 칸 dispose 에 자동 셈 타이머 · 요청 끊기 · B06 쉬는 틈 줄 dispose 때 비움 + 떠난 뒤 안 돎.
6. 계획고 근본(결정 2) — 노선 바뀐 저장에서 계획고 초안이 따라감만이면 안 보냄(추가 측점 `followedOnly` 와 같은 꼴 · `B05_Profile_Api_PlusStations.ts:86-88 · 140-146`) → 서버 이월이 정본 · 사용자가 초안 동안 ▲▼ 했을 때만 보냄.
시험(고친 대상만):
- `test_346_3_route_save_screen.py`(요약 글 · `routeSaveSummary(saved, carryOutside)` 소스 단언 고쳐 씀) · `test_472_6_profile_save.py` · `test_349_0u_save_rebase.py` · `test_283_route_reset_confirm.py` · `test_design_store_save.py` · `test_346_4_design_leave.py` · `test_213_2_ghost_edit_drop.py`(계획고 알림 바꾸면) · `test_494_1_idle_queue.py`(쉬는 틈 줄 바꾸면) · `test_317_3d_update.py` · `test_345_4_corridor_worker.py`(코리도 캐시) · `npm run typecheck`.
- 새 시험 — 다시 세움 앞뒤 window · document 듣개 수 같음(헬퍼로 addEventListener 셈) · 코리도 캐시 칸 수 = 1 · 노선 저장 뒤 토스트 = 실패만.
위험:
- 떠남 물음 — 같은 페이지 안 이동 다시 걸기(`design_leave.ts:110-113`) · [폐기] · 탭 닫기 물음 그대로여야 함.
- `overlay_drag` · `side_panels` = 공용 틀 — 진행단계 패널 끌기 · 열림 방향 · 창 크기 따라감 · M01 떼었다 붙임(WeakRef 의도) 확인.
- done 걷기 — 노선 바뀐 저장 뒤 「저장했습니다」 · 떠남 표 · 옛 노선 칸 비움(`design_save.ts:229-231`) 차례 유지.
- 계획고 근본 — 초안 동안 사용자 ▲▼ 가름이 틀리면 사용자 편집을 안 보냄 → 거울 시험 필요.
## 최근 관련 커밋(10-19 ~)
- `797857931` 494-1 쉬는 틈 200 ms · B05 횡단 판 들어올 때 고름 — 후보 6(주원인 아님).
- `52553a6c6` 494-2 새 교차 관 B04 기본값 — 요약 「새 교차 관」 수의 뜻만 바뀜.
- `e434d1792` 493-4 관 땅 위 좌표 지킴 — 관 측점 자리 어긋남(계획고 지움 갈래)을 줄였을 수 있음 · 검증 기록에 「[저장] 끝난 탭 응답 끊김」.
- `4aeaa9442` 493-5 · `2b7b12956` 493-3 · `21b8c3231` 493-1 · `7f5019c7f` 493-2 — 추가 측점 · 따라감 · 저장 갈래 · 새는 곳 없음(493-3 `plusAt` = WeakMap).
- `d2bf68b59` · `7a857d626` 492 — 493 이 걷음.
- 뿌리 오래됨 — `af85dea71`(09-04 진행단계 패널 끌기) · `47366d374`(10-16 노선 저장 결과 한 줄 · 다시 세움) · `4f2c7b7a0`(10-18 페이지 설정 document 듣개) · `4b6800e38`(10-19 한 본문 노선 저장).
## 사용자 결정 필요
1. 노선 [저장] 뒤 다시 세움 — (가) 지금처럼 페이지 안 다시 세움 + 새는 곳 막음(고칠 자리 2~5 · 부드러움) (나) [초기화]처럼 브라우저 새로고침(한 줄 · 메모리 확실히 비움 · 화면 깜빡임 · 다시 읽는 시간 · 떠남 물음 한 번 더(`design_leave.ts:69`) · 따라감 실패 알림은 새로고침 너머로 넘겨야 함).
2. 계획고 지움 알림 — (가) 노선 저장에서 알림만 지움(화면에 보인 계획고 편집이 말없이 사라질 수 있음 · 493 「보이는 화면 그대로」 어긋남) (나) 고칠 자리 6 으로 근본을 막고 지움.
3. 「새 교차 관 N 개」 — 저장 뒤 화면에 없던 자동 관이 새로 생김 · 이 한 줄만 남길지 · 다 지울지.
@@ -0,0 +1,100 @@
# 요구 298-4 · 298-6 조사 — 좌측 패널 하이라이트 · B06 평면 겹침
읽기 전용 조사(코드 · 브라우저 안 만짐) · 줄 번호 = `main_laptop_1` 83ef315fa · CSS 판정은 정적(화면 실측 안 함).
## 1. 298-4 좌측 패널 — 하이라이트가 닫힌 패널을 엶
### 지금
- 패널을 여는 길은 하나뿐 — `shared.revealLeft()`(design_page.ts:153) → `layout.setOptionsOpen(true)`(ui_template_workflow_layout.ts:220) → 제목 패널 `setOpen(true)`(ui_template_overlay.ts:209-224).
- 그 길을 부르는 곳도 하나 — `onReveal: () => isUserPick(...) && shared.revealLeft()`(design_shared.ts:316). `isUserPick`(design_user_pick.ts:26) = 진짜 누름(click · pointerup · contextmenu 등) 안 · 계산 차례 안 돎일 때만 참.
- `onReveal` 을 부르는 폼 함수 셋(B05_Profile_UI_Structures_Panel.ts) — `selectPipeByChainage`:600 · `selectById`:624 · `addAt`:643. 우클릭 추가만 「구조물 배치」 카드 펼침(:644) · 카드 스크롤(:663) · 첫 칸 focus(:665).
- `setOpen(true)` 은 취향 키 `frd_workflow_title_overlay_open`=true 까지 적음(overlay.ts:220) — B03 ~ B09 다른 페이지 첫 상태도 바뀜 · 본문 왼쪽 여백(`is-options-open`)이 바뀌어 3D · 평면 칸 크기도 출렁임 · 폭 840 미만은 서랍 + 덮개가 지도를 가림.
부르는 곳(누름 → 폼 함수) — 모두 `onReveal` 한 곳을 지남:
| 누름 | 길(파일:줄) | 패널 엶 | 카드 펼침 | 좌측 스크롤 |
| --- | --- | --- | --- | --- |
| 종단 알약(B05) | B05_Profile_UI_Profile_Render.ts:424 → design_view_profile.ts:161 · 165 | 엶 | 안 펼침 | 목록 줄 B05_Profile_UI_Structures_List.ts:230 · 242 |
| 평면 구조물 마커(B05 평면 · B06 평면 겹침 모두) | design_view_profile.ts:383 → :161 · 165 | 엶 | 안 펼침 | 목록 줄 |
| 종단 측점선 · 3D 측점 띠 | design_view_profile.ts:237 · 240 → Structures_Panel.ts:600 · 619-620 | 엶 | 안 펼침 | 목록 줄 |
| 배수유역도 유역 · 관 마커 | design_view_profile.ts:206-207(pickFromMap) → :237 | 엶 | 안 펼침 | 목록 줄 |
| 3D 부재 | design_view_profile.ts:332 → B05_Profile_UI_Structure_Pick_Session.ts:71 | 엶 | 안 펼침 | 목록 줄 |
| 종단 우클릭 [추가](B05) | design_view_profile.ts:201 · 203 · 210 → Structures_Panel.ts:643 | 엶 | 펼침(:644) | 카드(:663) · focus(:665) |
| 3D 노선 우클릭 메뉴 | B05_Profile_UI_Page_Structures.ts:256 → addAt | 엶 | 펼침 | 카드 |
| 횡단 카드 · 경고 칩 측점 단추 | design_view_cross.ts:391 → B06_Section_UI_Page_Structures_Panel.ts:593 · 599 | 엶 | 안 펼침 | 목록 줄 |
| 카드 벽 · 구체(옹벽 · 세월교 · BOX) | B06_Section_UI_Page_Structures_Selection.ts:95 · 223 · 238 → showAtChainage | 엶 | 안 펼침 | 강조 상자 B06_Section_UI_Adjust_Dock.ts:194 · 220 · 244 |
| 소단 | Structures_Selection.ts:209 → selectById | 엶 | 안 펼침 | 소단 상자 :214 |
| 노면공 형상 · 종단 알약(B06) | design_view_cross.ts:380 · 346 → B06 Structures_Panel.ts:572 | 엶 | 안 펼침 | 목록 줄 |
| 측점 없는 관 알약 | design_view_cross.ts:347 → B06 Structures_Panel.ts:615 | 엶 | 안 펼침 | 목록 줄 |
| 종단 우클릭 [추가](B06) | design_view_cross.ts:337 · 339 → B06 Structures_Panel.ts:518 · 520 → addAt | 엶 | 펼침 | 카드 |
| 좌측 목록 줄 | Structures_List.ts:228 · 240 | 이미 열림 | 안 펼침 | 목록 줄 |
안 여는 길(누름 밖 · `isUserPick` 거짓): 진입 · 보기 전환 되살림(design_view_cross.ts:476 · Pick_Session.ts:97 · B06 Structures_Panel.ts:378 — 해시 바뀜은 따로 도는 이벤트) · 방향키 · 묶음 다시 칠함 · 조정창 값(B06 Structures_Panel.ts:537) · 월류 높이 받음(:555).
### 원인
- 09-04 「바깥에서 고르면 좌측 패널도 편다」 + 398-1 「사용자 누름만」 규칙 — 고름이면 패널 상태를 안 보고 엶.
### 고칠 자리
- 한 곳(공용)으로 막힘 — 부르는 곳마다 고칠 일 없음.
- design_shared.ts:316 `onReveal` 줄 지움 · `revealLeft` 지움(design_page.ts:153 · design_shared.ts:115-116).
- 열린 패널은 지금 그대로 — 강조 · 폼 · 안쪽 scrollIntoView 는 폼 · 목록이 스스로 함(위 표 「좌측 스크롤」).
- 닫힌 동안 갱신 → 다음에 열 때:
- 폼 내용 · 목록 강조(`.is-selected`) · 강조 상자(`fieldset.is-active`) · 우클릭 추가의 카드 펼침 = DOM 상태 → 열면 그대로 보임.
- 스크롤은 안 맞음 — 닫힌 패널 몸통 = `display:none`(ui_template_overlay.css:297 이 :52 를 이김 · 같은 특정도 · 뒤 줄) → 닫힌 동안 scrollIntoView · focus 무효(엉뚱한 페이지 스크롤은 없음). 다시 보일 때 안쪽 스크롤 자리가 남는지는 브라우저 몫(실측 필요).
- ⇒ 열 때 한 번 밂: ui_template_workflow_layout.ts 옵션에 열림 알림 통로(:23-33 · :199) 하나 + design_page.ts buildLayout(:173-188)에서 열림 때 `left` 안 `.is-selected` · `fieldset.is-active` 첫 것을 scrollIntoView(nearest). ui_template_overlay.ts setOpen 에 넣으면 B03 ~ B09 도 바뀜 → 통로 쪽.
### 시험
- test_398_1 `test_뿌리_한_곳`(:54-73) 뒤집기 — `A00_Common/design` 에 `revealLeft` · `setOptionsOpen(` 없음.
- test_116(:84-90) · test_409(:124)는 Structures_Panel 의 `callbacks.onReveal?.()` 자리를 봄 — 부름 셋을 남기면 초록 · 지우면 함께 고침.
- 열 때 밀기 — node 도우미 하나(가짜 패널 · 열림 알림 → scrollIntoView 부름 1 · 닫힘 0).
- ORCA — 패널 닫고 위 표 누름 각각 → 제목 패널 `is-collapsed` 유지 · 취향 키 값 그대로 · 좌측 `.is-selected` 생김 → 패널 열기 → 고른 줄 rect 가 스크롤 칸 안. 패널 연 채 같은 누름 → 강조 · 스크롤 지금과 같음.
### 위험
- 우클릭 [추가]에서 필수 칸이 남으면 닫힌 패널 안 폼만 열림 → 사용자가 모름(A군 임시 배치만 화면에 보임) → 사용자 결정 1.
- `revealLeft` 지우면 `isUserPick` 쓸 곳 없음 — design_user_pick.ts · helper/test_398_1 이 그 파일만 시험 → 정리는 사용자 몫(시험 지우지 않음).
- 좋은 쪽 번짐 — 취향 키 덮어쓰기 · 본문 여백 출렁임 · 좁은 폭 서랍 가림이 함께 사라짐.
## 2. 298-6 B06 평면 겹침 대 하단 종단 판
### 지금
- DOM 자리 — `.design-page__main`(relative · overflow hidden · 세로 flex · design_page.css:15-23) 아래
- `.design-page__center`(relative · flex 1 · :25-31) → 보기 상자(display contents) → 카드 칸 + `.design-plan-peek`(absolute · z 4 · 위 붙음 · design_page.css:98-110 · 세움 design_view_cross.ts:414 → design_plan_peek.ts:43).
- `.design-page__profile`(display contents) → `.b05-route-profile`(z 3 · B05_Profile_UI_Style.css:41-57 · 횡단 보기 = relative · flex none · design_page.css:38-41 · 높이 `--b05-profile-height` 기본 60dvh · 최대 = main × 0.9 · B05_Profile_UI_Profile_Heights.ts:21 · 25-28).
- 쌓임 — 두 판 사이 조상에 z-index · transform · isolation 없음 → 같은 쌓임 문맥 · 평면(4)이 이미 종단(3) 위. 판 안 숫자는 공용 토큰 밖 지역 값.
- 열림 클래스 — `.design-plan-peek.is-open`(저장 안 함 · 처음 닫힘 design_plan_peek.ts:81 · 높이만 저장 `plan-peek-height`) · `.b05-route-profile.is-collapsed`(저장 `profile-collapsed` · B05_Profile_UI_Profile_Drag.ts:48-57).
- 공용 z 토큰 — ui_template_theme.css:220-226 · `--z-header` 100 · `--z-dropdown` 200(좌측 패널 · 우클릭 메뉴) · `--z-overlay` 900(로딩) · `--z-modal` 1000 · `--z-confirm` 1050 · `--z-toast` 1100 · 그 밖 서랍 덮개 199(ui_template_overlay.css:540) · ☰ 201(app_shell.ts:540) · 서랍 연 머리 900(app_shell.ts:464).
### 원인 — 순서가 아니라 자리 다툼
1. 크기 다툼 — 평면의 % 높이 · 최대(design_plan_peek.ts:39 `offsetParent.clientHeight − 48`)가 가운데 위 칸 기준. 종단 판이 커지면(최대 90 %) 칸이 줄어 최대 < 최소 120 → 최소로 고정 · 처음 높이 하한 160 px → 칸 밖(종단 판 쪽)으로 넘침. 종단을 연 뒤 · 키운 뒤에는 다시 맞춤 없음(맞춤은 평면 열 때만 :63).
2. 가림 — 넘친 평면(z 4)이 종단 판 윗부분(도구 줄 · 그래프 · 종단 접기 손잡이 top −24 px)을 덮음.
3. 손잡이 겹침 — 두 손잡이 모두 가로 가운데(ui_template_overlay.css:269-278 · design_page.css:143-148) · 평면 것 = 평면 아래 끝 밑 · 종단 것 = 종단 판 위 끝 위 → 칸 높이 ≤ 평면 높이 + 48 이면 겹침 → 평면 손잡이가 누름 가로챔 → 종단 접기 못 함.
4. 높이 끌기 겹침 — 평면 아래 끝 손잡이(design_page.css:113-118 · bottom −3 px)와 종단 위 끝 손잡이(top −3 px)가 한 줄에 오면 평면 것이 잡힘.
### 고칠 자리(가장 작은)
- design_page.css 규칙 한 줄 — 횡단 보기(`.design-page__main[data-view="cross"] > .design-page__center`)만 `position: static` → 평면 겹침 기준 = `.design-page__main`(위 칸 + 종단 판 전체). % 높이 · 최대가 전체 기준 → 종단 판 크기와 무관 · z 4 그대로 종단 위 · 보기 숨김 · 닫기는 지금 그대로(DOM 안 옮김). TS 고침 없음.
- 횡단 보기에서 가운데 칸을 기준으로 삼는 absolute 는 평면 겹침 하나뿐(B06 판들은 `.b06-section` relative 기준) · 종단 보기는 선택자 밖.
### 시험
- 정적 — design_page.css 에 위 규칙 · 평면 z(4) > 종단 z(3) · 평면 z < 199.
- ORCA 수치(검증 프로젝트) — B06 · 종단 판 90 % · 평면 열기 → 평면 `offsetParent` = `.design-page__main` · 평면 높이 ≥ 120 · 아래 끝 ≤ main 아래 끝 − 24 · 겹친 자리 `elementFromPoint` = 평면 안 · 종단 판 접고 펴도 평면 높이 그대로. 로딩 오버레이 · 모달 · 토스트 · 좁은 폭 서랍 · ☰ 을 띄운 채 같은 자리 → 그쪽이 위. 끝나면 판 높이 · 접힘 되돌림.
### 위험
- 처음 높이 기준이 칸 → 전체로 바뀜 — 종단 펼친 때 처음 높이 160 px → 전체 32 %(1000 px 화면 약 320 px)로 커짐 · 저장 높이 있으면 그대로(263-2 「지금의 70 %」 맞춘 값은 종단 접힌 때와 같음).
- 평면이 종단 판을 덮으면 종단 도구 줄 · 접기 손잡이 가림 → 평면을 줄이거나 닫아야 닿음 → 사용자 결정 2.
- z 를 199 이상 올리지 않음 — 서랍(200) · 덮개(199) · ☰(201) · 우클릭 메뉴(200) · 로딩(900) · 모달 · 확인창 · 토스트 아래 유지. 평면에 옮겨 온 `.b05-routeedit__busy`(B05_Profile_UI_Style_RouteEdit.css:409-413 · fixed · z modal)는 평면 쌓임(4) 안에 갇혀 서랍 · ☰ 아래 — 지금도 같음 · 평면 겹침은 도구 줄을 감춰 재탐색 길 드묾.
## 사용자 결정 필요
1. 우클릭 [추가](종단 · 3D · B06 종단)에서 필수 칸이 남을 때도 닫힌 패널을 안 열지 — 안 열면 토스트 한 줄로 알릴지.
2. 평면이 종단 판을 덮을 때 종단 접기 손잡이 — 가려도 됨 / 평면 손잡이를 가운데에서 비켜 둘 다 보이게.
3. 열린 패널에서 접힌 「구조물 배치」 카드 — 고름 때도 펼칠지(지금 = 우클릭 추가만 펼침 · 목록 강조는 카드 밖 아래 고정 칸이라 보임).
@@ -0,0 +1,180 @@
# 요구 298-5 보안 대 속도 — 계산 자리 고르기 조사 (읽기만 · 코드 X)
- 원문 = `docs/raw/요구_1020/사용자요구_1020.md` 요구 298 5번 · 기준 = `main_laptop_1` `83ef315fa` · 분석 = 클로드 에이전트 셋(Opus 5.5 · 읽기만) · 화면 · 서버 실측 없음(코드 · 설정 · 지난 측정값) · 「추정」 = 안 잰 값.
## 0. 결론
- 지금 모양 = **계산 코드 한 벌(TS) · 실행 자리 둘(브라우저 즉시 · 서버 정본)** — 요구 273 방향 그대로 472 로 진행 중 · B05 · B06 은 [저장] 때 서버가 다시 셈(위조는 막힘).
- 대가 = **거의 모든 식이 브라우저 묶음에 실림** — 서버 Node 묶음 계산 중 브라우저에 안 실린 것은 유토 배분(`computeHaulPlan`) 하나뿐 · B08 자재 · B09 원가 요율 식 · M02 표 엔진까지 실림.
- 가장 큰 빈틈은 계산 자리가 아님 — ① 임시 배포가 **vite 개발 서버**(원본 TS · 주석 그대로 · 설정상 저장소 파일까지 열릴 수 있음 · 실측 필요) ② **로그인만으로 M01 로직 · 일위대가 · 호표 통째 읽기** ③ 단가 계산 오라클 ④ 남의 프로젝트 표제란 읽기(IDOR).
- 「전부 서버」는 9-07 에 실측으로 접음 — 파이썬 0.31~0.41 s 대 브라우저 6 ms(약 50 배) · 회선 왕복이 더 붙음.
- 추천 = **(d) 섞음** — 조작 즉시 기하는 지금 길 · 단가 · 수량 · 내역 · 요율 · 마스터는 서버 단독 · 배포는 운영 묶음 · 마스터 길 권한 조임. 속도 손해 거의 0.
## 1. 이전 자료
| 때 | 자리 | 원문 · 결론 |
|---|---|---|
| 09-06 | `docs/raw/plans/2026-09-07_plan_completed_items.md` 0-9(약 89-95줄) | [저장] · [확정] 정본 = 서버 셈(사용자 최종 확정) — 「이유는 속도가 아니라 **보안** — 저장 경로가 브라우저 계산이면 유토 배분 · 운반거리 코드가 번들에 남아야 해서」 · 유토 배분을 서버로(`/haul-plan`) · 저장 대기 1.8~3.0 초 허용 |
| 09-06 | `main.py:507-512` · `common_util/common_util_audit.py:87-95` | 호출량 감시(사용자 지시 · 보안) — 「계산 결과는 화면에 나가도 된다는 방침이라, 남는 위험은 입력을 바꿔가며 출력을 긁어 모으는 것」 · 막지 않고 시스템 로그만 |
| 09-07 | `docs/raw/plans/2026-09-07_plan_completed_items_3.md` 0-8 | 목표(사용자 확정) — 「끌면서 보는 값은 0.1초 안에 결과까지, 놓고 나서 보는 값은 반응만 즉시면 되고 결과는 1초. [저장] · [확정]처럼 기다리는 조작은 진행 표시가 있으면 수 초도 허용」 |
| 09-07 | 같은 파일 「안 하기로 한 것(다시 꺼내지 말 것)」 | ① 계산 전부 서버 — 「종단 높이 변경 313ms(중앙 413), 표준단면 330ms … 파이썬 계산 자체가 벽(브라우저 6ms 의 약 50배)」 ② Node 상주 프로세스 — 「Node 단독 8회 폭 16ms … 기동값이 문제가 아님」 ③ 난독화 · 소스맵 · 도메인 상수 서버 배달 — 「배포 산출물에 소스맵 0개, 식별자 뭉개짐 … 남은 상수는 조도계수 · 관경 규격표 같은 공개 표준값」 |
| 10-05~06 | `docs/raw/사용자요구/2026-10-05_수정요구.md:156` | 「B05/B06의 실시간 제어와 동작 속도/보안 개선을 위해 많은 작업이 있었는데 좌측 패널 통합 이후 많이 느려진 걸로 보임. 재확인」 → 조사 `2026-10-06_54-5_속도_보안.md`(느린 곳 6 · 보안 흠 11 · H1 치명) → 10-6 답 「3. 반영」 → 54-22(H1 · H2 · S1 · `c8ca79063`) |
| 10-12 | `docs/raw/plans/2026-10-12_plan_271-276.md:24` | 273-2 B06 초안 바로 반영 — 「사용자: 편의 · 보안은 알아서 · 느리지 않게」 |
| 10-16 | 요구 230(`docs/raw/요구_1010_2/사용자요구_1010_2.md`) | 「현재의 내부 코드 모듈들의 계산 시간을 프론트에 보여주기까지 시간을 측정해서 보줘. 여기에 파일입력 후 전처리 로직도 포함 … 코드수정 아님」 → `2026-10-16_340_계산시간_측정.md` |
| 10-16 | 요구 232-2(같은 장부) | 「화면 프리즈 안되고 0.3초 안에 종단 횡단 평면(노선) 이 사로 연동」 → 341 · 346 · 349 |
| 10-16 | 메모리 「기능 먼저 · 속도는 따로」 · `PLAN.md` 350 | 「시간을 0.3 제한에 맞추기보다 일단은 기능을 제대로 구현 … 별도로 시간을 줄이기 위해 재검토」 → 350-1 미결 「(사용자가 시킬 때) 끌기 · 계획고 ▲▼ · 저장 체인 시간 다시 재고 줄일 거리 정함」 |
| 10-17 | 요구 242 답(`docs/raw/요구_1017/사용자요구_1017.md`) | 「사용자 편의성과 보안측면에서 잘 고려하여 적합한 방법을 제시」 → 384 · 388(로그아웃 지움 · [확정] 면적 서버 셈만 `23c664583` · 388-4 쓰기 권한 = 회사 소속 전체 · 요구 251-1 그대로) |
| 10-19 | 요구 273(같은 장부) | 「데이터 저장의 관점을 보면 보안과 사용자 편의성을 위해 충돌이남. 그래서 로직들은 프론트와 백엔드에서 동작하게 하는 로직을 동시에 구성하고 최종적으로 하나의관리코드에서 선택적으로 할수 있게 반영하는건 어때? 물런 코드가 하나면 유지보수에 좋을테니 더 좋고」 → PLAN 472 방향 「계산 코드 한 벌(TS 순수 계산) · 실행 자리 둘 · 계산마다 실행 자리를 등록부 한 곳에서 고름」 · 472-3 등록부 끝 · 472-2 · 472-4 · 472-5 · 472-6 남음 |
- 「시간 측정 + 프론트 · 백엔드 선택은 나중에」를 한 줄로 적은 장부 줄은 못 찾음 — 가까운 것 = 요구 230(측정) · 요구 273(관리 코드에서 선택) · PLAN 350-1(0.3 s 는 기능 뒤).
- 9-07 「다시 꺼내지 말 것」의 난독화 판단은 **운영 묶음** 전제 — 지금 검증 사용자는 개발 서버(5173)를 씀 → 전제가 안 맞음(3장 S1).
## 2. 지금 계산 자리 표
- 기호 — ① 파이썬 · TS 짝 · ② TS 를 서버 Node 로 · ③ 서버 단독 · 브만 = 브라우저만(입력 도구 · 표시).
- 「나감」 = 그 식이 브라우저 묶음에 실림(사용자 컴퓨터에서 보임) · 「결과만」 = 응답 값만.
- 숫자 — 등록부 45 id(`A00_Common/design/logic/design_calc_registry.ts` 28 + `_facility.ts` 17): browser_now 34 · server_canon 1(haul.plan) · server_only 4 · browser_only 6. 등록부 `site` 칸은 **기록 · 시험용** — 실행을 바꾸는 스위치 아님(런타임 import 0 · 시험 `test_472_3_*` 만 읽음).
- 짝 — 머리 15줄에 「짝」 적은 TS 127 파일(파이썬 파일 이름을 적은 것 약 53) · 거울 시험 26 파일(`resources/tester/*mirror*`).
### 2.1 서버 Node 묶음 일곱(`package.json` build:*)
| 묶음 | 진입 | 계산 | 브라우저에도 실림 |
|---|---|---|---|
| corridor | `B05_Profile/B05_Profile_Corridor_Node.ts` | 3D 길 모형 `buildCorridor` · 해시 | 예(웹 워커 · `B05_Profile_UI_Corridor_Worker_Client.ts:31`) |
| server-calc | `B06_Section/B06_Section_Server_Calc_Node.ts` | 구조물 면적 · 다단 벽 · 누가토량 · **유토 배분** | 배분만 아니오(`import type` 만 · `B05_Profile_Api_HaulPlan.ts:14`) |
| formula | `common_util/common_util_sheet_recalc_node.ts` | 표 수식 다시 셈 | 예 |
| spreadsheet | `common_util/common_util_spreadsheet_node.ts` | M02 면적산출 시트 · 면적표 · 이름 | 예(M02 DF · 면적 도면) |
| b07-extent | `B07_DesignDetail/B07_DesignDetail_Cross_Extent_Node.ts` | 횡단 덧그림 · 범위 | 덧그림 예 · 범위 셈만 서버 |
| b07-note | `B07_DesignDetail/B07_DesignDetail_Note_Options_Node.ts` | 구조물 옵션 글줄 | 예 |
| design-calc | `A00_Common/design/logic/design_calc_node.ts` | 12 id — 구조물 면적 · 누가토량 · 유토 배분 · 터파기 · 소단 구간 · 횡단 설계선 · 3D · 꼭짓점 세움 · 시설 따라감 · 추가 측점 · 다시 앉힘 · 측점 이월 | 배분 빼고 예 |
### 2.2 페이지별
| 계산 · 자료 | 자리 | 브라우저로 | [저장] 때 서버 다시 셈 |
|---|---|---|---|
| **B05 · B06** | | | |
| 노선 선 · 곡선 · 노드 규칙(R1 · R3) | ① | 나감 | 예 — `/route/replan` 이 처음부터 |
| 측점 틀 · 목록 · 열쇠(R5~R7) | ① | 나감 | 예 |
| 지반 표본 · 넓힘(G1 · G2) | ① · 지형 격자 1.4 MB 받음 | 나감 | 예 |
| 종단 계획선 · 곡선 법칙 · 직선 · 경고(P1~P8) | ① | 나감 | 예(경고 다시 · 472 덧) |
| 횡단 설계선 · 면적 · 보정 · 소단 · 맹암거(C1~C6) | ① → ② 옮기는 중(472-4 ②) | 나감 | 예 · 면적 = 서버 셈만(388-2) |
| 시설 따라감 · 추가 측점 · 꼭짓점 세움(F1 · F2 · P6) | ②(472-4 ①) | 나감 | 예 |
| 관 세트 · 벽 · 포장 · 세월교(F5 · F6 · F9) | ① | 나감 | 관 세트 예 · 세월교 높이 = 입력 그대로(없을 때만 서버 · `B04_PreProcess_Router_Basins.py:256-260`) |
| 구조물 면적 · 관 길이 · 터파기 · 누가토량(F7 · F8 · E1) | ② | 나감 | 예 |
| 유토 배분 · 운반거리(E2) | ② 서버 정본 단독 | 결과만 | 서버 |
| 3D 길 모형(D3) | ② + 웹 워커 | 나감 | 서버 Node 가 지음(브라우저 올림 없앰 · 342-2) |
| 배수유역 · 관 자리 제안 · 넓힘 · 노선 추천(D1 · D2 · G3 · N1) | ③ | 결과만 | 서버 |
| 평면 발자국 · 손잡이 · 스냅 · 직선화 · 반폭 맞춤 | 브만 | 나감 | 입력 도구라 해당 없음(B07 발자국은 파이썬 따로) |
| **B07** | | | |
| 도면 생성(평면 · 종단 · 횡단 · 토적도 · 표) · 횡단 수량산출표 | ③ | 결과만 | 서버 |
| 횡단 덧그림 · 구조물 글줄 | ② | 나감 | 서버 범위만 |
| **B08** | | | |
| 토적 · 토공집계 · 운반거리 · 구조물 집계 규칙 | ③(암 재가중 · 커플링만 ①) | 결과 + 그 식 | GET 때 서버 셈 |
| 자재 3표(단위수량 · 배합 · 할증 12/5/3 · 끝수) | ① `B08_Quantity_Material_Calc.ts` | **나감** | 예 — PUT 은 고친 값만 · 서버 셈(`Router_Material.py:247-254`) |
| 홍수량 · 거리이정표 · 운반 사이클(품셈 8-2-8) | ③ · ① · ① | 이정표 · 사이클 식 나감 | 예(입력만 저장) |
| **B09** | | | |
| 바탕단가 · 일위대가표 · 설계내역서 | ① `B09_Estimation_Calc_{Base,Labor,Bill}.ts` | **나감** | 예 — 사용자 값만 저장 · 응답은 서버 셈(`Router.py:205-219`) |
| 원가 요율(간접노무 · 보험 · 안전 · 일반관리 · 이윤 · VAT) | 식 ① `Calc_Total.ts:19-46` · 요율표 = 서버 파일 | **식 + 요율 압축표 통째**(`Router.py:191-192`) | 예 |
| 산근 · 집계표 넷 | ③ | 결과만 | 서버 |
| 인쇄 PDF · XLSX | 브라우저가 만든 HTML · 격자를 서버가 꼴만 바꿈 | — | **아니오(위조 가능)** · `draft/B09_Draft_Print_Router.py:335-352` |
| **M01 · M02** | | | |
| M01 단가산출 로직 셈 · M02 일위대가 셈 · 실무호표 맞춤 | ③ | 결과만 | 예 |
| M02 흐름도 평가 · 면적산출 시트 · 표 수식 | ① · ② · ② | **엔진 나감** | 흐름 줄 · 면적 · 표 예 · 일반 줄은 검사 뒤 그대로(관리자 입력) |
| M02 프로젝트 층 양식 PUT | — | — | area 만 다시 셈 · labor · structure · drawing 은 **받은 그대로**(`Router_Layers.py:343-368`) |
## 3. 보안 빈틈
| # | 무게 | 자리 | 빈틈 |
|---|---|---|---|
| S1 | **치명(설정 근거 · 실측 필요)** | `main.py:263-271`(`npm run dev -- --host`) · `:400-407` · `config/config_system.py:280`(ENVIRONMENT 기본 development) · `config/node_modules/vite` 8.1.3 기본 `server.fs` | 임시 배포(데스크탑 5173)가 vite 개발 서버 — 주석 달린 원본 TS + 인라인 소스맵이 그대로 나감. 게다가 `fs.allow` 기본 = 저장소 뿌리(package.json 폴더) · `fs.deny` 기본 = .env · 인증서 · .npmrc · .git 뿐 → `/@fs/` 주소로 `storage/`(모든 회사 프로젝트 · 로그인 없이) · `*.py` · `config/` 토큰 파일 · `tmp/` · `resources/knowledge` 가 받아질 수 있음(추정 — 요청 안 보냄). 닿는 범위 = Tailscale 안만인지도 확인 필요 |
| S2 | 높음 | `M02_MasterTemplete_Router_Combos.py:29-33` · `:83-131` · `Router_Layers.py:268-316` · `SO_Router.py:74-106` · `common_util_auth.py:126` | **로그인만**으로 M01 로직 전체(식 · 품) · 품셈 PDF(`/m02/logic-lib/*`) · 시스템 층 일위대가 · 사전조합 · 실무호표 · 구조물 정의 읽음 — M01 길을 시스템 관리자 전용으로 막은 뜻이 우회됨. 가입 직후 NO_COMPANY · PENDING 세션도 통과 |
| S3 | 높음 | `M02_Labor_Router.py:67-77` · B09 `Router.py:236-287` | 단가 오라클 — 아무 일위대가 문서를 넣으면 지금 M01 단가로 셈해 돌려줌(로그인만) · B09 logic-preview(회사) 같은 꼴 · 호출량 감시는 기록만(`main.py:513-519`) |
| S4 | 높음 | `M02_MasterTemplete_Router_Drawing.py:143-150` · `common_util_auth.py:199-201` | IDOR — `GET /api/m02/drawing-fields?project_id=` 회사 대조 없음 → 남의 프로젝트 표제란(공사명 · 발주처 · 이름 · 로고 · 서명). `require_project_access` 는 경로의 project_id 만 봄 → 쿼리로 받는 다른 길도 전수 점검 필요 |
| S5 | 중간 | B09 `Calc_*.ts` · `Calc_Total.ts:19-46` · B08 `Material_Calc.ts` · `Haul_Cycle.ts` · M02 `M02_DF_Eval.ts` · 스프레드시트 엔진 | 원가 · 수량 식 · 요율 상수가 브라우저 묶음. 운영 묶음(`config/node_modules/.build` JS 153 · 소스맵 0)은 식별자만 뭉개짐 — 속성 이름 · 글 · 숫자 상수 그대로(`cut_area_m2` 등 7 파일) → 개발자 도구 정렬로 읽힘 |
| S6 | 중간 | `B09_Draft_Print_Router.py:335-352` · `Router_Layers.py:343-368` · B09 `Store.py:92-95 · 253-255` · `B05_Profile_Structures_Repository.py:211-213` | 위조 · 검사 빈칸 — 인쇄물이 브라우저 값 그대로 · 프로젝트 층 양식 셋 받은 그대로 · B09 요율 · 단가 · 수량 범위 검사 없음 · 구조물 명세 정의 밖 키 · 길이 제한 없음(54-5 H3 남음) |
| S7 | 낮음 · 정책 | `B01_Dashboard/B01_Dashboard_Knowledge_Chat.py:20` · `:220` | 지식 원문 길 — 빼는 폴더 = 실무문서 · stmate · 다산소프트 · 「경쟁사 프로그램」 폴더는 안 빠짐(로그인 누구나) · 발췌가 바깥 AI API 로 나감 |
- 이미 막힌 것 — H1 · H2 경로 넘기(`c8ca79063`) · [확정] 면적 · 관 길이 = 서버 셈만(`23c664583`) · 로그아웃 · 계정 바뀜 지움(388-1) · 코리도 PUT 없앰(342-2) · 유토 배분 서버 단독 · 세션 쿠키 httponly · samesite lax · M01 · M02 쓰기 = 시스템 관리자.
- 역할 = SYSTEM_ADMIN · ADMIN · USER + is_master(COMPANY_ADMIN 없음) · 프로젝트 쓰기 = 회사 소속 전체(요구 251-1).
## 4. 속도 자료
### 4.1 목표(사용자)
- 끌면서 보는 값 0.1 s · 놓고 보는 값 반응 즉시 + 결과 1 s · [저장] 진행 표시 + 수 초(09-07).
- 평면 ↔ 종단 ↔ 횡단 0.3 s · 화면 안 멈춤(요구 232-2) — 맞추기는 기능 뒤(350-1).
### 4.2 실측(지난 값 · 랩탑 · localhost)
| 조작 · 계산 | 브라우저 | 서버 | 근거 |
|---|---|---|---|
| 노선 점 끌기 한 걸음 | 3~5 ms | — | 340 |
| 점 놓고 종단 / 횡단 · 유토곡선까지 | 0.05~0.12 s / 약 0.3 s | — | 341 |
| 끌기 한 번(넓은 종단 판) | 177~189 ms(전 300~350) | — | 349-0s · PLAN 350 |
| 계획고 ▲▼ 한 번 | 0.06 s(종단) · 0.21 s(횡단) | — | 340 |
| 같은 계산을 서버 파이썬으로 | 6 ms | 종단 높이 313 ms(중앙 413) · 표준단면 330 ms | 09-07 0-8 |
| 횡단 고침 · 측점 고르기 | 0.07 s · 0.04 s | — | 340 |
| [전체 측점 반영](66 측점) | 4.1 s | — | 340 |
| 3D 길 모형 짓기 | 1.6~2.0 s(지금 워커) | Node 1.0~1.9 s | 340 |
| 유토 배분(haul-plan) | 0.4 s 잠잠 뒤 | Node 125 ms(처음 753) | 340 |
| 배수유역 관 더함 · 옮김 | — | 0.20~0.28 s | 340 |
| B06 [확정] · 노선 저장 체인 | — | 2.2 s · 6.5~19.5 s | 340 · 349 |
| 요청마다 세션 확인 | — | 13.6 ms(원격 DB 왕복 약 12 ms) | 09-07 |
### 4.3 서버 Node 부름 비용
- 부를 때마다 새 `node` 프로세스(`common_util/common_util_node_bundle.py:113-128`) · 입출력 = 임시 JSON 파일(`:131-144`) · 상주 없음.
- 실행 0.1~0.2 s · 처음 0.75~1.6 s · 부르기 전 TS 약 330 파일 낡음 검사(30 s 기억 · `:54-92`) · 낡으면 그 자리에서 빌드.
- 상주 Node = 9-07 「안 함」 · 341 R24 「보류」.
### 4.4 빈칸(안 잰 것)
- 원격 회선(Tailscale · 사용자 회선) 왕복 — 측정 0. 지금 값은 전부 localhost.
- 운영 묶음 판 조작 시간 — 340 은 vite 개발 서버로 잼.
- 여러 사용자 동시 서버 부하 — 측정 0.
- 서버 단독으로 옮길 후보(B08 · B09 · M02 칸 고침) 왕복 — 측정 0.
## 5. 갈래 · 추천
| 갈래 | 내용 | 보안 이득 | 속도 손해 | 고칠 양 | 위험 |
|---|---|---|---|---|---|
| (a) 지금 그대로 | 브라우저 즉시 + [저장] 서버 다시 셈 | 0 — 위조만 막음 · 식 노출 그대로 | 0 | 0 | S1~S7 남음 |
| (b) 값어치만 서버 단독 | B09 Calc_* · 요율표 · B08 자재 · 운반 사이클 · M02 흐름도 · 시트 엔진 → 서버(유토 배분 선례 · 서버는 이미 다시 셈 → 브라우저 사본만 걷음) | 큼 — 원가 · 수량 규칙 감춤 | 작음 — 칸 고침마다 왕복 0.1~0.3 s(추정) · 「놓고 보는 값」 1 s 안 · M01 쉬운단가 0.3 s 뒤 셈 선례 | 중간 — 화면 import → 응답 · 짝 파이썬은 그대로 | 칸마다 「계산 중」 표시 · 최신만 · 서버 부하 |
| (c) 전부 서버 | 브라우저는 그리기만 | 최대 | **큼** — 끌기 0.1 s 불가(파이썬 50 배 + 회선) · 0.3 s 겨우 넘음 | 매우 큼 — browser_now 34 id 전부 · 화면 흐름 다시 | 9-07 결정 뒤집음 · 서버 부하 사용자 수 × 조작 |
| (d) 섞음(추천) | B05 · B06 기하 = 지금 길(472 계속) + (b) + 배포 = 운영 묶음(개발 서버 바깥 안 엶) + 마스터 길 권한 조임 · 오라클 막음 · 인쇄 = 서버 정본 · 난독화는 선택 | 큼 — S1~S6 거의 닫힘 | 거의 0 — 조작 많은 B05 · B06 그대로 | 중간 — 권한 몇 줄 · 묶음 배포 · (b) 몫 | 난독화 넣으면 새 의존성 · 빌드 · 흠 찾기 어려움 |
- 추천 = **(d)** — 차례(작은 것 · 큰 이득부터):
1. 배포 판 = 운영 묶음(`/static`) · 5173 개발 서버는 바깥에 안 엶 — 임시 배포는 전역 깃 싱크 때만.
2. 마스터 길 권한 — `/m02/logic-lib/*` · 시스템 층 GET · labor calc · drawing-fields 에 회사 · 역할 검사 · NO_COMPANY · PENDING 막음 · 쿼리 project_id 길 전수 대조.
3. 값어치 계산 서버 단독 — B09 → B08 → M02 차례 · 등록부에 「server_canon」 으로 올림.
4. 인쇄물 = 서버가 정본으로 다시 그림 · 입력 범위 검사(B09 · 구조물 명세).
5. B05 · B06 = 472 그대로 · 등록부 `site` 칸을 실제 스위치로 만들지는 측정 뒤.
6. 난독화 = 3 뒤 다시 판단 — 남는 것이 기하 · 공개 표준값이면 9-07 판단 유효.
- 요구 273 「하나의 관리코드에서 선택」 = 등록부 `site` 칸이 그 자리 · 지금은 기록뿐 → 스위치로 하려면 실행기(브라우저 · 서버) 고름을 그 칸에서 읽게 바꿈(472 뒤 · 별도 계획).
## 6. 측정 계획(「최적 조건」 찾기)
| 항목 | 정함 |
|---|---|
| 무엇을 | 조작 아홉 — 노선 교점 끌기 한 걸음 · 놓기 → 종단 · 횡단 · 계획고 ▲ 한 번 · 길게 2.5 초 · 횡단 카드 고침 · 구조물 칸 · B08 자재 칸 · B09 단가 · 요율 칸 · M02 일위대가 칸 · [저장] 체인 |
| 자리 대조 | 같은 입력으로 실행 자리 셋 — 브라우저 함수 · 서버 Node 묶음 · 서버 파이썬 짝(472-1 고정 자료 `resources/tester/fixtures/golden_472/` 재사용 = 같은 입력) · Node 는 기동 몫 · 계산 몫 나눔 |
| 회선 셋 | localhost · 랩탑 ↔ 데스크탑 Tailscale(임시 배포 폴더는 안 건드림 — 랩탑 서브 서버를 다른 PC 브라우저가 부름) · 느린 회선 흉내(크롬 회선 늦춤 RTT 50 ms · 10 Mbps) |
| 판 | 운영 묶음(vite build) 판 · 개발 서버 판 둘 다 — 340 은 개발 서버 값 |
| 어디서 | 랩탑 서브 한 창 · 자기 검증 프로젝트 · 저장 안 함 · 끝나면 되돌림 |
| 어떻게 | 340 조각 재사용(Event Timing · 긴 작업 · fetch 감쌈 · 서버 [계측] 줄) · p50 · p90 · 빠른 조작 20 번 · 느린 조작 3 번 |
| 부하 | 서버 단독 후보를 동시 5 · 10 세션으로 — p90 이 목표 넘으면 상주 Node 검토 |
| 보안 측정 | 운영 묶음에서 값어치 속성 이름 · 상수 찾기(09-07 「함수 이름 말고 속성 이름으로 볼 것」) · 5173 `/@fs/` 로 storage 파일 하나 받아지는지(사용자 허락 뒤 한 번) |
| 판정(제안) | 끌면서 = p90 0.1 s · 놓고 = 반응 0.1 s + 결과 0.3 s(B05 · B06) / 1 s(B08 · B09 · M02 칸) · 긴 작업 ≤ 100 ms · [저장] 진행 표시 + 10 s 안(추정 목표 · 사용자 정함) |
| 결과 꼴 | 계산마다 「자리 · p50 · p90 · 목표 · 판정 · 추천 자리」 표 → 등록부 `site` 값 정함 |
## 사용자 결정 필요
1. 갈래 고름 — (a) · (b) · (c) · (d) 중(추천 d).
2. 숨길 값어치 범위 — 원가 요율 · 단가 · 일위대가 · 수량 규칙 · M02 마스터 · 지식DB 원문 · B05 · B06 기하 식 중 어디까지.
3. 마스터 읽기 권한 — 로그인만(지금) · 회사 소속만 · 회사 관리자(ADMIN · is_master)만.
4. 임시 배포를 운영 묶음으로 바꾸는 때(전역 깃 싱크) · 5173 바깥 열림 확인 요청 한 번 허락.
5. 목표 시간 확정 — 0.1 s · 0.3 s · 1 s · [저장] 상한.
6. 묶음 난독화 도구 넣을지(새 의존성).
7. 측정 착수(PLAN 350-1 「사용자가 시킬 때」) — 지금 시킬지.
8. 호출 상한 — 감시만(지금) · 막기(오라클 대비).
9. 「경쟁사 프로그램」 원문 열람 · 지식 발췌 바깥 API 전송 허용 여부.
@@ -0,0 +1,144 @@
# 요구 299 G — 곡선 고름에 측점 고름 유지 · 임시 횡단 잡는 영역 · 단추 제목 줄로
근거: `docs/raw/요구_1020/사용자요구_1020.md` 「요구 299」 · `PLAN.md` 409 · 440 · 490 절. 읽기만 함(코드 · 브라우저 손 안 댐).
## 1. 평면 R(곡선) 누름에 측점 고름 풀림
### 지금
- 「R 누름」 = 평면 캔버스의 교각점 · 곡선 손잡이 · 호 누름(곡선 고름 · 곡선 패널 뜸). 캔버스에 R 글자는 없음 · 곡선 패널 안 누름은 캔버스로 안 감(`B05_Profile_UI_RouteEdit_Label.ts:216`).
- 증상은 **평면 구조물 마커(알약 꼴)로 고른 때만** 남. 일반 측점 눈금 · 종단 알약 · 좌측 목록으로 고른 것은 R 눌러도 안 풀림.
상태 자리 — 둘로 갈림:
| 화면 | 그리는 근거 | 자리 |
| --- | --- | --- |
| 평면 파란 법선(구조물) | 공용 고름 한 칸 `structure-pick`(세션) | `B05_Profile_UI_RouteEdit_StructureMarks.ts:151`(readSelection → 고른 마커) · `:204-207`(법선) |
| 평면 파란 법선(일반 측점) | 오른쪽 판 자체 값 `watching` | `B05_Profile_UI_RouteEdit.ts:337` → `B05_Profile_UI_RouteEdit_Render.ts:406-413` |
| 종단 세로선 강조 | 공용 고름 한 칸 구독 | `A00_Common/design/design_pick_profile.ts:144-158` |
| 오른쪽 판 횡단도 | 판 자체 값 `watching`(모듈 안 변수) · 공용 고름을 따라가되 평면 이름 알림은 거름 | `B05_Profile_UI_RouteEdit_Cross.ts:251` · `:339-356` |
- 공용 고름 = `A00_Common/design/design_selection.ts:113-191`(읽기 · 적기 · 알림). 법선(구조물) · 종단 강조는 이 한 칸 · 오른쪽 판만 따로 들고 있음.
### 원인
- 평면 누름 `B05_Profile_UI_RouteEdit_Pointer.ts:224` → 마커가 아니면 `:246` `marks.deselect()` 가 **교각점 · 손잡이 · 호 판정보다 먼저** 무조건 돎.
- `StructureMarks.ts:430` → 평면 마커가 고른 것이면 `:337-343` 공용 고름을 비움(출처 `PLAN_PICK_FROM`).
- 공용 고름이 비니 법선 · 종단 강조 · 좌측 폼(409-2 · `design_pick_profile.ts:155-157`) · B06 강조(`B06_Section_UI_Page_Pick_Sync.ts:84`)가 풀림.
- 오른쪽 판은 `Cross.ts:355` `if (from !== PLAN_PICK_FROM)` 로 이 알림을 거름 → 그림 남음 → 어긋남.
- 의도된 옛 결정: PLAN 409(정함 브레인) 「마커 아닌 곳 누름 · Esc = 마커 고름 풂 · 오른쪽 판은 마지막 측점 그대로」. 사용자 확정 아님.
### 고칠 자리
가장 작은 고침(A) — 곡선 고름 = 측점 고름 유지:
- `Pointer.ts:246` 한 줄을 조건부로 — 누른 것이 교각점(`choice.pick` 의 node) · 손잡이(`choice.handle`) · 호(눈금이 아닐 때 `arcAtScreen(...) >= 0`)면 `marks.deselect()` 건너뜀. 자리는 그대로(`if (hitNode >= 0)` 앞) 둬야 test_399 차례 판정이 맞음.
- 마커 고름 + 곡선 고름이 같이 남음 → 방향키는 교각점 몫(`StructureMarks.ts:382` 이미 그렇게 갈림) · Esc 는 마커만 풂(곡선 Esc 길 없음) · 부딪힘 없음.
덧 고침(B · 사용자 결정) — 남은 풀기도 어긋남 없게:
- `Cross.ts:355` 를 「평면 이름이어도 값 없음(풂)이면 따라감」으로 — 빈 곳 · 등고선 · Esc 로 마커 고름이 풀릴 때 오른쪽 판 · 이전 판도 비움. 평면 이름 null 을 내는 곳은 `StructureMarks.ts:339` 하나뿐 · 눈금 누름(`Cross.ts:386` 값 있음)은 그대로 거름.
누름별 표(평면 마커로 구조물 고른 뒤):
| 누름 | 지금 | A 뒤 | A+B 뒤 |
| --- | --- | --- | --- |
| 교각점 · 손잡이 · 호(R) | 풂 · 오른쪽 판 남음(어긋남) | 유지 | 유지 |
| 다른 마커(관 · 구조물) | 그 마커로 바뀜 | 같음 | 같음 |
| 일반 측점 눈금 | 그 측점으로 바뀜(판도) | 같음 | 같음 |
| 빈 땅 · 등고선 | 풂 · 판 남음(어긋남) | 같음(어긋남 남음) | 풂 · 판 비움 |
| Esc | 마커만 풂 · 판 남음(어긋남) | 같음(어긋남 남음) | 풂 · 판 비움 |
| Shift · 재기 | 유지(`:228-231` 먼저 끝남) | 같음 | 같음 |
| 다른 화면(종단 알약 · 목록 · B06 카드)이 고른 것 + 평면 빈 곳 | 유지(440) | 같음 | 같음 |
### 시험
- 새 `resources/tester/test_299_1_curve_keeps_pick.py` — Pointer 의 조건부 풀기(교각점 · 손잡이 · 호 = 안 풂) · B 면 `Cross.ts` 걸름 줄.
- 고칠 기존 시험: `test_409_mark_top_deselect.py:81`(B 면 걸름 줄 글이 바뀜) · `test_399_plan_structure_marks.py:82-87`(차례 판정 — A 를 제자리에 두면 안 고침) · `test_409` `test_누름_마커_먼저_교각점_풂`(제자리면 통과).
- 같이 돌릴 것: test_399 · test_409(+ helper_409_2) · test_440 · test_411_a · test_494_1.
- 화면(ORCA): 마커 고름 → 교각점 · 호 · 손잡이 누름 = 법선 · 종단 세로선 · 오른쪽 판 · 좌측 폼 그대로 · 빈 곳 · Esc(B 면 셋 다 풀림) · B06 평면 패널도 같은 길(같은 Pointer).
### 위험
- 409 검증의 「교점 누름 = 풀림」 기대가 뒤집힘 — PLAN 409 결정과 다른 길이라 계획서에 적어야 함.
- 마커 정보 창(벌룬)과 곡선 패널이 함께 떠 겹칠 수 — 화면에서 확인.
- B 는 「오른쪽 판 = 마지막 측점 그대로」(409 · 409-2 검증)를 걷음.
## 2. 임시 횡단 잡는 영역
### 지금
- 판정 한 함수 = `B05_Profile/logic/B05_Profile_Profile_CrossPick.ts:45-53` `crossPickAt` · 허용 거리 `:10` `GROUND_SLACK_PX = 8`.
- 표시(`mountCrossHint`)와 누름이 같은 감싸개 `crossPickFromEvent`(`B05_Profile_UI_Profile_CrossModal.ts:52-93`)를 부름 — `B05_Profile_UI_Profile_Render.ts:333`(누름) · `:493`(표시). 상수 하나만 바꾸면 둘이 같이 바뀜.
- 기준선 = **원지반선** 위아래(사용자 글 「종단계획선」은 이 표시를 가리킨 것으로 봄). 가로 여유 없음 · 그 x 의 정수 m.
- 단위 = 그래프 화면 px(세로 창 변환 뒤 원지반 y · `CrossModal.ts:79-80` · 포인터 정수 px `:84-85`) — 세로 자동 맞춤과 무관하게 같은 눈 크기. 490 검증 ±7 px 표시 · ±9 px 없음 = 8 px 그대로.
### 원인
- 원지반선 위아래 8 px(띠 16 px) — 측점선 잡는 폭(투명 선 14 px · 세로 전체 · `B06_Section/B06_Section_UI_Style_Cross.css:487-491`)에 비해 점 하나 자리만 좁게 걸림.
### 판정 차례 · 부딪힘
- 차례 = DOM 대상 먼저(`CrossModal.ts:58-64`): 추가 측점 선 = 그 측점 · 다른 측점선 · `button` = 그 몫(null) · 그 밖 빈 곳만 원지반 띠. 띠를 넓혀도 측점선 · ▲▼ 단추를 못 뺏음.
- ▲▼ 단추 = 그래프 위 · 아래 끝 2 px(21×17 · `B05_Profile_UI_Style_Table.css:412-447`) — 원지반이 끝에 붙은 곳만 띠가 닿고 단추가 이김.
- 끌기 이동 = 6 px 넘게 움직이면 뒤따르는 click 을 삼킴(`B05_Profile_UI_Profile_Wheel.ts:31` · `:157-163`) · 누른 채 움직임 = 표시 안 함(`CrossModal.ts:119`). 구조물 측점선 끌기 = 측점선 몫.
- 도구([직선화] · [쉬프트] · [자유지정]) 중 = 누름 · 표시 모두 도구 몫(`Render.ts:314-329` · `:496`). 우클릭 메뉴 = 따로(contextmenu).
- 계획선 자체엔 누름 동작 없음 — 원지반 · 계획선이 붙은 곳에서 띠가 계획선을 덮어도 뺏는 것 없음.
- 빈 곳 누름 = 고름 안 풂(`Render.ts:309`) — 넓혀서 잃는 동작 없음. 다만 빈 곳 누름이 창을 여는 자리가 늘어남.
넓힐 값 갈래:
| 값 | 띠 | 그래프 최소 높이 100 px(`B05_Profile_UI_Profile_Heights.ts:17`)에서 | 판단 |
| --- | --- | --- | --- |
| ±12 px | 24 px | 약 1/4 | 측점선 폭(14)보다 넓음 · 추천 |
| ±16 px | 32 px | 약 1/3 | 낮은 그래프에선 위 ▲ · 아래 ▼ 줄 근처까지 닿음(단추가 이김) |
### 고칠 자리
- `logic/B05_Profile_Profile_CrossPick.ts:10` 값 하나 + 머리 설명(`:6`) 숫자. 화면 파일 손 안 댐.
- 팝아웃 종단(`A00_Common/design/design_profile_popout.ts:64-116`) = 같은 DOM 조각을 새 창으로 옮길 뿐 · 같은 `renderProfile` 이 그림 → 같은 함수 · 같은 값(따로 고칠 것 없음 · 490 검증도 팝아웃 같음 확인).
### 시험
- `resources/tester/test_490_1_cross_pick.py` 고침: `:76` `slack == 8` → 새 값 · `:42` 손으로 적은 8 → 새 값(또는 결과 `slack` 을 받아 씀) · `:45-55` 경계 경우(77.8 · 78.3 · 61.3)를 새 값 끝 · 바깥으로 · `:79` 기대 목록 · `:3` 설명 숫자. 도우미 `helper_490_1_cross_pick.cjs` 는 그대로.
- 같이: `test_368_3_cross_modal.py`(글 확인만 · 안 바뀜).
- 화면(ORCA): 원지반선 ±(새 값−1) px = 원 · 글 · 누름 = 같은 m · ±(새 값+1) px = 없음 · 측점선 · ▲▼ 위 = 없음 · 끌기 뒤 창 안 뜸 · 팝아웃 · 밝음 · 어두움.
### 위험
- 낮은 그래프 · 원지반이 위아래 끝에 붙은 곳에서 띠와 단추 줄이 겹침(단추가 이기므로 동작 흠은 아님 · 표시 원 · 글이 단추 옆에 뜰 수).
- 덤(이번 고침 밖 · 491-1 남은 흠): 고른(빨강) 추가 측점 선은 `--plus` 클래스가 빠져(`B06_Section_UI_Longitudinal.ts:459`) `CrossModal.ts:60` 이 추가 측점으로 못 알아봄 — 같은 함수 자리.
## 3. 임시 횡단 창 단추를 제목 줄로
### 지금
- DOM = `B05_Profile_UI_Profile_CrossModal.ts:144-155` — 제목 줄 `.b05-crossmodal__head`(「임시 횡단」 · 측점 글) · 그림 칸 · 바닥 글 · **맨 아래 따로 단추 줄** `.b05-crossmodal__actions`([측점 추가] · [삭제] · [닫기] · 오른쪽 맞춤).
- 공용 모달(`ui-modal` · 바탕 막) 아님 — 자체 `section role=dialog` · 종단 칸 오른쪽 위 겹침(absolute · z 8 · `B05_Profile_UI_Profile_CrossModal.css:2-17`) · 창 안 누름은 그래프로 안 샘(`CrossModal.ts:163-165`).
- CSS: 제목 줄 flex · baseline · 틈 8(`CrossModal.css:23-27`) · 단추 줄 flex-end(`:48-52`).
- 단추 클래스 `ui-button` 은 어디에도 정의 없음(공용은 `ui-btn` · `ui_template/ui_template_elements_styles.ts:17-80`) → 지금 브라우저 기본 단추 꼴(테마 color-scheme 으로 밝음 · 어두움만 따라감).
### 원인
- 단추 줄이 바닥 글 아래 따로 있어 창 높이 한 줄 더 먹고 제목과 떨어짐.
### 고칠 자리
- `CrossModal.ts:151-155` 단추 묶음을 제목 줄 안(측점 글 뒤)으로 옮김 · 아래 따로 줄 걷음. 단추 찾기(`:160-161` · `:268`)는 클래스 · data-act 로 찾으므로 그대로.
- `CrossModal.css` — 제목 줄 `align-items: center` · 단추 묶음 `margin-left: auto`(오른쪽 맞춤) · 좁은 창 대비 줄바꿈 허용. [삭제](추가 측점일 때 [측점 추가] 대신)도 같은 줄로 따라감.
- 폭: 창 최대 380 px — 「임시 횡단」 + 「No.12+5」 + 단추 둘 ≈ 250 px · 한 줄에 들어감.
- 덤 제안: 제목 줄 높이에 맞게 `ui-btn ui-btn--ghost ui-btn--sm`(공용 작은 단추 · 두 테마 대비 확인된 꼴)로 바꿈 — 사용자 요청 밖이라 한 줄 결정.
### 시험
- 새 시험 한 개(또는 `test_368_3_cross_modal.py` 에 한 줄): 단추 묶음이 `.b05-crossmodal__head` 안 · 아래 따로 단추 줄 없음. 지금 단추 자리를 보는 시험 없음(`test_368_3` · `test_490_1` 은 다른 글만 봄).
- 화면(ORCA): 원지반 누름 → 창 제목 줄 오른쪽 끝 [측점 추가] · [닫기] · 추가 측점 누름 = [삭제] · [닫기] · 누름 동작 그대로 · 좁은 종단 칸 · 팝아웃 · 밝음 · 어두움 사진.
### 위험
- 작음. 공용 꼴로 바꾸면 단추 크기 · 색이 바뀜(덤 제안을 안 하면 꼴 그대로).
## 사용자 결정 필요
1. 마커 고름이 **빈 곳 · Esc** 로 풀릴 때 오른쪽 판 횡단도도 같이 비울지(1장 B) — 비움(추천 · 「어긋난 상태 금지」와 맞음 · 409 「판 그대로」 걷음) / 지금처럼 판 남김.
2. 임시 횡단 띠 넓이 — ±12 px(추천) / ±16 px.