본질
`virtual-scroll.ts:133-140` 의 `getPageAtY(docY)` 는 Y 좌표만 보고 페이지를 결정.
```ts
getPageAtY(docY: number): number {
for (let i = this.pageOffsets.length - 1; i >= 0; i--) {
if (docY >= this.pageOffsets[i]) {
return i;
}
}
return 0;
}
```
그리드 모드에서 한 row 의 모든 페이지는 동일한 `pageOffsets[i] = rowTop` 을 가짐 (`virtual-scroll.ts:80` `pageOffsets.push(rowTop)`). 따라서 `getPageAtY` 는 항상 row 의 마지막 페이지(highest index) 만 반환.
영향
input-handler-mouse 의 14곳 모두 `getPageAtY(contentY)` 로 페이지 인덱스 결정 → row 의 non-last 컬럼 페이지를 click 해도 last 컬럼 페이지의 인덱스로 잘못 처리.
정량 측정 (Task #685 Stage 3 e2e, 2026-05-08)
`samples/exam_kor.hwp` 16p, viewport 1600×1000, headless Chrome.
zoom=0.5 (columns=2):
| 의도 페이지 |
col |
hwpX |
CORRECT click 좌표 |
실제 cursor.rectPageIdx |
| 0 |
0 |
100 |
(273.8, 242) |
1 ❌ (기대 0) |
| 1 |
1 |
100 |
(845.0, 242) |
1 ✅ |
| 2 |
0 |
100 |
(273.8, 1045.7) |
3 ❌ (기대 2) |
→ col 0 (non-last) 페이지 click → row 의 last col 페이지로 cursor 이동.
zoom=0.25 (columns=5) 에서는 col 0~3 모두 col 4 (last) 로 잘못 처리 (5컬럼 중 4컬럼 영향).
한컴 호환 결함
한컴 오피스 그리드 모드는 click 한 페이지의 정확한 위치에 cursor 배치 (Task #685 참고). RHWP 만 X 좌표 무시 → 한컴 호환 결함.
Task #685 와의 관계
Task #685 (zoom ≤ 0.5 그리드 모드 click 좌표 단일 컬럼 가정 — 14곳 분기 일괄 어긋남) 는 `pageLefts[i]` 적용 헬퍼 (`getPageLeftResolved`) 도입으로 14곳 정정 완료. 그러나 본 결함 (`getPageAtY` 의 X 무시) 은 #685 본문 정량 측정이 last col (col 1 in 2-col, col 4 in 5-col) 만 검증했기 때문에 발견되지 않음. Stage 3 e2e assert 강화로 노출됨.
#685 의 정정은 last col 케이스에서는 정상 동작 (헬퍼가 올바른 pageLefts[i] 반환). non-last col 케이스는 본 후속 이슈에서 정정.
정정 방향
`virtualScroll` 에 X+Y 둘 다 고려하는 헬퍼 추가:
```ts
getPageAtPoint(docX: number, docY: number): number {
// 그리드 모드: 같은 row 안의 페이지 중 docX 가 속하는 col 의 pageIdx
// 단일 컬럼 모드: 기존 getPageAtY 와 동치
// pageLefts[i] / pageWidths[i] 로 col 결정
}
```
input-handler-mouse 의 14곳 `virtualScroll.getPageAtY(contentY)` 호출을 `virtualScroll.getPageAtPoint(contentX, contentY)` 로 일괄 치환.
우선순위
High (UX-blocking) — 사용자가 그리드 모드에서 가장 자주 보게 되는 col 0, col 1 등 non-last 컬럼 click 이 모두 어긋남. Task #685 정정만으로는 여전히 50% (2-col grid) ~ 80% (5-col grid) 의 click 케이스에서 잘못된 페이지 처리.
참고
본질
`virtual-scroll.ts:133-140` 의 `getPageAtY(docY)` 는 Y 좌표만 보고 페이지를 결정.
```ts
getPageAtY(docY: number): number {
for (let i = this.pageOffsets.length - 1; i >= 0; i--) {
if (docY >= this.pageOffsets[i]) {
return i;
}
}
return 0;
}
```
그리드 모드에서 한 row 의 모든 페이지는 동일한 `pageOffsets[i] = rowTop` 을 가짐 (`virtual-scroll.ts:80` `pageOffsets.push(rowTop)`). 따라서 `getPageAtY` 는 항상 row 의 마지막 페이지(highest index) 만 반환.
영향
input-handler-mouse 의 14곳 모두 `getPageAtY(contentY)` 로 페이지 인덱스 결정 → row 의 non-last 컬럼 페이지를 click 해도 last 컬럼 페이지의 인덱스로 잘못 처리.
정량 측정 (Task #685 Stage 3 e2e, 2026-05-08)
`samples/exam_kor.hwp` 16p, viewport 1600×1000, headless Chrome.
zoom=0.5 (columns=2):
→ col 0 (non-last) 페이지 click → row 의 last col 페이지로 cursor 이동.
zoom=0.25 (columns=5) 에서는 col 0~3 모두 col 4 (last) 로 잘못 처리 (5컬럼 중 4컬럼 영향).
한컴 호환 결함
한컴 오피스 그리드 모드는 click 한 페이지의 정확한 위치에 cursor 배치 (Task #685 참고). RHWP 만 X 좌표 무시 → 한컴 호환 결함.
Task #685 와의 관계
Task #685 (zoom ≤ 0.5 그리드 모드 click 좌표 단일 컬럼 가정 — 14곳 분기 일괄 어긋남) 는 `pageLefts[i]` 적용 헬퍼 (`getPageLeftResolved`) 도입으로 14곳 정정 완료. 그러나 본 결함 (`getPageAtY` 의 X 무시) 은 #685 본문 정량 측정이 last col (col 1 in 2-col, col 4 in 5-col) 만 검증했기 때문에 발견되지 않음. Stage 3 e2e assert 강화로 노출됨.
#685 의 정정은 last col 케이스에서는 정상 동작 (헬퍼가 올바른 pageLefts[i] 반환). non-last col 케이스는 본 후속 이슈에서 정정.
정정 방향
`virtualScroll` 에 X+Y 둘 다 고려하는 헬퍼 추가:
```ts
getPageAtPoint(docX: number, docY: number): number {
// 그리드 모드: 같은 row 안의 페이지 중 docX 가 속하는 col 의 pageIdx
// 단일 컬럼 모드: 기존 getPageAtY 와 동치
// pageLefts[i] / pageWidths[i] 로 col 결정
}
```
input-handler-mouse 의 14곳 `virtualScroll.getPageAtY(contentY)` 호출을 `virtualScroll.getPageAtPoint(contentX, contentY)` 로 일괄 치환.
우선순위
High (UX-blocking) — 사용자가 그리드 모드에서 가장 자주 보게 되는 col 0, col 1 등 non-last 컬럼 click 이 모두 어긋남. Task #685 정정만으로는 여전히 50% (2-col grid) ~ 80% (5-col grid) 의 click 케이스에서 잘못된 페이지 처리.
참고