본문으로 건너뛰기
5분 AI.
TRENDSHIFT MONTHLY #5 딥다이브 · 2026-10-23 분석

artcraft 딥다이브
— 장면을 만들고 AI를 지휘하는 창작 도구의 설계

데스크탑 앱

빠른 판단

무엇
2D 캔버스·3D 구도·카메라 편집과 여러 공급자의 AI 이미지·영상 생성을 연결한 데스크탑 창작 도구
이럴 때
참조와 장면 배치를 직접 조정하며 구도 설계·생성 요청·결과 수정을 한 흐름에서 진행할 때
입력
참조 이미지·캔버스·3D 장면과 생성 설정
결과
선택한 공급자가 생성한 이미지·영상과 편집 장면
한 문장을 넣고 결과를 기다리는 데서 더 나아가, 2D 캔버스와 3D 장면으로 구도·배치·카메라를 직접 정하는 창작 도구입니다. Rust·Tauri와 React가 만나는 경계, 외부 생성 서비스 연결, SQLite 작업 추적을 따라가며 오래 걸리는 일을 끊기지 않는 사용자 경험으로 만드는 법을 배웁니다. Rust 약 27.2만 줄, TypeScript·TSX 약 20.7만 줄의 모노레포입니다. (storytold/artcraft · ⭐35,748 · 포크 5,656 · 2026-10-23 GitHub 기준 · 코드: 커밋 800217e 기준)
목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

AI 모델을 만드는 연구 코드보다, 모델을 창작 과정에 연결하는 응용 프로그램

ArtCraft는 이미지·영상 생성을 중심으로, 캔버스 편집과 3D 장면 구성, 여러 생성 제공자 연결을 묶은 데스크톱 창작 도구입니다. README는 이를 “예술가를 위한 IDE”라고 소개합니다. 개발자가 코드 편집기 안에서 여러 도구를 쓰듯, 창작자가 장면 구성과 생성 결과를 한 흐름 안에서 다루게 하려는 방향입니다.

이 저장소에서 배울 핵심은 AI 모델의 내부보다, 창작 화면과 외부 생성 작업을 안정적으로 연결하는 앱의 구조입니다. 기본 앱은 React 화면을 Tauri로 감싸고 Rust가 인증·네트워크·작업 추적을 담당합니다. 루트 Cargo.toml의 default-members도 crates/desktop/artcraft를 가리킵니다.

전체를 이해하는 비유

ArtCraft는 촬영 스튜디오의 감독석에 가깝습니다

2D 캔버스는 콘티 보드, 3D 장면은 가상 세트장입니다. 창작자가 소품·배우·카메라를 배치하면, 앱이 그 의도를 생성 서비스에 전달합니다. 모델은 결과물을 만드는 제작팀이고, 작업 ID와 로컬 DB는 촬영 주문서와 진행 장부입니다.

감독석이 내 컴퓨터에 있다고 제작팀까지 모두 내 컴퓨터에서 일하는 것은 아닙니다. 현재 코드의 주요 생성 경로는 외부 API를 호출합니다. 화면 편집·미리보기와 모델 추론을 구분해야 하드웨어 요구와 개인정보 흐름도 이해할 수 있습니다.

사용자 행동을 코드의 책임으로 바꿔 보기

사용자 행동앱이 하는 일읽어볼 위치
그림을 배치한다Konva의 레이어·선·이미지와 편집 상태를 관리합니다.frontend/libs/components/pagedraw/
장면을 구성한다Three.js 장면과 카메라를 편집하고 UI에 상태를 알립니다.frontend/libs/components/pagescene/
생성을 누른다Tauri 명령으로 요청을 전달하고 생성 API의 작업 ID를 받습니다.crates/desktop/artcraft/src/core/commands/generate/ 아래의 Rust 코드
완료를 기다린다원격 상태와 로컬 작업을 맞추고 결과 이벤트를 보냅니다.crates/desktop/artcraft/src/services/storyteller/threads/

화면 편집은 프론트엔드 라이브러리에서, 계정과 작업 추적은 데스크톱 Rust 크레이트에서 찾을 수 있습니다. 한 기능의 양쪽 끝을 함께 읽으면 “누가 데이터를 가지고 있고, 누가 다음 행동을 결정하는가”가 드러납니다.

용어
모노레포와 크레이트
모노레포는 관련 앱·라이브러리를 한 저장소에 함께 두는 구성입니다. Rust의 크레이트(crate)는 컴파일 단위이고, Cargo 워크스페이스는 여러 크레이트의 의존성을 함께 관리합니다. 폴더가 많아도 한 번에 모두 읽을 필요는 없습니다. 한 기능이 통과하는 경계부터 따라가면 됩니다.

코드의 또 다른 입구는 artcraft-native/입니다. egui 기반의 별도 Rust 앱으로, 기본 Tauri 앱과 API 클라이언트 크레이트를 공유합니다. 같은 제품 영역을 웹 기술과 Rust UI로 각각 풀어낸 사례라 비교 학습에 좋습니다.

2왜 주목받는가

인기 순위와 제품의 설계적 매력은 구분해서 읽기

선정 기준은 TrendShift Monthly #5이며, 2026-10-23 GitHub 기준 스타는 35,748개입니다. 다만 순위와 누적 스타만으로 특정 기능이 유행의 직접 원인이라고 증명할 수는 없습니다. 아래는 README의 제품 방향과 소스 구조에서 읽을 수 있는 관심을 끌 만한 이유입니다.

ArtCraft의 설계적 매력은 모델 선택지를 늘리는 데 더해, 생성 전의 시각적 의도를 편집 가능한 상태로 만든다는 점에 있습니다. 다만 결과의 품질·속도가 다른 제품보다 높다는 비교 실험은 이 코드 분석으로 확인할 수 없습니다.

어떤 작업 방식과 비교하면 차이가 보일까?

작업 방식주로 직접 다루는 것ArtCraft에서 얻는 이점과 선택 기준
단일 모델의 입력 화면문장·참조 이미지·생성 옵션장면·캔버스와 생성 결과를 연결할 수 있습니다. 한 모델의 간단한 입력만 필요하면 통합 편집기가 과할 수도 있습니다.
ComfyUI노드 그래프로 표현한 생성 작업 흐름ArtCraft는 장면과 화면 조작을 출발점으로 삼습니다. 생성 파이프라인 자체를 조립하고 재사용하려면 ComfyUI의 접근도 함께 살펴볼 만합니다.
직접 만든 API 연동 화면한두 API의 요청·응답제공자 변환, 작업 복구, 결과 알림처럼 운영 단계에서 필요한 설계를 한꺼번에 관찰할 수 있습니다.

ComfyUI의 노드 그래프·재사용 작업 흐름 설명은 공식 README 기준입니다. 위 표는 작업 방식의 비교이며 기능 전수 조사나 성능 순위가 아닙니다. ComfyUI에도 클라우드 사용 경로가 있으므로 “ComfyUI는 로컬, ArtCraft는 클라우드”처럼 양분하면 부정확합니다.

초보자에게 유용한 관점
완성된 기능보다 책임을 나눈 방법을 먼저 배웁니다

파일 업로드, 외부 작업 접수, 상태 조회, 결과 표시를 처음부터 한 함수에 넣지 않는 방법을 관찰하세요. 채팅 앱의 첨부파일 처리나 보고서 생성 서비스에도 그대로 적용할 수 있는 설계 문제입니다.

