Skip to content

fix: 대형 파일 자동 접힘을 파일 길이가 아니라 변경량으로 판정한다 - #66

Merged
say8425 merged 4 commits into
mainfrom
fix/collapse-by-change-size
Aug 20, 2026
Merged

fix: 대형 파일 자동 접힘을 파일 길이가 아니라 변경량으로 판정한다#66
say8425 merged 4 commits into
mainfrom
fix/collapse-by-change-size

Conversation

@say8425

@say8425 say8425 commented Aug 20, 2026

Copy link
Copy Markdown
Owner

뷰어가 첫 등장한 대형 파일을 접어서 마운트하는데(main.tsisLargeFile), 그 판정에 넘기던 changedLines변경량이 아니라 파일 전량이었다. 정책은 "changes가 너무 길면 접는다"인데 실제로는 "파일이 길면 접는다"로 동작했다.

근본 원인

const changedLines = fileDiff.additionLines.length + fileDiff.deletionLines.length;

FileDiffMetadataadditionLines/deletionLines는 이름과 달리 string[]이고, 뷰어처럼 파일 전량으로 diff를 만들면(isPartial === falseparseDiffFromFile이 항상 그렇다) 각각 새 파일과 옛 파일의 전체 내용이다. packages/diffs/src/types.ts:235-256이 그렇게 문서화한다.

즉 실효 판정식이 파일 길이 × 2 > 1500, 사실상 파일 길이 750줄 초과였다.

실측(headerlab 워킹트리) — 1,166줄 CLAUDE.md+28/−2 = 30줄 변경이 2,306으로 읽혔다. 같은 트리에서 이렇게 잘못 접힌 파일이 5개였다:

파일 실제 변경 옛 로직이 읽은 값 접힘
CLAUDE.md 30 2,306 ✗ → ✓
tests/e2e/header-modification.spec.ts 388 4,114 ✗ → ✓
tests/unit/ScopeRail.test.tsx 158 2,342 ✗ → ✓
components/ScopeRail.tsx 164 2,188 ✗ → ✓
tests/unit/App.test.tsx 117 2,675 ✗ → ✓

수정

실제 +/- 줄 수는 Hunk 쪽 동명 number 필드에만 있다. 파일 헤더의 -M +N 배지도 정확히 그걸 세므로(createFileHeaderElement), 이제 화면 배지와 접힘 판정이 같은 값을 본다.

  • browser/largeFile.tscountChangedLines(hunks) 추가
  • browser/main.ts — 호출부 교체

임계값 1500과 lockfile 이름 규칙은 그대로다.

Screenshots

BeforeCLAUDE.md(-2 +28)가 접혀 있다. 158줄만 바뀐 tests/unit/ScopeRail.test.tsx(-103 +55)도 함께 접혔다.

before.png

After — 같은 파일이 펼쳐진 채 뜬다.

after.png

회귀망

이 종류의 버그는 유닛이 원리적으로 못 잡는다. isLargeFile 자체는 옳았고 호출부가 틀린 값을 넘겼을 뿐이라, 그 함수의 기존 테스트 5개가 전부 통과한 채로 버그가 살아 있었다. 같은 이유로 main.ts는 커버리지 게이트 밖이고 배선은 e2e가 맡는다.

large-file-collapse.e2e.ts세 방향을 한 파일에서 지킨다:

# 시나리오 기대
2,000줄 파일에 3줄 변경 안 접힘
800줄 전량 재작성 (1,600줄 변경) 접힘
1,000줄 lockfile (변경량 196줄 — 임계값 아래) 이름 규칙으로만 접힘

셋 다 뮤테이션으로 판별력을 확인했다: 원래 로직으로 되돌리면 ①이, countChangedLines를 0으로 퇴화시키면 ②만, LOCKFILE_NAMES를 비우면 ③만 죽는다.

