Tiling multi-terminal manager for WSL
Find a file
megaproxy 45d0f4cd8b Add a 2px gap around each leaf slot so per-pane borders don't merge
With panes packed edge-to-edge, adjacent .leaf elements' 2px borders
touched directly. When all (or many) panes went idle, every pane's
red border combined with its neighbour's to form continuous red lines
across the whole window — looking like a single grid pattern, not
distinct per-pane outlines.

Fix: padding: 2px on .leaf-slot (box-sizing: border-box). Each .leaf
ends up inset 2px on every side, so adjacent panes have a 4px dark
gap between them and their borders read as separate rectangles.
Affects all borders equally (idle red, active blue, broadcasting
orange) and gives the layout a cleaner separation overall.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 22:18:32 +01:00
scripts M5 ship infrastructure: icon, version, release script, README 2026-05-22 13:38:29 +01:00
src Add a 2px gap around each leaf slot so per-pane borders don't merge 2026-05-22 22:18:32 +01:00
src-tauri Default new panes to WSL home (~) instead of inherited Windows cwd 2026-05-22 21:43:38 +01:00
.gitignore Gitignore *.tsbuildinfo (tsc -b incremental cache) 2026-05-22 18:35:31 +01:00
CLAUDE.md Initial scaffold from M1 spike (tiletopia) 2026-05-22 12:31:29 +01:00
index.html Migrate frontend from Svelte 5 to React 18 2026-05-22 18:05:05 +01:00
memory.md M5 ship infrastructure: icon, version, release script, README 2026-05-22 13:38:29 +01:00
package.json Bump version to 0.2.1 2026-05-22 21:10:50 +01:00
pnpm-lock.yaml Migrate frontend from Svelte 5 to React 18 2026-05-22 18:05:05 +01:00
pnpm-workspace.yaml Initial scaffold from M1 spike (tiletopia) 2026-05-22 12:31:29 +01:00
README.md Add keyboard shortcuts (Ctrl+Shift chord style) 2026-05-22 21:32:51 +01:00
tsconfig.app.json Migrate frontend from Svelte 5 to React 18 2026-05-22 18:05:05 +01:00
tsconfig.json Migrate frontend from Svelte 5 to React 18 2026-05-22 18:05:05 +01:00
tsconfig.node.json Migrate frontend from Svelte 5 to React 18 2026-05-22 18:05:05 +01:00
vite.config.ts Migrate frontend from Svelte 5 to React 18 2026-05-22 18:05:05 +01:00

tiletopia

A Windows desktop app for running and arranging many WSL terminals at once. Built primarily for managing multiple claude sessions across projects in parallel; works for any multi-shell workflow.

  • Tiling layout — recursive splits, draggable dividers, preset layouts (single / 2-col / 3-col / 2-row / 2×2)
  • Per-pane distro + cwd + label, persisted across restarts
  • Broadcast input to a group of panes (per-pane 📡 chip, or global toggle in the titlebar)
  • Idle-detection toasts when a pane goes quiet
  • Ctrl+K palette to fuzzy-jump between panes

Install

  1. Download the latest tiletopia_<version>_x64-setup.exe from the releases page.
  2. Run it. Windows SmartScreen will warn "unrecognized publisher" — it's not code-signed. More info → Run anyway.
  3. Launch tiletopia from the Start menu. A window opens with one terminal pane bound to your default WSL distro.

Requirements

  • Windows 10/11 with WebView2 Runtime (preinstalled on Win11).
  • At least one WSL distro registered (wsl -l -v).

Using it

  • Split panes in the pane toolbar splits right, splits down. The new pane inherits the parent's distro + cwd.
  • Close pane×. The sibling expands to fill.
  • Rename pane — click the label in the toolbar, type, Enter (Esc to cancel).
  • Change distro — click the small Ubuntu ▾ chip; pick a distro from the popover. The pane respawns (old shell is killed).
  • Broadcast — toggle 📡 on two or more panes (orange border). Typing in any of them mirrors to all. The titlebar 📡 all off / 📡 all on / 📡 N/M button flips the whole group at once.
  • Preset layouts — titlebar buttons: 1 / 2H / 3H / 2V / 2×2. Confirms before replacing a multi-pane layout.
  • Active pane — click any pane → blue border + keyboard focus.
  • Jump to paneCtrl+K opens a fuzzy picker over label / distro / cwd. ↑/↓ to navigate, Enter to focus, Esc to close.

