본문으로 건너뛰기

Storytect

이야기를 도감처럼 펼치고 자기만의 세계관 구조로 정리하는 데스크톱 에디터

기획부터 구현까지 전 과정에 멀티 에이전트 오케스트레이터를 적용

개요

기간

2026.05

개요

개인 프로젝트

주요 기능

사용자 정의 클래스 에디터, 글로벌 시간바, 외부 AI 협업용 MCP 서버

사용 기술

TauriRustNext.jsReactTypeScript@xyflow/reactZustandMCP@mogiyoon/react-stable-timeline

주요 기능과 개발 과정

표준 JSON 인터페이스와 추출·화면 표시 분리

AI 활용추출 스킬MCP 도구 호출항목·관계 편집manifest.json재렌더원고표준 JSON 폴더외부 AIMCP 서버사용자 직접 편집파일 변경 감지Storytect 앱6템플릿 보기
추출·화면 표시를 분리한 덕분에 추출 스킬·MCP·직접 편집이라는 서로 다른 세 경로가 같은 표준 JSON 폴더로 모이고, 파일 변경 감지가 이를 한 화면에 반영함.
같은 JSON 폴더를 타임라인·관계도·위키로 동시에 연 화면
문제

추출 로직과 화면 표시 코드를 함께 묶으면 화면 구성을 바꿀 때마다 추출 결과의 형식도 바뀔 수 있음. 이 경우 기존 사용자 데이터와의 호환성을 유지하기 어렵고, 추출 스킬도 특정 시각화 도구에 묶여 다른 도구에서 재사용하기 어려워짐.

분석

brief.md의 핵심 원칙으로 추출과 화면 표시를 분리한다고 명시함. 앱은 표준 JSON 폴더만 읽고, manifest.json을 하나의 시작점으로 사용하는 구조를 선택함.

해결

앱이 추출 스킬에서 생성한 표준 JSON 폴더만 읽도록 구현함. manifest.json을 시작점으로 삼아 슬롯 목록·메타데이터·available_views를 한 파일에서 읽도록 설계함.

결과

이 구조 덕분에 추출 스킬을 시각화 도구와 관계없이 재사용할 수 있게 됨. 정해진 데이터 형식은 유지하면서 화면 표현은 자유롭게 다시 설계할 수 있게 됨.

모든 보기가 공유하는 시간바

상단 고정해당 시점 항목 강조·나머지 흐림모든 항목이 시간 보유전역 시간바관계도 보기위키 보기타임라인 보기시점 드래그Entity·Relation시간값
타임라인 안에 있던 시간 축을 전역 시간바로 끌어올림 — 모든 보기가 같은 기준으로 시점을 강조함.
문제

시간 축이 timeline 보기에만 있으면 관계도나 위키에서 같은 시점을 강조하려고 보기마다 시간 처리 코드를 따로 작성해야 함. 보기를 전환할 때 선택한 시점이 사라지는 문제도 생김.

분석

모든 Entity와 Relation이 시간 값을 갖도록 설정함. 일반적인 데이터 모델에서는 필요하지 않을 수 있지만, 이야기 데이터에서는 모든 값에 시간 정보가 필요하다고 판단함.

해결

기존 timeline 보기 안에 있던 시간 축을 앱 전체에서 쓰는 컴포넌트로 변경함. 모든 보기의 상단에 시간바를 고정하고, 특정 시점을 드래그하면 그 시점에 존재하는 항목은 강조하고 나머지는 흐리게 표시하도록 구현함.

결과

관계도·위키를 포함한 모든 보기에서 같은 기준으로 시점을 강조할 수 있게 됨. 이 구조는 화면 표시 방식을 두 차례 전면 교체한 뒤에도 그대로 유지됨.

사용자가 정의하는 4계층 모델 — 객체지향으로 기획해 ERD로 도착함

관계 역시 같은 항목슬롯 종류를 메타데이터로 선언갈피칸항목엮음인물·사건·관계 통일SlotMeta.class_meta
갈피 > 칸 > 항목 > 엮음의 4계층 구조. 관계 역시 항목으로 다뤄 인물·사건·관계가 같은 데이터 형식으로 통일됨.
문제

13개 보기를 나열하는 구조만으로는 사용자가 생각하는 세계관의 구성을 충분히 표현하기 어려웠음. 보기의 수를 늘리는 방식으로도 이 한계를 해결할 수 없었음.