README의 “62개 모델” 카탈로그에는 비활성·제한 표시가 섞여 있습니다. 실제 화면도 서버의 모델 목록을 받아 필터링합니다. 따라서 카탈로그 숫자를 “지금 누구나 실행할 수 있는 모델 수”로 읽어서는 안 됩니다.

3기술 스택 전체 지도

역할 → 선언된 의존성 → 잠금 파일의 해석 결과 순서로 보기

이 저장소의 “백엔드”는 우선 데스크톱 앱 안의 Rust 코어를 뜻합니다. 운영 API 서버 전체가 들어 있는 것은 아닙니다. AGENTS.md와 _docs/dev_setup.md는 서버·회사 도구·웹사이트 빌드가 별도 artcraft-services 저장소에 있다고 설명합니다.

Rust 코어: 명령·HTTP·비동기 작업·로컬 DB

기술실제 버전 근거이 저장소에서 맡는 역할
Tauri루트 Cargo.toml: 2.11.2
Cargo.lock: 2.11.2
Rust 코어와 시스템 WebView를 연결하고 명령·이벤트·플러그인을 제공합니다.
Tokio워크스페이스: 1.50.0
데스크톱 크레이트: 1.42
잠금 파일: 1.50.0
HTTP 대기와 반복 상태 조회를 비동기로 처리합니다. 선언 문자열이 다르더라도 잠금 결과는 하나로 합쳐질 수 있습니다.
Serde루트: 1.0.228
serde_json: 1.0.149
Rust 타입과 JSON을 변환합니다. Omni 요청에서는 동적으로 추가되는 옵션도 보존합니다.
reqwest / wreq루트: 0.12.23 / 6.0.0-rc.28HTTP 클라이언트입니다. wreq 관련 네이티브 빌드 도구도 필요합니다. 잠금 파일에는 reqwest 0.13.2도 공존합니다.
SQLx + SQLitecrates/schema/database/sqlite_tasks/Cargo.toml: 0.7.4파일 기반 로컬 작업 DB입니다. query!와 오프라인 쿼리 메타데이터를 사용합니다.
egui / eframeartcraft-native/Cargo.toml: 0.36
잠금 파일: 모두 0.36.2
별도 Rust UI 앱을 구성합니다. 기본 Tauri 앱의 UI 라이브러리는 아닙니다.

데스크톱 Cargo.toml에는 Rust edition 2021과 rust-version = "1.77.2"가 남아 있습니다. 그러나 sqlite_tasks와 artcraft_router는 edition 2024입니다. 이 단일 필드를 저장소 전체의 최소 Rust 버전 보장으로 해석하면 안 됩니다. CI는 stable 툴체인을 사용합니다.

프론트엔드: 화면 상태와 픽셀·3D·영상의 만남

기술frontend/package.json 선언소스에서 확인할 학습 주제
React / React DOM각 18.3.1편집 화면·모달·상태 구독을 컴포넌트로 조립합니다.
TypeScript5.8.3앱과 재사용 UI 사이의 계약, API 요청 타입을 표현합니다.
Zustand^5.0.5
잠금: 5.0.14
캔버스·장면 등 여러 화면 요소가 공유하는 편집 상태를 관리합니다.
Preact Signals@preact/signals-react ^3.0.1일부 인증·갤러리·화면 상태에 사용됩니다. 모든 상태가 Zustand 하나로 통일되어 있지는 않습니다.
Three.js^0.171.0
잠금: 0.171.0
장면·카메라·조명·메시를 다룹니다. AI 추론과 별개로 로컬 그래픽 처리가 필요합니다.
Konva / react-konva^9.3.20 / 앱 선언 ^18.2.102D 그림·레이어·마스크와 조작 도구를 표현합니다. Konva 잠금 버전은 9.3.22입니다.
opencut-wasm^0.2.10영상 편집기의 합성·효과 처리 경로에 연결됩니다.
Tailwind CSS3.4.3화면 스타일을 구성합니다. 현재 문서의 스타일과는 별개의 앱 의존성입니다.

Three.js의 기본 단위는 장면·카메라·렌더러입니다. 이 개념을 먼저 익히면 장면 편집 코드가 훨씬 쉬워집니다. 공식 장면 입문 문서는 이 세 요소의 관계를 설명합니다. ArtCraft 전체 엔진을 처음부터 읽기 전에 작은 장면 하나로 관계를 익히면 좋습니다.

용어
버전 범위와 잠금 파일
^5.0.5 같은 선언은 허용 범위이고, 잠금 파일의 5.0.14는 의존성 해석 결과입니다. 잠금 파일을 읽었다고 설치·실행까지 검증한 것은 아닙니다. 모노레포에서는 하위 앱에 다른 버전이 따로 들어갈 수도 있습니다.

빌드·배포·검사: 두 언어의 산출물을 묶는 연결 고리

구성실제 설정읽어야 할 의미
Nx루트 선언 21.2.3프론트엔드 앱과 라이브러리 작업을 관리합니다.
Vite루트 ^6.0.0, 앱 ^5.4.0
잠금: 루트 6.4.3, 앱 내부 5.4.21
단순히 “Vite 6 앱”이라고 쓰면 버전 공존을 놓칩니다. 실제 실행 위치와 패키지 해석도 확인해야 합니다.
Tauri 빌드 연결beforeBuildCommand: npx nx run artcraft:build먼저 웹 자산을 만들고 frontendDist가 가리키는 결과물을 앱에 넣습니다.
SQLx 오프라인 구성.sqlx/, SQLX_OFFLINE=true쿼리 검사에 필요한 메타데이터를 사용합니다. 생성 서비스를 오프라인으로 대체하는 기능은 아닙니다.
GitHub ActionsmacOS Universal / Windows 게시 워크플로플랫폼 도구·인증서·프론트 빌드·패키징이 결합됩니다. Linux 배포가 같은 매트릭스에서 활성화된 것은 아닙니다.
SonarCloud공통 워크플로를 @main으로 호출정적 분석 설정이 있습니다. 공유 워크플로 내부와 실제 분석 통과 여부는 이 저장소만으로 확정할 수 없습니다.

frontend/apps/artcraft/vite.config.ts에는 wasm(), topLevelAwait(), 워크스페이스 소스 경로 해석과 React 중복 방지 설정이 있습니다. 라이브러리의 미리 빌드한 결과물 대신 소스를 연결하는 방식도 눈여겨보세요.

설정 근거: 루트 Cargo.toml, 프론트엔드 의존성, 앱 의존성, npm 잠금 파일, Cargo 잠금 파일.

4아키텍처 심화 분석

화면에서 시작한 한 요청이 어디에 저장되고, 어떤 경로로 돌아오는가

먼저 실행 경계를 봅시다. 기본 앱의 main.rs는 artcraft_app_lib::run()을 호출합니다. crates/desktop/artcraft/src/lib.rs의 run()이 플러그인을 등록하고, .manage(...)로 상태를 보관하며, 명령 처리기를 연결합니다. 화면은 시스템 WebView 안에서 실행됩니다.

Tauri의 명령과 이벤트는 프로세스 사이의 통신 수단입니다. 화면에서 Rust를 부르는 것은 일반 함수처럼 보이지만, 실제로는 직렬화된 메시지가 경계를 통과합니다. 시스템 WebView와 코어의 역할은 공식 프로세스 모델과 IPC 문서를 참고하세요.

