Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
24a267d
Task #595 Stage 1: 본질 진단 + 재현 단위 테스트 + 광범위 sweep
johndoekim May 6, 2026
33fc7ac
Task #595 Stage 2: hit_test_header_footer 영역 정정 + 광범위 회귀 sweep
johndoekim May 6, 2026
938631f
Task #595 Stage 3: 최종 보고서 + 회귀 위험성 점검
johndoekim May 6, 2026
d591432
Task #595 후속 sweep 검증: 보류 2건 e2e 정량 측정 + Issue #685/#686 등록
johndoekim May 7, 2026
510ea23
Task #595 후속: Issue #685 진단 노트 framing 정정 — 한컴 호환 결함
johndoekim May 7, 2026
85a8306
Task #685 Stage 1: getPageLeftResolved 헬퍼 추가 + formBboxToOverlayRect 단순화
johndoekim May 7, 2026
c9617e0
Task #685 Stage 2: input-handler-mouse 14곳 헬퍼 치환 — 그리드 모드 click 좌표 정정
johndoekim May 7, 2026
89735d4
Task #685 Stage 3: 그리드 모드 click 좌표 e2e assert 강화 + 후속 결함 #689 분리
johndoekim May 7, 2026
5faae4d
Task #685: 최종 결과보고서 + 5/8 orders (closes #685, partial — #689 후속)
johndoekim May 7, 2026
ed3a029
Task #689 Stage 1: getPageAtPoint 헬퍼 도입 + 호출자 분류 확정
johndoekim May 7, 2026
dc8822c
Task #689 Stage 2: getPageAtY 18곳 → getPageAtPoint 치환 + Task #685 누락 …
johndoekim May 7, 2026
62de2a1
Task #689 Stage 3: e2e strict assert 활성화 + 그리드 모드 모든 col click 정합 자동 회귀화
johndoekim May 7, 2026
f1591fb
Task #689: 최종 결과보고서 + orders 갱신 (closes #689)
johndoekim May 7, 2026
9aa0fc5
Merge branch 'devel' into feature/issue-685-689-grid-mode-click
johndoekim May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions mydocs/orders/20260508.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# 오늘 할일 - 2026년 5월 8일

## M100 — v1.0.0 조판 엔진 (Task #595 후속 sweep 결함 정정 사이클)

