Imported from SlasshyOverhere/guimux (
AGENTS.md). Install upstream withnpx skills add SlasshyOverhere/guimux. Copyright stays with the author.
AGENTS.md
Guidance for AI coding agents working in this repository. Mirrors CLAUDE.md — keep both in sync when either changes.
Guimux: lean Tauri 2 + React worktree/terminal workbench. Worktree = git dir (path + branch). Terminal = generic PTY with cwd in a worktree. Splits = N independent PTYs per worktree.
Commands
npm run dev— Vite frontend only (port 8471,strictPort, ignoressrc-tauri).npm run tauri dev— full app (frontend athttp://localhost:8471persrc-tauri/tauri.conf.json).npm run typecheck/npm run build—tsc --noEmit/tsc --noEmit && vite build. No lint script.npm test— frontend unit tests:node --testover the dependency-free modules (updaterPolicy,menuPos,sidebar/statusLetter,sidebar/removeGuard), so they run without a bundler.cd src-tauri && cargo test— Rust tests (worktree_lifecycle,worktree_remove_guards_uncommitted_work,status_and_diff,encoded_command_matches_powershell,alias_stub_rejected, plus pty replay/epoch and fs cap tests). Single test:cd src-tauri && cargo test <name>..github/workflows/ci.ymlruns typecheck +npm test+npm run build, andcargo teston windows-latest and ubuntu-22.04, on every push to main and every PR.release.ymlonly handles tagged builds.npm run tauri build— production bundle (icons +assets/conpty/*resources).
Architecture
- Backend
src-tauri/src/:lib.rsregisters all IPC + plugins (store,notification,dialog;mcp-bridgedebug-only) and managesPtyManagerstate.pty.rs= generic PTY viaportable-pty(spawn/write/resize/kill/restart; eventspty:output-{id},pty:exit-{id}).worktree.rs=git worktreeCLI list/create (guimux/<ts>-<rand>branch,<name>-wtsibling dir)/remove (retried 5x for Windows locks)/merge into main|master.git.rs=project_detect(rev-parse toplevel -> git root),git_init,git_status(-zporcelain, rename-aware),git_diff(1MB cap).fs.rs=fs_tree(depth clamped 1-6, skips.git node_modules target dist .next __pycache__, dotfiles except.github)/read/write (1MB read cap, UTF-8 only).conpty_dll.rsstages bundled ConPTY on Windows. - Frontend
src/:App.tsx(topbar, worktree loader, keybindings, welcome/status),store.ts(zustand: projects, worktrees, binary pane-split tree per worktree inlayouts, broadcast, editor/palette/settings),persist.ts(tauriLazyStore guimux.json+ sync localStorage mirror, debounced 150ms),project.ts(detectToProjectnormalizes to git root).terminal/(SplitView+TerminalPane: xterm + fit/unicode11/serialize, scrollback 10k persisted to localStorage capped at 24 panes),sidebar/(WorktreeSidebarcomposesWorktreeRow/RowMenu/DiscoveredBlock/CreateWorktreeForm; letters instatusLetter.ts, remove guard inremoveGuard.ts, coalesced poll inuseWorktreeStatuses.ts),uiPrefs.ts(guarded chrome-state storage: widths, pins, collapses),menuPos.ts(zoom-aware menu placement),singleFlight.ts(one listing at a time),explorer/ExplorerPane(bundled Monaco, one model per file, explicit Ctrl+S save;monaco.tswires the local loader + workers and is imported lazily on first file open),palette/Palette(Ctrl+K),settings/SettingsPanel,chrome/WindowControls. - IPC: frontend never shells git; all git/pty/fs goes through
invokecommands inlib.rs. Broadcast =pty_writeto N visible panes, no prompt abstraction.
Gotchas
decorations: false— custom drag viastartDraggingon mousemove-after-press only; never drag on mousedown (swallows clicks on Windows). Dropdowns are divs withrole=button, closed viamousedowncapture + Escape.- No
StrictModeinmain.tsx(xterm 5.5.0 dispose race logs noise in dev double-mount). - PTY exit authority = shell process liveness (
try_waitpoll 120ms), never pipe EOF (TUIs deliver early EOF).pty_writepasses bytes through unmodified (\rstays\r). - Windows shell chain:
GUIMUX_SHELLoverride -> realpwsh.exe(rejectsWindowsApps0-byte aliases) -> inboxpowershell.exe -NoLogo -NoExit -EncodedCommand <utf16le-base64 bootstrap>(chcp 65001 + Set-Location) ->cmd /K chcp 65001. - Closing last pane opens a fresh shell (never null layout);
worktree_listerrors propagate (neverOk([])) so git failures (e.g. dubious ownership) show in banner, not "Starting terminal…" hang. Plain (non-git) projects get syntheticplain:<id>worktree so a shell always mounts. - PTY attach protocol: backend buffers per-PTY output (256KB ring) until frontend sends
pty_attach(id), which marks live + replays buffered bytes. Frontend registerspty:output-{id}listeners FIRST, then callspty_attach.pty_attacherrors on unknown ids -> pane spawns fresh, never blank. - Geometry: never spawn/resize with cols/rows < 1 (backend clamps; frontend skips resize until FitAddon reports cols>=2, rows>=1). Split children keyed by pane id so splits never remount surviving panes.
- Restart reuses the numeric id with a bumped epoch; stale exit-watchers/output pumps stay silent.
- WebGL (
@xterm/addon-webgl) is the primary renderer;onContextLossdisposes to canvas/DOM fallback, re-attempted on next visible mount. Hidden panes dispose WebGL, PTY stays alive. - Debug flags:
GUIMUX_PTY_DEBUG=1(backend spawn timestamps on stderr);localStorage guimux-stress=1+ reload runs the dev-only split/write/close stress loop (console[gm-stress];__gmStressStop()stops it). - A save made in the app's editor is announced to the dev server (
announceWrite+ theguimux-ignore-app-writesplugin invite.config.ts), so it does not hot-reload the app that made it. An edit from another editor still does. - App zoom via CSS
zoomon<html>(0.5-2); terminals refit via ResizeObserver. Ctrl+D splits h (outside inputs/terminal), Ctrl+K palette skipped when terminal focused (kill-line).
Custom Instructions
Comments
- Comment rarely — only where the code genuinely can't explain itself.
- One short tldr line, straight to the point. No multi-line essays, no restating the code, no commented-out code.
Commits
- Commit after each fix or feature the moment it's done and verified. Never batch unrelated work: 5 issues fixed = 5 commits, so history reads as a step-by-step trail.
- Conventional Commits, lowercase, short and imperative:
fix(pty): drop stale exit watcher,feat(sidebar): prune worktrees,refactor(store): flatten layout helpers. - Subject <= ~60 chars, no trailing period. Body optional and only for the why — 1-2 short lines max.
- No git attributions, ever. No
Co-Authored-By, no "Generated with …", no emoji footers, no tool signatures. Commit as the repo's normal author. - Commit only. Never
git pushor any remote-affecting command without explicit user approval.
Best Practices (this app)
- All OS work goes through Rust. The frontend never shells out to git/fs/pty: add a
#[tauri::command]in the matching module, register it inlib.rs'sinvoke_handler, call it viainvoke<T>("name", { camelCaseArgs }). - Keep the wire contract in sync. Rust serde structs and
src/types.tsmirror each other (snake_case fields) — change both in the same commit. - Propagate errors, don't swallow them. Backend errors surface in the banner; returning
Ok([])/empty on failure hides real problems (e.g. git dubious ownership) and strands the UI on "Starting terminal…". - PTY rules: register
pty:output-{id}listeners beforepty_attach; never spawn/resize below 1 col / 1 row; exit authority is process liveness, not pipe EOF;pty_writebytes pass through unmodified. - Pane identity: key split children by pane id; never remount a surviving pane; never leave a layout null (closing the last pane opens a fresh shell).
- Persist through
store.ts(zustand) +persist.ts(LazyStore+ debounced mirror) — no ad-hoc storage. - Windows first. File locks (retried removes), path separators, the shell fallback chain and ConPTY staging all matter; prefer
portable-ptyAPIs over spawning shells. - Stay lean. Match the terse existing style; don't add a dependency, abstraction, or config knob for a one-off. No
StrictMode(xterm dispose race). There is no lint tooling — don't assume it. - No runtime CDNs. The packaged CSP is
script-src 'self', so anything fetched at runtime (Monaco's AMD bundle is the precedent) breaks intauri buildwhile working fine under Vite. Bundle it and configureloader.config({ monaco })instead of letting a loader reach out. - Verify before committing:
npm run typecheckafter frontend edits,cd src-tauri && cargo testafter Rust edits,npm run buildwhen in doubt.