Keyboard shortcuts

Key Action
Ctrl+K open the jump-to-pane palette
Ctrl+Shift+E split active pane to the right
Ctrl+Shift+O split active pane downward
Ctrl+Shift+W close active pane
Ctrl+Shift+B toggle broadcast on active pane
Ctrl+Shift+Alt+B toggle broadcast on ALL panes (titlebar 📡)
Ctrl+Shift+←/→/↑/↓ focus neighbour pane in that direction

Shortcuts work while a terminal is focused (we capture before xterm.js sees the key). They DON'T fire while you're typing into a label edit or the palette input, so those still work normally.

  • Idle toasts — top-right notification when a pane goes quiet for 5 s. Useful for "I started a long task; tell me when it's done."

Layout + per-pane settings auto-save to %APPDATA%\com.megaproxy.tiletopia\workspace.json (debounced 500 ms).

Stack

  • Tauri 2 (Rust backend, WebView2 frontend) — small bundle, native NSIS installer.
  • React 18 + TypeScript + Vite + pnpm. (The v0.1.0 release was Svelte 5; v0.2.0+ is React after a ground-up rewrite of the frontend. Same data model, same backend, more reliable reactivity through the recursive Pane chain. The Svelte version is preserved on the svelte-archive branch.)
  • xterm.js + @xterm/addon-fit for terminal rendering.
  • portable-pty (Rust) spawning wsl.exe -d <distro> PTYs.

Build from source

This targets Windows; the Rust toolchain runs on the Windows host. Prereqs per Tauri docs: MSVC ("C++ build tools" workload), Rust, Node 20+, pnpm (corepack use pnpm@latest), at least one WSL distro.

git clone https://git.rdx4.com/megaproxy/tiletopia.git
cd tiletopia
pnpm install
pnpm tauri dev          # iterate
pnpm tauri build        # NSIS installer at src-tauri\target\release\bundle\nsis\

Keep the source on a Windows-native drive (e.g. C:\ or D:\). Running pnpm against a \\wsl.localhost\... UNC path crashes pnpm 11.x inside isDriveExFat (with a misleading error from the crashing hint formatter).

Run the tests

pnpm test          # vitest, 43 cases on the layout tree
pnpm test:watch
pnpm check         # tsc --noEmit (strict TypeScript pass)
pnpm build         # tsc -b && vite build — full production frontend bundle

The test suite covers the pure helpers in src/lib/layout/tree.ts. UI behavior, broadcast routing, and Tauri integration are manually tested.

Architecture

  • Backend (src-tauri/src/pty.rs): PtyManager holding Mutex<HashMap<PaneId, PaneHandle>> of portable-pty children. Each spawned pane gets a background reader thread that emits pane://{id}/data events (base64 byte chunks). Counterparts: write_to_pane / resize_pane / kill_pane. Workspace persistence via save_workspace / load_workspace writes to app.path().app_config_dir() with atomic tmp + rename.
  • Layout (src/lib/layout/tree.ts): binary tree of splits. HSplit | VSplit internal nodes with a ratio, Leaf at the bottom — same model as i3 / tmux / Zellij. Adaptive resize falls out of mutating one parent ratio. Pure helpers (splitLeaf, closeLeaf, changeDistro, setAllBroadcast, etc.) live in tree.ts with 43 vitest cases; the rendering chain (Pane.tsxSplitNode.tsx / LeafPane.tsx) is thin.
  • Orchestration — broadcast routing, idle detection, palette, active-pane focus all live in App.tsx. Shared state and operations reach descendants through a React Context (src/lib/layout/orchestration.tsx), so each LeafPane reads activeLeafId, distros, and the tree-mutation methods directly via useOrchestration() — no prop drilling through the recursive Pane chain.

License

No formal license yet. Public for inspection and personal use; if you want to redistribute, open an issue and ask.