mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-10 09:19:42 +02:00
fix(tiles): file drops, focus handoffs, pop-out fallback, split merge, tablet default
- A file dragged onto the tile grid navigated the browser away: the single view's drop handler sits on #terminalContainer, hidden while tiles are open. The #tileGrid section now cancels every file dragover and drop (bubble phase, so tab and tile drags stay with _acceptTabDrops), and a drop on a tile uploads its images to THAT tile's session through _uploadAndInsertImages, with the same "Only image files are supported" toast as image-input.js (now in the zh-CN table). - App-driven refocus no longer moves DOM focus into another session's xterm: a remote delete of the focused tile, _reconcileTileGrid and a socket closed with 4003/4004/4010 (_onTileExit) pass focus: false. removeTile gains a focus option; user-initiated removes keep focusing. - Popping out the last tile left the parked terminal's stale content under the popped-out tab (and snapshotted it on the next switch). _selectAfterTileGrid treats a detached session as unusable for both the focused id and the fallback. - "Open group as tiles" and Ctrl/Cmd+click with the grid closed pass mergeSplit: false, so an open split no longer adds its two sessions on top of a set already sized to the group, the count and the window. - Touch-primary devices (primary pointer coarse: iPad, Android tablets) default the Tiles button OFF in getDefaultSettings(); touchscreen laptops (fine primary pointer) keep the desktop default ON. The button, the App Settings chip and the Ctrl+Shift+G gate all resolve an absent key through these defaults, so they agree. CLAUDE.md and the invariants doc say so. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,7 @@
|
||||
* Real code via the shared vm harness (test/mocks/tile-grid-vm.ts). Port: N/A.
|
||||
*/
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { FakeTile, makeGridApp, resetGridHarness, type GridApp } from './mocks/tile-grid-vm.js';
|
||||
import { FakeTile, flushFrames, makeGridApp, resetGridHarness, type GridApp } from './mocks/tile-grid-vm.js';
|
||||
|
||||
const IDS = ['s-a', 's-b', 's-c'];
|
||||
|
||||
@@ -148,3 +148,57 @@ describe('a tiled session deleted elsewhere', () => {
|
||||
expect(app.showWelcome).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// The handoffs above are the APP's choice: focus moves (activeSessionId, the
|
||||
// focus paint) but the keyboard does not. Moving DOM focus into the
|
||||
// neighbour's xterm sent whatever the user was still typing, Enter included,
|
||||
// into another session's agent; the single view sends it nowhere. Only a
|
||||
// removal the user made (the x button, Remove Focused Tile) carries the
|
||||
// keyboard along.
|
||||
describe('an app-driven refocus never moves the keyboard into another session', () => {
|
||||
const live = (id: string) => FakeTile.all.find((t) => t.sessionId === id && !t._destroyed)!;
|
||||
const focusCalls = () => FakeTile.all.reduce((n, t) => n + t.terminal.focus.mock.calls.length, 0);
|
||||
/** A grid on IDS focused on s-b, every terminal built, no focus recorded yet. */
|
||||
function built() {
|
||||
const app = makeGridApp(IDS);
|
||||
app.openTileGrid(IDS, { focusedId: 's-b' });
|
||||
flushFrames();
|
||||
for (const t of FakeTile.all) t.terminal.focus.mockClear();
|
||||
return app;
|
||||
}
|
||||
|
||||
it('a remote delete of the focused tile hands focus to the neighbour, DOM focus untouched', () => {
|
||||
const app = built();
|
||||
app._onSessionDeleted({ id: 's-b' });
|
||||
expect(app._tileGrid.ids).toEqual(['s-a', 's-c']);
|
||||
expect(app.activeSessionId).toBe('s-c');
|
||||
expect(app._tileGrid.focusedId).toBe('s-c');
|
||||
expect(focusCalls()).toBe(0);
|
||||
// No deferred focus left to land later either.
|
||||
expect(app._tileGrid.focusOnConnect ?? null).toBeNull();
|
||||
});
|
||||
|
||||
it.each([4003, 4004, 4010])('a socket the server closed (%i) removes the tile, DOM focus untouched', (code) => {
|
||||
const app = built();
|
||||
app._onTileExit('s-b', live('s-b'), code);
|
||||
expect(app._tileGrid.ids).toEqual(['s-a', 's-c']);
|
||||
expect(app.activeSessionId).toBe('s-c');
|
||||
expect(focusCalls()).toBe(0);
|
||||
});
|
||||
|
||||
it('a reconcile that finds the focused session gone moves focus, DOM focus untouched', () => {
|
||||
const app = built();
|
||||
app.sessions.delete('s-b');
|
||||
app._reconcileTileGrid();
|
||||
expect(app._tileGrid.ids).toEqual(['s-a', 's-c']);
|
||||
expect(app.activeSessionId).toBe('s-a');
|
||||
expect(focusCalls()).toBe(0);
|
||||
});
|
||||
|
||||
it('a removal the user made still carries the keyboard to the neighbour', () => {
|
||||
const app = built();
|
||||
app.removeFocusedTile();
|
||||
expect(app.activeSessionId).toBe('s-c');
|
||||
expect(live('s-c').terminal.focus).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user