summaryrefslogtreecommitdiff
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
parent96225fb6159212a8851043a08d781aba721b4e78 (diff)
feat(tui): migrate Bubble Tea stack to charm.land v2
-rw-r--r--go.mod32
-rw-r--r--go.sum66
-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
28 files changed, 242 insertions, 249 deletions
diff --git a/go.mod b/go.mod
index 687a48d..2644495 100644
--- a/go.mod
+++ b/go.mod
@@ -3,35 +3,29 @@ module ior
go 1.26.0
require (
+ charm.land/bubbles/v2 v2.0.0
+ charm.land/bubbletea/v2 v2.0.1
+ charm.land/lipgloss/v2 v2.0.0
github.com/DataDog/zstd v1.5.7
github.com/aquasecurity/libbpfgo v0.6.0-libbpf-1.3.0.20240111220235-90dbffffbdab
- github.com/charmbracelet/bubbles v1.0.0
- github.com/charmbracelet/bubbletea v1.3.10
- github.com/charmbracelet/lipgloss v1.1.0
+ github.com/charmbracelet/x/term v0.2.2
github.com/magefile/mage v1.15.0
)
require (
github.com/atotto/clipboard v0.1.4 // indirect
- github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
- github.com/charmbracelet/colorprofile v0.4.1 // indirect
+ github.com/charmbracelet/colorprofile v0.4.2 // indirect
+ github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
- github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
- github.com/charmbracelet/x/term v0.2.2 // indirect
- github.com/clipperhouse/displaywidth v0.9.0 // indirect
- github.com/clipperhouse/stringish v0.1.1 // indirect
- github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
- github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
+ github.com/charmbracelet/x/termios v0.1.1 // indirect
+ github.com/charmbracelet/x/windows v0.2.2 // indirect
+ github.com/clipperhouse/displaywidth v0.11.0 // indirect
+ github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
- github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mattn/go-localereader v0.0.1 // indirect
- github.com/mattn/go-runewidth v0.0.19 // indirect
- github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
+ github.com/mattn/go-runewidth v0.0.20 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
- github.com/muesli/termenv v0.16.0 // indirect
- github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
- golang.org/x/sys v0.38.0 // indirect
- golang.org/x/text v0.3.8 // indirect
+ golang.org/x/sync v0.19.0 // indirect
+ golang.org/x/sys v0.41.0 // indirect
)
diff --git a/go.sum b/go.sum
index 4f3478c..49aaa46 100644
--- a/go.sum
+++ b/go.sum
@@ -1,53 +1,45 @@
+charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
+charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
+charm.land/bubbletea/v2 v2.0.1 h1:B8e9zzK7x9JJ+XvHGF4xnYu9Xa0E0y0MyggY6dbaCfQ=
+charm.land/bubbletea/v2 v2.0.1/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
+charm.land/lipgloss/v2 v2.0.0 h1:sd8N/B3x892oiOjFfBQdXBQp3cAkvjGaU5TvVZC3ivo=
+charm.land/lipgloss/v2 v2.0.0/go.mod h1:w6SnmsBFBmEFBodiEDurGS/sdUY/u1+v72DqUzc6J14=
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/aquasecurity/libbpfgo v0.6.0-libbpf-1.3.0.20240111220235-90dbffffbdab h1:w74AraWsnj+AgEOk2uERlLtECCWutMtuwCGCCWzpBBs=
github.com/aquasecurity/libbpfgo v0.6.0-libbpf-1.3.0.20240111220235-90dbffffbdab/go.mod h1:0rEApF1YBHGuZ4C8OYI9q5oDBVpgqtRqYATePl9mCDk=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
-github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
-github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
-github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
-github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
-github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
-github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
-github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
-github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
-github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
-github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
+github.com/aymanbagabas/go-udiff v0.4.0 h1:TKnLPh7IbnizJIBKFWa9mKayRUBQ9Kh1BPCk6w2PnYM=
+github.com/aymanbagabas/go-udiff v0.4.0/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
+github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
+github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
+github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
+github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
-github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
-github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
+github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
+github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
-github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
-github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
-github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
-github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
-github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
-github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
+github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
+github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
+github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
+github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
+github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
+github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
+github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
+github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
-github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
-github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
-github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
-github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
-github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
-github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
+github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
+github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
-github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
-github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
-github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
-github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
@@ -58,11 +50,9 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
-golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
-golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
-golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
+golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
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"
"strings"
- "github.com/charmbracelet/bubbles/key"
- "github.com/charmbracelet/bubbles/textinput"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/key"
+ "charm.land/bubbles/v2/textinput"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
)
const allPIDsLabel = "All PIDs"
@@ -86,7 +86,7 @@ func NewPIDWithKeys(keys KeyMap) Model {
input.Placeholder = "pid, comm, or cmdline"
input.Focus()
input.CharLimit = 0
- input.Width = 40
+ input.SetWidth(40)
return Model{
input: input,
@@ -117,7 +117,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
- m.input.Width = clamp(msg.Width-16, 10, 100)
+ m.input.SetWidth(clamp(msg.Width-16, 10, 100))
return m, nil
case processesLoadedMsg:
m.processes = msg.processes
@@ -138,17 +138,17 @@ func (m Model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch {
case key.Matches(msg, m.keys.Esc):
return m, tea.Quit
- case msg.Type == tea.KeyCtrlR:
+ case msg.Key().Mod&tea.ModCtrl != 0 && (msg.Key().Code == 'r' || msg.Key().Code == 'R'):
return m, m.scanCmd()
case key.Matches(msg, m.keys.Enter):
return m, m.emitSelection()
- case msg.Type == tea.KeyUp:
+ case msg.Key().Code == tea.KeyUp:
if m.selectedIndex > 0 {
m.selectedIndex--
}
m.input.Blur()
return m, nil
- case msg.Type == tea.KeyDown:
+ case msg.Key().Code == tea.KeyDown:
maxIndex := len(m.filtered)
if m.selectedIndex < maxIndex {
m.selectedIndex++
@@ -157,7 +157,7 @@ func (m Model) updateKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, nil
}
- if msg.Type == tea.KeyRunes && !m.input.Focused() {
+ if msg.Key().Text != "" && !m.input.Focused() {
if key.Matches(msg, m.keys.Refresh) {
return m, m.scanCmd()
}
@@ -240,7 +240,7 @@ func cloneProcesses(in []ProcessInfo) []ProcessInfo {
}
// View renders the PID picker with filter input, list, and help bar.
-func (m Model) View() string {
+func (m Model) View() tea.View {
var b strings.Builder
if m.mode == PickerModeTID {
if m.targetPID > 0 {
@@ -265,7 +265,7 @@ func (m Model) View() string {
b.WriteString("\n")
b.WriteString(helpBarStyle.Render(renderHelp(m.keys.PickerShortHelp())))
- return screenStyle.Render(b.String())
+ return tea.NewView(screenStyle.Render(b.String()))
}
func (m Model) renderRows() string {
diff --git a/internal/tui/pidpicker/model_test.go b/internal/tui/pidpicker/model_test.go
index 2d76508..c47e59b 100644
--- a/internal/tui/pidpicker/model_test.go
+++ b/internal/tui/pidpicker/model_test.go
@@ -5,7 +5,7 @@ import (
"strings"
"testing"
- tea "github.com/charmbracelet/bubbletea"
+ tea "charm.land/bubbletea/v2"
)
func TestApplyFilterByPIDCommAndCmdline(t *testing.T) {
@@ -39,7 +39,7 @@ func TestEnterEmitsAllPIDsAndSelectedPID(t *testing.T) {
m.processes = []ProcessInfo{{Pid: 7, Comm: "vim"}, {Pid: 9, Comm: "top"}}
m.applyFilter()
- modelAny, cmdAny := m.Update(tea.KeyMsg{Type: tea.KeyEnter})
+ modelAny, cmdAny := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
_ = modelAny
msgAny := cmdAny()
pidAny, ok := msgAny.(messages.PidSelectedMsg)
@@ -51,7 +51,7 @@ func TestEnterEmitsAllPIDsAndSelectedPID(t *testing.T) {
}
m.selectedIndex = 2
- modelOne, cmdOne := m.Update(tea.KeyMsg{Type: tea.KeyEnter})
+ modelOne, cmdOne := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
_ = modelOne
msgOne := cmdOne()
pidOne, ok := msgOne.(messages.PidSelectedMsg)
@@ -71,7 +71,7 @@ func TestEnterEmitsAllTIDsAndSelectedTIDInTIDMode(t *testing.T) {
}
m.applyFilter()
- modelAny, cmdAny := m.Update(tea.KeyMsg{Type: tea.KeyEnter})
+ modelAny, cmdAny := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
_ = modelAny
msgAny := cmdAny()
tidAny, ok := msgAny.(messages.TidSelectedMsg)
@@ -86,7 +86,7 @@ func TestEnterEmitsAllTIDsAndSelectedTIDInTIDMode(t *testing.T) {
}
m.selectedIndex = 2
- modelOne, cmdOne := m.Update(tea.KeyMsg{Type: tea.KeyEnter})
+ modelOne, cmdOne := m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
_ = modelOne
msgOne := cmdOne()
tidOne, ok := msgOne.(messages.TidSelectedMsg)
@@ -104,7 +104,7 @@ func TestEnterEmitsAllTIDsAndSelectedTIDInTIDMode(t *testing.T) {
func TestEscQuitsAndRefreshTriggersScan(t *testing.T) {
m := NewWithKeys(DefaultKeyMap())
- _, escCmd := m.Update(tea.KeyMsg{Type: tea.KeyEsc})
+ _, escCmd := m.Update(tea.KeyPressMsg{Code: tea.KeyEsc})
if escCmd == nil {
t.Fatalf("expected esc to return quit cmd")
}
@@ -112,7 +112,7 @@ func TestEscQuitsAndRefreshTriggersScan(t *testing.T) {
t.Fatalf("expected quit msg from esc, got %T", msg)
}
- _, refreshCmd := m.Update(tea.KeyMsg{Type: tea.KeyCtrlR})
+ _, refreshCmd := m.Update(tea.KeyPressMsg{Code: rune('r'), Text: "r", Mod: tea.ModCtrl})
if refreshCmd == nil {
t.Fatalf("expected refresh cmd")
}
@@ -124,7 +124,7 @@ func TestEscQuitsAndRefreshTriggersScan(t *testing.T) {
func TestRuneRDoesNotTriggerRefreshWhileFilterFocused(t *testing.T) {
m := NewWithKeys(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'})})
if cmd == nil {
t.Fatalf("expected textinput update cmd")
}
diff --git a/internal/tui/probes/model.go b/internal/tui/probes/model.go
index 5cec2c7..c50c696 100644
--- a/internal/tui/probes/model.go
+++ b/internal/tui/probes/model.go
@@ -6,9 +6,9 @@ import (
"strings"
"unicode/utf8"
- "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"
)
// Manager defines the probe operations used by the modal.
@@ -45,7 +45,7 @@ func NewModel(manager Manager) Model {
ti := textinput.New()
ti.Prompt = "/ "
ti.CharLimit = 0
- ti.Width = 28
+ ti.SetWidth(28)
return Model{
manager: manager,
textInput: ti,
@@ -110,7 +110,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.textInput.CursorEnd()
m.textInput.Focus()
return m, nil
- case " ", "enter":
+ case " ", "space", "enter":
selected := m.selectedSyscall()
if selected == "" {
return m, nil
diff --git a/internal/tui/probes/model_test.go b/internal/tui/probes/model_test.go
index 73a83bc..3a14675 100644
--- a/internal/tui/probes/model_test.go
+++ b/internal/tui/probes/model_test.go
@@ -5,7 +5,7 @@ import (
"ior/internal/probemanager"
- tea "github.com/charmbracelet/bubbletea"
+ tea "charm.land/bubbletea/v2"
)
type fakeManager struct {
@@ -61,7 +61,7 @@ func TestToggleEmitsProbeToggledMsg(t *testing.T) {
states: []probemanager.ProbeState{{Syscall: "read", Active: true}},
}
m := NewModel(fm).Open()
- next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{' '}})
+ next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{' '}[0], Text: string([]rune{' '})})
if cmd == nil {
t.Fatalf("expected toggle command")
}
@@ -90,7 +90,7 @@ func TestBulkKeysApplyGloballyNotOnlyFiltered(t *testing.T) {
m := NewModel(fm).Open()
m.search = "read"
- _, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'n'}})
+ _, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'n'}[0], Text: string([]rune{'n'})})
if cmd == nil {
t.Fatalf("expected bulk off command")
}
@@ -107,7 +107,7 @@ func TestBulkKeysApplyGloballyNotOnlyFiltered(t *testing.T) {
m = NewModel(fm).Open()
m.search = "read"
fm.toggles = nil
- _, cmd = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'a'}})
+ _, cmd = m.Update(tea.KeyPressMsg{Code: []rune{'a'}[0], Text: string([]rune{'a'})})
if cmd == nil {
t.Fatalf("expected bulk on command")
}
diff --git a/internal/tui/tui.go b/internal/tui/tui.go
index bdd3ab5..24a8ba5 100644
--- a/internal/tui/tui.go
+++ b/internal/tui/tui.go
@@ -20,10 +20,10 @@ import (
"sync"
"time"
- "github.com/charmbracelet/bubbles/key"
- "github.com/charmbracelet/bubbles/spinner"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/key"
+ "charm.land/bubbles/v2/spinner"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
)
// Screen identifies the currently active TUI screen.
@@ -144,7 +144,7 @@ func Run() error {
func RunWithTraceStarter(starter TraceStarter) error {
cfg := flags.Get()
model := newModelWithRuntimeConfig(cfg.PidFilter, cfg.PidFilter, cfg.TUIExportEnable, starter)
- program := tea.NewProgram(model, tea.WithAltScreen())
+ program := tea.NewProgram(model)
_, err := program.Run()
return err
}
@@ -227,9 +227,9 @@ func newModelWithRuntimeConfig(initialPID, startupPidFilter int, exportEnabled b
func (m Model) Init() tea.Cmd {
sizeCmd := initialWindowSizeCmd()
if m.screen == ScreenDashboard && m.attaching {
- return tea.Batch(sizeCmd, tea.WindowSize(), m.spin.Tick, m.beginTraceCmd())
+ return tea.Batch(sizeCmd, tea.RequestWindowSize, m.spin.Tick, m.beginTraceCmd())
}
- return tea.Batch(sizeCmd, tea.WindowSize(), m.pidPicker.Init())
+ return tea.Batch(sizeCmd, tea.RequestWindowSize, m.pidPicker.Init())
}
func initialWindowSizeCmd() tea.Cmd {
@@ -455,40 +455,40 @@ func (m *Model) stopTrace() {
}
// View renders the currently active screen and startup overlay state.
-func (m Model) View() string {
+func (m Model) View() tea.View {
if m.quitting {
- return ""
+ return altScreenView("")
}
width, height := common.EffectiveViewport(m.width, m.height)
if m.attaching {
line := fmt.Sprintf("%s Attaching tracepoints...", m.spin.View())
- return placeToViewport(width, height, ScreenStyle.Render(PanelStyle.Render(line)))
+ return altScreenView(placeToViewport(width, height, ScreenStyle.Render(PanelStyle.Render(line))))
}
if m.lastErr != nil {
- return placeToViewport(width, height, ScreenStyle.Render(ErrorStyle.Render(m.lastErr.Error())))
+ return altScreenView(placeToViewport(width, height, ScreenStyle.Render(ErrorStyle.Render(m.lastErr.Error()))))
}
switch m.screen {
case ScreenPIDPicker:
- base := m.pidPicker.View()
+ base := m.pidPicker.View().Content
if m.exporter.Visible() {
- return placeToViewport(width, height, m.exporter.View(width, height)+"\n"+base)
+ return altScreenView(placeToViewport(width, height, m.exporter.View(width, height)+"\n"+base))
}
- return placeToViewport(width, height, base)
+ return altScreenView(placeToViewport(width, height, base))
case ScreenDashboard:
- base := m.dashboard.View()
+ base := m.dashboard.View().Content
if m.probeModal.Visible() {
- return placeToViewport(width, height, m.probeModal.View(width, height))
+ return altScreenView(placeToViewport(width, height, m.probeModal.View(width, height)))
}
if m.exporter.Visible() {
- return placeToViewport(width, height, m.exporter.View(width, height)+"\n"+base)
+ return altScreenView(placeToViewport(width, height, m.exporter.View(width, height)+"\n"+base))
}
- return placeToViewport(width, height, base)
+ return altScreenView(placeToViewport(width, height, base))
default:
- return ""
+ return altScreenView("")
}
}
@@ -657,3 +657,9 @@ func placeToViewport(width, height int, content string) string {
}
return lipgloss.Place(width, height, lipgloss.Left, lipgloss.Top, content)
}
+
+func altScreenView(content string) tea.View {
+ view := tea.NewView(content)
+ view.AltScreen = true
+ return view
+}
diff --git a/internal/tui/tui_test.go b/internal/tui/tui_test.go
index 890dfc4..5c1ea5f 100644
--- a/internal/tui/tui_test.go
+++ b/internal/tui/tui_test.go
@@ -17,8 +17,8 @@ import (
"ior/internal/flags"
"ior/internal/tui/probes"
- "github.com/charmbracelet/bubbles/key"
- tea "github.com/charmbracelet/bubbletea"
+ "charm.land/bubbles/v2/key"
+ tea "charm.land/bubbletea/v2"
)
type fakeProbeManager struct {
@@ -98,14 +98,14 @@ func TestTracingErrorMessageClearsAttachingState(t *testing.T) {
func TestViewShowsAttachingAndErrorStates(t *testing.T) {
m := NewModel(-1, func(context.Context) error { return nil })
m.attaching = true
- attachingView := m.View()
+ attachingView := m.View().Content
if !strings.Contains(attachingView, "Attaching tracepoints...") {
t.Fatalf("expected attaching view, got %q", attachingView)
}
m.attaching = false
m.lastErr = errors.New("failed")
- errorView := m.View()
+ errorView := m.View().Content
if !strings.Contains(errorView, "failed") {
t.Fatalf("expected error view, got %q", errorView)
}
@@ -114,7 +114,7 @@ func TestViewShowsAttachingAndErrorStates(t *testing.T) {
func TestQuitKeySetsQuittingState(t *testing.T) {
m := NewModel(-1, func(context.Context) error { return nil })
- next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'q'}})
+ next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'q'}[0], Text: string([]rune{'q'})})
if cmd == nil {
t.Fatalf("expected quit cmd")
}
@@ -132,9 +132,9 @@ func TestQuitKeyMatchesSingleBindingWithoutPanic(t *testing.T) {
m := NewModel(-1, func(context.Context) error { return nil })
m.keys.Quit = key.NewBinding(key.WithKeys("x"), key.WithHelp("x", "quit"))
- _, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'z'}})
+ _, _ = m.Update(tea.KeyPressMsg{Code: []rune{'z'}[0], Text: string([]rune{'z'})})
- next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'x'}})
+ next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'x'}[0], Text: string([]rune{'x'})})
if cmd == nil {
t.Fatalf("expected quit cmd")
}
@@ -171,7 +171,7 @@ func TestQuitInvokesTraceStop(t *testing.T) {
close(done)
}
- _, quitCmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'q'}})
+ _, quitCmd := m.Update(tea.KeyPressMsg{Code: []rune{'q'}[0], Text: string([]rune{'q'})})
if quitCmd == nil {
t.Fatalf("expected quit command")
}
@@ -253,12 +253,12 @@ func TestTracingStartedRebindsEventStreamSource(t *testing.T) {
next, _ = m.Update(tea.WindowSizeMsg{Width: 120, Height: 30})
m = next.(Model)
- next, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'7'}})
+ next, _ = m.Update(tea.KeyPressMsg{Code: []rune{'7'}[0], Text: string([]rune{'7'})})
m = next.(Model)
next, _ = m.Update(messages.StatsTickMsg{})
m = next.(Model)
- if !strings.Contains(m.View(), "read") {
+ if !strings.Contains(m.View().Content, "read") {
t.Fatalf("expected stream tab to render rebound stream event")
}
}
@@ -271,7 +271,7 @@ func TestExportKeyOpensModalOnDashboard(t *testing.T) {
m.screen = ScreenDashboard
m.attaching = false
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'e'}[0], Text: string([]rune{'e'})})
updated := next.(Model)
if !updated.exporter.Visible() {
t.Fatalf("expected export modal to open on e key")
@@ -287,7 +287,7 @@ func TestSelectPIDKeyReturnsToFreshPickerAndStopsTrace(t *testing.T) {
stopped := false
m.traceStop = func() { stopped = true }
- next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'p'}})
+ next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'p'}[0], Text: string([]rune{'p'})})
updated := next.(Model)
if !stopped {
@@ -319,7 +319,7 @@ func TestSelectTIDKeyReturnsToPickerWhenPIDFilterIsAll(t *testing.T) {
stopped := false
m.traceStop = func() { stopped = true }
- next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'t'}})
+ next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'t'}[0], Text: string([]rune{'t'})})
updated := next.(Model)
if !stopped {
t.Fatalf("expected tracing stop before tid reselect")
@@ -344,7 +344,7 @@ func TestSelectTIDKeyReturnsToPickerWhenSinglePIDSelected(t *testing.T) {
stopped := false
m.traceStop = func() { stopped = true }
- next, cmd := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'t'}})
+ next, cmd := m.Update(tea.KeyPressMsg{Code: []rune{'t'}[0], Text: string([]rune{'t'})})
updated := next.(Model)
if !stopped {
t.Fatalf("expected tracing stop before tid reselect")
@@ -410,7 +410,7 @@ func TestExportKeyIgnoredWhenExportDisabled(t *testing.T) {
m.screen = ScreenDashboard
m.attaching = false
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'e'}[0], Text: string([]rune{'e'})})
updated := next.(Model)
if updated.exporter.Visible() {
t.Fatalf("expected export modal to remain closed when export is disabled")
@@ -427,23 +427,23 @@ func TestStreamFilterModalConsumesEKeyInsteadOfOpeningExport(t *testing.T) {
m.width = 120
m.height = 30
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'7'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'7'}[0], Text: string([]rune{'7'})})
m = next.(Model)
- next, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'f'}})
+ next, _ = m.Update(tea.KeyPressMsg{Code: []rune{'f'}[0], Text: string([]rune{'f'})})
m = next.(Model)
- next, _ = m.Update(tea.KeyMsg{Type: tea.KeyEnter})
+ next, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyEnter})
m = next.(Model)
for _, r := range []rune{'o', 'p', 'e'} {
- next, _ = m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{r}})
+ next, _ = m.Update(tea.KeyPressMsg{Code: []rune{r}[0], Text: string([]rune{r})})
m = next.(Model)
}
- next, _ = m.Update(tea.KeyMsg{Type: tea.KeyEsc})
+ next, _ = m.Update(tea.KeyPressMsg{Code: tea.KeyEsc})
m = next.(Model)
if m.exporter.Visible() {
t.Fatalf("expected export modal to remain closed while stream filter modal handles typing")
}
- if !strings.Contains(m.View(), "syscall~ope") {
+ if !strings.Contains(m.View().Content, "syscall~ope") {
t.Fatalf("expected typed syscall filter to be applied")
}
}
@@ -475,7 +475,7 @@ func TestRunExportCmdCSVWritesFile(t *testing.T) {
func TestHelpKeyDoesNotToggleOverlay(t *testing.T) {
m := NewModel(-1, func(context.Context) error { return nil })
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'?'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'?'}[0], Text: string([]rune{'?'})})
updated := next.(Model)
if updated.screen != ScreenPIDPicker {
t.Fatalf("expected ? to have no effect, got screen %v", updated.screen)
@@ -488,7 +488,7 @@ func TestViewShowsDashboardWithoutHelpOverlay(t *testing.T) {
m.width = 100
m.height = 30
- out := m.View()
+ out := m.View().Content
if !strings.Contains(out, "press H for help") {
t.Fatalf("expected bottom help hint in dashboard")
}
@@ -498,7 +498,7 @@ func TestQuestionMarkDoesNotBlockUnderlyingActions(t *testing.T) {
m := NewModel(-1, func(context.Context) error { return nil })
m.screen = ScreenDashboard
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'e'}[0], Text: string([]rune{'e'})})
updated := next.(Model)
if !updated.exporter.Visible() {
t.Fatalf("expected export modal to open; ? overlay is removed")
@@ -512,19 +512,19 @@ func TestQuestionMarkDoesNotBreakExportModalInput(t *testing.T) {
m := NewModel(-1, func(context.Context) error { return nil })
m.screen = ScreenDashboard
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'e'}[0], Text: string([]rune{'e'})})
updated := next.(Model)
if !updated.exporter.Visible() {
t.Fatalf("expected export modal to open")
}
- next, _ = updated.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'?'}})
+ next, _ = updated.Update(tea.KeyPressMsg{Code: []rune{'?'}[0], Text: string([]rune{'?'})})
updated = next.(Model)
if !updated.exporter.Visible() {
t.Fatalf("expected export modal to remain open after ? key")
}
- next, _ = updated.Update(tea.KeyMsg{Type: tea.KeyEsc})
+ next, _ = updated.Update(tea.KeyPressMsg{Code: tea.KeyEsc})
updated = next.(Model)
if updated.exporter.Visible() {
t.Fatalf("expected esc to close export modal")
@@ -540,7 +540,7 @@ func TestStatusBarHidesExportBindingWhenExportDisabled(t *testing.T) {
m.width = 100
m.height = 30
- out := m.View()
+ out := m.View().Content
if strings.Contains(out, "e snapshot export") {
t.Fatalf("did not expect export shortcut in status bar when export is disabled")
}
@@ -568,21 +568,21 @@ func TestDashboardTabKeysChangeActiveView(t *testing.T) {
m.width = 120
m.height = 30
- out := m.View()
+ out := m.View().Content
if !strings.Contains(out, "Overview: waiting for stats") {
t.Fatalf("expected overview waiting view by default")
}
- next, _ := m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'2'}})
+ next, _ := m.Update(tea.KeyPressMsg{Code: []rune{'2'}[0], Text: string([]rune{'2'})})
updated := next.(Model)
- out = updated.View()
+ out = updated.View().Content
if !strings.Contains(out, "Syscalls: waiting for stats") {
t.Fatalf("expected syscalls waiting view after pressing 2")
}
- next, _ = updated.Update(tea.KeyMsg{Type: tea.KeyTab})
+ next, _ = updated.Update(tea.KeyPressMsg{Code: tea.KeyTab})
updated = next.(Model)
- out = updated.View()
+ out = updated.View().Content
if !strings.Contains(out, "Files: waiting for stats") {
t.Fatalf("expected files waiting view after tab")
}
@@ -598,7 +598,7 @@ func TestProbeModalViewDoesNotStackDashboardContent(t *testing.T) {
m.height = 30
m.probeModal = m.probeModal.Open()
- out := m.View()
+ out := m.View().Content
if !strings.Contains(out, "Probes (") {
t.Fatalf("expected probe modal content, got %q", out)
}