Files
Aislo/docs/raw/plans/2026-09-01_plan_B07_usability_verification_hold.md
T
eomsangdonandClaude Opus 5 eb30b774f8 chore(docs): docs 폴더 git 추적 전환 · PLAN·OWNERS 최상위 이관
- `docs/` 를 .gitignore 에서 빼 git 추적 대상으로 전환 (위키·완료 이력·검증 기록)
- `docs/raw/PLAN.md` · `docs/raw/OWNERS.md` 를 저장소 최상위로 이동 후 .gitignore 에 등록
  — 창끼리 공유하되 저장소에는 안 올리는 장부
- 살아 있는 경로 참조 7개 파일 정정 (아카이브 107건은 그때 사실이라 그대로 둠)
- graphify 날짜별 산출물(`docs/wiki/graphify-out/20*/`) 제외 — `graphify update` 가 다시 만듦

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 18:19:07 +09:00

18 KiB

B07 사용자 편의성 전면 정리 — 교차검증 보류 원문

  • 상태: 구현·사용자 실측 완료 기록, Codex 교차검증 실패로 완료 아카이빙 보류
  • 상시 계획 후속: docs/raw/PLAN.md의 「B07 사용자 편의성 전면 정리 — 교차검증 보류」
  • 위키: docs/wiki/pages/B08_DesignDetail/B08_CAD_usability_2026-09-01.md
  • 실패 근거: docs/raw/verification/2026-09-01_verify_remaining_checked_items.md

B07 사용자 편의성 전면 정리 (2026-09-01 신규, 보조폴더)

배경: 사용자 지시로 B07 상세설계를 사용자 편의성 관점에서 전수 점검했다. 공용 브라우저(5174, 프로젝트 wdw c1bb453f)로 실제 조작해 15건을 짚었고, 사용자가 항목별로 반영 여부를 확정했다(2026-09-01). 아래는 반영 확정분 12건이다.

반영하지 않는 것(사용자 확정): ⑤ 진행단계 패널 겹침 — 현행 유지. ⑭ 목록 조회가 오래된 횡단 파일을 지우는 것 — 현행 유지. ⑮ 명령행이 하단 86px 차지 — 현행 유지.

① 편집 손실 방지 — 도면을 바꿔도 작업이 사라지지 않게

실측 재현(2026-09-01, 19_dataloss.py): 종단면도에 선 하나를 긋고(캔버스 서명 398928318 → 2270709582) 표지로 옮겼다가 돌아오니 서명이 원본과 완전히 동일 (398928318). 그은 선이 사라졌고 확인창도 뜨지 않았다(DIALOGS_ON_SWITCH=[]). UI_Page.ts:454 loadDrawing에 미저장 검사가 없고, 브리지가 받는 즉시 setEntities(…,false)로 덮은 뒤 resetUndoBaseline()으로 되돌리기 기록까지 지운다.

교차검증 상태(2026-09-01): 구현 기록은 보존하되 CAD 전체 시험 86건 중 6건 실패로 완료 아카이빙을 보류한다. 아래 항목은 재검증 대기 상태다.

  • CAD에 미저장 여부를 뒀다 (2026-09-01 완료). state.ts의 drawingDirty를 setEntities(…,true)·undo·redo에서 세우고, resetUndoBaseline()(도면 적재)과 저장 응답에서 내린다. 브리지가 drawing-changed에 dirty를 실어 부모가 받는다.
    • 단위 시험 src/state.test.ts 2건: 편집→섬/적재→내려감, 되돌리기도 미저장.
  • 도면을 바꾸기 전에 묻는다 (2026-09-01 완료). mayDiscardEdits()를 selectDrawing·navigateDrawing 앞에 뒀다. 겹쳐 누르면 늦게 온 응답이 화면을 덮던 것도 loadInFlight 가드로 막았다.
    • 자체검증(42_verify_guard.py, 종단면도): 선 하나 그어 n=487→488 dirty=true. 표지를 누르니 경고가 떴고(문구 확인), [취소]에 n=488 그대로. 다시 눌러 [확인]에 표지로 이동(n=14, dirty=false). 편집이 없는 상태로 토적도를 누르니 묻지 않았다. 고치기 전에는 경고 없이 n이 원본으로 돌아갔다.