| Issue | 타스크 | 상태 | 비고 |
|------|--------|------|------|
| **Task #685** | rhwp-studio 그리드 모드 (zoom ≤ 0.5) click 좌표 단일 컬럼 가정 14곳 분기 일괄 어긋남 정정 | **완료 (Stage 1+2+3, 부분 정정 — 작업지시자 scope 결정)** | Task #595 후속 sweep 으로 발견된 한컴 호환 결함. 분기 기준점: `da7461d` (Merge local/devel: Task #595 본 작업 + 후속 sweep, Issue #685/#686 등록 포함). **본질 결함**: `virtual-scroll.ts` 의 그리드 모드 (`pageLefts[i] = marginLeft + col*(pw+gap)`) 가 `input-handler-mouse.ts` 14곳 모두 단일 컬럼 가정 공식 `(scrollContent.clientWidth - pageDisplayWidth) / 2` 만 사용해 그리드 모드 click 좌표 ±수백 px 어긋남. **정정 (3 단계)**: (Stage 1) `virtual-scroll.ts` 에 `getPageLeftResolved(pageIdx, containerWidth)` 헬퍼 신규 (+12 LOC) — 그리드 모드 `pageLefts[i]` / 단일 컬럼 `(containerWidth - pageWidth) / 2` fallback. `input-handler.ts:2572-2586 formBboxToOverlayRect` 의 verbose sentinel 패턴 1곳도 헬퍼 호출로 단순화 (-3/+1 LOC, 동치 refactor). 기존 `getPageLeft` 4 호출자 (`canvas-view.ts`/`field-marker-renderer.ts`/`caret-renderer.ts`) 무회귀 보존. (Stage 2) `input-handler-mouse.ts` 14곳 (라인 23/129/176/279/296/357/431/**475**/**811**/**889**/931/1146/1196/1243) 헬퍼 일괄 치환 (+14/-14 LOC, 1:1 표현식 치환). `pw`/`pageDisplayWidth` 변수 + 페이지 인덱스 변수 (`pi`/`pageIdx`/`picBbox.pageIndex`) 모두 보존. 6 회 Edit (replace_all 3 + 컨텍스트 단건 3, prefix 분리). (Stage 3) `e2e/grid-mode-click-coord.test.mjs` assert 강화 (+54 LOC) — `dumpGridState` 헬퍼 동치성 + `probeClickAtPage` last-col only strict assert (non-last col 은 후속 결함 #689 로 SKIP 안내). 추가 probe: zoom=0.25 last col + zoom=1.0 baseline. 진단노트 `mydocs/troubleshootings/grid_mode_click_coord.md` 끝부분에 "정정 완료 — 부분 정정 (Task #685)" + #689 안내 +20 LOC. **검증**: typecheck 무에러 / vite build 정상 / `body-outside-click-fallback.test.mjs --mode=headless` 무회귀 / `grid-mode-click-coord.test.mjs --mode=headless` **PASS=11/FAIL=0/SKIP=2** (의도된 non-last col). last-col 정합 자동 회귀: zoom=1.0 page 0, zoom=0.5 page 1, zoom=0.25 page 4 모두 helperResolved max delta=0.00px + cursor.rectPageIdx 정합. **시각 검증 (작업지시자 직접, hwpctl_action_table_v11.hwp 그리드 모드, 2026-05-08)**: non-last col 페이지(1, 2, 4, 5 등) 어긋남 = #689 시각 재현 / last col 페이지(3, 6, 9 등) 정상 = #685 정정 효과 확인. **Stage 3 발견사항 → Issue #689 분리 등록**: `virtual-scroll.ts:133-140 getPageAtY(docY)` 가 Y 좌표만 보고 row 의 last page idx 만 반환 → non-last col click 어긋남 (Task #685 의 pageLeft 정정과 별개의 결함 영역). **작업지시자 결정 (scope 엄격 준수)**: Task #685 본문 명시 범위 (pageLeft 공식 14곳) 그대로 유지 + #689 분리 등록 + 즉시 #689 시작. 정정 방향: `getPageAtPoint(docX, docY)` 헬퍼 + 14곳 일괄 치환. 커밋 `769f534` (Stage 1) + `d982d50` (Stage 2) + `7fdf01d` (Stage 3). 수행계획서: `mydocs/plans/task_m100_685.md`. 구현계획서: `mydocs/plans/task_m100_685_impl.md`. 단계 보고서: `mydocs/working/task_m100_685_stage{1,2,3}.md`. 최종 결과 보고서: `mydocs/report/task_m100_685_report.md`. **`feedback_process_must_follow` 메모리 권위 영역 강화** — Stage 3 추가 결함 발견 시 scope 확장 충동 억제 + 작업지시자 결정 후 #689 분리 등록 패턴 정합. **`feedback_hancom_compat_specific_over_general` 정합** — 그리드 모드 한컴 호환 결함 (한컴 정상 / RHWP 어긋남) 영역 본질 정정. **회귀 차단 가드 영구 보존** — e2e assert 강화로 last-col 정합 자동 회귀 차단. **부분 정정 명시 패턴** — 진단노트 + 결과보고서 + orders 모두 "Task #685 = 부분 정정, #689 후속" 명시로 후속 영역 추적 가능성 확보. |
| **Issue #689** | rhwp-studio 그리드 모드 `getPageAtY` X 좌표 무시 — non-last col 페이지 click 어긋남 정정 | **완료 (Stage 1+2+3, Task #685 누락분 10곳 동반 정정 — 작업지시자 시각 검증 통과)** | Task #685 Stage 3 e2e assert 강화로 노출된 후속 결함. M100 (v1.0.0), 우선순위 High (UX-blocking). 분기 기준점: `7651d91` (Merge local/devel: Task #685). **본질 결함**: `virtual-scroll.ts:133-140 getPageAtY(docY)` Y-only loop 가 그리드 모드의 row-shared `pageOffsets[i]` 환경에서 항상 last page idx 만 반환 → non-last col click 시 row 의 last col 페이지로 cursor 처리. **정정 (3 단계)**: (Stage 1) `virtual-scroll.ts` 에 `getPageAtPoint(docX, docY)` 헬퍼 신규 (+33 LOC) — 단일 컬럼은 `getPageAtY` 동치, 그리드는 row(Y) 안에서 X 로 col 결정, gap 영역은 가장 가까운 페이지 fallback. coordinate-system.ts:18 의 `getPageAtY` 는 dead code (`documentToPage` 호출자 0건) 확인 → 미수정. (Stage 2) 마우스 컨텍스트 18곳 `getPageAtY` → `getPageAtPoint` 치환 (input-handler-mouse 12곳 + input-handler.ts 4곳 + table 1곳 + picture 1곳, 5 회 `replace_all`). 동시에 Task #685 sweep 누락 buggy pageLeft **10곳 동반 정정** — input-handler.ts 4곳 + table 3곳 (L400/74/111) + picture 1곳 + connector 2곳 (L85/152). 추가 4곳 (table L74/L111 + connector L85/L152) 은 `getPageAtY` 호출 없이 (state 또는 함수 매개변수에서 pageIdx 획득) buggy pageLeft 패턴 사용 — sweep 중 추가 발견 → 작업지시자 추가 승인 (2026-05-08) 으로 동반 정정. (Stage 3) `e2e/grid-mode-click-coord.test.mjs` non-last col SKIP 분기 제거 → 모든 col strict assert 일원화. zoom=0.25 last-col only probe → for 루프로 col 0~4 5건 모두 probe. 진단노트 `mydocs/troubleshootings/grid_mode_click_coord.md` 끝부분에 "완전 정정 완료 (Task #685 + #689 결합)" +18 LOC. **검증**: typecheck 무에러 / vite build 정상 / `body-outside-click-fallback.test.mjs --mode=headless` 무회귀 / `grid-mode-click-coord.test.mjs --mode=headless` **PASS=21/FAIL=0/SKIP=0** (이전 SKIP 2건 모두 PASS 로 전환). 모든 col rectPageIdx 정합: zoom=0.5 page 0 (1→0), page 2 (3→2), zoom=0.25 col 0~4 모두 정합. 단일 컬럼 zoom=1.0 무회귀. **시각 검증 (작업지시자 직접, hwpctl_action_table_v11.hwp 그리드 모드, 2026-05-08)**: 이전 단계 시점에 보고된 "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 라 robust (race condition 없음, ResizeObserver 누락 케이스 영향 없음, 미래 코드 변경 시 fragile 안 함), B 는 stateful 이라 setPageDimensions 호출에 의존. verbose trade-off 보다 robustness 우선. 커밋 `a22cbf0` (Stage 1) + `8369ebd` (Stage 2) + `5db03a8` (Stage 3). 수행계획서: `mydocs/plans/task_m100_689.md`. 구현계획서: `mydocs/plans/task_m100_689_impl.md`. 단계 보고서: `mydocs/working/task_m100_689_stage{1,2,3}.md`. 최종 결과 보고서: `mydocs/report/task_m100_689_report.md`. **Task #685 + #689 결합으로 그리드 모드 click 한컴 호환 완성** — 추가 후속 이슈 없음. **`feedback_process_must_follow` 메모리 권위 영역 강화** — Stage 2 sweep 중 추가 buggy pageLeft 4곳 발견 시 즉시 작업지시자 승인 받아 scope 확장 패턴 정합. **DRY 정합** — `getPageLeftResolved` 재사용으로 신규 좌표 계산 코드 0줄 (#685 도입분 활용). **stateless API 설계 정합** — robustness 우선 결정으로 race condition 없는 click-time 측정값 사용 패턴 권위 영역 확보. **회귀 차단 가드 영구 보존** — e2e PASS=21 strict assert 모든 col 정합 자동 회귀화 + 진단노트 권위 기록 + Task #685 와 #689 결합 효과 명시. |

