From 48e91fc1d928f181223a444f4602fe68af7065b4 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Tue, 13 Oct 2026 18:59:24 +0200 Subject: [PATCH] fix(tiles): a claim that lands mid-drag lets the dragged tile go (#584 merge-time fix) _onTileAdoptedElsewhere acted only on a tear-off, a pending tear-off or a drag that just ended. A header drag carries the tile id even when this window has Detach Tiles off (no tear-off), so another window could dock the tile and its claim could arrive before this drag ended; the tile then stayed here as well. The header drag (_draggedTileId) now counts as dragging too. Co-Authored-By: Claude Opus 5.5 (1M context) --- src/web/public/tile-grid.js | 4 +++- test/tile-grid-detach.test.ts | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/web/public/tile-grid.js b/src/web/public/tile-grid.js index 35ab7224..e78193f0 100644 --- a/src/web/public/tile-grid.js +++ b/src/web/public/tile-grid.js @@ -3004,7 +3004,9 @@ Object.assign(CodemanApp.prototype, { */ _onTileAdoptedElsewhere(sessionId) { const recent = this._lastTileDrag; - const dragging = this._tileTearOff?.id === sessionId; + // A header drag counts even without a tear-off (Detach Tiles off here): the + // other window can take the tile before this drag ends. + const dragging = this._tileTearOff?.id === sessionId || this._draggedTileId === sessionId; const pending = this._tileTearOffPending?.id === sessionId; const justEnded = recent?.id === sessionId && Date.now() - recent.at < TILE_ADOPT_WINDOW_MS; if (!dragging && !pending && !justEnded) return false; diff --git a/test/tile-grid-detach.test.ts b/test/tile-grid-detach.test.ts index d8b6205c..d3b983c9 100644 --- a/test/tile-grid-detach.test.ts +++ b/test/tile-grid-detach.test.ts @@ -316,6 +316,16 @@ describe('another window docking the tile this one dragged', () => { expect(localStore.get('codeman:tile-grid')).toBe(docked); }); + it('a claim that lands mid-drag lets the tile go, with no tear-off here (Detach Tiles off)', () => { + // The tile's id rides every header drag, so a window with Detach Tiles on can + // dock it while this one has the setting off; its claim can beat the dragend. + const app = openGrid(FOUR, { detach: false }); + startDrag('s-b'); + expect(app._tileTearOff ?? null).toBeNull(); + app._onWindowMessage({ type: 'tile-adopted', id: 's-b', by: 'c-there' }); + expect(app._tileGrid.ids).toEqual(['s-a', 's-c', 's-d']); + }); + it('a claim for a tile this window did not drag, or one it dragged long ago, is ignored', () => { const app = openGrid(FOUR); app._onWindowMessage({ type: 'tile-adopted', id: 's-b', by: 'c-there' });