Three xterm.js features, implemented together because they share the
XtermPane mount + the single attachCustomKeyEventHandler:
- Unicode 11: load @xterm/addon-unicode11, set activeVersion='11' after
the canvas renderer so emoji/CJK/box-drawing widths stop drifting.
- Find in scrollback: @xterm/addon-search + a new per-pane SearchBar
overlay (Ctrl+Shift+F to open, Enter/Shift+Enter next/prev, regex +
case toggles, Esc to close & refocus). Overlay is an absolutely-
positioned sibling in a position:relative wrapper so fit() is unaffected.
- Pane navigation: Ctrl+Alt+Arrow / Ctrl+Alt+HJKL (spatial neighbour via
findNeighborInDirection) and Alt+1..9 (Nth leaf in walkLeaves order).
XtermPane emits a NavigateIntent; App resolves the target leaf and sets
it active, reusing the existing isActive->focusTrigger refocus chain.
All chords live in one attachCustomKeyEventHandler (xterm replaces the
handler on each call). Shortcuts added to shortcuts.ts (SoT for README +
Help), including the Alt+digit shell-conflict caveat. tsc clean apart
from the three not-yet-installed addon modules.
Needs pnpm install on the Windows host + runtime verification.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The DOM renderer draws the cursor as a separate layered element; under
the Claude TUI's rapid cursor hide/show plus cursorBlink it leaves a
stale white block frozen where the cursor used to be. Load
@xterm/addon-canvas (composites the cursor into the text surface) with a
try/catch that falls back to the DOM renderer on init failure. Canvas
over WebGL because tiletopia runs many panes and WebView2 caps live
WebGL contexts (~16).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`tsc --noEmit` and `tsc -b` apply slightly different narrowing rules
on project-reference codebases — the prior check missed the spawn_pane
hostId narrowing bug (commit e1ceaab) that pnpm build immediately
flagged. Both tsconfig.app.json and tsconfig.node.json already set
`noEmit: true`, so `tsc -b` does no emission — the only difference
is build-mode dependency tracking + slightly stricter type checks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New scripts/build-mcpb.mjs packs a Claude Desktop extension bundle
(scripts/mcpb-wrapper.mjs + manifest + icon) into dist-mcpb/tiletopia.mcpb.
The wrapper reads the bearer token from %APPDATA% at launch and execs
`npx -y mcp-remote`, so no secrets are baked in and Regenerate keeps
working transparently. Run via `pnpm run build:mcpb`.
McpPanel gets a "Download .mcpb" button linking to the releases page; the
help-overlay tip and README MCP section both lead with the bundle install
path and keep the .mcp.json shim recipe as the Claude Code fallback.
Session-log entry in memory.md covers the design choices, especially why
the wrapper-script approach beat the alternatives (user_config prompt
would defeat one-click; baked-in token would be wrong for everyone else).
The shortcuts table in README was hand-maintained and kept drifting from
src/lib/shortcuts.ts (the data the in-app help overlay reads). Replace the
table with a marker block (<!-- SHORTCUTS:START --> ... <!-- SHORTCUTS:END -->)
populated by scripts/gen-readme-shortcuts.mjs. Includes TIPS too, not just
shortcuts. Script is plain Node + fs (no tsx/esbuild dep); reads shortcuts.ts
as text, strips TS type syntax, dynamic-imports the resulting .mjs.
Adds `pnpm gen:readme` script and a `--check` mode that exits 1 on drift
(for future CI wiring). Idempotent.
navigator.clipboard.readText() triggers WebView2's "Allow clipboard access?"
permission prompt on every paste. The plugin goes through IPC + the OS
clipboard directly, so the prompt never fires.
Wired the Rust plugin, granted clipboard-manager:allow-{read,write}-text in
the capabilities manifest, swapped XtermPane's copy/paste handler to use
the plugin's readText/writeText.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Changes since 0.2.1:
- Keyboard shortcuts: Ctrl+K palette + Ctrl+Shift+E/O/W for
split-right / split-down / close, Ctrl+Shift+B per-pane and
Ctrl+Shift+Alt+B global broadcast, Ctrl+Shift+Arrow for spatial
pane navigation
- New panes default to WSL home (~) instead of inherited Windows cwd
- Resize artifacts: rAF-throttle gutter drag, debounce PTY resize,
skip resizePane when cols/rows haven't actually changed (kills the
idle-flap loop that surfaced with many panes)
- Minimum pane size (180px) enforced on both split and gutter drag
- 2px gap around each leaf so per-pane borders read as distinct
rectangles instead of a continuous grid
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Changes since 0.2.0:
- Fix broadcast no-op (useEffect deps captured stale orch ref, so paneIds
got silently unregistered on every click → broadcastFrom found no peers)
- Flat-list layout architecture: render leaves as siblings keyed by id,
position via absolute boxes. PTYs survive any tree reshape.
- Drag a pane's toolbar onto another pane to swap them
- Idle reporting moved out of toast spam into a "N idle" titlebar badge
+ red pane border + red "idle" status text
- Themed terminal scrollbars
- Global 📡 broadcast toggle in the titlebar
- Presets preserve existing panes' shells (only kill what overflows the
preset's slot count, with a confirm dialog)
- React 18 frontend (Svelte version retired to svelte-archive branch)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.1.0 was the Svelte version, preserved on branch svelte-archive.
v0.2.0 marks the React rewrite: same features, dramatically more
reliable interactions (no more fighting prop reactivity through the
recursive Pane chain), plus scrollbar theming and the global
broadcast toggle in the titlebar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After hours of fighting Svelte 5's prop-reactivity through the
recursive Pane → SplitNode → LeafPane chain (props captured at
mount, never updated; context+getter pattern crashed; DOM-direct
workarounds created zombie-split click-intercept bugs), we
checkpointed the Svelte version (branch svelte-archive at e9015b2,
tarball at D:\archives\tiletopia-svelte-2026-05-22.tar.gz) and
rewrote the frontend in React.
Kept verbatim:
- All of src-tauri/ (Rust backend, Tauri config, icons)
- scripts/ (make-icon.py, release.sh)
- README.md, CLAUDE.md, memory.md
- src/lib/layout/tree.ts (pure TS — 43 tests still pass)
- src/ipc.ts (Tauri command wrappers)
Rewrote in React:
- src/App.tsx (top-level state via useState, OrchestrationProvider
for descendants via React.Context)
- src/lib/layout/orchestration.tsx (React Context API for shared
state — known-reliable reactivity, no Svelte 5 wall)
- src/lib/layout/Pane.tsx (recursive dispatcher)
- src/lib/layout/SplitNode.tsx (draggable gutter, local ratio state)
- src/lib/layout/LeafPane.tsx (toolbar + XtermPane)
- src/components/XtermPane.tsx (xterm.js wrapper, refs for callbacks)
- src/components/Notifications.tsx, Palette.tsx
Build: Vite + @vitejs/plugin-react. TypeScript strict. Same Tauri 2
config. Verified: pnpm check (clean), pnpm test (43/43 pass).
Not yet verified: pnpm tauri dev — that requires the Windows host.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 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>
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>