②③은 처음에 기존 스펙(retokenize-cache.e2e.ts, lockfile-freeze.e2e.ts)이 지킨다고 적었다가 거짓임을 확인하고 직접 채웠다. 두 스펙 어디에도 접힘 상태를 단언하는 코드가 없다 — 전자는 헤더를 click해 펼친 뒤 하이라이트를 기다리고, 후자는 hasHeader만 보는데 헤더는 접혔든 펼쳐졌든 있다. 자동 접힘이 사라지면 그 click이 반대로 파일을 접어 뒤따르는 단언이 깨지는 부수효과로만 빨간불이 된다. 게다가 그 lockfile 픽스처는 20줄마다 2줄을 바꿔 8k에서 1,596줄, 30k에서 5,996줄을 변경하므로(실측) 크기 규칙만으로 이미 접힌다 — LOCKFILE_NAMES를 통째로 비워도 초록이라 이름 규칙의 회귀망이 아니었다.

CLAUDE.md에도 이 함정(FileDiffMetadata.additionLines가 이름과 다르다)과 위 회귀망 사실관계를 기록했다.

Test plan

  • bun test621 pass / 0 fail (countChangedLines 테스트 4종 추가)
  • bun run test:e2e78 pass / 0 fail
  • bun run test:coverage100% 유지 (largeFile.ts 포함)
  • bun run typecheck
  • bun run lint / bun run format:check — 새 경고 없음
  • 뮤테이션 검증 3종 (위 표)
  • 실제 앱 수동 검증 — 스크린샷은 dist/cli.js를 실제로 빌드해 headerlab을 서빙한 화면이며, before는 base 커밋을 빌드해 찍었다

참고: e2e 첫 전체 실행에서 retokenize-cache·worker-highlight 2건이 실패했는데, 실패 지점이 #status"Loading…" 타임아웃(서버 응답 계층)이라 접힘 판정과 무관한 기존 Bun $ never-settle flake였다. 부하 없이 재실행해 그린을 확인했다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01ArwfmcvGjKnRWMiEFBfJzF

say8425 and others added 4 commits August 20, 2026 15:39
뷰어는 첫 등장한 대형 파일을 접어서 마운트하는데(main.ts의 isLargeFile),
그 판정에 넘기던 changedLines가 변경량이 아니라 파일 전량이었다.

FileDiffMetadata의 additionLines/deletionLines는 이름과 달리 string[]이고,
뷰어처럼 파일 전량으로 diff를 만들면(isPartial === false) 각각 새 파일과
옛 파일의 전체 내용이다(types.ts가 그렇게 문서화한다). 즉 실효 판정식이
"파일 길이 x 2 > 1500", 사실상 "파일 길이 > 750줄"이었다.

실측: 1,166줄 CLAUDE.md의 +28/-2 변경이 2,306으로 읽혔고, 같은 워킹트리에서
5개 파일이 이렇게 잘못 접혔다.

실제 변경 줄 수는 Hunk 쪽 동명 number 필드에만 있다. 파일 헤더의 +N -M
배지도 같은 값을 세므로(createFileHeaderElement), 이제 화면 배지와 접힘
판정이 일치한다. 임계값 1500과 lockfile 이름 규칙은 그대로다.

이 종류의 버그는 유닛이 원리적으로 못 잡는다 — isLargeFile 자체는 옳았고
호출부가 틀린 값을 넘겼을 뿐이라 기존 테스트 다섯 개가 전부 통과한 채로
버그가 살아 있었다. 그래서 배선까지 태우는 e2e를 회귀망으로 함께 넣는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ArwfmcvGjKnRWMiEFBfJzF
앞 커밋은 "접히지 않는다" 한 방향만 단언하고, 반대 방향은 기존 스펙이
지킨다고 문서에 적었다. 실측해보니 그 주장이 거짓이었다.