해결

사용자가 자기 세계관의 유형을 직접 정의할 수 있도록 클래스를 선언하고 인스턴스를 생성하는 객체지향 모델로 기획함. 13뷰를 6템플릿(Graph / Timeline / Matrix / Curve / List / Tree)에 매핑해 기존 데이터를 손실 없이 옮김.

해결

클래스 선언 모델을 에디터에 적용해 갈피·칸·항목·엮음의 4계층 구조를 설계함(PLANNING_OO.md). SlotMeta.class_meta를 도입해 슬롯 종류를 코드 대신 사용자 메타데이터에 선언하도록 변경함.

분석

갈피는 테이블, 칸은 칼럼, 항목은 행, 엮음은 테이블 간 관계에 대응함. 상속이나 메서드 같은 객체지향 요소는 사용되지 않음. 필요한 요소는 타입 정의와 참조뿐이었음.

결과

객체지향 모델로 시작했으나 실제 구현 결과는 사용자가 스키마를 직접 정의하는 ERD였음. 동일한 구조에 슬롯 메타데이터 검증과 관계 편집 기능을 추가할 수 있었던 이유임.

외부 AI 협업을 위한 MCP 서버

MCP 도구 19종읽기·편집직접 수정파일 변경 감지외부 AIMCP 서버표준 JSON 폴더사용자 화면 편집데스크톱 앱 재렌더
외부 AI의 MCP 편집과 사용자의 화면 편집이 같은 표준 JSON 폴더로 모이고, 파일 변경 감지로 한 화면에 함께 반영됨.
문제

사용자가 화면을 직접 클릭하는 방식만으로 편집하면 반복 작업이 많을 때 부담이 커짐. API 사용이 어려운 사용자도 '사이드 캐릭터를 따로 분리해줘', '등장하지 않는 인물 카드를 숨겨줘' 같은 도감 단위 작업을 외부 AI에 맡길 수 있는 방법이 필요했음.

분석

Storytect는 이미 추출과 화면 표시를 분리하고, manifest.json에서 시작하는 표준 JSON 폴더 규칙을 사용하고 있었음. 따라서 같은 데이터 규칙을 사용하는 외부 연결 지점을 추가할 수 있었음. 데스크톱 앱이 파일 변경을 감지해 화면을 바로 다시 그리므로 외부 AI가 수정한 내용과 사용자가 직접 수정한 내용이 한 화면에 함께 반영됨. MCP를 별도 시스템으로 만들지 않고 같은 도감을 편집하는 연결 지점으로 쓸 수 있었던 이유는 폴더 구조 규칙을 먼저 정해 두었기 때문임.

해결

프로젝트 폴더 하나를 작업 영역으로 사용하는 MCP 서버를 구축함. 읽기·편집·갈피 메타데이터 도구 19개를 통합 제공함. 데스크톱 앱의 편집 기능 대부분을 외부에서도 실행할 수 있게 함.

결과

외부 AI가 표준 JSON 폴더를 직접 읽고 수정할 수 있게 됨. Claude 데스크톱에 원고를 입력하고 MCP 도구를 호출하면 API 비용 없이 도감을 편집할 수 있는 경로가 추가됨.

문제 해결

Tauri webview가 OS 수준에서 dragDrop을 가로채는 문제

Tauri webview가 OS 수준 가로챔dragDropEnabled false표준 이벤트 그대로 전달외부 파일 드롭 비활성 (미사용 기능)사이드바 드래그HTML5 drag 이벤트이벤트 미전달가로채기 해제드래그 복구부작용 없음
브라우저에선 되던 드래그가 데스크톱에서 죽은 원인 — webview의 OS 수준 가로채기를 설정 한 줄로 해제함.
문제

사이드바에서 항목을 끌어 순서를 바꾸는 기능이 특정 시점부터 동작하지 않음. 브라우저에서는 정상 동작했지만 Tauri 데스크톱 앱에서는 HTML5 drag 이벤트가 발생하지 않았음.

분석

Tauri webview는 OS 수준에서 drag/drop을 가로채 외부 파일을 놓는 기능으로 처리함. 이 때문에 같은 React HTML5 drag 코드가 브라우저에서는 동작하지만 데스크톱 앱에서는 drag 이벤트가 webview까지 전달되지 않았음. dragDropEnabled: false는 webview가 이벤트를 가로채지 않고 표준 이벤트를 그대로 받게 하는 설정임. 이 설정을 쓰면 외부 파일을 놓는 기능은 비활성화됨. 현재 앱은 해당 기능을 사용하지 않으므로 문제가 없었고, 설정 한 줄을 바꿔 원인을 해결함.

