Skip to content

Release v0.5.0#3

Merged
moreih29 merged 28 commits into
mainfrom
develop
May 31, 2026
Merged

Release v0.5.0#3
moreih29 merged 28 commits into
mainfrom
develop

Conversation

@moreih29
Copy link
Copy Markdown
Owner

v0.4.0 이후 누적된 25개 커밋을 main으로 승격합니다. 머지 후 v0.5.0 태그로 Release를 발행합니다.

Added

  • 파일/폴더 아이콘 테마 설정 — Minimal(lucide) / Material 컬러 로고 선택
  • dirty-diff 거터 마커 + 인라인 peek (VSCode 스타일)
  • GitHub식 raw HTML 프리뷰 — 마크다운 미리보기가 <div align>·<img>·<details> 등을 렌더 (rehype-raw + sanitize, GitHub allowlist)

Changed

  • 탭 닫기 동작 통합⌘W가 untitled·browser 탭도 닫도록, untitled dirty-confirm 경로 일원화
  • README 개편 + MIT 라이선스, 단축키 표를 docs/SHORTCUTS.md로 분리
  • RELEASING.md 재편 — 브랜치 모델·SemVer 정책 추가

Fixed

  • 저장 후 외부 변경 거짓 양성 제거 (lastLoadedValue 동기화)
  • untitled 빈 버퍼가 clean에서 시작하도록 dirty 기준 수정
  • Material 아이콘 lazy 깜빡임 / glob 적용 범위 수정

내부 (사용자 영향 없음)

  • refactor 8: log facade 통일, git IPC withRepo HOF, editor 순환 의존 6→0, 커맨드 전처리 dedup 등
  • test: 격리 오염 제거 + 회귀 게이트, 결정성·파라미터화

Protocol & Remote 영향

  • 없음 (에이전트 프로토콜·원격 부트스트랩 변경 없음)

🤖 Generated with Claude Code

moreih29 and others added 28 commits May 30, 2026 11:01
- 파일트리 chevron: 커스텀 인라인 SVG → lucide ChevronRight
- git 패널 그룹/트리 chevron: ChevronDown/Right 스왑 → ChevronRight + rotate-90
  (파일트리와 동일한 회전 애니메이션 방식)
- git 패널 헤더 펴기/접기 토글 아이콘: FoldVertical/UnfoldVertical →
  ChevronsUpDown/ChevronsDownUp (접기 아이콘을 파일트리와 일치, 기능 유지)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Appearance 설정에서 아이콘 테마를 선택할 수 있게 한다. Minimal(기존 lucide
단색, 기본·불변 베이스라인)과 Material(material-icon-theme 기반 언어별 컬러
로고, opt-in) 2종을 제공한다.

- AppState.iconTheme(minimal|material) + theme.ts식 dual-write zustand 스토어
  (set+localStorage+appState.set) + bootstrap hydrate 배선
- 통합 <FileIcon kind size tone/> 래퍼로 4개 소비 지점(file-tree/row, 에디터
  탭, 검색 결과, git 폴더) 추상화. resolveLucide/resolveMaterial 순수함수 분리,
  Material 미커버 확장자는 per-icon lucide 폴백
- material-icon-theme(devDep) + vite-plugin-svgr 도입, 빌드타임 생성 스크립트
  (gen:icons)로 매핑 JSON + SVG 1087개 번들. 정적 최빈셋 + lazy glob 하이브리드
- Appearance 패널 Icon Theme SegmentedControl + settings.json(en/ko) i18n
- design.md §14 개정: Minimal 기본·불변 / Material opt-in 예외 명문화(§12/§13 교차 보정)
- 순수 리졸버 단위테스트 50개 추가

검증: tsc(tsc -b) 통과, biome 클린(신규/수정 파일), bun test 3002개(+신규 50) 통과,
electron-vite build exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
import.meta.glob 패턴에 `?react`를 직접 넣으면 Vite가 리터럴로 취급해 0개를
매칭한다. 그 결과 정적 import한 12개만 Material로 표시되고 나머지 모든
파일/폴더가 lucide로 폴백됐다. `?react`를 `{ query: "?react" }` 옵션으로 옮기고
glob 키 파싱 정규식을 쿼리 유무에 무관하게 보정.

