tiletopia/README.md
megaproxy a4cd82440b Add keyboard shortcuts (Ctrl+Shift chord style)
| Ctrl+K           | palette                                |
| Ctrl+Shift+E     | split active pane right                |
| Ctrl+Shift+O     | split active pane down                 |
| Ctrl+Shift+W     | close active pane                      |
| Ctrl+Shift+B     | toggle broadcast on active             |
| Ctrl+Shift+Alt+B | toggle broadcast on ALL panes          |
| Ctrl+Shift+Arrow | focus neighbour pane in that direction |

The handler attaches at capture phase on window so it wins against
xterm.js. It bails when a non-terminal <input>/<textarea> is focused
so label edits and the palette input keep working normally.

Spatial neighbour-finding lives in tree.ts as findNeighborInDirection
— picks the leaf whose centre is most aligned in the perpendicular
axis, breaking ties by primary-axis distance.

Tooltips on toolbar/titlebar buttons now mention their shortcuts;
README has a key-binding table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 21:32:51 +01:00

90 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](https://git.rdx4.com/megaproxy/tiletopia/releases).
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](https://developer.microsoft.com/microsoft-edge/webview2/) (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 pane** — `Ctrl+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](https://v2.tauri.app/start/prerequisites/#windows): MSVC ("C++ build tools" workload), Rust, Node 20+, pnpm (`corepack use pnpm@latest`), at least one WSL distro.
```powershell
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
```sh
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.tsx``SplitNode.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.