Phase 5: TUI client (Bubbletea)

- cmd/tui/main.go: entry point with --server, --token, --version flags
- cmd/tui/config.go: config from ~/.config/dumpster/config.yaml or env vars
- cmd/tui/auth.go: login flow, session cookie management
- cmd/tui/api.go: HTTP client for servers, channels, messages, voice
- cmd/tui/model.go: root bubbletea model with state management
- cmd/tui/theme.go: Gruvbox dark lipgloss styles
- cmd/tui/servers.go: server bar with [D] style initials
- cmd/tui/channels.go: channel list with #text and 🔊 voice
- cmd/tui/chat.go: IRC-style message viewport with colored usernames
- cmd/tui/input.go: message input with > _ prompt
- cmd/tui/members.go: member list with ●/◐/○ status
- cmd/tui/ws.go: WebSocket client for real-time events
- cmd/tui/voice.go: voice state tracking and mute toggle
- cmd/tui/keys.go: vim-style keybindings (j/k, g/G, Tab, /, etc)
- cmd/tui/views.go: multi-pane layout rendering
- Makefile: added build-tui and run-tui targets
- go.mod/go.sum: added charmbracelet dependencies
This commit is contained in:
2026-06-28 17:46:19 -04:00
parent 297cc47631
commit 2a6b3830d5
18 changed files with 2288 additions and 17 deletions
+159
View File
@@ -0,0 +1,159 @@
package main
import "github.com/charmbracelet/lipgloss"
// Gruvbox dark palette
var (
bg = lipgloss.Color("#282828")
bgS = lipgloss.Color("#3c3836")
bgT = lipgloss.Color("#504945")
fg = lipgloss.Color("#ebdbb2")
fgS = lipgloss.Color("#d5c4a1")
fgF = lipgloss.Color("#a89984")
red = lipgloss.Color("#fb4934")
grn = lipgloss.Color("#b8bb26")
org = lipgloss.Color("#fe8019")
aqua = lipgloss.Color("#8ec07c")
yel = lipgloss.Color("#fabd2f")
)
// Styles
var (
// Layout panes
ServerBarStyle = lipgloss.NewStyle().
Background(bgS).
Foreground(fg).
Padding(0, 1).
Border(lipgloss.NormalBorder(), false, true, false, false).
BorderForeground(bgT)
ChannelListStyle = lipgloss.NewStyle().
Background(bgS).
Foreground(fg).
Padding(0, 1)
ChatPaneStyle = lipgloss.NewStyle().
Background(bg).
Foreground(fg).
Padding(0, 1)
MemberListStyle = lipgloss.NewStyle().
Background(bgS).
Foreground(fg).
Padding(0, 1)
InputPaneStyle = lipgloss.NewStyle().
Background(bgS).
Foreground(fg).
Padding(0, 1)
// Focus indicators
FocusedBorder = lipgloss.NewStyle().
Border(lipgloss.NormalBorder(), false, false, true, false).
BorderForeground(aqua)
UnfocusedBorder = lipgloss.NewStyle().
Border(lipgloss.NormalBorder(), false, false, true, false).
BorderForeground(bgT)
// Server list
ServerActive = lipgloss.NewStyle().
Background(aqua).
Foreground(bg).
Bold(true).
Padding(0, 1).
Align(lipgloss.Center)
ServerInactive = lipgloss.NewStyle().
Background(bgS).
Foreground(fgF).
Padding(0, 1).
Align(lipgloss.Center)
ServerHover = lipgloss.NewStyle().
Background(bgT).
Foreground(fg).
Padding(0, 1).
Align(lipgloss.Center)
// Channel list
CategoryStyle = lipgloss.NewStyle().
Foreground(fgF).
Bold(true).
PaddingTop(1)
ChannelActive = lipgloss.NewStyle().
Foreground(fg).
Bold(true).
PaddingLeft(1)
ChannelInactive = lipgloss.NewStyle().
Foreground(fgF).
PaddingLeft(1)
ChannelHover = lipgloss.NewStyle().
Foreground(fg).
PaddingLeft(1)
VoiceChannelPrefix = lipgloss.NewStyle().Foreground(aqua)
TextChannelPrefix = lipgloss.NewStyle().Foreground(fgF)
// Chat
MessageStyle = lipgloss.NewStyle().
Foreground(fg)
TimestampStyle = lipgloss.NewStyle().
Foreground(fgF)
UsernameStyle = lipgloss.NewStyle().
Bold(true)
MentionStyle = lipgloss.NewStyle().
Foreground(aqua).
Bold(true)
EditedTag = lipgloss.NewStyle().
Foreground(fgF).
Italic(true)
// Status indicators
StatusOnline = lipgloss.NewStyle().Foreground(grn)
StatusIdle = lipgloss.NewStyle().Foreground(yel)
StatusOffline = lipgloss.NewStyle().Foreground(fgF)
// Voice
VoiceStatusStyle = lipgloss.NewStyle().
Background(lipgloss.Color("#1d2021")).
Foreground(aqua).
Padding(0, 1)
// Error/success
ErrorStyle = lipgloss.NewStyle().
Foreground(red).
Bold(true)
SuccessStyle = lipgloss.NewStyle().
Foreground(grn).
Bold(true)
// Help
HelpStyle = lipgloss.NewStyle().
Foreground(fgF).
Italic(true)
// Title
TitleStyle = lipgloss.NewStyle().
Foreground(aqua).
Bold(true).
Align(lipgloss.Center)
)
// UsernameColor returns a deterministic color for a username based on its content.
func UsernameColor(username string) lipgloss.Color {
colors := []lipgloss.Color{red, grn, org, aqua, yel, lipgloss.Color("#d3869b"), lipgloss.Color("#83a598")}
hash := 0
for _, c := range username {
hash += int(c)
}
return colors[hash%len(colors)]
}