② 배경 도면층은 마우스가 가도 반응하지 않게 (등고선·도각)

사용자 지시: 배수유역도의 수치지형 등고선을 도각처럼 배경으로 다룬다. 단, 마우스가 근처로 가도 하이라이트하지 않는다 — 도각도 포함.

등고선(b08-basin-contour)·계류·도각은 이미 locked=True라 선택·스냅에서 빠진다 (Engine_Cad_Basin.py:411-417). 남은 구멍은 하이라이트다 — input-controller.ts:272는 getPickableEntities()(잠금 제외)를 쓰지만 main.tsx:97은 queryEntitiesNearPoint() 결과를 거르지 않고 그대로 넣는다.

  • 근접 하이라이트에서 잠금 도면층 객체를 뺐다 (2026-09-01 완료). 거르는 자리는 state.ts:setHighlightedEntityIds() 한 곳 — main.tsx·input-controller.ts가 모두 이 함수를 지나므로 호출부가 늘어도 새지 않는다.
    • 자체검증(33_verify_rest.py): 도각만 있는 화면(표준도)에서 마우스를 가로로 40지점 훑어 하이라이트 0회.
    • 단위 시험 src/state.test.ts 2건: 잠금+자유 섞이면 자유만, 잠금뿐이면 빈 목록.

③ 준비 중 도면도 도각은 보이게

사용자 지시: 아직 만들지 않은 도면(계획평면도 4종·표준 횡단면도·표준도·용지도)도 누르면 빈 도각이라도 나오게 한다. 지금은 13칸 중 7칸이 눌리지 않는 회색 버튼이다 (17_overlap.py: disabled:7 / total:13).

  • 백엔드에 빈 도각 도면을 열었다 (2026-09-01 완료). Router_Support.BLANK_DRAWINGS 7종을 kind="blank"로 목록에 싣고, 단건 조회는 build_blank_drawing()이 도각(잠금)과 자유 도면층 작도 한 장을 낸다. 도면명은 도각 표제란에 채운다.
  • 프런트 DRAWING_GROUPS의 kind 없는 항목을 blankId로 물렸다. disabled를 없애고 "준비 중"은 옅은 표시(data-pending)와 툴팁으로만 남겼다.
  • 준비 중 도면은 확정 대상이 아니다 — applyConfirmButtonState()가 blank이면 확정 버튼을 비활성으로 둔다. all_confirmed(횡단만)는 손대지 않았다.
    • 자체검증(30_verify_reload.py·33_verify_rest.py): 목록 13칸 중 disabled 0 (고치기 전 7). 표준도를 누르니 도면층 작도(자유)·도각(잠금)이 실렸고 확정 버튼은 비활성이었다.
    • 시험 tmp/tests/test_b07_usability_2026_09_01.py 2건: 7종 라벨·id 중복 없음, 빈 도면에 도각(잠금)과 자유층이 함께 있음.

④ 도면층 목록 버튼이 잘리지 않게

실측(17_overlap.py): 도면층 행 5개 모두 내용 폭이 보이는 폭을 넘는다 (scrollW 237~286 / clientW 229). aside도 scrollW 267 / clientW 247, overflow-x: visible — 잘린 채 넘쳐 삭제 아이콘이 반만 보인다.

  • 이름은 줄이고 아이콘은 줄지 않게 잡았다 (2026-09-01 완료). 이름에 min-width:0+text-overflow:ellipsis, 아이콘 묶음에 flex-shrink:0, 행 안 아이콘 버튼 좌우 여백을 8px→2px로 줄였다.
  • 패널을 248px→300px로 넓혔다. 이름만 줄이니 이름 몫이 21px밖에 남지 않아 글자가 뭉개졌다(아이콘 6개가 229px 중 176px을 먹는다). 넓히려면 Toolbar.tsx의 인라인 지정도 걷어야 했다 — 거기 248px이 박혀 있어 CSS의 --cad-panel-width를 덮고 있었다. 이제 접힘만 0px로 두고 편 폭은 CSS가 정한다.
    • 자체검증(32_verify_core.py·34_verify_readonly.py): 잘린 요소 0개 (고치기 전 5개), 이름 폭 21px→81px, 패널 폭 248→300.

⑥ 되돌리기로는 확정이 풀리지 않게

