[BUG] Mods: mouse events land to the right of the pointer while a mod Client listens to pointer events (Orca, xterm.js)

Status Open
Reported on v2.1.295
Maintainer reply None cached
Activity 0 comments · opened Oct 9, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code.

What's Wrong?

When a mod pane contains a Client that calls surface.onPointer, mouse events in Claude Code land to the right of the real pointer.

In the docked pane (columns 90 to 196 of a 197-column terminal), each click lands several columns to the right of the pointer. Near column 170, the offset is about 7 columns.

Claude Code's own pane divider has the same offset. To drag the divider, I must put the pointer several columns to the left of it.

When the pane shows only standard elements (no Client), clicks and the divider are exact.

What Should Happen?

A mouse event lands in the cell under the pointer, with or without a mod Client on screen.

Error Messages/Logs

None.

Steps to Reproduce

I saw this with a larger drawing mod. The 4-file mod below uses the same pointer path and shows the reported cell.

  1. Create a folder pointer-drift with the 4 files below.
  2. In Orca, start Claude Code in fullscreen: claude --plugin-dir ./pointer-drift
  3. Run /pointer-drift. A pane opens on the right.
  4. Move the mouse over the pane. The █ block marks the cell that Claude Code reports. The last row shows the reported cell and the fine position.
  5. Near the right edge of the terminal, the block is several columns to the right of the pointer. fine has a value, so pixel mouse reports are on.
  6. Drag the pane divider. The divider has the same offset.

.claude-plugin/plugin.json

{ "name": "pointer-drift", "version": "0.1.0", "description": "Marks the cell Claude Code reports under the mouse pointer" }

hooks/hooks.json

{ "modules": ["./register.tsx"] }

hooks/register.tsx

import type { Register } from 'claude-code'

export const register: Register = on => {
  on('session.start', async ($, e, next) => {
    await $.command.register({ name: 'pointer-drift', description: 'Open a pane that marks the cell under the mouse pointer', immediate: true })
    return next(e)
  })

  on('command.run', { command: 'pointer-drift' }, async $ => {
    await $.ui.open({ id: 'pointer-drift', title: 'Pointer drift' })
    return { text: 'Move the mouse over the pane. The block marks the cell Claude Code reports.' }
  })

  on('ui.render', { component: 'Pane', requestId: 'pointer-drift' }, ($, e, next) => {
    if (e.surface !== 'terminal' && e.surface !== 'desktop') return next(e)
    const { Box, Client } = $.ui.resolve(e)
    return (
      <Box>
        <Client key="grid" module="./grid.tsx" props={null} width={e.props.bodyColumns} height={e.props.scroll.bodyRows} />
      </Box>
    )
  })
}

hooks/grid.tsx

import type { ClientModule } from 'claude-code'

type Pointer = { x: number; y: number; fine: string }

const Grid: ClientModule<null, Pointer> = (_props, surface) => {
  const at = surface.state ?? { x: -1, y: -1, fine: 'none' }
  surface.onPointer(ev => surface.setState({ x: ev.x, y: ev.y, fine: ev.fine === undefined ? 'none' : `${ev.fine.x.toFixed(2)}, ${ev.fine.y.toFixed(2)}` }))
  const { Box, Text } = surface.elements
  const rows = Array.from({ length: Math.max(0, surface.rows - 1) }, (_, y) =>
    Array.from({ length: surface.columns }, (_, x) => (x === at.x && y === at.y ? '█' : x % 10 === 0 ? '|' : '·')).join(''),
  )
  return (
    <Box flexDirection="column">
      {rows.map(row => (
        <Text>{row}</Text>
      ))}
      <Text>{`reported cell x=${at.x} y=${at.y}, fine=${at.fine}`}</Text>
    </Box>
  )
}

export default Grid

Claude Model

Opus

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.295 (Claude Code)

Platform

Anthropic API

Operating System

macOS

Terminal/Shell

Other: Orca 1.4.217 (based on xterm.js)

Additional Information

Cause, from the Claude Code 2.1.295 code:

  • While a Client has a pointer listener, the Client host calls retainFinePointer. Then syncMousePixels turns on SGR-Pixels mouse reports (DECSET 1016), because the terminal supports them and answered CSI 16 t.
  • Claude Code changes each pixel position to a cell as floor(px / cellWidth) + 1. cellWidth comes from the CSI 16 t answer.
  • Orca (xterm.js) sends SGR-Pixels positions in CSS pixels. Its cell width has a fraction, for example 8.2 CSS pixels. Orca answers CSI 16 t with Math.round(screen width / columns), which gives 8. It answers CSI 14 t with that rounded width times the columns.
  • Each column adds about 0.2 pixels of error, so the error grows from left to right.
  • Claude Code uses this conversion for all mouse events, so its own pane divider has the same offset while the Client is on screen.

Possible fixes:

  • Turn on pixel mouse reports only when the reported cell size is exact.
  • Let a Client ask for cell positions only, so it does not turn on pixel reports.

View original on GitHub ↗