Skip to content

feat(terminal): v3.1.0 — WebGL, tab types, agent launch, New AI task + PTY fixes#84

Draft
devlint wants to merge 65 commits into
mainfrom
feat/v3.1.0-terminal-pty
Draft

feat(terminal): v3.1.0 — WebGL, tab types, agent launch, New AI task + PTY fixes#84
devlint wants to merge 65 commits into
mainfrom
feat/v3.1.0-terminal-pty

Conversation

@devlint

@devlint devlint commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Summary

Four terminal features for v3.1.0, plus a round of bug fixes and security hardening.

1. WebGL renderer + inline search + clickable links

  • Switched xterm.js from Canvas renderer to WebGL for smoother rendering
  • Inline search via @xterm/addon-search — Ctrl+F or toolbar, prev/next navigation
  • Clickable links via @xterm/addon-web-links

2. Tab types + unread indicator

  • Tabs now carry a type: shell, claude, codex — each with a distinct icon
  • Unread dot appears on tabs that received new output while not focused

3. Fix agent launch — real PTY tab

  • "Launch Claude Code" and "Launch Codex" now open a real PTY shell tab running the agent
  • Previously opened a non-functional stub tab

4. "New AI task" — scratch worktree + Claude Code tab

  • New button creates a fresh git worktree and opens a Claude Code terminal tab in one click
  • Dev-server route POST /api/scratch-worktree-create uses realpath to canonicalize the cwd

Bug fixes & hardening

  • UTF-8 boundary carry buffer (terminal.rs): multibyte chars straddling 8 KB PTY read chunks were corrupted by from_utf8_lossy; fixed with a valid_up_to() carry-buffer loop
  • Async component race (App.vue): TerminalPanel is lazy-loaded via defineAsyncComponent; a single nextTick() wasn't enough — fixed with a watch(terminalPanelRef) promise so the PTY starts only when the element is mounted
  • CORS on SSE endpoint (dev-server): browser (port 1420) → dev server (port 3001) cross-origin request on EventSource was blocked; added Access-Control-Allow-Origin to /api/terminal-open SSE headers
  • spawn-helper execute bit (package.json root postinstall): pnpm doesn't preserve +x on node-pty's spawn-helper binary, causing posix_spawnp failures; fixed with find node_modules/.pnpm -name 'spawn-helper' -exec chmod +x {} +
  • Security (from final review): safe_repo_path() on all FS ops, shell executable whitelist, PTY orphan/zombie prevention on tab close, EventSource closed on shell EOF to prevent browser auto-reconnect spawning untracked shells, keystroke input buffer bounded
  • i18n: search button tooltips (terminal.searchPrev / terminal.searchNext) added to all 5 locales (en, fr, es, pt-BR, zh-CN)
  • Dev:web PTY: real node-pty shell via SSE replaces the previous stub — terminal is now fully functional in pnpm dev:web

Test plan

  • pnpm test — 320/320 passing
  • cargo build clean (no warnings)
  • Open terminal in dev:web (pnpm dev:web) — shell tab works, can type commands
  • Open terminal in Tauri app — shell tab works
  • Ctrl+F opens search bar; prev/next cycle through matches
  • Links in terminal output are clickable
  • Open Claude Code tab — launches agent
  • "New AI task" creates worktree + opens Claude Code tab
  • Unread dot appears on unfocused tab receiving output; clears on focus
  • Pasting multi-byte UTF-8 (e.g. echo "héllo 日本語") renders correctly

Vue du Terminal dans GitWand
image

Zoom sur Codex
image

@devlint devlint marked this pull request as draft June 25, 2026 09:05
@devlint devlint self-assigned this Jun 25, 2026
@devlint devlint changed the title Enhance terminal functionality with PTY improvements and UI updates feat(terminal): v3.1.0 — WebGL, tab types, agent launch, New AI task + PTY fixes Jun 25, 2026
@t1gu1

t1gu1 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

C'est nice de voir un début avec le terminal!
Je vais attendre que ce soit livré, mais j'ai bien hate de tester ça!

Il y a déjà quelques idées qui commecent a bouillonner dans ma tête. 🤯

@devlint

devlint commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

@t1gu1 Tu peux déjà récup la branch et tester, voire itérer 🚀 ;-)

@t1gu1

