# tiletopia A Windows desktop app for running and arranging many WSL terminals at once. Built primarily to manage multiple `claude` sessions across projects in parallel, but works for any multi-shell workflow. Status: **early — single-pane M1 works**. Tiling layout (M2), workspace persistence (M3), and cross-pane orchestration (M4) are the next milestones. See `memory.md`. ## Stack - **Tauri 2** (Rust backend, WebView2 frontend) — small bundle, native Windows installer via NSIS. - **Svelte 5** + TypeScript + Vite + pnpm. - **xterm.js** + `@xterm/addon-fit` for terminal rendering. - **`portable-pty`** (Rust crate) spawning `wsl.exe -d ` PTYs. ## Run This project targets Windows. Dev requires: - Windows 10/11 + [WebView2 Runtime](https://developer.microsoft.com/microsoft-edge/webview2/) (preinstalled on Win11). - [MSVC toolchain](https://v2.tauri.app/start/prerequisites/#windows) (VS Build Tools, "C++ build tools" workload). - [Rust](https://rustup.rs/) on the Windows host. - Node 20+ and pnpm (`corepack use pnpm@11.2.2`). - WSL with at least one distro installed. **Location matters.** The source must live on a Windows-native drive (here: `D:\dev\tiletopia\`). Don't run pnpm against the `\\wsl.localhost\...` UNC path — pnpm 11.x crashes inside `isDriveExFat` and the actual error gets swallowed by the crashing error-hint formatter. From a Windows shell: ```powershell cd D:\dev\tiletopia pnpm install pnpm tauri dev # iterate pnpm tauri build # NSIS installer at src-tauri\target\release\bundle\nsis\ ``` The WSL-side symlink at `~/claude/projects/tiletopia` points here for in-WSL editing. ## How it works (current state) - **Backend (`src-tauri/src/pty.rs`):** a `PtyManager` holding a `Mutex>` of `portable-pty` children. Each spawned pane gets a background reader thread that emits `pane://{id}/data` events to the frontend (base64-encoded byte chunks). Counterparts: `write_to_pane`, `resize_pane`, `kill_pane`. Distro enumeration parses `wsl.exe -l -q` (UTF-16LE). - **Frontend (`src/components/XtermPane.svelte`):** xterm.js + FitAddon mounted into a div. On mount, calls `spawn_pane`, subscribes to the pane's event stream, wires `term.onData` → `write_to_pane`, and uses a `ResizeObserver` to forward dimension changes to the PTY. - **App (`src/App.svelte`):** titlebar with clickable distro buttons (auto-picks first non-docker-desktop distro; user can override). One XtermPane wrapped in `{#key selected}` so changing distro destroys + respawns the pane. ## Layout ``` tiletopia/ ├── CLAUDE.md, memory.md, README.md ├── .gitignore, pnpm-workspace.yaml ├── package.json, vite.config.ts, svelte.config.js, tsconfig.json, tsconfig.node.json ├── index.html ├── src/ │ ├── main.ts # mounts App, imports xterm.css │ ├── App.svelte # titlebar + one XtermPane (M1) │ ├── styles.css │ ├── ipc.ts # typed Tauri command wrappers │ └── components/ │ └── XtermPane.svelte └── src-tauri/ ├── Cargo.toml, build.rs, tauri.conf.json ├── capabilities/default.json ├── icons/ # placeholder, copied from claude-usage-widget └── src/ ├── main.rs ├── lib.rs # tauri builder, registers commands, manages PtyManager ├── pty.rs # PtyManager + list_wsl_distros └── commands.rs # #[tauri::command] surface ``` ## Known gotchas (today) - **Don't `pnpm install` from a UNC path** (`\\wsl.localhost\...`). pnpm 11.x crashes in its `isDriveExFat` probe; the underlying error gets swallowed. - **Console flash on `wsl.exe -l -q`:** suppressed via the `CREATE_NO_WINDOW` flag in `pty.rs`. The PTY itself doesn't allocate a console (portable-pty uses ConPTY directly). - **base64 wire format:** xterm.js emits `string` from `onData`; we UTF-8 encode then base64. Not the fastest; switch to typed-array event payloads later if throughput is an issue. - **No icons of our own:** copied from `claude-usage-widget`. Replace before any release. - **Cargo build only works on Windows host** — Rust toolchain isn't installed in WSL. `pnpm check` runs in WSL and validates the Svelte/TS side.