@@ -36,6 +36,12 @@ type App struct {
3636 showHelp bool
3737 picker * PickerModel // non-nil when cross-project picker is open
3838
39+ // Check error navigation state (]e / [e)
40+ checkNavActive bool
41+ checkNavIndex int
42+ checkNavLocations []CheckNavLocation
43+ pendingKey rune // ']' or '[' waiting for 'e', 0 if none
44+
3945 tabBar TabBar
4046 hintBar HintBar
4147 statusBar StatusBar
@@ -210,6 +216,26 @@ func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
210216 }
211217 return a , nil
212218
219+ case NavigateToDocMsg :
220+ // Close overlay, navigate tree to document, enter check nav mode
221+ a .views .Pop ()
222+ qname := docNameToQualifiedName (msg .ModuleName , msg .DocumentName )
223+ if bv , ok := a .views .Base ().(BrowserView ); ok {
224+ cmd := bv .navigateToNode (qname )
225+ a .views .SetBase (bv )
226+ if tab := a .activeTabPtr (); tab != nil {
227+ tab .Miller = bv .miller
228+ tab .UpdateLabel ()
229+ a .syncTabBar ()
230+ }
231+ // Enter check nav mode
232+ a .checkNavActive = true
233+ a .checkNavIndex = msg .NavIndex
234+ a .checkNavLocations = extractCheckNavLocations (filterCheckErrors (a .checkErrors , "all" ))
235+ return a , cmd
236+ }
237+ return a , nil
238+
213239 case DiffOpenMsg :
214240 dv := NewDiffView (msg , a .width , a .height )
215241 a .views .Push (dv )
@@ -488,8 +514,18 @@ func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
488514 }
489515 // Update check overlay content if it's currently visible
490516 if ov , ok := a .views .Active ().(OverlayView ); ok && ov .refreshable {
491- content := renderCheckResults (a .checkErrors )
492- ov .overlay .Show ("mx check" , content , ov .overlay .width , ov .overlay .height )
517+ ov .checkErrors = a .checkErrors
518+ filtered := filterCheckErrors (a .checkErrors , ov .checkFilter )
519+ ov .checkNavLocs = extractCheckNavLocations (filtered )
520+ if ov .selectedIdx >= len (ov .checkNavLocs ) {
521+ ov .selectedIdx = max (0 , len (ov .checkNavLocs )- 1 )
522+ }
523+ if len (ov .checkNavLocs ) == 0 {
524+ ov .selectedIdx = - 1
525+ }
526+ title := renderCheckFilterTitle (a .checkErrors , ov .checkFilter )
527+ content := renderCheckResults (a .checkErrors , ov .checkFilter )
528+ ov .overlay .Show (title , content , ov .overlay .width , ov .overlay .height )
493529 a .views .SetActive (ov )
494530 }
495531 return a , nil
@@ -523,6 +559,68 @@ func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
523559func (a * App ) handleBrowserAppKeys (msg tea.KeyMsg ) tea.Cmd {
524560 tab := a .activeTabPtr ()
525561
562+ // Handle two-key sequence: ]e / [e (check error navigation)
563+ if a .pendingKey != 0 {
564+ pending := a .pendingKey
565+ a .pendingKey = 0
566+ if msg .String () == "e" && len (a .checkErrors ) > 0 {
567+ // Lazily initialize check nav state if not already active
568+ if ! a .checkNavActive {
569+ a .checkNavActive = true
570+ a .checkNavLocations = extractCheckNavLocations (filterCheckErrors (a .checkErrors , "all" ))
571+ a .checkNavIndex = - 1 // will be incremented to 0 for ], or wrapped to last for [
572+ }
573+ if pending == ']' {
574+ a .checkNavIndex ++
575+ if a .checkNavIndex >= len (a .checkNavLocations ) {
576+ a .checkNavIndex = 0 // wrap around
577+ }
578+ } else {
579+ a .checkNavIndex --
580+ if a .checkNavIndex < 0 {
581+ a .checkNavIndex = len (a .checkNavLocations ) - 1 // wrap around
582+ }
583+ }
584+ loc := a .checkNavLocations [a .checkNavIndex ]
585+ qname := docNameToQualifiedName (loc .ModuleName , loc .DocumentName )
586+ if bv , ok := a .views .Base ().(BrowserView ); ok {
587+ cmd := bv .navigateToNode (qname )
588+ a .views .SetBase (bv )
589+ if tab := a .activeTabPtr (); tab != nil {
590+ tab .Miller = bv .miller
591+ tab .UpdateLabel ()
592+ a .syncTabBar ()
593+ }
594+ return cmd
595+ }
596+ return handledCmd
597+ }
598+ // Not 'e' — fall through to normal handling for the pending key
599+ // Re-process the pending key's original action
600+ if pending == ']' {
601+ if a .activeTab < len (a .tabs )- 1 {
602+ a .activeTab ++
603+ a .syncBrowserView ()
604+ a .syncTabBar ()
605+ }
606+ } else if pending == '[' {
607+ if a .activeTab > 0 {
608+ a .activeTab --
609+ a .syncBrowserView ()
610+ a .syncTabBar ()
611+ }
612+ }
613+ // Now process the current key normally (fall through)
614+ }
615+
616+ // Non-nav keys exit check nav mode (preserve for ]/[/! which are nav-related)
617+ if a .checkNavActive {
618+ key := msg .String ()
619+ if key != "]" && key != "[" && key != "!" && key != "\\ !" {
620+ a .checkNavActive = false
621+ }
622+ }
623+
526624 switch msg .String () {
527625 case "q" :
528626 if a .watcher != nil {
@@ -574,6 +672,10 @@ func (a *App) handleBrowserAppKeys(msg tea.KeyMsg) tea.Cmd {
574672 return handledCmd
575673
576674 case "[" :
675+ if len (a .checkErrors ) > 0 {
676+ a .pendingKey = '['
677+ return handledCmd
678+ }
577679 if a .activeTab > 0 {
578680 a .activeTab --
579681 a .syncBrowserView ()
@@ -582,6 +684,10 @@ func (a *App) handleBrowserAppKeys(msg tea.KeyMsg) tea.Cmd {
582684 return handledCmd
583685
584686 case "]" :
687+ if len (a .checkErrors ) > 0 {
688+ a .pendingKey = ']'
689+ return handledCmd
690+ }
585691 if a .activeTab < len (a .tabs )- 1 {
586692 a .activeTab ++
587693 a .syncBrowserView ()
@@ -606,11 +712,17 @@ func (a *App) handleBrowserAppKeys(msg tea.KeyMsg) tea.Cmd {
606712 return handledCmd
607713
608714 case "!" , "\\ !" : // some terminals send "\\!" for shifted-1; accept both forms
609- content := renderCheckResults (a .checkErrors )
610- ov := NewOverlayView ("mx check" , content , a .width , a .height , OverlayViewOpts {
715+ filter := "all"
716+ title := renderCheckFilterTitle (a .checkErrors , filter )
717+ content := renderCheckResults (a .checkErrors , filter )
718+ navLocs := extractCheckNavLocations (a .checkErrors )
719+ ov := NewOverlayView (title , content , a .width , a .height , OverlayViewOpts {
611720 HideLineNumbers : true ,
612721 Refreshable : true ,
613722 RefreshMsg : MxCheckRerunMsg {},
723+ CheckFilter : filter ,
724+ CheckErrors : a .checkErrors ,
725+ CheckNavLocs : navLocs ,
614726 })
615727 a .views .Push (ov )
616728 return handledCmd
@@ -724,7 +836,15 @@ func (a App) View() string {
724836 a .statusBar .SetBreadcrumb (info .Breadcrumb )
725837 a .statusBar .SetPosition (info .Position )
726838 a .statusBar .SetMode (info .Mode )
727- a .statusBar .SetCheckBadge (formatCheckBadge (a .checkErrors , a .checkRunning ))
839+ if a .checkNavActive && len (a .checkNavLocations ) > 0 {
840+ loc := a .checkNavLocations [a .checkNavIndex ]
841+ navInfo := fmt .Sprintf ("[%d/%d] %s: %s ]e next [e prev" ,
842+ a .checkNavIndex + 1 , len (a .checkNavLocations ),
843+ loc .Code , docNameToQualifiedName (loc .ModuleName , loc .DocumentName ))
844+ a .statusBar .SetCheckBadge (CheckWarnStyle .Render (navInfo ))
845+ } else {
846+ a .statusBar .SetCheckBadge (formatCheckBadge (a .checkErrors , a .checkRunning ))
847+ }
728848 viewModeNames := a .collectViewModeNames ()
729849 a .statusBar .SetViewDepth (a .views .Depth (), viewModeNames )
730850 statusLine := StatusBarStyle .Width (a .width ).Render (a .statusBar .View (a .width ))
0 commit comments