## 작업 메모

### Task #685 본질
- **`feedback_process_must_follow` 메모리 권위 영역의 권위 케이스** — Stage 3 e2e assert 강화 시 추가 결함 (`getPageAtY` X 무시) 발견. scope 확장 충동 억제 + 작업지시자 결정 받아 #689 로 분리 등록.
- **하이퍼-워터폴 절차 정합** — 수행계획서 → 구현계획서 → Stage 1/2/3 → 최종보고서 모두 작업지시자 승인 게이트 거침. 단계별 보고서 (`_stage{N}.md`) + 단계별 커밋.
- **DRY 정합** — `getPageLeftResolved` 헬퍼 도입 시 기존 `input-handler.ts:2579` 의 verbose sentinel 패턴 (이미 sentinel-aware 직접 풀어 씀) 도 같은 헬퍼로 정리하여 총 15곳 통일. 작업지시자 결정 (Stage 0 게이트).
- **회귀 위험 영역 좁힘** — `canvas-view.ts` / `field-marker-renderer.ts` / `caret-renderer.ts` 4 호출자 미수정 (이미 그리드 인프라 정상 처리 중, 헬퍼 통일 욕심 시 회귀 위험).
- **HWP IR 표준 직접 사용** — `virtualScroll.pageLefts[i]` (이미 그리드 인프라가 채우는 정합 데이터) 그대로 적용. 신규 좌표 계산 코드 0줄.
- **시각 검증 일관성** — 작업지시자가 `hwpctl_action_table_v11.hwp` 그리드 모드에서 직접 시각 확인 → last col 정합 + non-last col 어긋남 = #689 시각 재현. 자동 e2e (PASS=11/SKIP=2) 와 시각 결과 정합.

