summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-05 19:20:18 +0200
committerPaul Buetow <paul@buetow.org>2026-03-05 19:20:18 +0200
commitbab716a6a5931c211fad2f17ee2f67e8d182a7fc (patch)
tree89b16ed3bf85198dd63751d2e1e54537c9340a1b /internal
parent96225fb6159212a8851043a08d781aba721b4e78 (diff)
feat(tui): migrate Bubble Tea stack to charm.land v2
Diffstat (limited to 'internal')
-rw-r--r--internal/tui/common/keys.go2
-rw-r--r--internal/tui/common/styles.go2
-rw-r--r--internal/tui/dashboard/files.go2
-rw-r--r--internal/tui/dashboard/histogram_test.go2
-rw-r--r--internal/tui/dashboard/model.go8
-rw-r--r--internal/tui/dashboard/model_test.go78
-rw-r--r--internal/tui/dashboard/overview.go2
-rw-r--r--internal/tui/dashboard/overview_test.go2
-rw-r--r--internal/tui/dashboard/processes.go2
-rw-r--r--internal/tui/dashboard/syscalls.go2
-rw-r--r--internal/tui/dashboard/tabs.go2
-rw-r--r--internal/tui/eventstream/exportmodal.go8
-rw-r--r--internal/tui/eventstream/filtermodal.go10
-rw-r--r--internal/tui/eventstream/filtermodal_test.go50
-rw-r--r--internal/tui/eventstream/model.go29
-rw-r--r--internal/tui/eventstream/render.go2
-rw-r--r--internal/tui/eventstream/render_test.go2
-rw-r--r--internal/tui/eventstream/searchmodal.go8
-rw-r--r--internal/tui/export/model.go4
-rw-r--r--internal/tui/export/model_test.go6
-rw-r--r--internal/tui/pidpicker/model.go24
-rw-r--r--internal/tui/pidpicker/model_test.go16
-rw-r--r--internal/tui/probes/model.go10
-rw-r--r--internal/tui/probes/model_test.go8
-rw-r--r--internal/tui/tui.go44
-rw-r--r--internal/tui/tui_test.go68
26 files changed, 201 insertions, 192 deletions
diff --git a/internal/tui/common/keys.go b/internal/tui/common/keys.go
index ba17998..31fdf64 100644
--- a/internal/tui/common/keys.go
+++ b/internal/tui/common/keys.go
@@ -1,6 +1,6 @@
package common
-import "github.com/charmbracelet/bubbles/key"
+import "charm.land/bubbles/v2/key"
// HelpSection groups related key bindings under a shared heading.
type HelpSection struct {
diff --git a/internal/tui/common/styles.go b/internal/tui/common/styles.go
index d4c75ff..06ed596 100644
--- a/internal/tui/common/styles.go
+++ b/internal/tui/common/styles.go
@@ -1,6 +1,6 @@
package common
-import "github.com/charmbracelet/lipgloss"
+import "charm.land/lipgloss/v2"
var (
// Palette colors shared across the TUI package.
diff --git a/internal/tui/dashboard/files.go b/internal/tui/dashboard/files.go
index 80e3037..0393553 100644
--- a/internal/tui/dashboard/files.go
+++ b/internal/tui/dashboard/files.go
@@ -7,7 +7,7 @@ import (
"sort"
"strconv"
- "github.com/charmbracelet/bubbles/table"
+ "charm.land/bubbles/v2/table"
)
type DirSnapshot struct {
diff --git a/internal/tui/dashboard/histogram_test.go b/internal/tui/dashboard/histogram_test.go
index 7790394..48297a2 100644
--- a/internal/tui/dashboard/histogram_test.go
+++ b/internal/tui/dashboard/histogram_test.go
@@ -6,7 +6,7 @@ import (
"ior/internal/statsengine"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
)
func TestRenderHistogramNoBuckets(t *testing.T) {
diff --git a/internal/tui/dashboard/model.go b/internal/tui/dashboard/model.go
index fc9caf6..39150e8 100644
--- a/internal/tui/dashboard/model.go
+++ b/internal/tui/dashboard/model.go
@@ -8,8 +8,8 @@ import (
"strings"
"time"
- "github.com/charmbracelet/bubbles/key"
- tea "github.com/charmbracelet/bubbletea"
+ "charm.land/bubbles/v2/key"
+ tea "charm.land/bubbletea/v2"
)
const defaultRefreshMs = 1000
@@ -288,7 +288,7 @@ func (m *Model) SetPidFilter(pid int) {
}
// View renders the tab bar, active tab scaffold, and help bar.
-func (m Model) View() string {
+func (m Model) View() tea.View {
width, height := common.EffectiveViewport(m.width, m.height)
activeHeight := height
streamModel := m.streamModel
@@ -319,7 +319,7 @@ func (m Model) View() string {
} else {
b.WriteString(renderHelpHint(width))
}
- return common.ScreenStyle.Render(b.String())
+ return tea.NewView(common.ScreenStyle.Render(b.String()))
}
func tickCmd(d time.Duration) tea.Cmd {
diff --git a/internal/tui/dashboard/model_test.go b/internal/tui/dashboard/model_test.go
index 87b60e3..642c702 100644
--- a/internal/tui/dashboard/model_test.go
+++ b/internal/tui/dashboard/model_test.go
@@ -12,7 +12,7 @@ import (
"ior/internal/tui/eventstream"
"ior/internal/tui/messages"
- tea "github.com/charmbracelet/bubbletea"
+ tea "charm.land/bubbletea/v2"
)
type fakeSnapshotSource struct {
@@ -28,31 +28,31 @@ func (f *fakeSnapshotSource) Snapshot() *statsengine.Snapshot {
func TestKeySwitchingChangesActiveTab(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'2'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'2'}[0], Text: string([]rune{'2'})})
model := next.(Model)
if model.activeTab != TabSyscalls {
t.Fatalf("expected syscalls tab, got %v", model.activeTab)
}
- next, _ = model.Update(tea.KeyMsg{Type: tea.KeyTab})
+ next, _ = model.Update(tea.KeyPressMsg{Code: tea.KeyTab})
model = next.(Model)
if model.activeTab != TabFiles {
t.Fatalf("expected next tab to be files, got %v", model.activeTab)
}
- next, _ = model.Update(tea.KeyMsg{Type: tea.KeyShiftTab})
+ next, _ = model.Update(tea.KeyPressMsg{Code: tea.KeyTab, Mod: tea.ModShift})
model = next.(Model)
if model.activeTab != TabSyscalls {
t.Fatalf("expected previous tab to be syscalls, got %v", model.activeTab)
}
- next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'7'}})
+ next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'7'}[0], Text: string([]rune{'7'})})
model = next.(Model)
if model.activeTab != TabStream {
t.Fatalf("expected stream tab on key 7, got %v", model.activeTab)
}
- next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'6'}})
+ next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'6'}[0], Text: string([]rune{'6'})})
model = next.(Model)
if model.activeTab != TabStream {
t.Fatalf("expected stream tab on key 6, got %v", model.activeTab)
@@ -62,25 +62,25 @@ func TestKeySwitchingChangesActiveTab(t *testing.T) {
func TestArrowAndViKeysDoNotCycleTabs(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRight})
+ next, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyRight})
model := next.(Model)
if model.activeTab != TabOverview {
t.Fatalf("expected right arrow not to change tabs, got %v", model.activeTab)
}
- next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'l'}})
+ next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'l'}[0], Text: string([]rune{'l'})})
model = next.(Model)
if model.activeTab != TabOverview {
t.Fatalf("expected l not to change tabs, got %v", model.activeTab)
}
- next, _ = model.Update(tea.KeyMsg{Type: tea.KeyLeft})
+ next, _ = model.Update(tea.KeyPressMsg{Code: tea.KeyLeft})
model = next.(Model)
if model.activeTab != TabOverview {
t.Fatalf("expected left arrow not to change tabs, got %v", model.activeTab)
}
- next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'h'}})
+ next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'h'}[0], Text: string([]rune{'h'})})
model = next.(Model)
if model.activeTab != TabOverview {
t.Fatalf("expected h not to change tabs, got %v", model.activeTab)
@@ -93,13 +93,13 @@ func TestSyscallsTabScrollsWithJK(t *testing.T) {
snap := statsengine.NewSnapshot(nil, nil, nil, []statsengine.SyscallSnapshot{{Name: "read", Count: 1}, {Name: "write", Count: 1}}, nil, nil, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{})
m.latest = &snap
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'j'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: string([]rune{'j'})})
model := next.(Model)
if model.syscallsOffset != 1 {
t.Fatalf("expected offset 1 after j, got %d", model.syscallsOffset)
}
- next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'k'}})
+ next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'k'}[0], Text: string([]rune{'k'})})
model = next.(Model)
if model.syscallsOffset != 0 {
t.Fatalf("expected offset 0 after k, got %d", model.syscallsOffset)
@@ -112,13 +112,13 @@ func TestProcessesTabScrollsWithJK(t *testing.T) {
snap := statsengine.NewSnapshot(nil, nil, nil, nil, nil, []statsengine.ProcessSnapshot{{PID: 1}, {PID: 2}}, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{})
m.latest = &snap
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'j'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: string([]rune{'j'})})
model := next.(Model)
if model.processesOffset != 1 {
t.Fatalf("expected processes offset 1 after j, got %d", model.processesOffset)
}
- next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'k'}})
+ next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'k'}[0], Text: string([]rune{'k'})})
model = next.(Model)
if model.processesOffset != 0 {
t.Fatalf("expected processes offset 0 after k, got %d", model.processesOffset)
@@ -131,13 +131,13 @@ func TestFilesTabScrollsWithJK(t *testing.T) {
snap := statsengine.NewSnapshot(nil, nil, nil, nil, []statsengine.FileSnapshot{{Path: "/a"}, {Path: "/b"}}, nil, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{})
m.latest = &snap
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'j'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: string([]rune{'j'})})
model := next.(Model)
if model.filesOffset != 1 {
t.Fatalf("expected files offset 1 after j, got %d", model.filesOffset)
}
- next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'k'}})
+ next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'k'}[0], Text: string([]rune{'k'})})
model = next.(Model)
if model.filesOffset != 0 {
t.Fatalf("expected files offset 0 after k, got %d", model.filesOffset)
@@ -155,7 +155,7 @@ func TestFilesTabGroupedScrollUsesDirectoryOffset(t *testing.T) {
}, nil, statsengine.HistogramSnapshot{}, statsengine.HistogramSnapshot{})
m.latest = &snap
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'j'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: string([]rune{'j'})})
model := next.(Model)
if model.filesDirOffset != 1 {
t.Fatalf("expected grouped dir offset 1 after j, got %d", model.filesDirOffset)
@@ -171,7 +171,7 @@ func TestStreamSpaceUnpauseSchedulesStreamTick(t *testing.T) {
m.activeTab = TabStream
m.streamModel.HandleKey("space") // pause
- next, cmd := m.Update(tea.KeyMsg{Type: tea.KeySpace})
+ next, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeySpace})
_ = next
if cmd == nil {
t.Fatalf("expected stream tick command when unpausing stream")
@@ -200,34 +200,34 @@ func TestStreamPausedSupportsJKArrowsAndPageKeys(t *testing.T) {
m.streamModel.Refresh()
_ = m.View()
- next, _ = m.Update(tea.KeyMsg{Type: tea.KeySpace}) // pause
+ next, _ = m.Update(tea.KeyPressMsg{Code: tea.KeySpace}) // pause
m = next.(Model)
- before := rowFromStreamView(t, m.View())
+ before := rowFromStreamView(t, m.View().Content)
- next, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'k'}})
+ next, _ = m.Update(tea.KeyPressMsg{Code: []rune{'k'}[0], Text: string([]rune{'k'})})
m = next.(Model)
- afterK := rowFromStreamView(t, m.View())
+ afterK := rowFromStreamView(t, m.View().Content)
if afterK >= before {
t.Fatalf("expected k to scroll up while paused: before=%d afterK=%d", before, afterK)
}
- next, _ = m.Update(tea.KeyMsg{Type: tea.KeyDown})
+ next, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyDown})
m = next.(Model)
- afterDown := rowFromStreamView(t, m.View())
+ afterDown := rowFromStreamView(t, m.View().Content)
if afterDown <= afterK {
t.Fatalf("expected down arrow to scroll down while paused: afterK=%d afterDown=%d", afterK, afterDown)
}
- next, _ = m.Update(tea.KeyMsg{Type: tea.KeyPgUp})
+ next, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyPgUp})
m = next.(Model)
- afterPgUp := rowFromStreamView(t, m.View())
+ afterPgUp := rowFromStreamView(t, m.View().Content)
if afterPgUp >= afterDown {
t.Fatalf("expected pgup to scroll up while paused: afterDown=%d afterPgUp=%d", afterDown, afterPgUp)
}
- next, _ = m.Update(tea.KeyMsg{Type: tea.KeyPgDown})
+ next, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyPgDown})
m = next.(Model)
- afterPgDown := rowFromStreamView(t, m.View())
+ afterPgDown := rowFromStreamView(t, m.View().Content)
if afterPgDown <= afterPgUp {
t.Fatalf("expected pgdown to scroll down while paused: afterPgUp=%d afterPgDown=%d", afterPgUp, afterPgDown)
}
@@ -251,14 +251,14 @@ func TestDirGroupKeyTogglesOnlyOnFilesTab(t *testing.T) {
m := NewModelWithConfig(nil, nil, 250, common.DefaultKeyMap())
m.activeTab = TabFiles
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'d'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'d'}[0], Text: string([]rune{'d'})})
model := next.(Model)
if !model.filesDirGrouped {
t.Fatalf("expected filesDirGrouped to toggle on files tab")
}
model.activeTab = TabOverview
- next, _ = model.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'d'}})
+ next, _ = model.Update(tea.KeyPressMsg{Code: []rune{'d'}[0], Text: string([]rune{'d'})})
model = next.(Model)
if !model.filesDirGrouped {
t.Fatalf("expected filesDirGrouped unchanged outside files tab")
@@ -272,7 +272,7 @@ func TestScrollOffsetDoesNotGrowUnbounded(t *testing.T) {
m.latest = &snap
for i := 0; i < 50; i++ {
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'j'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'j'}[0], Text: string([]rune{'j'})})
m = next.(Model)
}
if m.syscallsOffset != 1 {
@@ -284,7 +284,7 @@ func TestRefreshKeyEmitsRefreshTick(t *testing.T) {
snap := &statsengine.Snapshot{TotalSyscalls: 13}
engine := &fakeSnapshotSource{snap: snap}
m := NewModelWithConfig(engine, nil, 250, common.DefaultKeyMap())
- next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'r'}})
+ next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'r'}[0], Text: string([]rune{'r'})})
_ = next
if cmd == nil {
t.Fatalf("expected refresh command")
@@ -366,7 +366,7 @@ func TestStatsTickClampsGroupedFilesOffset(t *testing.T) {
func TestViewRendersTabBarAndHelp(t *testing.T) {
m := NewModelWithConfig(nil, nil, 1000, common.DefaultKeyMap())
- out := m.View()
+ out := m.View().Content
if !strings.Contains(out, "Overview") {
t.Fatalf("expected overview label in view")
}
@@ -405,7 +405,7 @@ func TestStreamTabViewKeepsTabAndHelpChromeVisible(t *testing.T) {
m.streamModel.SetSource(rb)
m.streamModel.Refresh()
- out := m.View()
+ out := m.View().Content
if !strings.Contains(out, "1:Overview") {
t.Fatalf("expected tab bar to remain visible in stream view")
}
@@ -416,21 +416,21 @@ func TestStreamTabViewKeepsTabAndHelpChromeVisible(t *testing.T) {
func TestHelpToggleWithH(t *testing.T) {
m := NewModelWithConfig(nil, nil, 1000, common.DefaultKeyMap())
- out := m.View()
+ out := m.View().Content
if !strings.Contains(out, "press H for help") {
t.Fatalf("expected default help hint")
}
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'H'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'H'}[0], Text: string([]rune{'H'})})
m = next.(Model)
- out = m.View()
+ out = m.View().Content
if !strings.Contains(out, "tab next tab") {
t.Fatalf("expected expanded help after pressing h")
}
- next, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'H'}})
+ next, _ = m.Update(tea.KeyPressMsg{Code: []rune{'H'}[0], Text: string([]rune{'H'})})
m = next.(Model)
- out = m.View()
+ out = m.View().Content
if !strings.Contains(out, "press H for help") {
t.Fatalf("expected help hint after pressing h again")
}
diff --git a/internal/tui/dashboard/overview.go b/internal/tui/dashboard/overview.go
index 5b8fab8..3ddeaf6 100644
--- a/internal/tui/dashboard/overview.go
+++ b/internal/tui/dashboard/overview.go
@@ -8,7 +8,7 @@ import (
"time"
"unicode/utf8"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
)
func renderOverview(snap *statsengine.Snapshot, width, height int) string {
diff --git a/internal/tui/dashboard/overview_test.go b/internal/tui/dashboard/overview_test.go
index 9895490..7de411c 100644
--- a/internal/tui/dashboard/overview_test.go
+++ b/internal/tui/dashboard/overview_test.go
@@ -7,7 +7,7 @@ import (
"ior/internal/statsengine"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
)
func TestRenderOverviewIncludesCoreMetrics(t *testing.T) {
diff --git a/internal/tui/dashboard/processes.go b/internal/tui/dashboard/processes.go
index 281a86a..a9de382 100644
--- a/internal/tui/dashboard/processes.go
+++ b/internal/tui/dashboard/processes.go
@@ -6,7 +6,7 @@ import (
"strconv"
"strings"
- "github.com/charmbracelet/bubbles/table"
+ "charm.land/bubbles/v2/table"
)
func renderProcesses(snap *statsengine.Snapshot, width, height int) string {
diff --git a/internal/tui/dashboard/syscalls.go b/internal/tui/dashboard/syscalls.go
index 23fe37c..31f719e 100644
--- a/internal/tui/dashboard/syscalls.go
+++ b/internal/tui/dashboard/syscalls.go
@@ -6,7 +6,7 @@ import (
"strconv"
"time"
- "github.com/charmbracelet/bubbles/table"
+ "charm.land/bubbles/v2/table"
)
func renderSyscalls(snap *statsengine.Snapshot, width, height int) string {
diff --git a/internal/tui/dashboard/tabs.go b/internal/tui/dashboard/tabs.go
index df8f03e..62c7762 100644
--- a/internal/tui/dashboard/tabs.go
+++ b/internal/tui/dashboard/tabs.go
@@ -6,7 +6,7 @@ import (
"strings"
"unicode/utf8"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
)
// Tab is a dashboard tab identifier.
diff --git a/internal/tui/eventstream/exportmodal.go b/internal/tui/eventstream/exportmodal.go
index cf020f7..70c57ee 100644
--- a/internal/tui/eventstream/exportmodal.go
+++ b/internal/tui/eventstream/exportmodal.go
@@ -3,9 +3,9 @@ package eventstream
import (
"strings"
- "github.com/charmbracelet/bubbles/textinput"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/textinput"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
)
type ExportModal struct {
@@ -18,7 +18,7 @@ func NewExportModal() ExportModal {
input := textinput.New()
input.Prompt = ""
input.CharLimit = 0
- input.Width = 44
+ input.SetWidth(44)
return ExportModal{textInput: input}
}
diff --git a/internal/tui/eventstream/filtermodal.go b/internal/tui/eventstream/filtermodal.go
index f98db7f..4090925 100644
--- a/internal/tui/eventstream/filtermodal.go
+++ b/internal/tui/eventstream/filtermodal.go
@@ -5,9 +5,9 @@ import (
"strconv"
"strings"
- "github.com/charmbracelet/bubbles/textinput"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/textinput"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
)
type fieldKey int
@@ -48,7 +48,7 @@ func NewFilterModal() FilterModal {
input := textinput.New()
input.Prompt = ""
input.CharLimit = 0
- input.Width = 24
+ input.SetWidth(24)
m := FilterModal{textInput: input}
m.fields = defaultFilterFields()
@@ -112,7 +112,7 @@ func (m FilterModal) Update(msg tea.Msg) FilterModal {
m.fields[m.activeField].opIndex = (m.fields[m.activeField].opIndex + 1) % len(compareOps)
}
return m
- case " ":
+ case " ", "space":
if !m.editing && m.fields[m.activeField].fieldKey == fieldErrorsOnly {
if strings.TrimSpace(m.fields[m.activeField].value) == "true" {
m.fields[m.activeField].value = "false"
diff --git a/internal/tui/eventstream/filtermodal_test.go b/internal/tui/eventstream/filtermodal_test.go
index ee53c82..a33cbb1 100644
--- a/internal/tui/eventstream/filtermodal_test.go
+++ b/internal/tui/eventstream/filtermodal_test.go
@@ -3,7 +3,7 @@ package eventstream
import (
"testing"
- tea "github.com/charmbracelet/bubbletea"
+ tea "charm.land/bubbletea/v2"
)
func TestFilterModalOpenClose(t *testing.T) {
@@ -17,7 +17,7 @@ func TestFilterModalOpenClose(t *testing.T) {
t.Fatalf("modal should be visible after open")
}
- m = m.Update(tea.KeyMsg{Type: tea.KeyEsc})
+ m = m.Update(tea.KeyPressMsg{Code: tea.KeyEsc})
if m.Visible() {
t.Fatalf("modal should close on esc")
}
@@ -29,11 +29,11 @@ func TestFilterModalNavigateFields(t *testing.T) {
t.Fatalf("activeField=%d, want 0", m.activeField)
}
- m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")})
+ m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))})
if m.activeField != 1 {
t.Fatalf("activeField=%d, want 1", m.activeField)
}
- m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("k")})
+ m = m.Update(tea.KeyPressMsg{Code: []rune("k")[0], Text: string([]rune("k"))})
if m.activeField != 0 {
t.Fatalf("activeField=%d, want 0", m.activeField)
}
@@ -43,34 +43,34 @@ func TestFilterModalEditAndBuildFilter(t *testing.T) {
m := NewFilterModal().Open(Filter{})
// Syscall = read
- m = m.Update(tea.KeyMsg{Type: tea.KeyEnter})
- m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("read")})
- m = m.Update(tea.KeyMsg{Type: tea.KeyEnter})
+ m = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
+ m = m.Update(tea.KeyPressMsg{Code: []rune("read")[0], Text: string([]rune("read"))})
+ m = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
// PID >= 123
- m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")})
- m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")})
- m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")})
- m = m.Update(tea.KeyMsg{Type: tea.KeyTab}) // '=' -> '>'
- m = m.Update(tea.KeyMsg{Type: tea.KeyEnter})
- m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("123")})
- m = m.Update(tea.KeyMsg{Type: tea.KeyEnter})
+ m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))})
+ m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))})
+ m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))})
+ m = m.Update(tea.KeyPressMsg{Code: tea.KeyTab}) // '=' -> '>'
+ m = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
+ m = m.Update(tea.KeyPressMsg{Code: []rune("123")[0], Text: string([]rune("123"))})
+ m = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
// Latency >= 1ms
- m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")})
- m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")})
- m = m.Update(tea.KeyMsg{Type: tea.KeyTab}) // '=' -> '>='
- m = m.Update(tea.KeyMsg{Type: tea.KeyEnter})
- m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("1ms")})
- m = m.Update(tea.KeyMsg{Type: tea.KeyEnter})
+ m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))})
+ m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))})
+ m = m.Update(tea.KeyPressMsg{Code: tea.KeyTab}) // '=' -> '>='
+ m = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
+ m = m.Update(tea.KeyPressMsg{Code: []rune("1ms")[0], Text: string([]rune("1ms"))})
+ m = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
// ErrorsOnly = true
for m.activeField < len(m.fields)-1 {
- m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("j")})
+ m = m.Update(tea.KeyPressMsg{Code: []rune("j")[0], Text: string([]rune("j"))})
}
- m = m.Update(tea.KeyMsg{Type: tea.KeySpace})
+ m = m.Update(tea.KeyPressMsg{Code: tea.KeySpace})
- m = m.Update(tea.KeyMsg{Type: tea.KeyEsc})
+ m = m.Update(tea.KeyPressMsg{Code: tea.KeyEsc})
if m.Visible() {
t.Fatalf("modal should close on esc")
}
@@ -98,8 +98,8 @@ func TestFilterModalClearAll(t *testing.T) {
}
m := NewFilterModal().Open(initial)
- m = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("c")})
- m = m.Update(tea.KeyMsg{Type: tea.KeyEsc})
+ m = m.Update(tea.KeyPressMsg{Code: []rune("c")[0], Text: string([]rune("c"))})
+ m = m.Update(tea.KeyPressMsg{Code: tea.KeyEsc})
f := m.Filter()
if f.IsActive() {
diff --git a/internal/tui/eventstream/model.go b/internal/tui/eventstream/model.go
index d9c4ee3..cb05e47 100644
--- a/internal/tui/eventstream/model.go
+++ b/internal/tui/eventstream/model.go
@@ -6,7 +6,7 @@ import (
"strconv"
"strings"
- tea "github.com/charmbracelet/bubbletea"
+ tea "charm.land/bubbletea/v2"
)
const (
@@ -354,7 +354,7 @@ func (m *Model) HandleKey(keyStr string) bool {
// HandleTeaKey handles stream keys based on Bubble Tea key message types first,
// then falls back to string matching for rune-driven shortcuts.
func (m *Model) HandleTeaKey(msg tea.KeyMsg) bool {
- switch msg.Type {
+ switch msg.Key().Code {
case tea.KeyLeft:
return m.HandleKey("left")
case tea.KeyRight:
@@ -373,9 +373,12 @@ func (m *Model) HandleTeaKey(msg tea.KeyMsg) bool {
return m.HandleKey("esc")
case tea.KeyEnter:
return m.HandleKey("enter")
- case tea.KeyRunes:
- if len(msg.Runes) == 1 {
- return m.HandleKey(string(msg.Runes[0]))
+ default:
+ if msg.Key().Text != "" {
+ runes := []rune(msg.Key().Text)
+ if len(runes) == 1 {
+ return m.HandleKey(msg.Key().Text)
+ }
}
}
return m.HandleKey(msg.String())
@@ -810,23 +813,23 @@ func (m *Model) clampSelection() {
func keyMsgFromString(keyStr string) tea.KeyMsg {
switch keyStr {
case "esc":
- return tea.KeyMsg{Type: tea.KeyEsc}
+ return tea.KeyPressMsg{Code: tea.KeyEsc}
case "enter":
- return tea.KeyMsg{Type: tea.KeyEnter}
+ return tea.KeyPressMsg{Code: tea.KeyEnter}
case "tab":
- return tea.KeyMsg{Type: tea.KeyTab}
+ return tea.KeyPressMsg{Code: tea.KeyTab}
case "up":
- return tea.KeyMsg{Type: tea.KeyUp}
+ return tea.KeyPressMsg{Code: tea.KeyUp}
case "down":
- return tea.KeyMsg{Type: tea.KeyDown}
+ return tea.KeyPressMsg{Code: tea.KeyDown}
case " ", "space":
- return tea.KeyMsg{Type: tea.KeySpace}
+ return tea.KeyPressMsg{Code: tea.KeySpace, Text: " "}
}
if keyStr == "" {
- return tea.KeyMsg{}
+ return tea.KeyPressMsg{}
}
runes := []rune(keyStr)
- return tea.KeyMsg{Type: tea.KeyRunes, Runes: runes}
+ return tea.KeyPressMsg{Code: runes[0], Text: keyStr}
}
func rowNumber(start, total int) int {
diff --git a/internal/tui/eventstream/render.go b/internal/tui/eventstream/render.go
index 1f539c6..f93a63d 100644
--- a/internal/tui/eventstream/render.go
+++ b/internal/tui/eventstream/render.go
@@ -6,7 +6,7 @@ import (
"strconv"
"strings"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
)
type columnLayout struct {
diff --git a/internal/tui/eventstream/render_test.go b/internal/tui/eventstream/render_test.go
index b020edf..6240c69 100644
--- a/internal/tui/eventstream/render_test.go
+++ b/internal/tui/eventstream/render_test.go
@@ -4,7 +4,7 @@ import (
"strings"
"testing"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
)
func TestRenderStatusAndFilterLines(t *testing.T) {
diff --git a/internal/tui/eventstream/searchmodal.go b/internal/tui/eventstream/searchmodal.go
index f744d00..94e9cd7 100644
--- a/internal/tui/eventstream/searchmodal.go
+++ b/internal/tui/eventstream/searchmodal.go
@@ -3,9 +3,9 @@ package eventstream
import (
"strings"
- "github.com/charmbracelet/bubbles/textinput"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/textinput"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
)
type SearchDirection int
@@ -26,7 +26,7 @@ func NewSearchModal() SearchModal {
input := textinput.New()
input.Prompt = ""
input.CharLimit = 0
- input.Width = 44
+ input.SetWidth(44)
return SearchModal{textInput: input, direction: SearchForward}
}
diff --git a/internal/tui/export/model.go b/internal/tui/export/model.go
index 57612db..9c77080 100644
--- a/internal/tui/export/model.go
+++ b/internal/tui/export/model.go
@@ -5,8 +5,8 @@ import (
"fmt"
"strings"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
)
// Option is a selectable export target.
diff --git a/internal/tui/export/model_test.go b/internal/tui/export/model_test.go
index a97cd8b..2d47435 100644
--- a/internal/tui/export/model_test.go
+++ b/internal/tui/export/model_test.go
@@ -5,7 +5,7 @@ import (
"strings"
"testing"
- tea "github.com/charmbracelet/bubbletea"
+ tea "charm.land/bubbletea/v2"
)
func TestOpenAndClose(t *testing.T) {
@@ -21,7 +21,7 @@ func TestOpenAndClose(t *testing.T) {
func TestEnterEmitsRequest(t *testing.T) {
m := NewModel().Open()
- next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEnter})
+ next, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
if cmd == nil {
t.Fatalf("expected request command on enter")
}
@@ -40,7 +40,7 @@ func TestEnterEmitsRequest(t *testing.T) {
func TestCancelOptionCloses(t *testing.T) {
m := NewModel().Open()
m.selected = len(optionValues) - 1
- next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyEnter})
+ next, cmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
if cmd != nil {
t.Fatalf("expected no command when selecting cancel")
}
diff --git a/internal/tui/pidpicker/model.go b/internal/tui/pidpicker/model.go
index 73f21ae..fff7614 100644
--- a/internal/tui/pidpicker/model.go
+++ b/internal/tui/pidpicker/model.go
@@ -6,10 +6,10 @@ import (
"ior/internal/tui/messages"