[내 컴퓨터: 기본 Tauri 앱] React 화면 |-- 2D 편집: Konva + Zustand |-- 3D 편집: Three.js + EngineEventBus |-- 영상 편집: 합성기 + WASM 연결 | | invoke("generate_image_command", { request }) v Rust / Tauri 코어 |-- AppEnvConfigs / 인증 상태 |-- OmniRequest: 로컬 메타데이터와 API 필드 분리 |-- 생성 명령 | |-- ArtCraft Omni 경로 | |-- 일부 기존 모델: 전용 처리기 | `-- 다른 제공자: 전용 처리기 / artcraft_router | |-- TaskDatabase ----> 로컬 SQLite tasks_v7.sqlite `-- Storyteller 상태 조회 루프 | | HTTPS 요청 / 작업 ID / 상태 응답 ================|=================================== [외부 서비스] v ArtCraft API / 제공자 API ----> 생성 작업 | `--> 결과 메타데이터 / CDN 파일 [완료 경로] 상태 조회 -> 자동 다운로드 처리 -> DB 성공 상태 -> Tauri 완료 이벤트 -> 화면·갤러리 갱신 [별도 앱] artcraft-native: egui 화면 <- 채널 Event <- Tokio + 공유 API 크레이트

이 도식은 핵심 책임을 요약한 것입니다. 모든 HTTP 요청이 생성 명령을 통과하는 것은 아닙니다. frontend/libs/tauri-utils/src/lib/FetchProxy.ts는 데스크톱에서 Tauri HTTP 플러그인, 일반 브라우저에서는 기본 fetch를 선택합니다. 모델 목록·미디어 조회 등에는 별도의 HTTP 호출 경로도 있습니다.

패턴 1. 경계에서 바꾸고, 새 API 필드는 보존하기

crates/desktop/artcraft/src/core/commands/generate/generate_image/generate_image_command.rs의 generate_image_command()는 uses_artcraft()와 uses_legacy_image_endpoint()를 확인합니다. 일반 ArtCraft 요청은 omni::generate()로, 일부 기존 편집 모델과 다른 제공자는 기존 처리 경로로 보냅니다.

아래는 crates/desktop/artcraft/src/core/commands/generate/omni/request.rs의 핵심 구조입니다. 명시된 세 frontend_* 필드는 완료 결과를 받을 화면과 전달할 정보를 기록합니다. 이 필드들과 provider를 제외한 나머지는 JSON 맵에 모아 새 모델 옵션이 추가돼도 쉽게 탈락하지 않게 합니다.

// 핵심 구조 발췌: 로컬 메타데이터는 명시적인 필드
#[derive(Clone, Deserialize, Serialize)]
pub struct OmniRequest {
  pub provider: Option<GenerationProvider>,
  pub frontend_caller: Option<TauriCommandCaller>,
  pub frontend_subscriber_id: Option<String>,
  pub frontend_subscriber_payload: Option<String>,
  #[serde(flatten)]
  pub fields: Map<String, Value>,
}

OmniRequest::api_fields()는 API에 전달할 필드만 복사하고 이름을 정리합니다. 이미지의 batch_size를 image_batch_count로, 영상의 옛 image_media_token을 start_frame_image_media_token으로 바꾸는 식입니다. canonical_model_id()는 기존 모델 별칭을 새 ID로 바꾸고, 모르는 ID는 그대로 통과시킵니다.

// 학습용 입력: 외부 요청을 보내지 않고 변환 결과만 읽습니다.
{
  "model": "future_image_v9",
  "batch_size": 2,
  "new_option": { "strength": 0.4 },
  "frontend_subscriber_id": "canvas-tab-3"
}

// api_fields(Modality::Image)의 결과
{
  "model": "future_image_v9",
  "image_batch_count": 2,
  "new_option": { "strength": 0.4 }
}

새 옵션 보존은 새 옵션의 유효성 보장과 다릅니다. 실제 지원 여부와 값 검증은 서버 응답까지 봐야 합니다. 파일 안의 future_model_and_options_survive_without_desktop_metadata 테스트는 이런 경계의 의도를 보여줍니다.

비유

택배 상자 속 상품 설명은 보존하고, 창고에서만 쓰는 선반 번호는 떼고 보내는 과정과 같습니다. 서버가 이해할 새 상품 정보까지 앱이 미리 전부 열거할 필요는 없지만, 창고 내부 정보와 배송 정보는 구분해야 합니다.

여기에는 흥미로운 예외도 있습니다. uses_artcraft()는 grok_로 시작하는 모델이나 Grok 제공자 값도 ArtCraft 경로로 보냅니다. 파일 주석은 네이티브 Grok 경로를 의도적으로 비활성화했다고 설명합니다. 따라서 Grok 관련 디렉토리가 존재한다는 사실만으로 현재 기본 화면이 직접 Grok에 연결된다고 판단하면 안 됩니다.

패턴 2. 접수와 완료를 나누고, 로컬 기록으로 연결하기

crates/desktop/artcraft/src/core/commands/generate/omni/dispatch.rs의 generate()는 로그인 정보를 확인하고, 요청 필드를 정리한 뒤 /v1/omni_gen/generate/{종류}에 POST합니다. 서버 응답에서 받은 작업 ID를 로컬 DB에 기록하고 접수 이벤트를 보냅니다. 이 시점에 최종 이미지가 만들어진 것은 아닙니다.

  1. idempotency_token이 없거나 null이면 UUID를 넣습니다.
  2. 견적 전용 estimate_only는 실제 생성 요청에서 제거합니다.
  3. job_tokens()가 대표 ID와 all_job_tokens를 중복 없이 모읍니다.
  4. 각 ID를 TaskEnqueueSuccess::insert_into_task_database_with_frontend_payload()로 기록합니다.
  5. 이후 별도의 상태 조회 루프가 원격 완료와 로컬 작업을 맞춥니다.

생성 API의 접수 성공과 결과물의 완성은 서로 다른 사건이며, 작업 ID가 두 사건을 연결합니다. API를 부른 함수 하나가 끝날 때까지 화면 전체를 묶어두지 않는 것이 이 구조의 출발점입니다.

용어
멱등성 키와 상관관계 ID
멱등성 키는 같은 작업을 중복 접수하지 않도록 서버가 식별하는 표식입니다. 상관관계 ID는 돌아온 결과가 어느 화면의 어느 요청에 해당하는지 연결합니다. idempotency_token과 frontend_subscriber_id는 이름도 목적도 다릅니다.

키가 자동 생성된다는 사실만으로 중복 실행을 완전히 막았다고 볼 수는 없습니다. 같은 논리적 요청을 다시 보낼 때 새 키를 만들면 서버는 별개의 요청으로 볼 수 있습니다. 재시도 동안 같은 키를 유지할지, 서버가 어떻게 보관하는지는 별도로 설계해야 합니다.

로컬 장부는 무엇을 기억할까?

_database/sql/artcraft_migrations/20250708000000_create_tasks_table.sql에는 작업 상태·종류·제공자·원격 ID·화면 구독 정보·성공 결과·실패 이유가 함께 정의되어 있습니다. 이미지 파일 자체를 SQLite 안에 넣는 구조로 읽으면 안 됩니다.

-- 실제 스키마의 일부 필드만 추린 읽기용 예시
CREATE TABLE tasks (
  id TEXT NOT NULL PRIMARY KEY,
  task_status TEXT NOT NULL,
  task_type TEXT NOT NULL,
  provider TEXT,
  provider_job_id TEXT,
  frontend_subscriber_id TEXT,
  frontend_subscriber_payload TEXT,
  on_complete_primary_media_file_cdn_url TEXT,
  on_failure_message TEXT
);
-- 원본에는 모델·배치·날짜·실패 유형 등의 필드도 있습니다.

crates/schema/database/sqlite_tasks/src/connection.rs의 run_migrations()는 SqliteJournalMode::Wal을 설정하고 마이그레이션을 바이너리에 포함합니다. crates/desktop/artcraft/src/core/state/task_database_version.rs의 버전은 7입니다.

완료는 폴링으로 발견합니다

crates/desktop/artcraft/src/services/storyteller/threads/storyteller_task_polling_thread/storyteller_task_polling_thread.rs의 polling_loop()는 5,000ms를 기다린 뒤 list_session_jobs(..., States::All)을 호출합니다. 작업 ID로 로컬 레코드를 찾아 성공·실패 처리기로 넘깁니다. 실시간 서버 푸시가 이 경로의 중심은 아닙니다.

상황실제 코드의 처리학습할 지점
정상 상태 조회내부 루프에서 매번 5초 대기실제 조회 간격에는 HTTP 응답 대기와 작업 처리 시간도 더해집니다.
조회 오류오류를 반환하고 바깥 루프에서 30초 대기장애가 났을 때 무한히 빠른 요청을 보내지 않습니다.
요청 제한 응답60초 추가 대기 후 오류 반환30초 바깥 대기도 이어집니다. “항상 60초 후 재시도”가 아닙니다.
이미 완료한 작업로컬 CompleteSuccess 상태면 건너뜀반복 조회에서 같은 작업을 계속 처리하는 일을 줄입니다.
한 결과의 다운로드 실패오류를 기록하고 다음 작업 계속 처리한 항목의 실패가 다른 완료 작업을 지연시키지 않도록 합니다.

handle_storyteller_successful_job.rs의 handle_successful_job()는 자동 다운로드 처리를 먼저 시도하고, DB 성공 상태를 갱신한 뒤 완료 이벤트를 보냅니다. 화면의 useGenerationCompleteEvent()는 generation-complete-event를 구독해 소리와 알림을 표시합니다.

이 순서는 유용하지만 정확히 한 번 전달을 보장하지는 않습니다. DB 갱신과 이벤트 전송이 하나의 원자적 트랜잭션은 아니기 때문입니다. DB 갱신 뒤 앱이 종료되면 그 이벤트가 다시 전송되지 않을 수 있습니다. 화면은 필요할 때 작업 목록을 다시 읽어 상태를 복원할 수 있어야 합니다.

구현의 한계도 함께 읽기
로컬 기록이 있다고 모든 장애가 자동 복구되지는 않습니다

외부 접수가 끝난 뒤 DB 저장에 실패하면 generate()는 오류 로그를 남기고 접수 성공 처리를 이어갑니다. 중복 생성을 부르는 재시도를 피하려는 선택이지만, 로컬 추적 기록이 빠질 수 있습니다. 또 connect_and_migrate()는 마이그레이션 실패 시 DB 파일을 삭제하고 재생성을 시도합니다. 이 DB를 영구 보관용 원장으로 간주해서는 안 됩니다.

패턴 3. 제공자 차이를 어댑터에 모으기

crates/api_clients/artcraft/artcraft_router/src/client/router_client.rs의 RouterClient는 제공자별 클라이언트를 enum으로 표현합니다. MultiRouterClientBuilder는 사용할 클라이언트를 선택해 조립하고, 없는 클라이언트를 요청하면 ClientNotConfigured 오류를 돌려줍니다.

crates/api_clients/artcraft/artcraft_router/src/generate/generate_image/generate_image_request_builder.rs의 GenerateImageRequestBuilder::build2()는 (provider, model) 조합을 보고 요청을 만듭니다. 미지원 조합은 명시적 오류가 됩니다. ImageGenerationRequest::send_request()는 실제 제공자 클라이언트로 전송합니다.

공통 이미지 생성 의도 | v GenerateImageRequestBuilder::build2() |-- 바로 보낼 수 있는 Request `-- 미디어 해석이 더 필요한 Draft | v ImageGenerationDraftRequest::finalize() | v ImageGenerationRequest::send_request(&RouterClient) |-- 제공자 A의 요청 형태 `-- 제공자 B의 요청 형태

이 라우터의 지원 enum과 ArtCraft Omni의 동적 모델 경로는 같은 층이 아닙니다. ArtCraft Omni는 새 문자열 옵션을 전달하고, 라우터의 특정 경로는 알려진 제공자·모델 조합을 변환합니다. “모델을 하나 추가하면 모든 제공자에서 즉시 사용 가능”한 구조는 아닙니다.

화면에도 같은 원칙이 보입니다. frontend/libs/components/pagescene/src/lib/adapter.ts의 PageSceneAdapter는 생성·저장·업로드 등의 계약을 정의합니다. 앱 쪽 useTauriPageSceneAdapter.tsx가 Tauri·HTTP 연결을 채웁니다. 재사용 3D 편집기에 플랫폼 지식을 몰아넣지 않는 방식입니다.

패턴 4. 엔진의 사건을 UI 상태로 번역하기

frontend/libs/components/pagescene/src/lib/engine/events/EngineEventBus.ts의 EngineEventBus는 이벤트 생성자별로 리스너 집합을 보관합니다. subscribe()는 해제 함수를 반환하고, emit()은 해당 종류를 구독한 함수들만 호출합니다.

// EngineStoreBridge의 실제 연결 방식 발췌
bus.subscribe(SelectionChangedEvent, (e) =>
  usePageSceneStore.getState().setSelectedObject(e.selection),
);

// 엔진의 선택 변경 → 화면의 선택 상태 변경
// 엔진 내부 전체가 React 컴포넌트를 알 필요는 없습니다.

EngineStoreBridge.ts의 생성자가 이러한 구독을 모읍니다. 장면 엔진이 객체를 선택했다고 알리면 브리지가 Zustand 저장소를 변경하고, 화면이 그 상태를 읽습니다. 이벤트 종류 검색은 맵으로 하지만 리스너 호출 비용은 해당 구독자 수에 비례합니다. “이벤트 처리가 전부 O(1)”이라고 확대하면 안 됩니다.

이 브라우저 내부 이벤트 버스와 Tauri 이벤트는 서로 다른 통신 수단입니다. 전자는 같은 JavaScript 실행 영역 안에서 엔진과 UI를 연결하고, 후자는 Rust 코어와 WebView의 경계를 넘습니다. “이벤트”라는 이름이 같아도 범위를 구분하세요.

비교 읽기: Rust UI는 같은 비동기 문제를 어떻게 풀까?

artcraft-native/src/backend/mod.rs의 Backend::new()는 Tokio 작업 스레드 4개와 std::sync::mpsc::channel()을 만듭니다. Backend::drain()은 도착한 Event를 모아 UI에 넘깁니다. UI가 네트워크 응답을 직접 기다리지 않게 하는 역할은 비슷하지만, Tauri IPC를 사용하지 않습니다.

Native README는 이미지·영상·오디오 생성 화면을 구현했다고 설명하며, 다른 도구는 웹 앱으로 연결하는 준비 중 화면이라고 밝힙니다. 기본 Tauri 앱의 2D·3D 편집 기능이 Native에 모두 옮겨졌다고 볼 수 없습니다. 이 비교는 성능 우열보다 같은 비동기 경계를 다른 UI 환경에서 표현하는 방식에 초점을 맞추세요.

핵심 근거: Omni 생성·견적 처리, 요청 변환과 테스트, 엔진과 UI 연결.

5디렉토리 구조 해부

이름의 크기보다 의존성 방향과 읽기 순서가 중요합니다
artcraft/ |-- Cargo.toml / Cargo.lock Rust 워크스페이스와 의존성 잠금 |-- artcraft-native/ 별도 egui 앱 | `-- src/ | |-- backend/ API·작업·세션·미디어 처리 | |-- pages/ 이미지·영상·오디오 화면 | `-- feed/ 작업과 미디어 목록 |-- crates/ | |-- desktop/artcraft/ | | |-- tauri.conf.json 기본 앱 설정 | | `-- src/ | | |-- lib.rs 초기화·상태·명령 등록 | | |-- core/ 공통 명령·상태·이벤트 | | `-- services/ 제공자별 로그인·작업 처리 | |-- api_clients/ | | |-- artcraft/ | | | |-- artcraft_api_defs/ API 요청·응답 타입 | | | |-- artcraft_client/ HTTP 엔드포인트 호출 | | | `-- artcraft_router/ 제공자·모델별 변환과 전송 | | `-- fal_client/ ... 개별 제공자 클라이언트 | |-- schema/ | | |-- database/sqlite_tasks/ 로컬 작업 DB 접근 | | `-- public/ 공통 enum·식별자 | |-- lib/ 파일·쿠키·오류 등 지원 코드 | |-- testing/ 테스트 지원 코드 | `-- vendor/tauri-plugin-http/ 쿠키 접근을 위한 수정 플러그인 |-- frontend/ | |-- apps/artcraft/app/ 기본 앱 화면과 정적 자산 | |-- libs/ | | |-- api/ HTTP API 타입과 호출 | | |-- tauri-api/ Rust 명령 호출 래퍼 | | |-- tauri-events/ Rust 이벤트 구독 | | |-- omni-gen/ 모델 목록·관련 훅 | | `-- components/ 캔버스·장면·영상·공통 UI | `-- scripts/ 개발 실행기와 검사 코드 |-- _database/sql/artcraft_migrations/ SQLite 스키마 |-- .sqlx/ 오프라인 쿼리 메타데이터 |-- .github/workflows/ 게시·정적 분석 설정 `-- _docs/ / docs/ / script/ 설명 문서·개발/빌드 도구

