fix(tui): move theme styles onto the Model to end cross-session races

applyTheme mutated ~18 package-global lipgloss styles while every SSH
session's tea.Program read them concurrently from its own goroutine.
Pressing T or opening a new connection raced other sessions' View and
bled themes across users.

Styles now live in an immutable per-Model struct built by newStyles;
free formatter helpers that consumed the globals became Model methods.
This commit is contained in:
2026-06-11 11:23:16 -04:00
parent f349d0dfd1
commit 274f0081e2
19 changed files with 311 additions and 312 deletions
+4 -4
View File
@@ -134,14 +134,14 @@ func TestComputeHistoryStats_Empty(t *testing.T) {
func TestStateChangeSparkline(t *testing.T) {
t.Run("empty", func(t *testing.T) {
if got := stateChangeSparkline(nil, 20); got != "" {
if got := styledModel.stateChangeSparkline(nil, 20); got != "" {
t.Errorf("expected empty for nil, got %q", got)
}
})
t.Run("single event", func(t *testing.T) {
changes := []models.StateChange{{ChangedAt: time.Now()}}
if got := stateChangeSparkline(changes, 20); got != "" {
if got := styledModel.stateChangeSparkline(changes, 20); got != "" {
t.Errorf("expected empty for single event, got %q", got)
}
})
@@ -152,7 +152,7 @@ func TestStateChangeSparkline(t *testing.T) {
{ChangedAt: now},
{ChangedAt: now.Add(-1 * time.Hour)},
}
got := stateChangeSparkline(changes, 20)
got := styledModel.stateChangeSparkline(changes, 20)
if got == "" {
t.Error("expected non-empty sparkline for two events")
}
@@ -164,7 +164,7 @@ func TestStateChangeSparkline(t *testing.T) {
{ChangedAt: now},
{ChangedAt: now.Add(-1 * time.Hour)},
}
if got := stateChangeSparkline(changes, 3); got != "" {
if got := styledModel.stateChangeSparkline(changes, 3); got != "" {
t.Errorf("expected empty for width 3, got %q", got)
}
})