Backend:
- save_workspace / load_workspace Tauri commands writing to
%APPDATA%\com.megaproxy.tiletopia\workspace.json with atomic
tmp+rename. Path from app.path().app_config_dir() (no dirs crate).
Layout helpers:
- tree.ts: changeDistro (with id swap to force XtermPane remount via
{#key}), changeLabel, presetSingle / TwoColumns / ThreeColumns /
TwoRows / TwoByTwo.
- New ops.ts with PaneOps interface bundling split / close /
setDistro / setLabel / distros, drilled through Pane chain
instead of individual callbacks.
UI:
- LeafPane: in-toolbar editable label (click to rename, Enter
saves, Esc cancels) and distro chip popover. Picking a different
distro respawns the pane.
- App.svelte: migrated from localStorage to APPDATA via the new
Tauri commands, debounced 500ms. One-time localStorage migration
on boot. Split inherits parent's distro+cwd. Titlebar preset
buttons with confirm when replacing >1 pane.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| src | ||
| src-tauri | ||
| .gitignore | ||
| CLAUDE.md | ||
| index.html | ||
| memory.md | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| svelte.config.js | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
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-fitfor terminal rendering. portable-pty(Rust crate) spawningwsl.exe -d <distro>PTYs.
Run
This project targets Windows. Dev requires:
- Windows 10/11 + WebView2 Runtime (preinstalled on Win11).
- MSVC toolchain (VS Build Tools, "C++ build tools" workload).
- Rust 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:
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): aPtyManagerholding aMutex<HashMap<PaneId, PaneHandle>>ofportable-ptychildren. Each spawned pane gets a background reader thread that emitspane://{id}/dataevents to the frontend (base64-encoded byte chunks). Counterparts:write_to_pane,resize_pane,kill_pane. Distro enumeration parseswsl.exe -l -q(UTF-16LE). - Frontend (
src/components/XtermPane.svelte): xterm.js + FitAddon mounted into a div. On mount, callsspawn_pane, subscribes to the pane's event stream, wiresterm.onData→write_to_pane, and uses aResizeObserverto 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 installfrom a UNC path (\\wsl.localhost\...). pnpm 11.x crashes in itsisDriveExFatprobe; the underlying error gets swallowed. - Console flash on
wsl.exe -l -q: suppressed via theCREATE_NO_WINDOWflag inpty.rs. The PTY itself doesn't allocate a console (portable-pty uses ConPTY directly). - base64 wire format: xterm.js emits
stringfromonData; 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 checkruns in WSL and validates the Svelte/TS side.