Compare commits

...

2 Commits

Author SHA1 Message Date
bad8b3abff style(tui): gofmt main.go and menu.go 2026-05-25 19:05:03 +02:00
6598f63cb2 refactor(tui): port to charm v2
bumped bubbletea, lipgloss, bubbles, wish, and huh to v2. Mostly
mechanical chur, new import paths, vewport getter/setter api, view()
returns tea.view now, huh.theme became an interface so the theme package
needed a small reshape. Render plumbing is gone (no more lipgloss.Render
field on the Model or MakeRenderer in main.go). All pages still work
over ssh. setting up for the refund composer next
2026-05-25 19:00:08 +02:00
23 changed files with 248 additions and 277 deletions

2
.gitignore vendored
View File

@@ -98,6 +98,8 @@ docs/
# Migration state (if using a migration tool that creates state files) # Migration state (if using a migration tool that creates state files)
migrations/*.state migrations/*.state
gh-dash/
loadtest/run.pid loadtest/run.pid

55
go.mod
View File

@@ -1,14 +1,14 @@
module terminalShop module terminalShop
go 1.25.7 go 1.25.9
require ( require (
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 charm.land/bubbles/v2 v2.1.0
github.com/charmbracelet/bubbletea v1.3.10 charm.land/bubbletea/v2 v2.0.6
github.com/charmbracelet/huh v0.8.0 charm.land/huh/v2 v2.0.3
github.com/charmbracelet/lipgloss v1.1.0 charm.land/lipgloss/v2 v2.0.3
charm.land/wish/v2 v2.0.1
github.com/charmbracelet/ssh v0.0.0-20250826160808-ebfa259c7309 github.com/charmbracelet/ssh v0.0.0-20250826160808-ebfa259c7309
github.com/charmbracelet/wish v1.4.7
github.com/foize/go.sgr v0.0.0-20140220094842-40bdfc98040c github.com/foize/go.sgr v0.0.0-20140220094842-40bdfc98040c
github.com/go-chi/chi/v5 v5.2.5 github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/cors v1.2.2 github.com/go-chi/cors v1.2.2
@@ -23,7 +23,7 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0
go.opentelemetry.io/otel/sdk v1.43.0 go.opentelemetry.io/otel/sdk v1.43.0
go.opentelemetry.io/otel/trace v1.43.0 go.opentelemetry.io/otel/trace v1.43.0
golang.org/x/crypto v0.49.0 golang.org/x/crypto v0.50.0
gorm.io/driver/postgres v1.6.0 gorm.io/driver/postgres v1.6.0
gorm.io/driver/sqlite v1.6.0 gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.1 gorm.io/gorm v1.31.1
@@ -32,38 +32,35 @@ require (
) )
require ( require (
charm.land/log/v2 v2.0.0 // indirect
github.com/ClickHouse/ch-go v0.61.5 // indirect github.com/ClickHouse/ch-go v0.61.5 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.30.0 // indirect github.com/ClickHouse/clickhouse-go/v2 v2.30.0 // indirect
github.com/andybalholm/brotli v1.1.1 // indirect github.com/andybalholm/brotli v1.1.1 // indirect
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/atotto/clipboard v0.1.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect github.com/catppuccin/go v0.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect
github.com/charmbracelet/keygen v0.5.3 // indirect github.com/charmbracelet/keygen v0.5.4 // indirect
github.com/charmbracelet/log v0.4.1 // indirect github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect github.com/charmbracelet/x/ansi v0.11.7 // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect github.com/charmbracelet/x/conpty v0.1.1 // indirect
github.com/charmbracelet/x/conpty v0.1.0 // indirect github.com/charmbracelet/x/errors v0.0.0-20251110184232-6ab307057ac7 // indirect
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 // indirect github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/input v0.3.4 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.0 // indirect github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.9.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
github.com/creack/pty v1.1.24 // indirect github.com/creack/pty v1.1.24 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-faster/city v1.0.1 // indirect github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.7.1 // indirect github.com/go-faster/errors v0.7.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect
@@ -78,15 +75,11 @@ require (
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.34 // indirect github.com/mattn/go-sqlite3 v1.14.34 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/paulmach/orb v0.11.1 // indirect github.com/paulmach/orb v0.11.1 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect
@@ -104,11 +97,11 @@ require (
go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
golang.org/x/net v0.52.0 // indirect golang.org/x/net v0.52.0 // indirect
golang.org/x/sync v0.20.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.35.0 // indirect golang.org/x/text v0.36.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
google.golang.org/grpc v1.80.0 // indirect google.golang.org/grpc v1.80.0 // indirect

124
go.sum
View File

@@ -1,3 +1,15 @@
charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g=
charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY=
charm.land/bubbletea/v2 v2.0.6 h1:UHN/91OyuhaOFGSrBXQ/hMZD8IO1Uc4BvHlgHXL2WJo=
charm.land/bubbletea/v2 v2.0.6/go.mod h1:MH/D8ZLlN3op37vQvijKuU29g3rqTp+aQapURFonF9g=
charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU=
charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc=
charm.land/lipgloss/v2 v2.0.3 h1:yM2zJ4Cf5Y51b7RHIwioil4ApI/aypFXXVHSwlM6RzU=
charm.land/lipgloss/v2 v2.0.3/go.mod h1:7myLU9iG/3xluAWzpY/fSxYYHCgoKTie7laxk6ATwXA=
charm.land/log/v2 v2.0.0 h1:SY3Cey7ipx86/MBXQHwsguOT6X1exT94mmJRdzTNs+s=
charm.land/log/v2 v2.0.0/go.mod h1:c3cZSRqm20qUVVAR1WmS/7ab8bgha3C6G7DjPcaVZz0=
charm.land/wish/v2 v2.0.1 h1:xYOsvQG/bYNRoKUBt0AEyMiCv9rksmCOG/fD5WifAzg=
charm.land/wish/v2 v2.0.1/go.mod h1:uBWdsKTFk9BE0JsdEv8KKF8/abBfRe/hKEGOBm3OKZA=
github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4= github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4=
github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg= github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg=
github.com/ClickHouse/clickhouse-go/v2 v2.30.0 h1:AG4D/hW39qa58+JHQIFOSnxyL46H6h2lrmGGk17dhFo= github.com/ClickHouse/clickhouse-go/v2 v2.30.0 h1:AG4D/hW39qa58+JHQIFOSnxyL46H6h2lrmGGk17dhFo=
@@ -10,10 +22,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= 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-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
@@ -22,52 +32,38 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws= github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw= github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/keygen v0.5.4 h1:XQYgf6UEaTGgQSSmiPpIQ78WfseNQp4Pz8N/c1OsrdA=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= github.com/charmbracelet/keygen v0.5.4/go.mod h1:t4oBRr41bvK7FaJsAaAQhhkUuHslzFXVjOBwA55CZNM=
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY=
github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
github.com/charmbracelet/keygen v0.5.3 h1:2MSDC62OUbDy6VmjIE2jM24LuXUvKywLCmaJDmr/Z/4=
github.com/charmbracelet/keygen v0.5.3/go.mod h1:TcpNoMAO5GSmhx3SgcEMqCrtn8BahKhB8AlwnLjRUpk=
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/charmbracelet/log v0.4.1 h1:6AYnoHKADkghm/vt4neaNEXkxcXLSV2g1rdyFDOpTyk=
github.com/charmbracelet/log v0.4.1/go.mod h1:pXgyTsqsVu4N9hGdHmQ0xEA4RsXof402LX9ZgiITn2I=
github.com/charmbracelet/ssh v0.0.0-20250826160808-ebfa259c7309 h1:dCVbCRRtg9+tsfiTXTp0WupDlHruAXyp+YoxGVofHHc= github.com/charmbracelet/ssh v0.0.0-20250826160808-ebfa259c7309 h1:dCVbCRRtg9+tsfiTXTp0WupDlHruAXyp+YoxGVofHHc=
github.com/charmbracelet/ssh v0.0.0-20250826160808-ebfa259c7309/go.mod h1:R9cISUs5kAH4Cq/rguNbSwcR+slE5Dfm8FEs//uoIGE= github.com/charmbracelet/ssh v0.0.0-20250826160808-ebfa259c7309/go.mod h1:R9cISUs5kAH4Cq/rguNbSwcR+slE5Dfm8FEs//uoIGE=
github.com/charmbracelet/wish v1.4.7 h1:O+jdLac3s6GaqkOHHSwezejNK04vl6VjO1A+hl8J8Yc= github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468 h1:Q9fO0y1Zo5KB/5Vu8JZoLGm1N3RzF9bNj3Ao3xoR+Ac=
github.com/charmbracelet/wish v1.4.7/go.mod h1:OBZ8vC62JC5cvbxJLh+bIWtG7Ctmct+ewziuUWK+G14= github.com/charmbracelet/ultraviolet v0.0.0-20260416155717-489999b90468/go.mod h1:bAAz7dh/FTYfC+oiHavL4mX1tOIBZ0ZwYjSi3qE6ivM=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk=
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U= github.com/charmbracelet/x/errors v0.0.0-20251110184232-6ab307057ac7 h1:4EG8pCHK5fa8dIxv97VHC8hdkJAz6QNm1WB9BuD/WhY=
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ= github.com/charmbracelet/x/errors v0.0.0-20251110184232-6ab307057ac7/go.mod h1:O2BTD/aMVQDmrvqroIO3fB6zXUuU07ZpVt21QTmZjRg=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= github.com/charmbracelet/x/exp/ordered v0.1.0 h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= github.com/charmbracelet/x/exp/ordered v0.1.0/go.mod h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/input v0.3.4 h1:Mujmnv/4DaitU0p+kIsrlfZl/UlmeLKw1wAP3e1fMN0=
github.com/charmbracelet/x/input v0.3.4/go.mod h1:JI8RcvdZWQIhn09VzeK3hdp4lTz7+yhiEdpEQtZN+2c=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= 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/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY= 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/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/windows v0.2.0 h1:ilXA1GJjTNkgOm94CLPeSz7rar54jtFatdmoiONPuEw= github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
github.com/charmbracelet/x/windows v0.2.0/go.mod h1:ZibNFR49ZFqCXgP76sYanisxRyC+EYrBE7TTknD8s1s= github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI= github.com/charmbracelet/x/xpty v0.1.3 h1:eGSitii4suhzrISYH50ZfufV3v085BXQwIytcOdFSsw=
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4= github.com/charmbracelet/x/xpty v0.1.3/go.mod h1:poPYpWuLDBFCKmKLDnhBp51ATa0ooD8FhypRwEFtH3Y=
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA= github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA= github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
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/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -75,8 +71,6 @@ 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/foize/go.sgr v0.0.0-20140220094842-40bdfc98040c h1:NhHTf8Ewd3NS46DylZB75Zi0UeATvDbDwFEmsO5ZVpQ= github.com/foize/go.sgr v0.0.0-20140220094842-40bdfc98040c h1:NhHTf8Ewd3NS46DylZB75Zi0UeATvDbDwFEmsO5ZVpQ=
@@ -91,8 +85,8 @@ github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -147,25 +141,17 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
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/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk=
github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
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/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= 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/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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
@@ -247,10 +233,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -273,19 +259,17 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=

16
main.go
View File

@@ -11,11 +11,11 @@ import (
"time" "time"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"charm.land/wish/v2"
"charm.land/wish/v2/bubbletea"
"charm.land/wish/v2/logging"
"github.com/charmbracelet/ssh" "github.com/charmbracelet/ssh"
"github.com/charmbracelet/wish"
"github.com/charmbracelet/wish/bubbletea"
"github.com/charmbracelet/wish/logging"
"terminalShop/pkg/auth" "terminalShop/pkg/auth"
"terminalShop/pkg/config" "terminalShop/pkg/config"
@@ -36,9 +36,6 @@ var (
) )
func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) { func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) {
renderer := bubbletea.MakeRenderer(s)
// Extract SSH public key from session
pubKey := s.PublicKey() pubKey := s.PublicKey()
var fingerprint, pubKeyStr string var fingerprint, pubKeyStr string
@@ -47,9 +44,8 @@ func teaHandler(s ssh.Session) (tea.Model, []tea.ProgramOption) {
pubKeyStr = auth.FormatSSHPublicKey(pubKey) pubKeyStr = auth.FormatSSHPublicKey(pubKey)
} }
// Create TUI model with user context (no registration screen needed!) m := tui.NewModelWithAuth(fingerprint, pubKeyStr, apiURL, authFingerprintKey, stripePublicKey)
m := tui.NewModelWithAuth(renderer, fingerprint, pubKeyStr, apiURL, authFingerprintKey, stripePublicKey) return m, nil
return m, []tea.ProgramOption{tea.WithAltScreen()}
} }
func main() { func main() {

View File

@@ -1,9 +1,9 @@
package tui package tui
import ( import (
"github.com/charmbracelet/bubbles/viewport" "charm.land/bubbles/v2/viewport"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
"terminalShop/pkg/models" "terminalShop/pkg/models"
) )
@@ -33,12 +33,12 @@ func (m Model) updateAccountViewport() Model {
} }
if !m.account.viewportReady { if !m.account.viewportReady {
m.account.detailViewport = viewport.New(detailWidth, availableHeight) m.account.detailViewport = viewport.New(viewport.WithWidth(detailWidth), viewport.WithHeight(availableHeight))
m.account.detailViewport.KeyMap = modifiedKeyMap m.account.detailViewport.KeyMap = modifiedKeyMap
m.account.viewportReady = true m.account.viewportReady = true
} else { } else {
m.account.detailViewport.Width = detailWidth m.account.detailViewport.SetWidth(detailWidth)
m.account.detailViewport.Height = availableHeight m.account.detailViewport.SetHeight(availableHeight)
} }
return m return m
@@ -128,8 +128,8 @@ func (m Model) scrollToAccountDetailItem() Model {
} }
targetY := headerOffset + (selectedIndex * itemHeight) targetY := headerOffset + (selectedIndex * itemHeight)
vpH := m.account.detailViewport.Height vpH := m.account.detailViewport.Height()
offset := m.account.detailViewport.YOffset offset := m.account.detailViewport.YOffset()
// 1-item buffer: scroll when the NEXT item would be off-screen, // 1-item buffer: scroll when the NEXT item would be off-screen,
// not when the current item hits the very edge // not when the current item hits the very edge
@@ -336,7 +336,7 @@ func (m Model) AccountUpdate(msg tea.Msg) (Model, tea.Cmd) {
{key: "q", value: "quit"}, {key: "q", value: "quit"},
} }
case 1: case 1:
m.account.orderYOffset = m.account.detailViewport.YOffset m.account.orderYOffset = m.account.detailViewport.YOffset()
m.account.orderViewState = 2 m.account.orderViewState = 2
m.account.detailViewport.GotoTop() m.account.detailViewport.GotoTop()
m.footer = []footerCommand{ m.footer = []footerCommand{

View File

@@ -6,7 +6,7 @@ import (
"terminalShop/pkg/models" "terminalShop/pkg/models"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
) )
func (m Model) OrdersView(width int) string { func (m Model) OrdersView(width int) string {

View File

@@ -1,7 +1,7 @@
package tui package tui
import ( import (
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
) )
func (m Model) BuildBreadcrumbs() string { func (m Model) BuildBreadcrumbs() string {

View File

@@ -3,9 +3,9 @@ package tui
import ( import (
"fmt" "fmt"
"github.com/charmbracelet/bubbles/viewport" "charm.land/bubbles/v2/viewport"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
) )
func (m Model) updateCartViewport() Model { func (m Model) updateCartViewport() Model {
@@ -17,12 +17,12 @@ func (m Model) updateCartViewport() Model {
availH = 1 availH = 1
} }
if !m.cart.viewportReady { if !m.cart.viewportReady {
m.cart.viewport = viewport.New(m.widthContent, availH) m.cart.viewport = viewport.New(viewport.WithWidth(m.widthContent), viewport.WithHeight(availH))
m.cart.viewport.KeyMap = viewport.KeyMap{} m.cart.viewport.KeyMap = viewport.KeyMap{}
m.cart.viewportReady = true m.cart.viewportReady = true
} else { } else {
m.cart.viewport.Width = m.widthContent m.cart.viewport.SetWidth(m.widthContent)
m.cart.viewport.Height = availH m.cart.viewport.SetHeight(availH)
} }
return m return m
} }
@@ -116,11 +116,11 @@ func (m Model) CartView() string {
} }
itemHeight := 4 + 2*boxPadding + 1 itemHeight := 4 + 2*boxPadding + 1
targetY := m.cart.cursor * itemHeight targetY := m.cart.cursor * itemHeight
if targetY < m.cart.viewport.YOffset { if targetY < m.cart.viewport.YOffset() {
m.cart.viewport.SetYOffset(targetY) m.cart.viewport.SetYOffset(targetY)
} }
if targetY+itemHeight > m.cart.viewport.YOffset+m.cart.viewport.Height { if targetY+itemHeight > m.cart.viewport.YOffset()+m.cart.viewport.Height() {
m.cart.viewport.SetYOffset(targetY - m.cart.viewport.Height + itemHeight) m.cart.viewport.SetYOffset(targetY - m.cart.viewport.Height() + itemHeight)
} }
} }
return lipgloss.Place( return lipgloss.Place(

View File

@@ -5,9 +5,9 @@ import (
"strings" "strings"
"terminalShop/pkg/models" "terminalShop/pkg/models"
"github.com/charmbracelet/bubbles/viewport" "charm.land/bubbles/v2/viewport"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
) )
// RenderConfirmation renders the order confirmation view // RenderConfirmation renders the order confirmation view
@@ -73,12 +73,12 @@ func (m Model) updateConfirmViewport() Model {
availH = 1 availH = 1
} }
if !m.confirm.viewportReady { if !m.confirm.viewportReady {
m.confirm.viewport = viewport.New(m.widthContent, availH) m.confirm.viewport = viewport.New(viewport.WithWidth(m.widthContent), viewport.WithHeight(availH))
m.confirm.viewport.KeyMap = viewport.DefaultKeyMap() m.confirm.viewport.KeyMap = viewport.DefaultKeyMap()
m.confirm.viewportReady = true m.confirm.viewportReady = true
} else { } else {
m.confirm.viewport.Width = m.widthContent m.confirm.viewport.SetWidth(m.widthContent)
m.confirm.viewport.Height = availH m.confirm.viewport.SetHeight(availH)
} }
return m return m
} }

View File

@@ -3,7 +3,7 @@ package tui
import ( import (
"strings" "strings"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
) )
func (m Model) BuildFooter() string { func (m Model) BuildFooter() string {

View File

@@ -3,8 +3,8 @@ package tui
import ( import (
"fmt" "fmt"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
"github.com/charmbracelet/lipgloss/table" "charm.land/lipgloss/v2/table"
) )
func (m Model) BuildHeader() string { func (m Model) BuildHeader() string {
@@ -44,7 +44,7 @@ func (m Model) BuildHeader() string {
tabs = []string{logo, shop, account, cart} tabs = []string{logo, shop, account, cart}
} }
tbl := table.New().Border(lipgloss.NormalBorder()).BorderStyle(m.renderer.NewStyle().Foreground(m.theme.Border())).Row(tabs...).Width(m.widthContent).StyleFunc(func(row, col int) lipgloss.Style { tbl := table.New().Border(lipgloss.NormalBorder()).BorderStyle(lipgloss.NewStyle().Foreground(m.theme.Border())).Row(tabs...).Width(m.widthContent).StyleFunc(func(row, col int) lipgloss.Style {
return m.theme.Base().Padding(0, 1).AlignHorizontal(lipgloss.Center) return m.theme.Base().Padding(0, 1).AlignHorizontal(lipgloss.Center)
}).Render() }).Render()

View File

@@ -1,8 +1,8 @@
package tui package tui
import ( import (
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
) )
func (m Model) BuildHelpView() string { func (m Model) BuildHelpView() string {

View File

@@ -1,9 +1,9 @@
package tui package tui
import ( import (
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
"fmt" "fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
) )
func (m Model) BuildMenuView() string { func (m Model) BuildMenuView() string {

View File

@@ -7,10 +7,9 @@ import (
"sort" "sort"
"time" "time"
"github.com/charmbracelet/bubbles/key" "charm.land/bubbles/v2/key"
"github.com/charmbracelet/bubbles/viewport" "charm.land/bubbles/v2/viewport"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/lipgloss"
"github.com/stripe/stripe-go/v78" "github.com/stripe/stripe-go/v78"
stripetoken "github.com/stripe/stripe-go/v78/token" stripetoken "github.com/stripe/stripe-go/v78/token"
@@ -114,8 +113,7 @@ type Model struct {
APIClient *api.Client APIClient *api.Client
// Rendering // Rendering
renderer *lipgloss.Renderer theme theme.Theme
theme theme.Theme
// Checkout flow state (spans multiple pages) // Checkout flow state (spans multiple pages)
CheckingOut bool CheckingOut bool
@@ -240,7 +238,6 @@ func (m Model) CartSwitch() (Model, tea.Cmd) {
m = m.SwitchPage(cartPage) m = m.SwitchPage(cartPage)
m = m.updateCartViewport() m = m.updateCartViewport()
if m.IsCartEmpty() { if m.IsCartEmpty() {
m.footer = []footerCommand{ m.footer = []footerCommand{
{key: "j/k", value: "items"}, {key: "j/k", value: "items"},
{key: "+/-", value: "qty"}, {key: "+/-", value: "qty"},
@@ -755,18 +752,12 @@ func (m Model) SavedCardsIsEmpty() bool {
return len(m.SavedCards) == 0 return len(m.SavedCards) == 0
} }
// NewModel creates a new model using the default renderer. // NewModel creates a new model.
func NewModel(username string) Model { func NewModel(username string) Model {
return newModelWithRenderer(username, lipgloss.DefaultRenderer())
}
// newModelWithRenderer creates a new model with the given renderer.
func newModelWithRenderer(username string, renderer *lipgloss.Renderer) Model {
m := Model{ m := Model{
Username: username, Username: username,
currentPage: splashPage, currentPage: splashPage,
renderer: renderer, theme: theme.BasicTheme(),
theme: theme.BasicTheme(renderer),
Coffees: []models.Coffee{ Coffees: []models.Coffee{
{ {
Name: "Espresso", Name: "Espresso",
@@ -833,8 +824,8 @@ func newModelWithRenderer(username string, renderer *lipgloss.Renderer) Model {
} }
// NewModelWithAuth creates a new model with user authentication context. // NewModelWithAuth creates a new model with user authentication context.
func NewModelWithAuth(renderer *lipgloss.Renderer, fingerprint string, pubKeyStr string, apiURL string, clientSecret string, stripePublicKey string) Model { func NewModelWithAuth(fingerprint string, pubKeyStr string, apiURL string, clientSecret string, stripePublicKey string) Model {
m := newModelWithRenderer("", renderer) m := NewModel("")
m.Fingerprint = fingerprint m.Fingerprint = fingerprint
m.AuthFingerprintKey = clientSecret m.AuthFingerprintKey = clientSecret
m.StripePublicKey = stripePublicKey m.StripePublicKey = stripePublicKey
@@ -860,7 +851,7 @@ func NewModelWithAuth(renderer *lipgloss.Renderer, fingerprint string, pubKeyStr
} }
func newViewport(w, h int) viewport.Model { func newViewport(w, h int) viewport.Model {
return viewport.New(w, h) return viewport.New(viewport.WithWidth(w), viewport.WithHeight(h))
} }
func (m Model) collectCardCmd() tea.Cmd { func (m Model) collectCardCmd() tea.Cmd {
@@ -876,8 +867,10 @@ func (m Model) collectCardCmd() tea.Cmd {
} }
} }
const ordersPollInterval = 10 * time.Second const (
const cardPollTimeout = 10 * time.Minute ordersPollInterval = 10 * time.Second
cardPollTimeout = 10 * time.Minute
)
type OrdersPollTickMsg struct{} type OrdersPollTickMsg struct{}

View File

@@ -5,10 +5,10 @@ import (
"strings" "strings"
"time" "time"
"github.com/charmbracelet/bubbles/viewport" "charm.land/bubbles/v2/viewport"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/huh" "charm.land/huh/v2"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
"terminalShop/pkg/models" "terminalShop/pkg/models"
"terminalShop/pkg/tui/qrfefe" "terminalShop/pkg/tui/qrfefe"
@@ -280,12 +280,12 @@ func (m Model) updatePaymentViewport() Model {
availH = 1 availH = 1
} }
if !m.payment.viewportReady { if !m.payment.viewportReady {
m.payment.viewport = viewport.New(m.widthContent, availH) m.payment.viewport = viewport.New(viewport.WithWidth(m.widthContent), viewport.WithHeight(availH))
m.payment.viewport.KeyMap = viewport.KeyMap{} m.payment.viewport.KeyMap = viewport.KeyMap{}
m.payment.viewportReady = true m.payment.viewportReady = true
} else { } else {
m.payment.viewport.Width = m.widthContent m.payment.viewport.SetWidth(m.widthContent)
m.payment.viewport.Height = availH m.payment.viewport.SetHeight(availH)
} }
return m return m
} }
@@ -301,7 +301,7 @@ func (m Model) PaymentPageView() string {
// Bypass viewport — it strips the raw ANSI codes qrfefe emits. // Bypass viewport — it strips the raw ANSI codes qrfefe emits.
return lipgloss.Place( return lipgloss.Place(
m.widthContainer, m.widthContainer,
m.payment.viewport.Height, m.payment.viewport.Height(),
lipgloss.Center, lipgloss.Center, lipgloss.Center, lipgloss.Center,
m.renderPaymentHttpsView(), m.renderPaymentHttpsView(),
) )
@@ -310,11 +310,11 @@ func (m Model) PaymentPageView() string {
m.payment.viewport.SetContent(content) m.payment.viewport.SetContent(content)
itemHeight := 3 itemHeight := 3
targetY := m.payment.cardCursor * itemHeight targetY := m.payment.cardCursor * itemHeight
if targetY < m.payment.viewport.YOffset { if targetY < m.payment.viewport.YOffset() {
m.payment.viewport.SetYOffset(targetY) m.payment.viewport.SetYOffset(targetY)
} }
if targetY+itemHeight > m.payment.viewport.YOffset+m.payment.viewport.Height { if targetY+itemHeight > m.payment.viewport.YOffset()+m.payment.viewport.Height() {
m.payment.viewport.SetYOffset(targetY - m.payment.viewport.Height + itemHeight + 1) m.payment.viewport.SetYOffset(targetY - m.payment.viewport.Height() + itemHeight + 1)
} }
if m.payment.cardCursor == len(m.SavedCards) { if m.payment.cardCursor == len(m.SavedCards) {
m.payment.viewport.GotoBottom() m.payment.viewport.GotoBottom()

View File

@@ -6,10 +6,10 @@ import (
"terminalShop/pkg/models" "terminalShop/pkg/models"
"github.com/charmbracelet/bubbles/viewport" "charm.land/bubbles/v2/viewport"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/huh" "charm.land/huh/v2"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
) )
// countries defines the available countries with their dial codes. // countries defines the available countries with their dial codes.
@@ -262,12 +262,12 @@ func (m Model) updateShippingViewport() Model {
availH = 1 availH = 1
} }
if !m.shipping.viewportReady { if !m.shipping.viewportReady {
m.shipping.viewport = viewport.New(m.widthContent, availH) m.shipping.viewport = viewport.New(viewport.WithWidth(m.widthContent), viewport.WithHeight(availH))
m.shipping.viewport.KeyMap = viewport.KeyMap{} m.shipping.viewport.KeyMap = viewport.KeyMap{}
m.shipping.viewportReady = true m.shipping.viewportReady = true
} else { } else {
m.shipping.viewport.Width = m.widthContent m.shipping.viewport.SetWidth(m.widthContent)
m.shipping.viewport.Height = availH m.shipping.viewport.SetHeight(availH)
} }
return m return m
} }
@@ -281,11 +281,11 @@ func (m Model) ShippingPageView() string {
m.shipping.viewport.SetContent(content) m.shipping.viewport.SetContent(content)
itemHeight := 4 itemHeight := 4
targetY := m.shipping.addressCursor * itemHeight targetY := m.shipping.addressCursor * itemHeight
if targetY < m.shipping.viewport.YOffset { if targetY < m.shipping.viewport.YOffset() {
m.shipping.viewport.SetYOffset(targetY) m.shipping.viewport.SetYOffset(targetY)
} }
if targetY+itemHeight > m.shipping.viewport.YOffset+m.shipping.viewport.Height { if targetY+itemHeight > m.shipping.viewport.YOffset()+m.shipping.viewport.Height() {
m.shipping.viewport.SetYOffset(targetY - m.shipping.viewport.Height + itemHeight + 1) m.shipping.viewport.SetYOffset(targetY - m.shipping.viewport.Height() + itemHeight + 1)
} }
if m.shipping.addressCursor == len(m.SavedAddresses) { if m.shipping.addressCursor == len(m.SavedAddresses) {
m.shipping.viewport.GotoBottom() m.shipping.viewport.GotoBottom()

View File

@@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/charmbracelet/bubbles/viewport" "charm.land/bubbles/v2/viewport"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
"terminalShop/pkg/models" "terminalShop/pkg/models"
) )
@@ -55,10 +55,10 @@ func (m Model) updateShopViewports() Model {
m.shop.detailViewport.KeyMap = modifiedKeyMap m.shop.detailViewport.KeyMap = modifiedKeyMap
m.shop.viewportsReady = true m.shop.viewportsReady = true
} else { } else {
m.shop.menuViewport.Width = menuWidth m.shop.menuViewport.SetWidth(menuWidth)
m.shop.menuViewport.Height = availableHeight m.shop.menuViewport.SetHeight(availableHeight)
m.shop.detailViewport.Width = detailWidth m.shop.detailViewport.SetWidth(detailWidth)
m.shop.detailViewport.Height = availableHeight m.shop.detailViewport.SetHeight(availableHeight)
} }
return m return m
} }
@@ -226,7 +226,7 @@ func (m Model) shopMoveSelected(previous bool) Model {
if m.shop.viewportsReady { if m.shop.viewportsReady {
targetY := (m.shop.selected + 2) * 1 targetY := (m.shop.selected + 2) * 1
m.shop.menuViewport.SetYOffset(targetY - m.shop.menuViewport.Height/2) m.shop.menuViewport.SetYOffset(targetY - m.shop.menuViewport.Height()/2)
m.shop.detailViewport.GotoTop() m.shop.detailViewport.GotoTop()
} }
return m return m

View File

@@ -5,9 +5,9 @@ import (
"terminalShop/pkg/api" "terminalShop/pkg/api"
"terminalShop/pkg/models" "terminalShop/pkg/models"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
) )
type DelayCompleteMsg struct{} type DelayCompleteMsg struct{}

View File

@@ -3,7 +3,7 @@ package tui
import ( import (
"strings" "strings"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
) )
func (m Model) createBoxInner(content string, selected bool, position lipgloss.Position, paddingH int, paddingV int, totalWidth int) string { func (m Model) createBoxInner(content string, selected bool, position lipgloss.Position, paddingH int, paddingV int, totalWidth int) string {

View File

@@ -1,51 +1,50 @@
package theme package theme
import ( import (
"github.com/charmbracelet/bubbles/help" "charm.land/bubbles/v2/help"
"github.com/charmbracelet/huh" "charm.land/huh/v2"
"github.com/charmbracelet/lipgloss" "charm.land/lipgloss/v2"
) )
// HuhTheme creates a huh form theme using this project's color palette. // HuhTheme creates a huh form theme using this project's color palette.
func HuhTheme(t Theme) *huh.Theme { func HuhTheme(t Theme) huh.Theme {
var th huh.Theme var s huh.Styles
th.FieldSeparator = t.renderer.NewStyle().SetString("\n\n") s.FieldSeparator = lipgloss.NewStyle().SetString("\n\n")
f := &th.Focused f := &s.Focused
f.Base = t.renderer.NewStyle(). f.Base = lipgloss.NewStyle().
PaddingLeft(1). PaddingLeft(1).
BorderStyle(lipgloss.ThickBorder()). BorderStyle(lipgloss.ThickBorder()).
BorderLeft(true). BorderLeft(true).
BorderForeground(t.accent) BorderForeground(t.accent)
f.Title = t.renderer.NewStyle().Foreground(t.body) f.Title = lipgloss.NewStyle().Foreground(t.body)
f.Description = t.renderer.NewStyle().Foreground(t.body) f.Description = lipgloss.NewStyle().Foreground(t.body)
f.TextInput.Cursor = t.renderer.NewStyle().Foreground(t.highlight) f.TextInput.Cursor = lipgloss.NewStyle().Foreground(t.highlight)
f.TextInput.Placeholder = t.renderer.NewStyle().Foreground(t.dim) f.TextInput.Placeholder = lipgloss.NewStyle().Foreground(t.dim)
f.TextInput.Prompt = t.renderer.NewStyle().Foreground(t.accent) f.TextInput.Prompt = lipgloss.NewStyle().Foreground(t.accent)
f.TextInput.Text = t.renderer.NewStyle().Foreground(t.accent) f.TextInput.Text = lipgloss.NewStyle().Foreground(t.accent)
f.ErrorIndicator = t.renderer.NewStyle().Foreground(t.error) f.ErrorIndicator = lipgloss.NewStyle().Foreground(t.error)
f.ErrorMessage = t.renderer.NewStyle().Foreground(t.error) f.ErrorMessage = lipgloss.NewStyle().Foreground(t.error)
f.SelectSelector = t.renderer.NewStyle().Foreground(t.accent).SetString("> ") f.SelectSelector = lipgloss.NewStyle().Foreground(t.accent).SetString("> ")
f.Option = t.renderer.NewStyle().Foreground(t.body) f.Option = lipgloss.NewStyle().Foreground(t.body)
f.SelectedOption = t.renderer.NewStyle().Foreground(t.highlight).Bold(true) f.SelectedOption = lipgloss.NewStyle().Foreground(t.highlight).Bold(true)
f.MultiSelectSelector = t.renderer.NewStyle().Foreground(t.accent).SetString("> ") f.MultiSelectSelector = lipgloss.NewStyle().Foreground(t.accent).SetString("> ")
f.SelectedPrefix = t.renderer.NewStyle().Foreground(t.highlight).SetString("[x] ") f.SelectedPrefix = lipgloss.NewStyle().Foreground(t.highlight).SetString("[x] ")
f.UnselectedPrefix = t.renderer.NewStyle().Foreground(t.dim).SetString("[ ] ") f.UnselectedPrefix = lipgloss.NewStyle().Foreground(t.dim).SetString("[ ] ")
f.UnselectedOption = t.renderer.NewStyle().Foreground(t.body) f.UnselectedOption = lipgloss.NewStyle().Foreground(t.body)
th.Help = help.New().Styles s.Help = help.New().Styles
th.Blurred = copyFieldStyles(*f) s.Blurred = copyFieldStyles(*f)
th.Blurred.Base = th.Blurred.Base.BorderStyle(lipgloss.HiddenBorder()) s.Blurred.Base = s.Blurred.Base.BorderStyle(lipgloss.HiddenBorder())
th.Blurred.Title = th.Blurred.Title.Foreground(t.body) s.Blurred.Title = s.Blurred.Title.Foreground(t.body)
th.Blurred.SelectSelector = t.renderer.NewStyle().SetString(" ") s.Blurred.SelectSelector = lipgloss.NewStyle().SetString(" ")
th.Blurred.SelectedOption = t.renderer.NewStyle().Foreground(t.success).Bold(true) s.Blurred.SelectedOption = lipgloss.NewStyle().Foreground(t.success).Bold(true)
th.Blurred.Option = t.renderer.NewStyle().Foreground(t.dim) s.Blurred.Option = lipgloss.NewStyle().Foreground(t.dim)
return &th return huh.ThemeFunc(func(bool) *huh.Styles { return &s })
} }
// copyFieldStyles returns a copy of FieldStyles (value type, assignment is a copy).
func copyFieldStyles(f huh.FieldStyles) huh.FieldStyles { return f } func copyFieldStyles(f huh.FieldStyles) huh.FieldStyles { return f }

View File

@@ -1,56 +1,55 @@
package theme package theme
import ( import (
"github.com/charmbracelet/huh" "image/color"
"github.com/charmbracelet/lipgloss"
"charm.land/huh/v2"
"charm.land/lipgloss/v2"
"charm.land/lipgloss/v2/compat"
) )
// Theme holds all colors and styles for the terminal coffee shop TUI. // Theme holds all colors and styles for the TUI
type Theme struct { type Theme struct {
renderer *lipgloss.Renderer background color.Color
border color.Color
background lipgloss.TerminalColor body color.Color
border lipgloss.TerminalColor dim color.Color
body lipgloss.TerminalColor accent color.Color
dim lipgloss.TerminalColor highlight color.Color
accent lipgloss.TerminalColor brand color.Color
highlight lipgloss.TerminalColor label color.Color
brand lipgloss.TerminalColor success color.Color
label lipgloss.TerminalColor error color.Color
success lipgloss.TerminalColor
error lipgloss.TerminalColor
base lipgloss.Style base lipgloss.Style
form *huh.Theme form huh.Theme
} }
// BasicTheme constructs the default theme for terminal coffee shop. func BasicTheme() Theme {
func BasicTheme(renderer *lipgloss.Renderer) Theme { t := Theme{}
t := Theme{renderer: renderer}
t.background = lipgloss.AdaptiveColor{Dark: "#000000", Light: "#FBFCFD"} t.background = compat.AdaptiveColor{Dark: lipgloss.Color("#000000"), Light: lipgloss.Color("#FBFCFD")}
t.border = lipgloss.AdaptiveColor{Dark: "#666666", Light: "#D7DBDF"} t.border = compat.AdaptiveColor{Dark: lipgloss.Color("#666666"), Light: lipgloss.Color("#D7DBDF")}
t.body = lipgloss.Color("#AAAAAA") t.body = lipgloss.Color("#AAAAAA")
t.dim = lipgloss.Color("#666666") t.dim = lipgloss.Color("#666666")
t.accent = lipgloss.AdaptiveColor{Dark: "#FFFFFF", Light: "#11181C"} t.accent = compat.AdaptiveColor{Dark: lipgloss.Color("#FFFFFF"), Light: lipgloss.Color("#11181C")}
t.highlight = lipgloss.Color("#4682B4") t.highlight = lipgloss.Color("#4682B4")
t.brand = lipgloss.Color("#4682B4") t.brand = lipgloss.Color("#4682B4")
t.label = lipgloss.Color("#999999") t.label = lipgloss.Color("#999999")
t.success = lipgloss.Color("#00FF00") t.success = lipgloss.Color("#00FF00")
t.error = lipgloss.Color("196") t.error = lipgloss.Color("196")
t.base = renderer.NewStyle().Foreground(t.body) t.base = lipgloss.NewStyle().Foreground(t.body)
t.form = HuhTheme(t) t.form = HuhTheme(t)
return t return t
} }
// this should resemble something like catppuccin func Catppuccin() Theme {
func Catppuccin(renderer *lipgloss.Renderer) Theme { t := Theme{}
t := Theme{renderer: renderer}
t.background = lipgloss.AdaptiveColor{Dark: "#1e1e2e", Light: "#eff1f5"} t.background = compat.AdaptiveColor{Dark: lipgloss.Color("#1e1e2e"), Light: lipgloss.Color("#eff1f5")}
t.border = lipgloss.AdaptiveColor{Dark: "#45475A", Light: "#BCC0CC"} t.border = compat.AdaptiveColor{Dark: lipgloss.Color("#45475A"), Light: lipgloss.Color("#BCC0CC")}
t.body = lipgloss.Color("#AAAAAA") t.body = lipgloss.Color("#AAAAAA")
t.dim = lipgloss.Color("#6C7086") t.dim = lipgloss.Color("#6C7086")
t.accent = lipgloss.Color("#CBA6F7") t.accent = lipgloss.Color("#CBA6F7")
@@ -60,25 +59,25 @@ func Catppuccin(renderer *lipgloss.Renderer) Theme {
t.success = lipgloss.Color("#A6E3A1") t.success = lipgloss.Color("#A6E3A1")
t.error = lipgloss.Color("#F38BA8") t.error = lipgloss.Color("#F38BA8")
t.base = renderer.NewStyle().Foreground(t.body) t.base = lipgloss.NewStyle().Foreground(t.body)
t.form = HuhTheme(t) t.form = HuhTheme(t)
return t return t
} }
// Color accessors — use in .Foreground() / .Background() calls. // Color accessors.
func (t Theme) Background() lipgloss.TerminalColor { return t.background } func (t Theme) Background() color.Color { return t.background }
func (t Theme) Border() lipgloss.TerminalColor { return t.border } func (t Theme) Border() color.Color { return t.border }
func (t Theme) Body() lipgloss.TerminalColor { return t.body } func (t Theme) Body() color.Color { return t.body }
func (t Theme) Dim() lipgloss.TerminalColor { return t.dim } func (t Theme) Dim() color.Color { return t.dim }
func (t Theme) Accent() lipgloss.TerminalColor { return t.accent } func (t Theme) Accent() color.Color { return t.accent }
func (t Theme) Highlight() lipgloss.TerminalColor { return t.highlight } func (t Theme) Highlight() color.Color { return t.highlight }
func (t Theme) Brand() lipgloss.TerminalColor { return t.brand } func (t Theme) Brand() color.Color { return t.brand }
func (t Theme) Label() lipgloss.TerminalColor { return t.label } func (t Theme) Label() color.Color { return t.label }
func (t Theme) Success() lipgloss.TerminalColor { return t.success } func (t Theme) Success() color.Color { return t.success }
func (t Theme) Error() lipgloss.TerminalColor { return t.error } func (t Theme) Error() color.Color { return t.error }
// Style helpers — return a copy of base with the correct foreground. // Style helpers.
// Chain .Bold(), .Width(), .Padding() etc. on the returned style.
func (t Theme) Base() lipgloss.Style { return t.base } func (t Theme) Base() lipgloss.Style { return t.base }
func (t Theme) TextBody() lipgloss.Style { return t.Base().Foreground(t.body) } func (t Theme) TextBody() lipgloss.Style { return t.Base().Foreground(t.body) }
func (t Theme) TextAccent() lipgloss.Style { return t.Base().Foreground(t.accent) } func (t Theme) TextAccent() lipgloss.Style { return t.Base().Foreground(t.accent) }
@@ -90,10 +89,8 @@ func (t Theme) TextSuccess() lipgloss.Style { return t.Base().Foreground(t.suc
func (t Theme) TextError() lipgloss.Style { return t.Base().Foreground(t.error) } func (t Theme) TextError() lipgloss.Style { return t.Base().Foreground(t.error) }
func (t Theme) TextLoading() lipgloss.Style { return t.Base().Foreground(lipgloss.Color("205")) } func (t Theme) TextLoading() lipgloss.Style { return t.Base().Foreground(lipgloss.Color("205")) }
// PanelError returns a style for the error banner: dark red bg, red fg.
func (t Theme) PanelError() lipgloss.Style { func (t Theme) PanelError() lipgloss.Style {
return t.Base().Background(lipgloss.Color("52")).Foreground(lipgloss.Color("196")) return t.Base().Background(lipgloss.Color("52")).Foreground(lipgloss.Color("196"))
} }
// Form returns the huh form theme for shipping and payment forms. func (t Theme) Form() huh.Theme { return t.form }
func (t Theme) Form() *huh.Theme { return t.form }

View File

@@ -6,7 +6,7 @@ import (
"terminalShop/pkg/models" "terminalShop/pkg/models"
"time" "time"
tea "github.com/charmbracelet/bubbletea" tea "charm.land/bubbletea/v2"
) )
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

View File

@@ -1,7 +1,8 @@
package tui package tui
import ( import (
"github.com/charmbracelet/lipgloss" tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
) )
// ResizeView renders a message when the terminal is too small. // ResizeView renders a message when the terminal is too small.
@@ -19,7 +20,13 @@ func (m Model) ResizeView() string {
) )
} }
func (m Model) View() string { func (m Model) View() tea.View {
v := tea.NewView(m.renderString())
v.AltScreen = true
return v
}
func (m Model) renderString() string {
if m.viewportWidth == 0 { if m.viewportWidth == 0 {
m.updateLayout(120, 30) m.updateLayout(120, 30)
} }