Skip to content

Commit be728f5

Browse files
committed
fix: cleanup styles
1 parent d554e79 commit be728f5

6 files changed

Lines changed: 111 additions & 32 deletions

File tree

internal/core/session/session.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type Session struct {
1717
Name string `json:"name"`
1818
Path string `json:"path"`
1919
Remote string `json:"remote"`
20+
Prompt string `json:"prompt,omitempty"`
2021
State State `json:"state"`
2122
CreatedAt time.Time `json:"created_at"`
2223
UpdatedAt time.Time `json:"updated_at"`

internal/hive/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ func (s *Service) CreateSession(ctx context.Context, opts CreateOptions) (*sessi
112112
Name: opts.Name,
113113
Path: path,
114114
Remote: remote,
115+
Prompt: opts.Prompt,
115116
State: session.StateActive,
116117
CreatedAt: now,
117118
UpdatedAt: now,

internal/tui/delegate.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func NewSessionDelegate() SessionDelegate {
5252

5353
// Height returns the height of each item.
5454
func (d SessionDelegate) Height() int {
55-
return 2
55+
return 3
5656
}
5757

5858
// Spacing returns the spacing between items.
@@ -92,6 +92,18 @@ func (d SessionDelegate) Render(w io.Writer, m list.Model, index int, item list.
9292
state := stateStyle.Render(string(s.State))
9393
desc := fmt.Sprintf("%s %s", state, s.Path)
9494

95+
// Build prompt line if present
96+
prompt := ""
97+
if s.Prompt != "" {
98+
// Truncate long prompts
99+
maxPromptLen := 80
100+
displayPrompt := s.Prompt
101+
if len(displayPrompt) > maxPromptLen {
102+
displayPrompt = displayPrompt[:maxPromptLen-3] + "..."
103+
}
104+
prompt = fmt.Sprintf(" %s", lipgloss.NewStyle().Foreground(colorGray).Render(displayPrompt))
105+
}
106+
95107
// Apply selection styling
96108
var titleStyle lipgloss.Style
97109
if isSelected {
@@ -104,5 +116,8 @@ func (d SessionDelegate) Render(w io.Writer, m list.Model, index int, item list.
104116

105117
// Write to output
106118
_, _ = fmt.Fprintf(w, "%s\n", titleStyle.Render(title))
107-
_, _ = fmt.Fprintf(w, " %s", desc)
119+
_, _ = fmt.Fprintf(w, " %s\n", desc)
120+
if prompt != "" {
121+
_, _ = fmt.Fprint(w, prompt)
122+
}
108123
}

internal/tui/keybindings.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package tui
33
import (
44
"context"
55
"fmt"
6+
"maps"
67
"os/exec"
8+
"slices"
79
"strings"
810

11+
"github.com/charmbracelet/bubbles/key"
912
"github.com/hay-kot/hive/internal/core/config"
1013
"github.com/hay-kot/hive/internal/core/session"
1114
"github.com/hay-kot/hive/internal/hive"
@@ -135,10 +138,14 @@ func (h *KeybindingHandler) executeShell(_ context.Context, cmd string) error {
135138
return c.Run()
136139
}
137140

138-
// HelpEntries returns all configured keybindings for display.
141+
// HelpEntries returns all configured keybindings for display, sorted by key.
139142
func (h *KeybindingHandler) HelpEntries() []string {
143+
// Get sorted keys for consistent ordering
144+
keys := slices.Sorted(maps.Keys(h.keybindings))
145+
140146
entries := make([]string, 0, len(h.keybindings))
141-
for key, kb := range h.keybindings {
147+
for _, key := range keys {
148+
kb := h.keybindings[key]
142149
help := kb.Help
143150
if help == "" && kb.Action != "" {
144151
help = kb.Action
@@ -156,3 +163,27 @@ func (h *KeybindingHandler) HelpString() string {
156163
entries := h.HelpEntries()
157164
return strings.Join(entries, " ")
158165
}
166+
167+
// KeyBindings returns key.Binding objects for integration with bubbles help system.
168+
func (h *KeybindingHandler) KeyBindings() []key.Binding {
169+
keys := slices.Sorted(maps.Keys(h.keybindings))
170+
bindings := make([]key.Binding, 0, len(keys))
171+
172+
for _, k := range keys {
173+
kb := h.keybindings[k]
174+
help := kb.Help
175+
if help == "" && kb.Action != "" {
176+
help = kb.Action
177+
}
178+
if help == "" {
179+
help = "shell"
180+
}
181+
182+
bindings = append(bindings, key.NewBinding(
183+
key.WithKeys(k),
184+
key.WithHelp(k, help),
185+
))
186+
}
187+
188+
return bindings
189+
}

internal/tui/model.go

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package tui
33
import (
44
"context"
55

6+
"github.com/charmbracelet/bubbles/key"
67
"github.com/charmbracelet/bubbles/list"
8+
"github.com/charmbracelet/bubbles/spinner"
79
tea "github.com/charmbracelet/bubbletea"
810
"github.com/charmbracelet/lipgloss"
911
"github.com/hay-kot/hive/internal/core/config"
@@ -17,20 +19,23 @@ type UIState int
1719
const (
1820
stateNormal UIState = iota
1921
stateConfirming
22+
stateLoading
2023
)
2124

2225
// Model is the main Bubble Tea model for the TUI.
2326
type Model struct {
24-
service *hive.Service
25-
list list.Model
26-
handler *KeybindingHandler
27-
state UIState
28-
modal Modal
29-
pending Action
30-
width int
31-
height int
32-
err error
33-
quitting bool
27+
service *hive.Service
28+
list list.Model
29+
handler *KeybindingHandler
30+
state UIState
31+
modal Modal
32+
pending Action
33+
width int
34+
height int
35+
err error
36+
spinner spinner.Model
37+
loadingMessage string
38+
quitting bool
3439
}
3540

3641
// sessionsLoadedMsg is sent when sessions are loaded.
@@ -55,17 +60,27 @@ func New(service *hive.Service, cfg *config.Config) Model {
5560

5661
handler := NewKeybindingHandler(cfg.Keybindings, service)
5762

63+
// Add custom keybindings to list help
64+
l.AdditionalShortHelpKeys = func() []key.Binding {
65+
return handler.KeyBindings()
66+
}
67+
68+
s := spinner.New()
69+
s.Spinner = spinner.Dot
70+
s.Style = spinnerStyle
71+
5872
return Model{
5973
service: service,
6074
list: l,
6175
handler: handler,
6276
state: stateNormal,
77+
spinner: s,
6378
}
6479
}
6580

6681
// Init initializes the model.
6782
func (m Model) Init() tea.Cmd {
68-
return m.loadSessions()
83+
return tea.Batch(m.loadSessions(), m.spinner.Tick)
6984
}
7085

7186
// loadSessions returns a command that loads sessions from the service.
@@ -90,24 +105,29 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
90105
case tea.WindowSizeMsg:
91106
m.width = msg.Width
92107
m.height = msg.Height
93-
m.list.SetSize(msg.Width, msg.Height-2) // Leave room for help bar
108+
m.list.SetSize(msg.Width, msg.Height)
94109
return m, nil
95110

96111
case sessionsLoadedMsg:
97112
if msg.err != nil {
98113
m.err = msg.err
114+
m.state = stateNormal
99115
return m, nil
100116
}
101117
items := make([]list.Item, len(msg.sessions))
102118
for i, s := range msg.sessions {
103119
items[i] = SessionItem{Session: s}
104120
}
105121
m.list.SetItems(items)
122+
m.state = stateNormal
106123
return m, nil
107124

108125
case actionCompleteMsg:
109126
if msg.err != nil {
110127
m.err = msg.err
128+
m.state = stateNormal
129+
m.pending = Action{}
130+
return m, nil
111131
}
112132
m.state = stateNormal
113133
m.pending = Action{}
@@ -116,6 +136,11 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
116136

117137
case tea.KeyMsg:
118138
return m.handleKey(msg)
139+
140+
case spinner.TickMsg:
141+
var cmd tea.Cmd
142+
m.spinner, cmd = m.spinner.Update(msg)
143+
return m, cmd
119144
}
120145

121146
var cmd tea.Cmd
@@ -164,6 +189,9 @@ func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
164189
m.modal = NewModal("Confirm", action.Confirm)
165190
return m, nil
166191
}
192+
// Show loading state for action
193+
m.state = stateLoading
194+
m.loadingMessage = "Processing..."
167195
return m, m.executeAction(action)
168196
}
169197

@@ -192,14 +220,22 @@ func (m Model) View() string {
192220
return ""
193221
}
194222

195-
if m.err != nil {
196-
return errorStyle.Render("Error: " + m.err.Error())
197-
}
198-
199223
// Build main view
200-
listView := m.list.View()
201-
helpBar := helpStyle.Render("[q] quit " + m.handler.HelpString())
202-
mainView := lipgloss.JoinVertical(lipgloss.Left, listView, helpBar)
224+
mainView := m.list.View()
225+
226+
// Overlay loading spinner if loading
227+
if m.state == stateLoading {
228+
w, h := m.width, m.height
229+
if w == 0 {
230+
w = 80
231+
}
232+
if h == 0 {
233+
h = 24
234+
}
235+
loadingView := lipgloss.JoinHorizontal(lipgloss.Left, m.spinner.View(), " "+m.loadingMessage)
236+
modal := NewModal("", loadingView)
237+
return modal.Overlay(mainView, w, h)
238+
}
203239

204240
// Overlay modal if confirming
205241
if m.state == stateConfirming {

internal/tui/styles.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
var (
1111
colorGreen = lipgloss.Color("#a6e3a1")
1212
colorYellow = lipgloss.Color("#f9e2af")
13-
colorRed = lipgloss.Color("#f38ba8")
1413
colorBlue = lipgloss.Color("#89b4fa")
1514
colorGray = lipgloss.Color("#6c7086")
1615
colorWhite = lipgloss.Color("#cdd6f4")
@@ -32,10 +31,6 @@ var (
3231
recycledStyle = lipgloss.NewStyle().
3332
Foreground(colorYellow)
3433

35-
// Help text style.
36-
helpStyle = lipgloss.NewStyle().
37-
Foreground(colorGray)
38-
3934
// Selected item style.
4035
selectedStyle = lipgloss.NewStyle().
4136
Foreground(colorWhite).
@@ -44,10 +39,6 @@ var (
4439
// Normal item style.
4540
normalStyle = lipgloss.NewStyle().
4641
Foreground(colorGray)
47-
48-
// Error message style.
49-
errorStyle = lipgloss.NewStyle().
50-
Foreground(colorRed)
5142
)
5243

5344
// Modal styles using lipgloss v2 for canvas/layer support.
@@ -64,4 +55,8 @@ var (
6455
modalHelpStyle = lipglossv2.NewStyle().
6556
Foreground(lipglossv2.Color("#6c7086")).
6657
MarginTop(1)
58+
59+
// Spinner style.
60+
spinnerStyle = lipgloss.NewStyle().
61+
Foreground(colorBlue)
6762
)

0 commit comments

Comments
 (0)