비슷한 이름을 분리해서 기억하기

위치담는 것처음 읽을 때의 질문
artcraft_api_defs서버와 주고받는 데이터의 형태성공 응답에는 파일이 오는가, 작업 ID가 오는가?
artcraft_client주소·인증·요청·응답 처리HTTP 오류가 어떤 Rust 오류로 바뀌는가?
artcraft_router제공자 간 의미와 형식의 차이같은 이미지 생성 의도를 각 제공자에 어떻게 맞추는가?
tauri-api화면에서 Rust 명령을 부르는 함수명령 이름과 전달하는 객체 모양이 일치하는가?
tauri-eventsRust가 보낸 사건의 화면 구독컴포넌트가 사라질 때 구독도 해제되는가?

crates/vendor/는 모두 직접 작성한 코드라는 뜻이 아닙니다. 데스크톱 Cargo.toml 주석은 HTTP 플러그인의 쿠키 내부 접근 때문에 수정 사본을 사용한다고 설명합니다. 원본과 다른 책임이 생긴 만큼 업데이트 때 변경을 유지해야 하는 비용도 있습니다.

처음부터 끝까지 읽는 대신 한 줄기를 고르기

  1. frontend/libs/tauri-api/src/lib/generate/GenerateImage.ts에서 요청의 입력 형태를 봅니다.
  2. generate_image_command()에서 경로 선택 조건을 읽습니다.
  3. 생성 명령의 OmniRequest와 generate()에서 필드 변환·원격 접수·로컬 기록을 찾습니다.
  4. storyteller_task_polling_thread.rs에서 완료를 발견하는 지점을 찾습니다.
  5. useGenerationCompleteEvent.ts로 돌아와 사용자에게 어떤 피드백이 보이는지 확인합니다.