- retokenize-cache.e2e.ts와 lockfile-freeze.e2e.ts 어디에도 접힘 상태를
  단언하는 코드가 없다. 전자는 헤더를 click해 펼친 뒤 하이라이트를 기다리고,
  후자는 hasHeader만 보는데 헤더는 접혔든 펼쳐졌든 있다. 자동 접힘이
  사라지면 그 click이 반대로 파일을 접어 뒤따르는 단언이 깨지는 부수효과로만
  빨간불이 된다.
- 게다가 lockfile 픽스처는 20줄마다 2줄을 바꿔 8k에서 1,596줄, 30k에서
  5,996줄을 변경한다(실측). 둘 다 임계값 1,500을 넘으므로 크기 규칙만으로
  이미 접힌다 — LOCKFILE_NAMES를 통째로 비워도 그 스펙은 초록이라 이름
  규칙의 회귀망이 아니었다.

그래서 large-file-collapse.e2e.ts가 세 방향을 한 파일에서 지키게 한다:
  1. 2,000줄 파일에 3줄 변경 -> 안 접힘
  2. 800줄 전량 재작성(1,600줄 변경) -> 접힘
  3. 1,000줄 lockfile(변경량 196줄로 임계값 아래) -> 이름 규칙으로만 접힘

셋 다 뮤테이션으로 판별력을 확인했다: countChangedLines를 0으로 퇴화시키면
2번만 죽고, LOCKFILE_NAMES를 비우면 3번만 죽는다. 기존 픽스처 옵션
(bigFileLines/lockfileLines)을 그대로 쓰므로 새 픽스처 배선은 없다.

함께 정정한 문서 주장 둘:
- 헤더 배지 표기 순서는 `-M +N`이다. createFileHeaderElement가 삭제 span을
  먼저 push하고 [data-metadata]에 order가 없어 DOM 순서가 곧 화면 순서다.
- "1,166줄 CLAUDE.md" 실측의 출처가 headerlab임을 밝힌다. 이 레포의
  CLAUDE.md는 147줄이라, 출처 없이 두면 수치가 틀린 것으로 오독된다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ArwfmcvGjKnRWMiEFBfJzF
코드 리뷰 지적 둘을 반영한다.

1. LARGE_FILE_LINE_THRESHOLD(1500)는 이번 수정으로 의미가 바뀌었는데
   값과 주석이 그대로였다. 예전엔 "파일 길이 × 2"와 비교돼 사실상 750줄
   파일을 걸렀고, 이제는 실제 변경 줄 수와 비교한다. 값은 의도적으로
   유지했다는 것과, 1,600 이상으로 올리면 large-file-collapse.e2e.ts ②가
   먼저 빨간불이 된다는 트립와이어 성질을 함께 적는다. 상수마다 근거를
   남기는 레포에서 이 줄만 맨몸이었다.

2. 앞 커밋이 ① 주석에 "파일 전량(2,000 + 1,997)이 4,000쯤"이라고 적었는데
   틀렸다. isPartial === false에서 deletionLines는 옛 파일 전량이고, 이
   픽스처는 줄을 교체할 뿐이라 옛 파일도 정확히 2,000줄이다. 실측하면
   2,000 + 2,000 = 정확히 4,000이라 1,997이 나올 자리가 없다. 틀린 숫자를
   고치려던 커밋이 새 틀린 숫자를 들인 셈이라 짚어둘 값어치가 있다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ArwfmcvGjKnRWMiEFBfJzF
앞 커밋에 test-results/.last-run.json이 딸려 들어갔다. .gitignore가
apps/viewer/test-results/ 로 경로를 고정하고 있었는데, Playwright는 실행
cwd 기준으로 산출물을 만들어 레포 루트에서 돌리면 루트에도 생긴다.

두 규칙에서 경로 접두를 떼어 어느 위치에서 돌리든 무시되게 한다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ArwfmcvGjKnRWMiEFBfJzF
@say8425
say8425 merged commit 7563667 into main Aug 20, 2026
13 of 15 checks passed
@say8425
say8425 deleted the fix/collapse-by-change-size branch August 20, 2026 10:14
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