분석

1. 이벤트 핸들러가 빠졌는지 확인 → 정상적으로 연결되어 있음을 확인 2. webview 내부 콘솔 로그 확인 → drag 이벤트 자체가 호출되지 않음을 확인 3. Tauri webview 동작 방식 분석 → OS 수준에서 drag&drop을 가로채는 구조 확인

해결

4. Tauri 설정에 dragDropEnabled: false 적용 → HTML5 drag 이벤트가 webview에 전달되도록 수정

결과

설정 한 줄을 바꿔 사이드바 드래그 기능을 복구함. 데스크톱 앱에서는 웹 표준 이벤트가 그대로 전달되지 않을 수 있음을 확인함.

vis-timeline에서 직접 구현을 거쳐 npm 패키지로 분리

subgroup 등 옵션 시도전체 데이터 기준 1회 배치npm 패키지 분리vis-timeline 도입줌·팬마다 행 이동실패타임라인 직접 구현행 고정 유지react-stable-timeline
행 고정 옵션이 모두 실패해 전체 데이터 기준으로 행을 한 번만 정하는 타임라인을 직접 구현하고, npm 패키지로 분리함.
문제

타임라인을 vis-timeline으로 구현했으나 줌·팬할 때마다 같은 사건이 다른 행으로 이동함. 라이브러리가 화면에 표시되는 픽셀 좌표를 기준으로 행 배치를 매번 다시 계산하기 때문임.

분석

행 고정 옵션(subgroup, stackSubgroups)을 모두 시도했으나 실행 중에는 예상대로 동작하지 않음. 전체 데이터를 기준으로 행을 한 번만 정하는 구조가 필요하다고 판단함.

해결

행 고정 옵션을 모두 시도했으나 문제가 해결되지 않음. 전체 데이터를 기준으로 행을 한 번만 결정하는 타임라인을 직접 구현함. 이후 해당 로직을 별도 npm 패키지로 분리함.

결과

줌·팬 중에도 같은 사건이 항상 같은 행에 유지됨. 분리한 패키지는 다른 프로젝트에서도 재사용할 수 있게 됨.

결과와 회고

결과

Tauri + Next.js 16 + Rust 기반 데스크톱 앱을 단일 바이너리로 빌드함. Rust 코드는 약 5,000줄이며 테스트 198개로 검증함

specta + tauri-specta 기반의 Rust↔TypeScript 자동 타입 생성 작업 흐름을 구축함

추출 스킬과 연결되는 표준 JSON 인터페이스를 설계하고, 화면 표시 구조를 두 차례 재구성한 뒤에도 데이터 호환성을 유지함(고정 카탈로그 → 사용자 정의 클래스 선언 → 빈 캔버스)

쉬운 화면 용어(갈피/엮음), 보기 방식 7개 통합 토글, 사이드바 폴더 트리를 구현함

외부 AI 협업용 MCP 서버를 구축해 외부에서도 같은 도감을 읽고 편집할 수 있게 함

기획과 구현 단계에 맞춘 멀티 에이전트 오케스트레이터를 직접 설계하고 운영함

회고

기획 문서를 brief.md, PLANNING_OO.md, PLANNING_REVISE.md로 나누어 두 차례의 전면 재설계 전후에 세운 가정과 결정 이유를 모두 보존할 수 있었음. 각 시점의 판단을 나중에도 확인할 수 있도록 기록하는 방식의 장점을 확인함.

핵심 구조를 적절히 나누어 두면 구현 방식이 크게 바뀌어도 중요한 원칙은 유지됨을 확인함. 글로벌 시간바와 추출·화면 표시 분리 원칙은 화면 표시 구조를 두 차례 전면 교체한 뒤에도 그대로 남았음.

도구를 설계할 때 사용자가 직접 정할 수 있는 범위를 단계적으로 넓히는 일이 중요함을 확인함. 고정 카탈로그 구조에서 사용자 정의 클래스 선언 구조를 거쳐 빈 캔버스 기반의 자유 편집기로 바꾸면서, 사용자가 도구의 구조까지 직접 정할 수 있는 범위가 넓어졌음.

라이선스