refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
package ui
import (
2026-06-05 21:30:50 +00:00
"context"
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
"fmt"
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
"io"
"log"
2026-06-05 20:46:04 +00:00
"strings"
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
tea "github.com/charmbracelet/bubbletea"
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
"github.com/charmbracelet/bubbles/list"
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
"github.com/charmbracelet/lipgloss"
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
"github.com/gmgauthier/gostations/internal/data"
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
"github.com/gmgauthier/gostations/internal/radio"
)
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
// item wraps a station for the bubbles list.
type item struct {
2026-06-05 20:36:49 +00:00
station radio . Station
isFavorite bool
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
}
2026-06-05 20:36:49 +00:00
func ( i item ) Title ( ) string { return i . station . Name }
func ( i item ) Description ( ) string {
return fmt . Sprintf ( "%s • %s kbps • %s" , i . station . Codec , i . station . Bitrate , truncate ( i . station . Url , 50 ) )
}
func ( i item ) FilterValue ( ) string {
return i . station . Name + " " + i . station . Tags + " " + i . station . Codec + " " + i . station . Url
}
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
func truncate ( s string , n int ) string {
if len ( s ) <= n {
return s
}
return s [ : n ] + "…"
}
// listDelegate for nice rendering.
type listDelegate struct { }
func ( d listDelegate ) Height ( ) int { return 2 }
func ( d listDelegate ) Spacing ( ) int { return 1 }
func ( d listDelegate ) Update ( msg tea . Msg , m * list . Model ) tea . Cmd { return nil }
func ( d listDelegate ) Render ( w io . Writer , m list . Model , index int , listItem list . Item ) {
i , ok := listItem . ( item )
if ! ok {
return
}
2026-06-05 20:36:49 +00:00
title := i . station . Name
if i . isFavorite {
title = "★ " + title
}
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
if m . Index ( ) == index {
title = lipgloss . NewStyle ( ) . Bold ( true ) . Render ( "▶ " + title )
} else {
title = " " + title
}
2026-06-05 20:36:49 +00:00
desc := fmt . Sprintf ( " %s • %s kbps • %s" , i . station . Codec , i . station . Bitrate , truncate ( i . station . Url , 45 ) )
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
fmt . Fprintf ( w , "%s\n%s" , title , desc )
}
// App is the root model. Currently focused on selection (playback integration later).
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
type App struct {
2026-06-05 21:30:50 +00:00
list list . Model
quitting bool
currentSearchTerm string
favs * data . Favorites
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
}
func NewApp ( initial [ ] radio . Station ) * App {
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
favs , err := data . NewFavorites ( )
if err != nil {
log . Printf ( "warning: could not load favorites: %v" , err )
}
favSet := map [ string ] bool { }
if favs != nil {
for _ , s := range favs . List ( ) {
favSet [ s . Url ] = true
}
}
items := make ( [ ] list . Item , len ( initial ) )
for i , s := range initial {
2026-06-05 20:36:49 +00:00
isFav := favSet [ s . Url ]
items [ i ] = item { station : s , isFavorite : isFav }
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
}
2026-06-05 21:30:50 +00:00
// Heuristic: if every provided initial station is a favorite, this was a "favorites" initial load
isFavoritesInitial := len ( initial ) > 0
if isFavoritesInitial {
for _ , s := range initial {
if ! favSet [ s . Url ] {
isFavoritesInitial = false
break
}
}
}
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
l := list . New ( items , listDelegate { } , 60 , 20 )
2026-06-05 21:30:50 +00:00
title := "GoStations - Radio Browser (new TUI • ★ = favorite)"
if isFavoritesInitial {
title = "GoStations - Your Favorites"
}
l . Title = title
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
l . SetShowStatusBar ( true )
l . SetFilteringEnabled ( true )
l . Styles . Title = lipgloss . NewStyle ( ) . Bold ( true ) . Foreground ( lipgloss . Color ( "63" ) )
2026-06-05 20:46:04 +00:00
// Use a custom substring/AND filter instead of the default fuzzy (which matches
// any combination of individual letters, e.g. "WFMT" matching stations with W or F or M or T).
// This makes "/WFMT" find stations containing the substring "wfmt" (case-insensitive),
// and multi-word searches require all words to appear somewhere in the target.
l . Filter = func ( term string , targets [ ] string ) [ ] list . Rank {
term = strings . TrimSpace ( term )
if term == "" {
ranks := make ( [ ] list . Rank , len ( targets ) )
for i := range targets {
ranks [ i ] = list . Rank { Index : i }
}
return ranks
}
words := strings . Fields ( strings . ToLower ( term ) )
var ranks [ ] list . Rank
for i , t := range targets {
tl := strings . ToLower ( t )
matches := true
for _ , w := range words {
if ! strings . Contains ( tl , w ) {
matches = false
break
}
}
if matches {
ranks = append ( ranks , list . Rank { Index : i } )
}
}
return ranks
}
2026-06-05 21:30:50 +00:00
return & App { list : l , favs : favs }
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
}
func ( a * App ) Init ( ) tea . Cmd {
return nil
}
func ( a * App ) Update ( msg tea . Msg ) ( tea . Model , tea . Cmd ) {
switch msg := msg . ( type ) {
case tea . KeyMsg :
switch msg . String ( ) {
case "q" , "ctrl+c" :
a . quitting = true
return a , tea . Quit
case "enter" :
2026-06-05 21:30:50 +00:00
if a . list . FilterState ( ) == list . Filtering || a . list . FilterState ( ) == list . FilterApplied {
// User pressed enter while in filter: treat the filter term as a new lookup/search
term := strings . TrimSpace ( a . list . FilterValue ( ) )
if term != "" {
a . currentSearchTerm = term
a . list . Title = fmt . Sprintf ( ` Searching for "%s"... ` , term )
return a , searchCmd ( term )
}
}
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
if i , ok := a . list . SelectedItem ( ) . ( item ) ; ok {
// Placeholder: for now just show what would be played.
// Later: switch to playback model + use player.Play
2026-06-05 20:36:49 +00:00
a . list . Title = "Would play: " + i . station . Name + " (press q)"
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
}
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
return a , nil
2026-06-05 21:30:50 +00:00
case "f" :
// Toggle favorite on the currently selected item
if sel , ok := a . list . SelectedItem ( ) . ( item ) ; ok {
masterIdx := a . list . GlobalIndex ( )
if a . favs == nil {
var ferr error
a . favs , ferr = data . NewFavorites ( )
if ferr != nil {
statusCmd := a . list . NewStatusMessage ( "Favorites unavailable: " + ferr . Error ( ) )
return a , statusCmd
}
}
url := sel . station . Url
wasFav := sel . isFavorite
if wasFav {
a . favs . Remove ( url )
} else {
a . favs . Add ( sel . station )
}
if saveErr := a . favs . Save ( ) ; saveErr != nil {
statusCmd := a . list . NewStatusMessage ( "Failed to save favorites: " + saveErr . Error ( ) )
return a , statusCmd
}
sel . isFavorite = ! wasFav
setCmd := a . list . SetItem ( masterIdx , sel )
status := "Removed from favorites"
if sel . isFavorite {
status = "Added to favorites ★"
}
statusCmd := a . list . NewStatusMessage ( status )
return a , tea . Batch ( setCmd , statusCmd )
}
return a , nil
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
}
2026-06-05 20:36:49 +00:00
// Auto-start filter on first alphanumeric character (better UX than requiring / first)
s := msg . String ( )
if len ( s ) == 1 {
r := rune ( s [ 0 ] )
if ( r >= 'a' && r <= 'z' ) || ( r >= 'A' && r <= 'Z' ) || ( r >= '0' && r <= '9' ) {
if a . list . FilterState ( ) != list . Filtering {
2026-06-05 20:46:04 +00:00
// Simulate pressing the filter key to activate, then feed the char
2026-06-05 20:36:49 +00:00
slash := tea . KeyMsg { Type : tea . KeyRunes , Runes : [ ] rune { '/' } }
2026-06-05 20:46:04 +00:00
var c1 tea . Cmd
a . list , c1 = a . list . Update ( slash )
var c2 tea . Cmd
a . list , c2 = a . list . Update ( msg )
return a , tea . Batch ( c1 , c2 )
2026-06-05 20:36:49 +00:00
}
}
}
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
case tea . WindowSizeMsg :
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
a . list . SetSize ( msg . Width - 4 , msg . Height - 4 )
2026-06-05 21:30:50 +00:00
case searchResultsMsg :
if msg . err != nil {
a . list . Title = fmt . Sprintf ( "Search error: %v (press / to search again)" , msg . err )
return a , nil
}
// Rebuild items (preserve favorites)
var favs * data . Favorites
var ferr error
if a . favs != nil {
favs = a . favs
// reload to get latest persisted state (in case of external edits)
favs , ferr = data . NewFavorites ( )
if ferr != nil {
favs = a . favs // fallback to in-memory
}
} else {
favs , ferr = data . NewFavorites ( )
}
a . favs = favs
favSet := map [ string ] bool { }
if favs != nil {
for _ , fs := range favs . List ( ) {
favSet [ fs . Url ] = true
}
}
newItems := make ( [ ] list . Item , len ( msg . stations ) )
for i , s := range msg . stations {
newItems [ i ] = item { station : s , isFavorite : favSet [ s . Url ] }
}
setCmd := a . list . SetItems ( newItems )
title := "GoStations - Radio Browser (new TUI • ★ = favorite)"
if a . currentSearchTerm != "" {
title = fmt . Sprintf ( "GoStations - Results for %q (%d)" , a . currentSearchTerm , len ( newItems ) )
}
a . list . Title = title
a . list . ResetFilter ( )
return a , setCmd
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
}
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
var cmd tea . Cmd
a . list , cmd = a . list . Update ( msg )
return a , cmd
}
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
func ( a * App ) View ( ) string {
if a . quitting {
return "Thanks for using GoStations!\n"
}
2026-06-05 21:30:50 +00:00
return "\n" + a . list . View ( ) + "\n\n(type to filter current list • ENTER while filtering = server search for term & refresh list • f = toggle ★ favorite • q quit • --legacy for old UI)\n"
}
// searchCmd performs an async station search (used for in-TUI lookups via the filter box).
func searchCmd ( name string ) tea . Cmd {
return func ( ) tea . Msg {
stations , err := radio . Search ( context . Background ( ) , name , "" , "" , "" , false )
return searchResultsMsg { stations : stations , err : err }
}
}
// searchResultsMsg is sent when a background search completes.
type searchResultsMsg struct {
stations [ ] radio . Station
err error
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
}
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
// Run starts the TUI (alt screen).
refactor: reorganize into internal packages, fix critical panics and error handling
Move browser, config, player, and radio logic into internal/{config,radio,player,ui,version}.
Add cached API host resolution, context-aware HTTP client, and nil/length guards to eliminate
RandomIP, nslookup, reverseLookup, and GetStations panics. Replace subExecute with clean
legacyPlayer using Run-only execution. Fix inverted -short test guards, unformatted config
error messages, and "Erorr" typo. Remove obsolete vendor/GOPATH logic from build scripts.
Update callers in stations.go and radiomenu.go to new paths; retain shims for transition.
2026-06-05 20:10:54 +00:00
func Run ( initial [ ] radio . Station ) error {
p := tea . NewProgram ( NewApp ( initial ) , tea . WithAltScreen ( ) )
_ , err := p . Run ( )
return err
}
feat(cli): default to bubbles TUI, add find/play subcmds + JSON favorites
- implement internal/ui with bubbles list + ★ fav markers, filter, enter stub
- add data/favorites (JSON roundtrip, Add/Remove, XDG), config tests
- wire subcommands: `find -j`, `play [url|search]`
- gate legacy wmenu behind --legacy; keep old flags for seeding
- fix inverted -short guards, format string, go.mod deps
- add unit tests for radio prune, player IsInstalled, ui keys, etc.
2026-06-05 20:23:11 +00:00
// Short is a small helper (duplicated from old for TUI list desc; can be shared later).
func Short ( s string , i int ) string {
runes := [ ] rune ( s )
if len ( runes ) > i {
return string ( runes [ : i ] )
}
return s
}