After this session's diagnostic confirmed the root cause (Svelte 5 prop reactivity does NOT propagate through Pane → SplitNode → LeafPane in this app — each LeafPane captures props at mount and never sees updates), restored the brute-force DOM workarounds that were proven to work and threw in a throttle for the gutter drag. What changed vs the broken intermediate state: - App polling: re-sync .leaf.active, .leaf.broadcasting, .bcast-chip.on classes from tree+activeLeafId state every 250ms. Bypasses Svelte reactivity entirely. - SplitNode drag: rAF-throttle the direct flex update so we stop spamming SIGWINCH to the PTYs (which was making shells redraw prompts repeatedly, creating the visual artifacts the user reported). - Close: keep the targeted PTY-kill + DOM-hide-the-side approach so panes visually disappear and siblings fill via flex auto-allocation. This isn't pretty, but it works. The proper fix is to either find / file the Svelte 5 bug, or migrate the frontend to a framework whose reactivity we can trust. Both deferred. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| scripts | ||
| 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 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
- Idle-detection toasts when a pane goes quiet
- Ctrl+K palette to fuzzy-jump between panes
Install
- Download the latest
tiletopia_<version>_x64-setup.exefrom the releases page. - Run it. Windows SmartScreen will warn "unrecognized publisher" — it's not code-signed. More info → Run anyway.
- 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. - 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 pane —
Ctrl+Kopens a fuzzy picker over label / distro / cwd. ↑/↓ to navigate, Enter to focus, Esc to close. - 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.
- Svelte 5 + TypeScript + Vite + pnpm.
- xterm.js +
@xterm/addon-fitfor terminal rendering. portable-pty(Rust) spawningwsl.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 # svelte-check
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):PtyManagerholdingMutex<HashMap<PaneId, PaneHandle>>ofportable-ptychildren. Each spawned pane gets a background reader thread that emitspane://{id}/dataevents (base64 byte chunks). Counterparts:write_to_pane/resize_pane/kill_pane. Workspace persistence viasave_workspace/load_workspacewrites toapp.path().app_config_dir()with atomic tmp + rename. - Layout (
src/lib/layout/tree.ts): binary tree of splits.HSplit | VSplitinternal nodes with a ratio,Leafat the bottom — same model as i3 / tmux / Zellij. Adaptive resize falls out of mutating one parent ratio. Pure helpers (splitLeaf,closeLeaf,changeDistro, etc.) live intree.ts; the rendering chain (Pane.svelte→SplitNode.svelte/LeafPane.svelte) is thin. - Orchestration — broadcast routing, idle detection, palette, active-pane focus all live in
App.svelteand reach the panes via aPaneOpsbundle (src/lib/layout/ops.ts) drilled through the Pane chain.
License
No formal license yet. Public for inspection and personal use; if you want to redistribute, open an issue and ask.