From ec9a21cdeb7e434bcb0b80d595d6ac5be9c6b275 Mon Sep 17 00:00:00 2001 From: Penguin Date: Wed, 5 Aug 2026 13:19:07 +0900 Subject: [PATCH 1/5] =?UTF-8?q?docs:=20=EB=AC=B8=EC=84=9C-=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=93=9C=EB=A6=AC=ED=94=84=ED=8A=B8=2017=EA=B1=B4?= =?UTF-8?q?=EC=9D=84=20=EA=B3=A0=EC=B9=98=EA=B3=A0=20=EA=B2=8C=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=EB=A1=9C=20=EB=AC=B6=EB=8A=94=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 전면 대조에서 나온 것들이다. 가장 아픈 건 CLI 플래그 파리티 테스트가 SKILL.md 하나만 봤다는 점이다 — 그 사이 `--fold-with-tree`가 npm README와 번역 4종에서 조용히 빠져 있었고(플래그를 만든 #14이 루트 README와 SKILL.md만 갱신했다), 게이트가 한 문서만 보는 한 나머지는 아무도 지키지 않는다. 그래서 테스트를 docs-flags-parity로 이름을 바꿔 일곱 문서 전부를 대조하게 했다. 이 테스트는 고치기 전 상태에서 정확히 그 다섯 문서를 빨간불로 잡는다. 틀린 설명: README 아키텍처가 agent skill을 apps/viewer 소속으로 적었지만 실제로는 레포 루트 skills/에 살고 build.ts가 dist/로 복사한다(번역 4종 동일). trees "46 src"는 trees만 style.css를 포함해 센 값이라 45로 고치고, 무엇을 세는지 규칙과 재계산 명령을 함께 박았다. 엔진 규모 27k는 실측 29,482줄이다. diff.ts:220-221은 221-222다. 모순: CLAUDE.md가 pr-check을 세 곳에서 다르게 열거했다(한 곳은 e2e 누락). README의 커버리지 제외 목록은 여섯 중 셋만 적고 "의도적으로 제외된 것"이라 단정했다 — scripts/**와 스펙 자신이 빠져 있었다(번역 4종 동일). 누락: WHAT 트리에 skills/·docs/·.github/·플러그인 매니페스트·CHANGELOG.md· bunfig.toml이 없었다. 125행이 "동기화 필수"라고 계약을 거는 대상과 83행이 "레포 루트에 있다"고 계약을 거는 파일이 지도에 없는 상태였다. prewarm.ts, bunfig preload의 happy-dom 함정, 배포물이 deps 0인 전량 번들이라는 사실도 어디에도 없었다. 데드코드: isDiffViewerDisabled/DIFFDECK_DISABLE을 지웠다. 호출부가 한 번도 없었고 유일한 참조가 자기 테스트라 커버리지 100%가 그대로 유지됐다 — 게이트로는 원리적으로 안 드러나는 종류였다. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01G1vWAequwbToDppRceT6C1 --- CLAUDE.md | 30 ++++++---- README.md | 13 ++-- apps/viewer/README.md | 31 +++++----- apps/viewer/__tests__/diff-config.test.ts | 9 --- .../__tests__/docs-flags-parity.test.ts | 59 +++++++++++++++++++ .../__tests__/skill-flags-parity.test.ts | 43 -------------- apps/viewer/server/config.ts | 3 - docs/README.es.md | 9 ++- docs/README.ja.md | 9 ++- docs/README.ko.md | 9 ++- docs/README.zh.md | 9 ++- 11 files changed, 127 insertions(+), 97 deletions(-) create mode 100644 apps/viewer/__tests__/docs-flags-parity.test.ts delete mode 100644 apps/viewer/__tests__/skill-flags-parity.test.ts diff --git a/CLAUDE.md b/CLAUDE.md index f4901b0..62e5c86 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,20 +10,29 @@ diffdeck/ │ ├── path-store/ # @diffdeck/path-store — 트리 순수 로직 (flatten·sort·projection·store), 18 src, deps 없음 │ ├── theming/ # @diffdeck/theming — 테마 시스템 + 테마 데이터, 14 src + themes/*.json 10개 │ ├── diffs/ # @diffdeck/diffs — CodeView diff 렌더 엔진, 144 src (CodeView.ts 3,563줄) -│ └── trees/ # @diffdeck/trees — FileTree 엔진, 46 src (vanilla 렌더, de-preact 완료) +│ └── trees/ # @diffdeck/trees — FileTree 엔진, 45 src (vanilla 렌더, de-preact 완료) ├── apps/ -│ └── viewer/ # @say8425/diffdeck — server/(데이터 API) + browser/(뷰어 프론트) + cli/(CLI 엔트리) + build.ts·cli.ts +│ └── viewer/ # @say8425/diffdeck — server/(데이터 API) + browser/(뷰어 프론트) + cli/(CLI 엔트리) + build.ts·cli.ts·README.md(npm 페이지) +├── skills/diffdeck/ # SKILL.md — 에이전트가 참조하는 유일한 구동 문서. build.ts가 dist/skills/로 복사한다 +├── docs/ # README 번역 4종(ko·ja·zh·es) + demo.gif·screenshot.png·grab.png +│ # (docs/superpowers/의 plan·spec은 .gitignore — 로컬 전용) ├── scripts/ │ ├── extract-sources.ts # 소스맵 sourcesContent → 원본 TS 복원 도구 (Foundation 일회성) │ ├── extract-sources.test.ts # 합성 소스맵 fixture로 hermetic (외부 체크아웃 의존 없음) │ ├── css-inline-plugin.ts # *.css?inline import용 Bun 플러그인 (런타임/번들러 2분리) -│ └── parity/ # 포크 렌더 패리티 하니스 (fixture·smoke test·main·build) +│ └── parity/ # 포크 렌더 패리티 하니스 (fixture·preload·smoke test·main·build) +├── .github/workflows/ # pr-check.yml (5잡 게이트) + release.yml (release-please → npm) +├── .claude-plugin/ .codex-plugin/ # 플러그인 매니페스트 — release-please extra-files가 버전을 bump +├── CHANGELOG.md # 레포 루트에 있다 (release-please changelog-path의 앞 슬래시 — CI 항목 참고) +├── bunfig.toml # 테스트 preload + 커버리지 게이트/제외 (테스트 3레인 항목 참고) ├── tsconfig.base.json # 공유 base (jsxImportSource: preact, @diffdeck/* path alias) ├── .oxlintrc.json / .oxfmtrc.json ├── NOTICE # pierre (Apache-2.0) 유래 고지 └── package.json # Bun workspace 루트 (workspaces: packages/*, apps/*) ``` +**위 `N src` 숫자의 정의**: `src/` 아래 **테스트를 뺀 `.ts` 파일 수**다(`style.css`·`themes/*.json`은 제외 — 그래서 따로 적었다). 규칙을 박아두는 이유는 예전에 trees만 `style.css`를 포함해 세는 바람에 46으로 적혀 있었기 때문이다. 재계산: `find packages//src -name '*.ts' -not -path '*__tests__*' | wc -l`. 이 숫자들은 게이트가 없으므로 드리프트한다 — 규모의 감을 주는 값으로만 읽고, 정확한 값이 필요하면 위 명령이 답한다. + **기술 스택**: Bun (런타임·번들·테스트·workspace), TypeScript 7 (Go 네이티브 컴파일러), oxlint/oxfmt **패키지 의존 그래프**: @@ -48,7 +57,7 @@ cc-statusline에 포함됐던 로컬 diff 뷰어를 독립 제품으로 분리 + "Grab" 기능은 diff의 특정 구간을 프롬프트와 함께 클립보드로 복사해 AI 에이전트에게 바로 붙여넣을 수 있게 한다. 진입 경로는 둘: ① 엔진 거터의 GitHub식 라인 선택 + 상주 "+" 버튼(CodeView의 `enableLineSelection`/`enableGutterUtility` 옵션으로 활성화), ② 코드 텍스트 드래그 릴리스 시 즉시 열리는 프롬프트 팝오버. 둘 다 `#grab-popover` 프롬프트 입력창을 열고, Enter로 "참조(파일 경로·상태·base)+스니펫+프롬프트"를 한 텍스트로 인코딩해 클립보드에 복사한다. `enableLineSelection`/`enableGutterUtility`는 전역 활성화라 모든 라인 호버에 "+"가 뜨고 find 매치 라인에도 상주하는데, 이는 의도된 UX 변화다. 텍스트 경로는 pointerup에서 "실제 드래그였는지"부터 게이트한다 — pointerDown→pointerup 이동 거리가 `DRAG_THRESHOLD`(6px)를 넘어야 하며(헤더 폴드 토글과 동일한 `movedBeyondThreshold` 관례), 이 게이트가 없으면 더블/트리플클릭의 네이티브 단어·문단 선택(마우스 이동 없이도 비어있지 않은 Selection을 만든다)이 곧장 팝오버를 열어버린다. 통과하면 스냅샷 의미론: `pointerup` 시점(한 틱 뒤)에 선택·파일·스니펫을 전부 고정해 이후 워커 하이라이트 DOM 교체나 recycle이 선택을 죽여도 안전하고, 팝오버는 스크롤로는 닫히지 않으며 Esc·팝오버 바깥 pointerdown·`renderPatch`(파일 목록 갱신) 발생 시, 그리고 복사 성공 후 1.2초 뒤 자동으로 닫힌다. 선택 소유권: 팝오버가 엔진 라인 선택(`codeView.selectedLines` — `data-selected-line`을 낳는 슬롯)을 "소유"한 경우에만 — 즉 거터 "+" 경로(`onGutterUtilityClick`)로 열렸을 때만 — 닫히거나 복사가 성공할 때 `codeView.clearSelectedLines()`로 그 선택을 해제한다(`onGutterUtilityClick`이 세우는 `grabOwnsLineSelection` 플래그를 `onCopied`/`onClosed`가 공통으로 가드). 텍스트 드래그 경로는 네이티브 브라우저 Selection만 읽을 뿐 이 슬롯을 건드리지 않으므로 플래그를 세우지 않는다 — 무조건 해제했다면 find 바가 `revealMatch`/`selectMatch`로 같은 슬롯에 세워 둔 매치 하이라이트가 텍스트 경로 팝오버의 Esc나 복사 성공만으로 지워지고, 팝오버를 연 적 없는 "파킹된"(드래그만 하고 아직 "+"를 안 누른) 거터 선택도 `renderPatch`(팝오버가 열렸든 아니든 진입부에서 항상 `close()`를 호출)마다 지워지는 회귀가 생긴다. 거터 경로에서 스테일 선택이 남으면 안 되는 이유: 엔진의 `InteractionManager.placeUtility()`는 활성 선택이 있으면 호버를 무시하고 "+"를 선택 하단 행에 고정하며(그 행이 더 이상 렌더 대상이 아니면 아예 숨김) 이후 다른 행 호버에서도 "+"가 뜨지 않게 된다. 텍스트 경로 팝오버가 열리면(`input.focus()`) 네이티브 드래그 선택은 붕괴한다 — 포커스가 문서 선택을 팝오버 input으로 **옮기는** 것이라(실측: `getComposedRanges`가 `#grab-popover`의 자식을 가리킨다) 페인팅만 멈추는 게 아니고, 강제로 되돌려도 첫 타이핑에 다시 붕괴한다. 그래서 잡은 라인은 **grab 하이라이트**가 대신 보여준다: `unsafeCSS`로 넣은 `::highlight(diffdeck-grab)`(파랑) + `CSS.highlights` 레지스트리로 칠하는 독립 채널이다. 앱이 shadow root에 직접 `