사용자 승인(2026-09-07). 앞서 취향을 localStorage 로 옮겨 **탭·재시작** 문제는 풀었고,
이 변경은 그 위에 **다른 PC 에서도 같은 배치**를 얹음. 사용자가 노트북·데스크톱 두 대를 오감.
- `db_management/018_user_ui_prefs.sql` — 사용자당 한 줄, `prefs` JSON 한 칸.
칸을 나누면 취향이 늘 때마다 마이그레이션이 또 필요해 한 칸에 담음.
- `GET/PUT /api/dashboard/me/ui-prefs` — 배치 값만. **설계값은 안 담음**(문자열만 받음).
- 로그인이 확인된 첫 순간에 한 번 받아 로컬 위에 얹고, 취향이 바뀌면 1.5초 모아 올림.
서버가 없거나 못 읽으면 **로컬 값으로 그대로 돔**(계획서 원문).
⚠ 같은 함정을 또 밟을 뻔했음 — 키만 아는 자리가 저장소를 직접 고르면 **올려보내기도 안 걸림**.
그래서 `storageOf` 를 없애고 **읽기·쓰기 창구 하나**(`readByKey`/`writeByKey`)로 모음.
저장소 선택과 서버 올려보내기가 그 한 곳에만 있음. 그물도 그 이름으로 갱신.
**DB 는 아직 적용하지 않았음** — 표가 없으면 API 가 실패하고 화면은 로컬 값으로 도는 것이
정상 동작임. 적용 시점은 다른 창들과 맞춘 뒤.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
오늘 같은 계통이 두 번 났음 — 키는 등록표에서 받는데 **저장소는 제 맘대로** 고르는 자리.
· `rockb` — 키를 날문자열로 만들어 읽는 쪽이 늘 빈 값(`f8fafd23`)
· 화면 취향 — 키는 맞았는데 저장소를 직접 골라 **절반만** 옮겨짐(`9db84d9d`)
남은 두 자리(암 경계선 저장·표시 반폭)도 `storageOf` 를 거치게 함. 지금은 둘 다 초안이라
세션이 맞지만, 통이 바뀌면 조용히 갈릴 자리였음.
그물도 넓힘(`test_session_keys_registered.py`) — 등록표 키를 쓰는 파일이 저장소를 이름으로
직접 고르면 깨짐. 고르는 곳은 `b_page_state` 하나. 앱 전역 값(`CURRENT_PROJECT_ID_KEY`)은
등록표 밖이라 제외하고 이유를 적어 둠. **훑어 보니 남은 위반 0곳.**
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PLAN 2-5 (2026-09-07 사용자 지시 4 「구조물 표시 통일」). B06 종단에는 구조물 마크가
아예 없었고 배수관·세월교만 다른 계통으로 그려졌음.
- B05 가 쓰는 **같은 부품**(`buildStructureLane`)을 B06 종단 그래프 아래에 붙임.
이름·중복 번호·툴팁·약호가 전부 B05 규칙 그대로 따라옴.
- 종단 차트가 `toX`(누가거리 → px)를 내보내 레인이 그래프와 같은 자리를 씀.
- 좌측 「구조물 배치」가 목록을 받을 때마다 `setStructureMarks` 로 넘김(구조물 정본 +
관 정본). 관을 가상 구조물로 바꾸는 `pipesToStructureMarks` 는 B05 안에 있던 것을
공용 자리(`B05_Profile_Api_Structures`)로 올려 **한 벌만** 둠.
- 알약을 누르면 그 자리에 가장 가까운 측점 카드를 고름(좌측 목록 클릭과 같은 규칙).
끌기는 B05 몫이라 여기서는 자리만 보임(배수유역 재분할이 걸림).
- 검증 훅 `window.__b06Marks` 추가(`__corridorBuild` 등과 같은 용도).
자체검증 — 실화면 B06 종단에 레인 1줄·알약 10개(「배수관 1~8 · 세월교 1~2」)가 B05 와
같은 표기로 뜸. 훅 값 `{structures:10, types:36}`. tsc 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
페이지마다 따로 얹으니 새로고침 뒤 세션 스냅샷이 저장분 기준으로 되돌아갔음
(초안 벽 3건이 화면에서 사라짐). 캐시·세션·새 요청 어느 길로 들어오든 지나는
공유 저장소에서 ① 저장 안 한 구조물 벽을 얹고 ② 구조물 면적을 다시 계산하게 모음.
검증: 새로고침 뒤에도 벽 5건(저장 2 + 초안 3)이 붙고 3+0.0 성토 8.06 → 5.73㎡ 유지.
테스트 390 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
유토곡선은 카드보다 먼저 계산돼, 구조물을 넣으면 카드 면적만 바뀌고 최종 누가토량은
옛 값으로 남았음. 구조물이 바뀌면 면적을 먼저 다시 얹고 그리게 순서를 바로잡음.
검증: 3+0.0 성토 8.06 → 5.73㎡, 최종 누가토량 553.4 → 546.4㎥ (저장 전, 즉시).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
좌측 「구조물 배치」로 넣은 옹벽·돌쌓기 같은 벽이 지금까지 측점만 심고 기하가 없어
절·성토 면적이 그대로였음(2026-09-06 사용자 확정: 반영해야 함).
- 독립 기슭막이가 쓰던 `section.revetment` 제원 자리에 그대로 얹음 — 기하·설계선
트림·폐회로 면적·3D 가 손대지 않고 따라옴.
- 서버 `B06_Section_Engine_Structures_Wall.py`(저장분) ↔ 브라우저
`common_util_structure_walls.ts`(미저장 초안) 짝 + 거울 테스트.
- 설치 측 칸이 없는 C군은 성토가 나는 쪽으로 자동 배치, 양측 절토면 세우지 않음.
- 구조물 목록이 바뀌면 B06 이 초안을 얹어 다시 그림.
검증: 40m 돌쌓기(찰) 추가 시 그 측점 성토 5.81 → 5.02㎡. 테스트 390 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 확정(2026-09-06): 조작은 캐시에 쌓이고 [저장]·[확정]에서만 영구저장소에
나간다. 카드 버튼(지반유형·단면유형·측구·포장·2단 비탈)은 누를 때마다
POST cross-design 이 나가 서버가 계산하고 바로 저장하고 있었다.
- 세션 초안 crossdesign 신설(B06_Section_Cross_Design_Session) — 선택값만 담는다.
- 계산은 브라우저 재계산 창구 하나로 돌린다(B05·B06 공용).
- 재계산 때 세션 선택이 정본보다 우선 — 새로고침 뒤에도 고른 값이 남는다.
- [저장]·[확정] patch 에 선택값 + 전 측점 면적을 실어 보낸다. 카드 버튼을 바꾸면
구조물이 없는 측점 면적도 달라지므로 구조물 측점만 보내면 수량이 어긋난다.
- 구조물 목록 주석 정정(이미 초안 방식인데 옛 주석만 남아 있었다).
검증: tsc --noEmit 통과, pytest 389 passed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
캐시·세션 일원화에서 조정창 값 8종을 옮기며 다단 구간값(`extraspan`)을 등록표에
빠뜨렸다. 표에 없는 이름을 꺼내다 예외가 나 페이지 렌더가 통째로 실패했고 화면에는
「준비 중인 페이지입니다」만 떴다(화면 확인으로 발견).
- `extraspan` 을 등록표에 넣음.
- 표에 없는 이름은 이제 **예외 대신 저장 건너뛰기** — 그 값만 안 남고 화면은 선다.
콘솔에 한 번 알려 표에 줄을 넣게 한다.
- B06 이 키를 만들 때 쓰던 형변환을 없앰 — 앞으로 등록표에 없는 이름은 컴파일에서
걸린다. 같은 사고가 화면까지 가지 않는다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-05 사용자 지시 건. 넘김 자체는 서 있었고 값이 중간에 지워지거나 대조에서
떨어지고 있었다.
- 부재키가 지워지던 길 — 3D 로 고른 뒤 선택 동기화가 **측점만** 다시 적어 부재키를
덮었다. 같은 측점이면 이미 적힌 부재키를 지키게 함(모든 호출자에 함께 적용).
- 측점 대조 허용오차 0.01m → 0.05m. 같은 화면의 다른 대조와 같은 값이며, 3D 가 준
누가거리가 소수점 아래에서 조금만 달라도 측점을 못 찾고 조용히 끝나던 원인.
- 「진입당 한 번」 빗장을 넘김값 서명 비교로 교체 — 자료가 늦게 와도 다시 서고,
같은 값이면 재렌더에도 스크롤이 안 튄다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
캐시·세션 일원화 2단계 (2026-09-06 사용자 지시).
- ② 설계 초안 이관 — 구조물 미저장분, 상단측 변경분, 페이지 간 넘김값(구조물 선택),
표준횡단 편집값, 조정창 4축·단수·연동값 8종, 암 경계선, 표시 반폭. 넘김값은 별도
임시 키를 없애고 초안 한 자리로 흡수.
- ④ 계산 결과 이관 — 최신 노선 응답, 서버가 준 규격 횡단·암 경계 기본값. 횡단 상세는
메모리 캐시에 더해 세션에도 한 벌 얹어 새로고침 뒤 첫 화면이 서버를 안 기다림
(용량을 넘으면 조용히 건너뛰고 예전대로 다시 받음).
- B05 진입의 강제 새로읽기를 걷어냄 — 캐시로 먼저 그리고 노선번호·지표면 서명으로
뒤에서 신선도만 확인, 달라졌을 때만 다시 그림.
- [초기화]의 초안 비우기를 `clearDrafts` 한 곳으로 모음(예전에는 파일마다 따로).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 바깥 선택이 폼에 실릴 때 접힌 좌측 패널 자동 펼침(onReveal 창구)
- 계곡 통과 시설 목록 행도 보이는 자리로 스크롤(rAF)
- 종단 테이블 구조물 구간 판정을 허용오차 비교로 교체 + 병합 측점 포함(강조 0건→6건)
- 구조물 선택 세션을 소비형에서 유지형으로 — B06 카드·목록·부재 선택도 같은 칸에 기록,
양쪽 진입 시 복원(목록 지연 로드 대비 이중 복원)
- 조정창 닫기가 세션 선택까지 지우던 결함 수정(부재 해제와 측점 해제 분리)
- B05 페이지 700줄 유지 위해 상단측 세션 보관을 Page_Helpers로 이동(동작 불변)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 이름표를 횡단도 이름으로 통일 — 세월교 유입·유출, BOX 좌·우, 본체는 body
- 조정창 하나가 여러 부재를 다루므로 `측이름:부재` 꼴 — 강조는 전체 키, 조정창은 앞칸만
- B06 갈래 태우기 신규 모듈 — 세월교/BOX/기슭막이 조정창으로 각각 보냄
- 저장 코리도 만료(BUILD_VERSION 91→92) — 재계산 비용 실측 차이 없음(5.1초 vs 4.7초)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 구조물 솔리드에 부재키(key) 부여 — B06과 같은 이름(inlet·outlet·extra{i}·bextra{i}·own·basin·pipe)
- 구조물 메쉬에 신원표(userData) — 이름 규칙은 그대로 유지
- 신규 모듈 두 개 — 3D 클릭·강조(Viewer_Structure_Pick), 좌측 패널 연결·B06 넘김(Structure_Pick_Session)
- 좌측 패널·종단에서 고른 것도 3D가 따라오도록 선택 동기화에 포트 추가
- B06 진입 시 세션 넘김값으로 그 측점 카드 선택·스크롤 + 기슭막이 조정창 열기
- 저장 코리도 만료(BUILD_VERSION 90→91) — 옛 저장본엔 부재키가 없음
- 뷰어 700줄 여유 — 검증용 요약을 Viewer_Debug로 분리(동작 불변)
- 검증 수단 — __corridorScene에 camera·project 추가
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
두 곳이 같은 뿌리였음. 브라우저 재계산은 **편집이 반영된 계획고**로 횡단 설계를 만드는데
`design_profiles` 는 저장분 그대로라 두 값의 기준이 갈렸음.
1. 재계산 창구가 방금 푼 계획선을 **공유 캐시에도 얹도록** 함
(`detail.longitudinal.design_profiles`). 이게 없으면 낡음 판정이 영원히 참이 되어
B06 유토곡선이 통째로 빈 채로 남았음(사용자 보고). `profile_alignment`(base_pvi)는
건드리지 않음 — 편집 델타의 기준선이라 편집분을 구워 넣으면 이중 적용됨.
2. B06 진입 정합이 **미저장 세션 편집을 못 보고 건너뛰던** 것. 서버에서 갓 받은 저장분
끼리는 늘 일치해 「낡지 않음」이 되는데, B05가 세션에 남긴 편집은 그 안에 없음.
세션 초안이 있으면 판정을 건너뛰고 무조건 맞추도록 함 — 재계산은 브라우저 안에서
끝나 값이 쌈.
화면 검증(5174, 같은 시점 비교):
수정 전 — B05 절토 4,774.1㎥ / 성토 16,715.5㎥, B06 절토 4,515.0㎥ / 성토 14,857.5㎥
수정 후 — 양쪽 모두 절토 4,774.1㎥ / 성토 16,690.7㎥ (일치)
B06 유토곡선 영역도 곡선이 다시 그려짐(문구 없음).
전체 테스트 183건 통과, `tsc --noEmit` 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
사용자 조작 중의 계산은 브라우저 안에서 끝나야 함(2026-09-03 사용자 확정). 계획고가
바뀔 때마다 전 측점 횡단 계산이 `POST …/cross-design/preview` 로 나가 왕복이 조작
속도를 지배했음.
- `refreshCrossDesigns` 를 로컬 계산으로 교체. 계획고는 저장 선형(`base_pvi`)에 편집
델타를 얹어 TS `buildAlignment` 로 여기서 풂 — 호출부(B05·B06) 시그니처는 그대로라
단일 창구가 유지됨. 서버 프리뷰는 선형 저장분이 없는 옛 데이터 폴백으로만 남음.
- 표준단면 config 기본값·암 경계 기본 오프셋을 `sections/context` 응답에서 세션에 기억
(`rememberStandardDefaults`·`rememberRockBoundaryDefault`). 프론트에 config 수치를
복제하지 않으면서 패널 없는 B05 도 같은 값으로 계산함.
- 디바운스 250 → 60ms — 왕복이 사라져 손을 떼는 즉시 유토곡선이 따라옴.
- 낡음 판정을 `hasStaleDesigns` 로 바꿔 첫 건에서 중단하고, 계획선 유효 샘플을
샘플 배열당 한 번만 걸러 이진탐색(그리기 프레임당 2.33ms → 0.07ms 실측).
- 파이썬 엔진 2파일·프리뷰 라우터에 「TS 짝 파일과 한 벌」 경고 블록 추가.
검증: `tmp/tests/test_b06_cross_design_mirror.py` 10건 — 토사/암 2단계/L형 측구/포장/
세월교 하강/사면 미닫힘 등에서 파이썬과 TS 결과가 면적·설계선까지 일치. 전체 183건 통과.
`tsc --noEmit` 통과. 실측: route 127(측점 128) 전 측점 로컬 재계산 7.5ms.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
같은 데이터를 두 화면에 보여 주는 기능인데 세 곳이 갈려 값이 달랐다(실측: 절토(자연)
B06 3,704.6㎥ ↔ B05 4,526.8㎥).
① 보는 노선 — B05는 최신 경로, B06은 최신 **확정** 경로를 열어 노선을 다시 탐색한
프로젝트에서 서로 다른 노선을 봤다(route 126 DRAFT ↔ 125 CONFIRMED, 같은 20m
측점 성토 4.82㎡ ↔ 85.9㎡). `get_workflow_route_context()`(최신 경로) 신설해 B06
화면 context가 그것을 쓴다. 납품 도면(B07)이 쓰는 확정 전용 창구는 그대로 둔다.
② 횡단 재계산 — 호출이 두 벌이라 인자가 갈렸다(B06만 표준 단면값·암 경계 오프셋 전달,
보존하는 사용자 부속값도 2개 ↔ 7개). `B06_Section_Cross_Refresh.refreshCrossDesigns()`
한 창구로 모으고 세션 편집값은 저장소에서 직접 읽어 패널 없는 B05도 같은 값을 보낸다.
③ 낡음 판정 — 「옛 암 2단계 필드 누락」 조건이 B06 페이지에만 있어 B05는 재계산을
건너뛰었다. 공용 `staleDesignChainages()` 안으로 옮겨 두 화면이 같은 시점에 같은
조치를 한다.
검증 — 공용 브라우저 실측: 두 화면 모두 route 126, 요약줄 문자열 완전 일치
(`절토(자연) 4,526.8㎥ · 성토 14,881.8㎥ · 토취 10,213.3㎥ · 최종 누가토량 −10,213.3㎥`).
pytest 370 passed·17 skipped(일원화 검사 4건 신설), typecheck·prettier·ruff 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
세월교와 같은 문제가 BOX암거에도 있었다 — 좌측 「구조물 배치」의 본체 규격·날개벽을
바꿔도 도면이 그대로였고, 폼 기본값이 스펙에서 오지 않았으며, 구체를 골라도 좌측
칸이 서지 않았다. `applyPipeOptionsToCache`가 `section.box`를 모르고 즉시 return하던
것이 뿌리다(세월교와 동일).
- `applyBoxFormOptions()` — 폼 옵션(`body_*`·`wing_*`)을 제어기로 보낸다.
`BoxControl`에 `setBody`·`setWing`을 더했다: 캐시(`section.box`)를 먼저 고치고
(`span_m`은 백엔드 `_box_set`과 같은 식으로 다시 잡는다) 정본 예약 + 카드 갱신.
- 날개벽 옵션 조립·해석을 `wingOptions()`·`wingPatchFrom()`으로 묶어 세월교와 나눠
쓴다 — 세월교 쪽 중복 코드가 사라졌다.
- `withBoxSpec()` — 폼 기본값을 `section.box`(내공 폭·높이, 날개벽)에서 읽는다.
- 좌·우 끝을 고르면 폼의 **날개벽(유입)/(유출)** 칸이 선다. 좌·우 ↔ 유입/유출
매핑은 기하가 날개벽을 고르는 기준(`uphill_side`)과 같다. 조정창은 결국 지우고
폼 이름(유입/유출)만 쓰기로 한 사용자 확정(2026-08-30)을 따랐다.
구체 길이·표고는 폼에 대응 칸이 없어 조정창 축으로 남는다(본체 높이는 규격이 이미
정한다 — 2026-08-30 사용자).
자체검증(공용 브라우저 5174 + 워크트리 백엔드 8001, 측점 10+0.9 BOX암거):
- tsc --noEmit 통과.
- 구체 선택 시 좌측 `날개벽(유입) (BOX암거)` 칸 강조(수정 전 강조 없음).
- 본체 규격 2.0×2.0 → 3.0×3.0 → 도면 라벨 `BOX 3.0×3.0`, 다시 2.0×2.0 → `BOX
2.0×2.0`. 한 번 고른 상태에서 연속 조작도 그대로 먹는다. 수정 전에는 라벨이
5.34m 구체와 함께 전혀 움직이지 않았다.
- 회귀: 세월교 날개 길이 2→4→2에서 조정창 표시가 4.0m→2.0m로 따라온다
(`wingOptions`/`wingPatchFrom` 공용화 뒤에도 동일).
- 조작값은 원래대로(BOX 2.0×2.0 · 세월교 날개 길이 2m) 되돌려 놓았다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
좌측 폼의 세월교 상세 항목(관종·관경·수량·월류 폭·날개벽 유입/유출)이 도면에
전혀 반영되지 않았다. `applyPipeOptionsToCache`가 `section.culvert`만 보고
세월교 측점(`section.ford`)에서는 즉시 return했기 때문이다.
프론트는 구조물 배치의 상세 UI를 쓰고 로직은 조정창 제어기 것을 쓴다
(2026-08-30 사용자 확정).
- `applyFordFormOptions()` 신설 — 폼 옵션 키를 `FordControl.setPipe`/`setWing`
인자로 옮긴다. 캐시 반영·바닥판 연장 재계산·정본 예약·카드 갱신은 제어기에
이미 있어 새로 만들지 않았다.
- `FordControl.setPipe` 확장 — `pipe_kind`(관종)·`ford_width_m`(월류 폭 =
`span_m`) 추가. 두 값은 조정창에 칸이 없고 폼만 보낸다.
- `withFordSpec()` — 폼 기본값을 `section.ford` 스펙에서 읽는다. 두 창이 같은
스펙을 보므로 어느 쪽에서 만졌든 값이 갈리지 않는다(수량(련) 빈 칸 해소).
월류 높이(`ford_height_m`)는 백엔드 `_ford_set`이 읽지 않는다 — 세월교는 구체
위 노면이라 파임이 없고, 물넘이포장 전용 값이다. 폼에는 남되 도면 반영처는 없다.
자체검증(공용 브라우저 5174, 측점 7+9.7 세월교):
- tsc --noEmit 통과.
- 폼 날개 길이(유입) 2→4 → 조정창 「날개 길이 4.0m」, 바닥판 7.63m→9.04m
(= +2×cos45°). 수정 전에는 둘 다 그대로였다.
- 폼 수량 3 → 조정창 「3련」, 폼 관경 Ø1200 → 조정창 select 1200.
- 재선택 왕복: 월류폭 12·수량 3·관경 1200·날개길이 4 그대로 되읽힘(스펙 정본).
- 진입 시 수량(련) 칸 = 1(수정 전 빈 칸), 관종 파형강관.
- 조작값은 원래 값(10/1/1000/2)으로 되돌려 놓았다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
조정창에서 옮겨 오는 [추가 기슭막이(단)] 행과 같은 값이라 두 벌이었다.
단 수 정본은 횡단 설계 patch(extra_wall_counts)이고 조작은 그 행이 한다 —
폼 칸과 그 값을 나르던 배선(tierCount·setTierCount)을 함께 걷어낸다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
다른 브랜치(feat/B05B06-구조물패널-일원화)의 B05·B06 구조물 배치 폼 일원화
결과를 받는다. 충돌 없음 — 그쪽은 B05/B06만, 이쪽은 B07/B08만 건드렸다.
그쪽 브랜치에 남아 있던 옛 폴더(B08_DesignDetail, B07_Quantity)는 이쪽 삭제가
반영돼 사라진다. main.py·라우트·척도 등 B07 작업물은 이쪽 것이 유지된다.
- 단 수를 바깥 행에서 좌·우 칸 안으로 옮긴다. 저장 키도 좌·우 각각
(inlet_revet_tiers·outlet_revet_tiers, 옛 tiers는 폴백·호환 기록).
폼 값은 다단 채널(extra·bextra)로 가고, 폼은 그 채널 값을 되읽는다.
- 좌·우 칸에도 조정창 행 이식 자리를 두어, 벽을 고르면 배관 유입구·유출구와
같이 그 칸이 강조되고 조정 항목이 옮겨 온다. 칸 이름표는 좌·우 그대로 둔다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 설치 측·단 수를 컨테이너 밖 한 행으로 빼고, 벽 제원은 [기슭막이 (좌)]·
[기슭막이 (우)] 두 칸으로 나눈다. 설치 측이 한쪽이면 그 칸만 뜬다.
좌·우 이름표는 그 측점 오르막 방향(uphill_side)이 정한다.
- 두 칸은 배관 유출구와 같은 양식이다 — [구조][형태] + 길이·높이·전/후.
구조 선택지는 기슭막이 하나뿐이지만 레이아웃을 맞추려 그대로 둔다.
- 값은 유입/유출 키로 좌·우 따로 저장한다(옛 한 벌 저장분은 양쪽으로 펼쳐 읽는다).
- 단 수에 [-][값][+] 조정칸을 붙이고, 폼 단 수를 다단 채널(extra·bextra)로
보낸다 — 배수관 측점처럼 횡단도에 단이 선다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
배관 유출구 기슭막이가 폼과 다른 형태로 그려졌다. 폼은 저장된 옵션을,
그림은 조정값(m·h)을 쓰는데 둘이 어긋나도 맞추는 자리가 없었다.
조작이 정본이므로 폼이 그려진 값을 따라간다 — wallForm 의존성을 추가하고
형태·높이는 빈칸 채우기가 아니라 덮어쓰기로 바꾼다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
원인 추적 결과 두 겹이었다.
① 배관 벽 높이는 spec.revet_height_m을 쓰지 않는다 — revetWallSpec이 관경 기준
하한과 조정값(adjust.h)만 본다. 폼 값을 스펙에만 넣어 그림이 안 바뀌었다.
이제 폼의 높이·형태를 조정창과 같은 채널(revetOffset.update)로 보낸다.
② 형태마다 높이 한계가 있다(돌쌓기(메) 2.0m). 한계를 넘겨 입력하면 그림은 그대로인데
폼 숫자만 커져 '제어가 안 된다'로 보였다. 기하가 적용한 값을 폼에 되돌리고
한계를 토스트로 알린다.
폼 기본 높이도 스펙이 아니라 지금 그려진 순수 높이(조정창 표시값)를 쓴다.
2026-08-29 사용자 지적 2건:
① 폼에서 높이·길이·기준측점 전/후를 고쳐도 도면이 그대로였다 — 조정창 조작값
(revet_adjust.h·m)이 폼 값보다 우선이라 그림이 옛 값을 유지했다. 폼이 높이·
형태를 정하면 그 벽의 조정값을 비워 폼 값이 이기게 했다.
② 폼 기본값은 예전 조정창이 보여주던 값이 기준이다 — 관 옵션이 비어 있으면
횡단 스펙(culvert 스펙: 형태·높이·길이·전/후·집수정 구간값·관종·관경)에서
채워 폼과 조정창이 같은 원천을 보게 했다. 저장된 옵션이 있으면 그쪽이 이긴다.
2026-08-29 사용자: 구조물 배치 컨테이너를 B06으로 가져온 이유가 이것이다 —
폼의 기준 측점 숫자를 바꾸면 관이 옮겨져야 한다.
- 관 저장기에 이동 예약(queueMove)을 더했다. 세션 키 b06:culvertmove에 담고,
[저장]·[확정]의 flush가 옵션 병합과 함께 pipe_points에 반영한다. 세부 배수유역
재분할은 서버가 관 목록을 다시 받아 처리한다.
- 폼에서 측점을 옮기면 목록·폼 캐시가 즉시 새 자리를 따르고, 옵션은 옮기기 전
자리로 예약한다(저장 시점 관 목록이 그 자리 기준).
- 이동 안내는 차단 문구에서 '저장 뒤 다시 계산' 알림으로 바꿨다.
원인: 좌측 폼의 실시간 반영이 세션 예약(culvertopt)과 목록 캐시까지만 갱신하고,
횡단 캐시(section.culvert 스펙)를 건드리지 않아 카드가 옛 값으로 남았다.
- 폼 옵션 키를 culvert 스펙 자리로 옮기는 applyPipeOptionsToCache 추가
(관종·관경, 유입·유출 기슭막이 형태·높이·길이·전/후, 집수정 구간값, 구조).
- 연장이 덮는 범위의 카드만 다시 그린다 — 조정창 조작과 같은 흐름.
- 화면 캐시만 고친다. 영구저장은 [저장]·[확정] 그대로.
원인: 앞선 커밋에서 넣은 '대표 벽 자동 선택'(selectDefaultWall)이 카드를 고를
때마다 벽을 골라 조정창까지 띄웠다(2026-08-29 사용자 보고: 기슭막이(유출) 창이
계속 떠 있음).
- selectDefaultWall과 그에 딸린 자동 선택 표시(withAutoAdopt 등)를 걷어냈다.
- 카드·목록 선택 리스너에서 남아 있는 벽 선택을 비운다. 벽을 찍어 카드가 딸려
선택된 경우는 건드리지 않는다(isWallSelecting 가드).
부작용: 단 수·옵션 행은 도면에서 벽을 고른 뒤에만 유입구·유출구에 붙는다
(사용자 확인: 측점 선택 시 자동 구조물 선택은 필요 없음).
검증: 목록 선택·카드 선택 시 조정창 0개·강조 없음, 벽 클릭 시 기슭막이(유출) 창
+ 유출구 강조 + 단 수·옵션 행 표시.
2026-08-29 사용자 지시 3건:
1. 유입구·유출구로 옮겨 온 단 수·옵션 행을 폼 양식(라벨+한 덩어리 컨트롤)으로
맞추고 2열 격자로 세운다.
2. 이미 고른 항목은 값이 바뀔 때마다 곧바로 반영한다([수정] 버튼 불필요).
버튼은 visibility만 꺼 자리를 남겨 삭제·리셋의 위치·크기가 흔들리지 않게 했다.
정본 주입이 되먹임으로 돌아와도 편집 중인 폼은 닫지 않는다.
3. 도면에서 구조물을 고르면 그쪽 그룹(유입구/유출구)을 붉은 계열로 강조한다.
카드만 고른 경우는 강조하지 않는다 — 대표 벽은 조용히 딸려 오고, 그 벽을
다시 눌러 선택을 풀면 강조만 꺼지고 행은 남는다.
검증: 슬롯 grid 2열, 카드 선택 시 강조 없음·행 2개, 다른 벽 클릭 시 유입구 그룹
붉은 테두리(rgb(192,57,43)), 재클릭 시 강조 해제·행 복귀. 길이 + 즉시 캐시
반영(11) 후 - 원복(10), 폼 유지. typecheck 통과.
2026-08-29 사용자 보고 두 건:
① 값을 고치고 [수정]하면 '추가·이동·삭제는 B05에서' 안내만 떴다 — B06에서 관
옵션 저장 경로가 없었다. 조정창 구간값과 같은 저장기(culvertOptions.queue)로
보내 캐시에 예약하고 [저장]·[확정]이 정본에 쓴다. 기준점 이동만 배수유역
재분할이 걸려 B05 몫으로 남기고, 안내 문구도 추가·삭제/이동으로 나눴다.
② 연동·경사 옵션과 단 수 행이 구조물 배치에서 사라졌다 — 그 행들은 고른 벽에
딸린 것이라 도면에서 벽을 찍기 전에는 이식될 창 자체가 없었다. 시설을 폼에
올릴 때 대표 벽(유출 기준벽, 없으면 유입)을 함께 골라 행이 서게 한다.
검증: 목록에서 배수관 선택 → 유출구 그룹에 [추가 기슭막이(단)]·[옵션] 표시.
길이 + 후 [수정] → '수정한 값은 [저장]·[확정] 때 반영됩니다' + 세션
b06:culvertopt에 inlet_revet_length_m 11 예약 확인. typecheck 통과.
- SectionView.setStationSelectListener 신설 — 카드(측점) 선택 변경 알림
- 좌측 패널 showAtChainage: 계곡 통과 시설(pipe_points) 우선, 없으면 그 측점을
덮는 구조물 정본 항목(점형 근사·구간형 범위), 둘 다 없으면 선택 해제
- 연동 링크 카드는 structureSpan.ownerOf로 소유 측점 시설을 보여 준다
검증(공용 브라우저): 4+4.3 배수관·7+9.7 세월교·10+0.9 BOX암거 카드 클릭 시
종류·기준 측점·목록 강조 일치. 링크 카드 4+0.0→4+4.3, 14+0.0→13+15.7 소유
측점 표시. 구조물 없는 0+0.0 카드는 폼 해제. typecheck 통과.
화면 실측에서 나왔다. 세션에 남은 미저장 구조물이 서버에 거절되자(등록되지 않은
타입 400) 예외가 B06 [저장] 전체를 중단시켜 sections/save가 아예 나가지 않았다.
flushPendingStructures를 .catch()로 감싸 토스트만 띄우고 횡단 저장은 계속한다.
미저장분은 세션에 그대로 남아 다음 [저장]에서 다시 시도된다.
CLAUDE.md 5장(조작·데이터 흐름 정책)을 코드에 반영한다. 조작분은 세션에만 쌓고
영구저장은 [저장]·[확정]에서만 하며, [초기화]는 재계산이 아니라 초기값 복원이다.
자동저장 폐지
- B06 조정창 기준벽 구간값: 800ms 디바운스 PUT을 없애고 세션(b06:culvertopt)에
담는다. flushCulvertOptions()를 B06 [저장]·[확정]과 B05 [저장]이 부른다.
- B05 구조물: 조작 즉시 PUT + 서버 재조회로 화면을 덮어쓰던 것을 세션
(b05:structures) 적재로 바꾼다. 저장 전에도 고르고 지울 수 있도록 식별자를
crypto.randomUUID()로 미리 발급한다(서버는 빈 값일 때만 새로 발급).
- B05에서 만지고 B06에서 확정하는 경로를 위해 flushPendingStructures()를 공용화.
초기값 스냅샷
- common_util_initial_snapshot: 자동설계 체인 성공 직후 정본 파일 4트리와
routes+자식 4표를 initial_snapshot/에 뜬다. 이후 읽기 전용.
- reset_route_design: 스냅샷이 있으면 DELETE와 같은 트랜잭션에서 행을 되세우고
파일을 되돌린다. 없으면 종전 재계산 폴백. 응답에 restored를 더한다.
조작 응답
- 등고선 재적용·B06 진입 정합·[모두 적용]에서 전체 화면 오버레이 제거.
- 재계산이 design을 갈아끼울 때 extra_spans를 보존한다(다른 조작값과 동일).
테스트: tmp/tests/test_initial_snapshot.py 5건 추가. 245 passed·8 failed(기존
실패 — 기슭막이 이관 때 placement가 interval→point로 바뀐 것을 테스트 미반영).
다단은 소유 벽 연장에 종속이라 길이·전/후를 따로 못 잡았다. 계곡부·능선부에서는
아래 단일수록 연장이 달라져 자동 규칙으로 못 잡으므로 수동 입력으로 간다.
조정창 UI는 기준벽 것을 그대로 쓴다(길이·기준측점 전·후 세 행).
- Const: EXTRA_SPAN_DEFAULT(10m·5/5) + applySpanPatch()로 길이↔전/후 산식을
기준벽과 공용화
- Station_Controls: StructureSpanControl에 tierValuesFor/updateTier 추가. 값은
세션(b06:extraspan)에 담고 캐시(design.extra_spans)에도 얹는다. 단 수를 줄이면
그 단의 구간값도 지운다
- View_Structure: 다단 키면 단별 제어기로, 기준벽은 종전 경로
- Wire: culvertReach에 단별 연장을 더하고, 링크 카드에서 각 단을 자기 구간값으로
판정한다(tierSpanOf)
- Corridor_Structures: 3D 스윕 범위를 단별 구간값으로
- 저장: CrossSectionPatch.extra_spans(프론트 payload + WallSpanPatch 스키마 +
확정 병합) — [저장]·[확정] 때 정본에 남는다
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
기슭막이 범위(길이·기준측점 전/후)를 B06 횡단도 조정창에서 지정하게 하고, 그
범위가 덮는 옆 측점의 기슭막이를 "연동" 파생물로 다루도록 체계를 세웠다.
값의 원천은 배수관 옵션(pipe_points) 하나다 — 같은 숫자를 B06 정본에 따로 담으면
B05 배수관 카드와 갈려 어느 쪽이 참인지 알 수 없다.
① 집수정 전/후 필드 신설 — inlet_basin_before_m·after_m(기본 각 1m), 길이 2m 유지.
종방향 구간 계산을 revetSpanOfSpec·basinSpanOfSpec 한 곳으로 모아 2D 링크 판정과
3D 스윕이 같은 값을 쓰게 했다.
② B06 조정창에 길이·전·후 행 — 기슭막이(유입·유출)·집수정 모두. 캐시를 고쳐 즉시
보여주고 저장은 조작이 멎으면 한 번만 나간다(세부유역 재계산이 매번 돌지 않게).
길이를 바꾸면 늘어난 몫만 지금 비율대로 나눠 담는다 — 총길이에서 비율로 다시
계산하면 0.1m 반올림이 쌓여 5.0/5.0이 5.6/5.4로 어긋난다.
③ 링크 카드 선택 허용 — "소유 측점 통째 복사"에서 "위치 4축만 물려받고 원지반
접합은 이 측점 지형으로 재계산"으로 바꿨다. 구조물 형식은 언제나 소유 측점 값이다.
손대기 전에도 어긋나지 않게 소유 측점이 실제로 적용한 4축(revetShift)을 명시값으로
물려준다.
④ 연동(측점별)·경사(전체 공통) 토글 — 연동 조작은 소유 측점 키에 실리고 그 연장이
덮는 측점을 함께 다시 그린다. 경사는 3D 전용 스위치다(끄면 스윕 프레임 dz=0).
횡단도까지 -dz로 밀면 노견에서 출발하는 성토선까지 내려가 노견과 벌어진다.
⑤ 가림 구간 선 정리 — 유입부 접속선과 유출부 접지 후 절토선을 2D·3D 모두에서
빼되 기하는 남긴다. 면적·수량 계산은 건드리지 않았다(전면 개편 대상).
⑥ 집수정은 소유 측점 횡단도 하나에만 — 링크 카드에서 집수정·계류측 다단을 뺀다.
⑦ 3D 구간 겹침 정리 — 측점 간격이 구조물 연장보다 좁으면 두 구간 제어점이 번갈아
서서 짧은 쪽(집수정 2m)이 잘려 보였다. 두 소유 측점의 중간에서 끊는다.
⑧ 조정창 정리 — 십자·9키를 맨 아래로, 창을 위쪽 기준 오버레이로(낮은 횡단도에서
제목이 잘리던 문제), 연동·경사를 1행 토글 버튼으로, 스크롤 자리 유지.
파일 분리: B06_Section_UI_Cross_View_Structure.ts(조정창 배선),
B06_Section_Api_Culvert_Options.ts(구간값 저장) — 700줄 제한.
BUILD_VERSION 14 → 17(저장 코리도 만료).
tsc/ruff/prettier 통과, pytest 196 passed(기존 실패 1건 유지).
작업이력·자체검증 기록: docs/raw/PLAN.md §4-9.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3D 예상형상은 종단·횡단 확정 뒤의 최종 산출물이고 3D 쪽 편집 경로는 없다
(2026-08-24 사용자 확정). 그런데 구조물(비정규) 측점은 횡단 정본 자체가 없어
조회할 때마다 프리뷰 기본값이 재계산됐고, 조정창 조작값은 세션에서 사라졌다.
정본 경로를 열고, 그 위에서 코리도 단차를 걷어냈다.
① 구조물 측점 정본화 — 확정·임시저장이 종단 정본 stations 중 DB 행이 없는
측점을 대상에 넣어 upsert. cross_sections 19 → 23행.
② 조정창 조작값 정본화 — 기슭막이 4축(x·d·h·m)·다단 단 수를 design.revet_adjust
/ extra_wall_counts로 저장하고, 3D culvertLayoutOf가 그 값을 기하 입력으로 쓴다.
카드 우선순위는 세션 → 정본 → 자동.
③ 노폭 연장의 노견 편입 — PIECE_COLS.shoulder 2→3으로 원 노견 끝 꺾임을 남기고,
조기 return을 없애 확폭이 구조물 구간 내내 유지되게 했다.
④ 종단 기울기 반영 — 구조물 구간 복사 단면에 계획고 차이를 전량 얹는다.
노견 표고 단차 0.49~0.61m 제거.
⑤ 접속 테이퍼 — 구간 경계를 제어점으로 세워 "복사 vs 보간" 두 규칙을 하나로
통합. 노폭 3.0m·성토비탈 8.1m 단차 제거. 접속 길이 = 인접 측점까지.
제어점 생성은 B05_Profile_UI_Corridor_Frames.ts로 분리(Build 699 → 635줄).
⑥ 인접 측점 링크 — 기슭막이 연장(기본 10m)이 덮는 옆 측점 횡단도에도 소유 측점
단면을 계획고 차이만 얹어 그린다. 요소별 연장 판정(관·집수정 제외), 표시 전용.
BUILD_VERSION 11 → 14 (저장 코리도 만료).
tsc/ruff/prettier 통과, pytest 192 passed (기존 실패 1건 유지).
작업이력·자체검증 기록: docs/raw/PLAN.md §4-8.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
자동 줌아웃 판정이 배수관용 slopeToeOffset(성토사면 5m에서 컷오프)을 써서, 사면이
5m를 넘는 측점은 필요 폭을 과소 산정했고 교차점이 화면 밖에 남았다(2026-08-23
사용자 보고).
- _UI_Cross_Fit 재작성: 설계선(design_line)과 지반선의 **실제 교차점**을 0.1m
간격으로 훑어 찾는다(노면·노견·측구 구간은 건너뜀). 설계선이 만나기 전에 끝나면
끝단 기울기 차로 외삽하고 1m 올림.
- 카드 자동 줌아웃(effectiveHalfWidth)이 이 값을 쓴다. 보유 샘플 밖으로는 넓히지
않는다(지반이 없어 빈 화면이 될 뿐).
- [적용]은 전 측점 중 필요한 최대 폭까지 함께 넓힌다 — 20m를 넘으면 그 값으로
재생성해 각 카드가 자동 줌아웃할 지반을 확보한다.
자체검증: node tmp/tests/test_toe_extrapolate.mjs (사면 18m 교차점을 5m 컷오프
없이 탐색, 설계선이 15m에서 끊겨도 외삽으로 같은 자리), tsc --noEmit 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 「횡단 보기 설정」 컨테이너 신설: 반폭 입력 + [적용] 버튼 한 줄. 표준 횡단면
설정의 [전체 측점 반영]에 물려 있던 반폭 적용을 떼어냈다 — 보유 샘플(20m) 안이면
전 측점 설계 재계산 없이 즉시 반영된다.
- 표준 횡단면 설정 컨테이너는 기본 접힘.
- 사이드 컨테이너 양식(여백·글자 크기·색)을 B05 .b05-route__panel-section 값으로
맞춤. 접힘 토글·외곽선은 이미 공용(ui-collapsible / ui-sidebar-section)이었다.
표준단면 「변수 위치 안내」 모식도 스타일은 현행 유지.
- 표준패널의 미사용 extraControl 인자 제거.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 카드 하단 ◀/▶/↺(개별 표시 반폭) 삭제. 조작구는 우측 상단 줌 버튼 하나로.
- 줌 버튼 역할 확장(원배율에서만 반폭 조작):
− 배율>1이면 축소, 원배율이면 표시 반폭 +1m
+ 배율>1이면 확대, 원배율이면 표시 반폭 −1m(하한 2m)
⤢ 배율 원복 + 절·성토 교차점이 다 보이는 폭으로 맞춤
- 교차점 맞춤(_UI_Cross_Fit): 보유 샘플(계산 반폭 20m) 안에서 교차점을 찾고,
샘플 끝까지 안 만나면 끝단 사면·지반 기울기 차로 만나는 자리를 추세 외삽해
올림 +1m을 요청 반폭으로 쓴다(상한 60m).
- 요청 반폭 > 보유 샘플이면 그 폭으로 재생성(regenerate) 후 해당 측점만 세션
반폭으로 넓게 표시. 계산 기본 20m·캐시는 그대로, 확정 시 기존 경로대로
display_half_width_m에 영구 저장.
- 재생성 로직을 _UI_Page_Common.createSampleWidener로 분리(700줄 제한),
[전체 측점 반영]도 같은 경로 사용. 연타 중복 요청 차단.
자체검증: node tmp/tests/test_toe_extrapolate.mjs (외삽 좌/우·발산 지형·+1m 올림),
tsc --noEmit 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-23 사용자 보고: 종단 그래프의 계획고 변화가 B06 횡단도에 반영 안 됨.
원인: 횡단 설계는 계산 당시 계획고(design.design_elevation_m) 기준으로 설계선
좌표를 굳혀 둔다. 계획선이 뒤에 바뀌면(사용자 편집·확정, 배수 최소고 같은
백엔드 규칙 도입) 설계선은 옛 자리에 남는데, 화면의 계획고 십자선과 3D
예상형상은 최신 계획선을 쓴다 - 두 기준이 어긋난다. 실측으로 한 노선에서
최대 2.21m 어긋남 확인. 지금까지 이 보정은 B06 진입 때만 돌아서, B05의 횡단
기준 유토곡선과 3D는 옛 설계선을 계속 썼다.
- staleDesignChainages() 신설(B06_Section_UI_Section_Common) - 계획선 보간값과
계산 기준의 차이로 재계산 대상을 판정하는 B05·B06 공용 규칙.
- B05 Profile_Panel: 진입 렌더에서 어긋남이 있으면 프리뷰 1회로 맞춘다.
- B06 Page: 같은 공용 규칙을 쓰도록 통일(옛 암 2단계 필드 누락 조건은 유지).
검증: tsc 통과 · pytest 191 passed(신규 stale 판정 6건) · 공용 브라우저 실측 -
계획고·지반고 배치 대조(전 측점 일치, 렌더 역산 RMS 0.004~0.03m), 측점 중심
좌표가 노선 폴리라인과 거리 0, 인위 stale 1건을 B05 진입으로 자동 복구(stale
1 to 0, 계획고 540.895 복원).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>