검증: electron-vite build에서 변환 모듈 3030→4105, material 청크 1078개 방출
(folder-rust/folder-src/jenkins 등 비정적 아이콘 청크 확인). tsc·biome 클린.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
기존엔 아이콘마다 lazy()+Suspense(fallback=lucide)라 첫 등장 시 lucide→컬러
스왑 깜빡임이 보였다. VS Code(아이콘을 CSS background-image URL로 동기 참조,
컴포넌트 코드분할 없음) 방식을 차용:

- 아이콘별 lazy 코드분할 폐기 → `?url` eager glob으로 iconName→에셋 URL을
  동기 매핑하고 <img src>로 직접 렌더. Suspense 경계가 없어 스왑 깜빡임 없음.
- assetsInlineLimit 함수로 material SVG는 base64 인라인 금지 → 파일로 방출.
  main 번들 5.79MB→4.95MB로 복귀(인라인 +850KB 제거), Minimal 기본 사용자가
  미사용 에셋을 짊어지지 않음. SVG는 on-demand 디스크 로드 후 브라우저 캐시.

검증: build 1087개 SVG 파일 방출, tsc·biome 클린, bun test 3002개 0 fail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
파일/폴더 아이콘 테마(Minimal lucide / Material 컬러 로고) 설정 추가.
- AppState.iconTheme + dual-write 스토어, Appearance 컨트롤 + i18n
- 통합 FileIcon 래퍼로 4지점(트리/탭/검색/git폴더) 추상화
- material-icon-theme 기반 1087 SVG 번들(동기 URL+<img>, 깜빡임 없음)
- design.md §14 개정(Minimal 기본·불변/Material opt-in 예외)
에디터에서 git HEAD 대비 수정 라인을 거터에 표시하고, 마커 클릭 시
변경 전/후를 인라인 peek로 보여준다. git 패널 diff 뷰를 열지 않아도
일반 에디터에서 어디가 어떻게 바뀌었는지 확인할 수 있다.

- diff 계산: monaco 번들 내부의 DefaultLinesDiffComputer 재사용
  (VSCode quick-diff와 동일 엔진). 라이브 버퍼 기준이라 저장 전에도 갱신
- 거터: 추가/수정/삭제 색 막대 + overview ruler/minimap 틱
  (--git-status-* 테마 변수 재사용)
- peek: VSCode ZoneWidget 방식(뷰존+오버레이 위젯)으로 뷰포트 고정,
  ↑/↓ 네비게이션은 revealLineInCenterIfOutsideViewport로 스크롤,
  헤더 아이콘은 lucide SVG
- untracked/비저장소 파일은 baseline=null → 마커 없음(VSCode 동일)
- tsconfig: bun 테스트 러너용 monaco esm 해석 별칭(vite/빌드 무영향)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
저장 성공 시 dirty-tracker만 갱신하고 엔트리의 lastLoadedValue는
편집 전 내용에 멈춰 있어서, 저장 직후 fs/git 이벤트의
reconcileExternalChange가 매번 diverged 분기로 빠져 diskDiverged를
거짓 설정했다. 현재는 이 플래그를 읽는 UI가 없어 무해하지만, 외부변경
충돌 경고를 얹으면 저장마다 거짓 경고가 뜨는 잠재 버그였다.

- cache: syncLoadedValueAfterSave(input, content) 추가
- save/service: 쓰기 성공 직후 baseline을 방금 쓴 내용으로 동기화
  (충돌 경로에서는 호출하지 않음)
