Beginner's architecture guide

CodeAssist
Tauri · Rust 입문 지도

Rust와 Tauri를 처음 보는 사람을 위한 현재 코드 안내서임. 로그인 뒤 Snap과 Snippet을 열고, 전역 단축키·캡처·붙여넣기가 Windows에서 실행되는 과정을 실제 파일 순서대로 설명함.

생성일: 2026-09-16

한눈에

React가 화면을 그리고, Rust가 데스크톱 앱을 운영하며, Tauri가 둘 사이의 다리가 됨.

화면 코드는 2_frontend, 데스크톱 실행부와 Windows 기능은 4_rust_tauri에 있음. 현재 화면은 로그인, Snap, Snippet만 남아 있음. Tauri가 React를 WebView 안에 띄우고 두 영역의 통신을 연결함.

Tauri 2Rust 2021React + TypeScriptWindows APISQLiteWebView
처음 볼 때 기준점
main.rs는 시작 버튼, lib.rs는 조립 설명서, bridge/는 프론트 통역사, shell/은 운영체제 작업자라고 보면 됨.

개발 배경 / 문제정의

브라우저만으로 할 수 없는 데스크톱 기능을 React 화면에 붙이는 것이 이 Rust 영역의 목적임.

CodeAssist는 일반 웹페이지와 달리 앱이 화면 뒤에 있어도 전역 단축키로 나타나야 함. 시스템 트레이에 머물고, 다른 프로그램을 기억했다가 코드를 붙여넣으며, 화면 영역도 캡처해야 함. 이런 일은 브라우저 권한 밖이므로 Rust가 맡음.

대상 독자는 React 경험도 Rust 경험도 없어도 됨. 소유권 같은 깊은 문법보다 “어느 파일이 언제 불리고, 데이터가 어느 방향으로 이동하는가”를 먼저 잡는 데 초점을 둠.

기대효과 / 임팩트

이 지도를 읽고 나면 기능 이름을 보고 수정할 파일과 통신 방향을 먼저 짚을 수 있음.

  • src-tauri 아래 설정과 Rust 파일의 역할을 구분함.
  • React의 invoke가 어떤 Rust 함수로 연결되는지 추적함.
  • Rust 이벤트가 React 화면에 도착하는 반대 흐름도 이해함.
  • 새 전역 단축키, 창 동작, 저장 기능을 어디에 추가할지 판단할 수 있음.

시스템 구조

한 프로세스 안에 WebView 화면과 Rust 호스트가 함께 있고, 명령과 이벤트가 경계를 넘음.

React SPA화면·라우팅·사용자 입력
2_frontend
Tauri bridgeinvoke: JS → Rust
event: Rust → JS
Rust host창·단축키·트레이·캡처
SQLite·Windows API

부팅 순서

1. main.rs운영체제가 실행 파일의 main 함수를 호출함
2. lib.rs의 run()Tauri Builder에 plugin, state, command, setup을 차례로 등록함
3. tauri.conf.json개발 서버 또는 빌드된 React 파일을 main WebView에 띄움
4. setup트레이를 만들고 전역 단축키를 등록한 뒤 이벤트 루프에 들어감

핵심 기능

사용자 기능은 로그인·Snap·Snippet 세 축이고, Rust는 이 화면들이 데스크톱처럼 동작하게 만듦.

로그인과 세션

보호된 도구에 들어가기 전 사용자를 확인하고 토큰 갱신과 만료 안내를 처리함.

Snap

대화 목록, 새 질문, 스트리밍 답변, 코드 탐색, 이미지 첨부를 한 흐름으로 제공함.

Snippet

SQLite의 코드 조각을 검색·미리보기·편집하고 원래 프로그램에 붙여넣음.

전역 단축키

Ctrl+Shift+8은 Snap, 7은 Snippet, 9는 영역 캡처를 실행함.

화면 캡처

Windows 오버레이에서 고른 영역을 PNG data URL로 바꿔 새 Snap 질문에 붙임.

창과 트레이

창 표시·숨김·크기 복원·항상 위·단일 실행을 Rust에서 관리함.

대상 앱 붙여넣기

CodeAssist를 열기 직전의 프로그램을 기억하고 선택한 스니펫을 돌려보냄.

전체 화면 지도

