<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Compose Desktop on monkshark.dev</title><link>https://monkshark.github.io/tags/compose-desktop/</link><description>Recent content in Compose Desktop on monkshark.dev</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Sun, 03 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://monkshark.github.io/tags/compose-desktop/index.xml" rel="self" type="application/rss+xml"/><item><title>#1 - 왜 또 IDE를 만드나</title><link>https://monkshark.github.io/p/page-ide-intro/</link><pubDate>Sun, 03 May 2026 00:00:00 +0000</pubDate><guid>https://monkshark.github.io/p/page-ide-intro/</guid><description>&lt;p&gt;새 프로젝트를 시작했다. 이름은 &lt;strong&gt;PAGE&lt;/strong&gt;, 데스크톱 IDE다.&lt;/p&gt;
&lt;p&gt;VSCode가 있고, JetBrains가 있고, Zed가 있고, 심지어 Claude Code도 있다. 그럼에도 또 하나의 에디터를 만든다는 게 합리적인 일인지 스스로도 몇 번 되물었다. 결국 시작한 이유는 단순했다. &lt;strong&gt;지금의 에디터들은 내가 코드를 어떻게 짜는지에 대해 너무 무관심하다.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="기존-에디터들이-답답한-지점들"&gt;&lt;a href="#%ea%b8%b0%ec%a1%b4-%ec%97%90%eb%94%94%ed%84%b0%eb%93%a4%ec%9d%b4-%eb%8b%b5%eb%8b%b5%ed%95%9c-%ec%a7%80%ec%a0%90%eb%93%a4" class="header-anchor"&gt;&lt;/a&gt;기존 에디터들이 답답한 지점들
&lt;/h2&gt;&lt;p&gt;오래 코드를 만지면서 누적된 불만이 네 가지 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI 도구가 외주 같다.&lt;/strong&gt; Claude Code를 자주 쓴다. 강력하고, 놀라울 정도로 똑똑하다. 다만 작업 단위가 명확하다. 명령을 주면 결과가 오고, 세션이 끝나면 사라진다. 옆에 상주하면서 내 코드를 함께 보는 동료라기보다는, 부르면 오는 외주 엔지니어에 가깝다. 코드를 쓰는 그 순간에 곁에서 관찰하고 짧게 거들어주는 모드가 없다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;코드 구조가 보이지 않는다.&lt;/strong&gt; 파일 트리는 디렉터리 구조일 뿐이다. 어떤 모듈이 어떤 모듈을 부르는지, 의존이 어떻게 흐르는지, 화면에 띄워주는 에디터를 거의 못 봤다. 머릿속에 직접 그래프를 그리면서 일한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;내 작업의 흐름이 사라진다.&lt;/strong&gt; Git 커밋은 결과 스냅샷이다. 그 사이에 어떤 시도를 하고, 무엇을 지웠고, 어디서 막혔는지의 시간축은 어디에도 남지 않는다. 한 시간 전의 나에게 묻고 싶을 때가 자주 있는데, 답해줄 도구가 없다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UI가 작업 도구로서만 존재한다.&lt;/strong&gt; 매일 8시간 보는 화면인데, 미적으로는 거의 0의 투자를 받는다. 기능은 갖췄지만 보고 싶은 화면은 아니다.&lt;/p&gt;
&lt;p&gt;이 네 가지 중 한두 개는 어딘가의 에디터가 부분적으로 풀고 있다. 다 동시에 푸는 건 본 적이 없다.&lt;/p&gt;
&lt;h2 id="다른-선택지는-없었나"&gt;&lt;a href="#%eb%8b%a4%eb%a5%b8-%ec%84%a0%ed%83%9d%ec%a7%80%eb%8a%94-%ec%97%86%ec%97%88%eb%82%98" class="header-anchor"&gt;&lt;/a&gt;다른 선택지는 없었나
&lt;/h2&gt;&lt;p&gt;처음에는 플러그인으로 풀어볼까 했다. JetBrains 플러그인, VSCode extension. 둘 다 검토했고 둘 다 포기했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;코드 그래프나 시간축 같은 기능은 호스트 에디터의 렌더링 모델에 깊이 들어가야 한다. 플러그인 API가 허락하는 범위 밖이다.&lt;/li&gt;
&lt;li&gt;AI 동행 모드는 에디터의 입력 이벤트 스트림 전체를 봐야 한다. 플러그인은 이벤트 일부만 본다.&lt;/li&gt;
&lt;li&gt;디자인 통제. 글래스모피즘 UI를 플러그인으로 만들면 결국 호스트 테마와 충돌한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그래서 통째로 새로 짜는 쪽으로 결정했다. 비합리적인 선택일 수 있다는 건 안다. 다만 부분적으로 푸는 것보다 빠를 거라고 판단했다.&lt;/p&gt;
&lt;h2 id="컨셉-page"&gt;&lt;a href="#%ec%bb%a8%ec%85%89-page" class="header-anchor"&gt;&lt;/a&gt;컨셉: PAGE
&lt;/h2&gt;&lt;p&gt;핵심 가치 네 가지의 앞글자를 모았다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;P&lt;/strong&gt;air — AI 동반자. 관찰자 / 대화 / 에이전트 / 튜터 네 가지 모드.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A&lt;/strong&gt;tlas — 코드 그래프 시각화. 모듈, 함수, 의존성을 공간으로.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;G&lt;/strong&gt;lass — 글래스모피즘 UI. 작업 도구이면서 보고 싶은 화면.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;E&lt;/strong&gt;cho — 키스트로크 타임라인. 작업의 시간축을 그대로 저장하고 되감기.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 네 가지를 한 번에 다 내놓는다는 뜻은 아니다. 마일스톤을 네 단계로 쪼갰고, 첫 단계에서는 &amp;ldquo;예쁜 다언어 에디터&amp;rdquo; 하나만 완성하는 것을 목표로 한다. 나머지 셋은 그 위에 한 층씩 얹는다.&lt;/p&gt;
&lt;p&gt;이름은 의도적으로 영단어 &amp;ldquo;page&amp;quot;와 겹친다. 한 페이지에서 네 가지 차원을 다 본다는 뜻을 살리고 싶었다.&lt;/p&gt;
&lt;h2 id="기술-스택-결정"&gt;&lt;a href="#%ea%b8%b0%ec%88%a0-%ec%8a%a4%ed%83%9d-%ea%b2%b0%ec%a0%95" class="header-anchor"&gt;&lt;/a&gt;기술 스택 결정
&lt;/h2&gt;&lt;p&gt;자바를 가장 오래 썼다. JVM 안에서 풀고 싶었다. 그래서 Kotlin + Compose Multiplatform Desktop으로 갔다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;jvmToolchain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;desktop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currentOs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;org.eclipse.lsp4j:org.eclipse.lsp4j:0.21.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;org.eclipse.jgit:org.eclipse.jgit:6.9.0.202403050737-r&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;org.xerial:sqlite-jdbc:3.45.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;com.squareup.okhttp3:okhttp:4.12.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;결정적인 이유 세 가지.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Compose Desktop은 JetBrains Fleet의 스택과 같다.&lt;/strong&gt; 데스크톱급 에디터를 띄울 수 있다는 산 증거가 있다는 게 컸다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JVM 생태계의 라이브러리를 그대로 쓴다.&lt;/strong&gt; LSP4J, JGit, SQLite JDBC, OkHttp. 다른 스택을 골랐으면 이 중 절반은 직접 다시 짜야 했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GPU 합성 + 글래스모피즘.&lt;/strong&gt; Skia 기반이라 블러/투명도 효과를 60fps로 끌고 갈 수 있다. Swing이었으면 포기했을 항목이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tauri(Rust)나 Electron도 후보였다. Tauri는 LSP/Tree-sitter/JGit을 다 직접 붙여야 했고, Electron은 기본 메모리 비용이 IDE에는 무거웠다. 둘 다 한 번씩 손에 잡고 보고 빠졌다.&lt;/p&gt;
&lt;h2 id="다언어-어떻게"&gt;&lt;a href="#%eb%8b%a4%ec%96%b8%ec%96%b4-%ec%96%b4%eb%96%bb%ea%b2%8c" class="header-anchor"&gt;&lt;/a&gt;다언어, 어떻게
&lt;/h2&gt;&lt;p&gt;이 IDE의 첫 약속은 &amp;ldquo;주요 언어 30개를 기본으로 켠다&amp;quot;이다. 직접 30개의 분석기를 짤 생각은 없다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;언어 정의는 JSON 한 개로.&lt;/strong&gt; 확장자, 트리시터 그래머, LSP 서버 명령, 디버거 어댑터 — 한 파일로 끝낸다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LSP 서버는 PATH에서 자동 감지.&lt;/strong&gt; 사용자가 &lt;code&gt;pyright&lt;/code&gt;를 깔면 파이썬이 자동으로 켜진다. 직접 등록할 필요 없다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;부재 시 정중히 안내.&lt;/strong&gt; 서버가 없으면 빨간 에러 대신 &amp;ldquo;이걸 깔면 켜집니다&amp;rdquo; 한 줄과 클릭 한 번 설치를 보여준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예를 들어 Dart/Flutter 정의는 이렇게 들어간다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dart&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;extensions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;.dart&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;treeSitter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tree-sitter-dart&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;lsp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;dart&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;language-server&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--protocol=lsp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;rootMarkers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pubspec.yaml&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;frameworks&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;flutter&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;추가하고 싶은 언어가 있으면 JSON 한 줄을 더하는 정도로 끝나야 한다. 그게 이 구조의 가치다.&lt;/p&gt;
&lt;h2 id="다음-글에서는"&gt;&lt;a href="#%eb%8b%a4%ec%9d%8c-%ea%b8%80%ec%97%90%ec%84%9c%eb%8a%94" class="header-anchor"&gt;&lt;/a&gt;다음 글에서는
&lt;/h2&gt;&lt;p&gt;PAGE의 모듈 구조를 갈랐다. core / editor / language / workspace / ui / atlas / echo / pair / runtime — 왜 이렇게 나눴고, 모듈 간 직접 의존을 어떻게 차단했는지, 그리고 그 경계가 나중에 Atlas의 그래프 노드가 되는 이유를 다음 글에서 다룬다.&lt;/p&gt;
&lt;p&gt;돌이켜보면, 새 도구를 만들겠다는 결정은 늘 비슷한 모양으로 온다. 기존의 답답함이 누적되다가, 어느 날 &amp;ldquo;이건 통째로 다시 짜는 게 빠르다&amp;quot;라는 한 줄이 머리에 박힌다. 그 한 줄로 시작했다. 이제부터의 기록이 그 한 줄이 옳았는지를 증명하거나, 부수거나 할 것이다.&lt;/p&gt;</description></item></channel></rss>