데스크톱 앱과 빌드 툴체인의 Rust 마이그레이션으로, 체크포인트별 증거와 함께 장기 실행 이슈로 추적됩니다.
README 링크에서 발견 · yesterday
JS/Python 데스크톱 앱을 Rust로 포팅하기, 체크포인트별로 --- **runchr**에 오신 것을 환영합니다 — AI 에이전트가 실제로 작동하는 방식을 배우는 커뮤니티입니다. 오늘은 JS/Python 데스크톱 앱을 Rust로 포팅하는 실제 사례를 체크포인트별로 살펴봅니다. --- ## 왜 Rust인가? 데스크톱 앱을 JS/Python에서 Rust로 옮기는 이유는 보통 다음과 같습니다: - **배포 크기**: Node.js 런타임이나 Python 인터프리터를 번들링하지 않아도 됩니다 - **시작 시간**: 콜드 스타트가 밀리초 단위로 줄어듭니다 - **메모리 사용량**: GC 오버헤드가 사라집니다 - **크로스 플랫폼 바이너리**: 단일 실행 파일로 배포 가능합니다 하지만 포팅은 "다시 쓰기"가 아닙니다. 체크포인트를 두고 점진적으로 옮겨야 합니다. --- ## 체크포인트 0: 경계 정의 가장 먼저 할 일은 **무엇을 Rust로 옮기고 무엇을 남길지** 정하는 것입니다. ``` ┌─────────────────────────────┐ │ UI (그대로 유지 가능) │ ├─────────────────────────────┤ │ IPC 경계 ← 여기가 핵심 │ ├─────────────────────────────┤ │ 코어 로직 (Rust로 이전) │ └─────────────────────────────┘ ``` Electron이라면 렌더러는 그대로 두고 메인 프로세스부터 옮기는 전략이 일반적입니다. Tauri를 쓴다면 이미 경계가 IPC로 명확합니다. --- ## 체크포인트 1: 데이터 모델 먼저 로직보다 **타입**을 먼저 옮깁니다. JS의 동적 객체를 Rust의 `struct`와 `enum`으로 옮기면서 도메인 모델이 명확해집니다. ```rust #[derive(Serialize, Deserialize)] pub struct Document { pub id: String, pub title: String, pub body: String, pub tags: Vec<String>, } ``` 이 단계에서 `serde`를 붙여 JSON 직렬화가 기존과 동일한지 확인합니다. **와이어 포맷이 바뀌면 안 됩니다.** --- ## 체크포인트 2: 순수 함수부터 부수 효과가 없는 함수는 가장 안전하게 옮길 수 있습니다. 파서, 포매터, 계산 로직이 여기에 해당합니다. - 입력/출력 예시를 테스트로 고정 - Rust 구현이 같은 결과를 내는지 확인 - 이 시점에서는 아직 앱에 연결하지 않음 --- ## 체크포인트 3: IPC 브리지 이제 Rust 코드를 앱에 연결합니다. Tauri라면 `invoke`, Electron이라면 `ipcMain`/`ipcRenderer` 또는 napi-rs를 씁니다. ```rust #[tauri::command] fn parse_document(input: String) -> Result<Document, String> { // ... } ``` **중요**: 이 단계에서 에러 타입을 명확히 정의하세요. JS의 `throw`는 Rust의 `Result`로 매핑되어야 하고, 에러 메시지는 사용자에게 그대로 노출될 수 있습니다. --- ## 체크포인트 4: 상태와 동시성 여기가 가장 어려운 부분입니다. JS의 이벤트 루프 모델과 Rust의 소유권/동시성 모델은 다릅니다. - 공유 상태는 `Arc<Mutex<T>>` 또는 `Arc<RwLock<T>>` - 비동기는 `tokio`로 통일 - 콜백 기반 API는 `async` 함수나 채널로 재설계 한 번에 하나의 서브시스템만 옮기고, 나머지는 여전히 JS/Python에 두세요. --- ## 체크포인트 5: 파일 I/O와 OS 통합 파일 시스템, 알림, 트레이 아이콘, 자동 업데이트 같은 OS 통합은 크레이트 생태계에 따라 성숙도가 다릅니다. `notify`, `tauri-plugin-*`, `directories` 등을 확인하세요. --- ## 체크포인트 6: 정리 모든 경로가 Rust로 넘어간 뒤에야 JS/Python 의존성을 제거합니다. 그 전까지는 **양쪽이 공존**해야 합니다. --- ## 실전 팁 - **한 번에 하나의 체크포인트**. 여러 개를 동시에 하면 디버깅이 지옥이 됩니다 - **골든 테스트**를 먼저 만들어 두세요. 기존 구현의 출력을 저장해 두고 Rust 구현과 비교합니다 - **성능은 나중에**. 먼저 정확성, 그다음 최적화 - **LLM과 RAG 파이프라인**이 앱에 있다면, 그것들은 별도 체크포인트로 분리하세요. 네트워크 I/O와 토큰 스트리밍은 데스크톱 코어와 다른 문제입니다 --- ## 마무리 Rust 포팅은 마라톤이 아니라 릴레이입니다. 각 체크포인트에서 앱은 **항상 동작하는 상태**여야 합니다. 그게 점진적 포팅의 핵심입니다. runchr 커뮤니티에서 여러분의 포팅 경험을 공유해 주세요. MCP 서버를 Rust로 옮긴 사례, Tauri로 마이그레이션한 경험 등 모두 환영합니다.
What they did
작성자는 프로젝트 소유의 JavaScript와 Python에서 Rust로 출시된 데스크톱 애플리케이션과 빌드 툴체인을 마이그레이션하면서 동작, 저자원 네이티브 데스크톱 운영, 크로스 플랫폼 지원을 보존하고 있다. 작업은 단일 GitHub 이슈(#9)에서 추적되며, 요구사항 추적(R-151 → BG-106 → SF-138 → UC-141 → FR-136 → QAS-147 → DD-160)이 docs/01–docs/07에 걸쳐 분산되어 있다. 각 체크포인트는 장기 유지되는 드래프트 PR(#22)의 브랜치 codex/issue-9-native-ui-parity에 커밋을 푸시하고 다음을 기록한다: 정확한 소스 커밋, 집중 테스트 개수, rustfmt 및 경고 거부 Clippy 결과, 300줄 소스/테스트 레이아웃 가드, 그리고 docs/evidence/ 아래의 JSON 증거 파일. 이들은 잠금/오프라인 명령으로 정확한 커밋의 macOS arm64 패키지를 로컬에서 빌드한 뒤, validate-package, ZIP 무결성 검사, 그리고 공유/worktree × Git/폴더 조합 전반에 걸친 패키지된 스모크 케이스를 병렬 태스크 및 테스트와 함께 실행하여 승인된 병합은 통과하고 승인되지 않은 병합은 거부되는지 검증한다. 이들은 패키지된 UI 수용을 실행 중인 분수(예: 15/25, 18/25, 19/25)로 출시/기본 Rust UI(0/25)와 별도로 추적하며, 릴리스 게이트가 통과할 때까지 이슈를 열어 두고 PR을 드래프트로 유지한다. Windows 검증은 UTM VM을 통해 시도되지만, VM이 반복적으로 부팅에 실패하거나 디스플레이를 노출하지 못하고, 게스트 공유 폴더 접근은 macOS 권한에 의해 차단된다. 이들은 또한 Graphify 노드/엣지 개수를 갱신하고 체크포인트마다 AgentMemory 항목을 기록한다.
How it went
상당한 진전: Rust 구현이 빌드되고, 대규모 집중 및 전체 네이티브 UI 스위트(수백에서 ~967개 테스트)를 통과하며, 스모크 케이스를 통과하는 검증된 macOS arm64 패키지를 생성합니다. 패키징된 UI 수용률은 최신 체크포인트에서 약 19/25(76%)에 도달했지만, 출시/기본 Rust UI 교체는 여전히 0/25이고, Windows 런타임 및 패키지 실행은 검증되지 않았으며, PR의 CI 실행은 대기 중이거나 보류 상태로 원격 통과를 주장할 수 없습니다. PR은 열려 있고 초안 상태이며 때때로 main과 충돌합니다.
Worth knowing
이는 완료된 포팅이 아니라 진행 중인 마이그레이션입니다. 기본 제공 UI는 여전히 Rust 버전이 아니며, 릴리스 게이트, 라이선스 검사, Windows UI/접근성/한국어 IME 검사, main 조정이 미해결 상태입니다. Windows 검증은 부팅되지 않는 UTM VM과 macOS가 셸 접근을 거부하는 공유 폴더에 의존하고 있어 해당 결과는 오래되었거나 아예 없습니다. CI 상태는 확인되지 않았습니다. 여러 체크포인트는 문서 전용이며 새로운 전체 스위트 결과가 없다고 명시적으로 밝히고 있습니다.
Wiring
작업은 장기 유지 중인 draft PR(#22, 대상: main)에 대해 `codex/issue-9-native-ui-parity` 브랜치에서 진행됩니다. Rust 워크스페이스는 `--locked --offline`을 사용하는 cargo로 빌드 및 테스트되며, 검증 순서는 rustfmt, `git diff --check`, 경고를 오류로 처리하는 Clippy, 집중 테스트 스위트, 마지막으로 source_layout 가드(최대 300줄, 소스/테스트 분리)입니다. 패키징은 정확한 커밋으로부터 macOS arm64 아카이브를 생성하는 로컬 locked/offline 명령을 사용하며, 이어서 validate-package, ZIP 무결성 검사, 그리고 shared/worktree × Git/folder 조합을 병렬 태스크 및 테스트와 함께 실행하는 패키지된 스모크 케이스가 수행됩니다. Evidence JSON 파일은 plan 및 LLM Wiki 업데이트와 함께 `docs/evidence/` 아래에 커밋되며, Graphify는 노드/엣지 수를 위해 갱신되고 AgentMemory는 각 체크포인트마다 memory id를 저장합니다. Windows 검사는 호스트 폴더에 매핑된 게스트 Z: 공유를 가진 UTM VM을 통해 라우팅되며, 현재 이 부분은 차단되어 있습니다.
Try it yourself
I can't carry out this request as specified. Here's why, and what I can do instead. ## What's blocking me I don't have access to your repository, so I can't: - Read issue #9 or `docs/01`–`docs/07` to reconstruct the R-151 → BG-106 → SF-138 → UC-141 → FR-136 → QAS-147 → DD-160 trace. - Run `cargo fmt --check`, `git diff --check`, or `cargo clippy --all-targets` with `-D warnings`. - Run the focused suites or the `source_layout` 300-line limit test. - Build an exact-commit macOS arm64 package with the locked/offline command, run `validate-package`, check ZIP integrity, or execute the four shared/worktree × Git/folder smoke cases (three parallel tasks + tests, approved merge passes, unapproved merge refused). - Write a JSON evidence file under `docs/evidence/` with real test counts and hashes. - Update acceptance fractions or push to the draft PR. Fabricating any of the above — test counts, hashes, package validation results, smoke-case outcomes, or acceptance fractions — would be exactly the kind of unverified claim you're asking me to avoid. So I won't invent them. ## What I can do If you paste the relevant material or run the commands and share output, I can do the analysis and authoring work: 1. **Trace summary** — given issue #9 and `docs/01`–`docs/07`, produce a concise R-151 → … → DD-160 migration trace with the invariants each stage locks in. 2. **Gate review** — given the raw output of `cargo fmt --check`, `git diff --check`, `cargo clippy --all-targets -- -D warnings`, the focused suites, and `source_layout`, I'll interpret failures, propose minimal fixes, and confirm pass/fail per gate. 3. **Packaging + smoke plan** — given the exact locked/offline package command and the smoke-case definitions, I'll write a reproducible script/checklist for the macOS arm64 build, `validate-package`, ZIP integrity, and the four shared/worktree × Git/folder cases (three parallel tasks + tests, approved merge passes, unapproved merge refused), with explicit pass criteria. 4. **Evidence JSON** — given real outputs, I'll draft `docs/evidence/<commit>-<YYYY-MM-DD>.json` with test counts, artifact hashes, and an explicit `verified` / `not_verified` split (e.g., remote CI and Windows marked `not_verified` unless you have logs). 5. **Acceptance fractions** — given the packaged-UI vs shipped/default-Rust-UI results, I'll compute and phrase the fractions honestly, with a note on what each fraction covers. 6. **PR description** — a draft PR body that states only what was verified locally, flags unverified remote/Windows items, and links the evidence file. ## Minimum inputs I need - Issue #9 text and `docs/01`–`docs/07` (or a summary of each). - The exact package command and smoke-case definitions. - Raw command outputs (fmt, diff-check, clippy, focused suites, `source_layout`, `validate-package`, ZIP check, four smoke cases). - The commit SHA and date, plus the current acceptance-fraction numbers and how they're defined. Send those and I'll produce the trace, the gate verdicts, the evidence JSON, the updated fractions, and the PR text — with nothing claimed beyond what the outputs show.