이 경로를 읽은 뒤 장면 편집이나 영상 합성으로 넓히면 됩니다. 수천 개 파일을 한꺼번에 이해하려 하기보다, 요청·응답·상태·이벤트 네 가지를 표시한 지도를 먼저 그리세요.

6학습 포인트 (기술별)

문법을 외우는 대신 실제 앱의 실패 상황과 연결하기

이 코드를 공부할 때는 정상 동작을 따라가는 데서 한 걸음 더 나아가, 응답이 늦거나 순서가 뒤집힐 때를 질문해 보세요. 비동기 앱의 어려움은 요청을 보내는 한 줄보다, 오래된 응답과 사라진 화면을 다루는 데 있습니다.

Rust · 데이터 모델링

Option·Result·enum으로 빠진 값과 실패를 표현하기

RouterClient는 가능한 제공자를 enum으로, MultiRouterClient는 설정되지 않은 클라이언트를 Option으로 표현합니다. getter는 Result를 반환하므로 “없음”이 조용히 넘어가지 않습니다.

실습 아이디어: 가짜 제공자 두 개를 가진 작은 enum을 만들고, 없는 클라이언트를 요청할 때 ClientNotConfigured에 해당하는 오류를 돌려주세요. 문자열 비교를 여러 곳에 흩뿌린 구현과 읽기 편의를 비교합니다.

Serde · API 호환성

강한 타입과 유연한 필드를 함께 사용하기

OmniRequest의 메타데이터는 고정 타입이고 나머지는 Map<String, Value>입니다. 무엇을 컴파일 단계에서 제한하고, 무엇을 서버의 계약에 맡길지 선택한 사례입니다.

실습 아이디어: 새 모델 ID·새 옵션을 가진 JSON에 로컬 구독 ID도 넣으세요. 변환 뒤 새 옵션은 남고 구독 ID는 API 필드에서 빠지는지 표로 확인합니다. 필드가 null일 때와 아예 없을 때도 나눠 봅니다.

Tokio · 비동기 경계

작업 대기와 UI 스레드의 일을 분리하기

Tauri 앱은 비동기 명령과 폴링 루프를 사용하고, Native 앱은 Backend::drain()으로 채널에 도착한 이벤트를 모읍니다. 화면이 네트워크 응답을 기다리며 멈추지 않게 하는 두 가지 표현입니다.

실습 아이디어: 가짜 생성 작업이 몇 초 뒤 이벤트를 보내는 작은 프로그램을 설계하세요. 기다리는 동안 카운터나 선택 상태가 바뀌도록 해 “작업 중”과 “앱 정지”의 차이를 체감합니다.

SQLite · 장애와 상태

원격 작업과 로컬 추적 레코드를 구분하기

create_task()의 SQL은 원격 ID와 화면 구독 정보를 함께 저장합니다. 생성 파일의 보관 위치와 작업의 상태 기록은 별개의 문제입니다. query!와 .sqlx/는 쿼리 형태의 검증을 돕지만, 원격 작업과 DB 사이의 원자성까지 보장하지는 않습니다.

