Initial scaffold from M1 spike (tiletopia)

Tauri 2 + Svelte 5 + xterm.js + portable-pty. Single full-window
WSL terminal pane with clickable distro picker. M1 verified manually
on Windows: window opens, xterm.js renders, claude TUI works,
resize reflows cleanly.

Graduated from ~/claude/ideas/wsl-mux/ per the approved plan at
~/.claude/plans/imperative-coalescing-feigenbaum.md. See memory.md
for decisions, open TODOs, and the M2-M5 roadmap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-22 12:31:29 +01:00
commit b352f8f049
36 changed files with 11534 additions and 0 deletions

75
README.md Normal file
View file

@ -0,0 +1,75 @@
# 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](https://developer.microsoft.com/microsoft-edge/webview2/) (preinstalled on Win11).
- [MSVC toolchain](https://v2.tauri.app/start/prerequisites/#windows) (VS Build Tools, "C++ build tools" workload).
- [Rust](https://rustup.rs/) 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:
```powershell
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.onData``write_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.