화면은 로그인과 Snap·Snippet에만 집중되며 캡처는 별도 route 없는 Rust 기능임.

Rust 쪽에서 직접 소환하는 중심 화면은 Snap과 Snippet임. 캡처는 독립 페이지가 아니라 Rust 오버레이 기능이며, 완료되면 /snap/new 화면으로 이미지를 보냄. 로그인은 보호된 두 도구로 들어가기 위한 입구임.

주소화면역할
/login로그인인증되지 않은 사용자의 입구임
/snapSnap 대화 목록대화 목록과 검색 결과를 보여줌
/snap/new새 Snap 대화질문과 캡처·클립보드 이미지를 첨부해 새 세션을 시작함
/snap/s/:idSnap 대화특정 세션의 메시지와 스트리밍 답변을 보여줌
/snippetSnippet Palette코드 조각을 검색·미리보기·편집하고 원래 앱에 붙여넣음
별도 주소 없음영역 캡처 오버레이Ctrl+Shift+9로 Rust가 띄우며 완료 뒤 /snap/new로 이동함
기본 이동
로그인 성공 뒤와 알 수 없는 주소로 접근했을 때 모두 /snap으로 이동함.

주요 시나리오 / 데이터 흐름

화면 캡처는 Windows 입력에서 시작해 Rust 이벤트를 거쳐 React 첨부 이미지로 끝남.

순서담당무슨 일이 생기는가
1hotkey.rsCtrl+Shift+9 입력을 감지하고 on_hotkey를 부름
2lib.rscapture::start에 완료 콜백을 넘김
3capture.rs별도 thread에서 전체 화면 오버레이와 Windows 메시지 루프를 실행함
4capture.rs선택 영역 픽셀을 BGRA에서 RGBA로 바꾸고 PNG·Base64로 인코딩함
5bridge/mod.rsnavigatecapture.image 이벤트를 bridge 채널로 보냄
6React bridge이벤트를 받아 새 채팅으로 이동하고 Composer에 이미지를 붙임

반대 방향: 스니펫 목록 요청

Reactinvoke("snippets_list")
commands.rs등록된 command 실행
snippets.rsSQLite 조회 후 JSON 반환

코드 구조 지도

프론트는 화면별 feature로, Rust는 조립·통신·운영체제 기능으로 나뉨.

2_frontend/src/ ├─ routes.tsx 로그인·Snap·Snippet route 트리 ├─ features/ │ ├─ auth/ 로그인·세션 확인·자동 갱신 │ ├─ snap/ 대화 목록·새 대화·스트리밍·이미지 입력 │ └─ snippets/ 검색·편집·미리보기 UI ├─ lib/bridge/ invoke와 event를 감싸는 TypeScript 통신부 └─ shared/components/ ├─ ProtectedRoute.tsx 로그인하지 않은 접근을 차단함 └─ PaletteShell.tsx Snap·Snippet 공통 수명과 세션을 관리함 4_rust_tauri/ ├─ package.json Tauri CLI 실행 명령 ├─ docs-lib/ 사용 중인 라이브러리 참고 문서 └─ src-tauri/ ├─ Cargo.toml Rust 패키지와 dependency 목록 ├─ tauri.conf.json 제품명·창·React 빌드 연결 설정 ├─ capabilities/default.json WebView에 허용할 Tauri 권한 ├─ build.rs Tauri 빌드 준비 코드 └─ src/ ├─ main.rs 실행 파일 진입점 ├─ lib.rs 앱 전체 조립과 기능별 동작 결정 ├─ bridge/ │ ├─ mod.rs Rust → React 이벤트 타입 │ ├─ commands.rs React → Rust command │ └─ snippets.rs SQLite 저장소 └─ shell/ ├─ hotkey.rs 전역 단축키 등록 ├─ tray.rs 시스템 트레이 ├─ window.rs 메인 창 상태와 크기 ├─ paste.rs 다른 앱·클립보드·키 입력 └─ capture.rs 영역 선택과 PNG 캡처

처음에는 routes.tsx → PaletteShell → lib.rs → bridge 또는 shell 순서로 읽는 편이 좋음. bridge는 “화면과 무엇을 주고받는가”, shell은 “Windows에서 실제로 어떻게 하는가”에 답함.

핵심 코드 해설 — 왜 이렇게 짰나