실습 아이디어: “서버 접수 성공 → DB 쓰기 실패”와 “DB 완료 → 이벤트 전송 실패”를 각각 그려보세요. 사용자가 재시도 버튼을 누르면 어떤 중복이나 누락이 생길지 설명합니다.

React · Zustand · 오래된 응답

마지막에 고른 이미지가 마지막에 도착하지 않을 수 있습니다

frontend/libs/components/pagedraw/src/lib/stores/SceneState.ts의 setBaseImageInfo()는 baseImageLoadGen을 증가시키고, 이미지 로드 콜백에서 현재 세대와 비교합니다. 이전 선택의 이미지가 늦게 도착해 새 선택을 덮어쓰는 경쟁 상태를 막습니다.

실습 아이디어: A 이미지는 800ms, B 이미지는 100ms 뒤에 로드되게 모의 구현하세요. A를 고른 직후 B를 골라도 최종 화면이 B인지 확인합니다. 단순히 응답이 온 순서대로 상태를 바꾸면 왜 실패하는지 설명해 보세요.

// SceneState의 세대 번호 방식을 축약한 학습용 예시
let generation = 0;

async function selectImage(id: string) {
  const myGeneration = ++generation;
  const image = await loadMockImage(id);
  if (myGeneration !== generation) return;
  showImage(image);
}
// loadMockImage와 showImage는 별도 학습용 함수입니다.
Three.js · Konva · 그래픽 수명

편집 도구와 출력 이미지를 분리하고, GPU 자원을 해제하기

frontend/libs/components/pagedraw/src/lib/hooks/useUpdateSnapshot.ts의 captureStageEditsBitmap()는 조작 핸들을 숨기고 필요한 레이어를 캡처한 뒤 finally에서 좌표·배율을 되돌리고 모든 레이어·핸들을 표시합니다. 기존의 숨김 상태까지 복원하는 것은 아닙니다. 저장할 그림과 사용자가 조작 중인 화면은 같지 않습니다.

frontend/libs/components/pagedraw/src/lib/components/Model3DOverlay.tsx는 정리 단계에서 애니메이션 프레임을 취소하고 controls·장면 객체·renderer를 해제합니다. 컴포넌트가 사라졌다고 그래픽 자원이 자동으로 전부 정리된다고 가정하지 않는 코드입니다.

실습 아이디어: 사각형과 선택 테두리를 함께 보여주는 작은 캔버스를 만들고, 내보내기에는 테두리가 포함되지 않게 하세요. 내보내기 오류가 나도 편집 화면이 복구되도록 설계합니다.

WASM · 캐시 · 성능

같은 픽셀을 매 프레임 다시 보내지 않기

frontend/libs/components/video-editor/src/lib/services/renderer/compositor/wasm-compositor.ts의 WasmCompositor는 텍스처 캐시를 관리하며, 소스·내용 해시와 크기를 비교해 불필요한 재업로드를 줄입니다. frontend/libs/components/video-editor/src/lib/services/renderer/gpu-renderer.ts는 GPU 초기화 실패를 기록하고 사용 가능 여부를 별도로 보관합니다.

실습 아이디어: 고정 배경과 움직이는 자막을 나눠, 매 프레임 바뀐 레이어만 다시 계산하는 구조를 종이에 그려보세요. 성능은 언어 이름보다 재계산·복사·업로드 횟수에 크게 영향을 받습니다.

처음 공부할 때는 위 주제 중 하나만 골라도 충분합니다. 예를 들어 React 경험이 있다면 이미지 선택의 경쟁 상태부터, Rust를 배우고 있다면 Option과 Result를 쓰는 제공자 접근부터 시작하세요.

7하드웨어 / 시스템 요구사항

소스 읽기, 데스크톱 빌드, 그래픽 편집, 원격 생성을 나눠 판단하기

원격 모델을 호출하는 데 필요한 장비와 3D 장면을 부드럽게 편집하는 데 필요한 장비는 서로 다른 문제입니다. 주요 생성 명령이 외부 API로 향하므로, 이 앱을 사용한다는 이유만으로 로컬 대형 모델용 VRAM을 요구한다고 볼 근거는 없습니다. 반면 장면·영상 편집에는 로컬 그래픽·메모리 자원이 사용됩니다.

목적확인되는 조건판단할 때 주의할 점
소스 학습텍스트 편집기와 파일 검색 도구API 계정·GPU·전체 빌드가 없어도 요청·스키마·이벤트 구조를 읽을 수 있습니다.
지원 운영체제README: Windows·macOS 안정 버전, Linux 소스 빌드 안내게시 CI는 macOS·Windows가 활성화되어 있습니다. OS별 최소 버전은 별도 배포 조건을 확인해야 합니다.
Node.js공통 사전 검사: 20+앱 manifest의 >=18.0.0보다 현재 실행 스크립트의 조건이 엄격합니다.
Rust·Tauri CLICI: Rust stable
개발 문서 예시: CLI 2.10.0
앱 의존성의 Tauri 2.11.2와 CLI 버전은 서로 다른 항목입니다.
macOS 빌드 도구루트 Cargo 주석: cmake, pkg-config, llvmwreq 네이티브 의존성과 Tauri 플랫폼 도구가 함께 필요합니다.
Windows 빌드 도구게시 CI: CMake·Strawberry Perl·pkgconfiglite·LLVM·NASM추가로 Rust와 Tauri가 요구하는 플랫폼 환경을 갖춰야 합니다.
Linux 환경Tauri의 WebKitGTK 계열 시스템 의존성배포판별 패키지명이 다릅니다. Unix 실행기에 WebKit 관련 우회 설정도 있습니다.
네트워크·계정생성 API·인증·미디어 다운로드 경로코드 공개와 서비스 무료 이용은 별개입니다. 제공자 계정·권한·크레딧 조건이 영향을 줍니다.

RAM·디스크·GPU 숫자는 어떻게 읽어야 할까?

README와 개발 환경 문서에는 제품 전체의 공식 최소 RAM·VRAM·디스크 용량 표가 없습니다. 따라서 임의의 “최소 8GB”나 “VRAM 12GB 필수”를 공식 요구사항으로 제시할 수 없습니다. Rust 빌드 캐시와 npm 의존성, 편집 미디어가 모두 디스크를 차지하므로 소스 폴더 크기를 설치 용량으로 쓰면 안 됩니다.

Windows 게시 워크플로는 프론트 빌드에 NODE_OPTIONS=--max-old-space-size=8192를 설정합니다. 이는 Node 프로세스의 힙 한도를 조정한 값이며, 제품이 RAM 8GB 컴퓨터에서 충분히 동작한다는 측정 결과가 아닙니다.

용어
VRAM과 시스템 RAM
VRAM은 GPU가 텍스처·그래픽 데이터 등을 보관하는 메모리이고, 시스템 RAM은 앱과 컴파일러 등이 쓰는 메모리입니다. 큰 3D 장면이 느린 문제와 Rust 빌드 중 메모리가 부족한 문제는 해결 지점이 다릅니다. 통합 메모리 장치에서는 물리적 구성이 또 다를 수 있습니다.

개발 설정에서 드러나는 현실적인 제약

플랫폼 의존성의 구체적인 설치 조건은 Tauri 공식 사전 요구사항, 앱의 현재 개발 흐름은 ArtCraft 개발 문서를 참고하세요.