### Task #685 vs Issue #689 영역 분리 정합
| 결함 영역 | Task #685 (완료) | Issue #689 (시작 예정) |
|----------|-----------------|------------------------|
| pageLeft 공식 (단일 컬럼 가정) | ✅ 정정 (`getPageLeftResolved`) | n/a |
| 페이지 인덱스 결정 (Y-only) | n/a | 🔄 정정 예정 (`getPageAtPoint`) |
| 영향 범위 | last col 정합 | non-last col 정합 |
| 검증 | last-col e2e assert | (#689 e2e 강화 예정) |

### #689 즉시 시작 절차
1. `local/task685` → `local/devel` 머지 (작업지시자 권한, `--no-ff`, "Merge local/devel: Task #685 ... (closes #685, partial — #689 후속)")
2. 이슈 #685 close (부분 정정 명시 코멘트 권장)
3. `local/devel` 에서 `local/task689` 분기
4. #689 수행계획서 작성 → 승인 게이트
## M100 — v1.0.0 조판 엔진 (PR 처리 — 5/7 사이클 영역의 연속 영역)

| Issue | 타스크 | 상태 | 비고 |
Expand Down
157 changes: 157 additions & 0 deletions mydocs/plans/task_m100_685.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Task #685 수행계획서 — zoom ≤ 0.5 그리드 모드 click 좌표 단일 컬럼 가정 일괄 정정

- **이슈**: [#685](https://github.com/edwardkim/rhwp/issues/685)
- **마일스톤**: M100 (v1.0.0)
- **브랜치**: `local/task685` (← `local/devel`)
- **우선순위**: High (UX-blocking)
- **작성일**: 2026-05-07

---

## 1. 배경 (Why)

### 본질 결함

`virtual-scroll.ts` 는 `zoom ≤ 0.5 + pages > 1 + viewport > 0` 조건에서 **다중 컬럼 그리드** 로 페이지를 배치하며, 각 페이지의 X 좌표를 `pageLefts[i] = marginLeft + col * (pw + gap)` 로 열별 계산한다 ([virtual-scroll.ts:54-92](../../rhwp-studio/src/view/virtual-scroll.ts#L54-L92)).

그러나 [`input-handler-mouse.ts`](../../rhwp-studio/src/engine/input-handler-mouse.ts) 의 마우스 좌표 → 페이지 좌표 변환 14곳 모두 **단일 컬럼 가정 공식** `(scrollContent.clientWidth - pageDisplayWidth) / 2` 만 사용 → 그리드 모드에서 모든 마우스 인터랙션이 ±수백 px 어긋남.

### 사용자 영향 (정량 측정, 2026-05-07)

[grid-mode-click-coord.test.mjs](../../rhwp-studio/e2e/grid-mode-click-coord.test.mjs) 결과 (`samples/exam_kor.hwp` 20p, viewport 1600×1000, headless Chrome):

| 줌 | columns | 좌측 끝 col delta_px | 우측 끝 col delta_px | 가운데 col |
|----|---------|---------------------|---------------------|-----------|
| 0.5 | 2 | +285.6 | −285.6 | (n/a) |
| 0.25 | 5 | +581.3 | −581.3 | 0 (우연한 정합) |
| 1.0 (baseline) | 1 | 0 | 0 | 0 (정상) |

실제 click 검증: zoom=0.5, page 1 (col 1), hwpX=100 의도 시 CORRECT click → `{sec:0, para:39, char:70}` 정상; BUGGY click → `{sec:0, para:31, char:0}` (page 0 영역으로 떨어짐).

### 한컴 호환 결함

한컴 오피스 그리드 모드(다중 페이지 동시 보기)는 정상 동작 확인 (사용자 직접 시연, 2026-05-07). RHWP 만 그리드 모드에서 어긋남 → 한컴 호환 결함.

---

## 2. 정정 범위 (What)

### 본 작업 범위 (in-scope)

1. **[virtual-scroll.ts](../../rhwp-studio/src/view/virtual-scroll.ts)** — 헬퍼 1개 추가:
```ts
/** 그리드 모드 sentinel(-1) 해소: 단일 컬럼은 fallback 공식, 그리드는 pageLefts[i] */
getPageLeftResolved(pageIdx: number, containerWidth: number): number {
const pl = this.pageLefts[pageIdx] ?? -1;
if (pl >= 0) return pl;
const pw = this.pageWidths[pageIdx] ?? 0;
return (containerWidth - pw) / 2;
}
```
기존 [`getPageLeft(pageIdx)`](../../rhwp-studio/src/view/virtual-scroll.ts#L155-L157) 는 raw accessor 로 보존 (canvas-view 등 4 호출자 무회귀).

2. **[input-handler-mouse.ts](../../rhwp-studio/src/engine/input-handler-mouse.ts)** — 14곳 일괄 치환:
- 라인 23, 129, 176, 279, 296, 357, 431, **475**, **811**, **889**, 931, 1146, 1196, 1243
- 변환: `(sc.clientWidth - pw) / 2` → `this.virtualScroll.getPageLeftResolved(pageIdx, sc.clientWidth)`

3. **[input-handler.ts:2579-2580](../../rhwp-studio/src/engine/input-handler.ts#L2579-L2580)** — verbose sentinel 패턴(`getPageLeft(pageIdx) >= 0 ? getPageLeft(pageIdx) : ...`) 1곳도 동일 헬퍼로 정리 (DRY).
→ 총 **15곳** 헬퍼 통일.

4. **[grid-mode-click-coord.test.mjs](../../rhwp-studio/e2e/grid-mode-click-coord.test.mjs)** — 현재 측정만 하는 테스트에 회귀 assert 추가:
- zoom=0.5, zoom=0.25 모든 col 의 CORRECT click → cursor.rectPageIdx === 의도한 pageIdx (assert)
- zoom=1.0 (baseline) 무회귀 assert
- `delta_px` (correct vs buggy 공식 차이) 는 측정 로그 그대로 보존 (수치 자체는 fix 와 무관, 진단 가치)

5. **[virtual-scroll 단위 테스트](../../rhwp-studio/src/view/__tests__/)** (위치는 Stage 1 에서 확정) — `getPageLeftResolved` 단위 케이스 3 종 추가:
- 단일 컬럼 모드 (sentinel −1 fallback)
- 그리드 모드 (pageLefts[i] 그대로)
- out-of-range (undefined → fallback 공식)

### 본 작업 미포함 (out-of-scope)

- [`canvas-view.ts:156`](../../rhwp-studio/src/view/canvas-view.ts#L156), [`field-marker-renderer.ts:84`](../../rhwp-studio/src/engine/field-marker-renderer.ts#L84), [`caret-renderer.ts:127`](../../rhwp-studio/src/engine/caret-renderer.ts#L127) 의 `getPageLeft` 호출은 **손대지 않는다**. 이들은 이미 그리드 인프라를 정상 처리 중. 헬퍼 통일 욕심내면 회귀 위험.
- `pageDisplayWidth` 변수가 click 좌표 외에 hit test bbox 등에 별도 사용되는 경우 변수는 보존하고 `pageLeft` 계산식만 헬퍼로 치환.
- 키보드 / IME / Touch 입력 경로의 동일 결함 가능성 — 별도 후속 조사 (본 타스크 범위 외).

---

## 3. 수행 절차 (How)

CLAUDE.md 의 하이퍼-워터폴 절차 준수:

| 단계 | 산출물 | 승인 게이트 |
|------|--------|-----------|
| 0 | 본 수행계획서 (`task_m100_685.md`) | **승인 요청** ← 현재 |
| 1 | 구현 계획서 (`task_m100_685_impl.md`, 3 단계) | 승인 요청 |
| 2 | Stage 1 단계 보고서 (`task_m100_685_stage1.md`) + 헬퍼 + 단위 테스트 커밋 | 승인 요청 |
| 3 | Stage 2 단계 보고서 (`task_m100_685_stage2.md`) + 15곳 치환 커밋 | 승인 요청 |
| 4 | Stage 3 단계 보고서 (`task_m100_685_stage3.md`) + e2e assert 커밋 | 승인 요청 |
| 5 | 최종 결과보고서 (`task_m100_685_report.md`) + 진단 노트 갱신 + orders 갱신 | 승인 요청 → 이슈 #685 close |

각 단계 커밋 시 `Task #685 Stage N: <subject>` 형식. 최종 단계에서 `closes #685`.

---

## 4. 검증 (Acceptance Criteria)

자동 검증:

- [ ] `npm run typecheck` (rhwp-studio) 통과
- [ ] `npm run build` (rhwp-studio) 성공
- [ ] virtual-scroll 단위 테스트 신규 케이스 3종 PASS
- [ ] `node rhwp-studio/e2e/grid-mode-click-coord.test.mjs --mode=headless` PASS (assert 강화 후)
- zoom=0.5/0.25 모든 col CORRECT click → rectPageIdx === pageIdx
- zoom=1.0 baseline 무회귀
- [ ] `node rhwp-studio/e2e/body-outside-click-fallback.test.mjs --mode=headless` 무회귀

수동 검증 (Stage 3 마지막):

- [ ] 호스트 Chrome CDP 모드 (`--mode=host`) 로 zoom=0.5, 0.25 그리드에서 양 끝 컬럼 페이지 본문 클릭 → 의도한 위치 캐럿 배치 시각 확인
- [ ] zoom=1.0 시 일반 클릭/더블클릭/우클릭 무회귀 시각 확인

코드 품질:

- [ ] [`input-handler-mouse.ts`](../../rhwp-studio/src/engine/input-handler-mouse.ts) 안에 `(scrollContent.clientWidth - pageDisplayWidth) / 2` 또는 `(sc.clientWidth - pw) / 2` 패턴이 0건 grep 결과
- [ ] [`input-handler.ts:2579-2580`](../../rhwp-studio/src/engine/input-handler.ts#L2579-L2580) 의 verbose sentinel 분기가 단순 헬퍼 호출로 단순화

---

## 5. 위험성 / 주의점

### scope 위험
- **`pageDisplayWidth` 변수 보존 점검**: 같은 함수 내에서 `pageDisplayWidth` 가 hit test bbox 계산 등 다른 곳에 재사용되는 경우 변수는 그대로 두고 `pageLeft = ...` 표현식만 헬퍼로 치환. Stage 2 에서 라인별 cross-check 필수.
- **`pageIdx` 변수명 차이**: 라인별로 `pi`, `pageIdx`, `picBbox.pageIndex` 등 다양함 → 치환 시 해당 함수 컨텍스트의 정확한 변수명 사용. 일괄 sed 금지.

### 회귀 위험
- 단일 컬럼 모드 (zoom > 0.5): `pageLefts[i] = -1` sentinel → 헬퍼가 fallback 공식 적용 → 기존 동작과 비트 단위로 동일해야 한다. Stage 1 단위 테스트로 보장.
- 그리드 인프라 (canvas-view 등 4곳)는 미수정 → 본 변경이 영향 못 줌.

### 측정 임계값
- e2e assert 의 `delta ≤ 1 px` 임계는 부동소수점 오차 흡수용. 실제 측정 결과 기반으로 Stage 3 에서 미세 조정 가능 (단 0.5 px 이상 어긋남이 발생하면 fix 미적용 의심).

---

## 6. 참고 자료

- 진단 노트: [`mydocs/troubleshootings/grid_mode_click_coord.md`](../troubleshootings/grid_mode_click_coord.md) — 본질, 14곳 표, 정량 측정, 권장 패턴 모두 포함
- e2e 측정 스크립트: [`rhwp-studio/e2e/grid-mode-click-coord.test.mjs`](../../rhwp-studio/e2e/grid-mode-click-coord.test.mjs)
- 관련 영역 (input-handler-mouse cluster): #658, #661, #669
- 모태 타스크: #595 (header bbox 누설 정정 — 후속 sweep 으로 본 이슈 발견)

---

## 7. 변경 파일 요약

| 파일 | 종류 | 변경 |
|------|------|------|
| `rhwp-studio/src/view/virtual-scroll.ts` | 수정 | `getPageLeftResolved` 추가 (~7 LOC) |
| `rhwp-studio/src/engine/input-handler-mouse.ts` | 수정 | 14곳 치환 (~14 LOC delta) |
| `rhwp-studio/src/engine/input-handler.ts` | 수정 | 1곳 단순화 (~−2 LOC) |
| `rhwp-studio/src/view/__tests__/virtual-scroll.test.*` | 수정/신규 | 단위 테스트 ~3 케이스 |
| `rhwp-studio/e2e/grid-mode-click-coord.test.mjs` | 수정 | assert 추가 (~30 LOC) |
| `mydocs/plans/task_m100_685.md` | 신규 | 본 문서 |
| `mydocs/plans/task_m100_685_impl.md` | 신규 | 구현 계획서 |
| `mydocs/working/task_m100_685_stage{1,2,3}.md` | 신규 | 단계 보고서 |
| `mydocs/report/task_m100_685_report.md` | 신규 | 최종 보고서 |
| `mydocs/troubleshootings/grid_mode_click_coord.md` | 갱신 | 끝부분에 "정정 완료 — Task #685" 기록 |
| `mydocs/orders/2026-05-07.md` (또는 작업 시작일) | 갱신 | 타스크 상태 |
Loading
Loading