Tiling multi-terminal manager for WSL
Find a file
megaproxy 64b90ebddb Add M3: APPDATA persistence + presets + per-pane distro/label
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>
2026-05-22 12:55:46 +01:00
src Add M3: APPDATA persistence + presets + per-pane distro/label 2026-05-22 12:55:46 +01:00
src-tauri Add M3: APPDATA persistence + presets + per-pane distro/label 2026-05-22 12:55:46 +01:00
.gitignore Add src-tauri/gen/ to gitignore 2026-05-22 12:32:35 +01:00
CLAUDE.md Initial scaffold from M1 spike (tiletopia) 2026-05-22 12:31:29 +01:00
index.html Initial scaffold from M1 spike (tiletopia) 2026-05-22 12:31:29 +01:00
memory.md Add M3: APPDATA persistence + presets + per-pane distro/label 2026-05-22 12:55:46 +01:00
package.json Initial scaffold from M1 spike (tiletopia) 2026-05-22 12:31:29 +01:00
pnpm-lock.yaml Initial scaffold from M1 spike (tiletopia) 2026-05-22 12:31:29 +01:00
pnpm-workspace.yaml Initial scaffold from M1 spike (tiletopia) 2026-05-22 12:31:29 +01:00
README.md Initial scaffold from M1 spike (tiletopia) 2026-05-22 12:31:29 +01:00
svelte.config.js Initial scaffold from M1 spike (tiletopia) 2026-05-22 12:31:29 +01:00
tsconfig.json Initial scaffold from M1 spike (tiletopia) 2026-05-22 12:31:29 +01:00
tsconfig.node.json Initial scaffold from M1 spike (tiletopia) 2026-05-22 12:31:29 +01:00
vite.config.ts Initial scaffold from M1 spike (tiletopia) 2026-05-22 12:31:29 +01:00

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 <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): a PtyManager holding a Mutex<HashMap<PaneId, PaneHandle>> 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.onDatawrite_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.