t1gu1 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Je vais avoir un temps pour regarder ça dans 5h/6h (Vers l'heure du souper, au Canada)

@t1gu1

t1gu1 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

@devlint
Je ne pense pas pouvoir itérer sur un branche d'un maintainer ou sur une branche d'un projet fork que je n'ai pas moi même créé.

À moins que tu me dit que ce soit un skill issue haha
Dans ce cas là faudrait que tu m'explique.

Enfin, je peux quand même cloner ta brancher et l'essayer!
Je ne peux juste pas vraiment y contribuer directement, à moins de créer une nouvelle PR de mon côté.

De mon côté, il faut que j'autorise les autres à pouvoir modifier ma branche/PR.
Aucune idée si l'inverse est possible?

image

@devlint

devlint commented Jun 25, 2026

Copy link
Copy Markdown
Owner Author

@t1gu1 Je t'ai ajouté en tant que collaborateur, tu devrais pouvoir contribuer directement sur le repo 👍

Laurent Guitton added 21 commits June 25, 2026 21:45
…ocking

- Wrap PtyHandle.writer in Arc<Mutex> so terminal_write clones the Arc,
  drops the registry guard, then performs write_all/flush without holding
  the global sessions lock (fixes stall when kernel PTY buffer is full).
- Add lock_sessions() helper that recovers from Mutex poisoning via
  unwrap_or_else(|e| e.into_inner()) instead of panicking; applied to all
  5 lock sites including the new writer Arc lock.
- enriched_path() now returns None when no extra paths were prepended.
- Replace #[allow(unreachable_code)] with #[cfg(not(target_os = "macos"))].
…, alive flag + coalesce test

- openTab: skip onChunk delivery while sessionId is still -1 (early chunk misroute fix)
- disposeRepo: clear pending debounce timer for repo before closing sessions
- closeTab: set tab.alive = false on spliced tab so callers see it as closed
- tests: add debounce coalescing test (2 calls 400ms apart → handler fires exactly once)
…rag listener cleanup

- Watch source changed from `tabs` (computed returning same array instance)
  to a derived string `id:sessionId|...` so the watch fires on every push/splice
- onBeforeUnmount now removes mousemove/mouseup drag listeners to prevent leaks
Add pure `resolveTerminalShortcut(e, focused)` to useTerminalSessions.ts
and wire it in App.vue onKeyDown before the repo-tab handlers, so that
Cmd+T/W/1-9 act on terminal tabs when the terminal panel has focus.
…tate

cursor/windsurf still launch externally; claude/other emit launch-agent
to App.vue which opens a PTY tab and types the tool command into it.
setTimeout fake-active removed; replaced with loadSessions() re-poll.
Add two user settings for the integrated PTY terminal:
- terminalFontSize (default 13): replaces the hardcoded fontSize in TerminalPanel.vue
- terminalShell (default ""): passed as shell override to terminalOpen; empty = auto

Both fields added to AppSettings interface + defaults in useSettings.ts AND
to the local Settings interface + defaults in SettingsPanel.vue (AGENTS.md sync rule).
UI controls added in the Editor tab (range slider for font size, text input for shell).

Shell is threaded: settings → openTerminalTab (App.vue) → openTab opts (useTerminalSessions) → terminalOpen shell param.

i18n keys added to all 5 locale files (en, fr, es, pt-BR, zh-CN):
settings.terminalFontSize, settings.terminalShell, settings.terminalShellHint
…t, complete i18n parity

- I1: add closeRepoTab() wrapper in App.vue that calls disposeRepo before
  removing a repo tab; wire all 3 close-tab call sites (⌘W, menu closeWindow,
  @close-tab template binding)
- I2: route PTY output by stable tab.id instead of sessionId; remove the
  tab.sessionId>=0 guard that dropped early chunks; add pendingChunks buffer
  in TerminalPanel so output arriving before xterm mounts is flushed on mountTab
- M6: add settings.terminalFontSize/terminalShell/terminalShellHint to pt-BR
  and zh-CN locale files (keys were missing from committed versions)
…click

Replace the Agents button in AppHeader with a Terminal button that emits
openTerminal event. Wire AppHeader to call openTerminalTab() when clicked,
which opens a shell PTY session in the active repository. Consumes terminal
i18n keys (headerLabel, headerTooltip) added in Task 1.

disabled
…SidebarListeners

The "Agents" header button was renamed to "Terminal". Two leftover references removed:
- RepoSidebar.vue defineEmits: openAgents event (never emitted)
- App.vue repoSidebarListeners: openAgents handler (dead code)

disabled
…po_path, shell validation, PTY orphan/zombie, EventSource leak, keystroke buffer)
@t1gu1 t1gu1 force-pushed the feat/v3.1.0-terminal-pty branch from a3015a6 to 167cadb Compare June 26, 2026 01:46
t1gu1 added 7 commits June 25, 2026 23:38
Header terminal button now toggles the panel instead of always spawning
a new tab: hide if open, spawn first tab only when none exist. Restyle
tab strip with rounded top corners and opacity states for active/hover.