Rust 코드는 함수, 타입, 성공·실패 표현만 먼저 알아도 이 프로젝트의 큰 흐름을 읽을 수 있음.

fn은 함수, let은 변수, pub은 다른 모듈에서도 쓸 수 있다는 뜻임. &AppHandle&는 값을 넘겨주는 대신 잠깐 빌려 쓴다는 표시임. match는 값에 따라 갈라지는 분기이고, Result<T, E>는 성공 Ok(T) 또는 실패 Err(E)를 담음.

fn show_palette(app: &AppHandle) -> tauri::Result<()> {
    window::show(app)?;
    Ok(())
}

main.rs를 얇게 둬서 실행과 앱 구성을 분리함.

main은 Rust 실행 파일이 반드시 가지는 시작 함수임. 실제 구성은 library의 run에 맡겨 테스트와 플랫폼 진입점을 다루기 쉬움.

fn main() {
    codeassist_tauri_lib::run()
}

Builder 체인은 앱에 필요한 부품을 등록한 뒤 한 번에 실행함.

.plugin은 Tauri 확장 기능, .manage는 여러 command가 함께 쓸 상태, .invoke_handler는 React가 부를 수 있는 함수 목록임. 마지막 .build 전까지는 설정을 모으는 단계임.

tauri::Builder::default()
    .plugin(tauri_plugin_global_shortcut::Builder::new().build())
    .manage(commands::RouteState::default())
    .manage(paste::PasteState::default())
    .invoke_handler(tauri::generate_handler![
        commands::window_hide,
        commands::report_route,
        commands::snippets_list,
    ])
    .setup(|app| { /* 시작 직후 준비 작업 */ Ok(()) })
    .build(tauri::generate_context!())
    .expect("Tauri 앱 실행 실패");

#[tauri::command]가 붙은 함수만 React의 invoke 요청을 받을 수 있음.

AppHandle은 앱 전체 조작 손잡이이고, State는 Builder에서 등록한 공유 상태를 꺼냄. Result<T, String>은 성공값 또는 화면에 전달할 오류를 표현함.

#[tauri::command]
pub fn report_route(
    app: AppHandle,
    path: String,
    state: State<'_, RouteState>,
) -> Result<(), String> {
    let mut routes = state.lock()
        .map_err(|_| "현재 화면 상태를 읽지 못했어")?;
    routes.report(&path);
    Ok(())
}

Rust enum 하나로 React에 보낼 이벤트 모양을 제한함.

serde가 enum을 JSON으로 바꿈. tag = "type" 때문에 결과에 type 필드가 생기고, React는 이 값으로 이벤트 종류를 나눔.

#[derive(Serialize, Clone)]
#[serde(tag = "type")]
pub enum Push {
    #[serde(rename = "navigate")]
    Navigate { path: String },
    #[serde(rename = "capture.image")]
    CaptureImage {
        #[serde(rename = "dataUrl")]
        data_url: String,
    },
}

캡처는 UI를 멈추지 않도록 별도 thread에서 실행함.

Windows 메시지 루프는 선택이 끝날 때까지 기다림. 메인 thread에서 실행하면 React 창까지 얼 수 있으므로 std::thread::spawn으로 분리함. AtomicBool은 캡처 창이 두 개 생기는 것을 막음.

if CAPTURING.swap(true, Ordering::AcqRel) {
    return;
}
std::thread::spawn(move || {
    let result = run_overlay()
        .and_then(|rect| rect.map(capture_png_data_url).transpose());
    CAPTURING.store(false, Ordering::Release);
    // 성공 결과를 콜백으로 전달함
});

기술 스택 & 선택 이유

UI 생산성은 웹 스택에서 얻고, 시스템 접근은 Rust와 전용 crate가 담당함.

기술현재 역할
Tauri 2WebView 창, command/event 통신, 앱 생명주기, bundle을 제공함
Rust앱 조립, 상태, 오류 처리, Windows 기능을 안전한 함수 경계로 묶음
React + TypeScript실제 화면, 라우팅, 채팅과 스니펫 사용자 경험을 담당함
windows crateGDI 캡처, 클립보드, 다른 창 탐색, 키 입력 같은 Win32 API를 호출함
rusqlite별도 DB 서버 없이 로컬 스니펫을 저장함
serdeRust 구조체와 enum을 프론트가 읽는 JSON으로 변환함