Skip to content

fix: vim visual mode on hover, port config, and Windows PTY#29

Merged
jesse23 merged 16 commits intomainfrom
fix/vim-visual-mode-on-hover
Apr 27, 2026
Merged

fix: vim visual mode on hover, port config, and Windows PTY#29
jesse23 merged 16 commits intomainfrom
fix/vim-visual-mode-on-hover

Conversation

@jesse23
Copy link
Copy Markdown
Owner

@jesse23 jesse23 commented Apr 22, 2026

Summary

  • Block vim visual mode on hover: ghostty-web encodes hover \mousemove\ (no buttons) as \x1b[<32;\ (Cb=32, same as left-button drag); Vim with \set mouse=a\ interprets this as extend-selection and enters visual mode. A capture-phase \mousemove\ listener tracks whether the last move was a hover (\�.buttons === 0\ with mouse tracking active); \onData\ then rewrites \x1b[<32;\ → \x1b[<35;\ (Cb=35 = no-button per SGR spec) for those events, so Vim sees a plain motion instead of a drag.
  • Fix spurious visual-mode toggle on slow drags: ghostty-web fires multiple \mousemove\ events per character cell, producing consecutive identical \x1b[<32;col;rowM\ sequences. Vim toggles visual mode on duplicate same-cell drag events. Consecutive duplicate drag sequences are now dropped before forwarding to the terminal.
  • Fix missed resizes on monitor hot-plug/DPI change: \ResizeObserver\ only fires for layout-box changes; added a \window resize\ listener alongside it so viewport-level changes (monitor reconnect, DPI switch) also trigger a \ it(). Both signals are coalesced via
    equestAnimationFrame\ to avoid redundant layout work.
  • Ignore \PORT\ env var, always use config port: \process.env.PORT\ was silently overriding the configured port; CLI and server now both read port exclusively from \config.json.
  • Use node-pty on Windows: \Bun.Terminal\ does not implement PTY on Windows (\Bun.spawn({ terminal })\ is a no-op there); fall back to node-pty on Windows so the server works under both Bun and Node.js on that platform.

Test plan

  • Open a terminal tab and hover over it while vim is open with \set mouse=a\ — confirm no visual mode activation
  • Start a vim split drag, move the pointer outside the terminal boundary — confirm drag continues tracking
  • Hot-plug a monitor or change DPI — confirm the terminal canvas resizes correctly
  • Run \�un run webtty\ on Windows — confirm sessions connect and stay connected
  • \�un test\ passes (all unit + integration tests green)

🤖 Generated with Claude Code

jesse23 and others added 4 commits April 21, 2026 23:20
- Block mousemove (e.buttons===0) when mouse tracking is active so
  ghostty-web's hover-as-button-32 misreport never triggers vim visual
  mode on mouse movement.
- Set pointer capture on the canvas on pointerdown so drag operations
  (e.g. vim split resize) are not interrupted when the pointer crosses
  outside the canvas boundary.
- Add window resize listener alongside ResizeObserver to catch monitor
  hot-plug and DPI changes that resize the viewport without triggering
  the element-level observer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
process.env.PORT was silently overriding the config-file port,
causing webtty to target a different port than configured when
PORT happened to be set in the shell environment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bun.Terminal does not support the terminal option on Windows
(throws ERR_INVALID_ARG_TYPE), causing the WebSocket handler to
crash on the first PTY spawn and dropping every connection with
"Connection lost". node-pty works correctly under Bun on Windows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves webtty’s client-side input/resize behavior and aligns server/CLI startup behavior and PTY backend selection with the project’s configuration and Windows compatibility goals.

Changes:

  • Prevents unwanted Vim visual-mode activation on hover and avoids losing drags when the pointer leaves the terminal canvas.
  • Improves terminal fitting on monitor hot-plug/DPI changes by adding a window.resize trigger in addition to ResizeObserver.
  • Removes process.env.PORT precedence in favor of always using config.json’s port, and switches Windows PTY handling to prefer node-pty.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
src/server/index.ts Stop using process.env.PORT; always bind to config.port.
src/pty/index.ts Disable Bun PTY backend on Windows; fall back to node-pty.
src/client/index.ts Add window resize fitting; pointer capture for drags; block hover mousemove propagation when mouse tracking is enabled.
src/cli/http.ts Read CLI port from config (explicitly ignoring env PORT).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cli/http.ts Outdated
Comment thread src/client/index.ts Outdated
jesse23 and others added 8 commits April 21, 2026 23:43
Prevents biome formatter failures on Windows where git autocrlf=true
was converting LF to CRLF on checkout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The CLI already reads port exclusively from config, so ambient PORT never
silently overrides a user's config. The server itself still needs to
honor PORT so integration tests can bind to a free port and the server
can be run directly with a custom port without editing config.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the CLI runs from source (src/cli/index.ts), __dirname is src/cli.
The useNode path previously resolved ../server/index.js to src/server/index.js
which does not exist. Now detect the running-from-source case via the .ts
extension and jump two levels up to dist/server/index.js instead.

Also write a port config into the test tmpHome before each runCli call so
the CLI subprocess reads the correct test port from loadConfig() rather
than defaulting to 2346.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- client: coalesce ResizeObserver + window resize calls through
  requestAnimationFrame so both signals produce one fit() per frame
  instead of two back-to-back layout passes.
- http: replace eager module-level PORT/BASE_URL constants with lazy
  getPort()/getBaseUrl() functions so loadConfig() is only called when
  a command actually needs the port, avoiding unnecessary disk I/O and
  config-file creation on every CLI import.
- test: normalize backslash paths before the server/index substring
  check so the mock works correctly on Windows.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
setPointerCapture on pointerdown interfered with ghostty-web's internal
pointer tracking, causing vim to enter and immediately exit visual mode
on any drag. The hover-mousemove blocker alone is sufficient to prevent
the original ghost visual-mode-on-hover issue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blocking mousemove DOM events in capture phase disrupted ghostty-web's
internal mouse state, breaking vim drag (visual mode entered then
immediately exited).

New approach: let ghostty-web see all mouse events normally, then drop
the outgoing \x1b[<32;...M (button-32 motion) sequences from onData
when no mouse button is held. ghostty-web uses button-32 for both hover
and button-1 drag; tracking mousedown/mouseup lets us distinguish them
and only suppress the hover case before it reaches the PTY.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ouse

ghostty-web encodes hover motion (e.buttons===0) as SGR button-32
(\x1b[<32;col;rowM) — the same code as a left-button drag — because its
internal mouseButtonsPressed bitmask is 0 when no button is held (0+32=32).
Vim with mouse=a enables mode 1003 (any-event tracking), so every hover
move arrived as <LeftDrag>, toggling visual mode on each cursor movement.

Two fixes in onData:

1. Hover rewrite: a capture-phase mousemove listener reads the DOM event's
   authoritative e.buttons before ghostty-web's bubble-phase handler encodes
   it.  When e.buttons===0, rewrite \x1b[<32; to \x1b[<35; (Cb=35 = 32+3,
   the correct SGR no-button code).  Position is still forwarded so vim can
   track the cursor for subsequent drag operations.

2. Drag dedup: ghostty-web fires multiple mousemove events per character cell.
   Vim treats <LeftDrag> at the same cell twice as a visual-mode toggle, so
   consecutive identical \x1b[<32;col;rowM sequences are deduplicated.

Both fixes are gated on hasMouseTracking() and the \x1b[<32; prefix, so
they are no-ops when mouse tracking is inactive or when other Cb values
(press, release, scroll) are involved.
Move rewriteHoverMotion and isDuplicateDrag out of index.ts into a
dedicated mouse.ts module so the pure transform logic can be unit-tested
without a browser environment.  Add mouse.test.ts with 12 tests covering
hover rewrite, drag dedup, and all pass-through cases (press, release,
scroll, plain text).

index.ts now imports from mouse.ts; runtime behaviour is unchanged.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cli/http.ts Outdated
Comment thread src/client/index.ts
Comment thread src/pty/index.ts Outdated
Comment thread .gitattributes
jesse23 added 4 commits April 26, 2026 16:22
- commands.test.ts: restore HOME by deleting the key (not setting string 'undefined') when it was unset before the test mutated it
- pty/index.test.ts: use COMSPEC/cmd.exe on Windows; skip interactive data test under Bun on Windows (Bun ConPTY pipe closes after initial banner)
- pty/node.ts: pass /d to cmd.exe to disable AutoRun (clink registry hook)
- websocket.test.ts: run server under Node on Windows+Bun (mirrors http.ts workaround); use platform NL for ws.send commands; broaden prompt regex to match cmd.exe prompt followed by ESC sequence
@jesse23 jesse23 merged commit 2a3546f into main Apr 27, 2026
3 checks passed
@jesse23 jesse23 deleted the fix/vim-visual-mode-on-hover branch April 27, 2026 03:02
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