🪄 Commit via GitWand
Bump new-tab font size and add black padded background to the
terminal host for better visual separation.

🪄 Commit via GitWand
Only pass `-l` to shells that accept it — nushell/powershell reject it
and the PTY dies on boot. Also force a SIGWINCH after first child output
so TUIs that latch 80×24 at boot snap to the real size, and guard
mountTab against concurrent runs spawning duplicate terminals.

🪄 Commit via GitWand
Wrap TerminalPanel in KeepAlive so toggling the dock deactivates rather
than unmounts the xterm instances, preserving buffers and PTY view. On
re-activation, refit each tab once its host is sized and kick a held
two-step resize so running TUI children (claude) get a real SIGWINCH and
redraw instead of staying blank until a manual resize.

🪄 Commit via GitWand
Replace the header terminal pill with a standalone tile on the AppDock
that rides along with the dock and reflects the panel's open state, freeing
header space and grouping navigation in one place.

🪄 Commit via GitWand
Fullscreen fills the app-body area while keeping the project list and
header visible; state persists via localStorage. Adds enter/exit
fullscreen labels across all locales.

🪄 Commit via GitWand
Let users resize the terminal panel from the left edge and both top
corners, not just the right edge — corners adjust width and height
together while pinning the bottom edge.

🪄 Commit via GitWand
@devlint

devlint commented Jun 26, 2026

Copy link
Copy Markdown
Owner Author

🤩
image

t1gu1 added 4 commits June 26, 2026 03:03
Render a dark placeholder surface prompting the user to open a tab,
instead of leaving a blank area when all tabs are closed.

🪄 Commit via GitWand
PTY spawn fails with a cryptic "spawn shell failed" when the Claude
Code or Codex CLI isn't installed or on PATH. Surface a clear,
localized message instead.

🪄 Commit via GitWand
Drive terminal layout (floating/fullscreen/bottom) from settings instead
of a localStorage fullscreen flag, with a dock context menu to switch
modes and an opt-out for hiding the terminal on view navigation.

🪄 Commit via GitWand
Track a persistent top offset so the panel floats anywhere instead of
being pinned to the bottom edge. Add tab-bar drag-to-move on both axes,
all four corner resize handles, and a bottom-edge resize handle.

🪄 Commit via GitWand
@t1gu1 t1gu1 force-pushed the feat/v3.1.0-terminal-pty branch from ea718f6 to cb36313 Compare June 26, 2026 08:01
Laurent Guitton and others added 6 commits June 26, 2026 04:01
Security:
- Strip CLAUDE_AUTH_OVERRIDE_ENV vars from PTY env before spawn (terminal.rs)
- Add first-class `agent` param so Claude/Codex never route through shell path,
  preserving the strip_claude_auth_env / claudeSpawnEnv infrastructure
- Replace hand-rolled CWD canonicalization with safe_repo_path(cwd, ".")
  to honor AGENTS.md's single-audited-guard rule

Concurrency / correctness (Rust):
- Wrap master PTY in Arc<Mutex> so terminal_resize releases the global sessions
  lock before the resize ioctl — prevents blocking terminal_write across all
  sessions during a resize
- Kill orphaned child process when try_clone_reader/take_writer fails post-spawn
- Extract macos_enriched_path() shared helper; PTY now gets the same PATH
  prefixes as hidden_cmd (adds /usr/local/sbin and /opt/local/bin)

Correctness (TypeScript / Vue):
- devTerminalOpen: reject Promise on initial connection failure (was hanging)
- openTerminalTab: add 10s timeout on terminalPanelRef watch (was hanging on
  async component load failure)
