diff --git a/src/web/public/tile-grid.js b/src/web/public/tile-grid.js index f965462c..076e642c 100644 --- a/src/web/public/tile-grid.js +++ b/src/web/public/tile-grid.js @@ -605,7 +605,12 @@ Object.assign(CodemanApp.prototype, { } if (btn.matches?.(':focus-visible')) this._scheduleTileHint({ keyboard: true }); }); - btn.addEventListener('blur', () => this._hideTileHint()); + // Leaving the button ends a click's suppression too: a keyboard focus that + // comes back later is a new arrival, even with the pointer still on it. + btn.addEventListener('blur', () => { + hint.suppressed = false; + this._hideTileHint(); + }); // Capture: these run before the button's own handlers, so the card is gone // before a right-click opens the count menu or a click opens the grid; and // it stays gone while the pointer rests there. diff --git a/test/tile-grid-hint.test.ts b/test/tile-grid-hint.test.ts index 5de151e4..d002ae28 100644 --- a/test/tile-grid-hint.test.ts +++ b/test/tile-grid-hint.test.ts @@ -281,6 +281,18 @@ describe('hiding', () => { } }); + it('a keyboard focus coming back after a click shows it, the pointer still resting there', () => { + shown(); + btn.dispatch('click', {}); + expect(card()!.hidden).toBe(true); + // Tab away and back, the mouse never moved. + btn.dispatch('blur', {}); + (btn as unknown as { matches: (s: string) => boolean }).matches = (s) => s === ':focus-visible'; + btn.dispatch('focus', {}); + vi.advanceTimersByTime(300); + expect(card()!.hidden).toBe(false); + }); + it('the count menu never opens beside it: openTileCountMenu hides it first', () => { const app = shown(); app.openTileCountMenu({ preventDefault: vi.fn() });