state.ts:528-542의 undo/redo가 무조건 DRAWING_CHANGED를 쏜다. 한 번 고쳤다가 Ctrl+Z로 원상복구해도 확정이 풀리고 서버로 해제 요청까지 나간다.

  • 되돌리기·다시하기는 확정을 풀지 않는다 (2026-09-01 완료). ⑧에서 부모가 drawing-changed로 확정을 푸는 경로 자체를 없애 해결했다 — 그 통지는 이제 미저장 표시만 세운다. undo/redo는 통지를 그대로 두되(미저장은 맞다) 확정 해제와는 무관해졌다.
    • 확정 도면은 애초에 되돌리기 명령이 막힌다(⑧ 읽기 전용, UNDO는 보기 명령이 아님).
    • 자체검증(35_verify_final.py): 그림 487→488 뒤 Ctrl+Z로 →487, 확정 해제 요청 없음(36_diagnose.py에서 네트워크 호출 0건).

⑦ 색상 선택기가 보이지 않는 문제 + 확정 해제 연발

실측(21_colorwidget.py): 색상 입력은 화면 안에 정상 배치돼 있다 (x:880 w:44 h:19 inView:true). 안 보이는 이유는 **값이 #ffffff**라 흰 종이 배경 위 흰 스와치이기 때문이다. state.ts:130 activeLineColor='#ffffff', :148 textColor:'#ffffff' — 검은 화면 CAD의 기본값이 흰 종이에 그대로 남아 있다. 그 상태로 그으면 선도 안 보인다.

  • 기본 그리기 색을 검정(#000000)으로 바꿨다 — 선·글자 둘 다 (2026-09-01 완료).
  • 색상 스와치 테두리를 진하게(--cad-text-muted) 줘 흰색을 골라도 자리가 보인다.
  • 색 고르는 동안 연발하지 않는다. paintNowCommitLater()가 드래그 중에는 화면만 칠하고(setEntities(…,false)), 멎고 350ms 뒤 한 번만 되돌리기·통지에 싣는다.
    • 자체검증(30_verify_reload.py): 색상 입력 value=#000000, 테두리 rgb(139,134,160)(전에는 배경과 같은 흰색이라 안 보였다).

⑧ 확정한 도면은 잠그고, 풀려면 [수정]을 누르게

사용자 지시: 확정하면 수정이 안 되게 한다. 수정하려면 확정 버튼 자리에 [수정] 버튼을 둬 명시적으로 푼다. 지금은 확정된 도면도 그대로 고쳐지고, 그 편집이 확정을 조용히 해제한다 — ⑥·⑦·⑩의 오작동이 모두 이 통로로 들어온다.

  • CAD에 읽기 전용 모드를 만들었다 (2026-09-01 완료). isDrawingReadOnly()가 참이면 runCommand()가 보는 명령만 통과시킨다 — 리본·명령행·단축키가 모두 이 함수를 지나므로 한 곳에서 막는다. 허용 목록(isViewOnlyCommand)은 허용을 명시하는 쪽으로 짰다: 보기·조회 전체 + 도면층 보이기/잠금 + 내보내기·복사. 새 편집 명령이 늘어도 자동으로 막힌다. 더블클릭 값 편집도 함께 막았다.
  • 도면을 실을 때 앞 도면의 그리기 도구를 내린다(runCommandInput('SELECT')). 이게 없으면 읽기 전용이 새 명령만 막아서, 켜 둔 채 확정한 도구는 확정본 위에도 계속 그렸다 — 실측으로 확정본에서 클릭 두 번에 선 2개가 늘었다.
  • 확정 버튼 자리를 상태에 따라 바꿨다 — 미확정 [현재 도면 확정] / 확정 [수정]. [수정]은 invalidate 뒤 원본 도면을 다시 실어 편집을 연다.
  • 확정 직후 저장본 재적재를 기다린다(await loadDrawing). 기다리지 않으면 오버레이가 먼저 걷혀 버튼은 [수정]인데 CAD는 아직 편집이 열린 순간이 생겼다.
  • 도면 변경 통지로 확정이 풀리던 경로를 없앴다 — CAD_CHANGED_MESSAGE는 이제 cadDirty만 세운다. 확정 해제는 [수정] 한 곳뿐이다.
    • 자체검증(40_verify_final2.py, 종단면도 487개): 그림 487→488 → 확정하니 btn=수정 readOnly=true dirty=false n=488 → 확정본에서 그리기 시도에 488→488 (막힘) → [수정]을 누르니 btn=현재 도면 확정 readOnly=false → 다시 그리니 487→488. 37_diagnose2.py로 명령 입력·클릭·Escape 전 단계에서 읽기 전용이 유지됨을 확인(호출 0건).
    • 단위 시험 src/state.test.ts 1건: confirmed 참/거짓/null에 따른 읽기 전용.

⑨ 수량표는 B07에서 고치지 않는다

사용자 확정: 수량표는 앞 단계(B05·B06) 결과물이다. 값을 바꾸려면 앞 페이지에서 고치고 다시 와야 한다. 지금은 도면 위 숫자를 더블클릭해 고칠 수 있는데 그림 글자만 바뀌고 저장되는 수량표 값은 그대로여서 화면과 저장값이 어긋난다 (aislo-drawing-bridge.ts:91-94가 setDesignQuantityTable을 부르지 않는다).

  • 더블클릭 편집에서 수량 산출표 도면층(b08-cross-table)을 뺐다 (2026-09-01 완료). 누르면 "수량표는 횡단설계(B06)에서 고칩니다"로 안내한다. 확정 도면이면 그보다 먼저 "[수정]을 누르세요"로 막힌다(⑧).
  • 쓰이지 않던 setDesignQuantityTable·notifyDrawingChangedByTable을 지웠다 — 둘 다 호출부가 하나도 없으면서 "고칠 수 있는 값"으로 오해를 남겼다.

⑩ 자동 백업을 도면별로 나눈다

백업 칸이 OPEN_WEB_CAD__RECOVERY 하나뿐이고 어느 도면 것인지 기록이 없다 (autosave.ts:34-47). 도면을 넘겨보면 마지막에 본 것만 남고, 그 백업을 되살리면 지금 열린 다른 도면 위에 붙는다.

  • 백업 키에 도면 id를 붙였다(…__RECOVERY__{drawingId}) (2026-09-01 완료). DesignMeta에 drawingId를 추가하고 브리지가 적재마다 setRecoveryScope()로 옮긴다.
  • 되살리기는 그 도면 칸만 읽는다 — 다른 도면 것이 붙지 않는다. 확정 도면에서는 되살리기 자체를 막았다(⑧과 같은 이유).
  • 서버 도면을 싣는 동안에는 백업하지 않는다(suspended) — 적재가 깨우는 UPDATE_STATE 때문에 5초 뒤 서버 도면이 사용자 백업을 덮고 있었다.
    • 자체검증(43_restore.py): 도면 넷을 열어 본 뒤 localStorage 키가 …__RECOVERY__longitudinal · __cover · __mass_haul · __blank_standard로 도면마다 따로 생겼다. 고치기 전에는 칸이 하나뿐이었다.

⑪ 횡단 장 번호가 구간을 따라가게

장 id를 순서대로 붙인다(Engine_Cad_Sheet.py:149 cross_s{번호}). B06 설계가 바뀌어 한 장에 담기는 측점 수가 달라지면 "1장"이라는 이름은 그대로인데 담는 구간이 달라지고 확정 표시도 그대로 남는다.

  • 장 id를 시작 측점 기준으로 바꿨다(cross_s00020m 꼴) (2026-09-01 완료). 구간이 달라지면 id도 달라지므로 옛 확정이 새 구간에 붙지 않는다.
  • 목록에 없는 id의 옛 확정 기록은 매니페스트에서 무시된다 — 확정 여부를 manifest_drawings.get(sheet["id"])로만 보므로 id가 달라지면 자연히 안 붙는다. 기록은 지우지 않는다.
  • 라벨의 "N장"은 그대로 뒀다 — 사용자가 부르는 이름은 바뀌지 않는다. CROSS_SHEET_ID는 옛 순번형(cross_s01)도 계속 읽는다(이미 확정한 매니페스트 호환).
    • 시험 tmp/tests/test_b07_usability_2026_09_01.py 2건: 장 나누기가 달라져도 같은 시작 측점이면 같은 id(cross_s00000m), 순번만 바뀐다. 패턴이 신형·옛형은 받고 측점 도면(cross_00020m)은 받지 않는다.
    • 화면 확인: 사이드 목록이 1장 (0~240m) · 2장 (260~350m) 그대로 보인다.

⑫ 회사 도각을 잘못 저장해도 페이지가 죽지 않게

저장할 때 entities가 목록인지만 본다(Router.py:415-417). 좌표가 빠진 도형 하나면 이후 모든 도면 요청이 500이 되고 되돌릴 버튼이 없다.

  • 저장 전에 도각 엔티티를 검사한다 (2026-09-01 완료). validate_template_entities()가 도면 조립이 실제로 쓰는 좌표(startPoint·endPoint· basePoint·point·center의 x·y와 radius)가 숫자인지 본다. 자식(children)까지 내려간다. 어긋나면 파일을 쓰지 않고 400으로 거절한다.
  • 기본 도각으로 되돌리기를 열었다. DELETE /projects/{id}/frame-template와 도각 편집 띠의 [기본 도각으로] 버튼(확인 후 실행).
    • 시험 tmp/tests/test_b07_usability_2026_09_01.py 7건: 정상값 3종 통과, 깨진 값 5종(좌표 없음·문자열·반지름 문자열·자식 좌표 None·도형 아님) 거절, 되돌리기가 파일을 지우고 두 번째 호출은 False.

⑬ 횡단 파일이 없어도 나머지 목록은 나오게

_cross_files가 cross_sections/ 없으면 예외를 던지고(Router_Support.py:69) _drawing_list 전체가 무너진다 — 표지·종단면도·토적도·유역도까지 함께 사라진다.

  • 횡단 장 계획이 실패해도 나머지 도면은 목록에 남긴다 (2026-09-01 완료). _drawing_list가 _cross_sheet_plan 실패를 잡아 그 그룹만 비우고 경고를 남긴다.
    • 사유를 응답에 싣는 것은 하지 않았다 — 횡단 그룹이 비면 사이드 패널이 이미 「횡단면도」 자리를 비워 보여 준다. 응답 스키마를 늘릴 만한 이득이 없다고 판단했다.
    • 시험 tmp/tests/test_b07_usability_2026_09_01.py 1건: 횡단 계획을 실패시키면 cross만 빠지고 longitudinal·cover·mass_haul·watershed·blank은 남는다.

자체검증 종합 (2026-09-01, 보조 워크트리 D:\aislo-wt\sub, 5174/8001):

갈래 결과
프런트 타입검사 npm run typecheck 통과
CAD 타입검사 npm run check-types 통과
CAD 단위시험 vitest src/state.test.ts 8건 통과 (신규 6건)
CAD 전체 vitest 81건 중 75건 통과. 실패 6건은 변경 전부터 있던 것 — 노드 환경에 window가 없어 나는 것으로, 2026-09-01 앞 판 기록과 같은 6건이다
파이썬 pytest tmp/tests/ 12건 통과 (신규 12건)
포맷 ruff format · prettier(변경 파일만)
화면 공용 브라우저 실조작 — 30·32·33·35~42 명령 파일

검증 중 드러나 함께 고친 것 (계획에 없던 것):

  • 확정 직후 재적재를 기다리지 않아 버튼은 [수정]인데 CAD는 편집이 열린 순간이 있었다 → await loadDrawing.
  • 읽기 전용이 새 명령만 막아 켜 둔 도구는 확정본에도 계속 그렸다 → 도면 적재 때 SELECT로 도구를 내린다.
  • 도면 전환을 겹쳐 누르면 늦게 온 응답이 화면을 덮었다 → loadInFlight 가드.
  • 도면층 삭제·객체 이동이 되돌리기에 안 실려 Ctrl+Z로 못 돌아왔다 → LayerManager의 두 곳에 setEntities(…, true).
  • 불러오기 실패가 화면에 아무 표시도 없었다(②의 짝) → data-loading·data-error를 받는 CSS와 토스트를 달았다. 유역도처럼 2~3초 걸리는 도면에서 "도면을 불러오는 중…"이 뜬다.

화면 상태 복구: 검증 중 바꾼 프로젝트(wdw)·테마(다크)·자동백업 칸을 사용자가 쓰던 값으로 되돌렸다 — 프로젝트 e418fcfc, 테마 light, 백업 칸 정리(44_restore2.py).