8직접 해볼 수 있는 실습 과제

외부 생성 요청 없이도 완성할 수 있는 다섯 과제

아래 과제는 원본 소스를 읽고 별도의 학습용 프로젝트에 작은 모형을 만드는 방식입니다. API 키나 실제 계정 대신 가짜 응답을 사용합니다. 실제 모델 호출을 모의 응답으로 바꾸면, 비용과 생성 시간에 흔들리지 않고 앱의 상태 설계에 집중할 수 있습니다.

과제 1 · 입문

한 요청의 왕복 지도 만들기 30~60분

목표: 화면의 함수 호출이 원격 접수와 완료 이벤트로 이어지는 과정을 설명합니다.

  1. GenerateImage.ts에서 invoke의 명령 이름을 찾습니다.
  2. 같은 이름의 Rust 함수에서 Omni 경로로 가는 조건을 적습니다.
  3. dispatch.rs에서 작업 ID를 꺼내는 곳과 DB 기록 지점을 표시합니다.
  4. 완료 처리기와 화면 이벤트 구독자를 연결한 그림을 만듭니다.

완료 기준: “함수 반환”과 “이미지 완성”을 서로 다른 점으로 표시하고, frontend_subscriber_id가 필요한 이유를 한 문단으로 설명합니다.

확장 질문: 접수 직후 앱이 종료되면 어떤 정보가 남아야 다시 추적할 수 있을까요?

과제 2 · 중급

요청 변환기를 순수 함수로 만들기 1~2시간

목표: 입력을 바꾸지 않고 API용 필드를 만드는 함수를 구현합니다. OmniRequest::api_fields()의 동작을 참고하되 HTTP 호출은 넣지 않습니다.

// 가짜 입력과 기대 출력: 실제 API 요청이 아닙니다.
const input = {
  model: "future_image_v9",
  batch_size: 2,
  image_batch_count: 3,
  new_option: true,
  frontend_subscriber_id: "tab-7",
};

const expected = {
  model: "future_image_v9",
  image_batch_count: 3,
  new_option: true,
};

완료 기준: 원본 입력이 그대로이고, 충돌하는 새 필드·null·알 수 없는 옵션의 처리 결과를 설명할 수 있습니다.

과제 3 · 중급

가짜 작업 큐와 새로고침 복원 2~3시간

목표: 생성 접수와 결과 표시를 분리합니다. 실제 SQLite를 연결하기 전에는 JSON이나 브라우저 저장소로 시작해도 됩니다.

  1. 생성 버튼을 누르면 job_demo_1 같은 가짜 ID를 만듭니다.
  2. 상태를 pending으로 저장하고 화면에 대기 카드를 표시합니다.
  3. 모의 조회가 같은 ID에 대해 “대기 → 처리 중 → 성공”을 반환하게 합니다.
  4. 새로고침 후에도 저장된 작업을 다시 표시합니다.
  5. 같은 완료 응답이 반복되어도 완료 알림이 계속 생기지 않게 합니다.

완료 기준: 작업 두 개 중 하나가 실패해도 다른 작업은 완료됩니다. 완료 결과에는 가짜 파일 주소나 텍스트만 사용해도 충분합니다.

생각할 점: 폴링 타이머만 중지하는 것과 원격 작업 자체를 취소하는 것은 다릅니다. 취소 버튼의 의미를 명확히 정의하세요.

과제 4 · 중급

선택 이벤트와 편집 상태를 연결하기 1~2시간

목표: 작은 사각형 편집 화면에 엔진·이벤트·저장소의 세 경계를 만듭니다.

  1. 엔진은 선택한 도형 ID를 가진 이벤트만 발행합니다.
  2. 브리지는 그 이벤트를 받아 Zustand 또는 간단한 저장소를 갱신합니다.
  3. 속성 패널은 저장소를 구독해 선택한 도형의 이름을 표시합니다.
  4. 브리지를 해제한 뒤에는 이벤트를 보내도 패널이 바뀌지 않게 합니다.

완료 기준: 엔진 코드에 React 컴포넌트나 Tauri import가 없습니다. 같은 이벤트를 콘솔 기록기와 상태 브리지 두 곳에서 구독할 수 있습니다.

확장 질문: 모든 마우스 이동을 전역 상태에 보내는 대신, 어떤 값만 UI로 전달해야 할까요?

과제 5 · 고급

“서버는 받았는데 앱은 기록하지 못했다” 해결하기 반나절 이상

목표: dispatch.rs의 접수 후 DB 쓰기 실패 상황을 모델링하고 복구 정책을 설계합니다. 실제 서비스를 호출하지 않고 가짜 서버와 임시 학습 DB만 사용합니다.

완료 기준: 요청 직전 종료·접수 직후 종료·DB 저장 실패·완료 알림 실패의 네 상황에서 중복 생성과 상태 누락이 어떻게 처리되는지 표로 설명합니다.

이 과제는 현재 구현을 그대로 재현하는 단계에서 더 나아간 설계 확장입니다. 서버에 같은 키의 조회·중복 방지 계약이 없으면 클라이언트만으로 모든 문제를 해결할 수 없다는 제약까지 적으세요.

9관련 기술 심화 학습 로드맵

6주 동안 작은 도구 하나를 점차 확장하는 순서

JavaScript 함수·배열·객체와 기본 HTML을 아는 독자를 기준으로 잡았습니다. Rust가 처음이라면 소유권·빌림·Option·Result를 익히는 준비 기간을 추가하세요. 각 주차의 결과물은 같은 작은 가짜 생성 앱에 쌓아갑니다.

주차배울 것과 코드 연결남길 결과물
1주차TypeScript 객체·유니온·비동기 함수
GenerateImage.ts 요청 타입 읽기
입력 폼과 모의 접수 응답, 요청의 왕복 지도
2주차React 상태·이펙트 정리·경쟁 상태
SceneState.ts의 세대 번호 읽기
오래된 응답이 최신 선택을 덮어쓰지 않는 화면
3주차Rust enum·Result·Serde
OmniRequest 변환 읽기
로컬 메타데이터와 API 필드를 분리하는 순수 함수
4주차Tauri 명령·이벤트와 상태
lib.rs, FetchProxy.ts 읽기
가짜 생성 명령을 보내고 완료 이벤트를 받는 데스크톱 모형
5주차SQLite·SQLx·폴링·멱등성
sqlite_tasks와 완료 처리기 읽기
새로고침·앱 재시작을 고려한 작업 목록과 실패 표
6주차Konva 또는 Three.js 중 하나
PageSceneAdapter, EngineStoreBridge 읽기
편집 화면에서 모의 생성 결과까지 연결되는 작은 창작 도구

학습의 완성 기준은 라이브러리 이름을 많이 아는 것이 아니라, 요청 하나의 성공·실패·재시작 경로를 설명하고 구현하는 것입니다. 6주차까지도 외부 모델이 꼭 필요하지 않습니다. 화면 조작과 작업 수명이 연결되면 핵심 설계는 충분히 연습한 셈입니다.

그다음에는 관심에 따라 갈라집니다

매주 남길 기록

기능 설명 하나, 흐름도 하나, 실패 상황 하나를 남기세요. 예를 들어 “이미지 선택은 세대 번호로 오래된 응답을 무시한다”를 코드 위치와 함께 설명할 수 있다면, 이미 다른 앱에도 옮겨 쓸 수 있는 지식을 얻은 것입니다.