- notifyOutput: cap debounce at 4s so continuous output can't starve git refresh
- closeTab: document Fix-3 coverage of the sessionId=-1 / disposeRepo race
- ResizeObserver: guard resize(-1) before session opens
- refitWhenSized: re-trigger on sessionId -1→valid transition so slow spawns
  don't leave TUI at 80×24
- Keyboard tab-switch (Cmd+1-9) now calls markRead(), matching mouse-click
- Capture repoPath at tab-open time so late output refreshes the correct repo
- AgentSessionsPanel: forward session.tool instead of hardcoding "claude"
- TerminalPanel: replace 3-path dropdown listener with single watchEffect
- TerminalPanel: extract getOrCreateBuf() helper (pendingChunks + pendingInput)
- dev-server: validate PTY id + string payload in terminal-write

Other:
- Add impeccable ignore for src/App.vue (semantic warning indicators)
- Set worktree.bgIsolation=none in .claude/settings.json
- Regression test for notifyOutput max-wait cap
Snapshot container/panel sizes at drag start instead of reading
offset* on every mousemove, which forced a layout reflow per event.
Also close PTY sessions concurrently via Promise.all on cleanup.

🪄 Commit via GitWand
Wire the clipboard-manager plugin so the terminal can copy, paste,
select-all, clear and search via a right-click menu. Add opt-in
copy-on-select and paste-on-right-click settings, with localized strings
across all locales.

🪄 Commit via GitWand
Switch terminal default from floating to bottom and disable hide-on-nav
so the dock is visible out of the box. Surface layout mode and
hide-on-nav controls in the terminal settings tab.

🪄 Commit via GitWand
Provide a discoverable affordance to open the terminal search bar without
relying on the keyboard shortcut or context menu.

🪄 Commit via GitWand
@t1gu1

t1gu1 commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Hey hey @devlint!

J'ai fait pas mal d'ajustement et d'itération de mon côté.
Je te laisse y jeter un oeil et gérer la suite!

@t1gu1

t1gu1 commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

@devlint
I found that closing an AI task 'worktree' doesn't actually remove it.
We might want to adjust how this behaves.

Here is what I propose:

  • Automatic visibility: Any active worktree (at least the Gitwand ones created by AI tasks) should automatically appear as a project in the project bar.
  • Auto-deletion on close: Closing an AI task/project that is linked to a worktree should delete the worktree simultaneously.
  • User confirmation: We can add a confirmation modal to warn the user before deletion.

WDYT?


EDIT: also, i just found that there is an error when i try to delete them from the GitTree:
image

…ment

Each project tab gets a caret submenu to switch its checkout in place
across main and every worktree. AI-task scratch worktrees can be merged
back or deleted from the menu, and closing a project now asks to confirm.

🪄 Commit via GitWand
@t1gu1

t1gu1 commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

How it looks

image image

t1gu1 added 2 commits June 27, 2026 17:11
Right-clicking a branch checked out in a worktree now offers "Delete
worktree" (and "Merge & delete" for scratch worktrees) instead of a
plain branch delete that git rejects. Reuses AiTaskCloseModal via a
new mode prop and reloads the branch list after removal.

🪄 Commit via GitWand
A branch checked out in a worktree can't be plain-checked-out, so switch
to its worktree instead, and return to the main worktree before checking
out non-worktree branches. Probe worktrees only when relevant.

🪄 Commit via GitWand
@t1gu1

t1gu1 commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

I made the GitTree works with worktree.
I'm not sure if it is perfect, but it is not even near as bad as it was. (Now it is usable)

Also, i add a thing where you can now name your AI Task.
So it will made it easier to know what is what.

image image

t1gu1 added 2 commits June 27, 2026 17:41
Prompt for a task name before creating the AI-task scratch worktree and
slugify it backend-side into `gitwand-scratch-<slug>`, so tasks are
recognisable on disk and in repo tabs instead of an opaque timestamp.
Collisions append the timestamp; blank names fall back to it.

🪄 Commit via GitWand
Stream non-default locales on demand to keep ~9k lines of
translation data out of the initial bundle (P1.2). Templates
re-render reactively once a locale chunk resolves.

🪄 Commit via GitWand
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants