@@ -78,6 +78,8 @@ export class ChangelogRunner implements InteractiveProgram {
7878 private selectedIndex = 0 ;
7979 private listOffset = 0 ;
8080 private detailOffset = 0 ;
81+ private hoverIndex : number | null = null ;
82+ private lastMousePos : { col : number ; row : number } | null = null ;
8183 private resizeUnsub : ( ( ) => void ) | null = null ;
8284 private disposed = false ;
8385 private detailCache : { index : number ; width : number ; lines : string [ ] } | null = null ;
@@ -161,6 +163,7 @@ export class ChangelogRunner implements InteractiveProgram {
161163 }
162164
163165 private handleMouse ( button : number , col : number , row : number , finalByte : string ) : void {
166+ this . lastMousePos = { col, row } ;
164167 // Wheel scroll routes to whichever column the cursor is over so each
165168 // side scrolls independently.
166169 if ( button === WHEEL_UP ) {
@@ -173,11 +176,17 @@ export class ChangelogRunner implements InteractiveProgram {
173176 else this . scrollDetail ( 1 ) ;
174177 return ;
175178 }
179+ // Motion (no button held). 1003h reports these as button code 35:
180+ // bit 5 (motion) | code 3 (no button). Highlight the list row under
181+ // the cursor so the user can see where a click would land.
182+ if ( button === 35 && finalByte === "M" ) {
183+ if ( this . listIndexAt ( col , row ) !== this . hoverIndex ) this . render ( ) ;
184+ return ;
185+ }
176186 // Left-button press on the version list selects that release.
177- if ( button === 0 && finalByte === "M" && col < LIST_WIDTH ) {
178- const bodyTop = HEADER_ROWS ;
179- const idx = this . listOffset + ( row - bodyTop ) ;
180- if ( idx >= 0 && idx < RELEASES . length ) {
187+ if ( button === 0 && finalByte === "M" ) {
188+ const idx = this . listIndexAt ( col , row ) ;
189+ if ( idx !== null ) {
181190 this . selectedIndex = idx ;
182191 this . detailOffset = 0 ;
183192 this . ensureSelectionVisible ( ) ;
@@ -186,6 +195,14 @@ export class ChangelogRunner implements InteractiveProgram {
186195 }
187196 }
188197
198+ private listIndexAt ( col : number , row : number ) : number | null {
199+ if ( col >= LIST_WIDTH ) return null ;
200+ const r = row - HEADER_ROWS ;
201+ if ( r < 0 || r >= this . bodyHeight ( ) ) return null ;
202+ const idx = this . listOffset + r ;
203+ return idx >= 0 && idx < RELEASES . length ? idx : null ;
204+ }
205+
189206 private moveSelection ( delta : number ) : void {
190207 const next = Math . max ( 0 , Math . min ( RELEASES . length - 1 , this . selectedIndex + delta ) ) ;
191208 if ( next === this . selectedIndex ) return ;
@@ -280,6 +297,12 @@ export class ChangelogRunner implements InteractiveProgram {
280297 private render ( ) : void {
281298 if ( this . disposed ) return ;
282299 this . lastSize = { ...this . size } ;
300+ // Re-sync hover from the cursor's last position so wheel-scrolls and
301+ // keyboard jumps move the highlight to whatever release is now under
302+ // the mouse.
303+ this . hoverIndex = this . lastMousePos
304+ ? this . listIndexAt ( this . lastMousePos . col , this . lastMousePos . row )
305+ : null ;
283306 const bodyH = this . bodyHeight ( ) ;
284307 const detailLines = this . getDetailLines ( ) ;
285308
@@ -296,6 +319,8 @@ export class ChangelogRunner implements InteractiveProgram {
296319 const padded = label . length > LIST_WIDTH - 2 ? label . slice ( 0 , LIST_WIDTH - 2 ) : label . padEnd ( LIST_WIDTH - 2 ) ;
297320 if ( idx === this . selectedIndex ) {
298321 leftCell = `${ fg ( 36 ) } ❯${ RESET } ${ BOLD } ${ padded } ${ RESET } ` ;
322+ } else if ( idx === this . hoverIndex ) {
323+ leftCell = `${ fg ( 36 ) } ›${ RESET } ${ padded } ` ;
299324 } else {
300325 leftCell = ` ${ padded } ` ;
301326 }
0 commit comments