10핵심 키워드 사전

앞에서 만난 용어를 코드의 역할과 함께 다시 확인하기
용어쉬운 설명
IDE여러 작업 도구를 하나의 흐름으로 묶은 개발 환경입니다. ArtCraft는 이를 창작 도구의 비유로 사용합니다.
모노레포여러 앱·라이브러리를 한 저장소에서 관리하는 구조입니다. ArtCraft에는 Rust 크레이트와 프론트엔드 라이브러리가 함께 있습니다.
크레이트 / 워크스페이스Rust의 컴파일 단위와, 여러 단위를 함께 관리하는 구성입니다. 루트 Cargo.toml에서 연결 관계를 볼 수 있습니다.
WebView앱 안에 넣은 웹 화면 실행기입니다. 기본 Tauri 앱은 그 안에 React 화면을 표시합니다.
IPC서로 다른 프로세스가 메시지를 주고받는 통신입니다. Tauri 명령·이벤트가 이 경계를 연결합니다.
직렬화 / 역직렬화메모리의 값을 전송·저장 가능한 형태로 바꾸고 다시 읽는 일입니다. Serde가 Rust와 JSON 사이의 변환을 돕습니다.
모달리티이미지·영상·오디오 같은 데이터 종류입니다. Modality는 생성 API의 경로와 작업 종류를 결정합니다.
어댑터서로 다른 인터페이스 사이를 연결하는 변환기입니다. 제공자 API의 차이나 플랫폼의 차이를 한곳에 모읍니다.
폴링주기적으로 상태를 물어보는 방식입니다. 이 앱의 주요 작업 조회 루프는 매 조회 전에 5초를 기다립니다. 실제 간격에는 요청·처리 시간도 더해집니다.
백오프실패·요청 제한이 생기면 재시도 전에 더 기다리는 정책입니다. 이 코드에서는 상황별 고정 대기 시간이 보입니다.
멱등성 키같은 논리적 작업의 중복 접수를 구분하는 표식입니다. 효과를 내려면 재시도 시 키 유지와 서버의 처리 계약이 필요합니다.
상관관계 ID돌아온 결과를 원래 요청·화면과 연결하는 번호입니다. 중복 방지 키와 목적이 다릅니다.
마이그레이션DB 테이블 구조를 만들거나 바꾸는 절차입니다. ArtCraft는 로컬 작업 스키마를 앱에 포함합니다.
WALSQLite가 변경 내용을 별도 로그에 기록하는 방식입니다. 동시 읽기·쓰기 운용에 도움을 주지만 모든 장애를 해결하지는 않습니다.
이벤트 버스사건을 발행하는 쪽과 듣는 쪽을 연결하는 통로입니다. 엔진 내부 버스와 Tauri 이벤트는 통신 범위가 다릅니다.
경쟁 상태처리 순서에 따라 결과가 바뀌는 상황입니다. 먼저 선택한 이미지가 나중에 도착해 새 선택을 덮어쓰는 문제가 한 예입니다.
WASM웹 실행 환경에서 사용할 수 있는 바이너리 형식입니다. 영상 편집기의 합성·효과 모듈 연결에 사용됩니다.
Gaussian splat공간을 많은 점 모양의 분포로 표현해 장면을 보여주는 방식입니다. 삼각형 표면을 잇는 메시와 데이터 표현이 다릅니다.
CDN / 미디어 토큰CDN은 결과 파일을 전달하는 분산 네트워크이고, 미디어 토큰은 서비스가 파일을 식별하는 ID입니다. 파일 주소와 식별자는 같은 개념이 아닙니다.

11참고 링크

제품의 현재 숫자와, 분석한 코드의 시점을 구분해서 보관하기

프로젝트와 핵심 소스

공식 기술 학습 자료

저장소 현황 (GitHub 메타데이터는 2026-10-23 기준)

항목확인 값과 해석
선정 근거TrendShift Monthly #5
스타 / 포크35,748 / 5,656 · 2026-10-23 GitHub 기준
생성일2022-03-31 · 저장소 생성 시점이며 현재 제품 형태의 출시일과는 다릅니다.
주 언어Rust · 화면에는 TypeScript·TSX도 대규모로 사용됩니다.
최신 릴리스artcraft-v0.41.0 · 2026-09-26
최근 push2026-10-22 · 저장소 갱신 시점이며 아래 분석 커밋의 날짜와 구분합니다.
분석 기준 커밋800217e · 2026-10-21
ci: add sonar-project.properties for CI-based analysis
소스의 앱 버전tauri.conf.json: 0.42.0
데스크톱 Cargo 패키지: 0.0.1 · 공개 릴리스 태그와 별도 표기입니다.
Rust 규모.rs 3,261파일 · 272,400줄
이 중 artcraft-native/ 47파일 · 13,958줄
TypeScript 규모.ts 1,207파일 · 114,304줄
.tsx 452파일 · 92,639줄
합계 206,943줄
보조 언어JS·MJS·CJS 합계 4,673줄, CSS 1,625줄, Python 1,337줄, SQL 127줄
규모 산정 범위커밋 800217e의 Git 추적 파일 5,824개가 기준입니다. 위 언어별 값은 확장자별 물리 줄 수이며 빈 줄·주석·테스트·생성 코드·vendor 코드도 포함합니다. 바이너리 자산의 크기는 줄 수에 반영하지 않습니다.
라이선스GitHub 대표 표기: Apache-2.0
README·데스크톱 Cargo.toml·NOTICE: MIT 또는 Apache-2.0
상표와 일부 제3자 자산은 별도 고지 대상입니다.

줄 수는 기능의 완성도나 직접 작성한 코드의 양을 뜻하지 않습니다. 특히 frontend/apps/artcraft/app/public/의 자산·보조 코드와 crates/vendor/까지 포함한 값입니다. 실제 학습은 전체 크기보다 한 기능의 호출 경로를 기준으로 나누는 편이 좋습니다.

쓰기 전에 알아둘 한계
공개된 클라이언트 코드와 실제 서비스의 범위는 다릅니다

이 글은 커밋 800217e의 정적 코드 분석입니다. 운영 서버의 내부 구현, 계정별 모델 가용성, 실제 생성 품질·지연 시간, OS별 실동작을 보증하지 않습니다. README의 모델 카탈로그에는 비활성·제한 항목이 있고 서버 목록도 바뀔 수 있습니다.

로드맵의 “ArtCraft 호스팅 서비스 의존성 제거”는 목표입니다. 현재 주요 생성 경로에는 ArtCraft API·인증·미디어 업로드 의존성이 보입니다. 완전한 오프라인 생성 도구나 운영 서버 전체를 포함한 자체 호스팅 묶음으로 이해하면 안 됩니다.

코드의 라이선스와 번들 자산의 권리는 동일하지 않습니다. NOTICE는 상표·제3자 라이선스와 별도 권리 자산을 구분합니다. 소스를 가져와 제품을 만들 때는 사용할 코드와 자산의 고지를 각각 확인해야 합니다.

ArtCraft를 통해 가져갈 가장 큰 배움은, 복잡한 창작 기능을 요청·상태·저장·이벤트의 경계로 나누어 읽는 방법입니다. 작은 모의 생성 앱에서 이 경계를 먼저 구현하면, 큰 저장소도 기능 단위로 차근차근 이해할 수 있습니다.