M5 ship infrastructure: icon, version, release script, README

- scripts/make-icon.py: generates a 1024x1024 source.png — dark
  rounded square + 2x2 tile grid with one active-blue tile and one
  broadcast-orange tile (matches the in-app accent colors).
  Regenerated all desktop icon sizes via 'pnpm tauri icon';
  pruned iOS/Android/UWP outputs.
- Version bump 0.0.1 -> 0.1.0 across package.json, Cargo.toml,
  tauri.conf.json. First real release.
- scripts/release.sh: takes vX.Y.Z, sanity-checks (clean tree,
  on main, in sync, tag matches package.json, installer exists,
  tag not already present), tags + pushes, uploads NSIS .exe to
  Forgejo via tea releases create --asset.
- README rewritten: Install section pointing at Forgejo releases,
  Using-it cheatsheet for all M2-M4 features (splits, broadcast,
  palette, etc.), Develop/Test/Release triplet for the WSL<->Windows
  workflow, icon regen instructions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-22 13:38:29 +01:00
parent b1412287be
commit dd1cf282e6
14 changed files with 270 additions and 59 deletions

127
README.md
View file

@ -1,75 +1,94 @@
# 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.
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.
Status: **early — single-pane M1 works**. Tiling layout (M2), workspace persistence (M3), and cross-pane orchestration (M4) are the next milestones. See `memory.md`.
- 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
## Stack
## Install
- **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.
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.
## Run
### Requirements
This project targets Windows. Dev requires:
- **Windows 10/11** with [WebView2 Runtime](https://developer.microsoft.com/microsoft-edge/webview2/) (preinstalled on Windows 11; downloadable on Windows 10).
- At least one WSL distro registered (`wsl -l -v` lists them).
- 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.
## Using it
**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.
- **Split panes**`⇥` in the pane toolbar splits right, `⇣` splits down. 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.
- **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`.
- **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.
- **Idle toasts** — top-right notification appears when a pane goes quiet for 5s. Useful for "I started a long task; tell me when it's done."
From a Windows shell:
Layout + per-pane settings auto-save to `%APPDATA%\com.megaproxy.tiletopia\workspace.json` (debounced 500 ms).
## Develop
Develop the Rust + frontend code in WSL; **build and run on the Windows host** (Tauri targets Windows, Rust toolchain is Windows-side).
**Source location matters.** The project must live on a Windows-native drive (`D:\dev\tiletopia\`). Don't run pnpm against the `\\wsl.localhost\...` UNC path — pnpm 11.x crashes inside `isDriveExFat` (the actual error gets swallowed by the crashing error-hint formatter). The WSL-side symlink at `~/claude/projects/tiletopia` is for editing, not building.
### Prereqs (Windows host)
- Windows 10/11 + WebView2 Runtime
- [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`)
### Build + iterate
```powershell
cd D:\dev\tiletopia
pnpm install
pnpm tauri dev # iterate
pnpm tauri dev # iterate — auto-reloads frontend on save, recompiles Rust on src-tauri changes
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.
### Test (WSL)
## 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
```sh
pnpm test # vitest, ~43 cases on the layout tree
pnpm test:watch # rerun on file change
pnpm check # svelte-check
```
## Known gotchas (today)
The test suite covers the pure tree helpers in `src/lib/layout/tree.ts`. UI behavior, broadcast routing, and Tauri integration are still manually tested.
- **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.
### Release
1. Bump version in `package.json`, `src-tauri/Cargo.toml`, `src-tauri/tauri.conf.json`. Commit + push.
2. On Windows: `pnpm tauri build` (produces `src-tauri\target\release\bundle\nsis\tiletopia_<ver>_x64-setup.exe`).
3. From WSL: `scripts/release.sh v0.1.0` — sanity-checks, tags `v0.1.0`, pushes the tag, and uploads the installer to Forgejo as a release via `tea`.
### Regenerate the icon
```sh
python3 scripts/make-icon.py
pnpm tauri icon src-tauri/icons/source.png
# Tauri's icon command writes iOS + Android + UWP outputs too; rm them.
rm -rf src-tauri/icons/{ios,android,Square*.png,StoreLogo.png,64x64.png,icon.png}
```
See `src-tauri/icons/README.md` for details.
## Architecture (quick tour)
- **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 to the frontend (base64 chunks). Counterparts: `write_to_pane` / `resize_pane` / `kill_pane`. Workspace persistence via `save_workspace` / `load_workspace` writing 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`, etc.) live in `tree.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.svelte` and are bundled into a `PaneOps` interface (`src/lib/layout/ops.ts`) drilled through the Pane chain.
- **Stack precedent** — mirrors `~/claude/projects/claude-usage-widget/`: same Tauri 2 + Svelte 5 + Vite + pnpm + NSIS Forgejo-release toolchain.
## License
Personal project; no formal license yet.