- test: 저장 성공 시 호출 / 충돌 시 미호출 회귀 테스트

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ErrorNotice 컴포넌트로 4개 뷰의 에러배너 중복 제거 (state-error 토큰/아이콘 픽셀 보존)
- usePathAutocomplete 훅으로 디렉터리 피커 경로 콤보박스 추출 (상태 5·effect 4 이동)
- hidden DOM-id 트리거(#picker-add-workspace-trigger)를 imperative ref handle로 교체

순효과 -180 LOC. UI/동작 불변. typecheck·biome(변경 라인)·기존 테스트 44 통과.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
키보드 close 경로(closeTabById)가 editor/terminal/diff/commit 4종만 처리해
untitled·browser 탭에서 Cmd+W가 무반응이었다. UI X 버튼 경로(handleCloseTab)와
동일하게 untitled(모델 release + closeTab)·browser(closeTab) 분기를 추가.

Close Others/All도 같은 함수를 쓰므로 함께 수정됨.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
entry.ts의 타입정의를 model/types.ts로, registerKnownModelUri/notifiers/provider state를 lsp leaf로 추출해 entry↔attach-*·bridge↔cache 순환 제거. madge 0 순환 확인.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
handleCloseTab/closeTabById 중복 switch를 단일 디스패처 closeTabWithConfirm으로 통합. untitled 닫기 시 Save/Don't Save/Cancel 다이얼로그 추가(saveUntitledModel 3-way outcome, saved→이중 close 방지). 감사 ①의 발산 사례이자 Cmd+W 버그 근본 해소.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
validateArgs→getOrDetect→not-repo→op→refreshStatus→catch 스켈레톤을 withRepo로 통합, 41개 핸들러를 1줄화. 봉투·GitError 서브타입·invalid-args 매핑 보존. 리더/fetchAll/sync/stash-pop/cherry-pick/stream은 형태가 달라 수동 유지.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
internal/git의 run→exitcode→GitError 시퀀스와 branch/workflow/status 에러생성자를 run.go의 capture/gitError로 통합. diff/commitDetail은 ladder가 달라(raw stderr) 의도적 제외. status fallback 문구가 args 포함으로 통일(도달불가 경로).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FromContext가 도달불가(WithLogger가 넣은 값을 아무도 안 읽음)인 agentlog 패키지 전체와 host.go의 put-but-never-get 배선 제거. go build/vet 통과.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
z.string().uuid() 4중 중복과 contract.ts fs arg shape 16개를 WorkspaceIdSchema/workspaceScoped로 통합. 의미상 workspaceId인 인라인만 치환, tabId/sessionId/bookmark id는 보존.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
withFocusedTreePaths/withFocusedSinglePath/makeClipboardCommand로 wsId→tree→paths 전처리와 copy/cut 바이트동일 복붙을 통합. 동작 보존.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main/renderer 110개 console.* 호출을 createLogger facade로 치환(conventions.md 강제사항). 함께: git store operations.ts 잉여 as 캐스트 12개 제거, 로깅 전환으로 깨진 테스트 4건을 logger-facade/계약 단언으로 갱신(log-test-spies 테스트 preload 추가).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
테스터10이 발견한 CRITICAL 2건:
1. closeUntitledWithConfirm이 untitled dirty 체크에 cacheUriFor(ws,"Untitled-N")를 써서 런타임 throw(absolutePath must be absolute) → untitled 탭 닫기가 전부 실패. untitledCacheUriFor(ws,index)로 교정(모델 캐시의 untitled:// 스킴과 일치).
2. use-group-actions.closeTabForId가 통합 안 된 제3의 타입 switch라 context-menu Close/Close Others/All이 untitled/git.commit/browser를 no-op → closeTabWithConfirm 위임으로 통일.
not-dirty 경로 회귀 테스트 추가. 전체 3012 tests 0 fail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
createUntitledEntry가 생성 직후 savedAlternativeVersionId=0/isDirty=true로
강제해, 아무것도 입력하지 않은 untitled도 닫을 때 save-confirm 모달이 떴다.
빈 모델의 현재 alt id를 저장 기준점으로 두어 시작은 clean, 실제 편집 시에만
dirty가 되도록 변경(undo로 빈 상태 복귀 시 다시 clean) — VSCode 동작과 일치.

cmd+s는 untitled의 경우 saveUntitledModel로 직접 라우팅되어 dirty 가드를
타지 않으므로, clean 시작이어도 빈 untitled 저장-as는 그대로 동작한다.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
W0: tests/setup.ts에 electron canonical hermetic stub 추가(ipcMain/ipcRenderer/
webContents/app/BrowserWindow surface), 오염원 lsp-channel(-cancel) 불완전 mock 정리.
process-global mock 순서의존으로 인한 단독통과·전체실패를 제거. 표준·역순·무작위
3시드 전부 3007 pass/0 fail/0 error. 프로덕션(src/**) 동작 변경 없음.

W0 게이트: scripts/test-gate.sh(full/solo/compare/shuffle/coverage/baseline-freeze)
+ tests/.coverage-baseline.txt(66.52% lines). 단독==전체·커버리지 superset 자동검증.

W1: .nexus/memory/pattern-test-design.md 신규(설계 의사결정 지침) + pattern-bun-mock-
conventions.md Rule 5(electron canonical stub) + pattern-test-quality.md E절(회귀 가드).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
W2 결정성(핫스팟): status-coalescer·browse-session-registry에 nowFn DI seam
(default Date.now, 호출부 불변 — Rule 3), file-delete 실지연 제거. 프로덕션 동작 불변.
W3a 무손실 파라미터화: file-icon-resolvers/url-classifier/keybinding-parse를
test.each로 압축, expect 카운트 51/61/49 보존(단언 약화 케이스는 별도 유지).
W3b 구현결합: claude-status since-값 단언 재작성(mutation spot-check 통과) +
EMPTY_TABS 항등 테스트 삭제. 다수 '참조동일성' 테스트는 useSyncExternalStore
return-state 가드 계약으로 정당 보존.
W4: 렌더러 180파일 C104/B37/R33 분류 → tests/REFACTOR-BACKLOG.md.

전 구간 게이트 green: full 3011 pass/0 fail/0 error, shuffle 다수 시드,
coverage 66.52%==baseline(무손실). next-cycle 잔여는 백로그에 정직 기록.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
게이트에 solo-all 모드 추가(전 314파일 단독 검사로 '전체통과·단독실패' 빈틈 마감).
단독실패 4개를 hermetic 수정(삭제 아님): terminal-services·load-external-entry는
ipc/client mock에 unwrapIpcResult 등 누락 surface 추가, bulk-close-cancel·pinned-tab은
mock 경로 불일치(barrel vs close-handler 직접 import) 교정.

과감 삭제 스윕 결과: 안전 삭제 대상 사실상 없음(후보 대부분 결과/상태 단언 기반 정당
테스트, AP-4 0건). 과도삭제 2건(open-editor·tabs/store: 회귀가드·제목파생 로직)은 되돌림.

검증: solo-all 314/314 PASS, full 0 fail/0 error, shuffle green, coverage 66.52%==baseline.
src/** 동작 변경 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tests/.coverage-baseline.txt, tests/REFACTOR-BACKLOG.md 삭제.
둘 다 이번 테스트 오버홀의 일회성 스캐폴딩/메모 — 코드 참조 없음
(내용은 .nexus/history.json 회고에 보존). test-gate.sh의 coverage 모드는
baseline 부재 시 안내만 출력하고 graceful 동작, 필요시 baseline-freeze로 재생성.
나머지 게이트 모드(full/solo/solo-all/shuffle)·설계 지침 문서는 유지.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
마크다운 프리뷰가 GitHub처럼 inline HTML(<div align>, <img width>,
<details> 등)을 렌더링하도록 rehype-raw를 추가하고, 곧바로
rehype-sanitize 기본 스키마(GitHub allowlist)로 정화한다.

- 플러그인 순서 raw → sanitize → slug → highlight: sanitize는
  raw 뒤·highlight 앞에 위치해야 함(allowlist가 <span> className을
  막아 hljs-* 구문강조가 제거되는 것을 방지)
- <script>(내용 포함)·<iframe>·on* 핸들러·잘못된 URL 스킴 제거
- 기존 워크스페이스 이미지 가드(nexus-workspace://)·링크 allowlist는
  HTML 출신 태그에도 그대로 적용
- 보안 회귀 테스트 갱신 + GitHub-allowlisted HTML 렌더 테스트 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
유사 에디터(Lapce/Void) 패턴에 맞춰 README를 매력→설치→세부
흐름으로 재구성한다.

- 히어로(스크린샷·배지·태그라인) + "왜 NexusCode" + Features
- 멀티 워크스페이스 전환 GIF, 멀티패널 히어로 이미지 추가
- 장황한 Gatekeeper 단계는 <details>로 접고, 풀 단축키 표는
  docs/SHORTCUTS.md로 분리(README엔 핵심만)
- 라이선스 TBD → MIT (LICENSE 파일 + package.json license 필드)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
배포 절차가 main 전제로만 쓰여 있어 develop→main 흐름이 누락돼 있던
것을 보완한다.

- 0단계 신규: 브랜치 모델(develop=통합/main=릴리스, CI는 main PR/push
  게이트)과 SemVer(pre-1.0) bump 정책
- 단계 재구성: 준비(develop) → main 승격(PR+CI) → Release 발행 → 검증.
  bump 커밋이 태그 대상이고 태그 target=main임을 명시
- 릴리스 노트 추출법(git log <tag>..develop) 추가, stale 예시 정리
- "미래 작업(Apple 서명)" 섹션 제거 (지식은 .nexus/memory에 잔존)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.4.0 이후 사용자 기능 추가(아이콘 테마·dirty-diff 거터·GitHub식 HTML
프리뷰·탭 동작 통합)로 SemVer minor bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
biome 2.4.14에서 noCommonJs가 nursery→style로 졸업하면서, 옛 카테고리를
가리키던 biome-ignore 주석이 suppressions/parse 에러를 내 CI lint를
막고 있었다. develop이 CI 게이트 밖이라 누적된 기존 문제로, 릴리스
작업과는 무관하다.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@moreih29 moreih29 merged commit 93916bf into main May 31, 2026
1 check passed
moreih29 added a commit that referenced this pull request Jun 2, 2026
Follow-up to the idle watchdog (e528ccd). Review surfaced six issues
across correctness, scope, and recovery; this addresses all of them.

#1 Monotonic clock: lastInbound was stored as wall-clock UnixNano and
compared via time.Since on a time.Unix value, which silently falls back
to wall-clock arithmetic. A laptop waking from sleep (local agent) or an
NTP step (remote) made elapsed jump past the limit and reap a live
session. Now anchored to a monotonic startMono via stampInbound/
idleElapsed.

#2 Scope: the watchdog ran for local agents too, where parent death
already arrives as stdin EOF (plus Pdeathsig on Linux) — pure downside.
Now gated on a new --idle-watchdog flag the SSH launch sets and the local
launch omits.

#3 Threshold: 60s limit with 3-ping margin was tight enough that a
stalled Electron main thread (ping is event-loop bound; ssh ServerAlive
is not) could trip it. Widened to 90s limit / 15s ping (6 slots), with
the check interval decoupled to limit/6 so the kill window stays tight.

#4 Contract: client ping was gated on heartbeat advertisement, the agent
watchdog on nothing — drift-prone. The agent now advertises idleWatchdogMs
in the Ready frame; the client pings iff positive, at idleWatchdogMs/6.

#5 Orphans: drainAndExit reaches os.Exit, which skips the `defer
pty.Close()`. Linux survived via Pdeathsig; a darwin remote (supported,
shipped) had only SIGHUP-on-fd-close, so SIGHUP-ignoring children
orphaned. PTY cleanup is now a shutdown hook that SIGKILLs each process
group on every OS.

#6 Recovery: the watchdog exited 0, which the client's handleClose treats
as a clean terminal exit (no reconnect). On a false positive (client
alive but stalled) the session died permanently. Now exits 75
(EX_TEMPFAIL) so the client reconnects.

Co-Authored-By: Claude Opus 4.8 <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