Skip to content

fix: 그리드 모드 click 좌표 단일 컬럼 가정 + getPageAtY X 무시 일괄 정정 (closes #685, #689)#693

Open
johndoekim wants to merge 14 commits intoedwardkim:develfrom
johndoekim:feature/issue-685-689-grid-mode-click
Open

fix: 그리드 모드 click 좌표 단일 컬럼 가정 + getPageAtY X 무시 일괄 정정 (closes #685, #689)#693
johndoekim wants to merge 14 commits intoedwardkim:develfrom
johndoekim:feature/issue-685-689-grid-mode-click

Conversation

@johndoekim
Copy link
Copy Markdown
Contributor

Stacked on PR #645 (Task #595)fix/issue-595-header-bbox-bleed 브랜치 위 cherry-pick. PR #645 머지 시 본 PR diff 가 자동으로 #685 + #689 commits 만 남음.

PR #645 의 후속 sweep (mydocs/troubleshootings/grid_mode_click_coord.md 진단노트) 에서 발견된 본 영역 결함 (#685, #689) 정합화. Task #685 + Task #689 합본 — 그리드 모드 click 좌표 한컴 호환 완성.

본질 결함 (2 영역)

Issue #685 — pageLeft 공식 단일 컬럼 가정 (14곳)

virtual-scroll.tszoom ≤ 0.5 + pages > 1 조건에서 다중 컬럼 그리드 (pageLefts[i] = marginLeft + col * (pw + gap)) 로 페이지 X 좌표를 열별 분리 저장. 그러나 input-handler-mouse.ts 의 14곳 모두 단일 컬럼 가정 공식 `(scrollContent.clientWidth - pageDisplayWidth) / 2` 사용 → 그리드 모드 click 좌표 ±수백 px 어긋남.

Issue #689 — getPageAtY 가 X 좌표 무시

`virtual-scroll.ts:133-140 getPageAtY(docY)` 가 Y 좌표만 보고 row 의 last page idx 만 반환 → non-last col 페이지 click 시 row 의 last col 페이지로 cursor 처리.

→ 두 결함이 결합되어야 그리드 모드 모든 col click 정합 (한컴 호환). #685 단독으로는 last col 만 정합. #685 발견 후 후속 sweep 으로 #689 등록.

한컴 호환 결함

한컴 오피스 그리드 모드 (다중 페이지 동시 보기) 는 모든 col 클릭 정상 처리 (사용자 직접 시연, 2026-05-07/08). RHWP 만 어긋남.

정정 영역

Task #685 — `getPageLeftResolved(pageIdx, containerWidth)` 헬퍼 도입 + 15곳 치환

  • `virtual-scroll.ts` 헬퍼 신규 (+12 LOC): 그리드 `pageLefts[i]`, 단일 컬럼 `(containerWidth - pageWidth) / 2` fallback (sentinel −1 해소).
  • `input-handler-mouse.ts` 14곳 + `input-handler.ts` 1곳 (`formBboxToOverlayRect` verbose sentinel 정리) = 15곳 헬퍼 일괄 치환.
  • e2e (`grid-mode-click-coord.test.mjs`) 회귀 assert 추가 (last-col strict).

Task #689 — `getPageAtPoint(docX, docY)` 헬퍼 도입 + 18곳 치환 + 누락 10곳 동반 정정

  • `virtual-scroll.ts` 헬퍼 신규 (+33 LOC): 단일 컬럼은 `getPageAtY` 동치, 그리드는 row(Y) 안에서 X 로 col 결정, gap 영역은 가장 가까운 페이지 fallback.
  • 마우스 컨텍스트 18곳 `getPageAtY` → `getPageAtPoint` 치환: input-handler-mouse 12곳 + input-handler.ts 4곳 + table 1곳 + picture 1곳.
  • Task rhwp-studio: zoom ≤ 0.5 그리드 모드 click 좌표 단일 컬럼 가정 — 14곳 분기 일괄 어긋남 #685 sweep 누락 buggy `pageLeft` 10곳 동반 정정 (작업지시자 추가 승인): input-handler.ts 4곳 + table 3곳 (L400/74/111) + picture 1곳 + connector 2곳 (L85/152). `getPageLeftResolved` 재사용 — 신규 좌표 계산 코드 0줄.
  • e2e SKIP 분기 제거 → 모든 col strict assert 일원화. zoom=0.25 col 0~4 5건 모두 probe 추가.

회귀 영역 좁힘 (의도적 미수정)

  • `canvas-view.ts` (vpCenter 2곳), `input-handler-keyboard.ts` (vpCenter 1곳) — viewport-center 의미 (X 무관) → `getPageAtY` 그대로 유지.
  • `coordinate-system.ts:18` (`documentToPage`) — dead code (호출자 0건) → 미수정.
  • `canvas-view.ts:156`, `field-marker-renderer.ts:84`, `caret-renderer.ts:127` — 기존 `getPageLeft` 호출 (raw accessor) 그대로 유지. 이미 그리드 인프라 정상 처리 중.

검증 (정량)

자동 검증 (모두 PASS)

검증 항목 결과
`npx tsc --noEmit` ✅ 무에러
`npx vite build` ✅ 성공 (85 modules transformed)
`body-outside-click-fallback.test.mjs --mode=headless` ✅ exit 0, 단일 컬럼 무회귀
`grid-mode-click-coord.test.mjs --mode=headless` PASS=21 / FAIL=0 / SKIP=0

Before vs After

케이스 Before After
zoom=1.0 page 0 (single col) rectPageIdx=0 ✅ rectPageIdx=0 ✅ (무회귀)
zoom=0.5 page 0 (col 0) rectPageIdx=1 ❌ rectPageIdx=0 ✅
zoom=0.5 page 1 (col 1 last) rectPageIdx=1 ✅ rectPageIdx=1 ✅
zoom=0.5 page 2 (col 0 row 1) rectPageIdx=3 ❌ rectPageIdx=2 ✅
zoom=0.25 col 0~3 (non-last) (어긋남) 모두 정합 ✅
zoom=0.25 col 4 (last) rectPageIdx=4 ✅ rectPageIdx=4 ✅

시각 검증 (작업지시자 직접, 2026-05-08)

`samples/hwpctl_action_table_v11.hwp` 그리드 모드 (zoom=0.5, columns=3) — 이전 보고된 "1, 2 페이지 클릭 안 됨" 결함 정합화. 작업지시자 평가: "오 정상적으로 잘 된다."

코드 품질 결정 (작업지시자 review, 2026-05-08)

`getPageLeftResolved(pageIdx, containerWidth)` API 의 `containerWidth` 인자 leaky abstraction 검토 — Option A (현재, stateless click 시점 `clientWidth` 직접 사용) vs Option B (`setPageDimensions` 시점 단일 컬럼도 실좌표 저장) 비교.

결정: Option A 유지. 이유:

  • A 는 stateless — race condition 없음, ResizeObserver 누락 케이스 robust, 미래 코드 변경 시 fragile 안 함
  • B 는 stateful — `pageLefts[i]` cache 가 setPageDimensions 호출에 의존
  • verbose trade-off 보다 robustness 우선

회귀 위험 영역

영역 위험 결과
단일 컬럼 모드 (zoom > 0.5) 헬퍼 fallback 식 회귀 ✅ OK — 명시 분기 + e2e 무회귀
그리드 모드 last-col #685 정합 영역 ✅ OK — strict assert 통과
그리드 모드 non-last col 본 PR 의 본질 효과 ✅ OK — 모든 col strict assert 통과
양식 오버레이 (`formBboxToOverlayRect`) 헬퍼 적용 동치성 ✅ OK — body-outside-click-fallback 무회귀
viewport-center (canvas-view, keyboard) 미수정 영역 ✅ OK — 기능 무관

산출물 (mydocs/)

코드 변경 (rhwp-studio)

  • `src/view/virtual-scroll.ts` (+45 LOC: getPageLeftResolved + getPageAtPoint)
  • `src/engine/input-handler-mouse.ts` (12곳 getPageAtY + 14곳 pageLeft)
  • `src/engine/input-handler.ts` (4곳 getPageAtY + 5곳 pageLeft)
  • `src/engine/input-handler-table.ts` (1곳 getPageAtY + 3곳 pageLeft)
  • `src/engine/input-handler-picture.ts` (1곳 getPageAtY + 1곳 pageLeft)
  • `src/engine/input-handler-connector.ts` (2곳 pageLeft)
  • `e2e/grid-mode-click-coord.test.mjs` (assert 강화 + 모든 col probe)

closes #685
closes #689

johndoekim and others added 13 commits May 7, 2026 01:48
본질 결함 위치 식별: src/renderer/layout.rs::build_header (line 928)
의 `expand_bbox_to_children` 호출이 머리말 자식 노드 (단 구분선 line
paraIdx=0 ci=2 h≈1227px) 의 bbox 까지 Header 영역으로 확장 →
hit_test_header_footer_native 가 본문 좌표를 머리말 hit 으로 잘못 인식 →
onDblClick 의 머리말 분기가 picture selection 분기보다 먼저 실행되어
수식 편집기 진입 차단.

발현: page 0 (1p) Header hit 60~145 정상 / page 1+ (2p~) 60~1355 결함
(본문 영역 80% 침범). 페이지 0 vs 1+ 차이 = 단 구분선 line 의 controlIdx
소속 차이 (page 0 ci=5 Body 자식 / page 1+ ci=2 Header 자식).

산출물:
- tests/issue_595.rs (5 케이스, 정정 전 3 fail / 2 pass)
- examples/inspect_595.rs (Header bbox sweep + 광범위 fixture sweep)
- rhwp-studio/e2e/issue-595.test.mjs (1365×1018 사용자 환경 e2e 진단)
- mydocs/plans/task_m100_595.md / task_m100_595_impl.md
- mydocs/working/task_m100_595_stage1.md

광범위 sweep (164 fixture / 1684 페이지): 영향 2 fixture / 32 페이지
(1.9%) — exam_math.hwp / exam_math_no.hwp 만 발현. 매우 특수한 양식
(머리말에 단 구분선 line 자식 포함). 정정 회귀 위험 매우 낮음.

이슈 본문 정오표: 이슈 가설 (a)/(b)/(c) 모두 TS 측 좌표 영역으로
한정되어 본질 좁히기 부족. 실제 결함은 Rust 측 build_page_tree 영역
(이슈 점검 범위 밖). 사용자 추가 단서 "hover 시 손바닥 표시는 뜨는데
클릭 반응 없음" 으로 dblclick 흐름의 다른 분기 (머리말 검사) 까지
진단 영역 확장.

Stage 2 정정 옵션 A 권장: hit_test_header_footer_native 에서
layout.header_area 로 hit 판정 (단일 함수 영역, 렌더링 동작 무영향,
회귀 위험 최소).

별도 발견: zoom ≤ 0.5 그리드 모드 좌표 결함 (input-handler-mouse.ts
의 pageLeft 단일 컬럼 가정) — 본 이슈와 별개 영역 / 별도 task 분리
검토.

refs edwardkim#595

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
본질 정정: src/document_core/queries/cursor_rect.rs::hit_test_header_footer_native
가 build_page_tree 의 Header/Footer 노드 bbox (expand_bbox_to_children 으로
자식 단 구분선 line 까지 확장됨) 대신 layout.header_area / layout.footer_area
(PageDef margin 으로 계산된 정확한 영역) 로 hit 판정.

LOC: +20 / -13 (단일 함수). build_page_tree 호출 제거 → mousedown 마다
호출되던 비싼 트리 빌드 비용 제거 (부수 효과).

검증 (모두 정정 전 → 정정 후):
- tests/issue_595.rs: 3 fail / 2 pass → 5 pass
- 본문 false-positive sweep: 32p → 0p
- 머리말 hit 정확화: +27p 부수 개선
- 꼬리말 hit: 동일 (회귀 0)
- cargo test --lib --release: 1140 passed (회귀 0)
- cargo clippy --release: clean
- WASM 빌드: 4,531,883 bytes
- 작업지시자 직접 시각 판정 ★ 통과 (정정 전 결함 / 정정 후 정상 모두 확인)

회귀 검증 도구: examples/inspect_595_regression.rs (영구 보존, 향후
hit_test_header_footer 영역 회귀 차단 가드).

별도 발견:
- 꼬리말 hit:false 16p (hwpctl_Action_Table__v1.1.hwp 1 fixture) — 정정 전후
  동일, 본 task 영역 밖
- zoom <= 0.5 그리드 모드 좌표 결함 — input-handler-mouse.ts 의 pageLeft
  단일 컬럼 가정, 본 이슈 별개 영역
모두 별도 issue/task 분리 검토.

refs edwardkim#595

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
본 task 종결 — Issue edwardkim#595 (exam_math.hwp 2페이지부터 수식 더블클릭
hitTest 오동작) 본질 정정 + 광범위 회귀 sweep + 작업지시자 시각 판정 ★
통과 + 이슈 본문 정오표 코멘트 등록 (#issuecomment-4389951401).

회귀 위험성 영역 점검 — 관련 CLOSED 이슈 (edwardkim#236 PageAreas 영역 공식 /
edwardkim#42 머리말 Picture 렌더링 / edwardkim#36 머리말 표 셀 이미지 / edwardkim#340 머리말 누출)
모두 회귀 위험 0. 본 정정이 edwardkim#236 정정의 영역을 일관 활용 (정확성
강화). 관련 함수 (hit_test_in_header_footer_native, get_active_hf_info,
find_section_for_page) 무수정. TS 측 호출처 단 2곳 (input-handler-mouse.ts
L494 onMouseDown / L784 onDblClick) 모두 정정 후 동작 정확.

보조 메모 영역 (본 task 분리) — 그리드 모드 (zoom ≤ 0.5) 좌표 결함 +
hwpctl_Action_Table 꼬리말 hit:false. 정정 전후 동일 (회귀 0) + 사용자
시각 검증 안 됨 + 한컴 호환 진단 필요 → 본 사이클 영역 밖, 등록 보류.
향후 사용자 시각 검증 또는 한컴 호환 비교로 결함 확정 시 별도 task 진입.

산출물:
- mydocs/report/task_m100_595_report.md (최종 보고서)

mydocs/orders/yyyymmdd.md 는 메인테이너 영역 (PR edwardkim#558 패턴 정합) — 본
PR 에서 제외, 메인테이너 PR 처리 후속에서 작성.

본 task 단계별 commit:
- Stage 1 (54c0af2): 본질 진단 + 재현 단위 테스트 + 광범위 sweep
- Stage 2 (0d20917): hit_test_header_footer 영역 정정 + 회귀 sweep
- Stage 3 (본 commit): 최종 보고서 + 회귀 위험성 점검

closes edwardkim#595

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…dwardkim#686 등록

edwardkim#595 Stage 3 보고서 §7 의 보류 2건 본질을 e2e 측정으로 확정:
- 보류 ① (Issue edwardkim#685): 그리드 모드 click 좌표 — input-handler-mouse 14곳
  단일 컬럼 가정. zoom=0.5/0.25 columns=2/5 측정 시 ±285.6/±581.3px 어긋남
- 보류 ② (Issue edwardkim#686): master page 글상자 dblclick 시 첫 페이지 점프 —
  isInTextBox 분기 + cursor.rect.pageIndex=0 + scrollCaretIntoView fallback
  (e2e: scroll delta=-11288, visible=[0,1])

산출물:
- mydocs/troubleshootings/grid_mode_click_coord.md  (정량 측정 표 포함)
- mydocs/troubleshootings/body_outside_click_fallback.md  (가설 b 확정)
- rhwp-studio/e2e/grid-mode-click-coord.test.mjs  (회귀 검증용)
- rhwp-studio/e2e/body-outside-click-fallback.test.mjs  (회귀 검증용)
- rhwp-studio/.gitignore: public/samples/ 측정용 심볼릭 링크 제외

소스 무수정. 정정 task 는 edwardkim#685/edwardkim#686 별도 진행.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
작업지시자 정정 (2026-05-07): 한컴 오피스도 다중 페이지 그리드 모드가
존재하며, 거기서 click 좌표는 정확히 처리됨. RHWP 만 어긋남 → 본 결함은
"한컴 호환 무관" 이 아닌 **한컴 호환 결함** 으로 정정.

- 영향 범위 섹션: 한컴 그리드 모드 정상 동작 명시
- "한컴 호환 무관성" → "한컴 호환 결함" 섹션명/본문 정정
- 기대 동작: 한컴 그리드와 동일하게 cursor 배치

Issue edwardkim#685 본문도 동일하게 정정 (gh issue edit).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…rlayRect 단순화

이슈 edwardkim#685 (zoom ≤ 0.5 그리드 모드 click 좌표 단일 컬럼 가정 — 14곳
분기 일괄 어긋남) 정정 작업의 1/3 단계.

본 단계는 동치 refactor (동작 변경 없음):

- virtual-scroll.ts: getPageLeftResolved(pageIdx, containerWidth) 헬퍼
  추가. 그리드 모드는 pageLefts[i], 단일 컬럼은 (containerWidth -
  pageWidth) / 2 fallback. 기존 getPageLeft(pageIdx) 는 raw accessor 로
  보존 (canvas-view 등 4 호출자 무회귀).

- input-handler.ts: formBboxToOverlayRect 의 verbose sentinel 패턴
  (`getPageLeft(pageIdx) >= 0 ? : (contentWidth - pageDisplayWidth) / 2`)
  을 헬퍼 호출 한 줄로 정리. pageDisplayWidth 변수는 사용처 없어 제거.

검증:
- npx tsc --noEmit 통과
- npx vite build 성공 (85 modules transformed)
- e2e body-outside-click-fallback.test.mjs --mode=headless exit 0 (단일
  컬럼 모드 sentinel fallback 경로 동치성 확인)

수행계획서: mydocs/plans/task_m100_685.md
구현계획서: mydocs/plans/task_m100_685_impl.md
단계 보고서: mydocs/working/task_m100_685_stage1.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ick 좌표 정정

이슈 edwardkim#685 의 본질 정정. zoom ≤ 0.5 그리드 모드에서 페이지 X 좌표를
열별로 분리 저장(pageLefts[i] = marginLeft + col*(pw+gap))함에도
input-handler-mouse 의 14개 분기 모두 단일 컬럼 가정 공식
`(clientWidth - pageWidth) / 2` 만 사용하여 ±수백 px click 좌표 어긋남.

Stage 1 에서 도입한 getPageLeftResolved(pageIdx, containerWidth) 헬퍼로
14곳을 일괄 치환:

영향 분기 (14곳, 핵심 사용자 영향 굵게):
  L23   onClick (연결선 모드)
  L129  onClick (선택된 표 이동)
  L176  onClick (다중 그림 선택 BBOX)
  L279  onClick (직선 끝점 핸들, picBbox.pageIndex)
  L296  onClick (회전 핸들, picBbox.pageIndex)
  L357  onClick (단일 그림 본체)
  L431  onClick (셀 선택 표 리사이즈)
  **L475  onClick (일반 click main path)**
  **L811  onDblClick (머리말/꼬리말 진입, sc as HTMLElement)**
  **L889  onContextMenu**
  L931  onMouseMove (연결선 미리보기)
  L1146 onMouseMove (그림 hover)
  L1196 onMouseMove (표 hover)
  L1243 handleResizeHover (표 경계선 hover)

치환 방식 (6 회 Edit):
- picBbox.pageIndex 2곳 (L279, L296) 컨텍스트 단건 Edit
- (sc as HTMLElement) 캐스트 1곳 (L811) 단일 Edit
- 나머지 11곳: 패턴별 replace_all 3 회 (4-space, 2-space prefix 분리)

`pw` / `pageDisplayWidth` 변수 선언은 hit test bbox 등 다른 용도 가능성으로
보존 — 표현식만 헬퍼 호출로 교체.

검증:
- grep sweep: `clientWidth - X) / 2` 패턴 0 건 잔여
- getPageLeftResolved 호출 수 14 (정확)
- npx tsc --noEmit 통과
- npx vite build 성공
- e2e body-outside-click-fallback.test.mjs --mode=headless exit 0
  (단일 컬럼 모드 click 좌표 무회귀 — 헬퍼 sentinel fallback 경로 정상)

본 정정의 본질 효과(그리드 모드 click 좌표 정합)는 Stage 3 에서
grid-mode-click-coord.test.mjs assert 강화로 자동 회귀화 예정.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ardkim#689 분리

이슈 edwardkim#685 정정 작업 3/3 단계. e2e grid-mode-click-coord.test.mjs 의
측정 로깅에 회귀 assert 추가하여 Task edwardkim#685 (pageLeft 공식 정정) 의
효과를 자동 회귀화.

assert 추가:
- dumpGridState: getPageLeftResolved(i, clientWidth) == 기대값
  (그리드 모드 pageLefts[i] 또는 단일 컬럼 fallback 공식, 모든 페이지
  max|delta| < 0.01 px)
- probeClickAtPage: CORRECT click → cursor.pos !== null (전 케이스),
  CORRECT click → cursor.rectPageIdx === pageIdx (last col 만 strict,
  non-last col 은 후속 결함 edwardkim#689 로 SKIP 안내)

추가 probe:
- zoom=0.25 last col (page 4) — 5-column 그리드 정합 검증
- zoom=1.0 page 0 — 단일 컬럼 baseline 무회귀 검증

검증 결과 (--mode=headless, exit 0):
  PASS=11 / FAIL=0 / SKIP=2 (의도된 non-last col 스킵)

Stage 3 진행 중 발견된 후속 결함 (Issue edwardkim#689 등록):
  virtual-scroll.ts getPageAtY(docY) 가 Y 좌표만 보고 row 의 last
  page idx 만 반환 → non-last col 페이지 click 시 row 의 last col
  페이지로 잘못 처리됨. Task edwardkim#685 의 pageLeft 정정과 별개의 결함
  영역으로, scope 엄격 준수 결정에 따라 별도 타스크 (edwardkim#689) 에서 진행.

Task edwardkim#685 의 정정은 last col 케이스 (zoom=1.0 col 0, zoom=0.5 col 1,
zoom=0.25 col 4 등) 에서 완전 정합 확인. 진단노트
(grid_mode_click_coord.md) 끝부분에 "정정 완료 — 부분 정정" + edwardkim#689
후속 안내 기록 추가.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ial — edwardkim#689 후속)

이슈 edwardkim#685 (그리드 모드 zoom ≤ 0.5 click 좌표 단일 컬럼 가정 14곳 분기 일괄
어긋남) 정정 완료. 본문 명시 범위 (pageLeft 공식 14곳) 그대로 정정 →
last col 정합. 작업지시자 시각 검증 (hwpctl_action_table_v11.hwp) 으로
last col 정상 동작 + non-last col 어긋남 = edwardkim#689 시각 재현 확인.

Stage 1+2+3 자동 검증 통과:
- typecheck / vite build / body-outside-click-fallback 무회귀
- grid-mode-click-coord PASS=11/FAIL=0/SKIP=2 (의도)
- last-col 정합: zoom=1.0 page 0, zoom=0.5 page 1, zoom=0.25 page 4
  모두 helperResolved max delta=0.00px + cursor.rectPageIdx 정합

부분 정정 명시:
- 진단노트 (grid_mode_click_coord.md) + 본 결과보고서 + orders
  모두 "Task edwardkim#685 = 부분 정정, edwardkim#689 후속" 명시
- Issue edwardkim#689 분리 등록 (Stage 3 e2e assert 강화로 노출): getPageAtY 가
  Y-only 로 row last page 만 반환 → non-last col click 어긋남.
  정정 방향: getPageAtPoint(docX, docY) 헬퍼 + 14곳 일괄 치환.
- 작업지시자 결정 (scope 엄격 준수): edwardkim#685 본문 범위 유지 + edwardkim#689
  분리 + 즉시 edwardkim#689 시작.

산출물:
- mydocs/report/task_m100_685_report.md (최종 결과보고서)
- mydocs/orders/20260508.md (5/8 오늘 할일)

후속 머지 절차 (작업지시자 권한):
- git merge local/task685 --no-ff -m "Merge local/devel: Task edwardkim#685 ..."
- gh issue close 685 (부분 정정 명시 코멘트 권장)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
이슈 edwardkim#689 (그리드 모드 getPageAtY X 좌표 무시 — non-last col 페이지 click
어긋남) 정정 작업의 1/3 단계.

본 단계는 헬퍼 도입 (단일 컬럼 모드에서 동작 변경 없음):

- virtual-scroll.ts: getPageAtPoint(docX, docY) 헬퍼 신규 (+33 LOC).
  단일 컬럼은 getPageAtY 동치, 그리드 모드는 row(Y) 결정 후 같은 row
  안에서 X 가 속하는 페이지 반환. gap 영역(페이지 사이) click 은
  가장 가까운 페이지로 fallback. getPageAtY 자체는 미수정 (viewport-
  center 호출자 보존).

호출자 분류 확정:
- 마우스 컨텍스트 (Stage 2 치환 대상, 20곳):
  input-handler-mouse 14곳 + input-handler.ts 4곳 (L612/875/972/1542)
  + input-handler-table 1곳 + input-handler-picture 1곳
- viewport-center 영역 (미수정): canvas-view 2곳 + input-handler-keyboard 1곳
- coordinate-system.ts:18 — dead code (documentToPage 호출자 0건),
  본 작업 범위 외

수행계획서 scope 확장 (작업지시자 결정, 2026-05-08): 마우스 컨텍스트
6곳 (input-handler.ts 4곳 + table/picture 2곳) 모두 getPageAtY 호출
직후 buggy pageLeft 패턴 (edwardkim#685 sweep 누락분) 도 함께 갖고 있어, 본
작업 안에서 동반 정정. 추가 헬퍼 신규 없음 (edwardkim#685 의 getPageLeftResolved
재사용).

검증:
- npx tsc --noEmit 통과
- npx vite build 성공 (389ms)
- e2e body-outside-click-fallback.test.mjs --mode=headless exit 0
  (단일 컬럼 모드 동치성 보장 확인)

수행계획서: mydocs/plans/task_m100_689.md
구현계획서: mydocs/plans/task_m100_689_impl.md
단계 보고서: mydocs/working/task_m100_689_stage1.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…dwardkim#685 누락 buggy pageLeft 10곳 동반 정정

이슈 edwardkim#689 (그리드 모드 getPageAtY X 좌표 무시 — non-last col 페이지
click 어긋남) 의 본질 정정. Stage 1 에서 도입한 getPageAtPoint 헬퍼로
마우스 컨텍스트 호출자 18곳 일괄 치환 + Task edwardkim#685 sweep 누락분 buggy
pageLeft 10곳 동반 정정.

getPageAtY → getPageAtPoint 치환 (18곳):
  input-handler-mouse.ts 12곳 (3 회 replace_all: cy/y/contentY)
  input-handler.ts 4곳 (L612/875/972/1542, 2 회 replace_all: cY/contentY)
  input-handler-table.ts 1곳 (L400)
  input-handler-picture.ts 1곳 (L594)

buggy pageLeft → getPageLeftResolved 동반 정정 (10곳):
  input-handler.ts 4곳 (L612/875/972/1542, getPageAtY 와 동일 함수)
  input-handler-table.ts 3곳 (L400 동일 함수 + L74/L111 추가 발견)
  input-handler-picture.ts 1곳 (L594 동일 함수)
  input-handler-connector.ts 2곳 (L85/L152 추가 발견)

추가 발견 4곳 (table L74/L111 + connector L85/L152) 은 getPageAtY 호출
없이 (state.edge.pageIndex 또는 함수 매개변수에서 pageIdx 획득) 도
buggy pageLeft 패턴 사용 — Task edwardkim#685 sweep 의 input-handler-mouse 한정
누락분. 작업지시자 추가 승인 (2026-05-08) 으로 동반 정정.

검증:
- grep sweep: clientWidth-X)/2 패턴 0 건 잔여
- getPageAtPoint 호출 19 (정의 1 + 사용 18 정확)
- getPageLeftResolved 호출 26 (정의 1 + edwardkim#685 사용 15 + edwardkim#689 사용 10 정확)
- npx tsc --noEmit 통과
- npx vite build 성공
- e2e body-outside-click-fallback.test.mjs --mode=headless exit 0
  (단일 컬럼 모드 양쪽 헬퍼 sentinel/Y-only fallback 무회귀)

수정 파일:
  rhwp-studio/src/engine/input-handler-mouse.ts
  rhwp-studio/src/engine/input-handler.ts
  rhwp-studio/src/engine/input-handler-table.ts
  rhwp-studio/src/engine/input-handler-picture.ts
  rhwp-studio/src/engine/input-handler-connector.ts

본 정정의 본질 효과 (그리드 모드 모든 col click 정합) 는 Stage 3 에서
grid-mode-click-coord.test.mjs 의 non-last col SKIP 분기를 strict
assert 로 활성화하여 자동 회귀화 예정.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ck 정합 자동 회귀화

이슈 edwardkim#689 정정 작업의 3/3 단계. Task edwardkim#685 Stage 3 에서 SKIP 처리했던
non-last col rectPageIdx 정합 assert 를 활성화하여 Task edwardkim#685 + edwardkim#689
결합 효과 자동 회귀화.

변경:
- grid-mode-click-coord.test.mjs probeClickAtPage:
  isLastCol 분기 + SKIP 로깅 제거 → 모든 col strict assert 일원화.
- [3b] zoom=0.25 last-col only probe → for 루프로 col 0~4 5건 모두
  probe (5-col 그리드의 모든 위치 정합 차단).
- 진단노트 grid_mode_click_coord.md 끝부분에 "완전 정정 완료 (Task
  edwardkim#685 + edwardkim#689 결합)" 섹션 추가 (getPageAtPoint 헬퍼 + 18곳 치환 +
  10곳 buggy pageLeft 동반 정정 + 모든 col 정합 표 + e2e 결과 기록).

검증 결과 (--mode=headless, exit 0):
  PASS=21 / FAIL=0 / SKIP=0 (이전 SKIP 2건 모두 PASS 로 전환)

세부 PASS:
- zoom=0.5/0.25/1.0 dumpGridState getPageLeftResolved 동치성 (3 PASS)
- zoom=0.25 col 0~4 cursor.pos + rectPageIdx 정합 (10 PASS)
- zoom=1.0 baseline cursor.pos + rectPageIdx (2 PASS)
- zoom=0.5 page 0 (col 0) / page 1 (col 1) / page 2 (col 0 of row 1)
  cursor.pos + rectPageIdx (6 PASS)

이전 어긋남 모두 정합화:
- zoom=0.5 page 0: rectPageIdx 1→0
- zoom=0.5 page 2: rectPageIdx 3→2

단일 컬럼 baseline (zoom=1.0) 무회귀 유지.

본 정정의 본질 효과 = Task edwardkim#685 + edwardkim#689 결합으로 그리드 모드 click
한컴 호환 완성. 시각 검증은 작업지시자 환경에서 1회 직접 확인 권장.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
이슈 edwardkim#689 (그리드 모드 getPageAtY X 좌표 무시 — non-last col 페이지
click 어긋남) 정정 완료. Task edwardkim#685 누락분 buggy pageLeft 10곳도 동반
정정. Task edwardkim#685 + edwardkim#689 결합으로 그리드 모드 click 한컴 호환 완성.

Stage 1+2+3 자동 검증:
- typecheck / vite build / body-outside-click-fallback 무회귀
- grid-mode-click-coord PASS=21/FAIL=0/SKIP=0 (모든 col strict)
- zoom=0.5 col 0/1, zoom=0.25 col 0~4, zoom=1.0 모두 rectPageIdx 정합

작업지시자 시각 검증 (hwpctl_action_table_v11.hwp, 2026-05-08):
이전 "1, 2 페이지 클릭 안 됨" 결함 정합화 — "오 정상적으로 잘 된다."

코드 품질 검토 결정 (작업지시자 review, 2026-05-08):
- getPageLeftResolved API 의 containerWidth 인자 leaky abstraction
  검토 → Option A (stateless click-time 측정) 유지
- 이유: race condition 없음, ResizeObserver 누락 케이스 robust,
  미래 변경 시 fragile 안 함. verbose trade-off 보다 robustness 우선

산출물:
- mydocs/report/task_m100_689_report.md (최종 결과보고서)
- mydocs/orders/20260508.md (edwardkim#689 [완료] + 추가 발견 4곳 + Option A 결정 메모)

후속 머지 절차 (작업지시자 권한):
- git merge local/task689 --no-ff -m "Merge local/devel: Task edwardkim#689 ..."
- gh issue close 689

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant