mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-10 01:09:43 +02:00
fix(tiles): send a tile's click report ephemeral, review follow-up
The tile's hand-encoded click report went through _handleDesktopTerminalClick and _sendSyntheticSgrTap to _sendInputAsync, so it took a seq, was persisted and would be redelivered after a reload. The documented TerminalTile rule (CLAUDE.md, Split-pane sessions) is that only typed input enters that queue and focus/mouse reports go out ephemeral, and the tile's own _onTerminalData says the same. Before #555 an opencode tile's click went through xterm's encoder and that ephemeral path. A click still unacknowledged when the page reloads, or sent during a server restart, could be replayed onto a later screen, where a press+release can pick a dialog option. _sendSyntheticSgrTap now takes an opt-in `ephemeral` field on its target and sends through _sendInputEphemeral when it is set; _handleDesktopTerminalClick passes the target through unchanged, and TerminalTile._installClickListener sets it. Without the flag nothing changes, so the primary pane's own click and touch tap reports stay on _sendInputAsync exactly as before (whether the primary pane should also go ephemeral is a separate question, out of scope here). Tests: the tile case now requires a frame with no seq and nothing pending in the reliable queue, and the targeted-click case in terminal-touch-tap spies on both send paths: a target with the flag goes ephemeral, an untargeted click and an untargeted tap stay durable. Dropping `ephemeral: true` from the tile, or the branch in _sendSyntheticSgrTap, turns the matching test red. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -583,12 +583,16 @@ describe('terminal touch tap mouse guard', () => {
|
||||
// primary pane's terminal and active session are not consulted.
|
||||
const { app } = loadTerminalUiHarness();
|
||||
const sent: Array<{ id: string; data: string }> = [];
|
||||
// A tile's report must stay out of the persisted queue: it asks for the
|
||||
// ephemeral path, and only a target that says so gets it.
|
||||
const durable: Array<{ id: string; data: string }> = [];
|
||||
app.activeSessionId = 'sess-1';
|
||||
app.sessions = new Map([
|
||||
['sess-1', { mode: 'claude', cliMouseTracking: false }],
|
||||
['s2', { mode: 'opencode', cliMouseTracking: true }],
|
||||
]);
|
||||
app._sendInputAsync = (id: string, data: string) => sent.push({ id, data });
|
||||
app._sendInputEphemeral = (id: string, data: string) => sent.push({ id, data });
|
||||
app._sendInputAsync = (id: string, data: string) => durable.push({ id, data });
|
||||
app._linkHovered = true; // the PRIMARY pane's hover: must not block the tile
|
||||
app.terminal = {
|
||||
cols: 80,
|
||||
@@ -618,14 +622,16 @@ describe('terminal touch tap mouse guard', () => {
|
||||
target: { closest: (sel: string) => (sel === '.xterm-screen' ? {} : null) },
|
||||
};
|
||||
|
||||
app._handleDesktopTerminalClick(click, { terminal: other, sessionId: 's2', linkHovered: false });
|
||||
const tile = { terminal: other, sessionId: 's2', linkHovered: false, ephemeral: true };
|
||||
app._handleDesktopTerminalClick(click, tile);
|
||||
expect(sent).toEqual([{ id: 's2', data: '\x1b[<0;21;6M\x1b[<0;21;6m' }]);
|
||||
expect(durable).toEqual([]);
|
||||
|
||||
// The tile's own selection and its own link hover do block it.
|
||||
otherSelected = true;
|
||||
app._handleDesktopTerminalClick(click, { terminal: other, sessionId: 's2', linkHovered: false });
|
||||
app._handleDesktopTerminalClick(click, tile);
|
||||
otherSelected = false;
|
||||
app._handleDesktopTerminalClick(click, { terminal: other, sessionId: 's2', linkHovered: true });
|
||||
app._handleDesktopTerminalClick(click, { ...tile, linkHovered: true });
|
||||
expect(sent).toHaveLength(1);
|
||||
|
||||
// With no target the primary pane answers for itself, exactly as before.
|
||||
@@ -633,6 +639,19 @@ describe('terminal touch tap mouse guard', () => {
|
||||
expect(app._shouldReportMouseToCli('s2')).toBe(true);
|
||||
app._handleDesktopTerminalClick(click);
|
||||
expect(sent).toHaveLength(1);
|
||||
|
||||
// And the primary pane's own reports stay on the durable queue: an
|
||||
// untargeted click and an untargeted touch tap.
|
||||
app.sessions.set('sess-1', { mode: 'claude', cliMouseTracking: true });
|
||||
app.terminal = { ...app.terminal, hasSelection: () => false, buffer: { active: { viewportY: 50, baseY: 50 } } };
|
||||
app._linkHovered = false;
|
||||
app._handleDesktopTerminalClick(click);
|
||||
app._sendSyntheticSgrTap(50, 50);
|
||||
expect(durable).toEqual([
|
||||
{ id: 'sess-1', data: '\x1b[<0;22;7M\x1b[<0;22;7m' }, // the primary's own origin (0, 0)
|
||||
{ id: 'sess-1', data: '\x1b[<0;7;4M\x1b[<0;7;4m' },
|
||||
]);
|
||||
expect(sent).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("tap: a target uses that pane's geometry and scroll position", () => {
|
||||
|
||||
Reference in New Issue
Block a user