diff --git a/docs/raw/조사/2026-10-15_325_유토곡선_배수유역_창분리.md b/docs/raw/조사/2026-10-15_325_유토곡선_배수유역_창분리.md
new file mode 100644
index 000000000..0a5ed5811
--- /dev/null
+++ b/docs/raw/조사/2026-10-15_325_유토곡선_배수유역_창분리.md
@@ -0,0 +1,47 @@
+# 325 유토곡선 · 배수유역 창분리 (요구 224-1)
+
+기준 = 837fe3d64. B05 · B06 종단도는 한 벌(`B05_Profile_UI_Profile_Panel.ts:30` · `B06_Section_UI_Section_View_Profile.ts:277` → `sharedProfile`) — 한 번 고치면 두 보기에 같이.
+
+## 지금 — 종단 [창 분할]
+
+- 단추: `A00_Common/design/design_shared.ts:153,158` 빈 button → `toolLead` · `:181` `attachProfilePopout`(`design_profile_popout.ts:25-29`) · 글 「창 분할」/「창 결합」 · 툴팁 `title` · 클래스 `ui-btn ui-btn--ghost b05-route-profile__popout`.
+- 열기: `window.open("", "aislo-profile-popout", "popup,width=1400,height=720")`(`:52`) · 막히면 토스트(`:54`) · 스타일 · html 속성 · body 배경 열 때 한 번 베낌(`:61-70`) · 종단 뿌리 DOM 을 새 창 body 로 옮김(`:71-73`).
+- 동기화: 따로 없음 — 코드 · 가게 · sessionStorage 는 메인 것 · 같은 함수가 다른 창 DOM 을 그림(BroadcastChannel · postMessage 안 씀) → 고름 · 커서 · 확대 · 스크롤 저절로 맞물림.
+- 이벤트: 새 창 keydown/keyup/pointerdown/pointerup/pointercancel · blur 를 메인 document 로 다시 쏨(`:14,76-90`) · 새 창 ResizeObserver → `profile.redraw`(`:93-96`).
+- 되돌림: 새 창 pagehide → `slot.append` + 다시 그림(`:31-38,97`) · 메인 pagehide · 페이지 떠남(`design_page.ts:263` → `profile.dispose`) → 새 창 닫고 되돌림(`:39-49`).
+- 화면 틀 · 시계 = 요소가 놓인 창 것(`common_util_view_window.ts:11-18` · 230-1/2) · 창 자리 · 크기 기억 없음(매번 1400×720) · 테마는 열 때만 베낌 · CSS `design_page.css:43-56`.
+
+## 종단 안 보조 판 셋
+
+| 판 | 만드는 곳 | 그리는 곳 | 접기 · 크기 |
+|---|---|---|---|
+| 표 | `design_profile_overlays.ts:61-63` · `_Profile_TableOverlay.ts` | `_Profile_Render.ts:173-199,409-411` | 손잡이 `table-open` · `table-height` |
+| 유토곡선 | overlays `:48-59` → `B05_Profile_UI_Profile_MassHaul.ts:190`(손잡이 200-204 · 판 208-249) | `_Profile_Render.ts:415-458` → `MassHaul.draw :360-489` | `masshaul-open`(:220,264-279) · 높이 `masshaul-height`(:224-242) · 범례 `masshaul-visible`(:281-286) |
+| 배수유역 | `design_profile.ts:181-199` `drainageSide`(`_Profile_SideTool.ts:11`) → `B05_Profile_UI_Drainage_Side.ts:26` → `_Drainage_Panel.ts:79` · 뼈대 `_Drainage_Chrome.ts:54-149` | 자체 캔버스 `_Drainage_Panel.ts:235-316` | `drainage-collapsed`(:556-567) · 폭 `drainage-width`(Chrome :139-147) |
+
+- 배치: 뿌리 = [「종단」 손잡이][높이 끌기][content](`design_profile.ts:207`) · content = [bodyWrap][배수 판](`:198-199`) · 유토 · 표 판은 bodyWrap 바닥에 absolute 로 겹침(`_Style_MassHaul.css:54-66,168-180`) · 높이 나눔 `_Profile_Heights.ts:66-179` · 유토는 종단 본문 크기 0 이면 건너뜀(`_Profile_Render.ts:125`).
+
+## 종단 그래프 좌측 상단 단추 줄
+
+- DOM: `.b05-route-profile__balance`(28px · `_Style.css:533-547`) 맨 앞 `span.b05-route-profile__tools` 안 [창 분할] + `button.b05-route-profile__tool` 8개(초기화 ↶ ↷ 직선화 자유지정 쉬프트 ▲ ▼).
+- 만드는 코드: `tools.render()`(`_Profile_Tools.ts:161-232` · 단추 함수 69-80) → `withToolLead`(`design_profile.ts:296-299`) → `renderBalanceBar`(`_Profile_Balance.ts:48-51`) · 그릴 때마다 새로.
+- 양식: 글자 단추 · `title` 툴팁 · `--btn-bar-padding` · `--btn-bar-font-size`(`ui_template_theme.css:169-170` · 높이 16 · 글 12) · [창 분할] = ghost + `design_page.css:53-56`.
+
+## 뗄 때 손볼 곳
+
+1. 높이 나눔 · 끌기가 「`overlay.hidden` 거짓 = 열림」만 봄(`_Profile_Heights.ts:70` · `_Profile_Drag.ts:146,172,194`) → 떼어도 메인이 자리 비움 · 높이 끌기 놓으면 새 창 높이가 저장 높이로 굳음 → 「열림 && 뿌리 안」.
+2. 유토 손잡이가 메인에 남음 → 뗀 동안 숨김.
+3. 「같이 줄이기」 인라인 높이(`_Profile_Heights.ts:147`)가 새 창에 남음 → 뗄 때 비움.
+4. 유토 세로 범위가 메인 보이는 구간을 따름(`_Profile_Render.ts:441,453-458`) → 새 창이 넓으면 잘림 → 뗀 동안 `viewRange` 안 씀(`MassHaul.ts:441`).
+5. 배수 지도 크기 관찰 = 메인 창 ResizeObserver(`_Drainage_Parts.ts:468`) → 놓인 창 것으로 바꿔 끼우는 `rehome`(직전 크기 지켜 배율 유지).
+6. 방향키 ←→ 는 마지막 누름이 `.b05-route-profile` 안일 때만(`B06_Section_UI_Cross_Panel_Base.ts:92`) → 선택자에 두 판 클래스.
+7. 종단 새 창 안에서 다시 떼면 메인 `window.open` 이 팝업 차단될 수 있음 → 단추가 놓인 창으로 엶.
+8. 창 이름은 조각마다 달리(`aislo-masshaul-popout` · `aislo-drainage-popout`) — 같으면 앞 창 body 를 덮어 조각이 사라짐.
+9. 한계: 메인 종단 패널 접으면 뗀 유토 멈춤 · 유토 창으로 메인을 다 가리면 메인 화면 틀이 멈춰 늦음 · 토스트 · 좌측 폼은 메인 창.
+
+700줄: `design_profile.ts` 700(시험 고정) · `design_view_profile.ts` 700 → 손대지 않음 · `_Drainage_Panel.ts` 700 → 한 줄 더하면 주석 합쳐 700. 다른 224 일감과 겹치는 파일 없음.
+
+## 시험
+
+- 있음: `test_282_1_profile_toolbar.py`(+ helper · 가짜 require 가 `showToast` 만 → popout 이 `viewOf` 를 가져오면 helper 도 고치거나 `button.ownerDocument?.defaultView ?? window`) · `test_187_1_b05_head_button_size.py`(`.b05-route-profile__tools > .b05-route-profile__popout {` 블록 정규식 → 새 단추 규칙은 따로) · `test_230_1_popout_wheel.py` · `test_230_2_popout_frames.py`(`POPOUT_FILES` 메인 창 rAF/setTimeout 금지) · `test_219_1_arrow_owner.py` · `test_design_profile_single.py` · `test_b05_drainage_side.py`(배수 파일은 `UI_Profile_` · `B06_Section` import 금지 · ≤700) · `test_281_1_drainage_zoom_range.py`.
+- 새로: `test_224_1a_drainage_rehome.py`(+ cjs) · `test_224_1b_masshaul_docked.py`(+ cjs) · `test_224_1c_subpanel_popout.py`(+ cjs · 단추 자리 · 글 · 클래스 · 창 이름 셋 다름 · 열림/닫힘/dispose 제자리 · 손잡이 숨김/보임).
diff --git a/docs/raw/조사/2026-10-15_326_측점스냅.md b/docs/raw/조사/2026-10-15_326_측점스냅.md
new file mode 100644
index 000000000..633ba2d57
--- /dev/null
+++ b/docs/raw/조사/2026-10-15_326_측점스냅.md
@@ -0,0 +1,35 @@
+# 326 계획노선 편집 — 측점 스냅 (요구 224-2)
+
+기준 = 837fe3d64.
+
+## 해석
+
+- 「호의 사이」 = BC~EC 누가거리 구간(서버는 곡선표 BC · EC 를 노선에 내려 구함 `B05_Profile/B05_Profile_Engine_Sections_Core.py:241-275` · 5 m 밖 곡선 버림 `:221`).
+- 「호의 중심」 = 원 중심(노선 밖)이 아니라 곡선 중점 MC(IP 에서 내각 2등분선으로 외할장 SL · 누가거리로 BC~EC 한가운데 · `resources/knowledge/original/산림과임업기술(임도)/2. 임도/5. 설계.md:191`).
+- 근거: 곡선부 BC · MC · EC 보조측점(`…/4. 노선측량.md:144`) · 「측점간의 번호는 20m이내에서 조정」(`:143`) · 교재 예시 표(`5. 설계.md:182-191`).
+- 후보: (가) 호 안 20 m 측점을 MC 로 옮김 — 채택 · (나) 편집 손잡이를 IP 대신 MC 에(「측점」 말과 안 맞음) · (다) 노선을 움직여 MC 를 격자에(곡선 수십 곳 동시 불가).
+- 실제 어긋남(저장소 자료 셈): 1078 m · 곡선 26 · R 12~199 → 호 안 측점 14곳 · MC 와 최대 11.3 m(호 616.1~646.5 · MC 631.3 에 620 · 640 둘) · 1153 m · 곡선 50(R 12 · CL ≤ 5.6) → 호 안 11곳(2.7 m 안) · 측점 없는 호 39곳. 측점 = 노선 모양과 상관없는 20 m 격자라 어떤 조작이든 같음.
+
+## 지금
+
+- 편집 창: `A00_Common/design/design_view_profile.ts:278-286` → `openRouteEditModal`(`B05_Profile/B05_Profile_UI_RouteEdit.ts:104-110`) · 단추 줄 `_RouteEdit_Chrome.ts:51-78`([노선 추천] | 분석 지형 · [1m 등고선] · [거리 재기] | 되돌리기 · 다시하기 · 초기화 · 취소 · 확인) · 켬끔 본보기 = [1m 등고선](`aria-pressed` + `is-active` `:63-65` · 배선 `_RouteEdit_Contour.ts:136-148`).
+- 조작: 노드 끌기 `_RouteEdit_Pointer.ts:265-294` · 곡선 손잡이 BC · EC `:241-263` + `_RouteEdit_Curve.ts:89-109` · R · 곡선 길이 · 고정 `_RouteEdit_CurveBar.ts:61-116` · 두 번 = 노드 더함 · 오른쪽 = 지움(`RouteEdit.ts:494-512`) · 측점 눈금 누름 = 횡단 미리보기(`_Pointer.ts:186-201`) · 추천 [적용] `_RouteEdit_Initial.ts:132-160` → `RouteEdit.ts:428-434` · 기본 노선 `_ensure_planned_initial`(`B05_Profile_Router_Replan.py:339-379`) · [초기화] `_Initial.ts:164-197` · `Router_Replan.py:282-297`.
+- 계산: 편집 중 TS 짝 `buildEditedPolyline`(`_Curve.ts:226-327`) · [확인] 서버 `replan`(`Router_Replan.py:580-669`) 전 체인.
+- 곡선 자료: apex · radius_m · tangent_m · inner_angle_deg · start(BC) · end(EC)(서버 `common_util/common_util_route_polyline.py:104-141` · TS `_Curve.ts:32-42`) · 원 중심 셈만(`:130-152`) · MC 셈 코드 없음.
+- 측점 정본 `generate_sections`: 20 m 격자 `_chainages`(`_Sections_Core.py:86-92,333`) · 관 · 구조물 0.005 m 안이면 격자에 얹음(`:338-357`) · 종류 bp/ep/irregular/regular(`:411-424`) · 곡선표는 확폭에만(`:368-385`) · BC · MC · EC 측점 안 심음 · 부르는 곳 둘 = `run_section_generation`(`B05_Profile_Engine_Sections.py:392-438`) · `sample_structure_sections`(`:513-551`) · 곡선표 읽기 `_load_design_curves`(`:71-96`).
+- 편집 창 눈금: `drawStationTicks`(`B04_PreProcess_UI_MapOverlays.ts:225-260`) ← `_RouteEdit_Render.ts:461-482` · 같은 격자 따로 셈 = 누름 `_Input.ts:368-407` · 기울기 라벨 `_Slope.ts:54-60` · 종단 띠 `_ProfileStrip.ts:26-60` · 횡단 미리보기 서버 = 가장 가까운 격자(`Router_Terrain.py:172-184`).
+- 측점 한 벌(272): `plantedChainages`(`common_util_structure_station.ts:235-242`) ↔ `resolve_extra_stations`(`Engine_Sections.py:250-275`) · `oneStationList`(`ts:254-274`) ↔ `_merge_irregular_into_longitudinal`(`Router_Confirm.py:295-343`) · `followMovedNodes`(`ts:276-298`) ↔ `_follow_moved_stations`(`Router_Confirm.py:204-292`) · 거울 시험 `test_272_1_station_list.py` · 정수 m = 구조물 · 관만 floor(x+0.5)(`common_util_structure_station.py:38-45` · `.ts:51-60`).
+
+## 구현 자리 (짝 ① · 조작 중 TS · [확인] 서버 정본)
+
+0. 쪼개기: `Router_Replan.py` 698줄 → 요청 모델 다섯(`:72-138`)을 `B05_Profile_Schema.py` 로(Replan 에서 다시 import · `replan.RouteInitialRequest` 그대로).
+1. 짝 셈: `_Sections_Core.py` `snap_curve_stations(stations, spans)`(호 안 BC+0.005 < s < EC−0.005 측점 중 MC 에 가장 가까운 하나 → floor(MC+0.5) · 같은 m 하나로) · `generate_sections(..., station_snap=False)`(spans 셈을 앞으로 · 격자 붙임은 스냅 뒤 목록) ↔ `_RouteEdit_Frame.ts`(77줄 · Sections_Core 짝 · `snapCurveStations` · `curveSpans` · `stationsAlong` · import 없이).
+2. 저장: 곡선표 `planned_route_curves.json` 에 `"station_snap"`([확인] 때만 · 작업본 · 스냅샷이 route 폴더째 복사 `common_util_initial_snapshot.py:39`) · 없으면 끔 · DB 옵션에 안 둠(`SectionGenerationOptions` 다시 짓는 곳 여섯) · `Router_Replan.py` 요청 칸 → `_write_planned_polyline`(`:300-336`) · GET `/route/plan` 돌려줌(`:558-577`) · 실패 되돌림(`:607-639`)에 `_curves.json` · `_nodes.csv` 도(기존 틈) · `Engine_Sections.py` `load_station_snap` → `run_section_generation` · `sample_structure_sections` 둘 다(한쪽만이면 [저장] 때 640 되살아남) · `Router_Terrain.py` 횡단 미리보기 `station_snap` + `design_curves=built.curves`.
+3. 편집 창: `_RouteEdit_Chrome.ts` [측점 스냅](1m 등고선 양식 · [거리 재기] 옆) · `RouteEdit.ts`(635줄) 상태 = 메모리 모드(되돌리기 밖) · 열 때 GET 값 · `stations()` 한 곳 → 그리기 · 누름 · 기울기 · 횡단 요청 · [확인] `changed` + `station_snap` · 함께 `_RouteEdit_Render.ts` · `B04_PreProcess_UI_MapOverlays.ts`(`chainages` 선택 칸) · `_Input.ts`(`stationAtScreen` 선택 칸 · 라벨 판정 값으로) · `_Pointer.ts` · `_Slope.ts` · `_Apply.ts` · `B05_Profile_Api_Replan.ts` · 선택 칸은 뒤에(B04 지도 · 배수유역도 · `helper_b05_routeedit_pick.cjs` 그대로).
+
+영향: B05 종단 · 3D · B06 · B07(`Router_Support_Basin.py:280-290`) · B08 = 서버 측점 목록 따라감 · 옮긴 측점의 B06 사용자 입력은 누가거리 정확 맞춤 이월이라 안 넘어감(`B03_FileInput_Service_Chain.py:574-586`) · 추천 비교값 · 종단 띠 균형 종단 = 20 m 격자 그대로 · B04 지도 · 배수유역도 눈금 격자 그대로(`_Drainage_Panel.ts` 700줄) · `common_util_route_polyline.py`(745줄) 손대지 않음.
+
+## 시험
+
+- 새로: `test_224_2_station_snap.py`(tsc 컴파일해 파이썬과 견줌 · 곡선 없음 · 측점 없는 호 · 하나 · 둘 · BC 위 · .5 올림 · 이웃 격자 같은 m · `generate_sections` 끔 = 지금과 같음 · 켬 = 640 없음 · 631 있음 · 631 에 관이면 `grid`).
+- 있음: `test_74_route_confirm_initial` · `test_route_initial` · `test_64_1_profile_toggle` · `test_b05_routeedit_frame_mirror` · `test_b05_routeedit_pick` · `test_b05_routeedit_chrome_modal` · `test_b05_routeedit_cross_width` · `test_272_1_station_list` · `test_272_2_station_sync` · `test_238_1_save_keeps_structure_stations` · `test_273_2_preview_stations` · `test_b05_extra_stations_supplier` · `test_regeneration_extra_stations` · `test_b05_station_roundtrip` · `test_b06_curve_widening` · `test_75_profile_strip`.
diff --git a/docs/raw/조사/2026-10-15_327-328_추천노선_저장_큰R.md b/docs/raw/조사/2026-10-15_327-328_추천노선_저장_큰R.md
new file mode 100644
index 000000000..86771733b
--- /dev/null
+++ b/docs/raw/조사/2026-10-15_327-328_추천노선_저장_큰R.md
@@ -0,0 +1,53 @@
+# 327 · 328 계획노선 추천 — 영구 저장 · 큰 R (요구 224-3 · 224-4)
+
+기준 = 837fe3d64. 경로는 저장소 뿌리 기준.
+
+## 지금 — 추천 계산 길
+
+- 화면: 편집 창 [노선 추천] → 모달 `B05_Profile/B05_Profile_UI_RouteEdit_Initial.ts:132-162` · 알약 다섯(예상노선 추적 · 영선 · 토공 최소 · 기울기 최소 · 비중 · `_Recommend.ts:66-74`) · 열면 지금 갈래 한 번 셈 `:552-554` · 알약 = `run() :423-480` · 서버 갈래 여덟 중 safe · crossings · cost 숨김(`_Initial_Model.ts:439`) · [적용] = 편집값 + sessionStorage `routeedit-initial`(`_Initial.ts:151-159` · `_Initial_Model.ts:561-564`) · 서버 안 씀 · 편집 창 [확인] → `POST /route/replan`(`_RouteEdit_Apply.ts:86-101`) → 작업본 `planned_route.csv` · `_nodes.csv` · `_curves.json` + `B05_Profile/route/initial_choice.json`(갈래 이름) + initial_snapshot 다시 뜸(`B05_Profile_Router_Replan.py:607-663`).
+- API: `POST /api/projects/{id}/route/initial`(`B05_Profile_Router_RouteInitial.py:27-126` · 화면 대기 5분 `B05_Profile_Api_RouteInitial.ts:15`) · 입력 = 예상노선 `B05_Profile/route/expected_route.csv`(없으면 initial_snapshot) `:59-61` · 최소 R `_plan_criteria :67` · 종단 상한 `:68-70` · 제약 칸(기울기 · 최소 R · 계류 이격 100 · 지반 고름 5 · 영선 목표) `:72-79` · 지형(확정 지표면 + 분석 지형) `:80-83` → 비용면 캐시 `B05_Profile_Engine_Solver.py:214-257` · 계류 `도엽_하천중심선.geojson` 소하천 이상(`Engine_RouteInitial.py:87-137`) · 관 지점 · mode · weights(50/25/25) · with_profile · design_speed.
+- 차례(`generate_initial_route` `Engine_RouteInitial.py:212-350`): ① 예상노선 추적 = 전처리 초기값 규칙(0.4 초) ② 나머지 = 후보 묶음(입력 지문 `_pool_key :361-372` · 메모리 4벌 `_POOLS :108-110`) — 격자 탐색(예상노선 ±100 m 복도 · 4 m 칸 · 16방향 Dijkstra · `Search.py:236-301`) · 비용(이격 · 토공 · 기울기² · 영선 · 위험 · 계류 · 길이 · 꺾임 R·θ) · 후보(목표 7 × 상한 3단 + 쓸기 11 · `Pool.py:50-213`) · `_trace Pool.py:99-114` → `route_metrics Metrics.py:115-293` · 균형 종단(후보 전부 대충 → 고른 하나 바로 · ±60 m 횡단 · 계획고 ±6 m · B06 `compute_cross_design` · `Profile.py:66-402`).
+- 1분 까닭: 같은 입력 첫 셈 35~80 초 · 같은 묶음 다른 갈래 3~5 초 · 묶음 짜기 1.9~2.7 초 · 대부분 = 후보 전부 균형 종단(63-4 때 1.8 → 18.3 초) · 메모리만이라 재시작 · 리로드 · 입력 바뀜마다 다시.
+- 저장 없음: 남는 것 = 메모리 `_POOLS` · sessionStorage 한 벌 · `initial_choice.json`. 머리글 「파일 안 씀」 = `Engine_RouteInitial.py:6` · `Router_RouteInitial.py:33` · `Router_Replan.py:29` · `Profile.py:4-5`.
+
+## 327 저장 설계
+
+- 키: 시점 · 종점만으론 부족(추천은 예상노선 전체 씀) · 기존 `_pool_key`(지형 · 예상노선 · 계류 · 상한 · 최소 R · 이격 · 고름 · 목표 기울기 내용 해시) + mode · weights · with_profile · design_speed · `ALGO_VERSION`(코드 판 · 선례 `B04_PreProcess_Router_Watershed.py:82-87,227-242` `RESPONSE_SCHEMA_VERSION`) · 관 지점은 뺌([확인] 때마다 다시 냄 · 덮개 수는 꺼낼 때 다시 셈 `Profile.py:432-451`).
+
+| 자리 | 판단 |
+|---|---|
+| `B05_Profile/route_recommend/<키>.json` | 씀 — initial_snapshot 대상(`common_util_initial_snapshot.py:38-43`) 밖이라 [초기화](`:154-159,260-274`)에 안 휩쓸림 · 자료 교체(`common_util_project_reset.py:23-54`) · 프로젝트 삭제 때 같이 지워짐 |
+| `B05_Profile/route/` | 안 됨 — [초기화]가 되돌림 |
+| initial_snapshot | 안 됨 — 쓰는 때 정해짐 · [확인]마다 통째 지움(`Router_RouteInitial.py:284` · `B03_FileInput_Service_Chain.py:449`) |
+| DB 표 | 안 씀 — 마이그레이션 · 정리 목록까지 |
+
+- 층 = 「서버 계산 캐시」(데이터 3층 밖 · 선례 비용면 npz `Solver.py:169-257` · 코리도 사전 생성 · 배수유역 응답) · 사용자 편집값 아님 → 자동저장 금지와 안 부딪힘.
+- 꼴: 키마다 파일 하나 `{version, key, pool, default, created_at, request, result}` · 있으면 셈 안 함 · 없으면 셈해 더함(덮어쓰기 없음) · `default` = 빈 제약 칸 + 50/25/25 + 설계 지형 · 이 표시는 안 지움 · 한 벌 10~30 KB.
+- 무효화: 지형 · 예상노선 · 계류 · 설정 바뀜 = 다른 키(옛 파일 남김) · 코드 바뀜 = `ALGO_VERSION` 올림 · 자료 교체 = 폴더째.
+- 미리 셈: 체인 끝(`Service_Chain.py:225-227` 예상노선 · `:343-351` 스냅샷 뒤)에서 예약만(`_schedule_background_task` `B03_FileInput_Router_Helpers.py:279-299` · 체인이 [초기화] 요청 안에서도 불림 `B05_Profile_Router_Lifecycle.py:246`) · 다섯 알약 ≈ CPU 1~1.5 분 · 진행 중 같은 키 = 기다림.
+
+## 328 큰 R
+
+- R 정하는 자리: `common_util/common_util_route_polyline.py:670-717`(접선 자리 = 앞뒤 직선 짧은 쪽 절반 `:673` · 기본 최소 `:682` · 못박은 R `:683-685` · R 맞춤 `_fit_radius_m :358-391,686-697` = 최소 이상에서 예정노선과 평균 거리 최소 · 단순화 갈래에서만 · 같은 방향 꺾임 묶기 `:449-576`) · 추천은 예상노선 추적만 맞춤 · 나머지 `_trace` = 편집 갈래 → R = 최소 → **추천 곡선 전부 12 m**(자리 모자라면 더 작게 + 위반 `:698-716`).
+- 12 m 출처: 설계속도 20 · 특수지형 `config/config_system_design.py:521-525` ← `B05_Profile_Engine_Grade.py:142-151` ← `Router_Replan.py:188-226`.
+- 제약: 이웃 접선 겹침(절반씩) · 탐색 경로 이탈(외할 E = R(sec(IA/2)−1)) · 종단경사 · 계류(맞춤 뒤 다시 잼 · 위반 늘면 안 됨) · 토공(R 크면 확폭 줄어 토공 줄음 `Profile.py:127-151`) · 곡선 길이 하한(법정 없음 · 표시 5 m `config_system_design.py:548-557`).
+- 방안 A(1단 · 「현재 노선 로직」 재사용): `_trace` 에서 탐색 경로를 예정노선처럼 폴리라인화 `build_planned_polyline(densify_route(nodes, 2 m), min_radius_m)` → 같은 단순화 · 묶음 · R 맞춤 · 최소 R 판보다 위반 많으면 최소 R 판 · 응답 꼴 같아 화면 `flattenServerPlan`(`_RouteEdit_Edits.ts:232-272`) 그대로 · +5~12 초.
+- 방안 B(2단 · 「클수록」): A 곡선마다 R 을 [맞춘 R, 접선 자리 ÷ tan(IA/2)] 안에서 탐색 경로와 최대 거리 ≤ 허용 이탈인 가장 큰 값 → 못박아(`simplify=False · radii`) 다시 짬(브라우저 짝 있음) · +1~3 초 · 허용 이탈 값은 사용자 확정.
+- 방안 C(탐색 자체 큰 R): 층 수 ∝ R · 1.5~2.5배 느림 · 안 씀.
+- 붙일 곳 `_trace` 한 곳 → 영선 · 토공 최소 · 기울기 최소 · 비중(+숨긴 셋) · 예상노선 추적은 그대로(`test_route_initial.py:102` · `Router_Replan.py:300-336` · 09-07 확정 「예정노선에 가장 가까운 R」).
+- 손대지 않음: `common_util_route_polyline.py`(745줄 · TS 짝) · `Router_Replan.py`(698줄). 설명 정정: `B05_Profile_UI_RouteEdit_Recommend_Help.ts:32` 추적 「최소 R」 → 「맞춘 R」.
+
+## 지식DB 근거 (값은 안 정함)
+
+- 법정 최소곡선반지름(별표2 Ⅰ.2.다): 설계속도 40 → 60/40 · 30 → 30/20 · 20 → 15/12(일반/특수) · 중심선 기준 · 내각 155° 이상 곡선 생략 · 배향곡선 10 m — `resources/knowledge/original/법률/산림자원의 조성 및 관리에 관한 법률 시행규칙/별표/별표2_…(제5조제1항 관련).md:151-165`(산불진화임도 `:436-450`).
+- 정리본 `resources/knowledge/technical_info/01_임도/02_상세설계/평면선형.md:17-72`(작업임도 규정 없음 · 확폭 · 45 m 이상 확폭 불요 · 곡선반지름 확보 우선).
+- 「곡선반지름은 … 가급적 큰 것이 좋음」 — `original/임도기술교본/1. 교본의 개요/3. 용어해설.md:95` · 확보 우선 `3. 임도 계획과 설계/3. 실시설계.md:229,273`.
+- 산식 · 표 `original/산림과임업기술(임도)/2. 임도/2. 임도구조.md:65-106`(R = ℓ²/4B · 20 m 목재 · 노폭 4 m → 25 m · R = V²/127(i+f) · 20 km/h 14~16 · 30 → 31~35 · 40 → 55~63) · 곡선부 종단 G − 80/R > S `:221-226` · K = R/I ≥ 3.0 `:235-248` · 예각 내각은 R 크게 못 둠 `:250`.
+- 25 m 이하 = 25톤 대형차 곤란 · 구조개량 대상 — `original/행정규칙/임도설치 및 관리 등에 관한 규정/별표/별표3_임도구조 개량사업(제17조 관련).md:25,55,77`.
+- 곡선 요소 식(TL · CL · SL · M · 접선 겹침) — `technical_info/01_임도/03_계산정보/곡선설치_계산.md:19-34`.
+
+## 시험
+
+- 추천: `test_route_initial.py` · `test_63_4_route_profile.py` · `test_77_recommend_widening.py` · `test_78_recommend_mass.py` · 화면 `test_b05_routeedit_initial.py`(+ cjs) · `test_b05_routeedit_initial_roundtrip.py`(+ cjs).
+- R: `test_route_polyline*.py`(curve_fit · edit · apex_roundtrip · idempotent · handle_drag) · `test_user_values_survive.py` · `test_plan_curve_limits.py` · `test_b05_curve_radius_floor.py` · `test_initial_snapshot.py`.
+- 새로: `test_224_3_recommend_store.py`(같은 입력 같은 키 · 하나만 바뀌어도 다른 키 · 두 번째 셈 0회 · 덮어쓰기 없음 · 스냅샷 복원 뒤 남음) · `test_224_4_recommend_radius.py`(R ≥ 최소 · 하나 이상 큼 · 위반 ≤ 최소 R 판 · 추적 그대로 · (B) 이탈 ≤ 허용).
diff --git a/docs/raw/조사/2026-10-15_329-330_외부일위대가_보기창_근거맞춤.md b/docs/raw/조사/2026-10-15_329-330_외부일위대가_보기창_근거맞춤.md
new file mode 100644
index 000000000..7bc52d850
--- /dev/null
+++ b/docs/raw/조사/2026-10-15_329-330_외부일위대가_보기창_근거맞춤.md
@@ -0,0 +1,52 @@
+# 329 · 330 외부 일위대가 — 엑셀 보기 창 · 단가산출 근거 맞춤 (요구 224-5 · 224-6)
+
+기준 = 9edaec82e(324 끝). M02 = `M02_MasterTemplete/`.
+
+## 지금
+
+- 올리기: `Hopyo_Side.ts:38-72` → Router `:201-216` → `_upload :174-179` → `Excel.read_book` → `Store.save :72-87` — 읽은 결과만 `실무자료/HP-######.json`(원본 엑셀 안 남김).
+- 본 화면: `Hopyo_View.ts:132-186` 호표 표 → `:207-268` 줄 표(11열) · 모달 = `choose() :359-394`(후보 5 + 다른 로직 찾기) 하나.
+- 불러오기 길 둘 · 둘 다 호표 하나씩: ① build `:414-425` → `POST hopyo-build`(Router `:291-302` → `Build.py:191-204`) ② [로직 추가] `Labor_Detail.ts:496-512` → `Hopyo_Pick.ts:18-58`.
+- 서버가 읽는 것: `Excel.py:69-97` 「일위대가」·「일위대가총괄표」 값만(xls formatting_info 없음 · xlsx read_only) → 호표 `{번, 이름, 규격, 단위, 금액}` · 줄 `{차례, 이름, 규격, 수량, 단위, 단가, 금액, 비고, 참조, 갈래}` · 검산. 격자 · 병합 · 열 폭 · 서식 · 행 번호 · 단가산출 시트 없음.
+- xlrd `formatting_info=True`(북부청 .xls 0.05 초): 병합 11 · 열 폭 15열 · 행 높이 · 숫자 형식 `#,##0` · 가로 맞춤 · 테두리 · 굵게/채움 없음. xlsx 병합 · 폭은 openpyxl 비 read_only(무거움) → xlsx 는 값만.
+- 크기: 북부청 일위대가 560행×15열(≈129 KB) · 단가산출 4,345행×6열(≈192 KB) · 거창 xlsx 단가산출 146열. 호표 행 범위 깔끔(제3호표 = 20–24행 · 68개 안 겹침).
+
+## 329 엑셀 보기 창 — 부품
+
+| 후보 | 근거 | 판단 |
+|---|---|---|
+| A00 산출근거 스프레드시트 `createSpreadsheet(host, book, {readOnly, tabs, onSelect})` | `A00_Common/spreadsheet/spreadsheet.ts:56-115` · Workbook `spreadsheet_types.ts:165-242`(병합 · 열 폭 · 행 높이 · 서식 · 틀고정 · 여러 시트) · M02 모달에서 읽기 전용 사용 중 `AreaCellPick.ts:49-118` | **씀** — 엑셀 모양 · 보이는 칸만 그림 · 늦게 불러옴 · 손잡이에 `select(범위)` 하나 더함(안쪽 `select :250-266` · `selectSpan` `spreadsheet_selection.ts:89` 있음) |
+| B08·B09 자료 표 `ui_template_data_table.ts` | focusRow · rowClass | 병합 · 열 글자 없음 |
+| 집계표 `createSheet` | 양식 문서용 | 안 맞음 |
+
+## 330 근거 맞춤 — 자료
+
+- 우리: 로직 1,469(돈 1,438) · 칸 = 키 · 원문번호 · 구분(산림품셈 · 건설품셈 공통/토목/건축/기계설비/유지관리) · 출처(「건설품셈 공통 3-2-1」) · 호표 줄 `{인력/재료/기계/로직, 요소 LB…, 단위, 수량식, 비목}` · 조항 묶음 `M02_Labor_Inputs.py:68-85` sources() · 인력 LB 378 · 기계 EQ 654.
+- 엑셀: 일위대가 줄 비고 「단산N참조」 → 단가산출 B열 머리 「N.이름[단위]」(북부청 268/268 · 사방 139/139 이어짐) · 블록 안 출처 글(「산림사업표준품셈 9-14…」 「[건설표준품셈(3-1-3)]」 · 오타 「건설표줌품셈」) · 직종 줄(「보통인부 : 172,068 × 0.1인」 · 글자 사이 빈칸) · 출처 있는 블록 북부청 90/95 · 사방 24/67 · 직종 있는 블록 69/95 · 48/67.
+- 지금 점수(`Match.py:297-325`): 두 글자 겹침 + 이름 같음 1 + 단위 0.15 + 산림 0.05 + 자재 규격 0.5 · 거름 `:307` 때문에 이름이 아주 다르면 후보에도 못 듦 · `_targets :215-256` 에 조항 · 직종 없음 · 북부청(기억 없음) 단가산출 268 = 같음 63 · 후보 200 · 없음 5.
+
+| 단산 | 엑셀 이름 · 근거 | 지금 1순위 | 근거 1순위 |
+|---|---|---|---|
+| 93 | 구조물터파기(인력100%)(토사) · 건설 3-2-1 · 특별인부 | 구조물터파기 육상토사 GF000101 | GC000083 굴착(인력/토사) |
+| 21·22·23 | 레미콘타설 · 건설 6-1-1 · 콘크리트공+보통인부 | 펌프카 타설 | GC000198 레디믹스트콘크리트 타설 |
+| 64 | 비탈면보호공(평떼) · 건설 4-1-1 · 조경공+보통인부 | 비탈면 보양 | GC000143 잔디붙임 |
+| 49 | 흙깍기(토사)(굴삭기1.0㎥) · 건설 3-2-3 | 없음(깍/깎) | GC000085 흙깎기(기계) 보통토사 |
+
+함정: ① 건설품셈 번호에 부문 없음(잔토처리 3-1-3 ↔ 우리 유지관리 철골재 철거) → 공통 · 토목 덤 · 직종 · 낱말 하나도 안 겹치면 버림 ② 품셈 판 다름(산림 5-23-3 ↔ 우리 5-22-3) → 조항 같음은 덤 점수만 · 자동 확정 안 씀 ③ 보통인부는 겹침에서 뺌 ④ 직종 없는 기계 로직은 부문 덤이 가름.
+
+## 위험 · 지킬 것
+
+- 줄 수: Match 530 · View 485 · Router 312 · Excel 269 · Build 204 · Api 190 · Side 128 · Pick 58 · spreadsheet.ts 363 · `Labor_Detail.ts` 693(시험 고정 · 손대지 않음 · 덧줄은 Hopyo_Pick).
+- 원본 읽기만: `resources/master_data/*.json` · `일위대가조합.json`(시험 master_hash).
+- 길 충돌: `/hopyo/{key}/{no:int}`(Router `:228`) → 격자 길은 `/hopyo-grid/{key}`.
+- `Match.py:464` 요약 `h[k]` → 옛 파일 KeyError → `.get`.
+- 격자는 따로 파일 `실무자료/격자/<키>.json`(HP json 에 넣으면 목록 · 고르기가 통째로 읽음) · 지우기 때 같이.
+- 이미 올린 파일 = 격자 · 근거 없음 → 다시 올려야 보임.
+- `A00 spreadsheet.ts` 공용(고쳐도 됨) · 계약 `spreadsheet_types.ts` 안 고침.
+- 기대값 바뀜: `test_m02_hopyo_match.py:106-108` 제3호표 구조물터파기 1순위 → 굴착(인력/토사)(의도).
+- STmate 꼴(「일위대가표」·「단가산출근거」 · 실무 xlsx 6개)은 지금 「시트 없음」 400 — 이번 밖.
+
+## 시험
+
+- 있음: `test_m02_hopyo_excel.py` · `_match.py` · `_build.py` · `_screen.py`(+ `helper_m02_hopyo_pick.cjs`) · `spreadsheet/test_spreadsheet_d_input.py`(+ `helper_spreadsheet_d.cjs`).
+- 새로: `test_m02_hopyo_grid.py`(병합 11 · 15열 · B20 제3호표 · `#,##0` · 행 범위 68 안 겹침 · [20,24] · 200/404 · 지우면 격자도 · 역할 · master_hash) · `test_m02_hopyo_basis.py`(블록 북부청 95 · 사방 67 · 단산93 → GC000083 · 21 → GC000198 · 64 → GC000143 위 둘 · 49 → GC000085 · 잔토처리에 철골재 철거 없음 · 근거 없는 줄 그대로 · master_hash).
diff --git a/docs/raw/조사/2026-10-15_331_CAD연속명령.md b/docs/raw/조사/2026-10-15_331_CAD연속명령.md
new file mode 100644
index 000000000..4fff5cbdd
--- /dev/null
+++ b/docs/raw/조사/2026-10-15_331_CAD연속명령.md
@@ -0,0 +1,66 @@
+# 331 내장 CAD 키보드 연속 명령 (요구 224-7)
+
+기준 = 837fe3d64. 경로 `B07_DesignDetail/openwebcad/` 아래 `src/…`.
+
+## 지금 — 우리 코드
+
+- 이미 됨: `L␣` → 첫 점 → `@100<30⏎`(한 줄 극좌표) · 마우스 방향 + `100⏎`(직접 거리).
+- 안 됨: `<30⏎` → `100⏎`(각도 고정 뒤 길이 · `<30` 읽는 곳 없음 · 버려짐) · 이동 · 복사 · 회전은 클릭만.
+- 버그: 시퀀스 도구(폴리선 등) 첫 점에 숫자 · `@x,y` → 기준점 없어 오류 → 「Error in tool actor」 · 도구 멈춤(`sequence-tool.ts:183-189,284-285` · `get-point-from-event.ts:26-28,42-44` · `state.ts:309-317`).
+
+| 길 | 자리 |
+|---|---|
+| 키 | `src/inputController/input-controller.ts:92` keydown → `handleKeyStroke :340-448` · 글자 대문자로 `this.text` 에 쌓음 `:441-447` |
+| 커서 옆 칸 | `draw :127-142` → `input-field-draw.ts:37-86`(안내 · 절대 x,y · 친 글 · 후보 6) |
+| 명령줄 | `components/CommandLine.tsx:17-39`(↑↓ 이력 · Tab 완성 `:78-81`) · 기본 숨김 `components/Toolbar.tsx:23` |
+| 단추 | `ribbon/Ribbon.tsx:172` · `components/QuickAccessBar.tsx:33` → `commands/run-command.ts:26-53` · 손가락 `input-touch.ts:122-140` |
+| 이름 · 별칭 | `commands/commands.{draw,modify,annotate,layer,utility,insert,view,file}.ts` 153개(별칭 82) → `commands/registry.ts:27-57` · 해석 `:116-120` = 이름 → 별칭 → 접두사 첫 후보 |
+| 상태 기계 | xstate 5.18.1 · 원본 기계 20(선 `tools/line-tool.ts:50-110`) + 우리 공장 `tools/factories/sequence-tool.ts:161-359`(약 100 명령) |
+| 프롬프트 | `meta.instructions` → `state.ts:297-308` → 커서 칸 · `CommandLine.tsx:87` |
+
+`handleEnterKey :474-599` 차례: 빈 Enter(대기면 마지막 명령 반복 `:483-486`) → TEXT_INPUT 단계면 글 그대로 `:498-508` → 명령 이름 `:509` → 숫자 `:516` → `x,y :531` → `@x,y :548` → `@d`) · 키보드 좌표 · 거리 시험 0건.
+
+규모: src 228 파일 ≈27,800 줄 · 700 넘음 `App.css` 1,102 · 가까움 `screenCanvas.drawController.ts` 691 · `state.ts` 647 · `MeasurementEntity.ts` 622 · `input-controller.ts` 620. 원본 OpenWebCAD(MIT · 77ab85739 들여옴 · 원본과 연결 없음 → 원본 파일 고쳐도 됨).
+
+## 조각 (코드 조사 제안)
+
+1. 각도 고정 `<각도` → 길이 — 새 `src/inputController/input-angle-lock.ts`(고정 저장 · `lockPoint(base, p)` · 안내) · `input-patterns.ts` `ANGLE_LOCK_REGEXP` · `input-controller.ts` `:509` 앞 분기 · draw 안내 줄 · Esc 풂 · `get-point-from-event.ts` 고정 중 미리보기 · 클릭 · 숫자를 고정 방향으로 · 한 점 쓰면 풂 · `line-tool.ts:134` · `sequence-tool.ts:179` 미리보기 · `run-command.ts:40` 새 명령이면 풂 — 확인 = `resources/tester/cad/keyboard-chain.test.ts`(`0,0` · `<30` · `100` → (86.603, 50) · `<90` · `50` → (86.603, 100) · 고정 없이 `100` = 마우스 방향).
+2. 시퀀스 첫 점 버그 — `sequence-tool.ts:284-285` NUMBER · RELATIVE 에 `!!lastPoint(context.values)` 조건.
+3. 이동 · 복사 키보드 값 — 시작 ABSOLUTE · 끝 NUMBER · ABSOLUTE · RELATIVE · `getPointFromEvent` 로.
+4. 회전 각도 숫자 — `rotate-tool.ts:156-174` NUMBER_INPUT · 음수 허용(`input-patterns.ts:4`).
+5. (선택) 선 중 `C` · `U` · 6. 원본 안내문 한글화 + 「(길이 · <각도 · @x,y)」 · 7. 동적 입력 `100 Tab 30⏎`.
+
+## 오토캐드 방식 (인터넷 조사 · Autodesk 도움말 우선)
+
+- 네 겹: 명령줄(글자) → 프롬프트 대기(점 · 값 · 선택지) → 점 입력 문법(`x,y` · `@` · `<` · 숫자만) → 보조 모드(직교 · 극 추적 · 스냅). 프롬프트 하나가 클릭 · 좌표 · 거리 · 선택지 글자 · 스냅 이름을 다 받음.
+- 명령줄: 이름 · 별칭(acad.pgp `A, *ARC` · 대소문자 무관) → Enter/Space · 자동완성(자주 쓴 순 · 중간 글자 · 지연 300ms) · ↑↓ 최근 입력 · 끝난 뒤 Enter/Space = 직전 명령 · Esc 취소 · 우클릭 = Enter/메뉴(시간 감지 짧게 Enter · 길게 메뉴) · 투명 명령 `'ZOOM`.
+- 프롬프트: `Specify next point or [Close/Undo]:` · 대괄호 = 선택지 · 대문자 글자 = 입력 키 · `<값>` = 기본값(Enter 면 유지) · 선택지 클릭 · 동적 입력 ↓ 목록.
+- 점 입력: 절대 `100,200` · 상대 `@30,0` · 극 `30<45` · 상대 극 `@30<45` · 각도 잠금 `<45` Enter → 커서로 쪽 → `30` Enter(다음 한 점만 · "Angle Override") · 직접 거리(커서 방향 + `30` Enter · 직교 · 극 추적과 같이) · 각도 = 동쪽 0° 반시계 + · 도분초 `45d30'15"` · 방위 `N45d30'E` · 소수점 `.` 만(`,` = 좌표 구분).
+- 동적 입력(F12): 둘째 점부터 기본 상대 극(`@` 생략) · 절대는 `#` · 치수 입력 = 길이 · 각도 칸 · 값 넣고 Tab = 그 칸 잠금 + 다음 칸 · Enter 확정.
+- 보조 모드: 극 추적 F10(증분 90 · 60 · 45 · 30 · 22.5 · 18 · 15 · 10 · 5° · 추가각) · 직교 F8(Shift 누르는 동안 반전) · 객체 스냅 F3 + 점 프롬프트에서 END · MID · CEN · INT · PER · TAN · NEA · QUA · NOD(다음 한 점만) · NON · 스냅 추적 F11 · 임시 덮어쓰기 Shift+키.
+- LINE 「선 → 각도 → 길이」 키 차례: 동적 입력 켬 = `L` Enter → 첫 점 → Tab `45` Tab(각도 잠김) `30` Enter 또는 `30` Tab `45` Enter 또는 `30<45` Enter · 끔 = `L` Enter → 첫 점 → `<45` Enter → 커서로 쪽 → `30` Enter 또는 `@30<45` Enter · `U` Enter = 마지막 선 취소 · `C` Enter = 첫 점으로 닫고 끝 · Enter · Esc = 끝 · 새 LINE 첫 점에서 Enter = 마지막 끝점에서 이어 그림.
+- 비슷한 프로그램: BricsCAD(같은 명령줄 · 동적 치수 길이 → Tab 잠금 → 각도 · F12 = Quad) · DraftSight(Quick Input F12 · Tab) · LibreCAD(`@100<90` · `@` 없으면 절대) · nanoCAD(`300` Tab `30` Enter) · Onshape(명령줄 없음 · 그린 뒤 치수 구속) · AutoCAD Web(명령줄 · 선택지 단추).
+- 웹 함정: 브라우저 F키(F1 · F3 · F5 · F6 · F7 · F10 · F11 · F12 · 막을 수 있는 건 캔버스 초점 때 preventDefault · 실측 필요) · Ctrl+W · T · N 못 막음 · 한글 IME(key "Process" · keyCode 229 · `event.code` 우선 · 조합 중 Enter 무시) · y축 아래(내부 수학각 하나 · 화면에서 뒤집기) · 우클릭 · 가운데 누름 기본 동작 막기.
+- 사용자 지정(설정 모달 후보): 별칭 편집(acad.pgp User Defined · 같은 별칭은 마지막이 이김) · 단축키(CUI) · 우클릭 모드 · 시간 · 휠 줌 방향(ZOOMWHEEL 0 = 앞으로 굴림 확대 · 1 = 반대) · 비율(ZOOMFACTOR 60) · 가운데 끌기 = 팬(MBUTTONPAN 1) · 동적 입력 기본(상대/절대 · 극/직교) · 극 추적 각 · 각 형식.
+- 추천 사양: 1단계 = 명령줄 · 별칭 + 자동완성 · 프롬프트 `[닫기(C)/명령취소(U)] <기본값>` · 점 파서 한 벌(`x,y` · `@dx,dy` · `d 서랍 900) · 저장 안 한 고침 물음 `ui_template/ui_template_leave_dialog.ts:13` `askLeave`(예 `B08_Quantity_UI_Drawing.ts:210`).
+- 탭 공용 부품 없음 · 공용 모양 `.ui-subtabs`(`ui_template_general_layout.css:58-70`) + `createButton` filled/ghost + `role=tab`(예 `B08_Quantity_UI_Milestone.ts:180-200` · `B09_Estimation_UI_TabBar.ts:30-60` · `M02_DF_InputPanel.ts:144-155`).
+
+## 마우스 현황 (당김 = 휠을 몸 쪽으로 굴리면 확대)
+
+| 화면 | 왼쪽 | 가운데 | 오른쪽 | 휠 | 근거 |
+|---|---|---|---|---|---|
+| B04 2D 지도 | 고름 · 관 끌기 | 팬 | 메뉴 | 줌 · 당김 | `B04_PreProcess_UI_MapViewer.ts:536-625` |
+| B04 · B05 3D | 회전 · 고름 | 팬 | 배치 메뉴 | 줌 · 당김 | `B04_PreProcess_UI_Camera.ts:140-254` · `B05_Profile_UI_Viewer.ts:145,266` |
+| B05 배수유역도 | 관 끌기 · 고름 | 팬 | 메뉴 | 줌 · 당김 | `B05_Profile_UI_Drainage_Interact.ts:60-119` |
+| B05 노선 편집 지도 | 노드 끌기 · 두 번 = 더함 | 팬 | 노드 지움 | 줌 · 당김 | `B05_Profile_UI_RouteEdit_Input.ts:49-146` |
+| 노선 편집 횡단 · B06 [상세] | 고름 | 팬 | — | 줌 · 당김 · 두 번 = 원배율 | `A00_Common/b_svg_zoom_pan.ts:162-264` |
+| B05 · B06 종단 · 유토 · 측점표 | 가로 이동 | 가로 이동 | 메뉴 | 가로 스크롤 · 확대는 단추 | `B05_Profile_UI_Profile_Wheel.ts:82-177` |
+| B06 횡단 카드 | 고름 | 팬 | 메뉴 | 목록 스크롤 | `B06_Section_UI_Cross_View_Zoom.ts:51-100` |
+| CAD(B07 · B06 상세 · M02 도면) | 고름 · 점 | 팬 | Enter | 줌 · 당김 | `openwebcad/src/inputController/input-controller.ts:150-300` · `input-wheel.ts:20-23` |
+| B08 거리이정표 | 빈 곳 끌기 = 팬 | 팬 | — | 줌 · 당김 | `B08_Quantity_UI_Milestone_Diagram.ts:157,463-474` |
+| B08 · B09 표 | 고름 · 두 번 = 입력 | — | 메뉴 | 스크롤 | `ui_template/ui_template_data_table.ts:514-529` |
+| M02 DF 그림 | 상자 끌기 · Space+왼쪽 = 팬 | 팬 | — | 줌 · 밀기 | `M02_DF_Diagram.ts:57-66,453-476` |
+| M02 노무 그림 | Space+왼쪽 = 팬 | 팬 | — | Ctrl+휠 · 밀기 | `M02_MasterTemplete_Labor_Diagram.ts:283-332` |
+| M02 시트 | 칸 고름 | — | 메뉴 | Ctrl+휠 · 밀기 | `A00_Common/spreadsheet/spreadsheet_grid.ts:270-284` |
+| 공용 돋보기 | — | — | 0.4초 누름(Z 키도) | 배율 · 밀기 | `ui_template/ui_template_loupe.ts:5-93` |
+
+- 팬 = 가운데 단추로 통일 · 휠 방향 둘로 갈림 · 공용 판정 `A00_Common/touch_bridge.ts:35-36` `wheelZoomsIn` 은 셋만 씀(B04 지도 · 배수유역도 · 노선 편집 지도) · 나머지 직접 박음(`b_svg_zoom_pan.ts:217` · `B04_PreProcess_UI_Camera.ts:253` · `input-wheel.ts:23` · `M02_DF_Diagram.ts:457` · `Labor_Diagram.ts:316` · `ui_template_loupe.ts:91` · `spreadsheet_grid.ts:274`).
+- 지금 사용자가 바꿀 수 있는 것: 터치 모드(localStorage `frd_touch_mode` · `device_context.ts:33,91-101`) · B05 3D 투영 · B09 행 누름(pref `b_page_state.ts:71,107`) — 마우스 단추 · 휠 방향 칸 없음. CAD 휠 지수 · 스냅 거리 상수(`openwebcad/src/App.consts.ts:52,150-160`) · 토글은 메모리(`state.ts:82,179-180`). maplibre 안 씀(평면 = 자체 캔버스).
+
+## 단축키 현황
+
+| 자리 | 키 | 근거 |
+|---|---|---|
+| 공용 판정 | Ctrl+Z · Shift+Z · Y · S · A(한글 자판 자리) | `A00_Common/key_shortcut.ts:28-49` |
+| 전역 | Esc = ☰ · 창 닫힘 · 고름 풂 · Z 누름 = 돋보기 | `app_shell.ts:173` · `ui_template_modal.ts:53` · `design/design_pick_escape.ts:15-29` · `ui_template_loupe.ts:66-72` |
+| B05 종단 | Ctrl+Z · Ctrl+Shift+Z · 방향키 · 자유지정 Enter · Esc · Backspace | `B05_Profile_UI_Profile_Panel_Tools.ts:310-349` |
+| B05 노선 편집 | Ctrl+Z · Y · Shift+Z | `B05_Profile_UI_RouteEdit_History.ts:157-165` |
+| B06 | 방향키 · ▲▼ · Enter/Space · Esc | `B06_Section_UI_Cross_Panel_Base.ts:264-290` · `_Cross_Design_Pick.ts:545-562` |
+| CAD | F3 · F7 · F8 · F10 · Ctrl+Z · Y · A · S(M02) · V · Delete · Esc · Enter · Space = Enter · 방향키 · 글자 = 명령 | `input-controller.ts:364-447` |
+| CAD 별칭 | 명령 153 · 별칭 82 · `commands.*.ts` `aliases` | `openwebcad/src/commands/registry.ts:50-57,116-135` |
+| B08 · B09 · 표 | Enter · F2 · 방향키 · Esc | `ui_template_data_table.ts:520-529` 외 |
+| M02 | Ctrl+S · Z · C/V · Delete · Esc · Space = 팬 · 시트 엑셀 키 | `M02_MasterTemplete_CardShell.ts:79-80` · `spreadsheet_keys.ts` |
+
+정의 표 = CAD 별칭 하나(엑셀 키 표 `spreadsheet_shortcuts.ts:1-10` 은 대조용) · 충돌 관리 없음(입력 칸 건너뜀 · `defaultPrevented` 양보 · 창 떠 있으면 양보 · CAD 는 iframe · 별칭 겹치면 먼저 것 `registry.ts:53-56` · 이름 > 별칭 `:119`).
+
+## 저장 — 새 표 · API 필요 없음
+
+- 표 `user_ui_prefs`(`db_management/018_user_ui_prefs.sql:17-25`) · `GET/PUT /api/dashboard/me/ui-prefs`(`B01_Dashboard_Router.py:122-133` · 값 문자열 · 통째 덮음 `Repository.py:554-573`).
+- 브라우저 `pref` 통 = localStorage `aislo:pref:*`(`A00_Common/b_page_state.ts:47-112,285-287`) · 바뀌면 1.5초 모아 PUT(`:337-375`) · 로그인 첫 세션에 서버 값 얹음(`A06_Login_Api_Fetch.ts:64-69` → `b_page_state.ts:378-404`) · CAD iframe 같은 오리진 → localStorage 같이 씀(테마 선례 `openwebcad/src/theme.ts:43-52`).
+
+## 설계 후보 · 위험
+
+- 읽기 층 새 `A00_Common/input_prefs.ts`(의존 없음 · CAD 도 import) — `INPUT_PREFS_KEY` · `DEFAULT_INPUT_PREFS`(= 지금 동작) · `inputPrefs()`(부를 때마다 읽음 · 깨짐/Node = 기본값) · `wheelIn(deltaY)` · 모달 [저장] 때만 `writeState("input-prefs", 값)`.
+- 배선 자리: 휠 = `touch_bridge.ts:36`(셋 자동) · `b_svg_zoom_pan.ts:217` · `B04_PreProcess_UI_Camera.ts:253` · `input-wheel.ts:23` · (일체면) M02 둘 · 돋보기 · 시트 · CAD 별칭 = `registry.ts:116-131`(이름 > 내 별칭 > 기본) · CAD 오른쪽 = `input-controller.ts:155`.
+- 위험: 700줄(`B04_PreProcess_UI_MapViewer.ts` 699 안 만짐 · `M02_DF_Diagram.ts` 692 한 줄 · `input-controller.ts` 620) · 공용 파일(`b_page_state.ts` · `app_shell.ts` · `touch_bridge.ts` · locale) 한 창이 맡음 · CAD `input-controller.ts` · `registry.ts` 는 331 과 겹침 → 331 뒤 · 휠 일체면 M02 그림 · 시트 · 돋보기 기본 방향 반대 · 계정 전환 시 앞 사람 값 남을 수 있음(기존 한계) · Node 시험(helper_164 · 266 등)이 `b_svg_zoom_pan` · `touch_bridge` 를 불러 돌림 → `input_prefs` 는 불러올 때 window 안 만짐 · CAD 명령 목록은 `commands.*.ts` import 시 xstate 딸려 옴 → 목록 JSON 한 벌 + 거울 시험.
+
+## 시험
+
+- 있음: `test_device_drawer.py`(서랍 차례) · `test_device_audit.py` · `test_touch_bridge.py` · `test_cad_touch.py` · `test_prefs_persist_across_tabs.py` · `test_b01_ui_prefs_dead_project.py` · `test_session_keys_registered.py` · 휠 · 팬(`test_164_detail_zoom.py` · `test_266_detail_home.py` · `test_281_1_drainage_zoom_range.py` · `test_230_1_popout_wheel.py` · `test_244_6/8_*` · `test_b05_routeedit_cross_width.py` · `test_cross_axes_zoom.mjs` · `test_cross_pan_bounds.mjs` · `openwebcad/src/inputController/input-wheel.test.ts`) · 돋보기 · 키 · 모달(`test_218_1_loupe.py` · `test_220_1` · `test_221_1` · `cad/ctrl-shortcut-hangul.test.ts` · `cad/ctrl-shortcut-sheet.test.ts` · `test_modal_head_actions.py` · `test_cad_host_tabs.py`).
+- 새로: `test_input_prefs.py`(+ cjs) · `cad/input-prefs-cad.test.ts` · `test_app_settings.py`.